From 6ce369c7be197080112693bb1f0e9db1ea600556 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke <marijn@haverbeke.nl> Date: Thu, 2 Feb 2017 14:31:17 +0100 Subject: [PATCH] Use ES6 classes for text markers and line objects --- src/line/line_data.js | 13 ++- src/model/line_widget.js | 61 +++++----- src/model/mark_text.js | 234 ++++++++++++++++++++------------------- src/util/misc.js | 10 +- 4 files changed, 166 insertions(+), 152 deletions(-) diff --git a/src/line/line_data.js b/src/line/line_data.js index 7583c3424..552c56b15 100644 --- a/src/line/line_data.js +++ b/src/line/line_data.js @@ -13,13 +13,16 @@ import { getLine, lineNo, updateLineHeight } from "./utils_line" // Line objects. These hold state related to a line, including // highlighting info (the styles array). -export function Line(text, markedSpans, estimateHeight) { - this.text = text - attachMarkedSpans(this, markedSpans) - this.height = estimateHeight ? estimateHeight(this) : 1 +export class Line { + constructor(text, markedSpans, estimateHeight) { + this.text = text + attachMarkedSpans(this, markedSpans) + this.height = estimateHeight ? estimateHeight(this) : 1 + } + + lineNo() { return lineNo(this) } } eventMixin(Line) -Line.prototype.lineNo = function() { return lineNo(this) } // Change the content (text, markers) of a line. Automatically // invalidates cached information and tries to re-estimate the diff --git a/src/model/line_widget.js b/src/model/line_widget.js index e832e8c48..5854e43df 100644 --- a/src/model/line_widget.js +++ b/src/model/line_widget.js @@ -9,11 +9,38 @@ import { eventMixin } from "../util/event" // Line widgets are block elements displayed above or below a line. -export function LineWidget(doc, node, options) { - if (options) for (let opt in options) if (options.hasOwnProperty(opt)) - this[opt] = options[opt] - this.doc = doc - this.node = node +export class LineWidget { + constructor(doc, node, options) { + if (options) for (let opt in options) if (options.hasOwnProperty(opt)) + this[opt] = options[opt] + this.doc = doc + this.node = node + } + + clear() { + let cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line) + if (no == null || !ws) return + for (let i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1) + if (!ws.length) line.widgets = null + let height = widgetHeight(this) + updateLineHeight(line, Math.max(0, line.height - height)) + if (cm) runInOp(cm, () => { + adjustScrollWhenAboveVisible(cm, line, -height) + regLineChange(cm, no, "widget") + }) + } + + changed() { + let oldH = this.height, cm = this.doc.cm, line = this.line + this.height = null + let diff = widgetHeight(this) - oldH + if (!diff) return + updateLineHeight(line, line.height + diff) + if (cm) runInOp(cm, () => { + cm.curOp.forceUpdate = true + adjustScrollWhenAboveVisible(cm, line, diff) + }) + } } eventMixin(LineWidget) @@ -22,30 +49,6 @@ function adjustScrollWhenAboveVisible(cm, line, diff) { addToScrollPos(cm, null, diff) } -LineWidget.prototype.clear = function() { - let cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line) - if (no == null || !ws) return - for (let i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1) - if (!ws.length) line.widgets = null - let height = widgetHeight(this) - updateLineHeight(line, Math.max(0, line.height - height)) - if (cm) runInOp(cm, () => { - adjustScrollWhenAboveVisible(cm, line, -height) - regLineChange(cm, no, "widget") - }) -} -LineWidget.prototype.changed = function() { - let oldH = this.height, cm = this.doc.cm, line = this.line - this.height = null - let diff = widgetHeight(this) - oldH - if (!diff) return - updateLineHeight(line, line.height + diff) - if (cm) runInOp(cm, () => { - cm.curOp.forceUpdate = true - adjustScrollWhenAboveVisible(cm, line, diff) - }) -} - export function addLineWidget(doc, handle, node, options) { let widget = new LineWidget(doc, node, options) let cm = doc.cm diff --git a/src/model/mark_text.js b/src/model/mark_text.js index 15ec28498..2e8ecf77b 100644 --- a/src/model/mark_text.js +++ b/src/model/mark_text.js @@ -32,118 +32,121 @@ import { reCheckSelection } from "./selection_updates" // when they overlap (they may nest, but not partially overlap). let nextMarkerId = 0 -export function TextMarker(doc, type) { - this.lines = [] - this.type = type - this.doc = doc - this.id = ++nextMarkerId -} -eventMixin(TextMarker) - -// Clear the marker. -TextMarker.prototype.clear = function() { - if (this.explicitlyCleared) return - let cm = this.doc.cm, withOp = cm && !cm.curOp - if (withOp) startOperation(cm) - if (hasHandler(this, "clear")) { - let found = this.find() - if (found) signalLater(this, "clear", found.from, found.to) +export class TextMarker { + constructor(doc, type) { + this.lines = [] + this.type = type + this.doc = doc + this.id = ++nextMarkerId } - let min = null, max = null - for (let i = 0; i < this.lines.length; ++i) { - let line = this.lines[i] - let span = getMarkedSpanFor(line.markedSpans, this) - if (cm && !this.collapsed) regLineChange(cm, lineNo(line), "text") - else if (cm) { - if (span.to != null) max = lineNo(line) - if (span.from != null) min = lineNo(line) + + // Clear the marker. + clear() { + if (this.explicitlyCleared) return + let cm = this.doc.cm, withOp = cm && !cm.curOp + if (withOp) startOperation(cm) + if (hasHandler(this, "clear")) { + let found = this.find() + if (found) signalLater(this, "clear", found.from, found.to) } - line.markedSpans = removeMarkedSpan(line.markedSpans, span) - if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm) - updateLineHeight(line, textHeight(cm.display)) + let min = null, max = null + for (let i = 0; i < this.lines.length; ++i) { + let line = this.lines[i] + let span = getMarkedSpanFor(line.markedSpans, this) + if (cm && !this.collapsed) regLineChange(cm, lineNo(line), "text") + else if (cm) { + if (span.to != null) max = lineNo(line) + if (span.from != null) min = lineNo(line) + } + line.markedSpans = removeMarkedSpan(line.markedSpans, span) + if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm) + updateLineHeight(line, textHeight(cm.display)) + } + if (cm && this.collapsed && !cm.options.lineWrapping) for (let i = 0; i < this.lines.length; ++i) { + let visual = visualLine(this.lines[i]), len = lineLength(visual) + if (len > cm.display.maxLineLength) { + cm.display.maxLine = visual + cm.display.maxLineLength = len + cm.display.maxLineChanged = true + } + } + + if (min != null && cm && this.collapsed) regChange(cm, min, max + 1) + this.lines.length = 0 + this.explicitlyCleared = true + if (this.atomic && this.doc.cantEdit) { + this.doc.cantEdit = false + if (cm) reCheckSelection(cm.doc) + } + if (cm) signalLater(cm, "markerCleared", cm, this) + if (withOp) endOperation(cm) + if (this.parent) this.parent.clear() } - if (cm && this.collapsed && !cm.options.lineWrapping) for (let i = 0; i < this.lines.length; ++i) { - let visual = visualLine(this.lines[i]), len = lineLength(visual) - if (len > cm.display.maxLineLength) { - cm.display.maxLine = visual - cm.display.maxLineLength = len - cm.display.maxLineChanged = true + + // Find the position of the marker in the document. Returns a {from, + // to} object by default. Side can be passed to get a specific side + // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the + // Pos objects returned contain a line object, rather than a line + // number (used to prevent looking up the same line twice). + find(side, lineObj) { + if (side == null && this.type == "bookmark") side = 1 + let from, to + for (let i = 0; i < this.lines.length; ++i) { + let line = this.lines[i] + let span = getMarkedSpanFor(line.markedSpans, this) + if (span.from != null) { + from = Pos(lineObj ? line : lineNo(line), span.from) + if (side == -1) return from + } + if (span.to != null) { + to = Pos(lineObj ? line : lineNo(line), span.to) + if (side == 1) return to + } } + return from && {from: from, to: to} } - if (min != null && cm && this.collapsed) regChange(cm, min, max + 1) - this.lines.length = 0 - this.explicitlyCleared = true - if (this.atomic && this.doc.cantEdit) { - this.doc.cantEdit = false - if (cm) reCheckSelection(cm.doc) + // Signals that the marker's widget changed, and surrounding layout + // should be recomputed. + changed() { + let pos = this.find(-1, true), widget = this, cm = this.doc.cm + if (!pos || !cm) return + runInOp(cm, () => { + let line = pos.line, lineN = lineNo(pos.line) + let view = findViewForLine(cm, lineN) + if (view) { + clearLineMeasurementCacheFor(view) + cm.curOp.selectionChanged = cm.curOp.forceUpdate = true + } + cm.curOp.updateMaxLine = true + if (!lineIsHidden(widget.doc, line) && widget.height != null) { + let oldHeight = widget.height + widget.height = null + let dHeight = widgetHeight(widget) - oldHeight + if (dHeight) + updateLineHeight(line, line.height + dHeight) + } + }) } - if (cm) signalLater(cm, "markerCleared", cm, this) - if (withOp) endOperation(cm) - if (this.parent) this.parent.clear() -} -// Find the position of the marker in the document. Returns a {from, -// to} object by default. Side can be passed to get a specific side -// -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the -// Pos objects returned contain a line object, rather than a line -// number (used to prevent looking up the same line twice). -TextMarker.prototype.find = function(side, lineObj) { - if (side == null && this.type == "bookmark") side = 1 - let from, to - for (let i = 0; i < this.lines.length; ++i) { - let line = this.lines[i] - let span = getMarkedSpanFor(line.markedSpans, this) - if (span.from != null) { - from = Pos(lineObj ? line : lineNo(line), span.from) - if (side == -1) return from - } - if (span.to != null) { - to = Pos(lineObj ? line : lineNo(line), span.to) - if (side == 1) return to + attachLine(line) { + if (!this.lines.length && this.doc.cm) { + let op = this.doc.cm.curOp + if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) + (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this) } + this.lines.push(line) } - return from && {from: from, to: to} -} -// Signals that the marker's widget changed, and surrounding layout -// should be recomputed. -TextMarker.prototype.changed = function() { - let pos = this.find(-1, true), widget = this, cm = this.doc.cm - if (!pos || !cm) return - runInOp(cm, () => { - let line = pos.line, lineN = lineNo(pos.line) - let view = findViewForLine(cm, lineN) - if (view) { - clearLineMeasurementCacheFor(view) - cm.curOp.selectionChanged = cm.curOp.forceUpdate = true - } - cm.curOp.updateMaxLine = true - if (!lineIsHidden(widget.doc, line) && widget.height != null) { - let oldHeight = widget.height - widget.height = null - let dHeight = widgetHeight(widget) - oldHeight - if (dHeight) - updateLineHeight(line, line.height + dHeight) + detachLine(line) { + this.lines.splice(indexOf(this.lines, line), 1) + if (!this.lines.length && this.doc.cm) { + let op = this.doc.cm.curOp + ;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this) } - }) -} - -TextMarker.prototype.attachLine = function(line) { - if (!this.lines.length && this.doc.cm) { - let op = this.doc.cm.curOp - if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) - (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this) - } - this.lines.push(line) -} -TextMarker.prototype.detachLine = function(line) { - this.lines.splice(indexOf(this.lines, line), 1) - if (!this.lines.length && this.doc.cm) { - let op = this.doc.cm.curOp - ;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this) } } +eventMixin(TextMarker) // Create a marker, wire it up to the right lines, and export function markText(doc, from, to, options, type) { @@ -221,24 +224,27 @@ export function markText(doc, from, to, options, type) { // A shared marker spans multiple linked documents. It is // implemented as a meta-marker-object controlling multiple normal // markers. -export function SharedTextMarker(markers, primary) { - this.markers = markers - this.primary = primary - for (let i = 0; i < markers.length; ++i) - markers[i].parent = this -} -eventMixin(SharedTextMarker) +export class SharedTextMarker { + constructor(markers, primary) { + this.markers = markers + this.primary = primary + for (let i = 0; i < markers.length; ++i) + markers[i].parent = this + } -SharedTextMarker.prototype.clear = function() { - if (this.explicitlyCleared) return - this.explicitlyCleared = true - for (let i = 0; i < this.markers.length; ++i) - this.markers[i].clear() - signalLater(this, "clear") -} -SharedTextMarker.prototype.find = function(side, lineObj) { - return this.primary.find(side, lineObj) + clear() { + if (this.explicitlyCleared) return + this.explicitlyCleared = true + for (let i = 0; i < this.markers.length; ++i) + this.markers[i].clear() + signalLater(this, "clear") + } + + find(side, lineObj) { + return this.primary.find(side, lineObj) + } } +eventMixin(SharedTextMarker) function markTextShared(doc, from, to, options, type) { options = copyObj(options) diff --git a/src/util/misc.js b/src/util/misc.js index dead8a6da..097eb050c 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -28,10 +28,12 @@ export function countColumn(string, end, tabSize, startIndex, startValue) { } } -export function Delayed() {this.id = null} -Delayed.prototype.set = function(ms, f) { - clearTimeout(this.id) - this.id = setTimeout(f, ms) +export class Delayed { + constructor() {this.id = null} + set(ms, f) { + clearTimeout(this.id) + this.id = setTimeout(f, ms) + } } export function indexOf(array, elt) { -- GitLab