use std::{ops::Range, time::Duration}; use collections::HashSet;use gpui::{AppContext, Task};use language::{language_settings::language_settings, BufferRow};use multi_buffer::{MultiBufferIndentGuide, MultiBufferRow};use text::{BufferId, LineIndent, Point};use ui::ViewContext;use util::ResultExt; use crate::{DisplaySnapshot, Editor}; struct ActiveIndentedRange { buffer_id: BufferId, row_range: Range<BufferRow>, indent: LineIndent,} #[derive(Default)]pub struct ActiveIndentGuidesState { pub dirty: bool, cursor_row: MultiBufferRow, pending_refresh: Option<Task<()>>, active_indent_range: Option<ActiveIndentedRange>,} impl ActiveIndentGuidesState { pub fn should_refresh(&self) -> bool { self.pending_refresh.is_none() && self.dirty }} impl Editor { pub fn indent_guides( &self, visible_buffer_range: Range<MultiBufferRow>, snapshot: &DisplaySnapshot, cx: &mut ViewContext<Editor>, ) -> Option<Vec<MultiBufferIndentGuide>> { let show_indent_guides = self.should_show_indent_guides().unwrap_or_else(|| { if let Some(buffer) = self.buffer().read(cx).as_singleton() { language_settings( buffer.read(cx).language().map(|l| l.name()), buffer.read(cx).file(), cx, ) .indent_guides .enabled } else { true } }); if !show_indent_guides { return None; } Some(indent_guides_in_range( visible_buffer_range, self.should_show_indent_guides() == Some(true), snapshot, cx, )) } pub fn find_active_indent_guide_indices( &mut self, indent_guides: &[MultiBufferIndentGuide], snapshot: &DisplaySnapshot, cx: &mut ViewContext<Editor>, ) -> Option<HashSet<usize>> { let selection = self.selections.newest::<Point>(cx); let cursor_row = MultiBufferRow(selection.head().row); let state = &mut self.active_indent_guides_state; if state .active_indent_range .as_ref() .map(|active_indent_range| { should_recalculate_indented_range( state.cursor_row, cursor_row, active_indent_range, snapshot, ) }) .unwrap_or(true) { state.dirty = true; } else { state.cursor_row = cursor_row; } if state.should_refresh() { state.cursor_row = cursor_row; state.dirty = false; if indent_guides.is_empty() { return None; } let snapshot = snapshot.clone(); let task = cx .background_executor() .spawn(resolve_indented_range(snapshot, cursor_row)); // Try to resolve the indent in a short amount of time, otherwise move it to a background task. match cx .background_executor() .block_with_timeout(Duration::from_micros(200), task) { Ok(result) => state.active_indent_range = result, Err(future) => { state.pending_refresh = Some(cx.spawn(|editor, mut cx| async move { let result = cx.background_executor().spawn(future).await; editor .update(&mut cx, |editor, _| { editor.active_indent_guides_state.active_indent_range = result; editor.active_indent_guides_state.pending_refresh = None; }) .log_err(); })); return None; } } } let active_indent_range = state.active_indent_range.as_ref()?; let candidates = indent_guides .iter() .enumerate() .filter(|(_, indent_guide)| { indent_guide.buffer_id == active_indent_range.buffer_id && indent_guide.indent_level() == active_indent_range.indent.len(indent_guide.tab_size) }); let mut matches = HashSet::default(); for (i, indent) in candidates { // Find matches that are either an exact match, partially on screen, or inside the enclosing indent if active_indent_range.row_range.start <= indent.end_row && indent.start_row <= active_indent_range.row_range.end { matches.insert(i); } } Some(matches) }} pub fn indent_guides_in_range( visible_buffer_range: Range<MultiBufferRow>, ignore_disabled_for_language: bool, snapshot: &DisplaySnapshot, cx: &AppContext,) -> Vec<MultiBufferIndentGuide> { let start_anchor = snapshot .buffer_snapshot .anchor_before(Point::new(visible_buffer_range.start.0, 0)); let end_anchor = snapshot .buffer_snapshot .anchor_after(Point::new(visible_buffer_range.end.0, 0)); snapshot .buffer_snapshot .indent_guides_in_range(start_anchor..end_anchor, ignore_disabled_for_language, cx) .into_iter() .filter(|indent_guide| { let start = MultiBufferRow(indent_guide.multibuffer_row_range.start.0.saturating_sub(1)); // Filter out indent guides that are inside a fold let is_folded = snapshot.is_line_folded(start); let line_indent = snapshot.line_indent_for_buffer_row(start); let contained_in_fold = line_indent.len(indent_guide.tab_size) <= indent_guide.indent_level(); !(is_folded && contained_in_fold) }) .collect()} async fn resolve_indented_range( snapshot: DisplaySnapshot, buffer_row: MultiBufferRow,) -> Option<ActiveIndentedRange> { let (buffer_row, buffer_snapshot, buffer_id) = if let Some((_, buffer_id, snapshot)) = snapshot.buffer_snapshot.as_singleton() { (buffer_row.0, snapshot, buffer_id) } else { let (snapshot, point) = snapshot.buffer_snapshot.buffer_line_for_row(buffer_row)?; let buffer_id = snapshot.remote_id(); (point.start.row, snapshot, buffer_id) }; buffer_snapshot .enclosing_indent(buffer_row) .await .map(|(row_range, indent)| ActiveIndentedRange { row_range, indent, buffer_id, })} fn should_recalculate_indented_range( prev_row: MultiBufferRow, new_row: MultiBufferRow, current_indent_range: &ActiveIndentedRange, snapshot: &DisplaySnapshot,) -> bool { if prev_row.0 == new_row.0 { return false; } if let Some((_, _, snapshot)) = snapshot.buffer_snapshot.as_singleton() { if !current_indent_range.row_range.contains(&new_row.0) { return true; } let old_line_indent = snapshot.line_indent_for_row(prev_row.0); let new_line_indent = snapshot.line_indent_for_row(new_row.0); if old_line_indent.is_line_empty() || new_line_indent.is_line_empty() || old_line_indent != new_line_indent || snapshot.max_point().row == new_row.0 { return true; } let next_line_indent = snapshot.line_indent_for_row(new_row.0 + 1); next_line_indent.is_line_empty() || next_line_indent != old_line_indent } else { true }}
use std::{ops::Range, time::Duration};
use collections::HashSet;
use gpui::{AppContext, Task};
use language::{language_settings::language_settings, BufferRow};
use multi_buffer::{MultiBufferIndentGuide, MultiBufferRow};
use text::{BufferId, LineIndent, Point};
use ui::ViewContext;
use util::ResultExt;
use crate::{DisplaySnapshot, Editor};
struct ActiveIndentedRange {
buffer_id: BufferId,
row_range: Range<BufferRow>,
indent: LineIndent,
}
#[derive(Default)]
pub struct ActiveIndentGuidesState {
pub dirty: bool,
cursor_row: MultiBufferRow,
pending_refresh: Option<Task<()>>,
active_indent_range: Option<ActiveIndentedRange>,
}
impl ActiveIndentGuidesState {
pub fn should_refresh(&self) -> bool {
self.pending_refresh.is_none() && self.dirty
}
}
impl Editor {
pub fn indent_guides(
&self,
visible_buffer_range: Range<MultiBufferRow>,
snapshot: &DisplaySnapshot,
cx: &mut ViewContext<Editor>,
) -> Option<Vec<MultiBufferIndentGuide>> {
let show_indent_guides = self.should_show_indent_guides().unwrap_or_else(|| {
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
language_settings(
buffer.read(cx).language().map(|l| l.name()),
buffer.read(cx).file(),
cx,
)
.indent_guides
.enabled
} else {
true
}
});
if !show_indent_guides {
return None;
}
Some(indent_guides_in_range(
visible_buffer_range,
self.should_show_indent_guides() == Some(true),
snapshot,
cx,
))
}
pub fn find_active_indent_guide_indices(
&mut self,
indent_guides: &[MultiBufferIndentGuide],
snapshot: &DisplaySnapshot,
cx: &mut ViewContext<Editor>,
) -> Option<HashSet<usize>> {
let selection = self.selections.newest::<Point>(cx);
let cursor_row = MultiBufferRow(selection.head().row);
let state = &mut self.active_indent_guides_state;
if state
.active_indent_range
.as_ref()
.map(|active_indent_range| {
should_recalculate_indented_range(
state.cursor_row,
cursor_row,
active_indent_range,
snapshot,
)
})
.unwrap_or(true)
{
state.dirty = true;
} else {
state.cursor_row = cursor_row;
}
if state.should_refresh() {
state.cursor_row = cursor_row;
state.dirty = false;
if indent_guides.is_empty() {
return None;
}
let snapshot = snapshot.clone();
let task = cx
.background_executor()
.spawn(resolve_indented_range(snapshot, cursor_row));
// Try to resolve the indent in a short amount of time, otherwise move it to a background task.
match cx
.background_executor()
.block_with_timeout(Duration::from_micros(200), task)
{
Ok(result) => state.active_indent_range = result,
Err(future) => {
state.pending_refresh = Some(cx.spawn(|editor, mut cx| async move {
let result = cx.background_executor().spawn(future).await;
editor
.update(&mut cx, |editor, _| {
editor.active_indent_guides_state.active_indent_range = result;
editor.active_indent_guides_state.pending_refresh = None;
})
.log_err();
}));
return None;
}
}
}
let active_indent_range = state.active_indent_range.as_ref()?;
let candidates = indent_guides
.iter()
.enumerate()
.filter(|(_, indent_guide)| {
indent_guide.buffer_id == active_indent_range.buffer_id
&& indent_guide.indent_level()
== active_indent_range.indent.len(indent_guide.tab_size)
});
let mut matches = HashSet::default();
for (i, indent) in candidates {
// Find matches that are either an exact match, partially on screen, or inside the enclosing indent
if active_indent_range.row_range.start <= indent.end_row
&& indent.start_row <= active_indent_range.row_range.end
{
matches.insert(i);
}
}
Some(matches)
}
}
pub fn indent_guides_in_range(
visible_buffer_range: Range<MultiBufferRow>,
ignore_disabled_for_language: bool,
snapshot: &DisplaySnapshot,
cx: &AppContext,
) -> Vec<MultiBufferIndentGuide> {
let start_anchor = snapshot
.buffer_snapshot
.anchor_before(Point::new(visible_buffer_range.start.0, 0));
let end_anchor = snapshot
.buffer_snapshot
.anchor_after(Point::new(visible_buffer_range.end.0, 0));
snapshot
.buffer_snapshot
.indent_guides_in_range(start_anchor..end_anchor, ignore_disabled_for_language, cx)
.into_iter()
.filter(|indent_guide| {
let start =
MultiBufferRow(indent_guide.multibuffer_row_range.start.0.saturating_sub(1));
// Filter out indent guides that are inside a fold
let is_folded = snapshot.is_line_folded(start);
let line_indent = snapshot.line_indent_for_buffer_row(start);
let contained_in_fold =
line_indent.len(indent_guide.tab_size) <= indent_guide.indent_level();
!(is_folded && contained_in_fold)
})
.collect()
}
async fn resolve_indented_range(
snapshot: DisplaySnapshot,
buffer_row: MultiBufferRow,
) -> Option<ActiveIndentedRange> {
let (buffer_row, buffer_snapshot, buffer_id) =
if let Some((_, buffer_id, snapshot)) = snapshot.buffer_snapshot.as_singleton() {
(buffer_row.0, snapshot, buffer_id)
} else {
let (snapshot, point) = snapshot.buffer_snapshot.buffer_line_for_row(buffer_row)?;
let buffer_id = snapshot.remote_id();
(point.start.row, snapshot, buffer_id)
};
buffer_snapshot
.enclosing_indent(buffer_row)
.await
.map(|(row_range, indent)| ActiveIndentedRange {
row_range,
indent,
buffer_id,
})
}
fn should_recalculate_indented_range(
prev_row: MultiBufferRow,
new_row: MultiBufferRow,
current_indent_range: &ActiveIndentedRange,
snapshot: &DisplaySnapshot,
) -> bool {
if prev_row.0 == new_row.0 {
return false;
}
if let Some((_, _, snapshot)) = snapshot.buffer_snapshot.as_singleton() {
if !current_indent_range.row_range.contains(&new_row.0) {
return true;
}
let old_line_indent = snapshot.line_indent_for_row(prev_row.0);
let new_line_indent = snapshot.line_indent_for_row(new_row.0);
if old_line_indent.is_line_empty()
|| new_line_indent.is_line_empty()
|| old_line_indent != new_line_indent
|| snapshot.max_point().row == new_row.0
{
return true;
}
let next_line_indent = snapshot.line_indent_for_row(new_row.0 + 1);
next_line_indent.is_line_empty() || next_line_indent != old_line_indent
} else {
true
}
}