Compare commits
20 Commits
telemetry-
...
v0.163.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b043fc6a7 | ||
|
|
faee7eaa57 | ||
|
|
8ff753f4c9 | ||
|
|
a930514960 | ||
|
|
6c2ad590a1 | ||
|
|
4b934f6abe | ||
|
|
9f53208503 | ||
|
|
47381ed170 | ||
|
|
3cadc84514 | ||
|
|
0d5046fc83 | ||
|
|
b49f020551 | ||
|
|
226680f800 | ||
|
|
a882efe679 | ||
|
|
21b1a0580c | ||
|
|
90baed50a9 | ||
|
|
7275ee53ee | ||
|
|
0a169c0f70 | ||
|
|
31ad658c13 | ||
|
|
8444a6f4ee | ||
|
|
209d666470 |
15
.github/workflows/ci.yml
vendored
15
.github/workflows/ci.yml
vendored
@@ -244,8 +244,8 @@ jobs:
|
||||
#
|
||||
# 25 was chosen arbitrarily.
|
||||
fetch-depth: 25
|
||||
fetch-tags: true
|
||||
clean: false
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Limit target directory size
|
||||
run: script/clear-target-dir-if-larger-than 100
|
||||
@@ -404,3 +404,16 @@ jobs:
|
||||
target/release/zed-linux-aarch64.tar.gz
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
auto-release-preview:
|
||||
name: Auto release preview
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre') }}
|
||||
needs: [bundle-mac, bundle-linux, bundle-linux-aarch64]
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- bundle
|
||||
steps:
|
||||
- name: gh release
|
||||
run: gh release edit $GITHUB_REF_NAME --draft=false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -15422,7 +15422,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.163.0"
|
||||
version = "0.163.2"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
|
||||
@@ -405,7 +405,7 @@
|
||||
"ctrl-shift-p": "command_palette::Toggle",
|
||||
"f1": "command_palette::Toggle",
|
||||
"ctrl-shift-m": "diagnostics::Deploy",
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||
"ctrl-shift-e": "pane::RevealInProjectPanel",
|
||||
"ctrl-shift-b": "outline_panel::ToggleFocus",
|
||||
"ctrl-?": "assistant::ToggleFocus",
|
||||
"ctrl-alt-s": "workspace::SaveAll",
|
||||
@@ -594,6 +594,7 @@
|
||||
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-ctrl-r": "project_panel::RevealInFileManager",
|
||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||
"ctrl-shift-e": "project_panel::ToggleFocus",
|
||||
"ctrl-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev",
|
||||
@@ -649,11 +650,16 @@
|
||||
"tab": "channel_modal::ToggleMode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "FileFinder",
|
||||
"bindings": {
|
||||
"ctrl": "file_finder::ToggleMenu"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "FileFinder && !menu_open",
|
||||
"bindings": {
|
||||
"ctrl-shift-p": "file_finder::SelectPrev",
|
||||
"ctrl": "file_finder::OpenMenu",
|
||||
"ctrl-j": "pane::SplitDown",
|
||||
"ctrl-k": "pane::SplitUp",
|
||||
"ctrl-h": "pane::SplitLeft",
|
||||
|
||||
@@ -340,7 +340,7 @@
|
||||
"alt-cmd-f12": "editor::GoToTypeDefinitionSplit",
|
||||
"alt-shift-f12": "editor::FindAllReferences",
|
||||
"ctrl-m": "editor::MoveToEnclosingBracket",
|
||||
"cmd-shift-\\": "editor::MoveToEnclosingBracket",
|
||||
"cmd-|": "editor::MoveToEnclosingBracket",
|
||||
"alt-cmd-[": "editor::Fold",
|
||||
"alt-cmd-]": "editor::UnfoldLines",
|
||||
"cmd-k cmd-l": "editor::ToggleFold",
|
||||
@@ -431,7 +431,7 @@
|
||||
"ctrl-shift-tab": ["tab_switcher::Toggle", { "select_last": true }],
|
||||
"cmd-shift-p": "command_palette::Toggle",
|
||||
"cmd-shift-m": "diagnostics::Deploy",
|
||||
"cmd-shift-e": "project_panel::ToggleFocus",
|
||||
"cmd-shift-e": "pane::RevealInProjectPanel",
|
||||
"cmd-shift-b": "outline_panel::ToggleFocus",
|
||||
"cmd-?": "assistant::ToggleFocus",
|
||||
"cmd-alt-s": "workspace::SaveAll",
|
||||
@@ -601,6 +601,7 @@
|
||||
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-cmd-r": "project_panel::RevealInFileManager",
|
||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||
"cmd-shift-e": "project_panel::ToggleFocus",
|
||||
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"cmd-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
@@ -647,11 +648,16 @@
|
||||
"tab": "channel_modal::ToggleMode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "FileFinder",
|
||||
"bindings": {
|
||||
"cmd": "file_finder::ToggleMenu"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "FileFinder && !menu_open",
|
||||
"bindings": {
|
||||
"cmd-shift-p": "file_finder::SelectPrev",
|
||||
"cmd": "file_finder::OpenMenu",
|
||||
"cmd-j": "pane::SplitDown",
|
||||
"cmd-k": "pane::SplitUp",
|
||||
"cmd-h": "pane::SplitLeft",
|
||||
|
||||
@@ -60,7 +60,6 @@ pub struct AssistantSettings {
|
||||
pub inline_alternatives: Vec<LanguageModelSelection>,
|
||||
pub using_outdated_settings_version: bool,
|
||||
pub enable_experimental_live_diffs: bool,
|
||||
pub show_hints: bool,
|
||||
}
|
||||
|
||||
impl AssistantSettings {
|
||||
@@ -203,7 +202,6 @@ impl AssistantSettingsContent {
|
||||
AssistantSettingsContent::Versioned(settings) => match settings {
|
||||
VersionedAssistantSettingsContent::V1(settings) => AssistantSettingsContentV2 {
|
||||
enabled: settings.enabled,
|
||||
show_hints: None,
|
||||
button: settings.button,
|
||||
dock: settings.dock,
|
||||
default_width: settings.default_width,
|
||||
@@ -244,7 +242,6 @@ impl AssistantSettingsContent {
|
||||
},
|
||||
AssistantSettingsContent::Legacy(settings) => AssistantSettingsContentV2 {
|
||||
enabled: None,
|
||||
show_hints: None,
|
||||
button: settings.button,
|
||||
dock: settings.dock,
|
||||
default_width: settings.default_width,
|
||||
@@ -357,7 +354,6 @@ impl Default for VersionedAssistantSettingsContent {
|
||||
fn default() -> Self {
|
||||
Self::V2(AssistantSettingsContentV2 {
|
||||
enabled: None,
|
||||
show_hints: None,
|
||||
button: None,
|
||||
dock: None,
|
||||
default_width: None,
|
||||
@@ -375,11 +371,6 @@ pub struct AssistantSettingsContentV2 {
|
||||
///
|
||||
/// Default: true
|
||||
enabled: Option<bool>,
|
||||
/// Whether to show inline hints that show keybindings for inline assistant
|
||||
/// and assistant panel.
|
||||
///
|
||||
/// Default: true
|
||||
show_hints: Option<bool>,
|
||||
/// Whether to show the assistant panel button in the status bar.
|
||||
///
|
||||
/// Default: true
|
||||
@@ -514,7 +505,6 @@ impl Settings for AssistantSettings {
|
||||
|
||||
let value = value.upgrade();
|
||||
merge(&mut settings.enabled, value.enabled);
|
||||
merge(&mut settings.show_hints, value.show_hints);
|
||||
merge(&mut settings.button, value.button);
|
||||
merge(&mut settings.dock, value.dock);
|
||||
merge(
|
||||
@@ -585,7 +575,6 @@ mod tests {
|
||||
}),
|
||||
inline_alternatives: None,
|
||||
enabled: None,
|
||||
show_hints: None,
|
||||
button: None,
|
||||
dock: None,
|
||||
default_width: None,
|
||||
|
||||
@@ -540,15 +540,6 @@ pub enum IsVimMode {
|
||||
No,
|
||||
}
|
||||
|
||||
pub trait ActiveLineTrailerProvider {
|
||||
fn render_active_line_trailer(
|
||||
&mut self,
|
||||
style: &EditorStyle,
|
||||
focus_handle: &FocusHandle,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<AnyElement>;
|
||||
}
|
||||
|
||||
/// Zed's primary text input `View`, allowing users to edit a [`MultiBuffer`]
|
||||
///
|
||||
/// See the [module level documentation](self) for more information.
|
||||
@@ -676,7 +667,6 @@ pub struct Editor {
|
||||
next_scroll_position: NextScrollCursorCenterTopBottom,
|
||||
addons: HashMap<TypeId, Box<dyn Addon>>,
|
||||
_scroll_cursor_center_top_bottom_task: Task<()>,
|
||||
active_line_trailer_provider: Option<Box<dyn ActiveLineTrailerProvider>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
|
||||
@@ -2215,7 +2205,6 @@ impl Editor {
|
||||
addons: HashMap::default(),
|
||||
_scroll_cursor_center_top_bottom_task: Task::ready(()),
|
||||
text_style_refinement: None,
|
||||
active_line_trailer_provider: None,
|
||||
};
|
||||
this.tasks_update_task = Some(this.refresh_runnables(cx));
|
||||
this._subscriptions.extend(project_subscriptions);
|
||||
@@ -2504,16 +2493,6 @@ impl Editor {
|
||||
self.refresh_inline_completion(false, false, cx);
|
||||
}
|
||||
|
||||
pub fn set_active_line_trailer_provider<T>(
|
||||
&mut self,
|
||||
provider: Option<T>,
|
||||
_cx: &mut ViewContext<Self>,
|
||||
) where
|
||||
T: ActiveLineTrailerProvider + 'static,
|
||||
{
|
||||
self.active_line_trailer_provider = provider.map(|provider| Box::new(provider) as Box<_>);
|
||||
}
|
||||
|
||||
pub fn placeholder_text(&self, _cx: &WindowContext) -> Option<&str> {
|
||||
self.placeholder_text.as_deref()
|
||||
}
|
||||
@@ -11870,29 +11849,6 @@ impl Editor {
|
||||
&& self.has_blame_entries(cx)
|
||||
}
|
||||
|
||||
pub fn render_active_line_trailer(
|
||||
&mut self,
|
||||
style: &EditorStyle,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<AnyElement> {
|
||||
let selection = self.selections.newest::<Point>(cx);
|
||||
if !selection.is_empty() {
|
||||
return None;
|
||||
};
|
||||
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let buffer_row = MultiBufferRow(selection.head().row);
|
||||
|
||||
if snapshot.line_len(buffer_row) != 0 || self.has_active_inline_completion(cx) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
self.active_line_trailer_provider
|
||||
.as_mut()?
|
||||
.render_active_line_trailer(style, &focus_handle, cx)
|
||||
}
|
||||
|
||||
fn has_blame_entries(&self, cx: &mut WindowContext) -> bool {
|
||||
self.blame()
|
||||
.map_or(false, |blame| blame.read(cx).has_generated_entries())
|
||||
@@ -14434,15 +14390,16 @@ impl ViewInputHandler for Editor {
|
||||
fn text_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
adjusted_range: &mut Option<Range<usize>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<String> {
|
||||
Some(
|
||||
self.buffer
|
||||
.read(cx)
|
||||
.read(cx)
|
||||
.text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end))
|
||||
.collect(),
|
||||
)
|
||||
let snapshot = self.buffer.read(cx).read(cx);
|
||||
let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
|
||||
let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
|
||||
if (start.0..end.0) != range_utf16 {
|
||||
adjusted_range.replace(start.0..end.0);
|
||||
}
|
||||
Some(snapshot.text_for_range(start..end).collect())
|
||||
}
|
||||
|
||||
fn selected_text_range(
|
||||
|
||||
@@ -1412,7 +1412,7 @@ impl EditorElement {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_active_line_trailer(
|
||||
fn layout_inline_blame(
|
||||
&self,
|
||||
display_row: DisplayRow,
|
||||
display_snapshot: &DisplaySnapshot,
|
||||
@@ -1424,71 +1424,61 @@ impl EditorElement {
|
||||
line_height: Pixels,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<AnyElement> {
|
||||
let render_inline_blame = self
|
||||
if !self
|
||||
.editor
|
||||
.update(cx, |editor, cx| editor.render_git_blame_inline(cx));
|
||||
if render_inline_blame {
|
||||
let workspace = self
|
||||
.editor
|
||||
.read(cx)
|
||||
.workspace
|
||||
.as_ref()
|
||||
.map(|(w, _)| w.clone());
|
||||
|
||||
let display_point = DisplayPoint::new(display_row, 0);
|
||||
let buffer_row = MultiBufferRow(display_point.to_point(display_snapshot).row);
|
||||
|
||||
let blame = self.editor.read(cx).blame.clone()?;
|
||||
let blame_entry = blame
|
||||
.update(cx, |blame, cx| {
|
||||
blame.blame_for_rows([Some(buffer_row)], cx).next()
|
||||
})
|
||||
.flatten()?;
|
||||
|
||||
let mut element =
|
||||
render_inline_blame_entry(&blame, blame_entry, &self.style, workspace, cx);
|
||||
|
||||
let start_y = content_origin.y
|
||||
+ line_height * (display_row.as_f32() - scroll_pixel_position.y / line_height);
|
||||
|
||||
let start_x = {
|
||||
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
|
||||
|
||||
let line_end = if let Some(crease_trailer) = crease_trailer {
|
||||
crease_trailer.bounds.right()
|
||||
} else {
|
||||
content_origin.x - scroll_pixel_position.x + line_layout.width
|
||||
};
|
||||
let padded_line_end = line_end + em_width * INLINE_BLAME_PADDING_EM_WIDTHS;
|
||||
|
||||
let min_column_in_pixels = ProjectSettings::get_global(cx)
|
||||
.git
|
||||
.inline_blame
|
||||
.and_then(|settings| settings.min_column)
|
||||
.map(|col| self.column_pixels(col as usize, cx))
|
||||
.unwrap_or(px(0.));
|
||||
let min_start = content_origin.x - scroll_pixel_position.x + min_column_in_pixels;
|
||||
|
||||
cmp::max(padded_line_end, min_start)
|
||||
};
|
||||
|
||||
let absolute_offset = point(start_x, start_y);
|
||||
element.prepaint_as_root(absolute_offset, AvailableSpace::min_size(), cx);
|
||||
|
||||
Some(element)
|
||||
} else if let Some(mut element) = self.editor.update(cx, |editor, cx| {
|
||||
editor.render_active_line_trailer(&self.style, cx)
|
||||
}) {
|
||||
let start_y = content_origin.y
|
||||
+ line_height * (display_row.as_f32() - scroll_pixel_position.y / line_height);
|
||||
let start_x = content_origin.x - scroll_pixel_position.x + em_width;
|
||||
let absolute_offset = point(start_x, start_y);
|
||||
element.prepaint_as_root(absolute_offset, AvailableSpace::min_size(), cx);
|
||||
|
||||
Some(element)
|
||||
} else {
|
||||
None
|
||||
.update(cx, |editor, cx| editor.render_git_blame_inline(cx))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let workspace = self
|
||||
.editor
|
||||
.read(cx)
|
||||
.workspace
|
||||
.as_ref()
|
||||
.map(|(w, _)| w.clone());
|
||||
|
||||
let display_point = DisplayPoint::new(display_row, 0);
|
||||
let buffer_row = MultiBufferRow(display_point.to_point(display_snapshot).row);
|
||||
|
||||
let blame = self.editor.read(cx).blame.clone()?;
|
||||
let blame_entry = blame
|
||||
.update(cx, |blame, cx| {
|
||||
blame.blame_for_rows([Some(buffer_row)], cx).next()
|
||||
})
|
||||
.flatten()?;
|
||||
|
||||
let mut element =
|
||||
render_inline_blame_entry(&blame, blame_entry, &self.style, workspace, cx);
|
||||
|
||||
let start_y = content_origin.y
|
||||
+ line_height * (display_row.as_f32() - scroll_pixel_position.y / line_height);
|
||||
|
||||
let start_x = {
|
||||
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
|
||||
|
||||
let line_end = if let Some(crease_trailer) = crease_trailer {
|
||||
crease_trailer.bounds.right()
|
||||
} else {
|
||||
content_origin.x - scroll_pixel_position.x + line_layout.width
|
||||
};
|
||||
let padded_line_end = line_end + em_width * INLINE_BLAME_PADDING_EM_WIDTHS;
|
||||
|
||||
let min_column_in_pixels = ProjectSettings::get_global(cx)
|
||||
.git
|
||||
.inline_blame
|
||||
.and_then(|settings| settings.min_column)
|
||||
.map(|col| self.column_pixels(col as usize, cx))
|
||||
.unwrap_or(px(0.));
|
||||
let min_start = content_origin.x - scroll_pixel_position.x + min_column_in_pixels;
|
||||
|
||||
cmp::max(padded_line_end, min_start)
|
||||
};
|
||||
|
||||
let absolute_offset = point(start_x, start_y);
|
||||
element.prepaint_as_root(absolute_offset, AvailableSpace::min_size(), cx);
|
||||
|
||||
Some(element)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -3464,7 +3454,7 @@ impl EditorElement {
|
||||
self.paint_lines(&invisible_display_ranges, layout, cx);
|
||||
self.paint_redactions(layout, cx);
|
||||
self.paint_cursors(layout, cx);
|
||||
self.paint_active_line_trailer(layout, cx);
|
||||
self.paint_inline_blame(layout, cx);
|
||||
cx.with_element_namespace("crease_trailers", |cx| {
|
||||
for trailer in layout.crease_trailers.iter_mut().flatten() {
|
||||
trailer.element.paint(cx);
|
||||
@@ -3946,10 +3936,10 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_active_line_trailer(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||
if let Some(mut element) = layout.active_line_trailer.take() {
|
||||
fn paint_inline_blame(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||
if let Some(mut inline_blame) = layout.inline_blame.take() {
|
||||
cx.paint_layer(layout.text_hitbox.bounds, |cx| {
|
||||
element.paint(cx);
|
||||
inline_blame.paint(cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5341,14 +5331,14 @@ impl Element for EditorElement {
|
||||
)
|
||||
});
|
||||
|
||||
let mut active_line_trailer = None;
|
||||
let mut inline_blame = None;
|
||||
if let Some(newest_selection_head) = newest_selection_head {
|
||||
let display_row = newest_selection_head.row();
|
||||
if (start_row..end_row).contains(&display_row) {
|
||||
let line_ix = display_row.minus(start_row) as usize;
|
||||
let line_layout = &line_layouts[line_ix];
|
||||
let crease_trailer_layout = crease_trailers[line_ix].as_ref();
|
||||
active_line_trailer = self.layout_active_line_trailer(
|
||||
inline_blame = self.layout_inline_blame(
|
||||
display_row,
|
||||
&snapshot.display_snapshot,
|
||||
line_layout,
|
||||
@@ -5667,7 +5657,7 @@ impl Element for EditorElement {
|
||||
line_elements,
|
||||
line_numbers,
|
||||
blamed_display_rows,
|
||||
active_line_trailer,
|
||||
inline_blame,
|
||||
blocks,
|
||||
cursors,
|
||||
visible_cursors,
|
||||
@@ -5804,7 +5794,7 @@ pub struct EditorLayout {
|
||||
line_numbers: Vec<Option<ShapedLine>>,
|
||||
display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,
|
||||
blamed_display_rows: Option<Vec<AnyElement>>,
|
||||
active_line_trailer: Option<AnyElement>,
|
||||
inline_blame: Option<AnyElement>,
|
||||
blocks: Vec<BlockLayout>,
|
||||
highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
|
||||
highlighted_gutter_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
|
||||
|
||||
@@ -42,7 +42,7 @@ use workspace::{
|
||||
Workspace,
|
||||
};
|
||||
|
||||
actions!(file_finder, [SelectPrev, OpenMenu]);
|
||||
actions!(file_finder, [SelectPrev, ToggleMenu]);
|
||||
|
||||
impl ModalView for FileFinder {
|
||||
fn on_before_dismiss(&mut self, cx: &mut ViewContext<Self>) -> workspace::DismissDecision {
|
||||
@@ -189,10 +189,12 @@ impl FileFinder {
|
||||
cx.dispatch_action(Box::new(menu::SelectPrev));
|
||||
}
|
||||
|
||||
fn handle_open_menu(&mut self, _: &OpenMenu, cx: &mut ViewContext<Self>) {
|
||||
fn handle_toggle_menu(&mut self, _: &ToggleMenu, cx: &mut ViewContext<Self>) {
|
||||
self.picker.update(cx, |picker, cx| {
|
||||
let menu_handle = &picker.delegate.popover_menu_handle;
|
||||
if !menu_handle.is_deployed() {
|
||||
if menu_handle.is_deployed() {
|
||||
menu_handle.hide(cx);
|
||||
} else {
|
||||
menu_handle.show(cx);
|
||||
}
|
||||
});
|
||||
@@ -282,7 +284,7 @@ impl Render for FileFinder {
|
||||
.w(modal_max_width)
|
||||
.on_modifiers_changed(cx.listener(Self::handle_modifiers_changed))
|
||||
.on_action(cx.listener(Self::handle_select_prev))
|
||||
.on_action(cx.listener(Self::handle_open_menu))
|
||||
.on_action(cx.listener(Self::handle_toggle_menu))
|
||||
.on_action(cx.listener(Self::go_to_file_split_left))
|
||||
.on_action(cx.listener(Self::go_to_file_split_right))
|
||||
.on_action(cx.listener(Self::go_to_file_split_up))
|
||||
@@ -1242,6 +1244,7 @@ impl PickerDelegate for FileFinderDelegate {
|
||||
}
|
||||
|
||||
fn render_footer(&self, cx: &mut ViewContext<Picker<Self>>) -> Option<AnyElement> {
|
||||
let context = self.focus_handle.clone();
|
||||
Some(
|
||||
h_flex()
|
||||
.w_full()
|
||||
@@ -1263,19 +1266,19 @@ impl PickerDelegate for FileFinderDelegate {
|
||||
.trigger(
|
||||
Button::new("actions-trigger", "Split Options")
|
||||
.selected_label_color(Color::Accent)
|
||||
.key_binding(KeyBinding::for_action_in(
|
||||
&OpenMenu,
|
||||
&self.focus_handle,
|
||||
cx,
|
||||
)),
|
||||
.key_binding(KeyBinding::for_action_in(&ToggleMenu, &context, cx)),
|
||||
)
|
||||
.menu({
|
||||
move |cx| {
|
||||
Some(ContextMenu::build(cx, move |menu, _| {
|
||||
menu.action("Split Left", pane::SplitLeft.boxed_clone())
|
||||
.action("Split Right", pane::SplitRight.boxed_clone())
|
||||
.action("Split Up", pane::SplitUp.boxed_clone())
|
||||
.action("Split Down", pane::SplitDown.boxed_clone())
|
||||
Some(ContextMenu::build(cx, {
|
||||
let context = context.clone();
|
||||
move |menu, _| {
|
||||
menu.context(context)
|
||||
.action("Split Left", pane::SplitLeft.boxed_clone())
|
||||
.action("Split Right", pane::SplitRight.boxed_clone())
|
||||
.action("Split Up", pane::SplitUp.boxed_clone())
|
||||
.action("Split Down", pane::SplitDown.boxed_clone())
|
||||
}
|
||||
}))
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -15,7 +15,10 @@ actions!(
|
||||
SelectAll,
|
||||
Home,
|
||||
End,
|
||||
ShowCharacterPalette
|
||||
ShowCharacterPalette,
|
||||
Paste,
|
||||
Cut,
|
||||
Copy,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -107,6 +110,28 @@ impl TextInput {
|
||||
cx.show_character_palette();
|
||||
}
|
||||
|
||||
fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
|
||||
if let Some(text) = cx.read_from_clipboard().and_then(|item| item.text()) {
|
||||
self.replace_text_in_range(None, &text.replace("\n", " "), cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
|
||||
if !self.selected_range.is_empty() {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(
|
||||
(&self.content[self.selected_range.clone()]).to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
fn cut(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
|
||||
if !self.selected_range.is_empty() {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(
|
||||
(&self.content[self.selected_range.clone()]).to_string(),
|
||||
));
|
||||
self.replace_text_in_range(None, "", cx)
|
||||
}
|
||||
}
|
||||
|
||||
fn move_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
|
||||
self.selected_range = offset..offset;
|
||||
cx.notify()
|
||||
@@ -219,9 +244,11 @@ impl ViewInputHandler for TextInput {
|
||||
fn text_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
actual_range: &mut Option<Range<usize>>,
|
||||
_cx: &mut ViewContext<Self>,
|
||||
) -> Option<String> {
|
||||
let range = self.range_from_utf16(&range_utf16);
|
||||
actual_range.replace(self.range_to_utf16(&range));
|
||||
Some(self.content[range].to_string())
|
||||
}
|
||||
|
||||
@@ -497,6 +524,9 @@ impl Render for TextInput {
|
||||
.on_action(cx.listener(Self::home))
|
||||
.on_action(cx.listener(Self::end))
|
||||
.on_action(cx.listener(Self::show_character_palette))
|
||||
.on_action(cx.listener(Self::paste))
|
||||
.on_action(cx.listener(Self::cut))
|
||||
.on_action(cx.listener(Self::copy))
|
||||
.on_mouse_down(MouseButton::Left, cx.listener(Self::on_mouse_down))
|
||||
.on_mouse_up(MouseButton::Left, cx.listener(Self::on_mouse_up))
|
||||
.on_mouse_up_out(MouseButton::Left, cx.listener(Self::on_mouse_up))
|
||||
@@ -581,8 +611,8 @@ impl Render for InputExample {
|
||||
format!(
|
||||
"{:} {}",
|
||||
ks.unparse(),
|
||||
if let Some(ime_key) = ks.ime_key.as_ref() {
|
||||
format!("-> {:?}", ime_key)
|
||||
if let Some(key_char) = ks.key_char.as_ref() {
|
||||
format!("-> {:?}", key_char)
|
||||
} else {
|
||||
"".to_owned()
|
||||
}
|
||||
@@ -602,6 +632,9 @@ fn main() {
|
||||
KeyBinding::new("shift-left", SelectLeft, None),
|
||||
KeyBinding::new("shift-right", SelectRight, None),
|
||||
KeyBinding::new("cmd-a", SelectAll, None),
|
||||
KeyBinding::new("cmd-v", Paste, None),
|
||||
KeyBinding::new("cmd-c", Copy, None),
|
||||
KeyBinding::new("cmd-x", Cut, None),
|
||||
KeyBinding::new("home", Home, None),
|
||||
KeyBinding::new("end", End, None),
|
||||
KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, None),
|
||||
|
||||
@@ -9,8 +9,12 @@ use std::ops::Range;
|
||||
/// See [`InputHandler`] for details on how to implement each method.
|
||||
pub trait ViewInputHandler: 'static + Sized {
|
||||
/// See [`InputHandler::text_for_range`] for details
|
||||
fn text_for_range(&mut self, range: Range<usize>, cx: &mut ViewContext<Self>)
|
||||
-> Option<String>;
|
||||
fn text_for_range(
|
||||
&mut self,
|
||||
range: Range<usize>,
|
||||
adjusted_range: &mut Option<Range<usize>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<String>;
|
||||
|
||||
/// See [`InputHandler::selected_text_range`] for details
|
||||
fn selected_text_range(
|
||||
@@ -89,10 +93,12 @@ impl<V: ViewInputHandler> InputHandler for ElementInputHandler<V> {
|
||||
fn text_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
adjusted_range: &mut Option<Range<usize>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<String> {
|
||||
self.view
|
||||
.update(cx, |view, cx| view.text_for_range(range_utf16, cx))
|
||||
self.view.update(cx, |view, cx| {
|
||||
view.text_for_range(range_utf16, adjusted_range, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn replace_text_in_range(
|
||||
|
||||
@@ -643,9 +643,13 @@ impl PlatformInputHandler {
|
||||
}
|
||||
|
||||
#[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
|
||||
fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String> {
|
||||
fn text_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
adjusted: &mut Option<Range<usize>>,
|
||||
) -> Option<String> {
|
||||
self.cx
|
||||
.update(|cx| self.handler.text_for_range(range_utf16, cx))
|
||||
.update(|cx| self.handler.text_for_range(range_utf16, adjusted, cx))
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
@@ -712,6 +716,7 @@ impl PlatformInputHandler {
|
||||
|
||||
/// A struct representing a selection in a text buffer, in UTF16 characters.
|
||||
/// This is different from a range because the head may be before the tail.
|
||||
#[derive(Debug)]
|
||||
pub struct UTF16Selection {
|
||||
/// The range of text in the document this selection corresponds to
|
||||
/// in UTF16 characters.
|
||||
@@ -749,6 +754,7 @@ pub trait InputHandler: 'static {
|
||||
fn text_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
adjusted_range: &mut Option<Range<usize>>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<String>;
|
||||
|
||||
|
||||
@@ -12,14 +12,15 @@ pub struct Keystroke {
|
||||
/// e.g. for option-s, key is "s"
|
||||
pub key: String,
|
||||
|
||||
/// ime_key is the character inserted by the IME engine when that key was pressed.
|
||||
/// e.g. for option-s, ime_key is "ß"
|
||||
pub ime_key: Option<String>,
|
||||
/// key_char is the character that could have been typed when
|
||||
/// this binding was pressed.
|
||||
/// e.g. for s this is "s", for option-s "ß", and cmd-s None
|
||||
pub key_char: Option<String>,
|
||||
}
|
||||
|
||||
impl Keystroke {
|
||||
/// When matching a key we cannot know whether the user intended to type
|
||||
/// the ime_key or the key itself. On some non-US keyboards keys we use in our
|
||||
/// the key_char or the key itself. On some non-US keyboards keys we use in our
|
||||
/// bindings are behind option (for example `$` is typed `alt-ç` on a Czech keyboard),
|
||||
/// and on some keyboards the IME handler converts a sequence of keys into a
|
||||
/// specific character (for example `"` is typed as `" space` on a brazilian keyboard).
|
||||
@@ -27,10 +28,10 @@ impl Keystroke {
|
||||
/// This method assumes that `self` was typed and `target' is in the keymap, and checks
|
||||
/// both possibilities for self against the target.
|
||||
pub(crate) fn should_match(&self, target: &Keystroke) -> bool {
|
||||
if let Some(ime_key) = self
|
||||
.ime_key
|
||||
if let Some(key_char) = self
|
||||
.key_char
|
||||
.as_ref()
|
||||
.filter(|ime_key| ime_key != &&self.key)
|
||||
.filter(|key_char| key_char != &&self.key)
|
||||
{
|
||||
let ime_modifiers = Modifiers {
|
||||
control: self.modifiers.control,
|
||||
@@ -38,7 +39,7 @@ impl Keystroke {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if &target.key == ime_key && target.modifiers == ime_modifiers {
|
||||
if &target.key == key_char && target.modifiers == ime_modifiers {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -47,9 +48,9 @@ impl Keystroke {
|
||||
}
|
||||
|
||||
/// key syntax is:
|
||||
/// [ctrl-][alt-][shift-][cmd-][fn-]key[->ime_key]
|
||||
/// ime_key syntax is only used for generating test events,
|
||||
/// when matching a key with an ime_key set will be matched without it.
|
||||
/// [ctrl-][alt-][shift-][cmd-][fn-]key[->key_char]
|
||||
/// key_char syntax is only used for generating test events,
|
||||
/// when matching a key with an key_char set will be matched without it.
|
||||
pub fn parse(source: &str) -> anyhow::Result<Self> {
|
||||
let mut control = false;
|
||||
let mut alt = false;
|
||||
@@ -57,7 +58,7 @@ impl Keystroke {
|
||||
let mut platform = false;
|
||||
let mut function = false;
|
||||
let mut key = None;
|
||||
let mut ime_key = None;
|
||||
let mut key_char = None;
|
||||
|
||||
let mut components = source.split('-').peekable();
|
||||
while let Some(component) = components.next() {
|
||||
@@ -74,7 +75,7 @@ impl Keystroke {
|
||||
break;
|
||||
} else if next.len() > 1 && next.starts_with('>') {
|
||||
key = Some(String::from(component));
|
||||
ime_key = Some(String::from(&next[1..]));
|
||||
key_char = Some(String::from(&next[1..]));
|
||||
components.next();
|
||||
} else {
|
||||
return Err(anyhow!("Invalid keystroke `{}`", source));
|
||||
@@ -118,7 +119,7 @@ impl Keystroke {
|
||||
function,
|
||||
},
|
||||
key,
|
||||
ime_key,
|
||||
key_char: key_char,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -154,7 +155,7 @@ impl Keystroke {
|
||||
/// Returns true if this keystroke left
|
||||
/// the ime system in an incomplete state.
|
||||
pub fn is_ime_in_progress(&self) -> bool {
|
||||
self.ime_key.is_none()
|
||||
self.key_char.is_none()
|
||||
&& (is_printable_key(&self.key) || self.key.is_empty())
|
||||
&& !(self.modifiers.platform
|
||||
|| self.modifiers.control
|
||||
@@ -162,17 +163,17 @@ impl Keystroke {
|
||||
|| self.modifiers.alt)
|
||||
}
|
||||
|
||||
/// Returns a new keystroke with the ime_key filled.
|
||||
/// Returns a new keystroke with the key_char filled.
|
||||
/// This is used for dispatch_keystroke where we want users to
|
||||
/// be able to simulate typing "space", etc.
|
||||
pub fn with_simulated_ime(mut self) -> Self {
|
||||
if self.ime_key.is_none()
|
||||
if self.key_char.is_none()
|
||||
&& !self.modifiers.platform
|
||||
&& !self.modifiers.control
|
||||
&& !self.modifiers.function
|
||||
&& !self.modifiers.alt
|
||||
{
|
||||
self.ime_key = match self.key.as_str() {
|
||||
self.key_char = match self.key.as_str() {
|
||||
"space" => Some(" ".into()),
|
||||
"tab" => Some("\t".into()),
|
||||
"enter" => Some("\n".into()),
|
||||
|
||||
@@ -742,14 +742,14 @@ impl Keystroke {
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore control characters (and DEL) for the purposes of ime_key
|
||||
let ime_key =
|
||||
// Ignore control characters (and DEL) for the purposes of key_char
|
||||
let key_char =
|
||||
(key_utf32 >= 32 && key_utf32 != 127 && !key_utf8.is_empty()).then_some(key_utf8);
|
||||
|
||||
Keystroke {
|
||||
modifiers,
|
||||
key,
|
||||
ime_key,
|
||||
key_char,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1208,7 +1208,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
||||
compose.feed(keysym);
|
||||
match compose.status() {
|
||||
xkb::Status::Composing => {
|
||||
keystroke.ime_key = None;
|
||||
keystroke.key_char = None;
|
||||
state.pre_edit_text =
|
||||
compose.utf8().or(Keystroke::underlying_dead_key(keysym));
|
||||
let pre_edit =
|
||||
@@ -1220,7 +1220,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
||||
|
||||
xkb::Status::Composed => {
|
||||
state.pre_edit_text.take();
|
||||
keystroke.ime_key = compose.utf8();
|
||||
keystroke.key_char = compose.utf8();
|
||||
if let Some(keysym) = compose.keysym() {
|
||||
keystroke.key = xkb::keysym_get_name(keysym);
|
||||
}
|
||||
@@ -1340,7 +1340,7 @@ impl Dispatch<zwp_text_input_v3::ZwpTextInputV3, ()> for WaylandClientStatePtr {
|
||||
keystroke: Keystroke {
|
||||
modifiers: Modifiers::default(),
|
||||
key: commit_text.clone(),
|
||||
ime_key: Some(commit_text),
|
||||
key_char: Some(commit_text),
|
||||
},
|
||||
is_held: false,
|
||||
}));
|
||||
|
||||
@@ -687,11 +687,11 @@ impl WaylandWindowStatePtr {
|
||||
}
|
||||
}
|
||||
if let PlatformInput::KeyDown(event) = input {
|
||||
if let Some(ime_key) = &event.keystroke.ime_key {
|
||||
if let Some(key_char) = &event.keystroke.key_char {
|
||||
let mut state = self.state.borrow_mut();
|
||||
if let Some(mut input_handler) = state.input_handler.take() {
|
||||
drop(state);
|
||||
input_handler.replace_text_in_range(None, ime_key);
|
||||
input_handler.replace_text_in_range(None, key_char);
|
||||
self.state.borrow_mut().input_handler = Some(input_handler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ pub struct X11ClientState {
|
||||
pub(crate) compose_state: Option<xkbc::compose::State>,
|
||||
pub(crate) pre_edit_text: Option<String>,
|
||||
pub(crate) composing: bool,
|
||||
pub(crate) pre_ime_key_down: Option<Keystroke>,
|
||||
pub(crate) pre_key_char_down: Option<Keystroke>,
|
||||
pub(crate) cursor_handle: cursor::Handle,
|
||||
pub(crate) cursor_styles: HashMap<xproto::Window, CursorStyle>,
|
||||
pub(crate) cursor_cache: HashMap<CursorStyle, xproto::Cursor>,
|
||||
@@ -446,7 +446,7 @@ impl X11Client {
|
||||
|
||||
compose_state,
|
||||
pre_edit_text: None,
|
||||
pre_ime_key_down: None,
|
||||
pre_key_char_down: None,
|
||||
composing: false,
|
||||
|
||||
cursor_handle,
|
||||
@@ -858,7 +858,7 @@ impl X11Client {
|
||||
|
||||
let modifiers = modifiers_from_state(event.state);
|
||||
state.modifiers = modifiers;
|
||||
state.pre_ime_key_down.take();
|
||||
state.pre_key_char_down.take();
|
||||
let keystroke = {
|
||||
let code = event.detail.into();
|
||||
let xkb_state = state.previous_xkb_state.clone();
|
||||
@@ -880,13 +880,13 @@ impl X11Client {
|
||||
match compose_state.status() {
|
||||
xkbc::Status::Composed => {
|
||||
state.pre_edit_text.take();
|
||||
keystroke.ime_key = compose_state.utf8();
|
||||
keystroke.key_char = compose_state.utf8();
|
||||
if let Some(keysym) = compose_state.keysym() {
|
||||
keystroke.key = xkbc::keysym_get_name(keysym);
|
||||
}
|
||||
}
|
||||
xkbc::Status::Composing => {
|
||||
keystroke.ime_key = None;
|
||||
keystroke.key_char = None;
|
||||
state.pre_edit_text = compose_state
|
||||
.utf8()
|
||||
.or(crate::Keystroke::underlying_dead_key(keysym));
|
||||
@@ -1156,7 +1156,7 @@ impl X11Client {
|
||||
match event {
|
||||
Event::KeyPress(event) | Event::KeyRelease(event) => {
|
||||
let mut state = self.0.borrow_mut();
|
||||
state.pre_ime_key_down = Some(Keystroke::from_xkb(
|
||||
state.pre_key_char_down = Some(Keystroke::from_xkb(
|
||||
&state.xkb,
|
||||
state.modifiers,
|
||||
event.detail.into(),
|
||||
@@ -1187,11 +1187,11 @@ impl X11Client {
|
||||
fn xim_handle_commit(&self, window: xproto::Window, text: String) -> Option<()> {
|
||||
let window = self.get_window(window).unwrap();
|
||||
let mut state = self.0.borrow_mut();
|
||||
let keystroke = state.pre_ime_key_down.take();
|
||||
let keystroke = state.pre_key_char_down.take();
|
||||
state.composing = false;
|
||||
drop(state);
|
||||
if let Some(mut keystroke) = keystroke {
|
||||
keystroke.ime_key = Some(text.clone());
|
||||
keystroke.key_char = Some(text.clone());
|
||||
window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
|
||||
keystroke,
|
||||
is_held: false,
|
||||
|
||||
@@ -846,9 +846,9 @@ impl X11WindowStatePtr {
|
||||
if let PlatformInput::KeyDown(event) = input {
|
||||
let mut state = self.state.borrow_mut();
|
||||
if let Some(mut input_handler) = state.input_handler.take() {
|
||||
if let Some(ime_key) = &event.keystroke.ime_key {
|
||||
if let Some(key_char) = &event.keystroke.key_char {
|
||||
drop(state);
|
||||
input_handler.replace_text_in_range(None, ime_key);
|
||||
input_handler.replace_text_in_range(None, key_char);
|
||||
state = self.state.borrow_mut();
|
||||
}
|
||||
state.input_handler = Some(input_handler);
|
||||
|
||||
@@ -245,7 +245,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
|
||||
.charactersIgnoringModifiers()
|
||||
.to_str()
|
||||
.to_string();
|
||||
let mut ime_key = None;
|
||||
let mut key_char = None;
|
||||
let first_char = characters.chars().next().map(|ch| ch as u16);
|
||||
let modifiers = native_event.modifierFlags();
|
||||
|
||||
@@ -261,13 +261,19 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
|
||||
#[allow(non_upper_case_globals)]
|
||||
let key = match first_char {
|
||||
Some(SPACE_KEY) => {
|
||||
ime_key = Some(" ".to_string());
|
||||
key_char = Some(" ".to_string());
|
||||
"space".to_string()
|
||||
}
|
||||
Some(TAB_KEY) => {
|
||||
key_char = Some("\t".to_string());
|
||||
"tab".to_string()
|
||||
}
|
||||
Some(ENTER_KEY) | Some(NUMPAD_ENTER_KEY) => {
|
||||
key_char = Some("\n".to_string());
|
||||
"enter".to_string()
|
||||
}
|
||||
Some(BACKSPACE_KEY) => "backspace".to_string(),
|
||||
Some(ENTER_KEY) | Some(NUMPAD_ENTER_KEY) => "enter".to_string(),
|
||||
Some(ESCAPE_KEY) => "escape".to_string(),
|
||||
Some(TAB_KEY) => "tab".to_string(),
|
||||
Some(SHIFT_TAB_KEY) => "tab".to_string(),
|
||||
Some(NSUpArrowFunctionKey) => "up".to_string(),
|
||||
Some(NSDownArrowFunctionKey) => "down".to_string(),
|
||||
@@ -335,6 +341,18 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
|
||||
chars_ignoring_modifiers = chars_with_cmd;
|
||||
}
|
||||
|
||||
if !control && !command && !function {
|
||||
let mut mods = NO_MOD;
|
||||
if shift {
|
||||
mods |= SHIFT_MOD;
|
||||
}
|
||||
if alt {
|
||||
mods |= OPTION_MOD;
|
||||
}
|
||||
|
||||
key_char = Some(chars_for_modified_key(native_event.keyCode(), mods));
|
||||
}
|
||||
|
||||
let mut key = if shift
|
||||
&& chars_ignoring_modifiers
|
||||
.chars()
|
||||
@@ -348,20 +366,6 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
|
||||
chars_ignoring_modifiers
|
||||
};
|
||||
|
||||
if always_use_cmd_layout || alt {
|
||||
let mut mods = NO_MOD;
|
||||
if shift {
|
||||
mods |= SHIFT_MOD;
|
||||
}
|
||||
if alt {
|
||||
mods |= OPTION_MOD;
|
||||
}
|
||||
let alt_key = chars_for_modified_key(native_event.keyCode(), mods);
|
||||
if alt_key != key {
|
||||
ime_key = Some(alt_key);
|
||||
}
|
||||
};
|
||||
|
||||
key
|
||||
}
|
||||
};
|
||||
@@ -375,7 +379,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
|
||||
function,
|
||||
},
|
||||
key,
|
||||
ime_key,
|
||||
key_char,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -844,7 +844,9 @@ impl Platform for MacPlatform {
|
||||
let app: id = msg_send![APP_CLASS, sharedApplication];
|
||||
let mut state = self.0.lock();
|
||||
let actions = &mut state.menu_actions;
|
||||
app.setMainMenu_(self.create_menu_bar(menus, NSWindow::delegate(app), actions, keymap));
|
||||
let menu = self.create_menu_bar(menus, NSWindow::delegate(app), actions, keymap);
|
||||
drop(state);
|
||||
app.setMainMenu_(menu);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ use std::{
|
||||
cell::Cell,
|
||||
ffi::{c_void, CStr},
|
||||
mem,
|
||||
ops::Range,
|
||||
path::PathBuf,
|
||||
ptr::{self, NonNull},
|
||||
rc::Rc,
|
||||
@@ -1283,18 +1284,17 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
|
||||
}
|
||||
|
||||
if event.is_held {
|
||||
let handled = with_input_handler(&this, |input_handler| {
|
||||
if !input_handler.apple_press_and_hold_enabled() {
|
||||
input_handler.replace_text_in_range(
|
||||
None,
|
||||
&event.keystroke.ime_key.unwrap_or(event.keystroke.key),
|
||||
);
|
||||
if let Some(key_char) = event.keystroke.key_char.as_ref() {
|
||||
let handled = with_input_handler(&this, |input_handler| {
|
||||
if !input_handler.apple_press_and_hold_enabled() {
|
||||
input_handler.replace_text_in_range(None, &key_char);
|
||||
return YES;
|
||||
}
|
||||
NO
|
||||
});
|
||||
if handled == Some(YES) {
|
||||
return YES;
|
||||
}
|
||||
NO
|
||||
});
|
||||
if handled == Some(YES) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1437,7 +1437,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
|
||||
let keystroke = Keystroke {
|
||||
modifiers: Default::default(),
|
||||
key: ".".into(),
|
||||
ime_key: None,
|
||||
key_char: None,
|
||||
};
|
||||
let event = PlatformInput::KeyDown(KeyDownEvent {
|
||||
keystroke: keystroke.clone(),
|
||||
@@ -1755,15 +1755,21 @@ extern "C" fn attributed_substring_for_proposed_range(
|
||||
this: &Object,
|
||||
_: Sel,
|
||||
range: NSRange,
|
||||
_actual_range: *mut c_void,
|
||||
actual_range: *mut c_void,
|
||||
) -> id {
|
||||
with_input_handler(this, |input_handler| {
|
||||
let range = range.to_range()?;
|
||||
if range.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut adjusted: Option<Range<usize>> = None;
|
||||
|
||||
let selected_text = input_handler.text_for_range(range.clone())?;
|
||||
let selected_text = input_handler.text_for_range(range.clone(), &mut adjusted)?;
|
||||
if let Some(adjusted) = adjusted {
|
||||
if adjusted != range {
|
||||
unsafe { (actual_range as *mut NSRange).write(NSRange::from(adjusted)) };
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
let string: id = msg_send![class!(NSAttributedString), alloc];
|
||||
let string: id = msg_send![string, initWithString: ns_string(&selected_text)];
|
||||
|
||||
@@ -386,7 +386,7 @@ fn handle_char_msg(
|
||||
return Some(1);
|
||||
};
|
||||
drop(lock);
|
||||
let ime_key = keystroke.ime_key.clone();
|
||||
let key_char = keystroke.key_char.clone();
|
||||
let event = KeyDownEvent {
|
||||
keystroke,
|
||||
is_held: lparam.0 & (0x1 << 30) > 0,
|
||||
@@ -397,7 +397,7 @@ fn handle_char_msg(
|
||||
if dispatch_event_result.default_prevented || !dispatch_event_result.propagate {
|
||||
return Some(0);
|
||||
}
|
||||
let Some(ime_char) = ime_key else {
|
||||
let Some(ime_char) = key_char else {
|
||||
return Some(1);
|
||||
};
|
||||
with_input_handler(&state_ptr, |input_handler| {
|
||||
@@ -1172,7 +1172,7 @@ fn parse_syskeydown_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
|
||||
Some(Keystroke {
|
||||
modifiers,
|
||||
key,
|
||||
ime_key: None,
|
||||
key_char: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1220,7 +1220,7 @@ fn parse_keydown_msg_keystroke(wparam: WPARAM) -> Option<KeystrokeOrModifier> {
|
||||
return Some(KeystrokeOrModifier::Keystroke(Keystroke {
|
||||
modifiers,
|
||||
key: format!("f{}", offset + 1),
|
||||
ime_key: None,
|
||||
key_char: None,
|
||||
}));
|
||||
};
|
||||
return None;
|
||||
@@ -1231,7 +1231,7 @@ fn parse_keydown_msg_keystroke(wparam: WPARAM) -> Option<KeystrokeOrModifier> {
|
||||
Some(KeystrokeOrModifier::Keystroke(Keystroke {
|
||||
modifiers,
|
||||
key,
|
||||
ime_key: None,
|
||||
key_char: None,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -1253,7 +1253,7 @@ fn parse_char_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
|
||||
Some(Keystroke {
|
||||
modifiers,
|
||||
key,
|
||||
ime_key: Some(first_char.to_string()),
|
||||
key_char: Some(first_char.to_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1327,7 +1327,7 @@ fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke>
|
||||
Some(Keystroke {
|
||||
modifiers,
|
||||
key,
|
||||
ime_key: None,
|
||||
key_char: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -3038,7 +3038,7 @@ impl<'a> WindowContext<'a> {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some(input) = keystroke.with_simulated_ime().ime_key {
|
||||
if let Some(input) = keystroke.key_char {
|
||||
if let Some(mut input_handler) = self.window.platform_window.take_input_handler() {
|
||||
input_handler.dispatch_input(&input, self);
|
||||
self.window.platform_window.set_input_handler(input_handler);
|
||||
@@ -3050,7 +3050,7 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
|
||||
/// Represent this action as a key binding string, to display in the UI.
|
||||
pub fn keystroke_text_for_action(&self, action: &dyn Action) -> String {
|
||||
pub fn keystroke_text_for(&self, action: &dyn Action) -> String {
|
||||
self.bindings_for_action(action)
|
||||
.into_iter()
|
||||
.next()
|
||||
@@ -3065,26 +3065,6 @@ impl<'a> WindowContext<'a> {
|
||||
.unwrap_or_else(|| action.name().to_string())
|
||||
}
|
||||
|
||||
/// Represent this action as a key binding string, to display in the UI.
|
||||
pub fn keystroke_text_for_action_in(
|
||||
&self,
|
||||
action: &dyn Action,
|
||||
focus_handle: &FocusHandle,
|
||||
) -> String {
|
||||
self.bindings_for_action_in(action, focus_handle)
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|binding| {
|
||||
binding
|
||||
.keystrokes()
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
})
|
||||
.unwrap_or_else(|| action.name().to_string())
|
||||
}
|
||||
|
||||
/// Dispatch a mouse or keyboard event on the window.
|
||||
#[profiling::function]
|
||||
pub fn dispatch_event(&mut self, event: PlatformInput) -> DispatchEventResult {
|
||||
@@ -3267,7 +3247,7 @@ impl<'a> WindowContext<'a> {
|
||||
if let Some(key) = key {
|
||||
keystroke = Some(Keystroke {
|
||||
key: key.to_string(),
|
||||
ime_key: None,
|
||||
key_char: None,
|
||||
modifiers: Modifiers::default(),
|
||||
});
|
||||
}
|
||||
@@ -3482,13 +3462,7 @@ impl<'a> WindowContext<'a> {
|
||||
if !self.propagate_event {
|
||||
continue 'replay;
|
||||
}
|
||||
if let Some(input) = replay
|
||||
.keystroke
|
||||
.with_simulated_ime()
|
||||
.ime_key
|
||||
.as_ref()
|
||||
.cloned()
|
||||
{
|
||||
if let Some(input) = replay.keystroke.key_char.as_ref().cloned() {
|
||||
if let Some(mut input_handler) = self.window.platform_window.take_input_handler() {
|
||||
input_handler.dispatch_input(&input, self);
|
||||
self.window.platform_window.set_input_handler(input_handler)
|
||||
|
||||
@@ -904,13 +904,17 @@ impl LspAdapter for PyLspAdapter {
|
||||
.unwrap_or_else(|| {
|
||||
json!({
|
||||
"plugins": {
|
||||
"rope_autoimport": {"enabled": true},
|
||||
"mypy": {"enabled": true}
|
||||
}
|
||||
"pycodestyle": {"enabled": false},
|
||||
"rope_autoimport": {"enabled": true, "memory": true},
|
||||
"pylsp_mypy": {"enabled": false}
|
||||
},
|
||||
"rope": {
|
||||
"ropeFolder": null
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
// If python.pythonPath is not set in user config, do so using our toolchain picker.
|
||||
// If user did not explicitly modify their python venv, use one from picker.
|
||||
if let Some(toolchain) = toolchain {
|
||||
if user_settings.is_null() {
|
||||
user_settings = Value::Object(serde_json::Map::default());
|
||||
@@ -926,23 +930,22 @@ impl LspAdapter for PyLspAdapter {
|
||||
.or_insert(Value::Object(serde_json::Map::default()))
|
||||
.as_object_mut()
|
||||
{
|
||||
jedi.insert(
|
||||
"environment".to_string(),
|
||||
Value::String(toolchain.path.clone().into()),
|
||||
);
|
||||
jedi.entry("environment".to_string())
|
||||
.or_insert_with(|| Value::String(toolchain.path.clone().into()));
|
||||
}
|
||||
if let Some(pylint) = python
|
||||
.entry("mypy")
|
||||
.entry("pylsp_mypy")
|
||||
.or_insert(Value::Object(serde_json::Map::default()))
|
||||
.as_object_mut()
|
||||
{
|
||||
pylint.insert(
|
||||
"overrides".to_string(),
|
||||
pylint.entry("overrides".to_string()).or_insert_with(|| {
|
||||
Value::Array(vec![
|
||||
Value::String("--python-executable".into()),
|
||||
Value::String(toolchain.path.into()),
|
||||
]),
|
||||
);
|
||||
Value::String("--cache-dir=/dev/null".into()),
|
||||
Value::Bool(true),
|
||||
])
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,7 +412,7 @@ impl LspAdapter for EsLintLspAdapter {
|
||||
_delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let url = build_asset_url(
|
||||
"microsoft/vscode-eslint",
|
||||
"zed-industries/vscode-eslint",
|
||||
Self::CURRENT_VERSION_TAG_NAME,
|
||||
Self::GITHUB_ASSET_KIND,
|
||||
)?;
|
||||
|
||||
@@ -206,7 +206,7 @@ fn render_markdown_list_item(
|
||||
let secondary_modifier = Keystroke {
|
||||
key: "".to_string(),
|
||||
modifiers: Modifiers::secondary_key(),
|
||||
ime_key: None,
|
||||
key_char: None,
|
||||
};
|
||||
Tooltip::text(
|
||||
format!("{}-click to toggle the checkbox", secondary_modifier),
|
||||
|
||||
@@ -3875,13 +3875,13 @@ impl OutlinePanel {
|
||||
.child({
|
||||
let keystroke = match self.position(cx) {
|
||||
DockPosition::Left => {
|
||||
cx.keystroke_text_for_action(&workspace::ToggleLeftDock)
|
||||
cx.keystroke_text_for(&workspace::ToggleLeftDock)
|
||||
}
|
||||
DockPosition::Bottom => {
|
||||
cx.keystroke_text_for_action(&workspace::ToggleBottomDock)
|
||||
cx.keystroke_text_for(&workspace::ToggleBottomDock)
|
||||
}
|
||||
DockPosition::Right => {
|
||||
cx.keystroke_text_for_action(&workspace::ToggleRightDock)
|
||||
cx.keystroke_text_for(&workspace::ToggleRightDock)
|
||||
}
|
||||
};
|
||||
Label::new(format!("Toggle this panel with {keystroke}"))
|
||||
|
||||
@@ -185,13 +185,13 @@ impl PickerDelegate for RecentProjectsDelegate {
|
||||
fn placeholder_text(&self, cx: &mut WindowContext) -> Arc<str> {
|
||||
let (create_window, reuse_window) = if self.create_new_window {
|
||||
(
|
||||
cx.keystroke_text_for_action(&menu::Confirm),
|
||||
cx.keystroke_text_for_action(&menu::SecondaryConfirm),
|
||||
cx.keystroke_text_for(&menu::Confirm),
|
||||
cx.keystroke_text_for(&menu::SecondaryConfirm),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
cx.keystroke_text_for_action(&menu::SecondaryConfirm),
|
||||
cx.keystroke_text_for_action(&menu::Confirm),
|
||||
cx.keystroke_text_for(&menu::SecondaryConfirm),
|
||||
cx.keystroke_text_for(&menu::Confirm),
|
||||
)
|
||||
};
|
||||
Arc::from(format!(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -343,7 +343,7 @@ mod test {
|
||||
function: false,
|
||||
},
|
||||
key: "🖖🏻".to_string(), //2 char string
|
||||
ime_key: None,
|
||||
key_char: None,
|
||||
};
|
||||
assert_eq!(to_esc_str(&ks, &TermMode::NONE, false), None);
|
||||
}
|
||||
|
||||
@@ -1001,6 +1001,7 @@ impl InputHandler for TerminalInputHandler {
|
||||
fn text_for_range(
|
||||
&mut self,
|
||||
_: std::ops::Range<usize>,
|
||||
_: &mut Option<std::ops::Range<usize>>,
|
||||
_: &mut WindowContext,
|
||||
) -> Option<String> {
|
||||
None
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::PlatformStyle;
|
||||
use crate::{h_flex, prelude::*, Icon, IconName, IconSize};
|
||||
use gpui::{relative, Action, FocusHandle, IntoElement, Keystroke, WindowContext};
|
||||
|
||||
#[derive(IntoElement, Clone)]
|
||||
#[derive(Debug, IntoElement, Clone)]
|
||||
pub struct KeyBinding {
|
||||
/// A keybinding consists of a key and a set of modifier keys.
|
||||
/// More then one keybinding produces a chord.
|
||||
|
||||
@@ -83,7 +83,7 @@ impl Vim {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
// handled by handle_literal_input
|
||||
if keystroke_event.keystroke.ime_key.is_some() {
|
||||
if keystroke_event.keystroke.key_char.is_some() {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description = "The fast, collaborative code editor."
|
||||
edition = "2021"
|
||||
name = "zed"
|
||||
version = "0.163.0"
|
||||
version = "0.163.2"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
authors = ["Zed Team <hi@zed.dev>"]
|
||||
|
||||
@@ -1 +1 @@
|
||||
dev
|
||||
stable
|
||||
@@ -66,7 +66,7 @@ use zed::{
|
||||
OpenRequest,
|
||||
};
|
||||
|
||||
use crate::zed::{assistant_hints, inline_completion_registry};
|
||||
use crate::zed::inline_completion_registry;
|
||||
|
||||
#[cfg(feature = "mimalloc")]
|
||||
#[global_allocator]
|
||||
@@ -401,7 +401,6 @@ fn main() {
|
||||
stdout_is_a_pty(),
|
||||
cx,
|
||||
);
|
||||
assistant_hints::init(cx);
|
||||
repl::init(
|
||||
app_state.fs.clone(),
|
||||
app_state.client.telemetry().clone(),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
mod app_menus;
|
||||
pub mod assistant_hints;
|
||||
pub mod inline_completion_registry;
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
pub(crate) mod linux_prompts;
|
||||
@@ -824,8 +823,13 @@ pub fn handle_keymap_file_changes(
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.on_keyboard_layout_change(move |_| {
|
||||
keyboard_layout_tx.unbounded_send(()).ok();
|
||||
let mut current_mapping = settings::get_key_equivalents(cx.keyboard_layout());
|
||||
cx.on_keyboard_layout_change(move |cx| {
|
||||
let next_mapping = settings::get_key_equivalents(cx.keyboard_layout());
|
||||
if next_mapping != current_mapping {
|
||||
current_mapping = next_mapping;
|
||||
keyboard_layout_tx.unbounded_send(()).ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
@@ -3162,12 +3166,7 @@ mod tests {
|
||||
.fs
|
||||
.save(
|
||||
"/settings.json".as_ref(),
|
||||
&r#"
|
||||
{
|
||||
"base_keymap": "Atom"
|
||||
}
|
||||
"#
|
||||
.into(),
|
||||
&r#"{"base_keymap": "Atom"}"#.into(),
|
||||
Default::default(),
|
||||
)
|
||||
.await
|
||||
@@ -3177,16 +3176,7 @@ mod tests {
|
||||
.fs
|
||||
.save(
|
||||
"/keymap.json".as_ref(),
|
||||
&r#"
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"backspace": "test1::A"
|
||||
}
|
||||
}
|
||||
]
|
||||
"#
|
||||
.into(),
|
||||
&r#"[{"bindings": {"backspace": "test1::A"}}]"#.into(),
|
||||
Default::default(),
|
||||
)
|
||||
.await
|
||||
@@ -3229,16 +3219,7 @@ mod tests {
|
||||
.fs
|
||||
.save(
|
||||
"/keymap.json".as_ref(),
|
||||
&r#"
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"backspace": "test1::B"
|
||||
}
|
||||
}
|
||||
]
|
||||
"#
|
||||
.into(),
|
||||
&r#"[{"bindings": {"backspace": "test1::B"}}]"#.into(),
|
||||
Default::default(),
|
||||
)
|
||||
.await
|
||||
@@ -3258,12 +3239,7 @@ mod tests {
|
||||
.fs
|
||||
.save(
|
||||
"/settings.json".as_ref(),
|
||||
&r#"
|
||||
{
|
||||
"base_keymap": "JetBrains"
|
||||
}
|
||||
"#
|
||||
.into(),
|
||||
&r#"{"base_keymap": "JetBrains"}"#.into(),
|
||||
Default::default(),
|
||||
)
|
||||
.await
|
||||
@@ -3290,24 +3266,20 @@ mod tests {
|
||||
// From the Atom keymap
|
||||
use workspace::ActivatePreviousPane;
|
||||
// From the JetBrains keymap
|
||||
use pane::ActivatePrevItem;
|
||||
use diagnostics::Deploy;
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, _| {
|
||||
workspace
|
||||
.register_action(|_, _: &A, _| {})
|
||||
.register_action(|_, _: &B, _| {});
|
||||
workspace.register_action(|_, _: &A, _cx| {});
|
||||
workspace.register_action(|_, _: &B, _cx| {});
|
||||
workspace.register_action(|_, _: &Deploy, _cx| {});
|
||||
})
|
||||
.unwrap();
|
||||
app_state
|
||||
.fs
|
||||
.save(
|
||||
"/settings.json".as_ref(),
|
||||
&r#"
|
||||
{
|
||||
"base_keymap": "Atom"
|
||||
}
|
||||
"#
|
||||
.into(),
|
||||
&r#"{"base_keymap": "Atom"}"#.into(),
|
||||
Default::default(),
|
||||
)
|
||||
.await
|
||||
@@ -3316,16 +3288,7 @@ mod tests {
|
||||
.fs
|
||||
.save(
|
||||
"/keymap.json".as_ref(),
|
||||
&r#"
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"backspace": "test2::A"
|
||||
}
|
||||
}
|
||||
]
|
||||
"#
|
||||
.into(),
|
||||
&r#"[{"bindings": {"backspace": "test2::A"}}]"#.into(),
|
||||
Default::default(),
|
||||
)
|
||||
.await
|
||||
@@ -3363,16 +3326,7 @@ mod tests {
|
||||
.fs
|
||||
.save(
|
||||
"/keymap.json".as_ref(),
|
||||
&r#"
|
||||
[
|
||||
{
|
||||
"bindings": {
|
||||
"backspace": null
|
||||
}
|
||||
}
|
||||
]
|
||||
"#
|
||||
.into(),
|
||||
&r#"[{"bindings": {"backspace": null}}]"#.into(),
|
||||
Default::default(),
|
||||
)
|
||||
.await
|
||||
@@ -3392,12 +3346,7 @@ mod tests {
|
||||
.fs
|
||||
.save(
|
||||
"/settings.json".as_ref(),
|
||||
&r#"
|
||||
{
|
||||
"base_keymap": "JetBrains"
|
||||
}
|
||||
"#
|
||||
.into(),
|
||||
&r#"{"base_keymap": "JetBrains"}"#.into(),
|
||||
Default::default(),
|
||||
)
|
||||
.await
|
||||
@@ -3405,12 +3354,7 @@ mod tests {
|
||||
|
||||
cx.background_executor.run_until_parked();
|
||||
|
||||
assert_key_bindings_for(
|
||||
workspace.into(),
|
||||
cx,
|
||||
vec![("[", &ActivatePrevItem)],
|
||||
line!(),
|
||||
);
|
||||
assert_key_bindings_for(workspace.into(), cx, vec![("6", &Deploy)], line!());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
use assistant::assistant_settings::AssistantSettings;
|
||||
use collections::HashMap;
|
||||
use editor::{ActiveLineTrailerProvider, Editor, EditorMode};
|
||||
use gpui::{AnyWindowHandle, AppContext, ViewContext, WeakView, WindowContext};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use theme::ActiveTheme;
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
let editors: Rc<RefCell<HashMap<WeakView<Editor>, AnyWindowHandle>>> = Rc::default();
|
||||
|
||||
cx.observe_new_views({
|
||||
let editors = editors.clone();
|
||||
move |_: &mut Workspace, cx: &mut ViewContext<Workspace>| {
|
||||
let workspace_handle = cx.view().clone();
|
||||
cx.subscribe(&workspace_handle, {
|
||||
let editors = editors.clone();
|
||||
move |_, _, event, cx| match event {
|
||||
workspace::Event::ItemAdded { item } => {
|
||||
if let Some(editor) = item.act_as::<Editor>(cx) {
|
||||
if editor.read(cx).mode() != EditorMode::Full {
|
||||
return;
|
||||
}
|
||||
|
||||
cx.on_release({
|
||||
let editor_handle = editor.downgrade();
|
||||
let editors = editors.clone();
|
||||
move |_, _, _| {
|
||||
editors.borrow_mut().remove(&editor_handle);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
editors
|
||||
.borrow_mut()
|
||||
.insert(editor.downgrade(), cx.window_handle());
|
||||
|
||||
let show_hints = should_show_hints(cx);
|
||||
editor.update(cx, |editor, cx| {
|
||||
assign_active_line_trailer_provider(editor, show_hints, cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
let mut show_hints = AssistantSettings::get_global(cx).show_hints;
|
||||
cx.observe_global::<SettingsStore>(move |cx| {
|
||||
let new_show_hints = should_show_hints(cx);
|
||||
if new_show_hints != show_hints {
|
||||
show_hints = new_show_hints;
|
||||
for (editor, window) in editors.borrow().iter() {
|
||||
_ = window.update(cx, |_window, cx| {
|
||||
_ = editor.update(cx, |editor, cx| {
|
||||
assign_active_line_trailer_provider(editor, show_hints, cx);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
struct AssistantHintsProvider;
|
||||
|
||||
impl ActiveLineTrailerProvider for AssistantHintsProvider {
|
||||
fn render_active_line_trailer(
|
||||
&mut self,
|
||||
style: &editor::EditorStyle,
|
||||
focus_handle: &gpui::FocusHandle,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<gpui::AnyElement> {
|
||||
if !focus_handle.is_focused(cx) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let chat_keybinding =
|
||||
cx.keystroke_text_for_action_in(&assistant::ToggleFocus, focus_handle);
|
||||
let generate_keybinding =
|
||||
cx.keystroke_text_for_action_in(&zed_actions::InlineAssist::default(), focus_handle);
|
||||
|
||||
Some(
|
||||
h_flex()
|
||||
.id("inline-assistant-instructions")
|
||||
.w_full()
|
||||
.font_family(style.text.font().family)
|
||||
.text_color(cx.theme().status().hint)
|
||||
.line_height(style.text.line_height)
|
||||
.child(format!(
|
||||
"{chat_keybinding} to chat, {generate_keybinding} to generate"
|
||||
))
|
||||
.into_any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn assign_active_line_trailer_provider(
|
||||
editor: &mut Editor,
|
||||
show_hints: bool,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let provider = show_hints.then_some(AssistantHintsProvider);
|
||||
editor.set_active_line_trailer_provider(provider, cx);
|
||||
}
|
||||
|
||||
fn should_show_hints(cx: &AppContext) -> bool {
|
||||
let assistant_settings = AssistantSettings::get_global(cx);
|
||||
assistant_settings.enabled && assistant_settings.show_hints
|
||||
}
|
||||
@@ -200,28 +200,18 @@ You must provide the model's Context Window in the `max_tokens` parameter, this
|
||||
{
|
||||
"assistant": {
|
||||
"enabled": true,
|
||||
"show_hints": true,
|
||||
"button": true,
|
||||
"dock": "right"
|
||||
"default_width": 480,
|
||||
"default_model": {
|
||||
"provider": "zed.dev",
|
||||
"model": "claude-3-5-sonnet"
|
||||
},
|
||||
"version": "2",
|
||||
"button": true,
|
||||
"default_width": 480,
|
||||
"dock": "right"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| key | type | default | description |
|
||||
| -------------- | ------- | ------- | ------------------------------------------------------------------------------------- |
|
||||
| enabled | boolean | true | Setting this to `false` will completely disable the assistant |
|
||||
| show_hints | boolean | true | Whether to to show hints in the editor explaining how to use assistant |
|
||||
| button | boolean | true | Show the assistant icon in the status bar |
|
||||
| dock | string | "right" | The default dock position for the assistant panel. Can be ["left", "right", "bottom"] |
|
||||
| default_height | string | null | The pixel height of the assistant panel when docked to the bottom |
|
||||
| default_width | string | null | The pixel width of the assistant panel when docked to the left or right |
|
||||
|
||||
#### Custom endpoints {#custom-endpoint}
|
||||
|
||||
You can use a custom API endpoint for different providers, as long as it's compatible with the providers API structure.
|
||||
@@ -281,3 +271,13 @@ will generate two outputs for every assist. One with Claude 3.5 Sonnet, and one
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Common Panel Settings
|
||||
|
||||
| key | type | default | description |
|
||||
| -------------- | ------- | ------- | ------------------------------------------------------------------------------------- |
|
||||
| enabled | boolean | true | Setting this to `false` will completely disable the assistant |
|
||||
| button | boolean | true | Show the assistant icon in the status bar |
|
||||
| dock | string | "right" | The default dock position for the assistant panel. Can be ["left", "right", "bottom"] |
|
||||
| default_height | string | null | The pixel height of the assistant panel when docked to the bottom |
|
||||
| default_width | string | null | The pixel width of the assistant panel when docked to the left or right |
|
||||
|
||||
@@ -2327,18 +2327,15 @@ Run the `theme selector: toggle` action in the command palette to see a current
|
||||
- Default:
|
||||
|
||||
```json
|
||||
{
|
||||
"assistant": {
|
||||
"enabled": true,
|
||||
"button": true,
|
||||
"dock": "right",
|
||||
"default_width": 640,
|
||||
"default_height": 320,
|
||||
"provider": "openai",
|
||||
"version": "1",
|
||||
"show_hints": true
|
||||
}
|
||||
}
|
||||
"assistant": {
|
||||
"enabled": true,
|
||||
"button": true,
|
||||
"dock": "right",
|
||||
"default_width": 640,
|
||||
"default_height": 320,
|
||||
"provider": "openai",
|
||||
"version": "1",
|
||||
},
|
||||
```
|
||||
|
||||
## Outline Panel
|
||||
|
||||
@@ -19,24 +19,45 @@ async function main() {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let priorVersion = [parts[0], parts[1], parts[2] - 1].join(".");
|
||||
let suffix = "";
|
||||
|
||||
if (channel == "preview") {
|
||||
suffix = "-pre";
|
||||
if (parts[2] == 0) {
|
||||
priorVersion = [parts[0], parts[1] - 1, 0].join(".");
|
||||
}
|
||||
} else if (!ensureTag(`v${priorVersion}`)) {
|
||||
console.log("Copy the release notes from preview.");
|
||||
// currently we can only draft notes for patch releases.
|
||||
if (parts[2] == 0) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let priorVersion = [parts[0], parts[1], parts[2] - 1].join(".");
|
||||
let suffix = channel == "preview" ? "-pre" : "";
|
||||
let [tag, priorTag] = [`v${version}${suffix}`, `v${priorVersion}${suffix}`];
|
||||
|
||||
if (!ensureTag(tag) || !ensureTag(priorTag)) {
|
||||
console.log("Could not draft release notes, missing a tag:", tag, priorTag);
|
||||
process.exit(0);
|
||||
try {
|
||||
execFileSync("rm", ["-rf", "target/shallow_clone"]);
|
||||
execFileSync("git", [
|
||||
"clone",
|
||||
"https://github.com/zed-industries/zed",
|
||||
"target/shallow_clone",
|
||||
"--filter=tree:0",
|
||||
"--no-checkout",
|
||||
"--branch",
|
||||
tag,
|
||||
"--depth",
|
||||
100,
|
||||
]);
|
||||
execFileSync("git", [
|
||||
"-C",
|
||||
"target/shallow_clone",
|
||||
"rev-parse",
|
||||
"--verify",
|
||||
tag,
|
||||
]);
|
||||
execFileSync("git", [
|
||||
"-C",
|
||||
"target/shallow_clone",
|
||||
"rev-parse",
|
||||
"--verify",
|
||||
priorTag,
|
||||
]);
|
||||
} catch (e) {
|
||||
console.error(e.stderr.toString());
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const newCommits = getCommits(priorTag, tag);
|
||||
@@ -69,7 +90,13 @@ async function main() {
|
||||
function getCommits(oldTag, newTag) {
|
||||
const pullRequestNumbers = execFileSync(
|
||||
"git",
|
||||
["log", `${oldTag}..${newTag}`, "--format=DIVIDER\n%H|||%B"],
|
||||
[
|
||||
"-C",
|
||||
"target/shallow_clone",
|
||||
"log",
|
||||
`${oldTag}..${newTag}`,
|
||||
"--format=DIVIDER\n%H|||%B",
|
||||
],
|
||||
{ encoding: "utf8" },
|
||||
)
|
||||
.replace(/\r\n/g, "\n")
|
||||
@@ -99,18 +126,3 @@ function getCommits(oldTag, newTag) {
|
||||
|
||||
return pullRequestNumbers;
|
||||
}
|
||||
|
||||
function ensureTag(tag) {
|
||||
try {
|
||||
execFileSync("git", ["rev-parse", "--verify", tag]);
|
||||
return true;
|
||||
} catch (e) {
|
||||
try {
|
||||
execFileSync("git"[("fetch", "origin", "--shallow-exclude", tag)]);
|
||||
execFileSync("git"[("fetch", "origin", "--deepen", "1")]);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user