gocui
GitHub概要
jroimartin/gocui
Minimalist Go package aimed at creating Console User Interfaces.
スター10,308
ウォッチ129
フォーク625
作成日:2014年1月4日
言語:Go
ライセンス:BSD 3-Clause "New" or "Revised" License
トピックス
cuigogocuigui
スター履歴
データ取得日時: 2025/7/25 11:09
gocui
gocuiは、Go言語向けのミニマリストなTUIライブラリです。ビューベースのアーキテクチャを採用し、ウィンドウ管理とキーバインディングに焦点を当てています。シンプルで軽量な設計により、素早くTUIアプリケーションを構築できます。
特徴
ビューベースアーキテクチャ
- ビュー管理: オーバーラップ可能なウィンドウ
- フォーカス管理: アクティブビューの切り替え
- レイアウト: カスタムレイアウトマネージャー
- 座標系: 絶対座標と相対座標のサポート
シンプルなAPI
- ミニマリスト設計: 必要最小限の機能
- イベントハンドリング: キーバインディングシステム
- エディタモード: 組み込みテキスト編集機能
- カーソル管理: ビュー内でのカーソル制御
パフォーマンス
- 軽量: 最小限の依存関係
- 効率的: 高速なレンダリング
- 低メモリ: 小さなメモリフットプリント
- スレッドセーフ: 並行処理対応
拡張性
- カスタムハンドラー: イベントハンドラーの追加
- レイアウトカスタマイズ: 独自レイアウトの実装
- ビューカスタマイズ: ビューの外観変更
- 組み込みエディタ: テキスト編集機能の拡張
基本的な使用方法
インストール
go get github.com/jroimartin/gocui
Hello World
package main
import (
"fmt"
"log"
"github.com/jroimartin/gocui"
)
func main() {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
defer g.Close()
g.SetManagerFunc(layout)
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
log.Panicln(err)
}
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
}
func layout(g *gocui.Gui) error {
maxX, maxY := g.Size()
if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2); err != nil {
if err != gocui.ErrUnknownView {
return err
}
fmt.Fprintln(v, "Hello World!")
}
return nil
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}
複数ビューの管理
package main
import (
"fmt"
"log"
"github.com/jroimartin/gocui"
)
func main() {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
defer g.Close()
g.Highlight = true
g.Cursor = true
g.SelFgColor = gocui.ColorGreen
g.SetManagerFunc(layout)
// キーバインディング
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, nextView); err != nil {
log.Panicln(err)
}
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
}
func layout(g *gocui.Gui) error {
maxX, maxY := g.Size()
// サイドバー
if v, err := g.SetView("side", -1, -1, 30, maxY); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = "サイドバー"
v.Highlight = true
v.SelBgColor = gocui.ColorGreen
v.SelFgColor = gocui.ColorBlack
fmt.Fprintln(v, "アイテム1")
fmt.Fprintln(v, "アイテム2")
fmt.Fprintln(v, "アイテム3")
if _, err := g.SetCurrentView("side"); err != nil {
return err
}
}
// メインビュー
if v, err := g.SetView("main", 30, -1, maxX, maxY); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = "メイン"
v.Wrap = true
v.Autoscroll = true
fmt.Fprintln(v, "メインコンテンツエリア")
fmt.Fprintln(v, "ここに詳細情報が表示されます")
}
return nil
}
func nextView(g *gocui.Gui, v *gocui.View) error {
if v == nil || v.Name() == "side" {
_, err := g.SetCurrentView("main")
return err
}
_, err := g.SetCurrentView("side")
return err
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}
エディタ機能
package main
import (
"fmt"
"log"
"strings"
"github.com/jroimartin/gocui"
)
func main() {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
defer g.Close()
g.SetManagerFunc(layout)
// エディタのキーバインディング
if err := g.SetKeybinding("editor", gocui.KeyEnter, gocui.ModNone, getLine); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("editor", gocui.KeyArrowDown, gocui.ModNone, cursorDown); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("editor", gocui.KeyArrowUp, gocui.ModNone, cursorUp); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
log.Panicln(err)
}
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
}
func layout(g *gocui.Gui) error {
maxX, maxY := g.Size()
// エディタビュー
if v, err := g.SetView("editor", 0, 0, maxX-1, maxY-5); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = "エディタ"
v.Editable = true
v.Wrap = true
fmt.Fprintln(v, "テキストを入力してください...")
fmt.Fprintln(v, "Enterキーで現在の行を下に表示します")
if _, err := g.SetCurrentView("editor"); err != nil {
return err
}
}
// 出力ビュー
if v, err := g.SetView("output", 0, maxY-5, maxX-1, maxY-1); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = "出力"
v.Wrap = true
v.Autoscroll = true
}
return nil
}
func getLine(g *gocui.Gui, v *gocui.View) error {
var l string
var err error
_, cy := v.Cursor()
if l, err = v.Line(cy); err != nil {
l = ""
}
// 出力ビューに表示
outputView, err := g.View("output")
if err != nil {
return err
}
fmt.Fprintln(outputView, strings.TrimSpace(l))
return nil
}
func cursorDown(g *gocui.Gui, v *gocui.View) error {
if v != nil {
cx, cy := v.Cursor()
if err := v.SetCursor(cx, cy+1); err != nil {
ox, oy := v.Origin()
if err := v.SetOrigin(ox, oy+1); err != nil {
return err
}
}
}
return nil
}
func cursorUp(g *gocui.Gui, v *gocui.View) error {
if v != nil {
ox, oy := v.Origin()
cx, cy := v.Cursor()
if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
if err := v.SetOrigin(ox, oy-1); err != nil {
return err
}
}
}
return nil
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}
カスタムレイアウト
package main
import (
"fmt"
"log"
"github.com/jroimartin/gocui"
)
type ViewConfig struct {
name string
x0, y0 float32
x1, y1 float32
title string
}
func main() {
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
log.Panicln(err)
}
defer g.Close()
// ビュー設定
views := []ViewConfig{
{"header", 0, 0, 1, 0.1, "ヘッダー"},
{"sidebar", 0, 0.1, 0.3, 0.9, "サイドバー"},
{"main", 0.3, 0.1, 1, 0.7, "メイン"},
{"bottom", 0.3, 0.7, 1, 0.9, "ボトム"},
{"footer", 0, 0.9, 1, 1, "フッター"},
}
g.SetManagerFunc(func(g *gocui.Gui) error {
return dynamicLayout(g, views)
})
// キーバインディング
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, nextView); err != nil {
log.Panicln(err)
}
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
}
func dynamicLayout(g *gocui.Gui, views []ViewConfig) error {
maxX, maxY := g.Size()
for _, vc := range views {
x0 := int(float32(maxX) * vc.x0)
y0 := int(float32(maxY) * vc.y0)
x1 := int(float32(maxX)*vc.x1) - 1
y1 := int(float32(maxY)*vc.y1) - 1
if v, err := g.SetView(vc.name, x0, y0, x1, y1); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = vc.title
v.Wrap = true
// コンテンツ追加
switch vc.name {
case "header":
fmt.Fprintln(v, "アプリケーション v1.0")
case "sidebar":
fmt.Fprintln(v, "メニュー1")
fmt.Fprintln(v, "メニュー2")
fmt.Fprintln(v, "メニュー3")
case "main":
fmt.Fprintln(v, "メインコンテンツエリア")
case "footer":
fmt.Fprintln(v, "Ctrl+C: 終了 | Tab: 切替")
}
}
}
return nil
}
func nextView(g *gocui.Gui, v *gocui.View) error {
views := []string{"header", "sidebar", "main", "bottom", "footer"}
currentView := g.CurrentView()
if currentView == nil {
return g.SetCurrentView(views[0])
}
currentName := currentView.Name()
for i, name := range views {
if name == currentName {
nextIndex := (i + 1) % len(views)
return g.SetCurrentView(views[nextIndex])
}
}
return nil
}
func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}
高度な機能
マウスサポート
g.Mouse = true
// マウスクリックハンドラー
if err := g.SetKeybinding("viewname", gocui.MouseLeft, gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
_, err := g.SetCurrentView(v.Name())
return err
}); err != nil {
return err
}
オーバーレイビュー
func showModal(g *gocui.Gui, message string) error {
maxX, maxY := g.Size()
if v, err := g.SetView("modal", maxX/2-20, maxY/2-2, maxX/2+20, maxY/2+2); err != nil {
if err != gocui.ErrUnknownView {
return err
}
v.Title = "モーダル"
fmt.Fprintln(v, message)
// 最前面に表示
if _, err := g.SetViewOnTop("modal"); err != nil {
return err
}
if _, err := g.SetCurrentView("modal"); err != nil {
return err
}
}
return nil
}
非同期更新
func updateAsync(g *gocui.Gui) {
go func() {
for {
time.Sleep(1 * time.Second)
g.Update(func(g *gocui.Gui) error {
v, err := g.View("status")
if err != nil {
return err
}
v.Clear()
fmt.Fprintf(v, "時刻: %s", time.Now().Format("15:04:05"))
return nil
})
}
}()
}
エコシステム
派生プロジェクト
- awesome-gocui: リソースと例のコレクション
- gocui-component: 再利用可能なコンポーネント
採用例
- lazygit: Git TUIクライアント
- lazydocker: Docker管理ツール
- docui: Docker TUI
- sshportal: SSHゲートウェイ
利点
- シンプル: ミニマリストな設計
- 軽量: 小さな依存関係
- 柔軟: カスタムレイアウトが容易
- 安定: 成熟したコードベース
- 人気: 多くのプロジェクトで採用
制約事項
- 機能限定: 基本的な機能のみ
- ウィジェット不足: 組み込みウィジェットが少ない
- ドキュメント: 限定的なドキュメント
- メンテナンス: 更新頻度が低い
他のライブラリとの比較
項目 | gocui | Bubble Tea | tview |
---|---|---|---|
設計思想 | ミニマリスト | モダン | フル機能 |
ウィジェット | 基本的 | コンポーネント | 豊富 |
学習コスト | 低 | 中 | 低 |
カスタマイズ性 | 高 | 非常に高 | 中 |
活発さ | 低 | 非常に高 | 高 |
まとめ
gocuiは、Go言語でシンプルなTUIアプリケーションを構築するための優れたライブラリです。ミニマリストな設計により、学習が容易で、カスタムレイアウトの実装も簡単です。高度なウィジェットは提供されていませんが、その分柔軟性が高く、lazygitやlazydockerなど多くの人気ツールで採用されています。シンプルで軽量なTUIライブラリを求める開発者に最適な選択肢です。