Compare commits

...

196 Commits

Author SHA1 Message Date
Roi Martin
fc12c654f2
Merge pull request #253 from tbouasli/patch-1
docs: removed dollar sign from scripts
2024-09-09 07:08:19 +02:00
Thomas Bouasli
031962f8d6
docs: removed dollar sign from scripts 2024-09-01 15:19:34 -03:00
Roi Martin
de100503e6 doc: update links to pkg.go.dev 2021-08-14 19:15:20 +02:00
Roi Martin
0214e0e872 all: go modules 2021-08-14 19:15:08 +02:00
gulyasm
c055c87ae8 adds ViewBufferLines to View 2018-04-11 17:57:51 +02:00
gulyasm
2cda4f9f05 adds BufferLines func to View 2018-04-11 17:57:51 +02:00
Miguel Mota
491cd051cf Add cointop to "Projects using gocui" 2018-04-11 12:23:13 +02:00
gulyasm
f2f6a1fab8 Adds jsonui to projects using gocui 2018-04-11 12:15:48 +02:00
Mike JS. Choi
df0b3dc8df Add fac to list of projects using gocui 2018-04-11 12:08:39 +02:00
Roi Martin
247e437c8e Merge branch 'kcli-readme' of https://github.com/cswank/gocui into cswank-kcli-readme 2018-04-11 12:04:34 +02:00
Roi Martin
1ec4d5dd73
Merge pull request #149 from alitari/master
Add kubexp to "Projects using gocui"
2018-04-11 11:59:40 +02:00
Alexander Krieg
dc8ca8416c
added kubexp to "Projects using gocui" 2018-03-11 18:15:46 +01:00
Craig Swank
25c0c27f2e Add cswank/kcli to README's list of projects using gocui. 2017-12-29 08:16:18 -07:00
Julien Breux
4f518eddb0 Add Pody project to Readme 2017-08-27 21:50:11 +02:00
Roi Martin
eb73455bec Add diagram project to README 2017-08-20 15:22:33 +02:00
Roi Martin
a251da5a9f Minor syntax clean-ups 2017-08-20 00:28:06 +02:00
Roi Martin
70497a1d13 Fix typo 2017-08-20 00:17:11 +02:00
Roi Martin
881edc4380 Add terminews project to README 2017-08-19 20:43:14 +02:00
Roi Martin
6564cfcacb Rename *Gui.Execute() to *Gui.Update() 2017-08-19 00:13:30 +02:00
Roi Martin
75a7ad2750 Do not move the cursor beyond EOF 2017-08-19 00:06:54 +02:00
Roi Martin
a4d0b30476 Fix warning in _examples/bufs.go 2017-08-18 10:15:20 +02:00
Roi Martin
ad91aa2b83 Refactoring of *View.writeRune 2017-08-18 09:21:00 +02:00
Roi Martin
2677ad0445 Fix trailing \x00 bug in edition mode 2017-08-18 01:00:47 +02:00
Roi Martin
c64aff6dc2 Add claws project to README 2017-08-17 00:04:36 +02:00
Roi Martin
7ba3ea9d2c Use ColorDefault as default bg and fg color 2017-08-16 19:28:26 +02:00
Roi Martin
7e8dafc560 Remove empty line 2017-08-16 00:58:08 +02:00
Roi Martin
e18bedd405 Add _examples/size.go 2017-08-15 22:37:31 +02:00
Roi Martin
70a276872a Add gotime project in README 2017-08-15 21:52:03 +02:00
Roi Martin
0f8c8e3c9e Remove unneeded parentheses 2017-08-15 21:13:34 +02:00
Roman Lisagor
d1b6db5a49 Bug fix: v.MoveCursor with dx < -1 or dx > 1 does not work if origin needs to be adjusted
The existing logic moves the origin by 1 in either direction. The new logic
adjusts the origin by the appropriate amount, and moves the cursor to the
beginning or end of the line.
2017-08-15 21:11:04 +02:00
telecoda
4316bb79d4 Check for collisions in reverse view order 2017-08-15 20:52:01 +02:00
Roi Martin
c48e902a26 Update dynamic.go to add SetViewOnBottom 2017-08-15 20:34:30 +02:00
Roi Martin
a984617410 Simplify SetViewOnBottom 2017-08-15 20:34:06 +02:00
Julien Breux
93acb816a8 Add set view on bottom feature 2017-08-15 20:19:15 +02:00
Roi Martin
612b0b2987 Update README 2017-03-07 08:52:14 +01:00
Roi Martin
5424b93b3b Add httplab and domainr to README 2017-03-07 08:45:14 +01:00
Roi Martin
ed41d1bd2c Fix error handling in *escapeInterpreter.parseOne() 2017-02-12 15:50:38 +01:00
Roi Martin
139487f578 Remove unneeded check in *escapeInterpreter.parseOne() 2017-02-12 15:40:49 +01:00
Niels Widger
e27f247a3e Support zero parameteter 'CSI n m' in parseOne
According to https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_codes,
a zero-parameter 'CSI n m' code (for setting SGR parameters) should be
treated as the same as 'CSI 0 m'.  This PR changes
escapeInterpreter.parseOne to support this shorthand.
2017-02-12 14:56:43 +01:00
Roi Martin
88d2b471d4 Rename *Gui.Ascii to *Gui.ASCII 2017-02-11 02:06:03 +01:00
Roi Martin
cfc4e0298f Add *Gui.Ascii to enable ASCII line art 2017-02-10 23:17:26 +01:00
Roi Martin
aff177f6e3 Add wuzz as project using gocui 2017-02-10 22:59:00 +01:00
Harmen
c690b943b6 _examples/goroutine.go: simpler shutdown logic 2017-02-06 00:30:29 +01:00
Dustin Willis Webber
7ac95c981b
Add MouseRelease, MouseUp and MouseDown 2016-11-24 16:23:44 -05:00
Roi Martin
78f1aee25c Set viewLines to nil on *View.Clear() 2016-11-14 21:56:17 +01:00
Roi Martin
1ad1e27a52 Minor fixes in escapeInterpreter 2016-11-14 21:50:15 +01:00
Roi Martin
aacdc2698f Rewind on *View.Clear() 2016-11-14 01:12:37 +01:00
Roi Martin
ba396278de Update README 2016-11-13 21:44:48 +01:00
Roi Martin
fced1c672f Update docs 2016-11-13 21:42:03 +01:00
Roi Martin
f95667af1a Fix typo in doc 2016-11-13 21:40:01 +01:00
Roi Martin
64e8d44eee Fix typo in doc 2016-11-13 21:35:54 +01:00
Roi Martin
6b3dc12c32 Update AUTHORS 2016-11-13 20:46:12 +01:00
Roi Martin
adef0057a9 Add more tests to _examples/colors256.go 2016-11-13 20:46:02 +01:00
Roi Martin
56a1633c76 Minor style changes 2016-11-13 20:45:04 +01:00
Dustin Willis Webber
ff841413b6
256 OutputMode support 2016-11-13 14:06:04 -05:00
Roi Martin
0975ddb2a8 SetManager and SetManagerFunc delete keybindings 2016-10-30 13:20:05 +01:00
Roi Martin
e41f0e8947 _examples/flow_layout.go: refactoring 2016-10-27 23:13:45 +02:00
Roi Martin
308833a2cf Add *Gui.Views(). Add flow layout example 2016-10-27 23:09:13 +02:00
Roi Martin
aa4ac778d3 Move Editor into View. Update docs 2016-10-27 00:43:28 +02:00
Roi Martin
717fd3eaae _examples/active.go: minor fixes 2016-10-27 00:41:50 +02:00
Roi Martin
19125a0a67 Do not edit the view if a keybinding matches the event 2016-10-26 23:22:51 +02:00
Roi Martin
357a541add Update doc and README 2016-10-25 11:41:17 +02:00
Roi Martin
fb08594c69 _examples/widgets.go: Minor changes 2016-10-24 23:34:32 +02:00
Roi Martin
6a590932dd _examples/widgets.go: Improve help message 2016-10-24 19:33:15 +02:00
Roi Martin
89ecdb537d _examples/widgets.go: Minor refactoring 2016-10-24 16:00:18 +02:00
Roi Martin
e5517b913e Minor change in _examples/widgets.go 2016-10-24 15:45:34 +02:00
Roi Martin
df63f19bd6 Add example "widgets.go" 2016-10-24 15:34:14 +02:00
Roi Martin
e38ba07224 Minor refactoring. Avoid shadow vars. 2016-10-24 08:50:51 +02:00
Roi Martin
40dbad569f Introduce GUI managers to replace layout functions 2016-10-24 08:36:23 +02:00
Roi Martin
fc121d98fd Update docs 2016-10-24 07:26:59 +02:00
Roi Martin
ddbc9be671 Merge *Gui.Init() with *Gui.NewGui() 2016-10-24 02:24:41 +02:00
Roi Martin
550f04e523 Simplify how frames are drawn 2016-10-19 00:15:47 +02:00
Roi Martin
d5cb1ac216 Minor fixes in doc 2016-10-19 00:12:01 +02:00
Roi Martin
a7019c8547 Add *Gui.Highlight to enable/disable current view's custom color 2016-10-18 23:30:43 +02:00
Roi Martin
c0ae071931 Use *Gui.{SelFgColor,SelBgColor} to set current view's color
- Use *Gui.{FgColor,BgColor} to set GUI's color.
- Use *Gui.{SelFgColor,SelBgColor} to set current view's color.
- Drop *Gui.ActiveColor and *View.ActiveColor.
2016-10-18 22:49:31 +02:00
Roi Martin
16db12db96 Update AUTHORS 2016-10-18 00:56:53 +02:00
Roi Martin
a109a3641c Use ActiveColor in dynamic.go. Fix bug in active.go 2016-10-18 00:36:26 +02:00
Roi Martin
463428abda Minor refactoring. Update examples 2016-10-18 00:24:16 +02:00
Henri Koski
7779534f95 Add attribute ActiveColor to configure the color of the current view 2016-10-17 23:25:56 +02:00
Roi Martin
ec14132e67 Rearrange screenshots 2016-10-16 18:54:00 +02:00
Roi Martin
1724fbebc8 Update README with projects using gocui and more screenshots
Related to issue #61.
2016-10-16 18:30:11 +02:00
Roi Martin
4e9ce9a8e2 Revert changes in *View.MoveCursor 2016-10-11 10:18:38 +02:00
Roi Martin
d822523f8c Make golint happy. Fix typo. 2016-10-11 08:52:03 +02:00
Roi Martin
cea263b118 Remove unnecessary error in onKey() 2016-10-11 08:41:22 +02:00
Roi Martin
10d2bb60ea Fix mouse mode 2016-10-11 08:34:27 +02:00
Roi Martin
76554e4f84 Execute keybind handlers before edition
Fix issue #54
2016-10-11 08:27:26 +02:00
Roi Martin
8d16527c1d Fix edition mode when Editable = true
Fix issue #53
2016-10-11 07:37:42 +02:00
Roi Martin
6314568953 Fix frames drawing.
Fix issue #57
2016-10-04 00:10:07 +02:00
Roi Martin
2e62b6ba19 Respect view's default background and foreground colors
Fix issue #58
2016-10-03 19:38:40 +02:00
Roi Martin
375270bca0 Replace "godoc" by "go doc" in README 2016-10-03 11:15:42 +02:00
Roi Martin
13f0442ee4 Minor style changes 2016-10-03 11:10:50 +02:00
govlas
f1f9c0fa53 Add *Gui.DeleteKeybindings
*Gui.DeleteKeybindings Deletes all keybindings of view
2016-10-03 11:03:11 +02:00
Roi Martin
30f7d65597 Refactoring 2016-08-30 23:29:55 +02:00
Roi Martin
2a0623774f Add *Gui.DeleteKeybinding 2016-08-30 23:10:07 +02:00
Roi Martin
5f143ef3de Fix typo 2016-08-30 22:15:03 +02:00
Gustavo Bittencourt
873bc2c17d Enable ESCKey binding
Permit the binding of the ESCKey correctly. The default setup is the
same (inputMode := termbox.InputAlt) if user doesn't set the
InputESC value explictly.
2016-08-28 00:29:12 -03:00
Roi Martin
aa556cf135 Add example save.go 2016-07-31 16:47:00 +02:00
Roi Martin
41745b2a15 Fix typo in doc 2016-07-20 21:35:02 +02:00
Roi Martin
2dcda558bf Update AUTHORS file 2016-05-23 00:52:20 +02:00
Roi Martin
132f267b70 Fix minor typo 2016-05-23 00:21:03 +02:00
Roi Martin
6f73260433 Update _examples/colors.go with @deweerdt's code from #39 2016-05-23 00:16:59 +02:00
Roi Martin
a67a34cd60 Use View's default colors in View.writeRune 2016-05-23 00:16:59 +02:00
Roi Martin
3758266eac Update documentation and README 2016-05-23 00:16:59 +02:00
Roi Martin
1b35b7cd26 escape: remove unnecessary type interpreterReturn 2016-05-23 00:16:59 +02:00
Roi Martin
40dec91023 Initial support for colored text
- View contents are stored as cells (rune + colors) instead of runes.
- Uses the escape interpreter coded by @deweerdt in #39.
2016-05-23 00:16:59 +02:00
Roi Martin
8c49240a03 Remove unreachable code 2016-05-17 22:42:36 +02:00
Roi Martin
0707386452 Fix #50: highlight for wrapped lines 2016-05-17 22:40:24 +02:00
Roi Martin
490199421a Show cursor in _examples/mask.go 2016-04-20 14:35:33 +02:00
Roi Martin
7ffb37ef13 Add example for mask 2016-04-20 14:34:09 +02:00
Danny Tylman
65dfdbf77a Add support for masked views 2016-04-20 14:33:14 +02:00
James Monger
336b3337d1 Update delete.go to dynamic.go in readme 2016-04-19 10:35:13 +01:00
Roi Martin
a8eba6db38 Rename _examples/delete.go to _examples/dynamic.go 2016-02-07 17:39:19 +01:00
Roi Martin
248e4f4437 Do not show cursor when it's out of screen 2016-02-07 16:58:15 +01:00
Roi Martin
a09a166064 Extend _examples/delete.go to test *Gui.SetViewOnTop 2016-02-07 16:52:31 +01:00
Roi Martin
28abcdb8e4 Support set views on top
- Add method *Gui.SetViewOnTop
- Add example _examples/ontop.go to test this feature
2016-02-07 16:52:31 +01:00
Roi Martin
e669e2b30d Add more test cases to _examples/title.go 2016-02-06 15:21:38 +01:00
Roi Martin
bb01d13a95 Add view title (based on the implementation of @conejoninja) 2016-02-06 14:50:30 +01:00
Roi Martin
c5595daed4 Fix typos in README 2016-02-01 15:39:59 +01:00
Roi Martin
8dea948d50 Minor improvements in README 2016-02-01 15:36:10 +01:00
Roi Martin
d0f0fa6008 Minor fixes in doc 2016-02-01 15:08:39 +01:00
Roi Martin
7f83fd5782 Fix typos in doc 2016-01-30 07:50:01 +01:00
Roi Martin
02b399dbd8 Update doc 2016-01-30 03:01:54 +01:00
Roi Martin
6a1d76b5c7 Update README.md 2016-01-30 02:55:46 +01:00
Roi Martin
5efdece4af Add _examples/hello.go and update README 2016-01-30 02:54:26 +01:00
Roi Martin
82070fd113 Improve doc 2016-01-30 02:44:09 +01:00
Roi Martin
7e313a536f Fix typos in doc 2016-01-30 02:38:49 +01:00
Roi Martin
4aed924ceb Editor refactoring. Add doc. Simplify _examples. 2016-01-30 02:36:10 +01:00
Roi Martin
f42f45fad3 Update README with information about concurrency 2016-01-26 10:19:11 +01:00
Roi Martin
ea4d0466c4 Rename Gui.ShowCursor to Gui.Cursor for consistency 2016-01-26 09:55:08 +01:00
Roi Martin
260872b0f7 Set cursor on mouse events 2016-01-26 09:40:06 +01:00
Roi Martin
b5f65e10ad Minor refactoring 2016-01-26 00:48:12 +01:00
Roi Martin
4b628e45b2 Add Gui.Execute to safely update the GUI from goroutines. 2016-01-26 00:06:54 +01:00
Roi Martin
639723c288 Rename Gui.EnableMouse to Gui.Mouse 2016-01-23 17:18:30 +01:00
Roi Martin
faa12e105a Full mouse support. Add mouse example. Make golint happy.
- Full mouse support based on hazbo's initial work.
- Add example to show mouse support.
- Fix comments, naming conventions, etc. to make golint happy.
2016-01-23 16:07:42 +01:00
Harry Lawrence
c36dfefa9b Enabled very basic mouse support
I see this as work in progress, for now. In terms of the public API, all the
end user has to do is something like g.EnableMouse = true. I think this is
reasonable and may not have to really change. Although I think the way it's
used with KeyBindings should maybe be changed. We need to expose the X and Y of
the click event - I'm not sure if this is possible currently, although Gui has
an unexported event field of type termbox.Event that I believe contains this
information.

Mouse support is something I need for a project I'm using Gocui, I noticed
there was an issue open in relation to this so I figured I'd send back what
I've started already.

Thanks for making gocui - it's awesome.
2015-12-11 11:51:43 +00:00
Roi Martin
e5bf60e36b Add method View.ViewBuffer 2015-08-16 18:08:34 +02:00
Roi Martin
af4769f30e Remove unnecessary call to runtime.LockOSThread() from stdin.go 2015-08-16 17:48:41 +02:00
Roi Martin
e2590f1300 Decouple edition mode via Editor interface 2015-02-24 13:05:33 +01:00
Roi Martin
4dfc4973e0 Decouple edition mode 2015-02-24 00:26:26 +01:00
Roi Martin
4ad26839aa Mark views as tainted when they are resized 2015-02-23 16:37:49 +01:00
Roi Martin
60388aa361 Add comments to View.editDelete() 2015-02-23 11:13:25 +01:00
Roi Martin
37e2387e4a Minor refactoring 2015-02-23 11:09:31 +01:00
Roi Martin
e009f09eb8 Fix detection of start/end of line in View.editDelete() 2015-02-23 11:07:08 +01:00
Roi Martin
ca0f876ab6 Fix cursor behaviour of View.editDelete() 2015-02-23 10:20:22 +01:00
Roi Martin
e63645a119 Support full edition mode in non-wrapping views 2015-02-23 02:01:49 +01:00
Roi Martin
622e7cbdf9 Initial implementation of the full edition mode 2015-02-23 00:34:41 +01:00
Roi Martin
b1ad4a9fa7 Remove internal call to View.SetCursor() and fix comment 2015-02-16 23:39:37 +01:00
Roi Martin
03998dd72c Refactor View.breakLine() 2015-02-15 02:17:13 +01:00
Roi Martin
f53d985c4e Break lines on enter when needed 2015-02-14 20:17:44 +01:00
Roi Martin
0193dee642 Refactoring of Gui.onKey() 2015-02-13 21:02:56 +01:00
Roi Martin
311dedb655 Move handleEdit to edit.go 2015-02-13 18:40:45 +01:00
Roi Martin
0e85b51ed2 Remove View.WrapPrefix 2015-02-04 10:37:15 +01:00
Roi Martin
0992dc1df0 Fix bug in realPosition() when len(v.viewLines) == 0 2015-02-04 09:25:25 +01:00
Roi Martin
8fa01b0c0e _examples/stdin.go: Disable autoscroll and edition 2015-02-04 02:25:06 +01:00
Roi Martin
4c83f5bfa9 Add example stdin.go 2015-02-04 02:22:54 +01:00
Roi Martin
708261503b Fix edit mode when View.Wrap is enabled 2015-02-04 01:59:03 +01:00
Roi Martin
b1d190d0d7 Convert View.viewLines to []viewLine 2015-02-03 18:29:38 +01:00
Roi Martin
0814e8024f More refactoring of View.draw() 2015-02-03 17:49:15 +01:00
Roi Martin
6279571a82 Fix build 2015-02-03 17:15:41 +01:00
Roi Martin
9b25959056 Optimizations on view redrawing when View.Wrap is enabled.
Refresh internal view buffer only when needed. Do not uses copy's.
2015-02-03 17:09:21 +01:00
Roi Martin
3438be9f43 Minor fix in _examples/demo.go 2015-02-02 01:32:15 +01:00
Roi Martin
f819237d78 Add more details to Gui.Flush()'s documentation 2015-02-02 01:04:33 +01:00
Roi Martin
d786a4aec1 Update Gui.Flush()'s comment. 2015-02-02 00:46:04 +01:00
Roi Martin
c9c982ea9d Protect Gui from being flushed concurrently 2015-02-02 00:42:34 +01:00
Roi Martin
3607eb8e1c Implement autoscroll. Fix scroll when View.Wrap is enabled 2015-02-01 22:49:07 +01:00
Roi Martin
d0c53d8574 Restructure README 2015-02-01 17:27:48 +01:00
Roi Martin
8de3a55f4e Minor aesthetic changes in README 2015-02-01 17:24:10 +01:00
Roi Martin
2e0c0342dc Add screenshots to README 2015-02-01 17:23:04 +01:00
Roi Martin
98a2fe7a6a Add missing LICENSE header. Fix typo in LICENSE headers 2015-01-31 20:39:43 +01:00
Roi Martin
f5cd17c3cc Rename ErrorQuit to Quit 2015-01-30 17:19:11 +01:00
Roi Martin
17f7615184 Handle '\n' and '\r' in View.Write()
Handle '\n' and '\r' in View.Write() so fmt.Print, fmt.Println, etc. work as
expected. This commit closes issue #25.
2015-01-29 16:19:25 +01:00
Roi Martin
c690c51bff _examples/delete: Move legend to layout 2015-01-26 23:23:46 +01:00
Roi Martin
9404aacd27 _examples/delete: Add legend. Fix bug in nextView 2015-01-26 23:19:54 +01:00
Roi Martin
d3c84c7bf4 _examples/delete.go: Refactoring 2015-01-25 14:16:27 +01:00
Roi Martin
2db4573e76 Add function Gui.ViewPosition(). Add example.
The example "delete.go" will be useful to test optimization algorithms.
2015-01-25 14:07:14 +01:00
Roi Martin
45bec2b33c Examples: More simplifications on demo.go 2015-01-24 15:03:03 +01:00
Roi Martin
25ba6858fb _examples/wrap: Minor changes 2015-01-24 14:38:08 +01:00
Roi Martin
8efd767c68 Improve README 2015-01-24 14:35:23 +01:00
Roi Martin
b9ce982fe5 Update README and doc 2015-01-24 14:26:26 +01:00
Roi Martin
cebc72c201 Add Modifier ModNone. Simplify examples 2015-01-24 14:23:46 +01:00
Roi Martin
9b902f9bec Examples: Rename _demos to _examples 2015-01-24 13:52:02 +01:00
Roi Martin
9a9d962740 demos: Rename demos to self-explanatory names 2015-01-23 22:03:44 +01:00
Roi Martin
7ed193e8f4 Fix typo in LICENSE 2014-12-25 11:58:41 +01:00
Roi Martin
a67c870eef Fix typos. 2014-11-15 13:50:56 +01:00
Roi Martin
530f266854 Merge branch 'rakoo-master' into v0.2-dev 2014-11-15 13:15:48 +01:00
Roi Martin
f78a0704f1 Minor fixes in Wrap
Print first character of the new line.
Control maxX and maxY to avoid invalid calls to View.SetRune.
Add error handling when calling View.SetRune.
2014-11-15 13:13:19 +01:00
Matthieu Rakotojaona
382efdcc54 Wrap content if too long 2014-11-14 20:52:05 +01:00
Roi Martin
264959b01e Minor code clean-up 2014-10-18 15:47:24 +02:00
Roi Martin
0aed73291b Merge branch 'master' of https://github.com/KayoticSully/gocui into Frame 2014-10-18 15:35:01 +02:00
Ryan Sullivan
8d584203d4 Added Support to toggle View Frames 2014-10-17 17:22:28 -04:00
Roi Martin (@nibble_ds)
cbacee3e65 Allow different colors per view 2014-05-03 15:20:46 +02:00
Roi Martin (@nibble_ds)
6b5681b670 Implement View.Read and View.Rewind. Update demo2 2014-05-01 14:32:30 +02:00
Roi Martin (@nibble_ds)
9f2bfa7213 Add View.Buffer() 2014-02-03 02:04:30 +01:00
Roi Martin (@nibble_ds)
4c22f4abd9 Update README.md 2014-01-28 09:36:57 +01:00
Roi Martin (@nibble_ds)
553b48c903 Fix typo in documentation. Minor fix in demo2 2014-01-27 22:50:02 +01:00
Roi Martin (@nibble_ds)
46b1452e71 Simplify demo2. Add Gui.CurrentView(). Fix keybindings 2014-01-27 22:41:58 +01:00
33 changed files with 3366 additions and 793 deletions

27
AUTHORS
View File

@ -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

View File

@ -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
View File

@ -1,44 +1,110 @@
GOCUI - Go Console User Interface
=================================
# GOCUI - Go Console User Interface
Minimalist Go library aimed at creating Console User Interfaces.
[![Go Reference](https://pkg.go.dev/badge/github.com/jroimartin/gocui.svg)](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
![r2cui](https://cloud.githubusercontent.com/assets/1223476/19418932/63645052-93ce-11e6-867c-da5e97e37237.png)
![_examples/demo.go](https://cloud.githubusercontent.com/assets/1223476/5992750/720b84f0-aa36-11e4-88ec-296fa3247b52.png)
![_examples/dynamic.go](https://cloud.githubusercontent.com/assets/1223476/5992751/76ad5cc2-aa36-11e4-8204-6a90269db827.png)
## 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! :)

View File

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}

View File

@ -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
View 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
View 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
View 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
}

View File

@ -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
View 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
View 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
View 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
View 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
View 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)
}
}

View File

@ -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
View File

@ -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
View File

@ -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
View 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
View 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
View 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
View File

@ -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
}

View File

@ -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
View File

@ -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
}