|
|
@ -1,16 +1,21 @@ |
|
|
|
'use strict'; |
|
|
|
import { Disposable, Event, EventEmitter, TextEditor, TextEditorSelectionChangeEvent, window } from 'vscode'; |
|
|
|
import { Disposable, Event, EventEmitter, Selection, TextEditor, TextEditorSelectionChangeEvent, window } from 'vscode'; |
|
|
|
import { isTextEditor } from '../constants'; |
|
|
|
import { debug, Deferrable, Functions } from '../system'; |
|
|
|
|
|
|
|
export interface LinesChangeEvent { |
|
|
|
readonly editor: TextEditor | undefined; |
|
|
|
readonly lines: number[] | undefined; |
|
|
|
readonly selections: LineSelection[] | undefined; |
|
|
|
|
|
|
|
readonly reason: 'editor' | 'selection'; |
|
|
|
readonly pending?: boolean; |
|
|
|
} |
|
|
|
|
|
|
|
export interface LineSelection { |
|
|
|
anchor: number; |
|
|
|
active: number; |
|
|
|
} |
|
|
|
|
|
|
|
export class LineTracker<T> implements Disposable { |
|
|
|
private _onDidChangeActiveLines = new EventEmitter<LinesChangeEvent>(); |
|
|
|
get onDidChangeActiveLines(): Event<LinesChangeEvent> { |
|
|
@ -34,7 +39,7 @@ export class LineTracker implements Disposable { |
|
|
|
|
|
|
|
this.reset(); |
|
|
|
this._editor = editor; |
|
|
|
this._lines = editor?.selections.map(s => s.active.line); |
|
|
|
this._selections = LineTracker.toLineSelections(editor?.selections); |
|
|
|
|
|
|
|
this.trigger('editor'); |
|
|
|
} |
|
|
@ -43,12 +48,12 @@ export class LineTracker implements Disposable { |
|
|
|
// If this isn't for our cached editor and its not a real editor -- kick out
|
|
|
|
if (this._editor !== e.textEditor && !isTextEditor(e.textEditor)) return; |
|
|
|
|
|
|
|
const lines = e.selections.map(s => s.active.line); |
|
|
|
if (this._editor === e.textEditor && this.includesAll(lines)) return; |
|
|
|
const selections = LineTracker.toLineSelections(e.selections); |
|
|
|
if (this._editor === e.textEditor && this.includes(selections)) return; |
|
|
|
|
|
|
|
this.reset(); |
|
|
|
this._editor = e.textEditor; |
|
|
|
this._lines = lines; |
|
|
|
this._selections = selections; |
|
|
|
|
|
|
|
this.trigger(this._editor === e.textEditor ? 'selection' : 'editor'); |
|
|
|
} |
|
|
@ -61,17 +66,34 @@ export class LineTracker implements Disposable { |
|
|
|
this._state.set(line, state); |
|
|
|
} |
|
|
|
|
|
|
|
private _lines: number[] | undefined; |
|
|
|
get lines(): number[] | undefined { |
|
|
|
return this._lines; |
|
|
|
private _selections: LineSelection[] | undefined; |
|
|
|
get selections(): LineSelection[] | undefined { |
|
|
|
return this._selections; |
|
|
|
} |
|
|
|
|
|
|
|
includes(line: number): boolean { |
|
|
|
return this._lines?.includes(line) ?? false; |
|
|
|
} |
|
|
|
includes(selections: LineSelection[]): boolean; |
|
|
|
includes(line: number, options?: { activeOnly: boolean }): boolean; |
|
|
|
includes(lineOrSelections: number | LineSelection[], options?: { activeOnly: boolean }): boolean { |
|
|
|
if (Array.isArray(lineOrSelections)) { |
|
|
|
return LineTracker.includes(lineOrSelections, this._selections); |
|
|
|
} |
|
|
|
|
|
|
|
if (this._selections == null || this._selections.length === 0) return false; |
|
|
|
|
|
|
|
includesAll(lines: number[] | undefined): boolean { |
|
|
|
return LineTracker.includesAll(lines, this._lines); |
|
|
|
const line = lineOrSelections; |
|
|
|
const activeOnly = options?.activeOnly ?? true; |
|
|
|
|
|
|
|
for (const selection of this._selections) { |
|
|
|
if ( |
|
|
|
line === selection.active || |
|
|
|
(!activeOnly && |
|
|
|
((selection.anchor >= line && line >= selection.active) || |
|
|
|
(selection.active >= line && line >= selection.anchor))) |
|
|
|
) { |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
refresh() { |
|
|
@ -174,13 +196,13 @@ export class LineTracker implements Disposable { |
|
|
|
} |
|
|
|
|
|
|
|
protected trigger(reason: 'editor' | 'selection') { |
|
|
|
this.onLinesChanged({ editor: this._editor, lines: this._lines, reason: reason }); |
|
|
|
this.onLinesChanged({ editor: this._editor, selections: this.selections, reason: reason }); |
|
|
|
} |
|
|
|
|
|
|
|
private _linesChangedDebounced: (((e: LinesChangeEvent) => void) & Deferrable) | undefined; |
|
|
|
|
|
|
|
private onLinesChanged(e: LinesChangeEvent) { |
|
|
|
if (e.lines === undefined) { |
|
|
|
if (e.selections === undefined) { |
|
|
|
setImmediate(() => { |
|
|
|
if (window.activeTextEditor !== e.editor) return; |
|
|
|
|
|
|
@ -199,12 +221,7 @@ export class LineTracker implements Disposable { |
|
|
|
(e: LinesChangeEvent) => { |
|
|
|
if (window.activeTextEditor !== e.editor) return; |
|
|
|
// Make sure we are still on the same lines
|
|
|
|
if ( |
|
|
|
!LineTracker.includesAll( |
|
|
|
e.lines, |
|
|
|
e.editor?.selections.map(s => s.active.line), |
|
|
|
) |
|
|
|
) { |
|
|
|
if (!LineTracker.includes(e.selections, LineTracker.toLineSelections(e.editor?.selections))) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
@ -223,10 +240,21 @@ export class LineTracker implements Disposable { |
|
|
|
this._linesChangedDebounced(e); |
|
|
|
} |
|
|
|
|
|
|
|
static includesAll(lines1: number[] | undefined, lines2: number[] | undefined): boolean { |
|
|
|
if (lines1 === undefined && lines2 === undefined) return true; |
|
|
|
if (lines1 === undefined || lines2 === undefined) return false; |
|
|
|
static includes(selections: LineSelection[] | undefined, inSelections: LineSelection[] | undefined): boolean { |
|
|
|
if (selections == null && inSelections == null) return true; |
|
|
|
if (selections == null || inSelections == null || selections.length !== inSelections.length) return false; |
|
|
|
|
|
|
|
let match; |
|
|
|
|
|
|
|
return selections.every((s, i) => { |
|
|
|
match = inSelections[i]; |
|
|
|
return s.active === match.active && s.anchor === match.anchor; |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
return lines2.length === lines1.length && lines2.every((v, i) => v === lines1[i]); |
|
|
|
static toLineSelections(selections: readonly Selection[]): LineSelection[]; |
|
|
|
static toLineSelections(selections: readonly Selection[] | undefined): LineSelection[] | undefined; |
|
|
|
static toLineSelections(selections: readonly Selection[] | undefined) { |
|
|
|
return selections?.map(s => ({ active: s.active.line, anchor: s.anchor.line })); |
|
|
|
} |
|
|
|
} |