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 <kittaakos@typefox.io>
  • Loading branch information
Akos Kitta committed Jul 18, 2018
1 parent 4ba93d6 commit 3419c3f
Show file tree
Hide file tree
Showing 12 changed files with 408 additions and 33 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();
});
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
33 changes: 30 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,20 @@ export class JavaClientContribution extends BaseLanguageClientContribution {
}, 5000);
}

protected applySemanticHighlighting(params: SemanticHighlightingParams): void {
const toRanges: (tuple: [number, SemanticHighlightingToken[]]) => SemanticHighlightingRange[] = tuple => {
const [line, tokens] = tuple;
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 +113,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');
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,42 @@
********************************************************************************/

import { injectable } from 'inversify';
import { JAVA_LANGUAGE_ID } from '../../common';
// import { JAVA_LANGUAGE_ID } from '../../common';
import { LanguageGrammarDefinitionContribution, TextmateRegistry } from '@theia/monaco/lib/browser/textmate';

@injectable()
export class JavaTextmateContribution implements LanguageGrammarDefinitionContribution {

// TODO: restore this!
registerTextmateLanguage(registry: TextmateRegistry) {
const javaDocGrammar = require('../../../data/javadoc.tmlanguage.json');
registry.registerTextMateGrammarScope('text.html.javadoc', {
async getGrammarDefinition() {
return {
format: 'json',
content: javaDocGrammar
};
}
});
const scope = 'source.java';
const javaGrammar = require('../../../data/java.tmlanguage.json');
registry.registerTextMateGrammarScope(scope, {
async getGrammarDefinition() {
return {
format: 'json',
content: javaGrammar
};
}
});
// registry.registerTextMateGrammarScope('text.html.basic', {
// async getGrammarDefinition() {
// return {
// format: 'json',
// content: '{}'
// };
// }
// });
// const javaDocGrammar = require('../../../data/javadoc.tmlanguage.json');
// registry.registerTextMateGrammarScope('text.html.javadoc', {
// async getGrammarDefinition() {
// return {
// format: 'json',
// content: javaDocGrammar
// };
// }
// });
// const scope = 'source.java';
// const javaGrammar = require('../../../data/java.tmlanguage.json');
// registry.registerTextMateGrammarScope(scope, {
// async getGrammarDefinition() {
// return {
// format: 'json',
// content: javaGrammar
// };
// }
// });

registry.mapLanguageIdToTextmateGrammar(JAVA_LANGUAGE_ID, scope);
// registry.mapLanguageIdToTextmateGrammar(JAVA_LANGUAGE_ID, scope);
}
}
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;
}
}

}
33 changes: 26 additions & 7 deletions packages/java/src/node/java-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
import * as os from 'os';
import * as path from 'path';
import * as glob from 'glob';
import { injectable } from "inversify";
import { Socket } from 'net';
import { injectable, inject } from "inversify";
import { createSocketConnection } from 'vscode-ws-jsonrpc/lib/server';
import { DEBUG_MODE } from '@theia/core/lib/node';
import { IConnection, BaseLanguageServerContribution } from "@theia/languages/lib/node";
import { JAVA_LANGUAGE_ID, JAVA_LANGUAGE_NAME } from '../common';
import { JavaCliContribution } from './java-cli-contribution';

export type ConfigurationType = 'config_win' | 'config_mac' | 'config_linux';
export const configurations = new Map<typeof process.platform, ConfigurationType>();
Expand All @@ -31,10 +34,23 @@ configurations.set('linux', 'config_linux');
@injectable()
export class JavaContribution extends BaseLanguageServerContribution {

@inject(JavaCliContribution)
protected readonly cli: JavaCliContribution;

readonly id = JAVA_LANGUAGE_ID;
readonly name = JAVA_LANGUAGE_NAME;

start(clientConnection: IConnection): void {

const socketPort = this.cli.lsPort();
if (socketPort) {
const socket = new Socket();
const serverConnection = createSocketConnection(socket, socket, () => socket.destroy());
this.forward(clientConnection, serverConnection);
socket.connect(socketPort);
return;
}

const serverPath = path.resolve(__dirname, '..', '..', 'server');
const jarPaths = glob.sync('**/plugins/org.eclipse.equinox.launcher_*.jar', { cwd: serverPath });
if (jarPaths.length === 0) {
Expand All @@ -49,17 +65,20 @@ export class JavaContribution extends BaseLanguageServerContribution {
}
const configurationPath = path.resolve(serverPath, configuration);
const command = 'java';
const args = [
const args: string[] = [];

if (DEBUG_MODE) {
args.push('-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=1044');
}

args.push(...[
'-Declipse.application=org.eclipse.jdt.ls.core.id1',
'-Dosgi.bundles.defaultStartLevel=4',
'-Declipse.product=org.eclipse.jdt.ls.core.product'
];
]);

if (DEBUG_MODE) {
args.push(
'-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1044',
'-Dlog.level=ALL'
);
args.push('-Dlog.level=ALL');
}

args.push(
Expand Down
Loading

0 comments on commit 3419c3f

Please sign in to comment.