From 8a4a5853745c6f63ce56e827ad107987c7f33a31 Mon Sep 17 00:00:00 2001 From: jess Date: Tue, 7 Apr 2026 21:02:24 -0700 Subject: [PATCH] add auto-indent on enter and content-based lang detection for untitled docs --- viewport/src/editor.rs | 81 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/viewport/src/editor.rs b/viewport/src/editor.rs index 7289c3d..1c2bf6a 100644 --- a/viewport/src/editor.rs +++ b/viewport/src/editor.rs @@ -144,8 +144,66 @@ impl EditorState { match message { Message::EditorAction(action) => { let is_edit = action.is_edit(); + + let auto_indent = if let text_editor::Action::Edit(text_editor::Edit::Enter) = &action { + let cursor = self.content.cursor(); + let line_text = self.content.line(cursor.position.line) + .map(|l| l.text.to_string()) + .unwrap_or_default(); + let base = leading_whitespace(&line_text).to_string(); + let trimmed = line_text.trim_end(); + let extra = matches!(trimmed.as_bytes().last(), Some(b'{' | b'[' | b'(')); + if extra { + Some(format!("{base} ")) + } else { + Some(base) + } + } else { + None + }; + + let dedent = if let text_editor::Action::Edit(text_editor::Edit::Insert(ch)) = &action { + matches!(ch, '}' | ']' | ')').then(|| { + let cursor = self.content.cursor(); + let line_text = self.content.line(cursor.position.line) + .map(|l| l.text.to_string()) + .unwrap_or_default(); + let prefix = &line_text[..cursor.position.column]; + if prefix.chars().all(|c| c == ' ' || c == '\t') && prefix.len() >= 4 { + Some(prefix.len()) + } else { + None + } + }).flatten() + } else { + None + }; + self.content.perform(action); + + if let Some(indent) = auto_indent { + if !indent.is_empty() { + self.content.perform(text_editor::Action::Edit( + text_editor::Edit::Paste(Arc::new(indent)), + )); + } + } + + if let Some(col) = dedent { + let remove = col.min(4); + self.content.perform(text_editor::Action::Move(Motion::Left)); + for _ in 0..remove { + self.content.perform(text_editor::Action::Edit( + text_editor::Edit::Backspace, + )); + } + self.content.perform(text_editor::Action::Move(Motion::Right)); + } + if is_edit { + if self.lang.is_none() { + self.lang = detect_lang_from_content(&self.content.text()); + } self.reparse(); self.run_eval(); } @@ -426,7 +484,30 @@ fn lang_from_extension(ext: &str) -> Option { "zig" => "zig", "sql" => "sql", "mk" => "make", + "cord" | "cordial" => "rust", _ => return None, }; Some(lang.to_string()) } + +fn detect_lang_from_content(text: &str) -> Option { + let keywords = ["fn ", "let ", "if ", "else ", "while ", "for ", "/="]; + let mut hits = 0; + for line in text.lines().take(50) { + let trimmed = line.trim(); + for kw in &keywords { + if trimmed.starts_with(kw) || trimmed.contains(&format!(" {kw}")) { + hits += 1; + } + } + if hits >= 2 { + return Some("rust".into()); + } + } + None +} + +fn leading_whitespace(line: &str) -> &str { + let end = line.len() - line.trim_start().len(); + &line[..end] +}