├── .gitignore ├── AUTHORS ├── LICENSE ├── README.md ├── _examples ├── Mark.Twain-Tom.Sawyer.txt ├── active.go ├── bufs.go ├── colors.go ├── colors256.go ├── demo.go ├── dynamic.go ├── flow_layout.go ├── goroutine.go ├── hello.go ├── layout.go ├── mask.go ├── mouse.go ├── ontop.go ├── overlap.go ├── size.go ├── stdin.go ├── title.go ├── widgets.go └── wrap.go ├── attribute.go ├── doc.go ├── edit.go ├── escape.go ├── go.mod ├── go.sum ├── gui.go ├── keybinding.go └── view.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of gocui authors for copyright purposes. 2 | 3 | # Names should be added to this file as 4 | # Name or Organization contribution 5 | # Contribution 6 | # The email address is not required for organizations. 7 | 8 | Roi Martin 9 | Main developer 10 | 11 | Ryan Sullivan 12 | Toggleable view frames 13 | 14 | Matthieu Rakotojaona 15 | Wrapped views 16 | 17 | Harry Lawrence 18 | Basic mouse support 19 | 20 | Danny Tylman 21 | Masked views 22 | 23 | Frederik Deweerdt 24 | Colored fonts 25 | 26 | Henri Koski 27 | Custom current view color 28 | 29 | Dustin Willis Webber 30 | 256-colors output mode support 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 The gocui Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | * Redistributions of source code must retain the above copyright 6 | notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright 8 | notice, this list of conditions and the following disclaimer in the 9 | documentation and/or other materials provided with the distribution. 10 | * Neither the name of the gocui Authors nor the names of its contributors 11 | may be used to endorse or promote products derived from this software 12 | without specific prior written permission. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GOCUI - Go Console User Interface 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/jroimartin/gocui.svg)](https://pkg.go.dev/github.com/jroimartin/gocui) 4 | 5 | Minimalist Go package aimed at creating Console User Interfaces. 6 | 7 | ## Features 8 | 9 | * Minimalist API. 10 | * Views (the "windows" in the GUI) implement the interface io.ReadWriter. 11 | * Support for overlapping views. 12 | * The GUI can be modified at runtime (concurrent-safe). 13 | * Global and view-level keybindings. 14 | * Mouse support. 15 | * Colored text. 16 | * Customizable edition mode. 17 | * Easy to build reusable widgets, complex layouts... 18 | 19 | ## Installation 20 | 21 | Execute: 22 | 23 | ```sh 24 | go get github.com/jroimartin/gocui 25 | ``` 26 | 27 | ## Documentation 28 | 29 | Execute: 30 | 31 | ```sh 32 | go doc github.com/jroimartin/gocui 33 | ``` 34 | 35 | Or visit [pkg.go.dev](https://pkg.go.dev/github.com/jroimartin/gocui) to read 36 | it online. 37 | 38 | ## Example 39 | 40 | ```go 41 | package main 42 | 43 | import ( 44 | "fmt" 45 | "log" 46 | 47 | "github.com/jroimartin/gocui" 48 | ) 49 | 50 | func main() { 51 | g, err := gocui.NewGui(gocui.OutputNormal) 52 | if err != nil { 53 | log.Panicln(err) 54 | } 55 | defer g.Close() 56 | 57 | g.SetManagerFunc(layout) 58 | 59 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { 60 | log.Panicln(err) 61 | } 62 | 63 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 64 | log.Panicln(err) 65 | } 66 | } 67 | 68 | func layout(g *gocui.Gui) error { 69 | maxX, maxY := g.Size() 70 | if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2); err != nil { 71 | if err != gocui.ErrUnknownView { 72 | return err 73 | } 74 | fmt.Fprintln(v, "Hello world!") 75 | } 76 | return nil 77 | } 78 | 79 | func quit(g *gocui.Gui, v *gocui.View) error { 80 | return gocui.ErrQuit 81 | } 82 | ``` 83 | 84 | ## Screenshots 85 | 86 | ![r2cui](https://cloud.githubusercontent.com/assets/1223476/19418932/63645052-93ce-11e6-867c-da5e97e37237.png) 87 | 88 | ![_examples/demo.go](https://cloud.githubusercontent.com/assets/1223476/5992750/720b84f0-aa36-11e4-88ec-296fa3247b52.png) 89 | 90 | ![_examples/dynamic.go](https://cloud.githubusercontent.com/assets/1223476/5992751/76ad5cc2-aa36-11e4-8204-6a90269db827.png) 91 | -------------------------------------------------------------------------------- /_examples/Mark.Twain-Tom.Sawyer.txt: -------------------------------------------------------------------------------- 1 | The Project Gutenberg EBook of The Adventures of Tom Sawyer, Complete 2 | by Mark Twain (Samuel Clemens) 3 | 4 | This eBook is for the use of anyone anywhere at no cost and with 5 | almost no restrictions whatsoever. You may copy it, give it away or 6 | re-use it under the terms of the Project Gutenberg License included 7 | with this eBook or online at www.gutenberg.net 8 | 9 | 10 | Title: The Adventures of Tom Sawyer, Complete 11 | 12 | Author: Mark Twain (Samuel Clemens) 13 | 14 | Release Date: August 20, 2006 [EBook #74] 15 | [Last updated: May 3, 2011] 16 | 17 | Language: English 18 | 19 | 20 | *** START OF THIS PROJECT GUTENBERG EBOOK TOM SAWYER *** 21 | 22 | 23 | 24 | 25 | Produced by David Widger. The previous edition was updated by Jose 26 | Menendez. 27 | 28 | 29 | 30 | 31 | 32 | THE ADVENTURES OF TOM SAWYER 33 | BY 34 | MARK TWAIN 35 | (Samuel Langhorne Clemens) 36 | 37 | 38 | 39 | 40 | P R E F A C E 41 | 42 | MOST of the adventures recorded in this book really occurred; one or 43 | two were experiences of my own, the rest those of boys who were 44 | schoolmates of mine. Huck Finn is drawn from life; Tom Sawyer also, but 45 | not from an individual--he is a combination of the characteristics of 46 | three boys whom I knew, and therefore belongs to the composite order of 47 | architecture. 48 | 49 | The odd superstitions touched upon were all prevalent among children 50 | and slaves in the West at the period of this story--that is to say, 51 | thirty or forty years ago. 52 | 53 | Although my book is intended mainly for the entertainment of boys and 54 | girls, I hope it will not be shunned by men and women on that account, 55 | for part of my plan has been to try to pleasantly remind adults of what 56 | they once were themselves, and of how they felt and thought and talked, 57 | and what queer enterprises they sometimes engaged in. 58 | 59 | THE AUTHOR. 60 | 61 | HARTFORD, 1876. 62 | 63 | 64 | 65 | T O M S A W Y E R 66 | 67 | 68 | 69 | CHAPTER I 70 | 71 | "TOM!" 72 | 73 | No answer. 74 | 75 | "TOM!" 76 | 77 | No answer. 78 | 79 | "What's gone with that boy, I wonder? You TOM!" 80 | 81 | No answer. 82 | 83 | The old lady pulled her spectacles down and looked over them about the 84 | room; then she put them up and looked out under them. She seldom or 85 | never looked THROUGH them for so small a thing as a boy; they were her 86 | state pair, the pride of her heart, and were built for "style," not 87 | service--she could have seen through a pair of stove-lids just as well. 88 | She looked perplexed for a moment, and then said, not fiercely, but 89 | still loud enough for the furniture to hear: 90 | 91 | "Well, I lay if I get hold of you I'll--" 92 | 93 | She did not finish, for by this time she was bending down and punching 94 | under the bed with the broom, and so she needed breath to punctuate the 95 | punches with. She resurrected nothing but the cat. 96 | 97 | "I never did see the beat of that boy!" 98 | 99 | She went to the open door and stood in it and looked out among the 100 | tomato vines and "jimpson" weeds that constituted the garden. No Tom. 101 | So she lifted up her voice at an angle calculated for distance and 102 | shouted: 103 | 104 | "Y-o-u-u TOM!" 105 | 106 | There was a slight noise behind her and she turned just in time to 107 | seize a small boy by the slack of his roundabout and arrest his flight. 108 | 109 | "There! I might 'a' thought of that closet. What you been doing in 110 | there?" 111 | 112 | "Nothing." 113 | 114 | "Nothing! Look at your hands. And look at your mouth. What IS that 115 | truck?" 116 | 117 | "I don't know, aunt." 118 | 119 | "Well, I know. It's jam--that's what it is. Forty times I've said if 120 | you didn't let that jam alone I'd skin you. Hand me that switch." 121 | 122 | The switch hovered in the air--the peril was desperate-- 123 | 124 | "My! Look behind you, aunt!" 125 | 126 | The old lady whirled round, and snatched her skirts out of danger. The 127 | lad fled on the instant, scrambled up the high board-fence, and 128 | disappeared over it. 129 | 130 | His aunt Polly stood surprised a moment, and then broke into a gentle 131 | laugh. 132 | 133 | "Hang the boy, can't I never learn anything? Ain't he played me tricks 134 | enough like that for me to be looking out for him by this time? But old 135 | fools is the biggest fools there is. Can't learn an old dog new tricks, 136 | as the saying is. But my goodness, he never plays them alike, two days, 137 | and how is a body to know what's coming? He 'pears to know just how 138 | long he can torment me before I get my dander up, and he knows if he 139 | can make out to put me off for a minute or make me laugh, it's all down 140 | again and I can't hit him a lick. I ain't doing my duty by that boy, 141 | and that's the Lord's truth, goodness knows. Spare the rod and spile 142 | the child, as the Good Book says. I'm a laying up sin and suffering for 143 | us both, I know. He's full of the Old Scratch, but laws-a-me! he's my 144 | own dead sister's boy, poor thing, and I ain't got the heart to lash 145 | him, somehow. Every time I let him off, my conscience does hurt me so, 146 | and every time I hit him my old heart most breaks. Well-a-well, man 147 | that is born of woman is of few days and full of trouble, as the 148 | Scripture says, and I reckon it's so. He'll play hookey this evening, * 149 | and [* Southwestern for "afternoon"] I'll just be obleeged to make him 150 | work, to-morrow, to punish him. It's mighty hard to make him work 151 | Saturdays, when all the boys is having holiday, but he hates work more 152 | than he hates anything else, and I've GOT to do some of my duty by him, 153 | or I'll be the ruination of the child." 154 | 155 | Tom did play hookey, and he had a very good time. He got back home 156 | barely in season to help Jim, the small colored boy, saw next-day's 157 | wood and split the kindlings before supper--at least he was there in 158 | time to tell his adventures to Jim while Jim did three-fourths of the 159 | work. Tom's younger brother (or rather half-brother) Sid was already 160 | through with his part of the work (picking up chips), for he was a 161 | quiet boy, and had no adventurous, troublesome ways. 162 | 163 | While Tom was eating his supper, and stealing sugar as opportunity 164 | offered, Aunt Polly asked him questions that were full of guile, and 165 | very deep--for she wanted to trap him into damaging revealments. Like 166 | many other simple-hearted souls, it was her pet vanity to believe she 167 | was endowed with a talent for dark and mysterious diplomacy, and she 168 | loved to contemplate her most transparent devices as marvels of low 169 | cunning. Said she: 170 | 171 | "Tom, it was middling warm in school, warn't it?" 172 | 173 | "Yes'm." 174 | 175 | "Powerful warm, warn't it?" 176 | 177 | "Yes'm." 178 | 179 | "Didn't you want to go in a-swimming, Tom?" 180 | 181 | A bit of a scare shot through Tom--a touch of uncomfortable suspicion. 182 | He searched Aunt Polly's face, but it told him nothing. So he said: 183 | 184 | "No'm--well, not very much." 185 | 186 | The old lady reached out her hand and felt Tom's shirt, and said: 187 | 188 | "But you ain't too warm now, though." And it flattered her to reflect 189 | that she had discovered that the shirt was dry without anybody knowing 190 | that that was what she had in her mind. But in spite of her, Tom knew 191 | where the wind lay, now. So he forestalled what might be the next move: 192 | 193 | "Some of us pumped on our heads--mine's damp yet. See?" 194 | 195 | Aunt Polly was vexed to think she had overlooked that bit of 196 | circumstantial evidence, and missed a trick. Then she had a new 197 | inspiration: 198 | 199 | "Tom, you didn't have to undo your shirt collar where I sewed it, to 200 | pump on your head, did you? Unbutton your jacket!" 201 | 202 | The trouble vanished out of Tom's face. He opened his jacket. His 203 | shirt collar was securely sewed. 204 | 205 | "Bother! Well, go 'long with you. I'd made sure you'd played hookey 206 | and been a-swimming. But I forgive ye, Tom. I reckon you're a kind of a 207 | singed cat, as the saying is--better'n you look. THIS time." 208 | 209 | She was half sorry her sagacity had miscarried, and half glad that Tom 210 | had stumbled into obedient conduct for once. 211 | 212 | But Sidney said: 213 | 214 | "Well, now, if I didn't think you sewed his collar with white thread, 215 | but it's black." 216 | 217 | "Why, I did sew it with white! Tom!" 218 | 219 | But Tom did not wait for the rest. As he went out at the door he said: 220 | 221 | "Siddy, I'll lick you for that." 222 | 223 | In a safe place Tom examined two large needles which were thrust into 224 | the lapels of his jacket, and had thread bound about them--one needle 225 | carried white thread and the other black. He said: 226 | 227 | "She'd never noticed if it hadn't been for Sid. Confound it! sometimes 228 | she sews it with white, and sometimes she sews it with black. I wish to 229 | geeminy she'd stick to one or t'other--I can't keep the run of 'em. But 230 | I bet you I'll lam Sid for that. I'll learn him!" 231 | 232 | He was not the Model Boy of the village. He knew the model boy very 233 | well though--and loathed him. 234 | 235 | Within two minutes, or even less, he had forgotten all his troubles. 236 | Not because his troubles were one whit less heavy and bitter to him 237 | than a man's are to a man, but because a new and powerful interest bore 238 | them down and drove them out of his mind for the time--just as men's 239 | misfortunes are forgotten in the excitement of new enterprises. This 240 | new interest was a valued novelty in whistling, which he had just 241 | acquired from a negro, and he was suffering to practise it undisturbed. 242 | It consisted in a peculiar bird-like turn, a sort of liquid warble, 243 | produced by touching the tongue to the roof of the mouth at short 244 | intervals in the midst of the music--the reader probably remembers how 245 | to do it, if he has ever been a boy. Diligence and attention soon gave 246 | him the knack of it, and he strode down the street with his mouth full 247 | of harmony and his soul full of gratitude. He felt much as an 248 | astronomer feels who has discovered a new planet--no doubt, as far as 249 | strong, deep, unalloyed pleasure is concerned, the advantage was with 250 | the boy, not the astronomer. 251 | 252 | The summer evenings were long. It was not dark, yet. Presently Tom 253 | checked his whistle. A stranger was before him--a boy a shade larger 254 | than himself. A new-comer of any age or either sex was an impressive 255 | curiosity in the poor little shabby village of St. Petersburg. This boy 256 | was well dressed, too--well dressed on a week-day. This was simply 257 | astounding. His cap was a dainty thing, his close-buttoned blue cloth 258 | roundabout was new and natty, and so were his pantaloons. He had shoes 259 | on--and it was only Friday. He even wore a necktie, a bright bit of 260 | ribbon. He had a citified air about him that ate into Tom's vitals. The 261 | more Tom stared at the splendid marvel, the higher he turned up his 262 | nose at his finery and the shabbier and shabbier his own outfit seemed 263 | to him to grow. Neither boy spoke. If one moved, the other moved--but 264 | only sidewise, in a circle; they kept face to face and eye to eye all 265 | the time. Finally Tom said: 266 | 267 | "I can lick you!" 268 | 269 | "I'd like to see you try it." 270 | 271 | "Well, I can do it." 272 | 273 | "No you can't, either." 274 | 275 | "Yes I can." 276 | 277 | "No you can't." 278 | 279 | "I can." 280 | 281 | "You can't." 282 | 283 | "Can!" 284 | 285 | "Can't!" 286 | 287 | An uncomfortable pause. Then Tom said: 288 | 289 | "What's your name?" 290 | 291 | "'Tisn't any of your business, maybe." 292 | 293 | "Well I 'low I'll MAKE it my business." 294 | 295 | "Well why don't you?" 296 | 297 | "If you say much, I will." 298 | 299 | "Much--much--MUCH. There now." 300 | 301 | "Oh, you think you're mighty smart, DON'T you? I could lick you with 302 | one hand tied behind me, if I wanted to." 303 | 304 | "Well why don't you DO it? You SAY you can do it." 305 | 306 | "Well I WILL, if you fool with me." 307 | 308 | "Oh yes--I've seen whole families in the same fix." 309 | 310 | "Smarty! You think you're SOME, now, DON'T you? Oh, what a hat!" 311 | 312 | "You can lump that hat if you don't like it. I dare you to knock it 313 | off--and anybody that'll take a dare will suck eggs." 314 | 315 | "You're a liar!" 316 | 317 | "You're another." 318 | 319 | "You're a fighting liar and dasn't take it up." 320 | 321 | "Aw--take a walk!" 322 | 323 | "Say--if you give me much more of your sass I'll take and bounce a 324 | rock off'n your head." 325 | 326 | "Oh, of COURSE you will." 327 | 328 | "Well I WILL." 329 | 330 | "Well why don't you DO it then? What do you keep SAYING you will for? 331 | Why don't you DO it? It's because you're afraid." 332 | 333 | "I AIN'T afraid." 334 | 335 | "You are." 336 | 337 | "I ain't." 338 | 339 | "You are." 340 | 341 | Another pause, and more eying and sidling around each other. Presently 342 | they were shoulder to shoulder. Tom said: 343 | 344 | "Get away from here!" 345 | 346 | "Go away yourself!" 347 | 348 | "I won't." 349 | 350 | "I won't either." 351 | 352 | So they stood, each with a foot placed at an angle as a brace, and 353 | both shoving with might and main, and glowering at each other with 354 | hate. But neither could get an advantage. After struggling till both 355 | were hot and flushed, each relaxed his strain with watchful caution, 356 | and Tom said: 357 | 358 | "You're a coward and a pup. I'll tell my big brother on you, and he 359 | can thrash you with his little finger, and I'll make him do it, too." 360 | 361 | "What do I care for your big brother? I've got a brother that's bigger 362 | than he is--and what's more, he can throw him over that fence, too." 363 | [Both brothers were imaginary.] 364 | 365 | "That's a lie." 366 | 367 | "YOUR saying so don't make it so." 368 | 369 | Tom drew a line in the dust with his big toe, and said: 370 | 371 | "I dare you to step over that, and I'll lick you till you can't stand 372 | up. Anybody that'll take a dare will steal sheep." 373 | 374 | The new boy stepped over promptly, and said: 375 | 376 | "Now you said you'd do it, now let's see you do it." 377 | 378 | "Don't you crowd me now; you better look out." 379 | 380 | "Well, you SAID you'd do it--why don't you do it?" 381 | 382 | "By jingo! for two cents I WILL do it." 383 | 384 | The new boy took two broad coppers out of his pocket and held them out 385 | with derision. Tom struck them to the ground. In an instant both boys 386 | were rolling and tumbling in the dirt, gripped together like cats; and 387 | for the space of a minute they tugged and tore at each other's hair and 388 | clothes, punched and scratched each other's nose, and covered 389 | themselves with dust and glory. Presently the confusion took form, and 390 | through the fog of battle Tom appeared, seated astride the new boy, and 391 | pounding him with his fists. "Holler 'nuff!" said he. 392 | 393 | The boy only struggled to free himself. He was crying--mainly from rage. 394 | 395 | "Holler 'nuff!"--and the pounding went on. 396 | 397 | At last the stranger got out a smothered "'Nuff!" and Tom let him up 398 | and said: 399 | 400 | "Now that'll learn you. Better look out who you're fooling with next 401 | time." 402 | 403 | The new boy went off brushing the dust from his clothes, sobbing, 404 | snuffling, and occasionally looking back and shaking his head and 405 | threatening what he would do to Tom the "next time he caught him out." 406 | To which Tom responded with jeers, and started off in high feather, and 407 | as soon as his back was turned the new boy snatched up a stone, threw 408 | it and hit him between the shoulders and then turned tail and ran like 409 | an antelope. Tom chased the traitor home, and thus found out where he 410 | lived. He then held a position at the gate for some time, daring the 411 | enemy to come outside, but the enemy only made faces at him through the 412 | window and declined. At last the enemy's mother appeared, and called 413 | Tom a bad, vicious, vulgar child, and ordered him away. So he went 414 | away; but he said he "'lowed" to "lay" for that boy. 415 | 416 | He got home pretty late that night, and when he climbed cautiously in 417 | at the window, he uncovered an ambuscade, in the person of his aunt; 418 | and when she saw the state his clothes were in her resolution to turn 419 | his Saturday holiday into captivity at hard labor became adamantine in 420 | its firmness. 421 | -------------------------------------------------------------------------------- /_examples/active.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | 11 | "github.com/jroimartin/gocui" 12 | ) 13 | 14 | var ( 15 | viewArr = []string{"v1", "v2", "v3", "v4"} 16 | active = 0 17 | ) 18 | 19 | func setCurrentViewOnTop(g *gocui.Gui, name string) (*gocui.View, error) { 20 | if _, err := g.SetCurrentView(name); err != nil { 21 | return nil, err 22 | } 23 | return g.SetViewOnTop(name) 24 | } 25 | 26 | func nextView(g *gocui.Gui, v *gocui.View) error { 27 | nextIndex := (active + 1) % len(viewArr) 28 | name := viewArr[nextIndex] 29 | 30 | out, err := g.View("v2") 31 | if err != nil { 32 | return err 33 | } 34 | fmt.Fprintln(out, "Going from view "+v.Name()+" to "+name) 35 | 36 | if _, err := setCurrentViewOnTop(g, name); err != nil { 37 | return err 38 | } 39 | 40 | if nextIndex == 0 || nextIndex == 3 { 41 | g.Cursor = true 42 | } else { 43 | g.Cursor = false 44 | } 45 | 46 | active = nextIndex 47 | return nil 48 | } 49 | 50 | func layout(g *gocui.Gui) error { 51 | maxX, maxY := g.Size() 52 | if v, err := g.SetView("v1", 0, 0, maxX/2-1, maxY/2-1); err != nil { 53 | if err != gocui.ErrUnknownView { 54 | return err 55 | } 56 | v.Title = "v1 (editable)" 57 | v.Editable = true 58 | v.Wrap = true 59 | 60 | if _, err = setCurrentViewOnTop(g, "v1"); err != nil { 61 | return err 62 | } 63 | } 64 | 65 | if v, err := g.SetView("v2", maxX/2-1, 0, maxX-1, maxY/2-1); err != nil { 66 | if err != gocui.ErrUnknownView { 67 | return err 68 | } 69 | v.Title = "v2" 70 | v.Wrap = true 71 | v.Autoscroll = true 72 | } 73 | if v, err := g.SetView("v3", 0, maxY/2-1, maxX/2-1, maxY-1); err != nil { 74 | if err != gocui.ErrUnknownView { 75 | return err 76 | } 77 | v.Title = "v3" 78 | v.Wrap = true 79 | v.Autoscroll = true 80 | fmt.Fprint(v, "Press TAB to change current view") 81 | } 82 | if v, err := g.SetView("v4", maxX/2, maxY/2, maxX-1, maxY-1); err != nil { 83 | if err != gocui.ErrUnknownView { 84 | return err 85 | } 86 | v.Title = "v4 (editable)" 87 | v.Editable = true 88 | } 89 | return nil 90 | } 91 | 92 | func quit(g *gocui.Gui, v *gocui.View) error { 93 | return gocui.ErrQuit 94 | } 95 | 96 | func main() { 97 | g, err := gocui.NewGui(gocui.OutputNormal) 98 | if err != nil { 99 | log.Panicln(err) 100 | } 101 | defer g.Close() 102 | 103 | g.Highlight = true 104 | g.Cursor = true 105 | g.SelFgColor = gocui.ColorGreen 106 | 107 | g.SetManagerFunc(layout) 108 | 109 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { 110 | log.Panicln(err) 111 | } 112 | if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, nextView); err != nil { 113 | log.Panicln(err) 114 | } 115 | 116 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 117 | log.Panicln(err) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /_examples/bufs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // WARNING: tricky code just for testing purposes, do not use as reference. 6 | 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | "log" 12 | 13 | "github.com/jroimartin/gocui" 14 | ) 15 | 16 | var vbuf, buf string 17 | 18 | func quit(g *gocui.Gui, v *gocui.View) error { 19 | vbuf = v.ViewBuffer() 20 | buf = v.Buffer() 21 | return gocui.ErrQuit 22 | } 23 | 24 | func overwrite(g *gocui.Gui, v *gocui.View) error { 25 | v.Overwrite = !v.Overwrite 26 | return nil 27 | } 28 | 29 | func layout(g *gocui.Gui) error { 30 | _, maxY := g.Size() 31 | if v, err := g.SetView("main", 0, 0, 20, maxY-1); err != nil { 32 | if err != gocui.ErrUnknownView { 33 | return err 34 | } 35 | v.Editable = true 36 | v.Wrap = true 37 | if _, err := g.SetCurrentView("main"); err != nil { 38 | return err 39 | } 40 | } 41 | return nil 42 | } 43 | 44 | func main() { 45 | g, err := gocui.NewGui(gocui.OutputNormal) 46 | if err != nil { 47 | log.Panicln(err) 48 | } 49 | 50 | g.Cursor = true 51 | g.Mouse = true 52 | 53 | g.SetManagerFunc(layout) 54 | 55 | if err := g.SetKeybinding("main", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { 56 | log.Panicln(err) 57 | } 58 | if err := g.SetKeybinding("main", gocui.KeyCtrlI, gocui.ModNone, overwrite); err != nil { 59 | log.Panicln(err) 60 | } 61 | 62 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 63 | log.Panicln(err) 64 | } 65 | 66 | g.Close() 67 | 68 | fmt.Printf("VBUF:\n%s\n", vbuf) 69 | fmt.Printf("BUF:\n%s\n", buf) 70 | } 71 | -------------------------------------------------------------------------------- /_examples/colors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | 11 | "github.com/jroimartin/gocui" 12 | ) 13 | 14 | func main() { 15 | g, err := gocui.NewGui(gocui.OutputNormal) 16 | if err != nil { 17 | log.Panicln(err) 18 | } 19 | defer g.Close() 20 | 21 | g.SetManagerFunc(layout) 22 | 23 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { 24 | log.Panicln(err) 25 | } 26 | 27 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 28 | log.Panicln(err) 29 | } 30 | } 31 | 32 | func layout(g *gocui.Gui) error { 33 | maxX, maxY := g.Size() 34 | if v, err := g.SetView("colors", maxX/2-7, maxY/2-12, maxX/2+7, maxY/2+13); err != nil { 35 | if err != gocui.ErrUnknownView { 36 | return err 37 | } 38 | for i := 0; i <= 7; i++ { 39 | for _, j := range []int{1, 4, 7} { 40 | fmt.Fprintf(v, "Hello \033[3%d;%dmcolors!\033[0m\n", i, j) 41 | } 42 | } 43 | } 44 | return nil 45 | } 46 | 47 | func quit(g *gocui.Gui, v *gocui.View) error { 48 | return gocui.ErrQuit 49 | } 50 | -------------------------------------------------------------------------------- /_examples/colors256.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | 11 | "github.com/jroimartin/gocui" 12 | ) 13 | 14 | func main() { 15 | g, err := gocui.NewGui(gocui.Output256) 16 | 17 | if err != nil { 18 | log.Panicln(err) 19 | } 20 | defer g.Close() 21 | 22 | g.SetManagerFunc(layout) 23 | 24 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { 25 | log.Panicln(err) 26 | } 27 | 28 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 29 | log.Panicln(err) 30 | } 31 | } 32 | 33 | func layout(g *gocui.Gui) error { 34 | maxX, maxY := g.Size() 35 | if v, err := g.SetView("colors", -1, -1, maxX, maxY); err != nil { 36 | if err != gocui.ErrUnknownView { 37 | return err 38 | } 39 | 40 | // 256-colors escape codes 41 | for i := 0; i < 256; i++ { 42 | str := fmt.Sprintf("\x1b[48;5;%dm\x1b[30m%3d\x1b[0m ", i, i) 43 | str += fmt.Sprintf("\x1b[38;5;%dm%3d\x1b[0m ", i, i) 44 | 45 | if (i+1)%10 == 0 { 46 | str += "\n" 47 | } 48 | 49 | fmt.Fprint(v, str) 50 | } 51 | 52 | fmt.Fprint(v, "\n\n") 53 | 54 | // 8-colors escape codes 55 | ctr := 0 56 | for i := 0; i <= 7; i++ { 57 | for _, j := range []int{1, 4, 7} { 58 | str := fmt.Sprintf("\x1b[3%d;%dm%d:%d\x1b[0m ", i, j, i, j) 59 | if (ctr+1)%20 == 0 { 60 | str += "\n" 61 | } 62 | 63 | fmt.Fprint(v, str) 64 | 65 | ctr++ 66 | } 67 | } 68 | } 69 | return nil 70 | } 71 | 72 | func quit(g *gocui.Gui, v *gocui.View) error { 73 | return gocui.ErrQuit 74 | } 75 | -------------------------------------------------------------------------------- /_examples/demo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "log" 12 | "strings" 13 | 14 | "github.com/jroimartin/gocui" 15 | ) 16 | 17 | func nextView(g *gocui.Gui, v *gocui.View) error { 18 | if v == nil || v.Name() == "side" { 19 | _, err := g.SetCurrentView("main") 20 | return err 21 | } 22 | _, err := g.SetCurrentView("side") 23 | return err 24 | } 25 | 26 | func cursorDown(g *gocui.Gui, v *gocui.View) error { 27 | if v != nil { 28 | cx, cy := v.Cursor() 29 | if err := v.SetCursor(cx, cy+1); err != nil { 30 | ox, oy := v.Origin() 31 | if err := v.SetOrigin(ox, oy+1); err != nil { 32 | return err 33 | } 34 | } 35 | } 36 | return nil 37 | } 38 | 39 | func cursorUp(g *gocui.Gui, v *gocui.View) error { 40 | if v != nil { 41 | ox, oy := v.Origin() 42 | cx, cy := v.Cursor() 43 | if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 { 44 | if err := v.SetOrigin(ox, oy-1); err != nil { 45 | return err 46 | } 47 | } 48 | } 49 | return nil 50 | } 51 | 52 | func getLine(g *gocui.Gui, v *gocui.View) error { 53 | var l string 54 | var err error 55 | 56 | _, cy := v.Cursor() 57 | if l, err = v.Line(cy); err != nil { 58 | l = "" 59 | } 60 | 61 | maxX, maxY := g.Size() 62 | if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil { 63 | if err != gocui.ErrUnknownView { 64 | return err 65 | } 66 | fmt.Fprintln(v, l) 67 | if _, err := g.SetCurrentView("msg"); err != nil { 68 | return err 69 | } 70 | } 71 | return nil 72 | } 73 | 74 | func delMsg(g *gocui.Gui, v *gocui.View) error { 75 | if err := g.DeleteView("msg"); err != nil { 76 | return err 77 | } 78 | if _, err := g.SetCurrentView("side"); err != nil { 79 | return err 80 | } 81 | return nil 82 | } 83 | 84 | func quit(g *gocui.Gui, v *gocui.View) error { 85 | return gocui.ErrQuit 86 | } 87 | 88 | func keybindings(g *gocui.Gui) error { 89 | if err := g.SetKeybinding("side", gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil { 90 | return err 91 | } 92 | if err := g.SetKeybinding("main", gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil { 93 | return err 94 | } 95 | if err := g.SetKeybinding("side", gocui.KeyArrowDown, gocui.ModNone, cursorDown); err != nil { 96 | return err 97 | } 98 | if err := g.SetKeybinding("side", gocui.KeyArrowUp, gocui.ModNone, cursorUp); err != nil { 99 | return err 100 | } 101 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { 102 | return err 103 | } 104 | if err := g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, getLine); err != nil { 105 | return err 106 | } 107 | if err := g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg); err != nil { 108 | return err 109 | } 110 | 111 | if err := g.SetKeybinding("main", gocui.KeyCtrlS, gocui.ModNone, saveMain); err != nil { 112 | return err 113 | } 114 | if err := g.SetKeybinding("main", gocui.KeyCtrlW, gocui.ModNone, saveVisualMain); err != nil { 115 | return err 116 | } 117 | return nil 118 | } 119 | 120 | func saveMain(g *gocui.Gui, v *gocui.View) error { 121 | f, err := ioutil.TempFile("", "gocui_demo_") 122 | if err != nil { 123 | return err 124 | } 125 | defer f.Close() 126 | 127 | p := make([]byte, 5) 128 | v.Rewind() 129 | for { 130 | n, err := v.Read(p) 131 | if n > 0 { 132 | if _, err := f.Write(p[:n]); err != nil { 133 | return err 134 | } 135 | } 136 | if err == io.EOF { 137 | break 138 | } 139 | if err != nil { 140 | return err 141 | } 142 | } 143 | return nil 144 | } 145 | 146 | func saveVisualMain(g *gocui.Gui, v *gocui.View) error { 147 | f, err := ioutil.TempFile("", "gocui_demo_") 148 | if err != nil { 149 | return err 150 | } 151 | defer f.Close() 152 | 153 | vb := v.ViewBuffer() 154 | if _, err := io.Copy(f, strings.NewReader(vb)); err != nil { 155 | return err 156 | } 157 | return nil 158 | } 159 | 160 | func layout(g *gocui.Gui) error { 161 | maxX, maxY := g.Size() 162 | if v, err := g.SetView("side", -1, -1, 30, maxY); err != nil { 163 | if err != gocui.ErrUnknownView { 164 | return err 165 | } 166 | v.Highlight = true 167 | v.SelBgColor = gocui.ColorGreen 168 | v.SelFgColor = gocui.ColorBlack 169 | fmt.Fprintln(v, "Item 1") 170 | fmt.Fprintln(v, "Item 2") 171 | fmt.Fprintln(v, "Item 3") 172 | fmt.Fprint(v, "\rWill be") 173 | fmt.Fprint(v, "deleted\rItem 4\nItem 5") 174 | } 175 | if v, err := g.SetView("main", 30, -1, maxX, maxY); err != nil { 176 | if err != gocui.ErrUnknownView { 177 | return err 178 | } 179 | b, err := ioutil.ReadFile("Mark.Twain-Tom.Sawyer.txt") 180 | if err != nil { 181 | panic(err) 182 | } 183 | fmt.Fprintf(v, "%s", b) 184 | v.Editable = true 185 | v.Wrap = true 186 | if _, err := g.SetCurrentView("main"); err != nil { 187 | return err 188 | } 189 | } 190 | return nil 191 | } 192 | 193 | func main() { 194 | g, err := gocui.NewGui(gocui.OutputNormal) 195 | if err != nil { 196 | log.Panicln(err) 197 | } 198 | defer g.Close() 199 | 200 | g.Cursor = true 201 | 202 | g.SetManagerFunc(layout) 203 | 204 | if err := keybindings(g); err != nil { 205 | log.Panicln(err) 206 | } 207 | 208 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 209 | log.Panicln(err) 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /_examples/dynamic.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "strings" 11 | 12 | "github.com/jroimartin/gocui" 13 | ) 14 | 15 | const delta = 1 16 | 17 | var ( 18 | views = []string{} 19 | curView = -1 20 | idxView = 0 21 | ) 22 | 23 | func main() { 24 | g, err := gocui.NewGui(gocui.OutputNormal) 25 | if err != nil { 26 | log.Panicln(err) 27 | } 28 | defer g.Close() 29 | 30 | g.Highlight = true 31 | g.SelFgColor = gocui.ColorRed 32 | 33 | g.SetManagerFunc(layout) 34 | 35 | if err := initKeybindings(g); err != nil { 36 | log.Panicln(err) 37 | } 38 | if err := newView(g); err != nil { 39 | log.Panicln(err) 40 | } 41 | 42 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 43 | log.Panicln(err) 44 | } 45 | } 46 | 47 | func layout(g *gocui.Gui) error { 48 | maxX, _ := g.Size() 49 | v, err := g.SetView("help", maxX-25, 0, maxX-1, 9) 50 | if err != nil { 51 | if err != gocui.ErrUnknownView { 52 | return err 53 | } 54 | fmt.Fprintln(v, "KEYBINDINGS") 55 | fmt.Fprintln(v, "Space: New View") 56 | fmt.Fprintln(v, "Tab: Next View") 57 | fmt.Fprintln(v, "← ↑ → ↓: Move View") 58 | fmt.Fprintln(v, "Backspace: Delete View") 59 | fmt.Fprintln(v, "t: Set view on top") 60 | fmt.Fprintln(v, "b: Set view on bottom") 61 | fmt.Fprintln(v, "^C: Exit") 62 | } 63 | return nil 64 | } 65 | 66 | func initKeybindings(g *gocui.Gui) error { 67 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, 68 | func(g *gocui.Gui, v *gocui.View) error { 69 | return gocui.ErrQuit 70 | }); err != nil { 71 | return err 72 | } 73 | if err := g.SetKeybinding("", gocui.KeySpace, gocui.ModNone, 74 | func(g *gocui.Gui, v *gocui.View) error { 75 | return newView(g) 76 | }); err != nil { 77 | return err 78 | } 79 | if err := g.SetKeybinding("", gocui.KeyBackspace2, gocui.ModNone, 80 | func(g *gocui.Gui, v *gocui.View) error { 81 | return delView(g) 82 | }); err != nil { 83 | return err 84 | } 85 | if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, 86 | func(g *gocui.Gui, v *gocui.View) error { 87 | return nextView(g, true) 88 | }); err != nil { 89 | return err 90 | } 91 | if err := g.SetKeybinding("", gocui.KeyArrowLeft, gocui.ModNone, 92 | func(g *gocui.Gui, v *gocui.View) error { 93 | return moveView(g, v, -delta, 0) 94 | }); err != nil { 95 | return err 96 | } 97 | if err := g.SetKeybinding("", gocui.KeyArrowRight, gocui.ModNone, 98 | func(g *gocui.Gui, v *gocui.View) error { 99 | return moveView(g, v, delta, 0) 100 | }); err != nil { 101 | return err 102 | } 103 | if err := g.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone, 104 | func(g *gocui.Gui, v *gocui.View) error { 105 | return moveView(g, v, 0, delta) 106 | }); err != nil { 107 | return err 108 | } 109 | if err := g.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone, 110 | func(g *gocui.Gui, v *gocui.View) error { 111 | return moveView(g, v, 0, -delta) 112 | }); err != nil { 113 | return err 114 | } 115 | if err := g.SetKeybinding("", 't', gocui.ModNone, 116 | func(g *gocui.Gui, v *gocui.View) error { 117 | _, err := g.SetViewOnTop(views[curView]) 118 | return err 119 | }); err != nil { 120 | return err 121 | } 122 | if err := g.SetKeybinding("", 'b', gocui.ModNone, 123 | func(g *gocui.Gui, v *gocui.View) error { 124 | _, err := g.SetViewOnBottom(views[curView]) 125 | return err 126 | }); err != nil { 127 | return err 128 | } 129 | return nil 130 | } 131 | 132 | func newView(g *gocui.Gui) error { 133 | maxX, maxY := g.Size() 134 | name := fmt.Sprintf("v%v", idxView) 135 | v, err := g.SetView(name, maxX/2-5, maxY/2-5, maxX/2+5, maxY/2+5) 136 | if err != nil { 137 | if err != gocui.ErrUnknownView { 138 | return err 139 | } 140 | v.Wrap = true 141 | fmt.Fprintln(v, strings.Repeat(name+" ", 30)) 142 | } 143 | if _, err := g.SetCurrentView(name); err != nil { 144 | return err 145 | } 146 | 147 | views = append(views, name) 148 | curView = len(views) - 1 149 | idxView += 1 150 | return nil 151 | } 152 | 153 | func delView(g *gocui.Gui) error { 154 | if len(views) <= 1 { 155 | return nil 156 | } 157 | 158 | if err := g.DeleteView(views[curView]); err != nil { 159 | return err 160 | } 161 | views = append(views[:curView], views[curView+1:]...) 162 | 163 | return nextView(g, false) 164 | } 165 | 166 | func nextView(g *gocui.Gui, disableCurrent bool) error { 167 | next := curView + 1 168 | if next > len(views)-1 { 169 | next = 0 170 | } 171 | 172 | if _, err := g.SetCurrentView(views[next]); err != nil { 173 | return err 174 | } 175 | 176 | curView = next 177 | return nil 178 | } 179 | 180 | func moveView(g *gocui.Gui, v *gocui.View, dx, dy int) error { 181 | name := v.Name() 182 | x0, y0, x1, y1, err := g.ViewPosition(name) 183 | if err != nil { 184 | return err 185 | } 186 | if _, err := g.SetView(name, x0+dx, y0+dy, x1+dx, y1+dy); err != nil { 187 | return err 188 | } 189 | return nil 190 | } 191 | -------------------------------------------------------------------------------- /_examples/flow_layout.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "strings" 11 | 12 | "github.com/jroimartin/gocui" 13 | ) 14 | 15 | type Label struct { 16 | name string 17 | w, h int 18 | body string 19 | } 20 | 21 | func NewLabel(name string, body string) *Label { 22 | lines := strings.Split(body, "\n") 23 | 24 | w := 0 25 | for _, l := range lines { 26 | if len(l) > w { 27 | w = len(l) 28 | } 29 | } 30 | h := len(lines) + 1 31 | w = w + 1 32 | 33 | return &Label{name: name, w: w, h: h, body: body} 34 | } 35 | 36 | func (w *Label) Layout(g *gocui.Gui) error { 37 | v, err := g.SetView(w.name, 0, 0, w.w, w.h) 38 | if err != nil { 39 | if err != gocui.ErrUnknownView { 40 | return err 41 | } 42 | fmt.Fprint(v, w.body) 43 | } 44 | return nil 45 | } 46 | 47 | func flowLayout(g *gocui.Gui) error { 48 | views := g.Views() 49 | x := 0 50 | for _, v := range views { 51 | w, h := v.Size() 52 | _, err := g.SetView(v.Name(), x, 0, x+w+1, h+1) 53 | if err != nil && err != gocui.ErrUnknownView { 54 | return err 55 | } 56 | x += w + 2 57 | } 58 | return nil 59 | } 60 | 61 | func main() { 62 | g, err := gocui.NewGui(gocui.OutputNormal) 63 | if err != nil { 64 | log.Panicln(err) 65 | } 66 | defer g.Close() 67 | 68 | l1 := NewLabel("l1", "This") 69 | l2 := NewLabel("l2", "is") 70 | l3 := NewLabel("l3", "a") 71 | l4 := NewLabel("l4", "flow\nlayout") 72 | l5 := NewLabel("l5", "!") 73 | fl := gocui.ManagerFunc(flowLayout) 74 | g.SetManager(l1, l2, l3, l4, l5, fl) 75 | 76 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { 77 | log.Panicln(err) 78 | } 79 | 80 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 81 | log.Panicln(err) 82 | } 83 | } 84 | 85 | func quit(g *gocui.Gui, v *gocui.View) error { 86 | return gocui.ErrQuit 87 | } 88 | -------------------------------------------------------------------------------- /_examples/goroutine.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "sync" 11 | "time" 12 | 13 | "github.com/jroimartin/gocui" 14 | ) 15 | 16 | const NumGoroutines = 10 17 | 18 | var ( 19 | done = make(chan struct{}) 20 | wg sync.WaitGroup 21 | 22 | mu sync.Mutex // protects ctr 23 | ctr = 0 24 | ) 25 | 26 | func main() { 27 | g, err := gocui.NewGui(gocui.OutputNormal) 28 | if err != nil { 29 | log.Panicln(err) 30 | } 31 | defer g.Close() 32 | 33 | g.SetManagerFunc(layout) 34 | 35 | if err := keybindings(g); err != nil { 36 | log.Panicln(err) 37 | } 38 | 39 | for i := 0; i < NumGoroutines; i++ { 40 | wg.Add(1) 41 | go counter(g) 42 | } 43 | 44 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 45 | log.Panicln(err) 46 | } 47 | 48 | wg.Wait() 49 | } 50 | 51 | func layout(g *gocui.Gui) error { 52 | if v, err := g.SetView("ctr", 2, 2, 12, 4); err != nil { 53 | if err != gocui.ErrUnknownView { 54 | return err 55 | } 56 | fmt.Fprintln(v, "0") 57 | } 58 | return nil 59 | } 60 | 61 | func keybindings(g *gocui.Gui) error { 62 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { 63 | return err 64 | } 65 | return nil 66 | } 67 | 68 | func quit(g *gocui.Gui, v *gocui.View) error { 69 | close(done) 70 | return gocui.ErrQuit 71 | } 72 | 73 | func counter(g *gocui.Gui) { 74 | defer wg.Done() 75 | 76 | for { 77 | select { 78 | case <-done: 79 | return 80 | case <-time.After(500 * time.Millisecond): 81 | mu.Lock() 82 | n := ctr 83 | ctr++ 84 | mu.Unlock() 85 | 86 | g.Update(func(g *gocui.Gui) error { 87 | v, err := g.View("ctr") 88 | if err != nil { 89 | return err 90 | } 91 | v.Clear() 92 | fmt.Fprintln(v, n) 93 | return nil 94 | }) 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /_examples/hello.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | 11 | "github.com/jroimartin/gocui" 12 | ) 13 | 14 | func main() { 15 | g, err := gocui.NewGui(gocui.OutputNormal) 16 | if err != nil { 17 | log.Panicln(err) 18 | } 19 | defer g.Close() 20 | 21 | g.SetManagerFunc(layout) 22 | 23 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { 24 | log.Panicln(err) 25 | } 26 | 27 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 28 | log.Panicln(err) 29 | } 30 | } 31 | 32 | func layout(g *gocui.Gui) error { 33 | maxX, maxY := g.Size() 34 | if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2); err != nil { 35 | if err != gocui.ErrUnknownView { 36 | return err 37 | } 38 | fmt.Fprintln(v, "Hello world!") 39 | } 40 | return nil 41 | } 42 | 43 | func quit(g *gocui.Gui, v *gocui.View) error { 44 | return gocui.ErrQuit 45 | } 46 | -------------------------------------------------------------------------------- /_examples/layout.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "log" 9 | 10 | "github.com/jroimartin/gocui" 11 | ) 12 | 13 | func layout(g *gocui.Gui) error { 14 | maxX, maxY := g.Size() 15 | if _, err := g.SetView("side", -1, -1, int(0.2*float32(maxX)), maxY-5); err != nil && 16 | err != gocui.ErrUnknownView { 17 | return err 18 | } 19 | if _, err := g.SetView("main", int(0.2*float32(maxX)), -1, maxX, maxY-5); err != nil && 20 | err != gocui.ErrUnknownView { 21 | return err 22 | } 23 | if _, err := g.SetView("cmdline", -1, maxY-5, maxX, maxY); err != nil && 24 | err != gocui.ErrUnknownView { 25 | return err 26 | } 27 | return nil 28 | } 29 | 30 | func quit(g *gocui.Gui, v *gocui.View) error { 31 | return gocui.ErrQuit 32 | } 33 | 34 | func main() { 35 | g, err := gocui.NewGui(gocui.OutputNormal) 36 | if err != nil { 37 | log.Panicln(err) 38 | } 39 | defer g.Close() 40 | 41 | g.SetManagerFunc(layout) 42 | 43 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { 44 | log.Panicln(err) 45 | } 46 | 47 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 48 | log.Panicln(err) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /_examples/mask.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | 11 | "github.com/jroimartin/gocui" 12 | ) 13 | 14 | func main() { 15 | g, err := gocui.NewGui(gocui.OutputNormal) 16 | if err != nil { 17 | log.Fatalln(err) 18 | } 19 | defer g.Close() 20 | 21 | g.Cursor = true 22 | 23 | g.SetManagerFunc(layout) 24 | 25 | if err := initKeybindings(g); err != nil { 26 | log.Fatalln(err) 27 | } 28 | 29 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 30 | log.Fatalln(err) 31 | } 32 | } 33 | 34 | func layout(g *gocui.Gui) error { 35 | maxX, maxY := g.Size() 36 | 37 | if v, err := g.SetView("help", maxX-23, 0, maxX-1, 3); err != nil { 38 | if err != gocui.ErrUnknownView { 39 | return err 40 | } 41 | v.Title = "Keybindings" 42 | fmt.Fprintln(v, "^a: Set mask") 43 | fmt.Fprintln(v, "^c: Exit") 44 | } 45 | 46 | if v, err := g.SetView("input", 0, 0, maxX-24, maxY-1); err != nil { 47 | if err != gocui.ErrUnknownView { 48 | return err 49 | } 50 | if _, err := g.SetCurrentView("input"); err != nil { 51 | return err 52 | } 53 | v.Editable = true 54 | v.Wrap = true 55 | } 56 | 57 | return nil 58 | } 59 | 60 | func initKeybindings(g *gocui.Gui) error { 61 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, 62 | func(g *gocui.Gui, v *gocui.View) error { 63 | return gocui.ErrQuit 64 | }); err != nil { 65 | return err 66 | } 67 | if err := g.SetKeybinding("input", gocui.KeyCtrlA, gocui.ModNone, 68 | func(g *gocui.Gui, v *gocui.View) error { 69 | v.Mask ^= '*' 70 | return nil 71 | }); err != nil { 72 | return err 73 | } 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /_examples/mouse.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | 11 | "github.com/jroimartin/gocui" 12 | ) 13 | 14 | func main() { 15 | g, err := gocui.NewGui(gocui.OutputNormal) 16 | if err != nil { 17 | log.Panicln(err) 18 | } 19 | defer g.Close() 20 | 21 | g.Cursor = true 22 | g.Mouse = true 23 | 24 | g.SetManagerFunc(layout) 25 | 26 | if err := keybindings(g); err != nil { 27 | log.Panicln(err) 28 | } 29 | 30 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 31 | log.Panicln(err) 32 | } 33 | } 34 | 35 | func layout(g *gocui.Gui) error { 36 | if v, err := g.SetView("but1", 2, 2, 22, 7); err != nil { 37 | if err != gocui.ErrUnknownView { 38 | return err 39 | } 40 | v.Highlight = true 41 | v.SelBgColor = gocui.ColorGreen 42 | v.SelFgColor = gocui.ColorBlack 43 | fmt.Fprintln(v, "Button 1 - line 1") 44 | fmt.Fprintln(v, "Button 1 - line 2") 45 | fmt.Fprintln(v, "Button 1 - line 3") 46 | fmt.Fprintln(v, "Button 1 - line 4") 47 | } 48 | if v, err := g.SetView("but2", 24, 2, 44, 4); err != nil { 49 | if err != gocui.ErrUnknownView { 50 | return err 51 | } 52 | v.Highlight = true 53 | v.SelBgColor = gocui.ColorGreen 54 | v.SelFgColor = gocui.ColorBlack 55 | fmt.Fprintln(v, "Button 2 - line 1") 56 | } 57 | return nil 58 | } 59 | 60 | func keybindings(g *gocui.Gui) error { 61 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { 62 | return err 63 | } 64 | for _, n := range []string{"but1", "but2"} { 65 | if err := g.SetKeybinding(n, gocui.MouseLeft, gocui.ModNone, showMsg); err != nil { 66 | return err 67 | } 68 | } 69 | if err := g.SetKeybinding("msg", gocui.MouseLeft, gocui.ModNone, delMsg); err != nil { 70 | return err 71 | } 72 | return nil 73 | } 74 | 75 | func quit(g *gocui.Gui, v *gocui.View) error { 76 | return gocui.ErrQuit 77 | } 78 | 79 | func showMsg(g *gocui.Gui, v *gocui.View) error { 80 | var l string 81 | var err error 82 | 83 | if _, err := g.SetCurrentView(v.Name()); err != nil { 84 | return err 85 | } 86 | 87 | _, cy := v.Cursor() 88 | if l, err = v.Line(cy); err != nil { 89 | l = "" 90 | } 91 | 92 | maxX, maxY := g.Size() 93 | if v, err := g.SetView("msg", maxX/2-10, maxY/2, maxX/2+10, maxY/2+2); err != nil { 94 | if err != gocui.ErrUnknownView { 95 | return err 96 | } 97 | fmt.Fprintln(v, l) 98 | } 99 | return nil 100 | } 101 | 102 | func delMsg(g *gocui.Gui, v *gocui.View) error { 103 | if err := g.DeleteView("msg"); err != nil { 104 | return err 105 | } 106 | return nil 107 | } 108 | -------------------------------------------------------------------------------- /_examples/ontop.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | 11 | "github.com/jroimartin/gocui" 12 | ) 13 | 14 | func main() { 15 | g, err := gocui.NewGui(gocui.OutputNormal) 16 | if err != nil { 17 | log.Panicln(err) 18 | } 19 | defer g.Close() 20 | 21 | g.SetManagerFunc(layout) 22 | 23 | if err := keybindings(g); err != nil { 24 | log.Panicln(err) 25 | } 26 | 27 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 28 | log.Panicln(err) 29 | } 30 | } 31 | 32 | func layout(g *gocui.Gui) error { 33 | if v, err := g.SetView("v1", 10, 2, 30, 6); err != nil { 34 | if err != gocui.ErrUnknownView { 35 | return err 36 | } 37 | fmt.Fprintln(v, "View #1") 38 | } 39 | if v, err := g.SetView("v2", 20, 4, 40, 8); err != nil { 40 | if err != gocui.ErrUnknownView { 41 | return err 42 | } 43 | fmt.Fprintln(v, "View #2") 44 | } 45 | if v, err := g.SetView("v3", 30, 6, 50, 10); err != nil { 46 | if err != gocui.ErrUnknownView { 47 | return err 48 | } 49 | fmt.Fprintln(v, "View #3") 50 | } 51 | 52 | return nil 53 | } 54 | 55 | func keybindings(g *gocui.Gui) error { 56 | err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { 57 | return gocui.ErrQuit 58 | }) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | err = g.SetKeybinding("", '1', gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { 64 | _, err := g.SetViewOnTop("v1") 65 | return err 66 | }) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | err = g.SetKeybinding("", '2', gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { 72 | _, err := g.SetViewOnTop("v2") 73 | return err 74 | }) 75 | if err != nil { 76 | return err 77 | } 78 | 79 | err = g.SetKeybinding("", '3', gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { 80 | _, err := g.SetViewOnTop("v3") 81 | return err 82 | }) 83 | if err != nil { 84 | return err 85 | } 86 | 87 | return nil 88 | } 89 | -------------------------------------------------------------------------------- /_examples/overlap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "log" 9 | 10 | "github.com/jroimartin/gocui" 11 | ) 12 | 13 | func layout(g *gocui.Gui) error { 14 | maxX, maxY := g.Size() 15 | if _, err := g.SetView("v1", -1, -1, 10, 10); err != nil && 16 | err != gocui.ErrUnknownView { 17 | return err 18 | } 19 | if _, err := g.SetView("v2", maxX-10, -1, maxX, 10); err != nil && 20 | err != gocui.ErrUnknownView { 21 | return err 22 | } 23 | if _, err := g.SetView("v3", maxX/2-5, -1, maxX/2+5, 10); err != nil && 24 | err != gocui.ErrUnknownView { 25 | return err 26 | } 27 | if _, err := g.SetView("v4", -1, maxY/2-5, 10, maxY/2+5); err != nil && 28 | err != gocui.ErrUnknownView { 29 | return err 30 | } 31 | if _, err := g.SetView("v5", maxX-10, maxY/2-5, maxX, maxY/2+5); err != nil && 32 | err != gocui.ErrUnknownView { 33 | return err 34 | } 35 | if _, err := g.SetView("v6", -1, maxY-10, 10, maxY); err != nil && 36 | err != gocui.ErrUnknownView { 37 | return err 38 | } 39 | if _, err := g.SetView("v7", maxX-10, maxY-10, maxX, maxY); err != nil && 40 | err != gocui.ErrUnknownView { 41 | return err 42 | } 43 | if _, err := g.SetView("v8", maxX/2-5, maxY-10, maxX/2+5, maxY); err != nil && 44 | err != gocui.ErrUnknownView { 45 | return err 46 | } 47 | if _, err := g.SetView("v9", maxX/2-5, maxY/2-5, maxX/2+5, maxY/2+5); err != nil && 48 | err != gocui.ErrUnknownView { 49 | return err 50 | } 51 | return nil 52 | } 53 | 54 | func quit(g *gocui.Gui, v *gocui.View) error { 55 | return gocui.ErrQuit 56 | } 57 | 58 | func main() { 59 | g, err := gocui.NewGui(gocui.OutputNormal) 60 | if err != nil { 61 | log.Panicln(err) 62 | } 63 | defer g.Close() 64 | 65 | g.SetManagerFunc(layout) 66 | 67 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { 68 | log.Panicln(err) 69 | } 70 | 71 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 72 | log.Panicln(err) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /_examples/size.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | 11 | "github.com/jroimartin/gocui" 12 | ) 13 | 14 | func main() { 15 | g, err := gocui.NewGui(gocui.OutputNormal) 16 | if err != nil { 17 | log.Panicln(err) 18 | } 19 | defer g.Close() 20 | 21 | g.SetManagerFunc(layout) 22 | 23 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { 24 | log.Panicln(err) 25 | } 26 | 27 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 28 | log.Panicln(err) 29 | } 30 | } 31 | 32 | func layout(g *gocui.Gui) error { 33 | maxX, maxY := g.Size() 34 | v, err := g.SetView("size", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2) 35 | if err != nil && err != gocui.ErrUnknownView { 36 | return err 37 | } 38 | v.Clear() 39 | fmt.Fprintf(v, "%d, %d", maxX, maxY) 40 | return nil 41 | } 42 | 43 | func quit(g *gocui.Gui, v *gocui.View) error { 44 | return gocui.ErrQuit 45 | } 46 | -------------------------------------------------------------------------------- /_examples/stdin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "encoding/hex" 9 | "fmt" 10 | "io" 11 | "log" 12 | "os" 13 | 14 | "github.com/jroimartin/gocui" 15 | ) 16 | 17 | func main() { 18 | g, err := gocui.NewGui(gocui.OutputNormal) 19 | if err != nil { 20 | log.Fatalln(err) 21 | } 22 | defer g.Close() 23 | 24 | g.Cursor = true 25 | 26 | g.SetManagerFunc(layout) 27 | 28 | if err := initKeybindings(g); err != nil { 29 | log.Fatalln(err) 30 | } 31 | 32 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 33 | log.Fatalln(err) 34 | } 35 | } 36 | 37 | func layout(g *gocui.Gui) error { 38 | maxX, _ := g.Size() 39 | 40 | if v, err := g.SetView("help", maxX-23, 0, maxX-1, 5); err != nil { 41 | if err != gocui.ErrUnknownView { 42 | return err 43 | } 44 | fmt.Fprintln(v, "KEYBINDINGS") 45 | fmt.Fprintln(v, "↑ ↓: Seek input") 46 | fmt.Fprintln(v, "a: Enable autoscroll") 47 | fmt.Fprintln(v, "^C: Exit") 48 | } 49 | 50 | if v, err := g.SetView("stdin", 0, 0, 80, 35); err != nil { 51 | if err != gocui.ErrUnknownView { 52 | return err 53 | } 54 | if _, err := g.SetCurrentView("stdin"); err != nil { 55 | return err 56 | } 57 | dumper := hex.Dumper(v) 58 | if _, err := io.Copy(dumper, os.Stdin); err != nil { 59 | return err 60 | } 61 | v.Wrap = true 62 | } 63 | 64 | return nil 65 | } 66 | 67 | func initKeybindings(g *gocui.Gui) error { 68 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { 69 | return err 70 | } 71 | if err := g.SetKeybinding("stdin", 'a', gocui.ModNone, autoscroll); err != nil { 72 | return err 73 | } 74 | if err := g.SetKeybinding("stdin", gocui.KeyArrowUp, gocui.ModNone, 75 | func(g *gocui.Gui, v *gocui.View) error { 76 | scrollView(v, -1) 77 | return nil 78 | }); err != nil { 79 | return err 80 | } 81 | if err := g.SetKeybinding("stdin", gocui.KeyArrowDown, gocui.ModNone, 82 | func(g *gocui.Gui, v *gocui.View) error { 83 | scrollView(v, 1) 84 | return nil 85 | }); err != nil { 86 | return err 87 | } 88 | return nil 89 | } 90 | 91 | func quit(g *gocui.Gui, v *gocui.View) error { 92 | return gocui.ErrQuit 93 | } 94 | 95 | func autoscroll(g *gocui.Gui, v *gocui.View) error { 96 | v.Autoscroll = true 97 | return nil 98 | } 99 | 100 | func scrollView(v *gocui.View, dy int) error { 101 | if v != nil { 102 | v.Autoscroll = false 103 | ox, oy := v.Origin() 104 | if err := v.SetOrigin(ox, oy+dy); err != nil { 105 | return err 106 | } 107 | } 108 | return nil 109 | } 110 | -------------------------------------------------------------------------------- /_examples/title.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "log" 9 | 10 | "github.com/jroimartin/gocui" 11 | ) 12 | 13 | func main() { 14 | g, err := gocui.NewGui(gocui.OutputNormal) 15 | if err != nil { 16 | log.Panicln(err) 17 | } 18 | defer g.Close() 19 | 20 | g.SetManagerFunc(layout) 21 | 22 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { 23 | log.Panicln(err) 24 | } 25 | 26 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 27 | log.Panicln(err) 28 | } 29 | } 30 | 31 | func quit(g *gocui.Gui, v *gocui.View) error { 32 | return gocui.ErrQuit 33 | } 34 | 35 | func layout(g *gocui.Gui) error { 36 | maxX, maxY := g.Size() 37 | 38 | // Overlap (front) 39 | if v, err := g.SetView("v1", 10, 2, 30, 6); err != nil { 40 | if err != gocui.ErrUnknownView { 41 | return err 42 | } 43 | v.Title = "Regular title" 44 | } 45 | if v, err := g.SetView("v2", 20, 4, 40, 8); err != nil { 46 | if err != gocui.ErrUnknownView { 47 | return err 48 | } 49 | v.Title = "Regular title" 50 | } 51 | 52 | // Overlap (back) 53 | if v, err := g.SetView("v3", 60, 4, 80, 8); err != nil { 54 | if err != gocui.ErrUnknownView { 55 | return err 56 | } 57 | v.Title = "Regular title" 58 | } 59 | if v, err := g.SetView("v4", 50, 2, 70, 6); err != nil { 60 | if err != gocui.ErrUnknownView { 61 | return err 62 | } 63 | v.Title = "Regular title" 64 | } 65 | 66 | // Overlap (frame) 67 | if v, err := g.SetView("v15", 90, 2, 110, 5); err != nil { 68 | if err != gocui.ErrUnknownView { 69 | return err 70 | } 71 | v.Title = "Regular title" 72 | } 73 | if v, err := g.SetView("v16", 100, 5, 120, 8); err != nil { 74 | if err != gocui.ErrUnknownView { 75 | return err 76 | } 77 | v.Title = "Regular title" 78 | } 79 | if v, err := g.SetView("v17", 140, 5, 160, 8); err != nil { 80 | if err != gocui.ErrUnknownView { 81 | return err 82 | } 83 | v.Title = "Regular title" 84 | } 85 | if v, err := g.SetView("v18", 130, 2, 150, 5); err != nil { 86 | if err != gocui.ErrUnknownView { 87 | return err 88 | } 89 | v.Title = "Regular title" 90 | } 91 | 92 | // Long title 93 | if v, err := g.SetView("v5", 10, 12, 30, 16); err != nil { 94 | if err != gocui.ErrUnknownView { 95 | return err 96 | } 97 | v.Title = "Long long long long title" 98 | } 99 | 100 | // No title 101 | if v, err := g.SetView("v6", 35, 12, 55, 16); err != nil { 102 | if err != gocui.ErrUnknownView { 103 | return err 104 | } 105 | v.Title = "" 106 | } 107 | if _, err := g.SetView("v7", 60, 12, 80, 16); err != nil { 108 | if err != gocui.ErrUnknownView { 109 | return err 110 | } 111 | } 112 | 113 | // Small view 114 | if v, err := g.SetView("v8", 85, 12, 88, 16); err != nil { 115 | if err != gocui.ErrUnknownView { 116 | return err 117 | } 118 | v.Title = "Regular title" 119 | } 120 | 121 | // Screen borders 122 | if v, err := g.SetView("v9", -10, 20, 10, 24); err != nil { 123 | if err != gocui.ErrUnknownView { 124 | return err 125 | } 126 | v.Title = "Regular title" 127 | } 128 | if v, err := g.SetView("v10", maxX-10, 20, maxX+10, 24); err != nil { 129 | if err != gocui.ErrUnknownView { 130 | return err 131 | } 132 | v.Title = "Regular title" 133 | } 134 | 135 | // Out of screen 136 | if v, err := g.SetView("v11", -21, 28, -1, 32); err != nil { 137 | if err != gocui.ErrUnknownView { 138 | return err 139 | } 140 | v.Title = "Regular title" 141 | } 142 | if v, err := g.SetView("v12", maxX, 28, maxX+20, 32); err != nil { 143 | if err != gocui.ErrUnknownView { 144 | return err 145 | } 146 | v.Title = "Regular title" 147 | } 148 | if v, err := g.SetView("v13", 10, -7, 30, -1); err != nil { 149 | if err != gocui.ErrUnknownView { 150 | return err 151 | } 152 | v.Title = "Regular title" 153 | } 154 | if v, err := g.SetView("v14", 10, maxY, 30, maxY+6); err != nil { 155 | if err != gocui.ErrUnknownView { 156 | return err 157 | } 158 | v.Title = "Regular title" 159 | } 160 | 161 | return nil 162 | } 163 | -------------------------------------------------------------------------------- /_examples/widgets.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "log" 11 | "strings" 12 | 13 | "github.com/jroimartin/gocui" 14 | ) 15 | 16 | const delta = 0.2 17 | 18 | type HelpWidget struct { 19 | name string 20 | x, y int 21 | w, h int 22 | body string 23 | } 24 | 25 | func NewHelpWidget(name string, x, y int, body string) *HelpWidget { 26 | lines := strings.Split(body, "\n") 27 | 28 | w := 0 29 | for _, l := range lines { 30 | if len(l) > w { 31 | w = len(l) 32 | } 33 | } 34 | h := len(lines) + 1 35 | w = w + 1 36 | 37 | return &HelpWidget{name: name, x: x, y: y, w: w, h: h, body: body} 38 | } 39 | 40 | func (w *HelpWidget) Layout(g *gocui.Gui) error { 41 | v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+w.h) 42 | if err != nil { 43 | if err != gocui.ErrUnknownView { 44 | return err 45 | } 46 | fmt.Fprint(v, w.body) 47 | } 48 | return nil 49 | } 50 | 51 | type StatusbarWidget struct { 52 | name string 53 | x, y int 54 | w int 55 | val float64 56 | } 57 | 58 | func NewStatusbarWidget(name string, x, y, w int) *StatusbarWidget { 59 | return &StatusbarWidget{name: name, x: x, y: y, w: w} 60 | } 61 | 62 | func (w *StatusbarWidget) SetVal(val float64) error { 63 | if val < 0 || val > 1 { 64 | return errors.New("invalid value") 65 | } 66 | w.val = val 67 | return nil 68 | } 69 | 70 | func (w *StatusbarWidget) Val() float64 { 71 | return w.val 72 | } 73 | 74 | func (w *StatusbarWidget) Layout(g *gocui.Gui) error { 75 | v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+2) 76 | if err != nil && err != gocui.ErrUnknownView { 77 | return err 78 | } 79 | v.Clear() 80 | 81 | rep := int(w.val * float64(w.w-1)) 82 | fmt.Fprint(v, strings.Repeat("▒", rep)) 83 | return nil 84 | } 85 | 86 | type ButtonWidget struct { 87 | name string 88 | x, y int 89 | w int 90 | label string 91 | handler func(g *gocui.Gui, v *gocui.View) error 92 | } 93 | 94 | func NewButtonWidget(name string, x, y int, label string, handler func(g *gocui.Gui, v *gocui.View) error) *ButtonWidget { 95 | return &ButtonWidget{name: name, x: x, y: y, w: len(label) + 1, label: label, handler: handler} 96 | } 97 | 98 | func (w *ButtonWidget) Layout(g *gocui.Gui) error { 99 | v, err := g.SetView(w.name, w.x, w.y, w.x+w.w, w.y+2) 100 | if err != nil { 101 | if err != gocui.ErrUnknownView { 102 | return err 103 | } 104 | if _, err := g.SetCurrentView(w.name); err != nil { 105 | return err 106 | } 107 | if err := g.SetKeybinding(w.name, gocui.KeyEnter, gocui.ModNone, w.handler); err != nil { 108 | return err 109 | } 110 | fmt.Fprint(v, w.label) 111 | } 112 | return nil 113 | } 114 | 115 | func main() { 116 | g, err := gocui.NewGui(gocui.OutputNormal) 117 | if err != nil { 118 | log.Panicln(err) 119 | } 120 | defer g.Close() 121 | 122 | g.Highlight = true 123 | g.SelFgColor = gocui.ColorRed 124 | 125 | help := NewHelpWidget("help", 1, 1, helpText) 126 | status := NewStatusbarWidget("status", 1, 7, 50) 127 | butdown := NewButtonWidget("butdown", 52, 7, "DOWN", statusDown(status)) 128 | butup := NewButtonWidget("butup", 58, 7, "UP", statusUp(status)) 129 | g.SetManager(help, status, butdown, butup) 130 | 131 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { 132 | log.Panicln(err) 133 | } 134 | if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, toggleButton); err != nil { 135 | log.Panicln(err) 136 | } 137 | 138 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 139 | log.Panicln(err) 140 | } 141 | } 142 | 143 | func quit(g *gocui.Gui, v *gocui.View) error { 144 | return gocui.ErrQuit 145 | } 146 | 147 | func toggleButton(g *gocui.Gui, v *gocui.View) error { 148 | nextview := "butdown" 149 | if v != nil && v.Name() == "butdown" { 150 | nextview = "butup" 151 | } 152 | _, err := g.SetCurrentView(nextview) 153 | return err 154 | } 155 | 156 | func statusUp(status *StatusbarWidget) func(g *gocui.Gui, v *gocui.View) error { 157 | return func(g *gocui.Gui, v *gocui.View) error { 158 | return statusSet(status, delta) 159 | } 160 | } 161 | 162 | func statusDown(status *StatusbarWidget) func(g *gocui.Gui, v *gocui.View) error { 163 | return func(g *gocui.Gui, v *gocui.View) error { 164 | return statusSet(status, -delta) 165 | } 166 | } 167 | 168 | func statusSet(sw *StatusbarWidget, inc float64) error { 169 | val := sw.Val() + inc 170 | if val < 0 || val > 1 { 171 | return nil 172 | } 173 | return sw.SetVal(val) 174 | } 175 | 176 | const helpText = `KEYBINDINGS 177 | Tab: Move between buttons 178 | Enter: Push button 179 | ^C: Exit` 180 | -------------------------------------------------------------------------------- /_examples/wrap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "strings" 11 | 12 | "github.com/jroimartin/gocui" 13 | ) 14 | 15 | func layout(g *gocui.Gui) error { 16 | maxX, maxY := g.Size() 17 | if v, err := g.SetView("main", 1, 1, maxX-1, maxY-1); err != nil { 18 | if err != gocui.ErrUnknownView { 19 | return err 20 | } 21 | v.Wrap = true 22 | 23 | line := strings.Repeat("This is a long line -- ", 10) 24 | fmt.Fprintf(v, "%s\n\n", line) 25 | fmt.Fprintln(v, "Short") 26 | } 27 | return nil 28 | } 29 | 30 | func quit(g *gocui.Gui, v *gocui.View) error { 31 | return gocui.ErrQuit 32 | } 33 | 34 | func main() { 35 | g, err := gocui.NewGui(gocui.OutputNormal) 36 | if err != nil { 37 | log.Panicln(err) 38 | } 39 | defer g.Close() 40 | 41 | g.SetManagerFunc(layout) 42 | 43 | if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { 44 | log.Panicln(err) 45 | } 46 | 47 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 48 | log.Panicln(err) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /attribute.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gocui 6 | 7 | import "github.com/nsf/termbox-go" 8 | 9 | // Attribute represents a terminal attribute, like color, font style, etc. They 10 | // can be combined using bitwise OR (|). Note that it is not possible to 11 | // combine multiple color attributes. 12 | type Attribute termbox.Attribute 13 | 14 | // Color attributes. 15 | const ( 16 | ColorDefault Attribute = Attribute(termbox.ColorDefault) 17 | ColorBlack = Attribute(termbox.ColorBlack) 18 | ColorRed = Attribute(termbox.ColorRed) 19 | ColorGreen = Attribute(termbox.ColorGreen) 20 | ColorYellow = Attribute(termbox.ColorYellow) 21 | ColorBlue = Attribute(termbox.ColorBlue) 22 | ColorMagenta = Attribute(termbox.ColorMagenta) 23 | ColorCyan = Attribute(termbox.ColorCyan) 24 | ColorWhite = Attribute(termbox.ColorWhite) 25 | ) 26 | 27 | // Text style attributes. 28 | const ( 29 | AttrBold Attribute = Attribute(termbox.AttrBold) 30 | AttrUnderline = Attribute(termbox.AttrUnderline) 31 | AttrReverse = Attribute(termbox.AttrReverse) 32 | ) 33 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package gocui allows to create console user interfaces. 7 | 8 | Create a new GUI: 9 | 10 | g, err := gocui.NewGui(gocui.OutputNormal) 11 | if err != nil { 12 | // handle error 13 | } 14 | defer g.Close() 15 | 16 | // Set GUI managers and key bindings 17 | // ... 18 | 19 | if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { 20 | // handle error 21 | } 22 | 23 | Set GUI managers: 24 | 25 | g.SetManager(mgr1, mgr2) 26 | 27 | Managers are in charge of GUI's layout and can be used to build widgets. On 28 | each iteration of the GUI's main loop, the Layout function of each configured 29 | manager is executed. Managers are used to set-up and update the application's 30 | main views, being possible to freely change them during execution. Also, it is 31 | important to mention that a main loop iteration is executed on each reported 32 | event (key-press, mouse event, window resize, etc). 33 | 34 | GUIs are composed by Views, you can think of it as buffers. Views implement the 35 | io.ReadWriter interface, so you can just write to them if you want to modify 36 | their content. The same is valid for reading. 37 | 38 | Create and initialize a view with absolute coordinates: 39 | 40 | if v, err := g.SetView("viewname", 2, 2, 22, 7); err != nil { 41 | if err != gocui.ErrUnknownView { 42 | // handle error 43 | } 44 | fmt.Fprintln(v, "This is a new view") 45 | // ... 46 | } 47 | 48 | Views can also be created using relative coordinates: 49 | 50 | maxX, maxY := g.Size() 51 | if v, err := g.SetView("viewname", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil { 52 | // ... 53 | } 54 | 55 | Configure keybindings: 56 | 57 | if err := g.SetKeybinding("viewname", gocui.KeyEnter, gocui.ModNone, fcn); err != nil { 58 | // handle error 59 | } 60 | 61 | gocui implements full mouse support that can be enabled with: 62 | 63 | g.Mouse = true 64 | 65 | Mouse events are handled like any other keybinding: 66 | 67 | if err := g.SetKeybinding("viewname", gocui.MouseLeft, gocui.ModNone, fcn); err != nil { 68 | // handle error 69 | } 70 | 71 | IMPORTANT: Views can only be created, destroyed or updated in three ways: from 72 | the Layout function within managers, from keybinding callbacks or via 73 | *Gui.Update(). The reason for this is that it allows gocui to be 74 | concurrent-safe. So, if you want to update your GUI from a goroutine, you must 75 | use *Gui.Update(). For example: 76 | 77 | g.Update(func(g *gocui.Gui) error { 78 | v, err := g.View("viewname") 79 | if err != nil { 80 | // handle error 81 | } 82 | v.Clear() 83 | fmt.Fprintln(v, "Writing from different goroutines") 84 | return nil 85 | }) 86 | 87 | By default, gocui provides a basic edition mode. This mode can be extended 88 | and customized creating a new Editor and assigning it to *View.Editor: 89 | 90 | type Editor interface { 91 | Edit(v *View, key Key, ch rune, mod Modifier) 92 | } 93 | 94 | DefaultEditor can be taken as example to create your own custom Editor: 95 | 96 | var DefaultEditor Editor = EditorFunc(simpleEditor) 97 | 98 | func simpleEditor(v *View, key Key, ch rune, mod Modifier) { 99 | switch { 100 | case ch != 0 && mod == 0: 101 | v.EditWrite(ch) 102 | case key == KeySpace: 103 | v.EditWrite(' ') 104 | case key == KeyBackspace || key == KeyBackspace2: 105 | v.EditDelete(true) 106 | // ... 107 | } 108 | } 109 | 110 | Colored text: 111 | 112 | Views allow to add colored text using ANSI colors. For example: 113 | 114 | fmt.Fprintln(v, "\x1b[0;31mHello world") 115 | 116 | For more information, see the examples in folder "_examples/". 117 | */ 118 | package gocui 119 | -------------------------------------------------------------------------------- /edit.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gocui 6 | 7 | import "errors" 8 | 9 | const maxInt = int(^uint(0) >> 1) 10 | 11 | // Editor interface must be satisfied by gocui editors. 12 | type Editor interface { 13 | Edit(v *View, key Key, ch rune, mod Modifier) 14 | } 15 | 16 | // The EditorFunc type is an adapter to allow the use of ordinary functions as 17 | // Editors. If f is a function with the appropriate signature, EditorFunc(f) 18 | // is an Editor object that calls f. 19 | type EditorFunc func(v *View, key Key, ch rune, mod Modifier) 20 | 21 | // Edit calls f(v, key, ch, mod) 22 | func (f EditorFunc) Edit(v *View, key Key, ch rune, mod Modifier) { 23 | f(v, key, ch, mod) 24 | } 25 | 26 | // DefaultEditor is the default editor. 27 | var DefaultEditor Editor = EditorFunc(simpleEditor) 28 | 29 | // simpleEditor is used as the default gocui editor. 30 | func simpleEditor(v *View, key Key, ch rune, mod Modifier) { 31 | switch { 32 | case ch != 0 && mod == 0: 33 | v.EditWrite(ch) 34 | case key == KeySpace: 35 | v.EditWrite(' ') 36 | case key == KeyBackspace || key == KeyBackspace2: 37 | v.EditDelete(true) 38 | case key == KeyDelete: 39 | v.EditDelete(false) 40 | case key == KeyInsert: 41 | v.Overwrite = !v.Overwrite 42 | case key == KeyEnter: 43 | v.EditNewLine() 44 | case key == KeyArrowDown: 45 | v.MoveCursor(0, 1, false) 46 | case key == KeyArrowUp: 47 | v.MoveCursor(0, -1, false) 48 | case key == KeyArrowLeft: 49 | v.MoveCursor(-1, 0, false) 50 | case key == KeyArrowRight: 51 | v.MoveCursor(1, 0, false) 52 | } 53 | } 54 | 55 | // EditWrite writes a rune at the cursor position. 56 | func (v *View) EditWrite(ch rune) { 57 | v.writeRune(v.cx, v.cy, ch) 58 | v.MoveCursor(1, 0, true) 59 | } 60 | 61 | // EditDelete deletes a rune at the cursor position. back determines the 62 | // direction. 63 | func (v *View) EditDelete(back bool) { 64 | x, y := v.ox+v.cx, v.oy+v.cy 65 | if y < 0 { 66 | return 67 | } else if y >= len(v.viewLines) { 68 | v.MoveCursor(-1, 0, true) 69 | return 70 | } 71 | 72 | maxX, _ := v.Size() 73 | if back { 74 | if x == 0 { // start of the line 75 | if y < 1 { 76 | return 77 | } 78 | 79 | var maxPrevWidth int 80 | if v.Wrap { 81 | maxPrevWidth = maxX 82 | } else { 83 | maxPrevWidth = maxInt 84 | } 85 | 86 | if v.viewLines[y].linesX == 0 { // regular line 87 | v.mergeLines(v.cy - 1) 88 | if len(v.viewLines[y-1].line) < maxPrevWidth { 89 | v.MoveCursor(-1, 0, true) 90 | } 91 | } else { // wrapped line 92 | v.deleteRune(len(v.viewLines[y-1].line)-1, v.cy-1) 93 | v.MoveCursor(-1, 0, true) 94 | } 95 | } else { // middle/end of the line 96 | v.deleteRune(v.cx-1, v.cy) 97 | v.MoveCursor(-1, 0, true) 98 | } 99 | } else { 100 | if x == len(v.viewLines[y].line) { // end of the line 101 | v.mergeLines(v.cy) 102 | } else { // start/middle of the line 103 | v.deleteRune(v.cx, v.cy) 104 | } 105 | } 106 | } 107 | 108 | // EditNewLine inserts a new line under the cursor. 109 | func (v *View) EditNewLine() { 110 | v.breakLine(v.cx, v.cy) 111 | v.ox = 0 112 | v.cx = 0 113 | v.MoveCursor(0, 1, true) 114 | } 115 | 116 | // MoveCursor moves the cursor taking into account the width of the line/view, 117 | // displacing the origin if necessary. 118 | func (v *View) MoveCursor(dx, dy int, writeMode bool) { 119 | maxX, maxY := v.Size() 120 | cx, cy := v.cx+dx, v.cy+dy 121 | x, y := v.ox+cx, v.oy+cy 122 | 123 | var curLineWidth, prevLineWidth int 124 | // get the width of the current line 125 | if writeMode { 126 | if v.Wrap { 127 | curLineWidth = maxX - 1 128 | } else { 129 | curLineWidth = maxInt 130 | } 131 | } else { 132 | if y >= 0 && y < len(v.viewLines) { 133 | curLineWidth = len(v.viewLines[y].line) 134 | if v.Wrap && curLineWidth >= maxX { 135 | curLineWidth = maxX - 1 136 | } 137 | } else { 138 | curLineWidth = 0 139 | } 140 | } 141 | // get the width of the previous line 142 | if y-1 >= 0 && y-1 < len(v.viewLines) { 143 | prevLineWidth = len(v.viewLines[y-1].line) 144 | } else { 145 | prevLineWidth = 0 146 | } 147 | 148 | // adjust cursor's x position and view's x origin 149 | if x > curLineWidth { // move to next line 150 | if dx > 0 { // horizontal movement 151 | cy++ 152 | if writeMode || v.oy+cy < len(v.viewLines) { 153 | if !v.Wrap { 154 | v.ox = 0 155 | } 156 | v.cx = 0 157 | } 158 | } else { // vertical movement 159 | if curLineWidth > 0 { // move cursor to the EOL 160 | if v.Wrap { 161 | v.cx = curLineWidth 162 | } else { 163 | ncx := curLineWidth - v.ox 164 | if ncx < 0 { 165 | v.ox += ncx 166 | if v.ox < 0 { 167 | v.ox = 0 168 | } 169 | v.cx = 0 170 | } else { 171 | v.cx = ncx 172 | } 173 | } 174 | } else { 175 | if writeMode || v.oy+cy < len(v.viewLines) { 176 | if !v.Wrap { 177 | v.ox = 0 178 | } 179 | v.cx = 0 180 | } 181 | } 182 | } 183 | } else if cx < 0 { 184 | if !v.Wrap && v.ox > 0 { // move origin to the left 185 | v.ox += cx 186 | v.cx = 0 187 | } else { // move to previous line 188 | cy-- 189 | if prevLineWidth > 0 { 190 | if !v.Wrap { // set origin so the EOL is visible 191 | nox := prevLineWidth - maxX + 1 192 | if nox < 0 { 193 | v.ox = 0 194 | } else { 195 | v.ox = nox 196 | } 197 | } 198 | v.cx = prevLineWidth 199 | } else { 200 | if !v.Wrap { 201 | v.ox = 0 202 | } 203 | v.cx = 0 204 | } 205 | } 206 | } else { // stay on the same line 207 | if v.Wrap { 208 | v.cx = cx 209 | } else { 210 | if cx >= maxX { 211 | v.ox += cx - maxX + 1 212 | v.cx = maxX 213 | } else { 214 | v.cx = cx 215 | } 216 | } 217 | } 218 | 219 | // adjust cursor's y position and view's y origin 220 | if cy < 0 { 221 | if v.oy > 0 { 222 | v.oy-- 223 | } 224 | } else if writeMode || v.oy+cy < len(v.viewLines) { 225 | if cy >= maxY { 226 | v.oy++ 227 | } else { 228 | v.cy = cy 229 | } 230 | } 231 | } 232 | 233 | // writeRune writes a rune into the view's internal buffer, at the 234 | // position corresponding to the point (x, y). The length of the internal 235 | // buffer is increased if the point is out of bounds. Overwrite mode is 236 | // governed by the value of View.overwrite. 237 | func (v *View) writeRune(x, y int, ch rune) error { 238 | v.tainted = true 239 | 240 | x, y, err := v.realPosition(x, y) 241 | if err != nil { 242 | return err 243 | } 244 | 245 | if x < 0 || y < 0 { 246 | return errors.New("invalid point") 247 | } 248 | 249 | if y >= len(v.lines) { 250 | s := make([][]cell, y-len(v.lines)+1) 251 | v.lines = append(v.lines, s...) 252 | } 253 | 254 | olen := len(v.lines[y]) 255 | 256 | var s []cell 257 | if x >= len(v.lines[y]) { 258 | s = make([]cell, x-len(v.lines[y])+1) 259 | } else if !v.Overwrite { 260 | s = make([]cell, 1) 261 | } 262 | v.lines[y] = append(v.lines[y], s...) 263 | 264 | if !v.Overwrite || (v.Overwrite && x >= olen-1) { 265 | copy(v.lines[y][x+1:], v.lines[y][x:]) 266 | } 267 | v.lines[y][x] = cell{ 268 | fgColor: v.FgColor, 269 | bgColor: v.BgColor, 270 | chr: ch, 271 | } 272 | 273 | return nil 274 | } 275 | 276 | // deleteRune removes a rune from the view's internal buffer, at the 277 | // position corresponding to the point (x, y). 278 | func (v *View) deleteRune(x, y int) error { 279 | v.tainted = true 280 | 281 | x, y, err := v.realPosition(x, y) 282 | if err != nil { 283 | return err 284 | } 285 | 286 | if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) { 287 | return errors.New("invalid point") 288 | } 289 | v.lines[y] = append(v.lines[y][:x], v.lines[y][x+1:]...) 290 | return nil 291 | } 292 | 293 | // mergeLines merges the lines "y" and "y+1" if possible. 294 | func (v *View) mergeLines(y int) error { 295 | v.tainted = true 296 | 297 | _, y, err := v.realPosition(0, y) 298 | if err != nil { 299 | return err 300 | } 301 | 302 | if y < 0 || y >= len(v.lines) { 303 | return errors.New("invalid point") 304 | } 305 | 306 | if y < len(v.lines)-1 { // otherwise we don't need to merge anything 307 | v.lines[y] = append(v.lines[y], v.lines[y+1]...) 308 | v.lines = append(v.lines[:y+1], v.lines[y+2:]...) 309 | } 310 | return nil 311 | } 312 | 313 | // breakLine breaks a line of the internal buffer at the position corresponding 314 | // to the point (x, y). 315 | func (v *View) breakLine(x, y int) error { 316 | v.tainted = true 317 | 318 | x, y, err := v.realPosition(x, y) 319 | if err != nil { 320 | return err 321 | } 322 | 323 | if y < 0 || y >= len(v.lines) { 324 | return errors.New("invalid point") 325 | } 326 | 327 | var left, right []cell 328 | if x < len(v.lines[y]) { // break line 329 | left = make([]cell, len(v.lines[y][:x])) 330 | copy(left, v.lines[y][:x]) 331 | right = make([]cell, len(v.lines[y][x:])) 332 | copy(right, v.lines[y][x:]) 333 | } else { // new empty line 334 | left = v.lines[y] 335 | } 336 | 337 | lines := make([][]cell, len(v.lines)+1) 338 | lines[y] = left 339 | lines[y+1] = right 340 | copy(lines, v.lines[:y]) 341 | copy(lines[y+2:], v.lines[y+1:]) 342 | v.lines = lines 343 | return nil 344 | } 345 | -------------------------------------------------------------------------------- /escape.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gocui 6 | 7 | import ( 8 | "errors" 9 | "strconv" 10 | ) 11 | 12 | type escapeInterpreter struct { 13 | state escapeState 14 | curch rune 15 | csiParam []string 16 | curFgColor, curBgColor Attribute 17 | mode OutputMode 18 | } 19 | 20 | type escapeState int 21 | 22 | const ( 23 | stateNone escapeState = iota 24 | stateEscape 25 | stateCSI 26 | stateParams 27 | ) 28 | 29 | var ( 30 | errNotCSI = errors.New("Not a CSI escape sequence") 31 | errCSIParseError = errors.New("CSI escape sequence parsing error") 32 | errCSITooLong = errors.New("CSI escape sequence is too long") 33 | ) 34 | 35 | // runes in case of error will output the non-parsed runes as a string. 36 | func (ei *escapeInterpreter) runes() []rune { 37 | switch ei.state { 38 | case stateNone: 39 | return []rune{0x1b} 40 | case stateEscape: 41 | return []rune{0x1b, ei.curch} 42 | case stateCSI: 43 | return []rune{0x1b, '[', ei.curch} 44 | case stateParams: 45 | ret := []rune{0x1b, '['} 46 | for _, s := range ei.csiParam { 47 | ret = append(ret, []rune(s)...) 48 | ret = append(ret, ';') 49 | } 50 | return append(ret, ei.curch) 51 | } 52 | return nil 53 | } 54 | 55 | // newEscapeInterpreter returns an escapeInterpreter that will be able to parse 56 | // terminal escape sequences. 57 | func newEscapeInterpreter(mode OutputMode) *escapeInterpreter { 58 | ei := &escapeInterpreter{ 59 | state: stateNone, 60 | curFgColor: ColorDefault, 61 | curBgColor: ColorDefault, 62 | mode: mode, 63 | } 64 | return ei 65 | } 66 | 67 | // reset sets the escapeInterpreter in initial state. 68 | func (ei *escapeInterpreter) reset() { 69 | ei.state = stateNone 70 | ei.curFgColor = ColorDefault 71 | ei.curBgColor = ColorDefault 72 | ei.csiParam = nil 73 | } 74 | 75 | // parseOne parses a rune. If isEscape is true, it means that the rune is part 76 | // of an escape sequence, and as such should not be printed verbatim. Otherwise, 77 | // it's not an escape sequence. 78 | func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) { 79 | // Sanity checks 80 | if len(ei.csiParam) > 20 { 81 | return false, errCSITooLong 82 | } 83 | if len(ei.csiParam) > 0 && len(ei.csiParam[len(ei.csiParam)-1]) > 255 { 84 | return false, errCSITooLong 85 | } 86 | 87 | ei.curch = ch 88 | 89 | switch ei.state { 90 | case stateNone: 91 | if ch == 0x1b { 92 | ei.state = stateEscape 93 | return true, nil 94 | } 95 | return false, nil 96 | case stateEscape: 97 | if ch == '[' { 98 | ei.state = stateCSI 99 | return true, nil 100 | } 101 | return false, errNotCSI 102 | case stateCSI: 103 | switch { 104 | case ch >= '0' && ch <= '9': 105 | ei.csiParam = append(ei.csiParam, "") 106 | case ch == 'm': 107 | ei.csiParam = append(ei.csiParam, "0") 108 | default: 109 | return false, errCSIParseError 110 | } 111 | ei.state = stateParams 112 | fallthrough 113 | case stateParams: 114 | switch { 115 | case ch >= '0' && ch <= '9': 116 | ei.csiParam[len(ei.csiParam)-1] += string(ch) 117 | return true, nil 118 | case ch == ';': 119 | ei.csiParam = append(ei.csiParam, "") 120 | return true, nil 121 | case ch == 'm': 122 | var err error 123 | switch ei.mode { 124 | case OutputNormal: 125 | err = ei.outputNormal() 126 | case Output256: 127 | err = ei.output256() 128 | } 129 | if err != nil { 130 | return false, errCSIParseError 131 | } 132 | 133 | ei.state = stateNone 134 | ei.csiParam = nil 135 | return true, nil 136 | default: 137 | return false, errCSIParseError 138 | } 139 | } 140 | return false, nil 141 | } 142 | 143 | // outputNormal provides 8 different colors: 144 | // black, red, green, yellow, blue, magenta, cyan, white 145 | func (ei *escapeInterpreter) outputNormal() error { 146 | for _, param := range ei.csiParam { 147 | p, err := strconv.Atoi(param) 148 | if err != nil { 149 | return errCSIParseError 150 | } 151 | 152 | switch { 153 | case p >= 30 && p <= 37: 154 | ei.curFgColor = Attribute(p - 30 + 1) 155 | case p == 39: 156 | ei.curFgColor = ColorDefault 157 | case p >= 40 && p <= 47: 158 | ei.curBgColor = Attribute(p - 40 + 1) 159 | case p == 49: 160 | ei.curBgColor = ColorDefault 161 | case p == 1: 162 | ei.curFgColor |= AttrBold 163 | case p == 4: 164 | ei.curFgColor |= AttrUnderline 165 | case p == 7: 166 | ei.curFgColor |= AttrReverse 167 | case p == 0: 168 | ei.curFgColor = ColorDefault 169 | ei.curBgColor = ColorDefault 170 | } 171 | } 172 | 173 | return nil 174 | } 175 | 176 | // output256 allows you to leverage the 256-colors terminal mode: 177 | // 0x01 - 0x08: the 8 colors as in OutputNormal 178 | // 0x09 - 0x10: Color* | AttrBold 179 | // 0x11 - 0xe8: 216 different colors 180 | // 0xe9 - 0x1ff: 24 different shades of grey 181 | func (ei *escapeInterpreter) output256() error { 182 | if len(ei.csiParam) < 3 { 183 | return ei.outputNormal() 184 | } 185 | 186 | mode, err := strconv.Atoi(ei.csiParam[1]) 187 | if err != nil { 188 | return errCSIParseError 189 | } 190 | if mode != 5 { 191 | return ei.outputNormal() 192 | } 193 | 194 | fgbg, err := strconv.Atoi(ei.csiParam[0]) 195 | if err != nil { 196 | return errCSIParseError 197 | } 198 | color, err := strconv.Atoi(ei.csiParam[2]) 199 | if err != nil { 200 | return errCSIParseError 201 | } 202 | 203 | switch fgbg { 204 | case 38: 205 | ei.curFgColor = Attribute(color + 1) 206 | 207 | for _, param := range ei.csiParam[3:] { 208 | p, err := strconv.Atoi(param) 209 | if err != nil { 210 | return errCSIParseError 211 | } 212 | 213 | switch { 214 | case p == 1: 215 | ei.curFgColor |= AttrBold 216 | case p == 4: 217 | ei.curFgColor |= AttrUnderline 218 | case p == 7: 219 | ei.curFgColor |= AttrReverse 220 | } 221 | } 222 | case 48: 223 | ei.curBgColor = Attribute(color + 1) 224 | default: 225 | return errCSIParseError 226 | } 227 | 228 | return nil 229 | } 230 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jroimartin/gocui 2 | 3 | go 1.16 4 | 5 | require github.com/nsf/termbox-go v1.1.1 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= 2 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 3 | github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY= 4 | github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo= 5 | -------------------------------------------------------------------------------- /gui.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gocui 6 | 7 | import ( 8 | "errors" 9 | 10 | "github.com/nsf/termbox-go" 11 | ) 12 | 13 | var ( 14 | // ErrQuit is used to decide if the MainLoop finished successfully. 15 | ErrQuit = errors.New("quit") 16 | 17 | // ErrUnknownView allows to assert if a View must be initialized. 18 | ErrUnknownView = errors.New("unknown view") 19 | ) 20 | 21 | // OutputMode represents the terminal's output mode (8 or 256 colors). 22 | type OutputMode termbox.OutputMode 23 | 24 | const ( 25 | // OutputNormal provides 8-colors terminal mode. 26 | OutputNormal = OutputMode(termbox.OutputNormal) 27 | 28 | // Output256 provides 256-colors terminal mode. 29 | Output256 = OutputMode(termbox.Output256) 30 | ) 31 | 32 | // Gui represents the whole User Interface, including the views, layouts 33 | // and keybindings. 34 | type Gui struct { 35 | tbEvents chan termbox.Event 36 | userEvents chan userEvent 37 | views []*View 38 | currentView *View 39 | managers []Manager 40 | keybindings []*keybinding 41 | maxX, maxY int 42 | outputMode OutputMode 43 | 44 | // BgColor and FgColor allow to configure the background and foreground 45 | // colors of the GUI. 46 | BgColor, FgColor Attribute 47 | 48 | // SelBgColor and SelFgColor allow to configure the background and 49 | // foreground colors of the frame of the current view. 50 | SelBgColor, SelFgColor Attribute 51 | 52 | // If Highlight is true, Sel{Bg,Fg}Colors will be used to draw the 53 | // frame of the current view. 54 | Highlight bool 55 | 56 | // If Cursor is true then the cursor is enabled. 57 | Cursor bool 58 | 59 | // If Mouse is true then mouse events will be enabled. 60 | Mouse bool 61 | 62 | // If InputEsc is true, when ESC sequence is in the buffer and it doesn't 63 | // match any known sequence, ESC means KeyEsc. 64 | InputEsc bool 65 | 66 | // If ASCII is true then use ASCII instead of unicode to draw the 67 | // interface. Using ASCII is more portable. 68 | ASCII bool 69 | } 70 | 71 | // NewGui returns a new Gui object with a given output mode. 72 | func NewGui(mode OutputMode) (*Gui, error) { 73 | if err := termbox.Init(); err != nil { 74 | return nil, err 75 | } 76 | 77 | g := &Gui{} 78 | 79 | g.outputMode = mode 80 | termbox.SetOutputMode(termbox.OutputMode(mode)) 81 | 82 | g.tbEvents = make(chan termbox.Event, 20) 83 | g.userEvents = make(chan userEvent, 20) 84 | 85 | g.maxX, g.maxY = termbox.Size() 86 | 87 | g.BgColor, g.FgColor = ColorDefault, ColorDefault 88 | g.SelBgColor, g.SelFgColor = ColorDefault, ColorDefault 89 | 90 | return g, nil 91 | } 92 | 93 | // Close finalizes the library. It should be called after a successful 94 | // initialization and when gocui is not needed anymore. 95 | func (g *Gui) Close() { 96 | termbox.Close() 97 | } 98 | 99 | // Size returns the terminal's size. 100 | func (g *Gui) Size() (x, y int) { 101 | return g.maxX, g.maxY 102 | } 103 | 104 | // SetRune writes a rune at the given point, relative to the top-left 105 | // corner of the terminal. It checks if the position is valid and applies 106 | // the given colors. 107 | func (g *Gui) SetRune(x, y int, ch rune, fgColor, bgColor Attribute) error { 108 | if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY { 109 | return errors.New("invalid point") 110 | } 111 | termbox.SetCell(x, y, ch, termbox.Attribute(fgColor), termbox.Attribute(bgColor)) 112 | return nil 113 | } 114 | 115 | // Rune returns the rune contained in the cell at the given position. 116 | // It checks if the position is valid. 117 | func (g *Gui) Rune(x, y int) (rune, error) { 118 | if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY { 119 | return ' ', errors.New("invalid point") 120 | } 121 | c := termbox.CellBuffer()[y*g.maxX+x] 122 | return c.Ch, nil 123 | } 124 | 125 | // SetView creates a new view with its top-left corner at (x0, y0) 126 | // and the bottom-right one at (x1, y1). If a view with the same name 127 | // already exists, its dimensions are updated; otherwise, the error 128 | // ErrUnknownView is returned, which allows to assert if the View must 129 | // be initialized. It checks if the position is valid. 130 | func (g *Gui) SetView(name string, x0, y0, x1, y1 int) (*View, error) { 131 | if x0 >= x1 || y0 >= y1 { 132 | return nil, errors.New("invalid dimensions") 133 | } 134 | if name == "" { 135 | return nil, errors.New("invalid name") 136 | } 137 | 138 | if v, err := g.View(name); err == nil { 139 | v.x0 = x0 140 | v.y0 = y0 141 | v.x1 = x1 142 | v.y1 = y1 143 | v.tainted = true 144 | return v, nil 145 | } 146 | 147 | v := newView(name, x0, y0, x1, y1, g.outputMode) 148 | v.BgColor, v.FgColor = g.BgColor, g.FgColor 149 | v.SelBgColor, v.SelFgColor = g.SelBgColor, g.SelFgColor 150 | g.views = append(g.views, v) 151 | return v, ErrUnknownView 152 | } 153 | 154 | // SetViewOnTop sets the given view on top of the existing ones. 155 | func (g *Gui) SetViewOnTop(name string) (*View, error) { 156 | for i, v := range g.views { 157 | if v.name == name { 158 | s := append(g.views[:i], g.views[i+1:]...) 159 | g.views = append(s, v) 160 | return v, nil 161 | } 162 | } 163 | return nil, ErrUnknownView 164 | } 165 | 166 | // SetViewOnBottom sets the given view on bottom of the existing ones. 167 | func (g *Gui) SetViewOnBottom(name string) (*View, error) { 168 | for i, v := range g.views { 169 | if v.name == name { 170 | s := append(g.views[:i], g.views[i+1:]...) 171 | g.views = append([]*View{v}, s...) 172 | return v, nil 173 | } 174 | } 175 | return nil, ErrUnknownView 176 | } 177 | 178 | // Views returns all the views in the GUI. 179 | func (g *Gui) Views() []*View { 180 | return g.views 181 | } 182 | 183 | // View returns a pointer to the view with the given name, or error 184 | // ErrUnknownView if a view with that name does not exist. 185 | func (g *Gui) View(name string) (*View, error) { 186 | for _, v := range g.views { 187 | if v.name == name { 188 | return v, nil 189 | } 190 | } 191 | return nil, ErrUnknownView 192 | } 193 | 194 | // ViewByPosition returns a pointer to a view matching the given position, or 195 | // error ErrUnknownView if a view in that position does not exist. 196 | func (g *Gui) ViewByPosition(x, y int) (*View, error) { 197 | // traverse views in reverse order checking top views first 198 | for i := len(g.views); i > 0; i-- { 199 | v := g.views[i-1] 200 | if x > v.x0 && x < v.x1 && y > v.y0 && y < v.y1 { 201 | return v, nil 202 | } 203 | } 204 | return nil, ErrUnknownView 205 | } 206 | 207 | // ViewPosition returns the coordinates of the view with the given name, or 208 | // error ErrUnknownView if a view with that name does not exist. 209 | func (g *Gui) ViewPosition(name string) (x0, y0, x1, y1 int, err error) { 210 | for _, v := range g.views { 211 | if v.name == name { 212 | return v.x0, v.y0, v.x1, v.y1, nil 213 | } 214 | } 215 | return 0, 0, 0, 0, ErrUnknownView 216 | } 217 | 218 | // DeleteView deletes a view by name. 219 | func (g *Gui) DeleteView(name string) error { 220 | for i, v := range g.views { 221 | if v.name == name { 222 | g.views = append(g.views[:i], g.views[i+1:]...) 223 | return nil 224 | } 225 | } 226 | return ErrUnknownView 227 | } 228 | 229 | // SetCurrentView gives the focus to a given view. 230 | func (g *Gui) SetCurrentView(name string) (*View, error) { 231 | for _, v := range g.views { 232 | if v.name == name { 233 | g.currentView = v 234 | return v, nil 235 | } 236 | } 237 | return nil, ErrUnknownView 238 | } 239 | 240 | // CurrentView returns the currently focused view, or nil if no view 241 | // owns the focus. 242 | func (g *Gui) CurrentView() *View { 243 | return g.currentView 244 | } 245 | 246 | // SetKeybinding creates a new keybinding. If viewname equals to "" 247 | // (empty string) then the keybinding will apply to all views. key must 248 | // be a rune or a Key. 249 | func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, handler func(*Gui, *View) error) error { 250 | var kb *keybinding 251 | 252 | k, ch, err := getKey(key) 253 | if err != nil { 254 | return err 255 | } 256 | kb = newKeybinding(viewname, k, ch, mod, handler) 257 | g.keybindings = append(g.keybindings, kb) 258 | return nil 259 | } 260 | 261 | // DeleteKeybinding deletes a keybinding. 262 | func (g *Gui) DeleteKeybinding(viewname string, key interface{}, mod Modifier) error { 263 | k, ch, err := getKey(key) 264 | if err != nil { 265 | return err 266 | } 267 | 268 | for i, kb := range g.keybindings { 269 | if kb.viewName == viewname && kb.ch == ch && kb.key == k && kb.mod == mod { 270 | g.keybindings = append(g.keybindings[:i], g.keybindings[i+1:]...) 271 | return nil 272 | } 273 | } 274 | return errors.New("keybinding not found") 275 | } 276 | 277 | // DeleteKeybindings deletes all keybindings of view. 278 | func (g *Gui) DeleteKeybindings(viewname string) { 279 | var s []*keybinding 280 | for _, kb := range g.keybindings { 281 | if kb.viewName != viewname { 282 | s = append(s, kb) 283 | } 284 | } 285 | g.keybindings = s 286 | } 287 | 288 | // getKey takes an empty interface with a key and returns the corresponding 289 | // typed Key or rune. 290 | func getKey(key interface{}) (Key, rune, error) { 291 | switch t := key.(type) { 292 | case Key: 293 | return t, 0, nil 294 | case rune: 295 | return 0, t, nil 296 | default: 297 | return 0, 0, errors.New("unknown type") 298 | } 299 | } 300 | 301 | // userEvent represents an event triggered by the user. 302 | type userEvent struct { 303 | f func(*Gui) error 304 | } 305 | 306 | // Update executes the passed function. This method can be called safely from a 307 | // goroutine in order to update the GUI. It is important to note that the 308 | // passed function won't be executed immediately, instead it will be added to 309 | // the user events queue. Given that Update spawns a goroutine, the order in 310 | // which the user events will be handled is not guaranteed. 311 | func (g *Gui) Update(f func(*Gui) error) { 312 | go func() { g.userEvents <- userEvent{f: f} }() 313 | } 314 | 315 | // A Manager is in charge of GUI's layout and can be used to build widgets. 316 | type Manager interface { 317 | // Layout is called every time the GUI is redrawn, it must contain the 318 | // base views and its initializations. 319 | Layout(*Gui) error 320 | } 321 | 322 | // The ManagerFunc type is an adapter to allow the use of ordinary functions as 323 | // Managers. If f is a function with the appropriate signature, ManagerFunc(f) 324 | // is an Manager object that calls f. 325 | type ManagerFunc func(*Gui) error 326 | 327 | // Layout calls f(g) 328 | func (f ManagerFunc) Layout(g *Gui) error { 329 | return f(g) 330 | } 331 | 332 | // SetManager sets the given GUI managers. It deletes all views and 333 | // keybindings. 334 | func (g *Gui) SetManager(managers ...Manager) { 335 | g.managers = managers 336 | g.currentView = nil 337 | g.views = nil 338 | g.keybindings = nil 339 | 340 | go func() { g.tbEvents <- termbox.Event{Type: termbox.EventResize} }() 341 | } 342 | 343 | // SetManagerFunc sets the given manager function. It deletes all views and 344 | // keybindings. 345 | func (g *Gui) SetManagerFunc(manager func(*Gui) error) { 346 | g.SetManager(ManagerFunc(manager)) 347 | } 348 | 349 | // MainLoop runs the main loop until an error is returned. A successful 350 | // finish should return ErrQuit. 351 | func (g *Gui) MainLoop() error { 352 | go func() { 353 | for { 354 | g.tbEvents <- termbox.PollEvent() 355 | } 356 | }() 357 | 358 | inputMode := termbox.InputAlt 359 | if g.InputEsc { 360 | inputMode = termbox.InputEsc 361 | } 362 | if g.Mouse { 363 | inputMode |= termbox.InputMouse 364 | } 365 | termbox.SetInputMode(inputMode) 366 | 367 | if err := g.flush(); err != nil { 368 | return err 369 | } 370 | for { 371 | select { 372 | case ev := <-g.tbEvents: 373 | if err := g.handleEvent(&ev); err != nil { 374 | return err 375 | } 376 | case ev := <-g.userEvents: 377 | if err := ev.f(g); err != nil { 378 | return err 379 | } 380 | } 381 | if err := g.consumeevents(); err != nil { 382 | return err 383 | } 384 | if err := g.flush(); err != nil { 385 | return err 386 | } 387 | } 388 | } 389 | 390 | // consumeevents handles the remaining events in the events pool. 391 | func (g *Gui) consumeevents() error { 392 | for { 393 | select { 394 | case ev := <-g.tbEvents: 395 | if err := g.handleEvent(&ev); err != nil { 396 | return err 397 | } 398 | case ev := <-g.userEvents: 399 | if err := ev.f(g); err != nil { 400 | return err 401 | } 402 | default: 403 | return nil 404 | } 405 | } 406 | } 407 | 408 | // handleEvent handles an event, based on its type (key-press, error, 409 | // etc.) 410 | func (g *Gui) handleEvent(ev *termbox.Event) error { 411 | switch ev.Type { 412 | case termbox.EventKey, termbox.EventMouse: 413 | return g.onKey(ev) 414 | case termbox.EventError: 415 | return ev.Err 416 | default: 417 | return nil 418 | } 419 | } 420 | 421 | // flush updates the gui, re-drawing frames and buffers. 422 | func (g *Gui) flush() error { 423 | termbox.Clear(termbox.Attribute(g.FgColor), termbox.Attribute(g.BgColor)) 424 | 425 | maxX, maxY := termbox.Size() 426 | // if GUI's size has changed, we need to redraw all views 427 | if maxX != g.maxX || maxY != g.maxY { 428 | for _, v := range g.views { 429 | v.tainted = true 430 | } 431 | } 432 | g.maxX, g.maxY = maxX, maxY 433 | 434 | for _, m := range g.managers { 435 | if err := m.Layout(g); err != nil { 436 | return err 437 | } 438 | } 439 | for _, v := range g.views { 440 | if v.Frame { 441 | var fgColor, bgColor Attribute 442 | if g.Highlight && v == g.currentView { 443 | fgColor = g.SelFgColor 444 | bgColor = g.SelBgColor 445 | } else { 446 | fgColor = g.FgColor 447 | bgColor = g.BgColor 448 | } 449 | 450 | if err := g.drawFrameEdges(v, fgColor, bgColor); err != nil { 451 | return err 452 | } 453 | if err := g.drawFrameCorners(v, fgColor, bgColor); err != nil { 454 | return err 455 | } 456 | if v.Title != "" { 457 | if err := g.drawTitle(v, fgColor, bgColor); err != nil { 458 | return err 459 | } 460 | } 461 | } 462 | if err := g.draw(v); err != nil { 463 | return err 464 | } 465 | } 466 | termbox.Flush() 467 | return nil 468 | } 469 | 470 | // drawFrameEdges draws the horizontal and vertical edges of a view. 471 | func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error { 472 | runeH, runeV := '─', '│' 473 | if g.ASCII { 474 | runeH, runeV = '-', '|' 475 | } 476 | 477 | for x := v.x0 + 1; x < v.x1 && x < g.maxX; x++ { 478 | if x < 0 { 479 | continue 480 | } 481 | if v.y0 > -1 && v.y0 < g.maxY { 482 | if err := g.SetRune(x, v.y0, runeH, fgColor, bgColor); err != nil { 483 | return err 484 | } 485 | } 486 | if v.y1 > -1 && v.y1 < g.maxY { 487 | if err := g.SetRune(x, v.y1, runeH, fgColor, bgColor); err != nil { 488 | return err 489 | } 490 | } 491 | } 492 | for y := v.y0 + 1; y < v.y1 && y < g.maxY; y++ { 493 | if y < 0 { 494 | continue 495 | } 496 | if v.x0 > -1 && v.x0 < g.maxX { 497 | if err := g.SetRune(v.x0, y, runeV, fgColor, bgColor); err != nil { 498 | return err 499 | } 500 | } 501 | if v.x1 > -1 && v.x1 < g.maxX { 502 | if err := g.SetRune(v.x1, y, runeV, fgColor, bgColor); err != nil { 503 | return err 504 | } 505 | } 506 | } 507 | return nil 508 | } 509 | 510 | // drawFrameCorners draws the corners of the view. 511 | func (g *Gui) drawFrameCorners(v *View, fgColor, bgColor Attribute) error { 512 | runeTL, runeTR, runeBL, runeBR := '┌', '┐', '└', '┘' 513 | if g.ASCII { 514 | runeTL, runeTR, runeBL, runeBR = '+', '+', '+', '+' 515 | } 516 | 517 | corners := []struct { 518 | x, y int 519 | ch rune 520 | }{{v.x0, v.y0, runeTL}, {v.x1, v.y0, runeTR}, {v.x0, v.y1, runeBL}, {v.x1, v.y1, runeBR}} 521 | 522 | for _, c := range corners { 523 | if c.x >= 0 && c.y >= 0 && c.x < g.maxX && c.y < g.maxY { 524 | if err := g.SetRune(c.x, c.y, c.ch, fgColor, bgColor); err != nil { 525 | return err 526 | } 527 | } 528 | } 529 | return nil 530 | } 531 | 532 | // drawTitle draws the title of the view. 533 | func (g *Gui) drawTitle(v *View, fgColor, bgColor Attribute) error { 534 | if v.y0 < 0 || v.y0 >= g.maxY { 535 | return nil 536 | } 537 | 538 | for i, ch := range v.Title { 539 | x := v.x0 + i + 2 540 | if x < 0 { 541 | continue 542 | } else if x > v.x1-2 || x >= g.maxX { 543 | break 544 | } 545 | if err := g.SetRune(x, v.y0, ch, fgColor, bgColor); err != nil { 546 | return err 547 | } 548 | } 549 | return nil 550 | } 551 | 552 | // draw manages the cursor and calls the draw function of a view. 553 | func (g *Gui) draw(v *View) error { 554 | if g.Cursor { 555 | if curview := g.currentView; curview != nil { 556 | vMaxX, vMaxY := curview.Size() 557 | if curview.cx < 0 { 558 | curview.cx = 0 559 | } else if curview.cx >= vMaxX { 560 | curview.cx = vMaxX - 1 561 | } 562 | if curview.cy < 0 { 563 | curview.cy = 0 564 | } else if curview.cy >= vMaxY { 565 | curview.cy = vMaxY - 1 566 | } 567 | 568 | gMaxX, gMaxY := g.Size() 569 | cx, cy := curview.x0+curview.cx+1, curview.y0+curview.cy+1 570 | if cx >= 0 && cx < gMaxX && cy >= 0 && cy < gMaxY { 571 | termbox.SetCursor(cx, cy) 572 | } else { 573 | termbox.HideCursor() 574 | } 575 | } 576 | } else { 577 | termbox.HideCursor() 578 | } 579 | 580 | v.clearRunes() 581 | if err := v.draw(); err != nil { 582 | return err 583 | } 584 | return nil 585 | } 586 | 587 | // onKey manages key-press events. A keybinding handler is called when 588 | // a key-press or mouse event satisfies a configured keybinding. Furthermore, 589 | // currentView's internal buffer is modified if currentView.Editable is true. 590 | func (g *Gui) onKey(ev *termbox.Event) error { 591 | switch ev.Type { 592 | case termbox.EventKey: 593 | matched, err := g.execKeybindings(g.currentView, ev) 594 | if err != nil { 595 | return err 596 | } 597 | if matched { 598 | break 599 | } 600 | if g.currentView != nil && g.currentView.Editable && g.currentView.Editor != nil { 601 | g.currentView.Editor.Edit(g.currentView, Key(ev.Key), ev.Ch, Modifier(ev.Mod)) 602 | } 603 | case termbox.EventMouse: 604 | mx, my := ev.MouseX, ev.MouseY 605 | v, err := g.ViewByPosition(mx, my) 606 | if err != nil { 607 | break 608 | } 609 | if err := v.SetCursor(mx-v.x0-1, my-v.y0-1); err != nil { 610 | return err 611 | } 612 | if _, err := g.execKeybindings(v, ev); err != nil { 613 | return err 614 | } 615 | } 616 | 617 | return nil 618 | } 619 | 620 | // execKeybindings executes the keybinding handlers that match the passed view 621 | // and event. The value of matched is true if there is a match and no errors. 622 | func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err error) { 623 | matched = false 624 | for _, kb := range g.keybindings { 625 | if kb.handler == nil { 626 | continue 627 | } 628 | if kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) && kb.matchView(v) { 629 | if err := kb.handler(g, v); err != nil { 630 | return false, err 631 | } 632 | matched = true 633 | } 634 | } 635 | return matched, nil 636 | } 637 | -------------------------------------------------------------------------------- /keybinding.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gocui 6 | 7 | import "github.com/nsf/termbox-go" 8 | 9 | // Keybidings are used to link a given key-press event with a handler. 10 | type keybinding struct { 11 | viewName string 12 | key Key 13 | ch rune 14 | mod Modifier 15 | handler func(*Gui, *View) error 16 | } 17 | 18 | // newKeybinding returns a new Keybinding object. 19 | func newKeybinding(viewname string, key Key, ch rune, mod Modifier, handler func(*Gui, *View) error) (kb *keybinding) { 20 | kb = &keybinding{ 21 | viewName: viewname, 22 | key: key, 23 | ch: ch, 24 | mod: mod, 25 | handler: handler, 26 | } 27 | return kb 28 | } 29 | 30 | // matchKeypress returns if the keybinding matches the keypress. 31 | func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool { 32 | return kb.key == key && kb.ch == ch && kb.mod == mod 33 | } 34 | 35 | // matchView returns if the keybinding matches the current view. 36 | func (kb *keybinding) matchView(v *View) bool { 37 | if kb.viewName == "" { 38 | return true 39 | } 40 | return v != nil && kb.viewName == v.name 41 | } 42 | 43 | // Key represents special keys or keys combinations. 44 | type Key termbox.Key 45 | 46 | // Special keys. 47 | const ( 48 | KeyF1 Key = Key(termbox.KeyF1) 49 | KeyF2 = Key(termbox.KeyF2) 50 | KeyF3 = Key(termbox.KeyF3) 51 | KeyF4 = Key(termbox.KeyF4) 52 | KeyF5 = Key(termbox.KeyF5) 53 | KeyF6 = Key(termbox.KeyF6) 54 | KeyF7 = Key(termbox.KeyF7) 55 | KeyF8 = Key(termbox.KeyF8) 56 | KeyF9 = Key(termbox.KeyF9) 57 | KeyF10 = Key(termbox.KeyF10) 58 | KeyF11 = Key(termbox.KeyF11) 59 | KeyF12 = Key(termbox.KeyF12) 60 | KeyInsert = Key(termbox.KeyInsert) 61 | KeyDelete = Key(termbox.KeyDelete) 62 | KeyHome = Key(termbox.KeyHome) 63 | KeyEnd = Key(termbox.KeyEnd) 64 | KeyPgup = Key(termbox.KeyPgup) 65 | KeyPgdn = Key(termbox.KeyPgdn) 66 | KeyArrowUp = Key(termbox.KeyArrowUp) 67 | KeyArrowDown = Key(termbox.KeyArrowDown) 68 | KeyArrowLeft = Key(termbox.KeyArrowLeft) 69 | KeyArrowRight = Key(termbox.KeyArrowRight) 70 | 71 | MouseLeft = Key(termbox.MouseLeft) 72 | MouseMiddle = Key(termbox.MouseMiddle) 73 | MouseRight = Key(termbox.MouseRight) 74 | MouseRelease = Key(termbox.MouseRelease) 75 | MouseWheelUp = Key(termbox.MouseWheelUp) 76 | MouseWheelDown = Key(termbox.MouseWheelDown) 77 | ) 78 | 79 | // Keys combinations. 80 | const ( 81 | KeyCtrlTilde Key = Key(termbox.KeyCtrlTilde) 82 | KeyCtrl2 = Key(termbox.KeyCtrl2) 83 | KeyCtrlSpace = Key(termbox.KeyCtrlSpace) 84 | KeyCtrlA = Key(termbox.KeyCtrlA) 85 | KeyCtrlB = Key(termbox.KeyCtrlB) 86 | KeyCtrlC = Key(termbox.KeyCtrlC) 87 | KeyCtrlD = Key(termbox.KeyCtrlD) 88 | KeyCtrlE = Key(termbox.KeyCtrlE) 89 | KeyCtrlF = Key(termbox.KeyCtrlF) 90 | KeyCtrlG = Key(termbox.KeyCtrlG) 91 | KeyBackspace = Key(termbox.KeyBackspace) 92 | KeyCtrlH = Key(termbox.KeyCtrlH) 93 | KeyTab = Key(termbox.KeyTab) 94 | KeyCtrlI = Key(termbox.KeyCtrlI) 95 | KeyCtrlJ = Key(termbox.KeyCtrlJ) 96 | KeyCtrlK = Key(termbox.KeyCtrlK) 97 | KeyCtrlL = Key(termbox.KeyCtrlL) 98 | KeyEnter = Key(termbox.KeyEnter) 99 | KeyCtrlM = Key(termbox.KeyCtrlM) 100 | KeyCtrlN = Key(termbox.KeyCtrlN) 101 | KeyCtrlO = Key(termbox.KeyCtrlO) 102 | KeyCtrlP = Key(termbox.KeyCtrlP) 103 | KeyCtrlQ = Key(termbox.KeyCtrlQ) 104 | KeyCtrlR = Key(termbox.KeyCtrlR) 105 | KeyCtrlS = Key(termbox.KeyCtrlS) 106 | KeyCtrlT = Key(termbox.KeyCtrlT) 107 | KeyCtrlU = Key(termbox.KeyCtrlU) 108 | KeyCtrlV = Key(termbox.KeyCtrlV) 109 | KeyCtrlW = Key(termbox.KeyCtrlW) 110 | KeyCtrlX = Key(termbox.KeyCtrlX) 111 | KeyCtrlY = Key(termbox.KeyCtrlY) 112 | KeyCtrlZ = Key(termbox.KeyCtrlZ) 113 | KeyEsc = Key(termbox.KeyEsc) 114 | KeyCtrlLsqBracket = Key(termbox.KeyCtrlLsqBracket) 115 | KeyCtrl3 = Key(termbox.KeyCtrl3) 116 | KeyCtrl4 = Key(termbox.KeyCtrl4) 117 | KeyCtrlBackslash = Key(termbox.KeyCtrlBackslash) 118 | KeyCtrl5 = Key(termbox.KeyCtrl5) 119 | KeyCtrlRsqBracket = Key(termbox.KeyCtrlRsqBracket) 120 | KeyCtrl6 = Key(termbox.KeyCtrl6) 121 | KeyCtrl7 = Key(termbox.KeyCtrl7) 122 | KeyCtrlSlash = Key(termbox.KeyCtrlSlash) 123 | KeyCtrlUnderscore = Key(termbox.KeyCtrlUnderscore) 124 | KeySpace = Key(termbox.KeySpace) 125 | KeyBackspace2 = Key(termbox.KeyBackspace2) 126 | KeyCtrl8 = Key(termbox.KeyCtrl8) 127 | ) 128 | 129 | // Modifier allows to define special keys combinations. They can be used 130 | // in combination with Keys or Runes when a new keybinding is defined. 131 | type Modifier termbox.Modifier 132 | 133 | // Modifiers. 134 | const ( 135 | ModNone Modifier = Modifier(0) 136 | ModAlt = Modifier(termbox.ModAlt) 137 | ) 138 | -------------------------------------------------------------------------------- /view.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The gocui Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gocui 6 | 7 | import ( 8 | "bytes" 9 | "errors" 10 | "io" 11 | "strings" 12 | 13 | "github.com/nsf/termbox-go" 14 | ) 15 | 16 | // A View is a window. It maintains its own internal buffer and cursor 17 | // position. 18 | type View struct { 19 | name string 20 | x0, y0, x1, y1 int 21 | ox, oy int 22 | cx, cy int 23 | lines [][]cell 24 | readOffset int 25 | readCache string 26 | 27 | tainted bool // marks if the viewBuffer must be updated 28 | viewLines []viewLine // internal representation of the view's buffer 29 | 30 | ei *escapeInterpreter // used to decode ESC sequences on Write 31 | 32 | // BgColor and FgColor allow to configure the background and foreground 33 | // colors of the View. 34 | BgColor, FgColor Attribute 35 | 36 | // SelBgColor and SelFgColor are used to configure the background and 37 | // foreground colors of the selected line, when it is highlighted. 38 | SelBgColor, SelFgColor Attribute 39 | 40 | // If Editable is true, keystrokes will be added to the view's internal 41 | // buffer at the cursor position. 42 | Editable bool 43 | 44 | // Editor allows to define the editor that manages the edition mode, 45 | // including keybindings or cursor behaviour. DefaultEditor is used by 46 | // default. 47 | Editor Editor 48 | 49 | // Overwrite enables or disables the overwrite mode of the view. 50 | Overwrite bool 51 | 52 | // If Highlight is true, Sel{Bg,Fg}Colors will be used 53 | // for the line under the cursor position. 54 | Highlight bool 55 | 56 | // If Frame is true, a border will be drawn around the view. 57 | Frame bool 58 | 59 | // If Wrap is true, the content that is written to this View is 60 | // automatically wrapped when it is longer than its width. If true the 61 | // view's x-origin will be ignored. 62 | Wrap bool 63 | 64 | // If Autoscroll is true, the View will automatically scroll down when the 65 | // text overflows. If true the view's y-origin will be ignored. 66 | Autoscroll bool 67 | 68 | // If Frame is true, Title allows to configure a title for the view. 69 | Title string 70 | 71 | // If Mask is true, the View will display the mask instead of the real 72 | // content 73 | Mask rune 74 | } 75 | 76 | type viewLine struct { 77 | linesX, linesY int // coordinates relative to v.lines 78 | line []cell 79 | } 80 | 81 | type cell struct { 82 | chr rune 83 | bgColor, fgColor Attribute 84 | } 85 | 86 | type lineType []cell 87 | 88 | // String returns a string from a given cell slice. 89 | func (l lineType) String() string { 90 | str := "" 91 | for _, c := range l { 92 | str += string(c.chr) 93 | } 94 | return str 95 | } 96 | 97 | // newView returns a new View object. 98 | func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View { 99 | v := &View{ 100 | name: name, 101 | x0: x0, 102 | y0: y0, 103 | x1: x1, 104 | y1: y1, 105 | Frame: true, 106 | Editor: DefaultEditor, 107 | tainted: true, 108 | ei: newEscapeInterpreter(mode), 109 | } 110 | return v 111 | } 112 | 113 | // Size returns the number of visible columns and rows in the View. 114 | func (v *View) Size() (x, y int) { 115 | return v.x1 - v.x0 - 1, v.y1 - v.y0 - 1 116 | } 117 | 118 | // Name returns the name of the view. 119 | func (v *View) Name() string { 120 | return v.name 121 | } 122 | 123 | // setRune sets a rune at the given point relative to the view. It applies the 124 | // specified colors, taking into account if the cell must be highlighted. Also, 125 | // it checks if the position is valid. 126 | func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error { 127 | maxX, maxY := v.Size() 128 | if x < 0 || x >= maxX || y < 0 || y >= maxY { 129 | return errors.New("invalid point") 130 | } 131 | 132 | var ( 133 | ry, rcy int 134 | err error 135 | ) 136 | if v.Highlight { 137 | _, ry, err = v.realPosition(x, y) 138 | if err != nil { 139 | return err 140 | } 141 | _, rcy, err = v.realPosition(v.cx, v.cy) 142 | if err != nil { 143 | return err 144 | } 145 | } 146 | 147 | if v.Mask != 0 { 148 | fgColor = v.FgColor 149 | bgColor = v.BgColor 150 | ch = v.Mask 151 | } else if v.Highlight && ry == rcy { 152 | fgColor = v.SelFgColor 153 | bgColor = v.SelBgColor 154 | } 155 | 156 | termbox.SetCell(v.x0+x+1, v.y0+y+1, ch, 157 | termbox.Attribute(fgColor), termbox.Attribute(bgColor)) 158 | 159 | return nil 160 | } 161 | 162 | // SetCursor sets the cursor position of the view at the given point, 163 | // relative to the view. It checks if the position is valid. 164 | func (v *View) SetCursor(x, y int) error { 165 | maxX, maxY := v.Size() 166 | if x < 0 || x >= maxX || y < 0 || y >= maxY { 167 | return errors.New("invalid point") 168 | } 169 | v.cx = x 170 | v.cy = y 171 | return nil 172 | } 173 | 174 | // Cursor returns the cursor position of the view. 175 | func (v *View) Cursor() (x, y int) { 176 | return v.cx, v.cy 177 | } 178 | 179 | // SetOrigin sets the origin position of the view's internal buffer, 180 | // so the buffer starts to be printed from this point, which means that 181 | // it is linked with the origin point of view. It can be used to 182 | // implement Horizontal and Vertical scrolling with just incrementing 183 | // or decrementing ox and oy. 184 | func (v *View) SetOrigin(x, y int) error { 185 | if x < 0 || y < 0 { 186 | return errors.New("invalid point") 187 | } 188 | v.ox = x 189 | v.oy = y 190 | return nil 191 | } 192 | 193 | // Origin returns the origin position of the view. 194 | func (v *View) Origin() (x, y int) { 195 | return v.ox, v.oy 196 | } 197 | 198 | // Write appends a byte slice into the view's internal buffer. Because 199 | // View implements the io.Writer interface, it can be passed as parameter 200 | // of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must 201 | // be called to clear the view's buffer. 202 | func (v *View) Write(p []byte) (n int, err error) { 203 | v.tainted = true 204 | 205 | for _, ch := range bytes.Runes(p) { 206 | switch ch { 207 | case '\n': 208 | v.lines = append(v.lines, nil) 209 | case '\r': 210 | nl := len(v.lines) 211 | if nl > 0 { 212 | v.lines[nl-1] = nil 213 | } else { 214 | v.lines = make([][]cell, 1) 215 | } 216 | default: 217 | cells := v.parseInput(ch) 218 | if cells == nil { 219 | continue 220 | } 221 | 222 | nl := len(v.lines) 223 | if nl > 0 { 224 | v.lines[nl-1] = append(v.lines[nl-1], cells...) 225 | } else { 226 | v.lines = append(v.lines, cells) 227 | } 228 | } 229 | } 230 | return len(p), nil 231 | } 232 | 233 | // parseInput parses char by char the input written to the View. It returns nil 234 | // while processing ESC sequences. Otherwise, it returns a cell slice that 235 | // contains the processed data. 236 | func (v *View) parseInput(ch rune) []cell { 237 | cells := []cell{} 238 | 239 | isEscape, err := v.ei.parseOne(ch) 240 | if err != nil { 241 | for _, r := range v.ei.runes() { 242 | c := cell{ 243 | fgColor: v.FgColor, 244 | bgColor: v.BgColor, 245 | chr: r, 246 | } 247 | cells = append(cells, c) 248 | } 249 | v.ei.reset() 250 | } else { 251 | if isEscape { 252 | return nil 253 | } 254 | c := cell{ 255 | fgColor: v.ei.curFgColor, 256 | bgColor: v.ei.curBgColor, 257 | chr: ch, 258 | } 259 | cells = append(cells, c) 260 | } 261 | 262 | return cells 263 | } 264 | 265 | // Read reads data into p. It returns the number of bytes read into p. 266 | // At EOF, err will be io.EOF. Calling Read() after Rewind() makes the 267 | // cache to be refreshed with the contents of the view. 268 | func (v *View) Read(p []byte) (n int, err error) { 269 | if v.readOffset == 0 { 270 | v.readCache = v.Buffer() 271 | } 272 | if v.readOffset < len(v.readCache) { 273 | n = copy(p, v.readCache[v.readOffset:]) 274 | v.readOffset += n 275 | } else { 276 | err = io.EOF 277 | } 278 | return 279 | } 280 | 281 | // Rewind sets the offset for the next Read to 0, which also refresh the 282 | // read cache. 283 | func (v *View) Rewind() { 284 | v.readOffset = 0 285 | } 286 | 287 | // draw re-draws the view's contents. 288 | func (v *View) draw() error { 289 | maxX, maxY := v.Size() 290 | 291 | if v.Wrap { 292 | if maxX == 0 { 293 | return errors.New("X size of the view cannot be 0") 294 | } 295 | v.ox = 0 296 | } 297 | if v.tainted { 298 | v.viewLines = nil 299 | for i, line := range v.lines { 300 | if v.Wrap { 301 | if len(line) < maxX { 302 | vline := viewLine{linesX: 0, linesY: i, line: line} 303 | v.viewLines = append(v.viewLines, vline) 304 | continue 305 | } else { 306 | for n := 0; n <= len(line); n += maxX { 307 | if len(line[n:]) <= maxX { 308 | vline := viewLine{linesX: n, linesY: i, line: line[n:]} 309 | v.viewLines = append(v.viewLines, vline) 310 | } else { 311 | vline := viewLine{linesX: n, linesY: i, line: line[n : n+maxX]} 312 | v.viewLines = append(v.viewLines, vline) 313 | } 314 | } 315 | } 316 | } else { 317 | vline := viewLine{linesX: 0, linesY: i, line: line} 318 | v.viewLines = append(v.viewLines, vline) 319 | } 320 | } 321 | v.tainted = false 322 | } 323 | 324 | if v.Autoscroll && len(v.viewLines) > maxY { 325 | v.oy = len(v.viewLines) - maxY 326 | } 327 | y := 0 328 | for i, vline := range v.viewLines { 329 | if i < v.oy { 330 | continue 331 | } 332 | if y >= maxY { 333 | break 334 | } 335 | x := 0 336 | for j, c := range vline.line { 337 | if j < v.ox { 338 | continue 339 | } 340 | if x >= maxX { 341 | break 342 | } 343 | 344 | fgColor := c.fgColor 345 | if fgColor == ColorDefault { 346 | fgColor = v.FgColor 347 | } 348 | bgColor := c.bgColor 349 | if bgColor == ColorDefault { 350 | bgColor = v.BgColor 351 | } 352 | 353 | if err := v.setRune(x, y, c.chr, fgColor, bgColor); err != nil { 354 | return err 355 | } 356 | x++ 357 | } 358 | y++ 359 | } 360 | return nil 361 | } 362 | 363 | // realPosition returns the position in the internal buffer corresponding to the 364 | // point (x, y) of the view. 365 | func (v *View) realPosition(vx, vy int) (x, y int, err error) { 366 | vx = v.ox + vx 367 | vy = v.oy + vy 368 | 369 | if vx < 0 || vy < 0 { 370 | return 0, 0, errors.New("invalid point") 371 | } 372 | 373 | if len(v.viewLines) == 0 { 374 | return vx, vy, nil 375 | } 376 | 377 | if vy < len(v.viewLines) { 378 | vline := v.viewLines[vy] 379 | x = vline.linesX + vx 380 | y = vline.linesY 381 | } else { 382 | vline := v.viewLines[len(v.viewLines)-1] 383 | x = vx 384 | y = vline.linesY + vy - len(v.viewLines) + 1 385 | } 386 | 387 | return x, y, nil 388 | } 389 | 390 | // Clear empties the view's internal buffer. 391 | func (v *View) Clear() { 392 | v.tainted = true 393 | 394 | v.lines = nil 395 | v.viewLines = nil 396 | v.readOffset = 0 397 | v.clearRunes() 398 | } 399 | 400 | // clearRunes erases all the cells in the view. 401 | func (v *View) clearRunes() { 402 | maxX, maxY := v.Size() 403 | for x := 0; x < maxX; x++ { 404 | for y := 0; y < maxY; y++ { 405 | termbox.SetCell(v.x0+x+1, v.y0+y+1, ' ', 406 | termbox.Attribute(v.FgColor), termbox.Attribute(v.BgColor)) 407 | } 408 | } 409 | } 410 | 411 | // BufferLines returns the lines in the view's internal 412 | // buffer. 413 | func (v *View) BufferLines() []string { 414 | lines := make([]string, len(v.lines)) 415 | for i, l := range v.lines { 416 | str := lineType(l).String() 417 | str = strings.Replace(str, "\x00", " ", -1) 418 | lines[i] = str 419 | } 420 | return lines 421 | } 422 | 423 | // Buffer returns a string with the contents of the view's internal 424 | // buffer. 425 | func (v *View) Buffer() string { 426 | str := "" 427 | for _, l := range v.lines { 428 | str += lineType(l).String() + "\n" 429 | } 430 | return strings.Replace(str, "\x00", " ", -1) 431 | } 432 | 433 | // ViewBufferLines returns the lines in the view's internal 434 | // buffer that is shown to the user. 435 | func (v *View) ViewBufferLines() []string { 436 | lines := make([]string, len(v.viewLines)) 437 | for i, l := range v.viewLines { 438 | str := lineType(l.line).String() 439 | str = strings.Replace(str, "\x00", " ", -1) 440 | lines[i] = str 441 | } 442 | return lines 443 | } 444 | 445 | // ViewBuffer returns a string with the contents of the view's buffer that is 446 | // shown to the user. 447 | func (v *View) ViewBuffer() string { 448 | str := "" 449 | for _, l := range v.viewLines { 450 | str += lineType(l.line).String() + "\n" 451 | } 452 | return strings.Replace(str, "\x00", " ", -1) 453 | } 454 | 455 | // Line returns a string with the line of the view's internal buffer 456 | // at the position corresponding to the point (x, y). 457 | func (v *View) Line(y int) (string, error) { 458 | _, y, err := v.realPosition(0, y) 459 | if err != nil { 460 | return "", err 461 | } 462 | 463 | if y < 0 || y >= len(v.lines) { 464 | return "", errors.New("invalid point") 465 | } 466 | 467 | return lineType(v.lines[y]).String(), nil 468 | } 469 | 470 | // Word returns a string with the word of the view's internal buffer 471 | // at the position corresponding to the point (x, y). 472 | func (v *View) Word(x, y int) (string, error) { 473 | x, y, err := v.realPosition(x, y) 474 | if err != nil { 475 | return "", err 476 | } 477 | 478 | if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) { 479 | return "", errors.New("invalid point") 480 | } 481 | 482 | str := lineType(v.lines[y]).String() 483 | 484 | nl := strings.LastIndexFunc(str[:x], indexFunc) 485 | if nl == -1 { 486 | nl = 0 487 | } else { 488 | nl = nl + 1 489 | } 490 | nr := strings.IndexFunc(str[x:], indexFunc) 491 | if nr == -1 { 492 | nr = len(str) 493 | } else { 494 | nr = nr + x 495 | } 496 | return string(str[nl:nr]), nil 497 | } 498 | 499 | // indexFunc allows to split lines by words taking into account spaces 500 | // and 0. 501 | func indexFunc(r rune) bool { 502 | return r == ' ' || r == 0 503 | } 504 | --------------------------------------------------------------------------------