Skip to content

Commit

Permalink
feat: editor state recover & lsp & search & session
Browse files Browse the repository at this point in the history
  • Loading branch information
zhanba committed Jan 18, 2024
1 parent a5b80d6 commit fe57df4
Show file tree
Hide file tree
Showing 34 changed files with 741 additions and 442 deletions.
66 changes: 27 additions & 39 deletions packages/libro-code-cell/src/code-cell-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
LibroExecutableCellView,
LibroOutputArea,
VirtualizedManagerHelper,
LirboContextKey,
} from '@difizen/libro-core';
import type { ViewSize } from '@difizen/mana-app';
import {
Expand Down Expand Up @@ -99,6 +100,7 @@ const CodeEditorViewComponent = forwardRef<HTMLDivElement>(
@transient()
@view('code-editor-cell-view')
export class LibroCodeCellView extends LibroExecutableCellView {
@inject(LirboContextKey) protected readonly lirboContextKey: LirboContextKey;
override view = CodeEditorViewComponent;

viewManager: ViewManager;
Expand All @@ -124,12 +126,6 @@ export class LibroCodeCellView extends LibroExecutableCellView {
@prop()
override editorStatus: EditorStatus = EditorStatus.NOTLOADED;

protected editorViewReadyDeferred: Deferred<void> = new Deferred<void>();

get editorReady() {
return this.editorViewReadyDeferred.promise;
}

protected outputAreaDeferred = new Deferred<LibroOutputArea>();
get outputAreaReady() {
return this.outputAreaDeferred.promise;
Expand Down Expand Up @@ -219,20 +215,16 @@ export class LibroCodeCellView extends LibroExecutableCellView {

override onViewMount() {
this.createEditor();
//选中cell时才focus
if (this.parent.model.active?.id === this.id) {
this.focus(!this.parent.model.commandMode);
}
}

setEditorHost(ref: any) {
const editorHostId = this.parent.id + this.id;

this.codeEditorManager.setEditorHostRef(editorHostId, ref);
}

protected getEditorOption(): CodeEditorViewOptions {
const option: CodeEditorViewOptions = {
uuid: `${this.parent.model.id}-${this.model.id}`,
editorHostId: this.parent.id + this.id,
model: this.model,
config: {
Expand All @@ -258,10 +250,14 @@ export class LibroCodeCellView extends LibroExecutableCellView {
const editorView = await this.codeEditorManager.getOrCreateEditorView(option);

this.editorView = editorView;
this.editorViewReadyDeferred.resolve();
this.editorStatus = EditorStatus.LOADED;

await this.afterEditorReady();
editorView.onEditorStatusChange((e) => {
if (e.status === 'ready') {
this.editor = this.editorView!.editor;
this.afterEditorReady();
}
});
}

protected async afterEditorReady() {
Expand All @@ -272,6 +268,23 @@ export class LibroCodeCellView extends LibroExecutableCellView {
);
});
this.editorView?.onModalChange((val) => (this.hasModal = val));
this.focusEditor();
}

protected focusEditor() {
//选中cell、编辑模式、非只读时才focus
if (
this.editorView?.editor &&
this.editorView.editorStatus === 'ready' &&
this.parent.model.active?.id === this.id &&
!this.parent.model.commandMode &&
this.lirboContextKey.commandModeEnabled === true && // 排除弹窗等情况
this.parent.model.readOnly === false
) {
this.editorView?.editor.setOption('styleActiveLine', true);
this.editorView?.editor.setOption('highlightActiveLineGutter', true);
this.editorView?.editor.focus();
}
}

override shouldEnterEditorMode(e: React.FocusEvent<HTMLElement>) {
Expand All @@ -287,32 +300,7 @@ export class LibroCodeCellView extends LibroExecutableCellView {

override focus = (toEdit: boolean) => {
if (toEdit) {
if (this.parent.model.readOnly === true) {
return;
}
if (!this.editorView) {
this.editorReady
.then(() => {
this.editorView?.editorReady.then(() => {
this.editorView?.editor?.setOption('styleActiveLine', true);
this.editorView?.editor?.setOption('highlightActiveLineGutter', true);
if (this.editorView?.editor?.hasFocus()) {
return;
}
this.editorView?.editor?.focus();
return;
});
return;
})
.catch(console.error);
} else {
this.editorView?.editor?.setOption('styleActiveLine', true);
this.editorView?.editor?.setOption('highlightActiveLineGutter', true);
if (this.editorView?.editor?.hasFocus()) {
return;
}
this.editorView?.editor?.focus();
}
this.focusEditor();
} else {
if (this.container?.current?.parentElement?.contains(document.activeElement)) {
return;
Expand Down
29 changes: 7 additions & 22 deletions packages/libro-code-editor/src/code-editor-manager.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,22 @@
import type { Contribution } from '@difizen/mana-app';
import {
Priority,
ViewManager,
contrib,
inject,
singleton,
Syringe,
} from '@difizen/mana-app';
import { Priority, ViewManager, contrib, inject, singleton } from '@difizen/mana-app';

import { CodeEditorInfoManager } from './code-editor-info-manager.js';
import type { IModel } from './code-editor-model.js';
import type { IEditor, IEditorConfig, IEditorOptions } from './code-editor-protocol.js';
import { CodeEditorContribution } from './code-editor-protocol.js';
import type { EditorState } from './code-editor-protocol.js';
import { CodeEditorSettings } from './code-editor-settings.js';
import type { CodeEditorViewOptions } from './code-editor-view.js';
import { CodeEditorView } from './code-editor-view.js';

/**
* A factory used to create a code editor.
*/
export type CodeEditorFactory = (options: IEditorOptions) => IEditor;

export const CodeEditorContribution = Syringe.defineToken('CodeEditorContribution');
export interface CodeEditorContribution {
canHandle(mime: string): number;
factory: CodeEditorFactory;
defaultConfig: IEditorConfig;
}

@singleton()
export class CodeEditorManager {
@contrib(CodeEditorContribution)
protected readonly codeEditorProvider: Contribution.Provider<CodeEditorContribution>;
@inject(ViewManager) protected readonly viewManager: ViewManager;
@inject(CodeEditorInfoManager) protected codeEditorInfoManager: CodeEditorInfoManager;
@inject(CodeEditorSettings) protected readonly codeEditorSettings: CodeEditorSettings;
protected stateCache: Map<string, EditorState> = new Map();

setEditorHostRef(id: string, ref: any) {
this.codeEditorInfoManager.setEditorHostRef(id, ref);
Expand Down Expand Up @@ -72,7 +55,9 @@ export class CodeEditorManager {
async getOrCreateEditorView(option: CodeEditorViewOptions): Promise<CodeEditorView> {
const factory = this.findCodeEditorProvider(option.model)?.factory;
if (!factory) {
throw new Error(`no code editor found for mimetype: ${option.model.mimeType}`);
throw new Error(
`no code editor factory registered for mimetype: ${option.model.mimeType}`,
);
}
const editorView = await this.viewManager.getOrCreateView<
CodeEditorView,
Expand Down
64 changes: 54 additions & 10 deletions packages/libro-code-editor/src/code-editor-protocol.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { JSONObject } from '@difizen/libro-common';
import type { JSONObject, JSONValue } from '@difizen/libro-common';
import type { Disposable, Event, ThemeType } from '@difizen/mana-app';
import { Syringe } from '@difizen/mana-app';

import type { IModel } from './code-editor-model.js';

Expand Down Expand Up @@ -203,7 +204,8 @@ export type EdgeLocation = 'top' | 'topLine' | 'bottom';
/**
* A widget that provides a code editor.
*/
export interface IEditor extends ISelectionOwner, Disposable {
export interface IEditor<S = any> extends ISelectionOwner, Disposable {
editorReady: Promise<void>;
/**
* A signal emitted when either the top or bottom edge is requested.
*/
Expand Down Expand Up @@ -390,6 +392,8 @@ export interface IEditor extends ISelectionOwner, Disposable {
onModalChange: Event<boolean>;

dispose: () => void;

getState: () => EditorState<S>;
}

export type EditorTheme = Record<ThemeType, string>;
Expand Down Expand Up @@ -560,14 +564,9 @@ export type CompletionProvider = (
) => Promise<CompletionReply>;

/**
* The options used to initialize an editor.
* The options used to initialize an editor state.
*/
export interface IEditorOptions {
/**
* The host widget used by the editor.
*/
host: HTMLElement;

export interface IEditorStateOptions {
/**
* The model used by the editor.
*/
Expand All @@ -576,7 +575,17 @@ export interface IEditorOptions {
/**
* The desired uuid for the editor.
*/
uuid?: string;
uuid: string;
}

/**
* The options used to initialize an editor.
*/
export interface IEditorOptions extends IEditorStateOptions {
/**
* The host widget used by the editor.
*/
host: HTMLElement;

/**
* The default selection style for the editor.
Expand Down Expand Up @@ -612,3 +621,38 @@ export interface SearchMatch {
*/
position: number;
}

export interface EditorState<T = any> {
// monaco model or codemirror state or other editor state
state: T;
cursorPosition?: IPosition;
selections?: IRange[];
toJSON: () => JSONValue;
dispose: (state: T) => void;
}

export type EditorStateFactory<T = any> = (
options: IEditorStateOptions,
) => EditorState<T>;

/**
* A factory used to create a code editor.
*/
export type CodeEditorFactory<T = EditorState> = (
options: IEditorOptions,
state?: T,
) => IEditor<T>;

export const CodeEditorContribution = Syringe.defineToken('CodeEditorContribution');
export interface CodeEditorContribution<T = any> {
canHandle(mime: string): number;
/**
* editor factory
*/
factory: CodeEditorFactory<T>;
/**
* editor state factory
*/
stateFactory?: EditorStateFactory<T>;
defaultConfig: IEditorConfig;
}
54 changes: 54 additions & 0 deletions packages/libro-code-editor/src/code-editor-state-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { Contribution } from '@difizen/mana-app';
import { Priority } from '@difizen/mana-app';
import { contrib } from '@difizen/mana-app';
import { singleton } from '@difizen/mana-app';

import type { IModel } from './code-editor-model.js';
import type { EditorState, IEditorStateOptions } from './code-editor-protocol.js';
import { CodeEditorContribution } from './code-editor-protocol.js';

@singleton()
export class CodeEditorStateManager {
protected readonly codeEditorProvider: Contribution.Provider<CodeEditorContribution>;
protected stateCache: Map<string, EditorState> = new Map();

constructor(
@contrib(CodeEditorContribution)
codeEditorProvider: Contribution.Provider<CodeEditorContribution>,
) {
this.codeEditorProvider = codeEditorProvider;
}

protected findCodeEditorProvider(model: IModel) {
const prioritized = Priority.sortSync(
this.codeEditorProvider.getContributions(),
(contribution) => contribution.canHandle(model.mimeType),
);
const sorted = prioritized.map((c) => c.value);
return sorted[0];
}

async getOrCreateEditorState(option: IEditorStateOptions): Promise<EditorState> {
if (this.stateCache.has(option.uuid)) {
const state = this.stateCache.get(option.uuid)!;
return state;
}
const factory = this.findCodeEditorProvider(option.model)?.stateFactory;
if (!factory) {
throw new Error(
`no code editor state factory registered for mimetype: ${option.model.mimeType}`,
);
}
const state = factory(option);
this.stateCache.set(option.uuid, state);
return state;
}

updateEditorState(id: string, state: EditorState) {
this.stateCache.set(id, state);
}

removeEditorState(id: string) {
this.stateCache.delete(id);
}
}
Loading

0 comments on commit fe57df4

Please sign in to comment.