diff options
author | Petter Rodhelind <petter.rodhelind@gmail.com> | 2018-02-24 23:43:22 +0100 |
---|---|---|
committer | Petter Rodhelind <petter.rodhelind@gmail.com> | 2018-02-24 23:43:22 +0100 |
commit | 1c1469d5522c7c7d7c03232d9c2711c5d96d3c18 (patch) | |
tree | d2bd4e1b17a4ab1cb496ae05be42daffcd4d1305 | |
parent | 9f351fc3691f30ae5b3c195ef186470987ac78f9 (diff) | |
download | poe-1c1469d5522c7c7d7c03232d9c2711c5d96d3c18.tar.gz poe-1c1469d5522c7c7d7c03232d9c2711c5d96d3c18.tar.bz2 poe-1c1469d5522c7c7d7c03232d9c2711c5d96d3c18.zip |
Bulk. Move fast and break things. Whatever.
Fixed scrolling and mouse selection in regards to soft wrap and tabstop.
Fixed empty buffer windir setting which will make new buffers start in
correct working dir.
Fixed automatic dot expansion on Open and Run.
Made commands not need a prepended ! to be considered an external command.
-rw-r--r-- | commands.go | 12 | ||||
-rw-r--r-- | style.go | 12 | ||||
-rw-r--r-- | text.go | 9 | ||||
-rw-r--r-- | todo | 13 | ||||
-rw-r--r-- | view.go | 176 | ||||
-rw-r--r-- | window.go | 20 |
6 files changed, 182 insertions, 60 deletions
diff --git a/commands.go b/commands.go index 023a112..9d31614 100644 --- a/commands.go +++ b/commands.go @@ -37,15 +37,11 @@ func RunCommand(input string) { // Edit shortcuts for external commands and piping switch input[0] { - case '!': - fallthrough - case '<': - fallthrough - case '>': - fallthrough - case '|': + case '!', '<', '>', '|': CmdEdit(input) } + + CmdEdit("!" + input) } func CmdExit(args string) { @@ -116,7 +112,7 @@ func CmdEdit(args string) { // if command produced output, print it outstr := string(out) if outstr != "" { - printMsg("%s\n", outstr) + printMsg("%s", outstr) } default: printMsg("?\n") @@ -9,9 +9,11 @@ var ( bodyHilightStyle tcell.Style // tag is the window tag line above the body - tagStyle tcell.Style - tagCursorStyle tcell.Style - tagHilightStyle tcell.Style + tagStyle tcell.Style + tagCursorStyle tcell.Style + tagHilightStyle tcell.Style + tagSquareStyle tcell.Style + tagSquareModifiedStyle tcell.Style // vertline is the vertical line separating columns vertlineStyle tcell.Style @@ -40,6 +42,10 @@ func InitStyles() { Foreground(tcell.ColorBlack) tagHilightStyle = tagStyle. Background(tcell.NewHexColor(0x8888cc)) + tagSquareStyle = tagStyle. + Background(tcell.NewHexColor(0x8888cc)) + tagSquareModifiedStyle = tagStyle. + Background(tcell.NewHexColor(0x0)) vertlineStyle = bodyStyle } @@ -74,6 +74,11 @@ func (t *Text) Len() int { return t.buf.Len() } +// String returns the entire text buffer as a string. +func (t *Text) String() string { + return string(t.buf.Bytes()) +} + // ReadRune reads a rune from buffer and advances the internal offset. This could be called in sequence to get all runes from buffer. This populates LastRune(). func (t *Text) ReadRune() (r rune, size int, err error) { r, size, err = t.ReadRuneAt(t.off) @@ -261,6 +266,10 @@ func (t *Text) Select(offset int) { offset, _ = t.Seek(offset, io.SeekStart) start, end := offset, offset + // space + //start -= t.PrevSpace(start) + //end += t.NextSpace(end) + // word start -= t.PrevWord(start) end += t.NextWord(end) @@ -1,15 +1,13 @@ jobs / commands - async extern exekvering via !<>| + async execution via !<>| file - spara tom buffer med nytt filnamn + save empty buffer with tag name check for overwrite overwrite when hash-verify fails on save backup files on disk window - hide - command Get -view - scroll must account for soft wraps + hide / collapse + command Get (update buffer content) text int64 as default in case of large files undvika in-ram buffer - swapfiles? @@ -18,6 +16,7 @@ text undo unlimited for real (undo undo) cache write Change{} until next action + refactor all Next/Prev-funcs --- visual calculate view percentage on screen @@ -29,4 +28,4 @@ visual another style for top menu do not change CurWin on mousepressed scroll on mpressed at bottom line - unprintableChar styling
\ No newline at end of file + unprintableChar styling @@ -3,6 +3,7 @@ package main import ( "io" "path/filepath" + "strings" "time" "unicode/utf8" @@ -92,79 +93,106 @@ func (v *View) SetCursor(pos, whence int) { } } -// XYToOffset translates mouse coordinates in a 2D terminal to the correct byte offset in buffer, accounting for rune length and width. -func (b *View) XYToOffset(x, y int) int { - offset := b.scrollpos +// XYToOffset translates mouse coordinates in a 2D terminal to the correct byte offset in buffer, accounting for rune length, width and tabstops. +func (v *View) XYToOffset(x, y int) int { + offset := v.scrollpos - // vertical (number of lines) - for y-b.y > 0 { - r, _, _ := b.text.ReadRuneAt(offset) + // vertical (number of visual lines) + for y-v.y > 0 { + r, _, _ := v.text.ReadRuneAt(offset) if r == '\n' { offset++ y-- continue } - // loop until next line - xw := 0 - for r != '\n' && xw < b.x+b.w { + // loop until next line, either new line or soft wrap at end of window width + xw := v.x + for r != '\n' && xw <= v.x+v.w { var n int - r, n, _ = b.text.ReadRuneAt(offset) + r, n, _ = v.text.ReadRuneAt(offset) offset += n rw := RuneWidth(r) - if rw == 0 { + if r == '\t' { + rw = v.tabstop - (xw-v.x)%v.tabstop + } else if rw == 0 { rw = 1 } + xw += rw } y-- } // horizontal - for x-b.x > 0 { - r, n, _ := b.text.ReadRuneAt(offset) + xw := v.x // for tabstop count + for x-v.x > 0 { + r, n, _ := v.text.ReadRuneAt(offset) if r == '\n' { break } - if r == '\t' { - x -= b.tabstop - 1 // TODO: modulo count - } offset += n rw := RuneWidth(r) - if rw == 0 { + if r == '\t' { + rw = v.tabstop - (xw-v.x)%v.tabstop + } else if rw == 0 { rw = 1 } + xw += rw // keep track of tabstop modulo x -= rw } return offset } -// Scroll will move the visible part of the buffer in number of lines. Negative means upwards. +// Scroll will move the visible part of the buffer in number of lines, accounting for soft wraps and tabstops. Negative means upwards. func (v *View) Scroll(n int) { offset := 0 - if n > 0 { + + xw := v.x // for tabstop count and soft wrap + switch { + case n > 0: // downwards, next line for n > 0 { - if r, _, _ := v.text.ReadRuneAt(v.scrollpos + offset); r == '\n' { - offset++ - } else { - offset += v.text.NextDelim('\n', v.scrollpos+offset) + 1 + r, size, err := v.text.ReadRuneAt(v.scrollpos + offset) + if err != nil { + break // hit EOF, stop scrolling + } + offset += size + + rw := RuneWidth(r) + if r == '\t' { + rw = v.tabstop - (xw-v.x)%v.tabstop + } else if rw == 0 { + rw = 1 + } + xw += rw + + if r == '\n' || xw > v.x+v.w { // new line or soft wrap + n-- // move down + xw = v.x // reset soft wrap } - n-- } v.scrollpos += offset - } - - if n < 0 { - offset += v.text.PrevDelim('\n', v.scrollpos) + case n < 0: // upwards, previous line + // This is kind of ugly, but it relies on the soft wrap + // counting in positive scrolling. It will scroll back to the + // nearest new line character and then scroll forward again + // until the very last iteration, which is the offset for the previous + // softwrap/nl. for n < 0 { - offset += v.text.PrevDelim('\n', v.scrollpos-offset) + start := v.scrollpos // save current offset + v.scrollpos -= v.text.PrevDelim('\n', v.scrollpos) // scroll back + if start-v.scrollpos == 1 { // if it was an empty new line, back up one more + v.scrollpos -= v.text.PrevDelim('\n', v.scrollpos) + } + prevlineoffset := v.scrollpos // previous (or one more) new line, may be way back + + for v.scrollpos < start { // scroll one line forward until we're back at current + prevlineoffset = v.scrollpos // save offset just before we jump forward again + v.Scroll(1) // used for the side effect of setting v.scrollpos + } + v.scrollpos = prevlineoffset n++ } - if v.scrollpos-offset > 0 { - v.scrollpos -= offset - 1 - } else { - v.scrollpos = 0 - } } // boundaries @@ -317,7 +345,7 @@ func (v *View) HandleEvent(ev tcell.Event) { v.mpressed = false } case tcell.Button1: - if v.mpressed { + if v.mpressed { // select text via click-n-drag if pos > v.mclickpos { v.text.SetDot(v.mclickpos, pos) } else { @@ -348,6 +376,45 @@ func (v *View) HandleEvent(ev tcell.Event) { v.Scroll(-1) case tcell.WheelDown: // scrolldown v.Scroll(1) + case tcell.Button2: // middle click + // if we clicked inside a current selection, run that one + q0, q1 := v.text.Dot() + if pos >= q0 && pos <= q1 && q0 != q1 { + RunCommand(v.text.ReadDot()) + return + } + + // otherwise, select non-space chars under mouse and run that + p := pos - v.text.PrevSpace(pos) + n := pos + v.text.NextSpace(pos) + v.text.SetDot(p, n) + fn := strings.Trim(v.text.ReadDot(), "\n\t ") + v.text.SetDot(q0, q1) + RunCommand(fn) + return + case tcell.Button3: // right click + // if we clicked inside a current selection, open that one + q0, q1 := v.text.Dot() + if pos >= q0 && pos <= q1 && q0 != q1 { + CmdOpen(v.text.ReadDot()) + return + } + + // otherwise, select everything inside surround spaces and open that + p := pos - v.text.PrevSpace(pos) + n := pos + v.text.NextSpace(pos) + v.text.SetDot(p, n) + fn := strings.Trim(v.text.ReadDot(), "\n\t ") + v.text.SetDot(q0, q1) + if fn == "" { // if it is still blank, abort + return + } + if fn != "" && fn[0] != filepath.Separator { + fn = CurWin.Dir() + string(filepath.Separator) + fn + fn = filepath.Clean(fn) + } + CmdOpen(fn) + return default: printMsg("%#v", btn) } @@ -390,6 +457,9 @@ func (v *View) HandleEvent(ev tcell.Event) { v.SetCursor(offset, io.SeekCurrent) return case tcell.KeyCtrlU: // delete line backwards + if v.text.ReadDot() != "" { + v.Delete() // delete current selection first + } offset := v.text.PrevDelim('\n', v.Cursor()) if offset > 1 && v.Cursor()-offset != 0 { offset -= 1 @@ -402,6 +472,9 @@ func (v *View) HandleEvent(ev tcell.Event) { v.Delete() return case tcell.KeyCtrlW: // delete word backwards + if v.text.ReadDot() != "" { + v.Delete() // delete current selection first + } startpos := v.Cursor() offset := v.text.PrevWord(v.Cursor()) if offset == 0 { @@ -423,23 +496,44 @@ func (v *View) HandleEvent(ev tcell.Event) { v.Delete() return case tcell.KeyCtrlG: // file info/statistics - printMsg("0x%.4x %q %d,%d/%d\noverflow: %d scroll: %d\ndot: %q\nprev nl: %d\n", + printMsg("0x%.4x %q %d,%d/%d\nbasedir: %s\nwindir: %s\n\nname: %s\nnameabs: %s\ntagname: %s\n", v.Rune(), v.Rune(), v.text.q0, v.text.q1, v.text.Len(), - v.opos, v.scrollpos, - v.text.ReadDot(), - v.text.PrevDelim('\n', v.Cursor())) + baseDir, CurWin.Dir(), CurWin.Name(), CurWin.NameAbs(), CurWin.NameFromTag()) return case tcell.KeyCtrlO: // open file/dir fn := v.text.ReadDot() - if fn != FnEmptyWin && fn[0] != filepath.Separator { + if fn == "" { // select all non-space characters + curpos := v.Cursor() + p := curpos - v.text.PrevSpace(curpos) + n := curpos + v.text.NextSpace(curpos) + v.text.SetDot(p, n) + fn = strings.Trim(v.text.ReadDot(), "\n\t ") + v.SetCursor(curpos, io.SeekStart) + if fn == "" { // if it is still blank, abort + return + } + } + if fn != "" && fn[0] != filepath.Separator { fn = CurWin.Dir() + string(filepath.Separator) + fn fn = filepath.Clean(fn) } CmdOpen(fn) return case tcell.KeyCtrlR: // run command in dot - RunCommand(v.text.ReadDot()) + cmd := v.text.ReadDot() + if cmd == "" { // select all non-space characters + curpos := v.Cursor() + p := curpos - v.text.PrevSpace(curpos) + n := curpos + v.text.NextSpace(curpos) + v.text.SetDot(p, n) + cmd = strings.Trim(v.text.ReadDot(), "\n\t ") + v.SetCursor(curpos, io.SeekStart) + if cmd == "" { // if it is still blank, abort + return + } + } + RunCommand(cmd) return case tcell.KeyCtrlC: // copy to clipboard if err := clipboard.WriteAll(v.text.ReadDot()); err != nil { @@ -68,7 +68,7 @@ func NewWindow(fn string) *Window { file: &File{name: fnabs}, } - fmt.Fprintf(win.tagline, "⌘ %s%s Del", + fmt.Fprintf(win.tagline, "%s%s Del ", win.Flags(), win.Name(), ) @@ -171,6 +171,10 @@ func (win *Window) SaveFile() (int, error) { return 0, nil } + if win.isdir { + return 0, nil + } + f, err := os.OpenFile(win.NameAbs(), os.O_RDWR|os.O_CREATE, 0644) if err != nil { return 0, err @@ -221,15 +225,29 @@ func (win *Window) Name() string { } func (win *Window) NameAbs() string { + if win.file.name == FnEmptyWin { + return "" + } s, _ := filepath.Abs(win.file.name) return s } +func (win *Window) NameFromTag() string { + tstr := win.tagline.text.String() + if tstr == "" { + return "" + } + return strings.Split(tstr, " ")[0] +} + // Dir returns the working directory of current window, without trailing path separator. func (win *Window) Dir() string { if win.isdir { return win.NameAbs() } + if win.Name() == FnEmptyWin { + return baseDir + } return filepath.Dir(win.NameAbs()) } |