diff --git a/src/display/highlight_worker.js b/src/display/highlight_worker.js index 1d964e640d6a16bcb06985b4b2c487f4bc928dc8..e868c42f3c58732af5b0f6387b62f0368151b7f4 100644 --- a/src/display/highlight_worker.js +++ b/src/display/highlight_worker.js @@ -8,20 +8,19 @@ import { regLineChange } from "./view_tracking" // HIGHLIGHT WORKER export function startWorker(cm, time) { - if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo) + if (cm.doc.highlightFrontier < cm.display.viewTo) cm.state.highlight.set(time, bind(highlightWorker, cm)) } function highlightWorker(cm) { let doc = cm.doc - if (doc.frontier < doc.first) doc.frontier = doc.first - if (doc.frontier >= cm.display.viewTo) return + if (doc.highlightFrontier >= cm.display.viewTo) return let end = +new Date + cm.options.workTime - let context = getContextBefore(cm, doc.frontier) + let context = getContextBefore(cm, doc.highlightFrontier) let changedLines = [] - doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), line => { - if (doc.frontier >= cm.display.viewFrom) { // Visible + doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), line => { + if (context.line >= cm.display.viewFrom) { // Visible let oldStyles = line.styles let resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null let highlighted = highlightLine(cm, line, context, true) @@ -33,19 +32,22 @@ function highlightWorker(cm) { let ischange = !oldStyles || oldStyles.length != line.styles.length || oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass) for (let i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i] - if (ischange) changedLines.push(doc.frontier) + if (ischange) changedLines.push(context.line) line.stateAfter = context.save() + context.nextLine() } else { if (line.text.length <= cm.options.maxHighlightLength) processLine(cm, line.text, context) - line.stateAfter = doc.frontier % 5 == 0 ? context.save() : null + line.stateAfter = context.line % 5 == 0 ? context.save() : null + context.nextLine() } - ++doc.frontier if (+new Date > end) { startWorker(cm, cm.options.workDelay) return true } }) + doc.highlightFrontier = context.line + doc.modeFrontier = Math.max(doc.modeFrontier, context.line) if (changedLines.length) runInOp(cm, () => { for (let i = 0; i < changedLines.length; i++) regLineChange(cm, changedLines[i], "text") diff --git a/src/display/mode_state.js b/src/display/mode_state.js index 8c4c60b0581a941611c1e1ea021c3d654262dcef..ca0a534ca44935c0f5a50681bc960fc78332fceb 100644 --- a/src/display/mode_state.js +++ b/src/display/mode_state.js @@ -15,7 +15,7 @@ export function resetModeState(cm) { if (line.stateAfter) line.stateAfter = null if (line.styles) line.styles = null }) - cm.doc.frontier = cm.doc.first + cm.doc.modeFrontier = cm.doc.highlightFrontier = cm.doc.first startWorker(cm, 100) cm.state.modeGen++ if (cm.curOp) regChange(cm) diff --git a/src/line/highlight.js b/src/line/highlight.js index f1114a5d98d93e332ab86dec7b589e3040a2053f..e835aa0411ce907106a0f1d16f350bfb262f1fe7 100644 --- a/src/line/highlight.js +++ b/src/line/highlight.js @@ -39,7 +39,7 @@ class Context { } save(copy) { - let state = copy ? copyState(this.doc.mode, this.state) : this.state + let state = copy !== false ? copyState(this.doc.mode, this.state) : this.state return this.maxLookAhead > 0 ? new SavedContext(state, this.maxLookAhead) : state } } @@ -95,11 +95,12 @@ export function getLineStyles(cm, line, updateFrontier) { let resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state) let result = highlightLine(cm, line, context) if (resetState) context.state = resetState - line.stateAfter = context.save() + line.stateAfter = context.save(!resetState) line.styles = result.styles if (result.classes) line.styleClasses = result.classes else if (line.styleClasses) line.styleClasses = null - if (updateFrontier === cm.doc.frontier) cm.doc.frontier++ + if (updateFrontier === cm.doc.highlightFrontier) + cm.doc.modeFrontier = Math.max(cm.doc.modeFrontier, ++cm.doc.highlightFrontier) } return line.styles } @@ -117,7 +118,7 @@ export function getContextBefore(cm, n, precise) { line.stateAfter = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo ? context.save() : null context.nextLine() }) - if (precise) doc.frontier = context.line + if (precise) doc.modeFrontier = context.line return context } @@ -240,7 +241,7 @@ function findStartLine(cm, n, precise) { for (let search = n; search > lim; --search) { if (search <= doc.first) return doc.first let line = getLine(doc, search - 1), after = line.stateAfter - if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.frontier)) + if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.modeFrontier)) return search let indented = countColumn(line.text, null, cm.options.tabSize) if (minline == null || minindent > indented) { @@ -250,3 +251,20 @@ function findStartLine(cm, n, precise) { } return minline } + +export function retreatFrontier(doc, n) { + doc.modeFrontier = Math.min(doc.modeFrontier, n) + if (doc.highlightFrontier < n - 10) return + let start = doc.first + for (let line = n - 1; line > start; line--) { + let saved = getLine(doc, line).stateAfter + // change is on 3 + // state on line 1 looked ahead 2 -- so saw 3 + // test 1 + 2 < 3 should cover this + if (saved && (!(saved instanceof SavedContext) || line + saved.lookAhead < n)) { + start = line + 1 + break + } + } + doc.highlightFrontier = Math.min(doc.highlightFrontier, start) +} diff --git a/src/model/Doc.js b/src/model/Doc.js index e76d3b152f7208f1c3bfc47b5c7c13abbf4b1dfc..c3da76d74e67466534be975708463fe7a8c2a624 100644 --- a/src/model/Doc.js +++ b/src/model/Doc.js @@ -29,7 +29,7 @@ let Doc = function(text, mode, firstLine, lineSep, direction) { this.scrollTop = this.scrollLeft = 0 this.cantEdit = false this.cleanGeneration = 1 - this.frontier = firstLine + this.modeFrontier = this.highlightFrontier = firstLine let start = Pos(firstLine, 0) this.sel = simpleSelection(start) this.history = new History(null) diff --git a/src/model/changes.js b/src/model/changes.js index c6692d4c5a96bd1f2dfd96bbad18e887b8716d04..214e0231abdbf53d65a5beaff2ec5a85f2a22491 100644 --- a/src/model/changes.js +++ b/src/model/changes.js @@ -1,3 +1,4 @@ +import { retreatFrontier } from "../line/highlight" import { startWorker } from "../display/highlight_worker" import { operation } from "../display/operations" import { regChange, regLineChange } from "../display/view_tracking" @@ -231,8 +232,7 @@ function makeChangeSingleDocInEditor(cm, change, spans) { if (recomputeMaxLength) cm.curOp.updateMaxLine = true } - // Adjust frontier, schedule worker - doc.frontier = Math.min(doc.frontier, from.line) + retreatFrontier(doc, from.line) startWorker(cm, 400) let lendiff = change.text.length - (to.line - from.line) - 1 diff --git a/test/test.js b/test/test.js index d1abc7a8122aa0c5f0cbf0caca283f9fb4ba3b7b..59b760d5ae0eaaa604f84167a7d7c66dfc183b10 100644 --- a/test/test.js +++ b/test/test.js @@ -2474,3 +2474,22 @@ testCM("delete_wrapped", function(cm) { cm.deleteH(-1, "char"); eq(cm.getLine(0), "1245"); }, {value: "12345", lineWrapping: true}) + +CodeMirror.defineMode("lookahead_mode", function() { + // Colors text as atom if the line two lines down has an x in it + return { + token: function(stream) { + stream.skipToEnd() + return /x/.test(stream.lookAhead(2)) ? "atom" : null + } + } +}) + +testCM("mode_lookahead", function(cm) { + eq(cm.getTokenAt(Pos(0, 1)).type, "atom") + eq(cm.getTokenAt(Pos(1, 1)).type, "atom") + eq(cm.getTokenAt(Pos(2, 1)).type, null) + cm.replaceRange("\n", Pos(2, 0)) + eq(cm.getTokenAt(Pos(0, 1)).type, null) + eq(cm.getTokenAt(Pos(1, 1)).type, "atom") +}, {value: "foo\na\nx\nx\n", mode: "lookahead_mode"})