mirror of https://github.com/liamg/aminal.git
add hinting
This commit is contained in:
parent
2907bb69eb
commit
1c96cb83d5
|
@ -88,12 +88,12 @@
|
|||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/image"
|
||||
packages = ["font","math/fixed"]
|
||||
packages = ["bmp","font","math/fixed"]
|
||||
revision = "991ec62608f3c0da01d400756917825d1e2fd528"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "62428a3f67dc7a8a89ecb5666b6d8a6641788f89c61d0b0b4dd99982923bb0c6"
|
||||
inputs-digest = "6f1eff05a1c5ca969d1f54eb8002e3213fda92b99a459e6729098b2497de5b8c"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
25
README.md
25
README.md
|
@ -10,7 +10,11 @@ The project is experimental at the moment, so you probably won't want to rely on
|
|||
|
||||
Ensure you have your latest graphics card drivers installed before use.
|
||||
|
||||
Sixels are now supported.
|
||||
## Contextual Hints
|
||||
|
||||

|
||||
|
||||
## Sixel Support
|
||||
|
||||

|
||||
|
||||
|
@ -55,13 +59,18 @@ Sixels are now supported.
|
|||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
| Operation | Key(s) |
|
||||
| ------------------ | ---------------- |
|
||||
| Copy | ctrl + shift + c |
|
||||
| Paste | ctrl + shift + v |
|
||||
| Toggle slomo | ctrl + shift + ; |
|
||||
| Interrupt (SIGINT) | ctrl + c |
|
||||
|
||||
| Operation | Key(s) |
|
||||
| --------------------- | -------------------- |
|
||||
| Select text | click + drag |
|
||||
| Select word | double click |
|
||||
| Select line | triple click |
|
||||
| Copy | ctrl + shift + c |
|
||||
| Paste | ctrl + shift + v |
|
||||
| Google selected text | ctrl + shift + g |
|
||||
| Report bug in aminal | ctrl + shift + r |
|
||||
| Explain text | ctrl + shift + click |
|
||||
| Toggle slomo | ctrl + shift + ; |
|
||||
|
||||
## Configuration
|
||||
|
||||
Aminal looks for a config file in `~/.aminal.toml`, and will write one there the first time it runs, if it doesn't already exist.
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
package buffer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/liamg/aminal/hints"
|
||||
)
|
||||
|
||||
func (buffer *Buffer) GetHintAtPosition(col uint16, row uint16) *hints.Hint {
|
||||
|
||||
cell := buffer.GetCell(col, row)
|
||||
if cell == nil || cell.Rune() == 0x00 {
|
||||
return nil
|
||||
}
|
||||
|
||||
candidate := ""
|
||||
|
||||
for i := col; i >= 0; i-- {
|
||||
cell := buffer.GetCell(i, row)
|
||||
if cell == nil {
|
||||
break
|
||||
}
|
||||
if isRuneWordSelectionMarker(cell.Rune()) {
|
||||
break
|
||||
}
|
||||
candidate = fmt.Sprintf("%c%s", cell.Rune(), candidate)
|
||||
}
|
||||
|
||||
trimmed := strings.TrimLeft(candidate, " ")
|
||||
sx := col - uint16(len(trimmed)-1)
|
||||
|
||||
for i := col + 1; i < buffer.viewWidth; i++ {
|
||||
cell := buffer.GetCell(i, row)
|
||||
if cell == nil {
|
||||
break
|
||||
}
|
||||
if isRuneWordSelectionMarker(cell.Rune()) {
|
||||
break
|
||||
}
|
||||
|
||||
candidate = fmt.Sprintf("%s%c", candidate, cell.Rune())
|
||||
}
|
||||
|
||||
line := buffer.lines[buffer.convertViewLineToRawLine(row)]
|
||||
|
||||
return hints.Get(strings.Trim(candidate, " "), line.String(), sx, row)
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package gui
|
||||
|
||||
import (
|
||||
"github.com/go-gl/gl/all-core/gl"
|
||||
"github.com/liamg/aminal/hints"
|
||||
)
|
||||
|
||||
type annotation struct {
|
||||
hint *hints.Hint
|
||||
}
|
||||
|
||||
func newAnnotation(it *hints.Hint) *annotation {
|
||||
return &annotation{
|
||||
hint: it,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *annotation) render(gui *GUI) {
|
||||
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
|
||||
|
||||
lines := gui.terminal.GetVisibleLines()
|
||||
for y := 0; y < len(lines); y++ {
|
||||
cells := lines[y].Cells()
|
||||
for x := 0; x < len(cells); x++ {
|
||||
if int(x) >= len(cells) {
|
||||
break
|
||||
}
|
||||
cell := cells[x]
|
||||
|
||||
var colour *[3]float32
|
||||
var alpha float32 = 0.6
|
||||
|
||||
if y == int(a.hint.StartY) {
|
||||
if x >= int(a.hint.StartX) && x <= int(a.hint.StartX+uint16(len(a.hint.Word))) {
|
||||
colour = &[3]float32{0.2, 1.0, 0.2}
|
||||
alpha = 1.0
|
||||
}
|
||||
}
|
||||
gui.renderer.DrawCellText(cell, uint(x), uint(y), alpha, colour)
|
||||
}
|
||||
}
|
||||
|
||||
gui.textbox(a.hint.StartX+1, a.hint.StartY+3, a.hint.Description)
|
||||
|
||||
}
|
70
gui/gui.go
70
gui/gui.go
|
@ -3,6 +3,7 @@ package gui
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
|
@ -18,29 +19,32 @@ import (
|
|||
)
|
||||
|
||||
type GUI struct {
|
||||
window *glfw.Window
|
||||
logger *zap.SugaredLogger
|
||||
config *config.Config
|
||||
terminal *terminal.Terminal
|
||||
width int //window width in pixels
|
||||
height int //window height in pixels
|
||||
font *glfont.Font
|
||||
boldFont *glfont.Font
|
||||
fontScale float32
|
||||
renderer *OpenGLRenderer
|
||||
colourAttr uint32
|
||||
mouseDown bool
|
||||
window *glfw.Window
|
||||
logger *zap.SugaredLogger
|
||||
config *config.Config
|
||||
terminal *terminal.Terminal
|
||||
width int //window width in pixels
|
||||
height int //window height in pixels
|
||||
font *glfont.Font
|
||||
boldFont *glfont.Font
|
||||
fontScale float32
|
||||
renderer *OpenGLRenderer
|
||||
colourAttr uint32
|
||||
mouseDown bool
|
||||
overlay overlay
|
||||
terminalAlpha float32
|
||||
}
|
||||
|
||||
func New(config *config.Config, terminal *terminal.Terminal, logger *zap.SugaredLogger) *GUI {
|
||||
|
||||
return &GUI{
|
||||
config: config,
|
||||
logger: logger,
|
||||
width: 800,
|
||||
height: 600,
|
||||
terminal: terminal,
|
||||
fontScale: 14.0,
|
||||
config: config,
|
||||
logger: logger,
|
||||
width: 800,
|
||||
height: 600,
|
||||
terminal: terminal,
|
||||
fontScale: 14.0,
|
||||
terminalAlpha: 1,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,14 +91,6 @@ func (gui *GUI) resize(w *glfw.Window, width int, height int) {
|
|||
|
||||
}
|
||||
|
||||
func (gui *GUI) glfwScrollCallback(w *glfw.Window, xoff float64, yoff float64) {
|
||||
if yoff > 0 {
|
||||
gui.terminal.ScrollUp(1)
|
||||
} else {
|
||||
gui.terminal.ScrollDown(1)
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *GUI) getTermSize() (uint, uint) {
|
||||
if gui.renderer == nil {
|
||||
return 0, 0
|
||||
|
@ -242,9 +238,9 @@ func (gui *GUI) Render() error {
|
|||
colour = &gui.config.ColourScheme.Selection
|
||||
}
|
||||
|
||||
gui.renderer.DrawCellBg(cell, uint(x), uint(y), cursor, colour)
|
||||
gui.renderer.DrawCellBg(cell, uint(x), uint(y), cursor, colour, false)
|
||||
if hasText {
|
||||
gui.renderer.DrawCellText(cell, uint(x), uint(y), nil)
|
||||
gui.renderer.DrawCellText(cell, uint(x), uint(y), 1.0, nil)
|
||||
}
|
||||
|
||||
gui.renderer.DrawCellImage(cell, uint(x), uint(y))
|
||||
|
@ -252,6 +248,8 @@ func (gui *GUI) Render() error {
|
|||
}
|
||||
}
|
||||
|
||||
gui.renderOverlay()
|
||||
|
||||
gui.window.SwapBuffers()
|
||||
|
||||
}
|
||||
|
@ -340,3 +338,19 @@ func (gui *GUI) createProgram() (uint32, error) {
|
|||
|
||||
return prog, nil
|
||||
}
|
||||
|
||||
func (gui *GUI) launchTarget(target string) {
|
||||
|
||||
cmd := "xdg-open"
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
cmd = "open"
|
||||
case "windows":
|
||||
cmd = "start"
|
||||
}
|
||||
|
||||
if err := exec.Command(cmd, target).Run(); err != nil {
|
||||
gui.logger.Errorf("Failed to launch external command %s: %s", cmd, err)
|
||||
}
|
||||
}
|
||||
|
|
15
gui/input.go
15
gui/input.go
|
@ -2,6 +2,7 @@ package gui
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-gl/glfw/v3.2/glfw"
|
||||
)
|
||||
|
@ -22,8 +23,15 @@ func modsPressed(pressed glfw.ModifierKey, mods ...glfw.ModifierKey) bool {
|
|||
}
|
||||
|
||||
func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
|
||||
|
||||
if action == glfw.Repeat || action == glfw.Press {
|
||||
|
||||
if gui.overlay != nil {
|
||||
if key == glfw.KeyEscape {
|
||||
gui.setOverlay(nil)
|
||||
}
|
||||
}
|
||||
|
||||
gui.logger.Debugf("KEY PRESS: key=0x%X scan=0x%X", key, scancode)
|
||||
|
||||
modStr := ""
|
||||
|
@ -39,6 +47,13 @@ func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Acti
|
|||
case glfw.KeyC:
|
||||
gui.window.SetClipboardString(gui.terminal.ActiveBuffer().GetSelectedText())
|
||||
return
|
||||
case glfw.KeyG:
|
||||
keywords := gui.terminal.ActiveBuffer().GetSelectedText()
|
||||
if keywords != "" {
|
||||
gui.launchTarget(fmt.Sprintf("https://www.google.com/search?q=%s", url.QueryEscape(keywords)))
|
||||
}
|
||||
case glfw.KeyR:
|
||||
gui.launchTarget("https://github.com/liamg/aminal/issues/new/choose")
|
||||
case glfw.KeyV:
|
||||
if s, err := gui.window.GetClipboardString(); err == nil {
|
||||
_ = gui.terminal.Paste([]byte(s))
|
||||
|
|
65
gui/mouse.go
65
gui/mouse.go
|
@ -3,20 +3,39 @@ package gui
|
|||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
"github.com/go-gl/glfw/v3.2/glfw"
|
||||
"github.com/liamg/aminal/terminal"
|
||||
)
|
||||
|
||||
func (gui *GUI) glfwScrollCallback(w *glfw.Window, xoff float64, yoff float64) {
|
||||
|
||||
if yoff > 0 {
|
||||
gui.terminal.ScrollUp(1)
|
||||
} else {
|
||||
gui.terminal.ScrollDown(1)
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *GUI) mouseMoveCallback(w *glfw.Window, xpos float64, ypos float64) {
|
||||
|
||||
px, py := w.GetCursorPos()
|
||||
x := uint16(math.Floor((px - float64(gui.renderer.areaX)) / float64(gui.renderer.CellWidth())))
|
||||
y := uint16(math.Floor((py - float64(gui.renderer.areaY)) / float64(gui.renderer.CellHeight())))
|
||||
|
||||
if gui.mouseDown {
|
||||
gui.terminal.ActiveBuffer().EndSelection(x, y, false)
|
||||
} else {
|
||||
|
||||
if gui.terminal.UsingMainBuffer() {
|
||||
hint := gui.terminal.ActiveBuffer().GetHintAtPosition(x, y)
|
||||
if hint != nil {
|
||||
gui.setOverlay(newAnnotation(hint))
|
||||
} else {
|
||||
gui.setOverlay(nil)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if url := gui.terminal.ActiveBuffer().GetURLAtPosition(x, y); url != "" {
|
||||
|
@ -26,24 +45,15 @@ func (gui *GUI) mouseMoveCallback(w *glfw.Window, xpos float64, ypos float64) {
|
|||
}
|
||||
}
|
||||
|
||||
func (gui *GUI) launchTarget(target string) {
|
||||
|
||||
cmd := "xdg-open"
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
cmd = "open"
|
||||
case "windows":
|
||||
cmd = "start"
|
||||
}
|
||||
|
||||
if err := exec.Command(cmd, target).Run(); err != nil {
|
||||
gui.logger.Errorf("Failed to launch external command %s: %s", cmd, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *GUI) mouseButtonCallback(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mod glfw.ModifierKey) {
|
||||
|
||||
if gui.overlay != nil {
|
||||
if button == glfw.MouseButtonRight && action == glfw.Release {
|
||||
gui.setOverlay(nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// before we forward clicks on (below), we need to handle them locally for url clicking, text highlighting etc.
|
||||
px, py := w.GetCursorPos()
|
||||
x := uint16(math.Floor((px - float64(gui.renderer.areaX)) / float64(gui.renderer.CellWidth())))
|
||||
|
@ -51,14 +61,17 @@ func (gui *GUI) mouseButtonCallback(w *glfw.Window, button glfw.MouseButton, act
|
|||
tx := int(x) + 1 // vt100 is 1 indexed
|
||||
ty := int(y) + 1
|
||||
|
||||
if action == glfw.Press {
|
||||
gui.mouseDown = true
|
||||
gui.terminal.ActiveBuffer().StartSelection(x, y)
|
||||
} else if action == glfw.Release {
|
||||
gui.mouseDown = false
|
||||
gui.terminal.ActiveBuffer().EndSelection(x, y, true)
|
||||
if url := gui.terminal.ActiveBuffer().GetURLAtPosition(x, y); url != "" {
|
||||
go gui.launchTarget(url)
|
||||
if button == glfw.MouseButtonLeft {
|
||||
|
||||
if action == glfw.Press {
|
||||
gui.mouseDown = true
|
||||
gui.terminal.ActiveBuffer().StartSelection(x, y)
|
||||
} else if action == glfw.Release {
|
||||
gui.mouseDown = false
|
||||
gui.terminal.ActiveBuffer().EndSelection(x, y, true)
|
||||
if url := gui.terminal.ActiveBuffer().GetURLAtPosition(x, y); url != "" {
|
||||
go gui.launchTarget(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
// https://www.xfree86.org/4.8.0/ctlseqs.html
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package gui
|
||||
|
||||
type overlay interface {
|
||||
render(gui *GUI)
|
||||
}
|
||||
|
||||
func (gui *GUI) setOverlay(m overlay) {
|
||||
defer gui.terminal.SetDirty()
|
||||
gui.overlay = m
|
||||
}
|
||||
|
||||
func (gui *GUI) renderOverlay() {
|
||||
if gui.overlay == nil || !gui.terminal.UsingMainBuffer() {
|
||||
return
|
||||
}
|
||||
|
||||
gui.overlay.render(gui)
|
||||
}
|
|
@ -195,7 +195,7 @@ func (r *OpenGLRenderer) DrawCursor(col uint, row uint, colour config.Colour) {
|
|||
rect.Draw()
|
||||
}
|
||||
|
||||
func (r *OpenGLRenderer) DrawCellBg(cell buffer.Cell, col uint, row uint, cursor bool, colour *config.Colour) {
|
||||
func (r *OpenGLRenderer) DrawCellBg(cell buffer.Cell, col uint, row uint, cursor bool, colour *config.Colour, force bool) {
|
||||
|
||||
var bg [3]float32
|
||||
|
||||
|
@ -212,7 +212,7 @@ func (r *OpenGLRenderer) DrawCellBg(cell buffer.Cell, col uint, row uint, cursor
|
|||
}
|
||||
}
|
||||
|
||||
if bg != r.config.ColourScheme.Background {
|
||||
if bg != r.config.ColourScheme.Background || force {
|
||||
rect := r.getRectangle(col, row)
|
||||
rect.setColour(bg)
|
||||
rect.Draw()
|
||||
|
@ -220,23 +220,20 @@ func (r *OpenGLRenderer) DrawCellBg(cell buffer.Cell, col uint, row uint, cursor
|
|||
|
||||
}
|
||||
|
||||
func (r *OpenGLRenderer) DrawCellText(cell buffer.Cell, col uint, row uint, colour *config.Colour) {
|
||||
func (r *OpenGLRenderer) DrawCellText(cell buffer.Cell, col uint, row uint, alpha float32, colour *[3]float32) {
|
||||
|
||||
var fg [3]float32
|
||||
|
||||
if colour != nil {
|
||||
fg = *colour
|
||||
} else if cell.Attr().Reverse {
|
||||
fg = cell.Bg()
|
||||
} else {
|
||||
if cell.Attr().Reverse {
|
||||
fg = cell.Bg()
|
||||
} else {
|
||||
fg = cell.Fg()
|
||||
}
|
||||
fg = cell.Fg()
|
||||
}
|
||||
|
||||
var alpha float32 = 1
|
||||
if cell.Attr().Dim {
|
||||
alpha = 0.5
|
||||
alpha = 0.5 * alpha
|
||||
}
|
||||
r.font.SetColor(fg[0], fg[1], fg[2], alpha)
|
||||
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/liamg/aminal/buffer"
|
||||
)
|
||||
|
||||
func (gui *GUI) textbox(col uint16, row uint16, text string) {
|
||||
|
||||
lines := []string{}
|
||||
line := ""
|
||||
word := ""
|
||||
|
||||
maxWidth := int(gui.terminal.ActiveBuffer().ViewWidth()) - 4
|
||||
maxHeight := (int(gui.terminal.ActiveBuffer().ViewHeight()) / 2) - 2
|
||||
|
||||
if maxHeight < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
longestLine := 0
|
||||
|
||||
addWord := func() {
|
||||
if len(line)+len(word) <= maxWidth {
|
||||
line = fmt.Sprintf("%s%s", line, word)
|
||||
if len(line) < maxWidth {
|
||||
line = fmt.Sprintf("%s ", line)
|
||||
} else {
|
||||
lines = append(lines, line)
|
||||
line = ""
|
||||
}
|
||||
} else {
|
||||
lines = append(lines, line)
|
||||
line = word
|
||||
for len(line) > maxWidth {
|
||||
// break word into bits
|
||||
}
|
||||
}
|
||||
|
||||
word = ""
|
||||
}
|
||||
|
||||
addLine := func() bool {
|
||||
addWord()
|
||||
if len(line) > longestLine {
|
||||
longestLine = len(line)
|
||||
}
|
||||
lines = append(lines, line)
|
||||
if len(lines) >= maxHeight-1 {
|
||||
lines = append(lines, "...")
|
||||
return true
|
||||
}
|
||||
line = ""
|
||||
return false
|
||||
}
|
||||
|
||||
var done = false
|
||||
|
||||
DONE:
|
||||
for _, c := range text {
|
||||
switch c {
|
||||
case 0x0d:
|
||||
continue
|
||||
case 0x0a:
|
||||
if done = addLine(); done {
|
||||
break DONE
|
||||
}
|
||||
case ' ':
|
||||
addWord()
|
||||
default:
|
||||
word = fmt.Sprintf("%s%c", word, c)
|
||||
}
|
||||
}
|
||||
if word != "" {
|
||||
addWord()
|
||||
}
|
||||
if line != "" && !done {
|
||||
addLine()
|
||||
}
|
||||
|
||||
for hx := col; hx < col+uint16(longestLine)+2; hx++ {
|
||||
for hy := row - 1; hy < row+uint16(len(lines))+1; hy++ {
|
||||
gui.renderer.DrawCellBg(buffer.NewBackgroundCell([3]float32{0, 0, 0}), uint(hx), uint(hy), false, nil, true)
|
||||
}
|
||||
}
|
||||
|
||||
x := (float32(col) * gui.renderer.cellWidth)
|
||||
|
||||
gui.font.SetColor(0.2, 1, 0.2, 1)
|
||||
|
||||
for i, line := range lines {
|
||||
y := (float32(row+1+uint16(i)) * gui.renderer.cellHeight) - (gui.font.LinePadding())
|
||||
gui.font.Print(x, y, fmt.Sprintf(" %s", line))
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package hints
|
||||
|
||||
type Hint struct {
|
||||
Word string
|
||||
StartX uint16
|
||||
StartY uint16
|
||||
Line string
|
||||
Description string
|
||||
}
|
||||
|
||||
type hinter func(word string, context string, wordX uint16, wordY uint16) *Hint
|
||||
|
||||
var hinters = []hinter{}
|
||||
|
||||
func Get(word string, context string, wordX uint16, wordY uint16) *Hint {
|
||||
for _, exp := range hinters {
|
||||
if h := exp(word, context, wordX, wordY); h != nil {
|
||||
return h
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
package hints
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type perms struct {
|
||||
IsDirectory bool
|
||||
Owner access
|
||||
Group access
|
||||
World access
|
||||
}
|
||||
|
||||
type access struct {
|
||||
Read bool
|
||||
Write bool
|
||||
Execute bool
|
||||
}
|
||||
|
||||
func (p perms) Numeric() string {
|
||||
return p.Owner.Numeric() + p.Group.Numeric() + p.World.Numeric()
|
||||
}
|
||||
|
||||
func (a access) Nice() string {
|
||||
all := []string{}
|
||||
if a.Read {
|
||||
all = append(all, "read")
|
||||
}
|
||||
if a.Write {
|
||||
all = append(all, "write")
|
||||
}
|
||||
if a.Execute {
|
||||
all = append(all, "execute")
|
||||
}
|
||||
|
||||
return strings.Join(all, ", ")
|
||||
}
|
||||
|
||||
func (a access) Numeric() string {
|
||||
var n uint8
|
||||
if a.Read {
|
||||
n += 4
|
||||
}
|
||||
if a.Write {
|
||||
n += 2
|
||||
}
|
||||
if a.Execute {
|
||||
n++
|
||||
}
|
||||
return fmt.Sprintf("%d", n)
|
||||
}
|
||||
|
||||
func parsePermissionString(s string) (perms, error) {
|
||||
if !isPermString(s) {
|
||||
return perms{}, fmt.Errorf("Invalid permission string")
|
||||
}
|
||||
p := perms{}
|
||||
p.IsDirectory = s[0] == 'd'
|
||||
|
||||
p.Owner.Read = s[1] == 'r'
|
||||
p.Owner.Write = s[2] == 'w'
|
||||
p.Owner.Execute = s[3] == 'x'
|
||||
p.Group.Read = s[4] == 'r'
|
||||
p.Group.Write = s[5] == 'w'
|
||||
p.Group.Execute = s[6] == 'x'
|
||||
p.World.Read = s[7] == 'r'
|
||||
p.World.Write = s[8] == 'w'
|
||||
p.World.Execute = s[9] == 'x'
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
hinters = append(hinters, hintPerms)
|
||||
}
|
||||
|
||||
func hintPerms(word string, context string, wordX uint16, wordY uint16) *Hint {
|
||||
|
||||
item := &Hint{
|
||||
Line: context,
|
||||
Word: word,
|
||||
StartX: wordX,
|
||||
StartY: wordY,
|
||||
}
|
||||
|
||||
if wordX == 0 {
|
||||
|
||||
p, err := parsePermissionString(word)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
typ := "file"
|
||||
if p.IsDirectory {
|
||||
typ = "directory"
|
||||
}
|
||||
|
||||
item.Description = fmt.Sprintf(`Permissions:
|
||||
Type: %s
|
||||
Numeric: %s
|
||||
Owner: %s
|
||||
Group: %s
|
||||
World: %s`,
|
||||
typ,
|
||||
p.Numeric(),
|
||||
p.Owner.Nice(),
|
||||
p.Group.Nice(),
|
||||
p.World.Nice(),
|
||||
)
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isPermString(s string) bool {
|
||||
re := regexp.MustCompile("[dl\\-sS]{1}[sSrwx\\-]{9}")
|
||||
return re.MatchString(s)
|
||||
}
|
|
@ -16,8 +16,9 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
MainBuffer uint8 = 0
|
||||
AltBuffer uint8 = 1
|
||||
MainBuffer uint8 = 0
|
||||
AltBuffer uint8 = 1
|
||||
InternalBuffer uint8 = 2
|
||||
)
|
||||
|
||||
type MouseMode uint
|
||||
|
@ -50,6 +51,7 @@ type Terminal struct {
|
|||
isDirty bool
|
||||
charWidth float32
|
||||
charHeight float32
|
||||
lastBuffer uint8
|
||||
}
|
||||
|
||||
type Modes struct {
|
||||
|
@ -77,6 +79,10 @@ func New(pty *os.File, logger *zap.SugaredLogger, config *config.Config) *Termin
|
|||
FgColour: config.ColourScheme.Foreground,
|
||||
BgColour: config.ColourScheme.Background,
|
||||
}),
|
||||
buffer.NewBuffer(1, 1, buffer.CellAttributes{
|
||||
FgColour: config.ColourScheme.Foreground,
|
||||
BgColour: config.ColourScheme.Background,
|
||||
}),
|
||||
},
|
||||
pty: pty,
|
||||
logger: logger,
|
||||
|
@ -131,16 +137,30 @@ func (terminal *Terminal) UseAltBuffer() {
|
|||
terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height))
|
||||
}
|
||||
|
||||
func (terminal *Terminal) UseInternalBuffer() {
|
||||
terminal.pauseChan <- true
|
||||
terminal.activeBufferIndex = InternalBuffer
|
||||
terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height))
|
||||
}
|
||||
|
||||
func (terminal *Terminal) ExitInternalBuffer() {
|
||||
terminal.activeBufferIndex = terminal.lastBuffer
|
||||
terminal.resumeChan <- true
|
||||
}
|
||||
|
||||
func (terminal *Terminal) ActiveBuffer() *buffer.Buffer {
|
||||
return terminal.buffers[terminal.activeBufferIndex]
|
||||
}
|
||||
|
||||
func (terminal *Terminal) UsingMainBuffer() bool {
|
||||
return terminal.activeBufferIndex == MainBuffer
|
||||
}
|
||||
|
||||
func (terminal *Terminal) GetScrollOffset() uint {
|
||||
return terminal.ActiveBuffer().GetScrollOffset()
|
||||
}
|
||||
|
||||
func (terminal *Terminal) ScrollDown(lines uint16) {
|
||||
terminal.logger.Infof("Scrolling down %d", lines)
|
||||
terminal.ActiveBuffer().ScrollDown(lines)
|
||||
|
||||
}
|
||||
|
@ -151,7 +171,6 @@ func (terminal *Terminal) SetCharSize(w float32, h float32) {
|
|||
}
|
||||
|
||||
func (terminal *Terminal) ScrollUp(lines uint16) {
|
||||
terminal.logger.Infof("Scrolling up %d", lines)
|
||||
terminal.ActiveBuffer().ScrollUp(lines)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue