▍ humdrum codex / glint v1.0.2
license AGPL-3.0
5.2 KB raw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// Command glint is a modeless terminal markdown editor with live styling.
package main

import (
	"flag"
	"fmt"
	"os"

	"glint/internal/app"
	"glint/internal/config"
	"glint/internal/configui"
	"glint/internal/keyprobe"

	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
	"github.com/muesli/termenv"
)

func init() {
	// Render glint's exact theme hexes regardless of the terminal's detected
	// profile or OS appearance — never downsample or adapt to the system.
	lipgloss.SetColorProfile(termenv.TrueColor)
}

// version is the build version, overridden at release time via
// -ldflags "-X main.version=<v>" (the Homebrew formula sets it).
var version = "dev"

const helpText = `glint — a modeless terminal markdown editor

USAGE
  glint [file]          open a file, or the fuzzy picker when no file is given
  glint <command>

COMMANDS  (each takes a short letter or the full word, one or two dashes:
           -n / --n / -new / --new all work)
  -n, --new [name]      new note in the current directory
                        combine with -i or -v to target the inbox or vault
  -t, --today           open today's daily note (in the vault)
  -d, --daily           browse the daily-notes folder
  -v, --vault           fuzzy picker over your vault, from anywhere
  -i, --inbox           fuzzy picker over your inbox
  -c, --config          interactive setup walkthrough (writes the config file)
  -h, --help            show this help
      --version         print the version

EDITOR KEYS
  Ctrl+S                save (an unnamed buffer prompts for a name)
  Ctrl+P                toggle the read preview
  Ctrl+F                fuzzy file picker
  Ctrl+G                find in document (Enter/down next, Shift+Tab/up prev)
  Ctrl+D                today's daily note
  Ctrl+N                new note in the current directory
  Ctrl+B                new note in the inbox
  Ctrl+T                cycle theme (flexoki-light / flexoki-dark / charm)
  Ctrl+C / Ctrl+X / Ctrl+V   copy / cut / paste (system clipboard)
  Shift+arrows          select text (Ctrl+Shift+left/right by word)
  Alt+left / Alt+right  move by word
  Ctrl+U / Ctrl+K       delete to start / end of line
  Ctrl+W                delete the word before the cursor
  Ctrl+Z / Ctrl+Y       undo / redo
  Ctrl+Q                quit (press twice if there are unsaved changes)
  Esc                   back to the editor

CONFIG
  ~/.config/glint/config.toml   (run 'glint -c' to set it up)
`

func main() {
	// Help and version short-circuit before flag parsing (-v means --vault, so
	// version is long-only).
	if len(os.Args) > 1 {
		switch os.Args[1] {
		case "-h", "--h", "-help", "--help":
			fmt.Print(helpText)
			return
		case "--version", "-version":
			fmt.Println("glint", version)
			return
		}
	}

	cfg, err := config.Load()
	if err != nil {
		fmt.Fprintln(os.Stderr, "glint: config:", err)
	}

	// Every command is a flag; both -x and --x (letter or word) work.
	flagNew := boolFlag("n", "new")
	flagToday := boolFlag("t", "today")
	flagDaily := boolFlag("d", "daily")
	flagVault := boolFlag("v", "vault")
	flagConfig := boolFlag("c", "config")
	flagInbox := boolFlag("i", "inbox")
	flagKeys := flag.Bool("keys", false, "show what the terminal sends for each key")
	flag.Parse()

	isNew := *flagNew[0] || *flagNew[1]
	isToday := *flagToday[0] || *flagToday[1]
	isDaily := *flagDaily[0] || *flagDaily[1]
	isVault := *flagVault[0] || *flagVault[1]
	isConfig := *flagConfig[0] || *flagConfig[1]
	isInbox := *flagInbox[0] || *flagInbox[1]

	// Standalone commands (no editor TUI).
	if isConfig {
		runOrDie(configui.Run())
		return
	}
	if *flagKeys {
		runOrDie(keyprobe.Run())
		return
	}

	name := ""
	if args := flag.Args(); len(args) > 0 {
		name = args[0]
	}

	a := app.New(cfg)
	var startErr error
	switch {
	case isNew:
		// New note in the current dir, or the inbox/vault when combined.
		dir := cfg.WorkingDir()
		if isInbox {
			dir = cfg.InboxRoot()
		}
		if isVault {
			dir = cfg.Vault()
		}
		startErr = a.StartNewIn(dir, name)
	case isToday:
		startErr = a.Start("", true) // today's daily note
	case isVault:
		startErr = a.StartPickerIn(cfg.Vault())
	case isInbox:
		startErr = a.StartPickerIn(cfg.InboxRoot())
	case isDaily:
		startErr = a.StartPickerIn(cfg.DailyDir()) // browse the daily folder
	case name != "":
		startErr = a.Start(name, false) // open a file
	default:
		startErr = a.Start("", false) // bare → fuzzy picker over the current dir
	}
	if startErr != nil {
		fmt.Fprintln(os.Stderr, "glint:", startErr)
		os.Exit(1)
	}

	run(a)
}

// boolFlag registers a short and long name for the same command and returns both
// pointers; either being set means the command was given (e.g. -n / --n / -new /
// --new). Go's flag package accepts both single- and double-dash for each name.
func boolFlag(short, long string) [2]*bool {
	return [2]*bool{
		flag.Bool(short, false, "command: -"+short+" / --"+long),
		flag.Bool(long, false, ""),
	}
}

func runOrDie(err error) {
	if err != nil {
		fmt.Fprintln(os.Stderr, "glint:", err)
		os.Exit(1)
	}
}

// run drives the Bubbletea program in the alternate screen.
func run(a *app.App) {
	if _, err := tea.NewProgram(a, tea.WithAltScreen(), tea.WithMouseCellMotion()).Run(); err != nil {
		fmt.Fprintln(os.Stderr, "glint:", err)
		os.Exit(1)
	}
}