-
Notifications
You must be signed in to change notification settings - Fork 29
/
element-navigation.ts
355 lines (311 loc) · 13.3 KB
/
element-navigation.ts
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
/********************************************************************************
* Copyright (c) 2020-2023 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { JsonPrimitive } from 'sprotty-protocol';
import { AnyObject, hasArrayProp, hasObjectProp, hasStringProp } from '../utils/type-util';
import { Action, RequestAction, ResponseAction } from './base-protocol';
import { Args, EditorContext } from './types';
/**
* A `NavigationTarget` identifies the object we want to navigate to via its uri and may further provide a label to display to the user.
* The corresponding namespace offers a set of utility function to interact with `NavigationTargets`.
*/
export interface NavigationTarget {
/**
* URI to identify the object we want to navigate to.
*/
uri: string;
/**
* Optional label to display to the user.
*/
label?: string;
/**
* Domain-specific arguments that may be interpreted directly or resolved further.
*/
args?: Args;
}
export namespace NavigationTarget {
export function is(object: unknown): object is NavigationTarget {
return AnyObject.is(object) && hasStringProp(object, 'uri');
}
/**
* Generic key to store element ids as additional argument
*/
export const ELEMENT_IDS = 'elementIds';
/**
* The separator that is used to store he values for the {@link ELEMENT_IDS} as a single string.
*/
export const ELEMENT_IDS_SEPARATOR = '&';
/**
* Generic key ot store the line property of a {@link TextPosition} as additional argument.
*/
export const TEXT_LINE = 'line';
/**
* Generic key ot store the character property of a {@link TextPosition} as additional argument.
*/
export const TEXT_COLUMN = 'column';
/**
* Denotes the position of the cursor in a text element.
*/
export interface TextPosition {
line: number;
/**
* The character number within the line. Also refereed to as column.
*/
character: number;
}
/**
* Utility function to check wether the given {@link NavigationTarget} has additional arguments defined.
* @param target The navigation target to check.
* @returns `true` if the navigation target has a non-empty `args` property, `false`
*/
export function hasArguments(target: NavigationTarget): boolean {
return target.args !== undefined && Object.keys(target.args).length > 0;
}
/**
* Adds a new key-value pair to the additional arguments of the given {@link NavigationTarget}.
* @param target The navigation target.
* @param key The key of the new argument.
* @param value The (primitive) value of the new argument.
*/
export function addArgument(target: NavigationTarget, key: string, value: JsonPrimitive): void {
if (!target.args) {
target.args = {};
}
target.args[key] = value;
}
/**
* Retrieves the element ids that have been stored with the generic {@link ELEMENT_IDS} key from the args of the
* given target.
* @param target The navigation target.
* @returns An array with the parsed element ids. The array is empty if no {@link ELEMENT_IDS} key is present in the args
* of the navigation target.
*/
export function getElementIds(target: NavigationTarget): string[] {
if (!target?.args?.[ELEMENT_IDS]) {
return [];
}
const elementIdsValue = target.args[ELEMENT_IDS].toString();
return elementIdsValue.split(ELEMENT_IDS_SEPARATOR);
}
/**
* Stores the given element ids in the given {@link NavigationTarget} as additional arguments using the generic {@link ELEMENT_IDS} key.
* @param target The navigation target.
* @param elementIds The element ids that should be stored.
* @returns the value of the {@link ELEMENT_IDS} key after storing the given element ids.
*/
export function setElementIds(target: NavigationTarget, ...elementIds: string[]): string {
if (target.args === undefined) {
target.args = {};
}
return (target.args[ELEMENT_IDS] = elementIds.join(ELEMENT_IDS_SEPARATOR));
}
/**
* Stores the given {@link TextPosition} in the given {@link NavigationTarget} as additional arguments using
* the generic {@link TEXT_LINE} & {@link TEXT_COLUMN} keys.
* @param target The navigation target.
* @param position The text position that should be stored.
*/
export function setTextPosition(target: NavigationTarget, position: TextPosition | undefined): void {
if (position) {
if (target.args === undefined) {
target.args = {};
}
target.args[TEXT_LINE] = position.line;
target.args[TEXT_COLUMN] = position.character;
}
}
/**
* Retrieves the {@link TextPosition} that have been stored with the generic {@link TEXT_LINE} & {@link TEXT_COLUMN} keys
* from the args of the given target.
* @param target The navigation target.
* @returns The parsed text position or `undefined` if one of the generic text keys is not present in the args
* of the navigation target.
*/
export function getTextPosition(target: NavigationTarget): TextPosition | undefined {
if (!target.args || !target.args[TEXT_LINE] || !target.args[TEXT_COLUMN]) {
return undefined;
}
return {
line: Number(target.args[TEXT_LINE]),
character: Number(target.args[TEXT_COLUMN])
};
}
}
/**
* Action that is usually sent from the client to the server to request navigation targets for a specific navigation type such as
* `documentation` or `implementation` in the given editor context.
* The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks
* and creating new `RequestNavigationTargetsActions`.
*/
export interface RequestNavigationTargetsAction extends RequestAction<SetNavigationTargetsAction> {
kind: typeof RequestNavigationTargetsAction.KIND;
/**
* Identifier of the type of navigation targets we want to retrieve, e.g., 'documentation', 'implementation', etc.
*/
targetTypeId: string;
editorContext: EditorContext;
}
export namespace RequestNavigationTargetsAction {
export const KIND = 'requestNavigationTargets';
export function is(object: unknown): object is RequestNavigationTargetsAction {
return RequestAction.hasKind(object, KIND) && hasStringProp(object, 'targetTypeId') && hasObjectProp(object, 'editorContext');
}
export function create(options: {
targetTypeId: string;
editorContext: EditorContext;
requestId?: string;
}): RequestNavigationTargetsAction {
return {
kind: KIND,
requestId: '',
...options
};
}
}
/**
* Response action from the server following a {@link RequestNavigationTargetsAction}. It contains all available navigation targets for the
* queried target type in the provided editor context. The server may also provide additional information using the arguments, e.g.,
* warnings, that can be interpreted by the client.
* The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks
* and creating new `SetNavigationTargetsActions`.
*/
export interface SetNavigationTargetsAction extends ResponseAction {
kind: typeof SetNavigationTargetsAction.KIND;
targets: NavigationTarget[];
/**
* Custom arguments that may be interpreted by the client.
*/
args?: Args;
}
export namespace SetNavigationTargetsAction {
export const KIND = 'setNavigationTargets';
export function is(object: unknown): object is SetNavigationTargetsAction {
return Action.hasKind(object, KIND) && hasArrayProp(object, 'targets');
}
export function create(targets: NavigationTarget[], options: { args?: Args; responseId?: string } = {}): SetNavigationTargetsAction {
return {
kind: KIND,
responseId: '',
targets,
...options
};
}
}
/**
* Action that triggers the navigation to a particular navigation target, such as element IDs, queries, etc..
* The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks
* and creating new `NavigateToTargetActions`.
*/
export interface NavigateToTargetAction extends Action {
kind: typeof NavigateToTargetAction.KIND;
target: NavigationTarget;
}
export namespace NavigateToTargetAction {
export const KIND = 'navigateToTarget';
export function is(object: unknown): object is NavigateToTargetAction {
return Action.hasKind(object, KIND) && hasObjectProp(object, 'target');
}
export function create(target: NavigationTarget): NavigateToTargetAction {
return {
kind: KIND,
target
};
}
}
/**
* If a client cannot navigate to a target directly, a {@link ResolveNavigationTargetAction} may be sent to the server to resolve the
* navigation target to one or more model elements. This may be useful in cases where the resolution of each target is expensive or the
* client architecture requires an indirection.
* The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks
* and creating new `ResolveNavigationTargetActions`.
*/
export interface ResolveNavigationTargetAction extends RequestAction<SetResolvedNavigationTargetAction> {
kind: typeof ResolveNavigationTargetAction.KIND;
/**
* The navigation target to resolve.
*/
navigationTarget: NavigationTarget;
}
export namespace ResolveNavigationTargetAction {
export const KIND = 'resolveNavigationTarget';
export function is(object: unknown): object is ResolveNavigationTargetAction {
return RequestAction.hasKind(object, KIND) && hasObjectProp(object, 'navigationTarget');
}
export function create(navigationTarget: NavigationTarget, options: { requestId?: string } = {}): ResolveNavigationTargetAction {
return {
kind: KIND,
requestId: '',
navigationTarget,
...options
};
}
}
/**
* An action sent from the server in response to a {@link ResolveNavigationTargetAction}. The response contains the resolved element ids
* for the given target and may contain additional information in the args property.
* The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks
* and creating new `SetResolvedNavigationTargetActions`.
*/
export interface SetResolvedNavigationTargetAction extends ResponseAction {
kind: typeof SetResolvedNavigationTargetAction.KIND;
/**
* The element ids of the resolved navigation target.
*/
elementIds: string[];
/**
* Custom arguments that may be interpreted by the client.
*/
args?: Args;
}
export namespace SetResolvedNavigationTargetAction {
export const KIND = 'setResolvedNavigationTarget';
export function is(object: unknown): object is SetResolvedNavigationTargetAction {
return Action.hasKind(object, KIND) && hasArrayProp(object, 'elementIds');
}
export function create(elementIds: string[], options: { args?: Args; responseId?: string } = {}): SetResolvedNavigationTargetAction {
return {
kind: KIND,
responseId: '',
elementIds,
...options
};
}
}
/**
* If a {@link NavigationTarget} cannot be resolved or the resolved target is something that is not part of our source model, e.g.,
* a separate documentation file, a {@link NavigateToExternalTargetAction} may be sent. Since the target it outside of the model scope such
* an action would be typically handled by an integration layer (such as the surrounding IDE).
* The corresponding namespace declares the action kind as constant and offers helper functions for type guard checks
* and creating new `NavigateToExternalTargetActions`.
*/
export interface NavigateToExternalTargetAction extends Action {
kind: typeof NavigateToExternalTargetAction.KIND;
/**
* The diagram-external target to which the client shall navigate.
*/
target: NavigationTarget;
}
export namespace NavigateToExternalTargetAction {
export const KIND = 'navigateToExternalTarget';
export function is(object: unknown): object is NavigateToExternalTargetAction {
return Action.hasKind(object, KIND) && hasObjectProp(object, 'target');
}
export function create(target: NavigationTarget): NavigateToExternalTargetAction {
return {
kind: KIND,
target
};
}
}