-
-
Notifications
You must be signed in to change notification settings - Fork 93
Expand file tree
/
Copy pathScopeRangeWatcher.ts
More file actions
148 lines (133 loc) · 4.65 KB
/
ScopeRangeWatcher.ts
File metadata and controls
148 lines (133 loc) · 4.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import { Disposable, showError } from "@cursorless/common";
import { pull } from "lodash";
import {
IterationScopeChangeEventCallback,
IterationScopeRangeConfig,
ScopeChangeEventCallback,
ScopeRangeConfig,
ScopeRanges,
} from "..";
import { Debouncer } from "../core/Debouncer";
import { LanguageDefinitions } from "../languages/LanguageDefinitions";
import { ide } from "../singletons/ide.singleton";
import { ScopeRangeProvider } from "./ScopeRangeProvider";
/**
* Watches for changes to the scope ranges of visible editors and notifies
* listeners when they change.
*/
export class ScopeRangeWatcher {
private disposables: Disposable[] = [];
private debouncer = new Debouncer(() => this.onChange());
private listeners: (() => void)[] = [];
constructor(
languageDefinitions: LanguageDefinitions,
private scopeRangeProvider: ScopeRangeProvider,
) {
this.disposables.push(
// An Event which fires when the array of visible editors has changed.
ide().onDidChangeVisibleTextEditors(this.debouncer.run),
// An event that fires when a text document opens
ide().onDidOpenTextDocument(this.debouncer.run),
// An Event that fires when a text document closes
ide().onDidCloseTextDocument(this.debouncer.run),
// An event that is emitted when a text document is changed. This usually
// happens when the contents changes but also when other things like the
// dirty-state changes.
ide().onDidChangeTextDocument(this.debouncer.run),
ide().onDidChangeTextEditorVisibleRanges(this.debouncer.run),
languageDefinitions.onDidChangeDefinition(this.debouncer.run),
this.debouncer,
);
this.onDidChangeScopeRanges = this.onDidChangeScopeRanges.bind(this);
this.onDidChangeIterationScopeRanges =
this.onDidChangeIterationScopeRanges.bind(this);
}
/**
* Registers a callback to be run when the scope ranges change for any visible
* editor. The callback will be run immediately once for each visible editor
* with the current scope ranges.
* @param callback The callback to run when the scope ranges change
* @param config The configuration for the scope ranges
* @returns A {@link Disposable} which will stop the callback from running
*/
onDidChangeScopeRanges(
callback: ScopeChangeEventCallback,
config: ScopeRangeConfig,
): Disposable {
const fn = () => {
ide().visibleTextEditors.forEach((editor) => {
let scopeRanges: ScopeRanges[];
try {
scopeRanges = this.scopeRangeProvider.provideScopeRanges(
editor,
config,
);
} catch (err) {
showError(
ide().messages,
"ScopeRangeWatcher.provide",
(err as Error).message,
);
// If there was a problem getting scopes for an editor, we show an
// error and clear any scopes we might have shown last time. This is
// especially important during development, but also seems like the
// robust thing to do generally.
scopeRanges = [];
if (ide().runMode === "test") {
// Fail hard if we're in test mode; otherwise recover
throw err;
}
}
callback(editor, scopeRanges);
});
};
this.listeners.push(fn);
fn();
return {
dispose: () => {
pull(this.listeners, fn);
},
};
}
/**
* Registers a callback to be run when the iteration scope ranges change for
* any visible editor. The callback will be run immediately once for each
* visible editor with the current iteration scope ranges.
* @param callback The callback to run when the scope ranges change
* @param config The configuration for the scope ranges
* @returns A {@link Disposable} which will stop the callback from running
*/
onDidChangeIterationScopeRanges(
callback: IterationScopeChangeEventCallback,
config: IterationScopeRangeConfig,
): Disposable {
const fn = () => {
ide().visibleTextEditors.forEach((editor) => {
callback(
editor,
this.scopeRangeProvider.provideIterationScopeRanges(editor, config),
);
});
};
this.listeners.push(fn);
fn();
return {
dispose: () => {
pull(this.listeners, fn);
},
};
}
private onChange() {
this.listeners.forEach((listener) => listener());
}
dispose(): void {
this.disposables.forEach(({ dispose }) => {
try {
dispose();
} catch (e) {
// do nothing; some of the VSCode disposables misbehave, and we don't
// want that to prevent us from disposing the rest of the disposables
}
});
}
}