diff options
| author | Paul Buetow <paul@buetow.org> | 2025-08-16 17:05:24 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-08-16 17:05:24 +0300 |
| commit | 778a3591bd27ce49acb6f8596f3c714351c412dc (patch) | |
| tree | d4617e94d9543532b82d63021e6e8d1ae272eeb1 | |
| parent | ad62a3eb132924858d23694273923f1fc13ca22e (diff) | |
lsp/completion: strip inline ;...; prompt markers via AdditionalTextEdits; update tests
| -rw-r--r-- | IDEAS.md | 10 | ||||
| -rw-r--r-- | internal/lsp/handlers.go | 54 | ||||
| -rw-r--r-- | internal/lsp/handlers_test.go | 21 | ||||
| -rw-r--r-- | internal/lsp/types.go | 19 |
4 files changed, 79 insertions, 25 deletions
@@ -19,6 +19,8 @@ * [X] Text completion in general * [/] Code generation using LLMs text * [ ] Be a replacement for 'github copilot cli' +* [ ] Be able to perform inline chats (keeping history in the document) +* [ ] Be able to switch the underlying model via a prompt Be able to select code blocks and perform code actions on them @@ -40,7 +42,7 @@ Be able to switch LLMs. * [ ] Useful: https://deepwiki.com/helix-editor/helix/4.3-language-server-protocol` -## Internal usage notes +## Usage notes Helix' `languages.toml` @@ -57,7 +59,7 @@ language-servers = [ "gopls", "golangci-lint-lsp", "hexai" ] command = "hexai" ` +## Prompting -### Write a new function prompt - -// Implement a function counting the number of files in a directory <<==prompt +* Write a new function: `;Implement a function counting the number of files in a directory;` +* In-place code add: `;Get number of files in a directory;` diff --git a/internal/lsp/handlers.go b/internal/lsp/handlers.go index d58227c..565be64 100644 --- a/internal/lsp/handlers.go +++ b/internal/lsp/handlers.go @@ -156,18 +156,48 @@ func (s *Server) tryLLMCompletion(p CompletionParams, above, current, below, fun } te, filter := computeTextEditAndFilter(cleaned, inParams, current, p) - label := labelForCompletion(cleaned, filter) - items := []CompletionItem{{ - Label: label, - Kind: 1, - Detail: "OpenAI through Hexai completion", - InsertTextFormat: 1, - FilterText: strings.TrimLeft(filter, " \t"), - TextEdit: te, - SortText: "0000", - Documentation: docStr, - }} - return items, true + rm := s.collectPromptRemovalEdits(p.TextDocument.URI) + label := labelForCompletion(cleaned, filter) + items := []CompletionItem{{ + Label: label, + Kind: 1, + Detail: "OpenAI through Hexai completion", + InsertTextFormat: 1, + FilterText: strings.TrimLeft(filter, " \t"), + TextEdit: te, + AdditionalTextEdits: rm, + SortText: "0000", + Documentation: docStr, + }} + return items, true +} + +// collectPromptRemovalEdits returns edits to remove all inline prompt markers. +// Supported form (inclusive): +// - ";...;" (optional single space after trailing ';') +// Multiple markers per line are supported. +func (s *Server) collectPromptRemovalEdits(uri string) []TextEdit { + d := s.getDocument(uri) + if d == nil || len(d.lines) == 0 { + return nil + } + var edits []TextEdit + for i, line := range d.lines { + // Scan for ;...; markers + startSemi := 0 + for startSemi < len(line) { + j := strings.Index(line[startSemi:], ";") + if j < 0 { break } + j += startSemi + k := strings.Index(line[j+1:], ";") + if k < 0 { break } + endChar := j + 1 + k + 1 // include trailing ';' + if endChar < len(line) && line[endChar] == ' ' { endChar++ } + edits = append(edits, TextEdit{Range: Range{Start: Position{Line: i, Character: j}, End: Position{Line: i, Character: endChar}}, NewText: ""}) + startSemi = endChar + } + } + return edits } func inParamList(current string, cursor int) bool { diff --git a/internal/lsp/handlers_test.go b/internal/lsp/handlers_test.go index ecd9de6..6ce1e5d 100644 --- a/internal/lsp/handlers_test.go +++ b/internal/lsp/handlers_test.go @@ -126,3 +126,24 @@ func TestComputeTextEditAndFilter_NoParensFallback(t *testing.T) { func contains(s, sub string) bool { return len(s) >= len(sub) && (func() bool { i := 0; for i+len(sub) <= len(s) { if s[i:i+len(sub)] == sub { return true }; i++ }; return false })() } + + +func TestCollectPromptRemovalEdits(t *testing.T) { + s := newTestServer() + uri := "file:///x.go" + src := `keep ;tag; this and ;another; that +no markers here` + s.setDocument(uri, src) + edits := s.collectPromptRemovalEdits(uri) + if len(edits) != 2 { + t.Fatalf("expected 2 edits, got %d", len(edits)) + } + // First occurrence ;tag; + e0 := edits[0] + if e0.Range.Start.Line != 0 { + t.Fatalf("e0 start line=%d want 0", e0.Range.Start.Line) + } + if s.getDocument(uri).lines[0][e0.Range.Start.Character:e0.Range.Start.Character+1] != ";" { + t.Fatalf("e0 start not at ;") + } +} diff --git a/internal/lsp/types.go b/internal/lsp/types.go index 3a9a397..9338e4d 100644 --- a/internal/lsp/types.go +++ b/internal/lsp/types.go @@ -49,15 +49,16 @@ type CompletionList struct { } type CompletionItem struct { - Label string `json:"label"` - Kind int `json:"kind,omitempty"` - Detail string `json:"detail,omitempty"` - InsertText string `json:"insertText,omitempty"` - InsertTextFormat int `json:"insertTextFormat,omitempty"` - FilterText string `json:"filterText,omitempty"` - TextEdit *TextEdit `json:"textEdit,omitempty"` - SortText string `json:"sortText,omitempty"` - Documentation string `json:"documentation,omitempty"` + Label string `json:"label"` + Kind int `json:"kind,omitempty"` + Detail string `json:"detail,omitempty"` + InsertText string `json:"insertText,omitempty"` + InsertTextFormat int `json:"insertTextFormat,omitempty"` + FilterText string `json:"filterText,omitempty"` + TextEdit *TextEdit `json:"textEdit,omitempty"` + AdditionalTextEdits []TextEdit `json:"additionalTextEdits,omitempty"` + SortText string `json:"sortText,omitempty"` + Documentation string `json:"documentation,omitempty"` } // LSP param types (subset) |
