Closed gwenn closed 10 years ago
Does this do what you want?
diff --git a/line.go b/line.go
index 53f725f..a6a0eaf 100644
--- a/line.go
+++ b/line.go
@@ -122,22 +122,23 @@ func (s *State) refresh(prompt string, buf string, pos int) error {
return err
}
-func (s *State) tabComplete(p string, line []rune) ([]rune, interface{}, error) {
+func (s *State) tabComplete(p string, line []rune, pos int) ([]rune, int, interface{}, error) {
if s.completer == nil {
- return line, rune(tab), nil
+ return line, pos, rune(tab), nil
}
- list := s.completer(string(line))
+ list := s.completer(string(line[:pos]))
if len(list) <= 0 {
- return line, rune(tab), nil
+ return line, pos, rune(tab), nil
}
listEntry := 0
+ tail := string(line[pos:])
for {
pick := list[listEntry]
- s.refresh(p, pick, len(pick))
+ s.refresh(p, pick+tail, utf8.RuneCountInString(pick))
next, err := s.readNext()
if err != nil {
- return line, rune(tab), err
+ return line, pos, rune(tab), err
}
if key, ok := next.(rune); ok {
if key == tab {
@@ -149,7 +150,7 @@ func (s *State) tabComplete(p string, line []rune) ([]rune, interface{}, error)
continue
}
if key == esc {
- return line, rune(esc), nil
+ return line, pos, rune(esc), nil
}
}
if a, ok := next.(action); ok && a == shiftTab {
@@ -160,10 +161,10 @@ func (s *State) tabComplete(p string, line []rune) ([]rune, interface{}, error)
}
continue
}
- return []rune(pick), next, nil
+ return []rune(pick + tail), utf8.RuneCountInString(pick), next, nil
}
// Not reached
- return line, rune(tab), nil
+ return line, pos, rune(tab), nil
}
// Prompt displays p, and then waits for user input. Prompt allows line editing
@@ -194,15 +195,12 @@ mainLoop:
return "", err
}
- if pos == len(line) {
- if key, ok := next.(rune); ok && key == tab {
- line, next, err = s.tabComplete(p, line)
- if err != nil {
- return "", err
- }
- pos = len(line)
- s.refresh(p, string(line), pos)
+ if key, ok := next.(rune); ok && key == tab {
+ line, pos, next, err = s.tabComplete(p, line, pos)
+ if err != nil {
+ return "", err
}
+ s.refresh(p, string(line), pos)
}
switch v := next.(type) {
Many thanks for your patch. But to make the completion work, I need the entire line (for example, to find that t is an alias to the table test) and ideally the cursor position (in the line). I know that editline/readline gives access to the entire line with the rl_line_buffer variable and to the cursor position with the rl_point variable. If the Completer function signature must be kept untouched, I've no idea how to make the (line, pos) variables, declared in the Prompt method, accessible to the Completer. Regards.
I was afraid of that.
I'm not willing to break the API. Therefore, the only way to do what you want is to add .SetCompleterWithPos(f CompleterWithPos)
(with a better name, of course). Internally, we should only save the WithPos version, and SetCompleter
would create a closure that saves the suffix from pos onwards, calls Completer with the prefix, and appends the suffix to all the replies.
I'd be willing to consider a patch that does the above, if you were to submit one.
Your proposition seems good to me. I will give it a try and send you a patch. Regards.
Ok, the first draft: No test has been performed to look for possible regression. I don't know if it is a good idea to let the user implements the logic needed to find the "word" to the left of the cursor... I will keep you posted if I find a better solution. Regards.
diff --git a/common.go b/common.go
index b424776..cc93188 100644
--- a/common.go
+++ b/common.go
@@ -19,7 +19,7 @@ type commonState struct {
terminalSupported bool
terminalOutput bool
history []string
- completer Completer
+ completer WordCompleter
columns int
}
@@ -99,12 +99,28 @@ func (s *State) getHistoryByPrefix(prefix string) (ph []string) {
return
}
-// Completer takes the currently edited line and returns a list
-// of completion candidates.
+// Completer takes the currently edited line content at the left of the cursor
+// and returns a list of completion candidates.
+// If the line is "Hello, wo!!!" and the cursor is before the first '!', "Hello, wo" is passed
+// to the completer which may return {"Hello, world", "Hello, Word"} to have "Hello, world!!!".
type Completer func(line string) []string
+// WordCompleter takes the currently edited line with the cursor position and
+// returns the completion candidates for the partial word to be completed.
+// If the line is "Hello, wo!!!" and the cursor is before the first '!', ("Hello, wo!!!", 9) is passed
+// to the completer which may returns ("Hello, ", {"world", "Word"}) to have "Hello, world!!!".
+type WordCompleter func(line string, pos int) (head string, completions []string)
+
// SetCompleter sets the completion function that Liner will call to
// fetch completion candidates when the user presses tab.
func (s *State) SetCompleter(f Completer) {
+ s.completer = func(line string, pos int) (string, []string) {
+ return "", f(line[:pos])
+ }
+}
+
+// SetWordCompleter sets the completion function that Liner will call to
+// fetch completion candidates when the user presses tab.
+func (s *State) SetWordCompleter(f WordCompleter) {
s.completer = f
}
diff --git a/line.go b/line.go
index 53f725f..b77ae77 100644
--- a/line.go
+++ b/line.go
@@ -122,22 +122,24 @@ func (s *State) refresh(prompt string, buf string, pos int) error {
return err
}
-func (s *State) tabComplete(p string, line []rune) ([]rune, interface{}, error) {
+func (s *State) tabComplete(p string, line []rune, pos int) ([]rune, int, interface{}, error) {
if s.completer == nil {
- return line, rune(tab), nil
+ return line, pos, rune(tab), nil
}
- list := s.completer(string(line))
+ head, list := s.completer(string(line), pos)
if len(list) <= 0 {
- return line, rune(tab), nil
+ return line, pos, rune(tab), nil
}
listEntry := 0
+ hl := utf8.RuneCountInString(head)
+ tail := string(line[pos:])
for {
pick := list[listEntry]
- s.refresh(p, pick, len(pick))
+ s.refresh(p, head+pick+tail, hl+utf8.RuneCountInString(pick))
next, err := s.readNext()
if err != nil {
- return line, rune(tab), err
+ return line, pos, rune(tab), err
}
if key, ok := next.(rune); ok {
if key == tab {
@@ -149,7 +151,7 @@ func (s *State) tabComplete(p string, line []rune) ([]rune, interface{}, error)
continue
}
if key == esc {
- return line, rune(esc), nil
+ return line, pos, rune(esc), nil
}
}
if a, ok := next.(action); ok && a == shiftTab {
@@ -160,10 +162,10 @@ func (s *State) tabComplete(p string, line []rune) ([]rune, interface{}, error)
}
continue
}
- return []rune(pick), next, nil
+ return []rune(head + pick + tail), hl + utf8.RuneCountInString(pick), next, nil
}
// Not reached
- return line, rune(tab), nil
+ return line, pos, rune(tab), nil
}
// Prompt displays p, and then waits for user input. Prompt allows line editing
@@ -194,15 +196,12 @@ mainLoop:
return "", err
}
- if pos == len(line) {
- if key, ok := next.(rune); ok && key == tab {
- line, next, err = s.tabComplete(p, line)
- if err != nil {
- return "", err
- }
- pos = len(line)
- s.refresh(p, string(line), pos)
+ if key, ok := next.(rune); ok && key == tab {
+ line, pos, next, err = s.tabComplete(p, line, pos)
+ if err != nil {
+ return "", err
}
+ s.refresh(p, string(line), pos)
}
switch v := next.(type) {
I don't know if it is a good idea to let the user implements the logic needed to find the "word" to the left of the cursor...
In general, liner can't tokenize the line, because liner doesn't know how the line should be tokenized. Different applications will have a different grammar.
My question is: Should we allow WordCompleter to erase or otherwise modify the tail?
Yes, it may be useful. But, for me, only the user can choose between erase or append mode (for example, in Jetbrains IDEA, one uses 'enter' to append or 'tab' to replace). And to make the replace mode works, we need to know where to stop...
If you want to replace part of the tail, you need to replace the whole tail (same problem as in my previous comment. Tokenizing can't be done without the grammar, and I'm against adding a grammar engine to liner).
Given the 2nd sentence in the README, it may not surprise you to discover that I prefer the Linenoise version you've described. It's the only option if you want to replace any part of the tail. Unless I've misread, it sounds like you do want to be able to replace part of the tail.
But since you're the one who will be using it (I'll probably keep using the original Completer), I can be convinced either way.
There are two similar issues for Linenoise:
And here is a patch where the WordCompleter returns the tail:
diff --git a/common.go b/common.go
index b424776..3ff110f 100644
--- a/common.go
+++ b/common.go
@@ -19,7 +19,7 @@ type commonState struct {
terminalSupported bool
terminalOutput bool
history []string
- completer Completer
+ completer WordCompleter
columns int
}
@@ -99,12 +99,28 @@ func (s *State) getHistoryByPrefix(prefix string) (ph []string) {
return
}
-// Completer takes the currently edited line and returns a list
-// of completion candidates.
+// Completer takes the currently edited line content at the left of the cursor
+// and returns a list of completion candidates.
+// If the line is "Hello, wo!!!" and the cursor is before the first '!', "Hello, wo" is passed
+// to the completer which may return {"Hello, world", "Hello, Word"} to have "Hello, world!!!".
type Completer func(line string) []string
+// WordCompleter takes the currently edited line with the cursor position and
+// returns the completion candidates for the partial word to be completed.
+// If the line is "Hello, wo!!!" and the cursor is before the first '!', ("Hello, wo!!!", 9) is passed
+// to the completer which may returns ("Hello, ", {"world", "Word"}, "!!!") to have "Hello, world!!!".
+type WordCompleter func(line string, pos int) (head string, completions []string, tail string)
+
// SetCompleter sets the completion function that Liner will call to
// fetch completion candidates when the user presses tab.
func (s *State) SetCompleter(f Completer) {
+ s.completer = func(line string, pos int) (string, []string, string) {
+ return "", f(line[:pos]), line[pos:]
+ }
+}
+
+// SetWordCompleter sets the completion function that Liner will call to
+// fetch completion candidates when the user presses tab.
+func (s *State) SetWordCompleter(f WordCompleter) {
s.completer = f
}
diff --git a/line.go b/line.go
index 53f725f..6411263 100644
--- a/line.go
+++ b/line.go
@@ -122,22 +122,23 @@ func (s *State) refresh(prompt string, buf string, pos int) error {
return err
}
-func (s *State) tabComplete(p string, line []rune) ([]rune, interface{}, error) {
+func (s *State) tabComplete(p string, line []rune, pos int) ([]rune, int, interface{}, error) {
if s.completer == nil {
- return line, rune(tab), nil
+ return line, pos, rune(tab), nil
}
- list := s.completer(string(line))
+ head, list, tail := s.completer(string(line), pos)
if len(list) <= 0 {
- return line, rune(tab), nil
+ return line, pos, rune(tab), nil
}
listEntry := 0
+ hl := utf8.RuneCountInString(head)
for {
pick := list[listEntry]
- s.refresh(p, pick, len(pick))
+ s.refresh(p, head+pick+tail, hl+utf8.RuneCountInString(pick))
next, err := s.readNext()
if err != nil {
- return line, rune(tab), err
+ return line, pos, rune(tab), err
}
if key, ok := next.(rune); ok {
if key == tab {
@@ -149,7 +150,7 @@ func (s *State) tabComplete(p string, line []rune) ([]rune, interface{}, error)
continue
}
if key == esc {
- return line, rune(esc), nil
+ return line, pos, rune(esc), nil
}
}
if a, ok := next.(action); ok && a == shiftTab {
@@ -160,10 +161,10 @@ func (s *State) tabComplete(p string, line []rune) ([]rune, interface{}, error)
}
continue
}
- return []rune(pick), next, nil
+ return []rune(head + pick + tail), hl + utf8.RuneCountInString(pick), next, nil
}
// Not reached
- return line, rune(tab), nil
+ return line, pos, rune(tab), nil
}
// Prompt displays p, and then waits for user input. Prompt allows line editing
@@ -194,15 +195,12 @@ mainLoop:
return "", err
}
- if pos == len(line) {
- if key, ok := next.(rune); ok && key == tab {
- line, next, err = s.tabComplete(p, line)
- if err != nil {
- return "", err
- }
- pos = len(line)
- s.refresh(p, string(line), pos)
+ if key, ok := next.(rune); ok && key == tab {
+ line, pos, next, err = s.tabComplete(p, line, pos)
+ if err != nil {
+ return "", err
}
+ s.refresh(p, string(line), pos)
}
switch v := next.(type) {
Thank you for the patch!
I've rebased your patch on top of mine and pushed it out.
Hello, Currently, the completion seems to work only at the end of the line. Would it be possible to make it work at current cursor position ? For an example: sql> create table test (id int, name text); sql> select t. from test t; When the cursor is after the dot, the table columns can be completed. Regards.