From dab3f585a48f73726afe6b6b1e8d937c8f4faa36 Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Wed, 16 Oct 2024 14:09:12 -0700 Subject: [PATCH 1/5] some basic clean up --- .../code-editor.component.ts | 291 ++++++++---------- 1 file changed, 124 insertions(+), 167 deletions(-) diff --git a/core/gui/src/app/workspace/component/code-editor-dialog/code-editor.component.ts b/core/gui/src/app/workspace/component/code-editor-dialog/code-editor.component.ts index 71e27e3db5b..16d6469ecfe 100644 --- a/core/gui/src/app/workspace/component/code-editor-dialog/code-editor.component.ts +++ b/core/gui/src/app/workspace/component/code-editor-dialog/code-editor.component.ts @@ -5,23 +5,23 @@ import { WorkflowVersionService } from "../../../dashboard/service/user/workflow import { YText } from "yjs/dist/src/types/YText"; import { getWebsocketUrl } from "src/app/common/util/url"; import { MonacoBinding } from "y-monaco"; -import { Subject, take } from "rxjs"; -import { takeUntil } from "rxjs/operators"; +import { from, Subject, take } from "rxjs"; import { CoeditorPresenceService } from "../../service/workflow-graph/model/coeditor-presence.service"; import { DomSanitizer, SafeStyle } from "@angular/platform-browser"; import { Coeditor } from "../../../common/type/user"; import { YType } from "../../types/shared-editing.interface"; -import { isUndefined } from "lodash"; import { FormControl } from "@angular/forms"; import { AIAssistantService, TypeAnnotationResponse } from "../../service/ai-assistant/ai-assistant.service"; import { AnnotationSuggestionComponent } from "./annotation-suggestion.component"; import { MonacoEditorLanguageClientWrapper, UserConfig } from "monaco-editor-wrapper"; import * as monaco from "monaco-editor"; -import { from } from "rxjs"; import "@codingame/monaco-vscode-python-default-extension"; import "@codingame/monaco-vscode-r-default-extension"; import "@codingame/monaco-vscode-java-default-extension"; +import { isDefined } from "../../../common/util/predicate"; +import { editor } from "vscode/editor.api"; +import IStandaloneCodeEditor = editor.IStandaloneCodeEditor; /** * CodeEditorComponent is the content of the dialogue invoked by CodeareaCustomTemplateComponent. @@ -42,17 +42,16 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy @ViewChild("container", { static: true }) containerElement!: ElementRef; @ViewChild(AnnotationSuggestionComponent) annotationSuggestion!: AnnotationSuggestionComponent; private code?: YText; - private editor?: any; private workflowVersionStreamSubject: Subject = new Subject(); - private operatorID!: string; + private currentOperatorId!: string; public title: string | undefined; public formControl!: FormControl; public componentRef: ComponentRef | undefined; public language: string = ""; public languageTitle: string = ""; - private wrapper?: MonacoEditorLanguageClientWrapper; + private editorWrapper: MonacoEditorLanguageClientWrapper = new MonacoEditorLanguageClientWrapper(); private monacoBinding?: MonacoBinding; // Boolean to determine whether the suggestion UI should be shown @@ -68,18 +67,13 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy // For "Add All Type Annotation" to show the UI individually private userResponseSubject?: Subject; private isMultipleVariables: boolean = false; - private componentDestroy = new Subject(); private generateLanguageTitle(language: string): string { return `${language.charAt(0).toUpperCase()}${language.slice(1)} UDF`; } - changeLanguage(newLanguage: string) { + setLanguage(newLanguage: string) { this.language = newLanguage; - console.log("change to ", newLanguage); - if (this.editor) { - monaco.editor.setModelLanguage(this.editor.getModel(), newLanguage); - } this.languageTitle = this.generateLanguageTitle(newLanguage); } @@ -88,68 +82,61 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy private workflowActionService: WorkflowActionService, private workflowVersionService: WorkflowVersionService, public coeditorPresenceService: CoeditorPresenceService, - private aiAssistantService: AIAssistantService + private aiAssistantService: AIAssistantService, ) { - const currentOperatorId = this.workflowActionService.getJointGraphWrapper().getCurrentHighlightedOperatorIDs()[0]; - const operatorType = this.workflowActionService.getTexeraGraph().getOperator(currentOperatorId).operatorType; + this.currentOperatorId = this.workflowActionService.getJointGraphWrapper().getCurrentHighlightedOperatorIDs()[0]; + const operatorType = this.workflowActionService.getTexeraGraph().getOperator(this.currentOperatorId).operatorType; if (operatorType === "RUDFSource" || operatorType === "RUDF") { - this.changeLanguage("r"); + this.setLanguage("r"); } else if ( operatorType === "PythonUDFV2" || operatorType === "PythonUDFSourceV2" || operatorType === "DualInputPortsPythonUDFV2" ) { - this.changeLanguage("python"); + this.setLanguage("python"); } else { - this.changeLanguage("java"); + this.setLanguage("java"); } - } - - ngAfterViewInit() { this.workflowActionService.getTexeraGraph().updateSharedModelAwareness("editingCode", true); - this.operatorID = this.workflowActionService.getJointGraphWrapper().getCurrentHighlightedOperatorIDs()[0]; - this.title = this.workflowActionService.getTexeraGraph().getOperator(this.operatorID).customDisplayName; - const style = localStorage.getItem(this.operatorID); - if (style) this.containerElement.nativeElement.style.cssText = style; + this.title = this.workflowActionService.getTexeraGraph().getOperator(this.currentOperatorId).customDisplayName; this.code = ( this.workflowActionService .getTexeraGraph() - .getSharedOperatorType(this.operatorID) + .getSharedOperatorType(this.currentOperatorId) .get("operatorProperties") as YType> ).get("code") as YText; - console.log("added this code ", this.code); - + // start editor this.workflowVersionService .getDisplayParticularVersionStream() - .pipe(takeUntil(this.workflowVersionStreamSubject)) + .pipe(untilDestroyed(this)) .subscribe((displayParticularVersion: boolean) => { if (displayParticularVersion) { this.initDiffEditor(); } else { - this.initMonaco(); + this.initializeMonacoEditor(); } }); } + ngAfterViewInit() { + // hacky solution to reset view after view is rendered. + const style = localStorage.getItem(this.currentOperatorId); + if (style) this.containerElement.nativeElement.style.cssText = style; + } + ngOnDestroy(): void { this.workflowActionService.getTexeraGraph().updateSharedModelAwareness("editingCode", false); - localStorage.setItem(this.operatorID, this.containerElement.nativeElement.style.cssText); + localStorage.setItem(this.currentOperatorId, this.containerElement.nativeElement.style.cssText); - if (this.monacoBinding) { + if (isDefined(this.monacoBinding)) { this.monacoBinding.destroy(); } - if (this.wrapper) { - this.wrapper.dispose(true); - } + this.editorWrapper.dispose(true); - if (this.editor !== undefined) { - this.editor.dispose(); - } - - if (!isUndefined(this.workflowVersionStreamSubject)) { + if (isDefined(this.workflowVersionStreamSubject)) { this.workflowVersionStreamSubject.next(); this.workflowVersionStreamSubject.complete(); } @@ -184,9 +171,9 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy } } - private checkPythonLanguageServerAvailability(): Promise { + private checkLanguageServerAvailability(websocketUrl: string): Promise { return new Promise(resolve => { - const socket = new WebSocket(getWebsocketUrl("/python-language-server", "3000")); + const socket = new WebSocket(websocketUrl); socket.onopen = () => { socket.close(); @@ -199,98 +186,77 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy }); } - private initMonaco(): void { - if (this.wrapper) { - from(this.wrapper.dispose(true)) - .pipe(takeUntil(this.componentDestroy)) - .subscribe({ - next: () => { - if (this.componentRef) { - this.componentRef.destroy(); - } - - this.initializeMonacoEditor(); - }, - }); - } else { - this.initializeMonacoEditor(); - } - } - /** * Create a Monaco editor and connect it to MonacoBinding. * @private */ private initializeMonacoEditor() { - if (!this.wrapper && this.code) { - const fileSuffix = this.getFileSuffixByLanguage(this.language); - this.wrapper = new MonacoEditorLanguageClientWrapper(); - - const userConfig: UserConfig = { - wrapperConfig: { - editorAppConfig: { - $type: "extended", - codeResources: { - main: { - text: this.code.toString(), - uri: `in-memory-${this.operatorID}.${fileSuffix}`, - }, - }, - userConfiguration: { - json: JSON.stringify({ - "workbench.colorTheme": "Default Dark Modern", - }), + const fileSuffix = this.getFileSuffixByLanguage(this.language); + const userConfig: UserConfig = { + wrapperConfig: { + editorAppConfig: { + $type: "extended", + codeResources: { + main: { + text: this.code?.toString() ?? "", + uri: `in-memory-${this.currentOperatorId}.${fileSuffix}`, }, }, + userConfiguration: { + json: JSON.stringify({ + "workbench.colorTheme": "Default Dark Modern", + }), + }, }, - }; + }, + }; - from(this.checkPythonLanguageServerAvailability()) - .pipe(takeUntil(this.componentDestroy)) - .subscribe(isServerAvailable => { - if (isServerAvailable && this.language === "python") { - userConfig.languageClientConfig = { - languageId: "python", - options: { - $type: "WebSocketUrl", - url: getWebsocketUrl("/python-language-server", "3000"), - }, - }; - } + const languageServerWebsocketUrl = getWebsocketUrl("/python-language-server", "3000") + from(this.checkLanguageServerAvailability(languageServerWebsocketUrl)) + .pipe(untilDestroyed(this)) + .subscribe(isServerAvailable => { + if (isServerAvailable && this.language === "python") { + userConfig.languageClientConfig = { + languageId: this.language, + options: { + $type: "WebSocketUrl", + url: languageServerWebsocketUrl, + }, + }; + } - from(this.wrapper!.initAndStart(userConfig, this.editorElement.nativeElement)) - .pipe(takeUntil(this.componentDestroy)) - .subscribe({ - next: () => { - this.formControl.statusChanges.pipe(untilDestroyed(this)).subscribe(() => { - const editorInstance = this.wrapper?.getEditor(); - if (editorInstance) { - editorInstance.updateOptions({ - readOnly: this.formControl.disabled, - }); - } - }); - this.editor = this.wrapper?.getEditor(); - if (this.code && this.editor) { - if (this.monacoBinding) { - this.monacoBinding.destroy(); - this.monacoBinding = undefined; - } - this.monacoBinding = new MonacoBinding( - this.code, - this.editor.getModel()!, - new Set([this.editor]), - this.workflowActionService.getTexeraGraph().getSharedModelAwareness() - ); + from(this.editorWrapper.initAndStart(userConfig, this.editorElement.nativeElement)) + .pipe(untilDestroyed(this)) + .subscribe({ + next: () => { + const editor = this.editorWrapper.getEditor(); + this.formControl.statusChanges.pipe(untilDestroyed(this)).subscribe(() => { + if (isDefined(editor)) { + editor.updateOptions({ readOnly: this.formControl.disabled}); } - this.setupAIAssistantActions(); - }, - }); - }); - } + }); + + if (!this.code || !editor) { + return; + } + if (this.monacoBinding) { + this.monacoBinding.destroy(); + } + this.monacoBinding = new MonacoBinding( + this.code, + editor.getModel()!, + new Set([editor]), + this.workflowActionService.getTexeraGraph().getSharedModelAwareness(), + ); + + this.setupAIAssistantActions(editor); + }, + }); + }); + } - private setupAIAssistantActions() { + private setupAIAssistantActions(editor:IStandaloneCodeEditor) { // Check if the AI provider is "openai" this.aiAssistantService .checkAIAssistantEnabled() @@ -299,15 +265,15 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy next: (isEnabled: string) => { if (isEnabled === "OpenAI") { // "Add Type Annotation" Button - this.editor.addAction({ + editor.addAction({ id: "type-annotation-action", label: "Add Type Annotation", contextMenuGroupId: "1_modification", contextMenuOrder: 1.0, - run: (ed: monaco.editor.IStandaloneCodeEditor) => { + run: (editor: monaco.editor.IStandaloneCodeEditor) => { // User selected code (including range and content) - const selection = ed.getSelection(); - const model = ed.getModel(); + const selection = editor.getSelection(); + const model = editor.getModel(); if (!model || !selection) { return; } @@ -317,19 +283,13 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy const code = model.getValueInRange(selection); // Start line of the selected code const lineNumber = selection.startLineNumber; - this.handleTypeAnnotation( - code, - selection, - ed as monaco.editor.IStandaloneCodeEditor, - lineNumber, - allcode - ); + this.handleTypeAnnotation(code, selection, editor, lineNumber, allcode); }, }); } // "Add All Type Annotation" Button - this.editor.addAction({ + editor.addAction({ id: "all-type-annotation-action", label: "Add All Type Annotations", contextMenuGroupId: "1_modification", @@ -346,7 +306,7 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy this.aiAssistantService .locateUnannotated(selectedCode, selection.startLineNumber) - .pipe(takeUntil(this.componentDestroy)) + .pipe(untilDestroyed(this)) .subscribe(variablesWithoutAnnotations => { // If no unannotated variable, then do nothing. if (variablesWithoutAnnotations.length == 0) { @@ -382,10 +342,10 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy currVariable.startLine, currVariable.startColumn + offset, currVariable.endLine, - currVariable.endColumn + offset + currVariable.endColumn + offset, ); - const highlight = this.editor.createDecorationsCollection([ + const highlight = editor.createDecorationsCollection([ { range: variableRange, options: { @@ -401,7 +361,7 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy variableRange, ed as monaco.editor.IStandaloneCodeEditor, variableLineNumber, - allCode + allCode, ); lastLine = variableLineNumber; @@ -412,7 +372,7 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy // Only take one response (accept/decline) const subscription = userResponseSubject .pipe(take(1)) - .pipe(takeUntil(this.componentDestroy)) + .pipe(untilDestroyed(this)) .subscribe(() => { highlight.clear(); subscription.unsubscribe(); @@ -433,11 +393,11 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy range: monaco.Range, editor: monaco.editor.IStandaloneCodeEditor, lineNumber: number, - allcode: string + allcode: string, ): void { this.aiAssistantService .getTypeAnnotations(code, lineNumber, allcode) - .pipe(takeUntil(this.componentDestroy)) + .pipe(untilDestroyed(this)) .subscribe({ next: (response: TypeAnnotationResponse) => { const choices = response.choices || []; @@ -482,9 +442,10 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy this.currentRange.startLineNumber, this.currentRange.startColumn, this.currentRange.endLineNumber, - this.currentRange.endColumn + this.currentRange.endColumn, ); - this.insertTypeAnnotations(this.editor, selection, this.currentSuggestion); + + this.insertTypeAnnotations(this.editorWrapper.getEditor()!, selection, this.currentSuggestion); // Only for "Add All Type Annotation" if (this.isMultipleVariables && this.userResponseSubject) { @@ -511,7 +472,7 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy private insertTypeAnnotations( editor: monaco.editor.IStandaloneCodeEditor, selection: monaco.Selection, - annotations: string + annotations: string, ) { const endLineNumber = selection.endLineNumber; const endColumn = selection.endColumn; @@ -521,32 +482,28 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy } private initDiffEditor(): void { - if (this.wrapper) { - from(this.wrapper.dispose(true)) - .pipe(takeUntil(this.componentDestroy)) - .subscribe({ - next: () => { - if (this.componentRef) { - this.componentRef.destroy(); - } - this.initializeDiffEditor(); - }, - }); - } else { - this.initializeDiffEditor(); - } + + from(this.editorWrapper.dispose(true)) + .pipe(untilDestroyed(this)) + .subscribe({ + next: () => { + if (this.componentRef) { + this.componentRef.destroy(); + } + this.initializeDiffEditor(); + }, + }); } private initializeDiffEditor(): void { - if (this.code && !this.wrapper) { - this.wrapper = new MonacoEditorLanguageClientWrapper(); + if (this.code) { const fileSuffix = this.getFileSuffixByLanguage(this.language); const currentWorkflowVersionCode = this.workflowActionService .getTempWorkflow() ?.content.operators?.filter( operator => operator.operatorID === - this.workflowActionService.getJointGraphWrapper().getCurrentHighlightedOperatorIDs()[0] + this.workflowActionService.getJointGraphWrapper().getCurrentHighlightedOperatorIDs()[0], )?.[0].operatorProperties.code; const userConfig: UserConfig = { @@ -556,11 +513,11 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy codeResources: { main: { text: currentWorkflowVersionCode, - uri: `in-memory-${this.operatorID}-version.${fileSuffix}`, + uri: `in-memory-${this.currentOperatorId}-version.${fileSuffix}`, }, original: { text: this.code.toString(), - uri: `in-memory-${this.operatorID}.${fileSuffix}`, + uri: `in-memory-${this.currentOperatorId}.${fileSuffix}`, }, }, useDiffEditor: true, @@ -576,8 +533,8 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy }, }; - from(this.checkPythonLanguageServerAvailability()) - .pipe(takeUntil(this.componentDestroy)) + from(this.checkLanguageServerAvailability(getWebsocketUrl("/python-language-server", "3000"))) + .pipe(untilDestroyed(this)) .subscribe(isServerAvailable => { if (isServerAvailable && this.language === "python") { userConfig.languageClientConfig = { @@ -589,12 +546,12 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy }; } - this.wrapper!.initAndStart(userConfig, this.editorElement.nativeElement); + this.editorWrapper.initAndStart(userConfig, this.editorElement.nativeElement); }); } } onFocus() { - this.workflowActionService.getJointGraphWrapper().highlightOperators(this.operatorID); + this.workflowActionService.getJointGraphWrapper().highlightOperators(this.currentOperatorId); } } From b954fd2c16a6ba3db3a129678afad5156e6752e9 Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Wed, 16 Oct 2024 14:56:08 -0700 Subject: [PATCH 2/5] get rid of manual check --- .../code-editor.component.ts | 218 +++++++----------- 1 file changed, 86 insertions(+), 132 deletions(-) diff --git a/core/gui/src/app/workspace/component/code-editor-dialog/code-editor.component.ts b/core/gui/src/app/workspace/component/code-editor-dialog/code-editor.component.ts index 16d6469ecfe..d146f866df0 100644 --- a/core/gui/src/app/workspace/component/code-editor-dialog/code-editor.component.ts +++ b/core/gui/src/app/workspace/component/code-editor-dialog/code-editor.component.ts @@ -5,7 +5,7 @@ import { WorkflowVersionService } from "../../../dashboard/service/user/workflow import { YText } from "yjs/dist/src/types/YText"; import { getWebsocketUrl } from "src/app/common/util/url"; import { MonacoBinding } from "y-monaco"; -import { from, Subject, take } from "rxjs"; +import { catchError, from, of, Subject, take } from "rxjs"; import { CoeditorPresenceService } from "../../service/workflow-graph/model/coeditor-presence.service"; import { DomSanitizer, SafeStyle } from "@angular/platform-browser"; import { Coeditor } from "../../../common/type/user"; @@ -21,6 +21,7 @@ import "@codingame/monaco-vscode-r-default-extension"; import "@codingame/monaco-vscode-java-default-extension"; import { isDefined } from "../../../common/util/predicate"; import { editor } from "vscode/editor.api"; +import { filter, switchMap } from "rxjs/operators"; import IStandaloneCodeEditor = editor.IStandaloneCodeEditor; /** @@ -107,6 +108,14 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy .get("operatorProperties") as YType> ).get("code") as YText; + + } + + ngAfterViewInit() { + // hacky solution to reset view after view is rendered. + const style = localStorage.getItem(this.currentOperatorId); + if (style) this.containerElement.nativeElement.style.cssText = style; + // start editor this.workflowVersionService .getDisplayParticularVersionStream() @@ -120,12 +129,6 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy }); } - ngAfterViewInit() { - // hacky solution to reset view after view is rendered. - const style = localStorage.getItem(this.currentOperatorId); - if (style) this.containerElement.nativeElement.style.cssText = style; - } - ngOnDestroy(): void { this.workflowActionService.getTexeraGraph().updateSharedModelAwareness("editingCode", false); localStorage.setItem(this.currentOperatorId, this.containerElement.nativeElement.style.cssText); @@ -171,21 +174,6 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy } } - private checkLanguageServerAvailability(websocketUrl: string): Promise { - return new Promise(resolve => { - const socket = new WebSocket(websocketUrl); - - socket.onopen = () => { - socket.close(); - resolve(true); - }; - - socket.onerror = () => { - resolve(false); - }; - }); - } - /** * Create a Monaco editor and connect it to MonacoBinding. * @private @@ -211,52 +199,52 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy }, }; - const languageServerWebsocketUrl = getWebsocketUrl("/python-language-server", "3000") - from(this.checkLanguageServerAvailability(languageServerWebsocketUrl)) - .pipe(untilDestroyed(this)) - .subscribe(isServerAvailable => { - if (isServerAvailable && this.language === "python") { - userConfig.languageClientConfig = { - languageId: this.language, - options: { - $type: "WebSocketUrl", - url: languageServerWebsocketUrl, - }, - }; - } - - from(this.editorWrapper.initAndStart(userConfig, this.editorElement.nativeElement)) - .pipe(untilDestroyed(this)) - .subscribe({ - next: () => { - const editor = this.editorWrapper.getEditor(); - this.formControl.statusChanges.pipe(untilDestroyed(this)).subscribe(() => { - if (isDefined(editor)) { - editor.updateOptions({ readOnly: this.formControl.disabled}); - } - }); + // optionally, configure python language client. + // it may fail if no valid connection is established, yet the failure would be ignored. + const languageServerWebsocketUrl = getWebsocketUrl("/python-language-server", "3000"); + if (this.language === "python") { + userConfig.languageClientConfig = { + languageId: this.language, + options: { + $type: "WebSocketUrl", + url: languageServerWebsocketUrl, + }, + }; + } - if (!this.code || !editor) { - return; - } - if (this.monacoBinding) { - this.monacoBinding.destroy(); - } - this.monacoBinding = new MonacoBinding( - this.code, - editor.getModel()!, - new Set([editor]), - this.workflowActionService.getTexeraGraph().getSharedModelAwareness(), - ); - - this.setupAIAssistantActions(editor); - }, + from(this.editorWrapper.initAndStart(userConfig, this.editorElement.nativeElement)) + .pipe( + switchMap(() => of(this.editorWrapper.getEditor())), + catchError((err) => of(this.editorWrapper.getEditor())), + filter(isDefined), + untilDestroyed(this), + ) + .subscribe( + (editor: IStandaloneCodeEditor) => { + this.formControl.statusChanges.pipe(untilDestroyed(this)).subscribe(() => { + if (isDefined(editor)) { + editor.updateOptions({ readOnly: this.formControl.disabled }); + } }); - }); + if (!this.code || !editor) { + return; + } + if (this.monacoBinding) { + this.monacoBinding.destroy(); + } + this.monacoBinding = new MonacoBinding( + this.code, + editor.getModel()!, + new Set([editor]), + this.workflowActionService.getTexeraGraph().getSharedModelAwareness(), + ); + this.setupAIAssistantActions(editor); + }, + ); } - private setupAIAssistantActions(editor:IStandaloneCodeEditor) { + private setupAIAssistantActions(editor: IStandaloneCodeEditor) { // Check if the AI provider is "openai" this.aiAssistantService .checkAIAssistantEnabled() @@ -278,12 +266,12 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy return; } // All the code in Python UDF - const allcode = model.getValue(); + const allCode = model.getValue(); // Content of user selected code - const code = model.getValueInRange(selection); + const userSelectedCode = model.getValueInRange(selection); // Start line of the selected code const lineNumber = selection.startLineNumber; - this.handleTypeAnnotation(code, selection, editor, lineNumber, allcode); + this.handleTypeAnnotation(userSelectedCode, selection, editor, lineNumber, allCode); }, }); } @@ -294,9 +282,9 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy label: "Add All Type Annotations", contextMenuGroupId: "1_modification", contextMenuOrder: 1.1, - run: (ed: monaco.editor.IStandaloneCodeEditor) => { - const selection = ed.getSelection(); - const model = ed.getModel(); + run: (editor: monaco.editor.IStandaloneCodeEditor) => { + const selection = editor.getSelection(); + const model = editor.getModel(); if (!model || !selection) { return; } @@ -356,26 +344,17 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy }, ]); - this.handleTypeAnnotation( - variableCode, - variableRange, - ed as monaco.editor.IStandaloneCodeEditor, - variableLineNumber, - allCode, - ); + this.handleTypeAnnotation(variableCode, variableRange, editor, variableLineNumber, allCode); lastLine = variableLineNumber; // Make sure the currVariable will not go to the next one until the user click the accept/decline button - if (this.userResponseSubject !== undefined) { - const userResponseSubject = this.userResponseSubject; - // Only take one response (accept/decline) - const subscription = userResponseSubject - .pipe(take(1)) + if (isDefined(this.userResponseSubject)) { + this.userResponseSubject + .pipe(take(1)) // Only take one response (accept/decline) .pipe(untilDestroyed(this)) .subscribe(() => { highlight.clear(); - subscription.unsubscribe(); processNextVariable(index + 1); }); } @@ -388,45 +367,34 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy }); } - private handleTypeAnnotation( - code: string, - range: monaco.Range, - editor: monaco.editor.IStandaloneCodeEditor, - lineNumber: number, - allcode: string, - ): void { + private handleTypeAnnotation(code: string, range: monaco.Range, editor: monaco.editor.IStandaloneCodeEditor, lineNumber: number, allCode: string): void { this.aiAssistantService - .getTypeAnnotations(code, lineNumber, allcode) + .getTypeAnnotations(code, lineNumber, allCode) .pipe(untilDestroyed(this)) - .subscribe({ - next: (response: TypeAnnotationResponse) => { - const choices = response.choices || []; - if (choices.length > 0 && choices[0].message && choices[0].message.content) { - this.currentSuggestion = choices[0].message.content.trim(); - this.currentCode = code; - this.currentRange = range; - - const position = editor.getScrolledVisiblePosition(range.getStartPosition()); - if (position) { - this.suggestionTop = position.top + 100; - this.suggestionLeft = position.left + 100; - } + .subscribe((response: TypeAnnotationResponse) => { + const choices = response.choices || []; + if (!(choices.length > 0 && choices[0].message && choices[0].message.content)) { + throw Error("Error: OpenAI response does not contain valid message content " + response); + } + this.currentSuggestion = choices[0].message.content.trim(); + this.currentCode = code; + this.currentRange = range; + + const position = editor.getScrolledVisiblePosition(range.getStartPosition()); + if (position) { + this.suggestionTop = position.top + 100; + this.suggestionLeft = position.left + 100; + } - this.showAnnotationSuggestion = true; + this.showAnnotationSuggestion = true; - if (this.annotationSuggestion) { - this.annotationSuggestion.code = this.currentCode; - this.annotationSuggestion.suggestion = this.currentSuggestion; - this.annotationSuggestion.top = this.suggestionTop; - this.annotationSuggestion.left = this.suggestionLeft; - } - } else { - console.error("Error: OpenAI response does not contain valid message content", response); - } - }, - error: (error: unknown) => { - console.error("Error fetching type annotations:", error); - }, + if (!this.annotationSuggestion) { + return; + } + this.annotationSuggestion.code = this.currentCode; + this.annotationSuggestion.suggestion = this.currentSuggestion; + this.annotationSuggestion.top = this.suggestionTop; + this.annotationSuggestion.left = this.suggestionLeft; }); } @@ -533,21 +501,7 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy }, }; - from(this.checkLanguageServerAvailability(getWebsocketUrl("/python-language-server", "3000"))) - .pipe(untilDestroyed(this)) - .subscribe(isServerAvailable => { - if (isServerAvailable && this.language === "python") { - userConfig.languageClientConfig = { - languageId: "python", - options: { - $type: "WebSocketUrl", - url: getWebsocketUrl("/python-language-server", "3000"), - }, - }; - } - - this.editorWrapper.initAndStart(userConfig, this.editorElement.nativeElement); - }); + this.editorWrapper.initAndStart(userConfig, this.editorElement.nativeElement); } } From 6298642286f4f9e4eedc668b67a7b54a2b5119cb Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:05:50 -0700 Subject: [PATCH 3/5] remove callback --- .../code-editor-dialog/code-editor.component.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/core/gui/src/app/workspace/component/code-editor-dialog/code-editor.component.ts b/core/gui/src/app/workspace/component/code-editor-dialog/code-editor.component.ts index d146f866df0..c1ce6994c62 100644 --- a/core/gui/src/app/workspace/component/code-editor-dialog/code-editor.component.ts +++ b/core/gui/src/app/workspace/component/code-editor-dialog/code-editor.component.ts @@ -212,6 +212,8 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy }; } + + // init monaco editor, optionally with attempt on language client. from(this.editorWrapper.initAndStart(userConfig, this.editorElement.nativeElement)) .pipe( switchMap(() => of(this.editorWrapper.getEditor())), @@ -219,15 +221,9 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy filter(isDefined), untilDestroyed(this), ) - .subscribe( - (editor: IStandaloneCodeEditor) => { - this.formControl.statusChanges.pipe(untilDestroyed(this)).subscribe(() => { - if (isDefined(editor)) { - editor.updateOptions({ readOnly: this.formControl.disabled }); - } - }); - - if (!this.code || !editor) { + .subscribe((editor: IStandaloneCodeEditor) => { + editor.updateOptions({ readOnly: this.formControl.disabled }); + if (!this.code) { return; } if (this.monacoBinding) { @@ -450,7 +446,6 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy } private initDiffEditor(): void { - from(this.editorWrapper.dispose(true)) .pipe(untilDestroyed(this)) .subscribe({ From 708ae2300d985a744520700667d49e59d51467b7 Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:30:22 -0700 Subject: [PATCH 4/5] fix diff editor --- .../code-editor.component.ts | 94 ++++++++----------- 1 file changed, 39 insertions(+), 55 deletions(-) diff --git a/core/gui/src/app/workspace/component/code-editor-dialog/code-editor.component.ts b/core/gui/src/app/workspace/component/code-editor-dialog/code-editor.component.ts index c1ce6994c62..ea2062278d1 100644 --- a/core/gui/src/app/workspace/component/code-editor-dialog/code-editor.component.ts +++ b/core/gui/src/app/workspace/component/code-editor-dialog/code-editor.component.ts @@ -122,7 +122,7 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy .pipe(untilDestroyed(this)) .subscribe((displayParticularVersion: boolean) => { if (displayParticularVersion) { - this.initDiffEditor(); + this.initializeDiffEditor(); } else { this.initializeMonacoEditor(); } @@ -240,6 +240,44 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy ); } + private initializeDiffEditor(): void { + const fileSuffix = this.getFileSuffixByLanguage(this.language); + const currentWorkflowVersionCode = this.workflowActionService + .getTempWorkflow() + ?.content.operators?.filter(operator => operator.operatorID === this.currentOperatorId, + )?.[0].operatorProperties.code; + + const userConfig: UserConfig = { + wrapperConfig: { + editorAppConfig: { + $type: "extended", + codeResources: { + main: { + text: currentWorkflowVersionCode, + uri: `in-memory-${this.currentOperatorId}-version.${fileSuffix}`, + }, + original: { + text: this.code?.toString() ?? "", + uri: `in-memory-${this.currentOperatorId}.${fileSuffix}`, + }, + }, + useDiffEditor: true, + diffEditorOptions: { + readOnly: true, + }, + userConfiguration: { + json: JSON.stringify({ + "workbench.colorTheme": "Default Dark Modern", + }), + }, + }, + }, + }; + + this.editorWrapper.initAndStart(userConfig, this.editorElement.nativeElement); + + } + private setupAIAssistantActions(editor: IStandaloneCodeEditor) { // Check if the AI provider is "openai" this.aiAssistantService @@ -445,60 +483,6 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy this.code?.insert(insertOffset, annotations); } - private initDiffEditor(): void { - from(this.editorWrapper.dispose(true)) - .pipe(untilDestroyed(this)) - .subscribe({ - next: () => { - if (this.componentRef) { - this.componentRef.destroy(); - } - this.initializeDiffEditor(); - }, - }); - } - - private initializeDiffEditor(): void { - if (this.code) { - const fileSuffix = this.getFileSuffixByLanguage(this.language); - const currentWorkflowVersionCode = this.workflowActionService - .getTempWorkflow() - ?.content.operators?.filter( - operator => - operator.operatorID === - this.workflowActionService.getJointGraphWrapper().getCurrentHighlightedOperatorIDs()[0], - )?.[0].operatorProperties.code; - - const userConfig: UserConfig = { - wrapperConfig: { - editorAppConfig: { - $type: "extended", - codeResources: { - main: { - text: currentWorkflowVersionCode, - uri: `in-memory-${this.currentOperatorId}-version.${fileSuffix}`, - }, - original: { - text: this.code.toString(), - uri: `in-memory-${this.currentOperatorId}.${fileSuffix}`, - }, - }, - useDiffEditor: true, - diffEditorOptions: { - readOnly: true, - }, - userConfiguration: { - json: JSON.stringify({ - "workbench.colorTheme": "Default Dark Modern", - }), - }, - }, - }, - }; - - this.editorWrapper.initAndStart(userConfig, this.editorElement.nativeElement); - } - } onFocus() { this.workflowActionService.getJointGraphWrapper().highlightOperators(this.currentOperatorId); From 24ff20137e7289fb6991fcd610e9dc08fd7a7254 Mon Sep 17 00:00:00 2001 From: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:35:59 -0700 Subject: [PATCH 5/5] fix format --- .../code-editor.component.ts | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/core/gui/src/app/workspace/component/code-editor-dialog/code-editor.component.ts b/core/gui/src/app/workspace/component/code-editor-dialog/code-editor.component.ts index ea2062278d1..03e2f970d84 100644 --- a/core/gui/src/app/workspace/component/code-editor-dialog/code-editor.component.ts +++ b/core/gui/src/app/workspace/component/code-editor-dialog/code-editor.component.ts @@ -83,7 +83,7 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy private workflowActionService: WorkflowActionService, private workflowVersionService: WorkflowVersionService, public coeditorPresenceService: CoeditorPresenceService, - private aiAssistantService: AIAssistantService, + private aiAssistantService: AIAssistantService ) { this.currentOperatorId = this.workflowActionService.getJointGraphWrapper().getCurrentHighlightedOperatorIDs()[0]; const operatorType = this.workflowActionService.getTexeraGraph().getOperator(this.currentOperatorId).operatorType; @@ -107,8 +107,6 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy .getSharedOperatorType(this.currentOperatorId) .get("operatorProperties") as YType> ).get("code") as YText; - - } ngAfterViewInit() { @@ -212,40 +210,38 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy }; } - // init monaco editor, optionally with attempt on language client. from(this.editorWrapper.initAndStart(userConfig, this.editorElement.nativeElement)) .pipe( switchMap(() => of(this.editorWrapper.getEditor())), - catchError((err) => of(this.editorWrapper.getEditor())), + catchError(() => of(this.editorWrapper.getEditor())), filter(isDefined), - untilDestroyed(this), + untilDestroyed(this) ) .subscribe((editor: IStandaloneCodeEditor) => { - editor.updateOptions({ readOnly: this.formControl.disabled }); - if (!this.code) { - return; - } - if (this.monacoBinding) { - this.monacoBinding.destroy(); - } - this.monacoBinding = new MonacoBinding( - this.code, - editor.getModel()!, - new Set([editor]), - this.workflowActionService.getTexeraGraph().getSharedModelAwareness(), - ); - this.setupAIAssistantActions(editor); - }, - ); + editor.updateOptions({ readOnly: this.formControl.disabled }); + if (!this.code) { + return; + } + if (this.monacoBinding) { + this.monacoBinding.destroy(); + } + this.monacoBinding = new MonacoBinding( + this.code, + editor.getModel()!, + new Set([editor]), + this.workflowActionService.getTexeraGraph().getSharedModelAwareness() + ); + this.setupAIAssistantActions(editor); + }); } private initializeDiffEditor(): void { const fileSuffix = this.getFileSuffixByLanguage(this.language); const currentWorkflowVersionCode = this.workflowActionService .getTempWorkflow() - ?.content.operators?.filter(operator => operator.operatorID === this.currentOperatorId, - )?.[0].operatorProperties.code; + ?.content.operators?.filter(operator => operator.operatorID === this.currentOperatorId)?.[0] + .operatorProperties.code; const userConfig: UserConfig = { wrapperConfig: { @@ -275,7 +271,6 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy }; this.editorWrapper.initAndStart(userConfig, this.editorElement.nativeElement); - } private setupAIAssistantActions(editor: IStandaloneCodeEditor) { @@ -364,7 +359,7 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy currVariable.startLine, currVariable.startColumn + offset, currVariable.endLine, - currVariable.endColumn + offset, + currVariable.endColumn + offset ); const highlight = editor.createDecorationsCollection([ @@ -401,7 +396,13 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy }); } - private handleTypeAnnotation(code: string, range: monaco.Range, editor: monaco.editor.IStandaloneCodeEditor, lineNumber: number, allCode: string): void { + private handleTypeAnnotation( + code: string, + range: monaco.Range, + editor: monaco.editor.IStandaloneCodeEditor, + lineNumber: number, + allCode: string + ): void { this.aiAssistantService .getTypeAnnotations(code, lineNumber, allCode) .pipe(untilDestroyed(this)) @@ -444,7 +445,7 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy this.currentRange.startLineNumber, this.currentRange.startColumn, this.currentRange.endLineNumber, - this.currentRange.endColumn, + this.currentRange.endColumn ); this.insertTypeAnnotations(this.editorWrapper.getEditor()!, selection, this.currentSuggestion); @@ -474,7 +475,7 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy private insertTypeAnnotations( editor: monaco.editor.IStandaloneCodeEditor, selection: monaco.Selection, - annotations: string, + annotations: string ) { const endLineNumber = selection.endLineNumber; const endColumn = selection.endColumn; @@ -483,7 +484,6 @@ export class CodeEditorComponent implements AfterViewInit, SafeStyle, OnDestroy this.code?.insert(insertOffset, annotations); } - onFocus() { this.workflowActionService.getJointGraphWrapper().highlightOperators(this.currentOperatorId); }