-
Notifications
You must be signed in to change notification settings - Fork 96
feat: AI code completion & intellisense for python editor #1263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
a51c18d
34eb74b
0aecd8f
50180f7
442617a
2ad36b9
875af5b
cca83b5
3fd51ef
ee17e4d
1150c9b
15c3469
148eba0
421e788
7253334
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,3 +26,5 @@ playground/ | |
| .turbo | ||
| styles.css | ||
| .aider* | ||
| app_templates/ | ||
| local/ | ||
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,9 +3,11 @@ | |
| ref="rootEl" | ||
| class="BuilderEmbeddedCodeEditor" | ||
| :class="{ | ||
| 'BuilderEmbeddedCodeEditor--full': variant === 'full', | ||
| 'BuilderEmbeddedCodeEditor--halfScreen': variant === 'half-screen', | ||
| 'BuilderEmbeddedCodeEditor--singleLine': variant === 'single-line', | ||
| 'BuilderEmbeddedCodeEditor--full': props.variant === 'full', | ||
| 'BuilderEmbeddedCodeEditor--halfScreen': | ||
| props.variant === 'half-screen', | ||
| 'BuilderEmbeddedCodeEditor--singleLine': | ||
| props.variant === 'single-line', | ||
| }" | ||
| > | ||
| <div ref="editorContainerEl" class="editorContainer"></div> | ||
|
|
@@ -18,16 +20,26 @@ import "./builderEditorWorker"; | |
| import { | ||
| onMounted, | ||
| onUnmounted, | ||
| PropType, | ||
| type PropType, | ||
| toRefs, | ||
| useTemplateRef, | ||
| watch, | ||
| } from "vue"; | ||
| import { syncModelWithLSP } from "./lsp/lspModelSync"; | ||
| import { setModelDiagnostics, setupLSPDiagnostics } from "./lsp/lspDiagnostics"; | ||
| import { useMonacopilot } from "../composables/useMonacopilot"; | ||
| import { useLogger } from "@/composables/useLogger"; | ||
| import { useCodeEditorSettings } from "@/composables/useCodeEditorSettings"; | ||
|
|
||
| const rootEl = useTemplateRef("rootEl"); | ||
| const editorContainerEl = useTemplateRef("editorContainerEl"); | ||
| const resizeObserver = new ResizeObserver(updateDimensions); | ||
| let editor: monaco.editor.IStandaloneCodeEditor = null; | ||
| let lspSyncDisposable: monaco.IDisposable | null = null; | ||
| let diagnosticsDisposable: monaco.IDisposable | null = null; | ||
| let monacopilotCleanup: (() => void) | null = null; | ||
|
|
||
| const { diagnosticsEnabled, aiCompletionEnabled } = useCodeEditorSettings(); | ||
|
|
||
| type EditorVariant = "full" | "minimal" | "half-screen" | "single-line"; | ||
|
|
||
|
|
@@ -44,6 +56,8 @@ const props = defineProps({ | |
| const { modelValue, disabled, language } = toRefs(props); | ||
| const emit = defineEmits(["update:modelValue"]); | ||
|
|
||
| const logger = useLogger(); | ||
|
|
||
| const VARIANTS_SETTINGS: Partial< | ||
| Record< | ||
| EditorVariant, | ||
|
|
@@ -54,6 +68,7 @@ const VARIANTS_SETTINGS: Partial< | |
| minimap: { | ||
| enabled: false, | ||
| }, | ||
| tabCompletion: "on", | ||
| }, | ||
| minimal: { | ||
| minimap: { | ||
|
|
@@ -94,38 +109,146 @@ watch(disabled, (isNewDisabled) => { | |
| }); | ||
|
|
||
| watch(modelValue, (newCode) => { | ||
| if (editor.getValue() == newCode) return; | ||
| if (!editor || editor.getValue() === newCode) return; | ||
| editor.getModel().setValue(newCode); | ||
| }); | ||
|
|
||
| watch(language, () => { | ||
| monaco.editor.setModelLanguage(editor.getModel(), language.value); | ||
| watch(language, (newLang) => { | ||
| if (!editor) return; | ||
| const model = editor.getModel(); | ||
| if (model.getLanguageId() === newLang) return; | ||
|
|
||
| // Dispose old LSP sync before changing language | ||
| if (lspSyncDisposable) { | ||
| lspSyncDisposable.dispose(); | ||
| lspSyncDisposable = null; | ||
| } | ||
|
|
||
| // Change language | ||
| monaco.editor.setModelLanguage(model, newLang); | ||
|
|
||
| // Re-sync if new language is Python | ||
| if (newLang === "python") { | ||
| try { | ||
| lspSyncDisposable = syncModelWithLSP(model); | ||
| } catch (error) { | ||
| logger.error("Failed to re-sync model with LSP:", error); | ||
| } | ||
| } | ||
|
Comment on lines
+116
to
+137
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Language switches only restart document sync, not the rest of the Python tooling. This watcher reopens LSP sync, but it never mirrors the Python-only setup from 🤖 Prompt for AI Agents |
||
| }); | ||
|
|
||
| watch(diagnosticsEnabled, (enabled) => { | ||
| if ( | ||
| !editor || | ||
| language.value !== "python" || | ||
| props.variant === "single-line" | ||
| ) { | ||
| return; | ||
| } | ||
|
|
||
| const model = editor.getModel(); | ||
| if (model && language.value === "python") { | ||
| diagnosticsDisposable?.dispose(); | ||
| if (enabled) { | ||
| diagnosticsDisposable = setupLSPDiagnostics(monaco); | ||
| } | ||
| model.setValue(model.getValue()); | ||
| } | ||
| }); | ||
|
bybash marked this conversation as resolved.
|
||
|
|
||
| // Watch AI completion setting changes | ||
| watch(aiCompletionEnabled, async (enabled) => { | ||
| if ( | ||
| !editor || | ||
| language.value !== "python" || | ||
| props.variant === "single-line" | ||
| ) | ||
| return; | ||
|
|
||
| if (enabled) { | ||
| // Enable AI completion | ||
| if (!monacopilotCleanup) { | ||
| try { | ||
| monacopilotCleanup = await useMonacopilot( | ||
| monaco, | ||
| editor, | ||
| language.value, | ||
| ); | ||
| } catch (error) { | ||
| logger.error("Failed to enable AI completion:", error); | ||
| } | ||
| } | ||
| } else { | ||
| // Disable AI completion | ||
| if (monacopilotCleanup) { | ||
| monacopilotCleanup(); | ||
| monacopilotCleanup = null; | ||
| } | ||
| } | ||
| }); | ||
|
bybash marked this conversation as resolved.
|
||
|
|
||
| onMounted(() => { | ||
| editor = monaco.editor.create(editorContainerEl.value, { | ||
| value: modelValue.value ?? "", | ||
| language: props.language, | ||
| onMounted(async () => { | ||
| // Create model with proper URI for LSP | ||
| const modelUri = monaco.Uri.parse(`inmemory://model/${Date.now()}.py`); | ||
| const model = monaco.editor.createModel( | ||
| modelValue.value ?? "", | ||
| language.value || "python", | ||
| language.value === "python" ? modelUri : undefined, | ||
| ); | ||
|
|
||
| editor = monaco.editor.create(editorContainerEl.value as HTMLElement, { | ||
| model: model, | ||
| readOnly: props.disabled, | ||
| fixedOverflowWidgets: true, | ||
| quickSuggestions: { | ||
| other: true, | ||
| comments: true, | ||
| strings: true, | ||
| }, | ||
| ...VARIANTS_SETTINGS[props.variant], | ||
| }); | ||
| editor.getModel().onDidChangeContent(() => { | ||
|
|
||
| model.onDidChangeContent(() => { | ||
| const newCode = editor.getValue(); | ||
| emit("update:modelValue", newCode); | ||
| }); | ||
| resizeObserver.observe(rootEl.value); | ||
|
|
||
| resizeObserver.observe(rootEl.value as Element); | ||
|
|
||
| // Manually sync model with LSP for Python language | ||
| // This is required because we're in a browser (no filesystem) | ||
| if (language.value === "python" && props.variant !== "single-line") { | ||
| try { | ||
| lspSyncDisposable = syncModelWithLSP(model); | ||
| } catch (error) { | ||
| logger.error("Failed to sync model with LSP:", error); | ||
| } | ||
|
|
||
| // Register AI-powered code completions (if AI completion enabled) | ||
| if (aiCompletionEnabled.value) { | ||
| try { | ||
| monacopilotCleanup = await useMonacopilot( | ||
| monaco, | ||
| editor, | ||
| language.value, | ||
| ); | ||
| } catch (error) { | ||
| logger.error("Failed to initialize monacopilot:", error); | ||
| } | ||
| } | ||
|
|
||
| diagnosticsDisposable?.dispose(); | ||
| if (diagnosticsEnabled.value) { | ||
| diagnosticsDisposable = setupLSPDiagnostics(monaco); | ||
| } | ||
|
Comment on lines
+218
to
+243
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The first Python editor can silently miss LSP wiring during startup.
🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| // when in modal, focus the editor and set the cursor to the last line | ||
| if (props.variant === "half-screen") { | ||
| editor.focus(); | ||
| editor.setPosition({ | ||
| lineNumber: editor.getModel().getLineCount(), | ||
| column: editor | ||
| .getModel() | ||
| .getLineLastNonWhitespaceColumn( | ||
| editor.getModel().getLineCount(), | ||
| ), | ||
| lineNumber: model.getLineCount(), | ||
| column: model.getLineLastNonWhitespaceColumn(model.getLineCount()), | ||
| }); | ||
| } | ||
| }); | ||
|
|
@@ -135,7 +258,32 @@ function updateDimensions() { | |
| } | ||
|
|
||
| onUnmounted(() => { | ||
| editor.dispose(); | ||
| // Clean up LSP sync | ||
| if (lspSyncDisposable) { | ||
| lspSyncDisposable.dispose(); | ||
| lspSyncDisposable = null; | ||
| } | ||
|
|
||
| const model = editor?.getModel(); | ||
|
|
||
| // Clear diagnostics before disposing model | ||
| if (model && language.value === "python") { | ||
| setModelDiagnostics(monaco, model, []); | ||
| } | ||
|
|
||
| if (editor) { | ||
| editor.dispose(); | ||
| } | ||
| if (model) { | ||
| model.dispose(); | ||
| } | ||
| if (monacopilotCleanup) { | ||
| monacopilotCleanup(); | ||
| } | ||
| if (diagnosticsDisposable) { | ||
| diagnosticsDisposable.dispose(); | ||
| diagnosticsDisposable = null; | ||
| } | ||
| resizeObserver.disconnect(); | ||
| }); | ||
| </script> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anchor these ignore rules to the repo root.
app_templates/currently matches any directory with that name, includingsrc/writer/app_templates/, and because it comes after Lines 19-20 it can effectively override the existing allowlist intent there.local/has the same overbroad matching behavior. If these are meant to ignore only top-level dev artifacts, make them root-anchored.Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents