Skip to content

Commit

Permalink
plugin: add LinkedEditingRanges support
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
vince-fugnitto committed May 11, 2022
1 parent cd33db1 commit a122fd8
Show file tree
Hide file tree
Showing 9 changed files with 235 additions and 44 deletions.
6 changes: 6 additions & 0 deletions packages/plugin-ext/src/common/plugin-api-rpc-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { UriComponents } from './uri-components';
import { CompletionItemTag } from '../plugin/types-impl';
import { Event as TheiaEvent } from '@theia/core/lib/common/event';
import { URI } from '@theia/core/shared/vscode-uri';
import { SerializedRegExp } from './plugin-api-rpc';

// Should contains internal Plugin API types

Expand Down Expand Up @@ -541,6 +542,11 @@ export interface CallHierarchyOutgoingCall {
fromRanges: Range[];
}

export interface LinkedEditingRanges {
ranges: Range[];
wordPattern?: SerializedRegExp;
}

export interface SearchInWorkspaceResult {
root: string;
fileUri: string;
Expand Down
5 changes: 4 additions & 1 deletion packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ import {
CommentThreadCollapsibleState,
CommentThread,
CommentThreadChangedEvent,
CodeActionProviderDocumentation
CodeActionProviderDocumentation,
LinkedEditingRanges
} from './plugin-api-rpc-model';
import { ExtPluginApi } from './plugin-ext-api-contribution';
import { KeysToAnyValues, KeysToKeysToAnyValue } from './types';
Expand Down Expand Up @@ -1483,6 +1484,7 @@ export interface LanguagesExt {
$provideRootDefinition(handle: number, resource: UriComponents, location: Position, token: CancellationToken): Promise<CallHierarchyItem[] | undefined>;
$provideCallers(handle: number, definition: CallHierarchyItem, token: CancellationToken): Promise<CallHierarchyIncomingCall[] | undefined>;
$provideCallees(handle: number, definition: CallHierarchyItem, token: CancellationToken): Promise<CallHierarchyOutgoingCall[] | undefined>;
$provideLinkedEditingRanges(handle: number, resource: UriComponents, position: Position, token: CancellationToken): Promise<LinkedEditingRanges | undefined>;
$releaseCallHierarchy(handle: number, session?: string): Promise<boolean>;
}

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

export interface WebviewInitData {
Expand Down
26 changes: 26 additions & 0 deletions packages/plugin-ext/src/main/browser/languages-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,32 @@ export class LanguagesMainImpl implements LanguagesMain, Disposable {
});
}

// --- linked editing range

$registerLinkedEditingRangeProvider(handle: number, selector: SerializedDocumentFilter[]): void {
const languageSelector = this.toLanguageSelector(selector);
const linkedEditingRangeProvider = this.createLinkedEditingRangeProvider(handle);
this.register(handle,
(monaco.languages.registerLinkedEditingRangeProvider as RegistrationFunction<monaco.languages.LinkedEditingRangeProvider>)(languageSelector, linkedEditingRangeProvider)
);
}

protected createLinkedEditingRangeProvider(handle: number): monaco.languages.LinkedEditingRangeProvider {
return {
provideLinkedEditingRanges: async (model: monaco.editor.ITextModel, position: monaco.Position, token: CancellationToken):
Promise<monaco.languages.LinkedEditingRanges | undefined> => {
const res = await this.proxy.$provideLinkedEditingRanges(handle, model.uri, position, token);
if (res) {
return {
ranges: res.ranges,
wordPattern: reviveRegExp(res.wordPattern)
};
}
return undefined;
}
};
};

}

function reviveMarker(marker: MarkerData): vst.Diagnostic {
Expand Down
55 changes: 55 additions & 0 deletions packages/plugin-ext/src/plugin/languages-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// *****************************************************************************
// Copyright (C) 2022 Ericsson 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 * as theia from '@theia/plugin';
import { SerializedIndentationRule, SerializedOnEnterRule, SerializedRegExp } from '../common';

export function serializeEnterRules(rules?: theia.OnEnterRule[]): SerializedOnEnterRule[] | undefined {
if (typeof rules === 'undefined' || rules === null) {
return undefined;
}

return rules.map(r =>
({
action: r.action,
beforeText: serializeRegExp(r.beforeText),
afterText: serializeRegExp(r.afterText)
} as SerializedOnEnterRule));
}

export function serializeRegExp(regexp?: RegExp): SerializedRegExp | undefined {
if (typeof regexp === 'undefined' || regexp === null) {
return undefined;
}

return {
pattern: regexp.source,
flags: (regexp.global ? 'g' : '') + (regexp.ignoreCase ? 'i' : '') + (regexp.multiline ? 'm' : '')
};
}

export function serializeIndentation(indentationRules?: theia.IndentationRule): SerializedIndentationRule | undefined {
if (typeof indentationRules === 'undefined' || indentationRules === null) {
return undefined;
}

return {
increaseIndentPattern: serializeRegExp(indentationRules.increaseIndentPattern),
decreaseIndentPattern: serializeRegExp(indentationRules.decreaseIndentPattern),
indentNextLinePattern: serializeRegExp(indentationRules.indentNextLinePattern),
unIndentedLinePattern: serializeRegExp(indentationRules.unIndentedLinePattern)
};
}
60 changes: 19 additions & 41 deletions packages/plugin-ext/src/plugin/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ import {
PLUGIN_RPC_CONTEXT,
LanguagesMain,
SerializedLanguageConfiguration,
SerializedRegExp,
SerializedOnEnterRule,
SerializedIndentationRule,
Position,
Selection,
RawColorInfo,
Expand Down Expand Up @@ -63,6 +60,7 @@ import {
CallHierarchyItem,
CallHierarchyIncomingCall,
CallHierarchyOutgoingCall,
LinkedEditingRanges,
} from '../common/plugin-api-rpc-model';
import { CompletionAdapter } from './languages/completion';
import { Diagnostics } from './languages/diagnostics';
Expand Down Expand Up @@ -94,6 +92,8 @@ import { BinaryBuffer } from '@theia/core/lib/common/buffer';
import { DocumentSemanticTokensAdapter, DocumentRangeSemanticTokensAdapter } from './languages/semantic-highlighting';
import { isReadonlyArray } from '../common/arrays';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { LinkedEditingRangeAdapter } from './languages/linked-editing-range';
import { serializeEnterRules, serializeIndentation, serializeRegExp } from './languages-utils';

type Adapter = CompletionAdapter |
SignatureHelpAdapter |
Expand All @@ -118,7 +118,8 @@ type Adapter = CompletionAdapter |
RenameAdapter |
CallHierarchyAdapter |
DocumentRangeSemanticTokensAdapter |
DocumentSemanticTokensAdapter;
DocumentSemanticTokensAdapter |
LinkedEditingRangeAdapter;

export class LanguagesExtImpl implements LanguagesExt {

Expand Down Expand Up @@ -630,6 +631,19 @@ export class LanguagesExtImpl implements LanguagesExt {
}
// ### Call Hierarchy Provider end

// ### Linked Editing Range Provider begin
registerLinkedEditingRangeProvider(selector: theia.DocumentSelector, provider: theia.LinkedEditingRangeProvider): theia.Disposable {
const handle = this.addNewAdapter(new LinkedEditingRangeAdapter(this.documents, provider));
this.proxy.$registerLinkedEditingRangeProvider(handle, this.transformDocumentSelector(selector));
return this.createDisposable(handle);
}

$provideLinkedEditingRanges(handle: number, resource: UriComponents, position: Position, token: theia.CancellationToken): Promise<LinkedEditingRanges | undefined> {
return this.withAdapter(handle, LinkedEditingRangeAdapter, async adapter => adapter.provideRanges(URI.revive(resource), position, token), undefined);
}

// ### Linked Editing Range Provider end

// #region semantic coloring

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

function serializeEnterRules(rules?: theia.OnEnterRule[]): SerializedOnEnterRule[] | undefined {
if (typeof rules === 'undefined' || rules === null) {
return undefined;
}

return rules.map(r =>
({
action: r.action,
beforeText: serializeRegExp(r.beforeText),
afterText: serializeRegExp(r.afterText)
} as SerializedOnEnterRule));
}

function serializeRegExp(regexp?: RegExp): SerializedRegExp | undefined {
if (typeof regexp === 'undefined' || regexp === null) {
return undefined;
}

return {
pattern: regexp.source,
flags: (regexp.global ? 'g' : '') + (regexp.ignoreCase ? 'i' : '') + (regexp.multiline ? 'm' : '')
};
}

function serializeIndentation(indentationRules?: theia.IndentationRule): SerializedIndentationRule | undefined {
if (typeof indentationRules === 'undefined' || indentationRules === null) {
return undefined;
}

return {
increaseIndentPattern: serializeRegExp(indentationRules.increaseIndentPattern),
decreaseIndentPattern: serializeRegExp(indentationRules.decreaseIndentPattern),
indentNextLinePattern: serializeRegExp(indentationRules.indentNextLinePattern),
unIndentedLinePattern: serializeRegExp(indentationRules.unIndentedLinePattern)
};
}

function getPluginLabel(pluginInfo: PluginInfo): string {
return pluginInfo.displayName || pluginInfo.name;
}

48 changes: 48 additions & 0 deletions packages/plugin-ext/src/plugin/languages/linked-editing-range.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// *****************************************************************************
// Copyright (C) 2022 Ericsson 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 * as theia from '@theia/plugin';
import * as rpc from '../../common/plugin-api-rpc';
import { DocumentsExtImpl } from '../documents';
import { LinkedEditingRanges } from '../../common/plugin-api-rpc-model';
import { URI } from '@theia/core/shared/vscode-uri';
import { coalesce } from '../../common/arrays';
import { fromRange, toPosition } from '../type-converters';
import { serializeRegExp } from '../languages-utils';

export class LinkedEditingRangeAdapter {

constructor(
private readonly documents: DocumentsExtImpl,
private readonly provider: theia.LinkedEditingRangeProvider
) { }

async provideRanges(resource: URI, position: rpc.Position, token: theia.CancellationToken): Promise<LinkedEditingRanges | undefined> {

const doc = this.documents.getDocument(resource);
const pos = toPosition(position);

const value = await this.provider.provideLinkedEditingRanges(doc, pos, token);
if (value && Array.isArray(value.ranges)) {
return {
ranges: coalesce(value.ranges.map(r => fromRange(r))),
wordPattern: serializeRegExp(value.wordPattern)
};
}
return undefined;
}

}
9 changes: 7 additions & 2 deletions packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ import {
SourceControlInputBoxValidationType,
URI,
FileDecoration,
ExtensionMode
ExtensionMode,
LinkedEditingRanges
} from './types-impl';
import { AuthenticationExtImpl } from './authentication-ext';
import { SymbolKind } from '../common/plugin-api-rpc-model';
Expand Down Expand Up @@ -779,6 +780,9 @@ export function createAPIFactory(
},
registerCallHierarchyProvider(selector: theia.DocumentSelector, provider: theia.CallHierarchyProvider): theia.Disposable {
return languagesExt.registerCallHierarchyProvider(selector, provider);
},
registerLinkedEditingRangeProvider(selector: theia.DocumentSelector, provider: theia.LinkedEditingRangeProvider): theia.Disposable {
return languagesExt.registerLinkedEditingRangeProvider(selector, provider);
}
};

Expand Down Expand Up @@ -1035,7 +1039,8 @@ export function createAPIFactory(
SourceControlInputBoxValidationType,
FileDecoration,
CancellationError,
ExtensionMode
ExtensionMode,
LinkedEditingRanges
};
};
}
Expand Down
12 changes: 12 additions & 0 deletions packages/plugin-ext/src/plugin/types-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2486,6 +2486,18 @@ export class CallHierarchyOutgoingCall {
}
}

@es5ClassCompat
export class LinkedEditingRanges {

ranges: theia.Range[];
wordPattern?: RegExp;

constructor(ranges: Range[], wordPattern?: RegExp) {
this.ranges = ranges;
this.wordPattern = wordPattern;
}
}

@es5ClassCompat
export class TimelineItem {
timestamp: number;
Expand Down
Loading

0 comments on commit a122fd8

Please sign in to comment.