Merge remote-tracking branch 'origin' into storybook
This commit is contained in:
commit
2fa9c037c8
110 changed files with 4110 additions and 1580 deletions
|
@ -37,10 +37,7 @@ impl BlinkManager {
|
|||
}
|
||||
|
||||
pub fn pause_blinking(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if !self.visible {
|
||||
self.visible = true;
|
||||
cx.notify();
|
||||
}
|
||||
self.show_cursor(cx);
|
||||
|
||||
let epoch = self.next_blink_epoch();
|
||||
let interval = self.blink_interval;
|
||||
|
@ -82,7 +79,13 @@ impl BlinkManager {
|
|||
})
|
||||
.detach();
|
||||
}
|
||||
} else if !self.visible {
|
||||
} else {
|
||||
self.show_cursor(cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show_cursor(&mut self, cx: &mut ModelContext<'_, BlinkManager>) {
|
||||
if !self.visible {
|
||||
self.visible = true;
|
||||
cx.notify();
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ use gpui::{
|
|||
elements::*,
|
||||
executor,
|
||||
fonts::{self, HighlightStyle, TextStyle},
|
||||
geometry::vector::Vector2F,
|
||||
geometry::vector::{vec2f, Vector2F},
|
||||
impl_actions,
|
||||
keymap_matcher::KeymapContext,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
|
@ -820,6 +820,7 @@ struct CompletionsMenu {
|
|||
id: CompletionId,
|
||||
initial_position: Anchor,
|
||||
buffer: ModelHandle<Buffer>,
|
||||
project: Option<ModelHandle<Project>>,
|
||||
completions: Arc<[Completion]>,
|
||||
match_candidates: Vec<StringMatchCandidate>,
|
||||
matches: Arc<[StringMatch]>,
|
||||
|
@ -863,6 +864,48 @@ impl CompletionsMenu {
|
|||
fn render(&self, style: EditorStyle, cx: &mut ViewContext<Editor>) -> AnyElement<Editor> {
|
||||
enum CompletionTag {}
|
||||
|
||||
let language_servers = self.project.as_ref().map(|project| {
|
||||
project
|
||||
.read(cx)
|
||||
.language_servers_for_buffer(self.buffer.read(cx), cx)
|
||||
.filter(|(_, server)| server.capabilities().completion_provider.is_some())
|
||||
.map(|(adapter, server)| (server.server_id(), adapter.short_name))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
let needs_server_name = language_servers
|
||||
.as_ref()
|
||||
.map_or(false, |servers| servers.len() > 1);
|
||||
|
||||
let get_server_name =
|
||||
move |lookup_server_id: lsp::LanguageServerId| -> Option<&'static str> {
|
||||
language_servers
|
||||
.iter()
|
||||
.flatten()
|
||||
.find_map(|(server_id, server_name)| {
|
||||
if *server_id == lookup_server_id {
|
||||
Some(*server_name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let widest_completion_ix = self
|
||||
.matches
|
||||
.iter()
|
||||
.enumerate()
|
||||
.max_by_key(|(_, mat)| {
|
||||
let completion = &self.completions[mat.candidate_id];
|
||||
let mut len = completion.label.text.chars().count();
|
||||
|
||||
if let Some(server_name) = get_server_name(completion.server_id) {
|
||||
len += server_name.chars().count();
|
||||
}
|
||||
|
||||
len
|
||||
})
|
||||
.map(|(ix, _)| ix);
|
||||
|
||||
let completions = self.completions.clone();
|
||||
let matches = self.matches.clone();
|
||||
let selected_item = self.selected_item;
|
||||
|
@ -889,19 +932,83 @@ impl CompletionsMenu {
|
|||
style.autocomplete.item
|
||||
};
|
||||
|
||||
Text::new(completion.label.text.clone(), style.text.clone())
|
||||
.with_soft_wrap(false)
|
||||
.with_highlights(combine_syntax_and_fuzzy_match_highlights(
|
||||
&completion.label.text,
|
||||
style.text.color.into(),
|
||||
styled_runs_for_code_label(
|
||||
&completion.label,
|
||||
&style.syntax,
|
||||
),
|
||||
&mat.positions,
|
||||
))
|
||||
.contained()
|
||||
.with_style(item_style)
|
||||
let completion_label =
|
||||
Text::new(completion.label.text.clone(), style.text.clone())
|
||||
.with_soft_wrap(false)
|
||||
.with_highlights(
|
||||
combine_syntax_and_fuzzy_match_highlights(
|
||||
&completion.label.text,
|
||||
style.text.color.into(),
|
||||
styled_runs_for_code_label(
|
||||
&completion.label,
|
||||
&style.syntax,
|
||||
),
|
||||
&mat.positions,
|
||||
),
|
||||
);
|
||||
|
||||
if let Some(server_name) = get_server_name(completion.server_id) {
|
||||
Flex::row()
|
||||
.with_child(completion_label)
|
||||
.with_children((|| {
|
||||
if !needs_server_name {
|
||||
return None;
|
||||
}
|
||||
|
||||
let text_style = TextStyle {
|
||||
color: style.autocomplete.server_name_color,
|
||||
font_size: style.text.font_size
|
||||
* style.autocomplete.server_name_size_percent,
|
||||
..style.text.clone()
|
||||
};
|
||||
|
||||
let label = Text::new(server_name, text_style)
|
||||
.aligned()
|
||||
.constrained()
|
||||
.dynamically(move |constraint, _, _| {
|
||||
gpui::SizeConstraint {
|
||||
min: constraint.min,
|
||||
max: vec2f(
|
||||
constraint.max.x(),
|
||||
constraint.min.y(),
|
||||
),
|
||||
}
|
||||
});
|
||||
|
||||
if Some(item_ix) == widest_completion_ix {
|
||||
Some(
|
||||
label
|
||||
.contained()
|
||||
.with_style(
|
||||
style
|
||||
.autocomplete
|
||||
.server_name_container,
|
||||
)
|
||||
.into_any(),
|
||||
)
|
||||
} else {
|
||||
Some(label.flex_float().into_any())
|
||||
}
|
||||
})())
|
||||
.into_any()
|
||||
} else {
|
||||
completion_label.into_any()
|
||||
}
|
||||
.contained()
|
||||
.with_style(item_style)
|
||||
.constrained()
|
||||
.dynamically(
|
||||
move |constraint, _, _| {
|
||||
if Some(item_ix) == widest_completion_ix {
|
||||
constraint
|
||||
} else {
|
||||
gpui::SizeConstraint {
|
||||
min: constraint.min,
|
||||
max: constraint.min,
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
|
@ -918,19 +1025,7 @@ impl CompletionsMenu {
|
|||
}
|
||||
},
|
||||
)
|
||||
.with_width_from_item(
|
||||
self.matches
|
||||
.iter()
|
||||
.enumerate()
|
||||
.max_by_key(|(_, mat)| {
|
||||
self.completions[mat.candidate_id]
|
||||
.label
|
||||
.text
|
||||
.chars()
|
||||
.count()
|
||||
})
|
||||
.map(|(ix, _)| ix),
|
||||
)
|
||||
.with_width_from_item(widest_completion_ix)
|
||||
.contained()
|
||||
.with_style(container_style)
|
||||
.into_any()
|
||||
|
@ -1454,6 +1549,16 @@ impl Editor {
|
|||
cx.observe(&display_map, Self::on_display_map_changed),
|
||||
cx.observe(&blink_manager, |_, _, cx| cx.notify()),
|
||||
cx.observe_global::<SettingsStore, _>(Self::settings_changed),
|
||||
cx.observe_window_activation(|editor, active, cx| {
|
||||
editor.blink_manager.update(cx, |blink_manager, cx| {
|
||||
if active {
|
||||
blink_manager.enable(cx);
|
||||
} else {
|
||||
blink_manager.show_cursor(cx);
|
||||
blink_manager.disable(cx);
|
||||
}
|
||||
});
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -1625,6 +1730,15 @@ impl Editor {
|
|||
self.read_only = read_only;
|
||||
}
|
||||
|
||||
pub fn set_field_editor_style(
|
||||
&mut self,
|
||||
style: Option<Arc<GetFieldEditorTheme>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.get_field_editor_theme = style;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn replica_id_map(&self) -> Option<&HashMap<ReplicaId, ReplicaId>> {
|
||||
self.replica_id_mapping.as_ref()
|
||||
}
|
||||
|
@ -2964,6 +3078,7 @@ impl Editor {
|
|||
});
|
||||
|
||||
let id = post_inc(&mut self.next_completion_id);
|
||||
let project = self.project.clone();
|
||||
let task = cx.spawn(|this, mut cx| {
|
||||
async move {
|
||||
let menu = if let Some(completions) = completions.await.log_err() {
|
||||
|
@ -2982,6 +3097,7 @@ impl Editor {
|
|||
})
|
||||
.collect(),
|
||||
buffer,
|
||||
project,
|
||||
completions: completions.into(),
|
||||
matches: Vec::new().into(),
|
||||
selected_item: 0,
|
||||
|
@ -4979,6 +5095,9 @@ impl Editor {
|
|||
self.unmark_text(cx);
|
||||
self.refresh_copilot_suggestions(true, cx);
|
||||
cx.emit(Event::Edited);
|
||||
cx.emit(Event::TransactionUndone {
|
||||
transaction_id: tx_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8418,6 +8537,9 @@ pub enum Event {
|
|||
local: bool,
|
||||
autoscroll: bool,
|
||||
},
|
||||
TransactionUndone {
|
||||
transaction_id: TransactionId,
|
||||
},
|
||||
Closed,
|
||||
}
|
||||
|
||||
|
@ -8458,7 +8580,7 @@ impl View for Editor {
|
|||
"Editor"
|
||||
}
|
||||
|
||||
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
if cx.is_self_focused() {
|
||||
let focused_event = EditorFocused(cx.handle());
|
||||
cx.emit(Event::Focused);
|
||||
|
@ -8466,7 +8588,7 @@ impl View for Editor {
|
|||
}
|
||||
if let Some(rename) = self.pending_rename.as_ref() {
|
||||
cx.focus(&rename.editor);
|
||||
} else {
|
||||
} else if cx.is_self_focused() || !focused.is::<Editor>() {
|
||||
if !self.focused {
|
||||
self.blink_manager.update(cx, BlinkManager::enable);
|
||||
}
|
||||
|
@ -9161,6 +9283,7 @@ pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator<Item = &'a str
|
|||
None
|
||||
})
|
||||
.flat_map(|word| word.split_inclusive('_'))
|
||||
.flat_map(|word| word.split_inclusive('-'))
|
||||
}
|
||||
|
||||
trait RangeToAnchorExt {
|
||||
|
|
|
@ -19,7 +19,8 @@ use gpui::{
|
|||
use indoc::indoc;
|
||||
use language::{
|
||||
language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent},
|
||||
BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegistry, Point,
|
||||
BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageRegistry,
|
||||
Override, Point,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use project::project_settings::{LspSettings, ProjectSettings};
|
||||
|
@ -7688,6 +7689,105 @@ async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
|
|||
cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorLspTestContext::new(
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
path_suffixes: vec!["jsx".into()],
|
||||
overrides: [(
|
||||
"element".into(),
|
||||
LanguageConfigOverride {
|
||||
word_characters: Override::Set(['-'].into_iter().collect()),
|
||||
..Default::default()
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_typescript::language_tsx()),
|
||||
)
|
||||
.with_override_query("(jsx_self_closing_element) @element")
|
||||
.unwrap(),
|
||||
lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
trigger_characters: Some(vec![":".to_string()]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
cx.lsp
|
||||
.handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
|
||||
Ok(Some(lsp::CompletionResponse::Array(vec![
|
||||
lsp::CompletionItem {
|
||||
label: "bg-blue".into(),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::CompletionItem {
|
||||
label: "bg-red".into(),
|
||||
..Default::default()
|
||||
},
|
||||
lsp::CompletionItem {
|
||||
label: "bg-yellow".into(),
|
||||
..Default::default()
|
||||
},
|
||||
])))
|
||||
});
|
||||
|
||||
cx.set_state(r#"<p class="bgˇ" />"#);
|
||||
|
||||
// Trigger completion when typing a dash, because the dash is an extra
|
||||
// word character in the 'element' scope, which contains the cursor.
|
||||
cx.simulate_keystroke("-");
|
||||
cx.foreground().run_until_parked();
|
||||
cx.update_editor(|editor, _| {
|
||||
if let Some(ContextMenu::Completions(menu)) = &editor.context_menu {
|
||||
assert_eq!(
|
||||
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
|
||||
&["bg-red", "bg-blue", "bg-yellow"]
|
||||
);
|
||||
} else {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
});
|
||||
|
||||
cx.simulate_keystroke("l");
|
||||
cx.foreground().run_until_parked();
|
||||
cx.update_editor(|editor, _| {
|
||||
if let Some(ContextMenu::Completions(menu)) = &editor.context_menu {
|
||||
assert_eq!(
|
||||
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
|
||||
&["bg-blue", "bg-yellow"]
|
||||
);
|
||||
} else {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
});
|
||||
|
||||
// When filtering completions, consider the character after the '-' to
|
||||
// be the start of a subword.
|
||||
cx.set_state(r#"<p class="yelˇ" />"#);
|
||||
cx.simulate_keystroke("l");
|
||||
cx.foreground().run_until_parked();
|
||||
cx.update_editor(|editor, _| {
|
||||
if let Some(ContextMenu::Completions(menu)) = &editor.context_menu {
|
||||
assert_eq!(
|
||||
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
|
||||
&["bg-yellow"]
|
||||
);
|
||||
} else {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
|
||||
let point = DisplayPoint::new(row as u32, column as u32);
|
||||
point..point
|
||||
|
|
|
@ -2251,7 +2251,7 @@ impl Element<Editor> for EditorElement {
|
|||
let replica_id = if let Some(mapping) = &editor.replica_id_mapping {
|
||||
mapping.get(&replica_id).copied()
|
||||
} else {
|
||||
None
|
||||
Some(replica_id)
|
||||
};
|
||||
|
||||
// The local selections match the leader's selections.
|
||||
|
|
|
@ -177,20 +177,20 @@ pub fn line_end(
|
|||
|
||||
pub fn previous_word_start(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
|
||||
let raw_point = point.to_point(map);
|
||||
let language = map.buffer_snapshot.language_at(raw_point);
|
||||
let scope = map.buffer_snapshot.language_scope_at(raw_point);
|
||||
|
||||
find_preceding_boundary(map, point, |left, right| {
|
||||
(char_kind(language, left) != char_kind(language, right) && !right.is_whitespace())
|
||||
(char_kind(&scope, left) != char_kind(&scope, right) && !right.is_whitespace())
|
||||
|| left == '\n'
|
||||
})
|
||||
}
|
||||
|
||||
pub fn previous_subword_start(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
|
||||
let raw_point = point.to_point(map);
|
||||
let language = map.buffer_snapshot.language_at(raw_point);
|
||||
let scope = map.buffer_snapshot.language_scope_at(raw_point);
|
||||
find_preceding_boundary(map, point, |left, right| {
|
||||
let is_word_start =
|
||||
char_kind(language, left) != char_kind(language, right) && !right.is_whitespace();
|
||||
char_kind(&scope, left) != char_kind(&scope, right) && !right.is_whitespace();
|
||||
let is_subword_start =
|
||||
left == '_' && right != '_' || left.is_lowercase() && right.is_uppercase();
|
||||
is_word_start || is_subword_start || left == '\n'
|
||||
|
@ -199,19 +199,19 @@ pub fn previous_subword_start(map: &DisplaySnapshot, point: DisplayPoint) -> Dis
|
|||
|
||||
pub fn next_word_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
|
||||
let raw_point = point.to_point(map);
|
||||
let language = map.buffer_snapshot.language_at(raw_point);
|
||||
let scope = map.buffer_snapshot.language_scope_at(raw_point);
|
||||
find_boundary(map, point, |left, right| {
|
||||
(char_kind(language, left) != char_kind(language, right) && !left.is_whitespace())
|
||||
(char_kind(&scope, left) != char_kind(&scope, right) && !left.is_whitespace())
|
||||
|| right == '\n'
|
||||
})
|
||||
}
|
||||
|
||||
pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
|
||||
let raw_point = point.to_point(map);
|
||||
let language = map.buffer_snapshot.language_at(raw_point);
|
||||
let scope = map.buffer_snapshot.language_scope_at(raw_point);
|
||||
find_boundary(map, point, |left, right| {
|
||||
let is_word_end =
|
||||
(char_kind(language, left) != char_kind(language, right)) && !left.is_whitespace();
|
||||
(char_kind(&scope, left) != char_kind(&scope, right)) && !left.is_whitespace();
|
||||
let is_subword_end =
|
||||
left != '_' && right == '_' || left.is_lowercase() && right.is_uppercase();
|
||||
is_word_end || is_subword_end || right == '\n'
|
||||
|
@ -399,14 +399,14 @@ pub fn find_boundary_in_line(
|
|||
|
||||
pub fn is_inside_word(map: &DisplaySnapshot, point: DisplayPoint) -> bool {
|
||||
let raw_point = point.to_point(map);
|
||||
let language = map.buffer_snapshot.language_at(raw_point);
|
||||
let scope = map.buffer_snapshot.language_scope_at(raw_point);
|
||||
let ix = map.clip_point(point, Bias::Left).to_offset(map, Bias::Left);
|
||||
let text = &map.buffer_snapshot;
|
||||
let next_char_kind = text.chars_at(ix).next().map(|c| char_kind(language, c));
|
||||
let next_char_kind = text.chars_at(ix).next().map(|c| char_kind(&scope, c));
|
||||
let prev_char_kind = text
|
||||
.reversed_chars_at(ix)
|
||||
.next()
|
||||
.map(|c| char_kind(language, c));
|
||||
.map(|c| char_kind(&scope, c));
|
||||
prev_char_kind.zip(next_char_kind) == Some((CharKind::Word, CharKind::Word))
|
||||
}
|
||||
|
||||
|
|
|
@ -617,6 +617,42 @@ impl MultiBuffer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn merge_transactions(
|
||||
&mut self,
|
||||
transaction: TransactionId,
|
||||
destination: TransactionId,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if let Some(buffer) = self.as_singleton() {
|
||||
buffer.update(cx, |buffer, _| {
|
||||
buffer.merge_transactions(transaction, destination)
|
||||
});
|
||||
} else {
|
||||
if let Some(transaction) = self.history.forget(transaction) {
|
||||
if let Some(destination) = self.history.transaction_mut(destination) {
|
||||
for (buffer_id, buffer_transaction_id) in transaction.buffer_transactions {
|
||||
if let Some(destination_buffer_transaction_id) =
|
||||
destination.buffer_transactions.get(&buffer_id)
|
||||
{
|
||||
if let Some(state) = self.buffers.borrow().get(&buffer_id) {
|
||||
state.buffer.update(cx, |buffer, _| {
|
||||
buffer.merge_transactions(
|
||||
buffer_transaction_id,
|
||||
*destination_buffer_transaction_id,
|
||||
)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
destination
|
||||
.buffer_transactions
|
||||
.insert(buffer_id, buffer_transaction_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize_last_transaction(&mut self, cx: &mut ModelContext<Self>) {
|
||||
self.history.finalize_last_transaction();
|
||||
for BufferState { buffer, .. } in self.buffers.borrow().values() {
|
||||
|
@ -788,6 +824,20 @@ impl MultiBuffer {
|
|||
None
|
||||
}
|
||||
|
||||
pub fn undo_transaction(&mut self, transaction_id: TransactionId, cx: &mut ModelContext<Self>) {
|
||||
if let Some(buffer) = self.as_singleton() {
|
||||
buffer.update(cx, |buffer, cx| buffer.undo_transaction(transaction_id, cx));
|
||||
} else if let Some(transaction) = self.history.remove_from_undo(transaction_id) {
|
||||
for (buffer_id, transaction_id) in &transaction.buffer_transactions {
|
||||
if let Some(BufferState { buffer, .. }) = self.buffers.borrow().get(buffer_id) {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.undo_transaction(*transaction_id, cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stream_excerpts_with_context_lines(
|
||||
&mut self,
|
||||
buffer: ModelHandle<Buffer>,
|
||||
|
@ -1367,13 +1417,13 @@ impl MultiBuffer {
|
|||
return false;
|
||||
}
|
||||
|
||||
let language = self.language_at(position.clone(), cx);
|
||||
|
||||
if char_kind(language.as_ref(), char) == CharKind::Word {
|
||||
let snapshot = self.snapshot(cx);
|
||||
let position = position.to_offset(&snapshot);
|
||||
let scope = snapshot.language_scope_at(position);
|
||||
if char_kind(&scope, char) == CharKind::Word {
|
||||
return true;
|
||||
}
|
||||
|
||||
let snapshot = self.snapshot(cx);
|
||||
let anchor = snapshot.anchor_before(position);
|
||||
anchor
|
||||
.buffer_id
|
||||
|
@ -1875,8 +1925,8 @@ impl MultiBufferSnapshot {
|
|||
let mut next_chars = self.chars_at(start).peekable();
|
||||
let mut prev_chars = self.reversed_chars_at(start).peekable();
|
||||
|
||||
let language = self.language_at(start);
|
||||
let kind = |c| char_kind(language, c);
|
||||
let scope = self.language_scope_at(start);
|
||||
let kind = |c| char_kind(&scope, c);
|
||||
let word_kind = cmp::max(
|
||||
prev_chars.peek().copied().map(kind),
|
||||
next_chars.peek().copied().map(kind),
|
||||
|
@ -2316,6 +2366,16 @@ impl MultiBufferSnapshot {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn prev_non_blank_row(&self, mut row: u32) -> Option<u32> {
|
||||
while row > 0 {
|
||||
row -= 1;
|
||||
if !self.is_line_blank(row) {
|
||||
return Some(row);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn line_len(&self, row: u32) -> u32 {
|
||||
if let Some((_, range)) = self.buffer_line_for_row(row) {
|
||||
range.end.column - range.start.column
|
||||
|
@ -3347,6 +3407,35 @@ impl History {
|
|||
}
|
||||
}
|
||||
|
||||
fn forget(&mut self, transaction_id: TransactionId) -> Option<Transaction> {
|
||||
if let Some(ix) = self
|
||||
.undo_stack
|
||||
.iter()
|
||||
.rposition(|transaction| transaction.id == transaction_id)
|
||||
{
|
||||
Some(self.undo_stack.remove(ix))
|
||||
} else if let Some(ix) = self
|
||||
.redo_stack
|
||||
.iter()
|
||||
.rposition(|transaction| transaction.id == transaction_id)
|
||||
{
|
||||
Some(self.redo_stack.remove(ix))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn transaction_mut(&mut self, transaction_id: TransactionId) -> Option<&mut Transaction> {
|
||||
self.undo_stack
|
||||
.iter_mut()
|
||||
.find(|transaction| transaction.id == transaction_id)
|
||||
.or_else(|| {
|
||||
self.redo_stack
|
||||
.iter_mut()
|
||||
.find(|transaction| transaction.id == transaction_id)
|
||||
})
|
||||
}
|
||||
|
||||
fn pop_undo(&mut self) -> Option<&mut Transaction> {
|
||||
assert_eq!(self.transaction_depth, 0);
|
||||
if let Some(transaction) = self.undo_stack.pop() {
|
||||
|
@ -3367,6 +3456,16 @@ impl History {
|
|||
}
|
||||
}
|
||||
|
||||
fn remove_from_undo(&mut self, transaction_id: TransactionId) -> Option<&Transaction> {
|
||||
let ix = self
|
||||
.undo_stack
|
||||
.iter()
|
||||
.rposition(|transaction| transaction.id == transaction_id)?;
|
||||
let transaction = self.undo_stack.remove(ix);
|
||||
self.redo_stack.push(transaction);
|
||||
self.redo_stack.last()
|
||||
}
|
||||
|
||||
fn group(&mut self) -> Option<TransactionId> {
|
||||
let mut count = 0;
|
||||
let mut transactions = self.undo_stack.iter();
|
||||
|
|
|
@ -51,7 +51,7 @@ impl<'a> EditorLspTestContext<'a> {
|
|||
language
|
||||
.path_suffixes()
|
||||
.first()
|
||||
.unwrap_or(&"txt".to_string())
|
||||
.expect("language must have a path suffix for EditorLspTestContext")
|
||||
);
|
||||
|
||||
let mut fake_servers = language
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue