diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..f00dc91b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,28 @@ +# Galaxy AI Agents - Ideas & Future Work + +## LLM-Powered Predictive Autocomplete + +**Idea:** As the user types in the code editor, stream the current context (surrounding code, file structure, recent edits) to an LLM and predict what they're about to write — offering inline ghost-text completions similar to GitHub Copilot. + +**Scope options:** +- By line (predict the rest of the current line) +- By function (predict the full function body) +- By class/module (predict structural code) + +**Challenges:** +- Latency: can't hit the LLM on every keystroke. Need aggressive debouncing (500ms+), speculative pre-fetching, and streaming partial results. +- Cost: high token volume. May need a small/fast model (Haiku) for inline suggestions with a larger model for multi-line predictions. +- Context window: need to efficiently pack relevant context (current file, imports, related types, recent edits) without blowing the token budget. +- Cancellation: must cancel in-flight requests when the user keeps typing past the prediction point. +- UX: ghost text rendering, Tab to accept, partial accept (word-by-word), dismiss on divergence. + +**Possible approaches:** +- Debounce + streaming: wait 500ms after last keystroke, stream tokens as they arrive, render as ghost text +- Predictive pre-fetch: on function signature completion or newline, proactively request the likely next block +- Local model: run a small code model locally for instant line completions, use cloud model for multi-line +- Hybrid: use LSP completions for symbol-level, LLM for line/block-level predictions + +**Integration points in Galaxy:** +- `app/src/code/completion.rs` — extend the completion state machine with an LLM provider +- `crates/ai/` — existing Bedrock/LLM infrastructure can be reused +- Editor decoration system — for rendering ghost text (similar to inlay hints) diff --git a/app/src/code/completion.rs b/app/src/code/completion.rs index 980f80d0..8470b233 100644 --- a/app/src/code/completion.rs +++ b/app/src/code/completion.rs @@ -141,6 +141,14 @@ impl LocalCodeEditorView { FeatureFlag::LspCompletion.is_enabled() } + /// Sync the editor's key interception flag with the completion state. + pub(super) fn sync_completion_intercept(&self, ctx: &mut ViewContext) { + let should_intercept = self.completion_state.is_showing(); + self.editor.update(ctx, |editor, _ctx| { + editor.completion_intercept_keys = should_intercept; + }); + } + /// Handle a user typing event — potentially trigger completion. pub(super) fn on_content_changed_for_completion(&mut self, ctx: &mut ViewContext) { if !Self::is_completion_enabled() { @@ -270,6 +278,7 @@ impl LocalCodeEditorView { Ok(Some(result)) if !result.items.is_empty() => result, _ => { self.completion_state = CompletionState::Idle; + self.sync_completion_intercept(ctx); ctx.notify(); return; } @@ -288,6 +297,7 @@ impl LocalCodeEditorView { let query = self.get_completion_filter_query(trigger_offset, ctx); self.completion_state.filter(&query); + self.sync_completion_intercept(ctx); ctx.notify(); } @@ -332,6 +342,7 @@ impl LocalCodeEditorView { }; self.completion_state = CompletionState::Idle; + self.sync_completion_intercept(ctx); self.editor.update(ctx, |editor, ctx| { let edit_range = if let Some(range) = text_edit_range { diff --git a/app/src/code/editor/view.rs b/app/src/code/editor/view.rs index 4b2cd0ca..cd2b429a 100644 --- a/app/src/code/editor/view.rs +++ b/app/src/code/editor/view.rs @@ -153,6 +153,12 @@ pub enum CodeEditorEvent { VimGotoDefinition, VimFindReferences, VimShowHover, + /// Emitted when Up is pressed and completion_intercept_keys is active. + CompletionNavigateUp, + /// Emitted when Down is pressed and completion_intercept_keys is active. + CompletionNavigateDown, + /// Emitted when Tab/Enter is pressed and completion_intercept_keys is active. + CompletionConfirm, } /// Store all states related to displaying the editor content. @@ -284,6 +290,9 @@ pub struct CodeEditorView { /// The offset where find references card is anchored (if showing). find_references_anchor_offset: Option, window_id: WindowId, + /// When true, Up/Down/Tab/Enter are redirected to completion events instead of + /// normal editor behavior. Set by the parent when a completion menu is visible. + pub completion_intercept_keys: bool, } impl CodeEditorView { @@ -427,6 +436,7 @@ impl CodeEditorView { show_find_references_provider: render_options.show_find_references_provider, find_references_anchor_offset: None, window_id: ctx.window_id(), + completion_intercept_keys: false, } } diff --git a/app/src/code/editor/view/actions.rs b/app/src/code/editor/view/actions.rs index 1b0a0780..cb1f032e 100644 --- a/app/src/code/editor/view/actions.rs +++ b/app/src/code/editor/view/actions.rs @@ -805,6 +805,28 @@ impl TypedActionView for CodeEditorView { return; } + // When completion menu is active, redirect navigation/confirm keys to the parent. + if self.completion_intercept_keys { + match action { + MoveUp => { + ctx.emit(super::CodeEditorEvent::CompletionNavigateUp); + return; + } + MoveDown => { + ctx.emit(super::CodeEditorEvent::CompletionNavigateDown); + return; + } + Tab | Enter => { + ctx.emit(super::CodeEditorEvent::CompletionConfirm); + return; + } + Escape => { + // Let escape fall through to normal handling (which emits EscapePressed) + } + _ => {} + } + } + match action { UserTyped(content) => self.user_insert(content, ctx), VimUserTyped(content) => { diff --git a/app/src/code/local_code_editor.rs b/app/src/code/local_code_editor.rs index 00e6b244..62e793e6 100644 --- a/app/src/code/local_code_editor.rs +++ b/app/src/code/local_code_editor.rs @@ -455,6 +455,17 @@ impl LocalCodeEditorView { CodeEditorEvent::DelayedRenderingFlushed => { ctx.emit(LocalCodeEditorEvent::DelayedRenderingFlushed); } + CodeEditorEvent::CompletionNavigateUp => { + me.completion_state.move_selection(-1); + ctx.notify(); + } + CodeEditorEvent::CompletionNavigateDown => { + me.completion_state.move_selection(1); + ctx.notify(); + } + CodeEditorEvent::CompletionConfirm => { + me.confirm_completion(ctx); + } CodeEditorEvent::VimGotoDefinition | CodeEditorEvent::VimFindReferences | CodeEditorEvent::VimShowHover => { @@ -1934,6 +1945,9 @@ impl LocalCodeEditorView { let had_refs = self.close_find_references_card(ctx); let had_hover = self.lsp_hover_state.clear(); let had_completion = self.completion_state.dismiss(); + if had_completion { + self.sync_completion_intercept(ctx); + } had_refs || had_hover || had_completion }