Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
11 changes: 7 additions & 4 deletions packages/client/src/base/action-dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { RequestAction } from '@eclipse-glsp/protocol';
import {
Action,
ActionDispatcher,
Expand All @@ -23,7 +24,6 @@ import {
GModelRoot,
IActionDispatcher,
RejectAction,
RequestAction,
ResponseAction,
SetModelAction,
TYPES
Expand Down Expand Up @@ -195,7 +195,7 @@ export class GLSPActionDispatcher extends ActionDispatcher implements IGModelRoo
}

override request<Res extends ResponseAction>(action: RequestAction<Res>): Promise<Res> {
if (!action.requestId && action.requestId === '') {
if (!action.requestId || action.requestId === '') {
// No request id has been specified. So we use a generated one.
action.requestId = RequestAction.generateRequestId();
}
Expand All @@ -214,13 +214,16 @@ export class GLSPActionDispatcher extends ActionDispatcher implements IGModelRoo
*/
requestUntil<Res extends ResponseAction>(
action: RequestAction<Res>,
timeoutMs = 2000,
timeoutMs: number = action.timeout ?? 2000,
rejectOnTimeout = false
): Promise<Res | undefined> {
if (!action.requestId && action.requestId === '') {
if (!action.requestId || action.requestId === '') {
// No request id has been specified. So we use a generated one.
action.requestId = RequestAction.generateRequestId();
}
// Stamp the effective timeout onto the action so the receiving side
// (handleServerRequest/handleClientRequest) can respect it.
action.timeout = timeoutMs;

const requestId = action.requestId;
const timeout = setTimeout(() => {
Expand Down
4 changes: 4 additions & 0 deletions packages/client/src/base/default.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { GetEditorContextAction } from '@eclipse-glsp/protocol';
import {
ActionHandlerRegistry,
FeatureModule,
GetSelectionAction,
KeyTool,
LocationPostprocessor,
MousePositionTracker,
Expand Down Expand Up @@ -83,6 +85,7 @@ export const defaultModule = new FeatureModule(

configureActionHandler(context, SetEditModeAction.KIND, EditorContextService);
configureActionHandler(context, SetDirtyStateAction.KIND, EditorContextService);
configureActionHandler(context, GetEditorContextAction.KIND, EditorContextService);

bind(FocusTracker).toSelf().inSingletonScope();
bind(TYPES.IDiagramStartup).toService(FocusTracker);
Expand Down Expand Up @@ -121,6 +124,7 @@ export const defaultModule = new FeatureModule(
bind(SelectionService).toSelf().inSingletonScope();
bind(TYPES.IGModelRootListener).toService(SelectionService);
bind(TYPES.IDiagramStartup).toService(SelectionService);
configureActionHandler(context, GetSelectionAction.KIND, SelectionService);

// Feedback Support ------------------------------------
// Generic re-usable feedback modifying css classes
Expand Down
14 changes: 12 additions & 2 deletions packages/client/src/base/editor-context-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { EditorContext, EditorContextResult, GetEditorContextAction } from '@eclipse-glsp/protocol';
import {
Action,
Args,
Bounds,
Disposable,
DisposableCollection,
EditMode,
EditorContext,
Emitter,
Event,
findParentByFeature,
Expand Down Expand Up @@ -177,6 +177,8 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra
return {
selectedElementIds: Array.from(this.selectionService.getSelectedElementIDs()),
lastMousePosition: this.mousePositionTracker.lastPositionOnDiagram,
viewport: this.viewportData,
canvasBounds: this.canvasBounds,
args
};
}
Expand All @@ -185,18 +187,26 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra
return {
selectedElementIds,
lastMousePosition: this.mousePositionTracker.lastPositionOnDiagram,
viewport: this.viewportData,
canvasBounds: this.canvasBounds,
args
};
}

handle(action: Action): void {
handle(action: Action): Action | void {
if (SetEditModeAction.is(action)) {
this.handleSetEditModeAction(action);
} else if (SetDirtyStateAction.is(action)) {
this.handleSetDirtyStateAction(action);
} else if (GetEditorContextAction.is(action)) {
return this.handleGetEditorContext(action);
}
}

protected handleGetEditorContext(action: GetEditorContextAction): EditorContextResult {
return EditorContextResult.create(this.get(), { responseId: action.requestId });
}

protected handleSetEditModeAction(action: SetEditModeAction): void {
const oldValue = this._editMode;
this._editMode = action.editMode;
Expand Down
34 changes: 34 additions & 0 deletions packages/client/src/base/model/glsp-model-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { RequestAction } from '@eclipse-glsp/protocol';
import {
Action,
ActionMessage,
Expand All @@ -22,10 +23,13 @@ import {
DisposeClientSessionParameters,
GLSPClient,
GModelRootSchema,
IActionDispatcher,
ILogger,
InitializeClientSessionParameters,
InitializeResult,
ModelSource,
RejectAction,
ResponseAction,
TYPES
} from '@eclipse-glsp/sprotty';
import { inject, injectable, preDestroy } from 'inversify';
Expand Down Expand Up @@ -94,6 +98,8 @@ export class GLSPModelSource extends ModelSource implements Disposable {
@inject(TYPES.IDiagramOptions)
protected options: IDiagramOptions;

declare readonly actionDispatcher: IActionDispatcher;

protected toDispose = new DisposableCollection();
clientId: string;

Expand Down Expand Up @@ -164,9 +170,37 @@ export class GLSPModelSource extends ModelSource implements Disposable {
const action = message.action;
ServerAction.mark(action);
this.logger.log(this, 'receiving', action);
if (RequestAction.is(action)) {
this.handleServerRequest(action);
return;
}
this.actionDispatcher.dispatch(action);
}

// Fire-and-forget: intentionally not awaited by messageReceived()
protected async handleServerRequest(action: RequestAction<ResponseAction>): Promise<void> {
try {
const response =
action.timeout !== undefined
? await this.actionDispatcher.requestUntil(action, action.timeout, true)
: await this.actionDispatcher.request(action);
if (response) {
Comment thread
tortmayr marked this conversation as resolved.
this.sendResponseToServer(response);
}
} catch (error) {
this.logger.error(this, `Failed to handle server request '${action.kind}' (${action.requestId})`, error);
const reject = RejectAction.create(`Failed to handle request '${action.kind}' (${action.requestId})`, {
responseId: action.requestId,
detail: error?.toString?.()
});
this.sendResponseToServer(reject);
}
}

protected sendResponseToServer(response: ResponseAction): void {
this.forwardToServer(response);
}

override initialize(registry: GLSPActionHandlerRegistry): void {
// Registering actions here is discouraged and it's recommended
// to implemented dedicated action handlers.
Expand Down
11 changes: 10 additions & 1 deletion packages/client/src/base/selection-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@ import {
Emitter,
Event,
GChildElement,
GetSelectionAction,
GModelElement,
GModelRoot,
IActionHandler,
ILogger,
LazyInjector,
SelectAction,
SelectAllAction,
SelectionResult,
SprottySelectAllCommand,
SprottySelectCommand,
TYPES,
Expand Down Expand Up @@ -60,7 +63,7 @@ export interface SelectionChange {
}

@injectable()
export class SelectionService implements IGModelRootListener, Disposable, IDiagramStartup {
export class SelectionService implements IGModelRootListener, IActionHandler, Disposable, IDiagramStartup {
@inject(TYPES.IFeedbackActionDispatcher)
protected feedbackDispatcher: IFeedbackActionDispatcher;

Expand All @@ -84,6 +87,12 @@ export class SelectionService implements IGModelRootListener, Disposable, IDiagr
this.lazyInjector.getAll<ISelectionListener>(TYPES.ISelectionListener).forEach(listener => this.addListener(listener));
}

handle(action: Action): Action | void {
if (GetSelectionAction.is(action)) {
return SelectionResult.create(this.getSelectedElementIDs(), action.requestId);
}
}

@preDestroy()
dispose(): void {
this.toDispose.dispose();
Expand Down
6 changes: 6 additions & 0 deletions packages/protocol/src/action-protocol/base-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ export interface RequestAction<Res extends ResponseAction> extends Action, sprot
* Unique id for this request. In order to match a response to this request, the response needs to have the same id.
*/
requestId: string;
/**
* Optional timeout in milliseconds. When set, `requestUntil()` uses this value instead of its
* default timeout. This allows the sender to control how long the receiver waits for a response.
* Precedence: explicit `timeoutMs` parameter > `action.timeout` > default (2000ms).
*/
timeout?: number;
/**
* Used to ensure correct typing. Clients must not use this property
*/
Expand Down
64 changes: 64 additions & 0 deletions packages/protocol/src/action-protocol/contexts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,67 @@ export namespace SetContextActions {
};
}
}

/**
* Sent from the server to the client to request the current {@link EditorContext}. This is the server-initiated
* counterpart to the `editorContext` parameter that is included in many client-to-server requests.
*
* All fields in the response represent a snapshot of the client state at the time the response is generated.
* There is no guarantee that the state has not changed by the time the server processes the response.
*
* If you only need a subset of the editor context, consider using a more specific action instead:
* - For selected elements only, use `GetSelectionAction`.
* - For viewport and canvas bounds only, use `GetViewportAction`.
*/
export interface GetEditorContextAction extends RequestAction<EditorContextResult> {
kind: typeof GetEditorContextAction.KIND;
}

export namespace GetEditorContextAction {
export const KIND = 'getEditorContext';

export function is(object: unknown): object is GetEditorContextAction {
return RequestAction.hasKind(object, KIND);
}

export function create(options: { requestId?: string; timeout?: number } = {}): GetEditorContextAction {
return {
kind: KIND,
requestId: '',
...options
};
}
}

/**
* Response to a {@link GetEditorContextAction} containing a snapshot of the client-side editor state.
*
* All fields in the {@link EditorContext} reflect the state at the time of response generation.
* The server should not assume that these values are still current when processing the response,
* as the client state may have changed in the meantime.
*/
export interface EditorContextResult extends ResponseAction {
kind: typeof EditorContextResult.KIND;

/**
* The editor context snapshot.
*/
readonly editorContext: EditorContext;
}

export namespace EditorContextResult {
export const KIND = 'editorContextResult';

export function is(object: unknown): object is EditorContextResult {
return Action.hasKind(object, KIND) && hasObjectProp(object, 'editorContext');
}

export function create(editorContext: EditorContext, options: { responseId?: string } = {}): EditorContextResult {
return {
kind: KIND,
responseId: '',
editorContext,
...options
};
}
}
12 changes: 11 additions & 1 deletion packages/protocol/src/action-protocol/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import * as sprotty from 'sprotty-protocol';
import { Dimension, Point } from 'sprotty-protocol';
import { Bounds, Dimension, Point, Viewport } from 'sprotty-protocol';
import { GModelElementSchema } from '../model/model-schema';
import { AnyObject, hasArrayProp, hasStringProp } from '../utils/type-util';
import { Action } from './base-protocol';
Expand Down Expand Up @@ -121,6 +121,16 @@ export interface EditorContext {
*/
readonly lastMousePosition?: Point;

/**
* The current viewport (scroll position and zoom level).
*/
readonly viewport?: Viewport;

/**
* The bounds of the canvas element in the browser.
*/
readonly canvasBounds?: Bounds;

/**
* Custom arguments.
*/
Expand Down
Loading