Files
web2py/applications/admin/static/edit_area/plugins/zencoding/zencoding.js
Massimo Di Pierro d421c1321b initial commit
2011-11-22 23:30:42 -06:00

420 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Layer that binds Zen Coding's actions to editArea
*
* @author Sergey Chikuyonok (serge.che@gmail.com)
* @link http://chikuyonok.ru
*
* @include "zen_coding.js"
* @include "html_matcher.js"
*/
var EditArea_zencoding = (function() {
/** @type {EditArea} Current editor's instance */
var editor = null,
editor_id = 0,
is_mac = (/mac\s+os/i.test(navigator.userAgent));
/**
* Return true if Alt key is pressed
* @param {Event} evt
* @return {Boolean}
*/
function AltPressed(e) {
if (window.event)
return (window.event.altKey);
else
return e.modifiers ? (e.altKey || (e.modifiers % 2)) : e.altKey;
};
/**
* Return true if Ctrl key is pressed
* @return {Boolean}
*/
function CtrlPressed(e) {
// as usual, Opera brings few "suprises" here
if (window.event && !is_mac) {
return (window.event.ctrlKey);
} else {
return (e.ctrlKey || (e.metaKey && window.opera) || (e.modifiers==2) || (e.modifiers==3) || (e.modifiers>5));
}
};
/**
* Return true if Shift key is pressed
* @return {Boolean}
*/
function ShiftPressed(e) {
if (window.event) {
return (window.event.shiftKey);
} else {
return (e.shiftKey || (e.modifiers>3));
}
};
/**
* Finds abbreviation in cucrrent editor and returns it
* @return {String|null}
*/
function findAbbreviation() {
var range = editor.getSelectionRange(editor_id);
if (range.start != range.end) {
// abbreviation is selected by user
return editor.getSelectedText(editor_id);
} else {
var content = editor.getValue(editor_id);
return zen_coding.extractAbbreviation(content.substring(0, range.start));
}
}
/**
* Returns full line on text for passed character position
*/
function getLine(char_pos) {
var content = editor.getValue(editor_id),
start_ix = char_pos,
end_ix,
ch;
function isNewline(ch) {
return ch == '\n' || ch == '\r';
}
// find the beginnig of the line
while (start_ix--) {
if (isNewline(content.charAt(start_ix))) {
start_ix++;
break;
}
}
// find the end of the line
for (end_ix = char_pos; end_ix < content.length; end_ix++) {
if (isNewline(content.charAt(end_ix)))
break;
}
return content.substring(start_ix, end_ix);
}
/**
* Returns padding of current editor's line
* @return {String}
*/
function getCurrentLinePadding() {
var range = editor.getSelectionRange(editor_id),
cur_line = getLine(range.start);
return (cur_line.match(/^(\s+)/) || [''])[0];
}
/**
* Replaces current editor's substring with new content. Multiline content
* will be automatically padded
*
* @param {String} editor_str Current editor's substring
* @param {String} content New content
*/
function replaceEditorContent(editor_str, content) {
if (!content)
return;
// add padding for current line
content = zen_coding.padString(content, getCurrentLinePadding());
// get char index where we need to place cursor
var range = editor.getSelectionRange(editor_id);
var start_pos = range.end - editor_str.length;
var cursor_pos = content.indexOf('|');
content = content.replace(/\|/g, '');
// replace content in editor
editor.setSelectionRange(editor_id, start_pos, start_pos + editor_str.length);
editor.setSelectedText(editor_id, content);
// place cursor
if (cursor_pos != -1)
editor.setSelectionRange(editor_id, start_pos + cursor_pos, start_pos + cursor_pos);
}
/**
* Search for the new edit point
* @param {Number} Search direction: -1 — left, 1 — right
* @param {Number} Initial offset from the current caret position
* @return {Number} Returns -1 if edit point wasn't found
*/
function findNewEditPoint(inc, offset) {
inc = inc || 1;
offset = offset || 0;
var content = editor.getValue(editor_id),
cur_point = editor.getSelectionRange(editor_id).start + offset,
max_len = content.length,
next_point = -1;
function ch(ix) {
return content.charAt(ix);
}
while (cur_point < max_len && cur_point > 0) {
cur_point += inc;
var cur_char = ch(cur_point),
next_char = ch(cur_point + 1),
prev_char = ch(cur_point - 1);
switch (cur_char) {
case '"':
case '\'':
if (next_char == cur_char && prev_char == '=') {
// empty attribute
next_point = cur_point + 1;
}
break;
case '>':
if (next_char == '<') {
// between tags
next_point = cur_point + 1;
}
break;
}
if (next_point != -1)
break;
}
return next_point;
}
/**
* Unindent content, thus preparing text for tag wrapping
* @param {String} text
* @return {String}
*/
function unindent(text) {
var pad = getCurrentLinePadding();
var lines = zen_coding.splitByLines(text);
for (var i = 0; i < lines.length; i++) {
if (lines[i].search(pad) == 0)
lines[i] = lines[i].substr(pad.length);
}
return lines.join(zen_coding.getNewline());
}
/**
* Wraps content with abbreviation
* @param {String} editor_type
* @param {String} profile_name
*/
function mainWrapWithAbbreviation(editor_type, profile_name) {
profile_name = profile_name || 'xhtml';
var range = editor.getSelectionRange(editor_id),
content = editor.getValue(editor_id),
start_offset = range.start,
end_offset = range.end,
abbr = prompt('Enter abbreviation');
if (!abbr)
return null;
if (start_offset == end_offset) {
// no selection, find tag pair
var range = HTMLPairMatcher(content, Math.max(start_offset, end_offset));
if (!range || range[0] == -1) // nothing to wrap
return null;
start_offset = range[0];
end_offset = range[1];
// narrow down selection until first non-space character
var re_space = /\s|\n|\r/;
function isSpace(ch) {
return re_space.test(ch);
}
while (start_offset < end_offset) {
if (!isSpace(content.charAt(start_offset)))
break;
start_offset++;
}
while (end_offset > start_offset) {
end_offset--;
if (!isSpace(content.charAt(end_offset))) {
end_offset++;
break;
}
}
}
var content = content.substring(start_offset, end_offset),
result = zen_coding.wrapWithAbbreviation(abbr, unindent(content), editor_type, profile_name);
if (result) {
editor.setSelectionRange(editor_id, end_offset, end_offset);
replaceEditorContent(content, result);
}
}
/**
* Performs Zen Coding action on keydown event
* @param {Event} evt
*/
function keyDown(evt) {
evt = evt || window.event;
var letter = String.fromCharCode(evt.keyCode).toLowerCase(),
stop_event = false;
if (CtrlPressed(evt) && !AltPressed(evt) && !ShiftPressed(evt)) {
switch (evt.keyCode) {
case 188: // Ctrl+, — expand abbreviation
case 44:
var abbr = findAbbreviation();
if (abbr) {
var profile_name = 'xhtml',
syntax = (editArea.current_code_lang in zen_settings) ? editArea.current_code_lang : 'html';
var content = zen_coding.expandAbbreviation(abbr, syntax, profile_name);
replaceEditorContent(abbr, content);
}
stop_event = true;
break;
case 77: // Ctrl+M — match pair
case 109:
var selection = editor.getSelectionRange(editor_id),
range = HTMLPairMatcher(editor.getValue(editor_id), Math.max(selection.start, selection.end));
if (range && range[0] != -1)
editor.setSelectionRange(editor_id, range[0], range[1]);
stop_event = true;
break;
case 72: // Ctrl+H — wrap with abbreviation
mainWrapWithAbbreviation('html', 'xhtml');
stop_event = true;
break;
}
}
if (CtrlPressed(evt) && !AltPressed(evt) && ShiftPressed(evt)) {
switch (evt.keyCode) {
case 37: // Ctrl+Shift+LEFT_ARROW prev edit point
var new_point = findNewEditPoint(-1),
range = editor.getSelectionRange(editor_id);
if (new_point == range.start)
// returned to the current position, start searching from the new one
new_point = findNewEditPoint(-1, -2);
if (new_point != -1)
editor.setSelectionRange(editor_id, new_point, new_point);
stop_event = true;
break;
case 39: // Shift+Ctrl+RIGHT_ARROW next edit point
var new_point = findNewEditPoint(1);
if (new_point != -1)
editor.setSelectionRange(editor_id, new_point, new_point);
stop_event = true;
break;
case 38: // Shift+Ctrl+UP_ARROW go to matching pair
var caret_pos = editor.getSelectionRange(editor_id).start,
content = editor.getValue(editor_id);
if (content.charAt(caret_pos) == '<')
// looks like caret is outside of tag pair
caret_pos++;
var range = HTMLPairMatcher(content, caret_pos);
if (range && range[0] != -1) {
// match found
var open_tag = HTMLPairMatcher.last_match.opening_tag,
close_tag = HTMLPairMatcher.last_match.closing_tag;
if (close_tag) { // exclude unary tags
var new_pos = -1;
if (open_tag.start <= caret_pos && open_tag.end >= caret_pos)
new_pos = close_tag.start
else if (close_tag.start <= caret_pos && close_tag.end >= caret_pos)
new_pos = open_tag.start;
if (new_pos != -1)
editor.setSelectionRange(editor_id, new_pos, new_pos);
}
}
stop_event = true;
break;
case 77: // Shift+Ctrl+M — merge lines
var range = editor.getSelectionRange(editor_id),
content = editor.getValue(editor_id),
start_ix = range.start,
end_ix = range.end;
if (start_ix == end_ix) {
// find matching tag
var pair = HTMLPairMatcher(content, start_ix);
if (pair) {
start_ix = pair[0];
end_ix = pair[1];
}
}
if (start_ix != end_ix) {
// got range, merge lines
var text = content.substring(start_ix, end_ix),
old_text = text;
var lines = text.split(/(\r|\n)/);
for (var i = 1; i < lines.length; i++) {
lines[i] = lines[i].replace(/^\s+/, '');
}
text = lines.join('').replace(/\s{2,}/, ' ');
editor.setSelectionRange(editor_id, end_ix, end_ix);
replaceEditorContent(old_text, text);
}
stop_event = true;
break;
}
}
if(stop_event){
// in case of a control that sould'nt be used by IE but that is used => THROW a javascript error that will stop key action
if(window.event) evt.keyCode = 0;
return false;
}
return true;
}
return {
init: function() {
editArea.load_script(this.baseURL+"core.js");
},
onkeydown: function(evt) {
editor = parent.editAreaLoader;
editor_id = editArea.id;
return keyDown(evt);
}
}
})();
editArea.add_plugin("zencoding", EditArea_zencoding);