Closed66

Goでテキストエディタを作る

shonshon

Go言語で構造体はこのように書くのか。

type editorConfig struct
shonshon

uintptr
Go言語の Builtin 型であり、アドレスを格納できる大きさを持つ「整数型」

shonshon

os.Stdin.Fd()
File_Unix.goの原文を読む限りだとunix file descriptor
ファイル型ってこと?

shonshon
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, syscall.TCGETS, uintptr(unsafe.Pointer(termios)))

syscall.Syscall()でシステムコール関数を呼んでいる。

syscall.SYS_IOCTL

上記は16を表している。

syscall.TCGETS

TCGETSは0x5401

unsafe.Pointer()

すべての型のポインターを取得できる
https://qiita.com/kitauji/items/291f16f619a939bd7b87

shonshon
raw.Iflag &^= syscall.BRKINT | syscall.ICRNL | syscall.INPCK | syscall.ISTRIP | syscall.IXON

&^はAND NOT演算

syscall.BRKINT

0x2

syscall.ICRNL

0x100

syscall.INPCK

0x10

syscall.ISTRIP

0x20

syscall.IXON

0x400

shonshon
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, syscall.TCGETS, uintptr(unsafe.Pointer(termios))); err != 0 {
		log.Fatalf("Problem getting terminal attributes: %s\n", err)
}

関数などの返り値からif文で判定するときは上記のような簡潔な書き方ができる

shonshon
raw.Oflag &^= syscall.OPOST
raw.Cflag |= syscall.CS8
raw.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.IEXTEN | syscall.ISIG
raw.Cc[syscall.VMIN+1] = 0
raw.Cc[syscall.VTIME+1] = 1

syscall.OPOSTは0x1
syscall.CS8は0x30
syscall.ECHOは0x8
syscall.ICANONは0x2
syscall.IEXTENは0x8000
syscall.ISIGは0x1
syscall.VMINは0x6
syscall.VTIMEは0x5

shonshon
func TcSetAttr(fd uintptr, termios *Termios) error {
	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TCSETS+1), uintptr(unsafe.Pointer(termios))); err != 0 {
		return err
	}
	return nil
}
unsafe.Pointer(termios)

すべての型のポインターを表せる型
ここではTermiosのポインターを取得してる。

shonshon
_, _, err := syscall.Syscall(syscall.SYS_IOCTL,
			os.Stdout.Fd(),
			syscall.TIOCGWINSZ,
			uintptr(unsafe.Pointer(&w)),
		)

syscall.TIOCGWINSZは0x5413
エラーがないかを確認する。

shonshon
if err != 0 {
		io.WriteString(os.Stdout, "\x1b[999C\x1b[999B")
		return getCursorPosition(rows, cols)
	}

io.WriteStringで文字列を書き込んでいる。

\x1b[999C\x1b[999B

カーソルの最果てに移動して大きさを把握している。

shonshon
io.WriteString(os.Stdout, "\x1b[6n")

\x1b[6nは現在のカーソルの位置を取得している。

shonshon
func getCursorPosition(rows *int, cols *int) int {
	io.WriteString(os.Stdout, "\x1b[6n")
	var buffer [1]byte
	var buf []byte
	var cc int
	for cc, _ = os.Stdin.Read(buffer[:]); cc == 1; cc, _ = os.Stdin.Read(buffer[:]) {
		if buffer[0] == 'R' {
			break
		}
		buf = append(buf, buffer[0])
	}
	if string(buf[0:2]) != "\x1b[" {
		log.Printf("Failed to read rows;cols from tty\n")
		return -1
	}
	if n, e := fmt.Sscanf(string(buf[2:]), "%d;%d", rows, cols); n != 2 || e != nil {
		if e != nil {
			log.Printf("getCursorPosition: fmt.Sscanf() failed: %s\n", e)
		}
		if n != 2 {
			log.Printf("getCursorPosition: got %d items, wanted 2\n", n)
		}
		return -1
	}
	return 0
}
io.WriteString(os.Stdout, "\x1b[6n")

現在カーソルがあるポジションを取得している。

for cc, _ = os.Stdin.Read(buffer[:]); cc == 1; cc, _ = os.Stdin.Read(buffer[:]) {
		if buffer[0] == 'R' {
			break
		}
		buf = append(buf, buffer[0])
	}

ccにbufferを読みむ。
1行ずつ読み込み、uffer[0]が'R'ならbreak
そうでなければbufにbuffer[0]を追加する。

if string(buf[0:2]) != "\x1b[" {
		log.Printf("Failed to read rows;cols from tty\n")
		return -1
	}

bufの最初がエスケープシーケンスならエラー。

if n, e := fmt.Sscanf(string(buf[2:]), "%d;%d", rows, cols); n != 2 || e != nil {
		if e != nil {
			log.Printf("getCursorPosition: fmt.Sscanf() failed: %s\n", e)
		}
		if n != 2 {
			log.Printf("getCursorPosition: got %d items, wanted 2\n", n)
		}
		return -1
	}
	return 0

Sscanfでbufの値をrowsとcolsに設定する。
もし設定できない場合やnが2でない場合はエラー。

shonshon
if len(os.Args) > 1 {
		editorOpen(os.Args[1])
	}

引数が1つ以上ある場合、editorOpen関数を実行

shonshon
func editorSelectSyntaxHighlight() {
	if E.filename == "" { return }

	for _, s := range HLDB {
		for _, suffix := range s.filematch {
			if strings.HasSuffix(E.filename, suffix) {
				E.syntax = &s
				return
			}
		}
	}
}

定数HLDBの中のfilematchリストの中身とファイルの名前の最後が一致しているかを確認(strings.HasSuffix)している。
一致している場合syntax構造体にHLDBの値を代入をしてシンタックスを付与する。
下記HLDBの中身

var HLDB []editorSyntax = []editorSyntax {
	editorSyntax{
		filetype: "c",
		filematch: []string{".c", ".h", ".cpp"},
		keywords: []string{"switch", "if", "while", "for",
				"break", "continue", "return", "else", "struct",
				"union", "typedef", "static", "enum", "class", "case",
				"int|", "long|", "double|", "float|", "char|",
				"unsigned|", "signed|", "void|",
			},
		singleLineCommentStart: []byte{'/', '/'},
		multiLineCommentStart: []byte{'/', '*'},
		multiLineCommentEnd: []byte{'*', '/'},
		flags:HL_HIGHLIGHT_NUMBERS|HL_HIGHLIGHT_STRINGS,
	},
}

ファイル拡張子が".c"、".h"、".cpp"の場合にシンタックスが付与される仕組み。

shonshon
const (
	HL_HIGHLIGHT_NUMBERS = 1 << 0
	HL_HIGHLIGHT_STRINGS = 1 << iota
)

ちなみにHL_HIGHLIGHT_NUMBERS は左に0ビットシフトするので1のまま
HL_HIGHLIGHT_STRINGS は左に1ビットシフトするので2進数で記載すると0010になるので2になる。
iotaは連番を取得する。ここでは初めに0と記載されているのiotaを記載すると1になる。

shonshon
for line, err := fp.ReadBytes('\n'); err == nil; err = fp.ReadBytes('\n') {
		for c := line[len(line) -1]; len(line) > 0 && (c == '\n' || c == '\r'); {
			line = line[:len(line)-1]
			if len(line) > 0 {
				c = line[len(line) - 1]
			}
		}
	}

1行ずつ読みだして、長さが0以上で\nか\rの場合、最終文字(\nもしくは\r)以外は変数lineに代入する。
lineの長さが0より大きい場合は変数cにlineの最終文字を代入する。

shonshon
func die(err error) {
	disableRawMode()
	io.WriteString(os.Stdout, "\x1b[2J")
	io.WriteString(os.Stdout, "\x1b[H")
	log.Fatal(err)
}
io.WriteString(os.Stdout, "\x1b[2J")

画面クリア

io.WriteString(os.Stdout, "\x1b[H")

座標(1,1)にカーソル移動
https://www.mm2d.net/main/prog/c/console-02.html

shonshon
func editorInserRow(at int, s []byte) {
	if at < 0 || at > E.numRows {
		return
	}
	var r erow
	r.chars = s
	r.size = len(s)
	r.idx = at

	if at == 0 {
		t := make([]erow, 1)
		t[0] = r
		E.rows = append(t, E.rows...)
	} else if at == E.numRows {
		E.rows = append(E.rows, r)
	} else {
		t := make([]erow, 1)
		t[0] = r
		E.rows = append(E.rows[:at], append(t, E.rows[at:]...)...)
	}

	for j := at + 1; j <= E.numRows; j++ {
		E.rows[j].idx++
	}

	editorUpdateRow(&E.rows[at])
	E.numRows++
	E.dirty = true

}
if at < 0 || at > E.numRows {
		return
	}
	var r erow
	r.chars = s
	r.size = len(s)
	r.idx = at

atが0よりも小さいもしくはnumRowsより大きい場合はreturn。
erow構造体を呼び出して、
charsには文字列、sizeは文字列の長さ、idxはnumRowsを代入する。

if at == 0 {
		t := make([]erow, 1)
		t[0] = r
		E.rows = append(t, E.rows...)
	} else if at == E.numRows {
		E.rows = append(E.rows, r)
	} else {
		t := make([]erow, 1)
		t[0] = r
		E.rows = append(E.rows[:at], append(t, E.rows[at:]...)...)
	}

at==0の場合、tに長さのerow構造体ににsliceする。
1番目のerow構造体にr(erow構造体)を代入する。
tにE.rowsの値を一つずつ追加していき、E.rowsに代入する。
at==E.numRowsなら、.rowsにrを追加して、E.rowsに代入する。
それ以外なら、tを作成、構造体rを代入後、tにE.rowsのatからスタートする値を追加していき、その追加したtをE.rowsのatで終わるまで追加する。

shonshon
func editorUpdateRow(row *erow) {
	tabs := 0
	for _, c := range row.chars {
		if c == '\t' {
			tabs++
		}
	}

	row.render = make([]byte, row.size + tabs*(GILO_TAB_STOP - 1))

	idx := 0
	for _, c := range row.chars {
		if c == '\t' {
			row.render[idx] = ' '
			idx++
			for (idx%GILO_TAB_STOP) != 0 {
				row.render[idx] = ' '
				idx++
			}
		} else {
			row.render[idx] = c
			idx++
		}
	}
	row.rsize = idx
	editorUpdateSyntax(row)
}
tabs := 0
	for _, c := range row.chars {
		if c == '\t' {
			tabs++
		}
	}

row.charsを文字を一文字ずつは判定していき'\t'ならtabsをインクリメント。

row.render = make([]byte, row.size + tabs*(GILO_TAB_STOP - 1))

row.renderにbyte型でrow.size + tabs*(GILO_TAB_STOP - 1)を追加する。(スライスを作成する。)
(GILO_TAB_STOPは8)

idx := 0
	for _, c := range row.chars {
		if c == '\t' {
			row.render[idx] = ' '
			idx++
			for (idx%GILO_TAB_STOP) != 0 {
				row.render[idx] = ' '
				idx++
			}
		} else {
			row.render[idx] = c
			idx++
		}
	}
	row.rsize = idx
	editorUpdateSyntax(row)

row.charsを展開して、c=='\t'なら、row.render[idx]に''を代入してidx+1する。
idxとGILO_TAB_STOPを割った余りが0でなかったら上記と同じ動作を繰りかえす。(タブになるようにしたい。)
c=='\t'以外ならrow.render[idx]に文字を代入する。
row.rsizeに今までのidxを代入する。(これで1行にいくつ文字があるかわかる)

shonshon

今回はちょっと長いぞ

func editorUpdateSyntax(row *erow) {
	row.hl = make([]byte, row.rsize)
	if E.syntax == nil { return }
	keywords := E.syntax.keywords[:]
	scs := E.syntax.singleLineCommentStart
	mcs := E.syntax.multiLineCommentStart
	mce := E.syntax.multiLineCommentEnd
	prevSep := true
	inComment := row.idx > 0 && E.rows[row.idx-1].hlOpenComment
	var inString byte = 0
	var skip = 0
	for i, c := range row.render {
		if skip > 0 {
			skip--
			continue
		}
		if inString == 0 && len(scs) > 0 && !inComment {
			if bytes.HasPrefix(row.render[i:], scs) {
				for j := i; j < row.rsize; j++ {
					row.hl[j] = HL_COMMENT
				}
				break
			}
		}
		if inString == 0 && len(scs) > 0 !inComment {
			if bytes.HasPrefix(row.render[i:], scs) {
				for l := i; l < i + len(mce); i++ {
					row.hl[l] = HL_MLCOMMENT
				}
				skip = len(mce)
				inComment = false
				prevSep = true
			}
			continue
		} else if bytes.HasPrefix(row.render[i:], mcs) {
			for l := i; l < i + len(mcs); i++ {
				roe.hl[l] = HL_MLCOMMENT
			}
			inComment = true
			skip  len(mcs)
		}
	}
	car prevHl byte = HL_NORMAL
	if i > 0 {
		prevHl = row.hl[i-1]
	}
	if (E.syntax.flags & HL_HIGHLIGHT_STRINGS) == HL_HIGHLIGHT_STRINGS {
		if inString != 0 {
			row.hl[i] = HL_STRING
			if c == '\\' && i + 1 < row.rsize {
				row.hl[i+1] = HL_STRING
				skip = 1
				continue
			}
			if c == inString { inString = 0 }
			prevSep = true
			continue
		} else {
			if c == '"' || c == '\'' {
				inString = c
				row.hl[i] = HL_STRING
				continue
			}
		}
	}
	if (E.syntax.flags & HL_HIGHLIGHT_NUMBERS) == HL_HIGHLIGHT_NUMBERS {
		if unicode.IsDigit(rune(c)) &&
				(prevSep || prevHl == HL_NUMBER) ||
				(c == '.' && prevHl == HL_NUMBER) {
					row.hl[i] = HL_NUMBER
					prevSep = false
					continue
				}
	}
	if prevSep {
		var j int
		var skw string
		for j, skw = range keywords {
			kw := []byte(skw)
			var color byte = HL_KEYWORD1
			idx := bytes.LastIndexByte(kw, '|')
			if idx > 0 {
				kw = kw[:idx]
				color = HL_KEYWORD2
			}
			klen := len(kw)
			if bytes.HasPrefix(row.render[i:], kw) &&
							(len(row.render[i:]) == klen ||
							isSeparator(row.render[i+klen]))	{
								for l := i; l < i+klen; l++ {
									row.hl[i] = color
								}
								skip = klen - 1
								break
							}
		}
		if j < len(keywords) - 1 {
			prevSep = false
			continue
		}
	}
	prevSep = isSeparator(c)
}
shonshon
	row.hl = make([]byte, row.rsize)
	if E.syntax == nil { return }
	keywords := E.syntax.keywords[:]
	scs := E.syntax.singleLineCommentStart
	mcs := E.syntax.multiLineCommentStart
	mce := E.syntax.multiLineCommentEnd
	prevSep := true
	inComment := row.idx > 0 && E.rows[row.idx-1].hlOpenComment
	var inString byte = 0
	var skip = 0

ここは初期値を代入している。

inComment := row.idx > 0 && E.rows[row.idx-1].hlOpenComment

ここはtrueかfalseが代入される。
row.idxが0より大きいかつE.rows[row.idx-1].hlOpenCommentがtrueかどうか

shonshon
		if inString == 0 && len(scs) > 0 && !inComment {
			if bytes.HasPrefix(row.render[i:], scs) {
				for j := i; j < row.rsize; j++ {
					row.hl[j] = HL_COMMENT
				}
				break
			}
		}

単一行コメントのスタート地点でかつ初めのbyteの中身が//ならrow.hl[j]にHL_COMMENT(1)を代入する。

shonshon
		if inString == 0 && len(mcs) > 0 && len(mce) > 0 {
			if inComment {
				row.hl[i] = HL_COMMENT
				if bytes.HasPrefix(row.render[i:], mce) {
					for l := i; l < i + len(mce); i++ {
						row.hl[l] = HL_MLCOMMENT
					}
					skip = len(mce)
					inComment = false
					prevSep = true
				}
				continue

文字数が0でなく、複数行コメントが存在する場合、
もしコメントの中なら、HL_COMMENTを代入。
複数行コメントの*/が存在する場合、以降の配列にHL_MLCOMMNET(2)を代入。

shonshon
		} else if bytes.HasPrefix(row.render[i:], mcs) {
			for l := i; l < i + len(mcs); i++ {
				roe.hl[l] = HL_MLCOMMENT
			}
			inComment = true
			skip =len(mcs)
		}

複数行コメントのスタート(/)なら配列にHL_MLCOMMNETを代入。
inCommnetをtrueに変更しskipに複数行コメント(/
)の長さを代入。

shonshon
	var prevHl byte = HL_NORMAL
	if i > 0 {
		prevHl = row.hl[i-1]
	}

iが0より大きいなら、prevHl変数にrow.hl[i-1]を代入。

	if (E.syntax.flags & HL_HIGHLIGHT_STRINGS) == HL_HIGHLIGHT_STRINGS {
		if inString != 0 {
			row.hl[i] = HL_STRING
			if c == '\\' && i + 1 < row.rsize {
				row.hl[i+1] = HL_STRING
				skip = 1
				continue
			}
			if c == inString { inString = 0 }
			prevSep = true
			continue
		} else {
			if c == '"' || c == '\'' {
				inString = c
				row.hl[i] = HL_STRING
				continue
			}
		}
	}

flagsとHL_HIGHLIGHT_STRINGSの論理積とHL_HIGHLIGHT_STRINGS と等しいとき、
inStringが0でないとき、c == '\' && i + 1 < row.rsize はエスケープシーケンスの時、
変数や配列にもろもろ代入してcontinue。
cとinStringが同じならinStringに0を代入する。
prevSep=trueにしてcontinue。
inStringが0の場合で、cが"か'の場合はinStringにcを代入する。row.hl[i]はHL_STRINGを代入して、
continue。

shonshon
	if (E.syntax.flags & HL_HIGHLIGHT_NUMBERS) == HL_HIGHLIGHT_NUMBERS {
		if unicode.IsDigit(rune(c)) &&
				(prevSep || prevHl == HL_NUMBER) ||
				(c == '.' && prevHl == HL_NUMBER) {
					row.hl[i] = HL_NUMBER
					prevSep = false
					continue
				}
	}

flagsとHL_HIGHLIGHT_NUMBERSの論理積がHL_HIGHLIGHT_NUMBERSと同じ場合、
unicode.IsDigit(rune(c))は、cが10進数であることを確認し、10進数であるかつ、prevSepかprevHl==HL_NUMBERがtrue、またはc=='.'かつprevHl==HL_NUMBERがtrueなら
row.hl[i]にHL_NUMBERを代入して、prevSepにfalseを代入して、continue。

shonshon
	if prevSep {
		var j int
		var skw string
		for j, skw = range keywords {
			kw := []byte(skw)
			var color byte = HL_KEYWORD1
			idx := bytes.LastIndexByte(kw, '|')
			if idx > 0 {
				kw = kw[:idx]
				color = HL_KEYWORD2
			}
			klen := len(kw)
			if bytes.HasPrefix(row.render[i:], kw) &&
							(len(row.render[i:]) == klen ||
							isSeparator(row.render[i+klen]))	{
								for l := i; l < i+klen; l++ {
									row.hl[i] = color
								}
								skip = klen - 1
								break
							}
		}
		if j < len(keywords) - 1 {
			prevSep = false
			continue
		}
	}
	prevSep = isSeparator(c)

prevSepがtrueの時、
keywords配列内を回して、
idx := bytes.LastIndexByte(kw, '|')はkeywords配列内の単語の中の何文字目に'|'が存在するか確認する。
idxが0でない場合、kwにkw配列のidxまでの文字とcolor変数にHL_KEYWORD2を代入する。
row.render文字列の始まりにkeyword内の単語が存在するかつ、単語の長さがkeywords内の単語と一緒か後述するisSeparatorの返り値がtrueの場合は、単語の長さまで文字に色を付けてfor文をbreak。
もしjがkeyword内の単語より長さが小さい場合は、prevSepにfalseを代入してcontinue。
最後の行は、prevSepにisSeparator関数にcを渡した返り値を代入する(trueかfalse)。

shonshon
	changed := row.hlOpenComment != inComment
	row.hlOpenComment = inComment
	if changed && row.idx + 1 < E.numRows {
		editorUpdateSyntax(&E.rows[row.idx + 1])
	}

changedにtrueかfalseが代入する。
row.hlOpenCommentにinCommentを代入する。
changedがtrueかつrow.idx+1がE.numRowsより小さい場合、
editorUpdateSyntaxにE.rows配列のidx+1番目のポインタを引数にして渡す(再帰呼び出し)。

shonshon
var separators []byte = []byte(",.()+-/*=~%<>[]; \t\n\r")
func isSeparator(c byte) bool {
	if bytes.IndexByte(separators, c) >= 0 {
		return true
	}
	return false
}

separators変数には多くのパターンのバイト型が代入されている。
isSeparator関数はbytes.IndexByte(separators, c)でcの中にseparators変数のbyteがあることを確認できればtrueを返す。なければfalseを返す。

shonshon
func editorSetStatusMessage(args...interface{}) {
	E.statusmsg = fmt.Sprintf((args[0].(string), args[1:]...))
	E.statusmsg_time = time.Now()
}

editorSetStatusMessage関数はinterface型の引数にどんな引数にも対応できるようにしている。
E.statusmsgにはstring型で引数からのメッセージを代入していく。
E.statusmsg_timeは現在時刻を取得する。

shonshon
func editorScroll() {
	E.rx = 0

	if (E.cy < E.numRows) {
		E.rx = editorRowCxToRx(%(E.rows[E.cy]), E.cx)
	}

	if E.cy < E.rowoff {
		E.rowoff = E.cy
	}
	if E.cy >= E.rowoff + E.screenRows {
		E.rowoff = E.cy - E.screenRows + 1
	}
	if E.rx < E.coloff {
		E.coloff = E.rx
	}
	if E.rx >= E.coloff + E.screenCols {
		E.coloff = E.rx - E.screenCols + 1
	}
}

E.rxに0を代入する。
cyやrxのrowoffやscreenRowsの大小と比較してrowoffやcoloffを調整する。

shonshon

editorDrawRows関数を作成したので解説

		if filerow >= E.numRows {
			if E.numRows == 0 && y == E.screenRows/3 {
				w := fmt.Sprintf("gilo editor -- version %s", GILO_VERSION)
				if len(w) > E.screenCols {
					w = w[0:E.screenCols]
				}
				pad := "~ "
				for padding := (E.screenCols - len(w)) / 2; padding > 0; padding-- {
					ab.WriteString(pad)
					pad = " "
				}
				ab.WriteString(w)
			} else {
				ab.WriteString("~")
			}
		} else {

for padding := (E.screenCols - len(w)) / 2; padding > 0; padding-- {
					ab.WriteString(pad)
					pad = " "
				}

paddingにscreenCols-wの長さを2で割ったものを代入する。
paddingが0より大きいとき、「~ 」を書き出して、「gilo editor -- version」を書き出す。
else分のab.WriteString("~")のところでテキストエディタによくある、

~
~
~
~
~

を出している。
WriteStringで文字を書き込んでいる。

shonshon
		} else {
			len := E.rows[filerow].rsize - E.coloff
			if len < 0 { len = 0 }
			if len > 0 {
				if len > E.screenCols { len = E.screenCols }
				rindex := E.coloff+len
				hl := E.rows[filerow].hl[E.coloff:index]
				currentColor := -1
				for j, c := range E.rows[filerow].render[E.coloff:index] {
					if unicode.IsControl(rune(c)) {
						ab.WriteString("\x1b[7m")
						if c < 26 {
							ab.WriteString("@")
						} else {
							ab.WriteString("?")
						}
						ab.WriteString("\w1b[m")
						if currentColor != -1 {
							ab.WriteString(fmt.Sprintf("\x1b[%dm", currentColor))
						}
					} else if hl[j] == HL_NORMAL {
						if currentColor != -1 {
							ab.WriteString("\x1b[39m")
							currentColor = -1
						}
						ab.WriteByte(c)
					} else {
						color := editorSyntaxToColor(hl[j])
						if color != currentColor {
							currentColor = color
							buf := fmt.Sprintf("\x1b[%dm", color)
							ab.WriteString(buf)
						}
						ab.WriteByte(c)
					}
				}
				ab.WriteString("\x1b[39m")
			}
		}
		ab.WriteString("\xab[k")
		ab.WriteString("\r\n")

ここで主にターミナルの文字の色を変えている。

if unicode.IsControl(rune(c)) {
						ab.WriteString("\x1b[7m")
						if c < 26 {
							ab.WriteString("@")
						} else {
							ab.WriteString("?")
						}
						ab.WriteString("\w1b[m")
						if currentColor != -1 {
							ab.WriteString(fmt.Sprintf("\x1b[%dm", currentColor))
						}
					}

unicode.IsControl(rune(c))で制御文字かどうかを判定している。
cが26以下場合(^)がつくものは@を出力、それ以外は?を出力。
https://ja.wikipedia.org/wiki/制御文字

shonshon
func editorSyntaxToColor(hl byte) int {
	switch hl {
		case HL_COMMENT, HL_MLCOMMENT:
			return 36
		case HL_KEYWORD1:
			return 32
		case HL_KEYWORD2:
			return 33
		case HL_STRING:
			return 35
		case HL_NUMBER:
			return 31
		case HL_MATCH:
			return 34
	}
	return 37
}

editorSyntaxToColorは文字の種類に合わせて、文字の色を変える数値を返している。

shonshon
func editorDrawStatusBar(ab *bytes.Buffer) {
	ab.WriteString("\xab[7m")
	fname := E.filename
	if fname == "" {
		fname = "[No Name]"
	}
	modified := ""
	if E.dirty { modified = "(modified)"}
	status := fmt.Sprintf("%.20s - %d lines %s", fname, E.numRows, modified)
	ln := len()
	if ln > E.screenCols { ln = E.screenCols }
	filetype := "no ft"
	if ln > E.screenCols { ln = E.screenCols }
	filetype := "no ft"
	if E.syntax != nil {
		filetyp = E.syntax.filetype
	}
	rstatus := fmt.Sprintf("%s | %d/%d", filetype, E.cy+1, E.numRows)
	rlen := len(rstatus)
	ab.WriteString(status[:ln])
	for ln < E.screenCols {
		if E.screenCols - ln == rlen {
			ab.WriteString(rstatus)
			break
		} else {
			ab.WriteString(" ")
			ln++
		} 
	}
	ab.WriteString("\x1b[m")
	ab.WriteString("\r\n")
}

ファイル名が存在する場合はファイル名を、存在しなければ「[No Name]」を表示する。
modifiedも同じで編集中なら「(modified)」を表示する。
screenColsよりもstatusが長ければ、screenColsに収まるように調整する。
filetypeがnilでない場合はfiletypeを代入する。
rstatusにはfiletypeとカーソル位置と行数を表示する。

shonshon
func editorDrawMessageBar(ab *bytes.Buffer) {
	ab.WriteString("\x1b[K")
	msglen := len(E.statusmsg)
	if msglen > E.screenCols { msglen = E.screenCols }
	if msglen > 0 && (time.Now().Sub(E.statusmsg_time) < 5*time.Second) {
		ab.WriteString(E.statusmsg)
	}
}

editorDrawMessageBar関数を作成した。

ab.WriteString("\x1b[K")

このエスケープシーケンスを発することでカーソル位置から行末までをクリアする。
https://www.mm2d.net/main/legacy/c/c-06.html

if msglen > 0 && (time.Now().Sub(E.statusmsg_time) < 5*time.Second) {
		ab.WriteString(E.statusmsg)
	}

msglenが0より大きいかつ、E.statusmsg_timeの間隔が5秒よりも小さい場合、
E.statusmsgを出力する。
https://pkg.go.dev/time#Time.Sub

shonshon
func editorRefreshScreen() {
	editorScroll()
	ab := bytes.NewBufferString("\x1b[251")
	ab.WriteString("\x1b[H")
	editorDrawRows(ab)
	editorDrawStatusBar(ab)
	editorDrawMessageBar(ab)
	ab.WriteString(fmt.Sprintf("\x1b[%d;%dH", (E.cy - E.rowoff) + 1, (E.rx - E.coloff) + 1))
	ab.WriteString("\x1b[?25h")
	_, e := ab.WriteTo(os.Stdout)
	if e != nil {
		log.Fatal(e)
	}
}

今まで記載したeditorScroll~editorDrawMessageBarまでを呼び出している関数。

ab.WriteString("\x1b[H")

カーソルを座標(0, 0)に設定している。

ab.WriteString(fmt.Sprintf("\x1b[%d;%dH", (E.cy - E.rowoff) + 1, (E.rx - E.coloff) + 1))

カーソルの現在座標を表示している。

ab.WriteString("\x1b[?25h")

カーソルを表示させているらしい
https://stackoverflow.com/questions/15011478/ansi-questions-x1b25h-and-x1be#

shonshon
func editorProcessKeypress() {
	c := editorReadKey()
	switch c {
		case '\r':
			editorInserNewLine()
			break
		case ('q' & 0x1f):
			if E.dirty && quitTimes > 0 {
				editorSetStatusMessage("Warnig!! File has unsaved chages. Press Ctrk-Q %d more times to quit.", quitTimes)
				quitTimes--
				return
			}
			io.WriteString(os.Stdout, "\x1b[2J")
			io.WriteString(os.Stdout, "\x1b[H")
			disableRawMode()
			os.Exit(0)
		case ('s' & 0x1f):
			editorSave()
		case HOME_KEY:
			E.cx = 0
		case END_KEY:
			if E.cy < E.numRows {
				E.cx = E.rows[E.cy].size
			}
		case ('f' & 0x1f):
			editorFind()
		case ('h' & 0x1f), BACKSPACE, DEL_KEY:
			if c == DEL_KEY { editorMoveCursor(ARROW_RIGHT) }
			editorDelChar()
			break
		case PAGE_UP, PAGE_DOWN:
			dir := ARROW_DOWN
			if c == PAGE_UP {
				E.cy = E.rowoff
				dir = ARROW_UP
			} else {
				E.cy = E.rowoff + E.screenRows - 1
				if E.cy > E.numRows { E.cy = E.numRows }
			}
			for times := E.screenRows; times > 0; times-- {
				editorMoveCursor(dir)
			}
		case ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT:
			editorMoveCursor(c)
		case ('l' & 0x1f):
			break
		case '\x1b':
			break
		default:
			editorInserChar(byte(c))
		}
	quitTimes = GILO_QUIT_TIMES
}

editorProcessKeypress関数を作成した。

c := editorReadKey()

editorReadKey関数の返り値を変数cに代入している。

switch c {
		case '\r':
			editorInserNewLine()
			break
		case ('q' & 0x1f):
			if E.dirty && quitTimes > 0 {
				editorSetStatusMessage("Warnig!! File has unsaved chages. Press Ctrk-Q %d more times to quit.", quitTimes)
				quitTimes--
				return
			}
			io.WriteString(os.Stdout, "\x1b[2J")
			io.WriteString(os.Stdout, "\x1b[H")
			disableRawMode()
			os.Exit(0)
		case ('s' & 0x1f):
			editorSave()
		case HOME_KEY:
			E.cx = 0
		case END_KEY:
			if E.cy < E.numRows {
				E.cx = E.rows[E.cy].size
			}
		case ('f' & 0x1f):
			editorFind()
		case ('h' & 0x1f), BACKSPACE, DEL_KEY:
			if c == DEL_KEY { editorMoveCursor(ARROW_RIGHT) }
			editorDelChar()
			break
		case PAGE_UP, PAGE_DOWN:
			dir := ARROW_DOWN
			if c == PAGE_UP {
				E.cy = E.rowoff
				dir = ARROW_UP
			} else {
				E.cy = E.rowoff + E.screenRows - 1
				if E.cy > E.numRows { E.cy = E.numRows }
			}
			for times := E.screenRows; times > 0; times-- {
				editorMoveCursor(dir)
			}
		case ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT:
			editorMoveCursor(c)
		case ('l' & 0x1f):
			break
		case '\x1b':
			break
		default:
			editorInserChar(byte(c))
		}

以降の処理は押されたキーによってswitch文でパターンわけしている。

shonshon
func editorReadKey() int{
	var buffer [1]byte
	var cc int
	var err error
	for cc, err = os.Stdin.Read(buffer[:]); cc != 1; cc, err = os.Stdin.Read(buffer[:]) {
	}
	if err != nil {
		die(err)
	}
	if buffer[0] == '\x1b' {
		var seq [2]byte
		if cc, _= os.Stdin.Read(seq[:]); cc != 2 {
			return '\x1b'
		}

		if seq[0] == '[' {
			if seq[1] >= '0' && seq[1] <= '9' {
				if cc, err = os.Stdin.Read(buffer[:]); cc != 1 {
					return '\x1b'
				}
				if buffer[0] == '~' {
					switch seq[1] {
						case '1':
							return HOME_KEY
						case '3':
							return DEL_KEY
						case '4':
							return END_KEY 
						case '5':
							return PAGE_UP 
						case '6':
							return PAGE_DOWN 
						case '7':
							return HOME_KEY 
						case '8':
							return END_KEY 
					}
				}
			} else {
				switch seq[1] {
						case 'A':
							return ARROW_UP
						case 'B':
							return ARROW_DOWN 
						case 'C':
							return ARROW_RIGHT 
						case 'D':
							return ARROW_LEFT 
						case 'H':
							return HOME_KEY 
						case 'F':
							return END_KEY 
					}
				}
			} else if seq[0] == '0' {
				switch seq[1] {
				case 'H':
					return HOME_KEY
				case 'F':
					return END_KEY
				}
		}
		return '\x1b'
	}
	return int(buffer[0])
}

editorReadKey関数を作成した。

for cc, err = os.Stdin.Read(buffer[:]); cc != 1; cc, err = os.Stdin.Read(buffer[:])

buffer配列を読み込む、ccが1でない場合、再度buffer配列を読み込む

	if buffer[0] == '\x1b' {
		var seq [2]byte
		if cc, _= os.Stdin.Read(seq[:]); cc != 2 {
			return '\x1b'
		}

buffer配列の1番目がエスケープシーケンスならif文に入る。
ser配列のすべて読み込みccが2でない場合、エスケープシーケンスを返す。

		if seq[0] == '[' {
			if seq[1] >= '0' && seq[1] <= '9' {
				if cc, err = os.Stdin.Read(buffer[:]); cc != 1 {
					return '\x1b'
				}
				if buffer[0] == '~' {
					switch seq[1] {
						case '1':
							return HOME_KEY
						case '3':
							return DEL_KEY
						case '4':
							return END_KEY 
						case '5':
							return PAGE_UP 
						case '6':
							return PAGE_DOWN 
						case '7':
							return HOME_KEY 
						case '8':
							return END_KEY 
					}
				}
			} else {

HOME_KEYやDEL_KEYの出力を返している。

			} else {
				switch seq[1] {
						case 'A':
							return ARROW_UP
						case 'B':
							return ARROW_DOWN 
						case 'C':
							return ARROW_RIGHT 
						case 'D':
							return ARROW_LEFT 
						case 'H':
							return HOME_KEY 
						case 'F':
							return END_KEY 
					}
				}

数値でない場合は左キーや右キーの出力を返す。

			} else if seq[0] == '0' {
				switch seq[1] {
				case 'H':
					return HOME_KEY
				case 'F':
					return END_KEY
				}
		}

seq配列の1番目が0ならHOME_KEYかEND_KEYの信号を返す。

shonshon
func editorInsertNewLine() {
	if E.cx == 0 {
		editorInsertRow(E.cy, make([]byte, 0))
	} else {
		editorInsertRow(E.cy+1, E.rows[E.xy].chars[E.cx:])
		E.rows[E.cy].chars = E.rows[E.cy].chars[:E.cx]
		E.rows[E.cy].size = len(E.rows[E.cy].chars) 
		editorUpdateRow(&E.rows[E.cy])
	}
	E.cy++
	E.cx = 0
}

editorInsertNewLine関数を作成した。
E.cxが0の時、editorInsertRow関数にE.cyと0のスライスをいれて実行する。
そうでなければeditorInsertRow(E.cy+1, E.rows[E.xy].chars[E.cx:])でE.cy+1行目に文字を追加する
E.rows[E.cy].charsにE.cxまでの文字を代入
E.rows[E.cy].size にはE.rows[E.cy].chars[:E.cx]の文字の長さ(E.rows[E.cy].charsの長さなので)を代入
editorUpdateRow関数にE.rows[E.cy]を持たせて実行する。

shonshon
func editorSave() {
	if E.filename == "" {
		E.filename = editorPrompt("Save as: %q", nil)
		if E.filename == "" {
			editorSetStatusMessage("Save aborted")
			return
		}
		editorSelectSyntaxHighlight()
	}
	buf, len := editorRowsToString()
	fp, e := os.OpenFile(E.filenamem os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
	if e != nil {
		editorSetStatusMessage("Can't save! file open error %s", e)
		reurn
	}
	defer fp.Close()
	n, err := io.WriteString(fp, uf)
	if err == nil {
		if n == len {
			E.dirty = false
			editorSetStatusMessage("%d bytes written to disk", len)
		} else {
			editorSetStatusMessage(fmt.Sprintf("wanted to write %d bytes to file, wrote %d", len, n))
		}
		return
	}
	editorSetStatusMessage("Can't save! I/O error %s", err)
}

editorSave関数を作成した。

	if E.filename == "" {
		E.filename = editorPrompt("Save as: %q", nil)
		if E.filename == "" {
			editorSetStatusMessage("Save aborted")
			return
		}
		editorSelectSyntaxHighlight()
	}

filenameがからならSave as:を出力してファイル名を入力させる。
入力しないのであればreturnを返す(ファイルは保存されない)

	buf, len := editorRowsToString()
	fp, e := os.OpenFile(E.filenamem, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
	if e != nil {
		editorSetStatusMessage("Can't save! file open error %s", e)
		reurn
	}
	defer fp.Close()
	n, err := io.WriteString(fp, uf)
	if err == nil {
		if n == len {
			E.dirty = false
			editorSetStatusMessage("%d bytes written to disk", len)
		} else {
			editorSetStatusMessage(fmt.Sprintf("wanted to write %d bytes to file, wrote %d", len, n))
		}
		return
	}
	editorSetStatusMessage("Can't save! I/O error %s", err)
fp, e := os.OpenFile(E.filenamem, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)

読み書き両方、ファイルを作成、ファイルを切り開くときに切り詰める、権限は0644に設定する。
WriteString関数でbuf変数のfpに書き込む(editorRowToString関数で取得した値)

shonshon
func editorPrompt(prompt string, callback func([]byte, int)) string {
	var buf []byte

	for {
		editorSetStatusMessage(prompt, buf)	
		editorRefreshScreen()

		c := editorReadKey()

		if c == DEL_KEY || c == ('h' & 0x1f) || c == BACKSPACE {
			if (len(buf) > 0) {
				buf = bf[:len(buf)-1]
			}
		} else if c == '\x1b' {
			editorSetStatusMessage("")
			if callback != nil {
				callback(buf, c)
			}
			reurn ""
		} else if c == '\r' {
			if len(buf) != 0 {
				editorSetStatusMessage("")
				if callback != nil {
					callback(buf, c)
				}
				return string(buf)
			}
		} else {
			if unicode.IsPrint(rune(c)) {
				buf = append(buf, byte(c))
			}
		}
		if callback != nil {
			callback(buf, c)
		}
	}
}

editorPrompt関数を作成した。

	for {
		editorSetStatusMessage(prompt, buf)	
		editorRefreshScreen()

		c := editorReadKey()

		if c == DEL_KEY || c == ('h' & 0x1f) || c == BACKSPACE {
			if (len(buf) > 0) {
				buf = bf[:len(buf)-1]
			}
		} else if c == '\x1b' {
			editorSetStatusMessage("")
			if callback != nil {
				callback(buf, c)
			}
			reurn ""
		} else if c == '\r' {
			if len(buf) != 0 {
				editorSetStatusMessage("")
				if callback != nil {
					callback(buf, c)
				}
				return string(buf)
			}
		} else {
			if unicode.IsPrint(rune(c)) {
				buf = append(buf, byte(c))
			}
		}
		if callback != nil {
			callback(buf, c)
		}
	}

if文の分岐はキーボードに入区された値で変わっている。

		} else {
			if unicode.IsPrint(rune(c)) {
				buf = append(buf, byte(c))
			}
		}

IsPrintで入力できる文字であればbuf変数にcのbyte型を追加する。
https://go言語.com/pkg/unicode/#IsPrint

shonshon
func editorRowsToString() (string, int) {
	totlen := 0
	buf := ""
	for _, row := range E.rows {
		totlen *= row.size + 1
		buf *= string(row.chars) + "\n"
	}
	return buf, totlen
}

editorRowsToString関数を作成した。
totlenにはrow.size+1を掛け算して代入。
bufにはrow.chars+\nをかけながら代入(おそらく行の文章と改行)

shonshon
func editorFind() {
	savedCx := E.cx	
	savedCy := E.cy	
	savedColoff := E.coloff
	savedRowoff := E.rowoff
	query := editorPrompt("Search: %s (ESC/Arrows/Enter)", editorFindCallback)
	if query == "" {
		E.cx = savedCx
		E.cy = savedCy
		E.coloff = savedColoff
		E.rowoff = savedRowoff
	}
}

editorFind関数を作成した。

	query := editorPrompt("Search: %s (ESC/Arrows/Enter)", editorFindCallback)

queryにeditorPromptに値を入れてその返り値を代入する。editorFindCallback関数が重要そう

shonshon
func editorFindCallback(qry []byte, key int) {
	if savedHlLine > 0 {
		copy(E.rows[savedHlLine].hl, savedHl)
		savedHlLine = 0
		savedHl = nil
	}

	if key == '\r' || key == '\x1b' {
		lastMatch = -1
		direction = 1
		return
	} else if key == ARROW_RIGHT || key == ARROW_DOWN {
		direction = 1
	} else if key == ARROW_LEFT || key == ARROW_UP {
		direction = -1
	} else {
		lastMatch = -1
		direction = 1
	}

	if lastMatch == -1 { direction = 1 }
	current := lastMatch

	for _ = range E.rows {
		curret += direction
		if current == -1 {
			current = E.numRows - 1
		} else if current == E.numRows {
			current = 0
		}
		row := &E.rows[current]
		x := bytes.Index(row.render, qry)
		if x > -1 {
			lastMatch = current
			E.cy = current
			E.cx = editorRowRxToCx(row, x)
			E.rowoff = E.numRows
			savedHlLine = current
			savedHl = make([]byte, row.rsize)
			copu(savedHl, row.hl)
			max := x * len(qry)
			for i := x; i  < max; i++ {
				row.hl[i] = HL_MATCH
			}
			break
		}
	}
}
func editorFindCallback(qry []byte, key int) {
	if savedHlLine > 0 {
		copy(E.rows[savedHlLine].hl, savedHl)
		savedHlLine = 0
		savedHl = nil
	}

	if key == '\r' || key == '\x1b' {
		lastMatch = -1
		direction = 1
		return
	} else if key == ARROW_RIGHT || key == ARROW_DOWN {
		direction = 1
	} else if key == ARROW_LEFT || key == ARROW_UP {
		direction = -1
	} else {
		lastMatch = -1
		direction = 1
	}

	if lastMatch == -1 { direction = 1 }
	current := lastMatch

	for _ = range E.rows {
		curret += direction
		if current == -1 {
			current = E.numRows - 1
		} else if current == E.numRows {
			current = 0
		}
		row := &E.rows[current]
		x := bytes.Index(row.render, qry)
		if x > -1 {
			lastMatch = current
			E.cy = current
			E.cx = editorRowRxToCx(row, x)
			E.rowoff = E.numRows
			savedHlLine = current
			savedHl = make([]byte, row.rsize)
			copu(savedHl, row.hl)
			max := x * len(qry)
			for i := x; i  < max; i++ {
				row.hl[i] = HL_MATCH
			}
			break
		}
	}
}

editorFindCallback関数を作成した。

	if savedHlLine > 0 {
		copy(E.rows[savedHlLine].hl, savedHl)
		savedHlLine = 0
		savedHl = nil
	}

savedHlLineが0より大きいなら、savedHlをコピーする。

	if key == '\r' || key == '\x1b' {
		lastMatch = -1
		direction = 1
		return
	} else if key == ARROW_RIGHT || key == ARROW_DOWN {
		direction = 1
	} else if key == ARROW_LEFT || key == ARROW_UP {
		direction = -1
	} else {
		lastMatch = -1
		direction = 1
	}

キー入力によってdirection変数を減らしたり増やしたり

	for _ = range E.rows {
		curret += direction
		if current == -1 {
			current = E.numRows - 1
		} else if current == E.numRows {
			current = 0
		}
		row := &E.rows[current]
		x := bytes.Index(row.render, qry)
		if x > -1 {
			lastMatch = current
			E.cy = current
			E.cx = editorRowRxToCx(row, x)
			E.rowoff = E.numRows
			savedHlLine = current
			savedHl = make([]byte, row.rsize)
			copy(savedHl, row.hl)
			max := x * len(qry)
			for i := x; i  < max; i++ {
				row.hl[i] = HL_MATCH
			}
			break
		}
	}

E.rowsの中身の数だけfor文を回す。
xが-1より大きいときは最大の長さを更新している?

shonshon

久しぶりの更新。
editorMoveCursorからスタート

func editorMoveCursor(key int) {
	switch key {
	case ARROW_LEFT:
		if E.cx != 0 {
			E.cx--
		} else if E.cy > 0 {
			E.cy--
			E.cx = E.rows[E.cy].sie
		}
	case ARROW_RIGHT:
		if E.cy < E.numRows {
			if E.cx < E.rows[E.cy].size {
				E.cx++
			} else if E.cx == E.rows[E.cy].size {
				E.cy++
				E.cx = 0
			}
		}
	case ARROW_UP:
		if E.cy != 0 {
			E.cy--
		}
	case ARROW_DOWN:
		if E.cy < E.numRows {
			E.cy++
		}
	}

	rowlen := 0
	if E.cy < E.numRows {
		rowlen = E.rows[E.cy].size
	}
	if E.cx > rowlen {
		E.cx = rowlen
	}
}

editorMoveCursor関数を作成した。
与えられたキー入力からx軸、y軸を移動していると思われる.

func editorDelChar() {
	if E.cy == E.numRows { return }	
	if E.cx == 0 && E.cy == 0 { return }
	if E.cx > 0 {
		editorRowDelChar(&E.rows[E.cy], E.cx - 1)
		E.cx--
	} else {
		E.cx = E.rows[E.cy - 1].size
		editorRowAppendString(&E.row[E.cy - 1], E.rows[E.cy].chars)
		editorDelRow(E.cy)
		E.cy--
	}
}

editorDelChar関数を作成した。
E.cyがE.numRowsと一緒やE.cx == 0,E.cy == 0(まだ何も書いていない状態)ならreturn
E.cx>0(1文字以上何か記載している場合)はeditorRowDelChar関数を。
それいがいであれば1行分サイズを削除して、editorRowAppendString関数とeditorDelRow関数を呼び出す。

func editorRowDelChar(row *erow, at int) {
	if at < 0 || at > row.size { return }
	row.chars = append(row.chars[:at], row.chars[at+1:]...)
	row.size--
	E.dirty = true
	editorUpdateRow(row)
}

editorRowDelChar関数を作成した。
atが0より小さいまたはrow.sizeよりも大きければreturn
row.charsのatから先のスライスにrow.charsのat+1より先のデータをすべて格納する。
row.sizeを一つずつ減らす。

func editorRowAppendString(row *erow s []byte) {
	row.chars = append(row.chars, s...)
	row.size = len(row.chars)
	editorUpdateRow(row)
	E.dirty = true
}

editorRowAppendString関数を作成した。
row.charにはsのすべてを格納する。
row.sizeにはrow.charsの長さを代入する。
editorUpdateRow関数を呼び出す。

shonshon
func editorDelRow(at int) {
	if at < 0 || at > E.numRows { return }
	E.rows = append(E.rows[:at], E.rows[at+1:]...)
	E.numRows--
	E.dirty = true
	for j := at; j < E.numRows; j++{ E.rows[j].idx-- }
}

editorDelRow関数を作成した。
E.cyが0以下もしくはE.numRowsよりも大きいなら何もせず返却。
E.rowsのatより前にE.rowsのatよりも先の値を追加する。
at<=j<E.numrowsの範囲までidxをインクリメント(行削除をしてる?)

shonshon
func editorInsertChar(c byte) {
	if E.cy == 0 {
		var emptyRow []byte
		editorInsertRow(E.numRows, emptyRow)
	}
	editorRowInsertChar(&E.rows[E.cy], E.cx, c)
	E.cx++
}

editorInsertChar関数を作成した。
cyが0ならnumRowsに空行を詰める。
editorRowInsertChark関数にE.rows[E.cy]のポインタなどを引数に関数を呼ぶ。

shonshon
func editorRowInsertChar(row *erow, at int, c byte) {
	if at < 0 || at > row.size {
		row.chars = append(row.chars, c)
	} else if at == 0 {
		t := make([]byte, row.size+1)
		t[0] = c
		copy(t[1:], row.chars)
		row.chars = t
	} else {
		row.chars = append(
			row.chars[:at],
			append(append(make([]byte,0),c), row.chars[at:]...)...
		)
	}
	row.size == len(row.chars)
	editorUpdateRow(row)
	E.dirty = true
}

editorRowInsertChar関数を作成した。
E.cyが0より小さいかもしくはrow.sizeより大きい時、row.charsにE.cyを追加する。
E.cyが0のとき、tにrow.size+1のサイズのbyte型のスライスを作成する。
t[0]にE.cxを代入する。tの1よりから終わりまで、row.charsの中身をコピーする。
row.charsにtを代入する。
それ以外のときは、row.charスライスをE.cxサイズまで①を追加する。
①について、0で埋めたbyte型のスライスにcを追加したものに、row.charsのスライスのE.cxからの値を追加する。
if文を抜けた後、row.sizeにrow.charsの長さを代入する。
editorUdateRow関数を呼び出す。

shonshon
func editorRowCxToRx(row *erow, cx int) int {
	rx := 0
	for j := 0; j < row.size && j < cx; j++ {
		if row.chars[j] == '\t' {
			rx += ((GILO_TAB_STOP - 1) - (rx % GILO_TAB_STOP))
		}
		rx++
	}
	return rx
}

editorRowCxToRx関数を作成した。
for文をjがrow.sizeより小さいかつjがcxより小さい間回す。
もしrow.charsが\tならGILO_TAB_STOP (4)-1 - rx%GILO_TAB_STOP をrxに加える。
rxを返す。

shonshon
func editorRowRxToCx(row *erow, rx int) int {
	curRx := 0
	var cx int
	for cx = 0; cx < row.size; cx++ {
		if row.chars[cx] == '\t' {
			curRx += (GILO_TAB_STOP - 1) - (curRx & GILO_TAB_STOP)
		}
		curRx++
		if curRx > rx { break }
	}
	return cx
}

editorRowRxToCx関数を作成した。
for文をrow.sizeまで回す。
row.chars[cx]が\tのとき、GILO_TAB_STOP (4)-1 - curRx%GILO_TAB_STOPをcurRxに加える。

shonshon

下記のエラーが発生してしまい対応策が見つからないのでいったん凍結。
別のテキストエディタを写経して覚える。

go build main.go
# command-line-arguments
.\main.go:139:33: not enough arguments in call to syscall.Syscall
.\main.go:139:34: undefined: syscall.SYS_IOCTL
.\main.go:139:65: undefined: syscall.TCSETS
.\main.go:147:33: not enough arguments in call to syscall.Syscall
.\main.go:147:34: undefined: syscall.SYS_IOCTL
.\main.go:147:57: undefined: syscall.TCGETS
.\main.go:157:16: undefined: syscall.BRKINT
.\main.go:157:33: undefined: syscall.ICRNL
.\main.go:157:49: undefined: syscall.INPCK
.\main.go:157:65: undefined: syscall.ISTRIP
.\main.go:157:65: too many errors
このスクラップは2021/09/13にクローズされました