Compare commits
14 Commits
add_subwor
...
add_join_l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a6f97a19c | ||
|
|
4737458b2f | ||
|
|
eabefea234 | ||
|
|
b6280f01dc | ||
|
|
031774cc61 | ||
|
|
0e95ea8888 | ||
|
|
b045b33743 | ||
|
|
95911aaa14 | ||
|
|
1a9f0a647a | ||
|
|
45c714110e | ||
|
|
4c84600630 | ||
|
|
564936e1fe | ||
|
|
3d4e0780c4 | ||
|
|
44a46e3713 |
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -2484,7 +2484,6 @@ dependencies = [
|
||||
"exec",
|
||||
"fork",
|
||||
"ipc-channel",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
"plist",
|
||||
@@ -2511,7 +2510,6 @@ dependencies = [
|
||||
"gpui",
|
||||
"http_client",
|
||||
"log",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
"postage",
|
||||
@@ -10266,7 +10264,6 @@ name = "release_channel"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"gpui",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -408,7 +408,6 @@ nanoid = "0.4"
|
||||
nbformat = { version = "0.9.0" }
|
||||
nix = "0.29"
|
||||
num-format = "0.4.4"
|
||||
once_cell = "1.19.0"
|
||||
ordered-float = "2.1.1"
|
||||
palette = { version = "0.7.5", default-features = false, features = ["std"] }
|
||||
parking_lot = "0.12.1"
|
||||
|
||||
@@ -197,6 +197,7 @@
|
||||
"d": ["vim::PushOperator", "Delete"],
|
||||
"shift-d": "vim::DeleteToEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"g shift-j": "vim::JoinLinesNoWhitespace",
|
||||
"y": ["vim::PushOperator", "Yank"],
|
||||
"shift-y": "vim::YankLine",
|
||||
"i": "vim::InsertBefore",
|
||||
@@ -278,6 +279,7 @@
|
||||
"g shift-i": "vim::VisualInsertFirstNonWhiteSpace",
|
||||
"g shift-a": "vim::VisualInsertEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"g shift-j": "vim::JoinLinesNoWhitespace",
|
||||
"r": ["vim::PushOperator", "Replace"],
|
||||
"ctrl-c": ["vim::SwitchMode", "Normal"],
|
||||
"escape": ["vim::SwitchMode", "Normal"],
|
||||
@@ -389,9 +391,6 @@
|
||||
"bindings": {
|
||||
"w": "vim::Word",
|
||||
"shift-w": ["vim::Word", { "ignorePunctuation": true }],
|
||||
// Subword TextObject
|
||||
// "w": "vim::Subword",
|
||||
// "shift-w": ["vim::Subword", { "ignorePunctuation": true }],
|
||||
"t": "vim::Tag",
|
||||
"s": "vim::Sentence",
|
||||
"p": "vim::Paragraph",
|
||||
|
||||
@@ -3,9 +3,9 @@ use std::sync::Arc;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use collections::HashMap;
|
||||
use gpui::{
|
||||
list, AbsoluteLength, AnyElement, AppContext, CornersRefinement, DefiniteLength,
|
||||
EdgesRefinement, Empty, Length, ListAlignment, ListState, Model, StyleRefinement, Subscription,
|
||||
TextStyleRefinement, View, WeakView,
|
||||
list, AbsoluteLength, AnyElement, AppContext, DefiniteLength, EdgesRefinement, Empty, Length,
|
||||
ListAlignment, ListState, Model, StyleRefinement, Subscription, TextStyleRefinement, View,
|
||||
WeakView,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::Role;
|
||||
@@ -108,7 +108,7 @@ impl ActiveThread {
|
||||
selection_background_color: cx.theme().players().local().selection,
|
||||
code_block: StyleRefinement {
|
||||
margin: EdgesRefinement {
|
||||
top: Some(Length::Definite(rems(0.5).into())),
|
||||
top: Some(Length::Definite(rems(1.0).into())),
|
||||
left: Some(Length::Definite(rems(0.).into())),
|
||||
right: Some(Length::Definite(rems(0.).into())),
|
||||
bottom: Some(Length::Definite(rems(1.).into())),
|
||||
@@ -120,19 +120,13 @@ impl ActiveThread {
|
||||
bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
|
||||
},
|
||||
background: Some(colors.editor_foreground.opacity(0.01).into()),
|
||||
border_color: Some(colors.border_variant.opacity(0.25)),
|
||||
border_color: Some(colors.border_variant.opacity(0.3)),
|
||||
border_widths: EdgesRefinement {
|
||||
top: Some(AbsoluteLength::Pixels(Pixels(1.0))),
|
||||
left: Some(AbsoluteLength::Pixels(Pixels(1.))),
|
||||
right: Some(AbsoluteLength::Pixels(Pixels(1.))),
|
||||
bottom: Some(AbsoluteLength::Pixels(Pixels(1.))),
|
||||
},
|
||||
corner_radii: CornersRefinement {
|
||||
top_left: Some(AbsoluteLength::Pixels(Pixels(2.))),
|
||||
top_right: Some(AbsoluteLength::Pixels(Pixels(2.))),
|
||||
bottom_right: Some(AbsoluteLength::Pixels(Pixels(2.))),
|
||||
bottom_left: Some(AbsoluteLength::Pixels(Pixels(2.))),
|
||||
},
|
||||
text: Some(TextStyleRefinement {
|
||||
font_family: Some(theme_settings.buffer_font.family.clone()),
|
||||
font_size: Some(buffer_font_size.into()),
|
||||
@@ -272,15 +266,20 @@ impl ActiveThread {
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(v_flex().p_2p5().text_ui(cx).child(markdown.clone()))
|
||||
.child(div().p_2p5().text_ui(cx).child(markdown.clone()))
|
||||
.when_some(context, |parent, context| {
|
||||
parent.child(
|
||||
h_flex().flex_wrap().gap_1().p_1p5().children(
|
||||
context
|
||||
.iter()
|
||||
.map(|context| ContextPill::new(context.clone())),
|
||||
),
|
||||
)
|
||||
if !context.is_empty() {
|
||||
parent.child(
|
||||
h_flex()
|
||||
.flex_wrap()
|
||||
.gap_1()
|
||||
.px_1p5()
|
||||
.pb_1p5()
|
||||
.children(context.iter().map(|c| ContextPill::new(c.clone()))),
|
||||
)
|
||||
} else {
|
||||
parent
|
||||
}
|
||||
}),
|
||||
)
|
||||
.into_any()
|
||||
|
||||
@@ -21,7 +21,7 @@ pub struct Context {
|
||||
pub text: SharedString,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ContextKind {
|
||||
File,
|
||||
Directory,
|
||||
|
||||
@@ -220,67 +220,73 @@ impl Render for MessageEditor {
|
||||
.p_2()
|
||||
.bg(bg_color)
|
||||
.child(self.context_strip.clone())
|
||||
.child({
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let text_style = TextStyle {
|
||||
color: cx.theme().colors().editor_foreground,
|
||||
font_family: settings.ui_font.family.clone(),
|
||||
font_features: settings.ui_font.features.clone(),
|
||||
font_size: font_size.into(),
|
||||
font_weight: settings.ui_font.weight,
|
||||
line_height: line_height.into(),
|
||||
..Default::default()
|
||||
};
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.child({
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let text_style = TextStyle {
|
||||
color: cx.theme().colors().text,
|
||||
font_family: settings.ui_font.family.clone(),
|
||||
font_features: settings.ui_font.features.clone(),
|
||||
font_size: font_size.into(),
|
||||
font_weight: settings.ui_font.weight,
|
||||
line_height: line_height.into(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
EditorElement::new(
|
||||
&self.editor,
|
||||
EditorStyle {
|
||||
background: bg_color,
|
||||
local_player: cx.theme().players().local(),
|
||||
text: text_style,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
})
|
||||
.child(
|
||||
PopoverMenu::new("inline-context-picker")
|
||||
.menu(move |_cx| Some(inline_context_picker.clone()))
|
||||
.attach(gpui::Corner::TopLeft)
|
||||
.anchor(gpui::Corner::BottomLeft)
|
||||
.offset(gpui::Point {
|
||||
x: px(0.0),
|
||||
y: px(-16.0),
|
||||
EditorElement::new(
|
||||
&self.editor,
|
||||
EditorStyle {
|
||||
background: bg_color,
|
||||
local_player: cx.theme().players().local(),
|
||||
text: text_style,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
})
|
||||
.with_handle(self.inline_context_picker_menu_handle.clone()),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(SwitchWithLabel::new(
|
||||
"use-tools",
|
||||
Label::new("Tools"),
|
||||
self.use_tools.into(),
|
||||
cx.listener(|this, selection, _cx| {
|
||||
this.use_tools = match selection {
|
||||
ToggleState::Selected => true,
|
||||
ToggleState::Unselected | ToggleState::Indeterminate => false,
|
||||
};
|
||||
}),
|
||||
))
|
||||
.child(
|
||||
h_flex().gap_1().child(self.model_selector.clone()).child(
|
||||
ButtonLike::new("chat")
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.child(Label::new("Submit"))
|
||||
.children(
|
||||
KeyBinding::for_action_in(&Chat, &focus_handle, cx)
|
||||
.map(|binding| binding.into_any_element()),
|
||||
)
|
||||
.on_click(move |_event, cx| {
|
||||
focus_handle.dispatch_action(&Chat, cx);
|
||||
PopoverMenu::new("inline-context-picker")
|
||||
.menu(move |_cx| Some(inline_context_picker.clone()))
|
||||
.attach(gpui::Corner::TopLeft)
|
||||
.anchor(gpui::Corner::BottomLeft)
|
||||
.offset(gpui::Point {
|
||||
x: px(0.0),
|
||||
y: px(-16.0),
|
||||
})
|
||||
.with_handle(self.inline_context_picker_menu_handle.clone()),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(SwitchWithLabel::new(
|
||||
"use-tools",
|
||||
Label::new("Tools"),
|
||||
self.use_tools.into(),
|
||||
cx.listener(|this, selection, _cx| {
|
||||
this.use_tools = match selection {
|
||||
ToggleState::Selected => true,
|
||||
ToggleState::Unselected | ToggleState::Indeterminate => {
|
||||
false
|
||||
}
|
||||
};
|
||||
}),
|
||||
),
|
||||
))
|
||||
.child(
|
||||
h_flex().gap_1().child(self.model_selector.clone()).child(
|
||||
ButtonLike::new("chat")
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.child(Label::new("Submit"))
|
||||
.children(
|
||||
KeyBinding::for_action_in(&Chat, &focus_handle, cx)
|
||||
.map(|binding| binding.into_any_element()),
|
||||
)
|
||||
.on_click(move |_event, cx| {
|
||||
focus_handle.dispatch_action(&Chat, cx);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::rc::Rc;
|
||||
use gpui::ClickEvent;
|
||||
use ui::{prelude::*, IconButtonShape};
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::context::{Context, ContextKind};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ContextPill {
|
||||
@@ -27,15 +27,28 @@ impl ContextPill {
|
||||
|
||||
impl RenderOnce for ContextPill {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let padding_right = if self.on_remove.is_some() {
|
||||
px(2.)
|
||||
} else {
|
||||
px(4.)
|
||||
};
|
||||
let icon = match self.context.kind {
|
||||
ContextKind::File => IconName::File,
|
||||
ContextKind::Directory => IconName::Folder,
|
||||
ContextKind::FetchedUrl => IconName::Globe,
|
||||
ContextKind::Thread => IconName::MessageCircle,
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.pl_1p5()
|
||||
.pr_0p5()
|
||||
.pl_1()
|
||||
.pr(padding_right)
|
||||
.pb(px(1.))
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border.opacity(0.5))
|
||||
.bg(cx.theme().colors().element_background)
|
||||
.rounded_md()
|
||||
.child(Icon::new(icon).size(IconSize::XSmall).color(Color::Muted))
|
||||
.child(Label::new(self.context.name.clone()).size(LabelSize::Small))
|
||||
.when_some(self.on_remove, |parent, on_remove| {
|
||||
parent.child(
|
||||
|
||||
@@ -25,7 +25,6 @@ anyhow.workspace = true
|
||||
clap.workspace = true
|
||||
collections.workspace = true
|
||||
ipc-channel = "0.19"
|
||||
once_cell.workspace = true
|
||||
parking_lot.workspace = true
|
||||
paths.workspace = true
|
||||
release_channel.workspace = true
|
||||
|
||||
@@ -277,6 +277,7 @@ mod linux {
|
||||
os::unix::net::{SocketAddr, UnixDatagram},
|
||||
path::{Path, PathBuf},
|
||||
process::{self, ExitStatus},
|
||||
sync::LazyLock,
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
@@ -284,12 +285,11 @@ mod linux {
|
||||
use anyhow::anyhow;
|
||||
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
|
||||
use fork::Fork;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{Detect, InstalledApp};
|
||||
|
||||
static RELEASE_CHANNEL: Lazy<String> =
|
||||
Lazy::new(|| include_str!("../../zed/RELEASE_CHANNEL").trim().to_string());
|
||||
static RELEASE_CHANNEL: LazyLock<String> =
|
||||
LazyLock::new(|| include_str!("../../zed/RELEASE_CHANNEL").trim().to_string());
|
||||
|
||||
struct App(PathBuf);
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ futures.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
log.workspace = true
|
||||
once_cell.workspace = true
|
||||
paths.workspace = true
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
|
||||
@@ -8,7 +8,6 @@ use futures::channel::mpsc;
|
||||
use futures::{Future, StreamExt};
|
||||
use gpui::{AppContext, BackgroundExecutor, Task};
|
||||
use http_client::{self, AsyncBody, HttpClient, HttpClientWithUrl, Method, Request};
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::Mutex;
|
||||
use release_channel::ReleaseChannel;
|
||||
use settings::{Settings, SettingsStore};
|
||||
@@ -16,7 +15,12 @@ use sha2::{Digest, Sha256};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::time::Instant;
|
||||
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
|
||||
use std::{
|
||||
env, mem,
|
||||
path::PathBuf,
|
||||
sync::{Arc, LazyLock},
|
||||
time::Duration,
|
||||
};
|
||||
use telemetry_events::{
|
||||
AppEvent, AssistantEvent, CallEvent, EditEvent, Event, EventRequestBody, EventWrapper,
|
||||
InlineCompletionEvent,
|
||||
@@ -84,7 +88,7 @@ const FLUSH_INTERVAL: Duration = Duration::from_secs(1);
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
const FLUSH_INTERVAL: Duration = Duration::from_secs(60 * 5);
|
||||
static ZED_CLIENT_CHECKSUM_SEED: Lazy<Option<Vec<u8>>> = Lazy::new(|| {
|
||||
static ZED_CLIENT_CHECKSUM_SEED: LazyLock<Option<Vec<u8>>> = LazyLock::new(|| {
|
||||
option_env!("ZED_CLIENT_CHECKSUM_SEED")
|
||||
.map(|s| s.as_bytes().into())
|
||||
.or_else(|| {
|
||||
|
||||
@@ -166,7 +166,7 @@ impl ProjectDiagnosticsEditor {
|
||||
let excerpts = cx.new_model(|cx| MultiBuffer::new(project_handle.read(cx).capability()));
|
||||
let editor = cx.new_view(|cx| {
|
||||
let mut editor =
|
||||
Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), false, cx);
|
||||
Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), true, cx);
|
||||
editor.set_vertical_scroll_margin(5, cx);
|
||||
editor
|
||||
});
|
||||
|
||||
@@ -167,10 +167,10 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(DisplayRow(0), FILE_HEADER.into()),
|
||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(15), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(16), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(25), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(16), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(18), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(27), EXCERPT_HEADER.into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -184,6 +184,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"\n", // expand
|
||||
" let x = vec![];\n",
|
||||
" let y = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
@@ -195,6 +196,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
" c(y);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" d(x);\n",
|
||||
"\n", // expand
|
||||
"\n", // context ellipsis
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
@@ -206,11 +208,13 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
" a(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" b(y);\n",
|
||||
"\n", // expand
|
||||
"\n", // context ellipsis
|
||||
" c(y);\n",
|
||||
" d(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
"}"
|
||||
"}",
|
||||
"\n", // expand
|
||||
)
|
||||
);
|
||||
|
||||
@@ -218,7 +222,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
editor.update(cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.selections.display_ranges(cx),
|
||||
[DisplayPoint::new(DisplayRow(12), 6)..DisplayPoint::new(DisplayRow(12), 6)]
|
||||
[DisplayPoint::new(DisplayRow(13), 6)..DisplayPoint::new(DisplayRow(13), 6)]
|
||||
);
|
||||
});
|
||||
|
||||
@@ -253,12 +257,12 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(DisplayRow(0), FILE_HEADER.into()),
|
||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(7), FILE_HEADER.into()),
|
||||
(DisplayRow(9), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(22), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(23), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(32), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(8), FILE_HEADER.into()),
|
||||
(DisplayRow(12), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(25), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(27), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(36), EXCERPT_HEADER.into()),
|
||||
]
|
||||
);
|
||||
|
||||
@@ -273,6 +277,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"\n", // expand
|
||||
"const a: i32 = 'a';\n",
|
||||
"\n", // supporting diagnostic
|
||||
"const b: i32 = c;\n",
|
||||
@@ -284,6 +289,8 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"\n", // expand
|
||||
"\n", // expand
|
||||
" let x = vec![];\n",
|
||||
" let y = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
@@ -299,6 +306,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // filename
|
||||
"\n", // expand
|
||||
"fn main() {\n",
|
||||
" let x = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
@@ -306,11 +314,13 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
" a(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" b(y);\n",
|
||||
"\n", // expand
|
||||
"\n", // context ellipsis
|
||||
" c(y);\n",
|
||||
" d(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
"}"
|
||||
"}",
|
||||
"\n", // expand
|
||||
)
|
||||
);
|
||||
|
||||
@@ -318,7 +328,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
editor.update(cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.selections.display_ranges(cx),
|
||||
[DisplayPoint::new(DisplayRow(19), 6)..DisplayPoint::new(DisplayRow(19), 6)]
|
||||
[DisplayPoint::new(DisplayRow(22), 6)..DisplayPoint::new(DisplayRow(22), 6)]
|
||||
);
|
||||
});
|
||||
|
||||
@@ -366,14 +376,14 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(DisplayRow(0), FILE_HEADER.into()),
|
||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(7), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(8), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(13), FILE_HEADER.into()),
|
||||
(DisplayRow(15), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(28), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(29), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(38), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(8), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(10), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(15), FILE_HEADER.into()),
|
||||
(DisplayRow(19), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(32), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(34), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(43), EXCERPT_HEADER.into()),
|
||||
]
|
||||
);
|
||||
|
||||
@@ -388,6 +398,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"\n", // expand
|
||||
"const a: i32 = 'a';\n",
|
||||
"\n", // supporting diagnostic
|
||||
"const b: i32 = c;\n",
|
||||
@@ -395,6 +406,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"\n", // expand
|
||||
"const a: i32 = 'a';\n",
|
||||
"const b: i32 = c;\n",
|
||||
"\n", // supporting diagnostic
|
||||
@@ -406,6 +418,8 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"\n", // expand
|
||||
"\n", // expand
|
||||
" let x = vec![];\n",
|
||||
" let y = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
@@ -421,6 +435,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // filename
|
||||
"\n", // expand
|
||||
"fn main() {\n",
|
||||
" let x = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
@@ -428,11 +443,13 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||
" a(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" b(y);\n",
|
||||
"\n", // expand
|
||||
"\n", // context ellipsis
|
||||
" c(y);\n",
|
||||
" d(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
"}"
|
||||
"}",
|
||||
"\n", // expand
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -513,7 +530,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(DisplayRow(0), FILE_HEADER.into()),
|
||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -524,8 +541,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"\n", // expand
|
||||
"a();\n", //
|
||||
"b();",
|
||||
"b();", "\n", // expand
|
||||
)
|
||||
);
|
||||
|
||||
@@ -561,9 +579,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(DisplayRow(0), FILE_HEADER.into()),
|
||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(6), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(7), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(7), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(9), DIAGNOSTIC_HEADER.into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -574,8 +592,10 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"\n", // expand
|
||||
"a();\n", // location
|
||||
"b();\n", //
|
||||
"\n", // expand
|
||||
"\n", // collapsed context
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
@@ -583,6 +603,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
"a();\n", // context
|
||||
"b();\n", //
|
||||
"c();", // context
|
||||
"\n", // expand
|
||||
)
|
||||
);
|
||||
|
||||
@@ -629,9 +650,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(DisplayRow(0), FILE_HEADER.into()),
|
||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(7), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(8), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(8), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(10), DIAGNOSTIC_HEADER.into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -642,9 +663,11 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"\n", // expand
|
||||
"a();\n", // location
|
||||
"b();\n", //
|
||||
"c();\n", // context
|
||||
"\n", // expand
|
||||
"\n", // collapsed context
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
@@ -652,6 +675,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
"b();\n", // context
|
||||
"c();\n", //
|
||||
"d();", // context
|
||||
"\n", // expand
|
||||
)
|
||||
);
|
||||
|
||||
@@ -687,9 +711,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(DisplayRow(0), FILE_HEADER.into()),
|
||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(7), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(8), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(8), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(10), DIAGNOSTIC_HEADER.into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -700,9 +724,11 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"\n", // expand
|
||||
"b();\n", // location
|
||||
"c();\n", //
|
||||
"d();\n", // context
|
||||
"\n", // expand
|
||||
"\n", // collapsed context
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
@@ -710,6 +736,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||
"c();\n", // context
|
||||
"d();\n", //
|
||||
"e();", // context
|
||||
"\n", // expand
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ impl_actions!(
|
||||
ToggleCodeActions,
|
||||
ToggleComments,
|
||||
UnfoldAt,
|
||||
FoldAtLevel
|
||||
FoldAtLevel,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ use crate::{
|
||||
pub use block_map::{
|
||||
Block, BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap,
|
||||
BlockPlacement, BlockPoint, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
|
||||
StickyHeaderExcerpt,
|
||||
};
|
||||
use block_map::{BlockRow, BlockSnapshot};
|
||||
use collections::{HashMap, HashSet};
|
||||
@@ -1105,6 +1106,10 @@ impl DisplaySnapshot {
|
||||
.map(|(row, block)| (DisplayRow(row), block))
|
||||
}
|
||||
|
||||
pub fn sticky_header_excerpt(&self, row: DisplayRow) -> Option<StickyHeaderExcerpt<'_>> {
|
||||
self.block_snapshot.sticky_header_excerpt(row.0)
|
||||
}
|
||||
|
||||
pub fn block_for_id(&self, id: BlockId) -> Option<Block> {
|
||||
self.block_snapshot.block_for_id(id)
|
||||
}
|
||||
|
||||
@@ -1411,6 +1411,66 @@ impl BlockSnapshot {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn sticky_header_excerpt(&self, top_row: u32) -> Option<StickyHeaderExcerpt<'_>> {
|
||||
let mut cursor = self.transforms.cursor::<BlockRow>(&());
|
||||
cursor.seek(&BlockRow(top_row), Bias::Left, &());
|
||||
|
||||
while let Some(transform) = cursor.item() {
|
||||
let start = cursor.start().0;
|
||||
let end = cursor.end(&()).0;
|
||||
|
||||
match &transform.block {
|
||||
Some(Block::ExcerptBoundary {
|
||||
prev_excerpt,
|
||||
next_excerpt,
|
||||
starts_new_buffer,
|
||||
show_excerpt_controls,
|
||||
..
|
||||
}) => {
|
||||
let matches_start = if *show_excerpt_controls && prev_excerpt.is_some() {
|
||||
start < top_row
|
||||
} else {
|
||||
start <= top_row
|
||||
};
|
||||
|
||||
if matches_start && top_row <= end {
|
||||
return next_excerpt.as_ref().map(|excerpt| StickyHeaderExcerpt {
|
||||
next_buffer_row: None,
|
||||
next_excerpt_controls_present: *show_excerpt_controls,
|
||||
excerpt,
|
||||
});
|
||||
}
|
||||
|
||||
let next_buffer_row = if *starts_new_buffer { Some(end) } else { None };
|
||||
|
||||
return prev_excerpt.as_ref().map(|excerpt| StickyHeaderExcerpt {
|
||||
excerpt,
|
||||
next_buffer_row,
|
||||
next_excerpt_controls_present: *show_excerpt_controls,
|
||||
});
|
||||
}
|
||||
Some(Block::FoldedBuffer {
|
||||
prev_excerpt: Some(excerpt),
|
||||
..
|
||||
}) if top_row <= start => {
|
||||
return Some(StickyHeaderExcerpt {
|
||||
next_buffer_row: Some(end),
|
||||
next_excerpt_controls_present: false,
|
||||
excerpt,
|
||||
});
|
||||
}
|
||||
Some(Block::FoldedBuffer { .. }) | Some(Block::Custom(_)) | None => {}
|
||||
}
|
||||
|
||||
// This is needed to iterate past None / FoldedBuffer / Custom blocks. For FoldedBuffer,
|
||||
// if scrolled slightly past the header of a folded block, the next block is needed for
|
||||
// the sticky header.
|
||||
cursor.next(&());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
|
||||
let buffer = self.wrap_snapshot.buffer_snapshot();
|
||||
let wrap_point = match block_id {
|
||||
@@ -1694,6 +1754,12 @@ impl<'a> BlockChunks<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StickyHeaderExcerpt<'a> {
|
||||
pub excerpt: &'a ExcerptInfo,
|
||||
pub next_excerpt_controls_present: bool,
|
||||
pub next_buffer_row: Option<u32>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for BlockChunks<'a> {
|
||||
type Item = Chunk<'a>;
|
||||
|
||||
|
||||
@@ -5842,7 +5842,7 @@ impl Editor {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn join_lines(&mut self, _: &JoinLines, cx: &mut ViewContext<Self>) {
|
||||
pub fn join_lines_impl(&mut self, insert_whitespace: bool, cx: &mut ViewContext<Self>) {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
@@ -5884,11 +5884,12 @@ impl Editor {
|
||||
let indent = snapshot.indent_size_for_line(next_line_row);
|
||||
let start_of_next_line = Point::new(next_line_row.0, indent.len);
|
||||
|
||||
let replace = if snapshot.line_len(next_line_row) > indent.len {
|
||||
" "
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let replace =
|
||||
if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
|
||||
" "
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
|
||||
@@ -5902,6 +5903,10 @@ impl Editor {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn join_lines(&mut self, _: &JoinLines, cx: &mut ViewContext<Self>) {
|
||||
self.join_lines_impl(true, cx);
|
||||
}
|
||||
|
||||
pub fn sort_lines_case_sensitive(
|
||||
&mut self,
|
||||
_: &SortLinesCaseSensitive,
|
||||
|
||||
@@ -22,7 +22,7 @@ use crate::{
|
||||
EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
|
||||
HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, InlineCompletion, JumpData, LineDown,
|
||||
LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection,
|
||||
SoftWrap, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT,
|
||||
SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT,
|
||||
GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
|
||||
};
|
||||
use client::ParticipantIndex;
|
||||
@@ -30,14 +30,14 @@ use collections::{BTreeMap, HashMap, HashSet};
|
||||
use file_icons::FileIcons;
|
||||
use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
|
||||
use gpui::{
|
||||
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
|
||||
transparent_black, Action, AnyElement, AvailableSpace, Axis, Bounds, ClickEvent, ClipboardItem,
|
||||
ContentMask, Corner, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler,
|
||||
Entity, FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
|
||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
|
||||
ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size,
|
||||
StatefulInteractiveElement, Style, Styled, Subscription, TextRun, TextStyleRefinement, View,
|
||||
ViewContext, WeakView, WindowContext,
|
||||
anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px, quad,
|
||||
relative, size, svg, transparent_black, Action, AnyElement, AvailableSpace, Axis, Bounds,
|
||||
ClickEvent, ClipboardItem, ContentMask, Corner, Corners, CursorStyle, DispatchPhase, Edges,
|
||||
Element, ElementInputHandler, Entity, FontId, GlobalElementId, Hitbox, Hsla,
|
||||
InteractiveElement, IntoElement, Length, ModifiersChangedEvent, MouseButton, MouseDownEvent,
|
||||
MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent,
|
||||
ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled, Subscription,
|
||||
TextRun, TextStyleRefinement, View, ViewContext, WeakView, WindowContext,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::{
|
||||
@@ -2210,9 +2210,9 @@ impl EditorElement {
|
||||
resized_blocks: &mut HashMap<CustomBlockId, u32>,
|
||||
selections: &[Selection<Point>],
|
||||
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
||||
sticky_header_excerpt_id: Option<ExcerptId>,
|
||||
cx: &mut WindowContext,
|
||||
) -> (AnyElement, Size<Pixels>) {
|
||||
let header_padding = px(6.0);
|
||||
let mut element = match block {
|
||||
Block::Custom(block) => {
|
||||
let block_start = block.start().to_point(&snapshot.buffer_snapshot);
|
||||
@@ -2305,14 +2305,7 @@ impl EditorElement {
|
||||
|
||||
let jump_data = jump_data(snapshot, block_row_start, *height, first_excerpt, cx);
|
||||
result
|
||||
.child(self.render_buffer_header(
|
||||
first_excerpt,
|
||||
header_padding,
|
||||
true,
|
||||
selected,
|
||||
jump_data,
|
||||
cx,
|
||||
))
|
||||
.child(self.render_buffer_header(first_excerpt, true, selected, jump_data, cx))
|
||||
.into_any_element()
|
||||
}
|
||||
Block::ExcerptBoundary {
|
||||
@@ -2347,14 +2340,19 @@ impl EditorElement {
|
||||
if let Some(next_excerpt) = next_excerpt {
|
||||
let jump_data = jump_data(snapshot, block_row_start, *height, next_excerpt, cx);
|
||||
if *starts_new_buffer {
|
||||
result = result.child(self.render_buffer_header(
|
||||
next_excerpt,
|
||||
header_padding,
|
||||
false,
|
||||
false,
|
||||
jump_data,
|
||||
cx,
|
||||
));
|
||||
if sticky_header_excerpt_id != Some(next_excerpt.id) {
|
||||
result = result.child(self.render_buffer_header(
|
||||
next_excerpt,
|
||||
false,
|
||||
false,
|
||||
jump_data,
|
||||
cx,
|
||||
));
|
||||
} else {
|
||||
result =
|
||||
result.child(div().h(FILE_HEADER_HEIGHT as f32 * cx.line_height()));
|
||||
}
|
||||
|
||||
if *show_excerpt_controls {
|
||||
result = result.child(
|
||||
h_flex()
|
||||
@@ -2507,7 +2505,6 @@ impl EditorElement {
|
||||
fn render_buffer_header(
|
||||
&self,
|
||||
for_excerpt: &ExcerptInfo,
|
||||
header_padding: Pixels,
|
||||
is_folded: bool,
|
||||
is_selected: bool,
|
||||
jump_data: JumpData,
|
||||
@@ -2531,8 +2528,8 @@ impl EditorElement {
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
|
||||
div()
|
||||
.px(header_padding)
|
||||
.pt(header_padding)
|
||||
.px_2()
|
||||
.pt_2()
|
||||
.w_full()
|
||||
.h(FILE_HEADER_HEIGHT as f32 * cx.line_height())
|
||||
.child(
|
||||
@@ -2686,6 +2683,7 @@ impl EditorElement {
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
selections: &[Selection<Point>],
|
||||
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
||||
sticky_header_excerpt_id: Option<ExcerptId>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Result<Vec<BlockLayout>, HashMap<CustomBlockId, u32>> {
|
||||
let (fixed_blocks, non_fixed_blocks) = snapshot
|
||||
@@ -2724,6 +2722,7 @@ impl EditorElement {
|
||||
&mut resized_blocks,
|
||||
selections,
|
||||
is_row_soft_wrapped,
|
||||
sticky_header_excerpt_id,
|
||||
cx,
|
||||
);
|
||||
fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
|
||||
@@ -2735,6 +2734,7 @@ impl EditorElement {
|
||||
style: BlockStyle::Fixed,
|
||||
});
|
||||
}
|
||||
|
||||
for (row, block) in non_fixed_blocks {
|
||||
let style = block.style();
|
||||
let width = match style {
|
||||
@@ -2770,6 +2770,7 @@ impl EditorElement {
|
||||
&mut resized_blocks,
|
||||
selections,
|
||||
is_row_soft_wrapped,
|
||||
sticky_header_excerpt_id,
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -2817,6 +2818,7 @@ impl EditorElement {
|
||||
&mut resized_blocks,
|
||||
selections,
|
||||
is_row_soft_wrapped,
|
||||
sticky_header_excerpt_id,
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -2883,6 +2885,71 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_sticky_buffer_header(
|
||||
&self,
|
||||
StickyHeaderExcerpt {
|
||||
excerpt,
|
||||
next_excerpt_controls_present,
|
||||
next_buffer_row,
|
||||
}: StickyHeaderExcerpt<'_>,
|
||||
scroll_position: f32,
|
||||
line_height: Pixels,
|
||||
snapshot: &EditorSnapshot,
|
||||
hitbox: &Hitbox,
|
||||
cx: &mut WindowContext,
|
||||
) -> AnyElement {
|
||||
let jump_data = jump_data(snapshot, DisplayRow(0), FILE_HEADER_HEIGHT, excerpt, cx);
|
||||
|
||||
let editor_bg_color = cx.theme().colors().editor_background;
|
||||
|
||||
let mut header = v_flex()
|
||||
.relative()
|
||||
.child(
|
||||
div()
|
||||
.w(hitbox.bounds.size.width)
|
||||
.h(FILE_HEADER_HEIGHT as f32 * line_height)
|
||||
.bg(linear_gradient(
|
||||
0.,
|
||||
linear_color_stop(editor_bg_color.opacity(0.), 0.),
|
||||
linear_color_stop(editor_bg_color, 0.6),
|
||||
))
|
||||
.absolute()
|
||||
.top_0(),
|
||||
)
|
||||
.child(
|
||||
self.render_buffer_header(excerpt, false, false, jump_data, cx)
|
||||
.into_any_element(),
|
||||
)
|
||||
.into_any_element();
|
||||
|
||||
let mut origin = hitbox.origin;
|
||||
|
||||
if let Some(next_buffer_row) = next_buffer_row {
|
||||
// Push up the sticky header when the excerpt is getting close to the top of the viewport
|
||||
|
||||
let mut max_row = next_buffer_row - FILE_HEADER_HEIGHT * 2;
|
||||
|
||||
if next_excerpt_controls_present {
|
||||
max_row -= MULTI_BUFFER_EXCERPT_HEADER_HEIGHT;
|
||||
}
|
||||
|
||||
let offset = scroll_position - max_row as f32;
|
||||
|
||||
if offset > 0.0 {
|
||||
origin.y -= Pixels(offset) * line_height;
|
||||
}
|
||||
}
|
||||
|
||||
let size = size(
|
||||
AvailableSpace::Definite(hitbox.size.width),
|
||||
AvailableSpace::MinContent,
|
||||
);
|
||||
|
||||
header.prepaint_as_root(origin, size, cx);
|
||||
|
||||
header
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_context_menu(
|
||||
&self,
|
||||
@@ -4945,11 +5012,14 @@ fn jump_data(
|
||||
let excerpt_start_row = language::ToPoint::to_point(&jump_anchor, buffer).row;
|
||||
jump_position.row - excerpt_start_row
|
||||
};
|
||||
let line_offset_from_top = block_row_start.0 + height + offset_from_excerpt_start
|
||||
- snapshot
|
||||
.scroll_anchor
|
||||
.scroll_position(&snapshot.display_snapshot)
|
||||
.y as u32;
|
||||
let line_offset_from_top = block_row_start.0
|
||||
+ height
|
||||
+ offset_from_excerpt_start.saturating_sub(
|
||||
snapshot
|
||||
.scroll_anchor
|
||||
.scroll_position(&snapshot.display_snapshot)
|
||||
.y as u32,
|
||||
);
|
||||
JumpData {
|
||||
excerpt_id: for_excerpt.id,
|
||||
anchor: jump_anchor,
|
||||
@@ -6096,6 +6166,14 @@ impl Element for EditorElement {
|
||||
let scroll_range_bounds = scrollbar_range_data.scroll_range;
|
||||
let mut scroll_width = scroll_range_bounds.size.width;
|
||||
|
||||
let sticky_header_excerpt = if snapshot.buffer_snapshot.show_headers() {
|
||||
snapshot.sticky_header_excerpt(start_row)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let sticky_header_excerpt_id =
|
||||
sticky_header_excerpt.as_ref().map(|top| top.excerpt.id);
|
||||
|
||||
let blocks = cx.with_element_namespace("blocks", |cx| {
|
||||
self.render_blocks(
|
||||
start_row..end_row,
|
||||
@@ -6111,6 +6189,7 @@ impl Element for EditorElement {
|
||||
&line_layouts,
|
||||
&local_selections,
|
||||
is_row_soft_wrapped,
|
||||
sticky_header_excerpt_id,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -6124,6 +6203,19 @@ impl Element for EditorElement {
|
||||
}
|
||||
};
|
||||
|
||||
let sticky_buffer_header = sticky_header_excerpt.map(|sticky_header_excerpt| {
|
||||
cx.with_element_namespace("blocks", |cx| {
|
||||
self.layout_sticky_buffer_header(
|
||||
sticky_header_excerpt,
|
||||
scroll_position.y,
|
||||
line_height,
|
||||
&snapshot,
|
||||
&hitbox,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
let start_buffer_row =
|
||||
MultiBufferRow(start_anchor.to_point(&snapshot.buffer_snapshot).row);
|
||||
let end_buffer_row =
|
||||
@@ -6251,6 +6343,7 @@ impl Element for EditorElement {
|
||||
);
|
||||
|
||||
let mut block_start_rows = HashSet::default();
|
||||
|
||||
cx.with_element_namespace("blocks", |cx| {
|
||||
self.layout_blocks(
|
||||
&mut blocks,
|
||||
@@ -6542,6 +6635,7 @@ impl Element for EditorElement {
|
||||
crease_trailers,
|
||||
tab_invisible,
|
||||
space_invisible,
|
||||
sticky_buffer_header,
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -6623,6 +6717,12 @@ impl Element for EditorElement {
|
||||
});
|
||||
}
|
||||
|
||||
cx.with_element_namespace("blocks", |cx| {
|
||||
if let Some(mut sticky_header) = layout.sticky_buffer_header.take() {
|
||||
sticky_header.paint(cx)
|
||||
}
|
||||
});
|
||||
|
||||
self.paint_scrollbars(layout, cx);
|
||||
self.paint_inline_completion_popover(layout, cx);
|
||||
self.paint_mouse_context_menu(layout, cx);
|
||||
@@ -6730,6 +6830,7 @@ pub struct EditorLayout {
|
||||
mouse_context_menu: Option<AnyElement>,
|
||||
tab_invisible: ShapedLine,
|
||||
space_invisible: ShapedLine,
|
||||
sticky_buffer_header: Option<AnyElement>,
|
||||
}
|
||||
|
||||
impl EditorLayout {
|
||||
|
||||
@@ -146,19 +146,18 @@ fn handle_size_msg(
|
||||
// Don't resize the renderer when the window is minimized, but record that it was minimized so
|
||||
// that on restore the swap chain can be recreated via `update_drawable_size_even_if_unchanged`.
|
||||
if wparam.0 == SIZE_MINIMIZED as usize {
|
||||
lock.is_minimized = Some(true);
|
||||
lock.restore_from_minimized = lock.callbacks.request_frame.take();
|
||||
return Some(0);
|
||||
}
|
||||
let may_have_been_minimized = lock.is_minimized.unwrap_or(true);
|
||||
lock.is_minimized = Some(false);
|
||||
|
||||
let width = lparam.loword().max(1) as i32;
|
||||
let height = lparam.hiword().max(1) as i32;
|
||||
let new_size = size(DevicePixels(width), DevicePixels(height));
|
||||
let scale_factor = lock.scale_factor;
|
||||
if may_have_been_minimized {
|
||||
if lock.restore_from_minimized.is_some() {
|
||||
lock.renderer
|
||||
.update_drawable_size_even_if_unchanged(new_size);
|
||||
lock.callbacks.request_frame = lock.restore_from_minimized.take();
|
||||
} else {
|
||||
lock.renderer.update_drawable_size(new_size);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ pub struct WindowsWindowState {
|
||||
pub fullscreen_restore_bounds: Bounds<Pixels>,
|
||||
pub border_offset: WindowBorderOffset,
|
||||
pub scale_factor: f32,
|
||||
pub is_minimized: Option<bool>,
|
||||
pub restore_from_minimized: Option<Box<dyn FnMut(RequestFrameOptions)>>,
|
||||
|
||||
pub callbacks: Callbacks,
|
||||
pub input_handler: Option<PlatformInputHandler>,
|
||||
@@ -94,7 +94,7 @@ impl WindowsWindowState {
|
||||
size: logical_size,
|
||||
};
|
||||
let border_offset = WindowBorderOffset::default();
|
||||
let is_minimized = None;
|
||||
let restore_from_minimized = None;
|
||||
let renderer = windows_renderer::init(gpu_context, hwnd, transparent)?;
|
||||
let callbacks = Callbacks::default();
|
||||
let input_handler = None;
|
||||
@@ -112,7 +112,7 @@ impl WindowsWindowState {
|
||||
fullscreen_restore_bounds,
|
||||
border_offset,
|
||||
scale_factor,
|
||||
is_minimized,
|
||||
restore_from_minimized,
|
||||
callbacks,
|
||||
input_handler,
|
||||
system_key_handled,
|
||||
|
||||
@@ -614,11 +614,11 @@ impl Element for MarkdownElement {
|
||||
};
|
||||
builder.push_div(
|
||||
div()
|
||||
.mb_1()
|
||||
.h_flex()
|
||||
.mb_2()
|
||||
.line_height(rems(1.3))
|
||||
.items_start()
|
||||
.gap_1()
|
||||
.line_height(rems(1.3))
|
||||
.child(bullet),
|
||||
range,
|
||||
markdown_end,
|
||||
|
||||
@@ -10,4 +10,3 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
gpui.workspace = true
|
||||
once_cell.workspace = true
|
||||
|
||||
@@ -2,24 +2,23 @@
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use std::{env, str::FromStr};
|
||||
use std::{env, str::FromStr, sync::LazyLock};
|
||||
|
||||
use gpui::{AppContext, Global, SemanticVersion};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
/// stable | dev | nightly | preview
|
||||
pub static RELEASE_CHANNEL_NAME: Lazy<String> = if cfg!(debug_assertions) {
|
||||
Lazy::new(|| {
|
||||
pub static RELEASE_CHANNEL_NAME: LazyLock<String> = LazyLock::new(|| {
|
||||
if cfg!(debug_assertions) {
|
||||
env::var("ZED_RELEASE_CHANNEL")
|
||||
.unwrap_or_else(|_| include_str!("../../zed/RELEASE_CHANNEL").trim().to_string())
|
||||
})
|
||||
} else {
|
||||
Lazy::new(|| include_str!("../../zed/RELEASE_CHANNEL").trim().to_string())
|
||||
};
|
||||
} else {
|
||||
include_str!("../../zed/RELEASE_CHANNEL").trim().to_string()
|
||||
}
|
||||
});
|
||||
|
||||
#[doc(hidden)]
|
||||
pub static RELEASE_CHANNEL: Lazy<ReleaseChannel> =
|
||||
Lazy::new(|| match ReleaseChannel::from_str(&RELEASE_CHANNEL_NAME) {
|
||||
pub static RELEASE_CHANNEL: LazyLock<ReleaseChannel> =
|
||||
LazyLock::new(|| match ReleaseChannel::from_str(&RELEASE_CHANNEL_NAME) {
|
||||
Ok(channel) => channel,
|
||||
_ => panic!("invalid release channel {}", *RELEASE_CHANNEL_NAME),
|
||||
});
|
||||
|
||||
@@ -44,6 +44,8 @@ actions!(
|
||||
InsertLineAbove,
|
||||
InsertLineBelow,
|
||||
InsertAtPrevious,
|
||||
JoinLines,
|
||||
JoinLinesNoWhitespace,
|
||||
DeleteLeft,
|
||||
DeleteRight,
|
||||
ChangeToEndOfLine,
|
||||
@@ -53,7 +55,6 @@ actions!(
|
||||
ChangeCase,
|
||||
ConvertToUpperCase,
|
||||
ConvertToLowerCase,
|
||||
JoinLines,
|
||||
ToggleComments,
|
||||
Undo,
|
||||
Redo,
|
||||
@@ -108,25 +109,11 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
|
||||
);
|
||||
});
|
||||
Vim::action(editor, cx, |vim, _: &JoinLines, cx| {
|
||||
vim.record_current_action(cx);
|
||||
let mut times = Vim::take_count(cx).unwrap_or(1);
|
||||
if vim.mode.is_visual() {
|
||||
times = 1;
|
||||
} else if times > 1 {
|
||||
// 2J joins two lines together (same as J or 1J)
|
||||
times -= 1;
|
||||
}
|
||||
vim.join_lines_impl(true, cx);
|
||||
});
|
||||
|
||||
vim.update_editor(cx, |_, editor, cx| {
|
||||
editor.transact(cx, |editor, cx| {
|
||||
for _ in 0..times {
|
||||
editor.join_lines(&Default::default(), cx)
|
||||
}
|
||||
})
|
||||
});
|
||||
if vim.mode.is_visual() {
|
||||
vim.switch_mode(Mode::Normal, true, cx)
|
||||
}
|
||||
Vim::action(editor, cx, |vim, _: &JoinLinesNoWhitespace, cx| {
|
||||
vim.join_lines_impl(false, cx);
|
||||
});
|
||||
|
||||
Vim::action(editor, cx, |vim, _: &Undo, cx| {
|
||||
@@ -401,6 +388,28 @@ impl Vim {
|
||||
});
|
||||
}
|
||||
|
||||
fn join_lines_impl(&mut self, insert_whitespace: bool, cx: &mut ViewContext<Self>) {
|
||||
self.record_current_action(cx);
|
||||
let mut times = Vim::take_count(cx).unwrap_or(1);
|
||||
if self.mode.is_visual() {
|
||||
times = 1;
|
||||
} else if times > 1 {
|
||||
// 2J joins two lines together (same as J or 1J)
|
||||
times -= 1;
|
||||
}
|
||||
|
||||
self.update_editor(cx, |_, editor, cx| {
|
||||
editor.transact(cx, |editor, cx| {
|
||||
for _ in 0..times {
|
||||
editor.join_lines_impl(insert_whitespace, cx)
|
||||
}
|
||||
})
|
||||
});
|
||||
if self.mode.is_visual() {
|
||||
self.switch_mode(Mode::Normal, true, cx)
|
||||
}
|
||||
}
|
||||
|
||||
fn yank_line(&mut self, _: &YankLine, cx: &mut ViewContext<Self>) {
|
||||
let count = Vim::take_count(cx);
|
||||
self.yank_motion(motion::Motion::CurrentLine, count, cx)
|
||||
|
||||
@@ -21,7 +21,6 @@ use serde::Deserialize;
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
|
||||
pub enum Object {
|
||||
Word { ignore_punctuation: bool },
|
||||
Subword { ignore_punctuation: bool },
|
||||
Sentence,
|
||||
Paragraph,
|
||||
Quotes,
|
||||
@@ -48,18 +47,12 @@ struct Word {
|
||||
}
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Subword {
|
||||
#[serde(default)]
|
||||
ignore_punctuation: bool,
|
||||
}
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct IndentObj {
|
||||
#[serde(default)]
|
||||
include_below: bool,
|
||||
}
|
||||
|
||||
impl_actions!(vim, [Word, Subword, IndentObj]);
|
||||
impl_actions!(vim, [Word, IndentObj]);
|
||||
|
||||
actions!(
|
||||
vim,
|
||||
@@ -90,13 +83,6 @@ pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
|
||||
vim.object(Object::Word { ignore_punctuation }, cx)
|
||||
},
|
||||
);
|
||||
Vim::action(
|
||||
editor,
|
||||
cx,
|
||||
|vim, &Subword { ignore_punctuation }: &Subword, cx| {
|
||||
vim.object(Object::Subword { ignore_punctuation }, cx)
|
||||
},
|
||||
);
|
||||
Vim::action(editor, cx, |vim, _: &Tag, cx| vim.object(Object::Tag, cx));
|
||||
Vim::action(editor, cx, |vim, _: &Sentence, cx| {
|
||||
vim.object(Object::Sentence, cx)
|
||||
@@ -168,7 +154,6 @@ impl Object {
|
||||
pub fn is_multiline(self) -> bool {
|
||||
match self {
|
||||
Object::Word { .. }
|
||||
| Object::Subword { .. }
|
||||
| Object::Quotes
|
||||
| Object::BackQuotes
|
||||
| Object::VerticalBars
|
||||
@@ -191,7 +176,6 @@ impl Object {
|
||||
pub fn always_expands_both_ways(self) -> bool {
|
||||
match self {
|
||||
Object::Word { .. }
|
||||
| Object::Subword { .. }
|
||||
| Object::Sentence
|
||||
| Object::Paragraph
|
||||
| Object::Argument
|
||||
@@ -214,7 +198,6 @@ impl Object {
|
||||
pub fn target_visual_mode(self, current_mode: Mode, around: bool) -> Mode {
|
||||
match self {
|
||||
Object::Word { .. }
|
||||
| Object::Subword { .. }
|
||||
| Object::Sentence
|
||||
| Object::Quotes
|
||||
| Object::BackQuotes
|
||||
@@ -260,13 +243,6 @@ impl Object {
|
||||
in_word(map, relative_to, ignore_punctuation)
|
||||
}
|
||||
}
|
||||
Object::Subword { ignore_punctuation } => {
|
||||
if around {
|
||||
around_subword(map, relative_to, ignore_punctuation)
|
||||
} else {
|
||||
in_subword(map, relative_to, ignore_punctuation)
|
||||
}
|
||||
}
|
||||
Object::Sentence => sentence(map, relative_to, around),
|
||||
Object::Paragraph => paragraph(map, relative_to, around),
|
||||
Object::Quotes => {
|
||||
@@ -374,63 +350,6 @@ fn in_word(
|
||||
Some(start..end)
|
||||
}
|
||||
|
||||
fn in_subword(
|
||||
map: &DisplaySnapshot,
|
||||
relative_to: DisplayPoint,
|
||||
ignore_punctuation: bool,
|
||||
) -> Option<Range<DisplayPoint>> {
|
||||
let offset = relative_to.to_offset(map, Bias::Left);
|
||||
// Use motion::right so that we consider the character under the cursor when looking for the start
|
||||
let classifier = map
|
||||
.buffer_snapshot
|
||||
.char_classifier_at(relative_to.to_point(map))
|
||||
.ignore_punctuation(ignore_punctuation);
|
||||
let in_subword = map
|
||||
.buffer_chars_at(offset)
|
||||
.next()
|
||||
.map(|(c, _)| {
|
||||
if classifier.is_word('-') {
|
||||
!classifier.is_whitespace(c) && c != '_' && c != '-'
|
||||
} else {
|
||||
!classifier.is_whitespace(c) && c != '_'
|
||||
}
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
let start = if in_subword {
|
||||
movement::find_preceding_boundary_display_point(
|
||||
map,
|
||||
right(map, relative_to, 1),
|
||||
movement::FindRange::SingleLine,
|
||||
|left, right| {
|
||||
let is_word_start = classifier.kind(left) != classifier.kind(right);
|
||||
let is_subword_start = classifier.is_word('-') && left == '-' && right != '-'
|
||||
|| left == '_' && right != '_'
|
||||
|| left.is_lowercase() && right.is_uppercase();
|
||||
is_word_start || is_subword_start
|
||||
},
|
||||
)
|
||||
} else {
|
||||
movement::find_boundary(map, relative_to, FindRange::SingleLine, |left, right| {
|
||||
let is_word_start = classifier.kind(left) != classifier.kind(right);
|
||||
let is_subword_start = classifier.is_word('-') && left == '-' && right != '-'
|
||||
|| left == '_' && right != '_'
|
||||
|| left.is_lowercase() && right.is_uppercase();
|
||||
is_word_start || is_subword_start
|
||||
})
|
||||
};
|
||||
|
||||
let end = movement::find_boundary(map, relative_to, FindRange::SingleLine, |left, right| {
|
||||
let is_word_end = classifier.kind(left) != classifier.kind(right);
|
||||
let is_subword_end = classifier.is_word('-') && left != '-' && right == '-'
|
||||
|| left != '_' && right == '_'
|
||||
|| left.is_lowercase() && right.is_uppercase();
|
||||
is_word_end || is_subword_end
|
||||
});
|
||||
|
||||
Some(start..end)
|
||||
}
|
||||
|
||||
pub fn surrounding_html_tag(
|
||||
map: &DisplaySnapshot,
|
||||
head: DisplayPoint,
|
||||
@@ -542,40 +461,6 @@ fn around_word(
|
||||
}
|
||||
}
|
||||
|
||||
fn around_subword(
|
||||
map: &DisplaySnapshot,
|
||||
relative_to: DisplayPoint,
|
||||
ignore_punctuation: bool,
|
||||
) -> Option<Range<DisplayPoint>> {
|
||||
// Use motion::right so that we consider the character under the cursor when looking for the start
|
||||
let classifier = map
|
||||
.buffer_snapshot
|
||||
.char_classifier_at(relative_to.to_point(map))
|
||||
.ignore_punctuation(ignore_punctuation);
|
||||
let start = movement::find_preceding_boundary_display_point(
|
||||
map,
|
||||
right(map, relative_to, 1),
|
||||
movement::FindRange::SingleLine,
|
||||
|left, right| {
|
||||
let is_word_start = classifier.kind(left) != classifier.kind(right);
|
||||
let is_subword_start = classifier.is_word('-') && left != '-' && right == '-'
|
||||
|| left != '_' && right == '_'
|
||||
|| left.is_lowercase() && right.is_uppercase();
|
||||
is_word_start || is_subword_start
|
||||
},
|
||||
);
|
||||
|
||||
let end = movement::find_boundary(map, relative_to, FindRange::SingleLine, |left, right| {
|
||||
let is_word_end = classifier.kind(left) != classifier.kind(right);
|
||||
let is_subword_end = classifier.is_word('-') && left != '-' && right == '-'
|
||||
|| left != '_' && right == '_'
|
||||
|| left.is_lowercase() && right.is_uppercase();
|
||||
is_word_end || is_subword_end
|
||||
});
|
||||
|
||||
Some(start..end)
|
||||
}
|
||||
|
||||
fn around_containing_word(
|
||||
map: &DisplaySnapshot,
|
||||
relative_to: DisplayPoint,
|
||||
|
||||
@@ -367,6 +367,46 @@ async fn test_join_lines(cx: &mut gpui::TestAppContext) {
|
||||
two three fourˇ five
|
||||
six
|
||||
"});
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
ˇone
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
six
|
||||
"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes("g shift-j").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
oneˇtwo
|
||||
three
|
||||
four
|
||||
five
|
||||
six
|
||||
"});
|
||||
cx.simulate_shared_keystrokes("3 g shift-j").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
onetwothreeˇfour
|
||||
five
|
||||
six
|
||||
"});
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
ˇone
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
six
|
||||
"})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes("j v 3 j g shift-j").await;
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
one
|
||||
twothreefourˇfive
|
||||
six
|
||||
"});
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
|
||||
@@ -11,3 +11,19 @@
|
||||
{"Key":"j"}
|
||||
{"Key":"shift-j"}
|
||||
{"Get":{"state":"one\ntwo three fourˇ five\nsix\n","mode":"Normal"}}
|
||||
{"Put":{"state":"ˇone\ntwo\nthree\nfour\nfive\nsix\n"}}
|
||||
{"Key":"g"}
|
||||
{"Key":"shift-j"}
|
||||
{"Get":{"state":"oneˇtwo\nthree\nfour\nfive\nsix\n","mode":"Normal"}}
|
||||
{"Key":"3"}
|
||||
{"Key":"g"}
|
||||
{"Key":"shift-j"}
|
||||
{"Get":{"state":"onetwothreeˇfour\nfive\nsix\n","mode":"Normal"}}
|
||||
{"Put":{"state":"ˇone\ntwo\nthree\nfour\nfive\nsix\n"}}
|
||||
{"Key":"j"}
|
||||
{"Key":"v"}
|
||||
{"Key":"3"}
|
||||
{"Key":"j"}
|
||||
{"Key":"g"}
|
||||
{"Key":"shift-j"}
|
||||
{"Get":{"state":"one\ntwothreefourˇfive\nsix\n","mode":"Normal"}}
|
||||
|
||||
@@ -392,20 +392,6 @@ Subword motion, which allows you to navigate and select individual words in came
|
||||
]
|
||||
```
|
||||
|
||||
Similarly subword text objects are available, and can be bound with:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"context": "vim_operator == a || vim_operator == i || vim_operator == cs",
|
||||
"bindings": {
|
||||
"w": "vim::Subword",
|
||||
"shift-w": ["vim::Subword", { "ignorePunctuation": true }],
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Vim mode comes with shortcuts to surround the selection in normal mode (`ys`), but it doesn't have a shortcut to add surrounds in visual mode. By default, `shift-s` substitutes the selection (erases the text and enters insert mode). To use `shift-s` to add surrounds in visual mode, you can add the following object to your keymap.
|
||||
|
||||
```json
|
||||
|
||||
Reference in New Issue
Block a user