-
Notifications
You must be signed in to change notification settings - Fork 132
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
Completion at cursor position #14
Comments
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. |
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 I'd be willing to consider a patch that does the above, if you were to submit one. |
Your proposition seems good to me. |
Ok, the first draft: 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) { |
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.
The text was updated successfully, but these errors were encountered: