examples/text_editor: minor cleanup and simplifications (#6818)
parent
b02f03e20a
commit
eacd6b5d54
|
@ -4,33 +4,8 @@
|
||||||
// A lot of funtionality is missing compared to your favourite editor :)
|
// A lot of funtionality is missing compared to your favourite editor :)
|
||||||
import strings
|
import strings
|
||||||
import os
|
import os
|
||||||
import term
|
|
||||||
import term.ui
|
import term.ui
|
||||||
|
|
||||||
type InputType = byte | rune | string
|
|
||||||
|
|
||||||
fn (ipt InputType) len() int {
|
|
||||||
match ipt {
|
|
||||||
byte, rune {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
string {
|
|
||||||
return it.len
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (ipt InputType) str() string {
|
|
||||||
match ipt {
|
|
||||||
byte, rune {
|
|
||||||
return ipt.str()
|
|
||||||
}
|
|
||||||
string {
|
|
||||||
return ipt.str()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Movement {
|
enum Movement {
|
||||||
up
|
up
|
||||||
down
|
down
|
||||||
|
@ -48,11 +23,12 @@ pub:
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
mut:
|
mut:
|
||||||
ti &ui.Context = 0
|
tui &ui.Context = 0
|
||||||
ed &Buffer = 0
|
ed &Buffer = 0
|
||||||
file string
|
file string
|
||||||
status string
|
status string
|
||||||
t int
|
t int
|
||||||
|
footer_height int = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut a App) set_status(msg string, duration_ms int) {
|
fn (mut a App) set_status(msg string, duration_ms int) {
|
||||||
|
@ -64,18 +40,14 @@ fn (mut a App) save() {
|
||||||
if a.file.len > 0 {
|
if a.file.len > 0 {
|
||||||
b := a.ed
|
b := a.ed
|
||||||
os.write_file(a.file, b.raw())
|
os.write_file(a.file, b.raw())
|
||||||
a.set_status('saved', 2000)
|
a.set_status('Saved', 2000)
|
||||||
} else {
|
} else {
|
||||||
a.set_status('No file loaded', 4000)
|
a.set_status('No file loaded', 4000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut a App) footer() {
|
fn (mut a App) footer() {
|
||||||
w, h := term.get_terminal_size()
|
w, h := a.tui.window_width, a.tui.window_height
|
||||||
term.set_cursor_position({
|
|
||||||
x: 0
|
|
||||||
y: h - 1
|
|
||||||
})
|
|
||||||
mut b := a.ed
|
mut b := a.ed
|
||||||
flat := b.flat()
|
flat := b.flat()
|
||||||
snip := if flat.len > 19 { flat[..20] } else { flat }
|
snip := if flat.len > 19 { flat[..20] } else { flat }
|
||||||
|
@ -88,10 +60,8 @@ fn (mut a App) footer() {
|
||||||
status = ''
|
status = ''
|
||||||
}
|
}
|
||||||
line := '─'.repeat(w) + '\n'
|
line := '─'.repeat(w) + '\n'
|
||||||
footer := line + '$finfo Line ${b.cursor.pos.y + 1}/$b.lines.len, Column ${b.cursor.pos.x +
|
footer := line + '$finfo Line ${b.cursor.pos_y + 1:4}/${b.lines.len:-4}, Column ${b.cursor.pos_x + 1:3}/${b.cur_line().len:-3} index: ${b.cursor_index():5} (ESC = quit, Ctrl+s = save) Raw: "$snip" $status'
|
||||||
1}/$b.cur_line().len index: $b.cursor_index() (ESC = quit, Ctrl+s = save) Raw: "$snip" $status'
|
a.tui.draw_text(0, h - 1, footer)
|
||||||
print(footer)
|
|
||||||
flush()
|
|
||||||
a.t -= 33
|
a.t -= 33
|
||||||
if a.t < 0 {
|
if a.t < 0 {
|
||||||
a.t = 0
|
a.t = 0
|
||||||
|
@ -99,7 +69,6 @@ fn (mut a App) footer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Buffer {
|
struct Buffer {
|
||||||
line_break string = '\n'
|
|
||||||
tab_width int = 4
|
tab_width int = 4
|
||||||
pub mut:
|
pub mut:
|
||||||
lines []string
|
lines []string
|
||||||
|
@ -107,31 +76,35 @@ pub mut:
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (b Buffer) flat() string {
|
fn (b Buffer) flat() string {
|
||||||
return b.raw().replace(b.line_break, r'\n').replace('\t', r'\t')
|
return b.raw().replace_each(['\n', r'\n', '\t', r'\t'])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (b Buffer) raw() string {
|
fn (b Buffer) raw() string {
|
||||||
return b.lines.join(b.line_break)
|
return b.lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (b Buffer) view() View {
|
fn (b Buffer) view(from int, to int) View {
|
||||||
l := b.cur_line()
|
l := b.cur_line()
|
||||||
mut x := 0
|
mut x := 0
|
||||||
for i := 0; i < b.cursor.pos.x && i < l.len; i++ {
|
for i := 0; i < b.cursor.pos_x && i < l.len; i++ {
|
||||||
if l[i] == `\t` {
|
if l[i] == `\t` {
|
||||||
x += b.tab_width
|
x += b.tab_width
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
x++
|
x++
|
||||||
}
|
}
|
||||||
p := CursorPosition{
|
mut lines := []string{}
|
||||||
y: b.cursor.pos.y
|
for i, line in b.lines {
|
||||||
x: x
|
if i >= from && i <= to {
|
||||||
|
lines << line
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return View{
|
raw := lines.join('\n')
|
||||||
raw: b.raw().replace('\t', strings.repeat(` `, b.tab_width))
|
return {
|
||||||
cursor: Cursor{
|
raw: raw.replace('\t', strings.repeat(` `, b.tab_width))
|
||||||
pos: p
|
cursor: {
|
||||||
|
pos_x: x
|
||||||
|
pos_y: b.cursor.pos_y
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,8 +120,8 @@ fn (b Buffer) cur_line() string {
|
||||||
fn (b Buffer) cursor_index() int {
|
fn (b Buffer) cursor_index() int {
|
||||||
mut i := 0
|
mut i := 0
|
||||||
for y, line in b.lines {
|
for y, line in b.lines {
|
||||||
if b.cursor.pos.y == y {
|
if b.cursor.pos_y == y {
|
||||||
i += b.cursor.pos.x
|
i += b.cursor.pos_x
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
i += line.len + 1
|
i += line.len + 1
|
||||||
|
@ -156,33 +129,31 @@ fn (b Buffer) cursor_index() int {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut b Buffer) put(ipt InputType) {
|
fn (mut b Buffer) put(s string) {
|
||||||
s := ipt.str()
|
has_line_ending := s.contains('\n')
|
||||||
has_line_ending := s.contains(b.line_break)
|
|
||||||
x, y := b.cursor.xy()
|
x, y := b.cursor.xy()
|
||||||
if b.lines.len == 0 {
|
if b.lines.len == 0 {
|
||||||
b.lines.prepend('')
|
b.lines.prepend('')
|
||||||
}
|
}
|
||||||
line := b.lines[y]
|
line := b.lines[y]
|
||||||
l := line[..x]
|
l, r := line[..x], line[x..]
|
||||||
r := line[x..]
|
|
||||||
if has_line_ending {
|
if has_line_ending {
|
||||||
mut lines := s.split(b.line_break)
|
mut lines := s.split('\n')
|
||||||
lines[0] = l + lines[0]
|
lines[0] = l + lines[0]
|
||||||
lines[lines.len - 1] += r
|
lines[lines.len - 1] += r
|
||||||
b.lines.delete(y)
|
b.lines.delete(y)
|
||||||
b.lines.insert(y, lines)
|
b.lines.insert(y, lines)
|
||||||
last := lines[lines.len - 1]
|
last := lines[lines.len - 1]
|
||||||
b.cursor.set(last.len, y + lines.len - 1)
|
b.cursor.set(last.len, y + lines.len - 1)
|
||||||
if s == b.line_break {
|
if s == '\n' {
|
||||||
b.cursor.set(0, b.cursor.pos.y)
|
b.cursor.set(0, b.cursor.pos_y)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
b.lines[y] = l + s + r
|
b.lines[y] = l + s + r
|
||||||
b.cursor.set(x + s.len, y)
|
b.cursor.set(x + s.len, y)
|
||||||
}
|
}
|
||||||
$if debug {
|
$if debug {
|
||||||
flat := s.replace(b.line_break, r'\n')
|
flat := s.replace('\n', r'\n')
|
||||||
eprintln(@MOD + '.' + @STRUCT + '::' + @FN + ' "$flat"')
|
eprintln(@MOD + '.' + @STRUCT + '::' + @FN + ' "$flat"')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -196,10 +167,8 @@ fn (mut b Buffer) del(amount int) string {
|
||||||
if x == 0 && y == 0 {
|
if x == 0 && y == 0 {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
} else {
|
} else if x >= b.cur_line().len && y >= b.lines.len - 1 {
|
||||||
if x >= b.cur_line().len && y >= b.lines.len - 1 {
|
return ''
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
mut removed := ''
|
mut removed := ''
|
||||||
if amount < 0 { // backspace (backward)
|
if amount < 0 { // backspace (backward)
|
||||||
|
@ -216,11 +185,11 @@ fn (mut b Buffer) del(amount int) string {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
line_above := b.lines[li - 1]
|
line_above := b.lines[li - 1]
|
||||||
b.cursor.pos.x = line_above.len
|
b.cursor.pos_x = line_above.len
|
||||||
} else {
|
} else {
|
||||||
left -= ln.len
|
left -= ln.len
|
||||||
}
|
}
|
||||||
b.cursor.pos.y--
|
b.cursor.pos_y--
|
||||||
} else {
|
} else {
|
||||||
if x == 0 {
|
if x == 0 {
|
||||||
if y == 0 {
|
if y == 0 {
|
||||||
|
@ -229,20 +198,20 @@ fn (mut b Buffer) del(amount int) string {
|
||||||
line_above := b.lines[li - 1]
|
line_above := b.lines[li - 1]
|
||||||
if ln.len == 0 { // at line break
|
if ln.len == 0 { // at line break
|
||||||
b.lines.delete(li)
|
b.lines.delete(li)
|
||||||
b.cursor.pos.y--
|
b.cursor.pos_y--
|
||||||
b.cursor.pos.x = line_above.len
|
b.cursor.pos_x = line_above.len
|
||||||
} else {
|
} else {
|
||||||
b.lines[li - 1] = line_above + ln
|
b.lines[li - 1] = line_above + ln
|
||||||
b.lines.delete(li)
|
b.lines.delete(li)
|
||||||
b.cursor.pos.y--
|
b.cursor.pos_y--
|
||||||
b.cursor.pos.x = line_above.len
|
b.cursor.pos_x = line_above.len
|
||||||
}
|
}
|
||||||
} else if x == 1 {
|
} else if x == 1 {
|
||||||
b.lines[li] = b.lines[li][left..]
|
b.lines[li] = b.lines[li][left..]
|
||||||
b.cursor.pos.x = 0
|
b.cursor.pos_x = 0
|
||||||
} else {
|
} else {
|
||||||
b.lines[li] = ln[..x - left] + ln[x..]
|
b.lines[li] = ln[..x - left] + ln[x..]
|
||||||
b.cursor.pos.x -= left
|
b.cursor.pos_x -= left
|
||||||
}
|
}
|
||||||
left = 0
|
left = 0
|
||||||
break
|
break
|
||||||
|
@ -271,7 +240,7 @@ fn (mut b Buffer) del(amount int) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$if debug {
|
$if debug {
|
||||||
flat := removed.replace(b.line_break, r'\n')
|
flat := removed.replace('\n', r'\n')
|
||||||
eprintln(@MOD + '.' + @STRUCT + '::' + @FN + ' "$flat"')
|
eprintln(@MOD + '.' + @STRUCT + '::' + @FN + ' "$flat"')
|
||||||
}
|
}
|
||||||
return removed
|
return removed
|
||||||
|
@ -289,85 +258,95 @@ fn (mut b Buffer) free() {
|
||||||
|
|
||||||
// move_cursor will navigate the cursor within the buffer bounds
|
// move_cursor will navigate the cursor within the buffer bounds
|
||||||
fn (mut b Buffer) move_cursor(amount int, movement Movement) {
|
fn (mut b Buffer) move_cursor(amount int, movement Movement) {
|
||||||
pos := b.cursor.pos
|
|
||||||
cur_line := b.cur_line()
|
cur_line := b.cur_line()
|
||||||
match movement {
|
match movement {
|
||||||
.up {
|
.up {
|
||||||
if pos.y - amount >= 0 {
|
if b.cursor.pos_y - amount >= 0 {
|
||||||
b.cursor.move(0, -amount)
|
b.cursor.move(0, -amount)
|
||||||
// Check the move
|
// Check the move
|
||||||
line := b.cur_line()
|
line := b.cur_line()
|
||||||
if b.cursor.pos.x > line.len {
|
if b.cursor.pos_x > line.len {
|
||||||
b.cursor.set(line.len, b.cursor.pos.y)
|
b.cursor.set(line.len, b.cursor.pos_y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.down {
|
.down {
|
||||||
if pos.y + amount < b.lines.len {
|
if b.cursor.pos_y + amount < b.lines.len {
|
||||||
b.cursor.move(0, amount)
|
b.cursor.move(0, amount)
|
||||||
// Check the move
|
// Check the move
|
||||||
line := b.cur_line()
|
line := b.cur_line()
|
||||||
if b.cursor.pos.x > line.len {
|
if b.cursor.pos_x > line.len {
|
||||||
b.cursor.set(line.len, b.cursor.pos.y)
|
b.cursor.set(line.len, b.cursor.pos_y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.left {
|
.left {
|
||||||
if pos.x - amount >= 0 {
|
if b.cursor.pos_x - amount >= 0 {
|
||||||
b.cursor.move(-amount, 0)
|
b.cursor.move(-amount, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.right {
|
.right {
|
||||||
if pos.x + amount <= cur_line.len {
|
if b.cursor.pos_x + amount <= cur_line.len {
|
||||||
b.cursor.move(amount, 0)
|
b.cursor.move(amount, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.home {
|
.home {
|
||||||
b.cursor.set(0, b.cursor.pos.y)
|
b.cursor.set(0, b.cursor.pos_y)
|
||||||
}
|
}
|
||||||
.end {
|
.end {
|
||||||
b.cursor.set(cur_line.len, b.cursor.pos.y)
|
b.cursor.set(cur_line.len, b.cursor.pos_y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cursor related
|
|
||||||
struct CursorPosition {
|
|
||||||
pub mut:
|
|
||||||
x int
|
|
||||||
y int
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Cursor {
|
struct Cursor {
|
||||||
pub mut:
|
pub mut:
|
||||||
pos CursorPosition
|
pos_x int
|
||||||
|
pos_y int
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut c Cursor) set(x int, y int) {
|
fn (mut c Cursor) set(x int, y int) {
|
||||||
c.pos.x = x
|
c.pos_x = x
|
||||||
c.pos.y = y
|
c.pos_y = y
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut c Cursor) move(x int, y int) {
|
fn (mut c Cursor) move(x int, y int) {
|
||||||
c.pos.x += x
|
c.pos_x += x
|
||||||
c.pos.y += y
|
c.pos_y += y
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (c Cursor) xy() (int, int) {
|
fn (c Cursor) xy() (int, int) {
|
||||||
return c.pos.x, c.pos.y
|
return c.pos_x, c.pos_y
|
||||||
}
|
}
|
||||||
|
|
||||||
// App callbacks
|
// App callbacks
|
||||||
fn init(x voidptr) {
|
fn init(x voidptr) {
|
||||||
mut app := &App(x)
|
mut app := &App(x)
|
||||||
app.ed = &Buffer{}
|
app.ed = &Buffer{}
|
||||||
|
mut init_y := 0
|
||||||
|
mut init_x := 0
|
||||||
if app.file.len > 0 {
|
if app.file.len > 0 {
|
||||||
|
if !os.is_file(app.file) && app.file.contains(':') {
|
||||||
|
// support the file:line:col: format
|
||||||
|
fparts := app.file.split(':')
|
||||||
|
if fparts.len > 0 {
|
||||||
|
app.file = fparts[0]
|
||||||
|
}
|
||||||
|
if fparts.len > 1 {
|
||||||
|
init_y = fparts[1].int() - 1
|
||||||
|
}
|
||||||
|
if fparts.len > 2 {
|
||||||
|
init_x = fparts[2].int() - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
if os.is_file(app.file) {
|
if os.is_file(app.file) {
|
||||||
mut b := app.ed
|
mut b := app.ed
|
||||||
content := os.read_file(app.file) or {
|
content := os.read_file(app.file) or {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
b.put(content)
|
b.put(content)
|
||||||
|
app.ed.cursor.pos_x = init_x
|
||||||
|
app.ed.cursor.pos_y = init_y
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -375,29 +354,22 @@ fn init(x voidptr) {
|
||||||
fn frame(x voidptr) {
|
fn frame(x voidptr) {
|
||||||
mut app := &App(x)
|
mut app := &App(x)
|
||||||
mut ed := app.ed
|
mut ed := app.ed
|
||||||
term.erase_clear()
|
app.tui.clear()
|
||||||
term.erase_del_clear()
|
scroll_limit := app.tui.window_height-app.footer_height-1
|
||||||
term.set_cursor_position({
|
mut view := View{}
|
||||||
x: 0
|
if ed.cursor.pos_y > scroll_limit { // Scroll
|
||||||
y: 0
|
view = ed.view(ed.cursor.pos_y-scroll_limit, ed.cursor.pos_y)
|
||||||
})
|
} else {
|
||||||
view := ed.view()
|
view = ed.view(0, scroll_limit)
|
||||||
print('$view.raw')
|
}
|
||||||
flush()
|
app.tui.draw_text(0, 0, view.raw)
|
||||||
app.footer()
|
app.footer()
|
||||||
term.set_cursor_position({
|
if ed.cursor.pos_y > scroll_limit {
|
||||||
x: view.cursor.pos.x + 1
|
app.tui.set_cursor_position(view.cursor.pos_x + 1, scroll_limit+1)
|
||||||
y: view.cursor.pos.y + 1
|
} else {
|
||||||
})
|
app.tui.set_cursor_position(view.cursor.pos_x + 1, view.cursor.pos_y + 1)
|
||||||
}
|
}
|
||||||
|
app.tui.flush()
|
||||||
fn cleanup(x voidptr) {
|
|
||||||
mut app := &App(x)
|
|
||||||
app.ed.free()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fail(error string) {
|
|
||||||
eprintln(error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn event(e &ui.Event, x voidptr) {
|
fn event(e &ui.Event, x voidptr) {
|
||||||
|
@ -406,10 +378,8 @@ fn event(e &ui.Event, x voidptr) {
|
||||||
if e.typ == .key_down {
|
if e.typ == .key_down {
|
||||||
match e.code {
|
match e.code {
|
||||||
.escape {
|
.escape {
|
||||||
term.set_cursor_position({
|
app.tui.set_cursor_position(0, 0)
|
||||||
x: 0
|
app.tui.flush()
|
||||||
y: 0
|
|
||||||
})
|
|
||||||
exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
.backspace {
|
.backspace {
|
||||||
|
@ -436,20 +406,22 @@ fn event(e &ui.Event, x voidptr) {
|
||||||
.end {
|
.end {
|
||||||
buffer.move_cursor(1, .end)
|
buffer.move_cursor(1, .end)
|
||||||
}
|
}
|
||||||
48...57 { // 0 - 9
|
48...57, 97...122 { // 0-9a-zA-Z
|
||||||
buffer.put(e.ascii)
|
if e.modifiers == ui.ctrl {
|
||||||
}
|
if e.code == .s {
|
||||||
97...122 { // a-zA-Z
|
app.save()
|
||||||
if e.modifiers == ui.ctrl && e.code == .s {
|
}
|
||||||
app.save()
|
} else if e.modifiers in [ui.shift, 0] {
|
||||||
} else {
|
buffer.put(e.ascii.str())
|
||||||
buffer.put(e.ascii)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
buffer.put(e.utf8.bytes().bytestr())
|
buffer.put(e.utf8.bytes().bytestr())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if e.typ == .mouse_scroll {
|
||||||
|
direction := if e.direction == .up { Movement.down } else { Movement.up }
|
||||||
|
buffer.move_cursor(1, direction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,14 +433,12 @@ if os.args.len > 1 {
|
||||||
mut app := &App{
|
mut app := &App{
|
||||||
file: file
|
file: file
|
||||||
}
|
}
|
||||||
app.ti = ui.init({
|
app.tui = ui.init({
|
||||||
user_data: app
|
user_data: app
|
||||||
init_fn: init
|
init_fn: init
|
||||||
frame_fn: frame
|
frame_fn: frame
|
||||||
cleanup_fn: cleanup
|
|
||||||
event_fn: event
|
event_fn: event
|
||||||
fail_fn: fail
|
|
||||||
capture_events: true
|
capture_events: true
|
||||||
frame_rate: 30
|
frame_rate: 30
|
||||||
})
|
})
|
||||||
app.ti.run()
|
app.tui.run()
|
||||||
|
|
Loading…
Reference in New Issue