Skip to content

Commit

Permalink
GH-1845: Extended the LSP with the semantic highlighting capabilities.
Browse files Browse the repository at this point in the history
Closes #1845

Signed-off-by: Akos Kitta <[email protected]>
  • Loading branch information
Akos Kitta committed Aug 1, 2018
1 parent c5554c8 commit ceb74d2
Show file tree
Hide file tree
Showing 18 changed files with 593 additions and 21 deletions.
5 changes: 5 additions & 0 deletions packages/core/src/common/disposable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,14 @@ export namespace Disposable {
}

export class DisposableCollection implements Disposable {

protected readonly disposables: Disposable[] = [];
protected readonly onDisposeEmitter = new Emitter<void>();

constructor(...toDispose: Disposable[]) {
toDispose.forEach(d => this.push(d));
}

get onDispose(): Event<void> {
return this.onDisposeEmitter.event;
}
Expand Down
3 changes: 3 additions & 0 deletions packages/editor/src/browser/editor-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { NavigationLocationUpdater } from './navigation/navigation-location-upda
import { NavigationLocationService } from './navigation/navigation-location-service';
import { NavigationLocationSimilarity } from './navigation/navigation-location-similarity';
import { EditorVariableContribution } from './editor-variable-contribution';
import { SemanticHighlightingService } from './semantic-highlight/semantic-highlighting-service';

export default new ContainerModule(bind => {
bindEditorPreferences(bind);
Expand Down Expand Up @@ -58,4 +59,6 @@ export default new ContainerModule(bind => {
bind(NavigationLocationSimilarity).toSelf().inSingletonScope();

bind(VariableContribution).to(EditorVariableContribution).inSingletonScope();

bind(SemanticHighlightingService).toSelf().inSingletonScope();
});
27 changes: 25 additions & 2 deletions packages/editor/src/browser/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,40 @@ export interface TextEditor extends Disposable, TextEditorSelection, Navigatable
*/
deltaDecorations(params: DeltaDecorationParams): string[];

/**
* Get the range associated with a decoration.
* @param id The decoration id.
* @return The decoration range or `undefined` if the decoration was not found.
*/
getDecorationRange(id: string): Range | undefined;

/**
* Gets all the decorations in a range as an array. Only `start.line` and `end.line` from `range` are used for filtering.
* So for now it returns all the decorations on the same line as `range`.
* @param range The range to search in.
* @return An array with the decorations.
*/
getDecorationsInRange(range: Range): EditorDecoration[];

/**
* Gets all the decorations for the lines between `startLineNumber` and `endLineNumber` as an array.
* @param startLineNumber The start line number.
* @param endLineNumber The end line number.
* @return An array with the decorations.
*/
getLinesDecorations(startLineNumber: number, endLineNumber: number): EditorDecoration[];

getVisibleColumn(position: Position): number;

/**
* Replaces the text of source given in ReplacetextParams.
* Replaces the text of source given in ReplaceTextParams.
* @param params: ReplaceTextParams
*/
replaceText(params: ReplaceTextParams): Promise<boolean>;

/**
* Execute edits on the editor.
* @param edits: edits created with `lsp.TextEdit.replace`, `lsp.TextEdit.instert`, `lsp.TextEdit.del`
* @param edits: edits created with `lsp.TextEdit.replace`, `lsp.TextEdit.insert`, `lsp.TextEdit.del`
*/
executeEdits(edits: lsp.TextEdit[]): boolean;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/********************************************************************************
* Copyright (C) 2018 TypeFox 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 { injectable } from 'inversify';
import { Position, Range } from 'vscode-languageserver-types';
import URI from '@theia/core/lib/common/uri';
import { Disposable } from '@theia/core/lib/common/disposable';

@injectable()
export class SemanticHighlightingService implements Disposable {

async decorate(uri: URI, ranges: SemanticHighlightingRange[]): Promise<void> {
// NOOP
}

dispose(): void {
// NOOP
}

}

export interface SemanticHighlightingRange extends Range {
readonly scopes: string[];
}

export { Position, Range };
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export class NsfwFileSystemWatcherServer implements FileSystemWatcherServer {
this.watchers.delete(watcherId);
this.debug('Stopping watching:', basePath);
watcher.stop();
this.options.info('Stopped watching.');
this.options.info('Stopped watching:', basePath);
});
this.watcherOptions.set(watcherId, {
ignored: options.ignored.map(pattern => new Minimatch(pattern))
Expand Down
12 changes: 7 additions & 5 deletions packages/java/scripts/download-jdt-ls.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ const path = require('path');
const packageJson = require('../package.json');
const shared = require('./shared');
const packagePath = path.join(__dirname, "..");
const serverPath = packageJson['ls.download.path'] || '/che/che-ls-jdt/snapshots/che-jdt-language-server-latest.tar.gz';
const downloadURI = packageJson['ls.download.base'] || 'https://www.eclipse.org/downloads/download.php?file=';
const archiveUri = downloadURI + serverPath + '&r=1';
// TODO revert this!
const serverPath = 'https://github.com/kittaakos/eclipse.jdt.ls-gh-715/raw/master/jdt-language-server-latest.tar.gz';//packageJson['ls.download.path'] || '/che/che-ls-jdt/snapshots/che-jdt-language-server-latest.tar.gz';
// const downloadURI = packageJson['ls.download.base'] || 'https://www.eclipse.org/downloads/download.php?file=';
// const archiveUri = downloadURI + serverPath + '&r=1';
const filename = path.basename(serverPath);
const downloadDir = 'download';
const downloadPath = path.join(packagePath, downloadDir);
Expand Down Expand Up @@ -63,12 +64,13 @@ function downloadJavaServer() {
response.pipe(file);
} else {
file.destroy();
reject(`failed to download with code: ${statusCode}`);
reject(new Error(`failed to download with code: ${statusCode}`));
}
})

};
downloadWithRedirect(archiveUri);
// downloadWithRedirect(archiveUri);
downloadWithRedirect(serverPath);
});
}

Expand Down
42 changes: 39 additions & 3 deletions packages/java/src/browser/java-client-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,19 @@ import { CommandService } from "@theia/core/lib/common";
import {
Window, ILanguageClient, BaseLanguageClientContribution, Workspace, Languages, LanguageClientFactory, LanguageClientOptions
} from '@theia/languages/lib/browser';
import URI from '@theia/core/lib/common/uri';
import { SemanticHighlightingService, SemanticHighlightingRange, Position } from "@theia/editor/lib/browser/semantic-highlight/semantic-highlighting-service";
import { JAVA_LANGUAGE_ID, JAVA_LANGUAGE_NAME } from '../common';
import { ActionableNotification, ActionableMessage, StatusReport, StatusNotification } from "./java-protocol";
import { StatusBar, StatusBarEntry, StatusBarAlignment } from "@theia/core/lib/browser";
import {
ActionableNotification,
ActionableMessage,
StatusReport,
StatusNotification,
SemanticHighlight,
SemanticHighlightingParams,
SemanticHighlightingToken
} from "./java-protocol";

@injectable()
export class JavaClientContribution extends BaseLanguageClientContribution {
Expand All @@ -37,7 +47,8 @@ export class JavaClientContribution extends BaseLanguageClientContribution {
@inject(LanguageClientFactory) protected readonly languageClientFactory: LanguageClientFactory,
@inject(Window) protected readonly window: Window,
@inject(CommandService) protected readonly commandService: CommandService,
@inject(StatusBar) protected readonly statusBar: StatusBar
@inject(StatusBar) protected readonly statusBar: StatusBar,
@inject(SemanticHighlightingService) protected readonly semanticHighlightingService: SemanticHighlightingService
) {
super(workspace, languages, languageClientFactory);
}
Expand All @@ -53,6 +64,7 @@ export class JavaClientContribution extends BaseLanguageClientContribution {
protected onReady(languageClient: ILanguageClient): void {
languageClient.onNotification(ActionableNotification.type, this.showActionableMessage.bind(this));
languageClient.onNotification(StatusNotification.type, this.showStatusMessage.bind(this));
languageClient.onNotification(SemanticHighlight.type, this.applySemanticHighlighting.bind(this));
super.onReady(languageClient);
}

Expand All @@ -73,6 +85,29 @@ export class JavaClientContribution extends BaseLanguageClientContribution {
}, 5000);
}

protected applySemanticHighlighting(params: SemanticHighlightingParams): void {
const toRanges: (tuple: [number, SemanticHighlightingToken[]]) => SemanticHighlightingRange[] = tuple => {
const [line, tokens] = tuple;
if (tokens.length === 0) {
return [
{
start: Position.create(line, 0),
end: Position.create(line, 0),
scopes: []
}
];
}
return tokens.map(token => ({
start: Position.create(line, token.character),
end: Position.create(line, token.character + token.length),
scopes: token.scopes
}));
};
const ranges = params.lines.map(line => [line.line, line.tokens]).map(toRanges).reduce((acc, current) => acc.concat(current), []);
const uri = new URI(params.uri);
this.semanticHighlightingService.decorate(uri, ranges);
}

protected showActionableMessage(message: ActionableMessage): void {
const items = message.commands || [];
this.window.showMessage(message.severity, message.message, ...items).then(command => {
Expand All @@ -87,7 +122,8 @@ export class JavaClientContribution extends BaseLanguageClientContribution {
const options = super.createOptions();
options.initializationOptions = {
extendedClientCapabilities: {
classFileContentsSupport: true
classFileContentsSupport: true,
semanticHighlighting: true
}
};
return options;
Expand Down
20 changes: 20 additions & 0 deletions packages/java/src/browser/java-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,30 @@ export interface ActionableMessage {
commands?: Command[];
}

export interface SemanticHighlightingParams {
readonly uri: string;
readonly lines: SemanticHighlightingInformation[];
}

export interface SemanticHighlightingInformation {
readonly line: number;
readonly tokens: SemanticHighlightingToken[];
}

export interface SemanticHighlightingToken {
readonly character: number;
readonly length: number;
readonly scopes: string[];
}

export namespace ClassFileContentsRequest {
export const type = new RequestType<TextDocumentIdentifier, string | undefined, void, void>('java/classFileContents');
}

export namespace ActionableNotification {
export const type = new NotificationType<ActionableMessage, void>('language/actionableNotification');
}

export namespace SemanticHighlight {
export const type = new NotificationType<SemanticHighlightingParams, void>('textDocument/semanticHighlighting');
}
6 changes: 5 additions & 1 deletion packages/java/src/node/java-backend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@
********************************************************************************/

import { ContainerModule } from "inversify";
import { LanguageServerContribution } from "@theia/languages/lib/node";
import { CliContribution } from '@theia/core/lib/node/cli';
import { LanguageServerContribution } from '@theia/languages/lib/node';
import { JavaContribution } from './java-contribution';
import { JavaCliContribution } from './java-cli-contribution';

export default new ContainerModule(bind => {
bind(LanguageServerContribution).to(JavaContribution).inSingletonScope();
bind(JavaCliContribution).toSelf().inSingletonScope();
bind(CliContribution).toService(JavaCliContribution);
});
58 changes: 58 additions & 0 deletions packages/java/src/node/java-cli-contribution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/********************************************************************************
* Copyright (C) 2018 TypeFox 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 { injectable } from 'inversify';
import { Argv, Arguments } from 'yargs';
import { CliContribution } from '@theia/core/src/node/cli';

@injectable()
export class JavaCliContribution implements CliContribution {

private static LS_PORT = 'java-ls';

protected _lsPort: number | undefined;

configure(conf: Argv): void {
conf.option(JavaCliContribution.LS_PORT, {
description: 'Can be specified if the backend should not start the Java LS process but create a socket server and wait until the Java LS connects.',
type: 'number',
nargs: 1
});
}

setArguments(args: Arguments): void {
this.setLsPort(args[JavaCliContribution.LS_PORT]);
}

lsPort(): number | undefined {
return this._lsPort;
}

// tslint:disable-next-line:no-any
protected setLsPort(port: any): void {
if (port !== undefined) {
const error = new Error(`The port for the Java LS must be an integer between 1 and 65535. It was: ${port}.`);
if (!Number.isInteger(port)) {
throw error;
}
if (port < 1 || port > 65535) {
throw error;
}
this._lsPort = port;
}
}

}
Loading

0 comments on commit ceb74d2

Please sign in to comment.