mirror of https://github.com/liamg/aminal.git
add hinting
This commit is contained in:
parent
2907bb69eb
commit
1c96cb83d5
|
@ -88,12 +88,12 @@
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/image"
|
name = "golang.org/x/image"
|
||||||
packages = ["font","math/fixed"]
|
packages = ["bmp","font","math/fixed"]
|
||||||
revision = "991ec62608f3c0da01d400756917825d1e2fd528"
|
revision = "991ec62608f3c0da01d400756917825d1e2fd528"
|
||||||
|
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "62428a3f67dc7a8a89ecb5666b6d8a6641788f89c61d0b0b4dd99982923bb0c6"
|
inputs-digest = "6f1eff05a1c5ca969d1f54eb8002e3213fda92b99a459e6729098b2497de5b8c"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
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.
|
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
|
## Keyboard Shortcuts
|
||||||
|
|
||||||
| Operation | Key(s) |
|
| Operation | Key(s) |
|
||||||
| ------------------ | ---------------- |
|
| --------------------- | -------------------- |
|
||||||
| Copy | ctrl + shift + c |
|
| Select text | click + drag |
|
||||||
| Paste | ctrl + shift + v |
|
| Select word | double click |
|
||||||
| Toggle slomo | ctrl + shift + ; |
|
| Select line | triple click |
|
||||||
| Interrupt (SIGINT) | ctrl + c |
|
| 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
|
## 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.
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -18,29 +19,32 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type GUI struct {
|
type GUI struct {
|
||||||
window *glfw.Window
|
window *glfw.Window
|
||||||
logger *zap.SugaredLogger
|
logger *zap.SugaredLogger
|
||||||
config *config.Config
|
config *config.Config
|
||||||
terminal *terminal.Terminal
|
terminal *terminal.Terminal
|
||||||
width int //window width in pixels
|
width int //window width in pixels
|
||||||
height int //window height in pixels
|
height int //window height in pixels
|
||||||
font *glfont.Font
|
font *glfont.Font
|
||||||
boldFont *glfont.Font
|
boldFont *glfont.Font
|
||||||
fontScale float32
|
fontScale float32
|
||||||
renderer *OpenGLRenderer
|
renderer *OpenGLRenderer
|
||||||
colourAttr uint32
|
colourAttr uint32
|
||||||
mouseDown bool
|
mouseDown bool
|
||||||
|
overlay overlay
|
||||||
|
terminalAlpha float32
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(config *config.Config, terminal *terminal.Terminal, logger *zap.SugaredLogger) *GUI {
|
func New(config *config.Config, terminal *terminal.Terminal, logger *zap.SugaredLogger) *GUI {
|
||||||
|
|
||||||
return &GUI{
|
return &GUI{
|
||||||
config: config,
|
config: config,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 600,
|
height: 600,
|
||||||
terminal: terminal,
|
terminal: terminal,
|
||||||
fontScale: 14.0,
|
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) {
|
func (gui *GUI) getTermSize() (uint, uint) {
|
||||||
if gui.renderer == nil {
|
if gui.renderer == nil {
|
||||||
return 0, 0
|
return 0, 0
|
||||||
|
@ -242,9 +238,9 @@ func (gui *GUI) Render() error {
|
||||||
colour = &gui.config.ColourScheme.Selection
|
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 {
|
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))
|
gui.renderer.DrawCellImage(cell, uint(x), uint(y))
|
||||||
|
@ -252,6 +248,8 @@ func (gui *GUI) Render() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gui.renderOverlay()
|
||||||
|
|
||||||
gui.window.SwapBuffers()
|
gui.window.SwapBuffers()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -340,3 +338,19 @@ func (gui *GUI) createProgram() (uint32, error) {
|
||||||
|
|
||||||
return prog, nil
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/go-gl/glfw/v3.2/glfw"
|
"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) {
|
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 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)
|
gui.logger.Debugf("KEY PRESS: key=0x%X scan=0x%X", key, scancode)
|
||||||
|
|
||||||
modStr := ""
|
modStr := ""
|
||||||
|
@ -39,6 +47,13 @@ func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Acti
|
||||||
case glfw.KeyC:
|
case glfw.KeyC:
|
||||||
gui.window.SetClipboardString(gui.terminal.ActiveBuffer().GetSelectedText())
|
gui.window.SetClipboardString(gui.terminal.ActiveBuffer().GetSelectedText())
|
||||||
return
|
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:
|
case glfw.KeyV:
|
||||||
if s, err := gui.window.GetClipboardString(); err == nil {
|
if s, err := gui.window.GetClipboardString(); err == nil {
|
||||||
_ = gui.terminal.Paste([]byte(s))
|
_ = gui.terminal.Paste([]byte(s))
|
||||||
|
|
65
gui/mouse.go
65
gui/mouse.go
|
@ -3,20 +3,39 @@ package gui
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/go-gl/glfw/v3.2/glfw"
|
"github.com/go-gl/glfw/v3.2/glfw"
|
||||||
"github.com/liamg/aminal/terminal"
|
"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) {
|
func (gui *GUI) mouseMoveCallback(w *glfw.Window, xpos float64, ypos float64) {
|
||||||
|
|
||||||
px, py := w.GetCursorPos()
|
px, py := w.GetCursorPos()
|
||||||
x := uint16(math.Floor((px - float64(gui.renderer.areaX)) / float64(gui.renderer.CellWidth())))
|
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())))
|
y := uint16(math.Floor((py - float64(gui.renderer.areaY)) / float64(gui.renderer.CellHeight())))
|
||||||
|
|
||||||
if gui.mouseDown {
|
if gui.mouseDown {
|
||||||
gui.terminal.ActiveBuffer().EndSelection(x, y, false)
|
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 != "" {
|
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) {
|
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.
|
// before we forward clicks on (below), we need to handle them locally for url clicking, text highlighting etc.
|
||||||
px, py := w.GetCursorPos()
|
px, py := w.GetCursorPos()
|
||||||
x := uint16(math.Floor((px - float64(gui.renderer.areaX)) / float64(gui.renderer.CellWidth())))
|
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
|
tx := int(x) + 1 // vt100 is 1 indexed
|
||||||
ty := int(y) + 1
|
ty := int(y) + 1
|
||||||
|
|
||||||
if action == glfw.Press {
|
if button == glfw.MouseButtonLeft {
|
||||||
gui.mouseDown = true
|
|
||||||
gui.terminal.ActiveBuffer().StartSelection(x, y)
|
if action == glfw.Press {
|
||||||
} else if action == glfw.Release {
|
gui.mouseDown = true
|
||||||
gui.mouseDown = false
|
gui.terminal.ActiveBuffer().StartSelection(x, y)
|
||||||
gui.terminal.ActiveBuffer().EndSelection(x, y, true)
|
} else if action == glfw.Release {
|
||||||
if url := gui.terminal.ActiveBuffer().GetURLAtPosition(x, y); url != "" {
|
gui.mouseDown = false
|
||||||
go gui.launchTarget(url)
|
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
|
// 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()
|
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
|
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 := r.getRectangle(col, row)
|
||||||
rect.setColour(bg)
|
rect.setColour(bg)
|
||||||
rect.Draw()
|
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
|
var fg [3]float32
|
||||||
|
|
||||||
if colour != nil {
|
if colour != nil {
|
||||||
fg = *colour
|
fg = *colour
|
||||||
|
} else if cell.Attr().Reverse {
|
||||||
|
fg = cell.Bg()
|
||||||
} else {
|
} else {
|
||||||
if cell.Attr().Reverse {
|
fg = cell.Fg()
|
||||||
fg = cell.Bg()
|
|
||||||
} else {
|
|
||||||
fg = cell.Fg()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var alpha float32 = 1
|
|
||||||
if cell.Attr().Dim {
|
if cell.Attr().Dim {
|
||||||
alpha = 0.5
|
alpha = 0.5 * alpha
|
||||||
}
|
}
|
||||||
r.font.SetColor(fg[0], fg[1], fg[2], 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 (
|
const (
|
||||||
MainBuffer uint8 = 0
|
MainBuffer uint8 = 0
|
||||||
AltBuffer uint8 = 1
|
AltBuffer uint8 = 1
|
||||||
|
InternalBuffer uint8 = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
type MouseMode uint
|
type MouseMode uint
|
||||||
|
@ -50,6 +51,7 @@ type Terminal struct {
|
||||||
isDirty bool
|
isDirty bool
|
||||||
charWidth float32
|
charWidth float32
|
||||||
charHeight float32
|
charHeight float32
|
||||||
|
lastBuffer uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
type Modes struct {
|
type Modes struct {
|
||||||
|
@ -77,6 +79,10 @@ func New(pty *os.File, logger *zap.SugaredLogger, config *config.Config) *Termin
|
||||||
FgColour: config.ColourScheme.Foreground,
|
FgColour: config.ColourScheme.Foreground,
|
||||||
BgColour: config.ColourScheme.Background,
|
BgColour: config.ColourScheme.Background,
|
||||||
}),
|
}),
|
||||||
|
buffer.NewBuffer(1, 1, buffer.CellAttributes{
|
||||||
|
FgColour: config.ColourScheme.Foreground,
|
||||||
|
BgColour: config.ColourScheme.Background,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
pty: pty,
|
pty: pty,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
@ -131,16 +137,30 @@ func (terminal *Terminal) UseAltBuffer() {
|
||||||
terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height))
|
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 {
|
func (terminal *Terminal) ActiveBuffer() *buffer.Buffer {
|
||||||
return terminal.buffers[terminal.activeBufferIndex]
|
return terminal.buffers[terminal.activeBufferIndex]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (terminal *Terminal) UsingMainBuffer() bool {
|
||||||
|
return terminal.activeBufferIndex == MainBuffer
|
||||||
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) GetScrollOffset() uint {
|
func (terminal *Terminal) GetScrollOffset() uint {
|
||||||
return terminal.ActiveBuffer().GetScrollOffset()
|
return terminal.ActiveBuffer().GetScrollOffset()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) ScrollDown(lines uint16) {
|
func (terminal *Terminal) ScrollDown(lines uint16) {
|
||||||
terminal.logger.Infof("Scrolling down %d", lines)
|
|
||||||
terminal.ActiveBuffer().ScrollDown(lines)
|
terminal.ActiveBuffer().ScrollDown(lines)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -151,7 +171,6 @@ func (terminal *Terminal) SetCharSize(w float32, h float32) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) ScrollUp(lines uint16) {
|
func (terminal *Terminal) ScrollUp(lines uint16) {
|
||||||
terminal.logger.Infof("Scrolling up %d", lines)
|
|
||||||
terminal.ActiveBuffer().ScrollUp(lines)
|
terminal.ActiveBuffer().ScrollUp(lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue