1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2025-12-20 23:57:59 +01:00

Document and clean up some of the trickier sections of CompletionContext.

This commit is contained in:
Kris Maglione
2010-11-13 15:34:47 -05:00
parent 152f8523af
commit 6ce1255c49

View File

@@ -160,10 +160,10 @@ const CompletionContext = Class("CompletionContext", {
* @property {CompletionContext} The top-level completion context.
*/
this.top = this;
this.__defineGetter__("incomplete", function () this.contextList.some(function (c) c.parent && c.incomplete));
this.__defineGetter__("waitingForTab", function () this.contextList.some(function (c) c.parent && c.waitingForTab));
this.__defineSetter__("incomplete", function (val) {});
this.__defineSetter__("waitingForTab", function (val) {});
this.__defineGetter__("incomplete", function () this._incomplete || this.contextList.some(function (c) c.parent && c.incomplete));
this.__defineGetter__("waitingForTab", function () this._waitingForTab || this.contextList.some(function (c) c.parent && c.waitingForTab));
this.__defineSetter__("incomplete", function (val) { this._incomplete = val; });
this.__defineSetter__("waitingForTab", function (val) { this._waitingForTab = val; });
this.reset();
}
/**
@@ -315,9 +315,17 @@ const CompletionContext = Class("CompletionContext", {
this.process = format.process || this.process;
},
/**
* @property {string | xml | null}
* The message displayed at the head of the completions for the
* current context.
*/
get message() this._message || (this.waitingForTab ? "Waiting for <Tab>" : null),
set message(val) this._message = val,
/**
* The prototype object for items returned by {@link items}.
*/
get itemPrototype() {
let res = {};
function result(quote) {
@@ -339,18 +347,39 @@ const CompletionContext = Class("CompletionContext", {
return res;
},
/**
* Returns true when the completions generated by {@link #generate}
* must be regenerated. May be set to true to invalidate the current
* completions.
*/
get regenerate() this._generate && (!this.completions || !this.itemCache[this.key] || this.cache.offset != this.offset),
set regenerate(val) { if (val) delete this.itemCache[this.key]; },
get generate() !this._generate ? null : function () {
/**
* A property which may be set to a function to generate the value
* of {@link completions} only when necessary. The generated
* completions are linked to the value in {@link #key} and may be
* invalidated by setting the {@link #regenerate} property.
*/
get generate() this._generate || null,
set generate(arg) {
this.hasItems = true;
this._generate = arg;
},
/**
* Generates the item list in {@link #completions} via the
* {@link #generate} method if the previously generated value is no
* longer valid.
*/
generateCompletions: function generateCompletions() {
if (this.offset != this.cache.offset || this.lastActivated != this.top.runCount) {
this.itemCache = {};
this.cache.offset = this.offset;
this.lastActivated = this.top.runCount;
}
if (!this.itemCache[this.key]) {
if (!this.itemCache[this.key])
try {
let res = this._generate.call(this);
let res = this._generate();
if (res != null)
this.itemCache[this.key] = res;
}
@@ -358,37 +387,42 @@ const CompletionContext = Class("CompletionContext", {
dactyl.reportError(e);
this.message = "Error: " + e;
}
}
return this.itemCache[this.key];
},
set generate(arg) {
this.hasItems = true;
this._generate = arg;
// XXX
this.noUpdate = true;
this.completions = this.itemCache[this.key];
this.noUpdate = false;
},
get ignoreCase() {
if ("_ignoreCase" in this)
return this._ignoreCase;
let mode = this.wildcase;
if (mode == "match")
return this._ignoreCase = false;
if (mode == "ignore")
return this._ignoreCase = true;
return this._ignoreCase = !/[A-Z]/.test(this.filter);
if (this._ignoreCase == null) {
let mode = this.wildcase;
if (mode == "match")
this._ignoreCase = false;
if (mode == "ignore")
this._ignoreCase = true;
this._ignoreCase = !/[A-Z]/.test(this.filter);
}
return this._ignoreCase;
},
set ignoreCase(val) this._ignoreCase = val,
/**
* Returns a list of all completion items which match the current
* filter. The items returned are objects containing one property
* for each corresponding property in {@link keys}. The returned
* list is generated on-demand from the item list in {@link completions}
* or generated by {@link generate}, and is cached as long as no
* properties which would invalidate the result are changed.
*/
get items() {
// Don't return any items if completions or generator haven't
// been set during this completion cycle.
if (!this.hasItems)
return [];
// Regenerate completions if we must
if (this.generate) {
// XXX
this.noUpdate = true;
this.completions = this.generate();
this.noUpdate = false;
}
if (this.generate)
this.generateCompletions();
let items = this.completions;
// Check for cache miss
@@ -404,7 +438,7 @@ const CompletionContext = Class("CompletionContext", {
this.cache.rows = [];
this.cache.filter = this.filter;
if (items == null)
return items;
return null;
let self = this;
delete this._substrings;
@@ -430,9 +464,10 @@ const CompletionContext = Class("CompletionContext", {
try {
// Item prototypes
let proto = this.itemPrototype;
if (!this.cache.constructed)
if (!this.cache.constructed) {
let proto = this.itemPrototype;
this.cache.constructed = items.map(function (item) ({ __proto__: proto, item: item }));
}
// Filters
let filtered = this.filterFunc(this.cache.constructed);
@@ -452,6 +487,10 @@ const CompletionContext = Class("CompletionContext", {
}
},
/**
* Returns a list of all substrings common to all items which
* include the current filter.
*/
get substrings() {
let items = this.items;
if (items.length == 0 || !this.hasItems)
@@ -460,11 +499,13 @@ const CompletionContext = Class("CompletionContext", {
return this._substrings;
let fixCase = this.ignoreCase ? String.toLowerCase : util.identity;
let text = fixCase(items[0].text);
let text = fixCase(items[0].text);
let filter = fixCase(this.filter);
// Exceedingly long substrings cause Gecko to go into convulsions
if (text.length > 100)
text = text.substr(0, 100);
let filter = fixCase(this.filter);
if (this.anchored) {
var compare = function compare(text, s) text.substr(0, s.length) == s;
var substrings = [text];
@@ -480,15 +521,18 @@ const CompletionContext = Class("CompletionContext", {
start = idx + 1;
}
}
substrings = items.reduce(function (res, item)
res.map(function (list) {
var m, len = list.length;
var n = list.length;
res.map(function (substring) {
// A simple binary search to find the longest substring
// of the given string which also matches the current
// item's text.
var m, len = substring.length;
var n = substring.length;
var i = 0;
while (n) {
m = Math.floor(n / 2);
let s = list[i + m];
let keep = compare(fixCase(item.text), list.substring(0, i + m));
let keep = compare(fixCase(item.text), substring.substring(0, i + m));
if (!keep)
len = i + m - 1;
if (!keep || m == 0)
@@ -498,9 +542,10 @@ const CompletionContext = Class("CompletionContext", {
n = n - m;
}
}
return len == list.length ? list : list.substr(0, Math.max(len, 0));
return len == substring.length ? substring : substring.substr(0, Math.max(len, 0));
}),
substrings);
let quote = this.quote;
if (quote)
substrings = substrings.map(function (str) quote[0] + quote[1](str));
@@ -532,6 +577,10 @@ const CompletionContext = Class("CompletionContext", {
this._filter = this._filter.substr(count);
},
/**
* Calls the {@link #cancel} method of all currently active
* sub-contexts.
*/
cancelAll: function () {
for (let [, context] in Iterator(this.contextList)) {
if (context.cancel)
@@ -571,6 +620,25 @@ const CompletionContext = Class("CompletionContext", {
yield [i, cache[i] = cache[i] || util.xmlToDom(self.createRow(items[i]), doc)];
},
/**
* Forks this completion context to create a new sub-context named
* as {this.name}/{name}. The new context is automatically advanced
* *offset* characters. If *completer* is provided, it is called
* with *self* as its 'this' object, the new context as its first
* argument, and any subsequent arguments after *completer* as its
* following arguments.
*
* If *completer* is provided, this function returns its return
* value, otherwise it returns the new completion context.
*
* @param {string} name The name of the new context.
* @param {number} offset The offset of the new context relative to
* the current context's offset.
* @param {object} self *completer*'s 'this' object. @optional
* @param {function|string} completer A completer function to call
* for the new context. If a string is provided, it is
* interpreted as a method to access on *self*.
*/
fork: function fork(name, offset, self, completer) {
if (typeof completer == "string")
completer = self[completer];
@@ -591,6 +659,21 @@ const CompletionContext = Class("CompletionContext", {
return context;
},
/**
* Highlights text in the nsIEditor associated with this completion
* context. *length* characters are highlighted from the position
* *start*, relative to the current context's offset, with the
* selection type *type* as defined in nsISelectionController.
*
* When called with no arguments, all highlights are removed. When
* called with a 0 length, all highlights of type *type* are
* removed.
*
* @param {number} start The position at which to start
* highlighting.
* @param {number} length The length of the substring to highlight.
* @param {string} type The selection type to highlight with.
*/
highlight: function highlight(start, length, type) {
if (arguments.length == 0) {
for (let type in this.selectionTypes)
@@ -614,15 +697,35 @@ const CompletionContext = Class("CompletionContext", {
catch (e) {}
},
match: function match(str) {
return this.matchString(this.filter, str);
},
pushProcessor: function pushProcess(i, fn) {
let next = this.process[i];
this.process[i] = function (item, text) fn(item, text, next);
/**
* Tests the given string for a match against the current filter,
* taking into account anchoring and case sensitivity rules.
*
* @param {string} str The string to match.
* @returns {boolean} True if the string matches, false otherwise.
*/
match: function match(str) this.matchString(this.filter, str),
/**
* Pushes a new output processor onto the processor chain of
* {@link #process}. The provided function is called with the item
* and text to process along with a reference to the processor
* previously installed in the given *index* of {@link #process}.
*
* @param {number} index The index into {@link #process}.
* @param {function(object, string, function)} func The new
* processor.
*/
pushProcessor: function pushProcess(index, func) {
let next = this.process[index];
this.process[index] = function (item, text) func(item, text, next);
},
/**
* Resets this completion context and all sub-contexts for use in a
* new completion cycle. May only be called on the top-level
* context.
*/
reset: function reset() {
let self = this;
if (this.parent)
@@ -649,9 +752,9 @@ const CompletionContext = Class("CompletionContext", {
// delete this.contexts[key];
for each (let context in this.contexts) {
context.hasItems = false;
if (context != context.top)
context.incomplete = false;
context.incomplete = false;
}
this.waitingForTab = false;
this.runCount++;
for each (let context in this.contextList)
context.lastActivated = this.runCount;