examples/text_editor: edit multiple files (#6827)
parent
50163508f8
commit
827fb62c29
|
@ -13,6 +13,8 @@ enum Movement {
|
||||||
right
|
right
|
||||||
home
|
home
|
||||||
end
|
end
|
||||||
|
page_up
|
||||||
|
page_down
|
||||||
}
|
}
|
||||||
|
|
||||||
struct View {
|
struct View {
|
||||||
|
@ -23,13 +25,14 @@ pub:
|
||||||
|
|
||||||
struct App {
|
struct App {
|
||||||
mut:
|
mut:
|
||||||
tui &tui.Context = 0
|
tui &tui.Context = 0
|
||||||
ed &Buffer = 0
|
ed &Buffer = 0
|
||||||
file string
|
current_file int
|
||||||
status string
|
files []string
|
||||||
t int
|
status string
|
||||||
|
t int
|
||||||
footer_height int = 2
|
footer_height int = 2
|
||||||
viewport int
|
viewport int
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut a App) set_status(msg string, duration_ms int) {
|
fn (mut a App) set_status(msg string, duration_ms int) {
|
||||||
|
@ -38,24 +41,50 @@ fn (mut a App) set_status(msg string, duration_ms int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut a App) save() {
|
fn (mut a App) save() {
|
||||||
if a.file.len > 0 {
|
if a.cfile().len > 0 {
|
||||||
b := a.ed
|
b := a.ed
|
||||||
os.write_file(a.file, b.raw())
|
os.write_file(a.cfile(), 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) cfile() string {
|
||||||
|
if a.files.len == 0 {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if a.current_file >= a.files.len {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return a.files[a.current_file]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut a App) visit_prev_file() {
|
||||||
|
if a.files.len == 0 {
|
||||||
|
a.current_file = 0
|
||||||
|
} else {
|
||||||
|
a.current_file = (a.current_file + a.files.len - 1) % a.files.len
|
||||||
|
}
|
||||||
|
a.init_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut a App) visit_next_file() {
|
||||||
|
if a.files.len == 0 {
|
||||||
|
a.current_file = 0
|
||||||
|
} else {
|
||||||
|
a.current_file = (a.current_file + a.files.len + 1) % a.files.len
|
||||||
|
}
|
||||||
|
a.init_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fn (mut a App) footer() {
|
fn (mut a App) footer() {
|
||||||
w, h := a.tui.window_width, a.tui.window_height
|
w, h := a.tui.window_width, a.tui.window_height
|
||||||
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 }
|
||||||
mut finfo := ''
|
finfo := if a.cfile().len > 0 { ' (' + os.file_name(a.cfile()) + ')' } else { '' }
|
||||||
if a.file.len > 0 {
|
|
||||||
finfo = ' (' + os.file_name(a.file) + ')'
|
|
||||||
}
|
|
||||||
mut status := a.status
|
mut status := a.status
|
||||||
a.tui.draw_text(0, h - 1, '─'.repeat(w))
|
a.tui.draw_text(0, h - 1, '─'.repeat(w))
|
||||||
footer := '$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)'
|
footer := '$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)'
|
||||||
|
@ -69,8 +98,16 @@ fn (mut a App) footer() {
|
||||||
if a.t <= 0 {
|
if a.t <= 0 {
|
||||||
status = ''
|
status = ''
|
||||||
} else {
|
} else {
|
||||||
a.tui.set_bg_color(r: 200, g: 200, b: 200)
|
a.tui.set_bg_color({
|
||||||
a.tui.set_color(r: 0, g: 0, b: 0)
|
r: 200
|
||||||
|
g: 200
|
||||||
|
b: 200
|
||||||
|
})
|
||||||
|
a.tui.set_color({
|
||||||
|
r: 0
|
||||||
|
g: 0
|
||||||
|
b: 0
|
||||||
|
})
|
||||||
a.tui.draw_text((w + 4 - status.len) / 2, h - 1, ' $status ')
|
a.tui.draw_text((w + 4 - status.len) / 2, h - 1, ' $status ')
|
||||||
a.tui.reset()
|
a.tui.reset()
|
||||||
a.t -= 33
|
a.t -= 33
|
||||||
|
@ -78,10 +115,10 @@ fn (mut a App) footer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Buffer {
|
struct Buffer {
|
||||||
tab_width int = 4
|
tab_width int = 4
|
||||||
pub mut:
|
pub mut:
|
||||||
lines []string
|
lines []string
|
||||||
cursor Cursor
|
cursor Cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (b Buffer) flat() string {
|
fn (b Buffer) flat() string {
|
||||||
|
@ -118,12 +155,15 @@ fn (b Buffer) view(from int, to int) View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (b Buffer) cur_line() string {
|
fn (b Buffer) line(i int) string {
|
||||||
_, y := b.cursor.xy()
|
if i < 0 || i >= b.lines.len {
|
||||||
if b.lines.len == 0 {
|
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
return b.lines[y]
|
return b.lines[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (b Buffer) cur_line() string {
|
||||||
|
return b.line(b.cursor.pos_y)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (b Buffer) cursor_index() int {
|
fn (b Buffer) cursor_index() int {
|
||||||
|
@ -265,38 +305,49 @@ fn (mut b Buffer) free() {
|
||||||
unsafe {b.lines.free()}
|
unsafe {b.lines.free()}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn (mut b Buffer) move_updown(amount int) {
|
||||||
|
b.cursor.move(0, amount)
|
||||||
|
// Check the move
|
||||||
|
line := b.cur_line()
|
||||||
|
if b.cursor.pos_x > line.len {
|
||||||
|
b.cursor.set(line.len, b.cursor.pos_y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
cur_line := b.cur_line()
|
cur_line := b.cur_line()
|
||||||
match movement {
|
match movement {
|
||||||
.up {
|
.up {
|
||||||
if b.cursor.pos_y - amount >= 0 {
|
if b.cursor.pos_y - amount >= 0 {
|
||||||
b.cursor.move(0, -amount)
|
b.move_updown(-amount)
|
||||||
// Check the move
|
|
||||||
line := b.cur_line()
|
|
||||||
if b.cursor.pos_x > line.len {
|
|
||||||
b.cursor.set(line.len, b.cursor.pos_y)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.down {
|
.down {
|
||||||
if b.cursor.pos_y + amount < b.lines.len {
|
if b.cursor.pos_y + amount < b.lines.len {
|
||||||
b.cursor.move(0, amount)
|
b.move_updown(amount)
|
||||||
// Check the move
|
|
||||||
line := b.cur_line()
|
|
||||||
if b.cursor.pos_x > line.len {
|
|
||||||
b.cursor.set(line.len, b.cursor.pos_y)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.page_up {
|
||||||
|
dlines := imin(b.cursor.pos_y, amount)
|
||||||
|
b.move_updown(-dlines)
|
||||||
|
}
|
||||||
|
.page_down {
|
||||||
|
dlines := imin(b.lines.len-1, b.cursor.pos_y + amount) - b.cursor.pos_y
|
||||||
|
b.move_updown(dlines)
|
||||||
|
}
|
||||||
.left {
|
.left {
|
||||||
if b.cursor.pos_x - amount >= 0 {
|
if b.cursor.pos_x - amount >= 0 {
|
||||||
b.cursor.move(-amount, 0)
|
b.cursor.move(-amount, 0)
|
||||||
|
} else if b.cursor.pos_y > 0 {
|
||||||
|
b.cursor.set(b.line(b.cursor.pos_y - 1).len, b.cursor.pos_y - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.right {
|
.right {
|
||||||
if b.cursor.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)
|
||||||
|
} else if b.cursor.pos_y + 1 < b.lines.len {
|
||||||
|
b.cursor.set(0, b.cursor.pos_y + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.home {
|
.home {
|
||||||
|
@ -308,6 +359,14 @@ fn (mut b Buffer) move_cursor(amount int, movement Movement) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn imax(x int, y int) int {
|
||||||
|
return if x < y { y } else { x }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn imin(x int, y int) int {
|
||||||
|
return if x < y { x } else { y }
|
||||||
|
}
|
||||||
|
|
||||||
struct Cursor {
|
struct Cursor {
|
||||||
pub mut:
|
pub mut:
|
||||||
pos_x int
|
pos_x int
|
||||||
|
@ -330,16 +389,21 @@ fn (c Cursor) xy() (int, int) {
|
||||||
|
|
||||||
// App callbacks
|
// App callbacks
|
||||||
fn init(x voidptr) {
|
fn init(x voidptr) {
|
||||||
mut app := &App(x)
|
mut a := &App(x)
|
||||||
app.ed = &Buffer{}
|
a.init_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut a App) init_file() {
|
||||||
|
a.ed = &Buffer{}
|
||||||
mut init_y := 0
|
mut init_y := 0
|
||||||
mut init_x := 0
|
mut init_x := 0
|
||||||
if app.file.len > 0 {
|
eprintln('> a.files: $a.files | a.current_file: $a.current_file')
|
||||||
if !os.is_file(app.file) && app.file.contains(':') {
|
if a.files.len > 0 && a.current_file < a.files.len && a.files[a.current_file].len > 0 {
|
||||||
|
if !os.is_file(a.files[a.current_file]) && a.files[a.current_file].contains(':') {
|
||||||
// support the file:line:col: format
|
// support the file:line:col: format
|
||||||
fparts := app.file.split(':')
|
fparts := a.files[a.current_file].split(':')
|
||||||
if fparts.len > 0 {
|
if fparts.len > 0 {
|
||||||
app.file = fparts[0]
|
a.files[a.current_file] = fparts[0]
|
||||||
}
|
}
|
||||||
if fparts.len > 1 {
|
if fparts.len > 1 {
|
||||||
init_y = fparts[1].int() - 1
|
init_y = fparts[1].int() - 1
|
||||||
|
@ -348,46 +412,50 @@ fn init(x voidptr) {
|
||||||
init_x = fparts[2].int() - 1
|
init_x = fparts[2].int() - 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if os.is_file(app.file) {
|
if os.is_file(a.files[a.current_file]) {
|
||||||
app.tui.set_window_title(/* 'vico: ' + */app.file)
|
// 'vico: ' +
|
||||||
mut b := app.ed
|
a.tui.set_window_title(a.files[a.current_file])
|
||||||
content := os.read_file(app.file) or {
|
mut b := a.ed
|
||||||
|
content := os.read_file(a.files[a.current_file]) or {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
b.put(content)
|
b.put(content)
|
||||||
app.ed.cursor.pos_x = init_x
|
a.ed.cursor.pos_x = init_x
|
||||||
app.ed.cursor.pos_y = init_y
|
a.ed.cursor.pos_y = init_y
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn frame(x voidptr) {
|
fn (a &App) view_height() int {
|
||||||
mut app := &App(x)
|
return a.tui.window_height - a.footer_height - 1
|
||||||
mut ed := app.ed
|
}
|
||||||
app.tui.clear()
|
|
||||||
|
|
||||||
scroll_limit := app.tui.window_height-app.footer_height-1
|
fn frame(x voidptr) {
|
||||||
|
mut a := &App(x)
|
||||||
|
mut ed := a.ed
|
||||||
|
a.tui.clear()
|
||||||
|
scroll_limit := a.view_height()
|
||||||
// scroll down
|
// scroll down
|
||||||
if ed.cursor.pos_y > app.viewport+scroll_limit { // scroll down
|
if ed.cursor.pos_y > a.viewport + scroll_limit { // scroll down
|
||||||
app.viewport = ed.cursor.pos_y-scroll_limit
|
a.viewport = ed.cursor.pos_y - scroll_limit
|
||||||
} else if ed.cursor.pos_y < app.viewport { // scroll up
|
} else if ed.cursor.pos_y < a.viewport { // scroll up
|
||||||
app.viewport = ed.cursor.pos_y
|
a.viewport = ed.cursor.pos_y
|
||||||
}
|
}
|
||||||
view := ed.view(app.viewport, scroll_limit+app.viewport)
|
view := ed.view(a.viewport, scroll_limit + a.viewport)
|
||||||
app.tui.draw_text(0, 0, view.raw)
|
a.tui.draw_text(0, 0, view.raw)
|
||||||
app.footer()
|
a.footer()
|
||||||
app.tui.set_cursor_position(view.cursor.pos_x + 1, ed.cursor.pos_y + 1 - app.viewport)
|
a.tui.set_cursor_position(view.cursor.pos_x + 1, ed.cursor.pos_y + 1 - a.viewport)
|
||||||
app.tui.flush()
|
a.tui.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn event(e &tui.Event, x voidptr) {
|
fn event(e &tui.Event, x voidptr) {
|
||||||
mut app := &App(x)
|
mut a := &App(x)
|
||||||
mut buffer := app.ed
|
mut buffer := a.ed
|
||||||
if e.typ == .key_down {
|
if e.typ == .key_down {
|
||||||
match e.code {
|
match e.code {
|
||||||
.escape {
|
.escape {
|
||||||
app.tui.set_cursor_position(0, 0)
|
a.tui.set_cursor_position(0, 0)
|
||||||
app.tui.flush()
|
a.tui.flush()
|
||||||
exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
.backspace {
|
.backspace {
|
||||||
|
@ -408,6 +476,12 @@ fn event(e &tui.Event, x voidptr) {
|
||||||
.down {
|
.down {
|
||||||
buffer.move_cursor(1, .down)
|
buffer.move_cursor(1, .down)
|
||||||
}
|
}
|
||||||
|
.page_up {
|
||||||
|
buffer.move_cursor(a.view_height(), .page_up)
|
||||||
|
}
|
||||||
|
.page_down {
|
||||||
|
buffer.move_cursor(a.view_height(), .page_down)
|
||||||
|
}
|
||||||
.home {
|
.home {
|
||||||
buffer.move_cursor(1, .home)
|
buffer.move_cursor(1, .home)
|
||||||
}
|
}
|
||||||
|
@ -417,13 +491,23 @@ fn event(e &tui.Event, x voidptr) {
|
||||||
48...57, 97...122 { // 0-9a-zA-Z
|
48...57, 97...122 { // 0-9a-zA-Z
|
||||||
if e.modifiers == tui.ctrl {
|
if e.modifiers == tui.ctrl {
|
||||||
if e.code == .s {
|
if e.code == .s {
|
||||||
app.save()
|
a.save()
|
||||||
}
|
}
|
||||||
} else if e.modifiers in [tui.shift, 0] {
|
} else if e.modifiers in [tui.shift, 0] {
|
||||||
buffer.put(e.ascii.str())
|
buffer.put(e.ascii.str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if e.modifiers == tui.alt {
|
||||||
|
if e.code == .comma {
|
||||||
|
a.visit_prev_file()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if e.code == .period {
|
||||||
|
a.visit_next_file()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
buffer.put(e.utf8.bytes().bytestr())
|
buffer.put(e.utf8.bytes().bytestr())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -433,19 +517,20 @@ fn event(e &tui.Event, x voidptr) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// main
|
fn main() {
|
||||||
mut file := ''
|
mut files := []string{}
|
||||||
if os.args.len > 1 {
|
if os.args.len > 1 {
|
||||||
file = os.args[1]
|
files << os.args[1..]
|
||||||
|
}
|
||||||
|
mut a := &App{
|
||||||
|
files: files
|
||||||
|
}
|
||||||
|
a.tui = tui.init({
|
||||||
|
user_data: a
|
||||||
|
init_fn: init
|
||||||
|
frame_fn: frame
|
||||||
|
event_fn: event
|
||||||
|
capture_events: true
|
||||||
|
})
|
||||||
|
a.tui.run()
|
||||||
}
|
}
|
||||||
mut app := &App{
|
|
||||||
file: file
|
|
||||||
}
|
|
||||||
app.tui = tui.init({
|
|
||||||
user_data: app
|
|
||||||
init_fn: init
|
|
||||||
frame_fn: frame
|
|
||||||
event_fn: event
|
|
||||||
capture_events: true
|
|
||||||
})
|
|
||||||
app.tui.run()
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ fn restore_terminal_state() {
|
||||||
fn (mut ctx Context) termios_setup() {
|
fn (mut ctx Context) termios_setup() {
|
||||||
// store the current title, so restore_terminal_state can get it back
|
// store the current title, so restore_terminal_state can get it back
|
||||||
ctx.save_title()
|
ctx.save_title()
|
||||||
|
|
||||||
mut termios := get_termios()
|
mut termios := get_termios()
|
||||||
|
|
||||||
if ctx.cfg.capture_events {
|
if ctx.cfg.capture_events {
|
||||||
|
@ -205,8 +205,8 @@ fn single_char(buf string) &Event {
|
||||||
|
|
||||||
|
|
||||||
// The bit `or`s here are really just `+`'s, just written in this way for a tiny performance improvement
|
// The bit `or`s here are really just `+`'s, just written in this way for a tiny performance improvement
|
||||||
// 10 == `\n` == enter, don't treat it as ctrl + j
|
// don't treat tab, enter as ctrl+i, ctrl+j
|
||||||
1 ... 9, 11 ... 26 { event = &Event{ typ: event.typ, ascii: event.ascii, utf8: event.utf8, code: KeyCode(96 | ch), modifiers: ctrl } }
|
1 ... 8, 11 ... 26 { event = &Event{ typ: event.typ, ascii: event.ascii, utf8: event.utf8, code: KeyCode(96 | ch), modifiers: ctrl } }
|
||||||
65 ... 90 { event = &Event{ typ: event.typ, ascii: event.ascii, utf8: event.utf8, code: KeyCode(32 | ch), modifiers: shift } }
|
65 ... 90 { event = &Event{ typ: event.typ, ascii: event.ascii, utf8: event.utf8, code: KeyCode(32 | ch), modifiers: shift } }
|
||||||
|
|
||||||
else {}
|
else {}
|
||||||
|
@ -343,6 +343,11 @@ fn escape_sequence(buf_ string) (&Event, int) {
|
||||||
else {}
|
else {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if buf == '[Z' {
|
||||||
|
code = .tab
|
||||||
|
modifiers |= shift
|
||||||
|
}
|
||||||
|
|
||||||
if buf.len == 5 && buf[0] == `[` && buf[1].is_digit() && buf[2] == `;` {
|
if buf.len == 5 && buf[0] == `[` && buf[1].is_digit() && buf[2] == `;` {
|
||||||
// code = KeyCode(buf[4] + 197)
|
// code = KeyCode(buf[4] + 197)
|
||||||
modifiers = match buf[3] {
|
modifiers = match buf[3] {
|
||||||
|
|
Loading…
Reference in New Issue