@eclipse-glsp/client
Version:
A sprotty-based client for GLSP
171 lines (149 loc) • 6.9 kB
text/typescript
/********************************************************************************
* Copyright (c) 2023-2025 Business Informatics Group (TU Wien) 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,
codiconCSSString,
CreateEdgeOperation,
GModelElement,
GModelRoot,
IActionHandler,
isConnectable,
LabeledAction,
name,
SetUIExtensionVisibilityAction,
toArray,
TriggerEdgeCreationAction
} from '@eclipse-glsp/sprotty';
import { injectable } from 'inversify';
import { CloseReason, toActionArray } from '../../../base/auto-complete/auto-complete-widget';
import { IAutocompleteSuggestionProvider, type AutocompleteSuggestion } from '../../../base/auto-complete/autocomplete-suggestion-provider';
import { EnableDefaultToolsAction } from '../../../base/tool-manager/tool';
import { GEdge } from '../../../model';
import { SearchAutocompletePalette, SearchAutocompleteSuggestion } from '../../search-palette/search-palette';
import { SetEdgeTargetSelectionAction } from './action';
export namespace EdgeAutocompletePaletteMetadata {
export const ID = 'edge-autocomplete-palette';
}
export interface EdgeAutocompleteContext {
role: 'source' | 'target';
triggerAction: TriggerEdgeCreationAction;
sourceId?: string;
targetId?: string;
}
export type EdgeAutocompletePaletteStages = 'initial' | 'source' | 'target' | 'reloading';
export class EdgeAutocompletePalette extends SearchAutocompletePalette implements IActionHandler {
protected edgeAutocompleteContext?: EdgeAutocompleteContext;
protected stage: EdgeAutocompletePaletteStages = 'initial';
override id(): string {
return EdgeAutocompletePaletteMetadata.ID;
}
override get searchContext(): string[] {
return [EdgeAutocompletePaletteMetadata.ID];
}
handle(action: Action): Action | void {
if (TriggerEdgeCreationAction.is(action)) {
this.stage = 'source';
this.edgeAutocompleteContext = {
triggerAction: action,
role: 'source'
};
}
}
protected override onBeforeShow(containerElement: HTMLElement, root: Readonly<GModelRoot>, ...contextElementIds: string[]): void {
super.onBeforeShow(containerElement, root, ...contextElementIds);
this.autocompleteWidget.inputField.placeholder = `Search for ${this.edgeAutocompleteContext?.role} elements`;
}
protected async reload(): Promise<void> {
this.stage = 'reloading';
this.hide();
}
protected override async provideSearchSuggestions(root: Readonly<GModelRoot>, input: string): Promise<SearchAutocompleteSuggestion[]> {
return (
await Promise.all(
this.suggestionRegistry
.providersForContext(this.searchContext)
.flatMap(provider => provider.getSuggestions(root, input, this.edgeAutocompleteContext))
)
)
.flat(1)
.filter(SearchAutocompleteSuggestion.is);
}
protected override async executeSuggestion(input: LabeledAction | Action[] | Action): Promise<void> {
const action = toActionArray(input)[0] as SetEdgeTargetSelectionAction;
if (this.edgeAutocompleteContext?.role === 'source') {
this.edgeAutocompleteContext.sourceId = action.elementId;
this.edgeAutocompleteContext.role = 'target';
this.reload();
} else if (this.edgeAutocompleteContext?.role === 'target') {
this.edgeAutocompleteContext.targetId = action.elementId;
}
if (this.edgeAutocompleteContext?.sourceId !== undefined && this.edgeAutocompleteContext?.targetId !== undefined) {
await this.actionDispatcher.dispatchAll([
CreateEdgeOperation.create({
elementTypeId: this.edgeAutocompleteContext.triggerAction.elementTypeId,
sourceElementId: this.edgeAutocompleteContext.sourceId,
targetElementId: this.edgeAutocompleteContext.targetId,
args: this.edgeAutocompleteContext.triggerAction.args
}),
EnableDefaultToolsAction.create()
]);
this.hide();
}
}
protected override autocompleteHide(reason: CloseReason): void {
if (this.stage === 'reloading') {
// Wait until the auto complete is closed before reloading
if (reason === 'blur') {
this.actionDispatcher.dispatch(
SetUIExtensionVisibilityAction.create({
extensionId: EdgeAutocompletePaletteMetadata.ID,
visible: true
})
);
this.stage = 'target';
}
} else if (reason !== 'submission') {
this.hide();
}
}
}
export class SetEdgeTargetGridSuggestionProvider implements IAutocompleteSuggestionProvider {
id = 'glsp.set-edge-target-autocomplete-suggestions';
canHandle(searchContext: string): boolean {
return searchContext === EdgeAutocompletePaletteMetadata.ID;
}
async getSuggestions(root: Readonly<GModelRoot>, text: string, context: EdgeAutocompleteContext): Promise<AutocompleteSuggestion[]> {
if (context === undefined) {
return [];
}
const proxyEdge = new GEdge();
proxyEdge.type = context.triggerAction.elementTypeId;
const nodes = toArray(root.index.all().filter(element => this.isAllowedSource(proxyEdge, element, context.role))) as GEdge[];
return nodes.map(node => ({
element: node,
action: {
label: `[${node.type}] ${name(node) ?? '<no-name>'}`,
actions: [SetEdgeTargetSelectionAction.create(node.id, context.role)],
icon: codiconCSSString('arrow-both')
}
}));
}
protected isAllowedSource(proxyEdge: GEdge, element: GModelElement | undefined, role: 'source' | 'target'): boolean {
return element !== undefined && proxyEdge !== undefined && isConnectable(element) && element.canConnect(proxyEdge, role);
}
}