7854a961a9
links, a sprint timer, placeholder scanning, corkboard, and a quick-capture inbox - Compile templates/snowflake into the binary via include_dir, so New project… never depends on files installed beside the executable; Settings ▸ Template becomes an optional external override, and a broken saved override now falls back to the bundled template instead of failing project generation. - Outline beats now link to a matching chapter for a one-click jump. - Sprint window: a daily whole-manuscript word goal plus a Pomodoro-style writing sprint timer, with a compact countdown in the status bar while in Distraction-free mode. - Placeholders window: find [TODO]/[TK]/[FIXME]/[XXX]/[NOTE]/[???] markers left in the draft and jump straight to any of them. - Snowflake progress strip in the file panel: a nudge-only readout of which snowflake stage folders actually have content. - Corkboard window: drag-reorderable synopsis cards, one per chapter. - Quick-capture popup (Ctrl+Shift+N): jot a stray idea straight into the project's Scratch Pad without losing your place in the draft. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
85 lines
3.3 KiB
Rust
85 lines
3.3 KiB
Rust
//! The Placeholders window: every `[TODO]`/`[TK]`/`[???]`-style marker left
|
|
//! in the manuscript, so a fast first-draft pass doesn't lose track of the
|
|
//! gaps it left behind.
|
|
|
|
use super::*;
|
|
use crate::placeholder_scan::find_placeholders;
|
|
|
|
impl App {
|
|
/// The Placeholders window: one row per marker found, grouped by file in
|
|
/// manuscript order. Clicking a row jumps straight to it.
|
|
pub(super) fn placeholders_window(&mut self, ctx: &egui::Context) {
|
|
let mut open = self.show_placeholders;
|
|
|
|
// Collect up front (reads the in-memory buffer for the open file,
|
|
// disk for the rest), so the window closure only reads.
|
|
let selected_name = self.selected.and_then(|i| self.files.get(i)).cloned();
|
|
let rows: Vec<(String, Vec<crate::placeholder_scan::Placeholder>)> = self
|
|
.manuscript_files()
|
|
.into_iter()
|
|
.filter_map(|(_, name)| {
|
|
let raw = if Some(name) == selected_name.as_ref() {
|
|
self.document().into_owned()
|
|
} else {
|
|
std::fs::read_to_string(self.path_for(name)).unwrap_or_default()
|
|
};
|
|
let found = find_placeholders(&raw);
|
|
if found.is_empty() {
|
|
None
|
|
} else {
|
|
Some((name.clone(), found))
|
|
}
|
|
})
|
|
.collect();
|
|
let total: usize = rows.iter().map(|(_, m)| m.len()).sum();
|
|
|
|
let mut jump_to: Option<(String, usize, usize)> = None;
|
|
|
|
egui::Window::new("Placeholders")
|
|
.open(&mut open)
|
|
.resizable(true)
|
|
.collapsible(false)
|
|
.default_width(480.0)
|
|
.default_height(420.0)
|
|
.show(ctx, |ui| {
|
|
ui.label(
|
|
egui::RichText::new(
|
|
"Bracketed markers like [TODO], [TK], [FIXME] or [???] left in \
|
|
the draft. Click one to jump to it.",
|
|
)
|
|
.small()
|
|
.weak(),
|
|
);
|
|
ui.separator();
|
|
egui::ScrollArea::vertical()
|
|
.auto_shrink([false, false])
|
|
.show(ui, |ui| {
|
|
if total == 0 {
|
|
ui.label("No placeholders found — 🎉");
|
|
return;
|
|
}
|
|
for (name, found) in &rows {
|
|
ui.label(egui::RichText::new(name).strong());
|
|
for marker in found {
|
|
if ui
|
|
.link(format!(" line {} · [{}]", marker.line, marker.marker))
|
|
.clicked()
|
|
{
|
|
jump_to = Some((name.clone(), marker.start, marker.end));
|
|
}
|
|
}
|
|
ui.add_space(4.0);
|
|
}
|
|
});
|
|
});
|
|
|
|
if let Some((name, start, end)) = jump_to {
|
|
if let Some(idx) = self.files.iter().position(|f| *f == name) {
|
|
self.select(idx);
|
|
self.issue_jump = Some((start, end));
|
|
}
|
|
}
|
|
self.show_placeholders = open;
|
|
}
|
|
}
|