summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-08-16 17:05:24 +0300
committerPaul Buetow <paul@buetow.org>2025-08-16 17:05:24 +0300
commit778a3591bd27ce49acb6f8596f3c714351c412dc (patch)
treed4617e94d9543532b82d63021e6e8d1ae272eeb1
parentad62a3eb132924858d23694273923f1fc13ca22e (diff)
lsp/completion: strip inline ;...; prompt markers via AdditionalTextEdits; update tests
-rw-r--r--IDEAS.md10
-rw-r--r--internal/lsp/handlers.go54
-rw-r--r--internal/lsp/handlers_test.go21
-rw-r--r--internal/lsp/types.go19
4 files changed, 79 insertions, 25 deletions
diff --git a/IDEAS.md b/IDEAS.md
index 7eabb94..30e7708 100644
--- a/IDEAS.md
+++ b/IDEAS.md
@@ -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)