mirror of
https://github.com/jroimartin/gocui.git
synced 2025-04-24 13:48:51 +08:00
Compare commits
196 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fc12c654f2 | ||
![]() |
031962f8d6 | ||
![]() |
de100503e6 | ||
![]() |
0214e0e872 | ||
![]() |
c055c87ae8 | ||
![]() |
2cda4f9f05 | ||
![]() |
491cd051cf | ||
![]() |
f2f6a1fab8 | ||
![]() |
df0b3dc8df | ||
![]() |
247e437c8e | ||
![]() |
1ec4d5dd73 | ||
![]() |
dc8ca8416c | ||
![]() |
25c0c27f2e | ||
![]() |
4f518eddb0 | ||
![]() |
eb73455bec | ||
![]() |
a251da5a9f | ||
![]() |
70497a1d13 | ||
![]() |
881edc4380 | ||
![]() |
6564cfcacb | ||
![]() |
75a7ad2750 | ||
![]() |
a4d0b30476 | ||
![]() |
ad91aa2b83 | ||
![]() |
2677ad0445 | ||
![]() |
c64aff6dc2 | ||
![]() |
7ba3ea9d2c | ||
![]() |
7e8dafc560 | ||
![]() |
e18bedd405 | ||
![]() |
70a276872a | ||
![]() |
0f8c8e3c9e | ||
![]() |
d1b6db5a49 | ||
![]() |
4316bb79d4 | ||
![]() |
c48e902a26 | ||
![]() |
a984617410 | ||
![]() |
93acb816a8 | ||
![]() |
612b0b2987 | ||
![]() |
5424b93b3b | ||
![]() |
ed41d1bd2c | ||
![]() |
139487f578 | ||
![]() |
e27f247a3e | ||
![]() |
88d2b471d4 | ||
![]() |
cfc4e0298f | ||
![]() |
aff177f6e3 | ||
![]() |
c690b943b6 | ||
![]() |
7ac95c981b | ||
![]() |
78f1aee25c | ||
![]() |
1ad1e27a52 | ||
![]() |
aacdc2698f | ||
![]() |
ba396278de | ||
![]() |
fced1c672f | ||
![]() |
f95667af1a | ||
![]() |
64e8d44eee | ||
![]() |
6b3dc12c32 | ||
![]() |
adef0057a9 | ||
![]() |
56a1633c76 | ||
![]() |
ff841413b6 | ||
![]() |
0975ddb2a8 | ||
![]() |
e41f0e8947 | ||
![]() |
308833a2cf | ||
![]() |
aa4ac778d3 | ||
![]() |
717fd3eaae | ||
![]() |
19125a0a67 | ||
![]() |
357a541add | ||
![]() |
fb08594c69 | ||
![]() |
6a590932dd | ||
![]() |
89ecdb537d | ||
![]() |
e5517b913e | ||
![]() |
df63f19bd6 | ||
![]() |
e38ba07224 | ||
![]() |
40dbad569f | ||
![]() |
fc121d98fd | ||
![]() |
ddbc9be671 | ||
![]() |
550f04e523 | ||
![]() |
d5cb1ac216 | ||
![]() |
a7019c8547 | ||
![]() |
c0ae071931 | ||
![]() |
16db12db96 | ||
![]() |
a109a3641c | ||
![]() |
463428abda | ||
![]() |
7779534f95 | ||
![]() |
ec14132e67 | ||
![]() |
1724fbebc8 | ||
![]() |
4e9ce9a8e2 | ||
![]() |
d822523f8c | ||
![]() |
cea263b118 | ||
![]() |
10d2bb60ea | ||
![]() |
76554e4f84 | ||
![]() |
8d16527c1d | ||
![]() |
6314568953 | ||
![]() |
2e62b6ba19 | ||
![]() |
375270bca0 | ||
![]() |
13f0442ee4 | ||
![]() |
f1f9c0fa53 | ||
![]() |
30f7d65597 | ||
![]() |
2a0623774f | ||
![]() |
5f143ef3de | ||
![]() |
873bc2c17d | ||
![]() |
aa556cf135 | ||
![]() |
41745b2a15 | ||
![]() |
2dcda558bf | ||
![]() |
132f267b70 | ||
![]() |
6f73260433 | ||
![]() |
a67a34cd60 | ||
![]() |
3758266eac | ||
![]() |
1b35b7cd26 | ||
![]() |
40dec91023 | ||
![]() |
8c49240a03 | ||
![]() |
0707386452 | ||
![]() |
490199421a | ||
![]() |
7ffb37ef13 | ||
![]() |
65dfdbf77a | ||
![]() |
336b3337d1 | ||
![]() |
a8eba6db38 | ||
![]() |
248e4f4437 | ||
![]() |
a09a166064 | ||
![]() |
28abcdb8e4 | ||
![]() |
e669e2b30d | ||
![]() |
bb01d13a95 | ||
![]() |
c5595daed4 | ||
![]() |
8dea948d50 | ||
![]() |
d0f0fa6008 | ||
![]() |
7f83fd5782 | ||
![]() |
02b399dbd8 | ||
![]() |
6a1d76b5c7 | ||
![]() |
5efdece4af | ||
![]() |
82070fd113 | ||
![]() |
7e313a536f | ||
![]() |
4aed924ceb | ||
![]() |
f42f45fad3 | ||
![]() |
ea4d0466c4 | ||
![]() |
260872b0f7 | ||
![]() |
b5f65e10ad | ||
![]() |
4b628e45b2 | ||
![]() |
639723c288 | ||
![]() |
faa12e105a | ||
![]() |
c36dfefa9b | ||
![]() |
e5bf60e36b | ||
![]() |
af4769f30e | ||
![]() |
e2590f1300 | ||
![]() |
4dfc4973e0 | ||
![]() |
4ad26839aa | ||
![]() |
60388aa361 | ||
![]() |
37e2387e4a | ||
![]() |
e009f09eb8 | ||
![]() |
ca0f876ab6 | ||
![]() |
e63645a119 | ||
![]() |
622e7cbdf9 | ||
![]() |
b1ad4a9fa7 | ||
![]() |
03998dd72c | ||
![]() |
f53d985c4e | ||
![]() |
0193dee642 | ||
![]() |
311dedb655 | ||
![]() |
0e85b51ed2 | ||
![]() |
0992dc1df0 | ||
![]() |
8fa01b0c0e | ||
![]() |
4c83f5bfa9 | ||
![]() |
708261503b | ||
![]() |
b1d190d0d7 | ||
![]() |
0814e8024f | ||
![]() |
6279571a82 | ||
![]() |
9b25959056 | ||
![]() |
3438be9f43 | ||
![]() |
f819237d78 | ||
![]() |
d786a4aec1 | ||
![]() |
c9c982ea9d | ||
![]() |
3607eb8e1c | ||
![]() |
d0c53d8574 | ||
![]() |
8de3a55f4e | ||
![]() |
2e0c0342dc | ||
![]() |
98a2fe7a6a | ||
![]() |
f5cd17c3cc | ||
![]() |
17f7615184 | ||
![]() |
c690c51bff | ||
![]() |
9404aacd27 | ||
![]() |
d3c84c7bf4 | ||
![]() |
2db4573e76 | ||
![]() |
45bec2b33c | ||
![]() |
25ba6858fb | ||
![]() |
8efd767c68 | ||
![]() |
b9ce982fe5 | ||
![]() |
cebc72c201 | ||
![]() |
9b902f9bec | ||
![]() |
9a9d962740 | ||
![]() |
7ed193e8f4 | ||
![]() |
a67c870eef | ||
![]() |
530f266854 | ||
![]() |
f78a0704f1 | ||
![]() |
382efdcc54 | ||
![]() |
264959b01e | ||
![]() |
0aed73291b | ||
![]() |
8d584203d4 | ||
![]() |
cbacee3e65 | ||
![]() |
6b5681b670 | ||
![]() |
9f2bfa7213 | ||
![]() |
4c22f4abd9 | ||
![]() |
553b48c903 | ||
![]() |
46b1452e71 |
27
AUTHORS
27
AUTHORS
@ -1,9 +1,30 @@
|
||||
# This is the official list of gocui authors for copyright purposes.
|
||||
|
||||
# Names should be added to this file as
|
||||
# Name or Organization <email address>
|
||||
# Name or Organization <email address> contribution
|
||||
# Contribution
|
||||
# The email address is not required for organizations.
|
||||
|
||||
# Please keep the list sorted.
|
||||
Roi Martin <jroi.martin@gmail.com>
|
||||
Main developer
|
||||
|
||||
Roi Martin (@nibble_ds) <jroi.martin@gmail.com>
|
||||
Ryan Sullivan <kayoticsully@gmail.com>
|
||||
Toggleable view frames
|
||||
|
||||
Matthieu Rakotojaona <matthieu.rakotojaona@gmail.com>
|
||||
Wrapped views
|
||||
|
||||
Harry Lawrence <hazbo@gmx.com>
|
||||
Basic mouse support
|
||||
|
||||
Danny Tylman <dtylman@gmail.com>
|
||||
Masked views
|
||||
|
||||
Frederik Deweerdt <frederik.deweerdt@gmail.com>
|
||||
Colored fonts
|
||||
|
||||
Henri Koski <henri.t.koski@gmail.com>
|
||||
Custom current view color
|
||||
|
||||
Dustin Willis Webber <dustin.webber@gmail.com>
|
||||
256-colors output mode support
|
||||
|
1
LICENSE
1
LICENSE
@ -1,5 +1,4 @@
|
||||
Copyright (c) 2014 The gocui Authors. All rights reserved.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
140
README.md
140
README.md
@ -1,44 +1,110 @@
|
||||
GOCUI - Go Console User Interface
|
||||
=================================
|
||||
# GOCUI - Go Console User Interface
|
||||
|
||||
Minimalist Go library aimed at creating Console User Interfaces.
|
||||
[](https://pkg.go.dev/github.com/jroimartin/gocui)
|
||||
|
||||
Installation
|
||||
------------
|
||||
go get github.com/jroimartin/gocui
|
||||
Minimalist Go package aimed at creating Console User Interfaces.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
godoc github.com/jroimartin/gocui
|
||||
## Features
|
||||
|
||||
Example
|
||||
-------
|
||||
func layout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("center", maxX/2-10, maxY/2, maxX/2+10, maxY/2+2); err != nil {
|
||||
if err != gocui.ErrorUnkView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, "This is an example")
|
||||
}
|
||||
return nil
|
||||
* Minimalist API.
|
||||
* Views (the "windows" in the GUI) implement the interface io.ReadWriter.
|
||||
* Support for overlapping views.
|
||||
* The GUI can be modified at runtime (concurrent-safe).
|
||||
* Global and view-level keybindings.
|
||||
* Mouse support.
|
||||
* Colored text.
|
||||
* Customizable edition mode.
|
||||
* Easy to build reusable widgets, complex layouts...
|
||||
|
||||
## Installation
|
||||
|
||||
Execute:
|
||||
|
||||
```sh
|
||||
go get github.com/jroimartin/gocui
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Execute:
|
||||
|
||||
```sh
|
||||
go doc github.com/jroimartin/gocui
|
||||
```
|
||||
|
||||
Or visit [pkg.go.dev](https://pkg.go.dev/github.com/jroimartin/gocui) to read
|
||||
it online.
|
||||
|
||||
## Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrorQuit
|
||||
defer g.Close()
|
||||
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
func main() {
|
||||
var err error
|
||||
g := gocui.NewGui()
|
||||
if err := g.Init(); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
g.SetLayout(layout)
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, 0, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
err = g.MainLoop()
|
||||
if err != nil && err != gocui.ErrorQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, "Hello world!")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
```
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Projects using gocui
|
||||
|
||||
* [komanda-cli](https://github.com/mephux/komanda-cli): IRC Client For Developers.
|
||||
* [vuls](https://github.com/future-architect/vuls): Agentless vulnerability scanner for Linux/FreeBSD.
|
||||
* [wuzz](https://github.com/asciimoo/wuzz): Interactive cli tool for HTTP inspection.
|
||||
* [httplab](https://github.com/gchaincl/httplab): Interactive web server.
|
||||
* [domainr](https://github.com/MichaelThessel/domainr): Tool that checks the availability of domains based on keywords.
|
||||
* [gotime](https://github.com/nanohard/gotime): Time tracker for projects and tasks.
|
||||
* [claws](https://github.com/thehowl/claws): Interactive command line client for testing websockets.
|
||||
* [terminews](http://github.com/antavelos/terminews): Terminal based RSS reader.
|
||||
* [diagram](https://github.com/esimov/diagram): Tool to convert ascii arts into hand drawn diagrams.
|
||||
* [pody](https://github.com/JulienBreux/pody): CLI app to manage Pods in a Kubernetes cluster.
|
||||
* [kubexp](https://github.com/alitari/kubexp): Kubernetes client.
|
||||
* [kcli](https://github.com/cswank/kcli): Tool for inspecting kafka topics/partitions/messages.
|
||||
* [fac](https://github.com/mkchoi212/fac): git merge conflict resolver
|
||||
* [jsonui](https://github.com/gulyasm/jsonui): Interactive JSON explorer for your terminal.
|
||||
* [cointop](https://github.com/miguelmota/cointop): Interactive terminal based UI application for tracking cryptocurrencies.
|
||||
|
||||
Note: if your project is not listed here, let us know! :)
|
||||
|
301
_demos/demo2.go
301
_demos/demo2.go
@ -1,301 +0,0 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
func focusMain(g *gocui.Gui, v *gocui.View) error {
|
||||
return g.SetCurrentView("main")
|
||||
}
|
||||
|
||||
func focusSide(g *gocui.Gui, v *gocui.View) error {
|
||||
return g.SetCurrentView("side")
|
||||
|
||||
}
|
||||
|
||||
func focusCmdLine(g *gocui.Gui, v *gocui.View) error {
|
||||
return g.SetCurrentView("cmdline")
|
||||
|
||||
}
|
||||
|
||||
func showHideCursor(g *gocui.Gui, v *gocui.View) error {
|
||||
g.ShowCursor = !g.ShowCursor
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func cursorDown(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
cx, cy := v.Cursor()
|
||||
if err := v.SetCursor(cx, cy+1); err != nil {
|
||||
ox, oy := v.Origin()
|
||||
if err := v.SetOrigin(ox, oy+1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cursorUp(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
ox, oy := v.Origin()
|
||||
cx, cy := v.Cursor()
|
||||
if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
|
||||
if err := v.SetOrigin(ox, oy-1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cursorLeft(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
ox, oy := v.Origin()
|
||||
cx, cy := v.Cursor()
|
||||
if err := v.SetCursor(cx-1, cy); err != nil && ox > 0 {
|
||||
if err := v.SetOrigin(ox-1, oy); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cursorRight(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
cx, cy := v.Cursor()
|
||||
if err := v.SetCursor(cx+1, cy); err != nil {
|
||||
ox, oy := v.Origin()
|
||||
if err := v.SetOrigin(ox+1, oy); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func clear(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
v.Clear()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeTest(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
fmt.Fprintln(v, "This is a test")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func showMsg(g *gocui.Gui, v *gocui.View) error {
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("msg", maxX/2-10, maxY/2-10, maxX/2+10, maxY/2+10); err != nil {
|
||||
if err != gocui.ErrorUnkView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, "This is a message")
|
||||
}
|
||||
if err := g.SetCurrentView("msg"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func delMsg(g *gocui.Gui, v *gocui.View) error {
|
||||
g.DeleteView("msg")
|
||||
return nil
|
||||
}
|
||||
|
||||
func setLayout1(g *gocui.Gui, v *gocui.View) error {
|
||||
g.SetLayout(layout)
|
||||
return nil
|
||||
}
|
||||
|
||||
func setLayout2(g *gocui.Gui, v *gocui.View) error {
|
||||
g.SetLayout(layout2)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getLine(g *gocui.Gui, v *gocui.View) error {
|
||||
var l string
|
||||
var err error
|
||||
|
||||
_, cy := v.Cursor()
|
||||
if l, err = v.Line(cy); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
|
||||
if err != gocui.ErrorUnkView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, l)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getWord(g *gocui.Gui, v *gocui.View) error {
|
||||
var w string
|
||||
var err error
|
||||
|
||||
cx, cy := v.Cursor()
|
||||
if w, err = v.Word(cx, cy); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
|
||||
if err != gocui.ErrorUnkView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(v, "\"%s\"", w)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func keybindings(g *gocui.Gui) error {
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlSpace, 0, focusMain); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlS, 0, focusSide); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlL, 0, focusCmdLine); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", 'c', gocui.ModAlt, showHideCursor); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyArrowDown, 0, cursorDown); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyArrowUp, 0, cursorUp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyArrowLeft, 0, cursorLeft); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyArrowRight, 0, cursorRight); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, 0, quit); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("main", 'q', 0, quit); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", 'c', 0, clear); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", 't', 0, writeTest); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", '1', 0, setLayout1); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", '2', 0, setLayout2); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", '3', 0, showMsg); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", '4', 0, delMsg); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", '9', 0, getWord); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", '0', 0, getLine); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrorQuit
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("side", -1, -1, 30, maxY-5); err != nil {
|
||||
if err != gocui.ErrorUnkView {
|
||||
return err
|
||||
}
|
||||
v.Highlight = true
|
||||
fmt.Fprintln(v, "Item 1")
|
||||
fmt.Fprintln(v, "Item 2")
|
||||
fmt.Fprintln(v, "Item 3")
|
||||
fmt.Fprintln(v, "Item 4")
|
||||
}
|
||||
if v, err := g.SetView("main", 30, -1, maxX, maxY-5); err != nil {
|
||||
if err != gocui.ErrorUnkView {
|
||||
return err
|
||||
}
|
||||
b, err := ioutil.ReadFile("Mark.Twain-Tom.Sawyer.txt")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Fprintf(v, "%s", b)
|
||||
if err := g.SetCurrentView("main"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if v, err := g.SetView("cmdline", -1, maxY-5, maxX, maxY); err != nil {
|
||||
if err != gocui.ErrorUnkView {
|
||||
return err
|
||||
}
|
||||
v.Editable = true
|
||||
fmt.Fprintln(v, "Command line test")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func layout2(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("center", maxX/2-10, maxY/2-10, maxX/2+10, maxY/2+10); err != nil {
|
||||
if err != gocui.ErrorUnkView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, "Center view test")
|
||||
if err := g.SetCurrentView("center"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
|
||||
g := gocui.NewGui()
|
||||
if err := g.Init(); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetLayout(layout)
|
||||
if err := keybindings(g); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
g.SelBgColor = gocui.ColorGreen
|
||||
g.SelFgColor = gocui.ColorBlack
|
||||
g.ShowCursor = true
|
||||
|
||||
err = g.MainLoop()
|
||||
if err != nil && err != gocui.ErrorQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
119
_examples/active.go
Normal file
119
_examples/active.go
Normal file
@ -0,0 +1,119 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
var (
|
||||
viewArr = []string{"v1", "v2", "v3", "v4"}
|
||||
active = 0
|
||||
)
|
||||
|
||||
func setCurrentViewOnTop(g *gocui.Gui, name string) (*gocui.View, error) {
|
||||
if _, err := g.SetCurrentView(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return g.SetViewOnTop(name)
|
||||
}
|
||||
|
||||
func nextView(g *gocui.Gui, v *gocui.View) error {
|
||||
nextIndex := (active + 1) % len(viewArr)
|
||||
name := viewArr[nextIndex]
|
||||
|
||||
out, err := g.View("v2")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(out, "Going from view "+v.Name()+" to "+name)
|
||||
|
||||
if _, err := setCurrentViewOnTop(g, name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if nextIndex == 0 || nextIndex == 3 {
|
||||
g.Cursor = true
|
||||
} else {
|
||||
g.Cursor = false
|
||||
}
|
||||
|
||||
active = nextIndex
|
||||
return nil
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("v1", 0, 0, maxX/2-1, maxY/2-1); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "v1 (editable)"
|
||||
v.Editable = true
|
||||
v.Wrap = true
|
||||
|
||||
if _, err = setCurrentViewOnTop(g, "v1"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if v, err := g.SetView("v2", maxX/2-1, 0, maxX-1, maxY/2-1); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "v2"
|
||||
v.Wrap = true
|
||||
v.Autoscroll = true
|
||||
}
|
||||
if v, err := g.SetView("v3", 0, maxY/2-1, maxX/2-1, maxY-1); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "v3"
|
||||
v.Wrap = true
|
||||
v.Autoscroll = true
|
||||
fmt.Fprint(v, "Press TAB to change current view")
|
||||
}
|
||||
if v, err := g.SetView("v4", maxX/2, maxY/2, maxX-1, maxY-1); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "v4 (editable)"
|
||||
v.Editable = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.Highlight = true
|
||||
g.Cursor = true
|
||||
g.SelFgColor = gocui.ColorGreen
|
||||
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, nextView); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
70
_examples/bufs.go
Normal file
70
_examples/bufs.go
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// WARNING: tricky code just for testing purposes, do not use as reference.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
var vbuf, buf string
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
vbuf = v.ViewBuffer()
|
||||
buf = v.Buffer()
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
|
||||
func overwrite(g *gocui.Gui, v *gocui.View) error {
|
||||
v.Overwrite = !v.Overwrite
|
||||
return nil
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
_, maxY := g.Size()
|
||||
if v, err := g.SetView("main", 0, 0, 20, maxY-1); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Editable = true
|
||||
v.Wrap = true
|
||||
if _, err := g.SetCurrentView("main"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
g.Cursor = true
|
||||
g.Mouse = true
|
||||
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := g.SetKeybinding("main", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
if err := g.SetKeybinding("main", gocui.KeyCtrlI, gocui.ModNone, overwrite); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
g.Close()
|
||||
|
||||
fmt.Printf("VBUF:\n%s\n", vbuf)
|
||||
fmt.Printf("BUF:\n%s\n", buf)
|
||||
}
|
49
_examples/colors.go
Normal file
49
_examples/colors.go
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("colors", maxX/2-7, maxY/2-12, maxX/2+7, maxY/2+13); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
for i := 0; i <= 7; i++ {
|
||||
for _, j := range []int{1, 4, 7} {
|
||||
fmt.Fprintf(v, "Hello \033[3%d;%dmcolors!\033[0m\n", i, j)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}
|
74
_examples/colors256.go
Normal file
74
_examples/colors256.go
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.Output256)
|
||||
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("colors", -1, -1, maxX, maxY); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
|
||||
// 256-colors escape codes
|
||||
for i := 0; i < 256; i++ {
|
||||
str := fmt.Sprintf("\x1b[48;5;%dm\x1b[30m%3d\x1b[0m ", i, i)
|
||||
str += fmt.Sprintf("\x1b[38;5;%dm%3d\x1b[0m ", i, i)
|
||||
|
||||
if (i+1)%10 == 0 {
|
||||
str += "\n"
|
||||
}
|
||||
|
||||
fmt.Fprint(v, str)
|
||||
}
|
||||
|
||||
fmt.Fprint(v, "\n\n")
|
||||
|
||||
// 8-colors escape codes
|
||||
ctr := 0
|
||||
for i := 0; i <= 7; i++ {
|
||||
for _, j := range []int{1, 4, 7} {
|
||||
str := fmt.Sprintf("\x1b[3%d;%dm%d:%d\x1b[0m ", i, j, i, j)
|
||||
if (ctr+1)%20 == 0 {
|
||||
str += "\n"
|
||||
}
|
||||
|
||||
fmt.Fprint(v, str)
|
||||
|
||||
ctr++
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}
|
211
_examples/demo.go
Normal file
211
_examples/demo.go
Normal file
@ -0,0 +1,211 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
func nextView(g *gocui.Gui, v *gocui.View) error {
|
||||
if v == nil || v.Name() == "side" {
|
||||
_, err := g.SetCurrentView("main")
|
||||
return err
|
||||
}
|
||||
_, err := g.SetCurrentView("side")
|
||||
return err
|
||||
}
|
||||
|
||||
func cursorDown(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
cx, cy := v.Cursor()
|
||||
if err := v.SetCursor(cx, cy+1); err != nil {
|
||||
ox, oy := v.Origin()
|
||||
if err := v.SetOrigin(ox, oy+1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cursorUp(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
ox, oy := v.Origin()
|
||||
cx, cy := v.Cursor()
|
||||
if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
|
||||
if err := v.SetOrigin(ox, oy-1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getLine(g *gocui.Gui, v *gocui.View) error {
|
||||
var l string
|
||||
var err error
|
||||
|
||||
_, cy := v.Cursor()
|
||||
if l, err = v.Line(cy); err != nil {
|
||||
l = ""
|
||||
}
|
||||
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, l)
|
||||
if _, err := g.SetCurrentView("msg"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func delMsg(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := g.DeleteView("msg"); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := g.SetCurrentView("side"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
|
||||
func keybindings(g *gocui.Gui) error {
|
||||
if err := g.SetKeybinding("side", gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("main", gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("side", gocui.KeyArrowDown, gocui.ModNone, cursorDown); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("side", gocui.KeyArrowUp, gocui.ModNone, cursorUp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, getLine); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := g.SetKeybinding("main", gocui.KeyCtrlS, gocui.ModNone, saveMain); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("main", gocui.KeyCtrlW, gocui.ModNone, saveVisualMain); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveMain(g *gocui.Gui, v *gocui.View) error {
|
||||
f, err := ioutil.TempFile("", "gocui_demo_")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
p := make([]byte, 5)
|
||||
v.Rewind()
|
||||
for {
|
||||
n, err := v.Read(p)
|
||||
if n > 0 {
|
||||
if _, err := f.Write(p[:n]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveVisualMain(g *gocui.Gui, v *gocui.View) error {
|
||||
f, err := ioutil.TempFile("", "gocui_demo_")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
vb := v.ViewBuffer()
|
||||
if _, err := io.Copy(f, strings.NewReader(vb)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("side", -1, -1, 30, maxY); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Highlight = true
|
||||
v.SelBgColor = gocui.ColorGreen
|
||||
v.SelFgColor = gocui.ColorBlack
|
||||
fmt.Fprintln(v, "Item 1")
|
||||
fmt.Fprintln(v, "Item 2")
|
||||
fmt.Fprintln(v, "Item 3")
|
||||
fmt.Fprint(v, "\rWill be")
|
||||
fmt.Fprint(v, "deleted\rItem 4\nItem 5")
|
||||
}
|
||||
if v, err := g.SetView("main", 30, -1, maxX, maxY); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
b, err := ioutil.ReadFile("Mark.Twain-Tom.Sawyer.txt")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Fprintf(v, "%s", b)
|
||||
v.Editable = true
|
||||
v.Wrap = true
|
||||
if _, err := g.SetCurrentView("main"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.Cursor = true
|
||||
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := keybindings(g); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
190
_examples/dynamic.go
Normal file
190
_examples/dynamic.go
Normal file
@ -0,0 +1,190 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
const delta = 1
|
||||
|
||||
var (
|
||||
views = []string{}
|
||||
curView = -1
|
||||
idxView = 0
|
||||
)
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.Highlight = true
|
||||
g.SelFgColor = gocui.ColorRed
|
||||
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := initKeybindings(g); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
if err := newView(g); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
maxX, _ := g.Size()
|
||||
v, err := g.SetView("help", maxX-25, 0, maxX-1, 9)
|
||||
if err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, "KEYBINDINGS")
|
||||
fmt.Fprintln(v, "Space: New View")
|
||||
fmt.Fprintln(v, "Tab: Next View")
|
||||
fmt.Fprintln(v, "← ↑ → ↓: Move View")
|
||||
fmt.Fprintln(v, "Backspace: Delete View")
|
||||
fmt.Fprintln(v, "t: Set view on top")
|
||||
fmt.Fprintln(v, "b: Set view on bottom")
|
||||
fmt.Fprintln(v, "^C: Exit")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initKeybindings(g *gocui.Gui) error {
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone,
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeySpace, gocui.ModNone,
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
return newView(g)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyBackspace2, gocui.ModNone,
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
return delView(g)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone,
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
return nextView(g, true)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyArrowLeft, gocui.ModNone,
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
return moveView(g, v, -delta, 0)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyArrowRight, gocui.ModNone,
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
return moveView(g, v, delta, 0)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone,
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
return moveView(g, v, 0, delta)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone,
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
return moveView(g, v, 0, -delta)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", 't', gocui.ModNone,
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
_, err := g.SetViewOnTop(views[curView])
|
||||
return err
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("", 'b', gocui.ModNone,
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
_, err := g.SetViewOnBottom(views[curView])
|
||||
return err
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newView(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
name := fmt.Sprintf("v%v", idxView)
|
||||
v, err := g.SetView(name, maxX/2-5, maxY/2-5, maxX/2+5, maxY/2+5)
|
||||
if err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Wrap = true
|
||||
fmt.Fprintln(v, strings.Repeat(name+" ", 30))
|
||||
}
|
||||
if _, err := g.SetCurrentView(name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
views = append(views, name)
|
||||
curView = len(views) - 1
|
||||
idxView += 1
|
||||
return nil
|
||||
}
|
||||
|
||||
func delView(g *gocui.Gui) error {
|
||||
if len(views) <= 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := g.DeleteView(views[curView]); err != nil {
|
||||
return err
|
||||
}
|
||||
views = append(views[:curView], views[curView+1:]...)
|
||||
|
||||
return nextView(g, false)
|
||||
}
|
||||
|
||||
func nextView(g *gocui.Gui, disableCurrent bool) error {
|
||||
next := curView + 1
|
||||
if next > len(views)-1 {
|
||||
next = 0
|
||||
}
|
||||
|
||||
if _, err := g.SetCurrentView(views[next]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
curView = next
|
||||
return nil
|
||||
}
|
||||
|
||||
func moveView(g *gocui.Gui, v *gocui.View, dx, dy int) error {
|
||||
name := v.Name()
|
||||
x0, y0, x1, y1, err := g.ViewPosition(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := g.SetView(name, x0+dx, y0+dy, x1+dx, y1+dy); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
87
_examples/flow_layout.go
Normal file
87
_examples/flow_layout.go
Normal file
@ -0,0 +1,87 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
type Label struct {
|
||||
name string
|
||||
w, h int
|
||||
body string
|
||||
}
|
||||
|
||||
func NewLabel(name string, body string) *Label {
|
||||
lines := strings.Split(body, "\n")
|
||||
|
||||
w := 0
|
||||
for _, l := range lines {
|
||||
if len(l) > w {
|
||||
w = len(l)
|
||||
}
|
||||
}
|
||||
h := len(lines) + 1
|
||||
w = w + 1
|
||||
|
||||
return &Label{name: name, w: w, h: h, body: body}
|
||||
}
|
||||
|
||||
func (w *Label) Layout(g *gocui.Gui) error {
|
||||
v, err := g.SetView(w.name, 0, 0, w.w, w.h)
|
||||
if err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprint(v, w.body)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func flowLayout(g *gocui.Gui) error {
|
||||
views := g.Views()
|
||||
x := 0
|
||||
for _, v := range views {
|
||||
w, h := v.Size()
|
||||
_, err := g.SetView(v.Name(), x, 0, x+w+1, h+1)
|
||||
if err != nil && err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
x += w + 2
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
l1 := NewLabel("l1", "This")
|
||||
l2 := NewLabel("l2", "is")
|
||||
l3 := NewLabel("l3", "a")
|
||||
l4 := NewLabel("l4", "flow\nlayout")
|
||||
l5 := NewLabel("l5", "!")
|
||||
fl := gocui.ManagerFunc(flowLayout)
|
||||
g.SetManager(l1, l2, l3, l4, l5, fl)
|
||||
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}
|
97
_examples/goroutine.go
Normal file
97
_examples/goroutine.go
Normal file
@ -0,0 +1,97 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
const NumGoroutines = 10
|
||||
|
||||
var (
|
||||
done = make(chan struct{})
|
||||
wg sync.WaitGroup
|
||||
|
||||
mu sync.Mutex // protects ctr
|
||||
ctr = 0
|
||||
)
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := keybindings(g); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
for i := 0; i < NumGoroutines; i++ {
|
||||
wg.Add(1)
|
||||
go counter(g)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
if v, err := g.SetView("ctr", 2, 2, 12, 4); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, "0")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func keybindings(g *gocui.Gui) error {
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
close(done)
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
|
||||
func counter(g *gocui.Gui) {
|
||||
defer wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
mu.Lock()
|
||||
n := ctr
|
||||
ctr++
|
||||
mu.Unlock()
|
||||
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
v, err := g.View("ctr")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Clear()
|
||||
fmt.Fprintln(v, n)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
45
_examples/hello.go
Normal file
45
_examples/hello.go
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, "Hello world!")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -13,41 +13,38 @@ import (
|
||||
func layout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
if _, err := g.SetView("side", -1, -1, int(0.2*float32(maxX)), maxY-5); err != nil &&
|
||||
err != gocui.ErrorUnkView {
|
||||
err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
if _, err := g.SetView("main", int(0.2*float32(maxX)), -1, maxX, maxY-5); err != nil &&
|
||||
err != gocui.ErrorUnkView {
|
||||
err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
if _, err := g.SetView("cmdline", -1, maxY-5, maxX, maxY); err != nil &&
|
||||
err != gocui.ErrorUnkView {
|
||||
err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrorQuit
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
|
||||
g := gocui.NewGui()
|
||||
if err := g.Init(); err != nil {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetLayout(layout)
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, 0, quit); err != nil {
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
err = g.MainLoop()
|
||||
if err != nil && err != gocui.ErrorQuit {
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
75
_examples/mask.go
Normal file
75
_examples/mask.go
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright 2015 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.Cursor = true
|
||||
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := initKeybindings(g); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
|
||||
if v, err := g.SetView("help", maxX-23, 0, maxX-1, 3); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Keybindings"
|
||||
fmt.Fprintln(v, "^a: Set mask")
|
||||
fmt.Fprintln(v, "^c: Exit")
|
||||
}
|
||||
|
||||
if v, err := g.SetView("input", 0, 0, maxX-24, maxY-1); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
if _, err := g.SetCurrentView("input"); err != nil {
|
||||
return err
|
||||
}
|
||||
v.Editable = true
|
||||
v.Wrap = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initKeybindings(g *gocui.Gui) error {
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone,
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("input", gocui.KeyCtrlA, gocui.ModNone,
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
v.Mask ^= '*'
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
107
_examples/mouse.go
Normal file
107
_examples/mouse.go
Normal file
@ -0,0 +1,107 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.Cursor = true
|
||||
g.Mouse = true
|
||||
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := keybindings(g); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
if v, err := g.SetView("but1", 2, 2, 22, 7); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Highlight = true
|
||||
v.SelBgColor = gocui.ColorGreen
|
||||
v.SelFgColor = gocui.ColorBlack
|
||||
fmt.Fprintln(v, "Button 1 - line 1")
|
||||
fmt.Fprintln(v, "Button 1 - line 2")
|
||||
fmt.Fprintln(v, "Button 1 - line 3")
|
||||
fmt.Fprintln(v, "Button 1 - line 4")
|
||||
}
|
||||
if v, err := g.SetView("but2", 24, 2, 44, 4); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Highlight = true
|
||||
v.SelBgColor = gocui.ColorGreen
|
||||
v.SelFgColor = gocui.ColorBlack
|
||||
fmt.Fprintln(v, "Button 2 - line 1")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func keybindings(g *gocui.Gui) error {
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, n := range []string{"but1", "but2"} {
|
||||
if err := g.SetKeybinding(n, gocui.MouseLeft, gocui.ModNone, showMsg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := g.SetKeybinding("msg", gocui.MouseLeft, gocui.ModNone, delMsg); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
|
||||
func showMsg(g *gocui.Gui, v *gocui.View) error {
|
||||
var l string
|
||||
var err error
|
||||
|
||||
if _, err := g.SetCurrentView(v.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, cy := v.Cursor()
|
||||
if l, err = v.Line(cy); err != nil {
|
||||
l = ""
|
||||
}
|
||||
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("msg", maxX/2-10, maxY/2, maxX/2+10, maxY/2+2); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, l)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func delMsg(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := g.DeleteView("msg"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
88
_examples/ontop.go
Normal file
88
_examples/ontop.go
Normal file
@ -0,0 +1,88 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := keybindings(g); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
if v, err := g.SetView("v1", 10, 2, 30, 6); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, "View #1")
|
||||
}
|
||||
if v, err := g.SetView("v2", 20, 4, 40, 8); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, "View #2")
|
||||
}
|
||||
if v, err := g.SetView("v3", 30, 6, 50, 10); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, "View #3")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func keybindings(g *gocui.Gui) error {
|
||||
err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = g.SetKeybinding("", '1', gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
|
||||
_, err := g.SetViewOnTop("v1")
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = g.SetKeybinding("", '2', gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
|
||||
_, err := g.SetViewOnTop("v2")
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = g.SetKeybinding("", '3', gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error {
|
||||
_, err := g.SetViewOnTop("v3")
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -13,64 +13,62 @@ import (
|
||||
func layout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
if _, err := g.SetView("v1", -1, -1, 10, 10); err != nil &&
|
||||
err != gocui.ErrorUnkView {
|
||||
err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
if _, err := g.SetView("v2", maxX-10, -1, maxX, 10); err != nil &&
|
||||
err != gocui.ErrorUnkView {
|
||||
err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
if _, err := g.SetView("v3", maxX/2-5, -1, maxX/2+5, 10); err != nil &&
|
||||
err != gocui.ErrorUnkView {
|
||||
err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
if _, err := g.SetView("v4", -1, maxY/2-5, 10, maxY/2+5); err != nil &&
|
||||
err != gocui.ErrorUnkView {
|
||||
err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
if _, err := g.SetView("v5", maxX-10, maxY/2-5, maxX, maxY/2+5); err != nil &&
|
||||
err != gocui.ErrorUnkView {
|
||||
err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
if _, err := g.SetView("v6", -1, maxY-10, 10, maxY); err != nil &&
|
||||
err != gocui.ErrorUnkView {
|
||||
err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
if _, err := g.SetView("v7", maxX-10, maxY-10, maxX, maxY); err != nil &&
|
||||
err != gocui.ErrorUnkView {
|
||||
err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
if _, err := g.SetView("v8", maxX/2-5, maxY-10, maxX/2+5, maxY); err != nil &&
|
||||
err != gocui.ErrorUnkView {
|
||||
err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
if _, err := g.SetView("v9", maxX/2-5, maxY/2-5, maxX/2+5, maxY/2+5); err != nil &&
|
||||
err != gocui.ErrorUnkView {
|
||||
err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrorQuit
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
|
||||
g := gocui.NewGui()
|
||||
if err := g.Init(); err != nil {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetLayout(layout)
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, 0, quit); err != nil {
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
err = g.MainLoop()
|
||||
if err != nil && err != gocui.ErrorQuit {
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
45
_examples/size.go
Normal file
45
_examples/size.go
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
v, err := g.SetView("size", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2)
|
||||
if err != nil && err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Clear()
|
||||
fmt.Fprintf(v, "%d, %d", maxX, maxY)
|
||||
return nil
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}
|
109
_examples/stdin.go
Normal file
109
_examples/stdin.go
Normal file
@ -0,0 +1,109 @@
|
||||
// Copyright 2015 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.Cursor = true
|
||||
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := initKeybindings(g); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
maxX, _ := g.Size()
|
||||
|
||||
if v, err := g.SetView("help", maxX-23, 0, maxX-1, 5); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, "KEYBINDINGS")
|
||||
fmt.Fprintln(v, "↑ ↓: Seek input")
|
||||
fmt.Fprintln(v, "a: Enable autoscroll")
|
||||
fmt.Fprintln(v, "^C: Exit")
|
||||
}
|
||||
|
||||
if v, err := g.SetView("stdin", 0, 0, 80, 35); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
if _, err := g.SetCurrentView("stdin"); err != nil {
|
||||
return err
|
||||
}
|
||||
dumper := hex.Dumper(v)
|
||||
if _, err := io.Copy(dumper, os.Stdin); err != nil {
|
||||
return err
|
||||
}
|
||||
v.Wrap = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initKeybindings(g *gocui.Gui) error {
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("stdin", 'a', gocui.ModNone, autoscroll); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("stdin", gocui.KeyArrowUp, gocui.ModNone,
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
scrollView(v, -1)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("stdin", gocui.KeyArrowDown, gocui.ModNone,
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
scrollView(v, 1)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
|
||||
func autoscroll(g *gocui.Gui, v *gocui.View) error {
|
||||
v.Autoscroll = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func scrollView(v *gocui.View, dy int) error {
|
||||
if v != nil {
|
||||
v.Autoscroll = false
|
||||
ox, oy := v.Origin()
|
||||
if err := v.SetOrigin(ox, oy+dy); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
162
_examples/title.go
Normal file
162
_examples/title.go
Normal file
@ -0,0 +1,162 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
|
||||
// Overlap (front)
|
||||
if v, err := g.SetView("v1", 10, 2, 30, 6); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Regular title"
|
||||
}
|
||||
if v, err := g.SetView("v2", 20, 4, 40, 8); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Regular title"
|
||||
}
|
||||
|
||||
// Overlap (back)
|
||||
if v, err := g.SetView("v3", 60, 4, 80, 8); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Regular title"
|
||||
}
|
||||
if v, err := g.SetView("v4", 50, 2, 70, 6); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Regular title"
|
||||
}
|
||||
|
||||
// Overlap (frame)
|
||||
if v, err := g.SetView("v15", 90, 2, 110, 5); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Regular title"
|
||||
}
|
||||
if v, err := g.SetView("v16", 100, 5, 120, 8); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Regular title"
|
||||
}
|
||||
if v, err := g.SetView("v17", 140, 5, 160, 8); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Regular title"
|
||||
}
|
||||
if v, err := g.SetView("v18", 130, 2, 150, 5); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Regular title"
|
||||
}
|
||||
|
||||
// Long title
|
||||
if v, err := g.SetView("v5", 10, 12, 30, 16); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Long long long long title"
|
||||
}
|
||||
|
||||
// No title
|
||||
if v, err := g.SetView("v6", 35, 12, 55, 16); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = ""
|
||||
}
|
||||
if _, err := g.SetView("v7", 60, 12, 80, 16); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Small view
|
||||
if v, err := g.SetView("v8", 85, 12, 88, 16); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Regular title"
|
||||
}
|
||||
|
||||
// Screen borders
|
||||
if v, err := g.SetView("v9", -10, 20, 10, 24); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Regular title"
|
||||
}
|
||||
if v, err := g.SetView("v10", maxX-10, 20, maxX+10, 24); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Regular title"
|
||||
}
|
||||
|
||||
// Out of screen
|
||||
if v, err := g.SetView("v11", -21, 28, -1, 32); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Regular title"
|
||||
}
|
||||
if v, err := g.SetView("v12", maxX, 28, maxX+20, 32); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Regular title"
|
||||
}
|
||||
if v, err := g.SetView("v13", 10, -7, 30, -1); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Regular title"
|
||||
}
|
||||
if v, err := g.SetView("v14", 10, maxY, 30, maxY+6); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = "Regular title"
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
179
_examples/widgets.go
Normal file
179
_examples/widgets.go
Normal file
@ -0,0 +1,179 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
const delta = 0.2
|
||||
|
||||
type HelpWidget struct {
|
||||
name string
|
||||
x, y int
|
||||
w, h int
|
||||
body string
|
||||
}
|
||||
|
||||
func NewHelpWidget(name string, x, y int, body string) *HelpWidget {
|
||||
lines := strings.Split(body, "\n")
|
||||
|
||||
w := 0
|
||||
for _, l := range lines {
|
||||
if len(l) > w {
|
||||
w = len(l)
|
||||
}
|
||||
}
|
||||
h := len(lines) + 1
|
||||
w = w + 1
|
||||
|
||||
return &HelpWidget{name: name, x: x, y: y, w: w, h: h, body: body}
|
||||
}
|
||||
|
||||
func (w *HelpWidget) Layout(g *gocui.Gui) error {
|
||||
v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+w.h)
|
||||
if err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprint(v, w.body)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type StatusbarWidget struct {
|
||||
name string
|
||||
x, y int
|
||||
w int
|
||||
val float64
|
||||
}
|
||||
|
||||
func NewStatusbarWidget(name string, x, y, w int) *StatusbarWidget {
|
||||
return &StatusbarWidget{name: name, x: x, y: y, w: w}
|
||||
}
|
||||
|
||||
func (w *StatusbarWidget) SetVal(val float64) error {
|
||||
if val < 0 || val > 1 {
|
||||
return errors.New("invalid value")
|
||||
}
|
||||
w.val = val
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *StatusbarWidget) Val() float64 {
|
||||
return w.val
|
||||
}
|
||||
|
||||
func (w *StatusbarWidget) Layout(g *gocui.Gui) error {
|
||||
v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+2)
|
||||
if err != nil && err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Clear()
|
||||
|
||||
rep := int(w.val * float64(w.w-1))
|
||||
fmt.Fprint(v, strings.Repeat("▒", rep))
|
||||
return nil
|
||||
}
|
||||
|
||||
type ButtonWidget struct {
|
||||
name string
|
||||
x, y int
|
||||
w int
|
||||
label string
|
||||
handler func(g *gocui.Gui, v *gocui.View) error
|
||||
}
|
||||
|
||||
func NewButtonWidget(name string, x, y int, label string, handler func(g *gocui.Gui, v *gocui.View) error) *ButtonWidget {
|
||||
return &ButtonWidget{name: name, x: x, y: y, w: len(label) + 1, label: label, handler: handler}
|
||||
}
|
||||
|
||||
func (w *ButtonWidget) Layout(g *gocui.Gui) error {
|
||||
v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+2)
|
||||
if err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
if _, err := g.SetCurrentView(w.name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding(w.name, gocui.KeyEnter, gocui.ModNone, w.handler); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprint(v, w.label)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.Highlight = true
|
||||
g.SelFgColor = gocui.ColorRed
|
||||
|
||||
help := NewHelpWidget("help", 1, 1, helpText)
|
||||
status := NewStatusbarWidget("status", 1, 7, 50)
|
||||
butdown := NewButtonWidget("butdown", 52, 7, "DOWN", statusDown(status))
|
||||
butup := NewButtonWidget("butup", 58, 7, "UP", statusUp(status))
|
||||
g.SetManager(help, status, butdown, butup)
|
||||
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, toggleButton); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
|
||||
func toggleButton(g *gocui.Gui, v *gocui.View) error {
|
||||
nextview := "butdown"
|
||||
if v != nil && v.Name() == "butdown" {
|
||||
nextview = "butup"
|
||||
}
|
||||
_, err := g.SetCurrentView(nextview)
|
||||
return err
|
||||
}
|
||||
|
||||
func statusUp(status *StatusbarWidget) func(g *gocui.Gui, v *gocui.View) error {
|
||||
return func(g *gocui.Gui, v *gocui.View) error {
|
||||
return statusSet(status, delta)
|
||||
}
|
||||
}
|
||||
|
||||
func statusDown(status *StatusbarWidget) func(g *gocui.Gui, v *gocui.View) error {
|
||||
return func(g *gocui.Gui, v *gocui.View) error {
|
||||
return statusSet(status, -delta)
|
||||
}
|
||||
}
|
||||
|
||||
func statusSet(sw *StatusbarWidget, inc float64) error {
|
||||
val := sw.Val() + inc
|
||||
if val < 0 || val > 1 {
|
||||
return nil
|
||||
}
|
||||
return sw.SetVal(val)
|
||||
}
|
||||
|
||||
const helpText = `KEYBINDINGS
|
||||
Tab: Move between buttons
|
||||
Enter: Push button
|
||||
^C: Exit`
|
50
_examples/wrap.go
Normal file
50
_examples/wrap.go
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
)
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("main", 1, 1, maxX-1, maxY-1); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Wrap = true
|
||||
|
||||
line := strings.Repeat("This is a long line -- ", 10)
|
||||
fmt.Fprintf(v, "%s\n\n", line)
|
||||
fmt.Fprintln(v, "Short")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
|
||||
func main() {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -6,8 +6,9 @@ package gocui
|
||||
|
||||
import "github.com/nsf/termbox-go"
|
||||
|
||||
// Attributes can be combined using bitwise OR (|). Note that it is not
|
||||
// possible to combine multiple color attributes.
|
||||
// Attribute represents a terminal attribute, like color, font style, etc. They
|
||||
// can be combined using bitwise OR (|). Note that it is not possible to
|
||||
// combine multiple color attributes.
|
||||
type Attribute termbox.Attribute
|
||||
|
||||
// Color attributes.
|
||||
|
132
doc.go
132
doc.go
@ -1,36 +1,118 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package gocui allows to create console user interfaces.
|
||||
|
||||
Example:
|
||||
Create a new GUI:
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("center", maxX/2-10, maxY/2, maxX/2+10, maxY/2+2); err != nil {
|
||||
if err != gocui.ErrorUnkView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, "This is an example")
|
||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
// Set GUI managers and key bindings
|
||||
// ...
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
// handle error
|
||||
}
|
||||
|
||||
Set GUI managers:
|
||||
|
||||
g.SetManager(mgr1, mgr2)
|
||||
|
||||
Managers are in charge of GUI's layout and can be used to build widgets. On
|
||||
each iteration of the GUI's main loop, the Layout function of each configured
|
||||
manager is executed. Managers are used to set-up and update the application's
|
||||
main views, being possible to freely change them during execution. Also, it is
|
||||
important to mention that a main loop iteration is executed on each reported
|
||||
event (key-press, mouse event, window resize, etc).
|
||||
|
||||
GUIs are composed by Views, you can think of it as buffers. Views implement the
|
||||
io.ReadWriter interface, so you can just write to them if you want to modify
|
||||
their content. The same is valid for reading.
|
||||
|
||||
Create and initialize a view with absolute coordinates:
|
||||
|
||||
if v, err := g.SetView("viewname", 2, 2, 22, 7); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
// handle error
|
||||
}
|
||||
fmt.Fprintln(v, "This is a new view")
|
||||
// ...
|
||||
}
|
||||
|
||||
Views can also be created using relative coordinates:
|
||||
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("viewname", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
|
||||
// ...
|
||||
}
|
||||
|
||||
Configure keybindings:
|
||||
|
||||
if err := g.SetKeybinding("viewname", gocui.KeyEnter, gocui.ModNone, fcn); err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
gocui implements full mouse support that can be enabled with:
|
||||
|
||||
g.Mouse = true
|
||||
|
||||
Mouse events are handled like any other keybinding:
|
||||
|
||||
if err := g.SetKeybinding("viewname", gocui.MouseLeft, gocui.ModNone, fcn); err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
IMPORTANT: Views can only be created, destroyed or updated in three ways: from
|
||||
the Layout function within managers, from keybinding callbacks or via
|
||||
*Gui.Update(). The reason for this is that it allows gocui to be
|
||||
concurrent-safe. So, if you want to update your GUI from a goroutine, you must
|
||||
use *Gui.Update(). For example:
|
||||
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
v, err := g.View("viewname")
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
v.Clear()
|
||||
fmt.Fprintln(v, "Writing from different goroutines")
|
||||
return nil
|
||||
})
|
||||
|
||||
By default, gocui provides a basic edition mode. This mode can be extended
|
||||
and customized creating a new Editor and assigning it to *View.Editor:
|
||||
|
||||
type Editor interface {
|
||||
Edit(v *View, key Key, ch rune, mod Modifier)
|
||||
}
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrorQuit
|
||||
}
|
||||
func main() {
|
||||
var err error
|
||||
g := gocui.NewGui()
|
||||
if err := g.Init(); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
g.SetLayout(layout)
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, 0, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
err = g.MainLoop()
|
||||
if err != nil && err != gocui.ErrorQuit {
|
||||
log.Panicln(err)
|
||||
|
||||
DefaultEditor can be taken as example to create your own custom Editor:
|
||||
|
||||
var DefaultEditor Editor = EditorFunc(simpleEditor)
|
||||
|
||||
func simpleEditor(v *View, key Key, ch rune, mod Modifier) {
|
||||
switch {
|
||||
case ch != 0 && mod == 0:
|
||||
v.EditWrite(ch)
|
||||
case key == KeySpace:
|
||||
v.EditWrite(' ')
|
||||
case key == KeyBackspace || key == KeyBackspace2:
|
||||
v.EditDelete(true)
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
Colored text:
|
||||
|
||||
Views allow to add colored text using ANSI colors. For example:
|
||||
|
||||
fmt.Fprintln(v, "\x1b[0;31mHello world")
|
||||
|
||||
For more information, see the examples in folder "_examples/".
|
||||
*/
|
||||
package gocui
|
||||
|
353
edit.go
353
edit.go
@ -1,49 +1,344 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gocui
|
||||
|
||||
// editWrite writes a rune at the cursor position.
|
||||
func (v *View) editWrite(ch rune) error {
|
||||
v.writeRune(v.cx, v.cy, ch)
|
||||
if err := v.SetCursor(v.cx+1, v.cy); err != nil {
|
||||
if err := v.SetOrigin(v.ox+1, v.oy); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
import "errors"
|
||||
|
||||
const maxInt = int(^uint(0) >> 1)
|
||||
|
||||
// Editor interface must be satisfied by gocui editors.
|
||||
type Editor interface {
|
||||
Edit(v *View, key Key, ch rune, mod Modifier)
|
||||
}
|
||||
|
||||
// editDelete deletes a rune at the cursor position. back determines
|
||||
// the direction.
|
||||
func (v *View) editDelete(back bool) error {
|
||||
// The EditorFunc type is an adapter to allow the use of ordinary functions as
|
||||
// Editors. If f is a function with the appropriate signature, EditorFunc(f)
|
||||
// is an Editor object that calls f.
|
||||
type EditorFunc func(v *View, key Key, ch rune, mod Modifier)
|
||||
|
||||
// Edit calls f(v, key, ch, mod)
|
||||
func (f EditorFunc) Edit(v *View, key Key, ch rune, mod Modifier) {
|
||||
f(v, key, ch, mod)
|
||||
}
|
||||
|
||||
// DefaultEditor is the default editor.
|
||||
var DefaultEditor Editor = EditorFunc(simpleEditor)
|
||||
|
||||
// simpleEditor is used as the default gocui editor.
|
||||
func simpleEditor(v *View, key Key, ch rune, mod Modifier) {
|
||||
switch {
|
||||
case ch != 0 && mod == 0:
|
||||
v.EditWrite(ch)
|
||||
case key == KeySpace:
|
||||
v.EditWrite(' ')
|
||||
case key == KeyBackspace || key == KeyBackspace2:
|
||||
v.EditDelete(true)
|
||||
case key == KeyDelete:
|
||||
v.EditDelete(false)
|
||||
case key == KeyInsert:
|
||||
v.Overwrite = !v.Overwrite
|
||||
case key == KeyEnter:
|
||||
v.EditNewLine()
|
||||
case key == KeyArrowDown:
|
||||
v.MoveCursor(0, 1, false)
|
||||
case key == KeyArrowUp:
|
||||
v.MoveCursor(0, -1, false)
|
||||
case key == KeyArrowLeft:
|
||||
v.MoveCursor(-1, 0, false)
|
||||
case key == KeyArrowRight:
|
||||
v.MoveCursor(1, 0, false)
|
||||
}
|
||||
}
|
||||
|
||||
// EditWrite writes a rune at the cursor position.
|
||||
func (v *View) EditWrite(ch rune) {
|
||||
v.writeRune(v.cx, v.cy, ch)
|
||||
v.MoveCursor(1, 0, true)
|
||||
}
|
||||
|
||||
// EditDelete deletes a rune at the cursor position. back determines the
|
||||
// direction.
|
||||
func (v *View) EditDelete(back bool) {
|
||||
x, y := v.ox+v.cx, v.oy+v.cy
|
||||
if y < 0 {
|
||||
return
|
||||
} else if y >= len(v.viewLines) {
|
||||
v.MoveCursor(-1, 0, true)
|
||||
return
|
||||
}
|
||||
|
||||
maxX, _ := v.Size()
|
||||
if back {
|
||||
v.deleteRune(v.cx-1, v.cy)
|
||||
if err := v.SetCursor(v.cx-1, v.cy); err != nil && v.ox > 0 {
|
||||
if err := v.SetOrigin(v.ox-1, v.oy); err != nil {
|
||||
return err
|
||||
if x == 0 { // start of the line
|
||||
if y < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
var maxPrevWidth int
|
||||
if v.Wrap {
|
||||
maxPrevWidth = maxX
|
||||
} else {
|
||||
maxPrevWidth = maxInt
|
||||
}
|
||||
|
||||
if v.viewLines[y].linesX == 0 { // regular line
|
||||
v.mergeLines(v.cy - 1)
|
||||
if len(v.viewLines[y-1].line) < maxPrevWidth {
|
||||
v.MoveCursor(-1, 0, true)
|
||||
}
|
||||
} else { // wrapped line
|
||||
v.deleteRune(len(v.viewLines[y-1].line)-1, v.cy-1)
|
||||
v.MoveCursor(-1, 0, true)
|
||||
}
|
||||
} else { // middle/end of the line
|
||||
v.deleteRune(v.cx-1, v.cy)
|
||||
v.MoveCursor(-1, 0, true)
|
||||
}
|
||||
} else {
|
||||
v.deleteRune(v.cx, v.cy)
|
||||
if x == len(v.viewLines[y].line) { // end of the line
|
||||
v.mergeLines(v.cy)
|
||||
} else { // start/middle of the line
|
||||
v.deleteRune(v.cx, v.cy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EditNewLine inserts a new line under the cursor.
|
||||
func (v *View) EditNewLine() {
|
||||
v.breakLine(v.cx, v.cy)
|
||||
v.ox = 0
|
||||
v.cx = 0
|
||||
v.MoveCursor(0, 1, true)
|
||||
}
|
||||
|
||||
// MoveCursor moves the cursor taking into account the width of the line/view,
|
||||
// displacing the origin if necessary.
|
||||
func (v *View) MoveCursor(dx, dy int, writeMode bool) {
|
||||
maxX, maxY := v.Size()
|
||||
cx, cy := v.cx+dx, v.cy+dy
|
||||
x, y := v.ox+cx, v.oy+cy
|
||||
|
||||
var curLineWidth, prevLineWidth int
|
||||
// get the width of the current line
|
||||
if writeMode {
|
||||
if v.Wrap {
|
||||
curLineWidth = maxX - 1
|
||||
} else {
|
||||
curLineWidth = maxInt
|
||||
}
|
||||
} else {
|
||||
if y >= 0 && y < len(v.viewLines) {
|
||||
curLineWidth = len(v.viewLines[y].line)
|
||||
if v.Wrap && curLineWidth >= maxX {
|
||||
curLineWidth = maxX - 1
|
||||
}
|
||||
} else {
|
||||
curLineWidth = 0
|
||||
}
|
||||
}
|
||||
// get the width of the previous line
|
||||
if y-1 >= 0 && y-1 < len(v.viewLines) {
|
||||
prevLineWidth = len(v.viewLines[y-1].line)
|
||||
} else {
|
||||
prevLineWidth = 0
|
||||
}
|
||||
|
||||
// adjust cursor's x position and view's x origin
|
||||
if x > curLineWidth { // move to next line
|
||||
if dx > 0 { // horizontal movement
|
||||
cy++
|
||||
if writeMode || v.oy+cy < len(v.viewLines) {
|
||||
if !v.Wrap {
|
||||
v.ox = 0
|
||||
}
|
||||
v.cx = 0
|
||||
}
|
||||
} else { // vertical movement
|
||||
if curLineWidth > 0 { // move cursor to the EOL
|
||||
if v.Wrap {
|
||||
v.cx = curLineWidth
|
||||
} else {
|
||||
ncx := curLineWidth - v.ox
|
||||
if ncx < 0 {
|
||||
v.ox += ncx
|
||||
if v.ox < 0 {
|
||||
v.ox = 0
|
||||
}
|
||||
v.cx = 0
|
||||
} else {
|
||||
v.cx = ncx
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if writeMode || v.oy+cy < len(v.viewLines) {
|
||||
if !v.Wrap {
|
||||
v.ox = 0
|
||||
}
|
||||
v.cx = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if cx < 0 {
|
||||
if !v.Wrap && v.ox > 0 { // move origin to the left
|
||||
v.ox += cx
|
||||
v.cx = 0
|
||||
} else { // move to previous line
|
||||
cy--
|
||||
if prevLineWidth > 0 {
|
||||
if !v.Wrap { // set origin so the EOL is visible
|
||||
nox := prevLineWidth - maxX + 1
|
||||
if nox < 0 {
|
||||
v.ox = 0
|
||||
} else {
|
||||
v.ox = nox
|
||||
}
|
||||
}
|
||||
v.cx = prevLineWidth
|
||||
} else {
|
||||
if !v.Wrap {
|
||||
v.ox = 0
|
||||
}
|
||||
v.cx = 0
|
||||
}
|
||||
}
|
||||
} else { // stay on the same line
|
||||
if v.Wrap {
|
||||
v.cx = cx
|
||||
} else {
|
||||
if cx >= maxX {
|
||||
v.ox += cx - maxX + 1
|
||||
v.cx = maxX
|
||||
} else {
|
||||
v.cx = cx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// adjust cursor's y position and view's y origin
|
||||
if cy < 0 {
|
||||
if v.oy > 0 {
|
||||
v.oy--
|
||||
}
|
||||
} else if writeMode || v.oy+cy < len(v.viewLines) {
|
||||
if cy >= maxY {
|
||||
v.oy++
|
||||
} else {
|
||||
v.cy = cy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// writeRune writes a rune into the view's internal buffer, at the
|
||||
// position corresponding to the point (x, y). The length of the internal
|
||||
// buffer is increased if the point is out of bounds. Overwrite mode is
|
||||
// governed by the value of View.overwrite.
|
||||
func (v *View) writeRune(x, y int, ch rune) error {
|
||||
v.tainted = true
|
||||
|
||||
x, y, err := v.realPosition(x, y)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if x < 0 || y < 0 {
|
||||
return errors.New("invalid point")
|
||||
}
|
||||
|
||||
if y >= len(v.lines) {
|
||||
s := make([][]cell, y-len(v.lines)+1)
|
||||
v.lines = append(v.lines, s...)
|
||||
}
|
||||
|
||||
olen := len(v.lines[y])
|
||||
|
||||
var s []cell
|
||||
if x >= len(v.lines[y]) {
|
||||
s = make([]cell, x-len(v.lines[y])+1)
|
||||
} else if !v.Overwrite {
|
||||
s = make([]cell, 1)
|
||||
}
|
||||
v.lines[y] = append(v.lines[y], s...)
|
||||
|
||||
if !v.Overwrite || (v.Overwrite && x >= olen-1) {
|
||||
copy(v.lines[y][x+1:], v.lines[y][x:])
|
||||
}
|
||||
v.lines[y][x] = cell{
|
||||
fgColor: v.FgColor,
|
||||
bgColor: v.BgColor,
|
||||
chr: ch,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteRune removes a rune from the view's internal buffer, at the
|
||||
// position corresponding to the point (x, y).
|
||||
func (v *View) deleteRune(x, y int) error {
|
||||
v.tainted = true
|
||||
|
||||
x, y, err := v.realPosition(x, y)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
|
||||
return errors.New("invalid point")
|
||||
}
|
||||
v.lines[y] = append(v.lines[y][:x], v.lines[y][x+1:]...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// mergeLines merges the lines "y" and "y+1" if possible.
|
||||
func (v *View) mergeLines(y int) error {
|
||||
v.tainted = true
|
||||
|
||||
_, y, err := v.realPosition(0, y)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if y < 0 || y >= len(v.lines) {
|
||||
return errors.New("invalid point")
|
||||
}
|
||||
|
||||
if y < len(v.lines)-1 { // otherwise we don't need to merge anything
|
||||
v.lines[y] = append(v.lines[y], v.lines[y+1]...)
|
||||
v.lines = append(v.lines[:y+1], v.lines[y+2:]...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// editLine inserts a new line under the cursor.
|
||||
func (v *View) editLine() error {
|
||||
v.addLine(v.cy + 1)
|
||||
if err := v.SetCursor(v.cx, v.cy+1); err != nil {
|
||||
if err := v.SetOrigin(v.ox, v.oy+1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := v.SetCursor(0, v.cy); err != nil {
|
||||
// breakLine breaks a line of the internal buffer at the position corresponding
|
||||
// to the point (x, y).
|
||||
func (v *View) breakLine(x, y int) error {
|
||||
v.tainted = true
|
||||
|
||||
x, y, err := v.realPosition(x, y)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := v.SetOrigin(0, v.oy); err != nil {
|
||||
return err
|
||||
|
||||
if y < 0 || y >= len(v.lines) {
|
||||
return errors.New("invalid point")
|
||||
}
|
||||
|
||||
var left, right []cell
|
||||
if x < len(v.lines[y]) { // break line
|
||||
left = make([]cell, len(v.lines[y][:x]))
|
||||
copy(left, v.lines[y][:x])
|
||||
right = make([]cell, len(v.lines[y][x:]))
|
||||
copy(right, v.lines[y][x:])
|
||||
} else { // new empty line
|
||||
left = v.lines[y]
|
||||
}
|
||||
|
||||
lines := make([][]cell, len(v.lines)+1)
|
||||
lines[y] = left
|
||||
lines[y+1] = right
|
||||
copy(lines, v.lines[:y])
|
||||
copy(lines[y+2:], v.lines[y+1:])
|
||||
v.lines = lines
|
||||
return nil
|
||||
}
|
||||
|
229
escape.go
Normal file
229
escape.go
Normal file
@ -0,0 +1,229 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gocui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type escapeInterpreter struct {
|
||||
state escapeState
|
||||
curch rune
|
||||
csiParam []string
|
||||
curFgColor, curBgColor Attribute
|
||||
mode OutputMode
|
||||
}
|
||||
|
||||
type escapeState int
|
||||
|
||||
const (
|
||||
stateNone escapeState = iota
|
||||
stateEscape
|
||||
stateCSI
|
||||
stateParams
|
||||
)
|
||||
|
||||
var (
|
||||
errNotCSI = errors.New("Not a CSI escape sequence")
|
||||
errCSIParseError = errors.New("CSI escape sequence parsing error")
|
||||
errCSITooLong = errors.New("CSI escape sequence is too long")
|
||||
)
|
||||
|
||||
// runes in case of error will output the non-parsed runes as a string.
|
||||
func (ei *escapeInterpreter) runes() []rune {
|
||||
switch ei.state {
|
||||
case stateNone:
|
||||
return []rune{0x1b}
|
||||
case stateEscape:
|
||||
return []rune{0x1b, ei.curch}
|
||||
case stateCSI:
|
||||
return []rune{0x1b, '[', ei.curch}
|
||||
case stateParams:
|
||||
ret := []rune{0x1b, '['}
|
||||
for _, s := range ei.csiParam {
|
||||
ret = append(ret, []rune(s)...)
|
||||
ret = append(ret, ';')
|
||||
}
|
||||
return append(ret, ei.curch)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// newEscapeInterpreter returns an escapeInterpreter that will be able to parse
|
||||
// terminal escape sequences.
|
||||
func newEscapeInterpreter(mode OutputMode) *escapeInterpreter {
|
||||
ei := &escapeInterpreter{
|
||||
state: stateNone,
|
||||
curFgColor: ColorDefault,
|
||||
curBgColor: ColorDefault,
|
||||
mode: mode,
|
||||
}
|
||||
return ei
|
||||
}
|
||||
|
||||
// reset sets the escapeInterpreter in initial state.
|
||||
func (ei *escapeInterpreter) reset() {
|
||||
ei.state = stateNone
|
||||
ei.curFgColor = ColorDefault
|
||||
ei.curBgColor = ColorDefault
|
||||
ei.csiParam = nil
|
||||
}
|
||||
|
||||
// parseOne parses a rune. If isEscape is true, it means that the rune is part
|
||||
// of an escape sequence, and as such should not be printed verbatim. Otherwise,
|
||||
// it's not an escape sequence.
|
||||
func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) {
|
||||
// Sanity checks
|
||||
if len(ei.csiParam) > 20 {
|
||||
return false, errCSITooLong
|
||||
}
|
||||
if len(ei.csiParam) > 0 && len(ei.csiParam[len(ei.csiParam)-1]) > 255 {
|
||||
return false, errCSITooLong
|
||||
}
|
||||
|
||||
ei.curch = ch
|
||||
|
||||
switch ei.state {
|
||||
case stateNone:
|
||||
if ch == 0x1b {
|
||||
ei.state = stateEscape
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
case stateEscape:
|
||||
if ch == '[' {
|
||||
ei.state = stateCSI
|
||||
return true, nil
|
||||
}
|
||||
return false, errNotCSI
|
||||
case stateCSI:
|
||||
switch {
|
||||
case ch >= '0' && ch <= '9':
|
||||
ei.csiParam = append(ei.csiParam, "")
|
||||
case ch == 'm':
|
||||
ei.csiParam = append(ei.csiParam, "0")
|
||||
default:
|
||||
return false, errCSIParseError
|
||||
}
|
||||
ei.state = stateParams
|
||||
fallthrough
|
||||
case stateParams:
|
||||
switch {
|
||||
case ch >= '0' && ch <= '9':
|
||||
ei.csiParam[len(ei.csiParam)-1] += string(ch)
|
||||
return true, nil
|
||||
case ch == ';':
|
||||
ei.csiParam = append(ei.csiParam, "")
|
||||
return true, nil
|
||||
case ch == 'm':
|
||||
var err error
|
||||
switch ei.mode {
|
||||
case OutputNormal:
|
||||
err = ei.outputNormal()
|
||||
case Output256:
|
||||
err = ei.output256()
|
||||
}
|
||||
if err != nil {
|
||||
return false, errCSIParseError
|
||||
}
|
||||
|
||||
ei.state = stateNone
|
||||
ei.csiParam = nil
|
||||
return true, nil
|
||||
default:
|
||||
return false, errCSIParseError
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// outputNormal provides 8 different colors:
|
||||
// black, red, green, yellow, blue, magenta, cyan, white
|
||||
func (ei *escapeInterpreter) outputNormal() error {
|
||||
for _, param := range ei.csiParam {
|
||||
p, err := strconv.Atoi(param)
|
||||
if err != nil {
|
||||
return errCSIParseError
|
||||
}
|
||||
|
||||
switch {
|
||||
case p >= 30 && p <= 37:
|
||||
ei.curFgColor = Attribute(p - 30 + 1)
|
||||
case p == 39:
|
||||
ei.curFgColor = ColorDefault
|
||||
case p >= 40 && p <= 47:
|
||||
ei.curBgColor = Attribute(p - 40 + 1)
|
||||
case p == 49:
|
||||
ei.curBgColor = ColorDefault
|
||||
case p == 1:
|
||||
ei.curFgColor |= AttrBold
|
||||
case p == 4:
|
||||
ei.curFgColor |= AttrUnderline
|
||||
case p == 7:
|
||||
ei.curFgColor |= AttrReverse
|
||||
case p == 0:
|
||||
ei.curFgColor = ColorDefault
|
||||
ei.curBgColor = ColorDefault
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// output256 allows you to leverage the 256-colors terminal mode:
|
||||
// 0x01 - 0x08: the 8 colors as in OutputNormal
|
||||
// 0x09 - 0x10: Color* | AttrBold
|
||||
// 0x11 - 0xe8: 216 different colors
|
||||
// 0xe9 - 0x1ff: 24 different shades of grey
|
||||
func (ei *escapeInterpreter) output256() error {
|
||||
if len(ei.csiParam) < 3 {
|
||||
return ei.outputNormal()
|
||||
}
|
||||
|
||||
mode, err := strconv.Atoi(ei.csiParam[1])
|
||||
if err != nil {
|
||||
return errCSIParseError
|
||||
}
|
||||
if mode != 5 {
|
||||
return ei.outputNormal()
|
||||
}
|
||||
|
||||
fgbg, err := strconv.Atoi(ei.csiParam[0])
|
||||
if err != nil {
|
||||
return errCSIParseError
|
||||
}
|
||||
color, err := strconv.Atoi(ei.csiParam[2])
|
||||
if err != nil {
|
||||
return errCSIParseError
|
||||
}
|
||||
|
||||
switch fgbg {
|
||||
case 38:
|
||||
ei.curFgColor = Attribute(color + 1)
|
||||
|
||||
for _, param := range ei.csiParam[3:] {
|
||||
p, err := strconv.Atoi(param)
|
||||
if err != nil {
|
||||
return errCSIParseError
|
||||
}
|
||||
|
||||
switch {
|
||||
case p == 1:
|
||||
ei.curFgColor |= AttrBold
|
||||
case p == 4:
|
||||
ei.curFgColor |= AttrUnderline
|
||||
case p == 7:
|
||||
ei.curFgColor |= AttrReverse
|
||||
}
|
||||
}
|
||||
case 48:
|
||||
ei.curBgColor = Attribute(color + 1)
|
||||
default:
|
||||
return errCSIParseError
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module github.com/jroimartin/gocui
|
||||
|
||||
go 1.16
|
||||
|
||||
require github.com/nsf/termbox-go v1.1.1
|
4
go.sum
Normal file
4
go.sum
Normal file
@ -0,0 +1,4 @@
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY=
|
||||
github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=
|
617
gui.go
617
gui.go
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -11,51 +11,83 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrorQuit is used to decide if the MainLoop finished succesfully.
|
||||
ErrorQuit error = errors.New("quit")
|
||||
// ErrQuit is used to decide if the MainLoop finished successfully.
|
||||
ErrQuit = errors.New("quit")
|
||||
|
||||
// ErrorUnkView allows to assert if a View must be initialized.
|
||||
ErrorUnkView error = errors.New("unknown view")
|
||||
// ErrUnknownView allows to assert if a View must be initialized.
|
||||
ErrUnknownView = errors.New("unknown view")
|
||||
)
|
||||
|
||||
// OutputMode represents the terminal's output mode (8 or 256 colors).
|
||||
type OutputMode termbox.OutputMode
|
||||
|
||||
const (
|
||||
// OutputNormal provides 8-colors terminal mode.
|
||||
OutputNormal = OutputMode(termbox.OutputNormal)
|
||||
|
||||
// Output256 provides 256-colors terminal mode.
|
||||
Output256 = OutputMode(termbox.Output256)
|
||||
)
|
||||
|
||||
// Gui represents the whole User Interface, including the views, layouts
|
||||
// and keybindings.
|
||||
type Gui struct {
|
||||
events chan termbox.Event
|
||||
tbEvents chan termbox.Event
|
||||
userEvents chan userEvent
|
||||
views []*View
|
||||
currentView *View
|
||||
layout func(*Gui) error
|
||||
managers []Manager
|
||||
keybindings []*keybinding
|
||||
maxX, maxY int
|
||||
outputMode OutputMode
|
||||
|
||||
// BgColor and FgColor allow to configure the background and foreground
|
||||
// colors of the GUI.
|
||||
BgColor, FgColor Attribute
|
||||
|
||||
// SelBgColor and SelFgColor are used to configure the background and
|
||||
// foreground colors of the selected line, when it is highlighted.
|
||||
// SelBgColor and SelFgColor allow to configure the background and
|
||||
// foreground colors of the frame of the current view.
|
||||
SelBgColor, SelFgColor Attribute
|
||||
|
||||
// If ShowCursor is true then the cursor is enabled.
|
||||
ShowCursor bool
|
||||
// If Highlight is true, Sel{Bg,Fg}Colors will be used to draw the
|
||||
// frame of the current view.
|
||||
Highlight bool
|
||||
|
||||
// If Cursor is true then the cursor is enabled.
|
||||
Cursor bool
|
||||
|
||||
// If Mouse is true then mouse events will be enabled.
|
||||
Mouse bool
|
||||
|
||||
// If InputEsc is true, when ESC sequence is in the buffer and it doesn't
|
||||
// match any known sequence, ESC means KeyEsc.
|
||||
InputEsc bool
|
||||
|
||||
// If ASCII is true then use ASCII instead of unicode to draw the
|
||||
// interface. Using ASCII is more portable.
|
||||
ASCII bool
|
||||
}
|
||||
|
||||
// NewGui returns a new Gui object.
|
||||
func NewGui() *Gui {
|
||||
return &Gui{}
|
||||
}
|
||||
|
||||
// Init initializes the library. This function must be called before
|
||||
// any other functions.
|
||||
func (g *Gui) Init() error {
|
||||
// NewGui returns a new Gui object with a given output mode.
|
||||
func NewGui(mode OutputMode) (*Gui, error) {
|
||||
if err := termbox.Init(); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
g.events = make(chan termbox.Event, 20)
|
||||
|
||||
g := &Gui{}
|
||||
|
||||
g.outputMode = mode
|
||||
termbox.SetOutputMode(termbox.OutputMode(mode))
|
||||
|
||||
g.tbEvents = make(chan termbox.Event, 20)
|
||||
g.userEvents = make(chan userEvent, 20)
|
||||
|
||||
g.maxX, g.maxY = termbox.Size()
|
||||
g.BgColor = ColorBlack
|
||||
g.FgColor = ColorWhite
|
||||
return nil
|
||||
|
||||
g.BgColor, g.FgColor = ColorDefault, ColorDefault
|
||||
g.SelBgColor, g.SelFgColor = ColorDefault, ColorDefault
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// Close finalizes the library. It should be called after a successful
|
||||
@ -71,12 +103,12 @@ func (g *Gui) Size() (x, y int) {
|
||||
|
||||
// SetRune writes a rune at the given point, relative to the top-left
|
||||
// corner of the terminal. It checks if the position is valid and applies
|
||||
// the gui's colors.
|
||||
func (g *Gui) SetRune(x, y int, ch rune) error {
|
||||
// the given colors.
|
||||
func (g *Gui) SetRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
|
||||
if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY {
|
||||
return errors.New("invalid point")
|
||||
}
|
||||
termbox.SetCell(x, y, ch, termbox.Attribute(g.FgColor), termbox.Attribute(g.BgColor))
|
||||
termbox.SetCell(x, y, ch, termbox.Attribute(fgColor), termbox.Attribute(bgColor))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -93,7 +125,7 @@ func (g *Gui) Rune(x, y int) (rune, error) {
|
||||
// SetView creates a new view with its top-left corner at (x0, y0)
|
||||
// and the bottom-right one at (x1, y1). If a view with the same name
|
||||
// already exists, its dimensions are updated; otherwise, the error
|
||||
// ErrorUnkView is returned, which allows to assert if the View must
|
||||
// ErrUnknownView is returned, which allows to assert if the View must
|
||||
// be initialized. It checks if the position is valid.
|
||||
func (g *Gui) SetView(name string, x0, y0, x1, y1 int) (*View, error) {
|
||||
if x0 >= x1 || y0 >= y1 {
|
||||
@ -103,30 +135,84 @@ func (g *Gui) SetView(name string, x0, y0, x1, y1 int) (*View, error) {
|
||||
return nil, errors.New("invalid name")
|
||||
}
|
||||
|
||||
if v := g.View(name); v != nil {
|
||||
if v, err := g.View(name); err == nil {
|
||||
v.x0 = x0
|
||||
v.y0 = y0
|
||||
v.x1 = x1
|
||||
v.y1 = y1
|
||||
v.tainted = true
|
||||
return v, nil
|
||||
}
|
||||
|
||||
v := newView(name, x0, y0, x1, y1)
|
||||
v.bgColor, v.fgColor = g.BgColor, g.FgColor
|
||||
v.selBgColor, v.selFgColor = g.SelBgColor, g.SelFgColor
|
||||
v := newView(name, x0, y0, x1, y1, g.outputMode)
|
||||
v.BgColor, v.FgColor = g.BgColor, g.FgColor
|
||||
v.SelBgColor, v.SelFgColor = g.SelBgColor, g.SelFgColor
|
||||
g.views = append(g.views, v)
|
||||
return v, ErrorUnkView
|
||||
return v, ErrUnknownView
|
||||
}
|
||||
|
||||
// View returns a pointer to the view with the given name, or nil if
|
||||
// a view with that name does not exist.
|
||||
func (g *Gui) View(name string) *View {
|
||||
for _, v := range g.views {
|
||||
// SetViewOnTop sets the given view on top of the existing ones.
|
||||
func (g *Gui) SetViewOnTop(name string) (*View, error) {
|
||||
for i, v := range g.views {
|
||||
if v.name == name {
|
||||
return v
|
||||
s := append(g.views[:i], g.views[i+1:]...)
|
||||
g.views = append(s, v)
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return nil, ErrUnknownView
|
||||
}
|
||||
|
||||
// SetViewOnBottom sets the given view on bottom of the existing ones.
|
||||
func (g *Gui) SetViewOnBottom(name string) (*View, error) {
|
||||
for i, v := range g.views {
|
||||
if v.name == name {
|
||||
s := append(g.views[:i], g.views[i+1:]...)
|
||||
g.views = append([]*View{v}, s...)
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrUnknownView
|
||||
}
|
||||
|
||||
// Views returns all the views in the GUI.
|
||||
func (g *Gui) Views() []*View {
|
||||
return g.views
|
||||
}
|
||||
|
||||
// View returns a pointer to the view with the given name, or error
|
||||
// ErrUnknownView if a view with that name does not exist.
|
||||
func (g *Gui) View(name string) (*View, error) {
|
||||
for _, v := range g.views {
|
||||
if v.name == name {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrUnknownView
|
||||
}
|
||||
|
||||
// ViewByPosition returns a pointer to a view matching the given position, or
|
||||
// error ErrUnknownView if a view in that position does not exist.
|
||||
func (g *Gui) ViewByPosition(x, y int) (*View, error) {
|
||||
// traverse views in reverse order checking top views first
|
||||
for i := len(g.views); i > 0; i-- {
|
||||
v := g.views[i-1]
|
||||
if x > v.x0 && x < v.x1 && y > v.y0 && y < v.y1 {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrUnknownView
|
||||
}
|
||||
|
||||
// ViewPosition returns the coordinates of the view with the given name, or
|
||||
// error ErrUnknownView if a view with that name does not exist.
|
||||
func (g *Gui) ViewPosition(name string) (x0, y0, x1, y1 int, err error) {
|
||||
for _, v := range g.views {
|
||||
if v.name == name {
|
||||
return v.x0, v.y0, v.x1, v.y1, nil
|
||||
}
|
||||
}
|
||||
return 0, 0, 0, 0, ErrUnknownView
|
||||
}
|
||||
|
||||
// DeleteView deletes a view by name.
|
||||
@ -137,85 +223,182 @@ func (g *Gui) DeleteView(name string) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrorUnkView
|
||||
return ErrUnknownView
|
||||
}
|
||||
|
||||
// SetCurrentView gives the focus to a given view.
|
||||
func (g *Gui) SetCurrentView(name string) error {
|
||||
func (g *Gui) SetCurrentView(name string) (*View, error) {
|
||||
for _, v := range g.views {
|
||||
if v.name == name {
|
||||
g.currentView = v
|
||||
return nil
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return ErrorUnkView
|
||||
return nil, ErrUnknownView
|
||||
}
|
||||
|
||||
// CurrentView returns the currently focused view, or nil if no view
|
||||
// owns the focus.
|
||||
func (g *Gui) CurrentView() *View {
|
||||
return g.currentView
|
||||
}
|
||||
|
||||
// SetKeybinding creates a new keybinding. If viewname equals to ""
|
||||
// (empty string) then the keybinding will apply to all views. key must
|
||||
// be a rune or a Key.
|
||||
func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, h KeybindingHandler) error {
|
||||
func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, handler func(*Gui, *View) error) error {
|
||||
var kb *keybinding
|
||||
|
||||
switch k := key.(type) {
|
||||
case Key:
|
||||
kb = newKeybinding(viewname, k, 0, mod, h)
|
||||
case rune:
|
||||
kb = newKeybinding(viewname, 0, k, mod, h)
|
||||
default:
|
||||
return errors.New("unknown type")
|
||||
k, ch, err := getKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kb = newKeybinding(viewname, k, ch, mod, handler)
|
||||
g.keybindings = append(g.keybindings, kb)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLayout sets the current layout. A layout is a function that
|
||||
// will be called everytime the gui is re-drawed, it must contain
|
||||
// the base views and its initializations.
|
||||
func (g *Gui) SetLayout(layout func(*Gui) error) {
|
||||
g.layout = layout
|
||||
// DeleteKeybinding deletes a keybinding.
|
||||
func (g *Gui) DeleteKeybinding(viewname string, key interface{}, mod Modifier) error {
|
||||
k, ch, err := getKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, kb := range g.keybindings {
|
||||
if kb.viewName == viewname && kb.ch == ch && kb.key == k && kb.mod == mod {
|
||||
g.keybindings = append(g.keybindings[:i], g.keybindings[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("keybinding not found")
|
||||
}
|
||||
|
||||
// DeleteKeybindings deletes all keybindings of view.
|
||||
func (g *Gui) DeleteKeybindings(viewname string) {
|
||||
var s []*keybinding
|
||||
for _, kb := range g.keybindings {
|
||||
if kb.viewName != viewname {
|
||||
s = append(s, kb)
|
||||
}
|
||||
}
|
||||
g.keybindings = s
|
||||
}
|
||||
|
||||
// getKey takes an empty interface with a key and returns the corresponding
|
||||
// typed Key or rune.
|
||||
func getKey(key interface{}) (Key, rune, error) {
|
||||
switch t := key.(type) {
|
||||
case Key:
|
||||
return t, 0, nil
|
||||
case rune:
|
||||
return 0, t, nil
|
||||
default:
|
||||
return 0, 0, errors.New("unknown type")
|
||||
}
|
||||
}
|
||||
|
||||
// userEvent represents an event triggered by the user.
|
||||
type userEvent struct {
|
||||
f func(*Gui) error
|
||||
}
|
||||
|
||||
// Update executes the passed function. This method can be called safely from a
|
||||
// goroutine in order to update the GUI. It is important to note that the
|
||||
// passed function won't be executed immediately, instead it will be added to
|
||||
// the user events queue. Given that Update spawns a goroutine, the order in
|
||||
// which the user events will be handled is not guaranteed.
|
||||
func (g *Gui) Update(f func(*Gui) error) {
|
||||
go func() { g.userEvents <- userEvent{f: f} }()
|
||||
}
|
||||
|
||||
// A Manager is in charge of GUI's layout and can be used to build widgets.
|
||||
type Manager interface {
|
||||
// Layout is called every time the GUI is redrawn, it must contain the
|
||||
// base views and its initializations.
|
||||
Layout(*Gui) error
|
||||
}
|
||||
|
||||
// The ManagerFunc type is an adapter to allow the use of ordinary functions as
|
||||
// Managers. If f is a function with the appropriate signature, ManagerFunc(f)
|
||||
// is an Manager object that calls f.
|
||||
type ManagerFunc func(*Gui) error
|
||||
|
||||
// Layout calls f(g)
|
||||
func (f ManagerFunc) Layout(g *Gui) error {
|
||||
return f(g)
|
||||
}
|
||||
|
||||
// SetManager sets the given GUI managers. It deletes all views and
|
||||
// keybindings.
|
||||
func (g *Gui) SetManager(managers ...Manager) {
|
||||
g.managers = managers
|
||||
g.currentView = nil
|
||||
g.views = nil
|
||||
go func() { g.events <- termbox.Event{Type: termbox.EventResize} }()
|
||||
g.keybindings = nil
|
||||
|
||||
go func() { g.tbEvents <- termbox.Event{Type: termbox.EventResize} }()
|
||||
}
|
||||
|
||||
// SetManagerFunc sets the given manager function. It deletes all views and
|
||||
// keybindings.
|
||||
func (g *Gui) SetManagerFunc(manager func(*Gui) error) {
|
||||
g.SetManager(ManagerFunc(manager))
|
||||
}
|
||||
|
||||
// MainLoop runs the main loop until an error is returned. A successful
|
||||
// finish should return ErrorQuit.
|
||||
// finish should return ErrQuit.
|
||||
func (g *Gui) MainLoop() error {
|
||||
go func() {
|
||||
for {
|
||||
g.events <- termbox.PollEvent()
|
||||
g.tbEvents <- termbox.PollEvent()
|
||||
}
|
||||
}()
|
||||
|
||||
termbox.SetInputMode(termbox.InputAlt)
|
||||
inputMode := termbox.InputAlt
|
||||
if g.InputEsc {
|
||||
inputMode = termbox.InputEsc
|
||||
}
|
||||
if g.Mouse {
|
||||
inputMode |= termbox.InputMouse
|
||||
}
|
||||
termbox.SetInputMode(inputMode)
|
||||
|
||||
if err := g.Flush(); err != nil {
|
||||
if err := g.flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
ev := <-g.events
|
||||
if err := g.handleEvent(&ev); err != nil {
|
||||
return err
|
||||
select {
|
||||
case ev := <-g.tbEvents:
|
||||
if err := g.handleEvent(&ev); err != nil {
|
||||
return err
|
||||
}
|
||||
case ev := <-g.userEvents:
|
||||
if err := ev.f(g); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := g.consumeevents(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.Flush(); err != nil {
|
||||
if err := g.flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// consumeevents handles the remaining events in the events pool.
|
||||
func (g *Gui) consumeevents() error {
|
||||
for {
|
||||
select {
|
||||
case ev := <-g.events:
|
||||
case ev := <-g.tbEvents:
|
||||
if err := g.handleEvent(&ev); err != nil {
|
||||
return err
|
||||
}
|
||||
case ev := <-g.userEvents:
|
||||
if err := ev.f(g); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
@ -226,7 +409,7 @@ func (g *Gui) consumeevents() error {
|
||||
// etc.)
|
||||
func (g *Gui) handleEvent(ev *termbox.Event) error {
|
||||
switch ev.Type {
|
||||
case termbox.EventKey:
|
||||
case termbox.EventKey, termbox.EventMouse:
|
||||
return g.onKey(ev)
|
||||
case termbox.EventError:
|
||||
return ev.Err
|
||||
@ -235,46 +418,73 @@ func (g *Gui) handleEvent(ev *termbox.Event) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Flush updates the gui, re-drawing frames and buffers.
|
||||
func (g *Gui) Flush() error {
|
||||
if g.layout == nil {
|
||||
return errors.New("Null layout")
|
||||
}
|
||||
|
||||
// flush updates the gui, re-drawing frames and buffers.
|
||||
func (g *Gui) flush() error {
|
||||
termbox.Clear(termbox.Attribute(g.FgColor), termbox.Attribute(g.BgColor))
|
||||
g.maxX, g.maxY = termbox.Size()
|
||||
if err := g.layout(g); err != nil {
|
||||
return err
|
||||
|
||||
maxX, maxY := termbox.Size()
|
||||
// if GUI's size has changed, we need to redraw all views
|
||||
if maxX != g.maxX || maxY != g.maxY {
|
||||
for _, v := range g.views {
|
||||
v.tainted = true
|
||||
}
|
||||
}
|
||||
g.maxX, g.maxY = maxX, maxY
|
||||
|
||||
for _, m := range g.managers {
|
||||
if err := m.Layout(g); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, v := range g.views {
|
||||
if err := g.drawFrame(v); err != nil {
|
||||
return err
|
||||
if v.Frame {
|
||||
var fgColor, bgColor Attribute
|
||||
if g.Highlight && v == g.currentView {
|
||||
fgColor = g.SelFgColor
|
||||
bgColor = g.SelBgColor
|
||||
} else {
|
||||
fgColor = g.FgColor
|
||||
bgColor = g.BgColor
|
||||
}
|
||||
|
||||
if err := g.drawFrameEdges(v, fgColor, bgColor); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.drawFrameCorners(v, fgColor, bgColor); err != nil {
|
||||
return err
|
||||
}
|
||||
if v.Title != "" {
|
||||
if err := g.drawTitle(v, fgColor, bgColor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := g.draw(v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := g.drawIntersections(); err != nil {
|
||||
return err
|
||||
}
|
||||
termbox.Flush()
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// drawFrame draws the horizontal and vertical edges of a view.
|
||||
func (g *Gui) drawFrame(v *View) error {
|
||||
// drawFrameEdges draws the horizontal and vertical edges of a view.
|
||||
func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error {
|
||||
runeH, runeV := '─', '│'
|
||||
if g.ASCII {
|
||||
runeH, runeV = '-', '|'
|
||||
}
|
||||
|
||||
for x := v.x0 + 1; x < v.x1 && x < g.maxX; x++ {
|
||||
if x < 0 {
|
||||
continue
|
||||
}
|
||||
if v.y0 > -1 && v.y0 < g.maxY {
|
||||
if err := g.SetRune(x, v.y0, '─'); err != nil {
|
||||
if err := g.SetRune(x, v.y0, runeH, fgColor, bgColor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if v.y1 > -1 && v.y1 < g.maxY {
|
||||
if err := g.SetRune(x, v.y1, '─'); err != nil {
|
||||
if err := g.SetRune(x, v.y1, runeH, fgColor, bgColor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -284,12 +494,12 @@ func (g *Gui) drawFrame(v *View) error {
|
||||
continue
|
||||
}
|
||||
if v.x0 > -1 && v.x0 < g.maxX {
|
||||
if err := g.SetRune(v.x0, y, '│'); err != nil {
|
||||
if err := g.SetRune(v.x0, y, runeV, fgColor, bgColor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if v.x1 > -1 && v.x1 < g.maxX {
|
||||
if err := g.SetRune(v.x1, y, '│'); err != nil {
|
||||
if err := g.SetRune(v.x1, y, runeV, fgColor, bgColor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -297,22 +507,71 @@ func (g *Gui) drawFrame(v *View) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// draw manages the cursor and calls the draw function of a view.
|
||||
func (g *Gui) draw(v *View) error {
|
||||
if g.ShowCursor {
|
||||
if v := g.currentView; v != nil {
|
||||
maxX, maxY := v.Size()
|
||||
cx, cy := v.cx, v.cy
|
||||
if v.cx >= maxX {
|
||||
cx = maxX - 1
|
||||
}
|
||||
if v.cy >= maxY {
|
||||
cy = maxY - 1
|
||||
}
|
||||
if err := v.SetCursor(cx, cy); err != nil {
|
||||
// drawFrameCorners draws the corners of the view.
|
||||
func (g *Gui) drawFrameCorners(v *View, fgColor, bgColor Attribute) error {
|
||||
runeTL, runeTR, runeBL, runeBR := '┌', '┐', '└', '┘'
|
||||
if g.ASCII {
|
||||
runeTL, runeTR, runeBL, runeBR = '+', '+', '+', '+'
|
||||
}
|
||||
|
||||
corners := []struct {
|
||||
x, y int
|
||||
ch rune
|
||||
}{{v.x0, v.y0, runeTL}, {v.x1, v.y0, runeTR}, {v.x0, v.y1, runeBL}, {v.x1, v.y1, runeBR}}
|
||||
|
||||
for _, c := range corners {
|
||||
if c.x >= 0 && c.y >= 0 && c.x < g.maxX && c.y < g.maxY {
|
||||
if err := g.SetRune(c.x, c.y, c.ch, fgColor, bgColor); err != nil {
|
||||
return err
|
||||
}
|
||||
termbox.SetCursor(v.x0+v.cx+1, v.y0+v.cy+1)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// drawTitle draws the title of the view.
|
||||
func (g *Gui) drawTitle(v *View, fgColor, bgColor Attribute) error {
|
||||
if v.y0 < 0 || v.y0 >= g.maxY {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i, ch := range v.Title {
|
||||
x := v.x0 + i + 2
|
||||
if x < 0 {
|
||||
continue
|
||||
} else if x > v.x1-2 || x >= g.maxX {
|
||||
break
|
||||
}
|
||||
if err := g.SetRune(x, v.y0, ch, fgColor, bgColor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// draw manages the cursor and calls the draw function of a view.
|
||||
func (g *Gui) draw(v *View) error {
|
||||
if g.Cursor {
|
||||
if curview := g.currentView; curview != nil {
|
||||
vMaxX, vMaxY := curview.Size()
|
||||
if curview.cx < 0 {
|
||||
curview.cx = 0
|
||||
} else if curview.cx >= vMaxX {
|
||||
curview.cx = vMaxX - 1
|
||||
}
|
||||
if curview.cy < 0 {
|
||||
curview.cy = 0
|
||||
} else if curview.cy >= vMaxY {
|
||||
curview.cy = vMaxY - 1
|
||||
}
|
||||
|
||||
gMaxX, gMaxY := g.Size()
|
||||
cx, cy := curview.x0+curview.cx+1, curview.y0+curview.cy+1
|
||||
if cx >= 0 && cx < gMaxX && cy >= 0 && cy < gMaxY {
|
||||
termbox.SetCursor(cx, cy)
|
||||
} else {
|
||||
termbox.HideCursor()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
termbox.HideCursor()
|
||||
@ -325,127 +584,53 @@ func (g *Gui) draw(v *View) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// drawIntersections draws the corners of each view, based on the type
|
||||
// of the edges that converge at these points.
|
||||
func (g *Gui) drawIntersections() error {
|
||||
for _, v := range g.views {
|
||||
if ch, ok := g.intersectionRune(v.x0, v.y0); ok {
|
||||
if err := g.SetRune(v.x0, v.y0, ch); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if ch, ok := g.intersectionRune(v.x0, v.y1); ok {
|
||||
if err := g.SetRune(v.x0, v.y1, ch); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if ch, ok := g.intersectionRune(v.x1, v.y0); ok {
|
||||
if err := g.SetRune(v.x1, v.y0, ch); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if ch, ok := g.intersectionRune(v.x1, v.y1); ok {
|
||||
if err := g.SetRune(v.x1, v.y1, ch); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// intersectionRune returns the correct intersection rune at a given
|
||||
// point.
|
||||
func (g *Gui) intersectionRune(x, y int) (rune, bool) {
|
||||
if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY {
|
||||
return ' ', false
|
||||
}
|
||||
|
||||
chTop, _ := g.Rune(x, y-1)
|
||||
top := verticalRune(chTop)
|
||||
chBottom, _ := g.Rune(x, y+1)
|
||||
bottom := verticalRune(chBottom)
|
||||
chLeft, _ := g.Rune(x-1, y)
|
||||
left := horizontalRune(chLeft)
|
||||
chRight, _ := g.Rune(x+1, y)
|
||||
right := horizontalRune(chRight)
|
||||
|
||||
var ch rune
|
||||
switch {
|
||||
case !top && bottom && !left && right:
|
||||
ch = '┌'
|
||||
case !top && bottom && left && !right:
|
||||
ch = '┐'
|
||||
case top && !bottom && !left && right:
|
||||
ch = '└'
|
||||
case top && !bottom && left && !right:
|
||||
ch = '┘'
|
||||
case top && bottom && left && right:
|
||||
ch = '┼'
|
||||
case top && bottom && !left && right:
|
||||
ch = '├'
|
||||
case top && bottom && left && !right:
|
||||
ch = '┤'
|
||||
case !top && bottom && left && right:
|
||||
ch = '┬'
|
||||
case top && !bottom && left && right:
|
||||
ch = '┴'
|
||||
default:
|
||||
return ' ', false
|
||||
}
|
||||
return ch, true
|
||||
}
|
||||
|
||||
// verticalRune returns if the given character is a vertical rune.
|
||||
func verticalRune(ch rune) bool {
|
||||
if ch == '│' || ch == '┼' || ch == '├' || ch == '┤' {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// verticalRune returns if the given character is a horizontal rune.
|
||||
func horizontalRune(ch rune) bool {
|
||||
if ch == '─' || ch == '┼' || ch == '┬' || ch == '┴' {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// onKey manages key-press events. A keybinding handler is called when
|
||||
// a key-press event satisfies a configured keybinding. Furthermore,
|
||||
// a key-press or mouse event satisfies a configured keybinding. Furthermore,
|
||||
// currentView's internal buffer is modified if currentView.Editable is true.
|
||||
func (g *Gui) onKey(ev *termbox.Event) error {
|
||||
if g.currentView != nil && g.currentView.Editable {
|
||||
if err := g.handleEdit(g.currentView, ev); err != nil {
|
||||
switch ev.Type {
|
||||
case termbox.EventKey:
|
||||
matched, err := g.execKeybindings(g.currentView, ev)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if matched {
|
||||
break
|
||||
}
|
||||
if g.currentView != nil && g.currentView.Editable && g.currentView.Editor != nil {
|
||||
g.currentView.Editor.Edit(g.currentView, Key(ev.Key), ev.Ch, Modifier(ev.Mod))
|
||||
}
|
||||
case termbox.EventMouse:
|
||||
mx, my := ev.MouseX, ev.MouseY
|
||||
v, err := g.ViewByPosition(mx, my)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if err := v.SetCursor(mx-v.x0-1, my-v.y0-1); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := g.execKeybindings(v, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, kb := range g.keybindings {
|
||||
if kb.h != nil && ev.Ch == kb.ch && Key(ev.Key) == kb.key && Modifier(ev.Mod) == kb.mod &&
|
||||
(kb.viewName == "" || (g.currentView != nil && kb.viewName == g.currentView.name)) {
|
||||
if err := kb.h(g, g.currentView); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleEdit manages the edition mode.
|
||||
func (g *Gui) handleEdit(v *View, ev *termbox.Event) error {
|
||||
switch {
|
||||
case ev.Ch != 0 && ev.Mod == 0:
|
||||
return v.editWrite(ev.Ch)
|
||||
case ev.Key == termbox.KeySpace:
|
||||
return v.editWrite(' ')
|
||||
case ev.Key == termbox.KeyBackspace || ev.Key == termbox.KeyBackspace2:
|
||||
return v.editDelete(true)
|
||||
case ev.Key == termbox.KeyDelete:
|
||||
return v.editDelete(false)
|
||||
case ev.Key == termbox.KeyInsert:
|
||||
v.overwrite = !v.overwrite
|
||||
case ev.Key == termbox.KeyEnter:
|
||||
return v.editLine()
|
||||
// execKeybindings executes the keybinding handlers that match the passed view
|
||||
// and event. The value of matched is true if there is a match and no errors.
|
||||
func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err error) {
|
||||
matched = false
|
||||
for _, kb := range g.keybindings {
|
||||
if kb.handler == nil {
|
||||
continue
|
||||
}
|
||||
if kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) && kb.matchView(v) {
|
||||
if err := kb.handler(g, v); err != nil {
|
||||
return false, err
|
||||
}
|
||||
matched = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return matched, nil
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -6,17 +6,42 @@ package gocui
|
||||
|
||||
import "github.com/nsf/termbox-go"
|
||||
|
||||
type (
|
||||
// Keys represent special keys or keys combinations.
|
||||
Key termbox.Key
|
||||
// Modifiers allow to define special keys combinations. They can be used
|
||||
// in combination with Keys or Runes when a new keybinding is defined.
|
||||
Modifier termbox.Modifier
|
||||
// KeybindingHandlers represent the actions linked to keybindings. The
|
||||
// handler is called when a key-press event satisfies a configured
|
||||
// keybinding.
|
||||
KeybindingHandler func(*Gui, *View) error
|
||||
)
|
||||
// Keybidings are used to link a given key-press event with a handler.
|
||||
type keybinding struct {
|
||||
viewName string
|
||||
key Key
|
||||
ch rune
|
||||
mod Modifier
|
||||
handler func(*Gui, *View) error
|
||||
}
|
||||
|
||||
// newKeybinding returns a new Keybinding object.
|
||||
func newKeybinding(viewname string, key Key, ch rune, mod Modifier, handler func(*Gui, *View) error) (kb *keybinding) {
|
||||
kb = &keybinding{
|
||||
viewName: viewname,
|
||||
key: key,
|
||||
ch: ch,
|
||||
mod: mod,
|
||||
handler: handler,
|
||||
}
|
||||
return kb
|
||||
}
|
||||
|
||||
// matchKeypress returns if the keybinding matches the keypress.
|
||||
func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool {
|
||||
return kb.key == key && kb.ch == ch && kb.mod == mod
|
||||
}
|
||||
|
||||
// matchView returns if the keybinding matches the current view.
|
||||
func (kb *keybinding) matchView(v *View) bool {
|
||||
if kb.viewName == "" {
|
||||
return true
|
||||
}
|
||||
return v != nil && kb.viewName == v.name
|
||||
}
|
||||
|
||||
// Key represents special keys or keys combinations.
|
||||
type Key termbox.Key
|
||||
|
||||
// Special keys.
|
||||
const (
|
||||
@ -42,6 +67,13 @@ const (
|
||||
KeyArrowDown = Key(termbox.KeyArrowDown)
|
||||
KeyArrowLeft = Key(termbox.KeyArrowLeft)
|
||||
KeyArrowRight = Key(termbox.KeyArrowRight)
|
||||
|
||||
MouseLeft = Key(termbox.MouseLeft)
|
||||
MouseMiddle = Key(termbox.MouseMiddle)
|
||||
MouseRight = Key(termbox.MouseRight)
|
||||
MouseRelease = Key(termbox.MouseRelease)
|
||||
MouseWheelUp = Key(termbox.MouseWheelUp)
|
||||
MouseWheelDown = Key(termbox.MouseWheelDown)
|
||||
)
|
||||
|
||||
// Keys combinations.
|
||||
@ -94,28 +126,12 @@ const (
|
||||
KeyCtrl8 = Key(termbox.KeyCtrl8)
|
||||
)
|
||||
|
||||
// Modifier allows to define special keys combinations. They can be used
|
||||
// in combination with Keys or Runes when a new keybinding is defined.
|
||||
type Modifier termbox.Modifier
|
||||
|
||||
// Modifiers.
|
||||
const (
|
||||
ModAlt Modifier = Modifier(termbox.ModAlt)
|
||||
ModNone Modifier = Modifier(0)
|
||||
ModAlt = Modifier(termbox.ModAlt)
|
||||
)
|
||||
|
||||
// Keybidings are used to link a given key-press event with an action.
|
||||
type keybinding struct {
|
||||
viewName string
|
||||
key Key
|
||||
ch rune
|
||||
mod Modifier
|
||||
h KeybindingHandler
|
||||
}
|
||||
|
||||
// newKeybinding returns a new Keybinding object.
|
||||
func newKeybinding(viewname string, key Key, ch rune, mod Modifier, h KeybindingHandler) (kb *keybinding) {
|
||||
kb = &keybinding{
|
||||
viewName: viewname,
|
||||
key: key,
|
||||
ch: ch,
|
||||
mod: mod,
|
||||
h: h,
|
||||
}
|
||||
return kb
|
||||
}
|
||||
|
443
view.go
443
view.go
@ -1,13 +1,13 @@
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Copyright 2014 The gocui Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gocui
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/nsf/termbox-go"
|
||||
@ -16,32 +16,96 @@ import (
|
||||
// A View is a window. It maintains its own internal buffer and cursor
|
||||
// position.
|
||||
type View struct {
|
||||
name string
|
||||
x0, y0, x1, y1 int
|
||||
ox, oy int
|
||||
cx, cy int
|
||||
lines [][]rune
|
||||
bgColor, fgColor Attribute
|
||||
selBgColor, selFgColor Attribute
|
||||
overwrite bool // overwrite in edit mode
|
||||
name string
|
||||
x0, y0, x1, y1 int
|
||||
ox, oy int
|
||||
cx, cy int
|
||||
lines [][]cell
|
||||
readOffset int
|
||||
readCache string
|
||||
|
||||
tainted bool // marks if the viewBuffer must be updated
|
||||
viewLines []viewLine // internal representation of the view's buffer
|
||||
|
||||
ei *escapeInterpreter // used to decode ESC sequences on Write
|
||||
|
||||
// BgColor and FgColor allow to configure the background and foreground
|
||||
// colors of the View.
|
||||
BgColor, FgColor Attribute
|
||||
|
||||
// SelBgColor and SelFgColor are used to configure the background and
|
||||
// foreground colors of the selected line, when it is highlighted.
|
||||
SelBgColor, SelFgColor Attribute
|
||||
|
||||
// If Editable is true, keystrokes will be added to the view's internal
|
||||
// buffer at the cursor position.
|
||||
Editable bool
|
||||
|
||||
// Editor allows to define the editor that manages the edition mode,
|
||||
// including keybindings or cursor behaviour. DefaultEditor is used by
|
||||
// default.
|
||||
Editor Editor
|
||||
|
||||
// Overwrite enables or disables the overwrite mode of the view.
|
||||
Overwrite bool
|
||||
|
||||
// If Highlight is true, Sel{Bg,Fg}Colors will be used
|
||||
// for the line under the cursor position.
|
||||
Highlight bool
|
||||
|
||||
// If Frame is true, a border will be drawn around the view.
|
||||
Frame bool
|
||||
|
||||
// If Wrap is true, the content that is written to this View is
|
||||
// automatically wrapped when it is longer than its width. If true the
|
||||
// view's x-origin will be ignored.
|
||||
Wrap bool
|
||||
|
||||
// If Autoscroll is true, the View will automatically scroll down when the
|
||||
// text overflows. If true the view's y-origin will be ignored.
|
||||
Autoscroll bool
|
||||
|
||||
// If Frame is true, Title allows to configure a title for the view.
|
||||
Title string
|
||||
|
||||
// If Mask is true, the View will display the mask instead of the real
|
||||
// content
|
||||
Mask rune
|
||||
}
|
||||
|
||||
type viewLine struct {
|
||||
linesX, linesY int // coordinates relative to v.lines
|
||||
line []cell
|
||||
}
|
||||
|
||||
type cell struct {
|
||||
chr rune
|
||||
bgColor, fgColor Attribute
|
||||
}
|
||||
|
||||
type lineType []cell
|
||||
|
||||
// String returns a string from a given cell slice.
|
||||
func (l lineType) String() string {
|
||||
str := ""
|
||||
for _, c := range l {
|
||||
str += string(c.chr)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// newView returns a new View object.
|
||||
func newView(name string, x0, y0, x1, y1 int) *View {
|
||||
func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
|
||||
v := &View{
|
||||
name: name,
|
||||
x0: x0,
|
||||
y0: y0,
|
||||
x1: x1,
|
||||
y1: y1,
|
||||
name: name,
|
||||
x0: x0,
|
||||
y0: y0,
|
||||
x1: x1,
|
||||
y1: y1,
|
||||
Frame: true,
|
||||
Editor: DefaultEditor,
|
||||
tainted: true,
|
||||
ei: newEscapeInterpreter(mode),
|
||||
}
|
||||
return v
|
||||
}
|
||||
@ -56,25 +120,42 @@ func (v *View) Name() string {
|
||||
return v.name
|
||||
}
|
||||
|
||||
// setRune writes a rune at the given point, relative to the view. It
|
||||
// checks if the position is valid and applies the view's colors, taking
|
||||
// into account if the cell must be highlighted.
|
||||
func (v *View) setRune(x, y int, ch rune) error {
|
||||
// setRune sets a rune at the given point relative to the view. It applies the
|
||||
// specified colors, taking into account if the cell must be highlighted. Also,
|
||||
// it checks if the position is valid.
|
||||
func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
|
||||
maxX, maxY := v.Size()
|
||||
if x < 0 || x >= maxX || y < 0 || y >= maxY {
|
||||
return errors.New("invalid point")
|
||||
}
|
||||
|
||||
var fgColor, bgColor Attribute
|
||||
if v.Highlight && y == v.cy {
|
||||
fgColor = v.selFgColor
|
||||
bgColor = v.selBgColor
|
||||
} else {
|
||||
fgColor = v.fgColor
|
||||
bgColor = v.bgColor
|
||||
var (
|
||||
ry, rcy int
|
||||
err error
|
||||
)
|
||||
if v.Highlight {
|
||||
_, ry, err = v.realPosition(x, y)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, rcy, err = v.realPosition(v.cx, v.cy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if v.Mask != 0 {
|
||||
fgColor = v.FgColor
|
||||
bgColor = v.BgColor
|
||||
ch = v.Mask
|
||||
} else if v.Highlight && ry == rcy {
|
||||
fgColor = v.SelFgColor
|
||||
bgColor = v.SelBgColor
|
||||
}
|
||||
|
||||
termbox.SetCell(v.x0+x+1, v.y0+y+1, ch,
|
||||
termbox.Attribute(fgColor), termbox.Attribute(bgColor))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -119,35 +200,158 @@ func (v *View) Origin() (x, y int) {
|
||||
// of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must
|
||||
// be called to clear the view's buffer.
|
||||
func (v *View) Write(p []byte) (n int, err error) {
|
||||
r := bytes.NewReader(p)
|
||||
s := bufio.NewScanner(r)
|
||||
for s.Scan() {
|
||||
line := bytes.Runes(s.Bytes())
|
||||
v.lines = append(v.lines, line)
|
||||
}
|
||||
if err := s.Err(); err != nil {
|
||||
return 0, err
|
||||
v.tainted = true
|
||||
|
||||
for _, ch := range bytes.Runes(p) {
|
||||
switch ch {
|
||||
case '\n':
|
||||
v.lines = append(v.lines, nil)
|
||||
case '\r':
|
||||
nl := len(v.lines)
|
||||
if nl > 0 {
|
||||
v.lines[nl-1] = nil
|
||||
} else {
|
||||
v.lines = make([][]cell, 1)
|
||||
}
|
||||
default:
|
||||
cells := v.parseInput(ch)
|
||||
if cells == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
nl := len(v.lines)
|
||||
if nl > 0 {
|
||||
v.lines[nl-1] = append(v.lines[nl-1], cells...)
|
||||
} else {
|
||||
v.lines = append(v.lines, cells)
|
||||
}
|
||||
}
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// parseInput parses char by char the input written to the View. It returns nil
|
||||
// while processing ESC sequences. Otherwise, it returns a cell slice that
|
||||
// contains the processed data.
|
||||
func (v *View) parseInput(ch rune) []cell {
|
||||
cells := []cell{}
|
||||
|
||||
isEscape, err := v.ei.parseOne(ch)
|
||||
if err != nil {
|
||||
for _, r := range v.ei.runes() {
|
||||
c := cell{
|
||||
fgColor: v.FgColor,
|
||||
bgColor: v.BgColor,
|
||||
chr: r,
|
||||
}
|
||||
cells = append(cells, c)
|
||||
}
|
||||
v.ei.reset()
|
||||
} else {
|
||||
if isEscape {
|
||||
return nil
|
||||
}
|
||||
c := cell{
|
||||
fgColor: v.ei.curFgColor,
|
||||
bgColor: v.ei.curBgColor,
|
||||
chr: ch,
|
||||
}
|
||||
cells = append(cells, c)
|
||||
}
|
||||
|
||||
return cells
|
||||
}
|
||||
|
||||
// Read reads data into p. It returns the number of bytes read into p.
|
||||
// At EOF, err will be io.EOF. Calling Read() after Rewind() makes the
|
||||
// cache to be refreshed with the contents of the view.
|
||||
func (v *View) Read(p []byte) (n int, err error) {
|
||||
if v.readOffset == 0 {
|
||||
v.readCache = v.Buffer()
|
||||
}
|
||||
if v.readOffset < len(v.readCache) {
|
||||
n = copy(p, v.readCache[v.readOffset:])
|
||||
v.readOffset += n
|
||||
} else {
|
||||
err = io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Rewind sets the offset for the next Read to 0, which also refresh the
|
||||
// read cache.
|
||||
func (v *View) Rewind() {
|
||||
v.readOffset = 0
|
||||
}
|
||||
|
||||
// draw re-draws the view's contents.
|
||||
func (v *View) draw() error {
|
||||
maxX, maxY := v.Size()
|
||||
|
||||
if v.Wrap {
|
||||
if maxX == 0 {
|
||||
return errors.New("X size of the view cannot be 0")
|
||||
}
|
||||
v.ox = 0
|
||||
}
|
||||
if v.tainted {
|
||||
v.viewLines = nil
|
||||
for i, line := range v.lines {
|
||||
if v.Wrap {
|
||||
if len(line) < maxX {
|
||||
vline := viewLine{linesX: 0, linesY: i, line: line}
|
||||
v.viewLines = append(v.viewLines, vline)
|
||||
continue
|
||||
} else {
|
||||
for n := 0; n <= len(line); n += maxX {
|
||||
if len(line[n:]) <= maxX {
|
||||
vline := viewLine{linesX: n, linesY: i, line: line[n:]}
|
||||
v.viewLines = append(v.viewLines, vline)
|
||||
} else {
|
||||
vline := viewLine{linesX: n, linesY: i, line: line[n : n+maxX]}
|
||||
v.viewLines = append(v.viewLines, vline)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
vline := viewLine{linesX: 0, linesY: i, line: line}
|
||||
v.viewLines = append(v.viewLines, vline)
|
||||
}
|
||||
}
|
||||
v.tainted = false
|
||||
}
|
||||
|
||||
if v.Autoscroll && len(v.viewLines) > maxY {
|
||||
v.oy = len(v.viewLines) - maxY
|
||||
}
|
||||
y := 0
|
||||
for i, line := range v.lines {
|
||||
for i, vline := range v.viewLines {
|
||||
if i < v.oy {
|
||||
continue
|
||||
}
|
||||
if y >= maxY {
|
||||
break
|
||||
}
|
||||
x := 0
|
||||
for j, ch := range line {
|
||||
for j, c := range vline.line {
|
||||
if j < v.ox {
|
||||
continue
|
||||
}
|
||||
if x >= 0 && x < maxX && y >= 0 && y < maxY {
|
||||
if err := v.setRune(x, y, ch); err != nil {
|
||||
return err
|
||||
}
|
||||
if x >= maxX {
|
||||
break
|
||||
}
|
||||
|
||||
fgColor := c.fgColor
|
||||
if fgColor == ColorDefault {
|
||||
fgColor = v.FgColor
|
||||
}
|
||||
bgColor := c.bgColor
|
||||
if bgColor == ColorDefault {
|
||||
bgColor = v.BgColor
|
||||
}
|
||||
|
||||
if err := v.setRune(x, y, c.chr, fgColor, bgColor); err != nil {
|
||||
return err
|
||||
}
|
||||
x++
|
||||
}
|
||||
@ -156,9 +360,40 @@ func (v *View) draw() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// realPosition returns the position in the internal buffer corresponding to the
|
||||
// point (x, y) of the view.
|
||||
func (v *View) realPosition(vx, vy int) (x, y int, err error) {
|
||||
vx = v.ox + vx
|
||||
vy = v.oy + vy
|
||||
|
||||
if vx < 0 || vy < 0 {
|
||||
return 0, 0, errors.New("invalid point")
|
||||
}
|
||||
|
||||
if len(v.viewLines) == 0 {
|
||||
return vx, vy, nil
|
||||
}
|
||||
|
||||
if vy < len(v.viewLines) {
|
||||
vline := v.viewLines[vy]
|
||||
x = vline.linesX + vx
|
||||
y = vline.linesY
|
||||
} else {
|
||||
vline := v.viewLines[len(v.viewLines)-1]
|
||||
x = vx
|
||||
y = vline.linesY + vy - len(v.viewLines) + 1
|
||||
}
|
||||
|
||||
return x, y, nil
|
||||
}
|
||||
|
||||
// Clear empties the view's internal buffer.
|
||||
func (v *View) Clear() {
|
||||
v.tainted = true
|
||||
|
||||
v.lines = nil
|
||||
v.viewLines = nil
|
||||
v.readOffset = 0
|
||||
v.clearRunes()
|
||||
}
|
||||
|
||||
@ -168,117 +403,101 @@ func (v *View) clearRunes() {
|
||||
for x := 0; x < maxX; x++ {
|
||||
for y := 0; y < maxY; y++ {
|
||||
termbox.SetCell(v.x0+x+1, v.y0+y+1, ' ',
|
||||
termbox.Attribute(v.fgColor), termbox.Attribute(v.bgColor))
|
||||
termbox.Attribute(v.FgColor), termbox.Attribute(v.BgColor))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// writeRune writes a rune into the view's internal buffer, at the
|
||||
// position corresponding to the point (x, y). The length of the internal
|
||||
// buffer is increased if the point is out of bounds. Overwrite mode is
|
||||
// governed by the value of View.overwrite.
|
||||
func (v *View) writeRune(x, y int, ch rune) error {
|
||||
x = v.ox + x
|
||||
y = v.oy + y
|
||||
|
||||
if x < 0 || y < 0 {
|
||||
return errors.New("invalid point")
|
||||
// BufferLines returns the lines in the view's internal
|
||||
// buffer.
|
||||
func (v *View) BufferLines() []string {
|
||||
lines := make([]string, len(v.lines))
|
||||
for i, l := range v.lines {
|
||||
str := lineType(l).String()
|
||||
str = strings.Replace(str, "\x00", " ", -1)
|
||||
lines[i] = str
|
||||
}
|
||||
|
||||
if y >= len(v.lines) {
|
||||
if y >= cap(v.lines) {
|
||||
s := make([][]rune, y+1, (y+1)*2)
|
||||
copy(s, v.lines)
|
||||
v.lines = s
|
||||
} else {
|
||||
v.lines = v.lines[:y+1]
|
||||
}
|
||||
}
|
||||
if v.lines[y] == nil {
|
||||
v.lines[y] = make([]rune, x+1, (x+1)*2)
|
||||
} else if x >= len(v.lines[y]) {
|
||||
if x >= cap(v.lines[y]) {
|
||||
s := make([]rune, x+1, (x+1)*2)
|
||||
copy(s, v.lines[y])
|
||||
v.lines[y] = s
|
||||
} else {
|
||||
v.lines[y] = v.lines[y][:x+1]
|
||||
}
|
||||
}
|
||||
if !v.overwrite {
|
||||
v.lines[y] = append(v.lines[y], ' ')
|
||||
copy(v.lines[y][x+1:], v.lines[y][x:])
|
||||
}
|
||||
v.lines[y][x] = ch
|
||||
return nil
|
||||
return lines
|
||||
}
|
||||
|
||||
// deleteRune removes a rune from the view's internal buffer, at the
|
||||
// position corresponding to the point (x, y).
|
||||
func (v *View) deleteRune(x, y int) error {
|
||||
x = v.ox + x
|
||||
y = v.oy + y
|
||||
|
||||
if x < 0 || y < 0 || y >= len(v.lines) || v.lines[y] == nil || x >= len(v.lines[y]) {
|
||||
return errors.New("invalid point")
|
||||
// Buffer returns a string with the contents of the view's internal
|
||||
// buffer.
|
||||
func (v *View) Buffer() string {
|
||||
str := ""
|
||||
for _, l := range v.lines {
|
||||
str += lineType(l).String() + "\n"
|
||||
}
|
||||
copy(v.lines[y][x:], v.lines[y][x+1:])
|
||||
v.lines[y][len(v.lines[y])-1] = ' '
|
||||
return nil
|
||||
return strings.Replace(str, "\x00", " ", -1)
|
||||
}
|
||||
|
||||
// addLine adds a line into the view's internal buffer at the position
|
||||
// corresponding to the point (x, y).
|
||||
func (v *View) addLine(y int) error {
|
||||
y = v.oy + y
|
||||
|
||||
if y < 0 || y >= len(v.lines) {
|
||||
return errors.New("invalid point")
|
||||
// ViewBufferLines returns the lines in the view's internal
|
||||
// buffer that is shown to the user.
|
||||
func (v *View) ViewBufferLines() []string {
|
||||
lines := make([]string, len(v.viewLines))
|
||||
for i, l := range v.viewLines {
|
||||
str := lineType(l.line).String()
|
||||
str = strings.Replace(str, "\x00", " ", -1)
|
||||
lines[i] = str
|
||||
}
|
||||
v.lines = append(v.lines, nil)
|
||||
copy(v.lines[y+1:], v.lines[y:])
|
||||
v.lines[y] = nil
|
||||
return nil
|
||||
return lines
|
||||
}
|
||||
|
||||
// ViewBuffer returns a string with the contents of the view's buffer that is
|
||||
// shown to the user.
|
||||
func (v *View) ViewBuffer() string {
|
||||
str := ""
|
||||
for _, l := range v.viewLines {
|
||||
str += lineType(l.line).String() + "\n"
|
||||
}
|
||||
return strings.Replace(str, "\x00", " ", -1)
|
||||
}
|
||||
|
||||
// Line returns a string with the line of the view's internal buffer
|
||||
// at the position corresponding to the point (x, y).
|
||||
func (v *View) Line(y int) (string, error) {
|
||||
y = v.oy + y
|
||||
_, y, err := v.realPosition(0, y)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if y < 0 || y >= len(v.lines) {
|
||||
return "", errors.New("invalid point")
|
||||
}
|
||||
return string(v.lines[y]), nil
|
||||
|
||||
return lineType(v.lines[y]).String(), nil
|
||||
}
|
||||
|
||||
// Word returns a string with the word of the view's internal buffer
|
||||
// at the position corresponding to the point (x, y).
|
||||
func (v *View) Word(x, y int) (string, error) {
|
||||
x = v.ox + x
|
||||
y = v.oy + y
|
||||
x, y, err := v.realPosition(x, y)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
|
||||
if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
|
||||
return "", errors.New("invalid point")
|
||||
}
|
||||
l := string(v.lines[y])
|
||||
nl := strings.LastIndexFunc(l[:x], indexFunc)
|
||||
|
||||
str := lineType(v.lines[y]).String()
|
||||
|
||||
nl := strings.LastIndexFunc(str[:x], indexFunc)
|
||||
if nl == -1 {
|
||||
nl = 0
|
||||
} else {
|
||||
nl = nl + 1
|
||||
}
|
||||
nr := strings.IndexFunc(l[x:], indexFunc)
|
||||
nr := strings.IndexFunc(str[x:], indexFunc)
|
||||
if nr == -1 {
|
||||
nr = len(l)
|
||||
nr = len(str)
|
||||
} else {
|
||||
nr = nr + x
|
||||
}
|
||||
return string(l[nl:nr]), nil
|
||||
return string(str[nl:nr]), nil
|
||||
}
|
||||
|
||||
// indexFunc allows to split lines by words taking into account spaces
|
||||
// and 0
|
||||
// and 0.
|
||||
func indexFunc(r rune) bool {
|
||||
return r == ' ' || r == 0
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user