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 #[arg(long)]
14 debug: bool,
15
16 #[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 #[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}