Skip to content

Commit 1265154

Browse files
plugin: add LinkedEditingRanges support
The commit adds support for the `LinkedEditingRanges` VS Code API including the provider, and registering the provider with `registerLinkedEditingRangeProvider`. Signed-off-by: vince-fugnitto <[email protected]>
1 parent 7d1f724 commit 1265154

File tree

9 files changed

+235
-44
lines changed

9 files changed

+235
-44
lines changed

packages/plugin-ext/src/common/plugin-api-rpc-model.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { UriComponents } from './uri-components';
2020
import { CompletionItemTag } from '../plugin/types-impl';
2121
import { Event as TheiaEvent } from '@theia/core/lib/common/event';
2222
import { URI } from '@theia/core/shared/vscode-uri';
23+
import { SerializedRegExp } from './plugin-api-rpc';
2324

2425
// Should contains internal Plugin API types
2526

@@ -541,6 +542,11 @@ export interface CallHierarchyOutgoingCall {
541542
fromRanges: Range[];
542543
}
543544

545+
export interface LinkedEditingRanges {
546+
ranges: Range[];
547+
wordPattern?: SerializedRegExp;
548+
}
549+
544550
export interface SearchInWorkspaceResult {
545551
root: string;
546552
fileUri: string;

packages/plugin-ext/src/common/plugin-api-rpc.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ import {
7171
CommentThreadCollapsibleState,
7272
CommentThread,
7373
CommentThreadChangedEvent,
74-
CodeActionProviderDocumentation
74+
CodeActionProviderDocumentation,
75+
LinkedEditingRanges
7576
} from './plugin-api-rpc-model';
7677
import { ExtPluginApi } from './plugin-ext-api-contribution';
7778
import { KeysToAnyValues, KeysToKeysToAnyValue } from './types';
@@ -1483,6 +1484,7 @@ export interface LanguagesExt {
14831484
$provideRootDefinition(handle: number, resource: UriComponents, location: Position, token: CancellationToken): Promise<CallHierarchyItem[] | undefined>;
14841485
$provideCallers(handle: number, definition: CallHierarchyItem, token: CancellationToken): Promise<CallHierarchyIncomingCall[] | undefined>;
14851486
$provideCallees(handle: number, definition: CallHierarchyItem, token: CancellationToken): Promise<CallHierarchyOutgoingCall[] | undefined>;
1487+
$provideLinkedEditingRanges(handle: number, resource: UriComponents, position: Position, token: CancellationToken): Promise<LinkedEditingRanges | undefined>;
14861488
$releaseCallHierarchy(handle: number, session?: string): Promise<boolean>;
14871489
}
14881490

@@ -1531,6 +1533,7 @@ export interface LanguagesMain {
15311533
$emitDocumentSemanticTokensEvent(eventHandle: number): void;
15321534
$registerDocumentRangeSemanticTokensProvider(handle: number, pluginInfo: PluginInfo, selector: SerializedDocumentFilter[], legend: theia.SemanticTokensLegend): void;
15331535
$registerCallHierarchyProvider(handle: number, selector: SerializedDocumentFilter[]): void;
1536+
$registerLinkedEditingRangeProvider(handle: number, selector: SerializedDocumentFilter[]): void;
15341537
}
15351538

15361539
export interface WebviewInitData {

packages/plugin-ext/src/main/browser/languages-main.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,32 @@ export class LanguagesMainImpl implements LanguagesMain, Disposable {
948948
});
949949
}
950950

951+
// --- linked editing range
952+
953+
$registerLinkedEditingRangeProvider(handle: number, selector: SerializedDocumentFilter[]): void {
954+
const languageSelector = this.toLanguageSelector(selector);
955+
const linkedEditingRangeProvider = this.createLinkedEditingRangeProvider(handle);
956+
this.register(handle,
957+
(monaco.languages.registerLinkedEditingRangeProvider as RegistrationFunction<monaco.languages.LinkedEditingRangeProvider>)(languageSelector, linkedEditingRangeProvider)
958+
);
959+
}
960+
961+
protected createLinkedEditingRangeProvider(handle: number): monaco.languages.LinkedEditingRangeProvider {
962+
return {
963+
provideLinkedEditingRanges: async (model: monaco.editor.ITextModel, position: monaco.Position, token: CancellationToken):
964+
Promise<monaco.languages.LinkedEditingRanges | undefined> => {
965+
const res = await this.proxy.$provideLinkedEditingRanges(handle, model.uri, position, token);
966+
if (res) {
967+
return {
968+
ranges: res.ranges,
969+
wordPattern: reviveRegExp(res.wordPattern)
970+
};
971+
}
972+
return undefined;
973+
}
974+
};
975+
};
976+
951977
}
952978

953979
function reviveMarker(marker: MarkerData): vst.Diagnostic {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// *****************************************************************************
2+
// Copyright (C) 2022 Ericsson 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 WITH Classpath-exception-2.0
15+
// *****************************************************************************
16+
17+
import * as theia from '@theia/plugin';
18+
import { SerializedIndentationRule, SerializedOnEnterRule, SerializedRegExp } from '../common';
19+
20+
export function serializeEnterRules(rules?: theia.OnEnterRule[]): SerializedOnEnterRule[] | undefined {
21+
if (typeof rules === 'undefined' || rules === null) {
22+
return undefined;
23+
}
24+
25+
return rules.map(r =>
26+
({
27+
action: r.action,
28+
beforeText: serializeRegExp(r.beforeText),
29+
afterText: serializeRegExp(r.afterText)
30+
} as SerializedOnEnterRule));
31+
}
32+
33+
export function serializeRegExp(regexp?: RegExp): SerializedRegExp | undefined {
34+
if (typeof regexp === 'undefined' || regexp === null) {
35+
return undefined;
36+
}
37+
38+
return {
39+
pattern: regexp.source,
40+
flags: (regexp.global ? 'g' : '') + (regexp.ignoreCase ? 'i' : '') + (regexp.multiline ? 'm' : '')
41+
};
42+
}
43+
44+
export function serializeIndentation(indentationRules?: theia.IndentationRule): SerializedIndentationRule | undefined {
45+
if (typeof indentationRules === 'undefined' || indentationRules === null) {
46+
return undefined;
47+
}
48+
49+
return {
50+
increaseIndentPattern: serializeRegExp(indentationRules.increaseIndentPattern),
51+
decreaseIndentPattern: serializeRegExp(indentationRules.decreaseIndentPattern),
52+
indentNextLinePattern: serializeRegExp(indentationRules.indentNextLinePattern),
53+
unIndentedLinePattern: serializeRegExp(indentationRules.unIndentedLinePattern)
54+
};
55+
}

packages/plugin-ext/src/plugin/languages.ts

Lines changed: 19 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ import {
1919
PLUGIN_RPC_CONTEXT,
2020
LanguagesMain,
2121
SerializedLanguageConfiguration,
22-
SerializedRegExp,
23-
SerializedOnEnterRule,
24-
SerializedIndentationRule,
2522
Position,
2623
Selection,
2724
RawColorInfo,
@@ -63,6 +60,7 @@ import {
6360
CallHierarchyItem,
6461
CallHierarchyIncomingCall,
6562
CallHierarchyOutgoingCall,
63+
LinkedEditingRanges,
6664
} from '../common/plugin-api-rpc-model';
6765
import { CompletionAdapter } from './languages/completion';
6866
import { Diagnostics } from './languages/diagnostics';
@@ -94,6 +92,8 @@ import { BinaryBuffer } from '@theia/core/lib/common/buffer';
9492
import { DocumentSemanticTokensAdapter, DocumentRangeSemanticTokensAdapter } from './languages/semantic-highlighting';
9593
import { isReadonlyArray } from '../common/arrays';
9694
import { DisposableCollection } from '@theia/core/lib/common/disposable';
95+
import { LinkedEditingRangeAdapter } from './languages/linked-editing-range';
96+
import { serializeEnterRules, serializeIndentation, serializeRegExp } from './languages-utils';
9797

9898
type Adapter = CompletionAdapter |
9999
SignatureHelpAdapter |
@@ -118,7 +118,8 @@ type Adapter = CompletionAdapter |
118118
RenameAdapter |
119119
CallHierarchyAdapter |
120120
DocumentRangeSemanticTokensAdapter |
121-
DocumentSemanticTokensAdapter;
121+
DocumentSemanticTokensAdapter |
122+
LinkedEditingRangeAdapter;
122123

123124
export class LanguagesExtImpl implements LanguagesExt {
124125

@@ -630,6 +631,19 @@ export class LanguagesExtImpl implements LanguagesExt {
630631
}
631632
// ### Call Hierarchy Provider end
632633

634+
// ### Linked Editing Range Provider begin
635+
registerLinkedEditingRangeProvider(selector: theia.DocumentSelector, provider: theia.LinkedEditingRangeProvider): theia.Disposable {
636+
const handle = this.addNewAdapter(new LinkedEditingRangeAdapter(this.documents, provider));
637+
this.proxy.$registerLinkedEditingRangeProvider(handle, this.transformDocumentSelector(selector));
638+
return this.createDisposable(handle);
639+
}
640+
641+
$provideLinkedEditingRanges(handle: number, resource: UriComponents, position: Position, token: theia.CancellationToken): Promise<LinkedEditingRanges | undefined> {
642+
return this.withAdapter(handle, LinkedEditingRangeAdapter, async adapter => adapter.provideRanges(URI.revive(resource), position, token), undefined);
643+
}
644+
645+
// ### Linked Editing Range Provider end
646+
633647
// #region semantic coloring
634648

635649
registerDocumentSemanticTokensProvider(selector: theia.DocumentSelector, provider: theia.DocumentSemanticTokensProvider, legend: theia.SemanticTokensLegend,
@@ -671,43 +685,7 @@ export class LanguagesExtImpl implements LanguagesExt {
671685
// #endregion
672686
}
673687

674-
function serializeEnterRules(rules?: theia.OnEnterRule[]): SerializedOnEnterRule[] | undefined {
675-
if (typeof rules === 'undefined' || rules === null) {
676-
return undefined;
677-
}
678-
679-
return rules.map(r =>
680-
({
681-
action: r.action,
682-
beforeText: serializeRegExp(r.beforeText),
683-
afterText: serializeRegExp(r.afterText)
684-
} as SerializedOnEnterRule));
685-
}
686-
687-
function serializeRegExp(regexp?: RegExp): SerializedRegExp | undefined {
688-
if (typeof regexp === 'undefined' || regexp === null) {
689-
return undefined;
690-
}
691-
692-
return {
693-
pattern: regexp.source,
694-
flags: (regexp.global ? 'g' : '') + (regexp.ignoreCase ? 'i' : '') + (regexp.multiline ? 'm' : '')
695-
};
696-
}
697-
698-
function serializeIndentation(indentationRules?: theia.IndentationRule): SerializedIndentationRule | undefined {
699-
if (typeof indentationRules === 'undefined' || indentationRules === null) {
700-
return undefined;
701-
}
702-
703-
return {
704-
increaseIndentPattern: serializeRegExp(indentationRules.increaseIndentPattern),
705-
decreaseIndentPattern: serializeRegExp(indentationRules.decreaseIndentPattern),
706-
indentNextLinePattern: serializeRegExp(indentationRules.indentNextLinePattern),
707-
unIndentedLinePattern: serializeRegExp(indentationRules.unIndentedLinePattern)
708-
};
709-
}
710-
711688
function getPluginLabel(pluginInfo: PluginInfo): string {
712689
return pluginInfo.displayName || pluginInfo.name;
713690
}
691+
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// *****************************************************************************
2+
// Copyright (C) 2022 Ericsson 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 WITH Classpath-exception-2.0
15+
// *****************************************************************************
16+
17+
import * as theia from '@theia/plugin';
18+
import * as rpc from '../../common/plugin-api-rpc';
19+
import { DocumentsExtImpl } from '../documents';
20+
import { LinkedEditingRanges } from '../../common/plugin-api-rpc-model';
21+
import { URI } from '@theia/core/shared/vscode-uri';
22+
import { coalesce } from '../../common/arrays';
23+
import { fromRange, toPosition } from '../type-converters';
24+
import { serializeRegExp } from '../languages-utils';
25+
26+
export class LinkedEditingRangeAdapter {
27+
28+
constructor(
29+
private readonly documents: DocumentsExtImpl,
30+
private readonly provider: theia.LinkedEditingRangeProvider
31+
) { }
32+
33+
async provideRanges(resource: URI, position: rpc.Position, token: theia.CancellationToken): Promise<LinkedEditingRanges | undefined> {
34+
35+
const doc = this.documents.getDocument(resource);
36+
const pos = toPosition(position);
37+
38+
const value = await this.provider.provideLinkedEditingRanges(doc, pos, token);
39+
if (value && Array.isArray(value.ranges)) {
40+
return {
41+
ranges: coalesce(value.ranges.map(r => fromRange(r))),
42+
wordPattern: serializeRegExp(value.wordPattern)
43+
};
44+
}
45+
return undefined;
46+
}
47+
48+
}

packages/plugin-ext/src/plugin/plugin-context.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ import {
140140
SourceControlInputBoxValidationType,
141141
URI,
142142
FileDecoration,
143-
ExtensionMode
143+
ExtensionMode,
144+
LinkedEditingRanges
144145
} from './types-impl';
145146
import { AuthenticationExtImpl } from './authentication-ext';
146147
import { SymbolKind } from '../common/plugin-api-rpc-model';
@@ -779,6 +780,9 @@ export function createAPIFactory(
779780
},
780781
registerCallHierarchyProvider(selector: theia.DocumentSelector, provider: theia.CallHierarchyProvider): theia.Disposable {
781782
return languagesExt.registerCallHierarchyProvider(selector, provider);
783+
},
784+
registerLinkedEditingRangeProvider(selector: theia.DocumentSelector, provider: theia.LinkedEditingRangeProvider): theia.Disposable {
785+
return languagesExt.registerLinkedEditingRangeProvider(selector, provider);
782786
}
783787
};
784788

@@ -1035,7 +1039,8 @@ export function createAPIFactory(
10351039
SourceControlInputBoxValidationType,
10361040
FileDecoration,
10371041
CancellationError,
1038-
ExtensionMode
1042+
ExtensionMode,
1043+
LinkedEditingRanges
10391044
};
10401045
};
10411046
}

packages/plugin-ext/src/plugin/types-impl.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2486,6 +2486,18 @@ export class CallHierarchyOutgoingCall {
24862486
}
24872487
}
24882488

2489+
@es5ClassCompat
2490+
export class LinkedEditingRanges {
2491+
2492+
ranges: theia.Range[];
2493+
wordPattern?: RegExp;
2494+
2495+
constructor(ranges: Range[], wordPattern?: RegExp) {
2496+
this.ranges = ranges;
2497+
this.wordPattern = wordPattern;
2498+
}
2499+
}
2500+
24892501
@es5ClassCompat
24902502
export class TimelineItem {
24912503
timestamp: number;

0 commit comments

Comments
 (0)