219 lines
8.0 KiB
JavaScript
219 lines
8.0 KiB
JavaScript
|
/* ***** BEGIN LICENSE BLOCK *****
|
||
|
* Distributed under the BSD license:
|
||
|
*
|
||
|
* Copyright (c) 2012, Ajax.org B.V.
|
||
|
* All rights reserved.
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions are met:
|
||
|
* * Redistributions of source code must retain the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer.
|
||
|
* * Redistributions in binary form must reproduce the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer in the
|
||
|
* documentation and/or other materials provided with the distribution.
|
||
|
* * Neither the name of Ajax.org B.V. nor the
|
||
|
* names of its contributors may be used to endorse or promote products
|
||
|
* derived from this software without specific prior written permission.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||
|
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
*
|
||
|
* ***** END LICENSE BLOCK ***** */
|
||
|
|
||
|
define(function(require, exports, module) {
|
||
|
"use strict";
|
||
|
|
||
|
var snippetManager = require("../snippets").snippetManager;
|
||
|
var Autocomplete = require("../autocomplete").Autocomplete;
|
||
|
var config = require("../config");
|
||
|
var lang = require("../lib/lang");
|
||
|
var util = require("../autocomplete/util");
|
||
|
|
||
|
var textCompleter = require("../autocomplete/text_completer");
|
||
|
var keyWordCompleter = {
|
||
|
getCompletions: function(editor, session, pos, prefix, callback) {
|
||
|
if (session.$mode.completer) {
|
||
|
return session.$mode.completer.getCompletions(editor, session, pos, prefix, callback);
|
||
|
}
|
||
|
var state = editor.session.getState(pos.row);
|
||
|
var completions = session.$mode.getCompletions(state, session, pos, prefix);
|
||
|
callback(null, completions);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var snippetCompleter = {
|
||
|
getCompletions: function(editor, session, pos, prefix, callback) {
|
||
|
var scopes = [];
|
||
|
|
||
|
// set scope to html-tag if we're inside an html tag
|
||
|
var token = session.getTokenAt(pos.row, pos.column);
|
||
|
if (token && token.type.match(/(tag-name|tag-open|tag-whitespace|attribute-name|attribute-value)\.xml$/))
|
||
|
scopes.push('html-tag');
|
||
|
else
|
||
|
scopes = snippetManager.getActiveScopes(editor);
|
||
|
|
||
|
var snippetMap = snippetManager.snippetMap;
|
||
|
var completions = [];
|
||
|
scopes.forEach(function(scope) {
|
||
|
var snippets = snippetMap[scope] || [];
|
||
|
for (var i = snippets.length; i--;) {
|
||
|
var s = snippets[i];
|
||
|
var caption = s.name || s.tabTrigger;
|
||
|
if (!caption)
|
||
|
continue;
|
||
|
completions.push({
|
||
|
caption: caption,
|
||
|
snippet: s.content,
|
||
|
meta: s.tabTrigger && !s.name ? s.tabTrigger + "\u21E5 " : "snippet",
|
||
|
type: "snippet"
|
||
|
});
|
||
|
}
|
||
|
}, this);
|
||
|
callback(null, completions);
|
||
|
},
|
||
|
getDocTooltip: function(item) {
|
||
|
if (item.type == "snippet" && !item.docHTML) {
|
||
|
item.docHTML = [
|
||
|
"<b>", lang.escapeHTML(item.caption), "</b>", "<hr></hr>",
|
||
|
lang.escapeHTML(item.snippet)
|
||
|
].join("");
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var completers = [snippetCompleter, textCompleter, keyWordCompleter];
|
||
|
// Modifies list of default completers
|
||
|
exports.setCompleters = function(val) {
|
||
|
completers.length = 0;
|
||
|
if (val) completers.push.apply(completers, val);
|
||
|
};
|
||
|
exports.addCompleter = function(completer) {
|
||
|
completers.push(completer);
|
||
|
};
|
||
|
|
||
|
// Exports existing completer so that user can construct his own set of completers.
|
||
|
exports.textCompleter = textCompleter;
|
||
|
exports.keyWordCompleter = keyWordCompleter;
|
||
|
exports.snippetCompleter = snippetCompleter;
|
||
|
|
||
|
var expandSnippet = {
|
||
|
name: "expandSnippet",
|
||
|
exec: function(editor) {
|
||
|
return snippetManager.expandWithTab(editor);
|
||
|
},
|
||
|
bindKey: "Tab"
|
||
|
};
|
||
|
|
||
|
var onChangeMode = function(e, editor) {
|
||
|
loadSnippetsForMode(editor.session.$mode);
|
||
|
};
|
||
|
|
||
|
var loadSnippetsForMode = function(mode) {
|
||
|
var id = mode.$id;
|
||
|
if (!snippetManager.files)
|
||
|
snippetManager.files = {};
|
||
|
loadSnippetFile(id);
|
||
|
if (mode.modes)
|
||
|
mode.modes.forEach(loadSnippetsForMode);
|
||
|
};
|
||
|
|
||
|
var loadSnippetFile = function(id) {
|
||
|
if (!id || snippetManager.files[id])
|
||
|
return;
|
||
|
var snippetFilePath = id.replace("mode", "snippets");
|
||
|
snippetManager.files[id] = {};
|
||
|
config.loadModule(snippetFilePath, function(m) {
|
||
|
if (m) {
|
||
|
snippetManager.files[id] = m;
|
||
|
if (!m.snippets && m.snippetText)
|
||
|
m.snippets = snippetManager.parseSnippetFile(m.snippetText);
|
||
|
snippetManager.register(m.snippets || [], m.scope);
|
||
|
if (m.includeScopes) {
|
||
|
snippetManager.snippetMap[m.scope].includeScopes = m.includeScopes;
|
||
|
m.includeScopes.forEach(function(x) {
|
||
|
loadSnippetFile("ace/mode/" + x);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
var doLiveAutocomplete = function(e) {
|
||
|
var editor = e.editor;
|
||
|
var hasCompleter = editor.completer && editor.completer.activated;
|
||
|
|
||
|
// We don't want to autocomplete with no prefix
|
||
|
if (e.command.name === "backspace") {
|
||
|
if (hasCompleter && !util.getCompletionPrefix(editor))
|
||
|
editor.completer.detach();
|
||
|
}
|
||
|
else if (e.command.name === "insertstring") {
|
||
|
var prefix = util.getCompletionPrefix(editor);
|
||
|
// Only autocomplete if there's a prefix that can be matched
|
||
|
if (prefix && !hasCompleter) {
|
||
|
if (!editor.completer) {
|
||
|
// Create new autocompleter
|
||
|
editor.completer = new Autocomplete();
|
||
|
}
|
||
|
// Disable autoInsert
|
||
|
editor.completer.autoInsert = false;
|
||
|
editor.completer.showPopup(editor);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var Editor = require("../editor").Editor;
|
||
|
require("../config").defineOptions(Editor.prototype, "editor", {
|
||
|
enableBasicAutocompletion: {
|
||
|
set: function(val) {
|
||
|
if (val) {
|
||
|
if (!this.completers)
|
||
|
this.completers = Array.isArray(val)? val: completers;
|
||
|
this.commands.addCommand(Autocomplete.startCommand);
|
||
|
} else {
|
||
|
this.commands.removeCommand(Autocomplete.startCommand);
|
||
|
}
|
||
|
},
|
||
|
value: false
|
||
|
},
|
||
|
/**
|
||
|
* Enable live autocomplete. If the value is an array, it is assumed to be an array of completers
|
||
|
* and will use them instead of the default completers.
|
||
|
*/
|
||
|
enableLiveAutocompletion: {
|
||
|
set: function(val) {
|
||
|
if (val) {
|
||
|
if (!this.completers)
|
||
|
this.completers = Array.isArray(val)? val: completers;
|
||
|
// On each change automatically trigger the autocomplete
|
||
|
this.commands.on('afterExec', doLiveAutocomplete);
|
||
|
} else {
|
||
|
this.commands.removeListener('afterExec', doLiveAutocomplete);
|
||
|
}
|
||
|
},
|
||
|
value: false
|
||
|
},
|
||
|
enableSnippets: {
|
||
|
set: function(val) {
|
||
|
if (val) {
|
||
|
this.commands.addCommand(expandSnippet);
|
||
|
this.on("changeMode", onChangeMode);
|
||
|
onChangeMode(null, this);
|
||
|
} else {
|
||
|
this.commands.removeCommand(expandSnippet);
|
||
|
this.off("changeMode", onChangeMode);
|
||
|
}
|
||
|
},
|
||
|
value: false
|
||
|
}
|
||
|
});
|
||
|
});
|