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