252 lines
7.6 KiB
JavaScript
252 lines
7.6 KiB
JavaScript
|
/* ***** 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 EventEmitter = require("./lib/event_emitter").EventEmitter;
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Tokenizes the current [[Document `Document`]] in the background, and caches the tokenized rows for future use.
|
||
|
*
|
||
|
* If a certain row is changed, everything below that row is re-tokenized.
|
||
|
*
|
||
|
* @class BackgroundTokenizer
|
||
|
**/
|
||
|
|
||
|
/**
|
||
|
* Creates a new `BackgroundTokenizer` object.
|
||
|
* @param {Tokenizer} tokenizer The tokenizer to use
|
||
|
* @param {Editor} editor The editor to associate with
|
||
|
*
|
||
|
* @constructor
|
||
|
**/
|
||
|
|
||
|
var BackgroundTokenizer = function(tokenizer, editor) {
|
||
|
this.running = false;
|
||
|
this.lines = [];
|
||
|
this.states = [];
|
||
|
this.currentLine = 0;
|
||
|
this.tokenizer = tokenizer;
|
||
|
|
||
|
var self = this;
|
||
|
|
||
|
this.$worker = function() {
|
||
|
if (!self.running) { return; }
|
||
|
|
||
|
var workerStart = new Date();
|
||
|
var currentLine = self.currentLine;
|
||
|
var endLine = -1;
|
||
|
var doc = self.doc;
|
||
|
|
||
|
var startLine = currentLine;
|
||
|
while (self.lines[currentLine])
|
||
|
currentLine++;
|
||
|
|
||
|
var len = doc.getLength();
|
||
|
var processedLines = 0;
|
||
|
self.running = false;
|
||
|
while (currentLine < len) {
|
||
|
self.$tokenizeRow(currentLine);
|
||
|
endLine = currentLine;
|
||
|
do {
|
||
|
currentLine++;
|
||
|
} while (self.lines[currentLine]);
|
||
|
|
||
|
// only check every 5 lines
|
||
|
processedLines ++;
|
||
|
if ((processedLines % 5 === 0) && (new Date() - workerStart) > 20) {
|
||
|
self.running = setTimeout(self.$worker, 20);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
self.currentLine = currentLine;
|
||
|
|
||
|
if (endLine == -1)
|
||
|
endLine = currentLine;
|
||
|
|
||
|
if (startLine <= endLine)
|
||
|
self.fireUpdateEvent(startLine, endLine);
|
||
|
};
|
||
|
};
|
||
|
|
||
|
(function(){
|
||
|
|
||
|
oop.implement(this, EventEmitter);
|
||
|
|
||
|
/**
|
||
|
* Sets a new tokenizer for this object.
|
||
|
*
|
||
|
* @param {Tokenizer} tokenizer The new tokenizer to use
|
||
|
*
|
||
|
**/
|
||
|
this.setTokenizer = function(tokenizer) {
|
||
|
this.tokenizer = tokenizer;
|
||
|
this.lines = [];
|
||
|
this.states = [];
|
||
|
|
||
|
this.start(0);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Sets a new document to associate with this object.
|
||
|
* @param {Document} doc The new document to associate with
|
||
|
**/
|
||
|
this.setDocument = function(doc) {
|
||
|
this.doc = doc;
|
||
|
this.lines = [];
|
||
|
this.states = [];
|
||
|
|
||
|
this.stop();
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Fires whenever the background tokeniziers between a range of rows are going to be updated.
|
||
|
*
|
||
|
* @event update
|
||
|
* @param {Object} e An object containing two properties, `first` and `last`, which indicate the rows of the region being updated.
|
||
|
*
|
||
|
**/
|
||
|
/**
|
||
|
* Emits the `'update'` event. `firstRow` and `lastRow` are used to define the boundaries of the region to be updated.
|
||
|
* @param {Number} firstRow The starting row region
|
||
|
* @param {Number} lastRow The final row region
|
||
|
*
|
||
|
**/
|
||
|
this.fireUpdateEvent = function(firstRow, lastRow) {
|
||
|
var data = {
|
||
|
first: firstRow,
|
||
|
last: lastRow
|
||
|
};
|
||
|
this._signal("update", {data: data});
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Starts tokenizing at the row indicated.
|
||
|
*
|
||
|
* @param {Number} startRow The row to start at
|
||
|
*
|
||
|
**/
|
||
|
this.start = function(startRow) {
|
||
|
this.currentLine = Math.min(startRow || 0, this.currentLine, this.doc.getLength());
|
||
|
|
||
|
// remove all cached items below this line
|
||
|
this.lines.splice(this.currentLine, this.lines.length);
|
||
|
this.states.splice(this.currentLine, this.states.length);
|
||
|
|
||
|
this.stop();
|
||
|
// pretty long delay to prevent the tokenizer from interfering with the user
|
||
|
this.running = setTimeout(this.$worker, 700);
|
||
|
};
|
||
|
|
||
|
this.scheduleStart = function() {
|
||
|
if (!this.running)
|
||
|
this.running = setTimeout(this.$worker, 700);
|
||
|
};
|
||
|
|
||
|
this.$updateOnChange = function(delta) {
|
||
|
var startRow = delta.start.row;
|
||
|
var len = delta.end.row - startRow;
|
||
|
|
||
|
if (len === 0) {
|
||
|
this.lines[startRow] = null;
|
||
|
} else if (delta.action == "remove") {
|
||
|
this.lines.splice(startRow, len + 1, null);
|
||
|
this.states.splice(startRow, len + 1, null);
|
||
|
} else {
|
||
|
var args = Array(len + 1);
|
||
|
args.unshift(startRow, 1);
|
||
|
this.lines.splice.apply(this.lines, args);
|
||
|
this.states.splice.apply(this.states, args);
|
||
|
}
|
||
|
|
||
|
this.currentLine = Math.min(startRow, this.currentLine, this.doc.getLength());
|
||
|
|
||
|
this.stop();
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Stops tokenizing.
|
||
|
*
|
||
|
**/
|
||
|
this.stop = function() {
|
||
|
if (this.running)
|
||
|
clearTimeout(this.running);
|
||
|
this.running = false;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Gives list of tokens of the row. (tokens are cached)
|
||
|
*
|
||
|
* @param {Number} row The row to get tokens at
|
||
|
*
|
||
|
*
|
||
|
*
|
||
|
**/
|
||
|
this.getTokens = function(row) {
|
||
|
return this.lines[row] || this.$tokenizeRow(row);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* [Returns the state of tokenization at the end of a row.]{: #BackgroundTokenizer.getState}
|
||
|
*
|
||
|
* @param {Number} row The row to get state at
|
||
|
**/
|
||
|
this.getState = function(row) {
|
||
|
if (this.currentLine == row)
|
||
|
this.$tokenizeRow(row);
|
||
|
return this.states[row] || "start";
|
||
|
};
|
||
|
|
||
|
this.$tokenizeRow = function(row) {
|
||
|
var line = this.doc.getLine(row);
|
||
|
var state = this.states[row - 1];
|
||
|
|
||
|
var data = this.tokenizer.getLineTokens(line, state, row);
|
||
|
|
||
|
if (this.states[row] + "" !== data.state + "") {
|
||
|
this.states[row] = data.state;
|
||
|
this.lines[row + 1] = null;
|
||
|
if (this.currentLine > row + 1)
|
||
|
this.currentLine = row + 1;
|
||
|
} else if (this.currentLine == row) {
|
||
|
this.currentLine = row + 1;
|
||
|
}
|
||
|
|
||
|
return this.lines[row] = data.tokens;
|
||
|
};
|
||
|
|
||
|
}).call(BackgroundTokenizer.prototype);
|
||
|
|
||
|
exports.BackgroundTokenizer = BackgroundTokenizer;
|
||
|
});
|