Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[plug-in] Languages API - Register formatting provider for a document range #2957

Merged
merged 1 commit into from
Sep 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/plugin-ext/src/api/plugin-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,7 @@ export interface LanguagesExt {
$provideSignatureHelp(handle: number, resource: UriComponents, position: Position): Promise<SignatureHelp | undefined>;
$provideHover(handle: number, resource: UriComponents, position: Position): Promise<Hover | undefined>;
$provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: FormattingOptions): Promise<ModelSingleEditOperation[] | undefined>;
$provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: Range, options: FormattingOptions): Promise<ModelSingleEditOperation[] | undefined>;
}

export interface LanguagesMain {
Expand All @@ -662,6 +663,7 @@ export interface LanguagesMain {
$clearDiagnostics(id: string): void;
$changeDiagnostics(id: string, delta: [UriComponents, MarkerData[]][]): void;
$registerDocumentFormattingSupport(handle: number, selector: SerializedDocumentFilter[]): void;
$registerRangeFormattingProvider(handle: number, selector: SerializedDocumentFilter[]): void;
}

export const PLUGIN_RPC_CONTEXT = {
Expand Down
25 changes: 24 additions & 1 deletion packages/plugin-ext/src/main/browser/languages-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
MAIN_RPC_CONTEXT,
LanguagesExt
} from '../../api/plugin-api';
import { SerializedDocumentFilter, MarkerData } from '../../api/model';
import { SerializedDocumentFilter, MarkerData, Range } from '../../api/model';
import { RPCProtocol } from '../../api/rpc-protocol';
import { fromLanguageSelector } from '../../plugin/type-converters';
import { UriComponents } from '@theia/plugin-ext/src/common/uri-components';
Expand Down Expand Up @@ -219,6 +219,29 @@ export class LanguagesMainImpl implements LanguagesMain {
};
}

$registerRangeFormattingProvider(handle: number, selector: SerializedDocumentFilter[]): void {
const languageSelector = fromLanguageSelector(selector);
const rangeFormattingEditProvider = this.createRangeFormattingProvider(handle, languageSelector);
const disposable = new DisposableCollection();
for (const language of getLanguages()) {
if (this.matchLanguage(languageSelector, language)) {
disposable.push(monaco.languages.registerDocumentRangeFormattingEditProvider(language, rangeFormattingEditProvider));
}
}
this.disposables.set(handle, disposable);
}

createRangeFormattingProvider(handle: number, selector: LanguageSelector | undefined): monaco.languages.DocumentRangeFormattingEditProvider {
return {
provideDocumentRangeFormattingEdits: (model, range: Range, options, token) => {
if (!this.matchModel(selector, MonacoModelIdentifier.fromModel(model))) {
return undefined!;
}
return this.proxy.$provideDocumentRangeFormattingEdits(handle, model.uri, range, options).then(v => v!);
}
};
}

protected matchModel(selector: LanguageSelector | undefined, model: MonacoModelIdentifier): boolean {
if (Array.isArray(selector)) {
return selector.some(filter => this.matchModel(filter, model));
Expand Down
16 changes: 15 additions & 1 deletion packages/plugin-ext/src/plugin/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
SerializedDocumentFilter,
SignatureHelp,
Hover,
Range,
SingleEditOperation,
FormattingOptions,
Definition,
Expand All @@ -48,9 +49,10 @@ import { Diagnostics } from './languages/diagnostics';
import { SignatureHelpAdapter } from './languages/signature';
import { HoverAdapter } from './languages/hover';
import { DocumentFormattingAdapter } from './languages/document-formatting';
import { RangeFormattingAdapter } from './languages/range-formatting';
import { DefinitionAdapter } from './languages/definition';

type Adapter = CompletionAdapter | SignatureHelpAdapter | HoverAdapter | DocumentFormattingAdapter | DefinitionAdapter;
type Adapter = CompletionAdapter | SignatureHelpAdapter | HoverAdapter | DocumentFormattingAdapter | RangeFormattingAdapter | DefinitionAdapter;

export class LanguagesExtImpl implements LanguagesExt {

Expand Down Expand Up @@ -229,6 +231,18 @@ export class LanguagesExtImpl implements LanguagesExt {
}
// ### Document Formatting Edit end

// ### Document Range Formatting Edit begin
registerDocumentRangeFormattingEditProvider(selector: theia.DocumentSelector, provider: theia.DocumentRangeFormattingEditProvider): theia.Disposable {
const callId = this.addNewAdapter(new RangeFormattingAdapter(provider, this.documents));
this.proxy.$registerRangeFormattingProvider(callId, this.transformDocumentSelector(selector));
return this.createDisposable(callId);
}

$provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: Range, options: FormattingOptions): Promise<SingleEditOperation[] | undefined> {
return this.withAdapter(handle, RangeFormattingAdapter, adapter => adapter.provideDocumentRangeFormattingEdits(URI.revive(resource), range, options));
}
// ### Document Range Formatting Edit end

}

function serializeEnterRules(rules?: theia.OnEnterRule[]): SerializedOnEnterRule[] | undefined {
Expand Down
47 changes: 47 additions & 0 deletions packages/plugin-ext/src/plugin/languages/range-formatting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/********************************************************************************
* Copyright (C) 2018 Red Hat, Inc. 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 { DocumentsExtImpl } from '../documents';
import * as Converter from '../type-converters';
import URI from 'vscode-uri/lib/umd';
import { FormattingOptions, SingleEditOperation, Range } from '../../api/model';

export class RangeFormattingAdapter {

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

provideDocumentRangeFormattingEdits(resource: URI, range: Range, options: FormattingOptions): Promise<SingleEditOperation[] | undefined> {
const document = this.documents.getDocumentData(resource);
if (!document) {
return Promise.reject(new Error(`There are no document for ${resource}`));
}

const doc = document.document;
const ran = Converter.toRange(range);

return Promise.resolve(this.provider.provideDocumentRangeFormattingEdits(doc, ran, <any>options, undefined)).then(value => {
if (Array.isArray(value)) {
return value.map(Converter.fromTextEdit);
}
return undefined;
});
}

}
3 changes: 3 additions & 0 deletions packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,9 @@ export function createAPIFactory(rpc: RPCProtocol, pluginManager: PluginManager)
},
registerDocumentFormattingEditProvider(selector: theia.DocumentSelector, provider: theia.DocumentFormattingEditProvider): theia.Disposable {
return languagesExt.registerDocumentFormattingEditProvider(selector, provider);
},
registerDocumentRangeFormattingEditProvider(selector: theia.DocumentSelector, provider: theia.DocumentRangeFormattingEditProvider): theia.Disposable {
return languagesExt.registerDocumentRangeFormattingEditProvider(selector, provider);
}
};

Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-ext/src/plugin/type-converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ export function toCompletionItemKind(type?: CompletionType): types.CompletionIte
export function fromTextEdit(edit: theia.TextEdit): SingleEditOperation {
return <SingleEditOperation>{
text: edit.newText,
range: fromRange(edit.range)
range: fromRange_(edit.range)
};
}

Expand Down
45 changes: 45 additions & 0 deletions packages/plugin/src/theia.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3649,6 +3649,34 @@ declare module '@theia/plugin' {
): ProviderResult<TextEdit[] | undefined>;
}

/**
* The document formatting provider interface defines the contract between extensions and
* the formatting-feature.
*/
export interface DocumentRangeFormattingEditProvider {

/**
* Provide formatting edits for a range in a document.
*
* The given range is a hint and providers can decide to format a smaller
* or larger range. Often this is done by adjusting the start and end
* of the range to full syntax nodes.
*
* @param document The document in which the command was invoked.
* @param range The range which should be formatted.
* @param options Options controlling formatting.
* @param token A cancellation token.
* @return A set of text edits or a thenable that resolves to such. The lack of a result can be
* signaled by returning `undefined`, `null`, or an empty array.
*/
provideDocumentRangeFormattingEdits(
document: TextDocument,
range: Range,
options: FormattingOptions,
token: CancellationToken | undefined
): ProviderResult<TextEdit[] | undefined>;
}

/**
* Value-object describing what options formatting should use.
*/
Expand Down Expand Up @@ -3824,6 +3852,23 @@ declare module '@theia/plugin' {
* @return A [disposable](#Disposable) that unregisters this provider when being disposed.
*/
export function registerDocumentFormattingEditProvider(selector: DocumentSelector, provider: DocumentFormattingEditProvider): Disposable;

/**
* Register a formatting provider for a document range.
*
* *Note:* A document range provider is also a [document formatter](#DocumentFormattingEditProvider)
* which means there is no need to [register](registerDocumentFormattingEditProvider) a document
* formatter when also registering a range provider.
*
* Multiple providers can be registered for a language. In that case providers are sorted
* by their [score](#languages.match) and the best-matching provider is used. Failure
* of the selected provider will cause a failure of the whole operation.
*
* @param selector A selector that defines the documents this provider is applicable to.
* @param provider A document range formatting edit provider.
* @return A [disposable](#Disposable) that unregisters this provider when being disposed.
*/
export function registerDocumentRangeFormattingEditProvider(selector: DocumentSelector, provider: DocumentRangeFormattingEditProvider): Disposable;
}

/**
Expand Down