mirror of
https://github.com/peco/peco.git
synced 2026-09-10 07:16:29 -04:00
Merge branch 'master' into make-some-pizza
Conflicts: ctx.go keymap.go matchers.go
This commit is contained in:
commit
124a994499
26
Changes
26
Changes
|
|
@ -1,6 +1,32 @@
|
|||
Changes
|
||||
=======
|
||||
|
||||
v0.1.4 - 17 Jun 2014
|
||||
Buts/Fixes:
|
||||
* Check for ev.Ch and ev.Key (should fix input problems)
|
||||
* Fix crashing issue on empty match
|
||||
Features:
|
||||
* In your config, setting the value to "-" will remove the
|
||||
binding.
|
||||
* Default ToggleSelect binding has been changed to
|
||||
ToggleSelectAndSelectNext
|
||||
|
||||
v0.1.3 - 17 Jun 2014
|
||||
Bugs/Fixes:
|
||||
* When dealing with fast/successive user input on large buffers,
|
||||
peco was taking too long to execute queries.
|
||||
* XDG style config directories are now searched, and if all fails,
|
||||
falls back to the original ~/.peco/config.json
|
||||
* Some internal cleanup
|
||||
Features:
|
||||
* Multiple line selection has been implemented. Ctrl-Space will
|
||||
toggle the currently selected line, and peco will exit after
|
||||
printing all the selected lines. Note that on OS X, Spotlight
|
||||
by default captures these keys. You may need to reconfigure
|
||||
your settings.
|
||||
* Custom matchers via external processes have been implemented.
|
||||
See the README for more details
|
||||
|
||||
v0.1.2 - 16 Jun 2014
|
||||
Bugs/Fixes:
|
||||
* Multiple queries were not being match fully until the end of line
|
||||
|
|
|
|||
19
README.md
19
README.md
|
|
@ -28,18 +28,24 @@ When you combine tools like zsh, peco, and [ghq](https://github.com/motemen/ghq)
|
|||
Features
|
||||
========
|
||||
|
||||
## Incremental search
|
||||
## Incremental Search
|
||||
|
||||
Search results are filtered as you type. This is great to drill down to the
|
||||
line you are looking for
|
||||
|
||||
Multiple terms turn the query into an "AND" query:
|
||||
|
||||

|
||||

|
||||
|
||||
When you find that line that you want, press enter, and the resulting line
|
||||
is printed to stdout, which allows you to pipe it to other tools
|
||||
|
||||
## Select Multiple Lines
|
||||
|
||||
You can select multiple lines!
|
||||
|
||||

|
||||
|
||||
## Select Matchers
|
||||
|
||||
Different types of matchers are available. Default is case-insensitive matcher, so lines with any case will match. You can toggle between IgnoreCase, CaseSensitive, and RegExp matchers. The RegExp matcher allows you to use any valid regular expression to match lines
|
||||
|
|
@ -197,6 +203,8 @@ Example:
|
|||
| peco.SelectNextPage | Jumps to next page|
|
||||
| peco.SelectPrevious | Selects previous line |
|
||||
| peco.SelectNext | Selects next line |
|
||||
| peco.ToggleSelection | Selects the current line, and saves it |
|
||||
| peco.ToggleSelectionAndSelectNext | Selects the current line, saves it, and proceeds to the next line |
|
||||
| peco.RotateMatcher | Rotate between matchers (by default, ignore-case/no-ignore-case)|
|
||||
| peco.Finish | Exits from peco, with success status |
|
||||
| peco.Cancel | Exits from peco, with failure status |
|
||||
|
|
@ -247,9 +255,9 @@ For now, styles of following 3 items can be customized in `config.json`.
|
|||
|
||||
This is an experimental feature. Please note that some details of this specificaiton may change
|
||||
|
||||
By default `peco` comes with `IgnoreCase`, `CaseSensitive`, and `Regexp` matchers, but it is possible to create your own custom matcher.
|
||||
By default `peco` comes with `IgnoreCase`, `CaseSensitive`, and `Regexp` matchers, but since v0.1.3, it is possible to create your own custom matcher.
|
||||
|
||||
The matcher will be executed via `Command.Run()` as an external process, and it will be passed the query values in the command line, and the original unaltered buffer is passed via `os.Stdin`. Your matcher must perform the matching, and print out to `os.Stdout` matched lines. Note that currently there is no way to specify where in the line the match occurred.
|
||||
The matcher will be executed via `Command.Run()` as an external process, and it will be passed the query values in the command line, and the original unaltered buffer is passed via `os.Stdin`. Your matcher must perform the matching, and print out to `os.Stdout` matched lines. Note that currently there is no way to specify where in the line the match occurred. Note that the matcher does not need to be a go program. It can be a perl/ruby/python/bash script, or anything else that is executable.
|
||||
|
||||
Once you have a matcher, you must specify how the matcher is spawned:
|
||||
|
||||
|
|
@ -267,7 +275,8 @@ You may specify as many matchers as you like.
|
|||
|
||||
## Examples
|
||||
|
||||
* [C/Migemo](https://github.com/lestrrat/peco/wiki/CustoMatcher-CMigemo)
|
||||
* [An example of a simple perl regexp matcher](https://gist.github.com/mattn/24712964da6e3112251c)
|
||||
* [C/Migemo](https://github.com/mattn/peco-cmigemo/)
|
||||
|
||||
Hacking
|
||||
=======
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/nsf/termbox-go"
|
||||
)
|
||||
|
||||
var version = "v0.1.2"
|
||||
var version = "v0.1.4"
|
||||
|
||||
func showHelp() {
|
||||
const v = `
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
)
|
||||
|
||||
var currentUser = user.Current
|
||||
|
||||
type Config struct {
|
||||
Keymap Keymap `json:"Keymap"`
|
||||
Matcher string `json:"Matcher"`
|
||||
|
|
@ -131,6 +132,7 @@ func stringsToStyle(raw []string) *Style {
|
|||
}
|
||||
|
||||
var _locateRcfileIn = locateRcfileIn
|
||||
|
||||
func locateRcfileIn(dir string) (string, error) {
|
||||
const basename = "config.json"
|
||||
file := filepath.Join(dir, basename)
|
||||
|
|
|
|||
4
ctx.go
4
ctx.go
|
|
@ -60,6 +60,7 @@ func (s Selection) Less(i, j int) bool {
|
|||
// data in this struct from anwyehre, only do so via channels
|
||||
type Ctx struct {
|
||||
enableSep bool
|
||||
statusMessage string
|
||||
result []Match
|
||||
loopCh chan struct{}
|
||||
queryCh chan string
|
||||
|
|
@ -83,6 +84,7 @@ type Ctx struct {
|
|||
func NewCtx(enableSep bool) *Ctx {
|
||||
return &Ctx{
|
||||
enableSep,
|
||||
"",
|
||||
[]Match{},
|
||||
make(chan struct{}), // loopCh. You never send messages to this. no point in buffering
|
||||
make(chan string, 5), // queryCh.
|
||||
|
|
@ -188,7 +190,7 @@ func (c *Ctx) NewView() *View {
|
|||
}
|
||||
|
||||
func (c *Ctx) NewFilter() *Filter {
|
||||
return &Filter{c}
|
||||
return &Filter{c, make(chan string)}
|
||||
}
|
||||
|
||||
func (c *Ctx) NewInput() *Input {
|
||||
|
|
|
|||
28
filter.go
28
filter.go
|
|
@ -2,19 +2,41 @@ package peco
|
|||
|
||||
type Filter struct {
|
||||
*Ctx
|
||||
jobs chan string
|
||||
}
|
||||
|
||||
func (f *Filter) Work(cancel chan struct{}, q string) {
|
||||
if q == "" {
|
||||
f.DrawMatches(nil)
|
||||
return
|
||||
}
|
||||
results := f.Matcher().Match(cancel, q, f.Buffer())
|
||||
f.statusMessage = ""
|
||||
f.selection.Clear()
|
||||
f.DrawMatches(results)
|
||||
}
|
||||
|
||||
func (f *Filter) Loop() {
|
||||
defer f.ReleaseWaitGroup()
|
||||
|
||||
// previous holds a channel that can cancel the previous
|
||||
// query. This is used when multiple queries come in succession
|
||||
// and the previous query is discarded anyway
|
||||
var previous chan struct{}
|
||||
for {
|
||||
select {
|
||||
case <-f.LoopCh():
|
||||
return
|
||||
case q := <-f.QueryCh():
|
||||
results := f.Matcher().Match(q, f.Buffer())
|
||||
f.selection.Clear()
|
||||
f.DrawMatches(results)
|
||||
if previous != nil {
|
||||
// Tell the previous query to stop
|
||||
previous <- struct{}{}
|
||||
}
|
||||
previous = make(chan struct{}, 1)
|
||||
|
||||
f.statusMessage = "Running query..."
|
||||
f.DrawMatches(nil)
|
||||
go f.Work(previous, q)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
input.go
2
input.go
|
|
@ -43,7 +43,7 @@ func (i *Input) Loop() {
|
|||
}
|
||||
|
||||
func (i *Input) handleKeyEvent(ev termbox.Event) {
|
||||
if h := i.config.Keymap.Handler(ev.Key); h != nil {
|
||||
if h := i.config.Keymap.Handler(ev); h != nil {
|
||||
h(i, ev)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
68
keymap.go
68
keymap.go
|
|
@ -131,7 +131,9 @@ func handleFinish(i *Input, _ termbox.Event) {
|
|||
|
||||
i.result = []Match{}
|
||||
for _, lineno := range i.selection {
|
||||
i.result = append(i.result, i.current[lineno-1])
|
||||
if lineno <= len(i.current) {
|
||||
i.result = append(i.result, i.current[lineno-1])
|
||||
}
|
||||
}
|
||||
i.Finish()
|
||||
}
|
||||
|
|
@ -144,6 +146,11 @@ func handleToggleSelection(i *Input, _ termbox.Event) {
|
|||
i.selection.Add(i.currentLine)
|
||||
}
|
||||
|
||||
func handleToggleSelectionAndSelectNext(i *Input, ev termbox.Event) {
|
||||
handleToggleSelection(i, ev)
|
||||
handleSelectNext(i, ev)
|
||||
}
|
||||
|
||||
// peco.Cancel -> end program, exit with failure
|
||||
func handleCancel(i *Input, ev termbox.Event) {
|
||||
i.ExitStatus = 1
|
||||
|
|
@ -430,27 +437,28 @@ func (ksk KeymapStringKey) ToKey() (k termbox.Key, err error) {
|
|||
}
|
||||
|
||||
var handlers = map[string]KeymapHandler{
|
||||
"peco.KillEndOfLine": handleKillEndOfLine,
|
||||
"peco.DeleteAll": handleDeleteAll,
|
||||
"peco.BeginningOfLine": handleBeginningOfLine,
|
||||
"peco.EndOfLine": handleEndOfLine,
|
||||
"peco.EndOfFile": handleEndOfFile,
|
||||
"peco.ForwardChar": handleForwardChar,
|
||||
"peco.BackwardChar": handleBackwardChar,
|
||||
"peco.ForwardWord": handleForwardWord,
|
||||
"peco.BackwardWord": handleBackwardWord,
|
||||
"peco.DeleteForwardChar": handleDeleteForwardChar,
|
||||
"peco.DeleteBackwardChar": handleDeleteBackwardChar,
|
||||
"peco.DeleteForwardWord": handleDeleteForwardWord,
|
||||
"peco.DeleteBackwardWord": handleDeleteBackwardWord,
|
||||
"peco.SelectPreviousPage": handleSelectPreviousPage,
|
||||
"peco.SelectNextPage": handleSelectNextPage,
|
||||
"peco.SelectPrevious": handleSelectPrevious,
|
||||
"peco.SelectNext": handleSelectNext,
|
||||
"peco.ToggleSelection": handleToggleSelection,
|
||||
"peco.RotateMatcher": handleRotateMatcher,
|
||||
"peco.Finish": handleFinish,
|
||||
"peco.Cancel": handleCancel,
|
||||
"peco.KillEndOfLine": handleKillEndOfLine,
|
||||
"peco.DeleteAll": handleDeleteAll,
|
||||
"peco.BeginningOfLine": handleBeginningOfLine,
|
||||
"peco.EndOfLine": handleEndOfLine,
|
||||
"peco.EndOfFile": handleEndOfFile,
|
||||
"peco.ForwardChar": handleForwardChar,
|
||||
"peco.BackwardChar": handleBackwardChar,
|
||||
"peco.ForwardWord": handleForwardWord,
|
||||
"peco.BackwardWord": handleBackwardWord,
|
||||
"peco.DeleteForwardChar": handleDeleteForwardChar,
|
||||
"peco.DeleteBackwardChar": handleDeleteBackwardChar,
|
||||
"peco.DeleteForwardWord": handleDeleteForwardWord,
|
||||
"peco.DeleteBackwardWord": handleDeleteBackwardWord,
|
||||
"peco.SelectPreviousPage": handleSelectPreviousPage,
|
||||
"peco.SelectNextPage": handleSelectNextPage,
|
||||
"peco.SelectPrevious": handleSelectPrevious,
|
||||
"peco.SelectNext": handleSelectNext,
|
||||
"peco.ToggleSelection": handleToggleSelection,
|
||||
"peco.ToggleSelectionAndSelectNext": handleToggleSelectionAndSelectNext,
|
||||
"peco.RotateMatcher": handleRotateMatcher,
|
||||
"peco.Finish": handleFinish,
|
||||
"peco.Cancel": handleCancel,
|
||||
}
|
||||
|
||||
func NewKeymap() Keymap {
|
||||
|
|
@ -474,13 +482,16 @@ func NewKeymap() Keymap {
|
|||
termbox.KeyCtrlK: handleKillEndOfLine,
|
||||
termbox.KeyCtrlU: handleKillBeginOfLine,
|
||||
termbox.KeyCtrlR: handleRotateMatcher,
|
||||
termbox.KeyCtrlSpace: handleToggleSelectionAndSelectNext,
|
||||
}
|
||||
}
|
||||
|
||||
func (km Keymap) Handler(k termbox.Key) KeymapHandler {
|
||||
h, ok := km[k]
|
||||
if ok {
|
||||
return h
|
||||
func (km Keymap) Handler(ev termbox.Event) KeymapHandler {
|
||||
if ev.Ch == 0 {
|
||||
h, ok := km[ev.Key]
|
||||
if ok {
|
||||
return h
|
||||
}
|
||||
}
|
||||
return handleAcceptChar
|
||||
}
|
||||
|
|
@ -498,6 +509,11 @@ func (km Keymap) UnmarshalJSON(buf []byte) error {
|
|||
continue
|
||||
}
|
||||
|
||||
if vs == "-" {
|
||||
delete(km, k)
|
||||
continue
|
||||
}
|
||||
|
||||
v, ok := handlers[vs]
|
||||
if !ok {
|
||||
fmt.Fprintf(os.Stderr, "Unknown handler %s", vs)
|
||||
|
|
|
|||
133
matchers.go
133
matchers.go
|
|
@ -12,13 +12,13 @@ import (
|
|||
// we have a DidMatch and NoMatch types instead of using []Match and []string.
|
||||
type Match interface {
|
||||
Buffer() string // Raw buffer, may contain null
|
||||
Line() string // Line to be displayed
|
||||
Line() string // Line to be displayed
|
||||
Output() string // Output string to be displayed after peco is done
|
||||
Indices() [][]int
|
||||
}
|
||||
|
||||
type MatchString struct {
|
||||
buf string
|
||||
buf string
|
||||
sepLoc int
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ func (m NoMatch) Indices() [][]int {
|
|||
return nil
|
||||
}
|
||||
|
||||
// DidMatch contains the actual match, and the indices to the matches
|
||||
// DidMatch contains the actual match, and the indices to the matches
|
||||
// in the line
|
||||
type DidMatch struct {
|
||||
*MatchString
|
||||
|
|
@ -92,7 +92,15 @@ func (d DidMatch) Indices() [][]int {
|
|||
// Matcher interface defines the API for things that want to
|
||||
// match against the buffer
|
||||
type Matcher interface {
|
||||
Match(string, []Match) []Match
|
||||
// Match takes in three parameters.
|
||||
//
|
||||
// The first chan is the channel where cancel requests are sent.
|
||||
// If you receive a request here, you should stop running your query.
|
||||
//
|
||||
// The second is the query. Do what you want with it
|
||||
//
|
||||
// The third is the buffer in which to match the query against.
|
||||
Match(chan struct{}, string, []Match) []Match
|
||||
String() string
|
||||
}
|
||||
|
||||
|
|
@ -118,8 +126,8 @@ type IgnoreCaseMatcher struct {
|
|||
|
||||
type CustomMatcher struct {
|
||||
enableSep bool
|
||||
name string
|
||||
args []string
|
||||
name string
|
||||
args []string
|
||||
}
|
||||
|
||||
func NewCaseSensitiveMatcher(enableSep bool) *CaseSensitiveMatcher {
|
||||
|
|
@ -210,19 +218,54 @@ func (m byStart) Less(i, j int) bool {
|
|||
return m[i][0] < m[j][0]
|
||||
}
|
||||
|
||||
func (m *RegexpMatcher) Match(q string, buffer []Match) []Match {
|
||||
func (m *RegexpMatcher) Match(quit chan struct{}, q string, buffer []Match) []Match {
|
||||
results := []Match{}
|
||||
regexps, err := m.QueryToRegexps(q)
|
||||
if err != nil {
|
||||
return results
|
||||
}
|
||||
|
||||
for _, line := range buffer {
|
||||
ms := m.MatchAllRegexps(regexps, line.Line())
|
||||
if ms == nil {
|
||||
continue
|
||||
// The actual matching is done in a separate goroutine
|
||||
iter := make(chan Match, len(buffer))
|
||||
go func() {
|
||||
// This protects us from panics, caused when we cancel the
|
||||
// query and forcefully close the channel (and thereby
|
||||
// causing a "close of a closed channel"
|
||||
defer func() { recover() }()
|
||||
|
||||
// This must be here to make sure the channel is properly
|
||||
// closed in normal cases
|
||||
defer close(iter)
|
||||
|
||||
// Iterate through the lines, and do the match.
|
||||
// Upon success, send it through the channel
|
||||
for _, match := range buffer {
|
||||
ms := m.MatchAllRegexps(regexps, match.Line())
|
||||
if ms == nil {
|
||||
continue
|
||||
}
|
||||
iter <- NewDidMatch(match.Buffer(), m.enableSep, ms)
|
||||
}
|
||||
iter <- nil
|
||||
}()
|
||||
|
||||
MATCH:
|
||||
for {
|
||||
select {
|
||||
case <-quit:
|
||||
// If we recieved a cancel request, we immediately bail out.
|
||||
// It's a little dirty, but we focefully terminate the other
|
||||
// goroutine by closing the channel, and invoking a panic
|
||||
close(iter)
|
||||
break MATCH
|
||||
case match := <-iter:
|
||||
// Receive elements from the goroutine performing the match
|
||||
if match == nil {
|
||||
break MATCH
|
||||
}
|
||||
|
||||
results = append(results, match)
|
||||
}
|
||||
results = append(results, NewDidMatch(line.Buffer(), m.enableSep, ms))
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
|
@ -263,40 +306,68 @@ Match:
|
|||
return matches
|
||||
}
|
||||
|
||||
func (m *CustomMatcher) Match(q string, buffer []Match) []Match {
|
||||
func (m *CustomMatcher) Match(quit chan struct{}, q string, buffer []Match) []Match {
|
||||
if len(m.args) < 1 {
|
||||
return []Match{}
|
||||
}
|
||||
|
||||
results := []Match{}
|
||||
if q != "" {
|
||||
lines := []Match{}
|
||||
matcherInput := ""
|
||||
if q == "" {
|
||||
for _, match := range buffer {
|
||||
matcherInput += match.Line() + "\n"
|
||||
lines = append(lines, match)
|
||||
results = append(results, NewDidMatch(match.Buffer(), m.enableSep, nil))
|
||||
}
|
||||
args := []string{}
|
||||
for _, arg := range m.args {
|
||||
if arg == "$QUERY" {
|
||||
arg = q
|
||||
return results
|
||||
}
|
||||
|
||||
// Receive elements from the goroutine performing the match
|
||||
lines := []Match{}
|
||||
matcherInput := ""
|
||||
for _, match := range buffer {
|
||||
matcherInput += match.Line() + "\n"
|
||||
lines = append(lines, match)
|
||||
}
|
||||
args := []string{}
|
||||
for _, arg := range m.args {
|
||||
if arg == "$QUERY" {
|
||||
arg = q
|
||||
}
|
||||
args = append(args, arg)
|
||||
}
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stdin = strings.NewReader(matcherInput)
|
||||
|
||||
// See RegexpMatcher.Match() for explanation of constructs
|
||||
iter := make(chan Match, len(buffer))
|
||||
go func() {
|
||||
defer func() { recover() }()
|
||||
defer func() {
|
||||
close(iter)
|
||||
if p := cmd.Process; p != nil {
|
||||
p.Kill()
|
||||
}
|
||||
args = append(args, arg)
|
||||
}
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stdin = strings.NewReader(matcherInput)
|
||||
}()
|
||||
b, err := cmd.Output()
|
||||
if err != nil {
|
||||
return []Match{}
|
||||
iter <- nil
|
||||
}
|
||||
for _, line := range strings.Split(string(b), "\n") {
|
||||
if len(line) > 0 {
|
||||
results = append(results, NewDidMatch(line, m.enableSep, nil))
|
||||
iter <- NewDidMatch(line, m.enableSep, nil)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, match := range buffer {
|
||||
results = append(results, NewDidMatch(match.Buffer(), m.enableSep, nil))
|
||||
iter <- nil
|
||||
}()
|
||||
MATCH:
|
||||
for {
|
||||
select {
|
||||
case <-quit:
|
||||
close(iter)
|
||||
break MATCH
|
||||
case match := <-iter:
|
||||
if match == nil {
|
||||
break MATCH
|
||||
}
|
||||
results = append(results, match)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
98
view.go
98
view.go
|
|
@ -8,33 +8,57 @@ import (
|
|||
"github.com/nsf/termbox-go"
|
||||
)
|
||||
|
||||
// View handles the drawing/updating the screen
|
||||
type View struct {
|
||||
*Ctx
|
||||
}
|
||||
|
||||
// PagingRequest can be sent to move the selection cursor
|
||||
type PagingRequest int
|
||||
|
||||
const (
|
||||
// ToNextLine moves the selection to the next line
|
||||
ToNextLine PagingRequest = iota
|
||||
// ToNextPage moves the selection to the next page
|
||||
ToNextPage
|
||||
// ToPrevLine moves the selection to the previous line
|
||||
ToPrevLine
|
||||
// ToPrevPage moves the selection to the previous page
|
||||
ToPrevPage
|
||||
)
|
||||
|
||||
func (u *View) Loop() {
|
||||
defer u.ReleaseWaitGroup()
|
||||
// Loop receives requests to update the screen
|
||||
func (v *View) Loop() {
|
||||
defer v.ReleaseWaitGroup()
|
||||
for {
|
||||
select {
|
||||
case <-u.LoopCh():
|
||||
case <-v.LoopCh():
|
||||
return
|
||||
case r := <-u.PagingCh():
|
||||
u.movePage(r)
|
||||
case lines := <-u.DrawCh():
|
||||
u.drawScreen(lines)
|
||||
case r := <-v.PagingCh():
|
||||
v.movePage(r)
|
||||
case lines := <-v.DrawCh():
|
||||
v.drawScreen(lines)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *View) printStatus() {
|
||||
w, h := termbox.Size()
|
||||
|
||||
msg := v.statusMessage
|
||||
width := runewidth.StringWidth(msg)
|
||||
|
||||
pad := make([]byte, w - width)
|
||||
for i := 0; i < w - width; i++ {
|
||||
pad[i] = ' '
|
||||
}
|
||||
|
||||
printTB(0, h - 2, termbox.ColorDefault, termbox.ColorDefault, string(pad))
|
||||
if width > 0 {
|
||||
printTB(w - width, h - 2, termbox.AttrReverse|termbox.ColorDefault|termbox.AttrBold, termbox.AttrReverse|termbox.ColorDefault, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func printTB(x, y int, fg, bg termbox.Attribute, msg string) {
|
||||
for len(msg) > 0 {
|
||||
c, w := utf8.DecodeRuneInString(msg)
|
||||
|
|
@ -83,17 +107,17 @@ func (v *View) movePage(p PagingRequest) {
|
|||
v.drawScreen(nil)
|
||||
}
|
||||
|
||||
func (u *View) drawScreen(targets []Match) {
|
||||
u.mutex.Lock()
|
||||
defer u.mutex.Unlock()
|
||||
func (v *View) drawScreen(targets []Match) {
|
||||
v.mutex.Lock()
|
||||
defer v.mutex.Unlock()
|
||||
|
||||
termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
|
||||
|
||||
if targets == nil {
|
||||
if current := u.Ctx.current; current != nil {
|
||||
targets = u.Ctx.current
|
||||
if current := v.Ctx.current; current != nil {
|
||||
targets = v.Ctx.current
|
||||
} else {
|
||||
targets = u.Ctx.lines
|
||||
targets = v.Ctx.lines
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -101,7 +125,7 @@ func (u *View) drawScreen(targets []Match) {
|
|||
perPage := height - 4
|
||||
|
||||
CALCULATE_PAGE:
|
||||
currentPage := ((u.Ctx.currentLine - 1) / perPage) + 1
|
||||
currentPage := ((v.Ctx.currentLine - 1) / perPage) + 1
|
||||
if currentPage <= 0 {
|
||||
currentPage = 1
|
||||
}
|
||||
|
|
@ -114,7 +138,7 @@ CALCULATE_PAGE:
|
|||
}
|
||||
|
||||
if maxPage < currentPage {
|
||||
u.Ctx.currentLine = offset
|
||||
v.Ctx.currentLine = offset
|
||||
goto CALCULATE_PAGE
|
||||
}
|
||||
|
||||
|
|
@ -122,24 +146,24 @@ CALCULATE_PAGE:
|
|||
promptLen := runewidth.StringWidth(prompt)
|
||||
printTB(0, 0, termbox.ColorDefault, termbox.ColorDefault, prompt)
|
||||
|
||||
if u.caretPos <= 0 {
|
||||
u.caretPos = 0 // sanity
|
||||
if v.caretPos <= 0 {
|
||||
v.caretPos = 0 // sanity
|
||||
}
|
||||
if u.caretPos > len(u.query) {
|
||||
u.caretPos = len(u.query)
|
||||
if v.caretPos > len(v.query) {
|
||||
v.caretPos = len(v.query)
|
||||
}
|
||||
|
||||
if u.caretPos == len(u.query) {
|
||||
if v.caretPos == len(v.query) {
|
||||
// the entire string + the caret after the string
|
||||
printTB(promptLen+1, 0, termbox.ColorDefault, termbox.ColorDefault, string(u.query))
|
||||
termbox.SetCell(promptLen+1+runewidth.StringWidth(string(u.query)), 0, ' ', termbox.ColorDefault|termbox.AttrReverse, termbox.ColorDefault|termbox.AttrReverse)
|
||||
printTB(promptLen+1, 0, termbox.ColorDefault, termbox.ColorDefault, string(v.query))
|
||||
termbox.SetCell(promptLen+1+runewidth.StringWidth(string(v.query)), 0, ' ', termbox.ColorDefault|termbox.AttrReverse, termbox.ColorDefault|termbox.AttrReverse)
|
||||
} else {
|
||||
// the caret is in the middle of the string
|
||||
prev := 0
|
||||
for i, r := range u.query {
|
||||
for i, r := range v.query {
|
||||
fg := termbox.ColorDefault
|
||||
bg := termbox.ColorDefault
|
||||
if i == u.caretPos {
|
||||
if i == v.caretPos {
|
||||
fg |= termbox.AttrReverse
|
||||
bg |= termbox.AttrReverse
|
||||
}
|
||||
|
|
@ -148,19 +172,19 @@ CALCULATE_PAGE:
|
|||
}
|
||||
}
|
||||
|
||||
pmsg := fmt.Sprintf("%s [%d/%d]", u.Ctx.Matcher().String(), currentPage, maxPage)
|
||||
pmsg := fmt.Sprintf("%s [%d/%d]", v.Ctx.Matcher().String(), currentPage, maxPage)
|
||||
|
||||
printTB(width-runewidth.StringWidth(pmsg), 0, termbox.ColorDefault, termbox.ColorDefault, pmsg)
|
||||
|
||||
for n := 1; n <= perPage; n++ {
|
||||
fgAttr := u.config.Style.Basic.fg
|
||||
bgAttr := u.config.Style.Basic.bg
|
||||
if n+offset == u.currentLine {
|
||||
fgAttr = u.config.Style.Selected.fg
|
||||
bgAttr = u.config.Style.Selected.bg
|
||||
} else if u.selection.Has(n+offset) {
|
||||
fgAttr = u.config.Style.SavedSelection.fg
|
||||
bgAttr = u.config.Style.SavedSelection.bg
|
||||
fgAttr := v.config.Style.Basic.fg
|
||||
bgAttr := v.config.Style.Basic.bg
|
||||
if n+offset == v.currentLine {
|
||||
fgAttr = v.config.Style.Selected.fg
|
||||
bgAttr = v.config.Style.Selected.bg
|
||||
} else if v.selection.Has(n+offset) {
|
||||
fgAttr = v.config.Style.SavedSelection.fg
|
||||
bgAttr = v.config.Style.SavedSelection.bg
|
||||
}
|
||||
|
||||
targetIdx := offset + n - 1
|
||||
|
|
@ -184,21 +208,23 @@ CALCULATE_PAGE:
|
|||
index += len(c)
|
||||
}
|
||||
c := line[m[0]:m[1]]
|
||||
printTB(prev, n, u.config.Style.Query.fg, bgAttr|u.config.Style.Query.bg, c)
|
||||
printTB(prev, n, v.config.Style.Query.fg, bgAttr|v.config.Style.Query.bg, c)
|
||||
prev += runewidth.StringWidth(c)
|
||||
index += len(c)
|
||||
}
|
||||
|
||||
m := matches[len(matches)-1]
|
||||
if m[0] > prev {
|
||||
printTB(prev, n, u.config.Style.Query.fg, bgAttr|u.config.Style.Query.bg, line[m[0]:m[1]])
|
||||
printTB(prev, n, v.config.Style.Query.fg, bgAttr|v.config.Style.Query.bg, line[m[0]:m[1]])
|
||||
} else if len(line) > m[1] {
|
||||
printTB(prev, n, fgAttr, bgAttr, line[m[1]:len(line)])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v.printStatus()
|
||||
termbox.Flush()
|
||||
|
||||
// FIXME
|
||||
u.current = targets
|
||||
v.current = targets
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue