Compare commits

...

13 Commits

Author SHA1 Message Date
Zed Bot
1d512ffaba Bump to 0.164.2 for @ConradIrwin 2024-12-03 20:35:46 +00:00
Max Brunsfeld
f0c7d0621d Add AutoIndent action and '=' vim operator (#21427)
Release Notes:

- vim: Added the `=` operator, for auto-indent

Co-authored-by: Conrad <conrad@zed.dev>
2024-12-03 12:34:05 -08:00
Conrad Irwin
061272bfd2 Fix panic in autoclosing (#21482)
Closes #14961

Release Notes:

- Fixed a panic when backspacing at the start of a buffer with
`always_treat_brackets_as_autoclosed` enabled.
2024-12-03 13:12:36 -07:00
gcp-cherry-pick-bot[bot]
8bf1edf2e7 Fix ctrl-alt-X shortcuts (cherry-pick #21473) (#21474)
Cherry-picked Fix ctrl-alt-X shortcuts (#21473)

The macOS input handler assumes that you want to insert control
sequences when
you type ctrl-alt-X (you probably don't...).

Release Notes:

- (nightly only) fix ctrl-alt-X shortcuts

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-12-03 13:11:41 -07:00
Conrad Irwin
c4a4c96922 Revert "macos: Add default keybind for ctrl-home / ctrl-end (#21007)"
This reverts commit 614b3b979b.

Release notes:
- N/A
2024-12-03 10:34:52 -07:00
gcp-cherry-pick-bot[bot]
b1dc23b9cc Properly handle opening of file-less excerpts (cherry-pick #21465) (#21472)
Cherry-picked Properly handle opening of file-less excerpts (#21465)

Follow-up of https://github.com/zed-industries/zed/pull/20491 and
https://github.com/zed-industries/zed/pull/20469
Closes https://github.com/zed-industries/zed/issues/21369

Release Notes:

- Fixed file-less excerpts always opening instead of activating

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2024-12-03 19:02:56 +02:00
Conrad Irwin
4b90302a33 Fix macOS IME overlay positioning (#21416)
Release Notes:

- Improved positioning of macOS IME overlay

---------

Co-authored-by: Richard Feldman <richard@zed.dev>
2024-12-03 09:15:04 -07:00
Conrad Irwin
0afc8b270c Fix dismissing the IME viewer with escape (#21413)
Co-Authored-By: Richard Feldman <richard@zed.dev>

Closes #21392

Release Notes:

- Fixed dismissing the macOS IME menu with escape when no marked text
was present

---------

Co-authored-by: Richard Feldman <richard@zed.dev>
2024-12-03 09:14:58 -07:00
Zed Bot
f3932acbb5 Bump to 0.164.1 for @osiewicz 2024-12-02 20:41:04 +00:00
gcp-cherry-pick-bot[bot]
9fb2099e9f workspace: Sanitize pinned tab count before usage (cherry-pick #21417) (#21423)
Cherry-picked workspace: Sanitize pinned tab count before usage (#21417)

Fixes all sorts of panics around usage of incorrect pinned tab count
that has been fixed in app itself, yet persists in user db.

Closes #ISSUE

Release Notes:

- N/A

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2024-12-02 21:33:28 +01:00
gcp-cherry-pick-bot[bot]
862a408214 toolchains: Run listing tasks on background thread (cherry-pick #21414) (#21420)
Cherry-picked toolchains: Run listing tasks on background thread
(#21414)

Potentially fixes #21404

This is a speculative fix, as while I was trying to repro this issue
I've noticed that introducing artificial delays in ToolchainLister::list
could impact apps responsiveness. These delays were essentially there to
stimulate PET taking a while to find venvs.

Release Notes:

- Improved app responsiveness in environments with multiple Python
virtual environments

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2024-12-02 21:25:57 +01:00
Mikayla Maki
9250d1d7ce Make Markdown images layout vertically instead of horizontally (#21247)
Release Notes:

- Fixed a bug in the Markdown preview where images in the same paragraph
would be rendered next to each other
2024-11-27 13:50:23 -05:00
Peter Tripp
389b202b75 v0.164.x preview 2024-11-27 09:48:24 -05:00
24 changed files with 569 additions and 212 deletions

2
Cargo.lock generated
View File

@@ -15614,7 +15614,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.164.0"
version = "0.164.2"
dependencies = [
"activity_indicator",
"anyhow",

View File

@@ -93,8 +93,6 @@
"ctrl-e": "editor::MoveToEndOfLine",
"cmd-up": "editor::MoveToBeginning",
"cmd-down": "editor::MoveToEnd",
"ctrl-home": "editor::MoveToBeginning",
"ctrl-end": "editor::MoveToEnd",
"shift-up": "editor::SelectUp",
"ctrl-shift-p": "editor::SelectUp",
"shift-down": "editor::SelectDown",

View File

@@ -55,10 +55,10 @@
"n": "vim::MoveToNextMatch",
"shift-n": "vim::MoveToPrevMatch",
"%": "vim::Matching",
"] }": ["vim::UnmatchedForward", { "char": "}" } ],
"[ {": ["vim::UnmatchedBackward", { "char": "{" } ],
"] )": ["vim::UnmatchedForward", { "char": ")" } ],
"[ (": ["vim::UnmatchedBackward", { "char": "(" } ],
"] }": ["vim::UnmatchedForward", { "char": "}" }],
"[ {": ["vim::UnmatchedBackward", { "char": "{" }],
"] )": ["vim::UnmatchedForward", { "char": ")" }],
"[ (": ["vim::UnmatchedBackward", { "char": "(" }],
"f": ["vim::PushOperator", { "FindForward": { "before": false } }],
"t": ["vim::PushOperator", { "FindForward": { "before": true } }],
"shift-f": ["vim::PushOperator", { "FindBackward": { "after": false } }],
@@ -209,6 +209,7 @@
"shift-s": "vim::SubstituteLine",
">": ["vim::PushOperator", "Indent"],
"<": ["vim::PushOperator", "Outdent"],
"=": ["vim::PushOperator", "AutoIndent"],
"g u": ["vim::PushOperator", "Lowercase"],
"g shift-u": ["vim::PushOperator", "Uppercase"],
"g ~": ["vim::PushOperator", "OppositeCase"],
@@ -275,6 +276,7 @@
"ctrl-[": ["vim::SwitchMode", "Normal"],
">": "vim::Indent",
"<": "vim::Outdent",
"=": "vim::AutoIndent",
"i": ["vim::PushOperator", { "Object": { "around": false } }],
"a": ["vim::PushOperator", { "Object": { "around": true } }],
"g c": "vim::ToggleComments",

View File

@@ -303,6 +303,7 @@ gpui::actions!(
OpenPermalinkToLine,
OpenUrl,
Outdent,
AutoIndent,
PageDown,
PageUp,
Paste,

View File

@@ -4142,8 +4142,10 @@ impl Editor {
if buffer.contains_str_at(selection.start, &pair.end) {
let pair_start_len = pair.start.len();
if buffer.contains_str_at(selection.start - pair_start_len, &pair.start)
{
if buffer.contains_str_at(
selection.start.saturating_sub(pair_start_len),
&pair.start,
) {
selection.start -= pair_start_len;
selection.end += pair.end.len();
@@ -6360,6 +6362,25 @@ impl Editor {
});
}
pub fn autoindent(&mut self, _: &AutoIndent, cx: &mut ViewContext<Self>) {
if self.read_only(cx) {
return;
}
let selections = self
.selections
.all::<usize>(cx)
.into_iter()
.map(|s| s.range());
self.transact(cx, |this, cx| {
this.buffer.update(cx, |buffer, cx| {
buffer.autoindent_ranges(selections, cx);
});
let selections = this.selections.all::<usize>(cx);
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
});
}
pub fn delete_line(&mut self, _: &DeleteLine, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let selections = self.selections.all::<Point>(cx);
@@ -12873,8 +12894,41 @@ impl Editor {
};
for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
let editor =
workspace.open_project_item::<Self>(pane.clone(), buffer, true, true, cx);
let editor = buffer
.read(cx)
.file()
.is_none()
.then(|| {
// Handle file-less buffers separately: those are not really the project items, so won't have a paroject path or entity id,
// so `workspace.open_project_item` will never find them, always opening a new editor.
// Instead, we try to activate the existing editor in the pane first.
let (editor, pane_item_index) =
pane.read(cx).items().enumerate().find_map(|(i, item)| {
let editor = item.downcast::<Editor>()?;
let singleton_buffer =
editor.read(cx).buffer().read(cx).as_singleton()?;
if singleton_buffer == buffer {
Some((editor, i))
} else {
None
}
})?;
pane.update(cx, |pane, cx| {
pane.activate_item(pane_item_index, true, true, cx)
});
Some(editor)
})
.flatten()
.unwrap_or_else(|| {
workspace.open_project_item::<Self>(
pane.clone(),
buffer,
true,
true,
cx,
)
});
editor.update(cx, |editor, cx| {
let autoscroll = match scroll_offset {
Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
@@ -14670,7 +14724,8 @@ impl ViewInputHandler for Editor {
let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
+ self.gutter_dimensions.width;
+ self.gutter_dimensions.width
+ self.gutter_dimensions.margin;
let y = line_height * (start.row().as_f32() - scroll_position.y);
Some(Bounds {

View File

@@ -34,6 +34,7 @@ use serde_json::{self, json};
use std::sync::atomic;
use std::sync::atomic::AtomicUsize;
use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
use test::editor_lsp_test_context::rust_lang;
use unindent::Unindent;
use util::{
assert_set_eq,
@@ -5458,7 +5459,7 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
}
#[gpui::test]
async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
async fn test_autoindent(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let language = Arc::new(
@@ -5520,6 +5521,89 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
});
}
#[gpui::test]
async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
{
let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
cx.set_state(indoc! {"
impl A {
fn b() {}
«fn c() {
}ˇ»
}
"});
cx.update_editor(|editor, cx| {
editor.autoindent(&Default::default(), cx);
});
cx.assert_editor_state(indoc! {"
impl A {
fn b() {}
«fn c() {
}ˇ»
}
"});
}
{
let mut cx = EditorTestContext::new_multibuffer(
cx,
[indoc! { "
impl A {
«
// a
fn b(){}
»
«
}
fn c(){}
»
"}],
);
let buffer = cx.update_editor(|editor, cx| {
let buffer = editor.buffer().update(cx, |buffer, _| {
buffer.all_buffers().iter().next().unwrap().clone()
});
buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
buffer
});
cx.run_until_parked();
cx.update_editor(|editor, cx| {
editor.select_all(&Default::default(), cx);
editor.autoindent(&Default::default(), cx)
});
cx.run_until_parked();
cx.update(|cx| {
pretty_assertions::assert_eq!(
buffer.read(cx).text(),
indoc! { "
impl A {
// a
fn b(){}
}
fn c(){}
" }
)
});
}
}
#[gpui::test]
async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
@@ -11700,7 +11784,7 @@ async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
multi_buffer_editor.update(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::Next), cx, |s| {
s.select_ranges(Some(60..70))
s.select_ranges(Some(70..70))
});
editor.open_excerpts(&OpenExcerpts, cx);
});
@@ -13912,20 +13996,6 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC
update_test_language_settings(cx, f);
}
pub(crate) fn rust_lang() -> Arc<Language> {
Arc::new(Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
))
}
#[track_caller]
fn assert_hunk_revert(
not_reverted_text_with_selections: &str,

View File

@@ -189,6 +189,7 @@ impl EditorElement {
register_action(view, cx, Editor::tab_prev);
register_action(view, cx, Editor::indent);
register_action(view, cx, Editor::outdent);
register_action(view, cx, Editor::autoindent);
register_action(view, cx, Editor::delete_line);
register_action(view, cx, Editor::join_lines);
register_action(view, cx, Editor::sort_lines_case_sensitive);

View File

@@ -1258,6 +1258,7 @@ pub mod tests {
use crate::{
scroll::{scroll_amount::ScrollAmount, Autoscroll},
test::editor_lsp_test_context::rust_lang,
ExcerptRange,
};
use futures::StreamExt;
@@ -2274,7 +2275,7 @@ pub mod tests {
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(crate::editor_tests::rust_lang());
language_registry.add(rust_lang());
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
@@ -2570,7 +2571,7 @@ pub mod tests {
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
let language = crate::editor_tests::rust_lang();
let language = rust_lang();
language_registry.add(language);
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
@@ -2922,7 +2923,7 @@ pub mod tests {
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(crate::editor_tests::rust_lang());
language_registry.add(rust_lang());
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
@@ -3153,7 +3154,7 @@ pub mod tests {
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(crate::editor_tests::rust_lang());
language_registry.add(rust_lang());
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
@@ -3396,7 +3397,7 @@ pub mod tests {
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(crate::editor_tests::rust_lang());
language_registry.add(rust_lang());
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {

View File

@@ -31,6 +31,47 @@ pub struct EditorLspTestContext {
pub buffer_lsp_url: lsp::Url,
}
pub(crate) fn rust_lang() -> Arc<Language> {
let language = Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
line_comments: vec!["// ".into(), "/// ".into(), "//! ".into()],
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_queries(LanguageQueries {
indents: Some(Cow::from(indoc! {r#"
[
((where_clause) _ @end)
(field_expression)
(call_expression)
(assignment_expression)
(let_declaration)
(let_chain)
(await_expression)
] @indent
(_ "[" "]" @end) @indent
(_ "<" ">" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent"#})),
brackets: Some(Cow::from(indoc! {r#"
("(" @open ")" @close)
("[" @open "]" @close)
("{" @open "}" @close)
("<" @open ">" @close)
("\"" @open "\"" @close)
(closure_parameters "|" @open "|" @close)"#})),
..Default::default()
})
.expect("Could not parse queries");
Arc::new(language)
}
impl EditorLspTestContext {
pub async fn new(
language: Language,
@@ -119,46 +160,7 @@ impl EditorLspTestContext {
capabilities: lsp::ServerCapabilities,
cx: &mut gpui::TestAppContext,
) -> EditorLspTestContext {
let language = Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
line_comments: vec!["// ".into(), "/// ".into(), "//! ".into()],
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_queries(LanguageQueries {
indents: Some(Cow::from(indoc! {r#"
[
((where_clause) _ @end)
(field_expression)
(call_expression)
(assignment_expression)
(let_declaration)
(let_chain)
(await_expression)
] @indent
(_ "[" "]" @end) @indent
(_ "<" ">" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent"#})),
brackets: Some(Cow::from(indoc! {r#"
("(" @open ")" @close)
("[" @open "]" @close)
("{" @open "}" @close)
("<" @open ">" @close)
("\"" @open "\"" @close)
(closure_parameters "|" @open "|" @close)"#})),
..Default::default()
})
.expect("Could not parse queries");
Self::new(language, capabilities, cx).await
Self::new(Arc::into_inner(rust_lang()).unwrap(), capabilities, cx).await
}
pub async fn new_typescript(

View File

@@ -331,6 +331,7 @@ struct MacWindowState {
traffic_light_position: Option<Point<Pixels>>,
previous_modifiers_changed_event: Option<PlatformInput>,
keystroke_for_do_command: Option<Keystroke>,
do_command_handled: Option<bool>,
external_files_dragged: bool,
// Whether the next left-mouse click is also the focusing click.
first_mouse: bool,
@@ -609,6 +610,7 @@ impl MacWindow {
.and_then(|titlebar| titlebar.traffic_light_position),
previous_modifiers_changed_event: None,
keystroke_for_do_command: None,
do_command_handled: None,
external_files_dragged: false,
first_mouse: false,
fullscreen_restore_bounds: Bounds::default(),
@@ -1251,14 +1253,25 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
// otherwise we only send to the input handler if we don't have a matching binding.
// The input handler may call `do_command_by_selector` if it doesn't know how to handle
// a key. If it does so, it will return YES so we won't send the key twice.
if is_composing || event.keystroke.key.is_empty() {
window_state.as_ref().lock().keystroke_for_do_command = Some(event.keystroke.clone());
// We also do this for non-printing keys (like arrow keys and escape) as the IME menu
// may need them even if there is no marked text;
// however we skip keys with control or the input handler adds control-characters to the buffer.
if is_composing || (event.keystroke.key_char.is_none() && !event.keystroke.modifiers.control) {
{
let mut lock = window_state.as_ref().lock();
lock.keystroke_for_do_command = Some(event.keystroke.clone());
lock.do_command_handled.take();
drop(lock);
}
let handled: BOOL = unsafe {
let input_context: id = msg_send![this, inputContext];
msg_send![input_context, handleEvent: native_event]
};
window_state.as_ref().lock().keystroke_for_do_command.take();
if handled == YES {
if let Some(handled) = window_state.as_ref().lock().do_command_handled.take() {
return handled as BOOL;
} else if handled == YES {
return YES;
}
@@ -1377,6 +1390,14 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
};
match &event {
PlatformInput::MouseDown(_) => {
drop(lock);
unsafe {
let input_context: id = msg_send![this, inputContext];
msg_send![input_context, handleEvent: native_event]
}
lock = window_state.as_ref().lock();
}
PlatformInput::MouseMove(
event @ MouseMoveEvent {
pressed_button: Some(_),
@@ -1683,7 +1704,10 @@ extern "C" fn first_rect_for_character_range(
let lock = state.lock();
let mut frame = NSWindow::frame(lock.native_window);
let content_layout_rect: CGRect = msg_send![lock.native_window, contentLayoutRect];
frame.origin.y -= frame.size.height - content_layout_rect.size.height;
let style_mask: NSWindowStyleMask = msg_send![lock.native_window, styleMask];
if !style_mask.contains(NSWindowStyleMask::NSFullSizeContentViewWindowMask) {
frame.origin.y -= frame.size.height - content_layout_rect.size.height;
}
frame
};
with_input_handler(this, |input_handler| {
@@ -1790,10 +1814,11 @@ extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) {
drop(lock);
if let Some((keystroke, mut callback)) = keystroke.zip(event_callback.as_mut()) {
(callback)(PlatformInput::KeyDown(KeyDownEvent {
let handled = (callback)(PlatformInput::KeyDown(KeyDownEvent {
keystroke,
is_held: false,
}));
state.as_ref().lock().do_command_handled = Some(!handled.propagate);
}
state.as_ref().lock().event_callback = event_callback;

View File

@@ -467,6 +467,7 @@ struct AutoindentRequest {
before_edit: BufferSnapshot,
entries: Vec<AutoindentRequestEntry>,
is_block_mode: bool,
ignore_empty_lines: bool,
}
#[derive(Debug, Clone)]
@@ -1381,7 +1382,7 @@ impl Buffer {
let autoindent_requests = self.autoindent_requests.clone();
Some(async move {
let mut indent_sizes = BTreeMap::new();
let mut indent_sizes = BTreeMap::<u32, (IndentSize, bool)>::new();
for request in autoindent_requests {
// Resolve each edited range to its row in the current buffer and in the
// buffer before this batch of edits.
@@ -1475,10 +1476,12 @@ impl Buffer {
let suggested_indent = indent_sizes
.get(&suggestion.basis_row)
.copied()
.map(|e| e.0)
.unwrap_or_else(|| {
snapshot.indent_size_for_line(suggestion.basis_row)
})
.with_delta(suggestion.delta, language_indent_size);
if old_suggestions.get(&new_row).map_or(
true,
|(old_indentation, was_within_error)| {
@@ -1486,7 +1489,10 @@ impl Buffer {
&& (!suggestion.within_error || *was_within_error)
},
) {
indent_sizes.insert(new_row, suggested_indent);
indent_sizes.insert(
new_row,
(suggested_indent, request.ignore_empty_lines),
);
}
}
}
@@ -1494,10 +1500,12 @@ impl Buffer {
if let (true, Some(original_indent_column)) =
(request.is_block_mode, original_indent_column)
{
let new_indent = indent_sizes
.get(&row_range.start)
.copied()
.unwrap_or_else(|| snapshot.indent_size_for_line(row_range.start));
let new_indent =
if let Some((indent, _)) = indent_sizes.get(&row_range.start) {
*indent
} else {
snapshot.indent_size_for_line(row_range.start)
};
let delta = new_indent.len as i64 - original_indent_column as i64;
if delta != 0 {
for row in row_range.skip(1) {
@@ -1512,7 +1520,7 @@ impl Buffer {
Ordering::Equal => {}
}
}
size
(size, request.ignore_empty_lines)
});
}
}
@@ -1523,6 +1531,15 @@ impl Buffer {
}
indent_sizes
.into_iter()
.filter_map(|(row, (indent, ignore_empty_lines))| {
if ignore_empty_lines && snapshot.line_len(row) == 0 {
None
} else {
Some((row, indent))
}
})
.collect()
})
}
@@ -2067,6 +2084,7 @@ impl Buffer {
before_edit,
entries,
is_block_mode: matches!(mode, AutoindentMode::Block { .. }),
ignore_empty_lines: false,
}));
}
@@ -2094,6 +2112,30 @@ impl Buffer {
cx.notify();
}
pub fn autoindent_ranges<I, T>(&mut self, ranges: I, cx: &mut ModelContext<Self>)
where
I: IntoIterator<Item = Range<T>>,
T: ToOffset + Copy,
{
let before_edit = self.snapshot();
let entries = ranges
.into_iter()
.map(|range| AutoindentRequestEntry {
range: before_edit.anchor_before(range.start)..before_edit.anchor_after(range.end),
first_line_is_new: true,
indent_size: before_edit.language_indent_size_at(range.start, cx),
original_indent_column: None,
})
.collect();
self.autoindent_requests.push(Arc::new(AutoindentRequest {
before_edit,
entries,
is_block_mode: false,
ignore_empty_lines: true,
}));
self.request_autoindent(cx);
}
// Inserts newlines at the given position to create an empty line, returning the start of the new line.
// You can also request the insertion of empty lines above and below the line starting at the returned point.
pub fn insert_empty_line(

View File

@@ -24,7 +24,7 @@ pub struct Toolchain {
pub as_json: serde_json::Value,
}
#[async_trait(?Send)]
#[async_trait]
pub trait ToolchainLister: Send + Sync {
async fn list(
&self,

View File

@@ -536,7 +536,7 @@ fn env_priority(kind: Option<PythonEnvironmentKind>) -> usize {
}
}
#[async_trait(?Send)]
#[async_trait]
impl ToolchainLister for PythonToolchainProvider {
async fn list(
&self,

View File

@@ -417,6 +417,7 @@ fn render_markdown_paragraph(parsed: &MarkdownParagraph, cx: &mut RenderContext)
cx.with_common_p(div())
.children(render_markdown_text(parsed, cx))
.flex()
.flex_col()
.into_any_element()
}

View File

@@ -325,6 +325,13 @@ struct ExcerptBytes<'a> {
reversed: bool,
}
struct BufferEdit {
range: Range<usize>,
new_text: Arc<str>,
is_insertion: bool,
original_indent_column: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ExpandExcerptDirection {
Up,
@@ -525,57 +532,146 @@ impl MultiBuffer {
pub fn edit<I, S, T>(
&self,
edits: I,
mut autoindent_mode: Option<AutoindentMode>,
autoindent_mode: Option<AutoindentMode>,
cx: &mut ModelContext<Self>,
) where
I: IntoIterator<Item = (Range<S>, T)>,
S: ToOffset,
T: Into<Arc<str>>,
{
if self.read_only() {
return;
}
if self.buffers.borrow().is_empty() {
return;
}
let snapshot = self.read(cx);
let edits = edits.into_iter().map(|(range, new_text)| {
let mut range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot);
if range.start > range.end {
mem::swap(&mut range.start, &mut range.end);
let edits = edits
.into_iter()
.map(|(range, new_text)| {
let mut range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot);
if range.start > range.end {
mem::swap(&mut range.start, &mut range.end);
}
(range, new_text.into())
})
.collect::<Vec<_>>();
return edit_internal(self, snapshot, edits, autoindent_mode, cx);
// Non-generic part of edit, hoisted out to avoid blowing up LLVM IR.
fn edit_internal(
this: &MultiBuffer,
snapshot: Ref<MultiBufferSnapshot>,
edits: Vec<(Range<usize>, Arc<str>)>,
mut autoindent_mode: Option<AutoindentMode>,
cx: &mut ModelContext<MultiBuffer>,
) {
if this.read_only() || this.buffers.borrow().is_empty() {
return;
}
if let Some(buffer) = this.as_singleton() {
buffer.update(cx, |buffer, cx| {
buffer.edit(edits, autoindent_mode, cx);
});
cx.emit(Event::ExcerptsEdited {
ids: this.excerpt_ids(),
});
return;
}
let original_indent_columns = match &mut autoindent_mode {
Some(AutoindentMode::Block {
original_indent_columns,
}) => mem::take(original_indent_columns),
_ => Default::default(),
};
let (buffer_edits, edited_excerpt_ids) =
this.convert_edits_to_buffer_edits(edits, &snapshot, &original_indent_columns);
drop(snapshot);
for (buffer_id, mut edits) in buffer_edits {
edits.sort_unstable_by_key(|edit| edit.range.start);
this.buffers.borrow()[&buffer_id]
.buffer
.update(cx, |buffer, cx| {
let mut edits = edits.into_iter().peekable();
let mut insertions = Vec::new();
let mut original_indent_columns = Vec::new();
let mut deletions = Vec::new();
let empty_str: Arc<str> = Arc::default();
while let Some(BufferEdit {
mut range,
new_text,
mut is_insertion,
original_indent_column,
}) = edits.next()
{
while let Some(BufferEdit {
range: next_range,
is_insertion: next_is_insertion,
..
}) = edits.peek()
{
if range.end >= next_range.start {
range.end = cmp::max(next_range.end, range.end);
is_insertion |= *next_is_insertion;
edits.next();
} else {
break;
}
}
if is_insertion {
original_indent_columns.push(original_indent_column);
insertions.push((
buffer.anchor_before(range.start)
..buffer.anchor_before(range.end),
new_text.clone(),
));
} else if !range.is_empty() {
deletions.push((
buffer.anchor_before(range.start)
..buffer.anchor_before(range.end),
empty_str.clone(),
));
}
}
let deletion_autoindent_mode =
if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
Some(AutoindentMode::Block {
original_indent_columns: Default::default(),
})
} else {
autoindent_mode.clone()
};
let insertion_autoindent_mode =
if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
Some(AutoindentMode::Block {
original_indent_columns,
})
} else {
autoindent_mode.clone()
};
buffer.edit(deletions, deletion_autoindent_mode, cx);
buffer.edit(insertions, insertion_autoindent_mode, cx);
})
}
(range, new_text)
});
if let Some(buffer) = self.as_singleton() {
buffer.update(cx, |buffer, cx| {
buffer.edit(edits, autoindent_mode, cx);
});
cx.emit(Event::ExcerptsEdited {
ids: self.excerpt_ids(),
ids: edited_excerpt_ids,
});
return;
}
}
let original_indent_columns = match &mut autoindent_mode {
Some(AutoindentMode::Block {
original_indent_columns,
}) => mem::take(original_indent_columns),
_ => Default::default(),
};
struct BufferEdit {
range: Range<usize>,
new_text: Arc<str>,
is_insertion: bool,
original_indent_column: u32,
}
fn convert_edits_to_buffer_edits(
&self,
edits: Vec<(Range<usize>, Arc<str>)>,
snapshot: &MultiBufferSnapshot,
original_indent_columns: &[u32],
) -> (HashMap<BufferId, Vec<BufferEdit>>, Vec<ExcerptId>) {
let mut buffer_edits: HashMap<BufferId, Vec<BufferEdit>> = Default::default();
let mut edited_excerpt_ids = Vec::new();
let mut cursor = snapshot.excerpts.cursor::<usize>(&());
for (ix, (range, new_text)) in edits.enumerate() {
let new_text: Arc<str> = new_text.into();
for (ix, (range, new_text)) in edits.into_iter().enumerate() {
let original_indent_column = original_indent_columns.get(ix).copied().unwrap_or(0);
cursor.seek(&range.start, Bias::Right, &());
if cursor.item().is_none() && range.start == *cursor.start() {
@@ -667,84 +763,71 @@ impl MultiBuffer {
}
}
}
(buffer_edits, edited_excerpt_ids)
}
drop(cursor);
drop(snapshot);
// Non-generic part of edit, hoisted out to avoid blowing up LLVM IR.
fn tail(
pub fn autoindent_ranges<I, S>(&self, ranges: I, cx: &mut ModelContext<Self>)
where
I: IntoIterator<Item = Range<S>>,
S: ToOffset,
{
let snapshot = self.read(cx);
let empty = Arc::<str>::from("");
let edits = ranges
.into_iter()
.map(|range| {
let mut range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot);
if range.start > range.end {
mem::swap(&mut range.start, &mut range.end);
}
(range, empty.clone())
})
.collect::<Vec<_>>();
return autoindent_ranges_internal(self, snapshot, edits, cx);
fn autoindent_ranges_internal(
this: &MultiBuffer,
buffer_edits: HashMap<BufferId, Vec<BufferEdit>>,
autoindent_mode: Option<AutoindentMode>,
edited_excerpt_ids: Vec<ExcerptId>,
snapshot: Ref<MultiBufferSnapshot>,
edits: Vec<(Range<usize>, Arc<str>)>,
cx: &mut ModelContext<MultiBuffer>,
) {
if this.read_only() || this.buffers.borrow().is_empty() {
return;
}
if let Some(buffer) = this.as_singleton() {
buffer.update(cx, |buffer, cx| {
buffer.autoindent_ranges(edits.into_iter().map(|e| e.0), cx);
});
cx.emit(Event::ExcerptsEdited {
ids: this.excerpt_ids(),
});
return;
}
let (buffer_edits, edited_excerpt_ids) =
this.convert_edits_to_buffer_edits(edits, &snapshot, &[]);
drop(snapshot);
for (buffer_id, mut edits) in buffer_edits {
edits.sort_unstable_by_key(|edit| edit.range.start);
let mut ranges: Vec<Range<usize>> = Vec::new();
for edit in edits {
if let Some(last_range) = ranges.last_mut() {
if edit.range.start <= last_range.end {
last_range.end = last_range.end.max(edit.range.end);
continue;
}
}
ranges.push(edit.range);
}
this.buffers.borrow()[&buffer_id]
.buffer
.update(cx, |buffer, cx| {
let mut edits = edits.into_iter().peekable();
let mut insertions = Vec::new();
let mut original_indent_columns = Vec::new();
let mut deletions = Vec::new();
let empty_str: Arc<str> = Arc::default();
while let Some(BufferEdit {
mut range,
new_text,
mut is_insertion,
original_indent_column,
}) = edits.next()
{
while let Some(BufferEdit {
range: next_range,
is_insertion: next_is_insertion,
..
}) = edits.peek()
{
if range.end >= next_range.start {
range.end = cmp::max(next_range.end, range.end);
is_insertion |= *next_is_insertion;
edits.next();
} else {
break;
}
}
if is_insertion {
original_indent_columns.push(original_indent_column);
insertions.push((
buffer.anchor_before(range.start)
..buffer.anchor_before(range.end),
new_text.clone(),
));
} else if !range.is_empty() {
deletions.push((
buffer.anchor_before(range.start)
..buffer.anchor_before(range.end),
empty_str.clone(),
));
}
}
let deletion_autoindent_mode =
if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
Some(AutoindentMode::Block {
original_indent_columns: Default::default(),
})
} else {
autoindent_mode.clone()
};
let insertion_autoindent_mode =
if let Some(AutoindentMode::Block { .. }) = autoindent_mode {
Some(AutoindentMode::Block {
original_indent_columns,
})
} else {
autoindent_mode.clone()
};
buffer.edit(deletions, deletion_autoindent_mode, cx);
buffer.edit(insertions, insertion_autoindent_mode, cx);
buffer.autoindent_ranges(ranges, cx);
})
}
@@ -752,7 +835,6 @@ impl MultiBuffer {
ids: edited_excerpt_ids,
});
}
tail(self, buffer_edits, autoindent_mode, edited_excerpt_ids, cx);
}
// Inserts newlines at the given position to create an empty line, returning the start of the new line.

View File

@@ -311,12 +311,14 @@ impl LocalToolchainStore {
})
.ok()?
.await;
let language = registry.language_for_name(&language_name.0).await.ok()?;
let toolchains = language
.toolchain_lister()?
.list(root.to_path_buf(), project_env)
.await;
Some(toolchains)
cx.background_executor()
.spawn(async move {
let language = registry.language_for_name(&language_name.0).await.ok()?;
let toolchains = language.toolchain_lister()?;
Some(toolchains.list(root.to_path_buf(), project_env).await)
})
.await
})
}
pub(crate) fn active_toolchain(

View File

@@ -9,9 +9,10 @@ use ui::ViewContext;
pub(crate) enum IndentDirection {
In,
Out,
Auto,
}
actions!(vim, [Indent, Outdent,]);
actions!(vim, [Indent, Outdent, AutoIndent]);
pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
Vim::action(editor, cx, |vim, _: &Indent, cx| {
@@ -49,6 +50,24 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
vim.switch_mode(Mode::Normal, true, cx)
}
});
Vim::action(editor, cx, |vim, _: &AutoIndent, cx| {
vim.record_current_action(cx);
let count = Vim::take_count(cx).unwrap_or(1);
vim.store_visual_marks(cx);
vim.update_editor(cx, |vim, editor, cx| {
editor.transact(cx, |editor, cx| {
let original_positions = vim.save_selection_starts(editor, cx);
for _ in 0..count {
editor.autoindent(&Default::default(), cx);
}
vim.restore_selection_cursors(editor, cx, original_positions);
});
});
if vim.mode.is_visual() {
vim.switch_mode(Mode::Normal, true, cx)
}
});
}
impl Vim {
@@ -71,10 +90,10 @@ impl Vim {
motion.expand_selection(map, selection, times, false, &text_layout_details);
});
});
if dir == IndentDirection::In {
editor.indent(&Default::default(), cx);
} else {
editor.outdent(&Default::default(), cx);
match dir {
IndentDirection::In => editor.indent(&Default::default(), cx),
IndentDirection::Out => editor.outdent(&Default::default(), cx),
IndentDirection::Auto => editor.autoindent(&Default::default(), cx),
}
editor.change_selections(None, cx, |s| {
s.move_with(|map, selection| {
@@ -104,10 +123,10 @@ impl Vim {
object.expand_selection(map, selection, around);
});
});
if dir == IndentDirection::In {
editor.indent(&Default::default(), cx);
} else {
editor.outdent(&Default::default(), cx);
match dir {
IndentDirection::In => editor.indent(&Default::default(), cx),
IndentDirection::Out => editor.outdent(&Default::default(), cx),
IndentDirection::Auto => editor.autoindent(&Default::default(), cx),
}
editor.change_selections(None, cx, |s| {
s.move_with(|map, selection| {
@@ -122,7 +141,11 @@ impl Vim {
#[cfg(test)]
mod test {
use crate::test::NeovimBackedTestContext;
use crate::{
state::Mode,
test::{NeovimBackedTestContext, VimTestContext},
};
use indoc::indoc;
#[gpui::test]
async fn test_indent_gv(cx: &mut gpui::TestAppContext) {
@@ -135,4 +158,46 @@ mod test {
.await
.assert_eq("« hello\n ˇ» world\n");
}
#[gpui::test]
async fn test_autoindent_op(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state(
indoc!(
"
fn a() {
b();
c();
d();
ˇe();
f();
g();
}
"
),
Mode::Normal,
);
cx.simulate_keystrokes("= a p");
cx.assert_state(
indoc!(
"
fn a() {
b();
c();
d();
ˇe();
f();
g();
}
"
),
Mode::Normal,
);
}
}

View File

@@ -170,6 +170,9 @@ impl Vim {
Some(Operator::Indent) => self.indent_motion(motion, times, IndentDirection::In, cx),
Some(Operator::Rewrap) => self.rewrap_motion(motion, times, cx),
Some(Operator::Outdent) => self.indent_motion(motion, times, IndentDirection::Out, cx),
Some(Operator::AutoIndent) => {
self.indent_motion(motion, times, IndentDirection::Auto, cx)
}
Some(Operator::Lowercase) => {
self.change_case_motion(motion, times, CaseTarget::Lowercase, cx)
}
@@ -202,6 +205,9 @@ impl Vim {
Some(Operator::Outdent) => {
self.indent_object(object, around, IndentDirection::Out, cx)
}
Some(Operator::AutoIndent) => {
self.indent_object(object, around, IndentDirection::Auto, cx)
}
Some(Operator::Rewrap) => self.rewrap_object(object, around, cx),
Some(Operator::Lowercase) => {
self.change_case_object(object, around, CaseTarget::Lowercase, cx)

View File

@@ -72,6 +72,7 @@ pub enum Operator {
Jump { line: bool },
Indent,
Outdent,
AutoIndent,
Rewrap,
Lowercase,
Uppercase,
@@ -465,6 +466,7 @@ impl Operator {
Operator::Jump { line: true } => "'",
Operator::Jump { line: false } => "`",
Operator::Indent => ">",
Operator::AutoIndent => "eq",
Operator::Rewrap => "gq",
Operator::Outdent => "<",
Operator::Uppercase => "gU",
@@ -510,6 +512,7 @@ impl Operator {
| Operator::Rewrap
| Operator::Indent
| Operator::Outdent
| Operator::AutoIndent
| Operator::Lowercase
| Operator::Uppercase
| Operator::Object { .. }

View File

@@ -470,6 +470,7 @@ impl Vim {
| Operator::Replace
| Operator::Indent
| Operator::Outdent
| Operator::AutoIndent
| Operator::Lowercase
| Operator::Uppercase
| Operator::OppositeCase

View File

@@ -1870,7 +1870,7 @@ impl Pane {
fn unpin_tab_at(&mut self, ix: usize, cx: &mut ViewContext<'_, Self>) {
maybe!({
let pane = cx.view().clone();
self.pinned_tab_count = self.pinned_tab_count.checked_sub(1).unwrap();
self.pinned_tab_count = self.pinned_tab_count.checked_sub(1)?;
let destination_index = self.pinned_tab_count;
let id = self.item_for_index(ix)?.item_id();

View File

@@ -473,7 +473,7 @@ impl SerializedPane {
})?;
}
pane.update(cx, |pane, _| {
pane.set_pinned_count(self.pinned_count);
pane.set_pinned_count(self.pinned_count.min(items.len()));
})?;
anyhow::Ok(items)

View File

@@ -2,7 +2,7 @@
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.164.0"
version = "0.164.2"
publish = false
license = "GPL-3.0-or-later"
authors = ["Zed Team <hi@zed.dev>"]

View File

@@ -1 +1 @@
dev
preview