@theia/editor 1.45.0 → 1.46.0-next.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -30
- package/lib/browser/decorations/editor-decoration-style.d.ts +8 -8
- package/lib/browser/decorations/editor-decoration-style.js +36 -36
- package/lib/browser/decorations/editor-decoration.d.ts +106 -106
- package/lib/browser/decorations/editor-decoration.js +37 -37
- package/lib/browser/decorations/editor-decorator.d.ts +6 -6
- package/lib/browser/decorations/editor-decorator.js +43 -43
- package/lib/browser/decorations/index.d.ts +3 -3
- package/lib/browser/decorations/index.js +30 -30
- package/lib/browser/diff-navigator.d.ts +9 -10
- package/lib/browser/diff-navigator.d.ts.map +1 -1
- package/lib/browser/diff-navigator.js +19 -19
- package/lib/browser/diff-navigator.js.map +1 -1
- package/lib/browser/editor-command.d.ts +102 -102
- package/lib/browser/editor-command.js +426 -426
- package/lib/browser/editor-contribution.d.ts +26 -26
- package/lib/browser/editor-contribution.js +198 -198
- package/lib/browser/editor-frontend-module.d.ts +5 -5
- package/lib/browser/editor-frontend-module.js +74 -74
- package/lib/browser/editor-generated-preference-schema.d.ts +282 -249
- package/lib/browser/editor-generated-preference-schema.d.ts.map +1 -1
- package/lib/browser/editor-generated-preference-schema.js +2451 -2316
- package/lib/browser/editor-generated-preference-schema.js.map +1 -1
- package/lib/browser/editor-keybinding.d.ts +5 -5
- package/lib/browser/editor-keybinding.js +55 -55
- package/lib/browser/editor-linenumber-contribution.d.ts +15 -15
- package/lib/browser/editor-linenumber-contribution.d.ts.map +1 -1
- package/lib/browser/editor-linenumber-contribution.js +95 -96
- package/lib/browser/editor-linenumber-contribution.js.map +1 -1
- package/lib/browser/editor-manager.d.ts +115 -115
- package/lib/browser/editor-manager.js +428 -428
- package/lib/browser/editor-menu.d.ts +48 -48
- package/lib/browser/editor-menu.js +210 -210
- package/lib/browser/editor-navigation-contribution.d.ts +67 -67
- package/lib/browser/editor-navigation-contribution.js +343 -343
- package/lib/browser/editor-preferences.d.ts +41 -41
- package/lib/browser/editor-preferences.js +176 -176
- package/lib/browser/editor-variable-contribution.d.ts +8 -8
- package/lib/browser/editor-variable-contribution.js +64 -64
- package/lib/browser/editor-widget-factory.d.ts +17 -17
- package/lib/browser/editor-widget-factory.js +91 -91
- package/lib/browser/editor-widget.d.ts +24 -24
- package/lib/browser/editor-widget.d.ts.map +1 -1
- package/lib/browser/editor-widget.js +122 -114
- package/lib/browser/editor-widget.js.map +1 -1
- package/lib/browser/editor.d.ts +295 -293
- package/lib/browser/editor.d.ts.map +1 -1
- package/lib/browser/editor.js +103 -103
- package/lib/browser/editor.js.map +1 -1
- package/lib/browser/index.d.ts +10 -10
- package/lib/browser/index.js +37 -37
- package/lib/browser/language-status/editor-language-status-service.d.ts +77 -77
- package/lib/browser/language-status/editor-language-status-service.js +251 -251
- package/lib/browser/navigation/navigation-location-service.d.ts +103 -103
- package/lib/browser/navigation/navigation-location-service.js +281 -281
- package/lib/browser/navigation/navigation-location-service.spec.d.ts +1 -1
- package/lib/browser/navigation/navigation-location-service.spec.js +184 -184
- package/lib/browser/navigation/navigation-location-similarity.d.ts +15 -15
- package/lib/browser/navigation/navigation-location-similarity.js +62 -62
- package/lib/browser/navigation/navigation-location-similarity.spec.d.ts +1 -1
- package/lib/browser/navigation/navigation-location-similarity.spec.js +32 -32
- package/lib/browser/navigation/navigation-location-updater.d.ts +35 -35
- package/lib/browser/navigation/navigation-location-updater.js +210 -210
- package/lib/browser/navigation/navigation-location-updater.spec.d.ts +1 -1
- package/lib/browser/navigation/navigation-location-updater.spec.js +177 -177
- package/lib/browser/navigation/navigation-location.d.ts +191 -191
- package/lib/browser/navigation/navigation-location.js +300 -300
- package/lib/browser/navigation/test/mock-navigation-location-updater.d.ts +15 -15
- package/lib/browser/navigation/test/mock-navigation-location-updater.js +38 -38
- package/lib/browser/quick-editor-service.d.ts +16 -16
- package/lib/browser/quick-editor-service.js +109 -109
- package/lib/browser/undo-redo-service.d.ts +23 -23
- package/lib/browser/undo-redo-service.js +110 -110
- package/lib/common/language-selector.d.ts +13 -13
- package/lib/common/language-selector.js +90 -90
- package/lib/package.spec.js +25 -25
- package/package.json +5 -5
- package/src/browser/decorations/editor-decoration-style.ts +41 -41
- package/src/browser/decorations/editor-decoration.ts +127 -127
- package/src/browser/decorations/editor-decorator.ts +36 -36
- package/src/browser/decorations/index.ts +19 -19
- package/src/browser/diff-navigator.ts +27 -28
- package/src/browser/editor-command.ts +414 -414
- package/src/browser/editor-contribution.ts +185 -185
- package/src/browser/editor-frontend-module.ts +87 -87
- package/src/browser/editor-generated-preference-schema.ts +2707 -2539
- package/src/browser/editor-keybinding.ts +55 -55
- package/src/browser/editor-linenumber-contribution.ts +88 -89
- package/src/browser/editor-manager.ts +442 -442
- package/src/browser/editor-menu.ts +224 -224
- package/src/browser/editor-navigation-contribution.ts +343 -343
- package/src/browser/editor-preferences.ts +226 -226
- package/src/browser/editor-variable-contribution.ts +54 -54
- package/src/browser/editor-widget-factory.ts +82 -82
- package/src/browser/editor-widget.ts +137 -130
- package/src/browser/editor.ts +360 -358
- package/src/browser/index.ts +26 -26
- package/src/browser/language-status/editor-language-status-service.ts +271 -271
- package/src/browser/language-status/editor-language-status.css +101 -101
- package/src/browser/navigation/navigation-location-service.spec.ts +245 -245
- package/src/browser/navigation/navigation-location-service.ts +284 -284
- package/src/browser/navigation/navigation-location-similarity.spec.ts +46 -46
- package/src/browser/navigation/navigation-location-similarity.ts +58 -58
- package/src/browser/navigation/navigation-location-updater.spec.ts +197 -197
- package/src/browser/navigation/navigation-location-updater.ts +220 -220
- package/src/browser/navigation/navigation-location.ts +418 -418
- package/src/browser/navigation/test/mock-navigation-location-updater.ts +41 -41
- package/src/browser/quick-editor-service.ts +94 -94
- package/src/browser/style/index.css +19 -19
- package/src/browser/undo-redo-service.ts +120 -120
- package/src/common/language-selector.ts +104 -104
- package/src/package.spec.ts +28 -28
|
@@ -1,284 +1,284 @@
|
|
|
1
|
-
// *****************************************************************************
|
|
2
|
-
// Copyright (C) 2018 TypeFox and others.
|
|
3
|
-
//
|
|
4
|
-
// This program and the accompanying materials are made available under the
|
|
5
|
-
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
-
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
-
//
|
|
8
|
-
// This Source Code may also be made available under the following Secondary
|
|
9
|
-
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
-
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
-
// with the GNU Classpath Exception which is available at
|
|
12
|
-
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
-
//
|
|
14
|
-
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
-
// *****************************************************************************
|
|
16
|
-
|
|
17
|
-
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
18
|
-
import { ILogger } from '@theia/core/lib/common/logger';
|
|
19
|
-
import { OpenerService, OpenerOptions, open } from '@theia/core/lib/browser/opener-service';
|
|
20
|
-
import { EditorOpenerOptions } from '../editor-manager';
|
|
21
|
-
import { NavigationLocationUpdater } from './navigation-location-updater';
|
|
22
|
-
import { NavigationLocationSimilarity } from './navigation-location-similarity';
|
|
23
|
-
import { NavigationLocation, Range, ContentChangeLocation, RecentlyClosedEditor } from './navigation-location';
|
|
24
|
-
import URI from '@theia/core/lib/common/uri';
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* The navigation location service.
|
|
28
|
-
* It also stores and manages navigation locations and recently closed editors.
|
|
29
|
-
*/
|
|
30
|
-
@injectable()
|
|
31
|
-
export class NavigationLocationService {
|
|
32
|
-
|
|
33
|
-
private static MAX_STACK_ITEMS = 30;
|
|
34
|
-
private static readonly MAX_RECENTLY_CLOSED_EDITORS = 20;
|
|
35
|
-
|
|
36
|
-
@inject(ILogger)
|
|
37
|
-
protected readonly logger: ILogger;
|
|
38
|
-
|
|
39
|
-
@inject(OpenerService)
|
|
40
|
-
protected readonly openerService: OpenerService;
|
|
41
|
-
|
|
42
|
-
@inject(NavigationLocationUpdater)
|
|
43
|
-
protected readonly updater: NavigationLocationUpdater;
|
|
44
|
-
|
|
45
|
-
@inject(NavigationLocationSimilarity)
|
|
46
|
-
protected readonly similarity: NavigationLocationSimilarity;
|
|
47
|
-
|
|
48
|
-
protected pointer = -1;
|
|
49
|
-
protected stack: NavigationLocation[] = [];
|
|
50
|
-
protected canRegister = true;
|
|
51
|
-
protected _lastEditLocation: ContentChangeLocation | undefined;
|
|
52
|
-
|
|
53
|
-
protected recentlyClosedEditors: RecentlyClosedEditor[] = [];
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Registers the give locations into the service.
|
|
57
|
-
*/
|
|
58
|
-
register(...locations: NavigationLocation[]): void {
|
|
59
|
-
if (this.canRegister) {
|
|
60
|
-
const max = this.maxStackItems();
|
|
61
|
-
[...locations].forEach(location => {
|
|
62
|
-
if (ContentChangeLocation.is(location)) {
|
|
63
|
-
this._lastEditLocation = location;
|
|
64
|
-
}
|
|
65
|
-
const current = this.currentLocation();
|
|
66
|
-
this.debug(`Registering new location: ${NavigationLocation.toString(location)}.`);
|
|
67
|
-
if (!this.isSimilar(current, location)) {
|
|
68
|
-
this.debug('Before location registration.');
|
|
69
|
-
this.debug(this.stackDump);
|
|
70
|
-
// Just like in VSCode; if we are not at the end of stack, we remove anything after.
|
|
71
|
-
if (this.stack.length > this.pointer + 1) {
|
|
72
|
-
this.debug(`Discarding all locations after ${this.pointer}.`);
|
|
73
|
-
this.stack = this.stack.slice(0, this.pointer + 1);
|
|
74
|
-
}
|
|
75
|
-
this.stack.push(location);
|
|
76
|
-
this.pointer = this.stack.length - 1;
|
|
77
|
-
if (this.stack.length > max) {
|
|
78
|
-
this.debug('Trimming exceeding locations.');
|
|
79
|
-
this.stack.shift();
|
|
80
|
-
this.pointer--;
|
|
81
|
-
}
|
|
82
|
-
this.debug('Updating preceding navigation locations.');
|
|
83
|
-
for (let i = this.stack.length - 1; i >= 0; i--) {
|
|
84
|
-
const candidate = this.stack[i];
|
|
85
|
-
const update = this.updater.affects(candidate, location);
|
|
86
|
-
if (update === undefined) {
|
|
87
|
-
this.debug(`Erasing obsolete location: ${NavigationLocation.toString(candidate)}.`);
|
|
88
|
-
this.stack.splice(i, 1);
|
|
89
|
-
this.pointer--;
|
|
90
|
-
} else if (typeof update !== 'boolean') {
|
|
91
|
-
this.debug(`Updating location at index: ${i} => ${NavigationLocation.toString(candidate)}.`);
|
|
92
|
-
this.stack[i] = update;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
this.debug('After location registration.');
|
|
96
|
-
this.debug(this.stackDump);
|
|
97
|
-
} else {
|
|
98
|
-
if (current) {
|
|
99
|
-
this.debug(`The new location ${NavigationLocation.toString(location)} is similar to the current one: ${NavigationLocation.toString(current)}. Aborting.`);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Navigates one back. Returns with the previous location, or `undefined` if it could not navigate back.
|
|
108
|
-
*/
|
|
109
|
-
async back(): Promise<NavigationLocation | undefined> {
|
|
110
|
-
this.debug('Navigating back.');
|
|
111
|
-
if (this.canGoBack()) {
|
|
112
|
-
this.pointer--;
|
|
113
|
-
await this.reveal();
|
|
114
|
-
this.debug(this.stackDump);
|
|
115
|
-
return this.currentLocation();
|
|
116
|
-
}
|
|
117
|
-
this.debug('Cannot navigate back.');
|
|
118
|
-
return undefined;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Navigates one forward. Returns with the next location, or `undefined` if it could not go forward.
|
|
123
|
-
*/
|
|
124
|
-
async forward(): Promise<NavigationLocation | undefined> {
|
|
125
|
-
this.debug('Navigating forward.');
|
|
126
|
-
if (this.canGoForward()) {
|
|
127
|
-
this.pointer++;
|
|
128
|
-
await this.reveal();
|
|
129
|
-
this.debug(this.stackDump);
|
|
130
|
-
return this.currentLocation();
|
|
131
|
-
}
|
|
132
|
-
this.debug('Cannot navigate forward.');
|
|
133
|
-
return undefined;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Checks whether the service can go [`back`](#back).
|
|
138
|
-
*/
|
|
139
|
-
canGoBack(): boolean {
|
|
140
|
-
return this.pointer >= 1;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Checks whether the service can go [`forward`](#forward).
|
|
145
|
-
*/
|
|
146
|
-
canGoForward(): boolean {
|
|
147
|
-
return this.pointer >= 0 && this.pointer !== this.stack.length - 1;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Returns with all known navigation locations in chronological order.
|
|
152
|
-
*/
|
|
153
|
-
locations(): ReadonlyArray<NavigationLocation> {
|
|
154
|
-
return this.stack;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Returns with the current location.
|
|
159
|
-
*/
|
|
160
|
-
currentLocation(): NavigationLocation | undefined {
|
|
161
|
-
return this.stack[this.pointer];
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Returns with the location of the most recent edition if any. If there were no modifications,
|
|
166
|
-
* returns `undefined`.
|
|
167
|
-
*/
|
|
168
|
-
lastEditLocation(): NavigationLocation | undefined {
|
|
169
|
-
return this._lastEditLocation;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Clears the total history.
|
|
174
|
-
*/
|
|
175
|
-
clearHistory(): void {
|
|
176
|
-
this.stack = [];
|
|
177
|
-
this.pointer = -1;
|
|
178
|
-
this._lastEditLocation = undefined;
|
|
179
|
-
this.recentlyClosedEditors = [];
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Reveals the location argument. If not given, reveals the `current location`. Does nothing, if the argument is `undefined`.
|
|
184
|
-
*/
|
|
185
|
-
async reveal(location: NavigationLocation | undefined = this.currentLocation()): Promise<void> {
|
|
186
|
-
if (location === undefined) {
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
try {
|
|
190
|
-
this.canRegister = false;
|
|
191
|
-
const { uri } = location;
|
|
192
|
-
const options = this.toOpenerOptions(location);
|
|
193
|
-
await open(this.openerService, uri, options);
|
|
194
|
-
} catch (e) {
|
|
195
|
-
this.logger.error(`Error occurred while revealing location: ${NavigationLocation.toString(location)}.`, e);
|
|
196
|
-
} finally {
|
|
197
|
-
this.canRegister = true;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* `true` if the two locations are similar.
|
|
203
|
-
*/
|
|
204
|
-
protected isSimilar(left: NavigationLocation | undefined, right: NavigationLocation | undefined): boolean {
|
|
205
|
-
return this.similarity.similar(left, right);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Returns with the number of navigation locations that the application can handle and manage.
|
|
210
|
-
* When the number of locations exceeds this number, old locations will be erased.
|
|
211
|
-
*/
|
|
212
|
-
protected maxStackItems(): number {
|
|
213
|
-
return NavigationLocationService.MAX_STACK_ITEMS;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Returns with the opener option for the location argument.
|
|
218
|
-
*/
|
|
219
|
-
protected toOpenerOptions(location: NavigationLocation): OpenerOptions {
|
|
220
|
-
let { start } = NavigationLocation.range(location);
|
|
221
|
-
// Here, the `start` and represents the previous state that has been updated with the `text`.
|
|
222
|
-
// So we calculate the range by appending the `text` length to the `start`.
|
|
223
|
-
if (ContentChangeLocation.is(location)) {
|
|
224
|
-
start = { ...start, character: start.character + location.context.text.length };
|
|
225
|
-
}
|
|
226
|
-
return {
|
|
227
|
-
selection: Range.create(start, start)
|
|
228
|
-
} as EditorOpenerOptions;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
private async debug(message: string | (() => string)): Promise<void> {
|
|
232
|
-
this.logger.trace(typeof message === 'string' ? message : message());
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
private get stackDump(): string {
|
|
236
|
-
return `----- Navigation location stack [${new Date()}] -----
|
|
237
|
-
Pointer: ${this.pointer}
|
|
238
|
-
${this.stack.map((location, i) => `${i}: ${JSON.stringify(NavigationLocation.toObject(location))}`).join('\n')}
|
|
239
|
-
----- o -----`;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Get the recently closed editors stack in chronological order.
|
|
244
|
-
*
|
|
245
|
-
* @returns readonly closed editors stack.
|
|
246
|
-
*/
|
|
247
|
-
get closedEditorsStack(): ReadonlyArray<RecentlyClosedEditor> {
|
|
248
|
-
return this.recentlyClosedEditors;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Get the last recently closed editor.
|
|
253
|
-
*
|
|
254
|
-
* @returns the recently closed editor if it exists.
|
|
255
|
-
*/
|
|
256
|
-
getLastClosedEditor(): RecentlyClosedEditor | undefined {
|
|
257
|
-
return this.recentlyClosedEditors[this.recentlyClosedEditors.length - 1];
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Add the recently closed editor to the history.
|
|
262
|
-
*
|
|
263
|
-
* @param editor the recently closed editor.
|
|
264
|
-
*/
|
|
265
|
-
addClosedEditor(editor: RecentlyClosedEditor): void {
|
|
266
|
-
this.removeClosedEditor(editor.uri);
|
|
267
|
-
this.recentlyClosedEditors.push(editor);
|
|
268
|
-
|
|
269
|
-
// Removes the oldest entry from the history if the maximum size is reached.
|
|
270
|
-
if (this.recentlyClosedEditors.length > NavigationLocationService.MAX_RECENTLY_CLOSED_EDITORS) {
|
|
271
|
-
this.recentlyClosedEditors.shift();
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Remove all occurrences of the given editor in the history if they exist.
|
|
277
|
-
*
|
|
278
|
-
* @param uri the uri of the editor that should be removed from the history.
|
|
279
|
-
*/
|
|
280
|
-
removeClosedEditor(uri: URI): void {
|
|
281
|
-
this.recentlyClosedEditors = this.recentlyClosedEditors.filter(e => !uri.isEqual(e.uri));
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
}
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2018 TypeFox and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
18
|
+
import { ILogger } from '@theia/core/lib/common/logger';
|
|
19
|
+
import { OpenerService, OpenerOptions, open } from '@theia/core/lib/browser/opener-service';
|
|
20
|
+
import { EditorOpenerOptions } from '../editor-manager';
|
|
21
|
+
import { NavigationLocationUpdater } from './navigation-location-updater';
|
|
22
|
+
import { NavigationLocationSimilarity } from './navigation-location-similarity';
|
|
23
|
+
import { NavigationLocation, Range, ContentChangeLocation, RecentlyClosedEditor } from './navigation-location';
|
|
24
|
+
import URI from '@theia/core/lib/common/uri';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The navigation location service.
|
|
28
|
+
* It also stores and manages navigation locations and recently closed editors.
|
|
29
|
+
*/
|
|
30
|
+
@injectable()
|
|
31
|
+
export class NavigationLocationService {
|
|
32
|
+
|
|
33
|
+
private static MAX_STACK_ITEMS = 30;
|
|
34
|
+
private static readonly MAX_RECENTLY_CLOSED_EDITORS = 20;
|
|
35
|
+
|
|
36
|
+
@inject(ILogger)
|
|
37
|
+
protected readonly logger: ILogger;
|
|
38
|
+
|
|
39
|
+
@inject(OpenerService)
|
|
40
|
+
protected readonly openerService: OpenerService;
|
|
41
|
+
|
|
42
|
+
@inject(NavigationLocationUpdater)
|
|
43
|
+
protected readonly updater: NavigationLocationUpdater;
|
|
44
|
+
|
|
45
|
+
@inject(NavigationLocationSimilarity)
|
|
46
|
+
protected readonly similarity: NavigationLocationSimilarity;
|
|
47
|
+
|
|
48
|
+
protected pointer = -1;
|
|
49
|
+
protected stack: NavigationLocation[] = [];
|
|
50
|
+
protected canRegister = true;
|
|
51
|
+
protected _lastEditLocation: ContentChangeLocation | undefined;
|
|
52
|
+
|
|
53
|
+
protected recentlyClosedEditors: RecentlyClosedEditor[] = [];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Registers the give locations into the service.
|
|
57
|
+
*/
|
|
58
|
+
register(...locations: NavigationLocation[]): void {
|
|
59
|
+
if (this.canRegister) {
|
|
60
|
+
const max = this.maxStackItems();
|
|
61
|
+
[...locations].forEach(location => {
|
|
62
|
+
if (ContentChangeLocation.is(location)) {
|
|
63
|
+
this._lastEditLocation = location;
|
|
64
|
+
}
|
|
65
|
+
const current = this.currentLocation();
|
|
66
|
+
this.debug(`Registering new location: ${NavigationLocation.toString(location)}.`);
|
|
67
|
+
if (!this.isSimilar(current, location)) {
|
|
68
|
+
this.debug('Before location registration.');
|
|
69
|
+
this.debug(this.stackDump);
|
|
70
|
+
// Just like in VSCode; if we are not at the end of stack, we remove anything after.
|
|
71
|
+
if (this.stack.length > this.pointer + 1) {
|
|
72
|
+
this.debug(`Discarding all locations after ${this.pointer}.`);
|
|
73
|
+
this.stack = this.stack.slice(0, this.pointer + 1);
|
|
74
|
+
}
|
|
75
|
+
this.stack.push(location);
|
|
76
|
+
this.pointer = this.stack.length - 1;
|
|
77
|
+
if (this.stack.length > max) {
|
|
78
|
+
this.debug('Trimming exceeding locations.');
|
|
79
|
+
this.stack.shift();
|
|
80
|
+
this.pointer--;
|
|
81
|
+
}
|
|
82
|
+
this.debug('Updating preceding navigation locations.');
|
|
83
|
+
for (let i = this.stack.length - 1; i >= 0; i--) {
|
|
84
|
+
const candidate = this.stack[i];
|
|
85
|
+
const update = this.updater.affects(candidate, location);
|
|
86
|
+
if (update === undefined) {
|
|
87
|
+
this.debug(`Erasing obsolete location: ${NavigationLocation.toString(candidate)}.`);
|
|
88
|
+
this.stack.splice(i, 1);
|
|
89
|
+
this.pointer--;
|
|
90
|
+
} else if (typeof update !== 'boolean') {
|
|
91
|
+
this.debug(`Updating location at index: ${i} => ${NavigationLocation.toString(candidate)}.`);
|
|
92
|
+
this.stack[i] = update;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
this.debug('After location registration.');
|
|
96
|
+
this.debug(this.stackDump);
|
|
97
|
+
} else {
|
|
98
|
+
if (current) {
|
|
99
|
+
this.debug(`The new location ${NavigationLocation.toString(location)} is similar to the current one: ${NavigationLocation.toString(current)}. Aborting.`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Navigates one back. Returns with the previous location, or `undefined` if it could not navigate back.
|
|
108
|
+
*/
|
|
109
|
+
async back(): Promise<NavigationLocation | undefined> {
|
|
110
|
+
this.debug('Navigating back.');
|
|
111
|
+
if (this.canGoBack()) {
|
|
112
|
+
this.pointer--;
|
|
113
|
+
await this.reveal();
|
|
114
|
+
this.debug(this.stackDump);
|
|
115
|
+
return this.currentLocation();
|
|
116
|
+
}
|
|
117
|
+
this.debug('Cannot navigate back.');
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Navigates one forward. Returns with the next location, or `undefined` if it could not go forward.
|
|
123
|
+
*/
|
|
124
|
+
async forward(): Promise<NavigationLocation | undefined> {
|
|
125
|
+
this.debug('Navigating forward.');
|
|
126
|
+
if (this.canGoForward()) {
|
|
127
|
+
this.pointer++;
|
|
128
|
+
await this.reveal();
|
|
129
|
+
this.debug(this.stackDump);
|
|
130
|
+
return this.currentLocation();
|
|
131
|
+
}
|
|
132
|
+
this.debug('Cannot navigate forward.');
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Checks whether the service can go [`back`](#back).
|
|
138
|
+
*/
|
|
139
|
+
canGoBack(): boolean {
|
|
140
|
+
return this.pointer >= 1;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Checks whether the service can go [`forward`](#forward).
|
|
145
|
+
*/
|
|
146
|
+
canGoForward(): boolean {
|
|
147
|
+
return this.pointer >= 0 && this.pointer !== this.stack.length - 1;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Returns with all known navigation locations in chronological order.
|
|
152
|
+
*/
|
|
153
|
+
locations(): ReadonlyArray<NavigationLocation> {
|
|
154
|
+
return this.stack;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Returns with the current location.
|
|
159
|
+
*/
|
|
160
|
+
currentLocation(): NavigationLocation | undefined {
|
|
161
|
+
return this.stack[this.pointer];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Returns with the location of the most recent edition if any. If there were no modifications,
|
|
166
|
+
* returns `undefined`.
|
|
167
|
+
*/
|
|
168
|
+
lastEditLocation(): NavigationLocation | undefined {
|
|
169
|
+
return this._lastEditLocation;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Clears the total history.
|
|
174
|
+
*/
|
|
175
|
+
clearHistory(): void {
|
|
176
|
+
this.stack = [];
|
|
177
|
+
this.pointer = -1;
|
|
178
|
+
this._lastEditLocation = undefined;
|
|
179
|
+
this.recentlyClosedEditors = [];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Reveals the location argument. If not given, reveals the `current location`. Does nothing, if the argument is `undefined`.
|
|
184
|
+
*/
|
|
185
|
+
async reveal(location: NavigationLocation | undefined = this.currentLocation()): Promise<void> {
|
|
186
|
+
if (location === undefined) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
this.canRegister = false;
|
|
191
|
+
const { uri } = location;
|
|
192
|
+
const options = this.toOpenerOptions(location);
|
|
193
|
+
await open(this.openerService, uri, options);
|
|
194
|
+
} catch (e) {
|
|
195
|
+
this.logger.error(`Error occurred while revealing location: ${NavigationLocation.toString(location)}.`, e);
|
|
196
|
+
} finally {
|
|
197
|
+
this.canRegister = true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* `true` if the two locations are similar.
|
|
203
|
+
*/
|
|
204
|
+
protected isSimilar(left: NavigationLocation | undefined, right: NavigationLocation | undefined): boolean {
|
|
205
|
+
return this.similarity.similar(left, right);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Returns with the number of navigation locations that the application can handle and manage.
|
|
210
|
+
* When the number of locations exceeds this number, old locations will be erased.
|
|
211
|
+
*/
|
|
212
|
+
protected maxStackItems(): number {
|
|
213
|
+
return NavigationLocationService.MAX_STACK_ITEMS;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Returns with the opener option for the location argument.
|
|
218
|
+
*/
|
|
219
|
+
protected toOpenerOptions(location: NavigationLocation): OpenerOptions {
|
|
220
|
+
let { start } = NavigationLocation.range(location);
|
|
221
|
+
// Here, the `start` and represents the previous state that has been updated with the `text`.
|
|
222
|
+
// So we calculate the range by appending the `text` length to the `start`.
|
|
223
|
+
if (ContentChangeLocation.is(location)) {
|
|
224
|
+
start = { ...start, character: start.character + location.context.text.length };
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
selection: Range.create(start, start)
|
|
228
|
+
} as EditorOpenerOptions;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private async debug(message: string | (() => string)): Promise<void> {
|
|
232
|
+
this.logger.trace(typeof message === 'string' ? message : message());
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private get stackDump(): string {
|
|
236
|
+
return `----- Navigation location stack [${new Date()}] -----
|
|
237
|
+
Pointer: ${this.pointer}
|
|
238
|
+
${this.stack.map((location, i) => `${i}: ${JSON.stringify(NavigationLocation.toObject(location))}`).join('\n')}
|
|
239
|
+
----- o -----`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Get the recently closed editors stack in chronological order.
|
|
244
|
+
*
|
|
245
|
+
* @returns readonly closed editors stack.
|
|
246
|
+
*/
|
|
247
|
+
get closedEditorsStack(): ReadonlyArray<RecentlyClosedEditor> {
|
|
248
|
+
return this.recentlyClosedEditors;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Get the last recently closed editor.
|
|
253
|
+
*
|
|
254
|
+
* @returns the recently closed editor if it exists.
|
|
255
|
+
*/
|
|
256
|
+
getLastClosedEditor(): RecentlyClosedEditor | undefined {
|
|
257
|
+
return this.recentlyClosedEditors[this.recentlyClosedEditors.length - 1];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Add the recently closed editor to the history.
|
|
262
|
+
*
|
|
263
|
+
* @param editor the recently closed editor.
|
|
264
|
+
*/
|
|
265
|
+
addClosedEditor(editor: RecentlyClosedEditor): void {
|
|
266
|
+
this.removeClosedEditor(editor.uri);
|
|
267
|
+
this.recentlyClosedEditors.push(editor);
|
|
268
|
+
|
|
269
|
+
// Removes the oldest entry from the history if the maximum size is reached.
|
|
270
|
+
if (this.recentlyClosedEditors.length > NavigationLocationService.MAX_RECENTLY_CLOSED_EDITORS) {
|
|
271
|
+
this.recentlyClosedEditors.shift();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Remove all occurrences of the given editor in the history if they exist.
|
|
277
|
+
*
|
|
278
|
+
* @param uri the uri of the editor that should be removed from the history.
|
|
279
|
+
*/
|
|
280
|
+
removeClosedEditor(uri: URI): void {
|
|
281
|
+
this.recentlyClosedEditors = this.recentlyClosedEditors.filter(e => !uri.isEqual(e.uri));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
}
|
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
// *****************************************************************************
|
|
2
|
-
// Copyright (C) 2018 TypeFox and others.
|
|
3
|
-
//
|
|
4
|
-
// This program and the accompanying materials are made available under the
|
|
5
|
-
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
-
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
-
//
|
|
8
|
-
// This Source Code may also be made available under the following Secondary
|
|
9
|
-
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
-
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
-
// with the GNU Classpath Exception which is available at
|
|
12
|
-
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
-
//
|
|
14
|
-
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
-
// *****************************************************************************
|
|
16
|
-
|
|
17
|
-
import { expect } from 'chai';
|
|
18
|
-
import { NavigationLocation } from './navigation-location';
|
|
19
|
-
import { NavigationLocationSimilarity } from './navigation-location-similarity';
|
|
20
|
-
|
|
21
|
-
describe('navigation-location-similarity', () => {
|
|
22
|
-
|
|
23
|
-
const similarity = new NavigationLocationSimilarity();
|
|
24
|
-
|
|
25
|
-
it('should never be similar if they belong to different resources', () => {
|
|
26
|
-
expect(similarity.similar(
|
|
27
|
-
NavigationLocation.create('file:///a', { line: 0, character: 0, }),
|
|
28
|
-
NavigationLocation.create('file:///b', { line: 0, character: 0, })
|
|
29
|
-
)).to.be.false;
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('should be true if the locations are withing the threshold', () => {
|
|
33
|
-
expect(similarity.similar(
|
|
34
|
-
NavigationLocation.create('file:///a', { start: { line: 0, character: 10 }, end: { line: 0, character: 15 } }),
|
|
35
|
-
NavigationLocation.create('file:///a', { start: { line: 10, character: 3 }, end: { line: 0, character: 5 } })
|
|
36
|
-
)).to.be.true;
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('should be false if the locations are outside of the threshold', () => {
|
|
40
|
-
expect(similarity.similar(
|
|
41
|
-
NavigationLocation.create('file:///a', { start: { line: 0, character: 10 }, end: { line: 0, character: 15 } }),
|
|
42
|
-
NavigationLocation.create('file:///a', { start: { line: 11, character: 3 }, end: { line: 0, character: 5 } })
|
|
43
|
-
)).to.be.true;
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
});
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2018 TypeFox and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { expect } from 'chai';
|
|
18
|
+
import { NavigationLocation } from './navigation-location';
|
|
19
|
+
import { NavigationLocationSimilarity } from './navigation-location-similarity';
|
|
20
|
+
|
|
21
|
+
describe('navigation-location-similarity', () => {
|
|
22
|
+
|
|
23
|
+
const similarity = new NavigationLocationSimilarity();
|
|
24
|
+
|
|
25
|
+
it('should never be similar if they belong to different resources', () => {
|
|
26
|
+
expect(similarity.similar(
|
|
27
|
+
NavigationLocation.create('file:///a', { line: 0, character: 0, }),
|
|
28
|
+
NavigationLocation.create('file:///b', { line: 0, character: 0, })
|
|
29
|
+
)).to.be.false;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should be true if the locations are withing the threshold', () => {
|
|
33
|
+
expect(similarity.similar(
|
|
34
|
+
NavigationLocation.create('file:///a', { start: { line: 0, character: 10 }, end: { line: 0, character: 15 } }),
|
|
35
|
+
NavigationLocation.create('file:///a', { start: { line: 10, character: 3 }, end: { line: 0, character: 5 } })
|
|
36
|
+
)).to.be.true;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should be false if the locations are outside of the threshold', () => {
|
|
40
|
+
expect(similarity.similar(
|
|
41
|
+
NavigationLocation.create('file:///a', { start: { line: 0, character: 10 }, end: { line: 0, character: 15 } }),
|
|
42
|
+
NavigationLocation.create('file:///a', { start: { line: 11, character: 3 }, end: { line: 0, character: 5 } })
|
|
43
|
+
)).to.be.true;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
});
|