当前位置: 首页 > 工具软件 > Theia > 使用案例 >

Theia 拓展开发常用代码总结

封烈
2023-12-01

前言

Theia 目前提供的开发文档中对于 API 的介绍不太详细,缺少可以直接执行的示例,新手在新功能开发中不太容易理解,本文将阅读源码过程的一些代码片段摘出来进行归纳总结,通过局部的代码片段窥探基于 Theia 如何定制 IDE。

获取工程路径

前端:

import { WorkspaceService } from '@theia/workspace/lib/browser';

private getCurrentWorkspaceUri(): URI | undefined {
    return this.workspaceService.workspace?.resource;
}

后端:

import { WorkspaceServer } from '@theia/workspace/lib/common';

private getProjectPath(): string | undefined {
  const projectPath = await this.workspaceServer.getMostRecentlyUsedWorkspace();
  if (projectPath) {
    return new URI(projectPath).path.toString();
  }
}

打开文件选择框

类似于 Electron showOpenDialog,自动根据运行环境切换。

import { FileDialogService } from '@theia/filesystem/lib/browser';

@inject(FileDialogService)
protected readonly fileDialogService: FileDialogService;

const uri = await this.fileDialogService.showOpenDialog({
  title: '标题',
  canSelectFiles: true,
  canSelectFolders: false
});

获取后端接口信息

前端拓展获取服务接口信息:

import { Endpoint } from '@theia/core/lib/browser/endpoint';
// 获取 http://localhost:{port}/files
new Endpoint({ path: 'files' }).getRestUrl().toString();

Tips: IDE 前端启动过程中获取到后端服务 address 信息后,将端口设置在 location.search 上。

后端拓展获取端口信息:

@injectable()
export class SimulatorEndpoint implements BackendApplicationContribution {
        onStart(server: http.Server | https.Server) {
        console.log('address: ', server.address());
    }
}

监听视图的添加与隐藏

this.shell.onDidAddWidget((widget: Widget) => {
  console.log('add widget: ', widget.id);
});
this.shell.onDidRemoveWidget((widget: Widget) => {
    console.log('remove widget: ', widget.id);
});

自定义配置文件目录

settings.jsonkeymaps.jsonrecentworkspace.json 等配置文件的根目录默认地址是 ~/.theia,可以通过复写 EnvVariablesServer API 的 getConfigDirUri 方法进行自定义,最简单的方法是继承EnvVariablesServerImpl 子类,然后将其重新绑定到 backend 模块:

// your-env-variables-server.ts:

import { injectable } from 'inversify';
import { EnvVariablesServerImpl } from '@theia/core/lib/node/env-variables';

@injectable()
export class YourEnvVariableServer extends EnvVariablesServerImpl {
    async getConfigDirUri(): Promise<string> {
        return 'file:///path/to/your/desired/config/dir';
    }
}

// your-backend-application-module.ts:

import { ContainerModule } from 'inversify';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { YourEnvVariableServer } from './your-env-variables-server';

export default new ContainerModule((bind, unbind, isBound, rebind) => {
    rebind(EnvVariablesServer).to(YourEnvVariableServer).inSingletonScope();
});

v1.5.0 有更加简单的配置方式,通过配置 process.env.THEIA_CONFIG_DIR 字段。

 

前端工程的配置修改方式:

rebind(PreferenceConfigurations).to(class extends PreferenceConfigurations {
  getPaths(): string[] {
    return [CUSTOM_CONFIG_DIR, '.theia', '.vscode'];
    }
}).inSingletonScope();

自定义编辑器视图

Theia 自定义编辑器视图比 VS Code 更简单,有两种实现方式:

  • 继承抽象类 WidgetOpenHandler,复写 createWidgetOptions 和 canHandle 方法关联 widget
  • 实现 OpenHandler 接口的 canHandle 和 open 方法

module 注册服务:

bind(ProjectConfigWidget).toSelf();
bind(WidgetFactory).toDynamicValue(ctx => ({
    id: ProjectConfigWidget.ID,
    createWidget: () => ctx.container.get<ProjectConfigWidget>(ProjectConfigWidget)
})).inSingletonScope();

bind(ProjectConfigOpenHandler).toSelf().inSingletonScope();
bind(OpenHandler).toService(ProjectConfigOpenHandler);
// project-config-open-handler.ts
@injectable()
export class ProjectConfigOpenHandler extends WidgetOpenHandler<ProjectConfigWidget> {
    readonly id = ProjectConfigWidget.ID;
    readonly label?: string = 'Preview';
    private defaultFileName: string = 'project.config.json';

    @inject(ApplicationShell)
    protected readonly shell: ApplicationShell;

    protected createWidgetOptions(uri: URI): Object {
        return {};
    }

    canHandle(uri: URI): number {
        if (uri.path.toString().includes(this.defaultFileName)) {
            return 1000;
        }
        return 0;
    }
}

面板中 iframe、WebView 无法响应鼠标点击事件

import { ApplicationShellMouseTracker } from '@theia/core/lib/browser/shell/application-shell-mouse-tracker';

@inject(ApplicationShellMouseTracker)
protected readonly mouseTracker: ApplicationShellMouseTracker;

// Style from core
const TRANSPARENT_OVERLAY_STYLE = 'theia-transparent-overlay';

@postConstruct()
protected init(): void {
  this.frame = this.createWebView();
  this.transparentOverlay = this.createTransparentOverlay();
  this.node.appendChild(this.frame);
  this.node.appendChild(this.transparentOverlay);
    this.toDispose.push(this.mouseTracker.onMousedown(e => {
        if (this.frame.style.display !== 'none') {
            this.transparentOverlay.style.display = 'block';
        }
    }));
    this.toDispose.push(this.mouseTracker.onMouseup(e => {
        if (this.frame.style.display !== 'none') {
            this.transparentOverlay.style.display = 'none';
        }
    }));
}

createWebView(): Electron.WebviewTag {
  const webview = document.createElement('webview') as Electron.WebviewTag;
  ...
  return webview;
}

createTransparentOverlay() {
  const transparentOverlay = document.createElement('div');
  transparentOverlay.classList.add(TRANSPARENT_OVERLAY_STYLE);
  transparentOverlay.style.display = 'none';
  return transparentOverlay;
}

检查编辑器是否可以保存

import { EditorManager, EditorWidget } from '@theia/editor/lib/browser';

@inject(EditorManager)
protected readonly editorManager: EditorManager;

/**
 * 检查编辑器是否可以保存
 */
checkEditorSaveable() {
  let isDirty = false;
  const trackedEditors: EditorWidget[] = this.editorManager.all;
  for (let widget of trackedEditors) {
    isDirty = widget.saveable.dirty;
    if (isDirty) break;
  }
  return isDirty;
}
 类似资料: