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

Lsp edit #1

Merged
merged 3 commits into from
Nov 2, 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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sprotty-theia",
"version": "0.1.18",
"version": "0.1.19",
"description": "Glue code for Sprotty diagrams in a Theia IDE",
"license": "(EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0)",
"keywords": [
Expand Down Expand Up @@ -32,7 +32,7 @@
"@theia/filesystem": "next",
"@theia/languages": "next",
"@theia/monaco": "next",
"sprotty": "^0.4.0"
"sprotty": "^0.4.2"
},
"devDependencies": {
"@types/chai": "^4.1.3",
Expand Down
122 changes: 122 additions & 0 deletions src/sprotty/languageserver/code-action-palette.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/********************************************************************************
* 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 { inject, injectable } from "inversify";
import { Action, EMPTY_ROOT, HtmlRootSchema, PopupHoverMouseListener, RequestPopupModelAction,
SButton, SButtonSchema, SetPopupModelAction, SModelElement, SModelElementSchema, SModelRootSchema } from "sprotty/lib";
import { TheiaDiagramServerProvider, IRootPopupModelProvider } from '../theia-diagram-server';
import { toLsRange } from './ranged';
import { CodeAction, CodeActionParams, CodeActionRequest, Range } from '@theia/languages/lib/browser';
import { WorkspaceEditAction } from "./workspace-edit-command";

@injectable()
export class CodeActionProvider {

@inject(TheiaDiagramServerProvider) diagramServerProvider: TheiaDiagramServerProvider;

async getCodeActions(range: Range, codeActionKind: string) {
const diagramServer = await this.diagramServerProvider();
const connector = await diagramServer.getConnector();
const languageClient = await connector.getLanguageClient();
return await languageClient.sendRequest(CodeActionRequest.type, <CodeActionParams>{
textDocument: {
uri: diagramServer.getSourceUri()
},
range,
context: {
diagnostics: [],
only: [codeActionKind]
}
});
}
}

/**
* A popup-palette based on code actions.
*/
@injectable()
export class CodeActionPalettePopupProvider implements IRootPopupModelProvider {

@inject(CodeActionProvider) codeActionProvider: CodeActionProvider;

async getPopupModel(action: RequestPopupModelAction, rootElement: SModelRootSchema): Promise<SModelElementSchema | undefined> {
const rangeString: string = (rootElement as any)['range'];
if (rangeString !== undefined) {
const range = toLsRange(rangeString);
const codeActions = await this.codeActionProvider.getCodeActions(range, 'sprotty.create');
if (codeActions) {
const buttons: PaletteButtonSchema[] = [];
codeActions.forEach(codeAction => {
if (CodeAction.is(codeAction)) {
buttons.push(<PaletteButtonSchema>{
id: codeAction.title,
type: 'button:create',
codeActionKind: codeAction.kind,
range
});
}
});
return <HtmlRootSchema>{
id: "palette",
type: "palette",
classes: ['sprotty-palette'],
children: buttons,
canvasBounds: action.bounds
};
}
}
return undefined;
}
}

export interface PaletteButtonSchema extends SButtonSchema {
codeActionKind: string;
range: Range;
}

export class PaletteButton extends SButton {
codeActionKind: string;
range: Range;
}

@injectable()
export class PaletteMouseListener extends PopupHoverMouseListener {

@inject(CodeActionProvider) codeActionProvider: CodeActionProvider;
@inject(TheiaDiagramServerProvider) diagramServerProvider: TheiaDiagramServerProvider;

mouseDown(target: SModelElement, event: MouseEvent): (Action | Promise<Action>)[] {
if (target instanceof PaletteButton) {
return [this.getWorkspaceEditAction(target)];
}
return [];
}

async getWorkspaceEditAction(target: PaletteButton): Promise<Action> {
const diagramServer = await this.diagramServerProvider();
const workspace = diagramServer.getWorkspace();
if (workspace) {
const codeActions = await this.codeActionProvider.getCodeActions(target.range, target.codeActionKind);
if (codeActions) {
for (let codeAction of codeActions) {
if (CodeAction.is(codeAction) && codeAction.edit)
return new WorkspaceEditAction(codeAction.edit, workspace);
}
}
}
return new SetPopupModelAction(EMPTY_ROOT);
}
}
89 changes: 89 additions & 0 deletions src/sprotty/languageserver/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/********************************************************************************
* 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 { TextEdit, Workspace, WorkspaceEdit } from "@theia/languages/lib/browser";
import { Action, CommandExecutionContext, isSelectable, SEdge, Selectable, SModelElement, SModelRoot, SChildElement } from "sprotty/lib";
import { AbstractWorkspaceEditCommand } from "./workspace-edit-command";
import { toLsRange, isRanged, Ranged } from "./ranged";

export class DeleteWithWorkspaceEditCommand extends AbstractWorkspaceEditCommand {
static readonly KIND = 'deleteWithWorkspaceEdit'

constructor(readonly action: DeleteWithWorkspaceEditAction) {
super();
}

get workspace() {
return this.action.workspace;
}

createWorkspaceEdit(context: CommandExecutionContext) {
const elementsToBeDeleted = this.findElementsToBeDeleted(context.root);
const textEdits: TextEdit[] = [];
// TODO: consider URIs from element traces
elementsToBeDeleted.forEach(e => {
textEdits.push({
range: toLsRange(e.range),
newText: ''
});
})
const workspaceEdit: WorkspaceEdit = {
changes: {
[this.action.sourceUri]: textEdits
}
}
return workspaceEdit;
}

private findElementsToBeDeleted(root: SModelRoot) {
const elements = new Set<SModelElement & Ranged>();
const index = root.index;
index.all().forEach(e => {
if (e && this.shouldDelete(e))
elements.add(e);
else if (e instanceof SEdge && isRanged(e)) {
const source = index.getById(e.sourceId);
const target = index.getById(e.targetId);
if (this.shouldDeleteParent(source)
|| this.shouldDeleteParent(target))
elements.add(e);
}
});
return elements
}

private shouldDelete<T extends SModelElement>(e: T): e is (Ranged & Selectable & T) {
return isSelectable(e) && e.selected && isRanged(e);
}

private shouldDeleteParent(source: SModelElement | undefined): boolean {
while (source) {
if (this.shouldDelete(source)) {
return true;
}
source = (source instanceof SChildElement) ? source.parent : undefined;
}
return false;
}
}

export class DeleteWithWorkspaceEditAction implements Action {
readonly kind = DeleteWithWorkspaceEditCommand.KIND

// TODO: consider URIs from individual element traces
constructor(readonly workspace: Workspace, readonly sourceUri: string) {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a serializable action, right? Do we need to make a distinction between serializable and non-serializable actions?

}

21 changes: 21 additions & 0 deletions src/sprotty/languageserver/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/********************************************************************************
* 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
********************************************************************************/

export * from './workspace-edit-command';
export * from './ranged';
export * from './traceable';
export * from './workspace-edit-command';
export * from './code-action-palette';
40 changes: 40 additions & 0 deletions src/sprotty/languageserver/ranged.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/********************************************************************************
* 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 { SModelElement, SModelExtension } from "sprotty/lib";
import { Range } from "@theia/languages/lib/browser";

export interface Ranged extends SModelExtension {
range: string
}

export function isRanged<T extends SModelElement>(element: T): element is Ranged & T {
return (element as any).range !== undefined
}

export function toLsRange(rangeString: string): Range {
const numbers = rangeString.split(/[:-]/).map(s => parseInt(s, 10));
return <Range>{
start: {
line: numbers[0],
character: numbers[1]
},
end: {
line: numbers[2],
character: numbers[3]
}
};
}
27 changes: 27 additions & 0 deletions src/sprotty/languageserver/traceable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/********************************************************************************
* 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 { SModelElement, SModelExtension } from "sprotty/lib";

export const traceFeature = Symbol("traceFeature")

export interface Traceable extends SModelExtension {
trace: string
}

export function isTraceable<T extends SModelElement>(element: T): element is Traceable & T {
return element.hasFeature(traceFeature) && (element as any).trace !== null
}
66 changes: 66 additions & 0 deletions src/sprotty/languageserver/workspace-edit-command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/********************************************************************************
* 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 { Action, Command, CommandExecutionContext, CommandResult } from "sprotty/lib";
import { Workspace, WorkspaceEdit } from "@theia/languages/lib/browser";

export abstract class AbstractWorkspaceEditCommand extends Command {

abstract createWorkspaceEdit(context: CommandExecutionContext): WorkspaceEdit
abstract get workspace(): Workspace

protected workspaceEdit: WorkspaceEdit | undefined;

execute(context: CommandExecutionContext): CommandResult {
this.workspaceEdit = this.createWorkspaceEdit(context)
this.workspace.applyEdit(this.workspaceEdit);
return context.root;
}

undo(context: CommandExecutionContext): CommandResult {
// TODO implement revert workspace edit
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please create an issue for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#7

return context.root;
}

redo(context: CommandExecutionContext): CommandResult {
// TODO implement revert workspace edit
return context.root;
}
}

export class WorkspaceEditCommand extends AbstractWorkspaceEditCommand {
static readonly KIND = 'workspaceEdit';

constructor(readonly action: WorkspaceEditAction) {
super();
}

get workspace() {
return this.action.workspace;
}

createWorkspaceEdit(context: CommandExecutionContext) {
return this.action.workspaceEdit;
}
}

/**
* This is a client only action, so it does not have to be serializable
*/
export class WorkspaceEditAction implements Action {
readonly kind = WorkspaceEditCommand.KIND;
constructor(readonly workspaceEdit: WorkspaceEdit, readonly workspace: Workspace) {}
}
Loading