commit
d4436277ee
20 changed files with 847 additions and 177 deletions
|
@ -36,6 +36,7 @@ pub struct DisplayMap {
|
|||
wrap_map: ModelHandle<WrapMap>,
|
||||
block_map: BlockMap,
|
||||
text_highlights: TextHighlights,
|
||||
pub clip_at_line_ends: bool,
|
||||
}
|
||||
|
||||
impl Entity for DisplayMap {
|
||||
|
@ -67,6 +68,7 @@ impl DisplayMap {
|
|||
wrap_map,
|
||||
block_map,
|
||||
text_highlights: Default::default(),
|
||||
clip_at_line_ends: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,6 +89,7 @@ impl DisplayMap {
|
|||
wraps_snapshot,
|
||||
blocks_snapshot,
|
||||
text_highlights: self.text_highlights.clone(),
|
||||
clip_at_line_ends: self.clip_at_line_ends,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,6 +208,7 @@ pub struct DisplaySnapshot {
|
|||
wraps_snapshot: wrap_map::WrapSnapshot,
|
||||
blocks_snapshot: block_map::BlockSnapshot,
|
||||
text_highlights: TextHighlights,
|
||||
clip_at_line_ends: bool,
|
||||
}
|
||||
|
||||
impl DisplaySnapshot {
|
||||
|
@ -332,7 +336,12 @@ impl DisplaySnapshot {
|
|||
}
|
||||
|
||||
pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
|
||||
DisplayPoint(self.blocks_snapshot.clip_point(point.0, bias))
|
||||
let mut clipped = self.blocks_snapshot.clip_point(point.0, bias);
|
||||
if self.clip_at_line_ends && clipped.column == self.line_len(clipped.row) {
|
||||
clipped.column = clipped.column.saturating_sub(1);
|
||||
clipped = self.blocks_snapshot.clip_point(clipped, Bias::Left);
|
||||
}
|
||||
DisplayPoint(clipped)
|
||||
}
|
||||
|
||||
pub fn folds_in_range<'a, T>(
|
||||
|
@ -488,19 +497,16 @@ impl ToDisplayPoint for Anchor {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
movement,
|
||||
test::{marked_text_ranges},
|
||||
};
|
||||
use crate::{movement, test::marked_display_snapshot};
|
||||
use gpui::{color::Color, elements::*, test::observe, MutableAppContext};
|
||||
use language::{Buffer, Language, LanguageConfig, RandomCharIter, SelectionGoal};
|
||||
use rand::{prelude::*, Rng};
|
||||
use smol::stream::StreamExt;
|
||||
use std::{env, sync::Arc};
|
||||
use theme::SyntaxTheme;
|
||||
use util::test::sample_text;
|
||||
use util::test::{marked_text_ranges, sample_text};
|
||||
use Bias::*;
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
|
@ -1133,49 +1139,70 @@ mod tests {
|
|||
|
||||
#[gpui::test]
|
||||
fn test_clip_point(cx: &mut gpui::MutableAppContext) {
|
||||
fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::MutableAppContext) {
|
||||
let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
|
||||
|
||||
match bias {
|
||||
Bias::Left => {
|
||||
if shift_right {
|
||||
*markers[1].column_mut() += 1;
|
||||
}
|
||||
|
||||
assert_eq!(unmarked_snapshot.clip_point(markers[1], bias), markers[0])
|
||||
}
|
||||
Bias::Right => {
|
||||
if shift_right {
|
||||
*markers[0].column_mut() += 1;
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
unmarked_snapshot.clip_point(dbg!(markers[0]), bias),
|
||||
markers[1]
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
use Bias::{Left, Right};
|
||||
assert("||α", false, Left, cx);
|
||||
assert("||α", true, Left, cx);
|
||||
assert("||α", false, Right, cx);
|
||||
assert("|α|", true, Right, cx);
|
||||
assert("||✋", false, Left, cx);
|
||||
assert("||✋", true, Left, cx);
|
||||
assert("||✋", false, Right, cx);
|
||||
assert("|✋|", true, Right, cx);
|
||||
assert("||🍐", false, Left, cx);
|
||||
assert("||🍐", true, Left, cx);
|
||||
assert("||🍐", false, Right, cx);
|
||||
assert("|🍐|", true, Right, cx);
|
||||
assert("||\t", false, Left, cx);
|
||||
assert("||\t", true, Left, cx);
|
||||
assert("||\t", false, Right, cx);
|
||||
assert("|\t|", true, Right, cx);
|
||||
assert(" ||\t", false, Left, cx);
|
||||
assert(" ||\t", true, Left, cx);
|
||||
assert(" ||\t", false, Right, cx);
|
||||
assert(" |\t|", true, Right, cx);
|
||||
assert(" ||\t", false, Left, cx);
|
||||
assert(" ||\t", false, Right, cx);
|
||||
}
|
||||
|
||||
let text = "\n'a', 'α',\t'✋',\t'❎', '🍐'\n";
|
||||
let display_text = "\n'a', 'α', '✋', '❎', '🍐'\n";
|
||||
let buffer = MultiBuffer::build_simple(text, cx);
|
||||
|
||||
let tab_size = 4;
|
||||
let font_cache = cx.font_cache();
|
||||
let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
|
||||
let font_id = font_cache
|
||||
.select_font(family_id, &Default::default())
|
||||
.unwrap();
|
||||
let font_size = 14.0;
|
||||
let map = cx.add_model(|cx| {
|
||||
DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, 1, 1, cx)
|
||||
});
|
||||
let map = map.update(cx, |map, cx| map.snapshot(cx));
|
||||
|
||||
assert_eq!(map.text(), display_text);
|
||||
for (input_column, bias, output_column) in vec![
|
||||
("'a', '".len(), Left, "'a', '".len()),
|
||||
("'a', '".len() + 1, Left, "'a', '".len()),
|
||||
("'a', '".len() + 1, Right, "'a', 'α".len()),
|
||||
("'a', 'α', ".len(), Left, "'a', 'α',".len()),
|
||||
("'a', 'α', ".len(), Right, "'a', 'α', ".len()),
|
||||
("'a', 'α', '".len() + 1, Left, "'a', 'α', '".len()),
|
||||
("'a', 'α', '".len() + 1, Right, "'a', 'α', '✋".len()),
|
||||
("'a', 'α', '✋',".len(), Right, "'a', 'α', '✋',".len()),
|
||||
("'a', 'α', '✋', ".len(), Left, "'a', 'α', '✋',".len()),
|
||||
(
|
||||
"'a', 'α', '✋', ".len(),
|
||||
Right,
|
||||
"'a', 'α', '✋', ".len(),
|
||||
),
|
||||
] {
|
||||
#[gpui::test]
|
||||
fn test_clip_at_line_ends(cx: &mut gpui::MutableAppContext) {
|
||||
fn assert(text: &str, cx: &mut gpui::MutableAppContext) {
|
||||
let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
|
||||
unmarked_snapshot.clip_at_line_ends = true;
|
||||
assert_eq!(
|
||||
map.clip_point(DisplayPoint::new(1, input_column as u32), bias),
|
||||
DisplayPoint::new(1, output_column as u32),
|
||||
"clip_point(({}, {}))",
|
||||
1,
|
||||
input_column,
|
||||
unmarked_snapshot.clip_point(markers[1], Bias::Left),
|
||||
markers[0]
|
||||
);
|
||||
}
|
||||
|
||||
assert("||", cx);
|
||||
assert("|a|", cx);
|
||||
assert("a|b|", cx);
|
||||
assert("a|α|", cx);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
|
|
@ -456,6 +456,8 @@ pub struct Editor {
|
|||
pending_rename: Option<RenameState>,
|
||||
searchable: bool,
|
||||
cursor_shape: CursorShape,
|
||||
keymap_context_layers: BTreeMap<TypeId, gpui::keymap::Context>,
|
||||
input_enabled: bool,
|
||||
leader_replica_id: Option<u16>,
|
||||
}
|
||||
|
||||
|
@ -932,6 +934,8 @@ impl Editor {
|
|||
searchable: true,
|
||||
override_text_style: None,
|
||||
cursor_shape: Default::default(),
|
||||
keymap_context_layers: Default::default(),
|
||||
input_enabled: true,
|
||||
leader_replica_id: None,
|
||||
};
|
||||
this.end_selection(cx);
|
||||
|
@ -1000,6 +1004,10 @@ impl Editor {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn mode(&self) -> EditorMode {
|
||||
self.mode
|
||||
}
|
||||
|
||||
pub fn set_placeholder_text(
|
||||
&mut self,
|
||||
placeholder_text: impl Into<Arc<str>>,
|
||||
|
@ -1063,6 +1071,24 @@ impl Editor {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut ViewContext<Self>) {
|
||||
self.display_map
|
||||
.update(cx, |map, _| map.clip_at_line_ends = clip);
|
||||
}
|
||||
|
||||
pub fn set_keymap_context_layer<Tag: 'static>(&mut self, context: gpui::keymap::Context) {
|
||||
self.keymap_context_layers
|
||||
.insert(TypeId::of::<Tag>(), context);
|
||||
}
|
||||
|
||||
pub fn remove_keymap_context_layer<Tag: 'static>(&mut self) {
|
||||
self.keymap_context_layers.remove(&TypeId::of::<Tag>());
|
||||
}
|
||||
|
||||
pub fn set_input_enabled(&mut self, input_enabled: bool) {
|
||||
self.input_enabled = input_enabled;
|
||||
}
|
||||
|
||||
pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> Vector2F {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
compute_scroll_position(&display_map, self.scroll_position, &self.scroll_top_anchor)
|
||||
|
@ -1742,6 +1768,11 @@ impl Editor {
|
|||
}
|
||||
|
||||
pub fn handle_input(&mut self, action: &Input, cx: &mut ViewContext<Self>) {
|
||||
if !self.input_enabled {
|
||||
cx.propagate_action();
|
||||
return;
|
||||
}
|
||||
|
||||
let text = action.0.as_ref();
|
||||
if !self.skip_autoclose_end(text, cx) {
|
||||
self.transact(cx, |this, cx| {
|
||||
|
@ -5741,26 +5772,31 @@ impl View for Editor {
|
|||
}
|
||||
|
||||
fn keymap_context(&self, _: &AppContext) -> gpui::keymap::Context {
|
||||
let mut cx = Self::default_keymap_context();
|
||||
let mut context = Self::default_keymap_context();
|
||||
let mode = match self.mode {
|
||||
EditorMode::SingleLine => "single_line",
|
||||
EditorMode::AutoHeight { .. } => "auto_height",
|
||||
EditorMode::Full => "full",
|
||||
};
|
||||
cx.map.insert("mode".into(), mode.into());
|
||||
context.map.insert("mode".into(), mode.into());
|
||||
if self.pending_rename.is_some() {
|
||||
cx.set.insert("renaming".into());
|
||||
context.set.insert("renaming".into());
|
||||
}
|
||||
match self.context_menu.as_ref() {
|
||||
Some(ContextMenu::Completions(_)) => {
|
||||
cx.set.insert("showing_completions".into());
|
||||
context.set.insert("showing_completions".into());
|
||||
}
|
||||
Some(ContextMenu::CodeActions(_)) => {
|
||||
cx.set.insert("showing_code_actions".into());
|
||||
context.set.insert("showing_code_actions".into());
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
cx
|
||||
|
||||
for layer in self.keymap_context_layers.values() {
|
||||
context.extend(layer);
|
||||
}
|
||||
|
||||
context
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6139,7 +6175,6 @@ pub fn styled_runs_for_code_label<'a>(
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::test::marked_text_by;
|
||||
|
||||
use super::*;
|
||||
use gpui::{
|
||||
|
@ -6153,7 +6188,7 @@ mod tests {
|
|||
use std::{cell::RefCell, rc::Rc, time::Instant};
|
||||
use text::Point;
|
||||
use unindent::Unindent;
|
||||
use util::test::sample_text;
|
||||
use util::test::{marked_text_by, sample_text};
|
||||
use workspace::FollowableItem;
|
||||
|
||||
#[gpui::test]
|
||||
|
|
|
@ -1292,7 +1292,7 @@ impl PaintState {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum CursorShape {
|
||||
Bar,
|
||||
Block,
|
||||
|
|
|
@ -266,13 +266,13 @@ pub fn surrounding_word(map: &DisplaySnapshot, position: DisplayPoint) -> Range<
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{test::marked_text, Buffer, DisplayMap, MultiBuffer};
|
||||
use crate::{test::marked_display_snapshot, Buffer, DisplayMap, MultiBuffer};
|
||||
use language::Point;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_previous_word_start(cx: &mut gpui::MutableAppContext) {
|
||||
fn assert(marked_text: &str, cx: &mut gpui::MutableAppContext) {
|
||||
let (snapshot, display_points) = marked_snapshot(marked_text, cx);
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
previous_word_start(&snapshot, display_points[1]),
|
||||
display_points[0]
|
||||
|
@ -298,7 +298,7 @@ mod tests {
|
|||
#[gpui::test]
|
||||
fn test_previous_subword_start(cx: &mut gpui::MutableAppContext) {
|
||||
fn assert(marked_text: &str, cx: &mut gpui::MutableAppContext) {
|
||||
let (snapshot, display_points) = marked_snapshot(marked_text, cx);
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
previous_subword_start(&snapshot, display_points[1]),
|
||||
display_points[0]
|
||||
|
@ -335,7 +335,7 @@ mod tests {
|
|||
cx: &mut gpui::MutableAppContext,
|
||||
is_boundary: impl FnMut(char, char) -> bool,
|
||||
) {
|
||||
let (snapshot, display_points) = marked_snapshot(marked_text, cx);
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
find_preceding_boundary(&snapshot, display_points[1], is_boundary),
|
||||
display_points[0]
|
||||
|
@ -362,7 +362,7 @@ mod tests {
|
|||
#[gpui::test]
|
||||
fn test_next_word_end(cx: &mut gpui::MutableAppContext) {
|
||||
fn assert(marked_text: &str, cx: &mut gpui::MutableAppContext) {
|
||||
let (snapshot, display_points) = marked_snapshot(marked_text, cx);
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
next_word_end(&snapshot, display_points[0]),
|
||||
display_points[1]
|
||||
|
@ -385,7 +385,7 @@ mod tests {
|
|||
#[gpui::test]
|
||||
fn test_next_subword_end(cx: &mut gpui::MutableAppContext) {
|
||||
fn assert(marked_text: &str, cx: &mut gpui::MutableAppContext) {
|
||||
let (snapshot, display_points) = marked_snapshot(marked_text, cx);
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
next_subword_end(&snapshot, display_points[0]),
|
||||
display_points[1]
|
||||
|
@ -421,7 +421,7 @@ mod tests {
|
|||
cx: &mut gpui::MutableAppContext,
|
||||
is_boundary: impl FnMut(char, char) -> bool,
|
||||
) {
|
||||
let (snapshot, display_points) = marked_snapshot(marked_text, cx);
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
find_boundary(&snapshot, display_points[0], is_boundary),
|
||||
display_points[1]
|
||||
|
@ -448,7 +448,7 @@ mod tests {
|
|||
#[gpui::test]
|
||||
fn test_surrounding_word(cx: &mut gpui::MutableAppContext) {
|
||||
fn assert(marked_text: &str, cx: &mut gpui::MutableAppContext) {
|
||||
let (snapshot, display_points) = marked_snapshot(marked_text, cx);
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
surrounding_word(&snapshot, display_points[1]),
|
||||
display_points[0]..display_points[2]
|
||||
|
@ -532,31 +532,4 @@ mod tests {
|
|||
(DisplayPoint::new(7, 2), SelectionGoal::Column(2)),
|
||||
);
|
||||
}
|
||||
|
||||
// Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one.
|
||||
fn marked_snapshot(
|
||||
text: &str,
|
||||
cx: &mut gpui::MutableAppContext,
|
||||
) -> (DisplaySnapshot, Vec<DisplayPoint>) {
|
||||
let (unmarked_text, markers) = marked_text(text);
|
||||
|
||||
let tab_size = 4;
|
||||
let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
|
||||
let font_id = cx
|
||||
.font_cache()
|
||||
.select_font(family_id, &Default::default())
|
||||
.unwrap();
|
||||
let font_size = 14.0;
|
||||
|
||||
let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
|
||||
let display_map = cx
|
||||
.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, 1, 1, cx));
|
||||
let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let markers = markers
|
||||
.into_iter()
|
||||
.map(|offset| offset.to_display_point(&snapshot))
|
||||
.collect();
|
||||
|
||||
(snapshot, markers)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use std::ops::Range;
|
||||
use util::test::marked_text;
|
||||
|
||||
use collections::HashMap;
|
||||
use crate::{
|
||||
display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
|
||||
DisplayPoint, MultiBuffer,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[ctor::ctor]
|
||||
|
@ -10,47 +13,29 @@ fn init_logger() {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn marked_text_by(
|
||||
marked_text: &str,
|
||||
markers: Vec<char>,
|
||||
) -> (String, HashMap<char, Vec<usize>>) {
|
||||
let mut extracted_markers: HashMap<char, Vec<usize>> = Default::default();
|
||||
let mut unmarked_text = String::new();
|
||||
// Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one.
|
||||
pub fn marked_display_snapshot(
|
||||
text: &str,
|
||||
cx: &mut gpui::MutableAppContext,
|
||||
) -> (DisplaySnapshot, Vec<DisplayPoint>) {
|
||||
let (unmarked_text, markers) = marked_text(text);
|
||||
|
||||
for char in marked_text.chars() {
|
||||
if markers.contains(&char) {
|
||||
let char_offsets = extracted_markers.entry(char).or_insert(Vec::new());
|
||||
char_offsets.push(unmarked_text.len());
|
||||
} else {
|
||||
unmarked_text.push(char);
|
||||
}
|
||||
}
|
||||
let tab_size = 4;
|
||||
let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
|
||||
let font_id = cx
|
||||
.font_cache()
|
||||
.select_font(family_id, &Default::default())
|
||||
.unwrap();
|
||||
let font_size = 14.0;
|
||||
|
||||
(unmarked_text, extracted_markers)
|
||||
}
|
||||
|
||||
pub fn marked_text(marked_text: &str) -> (String, Vec<usize>) {
|
||||
let (unmarked_text, mut markers) = marked_text_by(marked_text, vec!['|']);
|
||||
(unmarked_text, markers.remove(&'|').unwrap_or_else(Vec::new))
|
||||
}
|
||||
|
||||
pub fn marked_text_ranges(
|
||||
marked_text: &str,
|
||||
range_markers: Vec<(char, char)>,
|
||||
) -> (String, Vec<Range<usize>>) {
|
||||
let mut marker_chars = Vec::new();
|
||||
for (start, end) in range_markers.iter() {
|
||||
marker_chars.push(*start);
|
||||
marker_chars.push(*end);
|
||||
}
|
||||
let (unmarked_text, markers) = marked_text_by(marked_text, marker_chars);
|
||||
let ranges = range_markers
|
||||
.iter()
|
||||
.map(|(start_marker, end_marker)| {
|
||||
let start = markers.get(start_marker).unwrap()[0];
|
||||
let end = markers.get(end_marker).unwrap()[0];
|
||||
start..end
|
||||
})
|
||||
let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
|
||||
let display_map =
|
||||
cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, 1, 1, cx));
|
||||
let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let markers = markers
|
||||
.into_iter()
|
||||
.map(|offset| offset.to_display_point(&snapshot))
|
||||
.collect();
|
||||
(unmarked_text, ranges)
|
||||
|
||||
(snapshot, markers)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue