First pass at Haurtyun UI + C9 doc parsing

This commit is contained in:
Garen Torikian 2012-12-24 00:21:19 -08:00 committed by nightwing
commit ef19fdbcb5
7 changed files with 511 additions and 39 deletions

View file

@ -38,6 +38,7 @@ var UndoManager = require("ace/undomanager").UndoManager;
var dom = require("ace/lib/dom");
var HashHandler = require("ace/keyboard/hash_handler").HashHandler;
var TextMode = require("ace/mode/text").Mode;
var WorkerClient = require("./worker/worker_client").WorkerClient;
var mode = new TextMode();
mode.$tokenizer = {
@ -45,6 +46,8 @@ mode.$tokenizer = {
}
};
var worker = new WorkerClient(["ace"], "ace/autocomplete/autocomplete_worker", "AutocompleteWorker");
var Autocomplete = function() {
this.keyboardHandler = new HashHandler();
this.keyboardHandler.bindKeys(this.commands);
@ -68,28 +71,28 @@ var Autocomplete = function() {
el.style.display = "none"
popup.renderer.content.style.cursor="default"
var nop = function(){};
var noop = function(){};
popup.focus = nop;
popup.focus = noop;
popup.$isFocused = true;
popup.renderer.$cursorLayer.restartTimer = nop
popup.renderer.$cursorLayer.update = nop
popup.renderer.$cursorLayer.restartTimer = noop;
popup.renderer.$cursorLayer.update = noop;
popup.renderer.$cursorLayer.element.style.display = "none";
popup.renderer.maxLines = 6
popup.renderer.$keepTextAreaAtCursor=false
popup.setHighlightActiveLine(true)
popup.setSession(new EditSession(""))
popup.setHighlightActiveLine(true);
popup.setSession(new EditSession(""));
popup.on("mousedown", function(e) {
var pos = e.getDocumentPosition();
popup.moveCursorToPosition(pos);
popup.selection.clearSelection();
e.stop();
})
});
/* popup.session.setMode({
$
@ -158,20 +161,6 @@ var Autocomplete = function() {
el.style.display = "";
};
this.attachToEditor = function(editor) {
if (this.editor)
this.detach();
this.editor = editor;
if (editor.Autocomplete != this) {
if (editor.Autocomplete)
editor.Autocomplete.detach();
editor.Autocomplete = this;
}
editor.keyBinding.addKeyboardHandler(this.keyboardHandler)
editor.on("changeSelection", this.$changeListener)
editor.on("blur", this.$blurListener)
};
this.detach = function() {
this.editor.keyBinding.removeKeyboardHandler(this.keyboardHandler);
this.editor.removeEventListener("changeSelection", this.changeListener);
@ -232,20 +221,38 @@ var Autocomplete = function() {
};
this.complete = function(editor) {
this.attachToEditor(editor);
var data = this.gatherCompletions(editor);
this.completions = new FilteredList(data);
this.completions.setFilter("a")
if (data) {
if (data.length == 1)
this.insertMatch(0);
else
this.openPopup(editor);
if (this.editor)
this.detach();
var _self = this;
this.editor = editor;
if (editor.Autocomplete != this) {
if (editor.Autocomplete)
editor.Autocomplete.detach();
editor.Autocomplete = this;
}
};
this.gatherCompletions = function() {
return ["asdaf", "foo", "bar", "baz"];
editor.keyBinding.addKeyboardHandler(this.keyboardHandler);
editor.on("changeSelection", this.$changeListener);
editor.on("blur", this.$blurListener);
worker.attachToDocument(editor.session.getDocument(), {cursor: editor.getCursorPosition()});
worker.on("complete", function(data) {
_self.completions = new FilteredList(data.data.matches);
_self.completions.setFilter("a");
if (data) {
if (data.length == 1)
_self.insertMatch(0);
else
_self.openPopup(editor);
}
});
worker.on("terminate", function() {
console.log("term");
});
};
this.$singleLineEditor = function(el) {

View file

@ -0,0 +1,128 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* Copyright (c) 2010, 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 oop = require("../lib/oop");
var Mirror = require("../worker/mirror").Mirror;
var SyntaxDetector = require("./syntax_detector");
var completer = require("./text_completer");
var AutocompleteWorker = exports.AutocompleteWorker = function(sender) {
Mirror.call(this, sender);
};
oop.inherits(AutocompleteWorker, Mirror);
(function() {
// For code completion
function removeDuplicateMatches(matches) {
// First sort
matches.sort(function(a, b) {
if (a.name < b.name)
return 1;
else if (a.name > b.name)
return -1;
else
return 0;
});
for (var i = 0; i < matches.length - 1; i++) {
var a = matches[i];
var b = matches[i + 1];
if (a.name === b.name) {
// Duplicate!
if (a.priority < b.priority)
matches.splice(i, 1);
else if (a.priority > b.priority)
matches.splice(i+1, 1);
else if (a.score < b.score)
matches.splice(i, 1);
else if (a.score > b.score)
matches.splice(i+1, 1);
else
matches.splice(i, 1);
i--;
}
}
};
this.onUpdate = function() {
var _self = this;
var doc = this.doc.getValue();
var pos = this.data.cursor;
var part = SyntaxDetector.getContextSyntaxPart(this.doc, this.data.cursor, "javascript");
var language = part.language;
var currentPos = { line: pos.row, col: pos.column };
var currentNode = null;
var matches = [], ast = null;
completer.complete(_self.doc, ast, this.data.cursor, currentNode, function(completions) {
if (completions)
matches = matches.concat(completions);
removeDuplicateMatches(matches);
// Sort by priority, score
matches.sort(function(a, b) {
if (a.priority < b.priority)
return 1;
else if (a.priority > b.priority)
return -1;
else if (a.score < b.score)
return 1;
else if (a.score > b.score)
return -1;
else if (a.id && a.id === b.id) {
if (a.isFunction)
return -1;
else if (b.isFunction)
return 1;
}
if (a.name < b.name)
return -1;
else if(a.name > b.name)
return 1;
else
return 0;
});
_self.sender.emit("complete", {
pos: pos,
matches: matches,
line: _self.doc.getLine(pos.row)
});
return;
});
};
}).call(AutocompleteWorker.prototype);
});

View file

@ -0,0 +1,85 @@
define(function(require, exports, module) {
var ID_REGEX = /[a-zA-Z_0-9\$]/;
function retrievePrecedingIdentifier(text, pos, regex) {
regex = regex || ID_REGEX;
var buf = [];
for (var i = pos-1; i >= 0; i--) {
if (regex.test(text[i]))
buf.push(text[i]);
else
break;
}
return buf.reverse().join("");
}
function retrieveFollowingIdentifier(text, pos, regex) {
regex = regex || ID_REGEX;
var buf = [];
for (var i = pos; i < text.length; i++) {
if (regex.test(text[i]))
buf.push(text[i]);
else
break;
}
return buf;
}
function prefixBinarySearch(items, prefix) {
var startIndex = 0;
var stopIndex = items.length - 1;
var middle = Math.floor((stopIndex + startIndex) / 2);
while (stopIndex > startIndex && middle >= 0 && items[middle].indexOf(prefix) !== 0) {
if (prefix < items[middle]) {
stopIndex = middle - 1;
}
else if (prefix > items[middle]) {
startIndex = middle + 1;
}
middle = Math.floor((stopIndex + stopIndex) / 2);
}
// Look back to make sure we haven't skipped any
while (middle > 0 && items[middle-1].indexOf(prefix) === 0)
middle--;
return middle >= 0 ? middle : 0; // ensure we're not returning a negative index
}
function findCompletions(prefix, allIdentifiers) {
allIdentifiers.sort();
var startIdx = prefixBinarySearch(allIdentifiers, prefix);
var matches = [];
for (var i = startIdx; i < allIdentifiers.length && allIdentifiers[i].indexOf(prefix) === 0; i++)
matches.push(allIdentifiers[i]);
return matches;
}
function fetchText(staticPrefix, path) {
var xhr = new XMLHttpRequest();
xhr.open('GET', staticPrefix + "/" + path, false);
try {
xhr.send();
}
// Likely we got a cross-script error (equivalent with a 404 in our cloud setup)
catch(e) {
return false;
}
if (xhr.status === 200)
return xhr.responseText;
else
return false;
}
/** @deprecated Use retrievePrecedingIdentifier */
exports.retrievePreceedingIdentifier = function() {
console.error("Deprecated: 'retrievePreceedingIdentifier' - use 'retrievePrecedingIdentifier' instead");
return retrievePrecedingIdentifier.apply(null, arguments);
};
exports.retrievePrecedingIdentifier = retrievePrecedingIdentifier;
exports.retrieveFollowingIdentifier = retrieveFollowingIdentifier;
exports.findCompletions = findCompletions;
exports.fetchText = fetchText;
});

View file

@ -0,0 +1,176 @@
/**
* Cloud9 Language Foundation
*
* @copyright 2011, Ajax.org B.V.
* @license GPLv3 <http://www.gnu.org/licenses/gpl.txt>
*/
define(function(require, exports, module) {
var mixedLanguages = {
php: {
"default": "html",
"php-start": /<\?(?:php|\=)?/,
"php-end": /\?>/,
"css-start": /<style[^>]*>/,
"css-end": /<\/style>/,
"javascript-start": /<script(?:\"[^\"]*\"|'[^']*'|[^'">\/])*>/,
"javascript-end": /<\/script>/
},
html: {
"css-start": /<style[^>]*>/,
"css-end": /<\/style>/,
"javascript-start": /<script(?:\"[^\"]*\"|'[^']*'|[^'">\/])*>/,
"javascript-end": /<\/script>/
}
};
/* Now:
* - One level syntax nesting supported
* Future: (if worth it)
* - Have a stack to repesent it
* - Maintain a syntax tree for an opened file
*/
function getSyntaxRegions(doc, originalSyntax) {
if (! mixedLanguages[originalSyntax])
return [{
syntax: originalSyntax,
sl: 0,
sc: 0,
el: doc.getLength()-1,
ec: doc.getLine(doc.getLength()-1).length
}];
var lines = doc.getAllLines();
var type = mixedLanguages[originalSyntax];
var defaultSyntax = type["default"] || originalSyntax;
var starters = Object.keys(type).filter(function (m) {
return m.indexOf("-start") === m.length - 6;
});
var syntax = defaultSyntax;
var regions = [{syntax: syntax, sl: 0, sc: 0}];
var starter, endLang;
var tempS, tempM;
var i, m, cut, inLine = 0;
for (var row = 0; row < lines.length; row++) {
var line = lines[row];
m = null;
if (endLang) {
m = endLang.exec(line);
if (m) {
endLang = null;
syntax = defaultSyntax;
regions[regions.length-1].el = row;
regions[regions.length-1].ec = m.index + inLine;
regions.push({
syntax: syntax,
sl: row,
sc: m.index + inLine
});
cut = m.index + m[0].length;
lines[row] = line.substring(cut);
inLine += cut;
row--; // continue processing of the line
}
else {
inLine = 0;
}
}
else {
for (i = 0; i < starters.length; i++) {
tempS = starters[i];
tempM = type[tempS].exec(line);
if (tempM && (!m || m.index > tempM.index)) {
m = tempM;
starter = tempS;
}
}
if (m) {
syntax = starter.replace("-start", "");
endLang = type[syntax+"-end"];
regions[regions.length-1].el = row;
regions[regions.length-1].ec = inLine + m.index + m[0].length;
regions.push({
syntax: syntax,
sl: row,
sc: inLine + m.index + m[0].length
});
cut = m.index + m[0].length;
lines[row] = line.substring(m.index + m[0].length);
row--; // continue processing of the line
inLine += cut;
}
else {
inLine = 0;
}
}
}
regions[regions.length-1].el = lines.length;
regions[regions.length-1].ec = lines[lines.length-1].length;
return regions;
}
function getContextSyntaxPart(doc, pos, originalSyntax) {
if (! mixedLanguages[originalSyntax])
return {
language: originalSyntax,
value: doc.getValue(),
region: getSyntaxRegions(doc, originalSyntax)[0],
index: 0
};
var regions = getSyntaxRegions(doc, originalSyntax);
for (var i = 0; i < regions.length; i++) {
var region = regions[i];
if ((pos.row > region.sl && pos.row < region.el) ||
(pos.row === region.sl && pos.column >= region.sc) ||
(pos.row === region.el && pos.column <= region.ec))
return regionToCodePart(doc, region, i);
}
return null; // should never happen
}
function getContextSyntax(doc, pos, originalSyntax) {
var part = getContextSyntaxPart(doc, pos, originalSyntax);
return part && part.language; // should never happen
}
function regionToCodePart (doc, region, index) {
var lines = doc.getLines(region.sl, region.el);
return {
value: region.sl === region.el ? lines[0].substring(region.sc, region.ec) :
[lines[0].substring(region.sc)].concat(lines.slice(1, lines.length-1)).concat([lines[lines.length-1].substring(0, region.ec)]).join(doc.getNewLineCharacter()),
language: region.syntax,
region: region,
index: index
};
}
function getCodeParts (doc, originalSyntax) {
var regions = getSyntaxRegions(doc, originalSyntax);
return regions.map(function (region, i) {
return regionToCodePart(doc, region, i);
});
}
function posToRegion (region, pos) {
return {
row: pos.row - region.sl,
column: pos.column
};
}
function regionToPos (region, pos) {
return {
row: pos.row + region.sl,
column: pos.column
};
}
exports.getContextSyntax = getContextSyntax;
exports.getContextSyntaxPart = getContextSyntaxPart;
exports.getSyntaxRegions = getSyntaxRegions;
exports.getCodeParts = getCodeParts;
exports.posToRegion = posToRegion;
exports.regionToPos = regionToPos;
});

View file

@ -0,0 +1,74 @@
define(function(require, exports, module) {
var completeUtil = require("./complete_util");
var SPLIT_REGEX = /[^a-zA-Z_0-9\$]+/;
var completer = module.exports;
this.handlesLanguage = function(language) {
return true;
};
// For the current document, gives scores to identifiers not on frequency, but on distance from the current prefix
function wordDistanceAnalyzer(doc, pos, prefix) {
var text = doc.getValue().trim();
// Determine cursor's word index
var textBefore = doc.getLines(0, pos.row-1).join("\n") + "\n";
var currentLine = doc.getLine(pos.row);
textBefore += currentLine.substr(0, pos.column);
var prefixPosition = textBefore.trim().split(SPLIT_REGEX).length - 1;
// Split entire document into words
var identifiers = text.split(SPLIT_REGEX);
var identDict = {};
// Find prefix to find other identifiers close it
for (var i = 0; i < identifiers.length; i++) {
if (i === prefixPosition)
continue;
var ident = identifiers[i];
if (ident.length === 0)
continue;
var distance = Math.max(prefixPosition, i) - Math.min(prefixPosition, i);
// Score substracted from 100000 to force descending ordering
if (Object.prototype.hasOwnProperty.call(identDict, ident))
identDict[ident] = Math.max(1000000-distance, identDict[ident]);
else
identDict[ident] = 1000000-distance;
}
return identDict;
}
function analyze(doc, pos) {
var line = doc.getLine(pos.row);
var identifier = completeUtil.retrievePrecedingIdentifier(line, pos.column);
var analysisCache = wordDistanceAnalyzer(doc, pos, identifier);
return analysisCache;
}
completer.complete = function(doc, fullAst, pos, currentNode, callback) {
var identDict = analyze(doc, pos);
var line = doc.getLine(pos.row);
var identifier = completeUtil.retrievePrecedingIdentifier(line, pos.column);
var allIdentifiers = [];
for (var ident in identDict) {
allIdentifiers.push(ident);
}
var matches = completeUtil.findCompletions(identifier, allIdentifiers);
callback(matches);
/*callback(matches.map(function(m) {
return {
name : m,
replaceText : m,
icon : null,
score : identDict[m],
meta : "",
priority : 1
};
}));*/
};
});

View file

@ -7,7 +7,8 @@ var lang = require("../lib/lang");
var Mirror = exports.Mirror = function(sender) {
this.sender = sender;
var doc = this.doc = new Document("");
this.data = {};
var deferredUpdate = this.deferredUpdate = lang.delayedCall(this.onUpdate.bind(this));
var _self = this;
@ -25,8 +26,9 @@ var Mirror = exports.Mirror = function(sender) {
this.$timeout = timeout;
};
this.setValue = function(value) {
this.setValue = function(value, data) {
this.doc.setValue(value);
this.data = data;
this.deferredUpdate.schedule(this.$timeout);
};

View file

@ -144,12 +144,12 @@ var WorkerClient = function(topLevelNamespaces, mod, classname) {
catch(ex) {}
};
this.attachToDocument = function(doc) {
if(this.$doc)
this.attachToDocument = function(doc, data) {
if (this.$doc)
this.terminate();
this.$doc = doc;
this.call("setValue", [doc.getValue()]);
this.call("setValue", [doc.getValue(), data]);
doc.on("change", this.changeListener);
};