mirror of https://github.com/liamg/aminal.git
Merge branch 'master' into vttest-changes
This commit is contained in:
commit
6931354579
|
@ -102,4 +102,5 @@ workflows:
|
|||
tags:
|
||||
only: /^v.*/
|
||||
branches:
|
||||
ignore: /.*/
|
||||
ignore: /.*/
|
||||
|
||||
|
|
|
@ -29,4 +29,4 @@ If applicable, add screenshots to help explain your problem.
|
|||
Add any other context about the problem here.
|
||||
|
||||
**Logs**
|
||||
Run aminal with the `--debug` flag, then paste the relevant deug logs here.
|
||||
Run aminal with the `--debug` flag, then paste the relevant debug logs here.
|
||||
|
|
16
Makefile
16
Makefile
|
@ -3,9 +3,8 @@ BINARY := aminal
|
|||
FONTPATH := ./gui/packed-fonts
|
||||
|
||||
.PHONY: build
|
||||
build: test install-tools
|
||||
packr -v
|
||||
go build -ldflags "-X github.com/liamg/aminal/version.Version=`git describe --tags`"
|
||||
build:
|
||||
./build.sh `git describe --tags`
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
|
@ -13,8 +12,7 @@ test:
|
|||
go vet -v
|
||||
|
||||
.PHONY: install
|
||||
install: build install-tools
|
||||
packr -v
|
||||
install: build
|
||||
go install -ldflags "-X github.com/liamg/aminal/version.Version=`git describe --tags`"
|
||||
|
||||
.PHONY: install-tools
|
||||
|
@ -22,12 +20,6 @@ install-tools:
|
|||
which dep || curl -L https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
which packr || go get -u github.com/gobuffalo/packr/packr
|
||||
|
||||
.PHONY: update-fonts
|
||||
update-fonts: install-tools
|
||||
curl -L https://github.com/ryanoasis/nerd-fonts/raw/master/patched-fonts/Hack/Regular/complete/Hack%20Regular%20Nerd%20Font%20Complete.ttf -o "${FONTPATH}/Hack Regular Nerd Font Complete.ttf"
|
||||
curl -L https://github.com/ryanoasis/nerd-fonts/raw/master/patched-fonts/Hack/Bold/complete/Hack%20Bold%20Nerd%20Font%20Complete.ttf -o "${FONTPATH}/Hack Bold Nerd Font Complete.ttf"
|
||||
packr -v
|
||||
|
||||
.PHONY: build-linux
|
||||
build-linux:
|
||||
mkdir -p bin/linux
|
||||
|
@ -40,4 +32,4 @@ build-darwin:
|
|||
|
||||
.PHONY: package-debian
|
||||
package-debian: build-linux
|
||||
./scripts/package-debian.sh "${CIRCLE_TAG}" bin/linux/${BINARY}-linux-amd64
|
||||
./scripts/package-debian.sh "${CIRCLE_TAG}" bin/linux/${BINARY}-linux-amd64
|
||||
|
|
39
README.md
39
README.md
|
@ -30,31 +30,40 @@ Ensure you have your latest graphics card drivers installed before use.
|
|||
- Built-in patched fonts for powerline
|
||||
- Retina display support
|
||||
|
||||
## Quick Start
|
||||
## Installation
|
||||
|
||||
### Installation
|
||||
### MacOS
|
||||
|
||||
#### Prebuilt Binaries
|
||||
```
|
||||
brew tap liamg/aminal
|
||||
brew install aminal
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
A Windows version of Aminal is expected in the next 1-2 months.
|
||||
|
||||
### Prebuilt Binaries
|
||||
|
||||
Prebuilt binaries are available for Linux and OSX on the [releases](https://github.com/liamg/aminal/releases) page.
|
||||
|
||||
Download the binary and `sudo cp aminal-* /usr/local/bin/aminal`.
|
||||
Download the binary and `sudo cp aminal-* /usr/local/bin/aminal && chmod +x /usr/local/bin/aminal`.
|
||||
|
||||
#### Install with Go
|
||||
### Install with Go
|
||||
|
||||
```
|
||||
go get -u github.com/liamg/aminal
|
||||
```
|
||||
|
||||
### Build
|
||||
## Build
|
||||
|
||||
#### Dependencies
|
||||
### Dependencies
|
||||
|
||||
- On macOS, you need Xcode or Command Line Tools for Xcode (`xcode-select --install`) for required headers and libraries.
|
||||
- On Ubuntu/Debian-like Linux distributions, you need `libgl1-mesa-dev xorg-dev`.
|
||||
- On CentOS/Fedora-like Linux distributions, you need `libX11-devel libXcursor-devel libXrandr-devel libXinerama-devel mesa-libGL-devel libXi-devel`.
|
||||
|
||||
#### Building Locally
|
||||
### Building Locally
|
||||
|
||||
There are various make targets available, the most obvious being:
|
||||
|
||||
|
@ -82,7 +91,13 @@ As long as you have your `GOBIN` environment variable set up properly (and in `P
|
|||
|
||||
## 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 the following places, and stops when it finds one:
|
||||
|
||||
* `$XDG_CONFIG_HOME/aminal/config.toml`
|
||||
* `$HOME/.config/aminal/config.toml`
|
||||
* `$HOME/.aminal.toml`
|
||||
|
||||
It will write a config file to whichever of those directories exists (preferring the top of the list) the first time it runs, if one doesn't already exist.
|
||||
|
||||
You can ignore the config and use defaults by specifying `--ignore-config` as a CLI flag.
|
||||
|
||||
|
@ -92,7 +107,7 @@ You can ignore the config and use defaults by specifying `--ignore-config` as a
|
|||
debug = false # Enable debug logging to stdout. Defaults to false.
|
||||
slomo = false # Enable slow motion output mode, useful for debugging shells/terminal GUI apps etc. Defaults to false.
|
||||
shell = "/bin/bash" # The shell to run for the terminal session. Defaults to the users shell.
|
||||
search_url = "https://www.google.com/search?q=$QUERY" # The search engine to use for the "search selected text" action. Defaults to google. Set this to your own search url using $QUERY as the keywords to replcae when searching.
|
||||
search_url = "https://www.google.com/search?q=$QUERY" # The search engine to use for the "search selected text" action. Defaults to google. Set this to your own search url using $QUERY as the keywords to replace when searching.
|
||||
|
||||
[colours]
|
||||
cursor = "#e8dfd6"
|
||||
|
@ -133,3 +148,7 @@ search_url = "https://www.google.com/search?q=$QUERY" # The search engine to use
|
|||
| `--slomo` | Enable slomo mode, delay the handling of each incoming byte (or escape sequence) from the pty by 100ms. Useful for debugging.
|
||||
| `--shell [shell]` | Use the specified shell program instead of the user's usual one.
|
||||
| `--version` | Show the version of aminal and exit.
|
||||
|
||||
# Contributors
|
||||
|
||||
[](https://sourcerer.io/fame/liamg/liamg/aminal/links/0)[](https://sourcerer.io/fame/liamg/liamg/aminal/links/1)[](https://sourcerer.io/fame/liamg/liamg/aminal/links/2)[](https://sourcerer.io/fame/liamg/liamg/aminal/links/3)[](https://sourcerer.io/fame/liamg/liamg/aminal/links/4)[](https://sourcerer.io/fame/liamg/liamg/aminal/links/5)[](https://sourcerer.io/fame/liamg/liamg/aminal/links/6)[](https://sourcerer.io/fame/liamg/liamg/aminal/links/7)
|
||||
|
|
|
@ -27,6 +27,7 @@ type Buffer struct {
|
|||
selectionComplete bool // whether the selected text can update or whether it is final
|
||||
selectionExpanded bool // whether the selection to word expansion has already run on this point
|
||||
selectionClickTime time.Time
|
||||
defaultCell Cell
|
||||
}
|
||||
|
||||
type Position struct {
|
||||
|
@ -37,11 +38,12 @@ type Position struct {
|
|||
// NewBuffer creates a new terminal buffer
|
||||
func NewBuffer(viewCols uint16, viewLines uint16, attr CellAttributes) *Buffer {
|
||||
b := &Buffer{
|
||||
cursorX: 0,
|
||||
cursorY: 0,
|
||||
lines: []Line{},
|
||||
cursorAttr: attr,
|
||||
autoWrap: true,
|
||||
cursorX: 0,
|
||||
cursorY: 0,
|
||||
lines: []Line{},
|
||||
cursorAttr: attr,
|
||||
autoWrap: true,
|
||||
defaultCell: Cell{attr: attr},
|
||||
}
|
||||
b.SetVerticalMargins(0, uint(viewLines-1))
|
||||
b.ResizeView(viewCols, viewLines)
|
||||
|
@ -511,8 +513,7 @@ func (buffer *Buffer) InsertBlankCharacters(count int) {
|
|||
index := int(buffer.RawLine())
|
||||
for i := 0; i < count; i++ {
|
||||
cells := buffer.lines[index].cells
|
||||
c := Cell{}
|
||||
buffer.lines[index].cells = append(cells[:buffer.cursorX], append([]Cell{c}, cells[buffer.cursorX:]...)...)
|
||||
buffer.lines[index].cells = append(cells[:buffer.cursorX], append([]Cell{buffer.defaultCell}, cells[buffer.cursorX:]...)...)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -610,20 +611,10 @@ func (buffer *Buffer) ReverseIndex() {
|
|||
func (buffer *Buffer) Write(runes ...rune) {
|
||||
|
||||
// scroll to bottom on input
|
||||
inc := true
|
||||
buffer.scrollLinesFromBottom = 0
|
||||
|
||||
for _, r := range runes {
|
||||
if r == 0x0a {
|
||||
buffer.NewLine()
|
||||
continue
|
||||
} else if r == 0x0d {
|
||||
buffer.CarriageReturn()
|
||||
continue
|
||||
} else if r == 0x9 {
|
||||
buffer.Tab()
|
||||
continue
|
||||
}
|
||||
|
||||
line := buffer.getCurrentLine()
|
||||
|
||||
if buffer.replaceMode {
|
||||
|
@ -634,7 +625,7 @@ func (buffer *Buffer) Write(runes ...rune) {
|
|||
}
|
||||
|
||||
for int(buffer.CursorColumn()) >= len(line.cells) {
|
||||
line.cells = append(line.cells, NewBackgroundCell(buffer.cursorAttr.BgColour))
|
||||
line.cells = append(line.cells, buffer.defaultCell)
|
||||
}
|
||||
line.cells[buffer.cursorX].attr = buffer.cursorAttr
|
||||
line.cells[buffer.cursorX].setRune(r)
|
||||
|
@ -665,31 +656,23 @@ func (buffer *Buffer) Write(runes ...rune) {
|
|||
} else {
|
||||
|
||||
for int(buffer.CursorColumn()) >= len(line.cells) {
|
||||
line.cells = append(line.cells, NewBackgroundCell(buffer.cursorAttr.BgColour))
|
||||
line.cells = append(line.cells, buffer.defaultCell)
|
||||
}
|
||||
|
||||
cell := &line.cells[buffer.CursorColumn()]
|
||||
cell.setRune(r)
|
||||
cell.attr = buffer.cursorAttr
|
||||
|
||||
}
|
||||
|
||||
if inc {
|
||||
buffer.incrementCursorPosition()
|
||||
}
|
||||
buffer.incrementCursorPosition()
|
||||
}
|
||||
}
|
||||
|
||||
func (buffer *Buffer) incrementCursorPosition() {
|
||||
|
||||
defer buffer.emitDisplayChange()
|
||||
|
||||
// we can increment one column past the end of the line.
|
||||
// this is effectively the beginning of the next line, except when we \r etc.
|
||||
if buffer.CursorColumn() < buffer.Width() { // if not at end of line
|
||||
|
||||
if buffer.CursorColumn() < buffer.Width() {
|
||||
buffer.cursorX++
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -708,7 +691,6 @@ func (buffer *Buffer) Backspace() {
|
|||
}
|
||||
|
||||
func (buffer *Buffer) CarriageReturn() {
|
||||
defer buffer.emitDisplayChange()
|
||||
|
||||
for {
|
||||
line := buffer.getCurrentLine()
|
||||
|
@ -726,19 +708,14 @@ func (buffer *Buffer) CarriageReturn() {
|
|||
}
|
||||
|
||||
func (buffer *Buffer) Tab() {
|
||||
defer buffer.emitDisplayChange()
|
||||
tabSize := 4
|
||||
shift := int(buffer.cursorX-1) % tabSize
|
||||
if shift == 0 {
|
||||
shift = tabSize
|
||||
}
|
||||
shift := tabSize - (int(buffer.cursorX+1) % tabSize)
|
||||
for i := 0; i < shift; i++ {
|
||||
buffer.Write(' ')
|
||||
}
|
||||
}
|
||||
|
||||
func (buffer *Buffer) NewLine() {
|
||||
defer buffer.emitDisplayChange()
|
||||
|
||||
buffer.cursorX = 0
|
||||
buffer.Index()
|
||||
|
|
|
@ -9,12 +9,49 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTabbing(t *testing.T) {
|
||||
b := NewBuffer(30, 3, CellAttributes{})
|
||||
b.Write([]rune("hello")...)
|
||||
b.Tab()
|
||||
b.Write([]rune("x")...)
|
||||
b.Tab()
|
||||
b.Write([]rune("goodbye")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hell")...)
|
||||
b.Tab()
|
||||
b.Write([]rune("xxx")...)
|
||||
b.Tab()
|
||||
b.Write([]rune("good")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
expected := `
|
||||
hello x goodbye
|
||||
hell xxx good
|
||||
`
|
||||
|
||||
lines := b.GetVisibleLines()
|
||||
strs := []string{}
|
||||
for _, l := range lines {
|
||||
strs = append(strs, l.String())
|
||||
}
|
||||
require.Equal(t, strings.TrimSpace(expected), strings.Join(strs, "\n"))
|
||||
}
|
||||
|
||||
func TestOffsets(t *testing.T) {
|
||||
b := NewBuffer(10, 3, CellAttributes{})
|
||||
b.Write([]rune("hello\r\n")...)
|
||||
b.Write([]rune("hello\r\n")...)
|
||||
b.Write([]rune("hello\r\n")...)
|
||||
b.Write([]rune("hello\r\n")...)
|
||||
b.Write([]rune("hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello")...)
|
||||
assert.Equal(t, uint16(10), b.ViewWidth())
|
||||
assert.Equal(t, uint16(10), b.Width())
|
||||
|
@ -84,14 +121,14 @@ func TestWritingNewLineAsFirstRuneOnWrappedLine(t *testing.T) {
|
|||
b.Write('a', 'b', 'c')
|
||||
assert.Equal(t, uint16(3), b.cursorX)
|
||||
assert.Equal(t, uint16(0), b.cursorY)
|
||||
b.Write(0x0a)
|
||||
b.NewLine()
|
||||
assert.Equal(t, uint16(0), b.cursorX)
|
||||
assert.Equal(t, uint16(1), b.cursorY)
|
||||
|
||||
b.Write('d', 'e', 'f')
|
||||
assert.Equal(t, uint16(3), b.cursorX)
|
||||
assert.Equal(t, uint16(1), b.cursorY)
|
||||
b.Write(0x0a)
|
||||
b.NewLine()
|
||||
|
||||
assert.Equal(t, uint16(0), b.cursorX)
|
||||
assert.Equal(t, uint16(2), b.cursorY)
|
||||
|
@ -114,11 +151,11 @@ func TestWritingNewLineAsSecondRuneOnWrappedLine(t *testing.T) {
|
|||
*/
|
||||
|
||||
b.Write('a', 'b', 'c', 'd')
|
||||
b.Write(0x0a)
|
||||
b.NewLine()
|
||||
b.Write('e', 'f')
|
||||
b.Write(0x0a)
|
||||
b.Write(0x0a)
|
||||
b.Write(0x0a)
|
||||
b.NewLine()
|
||||
b.NewLine()
|
||||
b.NewLine()
|
||||
b.Write('z')
|
||||
|
||||
assert.Equal(t, "abc", b.lines[0].String())
|
||||
|
@ -170,19 +207,45 @@ func TestMovePosition(t *testing.T) {
|
|||
func TestVisibleLines(t *testing.T) {
|
||||
|
||||
b := NewBuffer(80, 10, CellAttributes{})
|
||||
b.Write([]rune("hello 1\r\n")...)
|
||||
b.Write([]rune("hello 2\r\n")...)
|
||||
b.Write([]rune("hello 3\r\n")...)
|
||||
b.Write([]rune("hello 4\r\n")...)
|
||||
b.Write([]rune("hello 5\r\n")...)
|
||||
b.Write([]rune("hello 6\r\n")...)
|
||||
b.Write([]rune("hello 7\r\n")...)
|
||||
b.Write([]rune("hello 8\r\n")...)
|
||||
b.Write([]rune("hello 9\r\n")...)
|
||||
b.Write([]rune("hello 10\r\n")...)
|
||||
b.Write([]rune("hello 11\r\n")...)
|
||||
b.Write([]rune("hello 12\r\n")...)
|
||||
b.Write([]rune("hello 13\r\n")...)
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 2")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 3")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 4")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 5")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 6")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 7")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 8")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 9")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 10")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 11")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 12")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 13")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 14")...)
|
||||
|
||||
lines := b.GetVisibleLines()
|
||||
|
@ -194,9 +257,13 @@ func TestVisibleLines(t *testing.T) {
|
|||
|
||||
func TestClearWithoutFullView(t *testing.T) {
|
||||
b := NewBuffer(80, 10, CellAttributes{})
|
||||
b.Write([]rune("hello 1\r\n")...)
|
||||
b.Write([]rune("hello 2\r\n")...)
|
||||
b.Write([]rune("hello 3")...)
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.Clear()
|
||||
lines := b.GetVisibleLines()
|
||||
for _, line := range lines {
|
||||
|
@ -206,14 +273,28 @@ func TestClearWithoutFullView(t *testing.T) {
|
|||
|
||||
func TestClearWithFullView(t *testing.T) {
|
||||
b := NewBuffer(80, 5, CellAttributes{})
|
||||
b.Write([]rune("hello 1\r\n")...)
|
||||
b.Write([]rune("hello 2\r\n")...)
|
||||
b.Write([]rune("hello 3\r\n")...)
|
||||
b.Write([]rune("hello 4\r\n")...)
|
||||
b.Write([]rune("hello 5\r\n")...)
|
||||
b.Write([]rune("hello 6\r\n")...)
|
||||
b.Write([]rune("hello 7\r\n")...)
|
||||
b.Write([]rune("hello 8\r\n")...)
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("hello 1")...)
|
||||
b.Clear()
|
||||
lines := b.GetVisibleLines()
|
||||
for _, line := range lines {
|
||||
|
@ -241,7 +322,10 @@ func TestCarriageReturnOnFullLine(t *testing.T) {
|
|||
|
||||
func TestCarriageReturnOnFullLastLine(t *testing.T) {
|
||||
b := NewBuffer(20, 2, CellAttributes{})
|
||||
b.Write([]rune("\nabcdeabcdeabcdeabcde\rxxxxxxxxxxxxxxxxxxxx")...)
|
||||
b.NewLine()
|
||||
b.Write([]rune("abcdeabcdeabcdeabcde")...)
|
||||
b.CarriageReturn()
|
||||
b.Write([]rune("xxxxxxxxxxxxxxxxxxxx")...)
|
||||
lines := b.GetVisibleLines()
|
||||
assert.Equal(t, "", lines[0].String())
|
||||
assert.Equal(t, "xxxxxxxxxxxxxxxxxxxx", lines[1].String())
|
||||
|
@ -249,7 +333,10 @@ func TestCarriageReturnOnFullLastLine(t *testing.T) {
|
|||
|
||||
func TestCarriageReturnOnWrappedLine(t *testing.T) {
|
||||
b := NewBuffer(80, 6, CellAttributes{})
|
||||
b.Write([]rune("hello!\rsecret")...)
|
||||
b.Write([]rune("hello!")...)
|
||||
b.CarriageReturn()
|
||||
b.Write([]rune("secret")...)
|
||||
|
||||
lines := b.GetVisibleLines()
|
||||
assert.Equal(t, "secret", lines[0].String())
|
||||
}
|
||||
|
@ -257,14 +344,22 @@ func TestCarriageReturnOnWrappedLine(t *testing.T) {
|
|||
func TestCarriageReturnOnLineThatDoesntExist(t *testing.T) {
|
||||
b := NewBuffer(6, 10, CellAttributes{})
|
||||
b.cursorY = 3
|
||||
b.Write('\r')
|
||||
b.CarriageReturn()
|
||||
assert.Equal(t, uint16(0), b.cursorX)
|
||||
assert.Equal(t, uint16(3), b.cursorY)
|
||||
}
|
||||
|
||||
func TestGetCell(t *testing.T) {
|
||||
b := NewBuffer(80, 20, CellAttributes{})
|
||||
b.Write([]rune("Hello\r\nthere\r\nsomething...")...)
|
||||
b.Write([]rune("Hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
||||
b.Write([]rune("there")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
||||
b.Write([]rune("something...")...)
|
||||
cell := b.GetCell(8, 2)
|
||||
require.NotNil(t, cell)
|
||||
assert.Equal(t, 'g', cell.Rune())
|
||||
|
@ -272,7 +367,17 @@ func TestGetCell(t *testing.T) {
|
|||
|
||||
func TestGetCellWithHistory(t *testing.T) {
|
||||
b := NewBuffer(80, 2, CellAttributes{})
|
||||
b.Write([]rune("Hello\r\nthere\r\nsomething...")...)
|
||||
|
||||
b.Write([]rune("Hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
||||
b.Write([]rune("there")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
||||
b.Write([]rune("something...")...)
|
||||
|
||||
cell := b.GetCell(8, 1)
|
||||
require.NotNil(t, cell)
|
||||
assert.Equal(t, 'g', cell.Rune())
|
||||
|
@ -301,7 +406,35 @@ func TestCursorPositionQuerying(t *testing.T) {
|
|||
|
||||
func TestRawPositionQuerying(t *testing.T) {
|
||||
b := NewBuffer(80, 5, CellAttributes{})
|
||||
b.Write([]rune("a\r\na\r\na\r\na\r\na\r\na\r\na\r\na\r\na\r\na")...)
|
||||
b.Write([]rune("a")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("a")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("a")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("a")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("a")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("a")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("a")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("a")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("a")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("a")...)
|
||||
|
||||
b.cursorX = 3
|
||||
b.cursorY = 4
|
||||
assert.Equal(t, uint64(9), b.RawLine())
|
||||
|
@ -310,7 +443,10 @@ func TestRawPositionQuerying(t *testing.T) {
|
|||
// CSI 2 K
|
||||
func TestEraseLine(t *testing.T) {
|
||||
b := NewBuffer(80, 5, CellAttributes{})
|
||||
b.Write([]rune("hello, this is a test\r\nthis line should be deleted")...)
|
||||
b.Write([]rune("hello, this is a test")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("this line should be deleted")...)
|
||||
b.EraseLine()
|
||||
assert.Equal(t, "hello, this is a test", b.lines[0].String())
|
||||
assert.Equal(t, "", b.lines[1].String())
|
||||
|
@ -319,7 +455,11 @@ func TestEraseLine(t *testing.T) {
|
|||
// CSI 1 K
|
||||
func TestEraseLineToCursor(t *testing.T) {
|
||||
b := NewBuffer(80, 5, CellAttributes{})
|
||||
b.Write([]rune("hello, this is a test\r\ndeleted")...)
|
||||
b.Write([]rune("hello, this is a test")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("deleted")...)
|
||||
|
||||
b.MovePosition(-3, 0)
|
||||
b.EraseLineToCursor()
|
||||
assert.Equal(t, "hello, this is a test", b.lines[0].String())
|
||||
|
@ -329,7 +469,10 @@ func TestEraseLineToCursor(t *testing.T) {
|
|||
// CSI 0 K
|
||||
func TestEraseLineAfterCursor(t *testing.T) {
|
||||
b := NewBuffer(80, 5, CellAttributes{})
|
||||
b.Write([]rune("hello, this is a test\r\ndeleted")...)
|
||||
b.Write([]rune("hello, this is a test")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("deleted")...)
|
||||
b.MovePosition(-3, 0)
|
||||
b.EraseLineFromCursor()
|
||||
assert.Equal(t, "hello, this is a test", b.lines[0].String())
|
||||
|
@ -337,7 +480,13 @@ func TestEraseLineAfterCursor(t *testing.T) {
|
|||
}
|
||||
func TestEraseDisplay(t *testing.T) {
|
||||
b := NewBuffer(80, 5, CellAttributes{})
|
||||
b.Write([]rune("hello\r\nasdasd\r\nthing")...)
|
||||
b.Write([]rune("hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("asdasd")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("thing")...)
|
||||
b.MovePosition(2, 1)
|
||||
b.EraseDisplay()
|
||||
lines := b.GetVisibleLines()
|
||||
|
@ -347,7 +496,13 @@ func TestEraseDisplay(t *testing.T) {
|
|||
}
|
||||
func TestEraseDisplayToCursor(t *testing.T) {
|
||||
b := NewBuffer(80, 5, CellAttributes{})
|
||||
b.Write([]rune("hello\r\nasdasd\r\nthing")...)
|
||||
b.Write([]rune("hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("asdasd")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("thing")...)
|
||||
b.MovePosition(-2, 0)
|
||||
b.EraseDisplayToCursor()
|
||||
lines := b.GetVisibleLines()
|
||||
|
@ -359,7 +514,13 @@ func TestEraseDisplayToCursor(t *testing.T) {
|
|||
|
||||
func TestEraseDisplayFromCursor(t *testing.T) {
|
||||
b := NewBuffer(80, 5, CellAttributes{})
|
||||
b.Write([]rune("hello\r\nasdasd\r\nthings")...)
|
||||
b.Write([]rune("hello")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("asdasd")...)
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
b.Write([]rune("things")...)
|
||||
b.MovePosition(-3, -1)
|
||||
b.EraseDisplayFromCursor()
|
||||
lines := b.GetVisibleLines()
|
||||
|
@ -381,9 +542,12 @@ func TestHorizontalResizeView(t *testing.T) {
|
|||
b := NewBuffer(80, 10, CellAttributes{})
|
||||
|
||||
// 60 characters
|
||||
b.Write([]rune(
|
||||
`hellohellohellohellohellohellohellohellohellohellohellohello
|
||||
goodbyegoodbye`)...)
|
||||
b.Write([]rune(`hellohellohellohellohellohellohellohellohellohellohellohello`)...)
|
||||
|
||||
b.CarriageReturn()
|
||||
b.NewLine()
|
||||
|
||||
b.Write([]rune(`goodbyegoodbye`)...)
|
||||
|
||||
require.Equal(t, uint16(14), b.cursorX)
|
||||
require.Equal(t, uint16(1), b.cursorY)
|
||||
|
|
|
@ -40,10 +40,16 @@ func (cell *Cell) Rune() rune {
|
|||
}
|
||||
|
||||
func (cell *Cell) Fg() [3]float32 {
|
||||
if cell.Attr().Reverse {
|
||||
return cell.attr.BgColour
|
||||
}
|
||||
return cell.attr.FgColour
|
||||
}
|
||||
|
||||
func (cell *Cell) Bg() [3]float32 {
|
||||
if cell.Attr().Reverse {
|
||||
return cell.attr.FgColour
|
||||
}
|
||||
return cell.attr.BgColour
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -eux
|
||||
|
||||
version="$1"
|
||||
|
||||
if [[ "$version" == "" ]]; then
|
||||
version=`git describe --tags`
|
||||
fi
|
||||
|
||||
if [[ "$version" == "" ]]; then
|
||||
echo "Error: Cannot determine version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export GOPATH="/tmp/.gobuild"
|
||||
SRCDIR="${GOPATH}/src/github.com/liamg/aminal"
|
||||
|
||||
[ -d ${GOPATH} ] && rm -rf ${GOPATH}
|
||||
mkdir -p ${GOPATH}/{src,pkg,bin}
|
||||
mkdir -p ${SRCDIR}
|
||||
cp -r . ${SRCDIR}
|
||||
(
|
||||
echo ${GOPATH}
|
||||
cd ${SRCDIR}
|
||||
go build -ldflags "-X github.com/liamg/aminal/version.Version=$version"
|
||||
)
|
||||
cp ${SRCDIR}/aminal ./aminal
|
||||
rm -rf /tmp/.gobuild
|
||||
|
21
config.go
21
config.go
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/liamg/aminal/config"
|
||||
"github.com/liamg/aminal/version"
|
||||
|
@ -48,10 +49,16 @@ func loadConfigFile() *config.Config {
|
|||
return &config.DefaultConfig
|
||||
}
|
||||
|
||||
places := []string{
|
||||
fmt.Sprintf("%s/.aminal.toml", home),
|
||||
places := []string{}
|
||||
|
||||
xdgHome := os.Getenv("XDG_CONFIG_HOME")
|
||||
if xdgHome != "" {
|
||||
places = append(places, fmt.Sprintf("%s/aminal/config.toml", xdgHome))
|
||||
}
|
||||
|
||||
places = append(places, fmt.Sprintf("%s/.config/aminal/config.toml", home))
|
||||
places = append(places, fmt.Sprintf("%s/.aminal.toml", home))
|
||||
|
||||
for _, place := range places {
|
||||
if b, err := ioutil.ReadFile(place); err == nil {
|
||||
if c, err := config.Parse(b); err == nil {
|
||||
|
@ -62,10 +69,18 @@ func loadConfigFile() *config.Config {
|
|||
}
|
||||
}
|
||||
|
||||
parts := strings.Split(places[0], string(os.PathSeparator))
|
||||
path := strings.Join(parts[0:len(parts)-1], string(os.PathSeparator))
|
||||
|
||||
err := os.MkdirAll(path, 0744)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if b, err := config.DefaultConfig.Encode(); err != nil {
|
||||
fmt.Printf("Failed to encode config file: %s\n", err)
|
||||
} else {
|
||||
if err := ioutil.WriteFile(fmt.Sprintf("%s/.aminal.toml", home), b, 0644); err != nil {
|
||||
if err := ioutil.WriteFile(fmt.Sprintf("%s/config.toml", path), b, 0644); err != nil {
|
||||
fmt.Printf("Failed to encode config file: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
type KeyCombination struct {
|
||||
mods glfw.ModifierKey
|
||||
key glfw.Key
|
||||
char rune
|
||||
}
|
||||
|
||||
type KeyMod string
|
||||
|
@ -28,41 +28,11 @@ var modMap = map[KeyMod]glfw.ModifierKey{
|
|||
super: glfw.ModSuper,
|
||||
}
|
||||
|
||||
var keyMap = map[string]glfw.Key{
|
||||
"a": glfw.KeyA,
|
||||
"b": glfw.KeyB,
|
||||
"c": glfw.KeyC,
|
||||
"d": glfw.KeyD,
|
||||
"e": glfw.KeyE,
|
||||
"f": glfw.KeyF,
|
||||
"g": glfw.KeyG,
|
||||
"h": glfw.KeyH,
|
||||
"i": glfw.KeyI,
|
||||
"j": glfw.KeyJ,
|
||||
"k": glfw.KeyK,
|
||||
"l": glfw.KeyL,
|
||||
"m": glfw.KeyM,
|
||||
"n": glfw.KeyN,
|
||||
"o": glfw.KeyO,
|
||||
"p": glfw.KeyP,
|
||||
"q": glfw.KeyQ,
|
||||
"r": glfw.KeyR,
|
||||
"s": glfw.KeyS,
|
||||
"t": glfw.KeyT,
|
||||
"u": glfw.KeyU,
|
||||
"v": glfw.KeyV,
|
||||
"w": glfw.KeyW,
|
||||
"x": glfw.KeyX,
|
||||
"y": glfw.KeyY,
|
||||
"z": glfw.KeyZ,
|
||||
";": glfw.KeySemicolon,
|
||||
}
|
||||
|
||||
// keyStr e.g. "ctrl + alt + a"
|
||||
func parseKeyCombination(keyStr string) (*KeyCombination, error) {
|
||||
|
||||
var mods glfw.ModifierKey
|
||||
var key *glfw.Key
|
||||
var key rune
|
||||
|
||||
keys := strings.Split(keyStr, "+")
|
||||
for _, k := range keys {
|
||||
|
@ -72,19 +42,15 @@ func parseKeyCombination(keyStr string) (*KeyCombination, error) {
|
|||
mods = mods + mod
|
||||
continue
|
||||
}
|
||||
mappedKey, ok := keyMap[k]
|
||||
if ok {
|
||||
if key != nil {
|
||||
return nil, fmt.Errorf("Multiple non-modifier keys specified in keyboard shortcut")
|
||||
}
|
||||
key = &mappedKey
|
||||
continue
|
||||
|
||||
if key > 0 {
|
||||
return nil, fmt.Errorf("Multiple non-modifier keys specified in keyboard shortcut")
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Unknown key '%s' in configured keyboard shortcut", k)
|
||||
key = rune(k[0])
|
||||
}
|
||||
|
||||
if key == nil {
|
||||
if key == 0 {
|
||||
return nil, fmt.Errorf("No non-modifier key specified in keyboard shortcut")
|
||||
}
|
||||
|
||||
|
@ -94,12 +60,12 @@ func parseKeyCombination(keyStr string) (*KeyCombination, error) {
|
|||
|
||||
return &KeyCombination{
|
||||
mods: mods,
|
||||
key: *key,
|
||||
char: key,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (combi KeyCombination) Match(pressedMods glfw.ModifierKey, pressedKey glfw.Key) bool {
|
||||
return pressedKey == combi.key && pressedMods == combi.mods
|
||||
func (combi KeyCombination) Match(pressedMods glfw.ModifierKey, pressedChar rune) bool {
|
||||
return pressedChar == combi.char && pressedMods == combi.mods
|
||||
}
|
||||
|
||||
func (keyMapConfig KeyMappingConfig) GenerateActionMap() (map[UserAction]*KeyCombination, error) {
|
||||
|
|
|
@ -14,14 +14,14 @@ func TestKeyCombinations(t *testing.T) {
|
|||
require.Nil(t, err)
|
||||
require.NotNil(t, combi)
|
||||
|
||||
assert.Equal(t, glfw.KeyA, combi.key)
|
||||
assert.Equal(t, 'a', combi.char)
|
||||
assert.Equal(t, glfw.ModControl+glfw.ModAlt, combi.mods)
|
||||
|
||||
assert.True(t, combi.Match(glfw.ModControl^glfw.ModAlt, glfw.KeyA))
|
||||
assert.False(t, combi.Match(glfw.ModControl^glfw.ModAlt, glfw.KeyB))
|
||||
assert.False(t, combi.Match(glfw.ModControl, glfw.KeyA))
|
||||
assert.False(t, combi.Match(glfw.ModAlt, glfw.KeyA))
|
||||
assert.False(t, combi.Match(0, glfw.KeyA))
|
||||
assert.False(t, combi.Match(glfw.ModControl^glfw.ModAlt^glfw.ModShift, glfw.KeyA))
|
||||
assert.True(t, combi.Match(glfw.ModControl^glfw.ModAlt, 'a'))
|
||||
assert.False(t, combi.Match(glfw.ModControl^glfw.ModAlt, 'b'))
|
||||
assert.False(t, combi.Match(glfw.ModControl, 'b'))
|
||||
assert.False(t, combi.Match(glfw.ModAlt, 'd'))
|
||||
assert.False(t, combi.Match(0, 'e'))
|
||||
assert.False(t, combi.Match(glfw.ModControl^glfw.ModAlt^glfw.ModShift, 'f'))
|
||||
|
||||
}
|
||||
|
|
|
@ -2,14 +2,15 @@ package glfont
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/draw"
|
||||
"io"
|
||||
|
||||
"github.com/go-gl/gl/all-core/gl"
|
||||
"github.com/golang/freetype"
|
||||
"github.com/golang/freetype/truetype"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/math/fixed"
|
||||
"image"
|
||||
"image/draw"
|
||||
"io"
|
||||
)
|
||||
|
||||
const DPI = 72
|
||||
|
@ -198,18 +199,18 @@ func (f *Font) Size(text string) (float32, float32) {
|
|||
return width, height
|
||||
}
|
||||
|
||||
func(f *Font) MaxSize() (float32, float32){
|
||||
b:= f.ttf.Bounds(fixed.Int26_6(f.scale))
|
||||
return float32(b.Max.X - b.Min.X),float32(b.Max.Y - b.Min.Y)
|
||||
func (f *Font) MaxSize() (float32, float32) {
|
||||
b := f.ttf.Bounds(fixed.Int26_6(f.scale))
|
||||
return float32(b.Max.X - b.Min.X), float32(b.Max.Y - b.Min.Y)
|
||||
}
|
||||
|
||||
func(f *Font) MinY() float32 {
|
||||
b:= f.ttf.Bounds(fixed.Int26_6(f.scale))
|
||||
func (f *Font) MinY() float32 {
|
||||
b := f.ttf.Bounds(fixed.Int26_6(f.scale))
|
||||
return float32(b.Min.Y)
|
||||
}
|
||||
|
||||
func(f *Font) MaxY() float32 {
|
||||
b:= f.ttf.Bounds(fixed.Int26_6(f.scale))
|
||||
func (f *Font) MaxY() float32 {
|
||||
b := f.ttf.Bounds(fixed.Int26_6(f.scale))
|
||||
return float32(b.Max.Y)
|
||||
}
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@ void main()
|
|||
|
||||
var vertexFontShader = `#version 150 core
|
||||
|
||||
//vertex position
|
||||
//vertex position
|
||||
in vec2 vert;
|
||||
|
||||
//pass through to fragTexCoord
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
//+build darwin
|
||||
|
||||
package gui
|
||||
|
||||
/*
|
||||
#cgo darwin CFLAGS: -x objective-c -Wno-deprecated-declarations
|
||||
#cgo darwin LDFLAGS: -framework Foundation
|
||||
#include <Cocoa/Cocoa.h>
|
||||
void cocoa_update_nsgl_context(void* id) {
|
||||
NSOpenGLContext *ctx = id;
|
||||
[ctx update];
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"github.com/go-gl/glfw/v3.2/glfw"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var nsglContextUpdateCounter int
|
||||
|
||||
func UpdateNSGLContext(window *glfw.Window) {
|
||||
if nsglContextUpdateCounter < 2 {
|
||||
ctx := window.GetNSGLContext()
|
||||
C.cocoa_update_nsgl_context(unsafe.Pointer(ctx))
|
||||
nsglContextUpdateCounter++
|
||||
}
|
||||
}
|
|
@ -28,16 +28,16 @@ func (a *annotation) render(gui *GUI) {
|
|||
}
|
||||
cell := cells[x]
|
||||
|
||||
var colour *[3]float32
|
||||
var colour [3]float32 = cell.Fg()
|
||||
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}
|
||||
colour = [3]float32{0.2, 1.0, 0.2}
|
||||
alpha = 1.0
|
||||
}
|
||||
}
|
||||
gui.renderer.DrawCellText(cell, uint(x), uint(y), alpha, colour)
|
||||
gui.renderer.DrawCellText(string(cell.Rune()), uint(x), uint(y), alpha, colour, cell.Attr().Bold)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,73 +5,25 @@ import "github.com/liamg/aminal/glfont"
|
|||
type FontMap struct {
|
||||
defaultFont *glfont.Font
|
||||
defaultBoldFont *glfont.Font
|
||||
runeMap map[rune]*glfont.Font
|
||||
ranges map[runeRange]*glfont.Font
|
||||
}
|
||||
|
||||
type runeRange struct {
|
||||
start rune
|
||||
end rune // inclusive
|
||||
}
|
||||
|
||||
func NewFontMap(defaultFont *glfont.Font, defaultBoldFont *glfont.Font) *FontMap {
|
||||
return &FontMap{
|
||||
defaultFont: defaultFont,
|
||||
defaultBoldFont: defaultBoldFont,
|
||||
runeMap: map[rune]*glfont.Font{},
|
||||
ranges: map[runeRange]*glfont.Font{},
|
||||
}
|
||||
}
|
||||
|
||||
func (fm *FontMap) UpdateResolution(w int, h int) {
|
||||
fm.defaultFont.UpdateResolution(w, h)
|
||||
fm.defaultBoldFont.UpdateResolution(w, h)
|
||||
for _, f := range fm.ranges {
|
||||
f.UpdateResolution(w, h)
|
||||
}
|
||||
}
|
||||
|
||||
func (fm *FontMap) findOverride(r rune) *glfont.Font {
|
||||
|
||||
override, ok := fm.runeMap[r]
|
||||
if ok {
|
||||
return override
|
||||
}
|
||||
|
||||
for rr, f := range fm.ranges {
|
||||
if r >= rr.start && r <= rr.end {
|
||||
fm.runeMap[r] = f
|
||||
return f
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fm *FontMap) setOverrideRange(start rune, end rune, font *glfont.Font) {
|
||||
fm.ranges[runeRange{start: start, end: end}] = font
|
||||
}
|
||||
|
||||
func (fm *FontMap) GetFont(r rune) *glfont.Font {
|
||||
if r <= 0xff {
|
||||
return fm.defaultFont
|
||||
}
|
||||
|
||||
if f := fm.findOverride(r); f != nil {
|
||||
return f
|
||||
}
|
||||
func (fm *FontMap) DefaultFont() *glfont.Font {
|
||||
|
||||
return fm.defaultFont
|
||||
}
|
||||
|
||||
func (fm *FontMap) GetBoldFont(r rune) *glfont.Font {
|
||||
if r <= 0xff {
|
||||
return fm.defaultBoldFont
|
||||
}
|
||||
|
||||
if f := fm.findOverride(r); f != nil {
|
||||
return f
|
||||
}
|
||||
|
||||
func (fm *FontMap) BoldFont() *glfont.Font {
|
||||
return fm.defaultBoldFont
|
||||
}
|
||||
|
|
|
@ -39,12 +39,11 @@ func (gui *GUI) loadFonts() error {
|
|||
|
||||
if gui.fontMap == nil {
|
||||
gui.fontMap = NewFontMap(defaultFont, boldFont)
|
||||
}else{
|
||||
} else {
|
||||
gui.fontMap.defaultFont = defaultFont
|
||||
gui.fontMap.defaultBoldFont = boldFont
|
||||
}
|
||||
|
||||
|
||||
// add special non-ascii fonts here
|
||||
|
||||
return nil
|
||||
|
|
212
gui/gui.go
212
gui/gui.go
|
@ -6,6 +6,7 @@ import (
|
|||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-gl/gl/all-core/gl"
|
||||
|
@ -33,6 +34,7 @@ type GUI struct {
|
|||
terminalAlpha float32
|
||||
showDebugInfo bool
|
||||
keyboardShortcuts map[config.UserAction]*config.KeyCombination
|
||||
resizeLock *sync.Mutex
|
||||
}
|
||||
|
||||
func New(config *config.Config, terminal *terminal.Terminal, logger *zap.SugaredLogger) (*GUI, error) {
|
||||
|
@ -51,6 +53,7 @@ func New(config *config.Config, terminal *terminal.Terminal, logger *zap.Sugared
|
|||
fontScale: 14.0,
|
||||
terminalAlpha: 1,
|
||||
keyboardShortcuts: shortcuts,
|
||||
resizeLock: &sync.Mutex{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -65,6 +68,9 @@ func (gui *GUI) scale() float32 {
|
|||
// can only be called on OS thread
|
||||
func (gui *GUI) resize(w *glfw.Window, width int, height int) {
|
||||
|
||||
gui.resizeLock.Lock()
|
||||
defer gui.resizeLock.Unlock()
|
||||
|
||||
gui.logger.Debugf("Initiating GUI resize to %dx%d", width, height)
|
||||
|
||||
gui.width = width
|
||||
|
@ -91,6 +97,8 @@ func (gui *GUI) resize(w *glfw.Window, width int, height int) {
|
|||
|
||||
gui.logger.Debugf("Resize complete!")
|
||||
|
||||
gui.redraw(buffer.NewBackgroundCell(gui.config.ColourScheme.Background))
|
||||
gui.window.SwapBuffers()
|
||||
}
|
||||
|
||||
func (gui *GUI) getTermSize() (uint, uint) {
|
||||
|
@ -205,6 +213,7 @@ func (gui *GUI) Render() error {
|
|||
}()
|
||||
|
||||
startTime := time.Now()
|
||||
showMessage := true
|
||||
|
||||
for !gui.window.ShouldClose() {
|
||||
|
||||
|
@ -218,66 +227,7 @@ func (gui *GUI) Render() error {
|
|||
|
||||
if gui.terminal.CheckDirty() {
|
||||
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
|
||||
|
||||
lines := gui.terminal.GetVisibleLines()
|
||||
lineCount := int(gui.terminal.ActiveBuffer().ViewHeight())
|
||||
colCount := int(gui.terminal.ActiveBuffer().ViewWidth())
|
||||
for y := 0; y < lineCount; y++ {
|
||||
for x := 0; x < colCount; x++ {
|
||||
|
||||
cell := defaultCell
|
||||
|
||||
if y < len(lines) {
|
||||
cells := lines[y].Cells()
|
||||
if x < len(cells) {
|
||||
cell = cells[x]
|
||||
}
|
||||
}
|
||||
|
||||
cursor := false
|
||||
if gui.terminal.Modes().ShowCursor {
|
||||
cx := uint(gui.terminal.GetLogicalCursorX())
|
||||
cy := uint(gui.terminal.GetLogicalCursorY())
|
||||
cy = cy + uint(gui.terminal.GetScrollOffset())
|
||||
cursor = cx == uint(x) && cy == uint(y)
|
||||
}
|
||||
|
||||
var colour *config.Colour
|
||||
|
||||
if gui.terminal.ActiveBuffer().InSelection(uint16(x), uint16(y)) {
|
||||
colour = &gui.config.ColourScheme.Selection
|
||||
}
|
||||
if cell.Image() != nil {
|
||||
gui.renderer.DrawCellImage(cell, uint(x), uint(y))
|
||||
} else {
|
||||
gui.renderer.DrawCellBg(cell, uint(x), uint(y), cursor, colour, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
for y := 0; y < lineCount; y++ {
|
||||
for x := 0; x < colCount; x++ {
|
||||
|
||||
cell := defaultCell
|
||||
hasText := false
|
||||
|
||||
if y < len(lines) {
|
||||
cells := lines[y].Cells()
|
||||
if x < len(cells) {
|
||||
cell = cells[x]
|
||||
if cell.Rune() != 0 && cell.Rune() != 32 {
|
||||
hasText = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hasText {
|
||||
gui.renderer.DrawCellText(cell, uint(x), uint(y), 1.0, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gui.renderOverlay()
|
||||
gui.redraw(defaultCell)
|
||||
|
||||
if gui.showDebugInfo {
|
||||
gui.textbox(2, 2, fmt.Sprintf(`Cursor: %d,%d
|
||||
|
@ -295,26 +245,29 @@ Buffer Size: %d lines
|
|||
)
|
||||
}
|
||||
|
||||
if latestVersion != "" && time.Since(startTime) < time.Second*10 && gui.terminal.ActiveBuffer().RawLine() == 0 {
|
||||
time.AfterFunc(time.Second, gui.terminal.SetDirty)
|
||||
_, h := gui.terminal.GetSize()
|
||||
var msg string
|
||||
if version.Version == "" {
|
||||
msg = "You are using a development build of Aminal."
|
||||
if showMessage {
|
||||
if latestVersion != "" && time.Since(startTime) < time.Second*10 && gui.terminal.ActiveBuffer().RawLine() == 0 {
|
||||
time.AfterFunc(time.Second, gui.terminal.SetDirty)
|
||||
_, h := gui.terminal.GetSize()
|
||||
var msg string
|
||||
if version.Version == "" {
|
||||
msg = "You are using a development build of Aminal."
|
||||
} else {
|
||||
msg = fmt.Sprintf("Version %s of Aminal is now available.", strings.Replace(latestVersion, "v", "", -1))
|
||||
}
|
||||
gui.textbox(
|
||||
2,
|
||||
uint16(h-3),
|
||||
fmt.Sprintf("%s (%d)", msg, 10-int(time.Since(startTime).Seconds())),
|
||||
[3]float32{1, 1, 1},
|
||||
[3]float32{0, 0.5, 0},
|
||||
)
|
||||
} else {
|
||||
msg = fmt.Sprintf("Version %s of Aminal is now available.", strings.Replace(latestVersion, "v", "", -1))
|
||||
showMessage = false
|
||||
}
|
||||
gui.textbox(
|
||||
2,
|
||||
uint16(h-3),
|
||||
fmt.Sprintf("%s (%d)", msg, 10-int(time.Since(startTime).Seconds())),
|
||||
[3]float32{1, 1, 1},
|
||||
[3]float32{0, 0.5, 0},
|
||||
)
|
||||
}
|
||||
|
||||
gui.window.SwapBuffers()
|
||||
|
||||
gui.SwapBuffers()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -324,6 +277,93 @@ Buffer Size: %d lines
|
|||
|
||||
}
|
||||
|
||||
func (gui *GUI) redraw(defaultCell buffer.Cell) {
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
|
||||
lines := gui.terminal.GetVisibleLines()
|
||||
lineCount := int(gui.terminal.ActiveBuffer().ViewHeight())
|
||||
colCount := int(gui.terminal.ActiveBuffer().ViewWidth())
|
||||
cx := uint(gui.terminal.GetLogicalCursorX())
|
||||
cy := uint(gui.terminal.GetLogicalCursorY()) + uint(gui.terminal.GetScrollOffset())
|
||||
var colour *config.Colour
|
||||
for y := 0; y < lineCount; y++ {
|
||||
if y < len(lines) {
|
||||
cells := lines[y].Cells()
|
||||
for x := 0; x < colCount; x++ {
|
||||
|
||||
cursor := false
|
||||
if gui.terminal.Modes().ShowCursor {
|
||||
cursor = cx == uint(x) && cy == uint(y)
|
||||
}
|
||||
|
||||
if gui.terminal.ActiveBuffer().InSelection(uint16(x), uint16(y)) {
|
||||
colour = &gui.config.ColourScheme.Selection
|
||||
} else {
|
||||
colour = nil
|
||||
}
|
||||
|
||||
cell := defaultCell
|
||||
if colour != nil || cursor || x < len(cells) {
|
||||
|
||||
if x < len(cells) {
|
||||
cell = cells[x]
|
||||
if cell.Image() != nil {
|
||||
gui.renderer.DrawCellImage(cell, uint(x), uint(y))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
gui.renderer.DrawCellBg(cell, uint(x), uint(y), cursor, colour, false)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
for y := 0; y < lineCount; y++ {
|
||||
|
||||
if y < len(lines) {
|
||||
|
||||
bufStr := ""
|
||||
bold := false
|
||||
dim := false
|
||||
col := 0
|
||||
colour := [3]float32{0, 0, 0}
|
||||
cells := lines[y].Cells()
|
||||
|
||||
for x := 0; x < colCount; x++ {
|
||||
if x < len(cells) {
|
||||
cell := cells[x]
|
||||
if bufStr != "" && (cell.Attr().Dim != dim || cell.Attr().Bold != bold || colour != cell.Fg()) {
|
||||
var alpha float32 = 1.0
|
||||
if dim {
|
||||
alpha = 0.5
|
||||
}
|
||||
gui.renderer.DrawCellText(bufStr, uint(col), uint(y), alpha, colour, bold)
|
||||
col = x
|
||||
bufStr = ""
|
||||
}
|
||||
dim = cell.Attr().Dim
|
||||
colour = cell.Fg()
|
||||
bold = cell.Attr().Bold
|
||||
r := cell.Rune()
|
||||
if r == 0 {
|
||||
r = ' '
|
||||
}
|
||||
bufStr += string(r)
|
||||
}
|
||||
}
|
||||
if bufStr != "" {
|
||||
var alpha float32 = 1.0
|
||||
if dim {
|
||||
alpha = 0.5
|
||||
}
|
||||
gui.renderer.DrawCellText(bufStr, uint(col), uint(y), alpha, colour, bold)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
gui.renderOverlay()
|
||||
}
|
||||
|
||||
func (gui *GUI) createWindow() (*glfw.Window, error) {
|
||||
if err := glfw.Init(); err != nil {
|
||||
return nil, fmt.Errorf("Failed to initialise GLFW: %s", err)
|
||||
|
@ -352,7 +392,7 @@ func (gui *GUI) createWindow() (*glfw.Window, error) {
|
|||
window, err = gui.createWindowWithOpenGLVersion(v[0], v[1])
|
||||
if err != nil {
|
||||
gui.logger.Warnf("Failed to create window: %s. Will attempt older version...", err)
|
||||
}else{
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -369,7 +409,7 @@ func (gui *GUI) createWindow() (*glfw.Window, error) {
|
|||
return window, nil
|
||||
}
|
||||
|
||||
func(gui *GUI) createWindowWithOpenGLVersion(major int, minor int) (*glfw.Window, error) {
|
||||
func (gui *GUI) createWindowWithOpenGLVersion(major int, minor int) (*glfw.Window, error) {
|
||||
|
||||
glfw.WindowHint(glfw.ContextVersionMajor, major)
|
||||
glfw.WindowHint(glfw.ContextVersionMinor, minor)
|
||||
|
@ -377,13 +417,13 @@ func(gui *GUI) createWindowWithOpenGLVersion(major int, minor int) (*glfw.Window
|
|||
window, err := glfw.CreateWindow(gui.width, gui.height, "Terminal", nil, nil)
|
||||
if err != nil {
|
||||
e := err.Error()
|
||||
if i := strings.Index(e, ", got version "); i > - 1 {
|
||||
if i := strings.Index(e, ", got version "); i > -1 {
|
||||
v := strings.Split(strings.TrimSpace(e[i+14:]), ".")
|
||||
if len(v) == 2 {
|
||||
major, err := strconv.Atoi(v[0])
|
||||
if err == nil {
|
||||
if minor, err := strconv.Atoi(v[1]); err == nil {
|
||||
return gui.createWindowWithOpenGLVersion(major, minor)
|
||||
maj, mjErr := strconv.Atoi(v[0])
|
||||
if mjErr == nil {
|
||||
if min, miErr := strconv.Atoi(v[1]); miErr == nil {
|
||||
return gui.createWindowWithOpenGLVersion(maj, min)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -395,6 +435,7 @@ func(gui *GUI) createWindowWithOpenGLVersion(major int, minor int) (*glfw.Window
|
|||
|
||||
return window, nil
|
||||
}
|
||||
|
||||
// initOpenGL initializes OpenGL and returns an intiialized program.
|
||||
func (gui *GUI) createProgram() (uint32, error) {
|
||||
if err := gl.Init(); err != nil {
|
||||
|
@ -437,3 +478,8 @@ func (gui *GUI) launchTarget(target string) {
|
|||
gui.logger.Errorf("Failed to launch external command %s: %s", cmd, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *GUI) SwapBuffers() {
|
||||
UpdateNSGLContext(gui.window)
|
||||
gui.window.SwapBuffers()
|
||||
}
|
||||
|
|
190
gui/input.go
190
gui/input.go
|
@ -21,6 +21,28 @@ func modsPressed(pressed glfw.ModifierKey, mods ...glfw.ModifierKey) bool {
|
|||
return pressed == 0
|
||||
}
|
||||
|
||||
func getModStr(mods glfw.ModifierKey) string {
|
||||
|
||||
switch true {
|
||||
case modsPressed(mods, glfw.ModControl, glfw.ModShift, glfw.ModAlt):
|
||||
return "8"
|
||||
case modsPressed(mods, glfw.ModControl, glfw.ModAlt):
|
||||
return "7"
|
||||
case modsPressed(mods, glfw.ModControl, glfw.ModShift):
|
||||
return "6"
|
||||
case modsPressed(mods, glfw.ModControl):
|
||||
return "5"
|
||||
case modsPressed(mods, glfw.ModAlt, glfw.ModShift):
|
||||
return "4"
|
||||
case modsPressed(mods, glfw.ModAlt):
|
||||
return "3"
|
||||
case modsPressed(mods, glfw.ModShift):
|
||||
return "2"
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -31,128 +53,33 @@ func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Acti
|
|||
}
|
||||
}
|
||||
|
||||
for userAction, shortcut := range gui.keyboardShortcuts {
|
||||
|
||||
if shortcut.Match(mods, key) {
|
||||
|
||||
f, ok := actionMap[userAction]
|
||||
if ok {
|
||||
f(gui)
|
||||
break
|
||||
// get key name to handle alternative keyboard layouts
|
||||
name := glfw.GetKeyName(key, scancode)
|
||||
if len(name) == 1 {
|
||||
r := rune(name[0])
|
||||
for userAction, shortcut := range gui.keyboardShortcuts {
|
||||
if shortcut.Match(mods, r) {
|
||||
f, ok := actionMap[userAction]
|
||||
if ok {
|
||||
f(gui)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch key {
|
||||
case glfw.KeyD:
|
||||
|
||||
case glfw.KeyG:
|
||||
|
||||
case glfw.KeyR:
|
||||
gui.launchTarget("https://github.com/liamg/aminal/issues/new/choose")
|
||||
case glfw.KeySemicolon:
|
||||
gui.config.Slomo = !gui.config.Slomo
|
||||
// standard ctrl codes e.g. ^C
|
||||
if modsPressed(mods, glfw.ModControl) {
|
||||
if r >= 97 && r < 123 {
|
||||
gui.terminal.Write([]byte{byte(r) - 96})
|
||||
return
|
||||
} else if r >= 65 && r < 91 {
|
||||
gui.terminal.Write([]byte{byte(r) - 64})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modStr := ""
|
||||
switch true {
|
||||
case modsPressed(mods, glfw.ModControl, glfw.ModShift, glfw.ModAlt):
|
||||
modStr = "8"
|
||||
case modsPressed(mods, glfw.ModControl, glfw.ModAlt):
|
||||
modStr = "7"
|
||||
case modsPressed(mods, glfw.ModControl, glfw.ModShift):
|
||||
modStr = "6"
|
||||
case modsPressed(mods, glfw.ModControl):
|
||||
modStr = "5"
|
||||
switch key {
|
||||
case glfw.KeyA:
|
||||
gui.terminal.Write([]byte{0x1})
|
||||
return
|
||||
case glfw.KeyB:
|
||||
gui.terminal.Write([]byte{0x2})
|
||||
return
|
||||
case glfw.KeyC: // ctrl^c
|
||||
gui.terminal.Write([]byte{0x3}) // send EOT
|
||||
return
|
||||
case glfw.KeyD:
|
||||
gui.terminal.Write([]byte{0x4}) // send EOT
|
||||
return
|
||||
case glfw.KeyE:
|
||||
gui.terminal.Write([]byte{0x5})
|
||||
return
|
||||
case glfw.KeyF:
|
||||
gui.terminal.Write([]byte{0x6})
|
||||
return
|
||||
case glfw.KeyG:
|
||||
gui.terminal.Write([]byte{0x7})
|
||||
return
|
||||
case glfw.KeyH:
|
||||
gui.terminal.Write([]byte{0x08})
|
||||
return
|
||||
case glfw.KeyI:
|
||||
gui.terminal.Write([]byte{0x9})
|
||||
return
|
||||
case glfw.KeyJ:
|
||||
gui.terminal.Write([]byte{0x0a})
|
||||
return
|
||||
case glfw.KeyK:
|
||||
gui.terminal.Write([]byte{0x0b})
|
||||
return
|
||||
case glfw.KeyL:
|
||||
gui.terminal.Write([]byte{0x0c})
|
||||
return
|
||||
case glfw.KeyM:
|
||||
gui.terminal.Write([]byte{0x0d})
|
||||
return
|
||||
case glfw.KeyN:
|
||||
gui.terminal.Write([]byte{0x0e})
|
||||
return
|
||||
case glfw.KeyO:
|
||||
gui.terminal.Write([]byte{0x0f})
|
||||
return
|
||||
case glfw.KeyP:
|
||||
gui.terminal.Write([]byte{0x10})
|
||||
return
|
||||
case glfw.KeyQ:
|
||||
gui.terminal.Write([]byte{0x11})
|
||||
return
|
||||
case glfw.KeyR:
|
||||
gui.terminal.Write([]byte{0x12})
|
||||
return
|
||||
case glfw.KeyS:
|
||||
gui.terminal.Write([]byte{0x13})
|
||||
return
|
||||
case glfw.KeyT:
|
||||
gui.terminal.Write([]byte{0x14})
|
||||
return
|
||||
case glfw.KeyU:
|
||||
gui.terminal.Write([]byte{0x15})
|
||||
return
|
||||
case glfw.KeyV:
|
||||
gui.terminal.Write([]byte{0x16})
|
||||
return
|
||||
case glfw.KeyW:
|
||||
gui.terminal.Write([]byte{0x17})
|
||||
return
|
||||
case glfw.KeyX:
|
||||
gui.terminal.Write([]byte{0x18})
|
||||
return
|
||||
case glfw.KeyY:
|
||||
gui.terminal.Write([]byte{0x19})
|
||||
return
|
||||
case glfw.KeyZ:
|
||||
gui.terminal.Write([]byte{0x1a})
|
||||
return
|
||||
}
|
||||
case modsPressed(mods, glfw.ModAlt, glfw.ModShift):
|
||||
modStr = "4"
|
||||
case modsPressed(mods, glfw.ModAlt):
|
||||
modStr = "3"
|
||||
case modsPressed(mods, glfw.ModShift):
|
||||
modStr = "2"
|
||||
|
||||
}
|
||||
modStr := getModStr(mods)
|
||||
|
||||
switch key {
|
||||
case glfw.KeyF1:
|
||||
|
@ -240,10 +167,14 @@ func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Acti
|
|||
'3', '~',
|
||||
})
|
||||
case glfw.KeyHome:
|
||||
if modStr == "" {
|
||||
gui.terminal.Write([]byte("\x1b[1~"))
|
||||
if gui.terminal.IsApplicationCursorKeysModeEnabled() {
|
||||
if modStr == "" {
|
||||
gui.terminal.Write([]byte("\x1b[1~"))
|
||||
} else {
|
||||
gui.terminal.Write([]byte(fmt.Sprintf("\x1b[1;%s~", modStr)))
|
||||
}
|
||||
} else {
|
||||
gui.terminal.Write([]byte(fmt.Sprintf("\x1b[1;%s~", modStr)))
|
||||
gui.terminal.Write([]byte("\x1b[H"))
|
||||
}
|
||||
case glfw.KeyEnd:
|
||||
if modStr == "" {
|
||||
|
@ -276,17 +207,9 @@ func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Acti
|
|||
})
|
||||
}
|
||||
case glfw.KeyTab:
|
||||
if gui.terminal.IsApplicationCursorKeysModeEnabled() {
|
||||
gui.terminal.Write([]byte{
|
||||
0x1b,
|
||||
'O',
|
||||
'I',
|
||||
})
|
||||
} else {
|
||||
gui.terminal.Write([]byte{
|
||||
0x09,
|
||||
})
|
||||
}
|
||||
gui.terminal.Write([]byte{
|
||||
0x09,
|
||||
})
|
||||
case glfw.KeyEnter:
|
||||
gui.terminal.Write([]byte{
|
||||
0x0d,
|
||||
|
@ -304,7 +227,11 @@ func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Acti
|
|||
})
|
||||
}
|
||||
case glfw.KeyBackspace:
|
||||
gui.terminal.Write([]byte{0x08})
|
||||
if modsPressed(mods, glfw.ModAlt) {
|
||||
gui.terminal.Write([]byte{0x17}) // ctrl-w/delete word
|
||||
} else {
|
||||
gui.terminal.Write([]byte{0x8})
|
||||
}
|
||||
case glfw.KeyUp:
|
||||
if modStr != "" {
|
||||
gui.terminal.Write([]byte(fmt.Sprintf("\x1b[1;%sA", modStr)))
|
||||
|
@ -379,9 +306,6 @@ func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Acti
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
//gui.logger.Debugf("Key pressed: 0x%X %q", key, string([]byte{byte(key)}))
|
||||
//gui.terminal.Write([]byte{byte(scancode)})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
// +build !darwin
|
||||
|
||||
package gui
|
||||
|
||||
import "github.com/go-gl/glfw/v3.2/glfw"
|
||||
|
||||
func UpdateNSGLContext(window *glfw.Window) {
|
||||
|
||||
}
|
|
@ -57,11 +57,10 @@ func (r *OpenGLRenderer) newRectangle(x float32, y float32, colourAttr uint32) *
|
|||
halfAreaWidth := float32(r.areaWidth / 2)
|
||||
halfAreaHeight := float32(r.areaHeight / 2)
|
||||
|
||||
|
||||
x = (x - halfAreaWidth) / halfAreaWidth
|
||||
y = -(y - ( halfAreaHeight)) / halfAreaHeight
|
||||
y = -(y - (halfAreaHeight)) / halfAreaHeight
|
||||
w := r.cellWidth / halfAreaWidth
|
||||
h := (r.cellHeight ) / halfAreaHeight
|
||||
h := (r.cellHeight) / halfAreaHeight
|
||||
|
||||
rect := &rectangle{
|
||||
points: []float32{
|
||||
|
@ -162,10 +161,10 @@ func (r *OpenGLRenderer) SetArea(areaX int, areaY int, areaWidth int, areaHeight
|
|||
r.areaHeight = areaHeight
|
||||
r.areaX = areaX
|
||||
r.areaY = areaY
|
||||
f := r.fontMap.GetFont('X')
|
||||
f := r.fontMap.DefaultFont()
|
||||
_, r.cellHeight = f.MaxSize()
|
||||
r.cellWidth, _ = f.Size("X")
|
||||
//= f.LineHeight() // includes vertical padding
|
||||
//= f.LineHeight() // includes vertical padding
|
||||
r.termCols = uint(math.Floor(float64(float32(r.areaWidth) / r.cellWidth)))
|
||||
r.termRows = uint(math.Floor(float64(float32(r.areaHeight) / r.cellHeight)))
|
||||
r.rectangles = map[[2]uint]*rectangle{}
|
||||
|
@ -179,7 +178,7 @@ func (r *OpenGLRenderer) getRectangle(col uint, row uint) *rectangle {
|
|||
}
|
||||
|
||||
x := float32(float32(col) * r.cellWidth)
|
||||
y := float32(float32(row) * r.cellHeight) + r.cellHeight
|
||||
y := float32(float32(row)*r.cellHeight) + r.cellHeight
|
||||
|
||||
r.rectangles[[2]uint{col, row}] = r.newRectangle(x, y, r.colourAttr)
|
||||
return r.rectangles[[2]uint{col, row}]
|
||||
|
@ -201,8 +200,6 @@ func (r *OpenGLRenderer) DrawCellBg(cell buffer.Cell, col uint, row uint, cursor
|
|||
|
||||
if cursor {
|
||||
bg = r.config.ColourScheme.Cursor
|
||||
} else if cell.Attr().Reverse {
|
||||
bg = cell.Fg()
|
||||
} else {
|
||||
bg = cell.Bg()
|
||||
}
|
||||
|
@ -216,32 +213,21 @@ func (r *OpenGLRenderer) DrawCellBg(cell buffer.Cell, col uint, row uint, cursor
|
|||
|
||||
}
|
||||
|
||||
func (r *OpenGLRenderer) DrawCellText(cell buffer.Cell, col uint, row uint, alpha float32, colour *[3]float32) {
|
||||
func (r *OpenGLRenderer) DrawCellText(text string, col uint, row uint, alpha float32, colour [3]float32, bold bool) {
|
||||
|
||||
var fg [3]float32
|
||||
|
||||
if colour != nil {
|
||||
fg = *colour
|
||||
} else if cell.Attr().Reverse {
|
||||
fg = cell.Bg()
|
||||
var f *glfont.Font
|
||||
if bold {
|
||||
f = r.fontMap.BoldFont()
|
||||
} else {
|
||||
fg = cell.Fg()
|
||||
f = r.fontMap.DefaultFont()
|
||||
}
|
||||
|
||||
f := r.fontMap.GetFont(cell.Rune())
|
||||
if cell.Attr().Bold {
|
||||
f = r.fontMap.GetBoldFont(cell.Rune())
|
||||
}
|
||||
|
||||
if cell.Attr().Dim {
|
||||
alpha = 0.5 * alpha
|
||||
}
|
||||
f.SetColor(fg[0], fg[1], fg[2], alpha)
|
||||
f.SetColor(colour[0], colour[1], colour[2], alpha)
|
||||
|
||||
x := float32(r.areaX) + float32(col)*r.cellWidth
|
||||
y := float32(r.areaY) + (float32(row+1) * r.cellHeight) + f.MinY()
|
||||
|
||||
f.Print(x, y, string(cell.Rune()))
|
||||
f.Print(x, y, text)
|
||||
}
|
||||
|
||||
func (r *OpenGLRenderer) DrawCellImage(cell buffer.Cell, col uint, row uint) {
|
||||
|
|
|
@ -24,7 +24,7 @@ const (
|
|||
smooth in vec3 theColour;
|
||||
out vec4 outColour;
|
||||
void main() {
|
||||
outColour = vec4(theColour, 1.0);
|
||||
outColour = vec4(theColour, 1.0);
|
||||
}
|
||||
` + "\x00"
|
||||
)
|
||||
|
|
|
@ -89,7 +89,7 @@ DONE:
|
|||
|
||||
x := float32(col) * gui.renderer.cellWidth
|
||||
|
||||
f := gui.fontMap.GetFont('X')
|
||||
f := gui.fontMap.DefaultFont()
|
||||
f.SetColor(fg[0], fg[1], fg[2], 1)
|
||||
|
||||
for i, line := range lines {
|
||||
|
|
BIN
hint.colour.png
BIN
hint.colour.png
Binary file not shown.
Before Width: | Height: | Size: 12 KiB |
14
main.go
14
main.go
|
@ -31,13 +31,13 @@ func main() {
|
|||
logger.Fatalf("Failed to allocate pty: %s", err)
|
||||
}
|
||||
|
||||
shellStr, err := loginshell.Shell()
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to ascertain your shell: %s", err)
|
||||
}
|
||||
|
||||
if conf.Shell != "" {
|
||||
shellStr = conf.Shell
|
||||
shellStr := conf.Shell
|
||||
if shellStr == "" {
|
||||
loginShell, err := loginshell.Shell()
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to ascertain your shell: %s", err)
|
||||
}
|
||||
shellStr = loginShell
|
||||
}
|
||||
|
||||
os.Setenv("TERM", "xterm-256color") // controversial! easier than installing terminfo everywhere, but obviously going to be slightly different to xterm functionality, so we'll see...
|
||||
|
|
BIN
powerline.png
BIN
powerline.png
Binary file not shown.
Before Width: | Height: | Size: 5.9 KiB |
|
@ -1,7 +1,6 @@
|
|||
package terminal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -27,21 +26,25 @@ var escapeSequenceMap = map[rune]escapeSequenceHandler{
|
|||
|
||||
func newLineSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||
terminal.ActiveBuffer().NewLine()
|
||||
terminal.isDirty = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func tabSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||
terminal.ActiveBuffer().Tab()
|
||||
terminal.isDirty = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func carriageReturnSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||
terminal.ActiveBuffer().CarriageReturn()
|
||||
terminal.isDirty = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func backspaceSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||
terminal.ActiveBuffer().Backspace()
|
||||
terminal.isDirty = true
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -65,47 +68,33 @@ func shiftInSequenceHandler(pty chan rune, terminal *Terminal) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (terminal *Terminal) processInput(ctx context.Context, pty chan rune) {
|
||||
func (terminal *Terminal) processInput(pty chan rune) {
|
||||
|
||||
// https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
|
||||
for {
|
||||
var b rune
|
||||
|
||||
select {
|
||||
case <-terminal.pauseChan:
|
||||
// @todo alert user when terminal is suspended
|
||||
terminal.logger.Debugf("Terminal suspended")
|
||||
<-terminal.resumeChan
|
||||
case <-ctx.Done():
|
||||
break
|
||||
default:
|
||||
}
|
||||
for {
|
||||
|
||||
if terminal.config.Slomo {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
|
||||
b := <-pty
|
||||
b = <-pty
|
||||
|
||||
terminal.logger.Debugf("0x%q", string(b))
|
||||
|
||||
handler, ok := escapeSequenceMap[b]
|
||||
|
||||
if ok {
|
||||
//terminal.logger.Debugf("Handling escape sequence: 0x%x", b)
|
||||
if err := handler(pty, terminal); err != nil {
|
||||
terminal.logger.Errorf("Error handling escape sequence: %s", err)
|
||||
}
|
||||
} else {
|
||||
//terminal.logger.Debugf("Received character 0x%X: %q", b, string(b))
|
||||
if b >= 0x20 {
|
||||
//terminal.logger.Debugf("%c", b)
|
||||
terminal.ActiveBuffer().Write(b)
|
||||
} else {
|
||||
terminal.logger.Error("Non-readable rune received: 0x%X", b)
|
||||
if b < 0x20 {
|
||||
if handler, ok := escapeSequenceMap[b]; ok {
|
||||
//terminal.logger.Debugf("Handling escape sequence: 0x%x", b)
|
||||
if err := handler(pty, terminal); err != nil {
|
||||
terminal.logger.Errorf("Error handling escape sequence: %s", err)
|
||||
}
|
||||
terminal.isDirty = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
//terminal.logger.Debugf("Received character 0x%X: %q", b, string(b))
|
||||
terminal.ActiveBuffer().Write(b)
|
||||
terminal.isDirty = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package terminal
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
@ -35,7 +34,7 @@ const (
|
|||
type Terminal struct {
|
||||
program uint32
|
||||
buffers []*buffer.Buffer
|
||||
activeBufferIndex uint8
|
||||
activeBuffer *buffer.Buffer
|
||||
lock sync.Mutex
|
||||
pty *os.File
|
||||
logger *zap.SugaredLogger
|
||||
|
@ -43,8 +42,6 @@ type Terminal struct {
|
|||
size Winsize
|
||||
config *config.Config
|
||||
titleHandlers []chan bool
|
||||
pauseChan chan bool
|
||||
resumeChan chan bool
|
||||
modes Modes
|
||||
mouseMode MouseMode
|
||||
bracketedPasteMode bool
|
||||
|
@ -87,13 +84,11 @@ func New(pty *os.File, logger *zap.SugaredLogger, config *config.Config) *Termin
|
|||
logger: logger,
|
||||
config: config,
|
||||
titleHandlers: []chan bool{},
|
||||
pauseChan: make(chan bool, 1),
|
||||
resumeChan: make(chan bool, 1),
|
||||
modes: Modes{
|
||||
ShowCursor: true,
|
||||
},
|
||||
}
|
||||
|
||||
t.activeBuffer = t.buffers[0]
|
||||
return t
|
||||
|
||||
}
|
||||
|
@ -129,32 +124,30 @@ func (terminal *Terminal) GetMouseMode() MouseMode {
|
|||
}
|
||||
|
||||
func (terminal *Terminal) UseMainBuffer() {
|
||||
terminal.activeBufferIndex = MainBuffer
|
||||
terminal.activeBuffer = terminal.buffers[MainBuffer]
|
||||
terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height))
|
||||
}
|
||||
|
||||
func (terminal *Terminal) UseAltBuffer() {
|
||||
terminal.activeBufferIndex = AltBuffer
|
||||
terminal.activeBuffer = terminal.buffers[AltBuffer]
|
||||
terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height))
|
||||
}
|
||||
|
||||
func (terminal *Terminal) UseInternalBuffer() {
|
||||
terminal.pauseChan <- true
|
||||
terminal.activeBufferIndex = InternalBuffer
|
||||
terminal.activeBuffer = terminal.buffers[InternalBuffer]
|
||||
terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height))
|
||||
}
|
||||
|
||||
func (terminal *Terminal) ExitInternalBuffer() {
|
||||
terminal.activeBufferIndex = terminal.lastBuffer
|
||||
terminal.resumeChan <- true
|
||||
terminal.activeBuffer = terminal.buffers[terminal.lastBuffer]
|
||||
}
|
||||
|
||||
func (terminal *Terminal) ActiveBuffer() *buffer.Buffer {
|
||||
return terminal.buffers[terminal.activeBufferIndex]
|
||||
return terminal.activeBuffer
|
||||
}
|
||||
|
||||
func (terminal *Terminal) UsingMainBuffer() bool {
|
||||
return terminal.activeBufferIndex == MainBuffer
|
||||
return terminal.activeBuffer == terminal.buffers[MainBuffer]
|
||||
}
|
||||
|
||||
func (terminal *Terminal) GetScrollOffset() uint {
|
||||
|
@ -255,20 +248,17 @@ func (terminal *Terminal) Read() error {
|
|||
buffer := make(chan rune, 0xffff)
|
||||
|
||||
reader := bufio.NewReader(terminal.pty)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
go terminal.processInput(ctx, buffer)
|
||||
go terminal.processInput(buffer)
|
||||
for {
|
||||
r, size, err := reader.ReadRune()
|
||||
r, _, err := reader.ReadRune()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return err
|
||||
} else if size > 0 {
|
||||
buffer <- r
|
||||
}
|
||||
buffer <- r
|
||||
}
|
||||
|
||||
//clean exit
|
||||
|
|
Loading…
Reference in New Issue