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/.md and open it namingRename // rename the current file to .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" } // Re-entering the dialog (e.g. a second Ctrl+N while it's already open) // must not make the dialog its own return mode — that would strand Esc // and confirm in ModeNamePrompt forever. if a.mode != ModeNamePrompt { 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 != "" && isHomeOrChild(dir, home) { dir = "~" + strings.TrimPrefix(dir, home) } return strings.TrimSuffix(dir, string(filepath.Separator)) + string(filepath.Separator) } // isHomeOrChild reports whether dir is home itself or a path under it, // requiring the match to end at a path separator rather than merely sharing a // string prefix (so /Users/foobar does not collapse under home=/Users/foo). func isHomeOrChild(dir, home string) bool { if dir == home { return true } return strings.HasPrefix(dir, home+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() }