Created a Context structure, to simplify initialization

pull/6118/head
SoraTenshi 4 months ago
parent b460ed6384
commit 3c8b834b61

@ -4,11 +4,11 @@ use helix_core::visual_coords_at_pos;
use helix_core::{
syntax::RopeProvider,
text_annotations::TextAnnotations,
tree_sitter::{QueryCursor, QueryMatch},
tree_sitter::{Node, QueryCursor, QueryMatch},
Position,
};
use helix_view::{view::ViewPosition, Document, Theme, View};
use helix_view::{editor::Config, graphics::Rect, view::ViewPosition, Document, Theme, View};
use tui::buffer::Buffer as Surface;
@ -27,42 +27,61 @@ pub struct StickyNode {
pub has_context_end: bool,
}
fn get_context_paired_range(
query_match: &QueryMatch,
start_index: u32,
end_index: u32,
top_first_byte: usize,
last_scan_byte: usize,
) -> Option<std::ops::Range<usize>> {
// get all the captured @context.params nodes
let end_nodes = once_cell::unsync::Lazy::new(|| {
query_match
.nodes_for_capture_index(end_index)
.collect::<Vec<_>>()
});
#[derive(Debug)]
struct StickyNodeContext {
/// The row on which the cursor is placed
pub visual_row: usize,
/// This marks the location of which we take possible out of range nodes
/// e.g. on follows-cursor: 'on', this would be the parent nodes, relative to the *cursor*
/// on follows-cursor: 'off', this would be the parent nodes, relative to the *topmost screen*
pub context_location: usize,
/// The topmost byte that is visible (and not hidden behind sticky nodes)
pub topmost_byte: usize,
/// The anchor of the view offset
pub anchor_line: usize,
/// The viewport
pub viewport: Rect,
}
query_match
.nodes_for_capture_index(start_index)
.find_map(|context| {
let ctx_start_range = context.byte_range();
impl StickyNodeContext {
pub fn from_context(
last_nodes: &Option<Vec<StickyNode>>,
doc: &Document,
view: &View,
config: &Config,
cursor_cache: &Option<Option<Position>>,
) -> Option<Self> {
let Some(cursor_cache) = cursor_cache else {
return None;
};
let cursor_cache = cursor_cache.as_ref()?;
let text = doc.text().slice(..);
let viewport = view.inner_area(doc);
let cursor_byte = text.char_to_byte(doc.selection(view.id).primary().cursor(text));
// filter all matches that are out of scope, based on follows-cursor
let start_range_contains_bytes = ctx_start_range.contains(&top_first_byte)
&& ctx_start_range.contains(&last_scan_byte);
if !start_range_contains_bytes {
return None;
}
let anchor_line = text.char_to_line(view.offset.anchor);
let visual_cursor_row = cursor_cache.row;
let ctx_start_row = context.start_position().row;
let ctx_start_byte = ctx_start_range.start;
if visual_cursor_row == 0 {
return None;
}
end_nodes.iter().find_map(|it| {
let end = it.end_byte();
// check whether or not @context.params nodes are on different lines
(ctx_start_row != it.end_position().row && ctx_start_range.contains(&end))
.then_some(ctx_start_byte..end.saturating_sub(1))
})
let top_first_byte =
text.line_to_byte(anchor_line + last_nodes.as_ref().map_or(0, |nodes| nodes.len()));
let last_scan_byte = if config.sticky_context.follow_cursor {
cursor_byte
} else {
top_first_byte
};
Some(Self {
visual_row: visual_cursor_row,
context_location: last_scan_byte,
topmost_byte: top_first_byte,
anchor_line,
viewport,
})
}
}
/// Calculates the sticky nodes
@ -70,59 +89,33 @@ pub fn calculate_sticky_nodes(
nodes: &Option<Vec<StickyNode>>,
doc: &Document,
view: &View,
config: &helix_view::editor::Config,
config: &Config,
cursor_cache: &Option<Option<Position>>,
) -> Option<Vec<StickyNode>> {
let Some(cursor_cache) = cursor_cache else {
let Some(context) = StickyNodeContext::from_context(nodes, doc, view, config, cursor_cache)
else {
return None;
};
let cursor_cache = cursor_cache.as_ref()?;
let syntax = doc.syntax()?;
let tree = syntax.tree();
let text = doc.text().slice(..);
let viewport = view.inner_area(doc);
let cursor_byte = text.char_to_byte(doc.selection(view.id).primary().cursor(text));
let anchor_line = text.char_to_line(view.offset.anchor);
let visual_cursor_row = cursor_cache.row;
if visual_cursor_row == 0 {
return None;
}
let top_first_byte =
text.line_to_byte(anchor_line + nodes.as_ref().map_or(0, |nodes| nodes.len()));
let last_scan_byte = if config.sticky_context.follow_cursor {
cursor_byte
} else {
top_first_byte
};
let mut cached_nodes: Vec<StickyNode> = Vec::new();
// nothing has changed, so the cached result can be returned
if let Some(nodes) = nodes {
if nodes.iter().any(|node| view.offset.anchor == node.anchor) {
return Some(
nodes
.iter()
.take(visual_cursor_row as usize)
.cloned()
.collect(),
);
} else {
cached_nodes = nodes.clone();
// clear up the last node
if let Some(popped) = cached_nodes.pop() {
if popped.indicator.is_some() {
_ = cached_nodes.pop();
}
return Some(nodes.iter().take(context.visual_row).cloned().collect());
}
cached_nodes = nodes.clone();
if let Some(popped) = cached_nodes.pop() {
if popped.indicator.is_some() {
_ = cached_nodes.pop();
}
// the node before is also important to clear, as in upwards movement
// we might encounter issues there
_ = cached_nodes.pop();
}
_ = cached_nodes.pop();
}
let start_byte_range = cached_nodes
@ -134,7 +127,7 @@ pub fn calculate_sticky_nodes(
let start_byte = if start_byte_range.start != tree.root_node().start_byte() {
start_byte_range.start
} else {
last_scan_byte
context.context_location
};
let mut start_node = tree
@ -172,7 +165,7 @@ pub fn calculate_sticky_nodes(
// only run the query from start to the cursor location
let mut cursor = QueryCursor::new();
cursor.set_byte_range(start_byte_range.start..last_scan_byte);
cursor.set_byte_range(start_byte_range.start..context.context_location);
let query = &context_nodes.query;
let query_nodes = cursor.matches(
query,
@ -186,16 +179,19 @@ pub fn calculate_sticky_nodes(
&matched_node,
start_index,
end_index,
top_first_byte,
last_scan_byte,
context.topmost_byte,
context.context_location,
);
for node in matched_node.nodes_for_capture_index(start_index) {
if (!node.byte_range().contains(&last_scan_byte)
|| !node.byte_range().contains(&top_first_byte))
&& node.start_position().row != anchor_line + result.len()
&& node_byte_range.is_none()
{
if node_in_range(
node,
context.anchor_line,
&node_byte_range.clone(),
context.context_location,
context.topmost_byte,
result.len(),
) {
continue;
}
@ -233,7 +229,7 @@ pub fn calculate_sticky_nodes(
// always cap the maximum amount of sticky contextes to 1/3 of the viewport
// unless configured otherwise
let max_lines = config.sticky_context.max_lines as u16;
let max_nodes_amount = max_lines.min(viewport.height / 3) as usize;
let max_nodes_amount = max_lines.min(context.viewport.height / 3) as usize;
let skip = res.len().saturating_sub(max_nodes_amount);
@ -243,7 +239,7 @@ pub fn calculate_sticky_nodes(
.skip(skip)
.enumerate()
.take_while(|(i, _)| {
*i + Into::<usize>::into(config.sticky_context.indicator) != visual_cursor_row as usize
*i + Into::<usize>::into(config.sticky_context.indicator) != context.visual_row
}) // also only nodes that don't overlap with the visual cursor position
.map(|(i, node)| {
let mut new_node = node.clone();
@ -253,21 +249,78 @@ pub fn calculate_sticky_nodes(
.collect();
if config.sticky_context.indicator {
let str = "─".repeat(viewport.width as usize);
res.push(StickyNode {
line: usize::MAX,
visual_line: res.len() as u16,
byte_range: 0..0,
indicator: Some(str),
anchor: view.offset.anchor,
has_context_end: false,
});
res = add_indicator(&context.viewport, view, res);
}
Some(res)
}
fn get_context_paired_range(
query_match: &QueryMatch,
start_index: u32,
end_index: u32,
top_first_byte: usize,
last_scan_byte: usize,
) -> Option<std::ops::Range<usize>> {
// get all the captured @context.params nodes
let end_nodes = once_cell::unsync::Lazy::new(|| {
query_match
.nodes_for_capture_index(end_index)
.collect::<Vec<_>>()
});
query_match
.nodes_for_capture_index(start_index)
.find_map(|context| {
let ctx_start_range = context.byte_range();
// filter all matches that are out of scope, based on follows-cursor
let start_range_contains_bytes = ctx_start_range.contains(&top_first_byte)
&& ctx_start_range.contains(&last_scan_byte);
if !start_range_contains_bytes {
return None;
}
let ctx_start_row = context.start_position().row;
let ctx_start_byte = ctx_start_range.start;
end_nodes.iter().find_map(|it| {
let end = it.end_byte();
// check whether or not @context.params nodes are on different lines
(ctx_start_row != it.end_position().row && ctx_start_range.contains(&end))
.then_some(ctx_start_byte..end.saturating_sub(1))
})
})
}
fn node_in_range(
node: Node,
anchor: usize,
node_byte_range: &Option<std::ops::Range<usize>>,
last_scan_byte: usize,
topmost_byte: usize,
result_len: usize,
) -> bool {
(!node.byte_range().contains(&last_scan_byte) || !node.byte_range().contains(&topmost_byte))
&& node.start_position().row != anchor + result_len
&& node_byte_range.is_none()
}
fn add_indicator(viewport: &Rect, view: &View, res: Vec<StickyNode>) -> Vec<StickyNode> {
let mut res = res;
let str = "─".repeat(viewport.width as usize);
res.push(StickyNode {
line: usize::MAX,
visual_line: res.len() as u16,
byte_range: 0..0,
indicator: Some(str),
anchor: view.offset.anchor,
has_context_end: false,
});
res
}
/// Render the sticky context
pub fn render_sticky_context(
doc: &Document,

Loading…
Cancel
Save