Compare commits

...

8 Commits

Author SHA1 Message Date
cor 2649df1be7
Merge ef56d34f60 into 395a71bf53 2025-07-24 11:08:48 +02:00
kiara 395a71bf53
languages: nix formatter (#14046) 2025-07-23 12:51:17 -04:00
Ian Hobson 1e4bf6704a
Update Koto grammar and queries, add formatter (#14049) 2025-07-23 12:47:47 -04:00
cor ef56d34f60
chore: fmt 2025-06-25 16:35:23 +02:00
cor 0fa6b1408b
fix: clippy fixes 2025-06-23 19:31:58 +02:00
cor 7d17fd7ba3
fix: show primary cursor when command prompt is active
When the command prompt (:) is active, the terminal cursor moves to the
prompt line, leaving the primary cursor position in the editor invisible.
This change detects when a prompt component is active and forces the
primary cursor to be drawn manually in the editor while the prompt
is shown.

- Add prompt_active field to EditorView to track prompt state
- Update compositor to detect and communicate prompt state to EditorView
- Modify cursor highlighting logic to force primary cursor drawing when prompt is active
- Maintain real terminal cursor for prompt while showing fake cursor in editor
2025-06-23 16:52:27 +02:00
cor 5af123a4d9
fix: address clippy warning about match single binding 2025-06-23 15:17:22 +02:00
cor 395371db51
fix: use real terminal cursor for block mode
Previously, block cursors were always drawn manually as part of the
selection/highlight rendering, which prevented terminal cursor effects
(like cursor trails) from working. This change makes block cursors
behave like bar/underline cursors by using the terminal's real cursor
for the primary selection.

- Skip rendering primary cursor in block mode when terminal is focused
- Let terminal handle primary cursor rendering for all cursor types
- Simplify cursor method to return actual cursor kind when focused

This allows terminal-based cursor effects to work with block cursors
while maintaining proper cursor visibility when the terminal is unfocused.
2025-06-23 15:07:26 +02:00
7 changed files with 46 additions and 39 deletions

View File

@ -177,7 +177,14 @@ impl Compositor {
} }
pub fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { pub fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
// Check if there are prompt layers active and update EditorView
let has_prompt = self.has_component("helix_term::ui::prompt::Prompt");
for layer in &mut self.layers { for layer in &mut self.layers {
// Update prompt state for EditorView
if let Some(editor_view) = layer.as_any_mut().downcast_mut::<crate::ui::EditorView>() {
editor_view.prompt_active = has_prompt;
}
layer.render(area, surface, cx); layer.render(area, surface, cx);
} }
} }

View File

@ -44,6 +44,8 @@ pub struct EditorView {
spinners: ProgressSpinners, spinners: ProgressSpinners,
/// Tracks if the terminal window is focused by reaction to terminal focus events /// Tracks if the terminal window is focused by reaction to terminal focus events
terminal_focused: bool, terminal_focused: bool,
/// Tracks if there are prompt layers active (updated by compositor)
pub prompt_active: bool,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -67,6 +69,7 @@ impl EditorView {
completion: None, completion: None,
spinners: ProgressSpinners::default(), spinners: ProgressSpinners::default(),
terminal_focused: true, terminal_focused: true,
prompt_active: false,
} }
} }
@ -133,7 +136,7 @@ impl EditorView {
if let Some(tabstops) = Self::tabstop_highlights(doc, theme) { if let Some(tabstops) = Self::tabstop_highlights(doc, theme) {
overlays.push(tabstops); overlays.push(tabstops);
} }
overlays.push(Self::doc_selection_highlights( overlays.push(self.doc_selection_highlights(
editor.mode(), editor.mode(),
doc, doc,
view, view,
@ -427,7 +430,8 @@ impl EditorView {
} }
/// Get highlight spans for selections in a document view. /// Get highlight spans for selections in a document view.
pub fn doc_selection_highlights( fn doc_selection_highlights(
&self,
mode: Mode, mode: Mode,
doc: &Document, doc: &Document,
view: &View, view: &View,
@ -481,12 +485,9 @@ impl EditorView {
// Special-case: cursor at end of the rope. // Special-case: cursor at end of the rope.
if range.head == range.anchor && range.head == text.len_chars() { if range.head == range.anchor && range.head == text.len_chars() {
if !selection_is_primary || (cursor_is_block && is_terminal_focused) { if !selection_is_primary || !is_terminal_focused || self.prompt_active {
// Bar and underline cursors are drawn by the terminal // Primary cursor is drawn by the terminal when focused and no prompt is active
// BUG: If the editor area loses focus while having a bar or // Secondary cursors, unfocused primary cursor, and editor cursor when prompt is active are drawn manually
// underline cursor (eg. when a regex prompt has focus) then
// the primary cursor will be invisible. This doesn't happen
// with block cursors since we manually draw *all* cursors.
spans.push((cursor_scope, range.head..range.head + 1)); spans.push((cursor_scope, range.head..range.head + 1));
} }
continue; continue;
@ -504,17 +505,17 @@ impl EditorView {
cursor_start cursor_start
}; };
spans.push((selection_scope, range.anchor..selection_end)); spans.push((selection_scope, range.anchor..selection_end));
// add block cursors // add cursors
// skip primary cursor if terminal is unfocused - crossterm cursor is used in that case // skip primary cursor if terminal is focused and no prompt is active - terminal cursor is used in that case
if !selection_is_primary || (cursor_is_block && is_terminal_focused) { if !selection_is_primary || !is_terminal_focused || self.prompt_active {
spans.push((cursor_scope, cursor_start..range.head)); spans.push((cursor_scope, cursor_start..range.head));
} }
} else { } else {
// Reverse case. // Reverse case.
let cursor_end = next_grapheme_boundary(text, range.head); let cursor_end = next_grapheme_boundary(text, range.head);
// add block cursors // add cursors
// skip primary cursor if terminal is unfocused - crossterm cursor is used in that case // skip primary cursor if terminal is focused and no prompt is active - terminal cursor is used in that case
if !selection_is_primary || (cursor_is_block && is_terminal_focused) { if !selection_is_primary || !is_terminal_focused || self.prompt_active {
spans.push((cursor_scope, range.head..cursor_end)); spans.push((cursor_scope, range.head..cursor_end));
} }
// non block cursors look like they exclude the cursor // non block cursors look like they exclude the cursor
@ -1591,17 +1592,12 @@ impl Component for EditorView {
} }
fn cursor(&self, _area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) { fn cursor(&self, _area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) {
match editor.cursor() { let (pos, kind) = editor.cursor();
// all block cursors are drawn manually if self.terminal_focused {
(pos, CursorKind::Block) => { (pos, kind)
if self.terminal_focused { } else {
(pos, CursorKind::Hidden) // use underline cursor when terminal loses focus for visibility
} else { (pos, CursorKind::Underline)
// use crossterm cursor when terminal loses focus
(pos, CursorKind::Underline)
}
}
cursor => cursor,
} }
} }
} }

View File

@ -746,7 +746,7 @@ async fn test_hardlink_write() -> anyhow::Result<()> {
async fn edit_file_with_content(file_content: &[u8]) -> anyhow::Result<()> { async fn edit_file_with_content(file_content: &[u8]) -> anyhow::Result<()> {
let mut file = tempfile::NamedTempFile::new()?; let mut file = tempfile::NamedTempFile::new()?;
file.as_file_mut().write_all(&file_content)?; file.as_file_mut().write_all(file_content)?;
helpers::test_key_sequence( helpers::test_key_sequence(
&mut helpers::AppBuilder::new() &mut helpers::AppBuilder::new()

View File

@ -61,6 +61,7 @@ pub struct TestCase {
pub out_text: String, pub out_text: String,
pub out_selection: Selection, pub out_selection: Selection,
#[allow(dead_code)]
pub line_feed_handling: LineFeedHandling, pub line_feed_handling: LineFeedHandling,
} }
@ -425,7 +426,7 @@ pub fn reload_file(file: &mut NamedTempFile) -> anyhow::Result<()> {
let f = std::fs::OpenOptions::new() let f = std::fs::OpenOptions::new()
.write(true) .write(true)
.read(true) .read(true)
.open(&path)?; .open(path)?;
*file.as_file_mut() = f; *file.as_file_mut() = f;
Ok(()) Ok(())
} }

View File

@ -1022,6 +1022,7 @@ shebangs = []
comment-token = "#" comment-token = "#"
language-servers = [ "nil", "nixd" ] language-servers = [ "nil", "nixd" ]
indent = { tab-width = 2, unit = " " } indent = { tab-width = 2, unit = " " }
formatter = { command = "nixfmt" }
[[grammar]] [[grammar]]
name = "nix" name = "nix"
@ -4243,10 +4244,11 @@ comment-token = "#"
block-comment-tokens = ["#-", "-#"] block-comment-tokens = ["#-", "-#"]
indent = { tab-width = 2, unit = " " } indent = { tab-width = 2, unit = " " }
language-servers = ["koto-ls"] language-servers = ["koto-ls"]
formatter = {command = "koto", args = ["--format"]}
[[grammar]] [[grammar]]
name = "koto" name = "koto"
source = { git = "https://github.com/koto-lang/tree-sitter-koto", rev = "b420f7922d0d74905fd0d771e5b83be9ee8a8a9a" } source = { git = "https://github.com/koto-lang/tree-sitter-koto", rev = "2ffc77c14f0ac1674384ff629bfc207b9c57ed89" }
[[language]] [[language]]
name = "gpr" name = "gpr"

View File

@ -5,11 +5,13 @@
"*" "*"
"/" "/"
"%" "%"
"^"
"+=" "+="
"-=" "-="
"*=" "*="
"/=" "/="
"%=" "%="
"^="
"==" "=="
"!=" "!="
"<" "<"
@ -99,12 +101,18 @@
(export (export
(identifier) @namespace) (identifier) @namespace)
(call (chain
function: (identifier) @function.method) start: (identifier) @function)
(chain (chain
lookup: (identifier) @variable.other.member) lookup: (identifier) @variable.other.member)
(call
function: (identifier)) @function
(call_arg
(identifier) @variable.other.member)
[ [
(true) (true)
(false) (false)
@ -139,13 +147,10 @@
(self) @variable.builtin (self) @variable.builtin
(variable (type
type: (identifier) @type) _ @type)
(arg (arg
(_ (identifier) @variable.parameter)) (_ (identifier) @variable.parameter))
(ellipsis) @variable.parameter (ellipsis) @variable.parameter
(function
output_type: (identifier) @type)

View File

@ -11,10 +11,6 @@
(call_args (call_args
((call_arg) @parameter.inside . ","? @parameter.around) @parameter.around) ((call_arg) @parameter.inside . ","? @parameter.around) @parameter.around)
(chain
call: (tuple
((element) @parameter.inside . ","? @parameter.around) @parameter.around))
(map (map
((entry_inline) @entry.inside . ","? @entry.around) @entry.around) ((entry_inline) @entry.inside . ","? @entry.around) @entry.around)