Skip to main content

indigo_cli/
main.rs

1use clap::Parser as _;
2use indigo_core::{key::is, prelude::*, syntax::Language};
3use std::{io, process::ExitCode, sync::Arc};
4use tracing_subscriber::EnvFilter;
5
6#[derive(Debug, clap::Parser)]
7struct Args {
8    keys: Keys,
9
10    /// Enable debugging functionality
11    ///
12    /// - Enter `<c-l>` to print information and a diff since the last `<c-l>` key was encountered.
13    #[arg(long)]
14    debug: bool,
15
16    /// Parse the buffer as this language (e.g. `rust`).
17    #[arg(long)]
18    language: Option<Language>,
19
20    #[arg(long, env = "INDIGO_LOG", default_value_t)]
21    log_filter: Arc<str>,
22}
23
24fn main() -> anyhow::Result<ExitCode> {
25    #[cfg(debug_assertions)]
26    if std::env::var("RUST_BACKTRACE").is_err() {
27        // SAFETY: At this point the program is single-threaded. There are no other threads that
28        // could be reading from or writing to the environment.
29        //
30        // NOTE: Replace with `std::panic::set_backtrace_style` once it stabilizes.
31        // https://github.com/rust-lang/rust/issues/93346
32        #[expect(unsafe_code)]
33        unsafe {
34            std::env::set_var("RUST_BACKTRACE", "1");
35        }
36    }
37
38    let args = Args::parse();
39
40    tracing_subscriber::fmt()
41        .with_env_filter(EnvFilter::new(args.log_filter))
42        .with_writer(io::stderr)
43        .init();
44
45    let rope = Rope::from_reader(io::BufReader::new(io::stdin()))?;
46
47    let mut buffer = Buffer::from(rope);
48
49    if let Some(language) = args.language {
50        buffer.text.set_language(language);
51    }
52
53    let mut editor = Editor::from(buffer);
54
55    let mut debug_keys = vec![];
56    let mut debug_rope = editor.focused_buffer().text.rope().clone();
57
58    for key in args.keys.0 {
59        if args.debug {
60            debug_keys.push(key);
61        }
62
63        let event = Event::Key(KeyEvent {
64            key,
65            kind: KeyEventKind::Press,
66        });
67
68        let handled = editor.handle_event(event)?;
69
70        if args.debug && !handled && !is(&key, "<c-l>") {
71            eprintln!("unhandled key: {key}");
72        }
73
74        if args.debug && is(&key, "<c-l>") {
75            let left = debug_rope.to_string();
76            let right = editor.focused_buffer().text.rope().to_string();
77            eprint!("keys: ");
78            for key in &debug_keys {
79                if !is(key, "<c-l>") {
80                    eprint!("{key}");
81                }
82            }
83            let window = editor.focused_window();
84            let selection = window.selection();
85            eprintln!("\nranges:");
86            let primary_range = selection.state().primary_range;
87            selection.for_each(|index, range| {
88                let tail = range.tail().byte_index();
89                let head = range.head().byte_index();
90                let start = range.start().byte_index();
91                let end = range.end().byte_index();
92                let direction = if range.is_forward() {
93                    "forward"
94                } else {
95                    "backward"
96                };
97                let primary = if index == primary_range { " primary" } else { "" };
98                let preview = escaped_preview(&range.slice().to_string());
99                eprintln!(
100                    "  {index}: tail={tail} head={head} start={start} end={end} direction={direction}{primary} text=\"{preview}\""
101                );
102            });
103            eprintln!("text:\n```diff");
104            for diff in diff::lines(&left, &right) {
105                match diff {
106                    diff::Result::Left(l) => eprintln!("{RED}-{l}{RESET}"),
107                    diff::Result::Both(l, _) => eprintln!(" {l}"),
108                    diff::Result::Right(r) => eprintln!("{GREEN}+{r}{RESET}"),
109                }
110            }
111            eprintln!("```");
112            debug_keys.clear();
113            debug_rope = editor.focused_window().buffer().text.rope().clone();
114        }
115    }
116
117    let _result = editor
118        .focused_window()
119        .buffer()
120        .text
121        .rope()
122        .write_to(io::LineWriter::new(io::stdout()));
123
124    let exit_code = editor.exit_code().unwrap_or(ExitCode::SUCCESS);
125
126    Ok(exit_code)
127}
128
129const RED: &str = "\x1b[0;31m";
130const GREEN: &str = "\x1b[0;32m";
131const RESET: &str = "\x1b[0m";
132
133fn escaped_preview(text: &str) -> String {
134    const LIMIT: usize = 80;
135    let mut preview = String::new();
136    for (index, char) in text.chars().enumerate() {
137        if index == LIMIT {
138            preview.push_str("...");
139            break;
140        }
141        preview.extend(char.escape_default());
142    }
143    preview
144}