โ– humdrum codex / glint v1.1.2
license AGPL-3.0
2.7 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
package app

import (
	"os"
	"path/filepath"
	"strings"

	"glint/internal/dialog"

	tea "github.com/charmbracelet/bubbletea"
)

// namingKind is what an open naming dialog will do when confirmed.
type namingKind int

const (
	namingNew    namingKind = iota // create dialogDir/<name>.md and open it
	namingRename                   // rename the current file to <name>.md
)

// openNamePrompt shows the centered naming dialog. dir is the folder a new name
// resolves under; prefill seeds the input (the picker query, or the current
// basename for a rename). Cancelling returns to whatever mode was active.
func (a *App) openNamePrompt(kind namingKind, dir, prefill string) {
	title := "New note"
	if kind == namingRename {
		title = "Rename"
	}
	a.prevMode = a.mode
	a.dialogKind = kind
	a.dialogDir = dir
	a.dialog.SetWidth(a.contentWidth())
	a.dialog.Open(title, a.dirLabel(dir), prefill)
	a.mode = ModeNamePrompt
	a.status = ""
}

// renameFile is the F2 handler: rename the open file, or name a buffer that has
// no path yet. Only meaningful over the editor.
func (a *App) renameFile() (tea.Model, tea.Cmd) {
	if a.mode == ModePicker || a.mode == ModeNamePrompt {
		return a, nil
	}
	if a.path == "" {
		return a.nameCurrentBuffer()
	}
	base := filepath.Base(a.path)
	a.openNamePrompt(namingRename, filepath.Dir(a.path), strings.TrimSuffix(base, filepath.Ext(base)))
	return a, nil
}

// nameCurrentBuffer names a pathless buffer, then writes it โ€” the job the
// bottom-bar save-as prompt used to do.
func (a *App) nameCurrentBuffer() (tea.Model, tea.Cmd) {
	dir := a.saveDir
	if dir == "" {
		dir = a.cfg.InboxRoot()
	}
	a.openNamePrompt(namingRename, dir, "")
	return a, nil
}

// handleNameKey routes a key into the dialog and acts on what it reports.
func (a *App) handleNameKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
	res, cmd := a.dialog.Update(msg)
	switch res {
	case dialog.ResultConfirm:
		return a.confirmName()
	case dialog.ResultCancel:
		return a.cancelName()
	}
	return a, cmd
}

// cancelName closes the dialog and restores the mode it opened over, writing
// nothing.
func (a *App) cancelName() (tea.Model, tea.Cmd) {
	a.mode = a.prevMode
	a.status = ""
	return a, nil
}

// dirLabel renders dir for the dialog's hint: $HOME collapsed to ~, one
// trailing separator so it reads as a folder.
func (a *App) dirLabel(dir string) string {
	if home, err := os.UserHomeDir(); err == nil && home != "" && strings.HasPrefix(dir, home) {
		dir = "~" + strings.TrimPrefix(dir, home)
	}
	return strings.TrimSuffix(dir, string(filepath.Separator)) + string(filepath.Separator)
}

// confirmName applies the dialog's name. Implemented in the next task.
func (a *App) confirmName() (tea.Model, tea.Cmd) {
	return a.cancelName()
}