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:
|
tags:
|
||||||
only: /^v.*/
|
only: /^v.*/
|
||||||
branches:
|
branches:
|
||||||
ignore: /.*/
|
ignore: /.*/
|
||||||
|
|
||||||
|
|
|
@ -29,4 +29,4 @@ If applicable, add screenshots to help explain your problem.
|
||||||
Add any other context about the problem here.
|
Add any other context about the problem here.
|
||||||
|
|
||||||
**Logs**
|
**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
|
FONTPATH := ./gui/packed-fonts
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: test install-tools
|
build:
|
||||||
packr -v
|
./build.sh `git describe --tags`
|
||||||
go build -ldflags "-X github.com/liamg/aminal/version.Version=`git describe --tags`"
|
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
|
@ -13,8 +12,7 @@ test:
|
||||||
go vet -v
|
go vet -v
|
||||||
|
|
||||||
.PHONY: install
|
.PHONY: install
|
||||||
install: build install-tools
|
install: build
|
||||||
packr -v
|
|
||||||
go install -ldflags "-X github.com/liamg/aminal/version.Version=`git describe --tags`"
|
go install -ldflags "-X github.com/liamg/aminal/version.Version=`git describe --tags`"
|
||||||
|
|
||||||
.PHONY: install-tools
|
.PHONY: install-tools
|
||||||
|
@ -22,12 +20,6 @@ install-tools:
|
||||||
which dep || curl -L https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
which dep || curl -L https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||||
which packr || go get -u github.com/gobuffalo/packr/packr
|
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
|
.PHONY: build-linux
|
||||||
build-linux:
|
build-linux:
|
||||||
mkdir -p bin/linux
|
mkdir -p bin/linux
|
||||||
|
@ -40,4 +32,4 @@ build-darwin:
|
||||||
|
|
||||||
.PHONY: package-debian
|
.PHONY: package-debian
|
||||||
package-debian: build-linux
|
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
|
- Built-in patched fonts for powerline
|
||||||
- Retina display support
|
- 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.
|
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
|
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 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 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`.
|
- 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:
|
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
|
## 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.
|
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.
|
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.
|
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.
|
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]
|
[colours]
|
||||||
cursor = "#e8dfd6"
|
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.
|
| `--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.
|
| `--shell [shell]` | Use the specified shell program instead of the user's usual one.
|
||||||
| `--version` | Show the version of aminal and exit.
|
| `--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
|
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
|
selectionExpanded bool // whether the selection to word expansion has already run on this point
|
||||||
selectionClickTime time.Time
|
selectionClickTime time.Time
|
||||||
|
defaultCell Cell
|
||||||
}
|
}
|
||||||
|
|
||||||
type Position struct {
|
type Position struct {
|
||||||
|
@ -37,11 +38,12 @@ type Position struct {
|
||||||
// NewBuffer creates a new terminal buffer
|
// NewBuffer creates a new terminal buffer
|
||||||
func NewBuffer(viewCols uint16, viewLines uint16, attr CellAttributes) *Buffer {
|
func NewBuffer(viewCols uint16, viewLines uint16, attr CellAttributes) *Buffer {
|
||||||
b := &Buffer{
|
b := &Buffer{
|
||||||
cursorX: 0,
|
cursorX: 0,
|
||||||
cursorY: 0,
|
cursorY: 0,
|
||||||
lines: []Line{},
|
lines: []Line{},
|
||||||
cursorAttr: attr,
|
cursorAttr: attr,
|
||||||
autoWrap: true,
|
autoWrap: true,
|
||||||
|
defaultCell: Cell{attr: attr},
|
||||||
}
|
}
|
||||||
b.SetVerticalMargins(0, uint(viewLines-1))
|
b.SetVerticalMargins(0, uint(viewLines-1))
|
||||||
b.ResizeView(viewCols, viewLines)
|
b.ResizeView(viewCols, viewLines)
|
||||||
|
@ -511,8 +513,7 @@ func (buffer *Buffer) InsertBlankCharacters(count int) {
|
||||||
index := int(buffer.RawLine())
|
index := int(buffer.RawLine())
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
cells := buffer.lines[index].cells
|
cells := buffer.lines[index].cells
|
||||||
c := Cell{}
|
buffer.lines[index].cells = append(cells[:buffer.cursorX], append([]Cell{buffer.defaultCell}, cells[buffer.cursorX:]...)...)
|
||||||
buffer.lines[index].cells = append(cells[:buffer.cursorX], append([]Cell{c}, cells[buffer.cursorX:]...)...)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -610,20 +611,10 @@ func (buffer *Buffer) ReverseIndex() {
|
||||||
func (buffer *Buffer) Write(runes ...rune) {
|
func (buffer *Buffer) Write(runes ...rune) {
|
||||||
|
|
||||||
// scroll to bottom on input
|
// scroll to bottom on input
|
||||||
inc := true
|
|
||||||
buffer.scrollLinesFromBottom = 0
|
buffer.scrollLinesFromBottom = 0
|
||||||
|
|
||||||
for _, r := range runes {
|
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()
|
line := buffer.getCurrentLine()
|
||||||
|
|
||||||
if buffer.replaceMode {
|
if buffer.replaceMode {
|
||||||
|
@ -634,7 +625,7 @@ func (buffer *Buffer) Write(runes ...rune) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for int(buffer.CursorColumn()) >= len(line.cells) {
|
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].attr = buffer.cursorAttr
|
||||||
line.cells[buffer.cursorX].setRune(r)
|
line.cells[buffer.cursorX].setRune(r)
|
||||||
|
@ -665,31 +656,23 @@ func (buffer *Buffer) Write(runes ...rune) {
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
for int(buffer.CursorColumn()) >= len(line.cells) {
|
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 := &line.cells[buffer.CursorColumn()]
|
||||||
cell.setRune(r)
|
cell.setRune(r)
|
||||||
cell.attr = buffer.cursorAttr
|
cell.attr = buffer.cursorAttr
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if inc {
|
buffer.incrementCursorPosition()
|
||||||
buffer.incrementCursorPosition()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) incrementCursorPosition() {
|
func (buffer *Buffer) incrementCursorPosition() {
|
||||||
|
|
||||||
defer buffer.emitDisplayChange()
|
|
||||||
|
|
||||||
// we can increment one column past the end of the line.
|
// 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.
|
// 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++
|
buffer.cursorX++
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -708,7 +691,6 @@ func (buffer *Buffer) Backspace() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) CarriageReturn() {
|
func (buffer *Buffer) CarriageReturn() {
|
||||||
defer buffer.emitDisplayChange()
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
line := buffer.getCurrentLine()
|
line := buffer.getCurrentLine()
|
||||||
|
@ -726,19 +708,14 @@ func (buffer *Buffer) CarriageReturn() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) Tab() {
|
func (buffer *Buffer) Tab() {
|
||||||
defer buffer.emitDisplayChange()
|
|
||||||
tabSize := 4
|
tabSize := 4
|
||||||
shift := int(buffer.cursorX-1) % tabSize
|
shift := tabSize - (int(buffer.cursorX+1) % tabSize)
|
||||||
if shift == 0 {
|
|
||||||
shift = tabSize
|
|
||||||
}
|
|
||||||
for i := 0; i < shift; i++ {
|
for i := 0; i < shift; i++ {
|
||||||
buffer.Write(' ')
|
buffer.Write(' ')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (buffer *Buffer) NewLine() {
|
func (buffer *Buffer) NewLine() {
|
||||||
defer buffer.emitDisplayChange()
|
|
||||||
|
|
||||||
buffer.cursorX = 0
|
buffer.cursorX = 0
|
||||||
buffer.Index()
|
buffer.Index()
|
||||||
|
|
|
@ -9,12 +9,49 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"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) {
|
func TestOffsets(t *testing.T) {
|
||||||
b := NewBuffer(10, 3, CellAttributes{})
|
b := NewBuffer(10, 3, CellAttributes{})
|
||||||
b.Write([]rune("hello\r\n")...)
|
b.Write([]rune("hello")...)
|
||||||
b.Write([]rune("hello\r\n")...)
|
b.CarriageReturn()
|
||||||
b.Write([]rune("hello\r\n")...)
|
b.NewLine()
|
||||||
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.Write([]rune("hello")...)
|
||||||
assert.Equal(t, uint16(10), b.ViewWidth())
|
assert.Equal(t, uint16(10), b.ViewWidth())
|
||||||
assert.Equal(t, uint16(10), b.Width())
|
assert.Equal(t, uint16(10), b.Width())
|
||||||
|
@ -84,14 +121,14 @@ func TestWritingNewLineAsFirstRuneOnWrappedLine(t *testing.T) {
|
||||||
b.Write('a', 'b', 'c')
|
b.Write('a', 'b', 'c')
|
||||||
assert.Equal(t, uint16(3), b.cursorX)
|
assert.Equal(t, uint16(3), b.cursorX)
|
||||||
assert.Equal(t, uint16(0), b.cursorY)
|
assert.Equal(t, uint16(0), b.cursorY)
|
||||||
b.Write(0x0a)
|
b.NewLine()
|
||||||
assert.Equal(t, uint16(0), b.cursorX)
|
assert.Equal(t, uint16(0), b.cursorX)
|
||||||
assert.Equal(t, uint16(1), b.cursorY)
|
assert.Equal(t, uint16(1), b.cursorY)
|
||||||
|
|
||||||
b.Write('d', 'e', 'f')
|
b.Write('d', 'e', 'f')
|
||||||
assert.Equal(t, uint16(3), b.cursorX)
|
assert.Equal(t, uint16(3), b.cursorX)
|
||||||
assert.Equal(t, uint16(1), b.cursorY)
|
assert.Equal(t, uint16(1), b.cursorY)
|
||||||
b.Write(0x0a)
|
b.NewLine()
|
||||||
|
|
||||||
assert.Equal(t, uint16(0), b.cursorX)
|
assert.Equal(t, uint16(0), b.cursorX)
|
||||||
assert.Equal(t, uint16(2), b.cursorY)
|
assert.Equal(t, uint16(2), b.cursorY)
|
||||||
|
@ -114,11 +151,11 @@ func TestWritingNewLineAsSecondRuneOnWrappedLine(t *testing.T) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
b.Write('a', 'b', 'c', 'd')
|
b.Write('a', 'b', 'c', 'd')
|
||||||
b.Write(0x0a)
|
b.NewLine()
|
||||||
b.Write('e', 'f')
|
b.Write('e', 'f')
|
||||||
b.Write(0x0a)
|
b.NewLine()
|
||||||
b.Write(0x0a)
|
b.NewLine()
|
||||||
b.Write(0x0a)
|
b.NewLine()
|
||||||
b.Write('z')
|
b.Write('z')
|
||||||
|
|
||||||
assert.Equal(t, "abc", b.lines[0].String())
|
assert.Equal(t, "abc", b.lines[0].String())
|
||||||
|
@ -170,19 +207,45 @@ func TestMovePosition(t *testing.T) {
|
||||||
func TestVisibleLines(t *testing.T) {
|
func TestVisibleLines(t *testing.T) {
|
||||||
|
|
||||||
b := NewBuffer(80, 10, CellAttributes{})
|
b := NewBuffer(80, 10, CellAttributes{})
|
||||||
b.Write([]rune("hello 1\r\n")...)
|
b.Write([]rune("hello 1")...)
|
||||||
b.Write([]rune("hello 2\r\n")...)
|
b.CarriageReturn()
|
||||||
b.Write([]rune("hello 3\r\n")...)
|
b.NewLine()
|
||||||
b.Write([]rune("hello 4\r\n")...)
|
b.Write([]rune("hello 2")...)
|
||||||
b.Write([]rune("hello 5\r\n")...)
|
b.CarriageReturn()
|
||||||
b.Write([]rune("hello 6\r\n")...)
|
b.NewLine()
|
||||||
b.Write([]rune("hello 7\r\n")...)
|
b.Write([]rune("hello 3")...)
|
||||||
b.Write([]rune("hello 8\r\n")...)
|
b.CarriageReturn()
|
||||||
b.Write([]rune("hello 9\r\n")...)
|
b.NewLine()
|
||||||
b.Write([]rune("hello 10\r\n")...)
|
b.Write([]rune("hello 4")...)
|
||||||
b.Write([]rune("hello 11\r\n")...)
|
b.CarriageReturn()
|
||||||
b.Write([]rune("hello 12\r\n")...)
|
b.NewLine()
|
||||||
b.Write([]rune("hello 13\r\n")...)
|
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")...)
|
b.Write([]rune("hello 14")...)
|
||||||
|
|
||||||
lines := b.GetVisibleLines()
|
lines := b.GetVisibleLines()
|
||||||
|
@ -194,9 +257,13 @@ func TestVisibleLines(t *testing.T) {
|
||||||
|
|
||||||
func TestClearWithoutFullView(t *testing.T) {
|
func TestClearWithoutFullView(t *testing.T) {
|
||||||
b := NewBuffer(80, 10, CellAttributes{})
|
b := NewBuffer(80, 10, CellAttributes{})
|
||||||
b.Write([]rune("hello 1\r\n")...)
|
b.Write([]rune("hello 1")...)
|
||||||
b.Write([]rune("hello 2\r\n")...)
|
b.CarriageReturn()
|
||||||
b.Write([]rune("hello 3")...)
|
b.NewLine()
|
||||||
|
b.Write([]rune("hello 1")...)
|
||||||
|
b.CarriageReturn()
|
||||||
|
b.NewLine()
|
||||||
|
b.Write([]rune("hello 1")...)
|
||||||
b.Clear()
|
b.Clear()
|
||||||
lines := b.GetVisibleLines()
|
lines := b.GetVisibleLines()
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
|
@ -206,14 +273,28 @@ func TestClearWithoutFullView(t *testing.T) {
|
||||||
|
|
||||||
func TestClearWithFullView(t *testing.T) {
|
func TestClearWithFullView(t *testing.T) {
|
||||||
b := NewBuffer(80, 5, CellAttributes{})
|
b := NewBuffer(80, 5, CellAttributes{})
|
||||||
b.Write([]rune("hello 1\r\n")...)
|
b.Write([]rune("hello 1")...)
|
||||||
b.Write([]rune("hello 2\r\n")...)
|
b.CarriageReturn()
|
||||||
b.Write([]rune("hello 3\r\n")...)
|
b.NewLine()
|
||||||
b.Write([]rune("hello 4\r\n")...)
|
b.Write([]rune("hello 1")...)
|
||||||
b.Write([]rune("hello 5\r\n")...)
|
b.CarriageReturn()
|
||||||
b.Write([]rune("hello 6\r\n")...)
|
b.NewLine()
|
||||||
b.Write([]rune("hello 7\r\n")...)
|
b.Write([]rune("hello 1")...)
|
||||||
b.Write([]rune("hello 8\r\n")...)
|
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()
|
b.Clear()
|
||||||
lines := b.GetVisibleLines()
|
lines := b.GetVisibleLines()
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
|
@ -241,7 +322,10 @@ func TestCarriageReturnOnFullLine(t *testing.T) {
|
||||||
|
|
||||||
func TestCarriageReturnOnFullLastLine(t *testing.T) {
|
func TestCarriageReturnOnFullLastLine(t *testing.T) {
|
||||||
b := NewBuffer(20, 2, CellAttributes{})
|
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()
|
lines := b.GetVisibleLines()
|
||||||
assert.Equal(t, "", lines[0].String())
|
assert.Equal(t, "", lines[0].String())
|
||||||
assert.Equal(t, "xxxxxxxxxxxxxxxxxxxx", lines[1].String())
|
assert.Equal(t, "xxxxxxxxxxxxxxxxxxxx", lines[1].String())
|
||||||
|
@ -249,7 +333,10 @@ func TestCarriageReturnOnFullLastLine(t *testing.T) {
|
||||||
|
|
||||||
func TestCarriageReturnOnWrappedLine(t *testing.T) {
|
func TestCarriageReturnOnWrappedLine(t *testing.T) {
|
||||||
b := NewBuffer(80, 6, CellAttributes{})
|
b := NewBuffer(80, 6, CellAttributes{})
|
||||||
b.Write([]rune("hello!\rsecret")...)
|
b.Write([]rune("hello!")...)
|
||||||
|
b.CarriageReturn()
|
||||||
|
b.Write([]rune("secret")...)
|
||||||
|
|
||||||
lines := b.GetVisibleLines()
|
lines := b.GetVisibleLines()
|
||||||
assert.Equal(t, "secret", lines[0].String())
|
assert.Equal(t, "secret", lines[0].String())
|
||||||
}
|
}
|
||||||
|
@ -257,14 +344,22 @@ func TestCarriageReturnOnWrappedLine(t *testing.T) {
|
||||||
func TestCarriageReturnOnLineThatDoesntExist(t *testing.T) {
|
func TestCarriageReturnOnLineThatDoesntExist(t *testing.T) {
|
||||||
b := NewBuffer(6, 10, CellAttributes{})
|
b := NewBuffer(6, 10, CellAttributes{})
|
||||||
b.cursorY = 3
|
b.cursorY = 3
|
||||||
b.Write('\r')
|
b.CarriageReturn()
|
||||||
assert.Equal(t, uint16(0), b.cursorX)
|
assert.Equal(t, uint16(0), b.cursorX)
|
||||||
assert.Equal(t, uint16(3), b.cursorY)
|
assert.Equal(t, uint16(3), b.cursorY)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetCell(t *testing.T) {
|
func TestGetCell(t *testing.T) {
|
||||||
b := NewBuffer(80, 20, CellAttributes{})
|
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)
|
cell := b.GetCell(8, 2)
|
||||||
require.NotNil(t, cell)
|
require.NotNil(t, cell)
|
||||||
assert.Equal(t, 'g', cell.Rune())
|
assert.Equal(t, 'g', cell.Rune())
|
||||||
|
@ -272,7 +367,17 @@ func TestGetCell(t *testing.T) {
|
||||||
|
|
||||||
func TestGetCellWithHistory(t *testing.T) {
|
func TestGetCellWithHistory(t *testing.T) {
|
||||||
b := NewBuffer(80, 2, CellAttributes{})
|
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)
|
cell := b.GetCell(8, 1)
|
||||||
require.NotNil(t, cell)
|
require.NotNil(t, cell)
|
||||||
assert.Equal(t, 'g', cell.Rune())
|
assert.Equal(t, 'g', cell.Rune())
|
||||||
|
@ -301,7 +406,35 @@ func TestCursorPositionQuerying(t *testing.T) {
|
||||||
|
|
||||||
func TestRawPositionQuerying(t *testing.T) {
|
func TestRawPositionQuerying(t *testing.T) {
|
||||||
b := NewBuffer(80, 5, CellAttributes{})
|
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.cursorX = 3
|
||||||
b.cursorY = 4
|
b.cursorY = 4
|
||||||
assert.Equal(t, uint64(9), b.RawLine())
|
assert.Equal(t, uint64(9), b.RawLine())
|
||||||
|
@ -310,7 +443,10 @@ func TestRawPositionQuerying(t *testing.T) {
|
||||||
// CSI 2 K
|
// CSI 2 K
|
||||||
func TestEraseLine(t *testing.T) {
|
func TestEraseLine(t *testing.T) {
|
||||||
b := NewBuffer(80, 5, CellAttributes{})
|
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()
|
b.EraseLine()
|
||||||
assert.Equal(t, "hello, this is a test", b.lines[0].String())
|
assert.Equal(t, "hello, this is a test", b.lines[0].String())
|
||||||
assert.Equal(t, "", b.lines[1].String())
|
assert.Equal(t, "", b.lines[1].String())
|
||||||
|
@ -319,7 +455,11 @@ func TestEraseLine(t *testing.T) {
|
||||||
// CSI 1 K
|
// CSI 1 K
|
||||||
func TestEraseLineToCursor(t *testing.T) {
|
func TestEraseLineToCursor(t *testing.T) {
|
||||||
b := NewBuffer(80, 5, CellAttributes{})
|
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.MovePosition(-3, 0)
|
||||||
b.EraseLineToCursor()
|
b.EraseLineToCursor()
|
||||||
assert.Equal(t, "hello, this is a test", b.lines[0].String())
|
assert.Equal(t, "hello, this is a test", b.lines[0].String())
|
||||||
|
@ -329,7 +469,10 @@ func TestEraseLineToCursor(t *testing.T) {
|
||||||
// CSI 0 K
|
// CSI 0 K
|
||||||
func TestEraseLineAfterCursor(t *testing.T) {
|
func TestEraseLineAfterCursor(t *testing.T) {
|
||||||
b := NewBuffer(80, 5, CellAttributes{})
|
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.MovePosition(-3, 0)
|
||||||
b.EraseLineFromCursor()
|
b.EraseLineFromCursor()
|
||||||
assert.Equal(t, "hello, this is a test", b.lines[0].String())
|
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) {
|
func TestEraseDisplay(t *testing.T) {
|
||||||
b := NewBuffer(80, 5, CellAttributes{})
|
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.MovePosition(2, 1)
|
||||||
b.EraseDisplay()
|
b.EraseDisplay()
|
||||||
lines := b.GetVisibleLines()
|
lines := b.GetVisibleLines()
|
||||||
|
@ -347,7 +496,13 @@ func TestEraseDisplay(t *testing.T) {
|
||||||
}
|
}
|
||||||
func TestEraseDisplayToCursor(t *testing.T) {
|
func TestEraseDisplayToCursor(t *testing.T) {
|
||||||
b := NewBuffer(80, 5, CellAttributes{})
|
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.MovePosition(-2, 0)
|
||||||
b.EraseDisplayToCursor()
|
b.EraseDisplayToCursor()
|
||||||
lines := b.GetVisibleLines()
|
lines := b.GetVisibleLines()
|
||||||
|
@ -359,7 +514,13 @@ func TestEraseDisplayToCursor(t *testing.T) {
|
||||||
|
|
||||||
func TestEraseDisplayFromCursor(t *testing.T) {
|
func TestEraseDisplayFromCursor(t *testing.T) {
|
||||||
b := NewBuffer(80, 5, CellAttributes{})
|
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.MovePosition(-3, -1)
|
||||||
b.EraseDisplayFromCursor()
|
b.EraseDisplayFromCursor()
|
||||||
lines := b.GetVisibleLines()
|
lines := b.GetVisibleLines()
|
||||||
|
@ -381,9 +542,12 @@ func TestHorizontalResizeView(t *testing.T) {
|
||||||
b := NewBuffer(80, 10, CellAttributes{})
|
b := NewBuffer(80, 10, CellAttributes{})
|
||||||
|
|
||||||
// 60 characters
|
// 60 characters
|
||||||
b.Write([]rune(
|
b.Write([]rune(`hellohellohellohellohellohellohellohellohellohellohellohello`)...)
|
||||||
`hellohellohellohellohellohellohellohellohellohellohellohello
|
|
||||||
goodbyegoodbye`)...)
|
b.CarriageReturn()
|
||||||
|
b.NewLine()
|
||||||
|
|
||||||
|
b.Write([]rune(`goodbyegoodbye`)...)
|
||||||
|
|
||||||
require.Equal(t, uint16(14), b.cursorX)
|
require.Equal(t, uint16(14), b.cursorX)
|
||||||
require.Equal(t, uint16(1), b.cursorY)
|
require.Equal(t, uint16(1), b.cursorY)
|
||||||
|
|
|
@ -40,10 +40,16 @@ func (cell *Cell) Rune() rune {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cell *Cell) Fg() [3]float32 {
|
func (cell *Cell) Fg() [3]float32 {
|
||||||
|
if cell.Attr().Reverse {
|
||||||
|
return cell.attr.BgColour
|
||||||
|
}
|
||||||
return cell.attr.FgColour
|
return cell.attr.FgColour
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cell *Cell) Bg() [3]float32 {
|
func (cell *Cell) Bg() [3]float32 {
|
||||||
|
if cell.Attr().Reverse {
|
||||||
|
return cell.attr.FgColour
|
||||||
|
}
|
||||||
return cell.attr.BgColour
|
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"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/liamg/aminal/config"
|
"github.com/liamg/aminal/config"
|
||||||
"github.com/liamg/aminal/version"
|
"github.com/liamg/aminal/version"
|
||||||
|
@ -48,10 +49,16 @@ func loadConfigFile() *config.Config {
|
||||||
return &config.DefaultConfig
|
return &config.DefaultConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
places := []string{
|
places := []string{}
|
||||||
fmt.Sprintf("%s/.aminal.toml", home),
|
|
||||||
|
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 {
|
for _, place := range places {
|
||||||
if b, err := ioutil.ReadFile(place); err == nil {
|
if b, err := ioutil.ReadFile(place); err == nil {
|
||||||
if c, err := config.Parse(b); 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 {
|
if b, err := config.DefaultConfig.Encode(); err != nil {
|
||||||
fmt.Printf("Failed to encode config file: %s\n", err)
|
fmt.Printf("Failed to encode config file: %s\n", err)
|
||||||
} else {
|
} 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)
|
fmt.Printf("Failed to encode config file: %s\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
|
|
||||||
type KeyCombination struct {
|
type KeyCombination struct {
|
||||||
mods glfw.ModifierKey
|
mods glfw.ModifierKey
|
||||||
key glfw.Key
|
char rune
|
||||||
}
|
}
|
||||||
|
|
||||||
type KeyMod string
|
type KeyMod string
|
||||||
|
@ -28,41 +28,11 @@ var modMap = map[KeyMod]glfw.ModifierKey{
|
||||||
super: glfw.ModSuper,
|
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"
|
// keyStr e.g. "ctrl + alt + a"
|
||||||
func parseKeyCombination(keyStr string) (*KeyCombination, error) {
|
func parseKeyCombination(keyStr string) (*KeyCombination, error) {
|
||||||
|
|
||||||
var mods glfw.ModifierKey
|
var mods glfw.ModifierKey
|
||||||
var key *glfw.Key
|
var key rune
|
||||||
|
|
||||||
keys := strings.Split(keyStr, "+")
|
keys := strings.Split(keyStr, "+")
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
|
@ -72,19 +42,15 @@ func parseKeyCombination(keyStr string) (*KeyCombination, error) {
|
||||||
mods = mods + mod
|
mods = mods + mod
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mappedKey, ok := keyMap[k]
|
|
||||||
if ok {
|
if key > 0 {
|
||||||
if key != nil {
|
return nil, fmt.Errorf("Multiple non-modifier keys specified in keyboard shortcut")
|
||||||
return nil, fmt.Errorf("Multiple non-modifier keys specified in keyboard shortcut")
|
|
||||||
}
|
|
||||||
key = &mappedKey
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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")
|
return nil, fmt.Errorf("No non-modifier key specified in keyboard shortcut")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,12 +60,12 @@ func parseKeyCombination(keyStr string) (*KeyCombination, error) {
|
||||||
|
|
||||||
return &KeyCombination{
|
return &KeyCombination{
|
||||||
mods: mods,
|
mods: mods,
|
||||||
key: *key,
|
char: key,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (combi KeyCombination) Match(pressedMods glfw.ModifierKey, pressedKey glfw.Key) bool {
|
func (combi KeyCombination) Match(pressedMods glfw.ModifierKey, pressedChar rune) bool {
|
||||||
return pressedKey == combi.key && pressedMods == combi.mods
|
return pressedChar == combi.char && pressedMods == combi.mods
|
||||||
}
|
}
|
||||||
|
|
||||||
func (keyMapConfig KeyMappingConfig) GenerateActionMap() (map[UserAction]*KeyCombination, error) {
|
func (keyMapConfig KeyMappingConfig) GenerateActionMap() (map[UserAction]*KeyCombination, error) {
|
||||||
|
|
|
@ -14,14 +14,14 @@ func TestKeyCombinations(t *testing.T) {
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.NotNil(t, combi)
|
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.Equal(t, glfw.ModControl+glfw.ModAlt, combi.mods)
|
||||||
|
|
||||||
assert.True(t, combi.Match(glfw.ModControl^glfw.ModAlt, glfw.KeyA))
|
assert.True(t, combi.Match(glfw.ModControl^glfw.ModAlt, 'a'))
|
||||||
assert.False(t, combi.Match(glfw.ModControl^glfw.ModAlt, glfw.KeyB))
|
assert.False(t, combi.Match(glfw.ModControl^glfw.ModAlt, 'b'))
|
||||||
assert.False(t, combi.Match(glfw.ModControl, glfw.KeyA))
|
assert.False(t, combi.Match(glfw.ModControl, 'b'))
|
||||||
assert.False(t, combi.Match(glfw.ModAlt, glfw.KeyA))
|
assert.False(t, combi.Match(glfw.ModAlt, 'd'))
|
||||||
assert.False(t, combi.Match(0, glfw.KeyA))
|
assert.False(t, combi.Match(0, 'e'))
|
||||||
assert.False(t, combi.Match(glfw.ModControl^glfw.ModAlt^glfw.ModShift, glfw.KeyA))
|
assert.False(t, combi.Match(glfw.ModControl^glfw.ModAlt^glfw.ModShift, 'f'))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,15 @@ package glfont
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/draw"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/go-gl/gl/all-core/gl"
|
"github.com/go-gl/gl/all-core/gl"
|
||||||
"github.com/golang/freetype"
|
"github.com/golang/freetype"
|
||||||
"github.com/golang/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
"golang.org/x/image/font"
|
"golang.org/x/image/font"
|
||||||
"golang.org/x/image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
"image"
|
|
||||||
"image/draw"
|
|
||||||
"io"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const DPI = 72
|
const DPI = 72
|
||||||
|
@ -198,18 +199,18 @@ func (f *Font) Size(text string) (float32, float32) {
|
||||||
return width, height
|
return width, height
|
||||||
}
|
}
|
||||||
|
|
||||||
func(f *Font) MaxSize() (float32, float32){
|
func (f *Font) MaxSize() (float32, float32) {
|
||||||
b:= f.ttf.Bounds(fixed.Int26_6(f.scale))
|
b := f.ttf.Bounds(fixed.Int26_6(f.scale))
|
||||||
return float32(b.Max.X - b.Min.X),float32(b.Max.Y - b.Min.Y)
|
return float32(b.Max.X - b.Min.X), float32(b.Max.Y - b.Min.Y)
|
||||||
}
|
}
|
||||||
|
|
||||||
func(f *Font) MinY() float32 {
|
func (f *Font) MinY() float32 {
|
||||||
b:= f.ttf.Bounds(fixed.Int26_6(f.scale))
|
b := f.ttf.Bounds(fixed.Int26_6(f.scale))
|
||||||
return float32(b.Min.Y)
|
return float32(b.Min.Y)
|
||||||
}
|
}
|
||||||
|
|
||||||
func(f *Font) MaxY() float32 {
|
func (f *Font) MaxY() float32 {
|
||||||
b:= f.ttf.Bounds(fixed.Int26_6(f.scale))
|
b := f.ttf.Bounds(fixed.Int26_6(f.scale))
|
||||||
return float32(b.Max.Y)
|
return float32(b.Max.Y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ void main()
|
||||||
|
|
||||||
var vertexFontShader = `#version 150 core
|
var vertexFontShader = `#version 150 core
|
||||||
|
|
||||||
//vertex position
|
//vertex position
|
||||||
in vec2 vert;
|
in vec2 vert;
|
||||||
|
|
||||||
//pass through to fragTexCoord
|
//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]
|
cell := cells[x]
|
||||||
|
|
||||||
var colour *[3]float32
|
var colour [3]float32 = cell.Fg()
|
||||||
var alpha float32 = 0.6
|
var alpha float32 = 0.6
|
||||||
|
|
||||||
if y == int(a.hint.StartY) {
|
if y == int(a.hint.StartY) {
|
||||||
if x >= int(a.hint.StartX) && x <= int(a.hint.StartX+uint16(len(a.hint.Word))) {
|
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
|
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 {
|
type FontMap struct {
|
||||||
defaultFont *glfont.Font
|
defaultFont *glfont.Font
|
||||||
defaultBoldFont *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 {
|
func NewFontMap(defaultFont *glfont.Font, defaultBoldFont *glfont.Font) *FontMap {
|
||||||
return &FontMap{
|
return &FontMap{
|
||||||
defaultFont: defaultFont,
|
defaultFont: defaultFont,
|
||||||
defaultBoldFont: defaultBoldFont,
|
defaultBoldFont: defaultBoldFont,
|
||||||
runeMap: map[rune]*glfont.Font{},
|
|
||||||
ranges: map[runeRange]*glfont.Font{},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fm *FontMap) UpdateResolution(w int, h int) {
|
func (fm *FontMap) UpdateResolution(w int, h int) {
|
||||||
fm.defaultFont.UpdateResolution(w, h)
|
fm.defaultFont.UpdateResolution(w, h)
|
||||||
fm.defaultBoldFont.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 {
|
func (fm *FontMap) DefaultFont() *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
|
|
||||||
}
|
|
||||||
|
|
||||||
return fm.defaultFont
|
return fm.defaultFont
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fm *FontMap) GetBoldFont(r rune) *glfont.Font {
|
func (fm *FontMap) BoldFont() *glfont.Font {
|
||||||
if r <= 0xff {
|
|
||||||
return fm.defaultBoldFont
|
|
||||||
}
|
|
||||||
|
|
||||||
if f := fm.findOverride(r); f != nil {
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
return fm.defaultBoldFont
|
return fm.defaultBoldFont
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,12 +39,11 @@ func (gui *GUI) loadFonts() error {
|
||||||
|
|
||||||
if gui.fontMap == nil {
|
if gui.fontMap == nil {
|
||||||
gui.fontMap = NewFontMap(defaultFont, boldFont)
|
gui.fontMap = NewFontMap(defaultFont, boldFont)
|
||||||
}else{
|
} else {
|
||||||
gui.fontMap.defaultFont = defaultFont
|
gui.fontMap.defaultFont = defaultFont
|
||||||
gui.fontMap.defaultBoldFont = boldFont
|
gui.fontMap.defaultBoldFont = boldFont
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// add special non-ascii fonts here
|
// add special non-ascii fonts here
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
212
gui/gui.go
212
gui/gui.go
|
@ -6,6 +6,7 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-gl/gl/all-core/gl"
|
"github.com/go-gl/gl/all-core/gl"
|
||||||
|
@ -33,6 +34,7 @@ type GUI struct {
|
||||||
terminalAlpha float32
|
terminalAlpha float32
|
||||||
showDebugInfo bool
|
showDebugInfo bool
|
||||||
keyboardShortcuts map[config.UserAction]*config.KeyCombination
|
keyboardShortcuts map[config.UserAction]*config.KeyCombination
|
||||||
|
resizeLock *sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(config *config.Config, terminal *terminal.Terminal, logger *zap.SugaredLogger) (*GUI, error) {
|
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,
|
fontScale: 14.0,
|
||||||
terminalAlpha: 1,
|
terminalAlpha: 1,
|
||||||
keyboardShortcuts: shortcuts,
|
keyboardShortcuts: shortcuts,
|
||||||
|
resizeLock: &sync.Mutex{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +68,9 @@ func (gui *GUI) scale() float32 {
|
||||||
// can only be called on OS thread
|
// can only be called on OS thread
|
||||||
func (gui *GUI) resize(w *glfw.Window, width int, height int) {
|
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.logger.Debugf("Initiating GUI resize to %dx%d", width, height)
|
||||||
|
|
||||||
gui.width = width
|
gui.width = width
|
||||||
|
@ -91,6 +97,8 @@ func (gui *GUI) resize(w *glfw.Window, width int, height int) {
|
||||||
|
|
||||||
gui.logger.Debugf("Resize complete!")
|
gui.logger.Debugf("Resize complete!")
|
||||||
|
|
||||||
|
gui.redraw(buffer.NewBackgroundCell(gui.config.ColourScheme.Background))
|
||||||
|
gui.window.SwapBuffers()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *GUI) getTermSize() (uint, uint) {
|
func (gui *GUI) getTermSize() (uint, uint) {
|
||||||
|
@ -205,6 +213,7 @@ func (gui *GUI) Render() error {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
showMessage := true
|
||||||
|
|
||||||
for !gui.window.ShouldClose() {
|
for !gui.window.ShouldClose() {
|
||||||
|
|
||||||
|
@ -218,66 +227,7 @@ func (gui *GUI) Render() error {
|
||||||
|
|
||||||
if gui.terminal.CheckDirty() {
|
if gui.terminal.CheckDirty() {
|
||||||
|
|
||||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
|
gui.redraw(defaultCell)
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
if gui.showDebugInfo {
|
if gui.showDebugInfo {
|
||||||
gui.textbox(2, 2, fmt.Sprintf(`Cursor: %d,%d
|
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 {
|
if showMessage {
|
||||||
time.AfterFunc(time.Second, gui.terminal.SetDirty)
|
if latestVersion != "" && time.Since(startTime) < time.Second*10 && gui.terminal.ActiveBuffer().RawLine() == 0 {
|
||||||
_, h := gui.terminal.GetSize()
|
time.AfterFunc(time.Second, gui.terminal.SetDirty)
|
||||||
var msg string
|
_, h := gui.terminal.GetSize()
|
||||||
if version.Version == "" {
|
var msg string
|
||||||
msg = "You are using a development build of Aminal."
|
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 {
|
} 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) {
|
func (gui *GUI) createWindow() (*glfw.Window, error) {
|
||||||
if err := glfw.Init(); err != nil {
|
if err := glfw.Init(); err != nil {
|
||||||
return nil, fmt.Errorf("Failed to initialise GLFW: %s", err)
|
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])
|
window, err = gui.createWindowWithOpenGLVersion(v[0], v[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gui.logger.Warnf("Failed to create window: %s. Will attempt older version...", err)
|
gui.logger.Warnf("Failed to create window: %s. Will attempt older version...", err)
|
||||||
}else{
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -369,7 +409,7 @@ func (gui *GUI) createWindow() (*glfw.Window, error) {
|
||||||
return window, nil
|
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.ContextVersionMajor, major)
|
||||||
glfw.WindowHint(glfw.ContextVersionMinor, minor)
|
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)
|
window, err := glfw.CreateWindow(gui.width, gui.height, "Terminal", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e := err.Error()
|
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:]), ".")
|
v := strings.Split(strings.TrimSpace(e[i+14:]), ".")
|
||||||
if len(v) == 2 {
|
if len(v) == 2 {
|
||||||
major, err := strconv.Atoi(v[0])
|
maj, mjErr := strconv.Atoi(v[0])
|
||||||
if err == nil {
|
if mjErr == nil {
|
||||||
if minor, err := strconv.Atoi(v[1]); err == nil {
|
if min, miErr := strconv.Atoi(v[1]); miErr == nil {
|
||||||
return gui.createWindowWithOpenGLVersion(major, minor)
|
return gui.createWindowWithOpenGLVersion(maj, min)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -395,6 +435,7 @@ func(gui *GUI) createWindowWithOpenGLVersion(major int, minor int) (*glfw.Window
|
||||||
|
|
||||||
return window, nil
|
return window, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// initOpenGL initializes OpenGL and returns an intiialized program.
|
// initOpenGL initializes OpenGL and returns an intiialized program.
|
||||||
func (gui *GUI) createProgram() (uint32, error) {
|
func (gui *GUI) createProgram() (uint32, error) {
|
||||||
if err := gl.Init(); err != nil {
|
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)
|
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
|
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) {
|
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 {
|
||||||
|
@ -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 {
|
// get key name to handle alternative keyboard layouts
|
||||||
|
name := glfw.GetKeyName(key, scancode)
|
||||||
if shortcut.Match(mods, key) {
|
if len(name) == 1 {
|
||||||
|
r := rune(name[0])
|
||||||
f, ok := actionMap[userAction]
|
for userAction, shortcut := range gui.keyboardShortcuts {
|
||||||
if ok {
|
if shortcut.Match(mods, r) {
|
||||||
f(gui)
|
f, ok := actionMap[userAction]
|
||||||
break
|
if ok {
|
||||||
|
f(gui)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch key {
|
// standard ctrl codes e.g. ^C
|
||||||
case glfw.KeyD:
|
if modsPressed(mods, glfw.ModControl) {
|
||||||
|
if r >= 97 && r < 123 {
|
||||||
case glfw.KeyG:
|
gui.terminal.Write([]byte{byte(r) - 96})
|
||||||
|
return
|
||||||
case glfw.KeyR:
|
} else if r >= 65 && r < 91 {
|
||||||
gui.launchTarget("https://github.com/liamg/aminal/issues/new/choose")
|
gui.terminal.Write([]byte{byte(r) - 64})
|
||||||
case glfw.KeySemicolon:
|
|
||||||
gui.config.Slomo = !gui.config.Slomo
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
modStr := ""
|
modStr := getModStr(mods)
|
||||||
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"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
switch key {
|
switch key {
|
||||||
case glfw.KeyF1:
|
case glfw.KeyF1:
|
||||||
|
@ -240,10 +167,14 @@ func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Acti
|
||||||
'3', '~',
|
'3', '~',
|
||||||
})
|
})
|
||||||
case glfw.KeyHome:
|
case glfw.KeyHome:
|
||||||
if modStr == "" {
|
if gui.terminal.IsApplicationCursorKeysModeEnabled() {
|
||||||
gui.terminal.Write([]byte("\x1b[1~"))
|
if modStr == "" {
|
||||||
|
gui.terminal.Write([]byte("\x1b[1~"))
|
||||||
|
} else {
|
||||||
|
gui.terminal.Write([]byte(fmt.Sprintf("\x1b[1;%s~", modStr)))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
gui.terminal.Write([]byte(fmt.Sprintf("\x1b[1;%s~", modStr)))
|
gui.terminal.Write([]byte("\x1b[H"))
|
||||||
}
|
}
|
||||||
case glfw.KeyEnd:
|
case glfw.KeyEnd:
|
||||||
if modStr == "" {
|
if modStr == "" {
|
||||||
|
@ -276,17 +207,9 @@ func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Acti
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
case glfw.KeyTab:
|
case glfw.KeyTab:
|
||||||
if gui.terminal.IsApplicationCursorKeysModeEnabled() {
|
gui.terminal.Write([]byte{
|
||||||
gui.terminal.Write([]byte{
|
0x09,
|
||||||
0x1b,
|
})
|
||||||
'O',
|
|
||||||
'I',
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
gui.terminal.Write([]byte{
|
|
||||||
0x09,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
case glfw.KeyEnter:
|
case glfw.KeyEnter:
|
||||||
gui.terminal.Write([]byte{
|
gui.terminal.Write([]byte{
|
||||||
0x0d,
|
0x0d,
|
||||||
|
@ -304,7 +227,11 @@ func (gui *GUI) key(w *glfw.Window, key glfw.Key, scancode int, action glfw.Acti
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
case glfw.KeyBackspace:
|
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:
|
case glfw.KeyUp:
|
||||||
if modStr != "" {
|
if modStr != "" {
|
||||||
gui.terminal.Write([]byte(fmt.Sprintf("\x1b[1;%sA", 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)
|
halfAreaWidth := float32(r.areaWidth / 2)
|
||||||
halfAreaHeight := float32(r.areaHeight / 2)
|
halfAreaHeight := float32(r.areaHeight / 2)
|
||||||
|
|
||||||
|
|
||||||
x = (x - halfAreaWidth) / halfAreaWidth
|
x = (x - halfAreaWidth) / halfAreaWidth
|
||||||
y = -(y - ( halfAreaHeight)) / halfAreaHeight
|
y = -(y - (halfAreaHeight)) / halfAreaHeight
|
||||||
w := r.cellWidth / halfAreaWidth
|
w := r.cellWidth / halfAreaWidth
|
||||||
h := (r.cellHeight ) / halfAreaHeight
|
h := (r.cellHeight) / halfAreaHeight
|
||||||
|
|
||||||
rect := &rectangle{
|
rect := &rectangle{
|
||||||
points: []float32{
|
points: []float32{
|
||||||
|
@ -162,10 +161,10 @@ func (r *OpenGLRenderer) SetArea(areaX int, areaY int, areaWidth int, areaHeight
|
||||||
r.areaHeight = areaHeight
|
r.areaHeight = areaHeight
|
||||||
r.areaX = areaX
|
r.areaX = areaX
|
||||||
r.areaY = areaY
|
r.areaY = areaY
|
||||||
f := r.fontMap.GetFont('X')
|
f := r.fontMap.DefaultFont()
|
||||||
_, r.cellHeight = f.MaxSize()
|
_, r.cellHeight = f.MaxSize()
|
||||||
r.cellWidth, _ = f.Size("X")
|
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.termCols = uint(math.Floor(float64(float32(r.areaWidth) / r.cellWidth)))
|
||||||
r.termRows = uint(math.Floor(float64(float32(r.areaHeight) / r.cellHeight)))
|
r.termRows = uint(math.Floor(float64(float32(r.areaHeight) / r.cellHeight)))
|
||||||
r.rectangles = map[[2]uint]*rectangle{}
|
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)
|
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)
|
r.rectangles[[2]uint{col, row}] = r.newRectangle(x, y, r.colourAttr)
|
||||||
return r.rectangles[[2]uint{col, row}]
|
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 {
|
if cursor {
|
||||||
bg = r.config.ColourScheme.Cursor
|
bg = r.config.ColourScheme.Cursor
|
||||||
} else if cell.Attr().Reverse {
|
|
||||||
bg = cell.Fg()
|
|
||||||
} else {
|
} else {
|
||||||
bg = cell.Bg()
|
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
|
var f *glfont.Font
|
||||||
|
if bold {
|
||||||
if colour != nil {
|
f = r.fontMap.BoldFont()
|
||||||
fg = *colour
|
|
||||||
} else if cell.Attr().Reverse {
|
|
||||||
fg = cell.Bg()
|
|
||||||
} else {
|
} else {
|
||||||
fg = cell.Fg()
|
f = r.fontMap.DefaultFont()
|
||||||
}
|
}
|
||||||
|
|
||||||
f := r.fontMap.GetFont(cell.Rune())
|
f.SetColor(colour[0], colour[1], colour[2], alpha)
|
||||||
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)
|
|
||||||
|
|
||||||
x := float32(r.areaX) + float32(col)*r.cellWidth
|
x := float32(r.areaX) + float32(col)*r.cellWidth
|
||||||
y := float32(r.areaY) + (float32(row+1) * r.cellHeight) + f.MinY()
|
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) {
|
func (r *OpenGLRenderer) DrawCellImage(cell buffer.Cell, col uint, row uint) {
|
||||||
|
|
|
@ -24,7 +24,7 @@ const (
|
||||||
smooth in vec3 theColour;
|
smooth in vec3 theColour;
|
||||||
out vec4 outColour;
|
out vec4 outColour;
|
||||||
void main() {
|
void main() {
|
||||||
outColour = vec4(theColour, 1.0);
|
outColour = vec4(theColour, 1.0);
|
||||||
}
|
}
|
||||||
` + "\x00"
|
` + "\x00"
|
||||||
)
|
)
|
||||||
|
|
|
@ -89,7 +89,7 @@ DONE:
|
||||||
|
|
||||||
x := float32(col) * gui.renderer.cellWidth
|
x := float32(col) * gui.renderer.cellWidth
|
||||||
|
|
||||||
f := gui.fontMap.GetFont('X')
|
f := gui.fontMap.DefaultFont()
|
||||||
f.SetColor(fg[0], fg[1], fg[2], 1)
|
f.SetColor(fg[0], fg[1], fg[2], 1)
|
||||||
|
|
||||||
for i, line := range lines {
|
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)
|
logger.Fatalf("Failed to allocate pty: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
shellStr, err := loginshell.Shell()
|
shellStr := conf.Shell
|
||||||
if err != nil {
|
if shellStr == "" {
|
||||||
logger.Fatalf("Failed to ascertain your shell: %s", err)
|
loginShell, err := loginshell.Shell()
|
||||||
}
|
if err != nil {
|
||||||
|
logger.Fatalf("Failed to ascertain your shell: %s", err)
|
||||||
if conf.Shell != "" {
|
}
|
||||||
shellStr = conf.Shell
|
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...
|
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
|
package terminal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,21 +26,25 @@ var escapeSequenceMap = map[rune]escapeSequenceHandler{
|
||||||
|
|
||||||
func newLineSequenceHandler(pty chan rune, terminal *Terminal) error {
|
func newLineSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||||
terminal.ActiveBuffer().NewLine()
|
terminal.ActiveBuffer().NewLine()
|
||||||
|
terminal.isDirty = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func tabSequenceHandler(pty chan rune, terminal *Terminal) error {
|
func tabSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||||
terminal.ActiveBuffer().Tab()
|
terminal.ActiveBuffer().Tab()
|
||||||
|
terminal.isDirty = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func carriageReturnSequenceHandler(pty chan rune, terminal *Terminal) error {
|
func carriageReturnSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||||
terminal.ActiveBuffer().CarriageReturn()
|
terminal.ActiveBuffer().CarriageReturn()
|
||||||
|
terminal.isDirty = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func backspaceSequenceHandler(pty chan rune, terminal *Terminal) error {
|
func backspaceSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||||
terminal.ActiveBuffer().Backspace()
|
terminal.ActiveBuffer().Backspace()
|
||||||
|
terminal.isDirty = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,47 +68,33 @@ func shiftInSequenceHandler(pty chan rune, terminal *Terminal) error {
|
||||||
return nil
|
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
|
// https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||||
|
|
||||||
for {
|
var b rune
|
||||||
|
|
||||||
select {
|
for {
|
||||||
case <-terminal.pauseChan:
|
|
||||||
// @todo alert user when terminal is suspended
|
|
||||||
terminal.logger.Debugf("Terminal suspended")
|
|
||||||
<-terminal.resumeChan
|
|
||||||
case <-ctx.Done():
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
if terminal.config.Slomo {
|
if terminal.config.Slomo {
|
||||||
time.Sleep(time.Millisecond * 100)
|
time.Sleep(time.Millisecond * 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
b := <-pty
|
b = <-pty
|
||||||
|
|
||||||
terminal.logger.Debugf("0x%q", string(b))
|
if b < 0x20 {
|
||||||
|
if handler, ok := escapeSequenceMap[b]; ok {
|
||||||
handler, ok := escapeSequenceMap[b]
|
//terminal.logger.Debugf("Handling escape sequence: 0x%x", b)
|
||||||
|
if err := handler(pty, terminal); err != nil {
|
||||||
if ok {
|
terminal.logger.Errorf("Error handling escape sequence: %s", err)
|
||||||
//terminal.logger.Debugf("Handling escape sequence: 0x%x", b)
|
}
|
||||||
if err := handler(pty, terminal); err != nil {
|
terminal.isDirty = true
|
||||||
terminal.logger.Errorf("Error handling escape sequence: %s", err)
|
continue
|
||||||
}
|
|
||||||
} 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//terminal.logger.Debugf("Received character 0x%X: %q", b, string(b))
|
||||||
|
terminal.ActiveBuffer().Write(b)
|
||||||
terminal.isDirty = true
|
terminal.isDirty = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package terminal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
@ -35,7 +34,7 @@ const (
|
||||||
type Terminal struct {
|
type Terminal struct {
|
||||||
program uint32
|
program uint32
|
||||||
buffers []*buffer.Buffer
|
buffers []*buffer.Buffer
|
||||||
activeBufferIndex uint8
|
activeBuffer *buffer.Buffer
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
pty *os.File
|
pty *os.File
|
||||||
logger *zap.SugaredLogger
|
logger *zap.SugaredLogger
|
||||||
|
@ -43,8 +42,6 @@ type Terminal struct {
|
||||||
size Winsize
|
size Winsize
|
||||||
config *config.Config
|
config *config.Config
|
||||||
titleHandlers []chan bool
|
titleHandlers []chan bool
|
||||||
pauseChan chan bool
|
|
||||||
resumeChan chan bool
|
|
||||||
modes Modes
|
modes Modes
|
||||||
mouseMode MouseMode
|
mouseMode MouseMode
|
||||||
bracketedPasteMode bool
|
bracketedPasteMode bool
|
||||||
|
@ -87,13 +84,11 @@ func New(pty *os.File, logger *zap.SugaredLogger, config *config.Config) *Termin
|
||||||
logger: logger,
|
logger: logger,
|
||||||
config: config,
|
config: config,
|
||||||
titleHandlers: []chan bool{},
|
titleHandlers: []chan bool{},
|
||||||
pauseChan: make(chan bool, 1),
|
|
||||||
resumeChan: make(chan bool, 1),
|
|
||||||
modes: Modes{
|
modes: Modes{
|
||||||
ShowCursor: true,
|
ShowCursor: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
t.activeBuffer = t.buffers[0]
|
||||||
return t
|
return t
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -129,32 +124,30 @@ func (terminal *Terminal) GetMouseMode() MouseMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) UseMainBuffer() {
|
func (terminal *Terminal) UseMainBuffer() {
|
||||||
terminal.activeBufferIndex = MainBuffer
|
terminal.activeBuffer = terminal.buffers[MainBuffer]
|
||||||
terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height))
|
terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) UseAltBuffer() {
|
func (terminal *Terminal) UseAltBuffer() {
|
||||||
terminal.activeBufferIndex = AltBuffer
|
terminal.activeBuffer = terminal.buffers[AltBuffer]
|
||||||
terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height))
|
terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) UseInternalBuffer() {
|
func (terminal *Terminal) UseInternalBuffer() {
|
||||||
terminal.pauseChan <- true
|
terminal.activeBuffer = terminal.buffers[InternalBuffer]
|
||||||
terminal.activeBufferIndex = InternalBuffer
|
|
||||||
terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height))
|
terminal.SetSize(uint(terminal.size.Width), uint(terminal.size.Height))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) ExitInternalBuffer() {
|
func (terminal *Terminal) ExitInternalBuffer() {
|
||||||
terminal.activeBufferIndex = terminal.lastBuffer
|
terminal.activeBuffer = terminal.buffers[terminal.lastBuffer]
|
||||||
terminal.resumeChan <- true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) ActiveBuffer() *buffer.Buffer {
|
func (terminal *Terminal) ActiveBuffer() *buffer.Buffer {
|
||||||
return terminal.buffers[terminal.activeBufferIndex]
|
return terminal.activeBuffer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) UsingMainBuffer() bool {
|
func (terminal *Terminal) UsingMainBuffer() bool {
|
||||||
return terminal.activeBufferIndex == MainBuffer
|
return terminal.activeBuffer == terminal.buffers[MainBuffer]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (terminal *Terminal) GetScrollOffset() uint {
|
func (terminal *Terminal) GetScrollOffset() uint {
|
||||||
|
@ -255,20 +248,17 @@ func (terminal *Terminal) Read() error {
|
||||||
buffer := make(chan rune, 0xffff)
|
buffer := make(chan rune, 0xffff)
|
||||||
|
|
||||||
reader := bufio.NewReader(terminal.pty)
|
reader := bufio.NewReader(terminal.pty)
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
go terminal.processInput(ctx, buffer)
|
go terminal.processInput(buffer)
|
||||||
for {
|
for {
|
||||||
r, size, err := reader.ReadRune()
|
r, _, err := reader.ReadRune()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
} else if size > 0 {
|
|
||||||
buffer <- r
|
|
||||||
}
|
}
|
||||||
|
buffer <- r
|
||||||
}
|
}
|
||||||
|
|
||||||
//clean exit
|
//clean exit
|
||||||
|
|
Loading…
Reference in New Issue