• Что бы вступить в ряды "Принятый кодер" Вам нужно:
    Написать 10 полезных сообщений или тем и Получить 10 симпатий.
    Для того кто не хочет терять время,может пожертвовать средства для поддержки сервеса, и вступить в ряды VIP на месяц, дополнительная информация в лс.

  • Пользаватели которые будут спамить, уходят в бан без предупреждения. Спам сообщения определяется администрацией и модератором.

  • Гость, Что бы Вы хотели увидеть на нашем Форуме? Изложить свои идеи и пожелания по улучшению форума Вы можете поделиться с нами здесь. ----> Перейдите сюда
  • Все пользователи не прошедшие проверку электронной почты будут заблокированы. Все вопросы с разблокировкой обращайтесь по адресу электронной почте : info@guardianelinks.com . Не пришло сообщение о проверке или о сбросе также сообщите нам.

Create a Text Editor in Go - Moving the Cursor

Sascha Оффлайн

Sascha

Заместитель Администратора
Команда форума
Администратор
Регистрация
9 Май 2015
Сообщения
1,605
Баллы
155
You can access the code of this chapter in the

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

in the movingaround branch

Currently your file structure should look something like this:


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.



Cursor state


First we need a way to track the cursor position

File: editor/editor.go


type EditorConfig struct {
...
cx, cy int
}

func NewEditor(f func()) *EditorConfig {
...
return &EditorConfig{
...
cx: 0,
cy: 0,
}
}




File: editor/output.go


func (e *EditorConfig) editorRefreshScreen() {
...
e.editorDrawRows(abuf)
fmt.Fprintf(abuf, "%c[%d;%dH", utils.ESC, e.cy + 1, e.cx+1)
fmt.Fprintf(abuf, "%c[?25h", utils.ESC)
...
}

func (e *EditorConfig) editorDrawRows(abuf *ab.AppendBuffer) {
for y := range e.rows {
if y == e.rows/3 {
...
welcomeLen := min(len(welcomeMessage), e.cols)
...
}
}
}



Cursor movement


Now that we've tracked the cursor position, we can add the logic to move the cursor with vim-like keys so we will be using h, j, k and l

File: editor/input.go


func (e *EditorConfig) editorProcessKeypress() {
...
switch b {
case utils.CtrlKey('q'):
utils.SafeExit(e.restoreFunc, nil)
case 'h', 'j', 'k', 'l':
e.editorMoveCursor(b)
}
}

func (e *EditorConfig) editorMoveCursor(key byte) {
switch key {
case 'h':
e.cx--
case 'j':
e.cy++
case 'k':
e.cy--
case 'l':
e.cx++
}
}



Reading the arrow keys


Lets read the arrow keys next, each one is represented by 3 bytes:

  • Up Arrow is represented by <Esc> [ A
  • Down Arrow is represented by <Esc> [ B
  • Right Arrow is represented by <Esc> [ C
  • Left Arrow is represented by <Esc> [ D

So with that in mind, lets read them

File: editor/terminal.go


import (
"github.com/alcb1310/kilo-go/utils"
)

func (e *EditorConfig) editorReadKey() (byte, error) {
b, err := e.reader.ReadByte()

if b == utils.ESC {
seq := make([]byte, 2)

seq[0], err = e.reader.ReadByte()
if err != nil {
return utils.ESC, nil
}
seq[1], err = e.reader.ReadByte()
if err != nil {
return utils.ESC, nil
}

if seq[0] == '[' {
switch seq[1] {
case 'A':
return 'k', nil
case 'B':
return 'j', nil
case 'C':
return 'l', nil
case 'D':
return 'h', nil
}
}

return utils.ESC, nil
}

return b, err
}



Refactor: improve readability


Now that we have the arrow keys working, lets improve the readability by creating constants for it

File: utils/constants.go


const (
ARROW_UP = 'k'
ARROW_DOWN = 'j'
ARROW_LEFT = 'h'
ARROW_RIGHT = 'l'
)




File: editor/input.go


func (e *EditorConfig) editorProcessKeypress() {
...
switch b {
case utils.CtrlKey('q'):
utils.SafeExit(e.restoreFunc, nil)
case utils.ARROW_DOWN, utils.ARROW_LEFT, utils.ARROW_RIGHT, utils.ARROW_UP:
e.editorMoveCursor(b)
}
}

func (e *EditorConfig) editorMoveCursor(key byte) {
switch key {
case utils.ARROW_LEFT:
e.cx--
case utils.ARROW_DOWN:
e.cy++
case utils.ARROW_UP:
e.cy--
case utils.ARROW_RIGHT:
e.cx++
}
}




File: editor/terminal.go


func (e *EditorConfig) editorReadKey() (byte, error) {
...
if seq[0] == '[' {
switch seq[1] {
case 'A':
return utils.ARROW_UP, nil
case 'B':
return utils.ARROW_DOWN, nil
case 'C':
return utils.ARROW_RIGHT, nil
case 'D':
return utils.ARROW_LEFT, nil
}
}
...
}



Refactor: only use arrows to move


Now that we've added constants to the arrow keys we can give them a value outside of the byte range and start using them as an int. However doing so, we will need to change several files in order to return int instead of byte

File: utils/constant.go


const (
ARROW_UP = iota + 1000
ARROW_DOWN
ARROW_LEFT
ARROW_RIGHT
)




File: utils/ctrl.go


func CtrlKey(key byte) int {
return int(key & 0x1f)
}




File editor/input.go


func (e *EditorConfig) editorMoveCursor(key int) {
...
}




File editor/terminal.go


func (e *EditorConfig) editorReadKey() (int, error) {
...
return int(b), err
}



Note: We mostly needed to change the function signature for it to work
Prevent moving the cursor off screen


Currently, we can have the cx and cy values to go into the negatives or go past the right and bottom edges of the screen, so lets prevent that

File: editor/input.go


func (e *EditorConfig) editorMoveCursor(key int) {
switch key {
case utils.ARROW_LEFT:
if e.cx != 0 {
e.cx--
}
case utils.ARROW_DOWN:
if e.cy != e.rows-1 {
e.cy++
}
case utils.ARROW_UP:
if e.cy != 0 {
e.cy--
}
case utils.ARROW_RIGHT:
if e.cx != e.cols-1 {
e.cx++
}
}
}



The Page Up and Page Down keys


To complete our movements, we need to detect a few more special keypresses that use escape sequences, like the arrow keys did. We'll start with the Page Up which is sent as <Esc> [ 5 ~ and Page Down which is sent as <Esc> [ 6 ~

File utils/constants.go


const (
ARROW_UP = iota + 1000
ARROW_DOWN
ARROW_LEFT
ARROW_RIGHT
PAGE_UP
PAGE_DOWN
)




File: editor/terminal.go


func (e *EditorConfig) editorProcessKeypress() {
...
case utils.PAGE_DOWN, utils.PAGE_UP:
times := e.rows
for range times {
if b == utils.PAGE_DOWN {
e.editorMoveCursor(utils.ARROW_DOWN)
} else {
e.editorMoveCursor(utils.ARROW_UP)
}
}
}
}




File: editor/terminal.go


func (e *EditorConfig) editorReadKey() (int, error) {
...
if b == utils.ESC {
seq := make([]byte, 3)
...
if seq[0] == '[' {
if seq[1] >= '0' && seq[1] <= '9' {
seq[2], err = e.reader.ReadByte()
if err != nil {
return utils.ESC, nil
}

if seq[2] == '~' {
switch seq[1] {
case '5':
return utils.PAGE_UP, nil
case '6':
return utils.PAGE_DOWN, nil
}
}
} else {
switch seq[1] {
case 'A':
return utils.ARROW_UP, nil
case 'B':
return utils.ARROW_DOWN, nil
case 'C':
return utils.ARROW_RIGHT, nil
case 'D':
return utils.ARROW_LEFT, nil
}
}
}
...
}



The Home and End keys


Like the previous keys, these keys also send escape sequences. Unlike previous keys, there are many different escape sequences that could be sent by these keys.

  • The Home key could be sent as <Esc> [ 1 ~, <Esc> [ 7 ~, <Esc> [ H or <Esc> O H
  • The End key could be sent as <Esc> [ 4 ~, <Esc> [ 8 ~, <Esc> [ F or <Esc> O F

File: utils/constants.go


const (
ARROW_UP = iota + 1000
ARROW_DOWN
ARROW_LEFT
ARROW_RIGHT
HOME_KEY
END_KEY
PAGE_UP
PAGE_DOWN
)




File: editor/terminal.go


func (e *EditorConfig) editorReadKey() (int, error) {
...
if seq[0] == '[' {
if seq[1] >= '0' && seq[1] <= '9' {
...
if seq[2] == '~' {
switch seq[1] {
case '1':
return utils.HOME_KEY, nil
case '4':
return utils.END_KEY, nil
case '5':
return utils.PAGE_UP, nil
case '6':
return utils.PAGE_DOWN, nil
case '7':
return utils.HOME_KEY, nil
case '8':
return utils.END_KEY, nil
}
}
} else {
switch seq[1] {
case 'A':
return utils.ARROW_UP, nil
case 'B':
return utils.ARROW_DOWN, nil
case 'C':
return utils.ARROW_RIGHT, nil
case 'D':
return utils.ARROW_LEFT, nil
case 'H':
return utils.HOME_KEY, nil
case 'F':
return utils.END_KEY, nil
}
}
} else if seq[0] == 'O' {
switch seq[1] {
case 'H':
return utils.HOME_KEY, nil
case 'F':
return utils.END_KEY, nil
}
}
...
}




File: editor/input.go


func (e *EditorConfig) editorProcessKeypress() {
...
switch b {
...
case utils.HOME_KEY:
e.cx = 0
case utils.END_KEY:
e.cx = e.cols - 1
}
}



The Delete key


Lastly we will detect when the Delete key is pressed. It simply sends the escape sequence <Esc> [ 3 ~, so it will be easy to add it to our switch statement. For now we will just log when the key is pressed

File: utils/constants.go


const (
ARROW_LEFT = iota + 1000
ARROW_RIGHT
ARROW_UP
ARROW_DOWN
DEL_KEY
HOME_KEY
END_KEY
PAGE_UP
PAGE_DOWN
)




File: editor/terminal.go


func (e *EditorConfig) editorReadKey() (int, error) {
...
if b == utils.ESC {
...
switch seq[0] {
case '[':
if seq[1] >= '0' && seq[1] <= '9' {
...
if seq[2] == '~' {
switch seq[1] {
case '1':
return utils.HOME_KEY, nil
case '3':
return utils.DEL_KEY, nil
...
}
}
...
case 'O':
switch seq[1] {
case 'H':
return utils.HOME_KEY, nil
case 'F':
return utils.END_KEY, nil
}
}

...
}




File: editor/input.go


func (e *EditorConfig) editorProcessKeypress() {
...
switch b {
...
case utils.DEL_KEY:
slog.Info("DEL_KEY")
...
}



Источник:

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

 
Вверх Снизу