aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPetter Rodhelind <petter.rodhelind@gmail.com>2018-02-24 23:43:22 +0100
committerPetter Rodhelind <petter.rodhelind@gmail.com>2018-02-24 23:43:22 +0100
commit1c1469d5522c7c7d7c03232d9c2711c5d96d3c18 (patch)
treed2bd4e1b17a4ab1cb496ae05be42daffcd4d1305
parent9f351fc3691f30ae5b3c195ef186470987ac78f9 (diff)
downloadpoe-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.go12
-rw-r--r--style.go12
-rw-r--r--text.go9
-rw-r--r--todo13
-rw-r--r--view.go176
-rw-r--r--window.go20
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")
diff --git a/style.go b/style.go
index 57debbf..eaff897 100644
--- a/style.go
+++ b/style.go
@@ -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
}
diff --git a/text.go b/text.go
index 5b50be9..b9398ed 100644
--- a/text.go
+++ b/text.go
@@ -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)
diff --git a/todo b/todo
index b79f2f5..bffdcd4 100644
--- a/todo
+++ b/todo
@@ -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
diff --git a/view.go b/view.go
index d0e6bba..7bf1860 100644
--- a/view.go
+++ b/view.go
@@ -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 {
diff --git a/window.go b/window.go
index c3a5c72..9560ec6 100644
--- a/window.go
+++ b/window.go
@@ -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())
}