Merge pull request #745 from peco/extract-query-package

Extract query package
This commit is contained in:
lestrrat 2026-02-20 15:18:36 +09:00 committed by GitHub
commit 7b54585c95
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 129 additions and 137 deletions

View file

@ -13,6 +13,7 @@ import (
"github.com/peco/peco/hub"
"github.com/peco/peco/internal/keyseq"
"github.com/peco/peco/line"
"github.com/peco/peco/query"
"github.com/peco/peco/selection"
"github.com/stretchr/testify/require"
)
@ -265,12 +266,12 @@ func TestViewAroundActionName(t *testing.T) {
require.True(t, ok, "peco.ViewArround must remain registered for backward compatibility")
}
func expectCaretPos(t *testing.T, c *Caret, expect int) {
func expectCaretPos(t *testing.T, c *query.Caret, expect int) {
t.Helper()
require.Equal(t, expect, c.Pos(), "Expected caret position %d, got %d", expect, c.Pos())
}
func expectQueryString(t *testing.T, q *Query, expect string) {
func expectQueryString(t *testing.T, q *query.Text, expect string) {
t.Helper()
require.Equal(t, expect, q.String(), "Expected '%s', got '%s'", expect, q.String())
}

View file

@ -1,36 +0,0 @@
package peco
import "sync"
// Caret tracks the cursor position within the query line.
type Caret struct {
mutex sync.Mutex
pos int
}
// Pos returns the current caret position, thread-safe.
func (c *Caret) Pos() int {
c.mutex.Lock()
defer c.mutex.Unlock()
return c.pos
}
// setPosNL sets the caret position without acquiring the mutex.
// The caller must already hold the lock.
func (c *Caret) setPosNL(p int) {
c.pos = p
}
// SetPos sets the caret position, thread-safe.
func (c *Caret) SetPos(p int) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.setPosNL(p)
}
// Move moves the caret by the given delta, thread-safe.
func (c *Caret) Move(diff int) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.setPosNL(c.pos + diff)
}

View file

@ -1,67 +0,0 @@
package peco
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestCaretInitialPos(t *testing.T) {
t.Parallel()
var c Caret
require.Equal(t, 0, c.Pos())
}
func TestCaretSetPos(t *testing.T) {
t.Parallel()
var c Caret
c.SetPos(5)
require.Equal(t, 5, c.Pos())
c.SetPos(0)
require.Equal(t, 0, c.Pos())
c.SetPos(100)
require.Equal(t, 100, c.Pos())
}
func TestCaretMove(t *testing.T) {
t.Parallel()
var c Caret
c.SetPos(5)
c.Move(3)
require.Equal(t, 8, c.Pos())
c.Move(-2)
require.Equal(t, 6, c.Pos())
// Move to negative territory
c.Move(-10)
require.Equal(t, -4, c.Pos())
}
func TestCaretMoveFromZero(t *testing.T) {
t.Parallel()
var c Caret
c.Move(1)
require.Equal(t, 1, c.Pos())
c.Move(-1)
require.Equal(t, 0, c.Pos())
}
func TestCaretMultipleMoves(t *testing.T) {
t.Parallel()
var c Caret
for range 10 {
c.Move(1)
}
require.Equal(t, 10, c.Pos())
for range 5 {
c.Move(-1)
}
require.Equal(t, 5, c.Pos())
}

View file

@ -22,6 +22,7 @@ import (
"github.com/peco/peco/internal/util"
"github.com/peco/peco/line"
"github.com/peco/peco/pipeline"
"github.com/peco/peco/query"
"github.com/peco/peco/selection"
"github.com/peco/peco/sig"
)
@ -50,7 +51,7 @@ type Peco struct {
args []string
bufferSize int
caret Caret
caret query.Caret
// Config contains the values read in from config file
config Config
currentLineBuffer Buffer
@ -69,7 +70,7 @@ type Peco struct {
onCancel OnCancelBehavior
printQuery bool
prompt string
query Query
query query.Text
queryExec QueryExecState
readyCh chan struct{}
resultCh chan line.Line
@ -306,7 +307,7 @@ func (p *Peco) Filters() *filter.Set {
return &p.filters
}
func (p *Peco) Query() *Query {
func (p *Peco) Query() *query.Text {
return &p.query
}
@ -314,7 +315,7 @@ func (p *Peco) QueryExec() *QueryExecState {
return &p.queryExec
}
func (p *Peco) Caret() *Caret {
func (p *Peco) Caret() *query.Caret {
return &p.caret
}

View file

@ -1,35 +1,35 @@
package peco
package query
import "sync"
// Query holds the current query string and an optional saved query
// Text holds the current query string and an optional saved query
// for restore-after-cancel behavior.
type Query struct {
type Text struct {
query []rune
savedQuery []rune
mutex sync.Mutex
}
func (q *Query) Set(s string) {
func (q *Text) Set(s string) {
q.mutex.Lock()
defer q.mutex.Unlock()
q.query = []rune(s)
}
func (q *Query) Reset() {
func (q *Text) Reset() {
q.mutex.Lock()
defer q.mutex.Unlock()
q.query = []rune(nil)
}
func (q *Query) RestoreSavedQuery() {
func (q *Text) RestoreSavedQuery() {
q.mutex.Lock()
defer q.mutex.Unlock()
q.query = q.savedQuery
q.savedQuery = []rune(nil)
}
func (q *Query) SaveQuery() {
func (q *Text) SaveQuery() {
q.mutex.Lock()
defer q.mutex.Unlock()
q.savedQuery = q.query
@ -37,7 +37,7 @@ func (q *Query) SaveQuery() {
}
// DeleteRange deletes runes in the range [start, end) from the query with boundary validation.
func (q *Query) DeleteRange(start, end int) {
func (q *Text) DeleteRange(start, end int) {
q.mutex.Lock()
defer q.mutex.Unlock()
if start == -1 {
@ -59,20 +59,20 @@ func (q *Query) DeleteRange(start, end int) {
q.query = q.query[:l-(end-start)]
}
func (q *Query) String() string {
func (q *Text) String() string {
q.mutex.Lock()
defer q.mutex.Unlock()
return string(q.query)
}
func (q *Query) Len() int {
func (q *Text) Len() int {
q.mutex.Lock()
defer q.mutex.Unlock()
return len(q.query)
}
// RuneSlice returns a copy of the query runes
func (q *Query) RuneSlice() []rune {
func (q *Text) RuneSlice() []rune {
q.mutex.Lock()
defer q.mutex.Unlock()
out := make([]rune, len(q.query))
@ -80,7 +80,7 @@ func (q *Query) RuneSlice() []rune {
return out
}
func (q *Query) RuneAt(where int) rune {
func (q *Text) RuneAt(where int) rune {
q.mutex.Lock()
defer q.mutex.Unlock()
if where < 0 || where >= len(q.query) {
@ -90,7 +90,7 @@ func (q *Query) RuneAt(where int) rune {
}
// InsertAt inserts a rune at the specified position in the query.
func (q *Query) InsertAt(ch rune, where int) {
func (q *Text) InsertAt(ch rune, where int) {
q.mutex.Lock()
defer q.mutex.Unlock()
@ -106,3 +106,36 @@ func (q *Query) InsertAt(ch rune, where int) {
copy(buf[where+1:], sq[where:])
q.query = buf
}
// Caret tracks the cursor position within the query line.
type Caret struct {
mutex sync.Mutex
pos int
}
// Pos returns the current caret position, thread-safe.
func (c *Caret) Pos() int {
c.mutex.Lock()
defer c.mutex.Unlock()
return c.pos
}
// setPosNL sets the caret position without acquiring the mutex.
// The caller must already hold the lock.
func (c *Caret) setPosNL(p int) {
c.pos = p
}
// SetPos sets the caret position, thread-safe.
func (c *Caret) SetPos(p int) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.setPosNL(p)
}
// Move moves the caret by the given delta, thread-safe.
func (c *Caret) Move(diff int) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.setPosNL(c.pos + diff)
}

View file

@ -1,4 +1,4 @@
package peco
package query
import (
"testing"
@ -21,7 +21,7 @@ func TestQuerySetAndString(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var q Query
var q Text
q.Set(tt.input)
require.Equal(t, tt.input, q.String())
})
@ -30,7 +30,7 @@ func TestQuerySetAndString(t *testing.T) {
func TestQueryLen(t *testing.T) {
t.Parallel()
var q Query
var q Text
require.Equal(t, 0, q.Len())
q.Set("hello")
@ -43,7 +43,7 @@ func TestQueryLen(t *testing.T) {
func TestQueryReset(t *testing.T) {
t.Parallel()
var q Query
var q Text
q.Set("hello")
require.Equal(t, 5, q.Len())
@ -54,7 +54,7 @@ func TestQueryReset(t *testing.T) {
func TestQuerySaveAndRestore(t *testing.T) {
t.Parallel()
var q Query
var q Text
q.Set("original")
q.SaveQuery()
@ -72,7 +72,7 @@ func TestQuerySaveAndRestore(t *testing.T) {
func TestQuerySaveAndRestoreEmpty(t *testing.T) {
t.Parallel()
var q Query
var q Text
// Save an empty query
q.SaveQuery()
require.Equal(t, "", q.String())
@ -104,7 +104,7 @@ func TestQueryDeleteRange(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var q Query
var q Text
q.Set(tt.initial)
q.DeleteRange(tt.start, tt.end)
require.Equal(t, tt.expected, q.String())
@ -114,7 +114,7 @@ func TestQueryDeleteRange(t *testing.T) {
func TestQueryRuneSlice(t *testing.T) {
t.Parallel()
var q Query
var q Text
q.Set("hello")
runes := q.RuneSlice()
@ -127,14 +127,14 @@ func TestQueryRuneSlice(t *testing.T) {
func TestQueryRuneSliceEmpty(t *testing.T) {
t.Parallel()
var q Query
var q Text
runes := q.RuneSlice()
require.Empty(t, runes)
}
func TestQueryRuneAt(t *testing.T) {
t.Parallel()
var q Query
var q Text
q.Set("hello")
require.Equal(t, 'h', q.RuneAt(0))
@ -144,7 +144,7 @@ func TestQueryRuneAt(t *testing.T) {
func TestQueryRuneAtUnicode(t *testing.T) {
t.Parallel()
var q Query
var q Text
q.Set("あいう")
require.Equal(t, 'あ', q.RuneAt(0))
@ -154,7 +154,7 @@ func TestQueryRuneAtUnicode(t *testing.T) {
func TestQueryRuneAtOutOfBounds(t *testing.T) {
t.Parallel()
var q Query
var q Text
q.Set("hello")
// Out-of-bounds index returns zero rune without panicking
@ -166,7 +166,7 @@ func TestQueryRuneAtOutOfBounds(t *testing.T) {
require.Equal(t, rune(0), q.RuneAt(-100))
// Empty query: any index returns zero rune
var empty Query
var empty Text
require.Equal(t, rune(0), empty.RuneAt(0))
require.Equal(t, rune(0), empty.RuneAt(-1))
}
@ -189,7 +189,7 @@ func TestQueryInsertAt(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var q Query
var q Text
q.Set(tt.initial)
q.InsertAt(tt.ch, tt.where)
require.Equal(t, tt.expected, q.String())
@ -199,7 +199,7 @@ func TestQueryInsertAt(t *testing.T) {
func TestQueryMultipleInserts(t *testing.T) {
t.Parallel()
var q Query
var q Text
// Build "abc" by inserting one character at a time
q.InsertAt('a', 0)
q.InsertAt('b', 1)
@ -214,3 +214,63 @@ func TestQueryMultipleInserts(t *testing.T) {
q.InsertAt('X', 2)
require.Equal(t, "0aXbc", q.String())
}
func TestCaretInitialPos(t *testing.T) {
t.Parallel()
var c Caret
require.Equal(t, 0, c.Pos())
}
func TestCaretSetPos(t *testing.T) {
t.Parallel()
var c Caret
c.SetPos(5)
require.Equal(t, 5, c.Pos())
c.SetPos(0)
require.Equal(t, 0, c.Pos())
c.SetPos(100)
require.Equal(t, 100, c.Pos())
}
func TestCaretMove(t *testing.T) {
t.Parallel()
var c Caret
c.SetPos(5)
c.Move(3)
require.Equal(t, 8, c.Pos())
c.Move(-2)
require.Equal(t, 6, c.Pos())
// Move to negative territory
c.Move(-10)
require.Equal(t, -4, c.Pos())
}
func TestCaretMoveFromZero(t *testing.T) {
t.Parallel()
var c Caret
c.Move(1)
require.Equal(t, 1, c.Pos())
c.Move(-1)
require.Equal(t, 0, c.Pos())
}
func TestCaretMultipleMoves(t *testing.T) {
t.Parallel()
var c Caret
for range 10 {
c.Move(1)
}
require.Equal(t, 10, c.Pos())
for range 5 {
c.Move(-1)
}
require.Equal(t, 5, c.Pos())
}