Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Continuous Integration

env:
GO_VERSION: 1.20
GO_VERSION: 1.25

on:
push:
Expand All @@ -18,7 +18,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: 1.20.x
go-version: 1.25.x
- name: Test code
run: |
go test ./...
Expand Down
19 changes: 9 additions & 10 deletions edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@

package gocui

import (
"unicode"
)

// Editor interface must be satisfied by gocui editors.
type Editor interface {
Edit(v *View, key Key, ch rune, mod Modifier) bool
Expand All @@ -29,6 +25,9 @@ var DefaultEditor Editor = EditorFunc(SimpleEditor)
// SimpleEditor is used as the default gocui editor.
func SimpleEditor(v *View, key Key, ch rune, mod Modifier) bool {
switch {
case (key == KeyBackspace || key == KeyBackspace2) && (mod&ModAlt) != 0,
key == KeyCtrlW:
v.TextArea.BackSpaceWord()
case key == KeyBackspace || key == KeyBackspace2:
v.TextArea.BackSpaceChar()
case key == KeyCtrlD || key == KeyDelete:
Expand All @@ -37,18 +36,18 @@ func SimpleEditor(v *View, key Key, ch rune, mod Modifier) bool {
v.TextArea.MoveCursorDown()
case key == KeyArrowUp:
v.TextArea.MoveCursorUp()
case key == KeyArrowLeft && (mod&ModAlt) != 0:
case (key == KeyArrowLeft || ch == 'b') && (mod&ModAlt) != 0:
v.TextArea.MoveLeftWord()
case key == KeyArrowLeft:
v.TextArea.MoveCursorLeft()
case key == KeyArrowRight && (mod&ModAlt) != 0:
case (key == KeyArrowRight || ch == 'f') && (mod&ModAlt) != 0:
v.TextArea.MoveRightWord()
case key == KeyArrowRight:
v.TextArea.MoveCursorRight()
case key == KeyEnter:
v.TextArea.TypeRune('\n')
v.TextArea.TypeCharacter("\n")
case key == KeySpace:
v.TextArea.TypeRune(' ')
v.TextArea.TypeCharacter(" ")
case key == KeyInsert:
v.TextArea.ToggleOverwrite()
case key == KeyCtrlU:
Expand All @@ -63,8 +62,8 @@ func SimpleEditor(v *View, key Key, ch rune, mod Modifier) bool {
v.TextArea.BackSpaceWord()
case key == KeyCtrlY:
v.TextArea.Yank()
case unicode.IsPrint(ch):
v.TextArea.TypeRune(ch)
case ch != 0:
v.TextArea.TypeCharacter(string(ch))
default:
return false
}
Expand Down
75 changes: 38 additions & 37 deletions escape.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@ package gocui

import (
"strconv"
"strings"

"github.com/go-errors/errors"
)

type escapeInterpreter struct {
state escapeState
curch rune
curch string
csiParam []string
curFgColor, curBgColor Attribute
mode OutputMode
instruction instruction
hyperlink string
hyperlink strings.Builder
}

type (
Expand Down Expand Up @@ -68,20 +69,20 @@ var (
errOSCParseError = errors.New("OSC escape sequence parsing error")
)

// runes in case of error will output the non-parsed runes as a string.
func (ei *escapeInterpreter) runes() []rune {
// characters in case of error will output the non-parsed characters as a string.
func (ei *escapeInterpreter) characters() []string {
switch ei.state {
case stateNone:
return []rune{0x1b}
return []string{"\x1b"}
case stateEscape:
return []rune{0x1b, ei.curch}
return []string{"\x1b", ei.curch}
case stateCSI:
return []rune{0x1b, '[', ei.curch}
return []string{"\x1b", "[", ei.curch}
case stateParams:
ret := []rune{0x1b, '['}
ret := []string{"\x1b", "["}
for _, s := range ei.csiParam {
ret = append(ret, []rune(s)...)
ret = append(ret, ';')
ret = append(ret, s)
ret = append(ret, ";")
}
return append(ret, ei.curch)
default:
Expand Down Expand Up @@ -114,10 +115,10 @@ func (ei *escapeInterpreter) instructionRead() {
ei.instruction = noInstruction{}
}

// parseOne parses a rune. If isEscape is true, it means that the rune is part
// of an escape sequence, and as such should not be printed verbatim. Otherwise,
// it's not an escape sequence.
func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) {
// parseOne parses a character (grapheme cluster). If isEscape is true, it means that the character
// is part of an escape sequence, and as such should not be printed verbatim. Otherwise, it's not an
// escape sequence.
func (ei *escapeInterpreter) parseOne(ch []byte) (isEscape bool, err error) {
// Sanity checks
if len(ei.csiParam) > 20 {
return false, errCSITooLong
Expand All @@ -126,33 +127,33 @@ func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) {
return false, errCSITooLong
}

ei.curch = ch
ei.curch = string(ch)

switch ei.state {
case stateNone:
if ch == 0x1b {
if characterEquals(ch, 0x1b) {
ei.state = stateEscape
return true, nil
}
return false, nil
case stateEscape:
switch ch {
case '[':
switch {
case characterEquals(ch, '['):
ei.state = stateCSI
return true, nil
case ']':
case characterEquals(ch, ']'):
ei.state = stateOSC
return true, nil
default:
return false, errNotCSI
}
case stateCSI:
switch {
case ch >= '0' && ch <= '9':
case len(ch) == 1 && ch[0] >= '0' && ch[0] <= '9':
ei.csiParam = append(ei.csiParam, "")
case ch == 'm':
case characterEquals(ch, 'm'):
ei.csiParam = append(ei.csiParam, "0")
case ch == 'K':
case characterEquals(ch, 'K'):
// fall through
default:
return false, errCSIParseError
Expand All @@ -161,21 +162,21 @@ func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) {
fallthrough
case stateParams:
switch {
case ch >= '0' && ch <= '9':
case len(ch) == 1 && ch[0] >= '0' && ch[0] <= '9':
ei.csiParam[len(ei.csiParam)-1] += string(ch)
return true, nil
case ch == ';':
case characterEquals(ch, ';'):
ei.csiParam = append(ei.csiParam, "")
return true, nil
case ch == 'm':
case characterEquals(ch, 'm'):
if err := ei.outputCSI(); err != nil {
return false, errCSIParseError
}

ei.state = stateNone
ei.csiParam = nil
return true, nil
case ch == 'K':
case characterEquals(ch, 'K'):
p := 0
if len(ei.csiParam) != 0 && ei.csiParam[0] != "" {
p, err = strconv.Atoi(ei.csiParam[0])
Expand All @@ -198,44 +199,44 @@ func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) {
return false, errCSIParseError
}
case stateOSC:
if ch == '8' {
if characterEquals(ch, '8') {
ei.state = stateOSCWaitForParams
ei.hyperlink = ""
ei.hyperlink.Reset()
return true, nil
}

ei.state = stateOSCSkipUnknown
return true, nil
case stateOSCWaitForParams:
if ch != ';' {
if !characterEquals(ch, ';') {
return true, errOSCParseError
}

ei.state = stateOSCParams
return true, nil
case stateOSCParams:
if ch == ';' {
if characterEquals(ch, ';') {
ei.state = stateOSCHyperlink
}
return true, nil
case stateOSCHyperlink:
switch ch {
case 0x07:
switch {
case characterEquals(ch, 0x07):
ei.state = stateNone
case 0x1b:
case characterEquals(ch, 0x1b):
ei.state = stateOSCEndEscape
default:
ei.hyperlink += string(ch)
ei.hyperlink.Write(ch)
}
return true, nil
case stateOSCEndEscape:
ei.state = stateNone
return true, nil
case stateOSCSkipUnknown:
switch ch {
case 0x07:
switch {
case characterEquals(ch, 0x07):
ei.state = stateNone
case 0x1b:
case characterEquals(ch, 0x1b):
ei.state = stateOSCEndEscape
}
return true, nil
Expand Down
6 changes: 3 additions & 3 deletions escape_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ func TestParseOne(t *testing.T) {
var ei *escapeInterpreter

ei = newEscapeInterpreter(OutputNormal)
isEscape, err := ei.parseOne('a')
isEscape, err := ei.parseOne([]byte{'a'})
assert.Equal(t, false, isEscape)
assert.NoError(t, err)

Expand Down Expand Up @@ -131,8 +131,8 @@ func TestParseOneColours(t *testing.T) {
}

func parseEscRunes(t *testing.T, ei *escapeInterpreter, runes string) {
for _, r := range runes {
isEscape, err := ei.parseOne(r)
for _, b := range []byte(runes) {
isEscape, err := ei.parseOne([]byte{b})
assert.Equal(t, true, isEscape)
assert.NoError(t, err)
}
Expand Down
18 changes: 14 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
module github.com/jesseduffield/gocui

go 1.12
go 1.25

require (
github.com/gdamore/tcell/v2 v2.8.0
github.com/gdamore/tcell/v2 v2.13.5
github.com/go-errors/errors v1.0.2
github.com/mattn/go-runewidth v0.0.16
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rivo/uniseg v0.4.7
github.com/stretchr/testify v1.7.0
)

require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/gdamore/encoding v1.0.1 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/term v0.38.0 // indirect
golang.org/x/text v0.32.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)
Loading