mirror of
https://github.com/xevidos/codiad.git
synced 2024-12-22 13:52:16 +01:00
Removed autocomplete in favor for ace\'s built in autocomplete. Added verbose option to autosave console messages.
This commit is contained in:
parent
db5acdd4df
commit
0408ad74b3
6 changed files with 64 additions and 704 deletions
|
@ -1,696 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) Codiad & Kent Safranski (codiad.com), distributed
|
|
||||||
* as-is and without warranty under the MIT License. See
|
|
||||||
* [root]/license.txt for more. This information must remain intact.
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function (global, $) {
|
|
||||||
|
|
||||||
var EventEmitter = ace.require('ace/lib/event_emitter').EventEmitter;
|
|
||||||
var Range = ace.require('ace/range').Range;
|
|
||||||
|
|
||||||
|
|
||||||
var codiad = global.codiad;
|
|
||||||
|
|
||||||
$(function () {
|
|
||||||
codiad.autocomplete.init();
|
|
||||||
});
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Autocomplete Component for Codiad
|
|
||||||
// ---------------------------------
|
|
||||||
// Show a popup with word completion suggestions.
|
|
||||||
//
|
|
||||||
//////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
codiad.autocomplete = {
|
|
||||||
|
|
||||||
wordRegex: /[^a-zA-Z_0-9\$]+/,
|
|
||||||
|
|
||||||
isVisible: false,
|
|
||||||
|
|
||||||
standardGoLineDownExec: null,
|
|
||||||
|
|
||||||
standardGoLineUpExec: null,
|
|
||||||
|
|
||||||
_suggestionCache: null,
|
|
||||||
|
|
||||||
standardGoToRightExec: null,
|
|
||||||
|
|
||||||
standardGoToLeftExec: null,
|
|
||||||
|
|
||||||
standardIndentExec: null,
|
|
||||||
|
|
||||||
init: function () {
|
|
||||||
var _this = this;
|
|
||||||
|
|
||||||
this.$onDocumentChange = this.onDocumentChange.bind(this);
|
|
||||||
this.$selectNextSuggestion = this.selectNextSuggestion.bind(this);
|
|
||||||
this.$selectPreviousSuggestion = this.selectPreviousSuggestion.bind(this);
|
|
||||||
this.$complete = this.complete.bind(this);
|
|
||||||
this.$hide = this.hide.bind(this);
|
|
||||||
|
|
||||||
/* Catch click on suggestion */
|
|
||||||
$('#autocomplete li').live('click', function () {
|
|
||||||
$('#autocomplete li.active-suggestion').removeClass('active-suggestion');
|
|
||||||
$(this).addClass('active-suggestion');
|
|
||||||
_this.complete();
|
|
||||||
});
|
|
||||||
|
|
||||||
/* In debug mode, run some tests here. */
|
|
||||||
// this._testSimpleMatchScorer();
|
|
||||||
// this._testFuzzyMatcher();
|
|
||||||
},
|
|
||||||
|
|
||||||
suggest: function () {
|
|
||||||
var _this = this;
|
|
||||||
|
|
||||||
var cursorPosition = this._getEditor().getCursorPosition();
|
|
||||||
var foundSuggestions = this.updateSuggestions(cursorPosition);
|
|
||||||
if (foundSuggestions) {
|
|
||||||
this.addListenerToOnDocumentChange();
|
|
||||||
|
|
||||||
// Show the completion popup.
|
|
||||||
this.show();
|
|
||||||
|
|
||||||
// handle click-out autoclosing.
|
|
||||||
var fn = function ( event ) {
|
|
||||||
let keycodes = [ 9, 10, 13 ]
|
|
||||||
|
|
||||||
/*if( ! keycodes.includes( event.keyCode ) ) {
|
|
||||||
return;
|
|
||||||
}*/
|
|
||||||
_this.hide();
|
|
||||||
$(window).off('click', fn);
|
|
||||||
};
|
|
||||||
$(window).on('click', fn);
|
|
||||||
} else {
|
|
||||||
this.clearSuggestionCache();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Update the suggestions for the word at the given position. Return true if
|
|
||||||
* some suitable suggestions could be found, false if not. */
|
|
||||||
updateSuggestions: function (position) {
|
|
||||||
var _this = this;
|
|
||||||
|
|
||||||
var session = this._getEditSession();
|
|
||||||
|
|
||||||
/* Extract the word being typed. Keep only the part of the word
|
|
||||||
* which is before the given position. It is somehow the prefix of
|
|
||||||
* the wanted full word. Make sure we only keep one word. */
|
|
||||||
var token = session.getTokenAt(position.row, position.column);
|
|
||||||
if (!token) {
|
|
||||||
/* No word at the given position. */
|
|
||||||
this.removeSuggestions();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var prefix = token.value.substr(0, position.column - token.start);
|
|
||||||
prefix = prefix.split(this.wordRegex).slice(-1)[0];
|
|
||||||
if (prefix === '') {
|
|
||||||
/* No word at the given position. */
|
|
||||||
this.removeSuggestions();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Build and order the suggestions themselves. */
|
|
||||||
// TODO cache suggestions and augment them incrementally.
|
|
||||||
var suggestionsAndDistance = this.getSuggestions(position);
|
|
||||||
var suggestions = this.rankSuggestions(prefix, suggestionsAndDistance);
|
|
||||||
if (suggestions.length < 1) {
|
|
||||||
/* No suitable suggestions found. */
|
|
||||||
this.removeSuggestions();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Remove the existing suggestions and populate the popup with the
|
|
||||||
* updated ones. */
|
|
||||||
this.removeSuggestions();
|
|
||||||
var popupContent = $('#autocomplete #suggestions');
|
|
||||||
$.each(suggestions, function (index, suggestion) {
|
|
||||||
/* First get rid of the suggestion suffix. */
|
|
||||||
suggestion = suggestion.slice(0, -1);
|
|
||||||
|
|
||||||
var indexes = _this.getMatchIndexes(prefix, suggestion);
|
|
||||||
$.each(indexes.reverse(), function (index, matchIndex) {
|
|
||||||
suggestion = suggestion.substr(0, matchIndex) +
|
|
||||||
'<span class="matched">' +
|
|
||||||
suggestion.substr(matchIndex, 1) +
|
|
||||||
'</span>' +
|
|
||||||
suggestion.substr(matchIndex + 1);
|
|
||||||
});
|
|
||||||
popupContent.append('<li class="suggestion">' + suggestion + '</li>');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.selectFirstSuggestion();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
show: function () {
|
|
||||||
this.isVisible = true;
|
|
||||||
|
|
||||||
var popup = $('#autocomplete');
|
|
||||||
popup.css({
|
|
||||||
'top': this._computeTopOffset(),
|
|
||||||
'left': this._computeLeftOffset(),
|
|
||||||
'font-family': $('.ace_editor').css('font-family'),
|
|
||||||
'font-size': $('.ace_editor').css('font-size')
|
|
||||||
});
|
|
||||||
popup.slideToggle('fast', function(){
|
|
||||||
$(this).css('overflow', '');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.addKeyboardCommands();
|
|
||||||
},
|
|
||||||
|
|
||||||
hide: function () {
|
|
||||||
this.isVisible = false;
|
|
||||||
|
|
||||||
$('#autocomplete').hide();
|
|
||||||
this.removeSuggestions();
|
|
||||||
this.clearSuggestionCache();
|
|
||||||
|
|
||||||
this.removeListenerToOnDocumentChange();
|
|
||||||
this.removeKeyboardCommands();
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Return a jQuery object containing the currently selected suggestion. */
|
|
||||||
getSelectedSuggestion: function () {
|
|
||||||
var selectedSuggestion = $('#autocomplete li.suggestion.active-suggestion');
|
|
||||||
|
|
||||||
if (selectedSuggestion.length < 1) {
|
|
||||||
alert(i18n('No suggestion selected. Might be a bug.'));
|
|
||||||
} else if (selectedSuggestion.length > 1) {
|
|
||||||
alert(i18n('More than one suggestions selected. Might be a bug.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return selectedSuggestion;
|
|
||||||
},
|
|
||||||
|
|
||||||
selectFirstSuggestion: function () {
|
|
||||||
var firstChild = $('li.suggestion:first-child');
|
|
||||||
firstChild.addClass('active-suggestion');
|
|
||||||
this._ensureVisible(firstChild, $('#autocomplete'));
|
|
||||||
},
|
|
||||||
|
|
||||||
selectLastSuggestion: function () {
|
|
||||||
var lastChild = $('li.suggestion:last-child');
|
|
||||||
lastChild.addClass('active-suggestion');
|
|
||||||
this._ensureVisible(lastChild, $('#autocomplete'));
|
|
||||||
},
|
|
||||||
|
|
||||||
selectNextSuggestion: function () {
|
|
||||||
var selectedSuggestion = this.getSelectedSuggestion();
|
|
||||||
selectedSuggestion.removeClass('active-suggestion');
|
|
||||||
var nextSuggestion = selectedSuggestion.next();
|
|
||||||
if (nextSuggestion.length > 0) {
|
|
||||||
nextSuggestion.addClass('active-suggestion');
|
|
||||||
this._ensureVisible(nextSuggestion, $('#autocomplete'));
|
|
||||||
} else {
|
|
||||||
/* The currently selected suggestion is the last one.
|
|
||||||
* Go back to first one. */
|
|
||||||
this.selectFirstSuggestion();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
selectPreviousSuggestion: function () {
|
|
||||||
var selectedSuggestion = this.getSelectedSuggestion();
|
|
||||||
selectedSuggestion.removeClass('active-suggestion');
|
|
||||||
var previousSuggestion = selectedSuggestion.prev();
|
|
||||||
if (previousSuggestion.length > 0) {
|
|
||||||
previousSuggestion.addClass('active-suggestion');
|
|
||||||
this._ensureVisible(previousSuggestion, $('#autocomplete'));
|
|
||||||
} else {
|
|
||||||
/* The currently selected suggestion is the first one.
|
|
||||||
* Go back to last one. */
|
|
||||||
this.selectLastSuggestion();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
addListenerToOnDocumentChange: function () {
|
|
||||||
var session = this._getEditSession();
|
|
||||||
session.addEventListener('change', this.$onDocumentChange);
|
|
||||||
},
|
|
||||||
|
|
||||||
removeListenerToOnDocumentChange: function () {
|
|
||||||
var session = this._getEditSession();
|
|
||||||
session.removeEventListener('change', this.$onDocumentChange);
|
|
||||||
},
|
|
||||||
|
|
||||||
onDocumentChange: function (e) {
|
|
||||||
|
|
||||||
if ( e.data === undefined || e.data === null || e.data.text.search(/^\s+$/) !== -1) {
|
|
||||||
this.hide();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var position = null;
|
|
||||||
if (e.data.action === 'insertText') {
|
|
||||||
|
|
||||||
if( codiad.autosave.saving === false ) {
|
|
||||||
position = e.data.range.end;
|
|
||||||
} else {
|
|
||||||
var start = new Date().getTime();
|
|
||||||
for (var i = 0; i < 1e7; i++) {
|
|
||||||
if ((new Date().getTime() - start) > 50){
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
position = e.data.range.end;
|
|
||||||
}
|
|
||||||
} else if (e.data.action === 'removeText') {
|
|
||||||
position = e.data.range.start;
|
|
||||||
} else {
|
|
||||||
alert('Unkown document change action.');
|
|
||||||
}
|
|
||||||
|
|
||||||
var foundSuggestions = this.updateSuggestions(position);
|
|
||||||
if (!foundSuggestions) {
|
|
||||||
this.hide();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
addKeyboardCommands: function () {
|
|
||||||
var _this = this;
|
|
||||||
var commandManager = this._getEditor().commands;
|
|
||||||
|
|
||||||
/* Save the standard commands that will be overwritten. */
|
|
||||||
this.standardGoLineDownExec = commandManager.commands.golinedown.exec;
|
|
||||||
this.standardGoLineUpExec = commandManager.commands.golineup.exec;
|
|
||||||
|
|
||||||
this.standardGoToRightExec = commandManager.commands.gotoright.exec;
|
|
||||||
this.standardGoToLeftExec = commandManager.commands.gotoleft.exec;
|
|
||||||
this.standardIndentExec = commandManager.commands.indent.exec;
|
|
||||||
|
|
||||||
/* Overwrite with the completion specific implementations. */
|
|
||||||
commandManager.commands.golinedown.exec = this.$selectNextSuggestion;
|
|
||||||
commandManager.commands.golineup.exec = this.$selectPreviousSuggestion;
|
|
||||||
|
|
||||||
commandManager.commands.gotoright.exec = this.$complete;
|
|
||||||
commandManager.commands.gotoleft.exec = this.$hide;
|
|
||||||
commandManager.commands.indent.exec = this.$complete;
|
|
||||||
|
|
||||||
commandManager.addCommand({
|
|
||||||
name: 'hideautocomplete',
|
|
||||||
bindKey: 'Esc',
|
|
||||||
exec: function () {
|
|
||||||
_this.hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
commandManager.addCommand({
|
|
||||||
name: 'autocomplete',
|
|
||||||
bindKey: 'Return',
|
|
||||||
exec: this.$complete
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
removeKeyboardCommands: function () {
|
|
||||||
var commandManager = this._getEditor().commands;
|
|
||||||
|
|
||||||
/* Make sure the standard exec have been initialized. */
|
|
||||||
if (this.standardGoLineDownExec !== null) {
|
|
||||||
commandManager.commands.golinedown.exec = this.standardGoLineDownExec;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.standardGoLineUpExec !== null) {
|
|
||||||
commandManager.commands.golineup.exec = this.standardGoLineUpExec;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.standardGoToRightExec !== null) {
|
|
||||||
commandManager.commands.gotoright.exec = this.standardGoToRightExec;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.standardGoToLeftExec !== null) {
|
|
||||||
commandManager.commands.gotoleft.exec = this.standardGoToLeftExec;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.standardIndentExec !== null) {
|
|
||||||
commandManager.commands.indent.exec = this.standardIndentExec;
|
|
||||||
}
|
|
||||||
|
|
||||||
commandManager.removeCommand('hideautocomplete');
|
|
||||||
commandManager.removeCommand('autocomplete');
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Complete the word at the given position with the currently selected
|
|
||||||
* suggestion. Only the part of the word before the position is
|
|
||||||
* replaced. */
|
|
||||||
complete: function (position) {
|
|
||||||
var editor = this._getEditor();
|
|
||||||
var session = this._getEditSession();
|
|
||||||
|
|
||||||
var position = editor.getCursorPosition();
|
|
||||||
|
|
||||||
/* Get the length of the word being typed. */
|
|
||||||
var token = session.getTokenAt(position.row, position.column);
|
|
||||||
if (!token) {
|
|
||||||
/* No token at the given position. */
|
|
||||||
this.clearSuggestionCache();
|
|
||||||
this.hide();
|
|
||||||
editor.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
var prefix = token.value.substr(0, position.column - token.start);
|
|
||||||
var prefixLength = prefix.split(this.wordRegex).slice(-1)[0].length;
|
|
||||||
|
|
||||||
var range = new Range(position.row,
|
|
||||||
position.column - prefixLength,
|
|
||||||
position.row,
|
|
||||||
position.column);
|
|
||||||
|
|
||||||
var suggestion = this.getSelectedSuggestion().text();
|
|
||||||
session.replace(range, suggestion);
|
|
||||||
|
|
||||||
this.hide();
|
|
||||||
editor.focus();
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Remove the suggestions from the Dom. */
|
|
||||||
removeSuggestions: function () {
|
|
||||||
$('.suggestion').remove();
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Get suggestions of completion for the current position in the
|
|
||||||
* document. */
|
|
||||||
getSuggestions: function (position) {
|
|
||||||
|
|
||||||
/* If suggestions are cached,
|
|
||||||
* return them directely */
|
|
||||||
if (this._suggestionCache) {
|
|
||||||
return this._suggestionCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
var doc = this._getDocument();
|
|
||||||
|
|
||||||
/* FIXME For now, make suggestions on the whole file content except
|
|
||||||
* the current token. Might be a little bit smarter, e.g., remove
|
|
||||||
* all the keywords associated with the current language. */
|
|
||||||
|
|
||||||
/* Get all the text, put a marker at the cursor position. The
|
|
||||||
* marker uses word character so that it won't be discarded by a
|
|
||||||
* word split. */
|
|
||||||
var markerString = '__autocomplete_marker__';
|
|
||||||
var text = doc.getLines(0, position.row - 1).join("\n") + "\n";
|
|
||||||
var currentLine = doc.getLine(position.row);
|
|
||||||
text += currentLine.substr(0, position.column);
|
|
||||||
text += markerString;
|
|
||||||
if (position.column === currentLine.length) {
|
|
||||||
// position is at end of line, add a break line.
|
|
||||||
text += "\n";
|
|
||||||
}
|
|
||||||
text += currentLine.substr(position.column + 1);
|
|
||||||
text += doc.getLines(position.row + 1, doc.getLength()).join("\n") + "\n";
|
|
||||||
|
|
||||||
/* Split the text into words. */
|
|
||||||
var suggestions = text.split(this.wordRegex);
|
|
||||||
|
|
||||||
/* Get the index of the word at the cursor position. */
|
|
||||||
var markerIndex = 0;
|
|
||||||
var markedWord = '';
|
|
||||||
$.each(suggestions, function (index, value) {
|
|
||||||
if (value.search(markerString) !== -1) {
|
|
||||||
markerIndex = index;
|
|
||||||
markedWord = value;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Build an object associating the suggestions with their distance
|
|
||||||
* to the word at cursor position. To make sure that no suggestion
|
|
||||||
* string overrides a built-in method of the suggestionsAndDistance
|
|
||||||
* object, suffix all the suggestions with '-'. Afterward, make
|
|
||||||
* sure of removing that suffix before using the stored
|
|
||||||
* suggestions! */
|
|
||||||
var suggestionsAndDistance = {};
|
|
||||||
$.each(suggestions, function (index, suggestion) {
|
|
||||||
var distance = Math.abs(index - markerIndex);
|
|
||||||
if (!suggestionsAndDistance[suggestion + '-'] ||
|
|
||||||
distance < suggestionsAndDistance[suggestion]) {
|
|
||||||
suggestionsAndDistance[suggestion + '-'] = distance;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Remove from the suggestions the word under the cursor. */
|
|
||||||
delete suggestionsAndDistance[markedWord + '-'];
|
|
||||||
|
|
||||||
/* Fill the cache */
|
|
||||||
this._suggestionCache = suggestionsAndDistance;
|
|
||||||
|
|
||||||
return suggestionsAndDistance;
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Clear the suggestion cache */
|
|
||||||
clearSuggestionCache: function () {
|
|
||||||
this._suggestionCache = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Given an object associating suggestions and their distances to the
|
|
||||||
* word under the cursor (the prefix), return a ranked array of
|
|
||||||
* suggestions with the best match first. The suggestions are ranked
|
|
||||||
* based on if they match the prefix fuzzily, how much they match the
|
|
||||||
* given prefix in the computeSimpleMatchScore sense and on their
|
|
||||||
* distances to the prefix. */
|
|
||||||
rankSuggestions: function (prefix, suggestionsAndDistance) {
|
|
||||||
/* Initialize maxScore to one to ensure removing the non matching
|
|
||||||
* suggestions (those with a zero score). */
|
|
||||||
var maxScore = 1;
|
|
||||||
var suggestionsAndMatchScore = {};
|
|
||||||
for (var suggestion in suggestionsAndDistance) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(suggestionsAndDistance, suggestion)) {
|
|
||||||
var score = this.computeSimpleMatchScore(prefix, suggestion);
|
|
||||||
if (score > maxScore) {
|
|
||||||
maxScore = score;
|
|
||||||
}
|
|
||||||
suggestionsAndMatchScore[suggestion] = score;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Remove the suggestions that do not match the prefix fuzzily. */
|
|
||||||
for (suggestion in suggestionsAndMatchScore) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(suggestionsAndMatchScore, suggestion)) {
|
|
||||||
if (!this.isMatchingFuzzily(prefix, suggestion)) {
|
|
||||||
delete suggestionsAndMatchScore[suggestion];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Now for each suggestion we have its matching score and its
|
|
||||||
* distance to the word under the cursor. So compute its final
|
|
||||||
* score as a combination of both. */
|
|
||||||
var suggestionsAndFinalScore = {};
|
|
||||||
for (suggestion in suggestionsAndMatchScore) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(suggestionsAndMatchScore, suggestion)) {
|
|
||||||
//suggestionsAndFinalScore[suggestion] = suggestionsAndMatchScore[suggestion] -
|
|
||||||
// suggestionsAndDistance[suggestion];
|
|
||||||
suggestionsAndFinalScore[suggestion] = suggestionsAndMatchScore[suggestion] / suggestion.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make an array of suggestions and make sure to rank them in the
|
|
||||||
* ascending scores order. */
|
|
||||||
var suggestions = [];
|
|
||||||
for (suggestion in suggestionsAndFinalScore) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(suggestionsAndFinalScore, suggestion)) {
|
|
||||||
suggestions.push(suggestion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suggestions.sort(function (firstSuggestion, secondSuggestion) {
|
|
||||||
return suggestionsAndFinalScore[secondSuggestion] - suggestionsAndFinalScore[firstSuggestion];
|
|
||||||
});
|
|
||||||
|
|
||||||
return suggestions;
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Return the number of consecutive letters starting from the first
|
|
||||||
* letter in suggestion that match prefix. For instance,
|
|
||||||
* this.computeSimpleMatchScore(cod, codiad) will return 3. If
|
|
||||||
* suggestion is shorter than prefix, return a score of zero. The score
|
|
||||||
* is computed using a Vim-like smartcase behavior. */
|
|
||||||
computeSimpleMatchScore: function (prefix, suggestion) {
|
|
||||||
/* Use a Vim-like smartcase behavior. If prefix is all lowercase,
|
|
||||||
* compute the match score case insensitive, if it is not, compute
|
|
||||||
* the score case sensitive. */
|
|
||||||
var localSuggestion = this._isLowerCase(prefix) ? suggestion.toLowerCase() : suggestion;
|
|
||||||
|
|
||||||
if (localSuggestion.length < prefix.length) {
|
|
||||||
return 0;
|
|
||||||
} else if (localSuggestion === prefix) {
|
|
||||||
return prefix.length;
|
|
||||||
} else {
|
|
||||||
var score = 0;
|
|
||||||
for (var i = 0; i < prefix.length; ++i) {
|
|
||||||
if (localSuggestion[i] === prefix[i]) {
|
|
||||||
++score;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return score;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Return true if suggestion fuzzily matches prefix. Because everybody
|
|
||||||
* loves fuzzy matches.
|
|
||||||
* For instance, this.isMatchingFuzzily(mlf, mylongfunctionname)
|
|
||||||
* will return true. The score is computed using a Vim-like smartcase
|
|
||||||
* behavior. */
|
|
||||||
isMatchingFuzzily: function (prefix, suggestion) {
|
|
||||||
/* Use a Vim-like smartcase behavior. If prefix is all lowercase,
|
|
||||||
* compute the match score case insensitive, if it is not, compute
|
|
||||||
* the score case sensitive. */
|
|
||||||
var localSuggestion = this._isLowerCase(prefix) ? suggestion.toLowerCase() : suggestion;
|
|
||||||
|
|
||||||
/* Escape the characters that have a special meaning for regex in
|
|
||||||
* the prefix. */
|
|
||||||
var localPrefix = prefix.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
||||||
|
|
||||||
var fuzzyRegex = '^.*?';
|
|
||||||
for (var i = 0; i < localPrefix.length; ++i) {
|
|
||||||
if (localPrefix[i] === '\\') {
|
|
||||||
fuzzyRegex += localPrefix[i];
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
fuzzyRegex += localPrefix[i];
|
|
||||||
fuzzyRegex += '.*?';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (localSuggestion.search(fuzzyRegex) !== -1) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getMatchIndexes: function (prefix, suggestion) {
|
|
||||||
/* Use a Vim-like smartcase behavior. If prefix is all lowercase,
|
|
||||||
* find the match indexes case insensitive, if it is not, find them
|
|
||||||
* case sensitive. */
|
|
||||||
var localSuggestion = this._isLowerCase(prefix) ? suggestion.toLowerCase() : suggestion;
|
|
||||||
|
|
||||||
var matchIndexes = [];
|
|
||||||
var startIndex = 0;
|
|
||||||
for (var i = 0; i < prefix.length; ++i) {
|
|
||||||
var index = localSuggestion.indexOf(prefix[i], startIndex);
|
|
||||||
matchIndexes.push(index);
|
|
||||||
startIndex = index + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchIndexes;
|
|
||||||
},
|
|
||||||
|
|
||||||
_isLowerCase: function (str) {
|
|
||||||
return (str.toLowerCase() === str);
|
|
||||||
},
|
|
||||||
|
|
||||||
_ensureVisible: function (el, parent) {
|
|
||||||
var offset = 1;
|
|
||||||
var paneMin = parent.scrollTop();
|
|
||||||
var paneMax = paneMin + parent.innerHeight();
|
|
||||||
var itemMin = el.position().top + paneMin - offset;
|
|
||||||
var itemMax = itemMin + el.outerHeight() + 2*offset;
|
|
||||||
if (itemMax > paneMax) {
|
|
||||||
parent.stop().animate({
|
|
||||||
scrollTop: itemMax - parent.innerHeight()
|
|
||||||
}, 100);
|
|
||||||
} else if (itemMin < paneMin) {
|
|
||||||
parent.stop().animate({
|
|
||||||
scrollTop: itemMin
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_computeTopOffset: function () {
|
|
||||||
/* FIXME How to handle multiple cursors? This seems to compute the
|
|
||||||
* offset using the position of the last created cursor. */
|
|
||||||
var cursor = $('.ace_cursor');
|
|
||||||
if (cursor.length > 0) {
|
|
||||||
var fontSize = codiad.editor.getActive().container.style.fontSize.replace('px', '');
|
|
||||||
var interLine = 1.7;
|
|
||||||
cursor = $(cursor[0]);
|
|
||||||
var top = cursor.offset().top + fontSize * interLine;
|
|
||||||
return top;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_computeLeftOffset: function () {
|
|
||||||
/* FIXME How to handle multiple cursors? This seems to compute the
|
|
||||||
* offset using the position of the last created cursor. */
|
|
||||||
var cursor = $('.ace_cursor');
|
|
||||||
if (cursor.length > 0) {
|
|
||||||
cursor = $(cursor[0]);
|
|
||||||
var left = cursor.offset().left;
|
|
||||||
return left;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Set of helper methods to manipulate the editor. */
|
|
||||||
_getEditor: function () {
|
|
||||||
return codiad.editor.getActive();
|
|
||||||
},
|
|
||||||
|
|
||||||
_getEditSession: function () {
|
|
||||||
return codiad.editor.getActive().getSession();
|
|
||||||
},
|
|
||||||
|
|
||||||
_getDocument: function () {
|
|
||||||
return codiad.editor.getActive().getSession().getDocument();
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Some unit tests. */
|
|
||||||
//i don't need to translate this... this is just for testing things...
|
|
||||||
_testSimpleMatchScorer: function () {
|
|
||||||
var prefix = 'myprefix';
|
|
||||||
var suggestion = 'myprefixisshort';
|
|
||||||
var score = this.computeSimpleMatchScore(prefix, suggestion);
|
|
||||||
if (score !== 8) {
|
|
||||||
alert('_testSimpleMatchScorer lowercase test failed.');
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix = 'MYPREFIX';
|
|
||||||
suggestion = 'MYPREFIXISSHORT';
|
|
||||||
score = this.computeSimpleMatchScore(prefix, suggestion);
|
|
||||||
if (score !== 8) {
|
|
||||||
alert('_testSimpleMatchScorer uppercase test failed.');
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix = 'myPrefix';
|
|
||||||
suggestion = 'myprefixisshort';
|
|
||||||
score = this.computeSimpleMatchScore(prefix, suggestion);
|
|
||||||
if (score !== 2) {
|
|
||||||
alert('_testSimpleMatchScorer mixed case vs. lowercase test failed.');
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix = 'myPrefixIs';
|
|
||||||
suggestion = 'myPrefixIsShort';
|
|
||||||
score = this.computeSimpleMatchScore(prefix, suggestion);
|
|
||||||
if (score !== 10) {
|
|
||||||
alert('_testSimpleMatchScorer mixed case test failed.');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_testFuzzyMatcher: function () {
|
|
||||||
var isMatching = this.isMatchingFuzzily('mlf', 'mylongfunctionname');
|
|
||||||
if (!isMatching) {
|
|
||||||
alert('_testFuzzyMatcher mlf vs. mylongfunctionname failed.');
|
|
||||||
}
|
|
||||||
|
|
||||||
isMatching = this.isMatchingFuzzily('mLn', 'myLongFunctionName');
|
|
||||||
if (!isMatching) {
|
|
||||||
alert('_testFuzzyMatcher mLn vs. myLongFunctionName failed.');
|
|
||||||
}
|
|
||||||
isMatching = this.isMatchingFuzzily('mLFuny', 'myLongFunctionName');
|
|
||||||
if (isMatching) {
|
|
||||||
alert('_testFuzzyMatcher mLFuny. myLongFunctionName failed.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
})(this, jQuery);
|
|
|
@ -74,7 +74,10 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log( 'Auto save Enabled' );
|
if( codiad.auto_save.verbose ) {
|
||||||
|
|
||||||
|
console.log( 'Auto save Enabled' );
|
||||||
|
}
|
||||||
this.auto_save_trigger = setInterval( this.auto_save, 256 );
|
this.auto_save_trigger = setInterval( this.auto_save, 256 );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
var Editor = ace.require('ace/editor').Editor;
|
var Editor = ace.require('ace/editor').Editor;
|
||||||
var EditSession = ace.require('ace/edit_session').EditSession;
|
var EditSession = ace.require('ace/edit_session').EditSession;
|
||||||
var UndoManager = ace.require("ace/undomanager").UndoManager;
|
var UndoManager = ace.require("ace/undomanager").UndoManager;
|
||||||
|
|
||||||
// Editor modes that have been loaded
|
// Editor modes that have been loaded
|
||||||
var editorModes = {};
|
var editorModes = {};
|
||||||
|
|
||||||
|
@ -386,6 +386,7 @@
|
||||||
|
|
||||||
// Settings for Editor instances
|
// Settings for Editor instances
|
||||||
settings: {
|
settings: {
|
||||||
|
autocomplete: false,
|
||||||
theme: 'twilight',
|
theme: 'twilight',
|
||||||
fontSize: '13px',
|
fontSize: '13px',
|
||||||
printMargin: false,
|
printMargin: false,
|
||||||
|
@ -450,8 +451,19 @@
|
||||||
'tabSize',
|
'tabSize',
|
||||||
'theme',
|
'theme',
|
||||||
];
|
];
|
||||||
|
var bool_options = [
|
||||||
|
'autocomplete',
|
||||||
|
'printMargin',
|
||||||
|
'highlightLine',
|
||||||
|
'indentGuides',
|
||||||
|
'wrapMode',
|
||||||
|
'rightSidebarTrigger',
|
||||||
|
'fileManagerTrigger',
|
||||||
|
'softTabs',
|
||||||
|
'persistentModal',
|
||||||
|
];
|
||||||
|
|
||||||
$.each(options, async function( idx, key ) {
|
$.each( options, async function( idx, key ) {
|
||||||
|
|
||||||
var localValue = await codiad.settings.get_option( 'codiad.editor.' + key );
|
var localValue = await codiad.settings.get_option( 'codiad.editor.' + key );
|
||||||
if ( localValue !== null ) {
|
if ( localValue !== null ) {
|
||||||
|
@ -460,8 +472,7 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$.each(['printMargin', 'highlightLine', 'indentGuides', 'wrapMode', 'rightSidebarTrigger', 'fileManagerTrigger', 'softTabs', 'persistentModal'],
|
$.each( bool_options, async function(idx, key) {
|
||||||
async function(idx, key) {
|
|
||||||
var localValue = await codiad.settings.get_option( 'codiad.editor.' + key );
|
var localValue = await codiad.settings.get_option( 'codiad.editor.' + key );
|
||||||
if (localValue === null) {
|
if (localValue === null) {
|
||||||
return;
|
return;
|
||||||
|
@ -495,6 +506,11 @@
|
||||||
this.setTabSize(this.settings.tabSize, i);
|
this.setTabSize(this.settings.tabSize, i);
|
||||||
this.setSoftTabs(this.settings.softTabs, i);
|
this.setSoftTabs(this.settings.softTabs, i);
|
||||||
this.setOverScroll(this.settings.overScroll, i);
|
this.setOverScroll(this.settings.overScroll, i);
|
||||||
|
i.setOptions({
|
||||||
|
enableBasicAutocompletion: true,
|
||||||
|
enableSnippets: true,
|
||||||
|
enableLiveAutocompletion: this.settings.autocomplete
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
|
@ -540,12 +556,13 @@
|
||||||
pContainer.setChild(idx, sc);
|
pContainer.setChild(idx, sc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ace.require("ace/ext/language_tools");
|
||||||
var i = ace.edit(el[0]);
|
var i = ace.edit(el[0]);
|
||||||
var resizeEditor = function(){
|
var resizeEditor = function(){
|
||||||
i.resize();
|
i.resize();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (sc) {
|
if (sc) {
|
||||||
i.splitContainer = sc;
|
i.splitContainer = sc;
|
||||||
i.splitIdx = chIdx;
|
i.splitIdx = chIdx;
|
||||||
|
@ -1564,6 +1581,24 @@
|
||||||
//Database
|
//Database
|
||||||
codiad.settings.update_option( 'codiad.editor.overScroll', s );
|
codiad.settings.update_option( 'codiad.editor.overScroll', s );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setLiveAutocomplete: function( s, i ) {
|
||||||
|
|
||||||
|
if (i) {
|
||||||
|
i.setOptions({
|
||||||
|
enableLiveAutocompletion: s
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.settings.autocomplete = s;
|
||||||
|
this.forEach(function(i) {
|
||||||
|
i.setOptions({
|
||||||
|
enableLiveAutocompletion: s
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//Database
|
||||||
|
codiad.settings.update_option( 'codiad.editor.autocomplete', s );
|
||||||
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -140,6 +140,10 @@
|
||||||
case "codiad.editor.overScroll":
|
case "codiad.editor.overScroll":
|
||||||
codiad.editor.setOverScroll(val);
|
codiad.editor.setOverScroll(val);
|
||||||
break;
|
break;
|
||||||
|
case "codiad.editor.autocomplete":
|
||||||
|
var bool_val = (val == "true");
|
||||||
|
codiad.editor.setLiveAutocomplete(bool_val)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -185,4 +185,17 @@
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<td><?php i18n("Live Autocomplete"); ?></td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
<select class="setting" data-setting="codiad.editor.autocomplete">
|
||||||
|
<option value="false" selected><?php i18n("No"); ?></option>
|
||||||
|
<option value="true"><?php i18n("Yes"); ?></option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -417,11 +417,12 @@ if( defined( "SITE_NAME" ) && ! ( SITE_NAME === "" || SITE_NAME === null ) ) {
|
||||||
|
|
||||||
<!-- ACE -->
|
<!-- ACE -->
|
||||||
<script src="components/editor/ace-editor/ace.js"></script>
|
<script src="components/editor/ace-editor/ace.js"></script>
|
||||||
|
<script src="components/editor/ace-editor/ext-language_tools.js"></script>
|
||||||
|
|
||||||
<!-- Codiad System Variables -->
|
<!-- Codiad System Variables -->
|
||||||
<script>
|
<script>
|
||||||
codiad.system = {};
|
codiad.system = {};
|
||||||
codiad.system.site_id = `<?php echo SITE_ID;?>`;
|
codiad.system.site_id = `<?php echo $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];?>`;
|
||||||
codiad.system.session_id = `<?php echo SESSION_ID;?>`;
|
codiad.system.session_id = `<?php echo SESSION_ID;?>`;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue