Migrate keymap and settings + edit predictions rename (#23834)

- [x] snake case keymap properties
- [x] flatten actions
- [x] keymap migration + notfication
- [x] settings migration + notification
- [x] inline completions -> edit predictions 

### future: 
- keymap notification doesn't show up on start up, only on keymap save.
this is existing bug in zed, will be addressed in seperate PR.

Release Notes:

- Added a notification for deprecated settings and keymaps, allowing you
to migrate them with a single click. A backup of your existing keymap
and settings will be created in your home directory.
- Modified some keymap actions and settings for consistency.

---------

Co-authored-by: Piotr Osiewicz <piotr@zed.dev>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
smit 2025-02-07 21:17:07 +05:30 committed by GitHub
parent a1544f47ad
commit 00c2a30059
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
58 changed files with 2106 additions and 617 deletions

View file

@ -17,7 +17,7 @@ use gpui::{
use http_client::github::get_release_by_tag_name;
use http_client::HttpClient;
use language::{
language_settings::{all_language_settings, language_settings, InlineCompletionProvider},
language_settings::{all_language_settings, language_settings, EditPredictionProvider},
point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16,
ToPointUtf16,
};
@ -368,8 +368,8 @@ impl Copilot {
let server_id = self.server_id;
let http = self.http.clone();
let node_runtime = self.node_runtime.clone();
if all_language_settings(None, cx).inline_completions.provider
== InlineCompletionProvider::Copilot
if all_language_settings(None, cx).edit_predictions.provider
== EditPredictionProvider::Copilot
{
if matches!(self.server, CopilotServer::Disabled) {
let start_task = cx

View file

@ -1,7 +1,7 @@
use crate::{Completion, Copilot};
use anyhow::Result;
use gpui::{App, Context, Entity, EntityId, Task};
use inline_completion::{Direction, InlineCompletion, InlineCompletionProvider};
use inline_completion::{Direction, EditPredictionProvider, InlineCompletion};
use language::{language_settings::AllLanguageSettings, Buffer, OffsetRangeExt, ToOffset};
use project::Project;
use settings::Settings;
@ -48,7 +48,7 @@ impl CopilotCompletionProvider {
}
}
impl InlineCompletionProvider for CopilotCompletionProvider {
impl EditPredictionProvider for CopilotCompletionProvider {
fn name() -> &'static str {
"copilot"
}
@ -301,7 +301,7 @@ mod tests {
.await;
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
cx.update_editor(|editor, window, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
});
cx.set_state(indoc! {"
@ -436,8 +436,8 @@ mod tests {
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
// AcceptInlineCompletion when there is an active suggestion inserts it.
editor.accept_inline_completion(&Default::default(), window, cx);
// AcceptEditPrediction when there is an active suggestion inserts it.
editor.accept_edit_prediction(&Default::default(), window, cx);
assert!(!editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
@ -482,7 +482,7 @@ mod tests {
);
cx.update_editor(|editor, window, cx| {
editor.next_inline_completion(&Default::default(), window, cx)
editor.next_edit_prediction(&Default::default(), window, cx)
});
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, window, cx| {
@ -496,8 +496,8 @@ mod tests {
assert_eq!(editor.text(cx), "fn foo() {\n \n}");
assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
// Using AcceptInlineCompletion again accepts the suggestion.
editor.accept_inline_completion(&Default::default(), window, cx);
// Using AcceptEditPrediction again accepts the suggestion.
editor.accept_edit_prediction(&Default::default(), window, cx);
assert!(!editor.has_active_inline_completion());
assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
@ -526,7 +526,7 @@ mod tests {
.await;
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
cx.update_editor(|editor, window, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
});
// Setup the editor with a completion request.
@ -650,7 +650,7 @@ mod tests {
.await;
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
cx.update_editor(|editor, window, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
});
cx.set_state(indoc! {"
@ -669,7 +669,7 @@ mod tests {
vec![],
);
cx.update_editor(|editor, window, cx| {
editor.next_inline_completion(&Default::default(), window, cx)
editor.next_edit_prediction(&Default::default(), window, cx)
});
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, window, cx| {
@ -740,7 +740,7 @@ mod tests {
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
editor
.update(cx, |editor, window, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
})
.unwrap();
@ -758,7 +758,7 @@ mod tests {
editor.change_selections(None, window, cx, |s| {
s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
});
editor.next_inline_completion(&Default::default(), window, cx);
editor.next_edit_prediction(&Default::default(), window, cx);
});
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
_ = editor.update(cx, |editor, _, cx| {
@ -834,7 +834,7 @@ mod tests {
.await;
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
cx.update_editor(|editor, window, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
});
cx.set_state(indoc! {"
@ -862,7 +862,7 @@ mod tests {
vec![],
);
cx.update_editor(|editor, window, cx| {
editor.next_inline_completion(&Default::default(), window, cx)
editor.next_edit_prediction(&Default::default(), window, cx)
});
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, _, cx| {
@ -930,7 +930,7 @@ mod tests {
async fn test_copilot_disabled_globs(executor: BackgroundExecutor, cx: &mut TestAppContext) {
init_test(cx, |settings| {
settings
.inline_completions
.edit_predictions
.get_or_insert(Default::default())
.disabled_globs = Some(vec![".env*".to_string()]);
});
@ -992,7 +992,7 @@ mod tests {
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
editor
.update(cx, |editor, window, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
})
.unwrap();

View file

@ -3,56 +3,64 @@ use super::*;
use gpui::{action_as, action_with_deprecated_aliases};
use schemars::JsonSchema;
use util::serde::default_true;
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SelectNext {
#[serde(default)]
pub replace_newest: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SelectPrevious {
#[serde(default)]
pub replace_newest: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct MoveToBeginningOfLine {
#[serde(default = "default_true")]
pub stop_at_soft_wraps: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SelectToBeginningOfLine {
#[serde(default)]
pub(super) stop_at_soft_wraps: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct MovePageUp {
#[serde(default)]
pub(super) center_cursor: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct MovePageDown {
#[serde(default)]
pub(super) center_cursor: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct MoveToEndOfLine {
#[serde(default = "default_true")]
pub stop_at_soft_wraps: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SelectToEndOfLine {
#[serde(default)]
pub(super) stop_at_soft_wraps: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ToggleCodeActions {
// Display row from which the action was deployed.
#[serde(default)]
@ -61,24 +69,28 @@ pub struct ToggleCodeActions {
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ConfirmCompletion {
#[serde(default)]
pub item_ix: Option<usize>,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ComposeCompletion {
#[serde(default)]
pub item_ix: Option<usize>,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ConfirmCodeAction {
#[serde(default)]
pub item_ix: Option<usize>,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ToggleComments {
#[serde(default)]
pub advance_downwards: bool,
@ -87,60 +99,70 @@ pub struct ToggleComments {
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct FoldAt {
#[serde(skip)]
pub buffer_row: MultiBufferRow,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct UnfoldAt {
#[serde(skip)]
pub buffer_row: MultiBufferRow,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct MoveUpByLines {
#[serde(default)]
pub(super) lines: u32,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct MoveDownByLines {
#[serde(default)]
pub(super) lines: u32,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SelectUpByLines {
#[serde(default)]
pub(super) lines: u32,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SelectDownByLines {
#[serde(default)]
pub(super) lines: u32,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ExpandExcerpts {
#[serde(default)]
pub(super) lines: u32,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ExpandExcerptsUp {
#[serde(default)]
pub(super) lines: u32,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ExpandExcerptsDown {
#[serde(default)]
pub(super) lines: u32,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ShowCompletions {
#[serde(default)]
pub(super) trigger: Option<String>,
@ -150,23 +172,24 @@ pub struct ShowCompletions {
pub struct HandleInput(pub String);
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct DeleteToNextWordEnd {
#[serde(default)]
pub ignore_newlines: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct DeleteToPreviousWordStart {
#[serde(default)]
pub ignore_newlines: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct FoldAtLevel {
pub level: u32,
}
pub struct FoldAtLevel(pub u32);
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SpawnNearestTask {
#[serde(default)]
pub reveal: task::RevealStrategy,
@ -216,9 +239,9 @@ impl_actions!(
gpui::actions!(
editor,
[
AcceptInlineCompletion,
AcceptEditPrediction,
AcceptPartialCopilotSuggestion,
AcceptPartialInlineCompletion,
AcceptPartialEditPrediction,
AddSelectionAbove,
AddSelectionBelow,
ApplyAllDiffHunks,
@ -310,7 +333,7 @@ gpui::actions!(
Newline,
NewlineAbove,
NewlineBelow,
NextInlineCompletion,
NextEditPrediction,
NextScreen,
OpenContextMenu,
OpenExcerpts,
@ -325,7 +348,7 @@ gpui::actions!(
PageDown,
PageUp,
Paste,
PreviousInlineCompletion,
PreviousEditPrediction,
Redo,
RedoSelection,
Rename,
@ -361,7 +384,7 @@ gpui::actions!(
SelectToStartOfParagraph,
SelectUp,
ShowCharacterPalette,
ShowInlineCompletion,
ShowEditPrediction,
ShowSignatureHelp,
ShuffleLines,
SortLinesCaseInsensitive,
@ -375,7 +398,7 @@ gpui::actions!(
ToggleGitBlameInline,
ToggleIndentGuides,
ToggleInlayHints,
ToggleInlineCompletions,
ToggleEditPrediction,
ToggleLineNumbers,
SwapSelectionEnds,
SetMark,

View file

@ -517,7 +517,6 @@ impl CompletionsMenu {
} else {
None
};
let color_swatch = completion
.color()
.map(|color| div().size_4().bg(color).rounded_sm());

View file

@ -90,7 +90,7 @@ use hover_popover::{hide_hover, HoverState};
use indent_guides::ActiveIndentGuidesState;
use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
pub use inline_completion::Direction;
use inline_completion::{InlineCompletionProvider, InlineCompletionProviderHandle};
use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
pub use items::MAX_TAB_TITLE_LEN;
use itertools::Itertools;
use language::{
@ -674,7 +674,7 @@ pub struct Editor {
pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
gutter_hovered: bool,
hovered_link_state: Option<HoveredLinkState>,
inline_completion_provider: Option<RegisteredInlineCompletionProvider>,
edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
active_inline_completion: Option<InlineCompletionState>,
/// Used to prevent flickering as the user types while the menu is open
@ -1371,7 +1371,7 @@ impl Editor {
hover_state: Default::default(),
pending_mouse_down: None,
hovered_link_state: Default::default(),
inline_completion_provider: None,
edit_prediction_provider: None,
active_inline_completion: None,
stale_inline_completion_in_menu: None,
previewing_inline_completion: false,
@ -1524,10 +1524,10 @@ impl Editor {
if self.has_active_inline_completion() {
key_context.add("copilot_suggestion");
key_context.add("inline_completion");
key_context.add("edit_prediction");
if showing_completions || self.inline_completion_requires_modifier(cx) {
key_context.add("inline_completion_requires_modifier");
if showing_completions || self.edit_prediction_requires_modifier(cx) {
key_context.add("edit_prediction_requires_modifier");
}
}
@ -1737,15 +1737,15 @@ impl Editor {
self.semantics_provider = provider;
}
pub fn set_inline_completion_provider<T>(
pub fn set_edit_prediction_provider<T>(
&mut self,
provider: Option<Entity<T>>,
window: &mut Window,
cx: &mut Context<Self>,
) where
T: InlineCompletionProvider,
T: EditPredictionProvider,
{
self.inline_completion_provider =
self.edit_prediction_provider =
provider.map(|provider| RegisteredInlineCompletionProvider {
_subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
if this.focus_handle.is_focused(window) {
@ -1877,7 +1877,7 @@ impl Editor {
pub fn toggle_inline_completions(
&mut self,
_: &ToggleInlineCompletions,
_: &ToggleEditPrediction,
window: &mut Window,
cx: &mut Context<Self>,
) {
@ -1900,11 +1900,11 @@ impl Editor {
pub fn set_show_inline_completions(
&mut self,
show_inline_completions: Option<bool>,
show_edit_predictions: Option<bool>,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.show_inline_completions_override = show_inline_completions;
self.show_inline_completions_override = show_edit_predictions;
self.refresh_inline_completion(false, true, window, cx);
}
@ -1932,7 +1932,7 @@ impl Editor {
scope.override_name().map_or(false, |scope_name| {
settings
.inline_completions_disabled_in
.edit_predictions_disabled_in
.iter()
.any(|s| s == scope_name)
})
@ -3015,7 +3015,7 @@ impl Editor {
}
let trigger_in_words =
this.show_inline_completions_in_menu(cx) || !had_active_inline_completion;
this.show_edit_predictions_in_menu(cx) || !had_active_inline_completion;
this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
linked_editing_ranges::refresh_linked_ranges(this, window, cx);
this.refresh_inline_completion(true, false, window, cx);
@ -3908,7 +3908,7 @@ impl Editor {
*editor.context_menu.borrow_mut() =
Some(CodeContextMenu::Completions(menu));
if editor.show_inline_completions_in_menu(cx) {
if editor.show_edit_predictions_in_menu(cx) {
editor.update_visible_inline_completion(window, cx);
} else {
editor.discard_inline_completion(false, cx);
@ -3922,7 +3922,7 @@ impl Editor {
// If it was already hidden and we don't show inline
// completions in the menu, we should also show the
// inline-completion when available.
if was_hidden && editor.show_inline_completions_in_menu(cx) {
if was_hidden && editor.show_edit_predictions_in_menu(cx) {
editor.update_visible_inline_completion(window, cx);
}
}
@ -3972,7 +3972,7 @@ impl Editor {
let entries = completions_menu.entries.borrow();
let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
if self.show_inline_completions_in_menu(cx) {
if self.show_edit_predictions_in_menu(cx) {
self.discard_inline_completion(true, cx);
}
let candidate_id = mat.candidate_id;
@ -4653,7 +4653,7 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<()> {
let provider = self.inline_completion_provider()?;
let provider = self.edit_prediction_provider()?;
let cursor = self.selections.newest_anchor().head();
let (buffer, cursor_buffer_position) =
self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
@ -4694,7 +4694,7 @@ impl Editor {
}
}
fn inline_completion_requires_modifier(&self, cx: &App) -> bool {
fn edit_prediction_requires_modifier(&self, cx: &App) -> bool {
let cursor = self.selections.newest_anchor().head();
self.buffer
@ -4731,7 +4731,7 @@ impl Editor {
buffer.file(),
cx,
)
.show_inline_completions
.show_edit_predictions
}
}
@ -4753,7 +4753,7 @@ impl Editor {
cx: &App,
) -> bool {
maybe!({
let provider = self.inline_completion_provider()?;
let provider = self.edit_prediction_provider()?;
if !provider.is_enabled(&buffer, buffer_position, cx) {
return Some(false);
}
@ -4773,7 +4773,7 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<()> {
let provider = self.inline_completion_provider()?;
let provider = self.edit_prediction_provider()?;
let cursor = self.selections.newest_anchor().head();
let (buffer, cursor_buffer_position) =
self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
@ -4791,7 +4791,7 @@ impl Editor {
pub fn show_inline_completion(
&mut self,
_: &ShowInlineCompletion,
_: &ShowEditPrediction,
window: &mut Window,
cx: &mut Context<Self>,
) {
@ -4826,9 +4826,9 @@ impl Editor {
.detach();
}
pub fn next_inline_completion(
pub fn next_edit_prediction(
&mut self,
_: &NextInlineCompletion,
_: &NextEditPrediction,
window: &mut Window,
cx: &mut Context<Self>,
) {
@ -4844,9 +4844,9 @@ impl Editor {
}
}
pub fn previous_inline_completion(
pub fn previous_edit_prediction(
&mut self,
_: &PreviousInlineCompletion,
_: &PreviousEditPrediction,
window: &mut Window,
cx: &mut Context<Self>,
) {
@ -4862,9 +4862,9 @@ impl Editor {
}
}
pub fn accept_inline_completion(
pub fn accept_edit_prediction(
&mut self,
_: &AcceptInlineCompletion,
_: &AcceptEditPrediction,
window: &mut Window,
cx: &mut Context<Self>,
) {
@ -4885,7 +4885,7 @@ impl Editor {
}
}
if self.show_inline_completions_in_menu(cx) {
if self.show_edit_predictions_in_menu(cx) {
self.hide_context_menu(window, cx);
}
@ -4904,7 +4904,7 @@ impl Editor {
});
}
InlineCompletion::Edit { edits, .. } => {
if let Some(provider) = self.inline_completion_provider() {
if let Some(provider) = self.edit_prediction_provider() {
provider.accept(cx);
}
@ -4931,7 +4931,7 @@ impl Editor {
pub fn accept_partial_inline_completion(
&mut self,
_: &AcceptPartialInlineCompletion,
_: &AcceptPartialEditPrediction,
window: &mut Window,
cx: &mut Context<Self>,
) {
@ -4988,7 +4988,7 @@ impl Editor {
self.refresh_inline_completion(true, true, window, cx);
cx.notify();
} else {
self.accept_inline_completion(&Default::default(), window, cx);
self.accept_edit_prediction(&Default::default(), window, cx);
}
}
}
@ -5003,7 +5003,7 @@ impl Editor {
self.report_inline_completion_event(false, cx);
}
if let Some(provider) = self.inline_completion_provider() {
if let Some(provider) = self.edit_prediction_provider() {
provider.discard(cx);
}
@ -5011,7 +5011,7 @@ impl Editor {
}
fn report_inline_completion_event(&self, accepted: bool, cx: &App) {
let Some(provider) = self.inline_completion_provider() else {
let Some(provider) = self.edit_prediction_provider() else {
return;
};
@ -5064,7 +5064,7 @@ impl Editor {
cx: &App,
) -> bool {
if self.previewing_inline_completion
|| !self.show_inline_completions_in_menu(cx)
|| !self.show_edit_predictions_in_menu(cx)
|| !self.should_show_inline_completions(cx)
{
return false;
@ -5074,7 +5074,7 @@ impl Editor {
return true;
}
has_completion && self.inline_completion_requires_modifier(cx)
has_completion && self.edit_prediction_requires_modifier(cx)
}
fn update_inline_completion_preview(
@ -5083,7 +5083,7 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Self>,
) {
if !self.show_inline_completions_in_menu(cx) {
if !self.show_edit_predictions_in_menu(cx) {
return;
}
@ -5103,7 +5103,7 @@ impl Editor {
let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
let excerpt_id = cursor.excerpt_id;
let show_in_menu = self.show_inline_completions_in_menu(cx);
let show_in_menu = self.show_edit_predictions_in_menu(cx);
let completions_menu_has_precedence = !show_in_menu
&& (self.context_menu.borrow().is_some()
|| (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
@ -5123,7 +5123,7 @@ impl Editor {
}
self.take_active_inline_completion(cx);
let provider = self.inline_completion_provider()?;
let provider = self.edit_prediction_provider()?;
let (buffer, cursor_buffer_position) =
self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
@ -5258,20 +5258,20 @@ impl Editor {
Some(())
}
pub fn inline_completion_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
Some(self.inline_completion_provider.as_ref()?.provider.clone())
pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
Some(self.edit_prediction_provider.as_ref()?.provider.clone())
}
fn show_inline_completions_in_menu(&self, cx: &App) -> bool {
fn show_edit_predictions_in_menu(&self, cx: &App) -> bool {
let by_provider = matches!(
self.menu_inline_completions_policy,
MenuInlineCompletionsPolicy::ByProvider
);
by_provider
&& EditorSettings::get_global(cx).show_inline_completions_in_menu
&& EditorSettings::get_global(cx).show_edit_predictions_in_menu
&& self
.inline_completion_provider()
.edit_prediction_provider()
.map_or(false, |provider| provider.show_completions_in_menu())
}
@ -5524,7 +5524,7 @@ impl Editor {
window: &Window,
cx: &mut Context<Editor>,
) -> Option<AnyElement> {
let provider = self.inline_completion_provider.as_ref()?;
let provider = self.edit_prediction_provider.as_ref()?;
if provider.provider.needs_terms_acceptance(cx) {
return Some(
@ -11808,7 +11808,7 @@ impl Editor {
return;
}
let fold_at_level = fold_at.level;
let fold_at_level = fold_at.0;
let snapshot = self.buffer.read(cx).snapshot(cx);
let mut to_fold = Vec::new();
let mut stack = vec![(0, snapshot.max_row().0, 1)];
@ -14202,14 +14202,14 @@ impl Editor {
.get("vim_mode")
== Some(&serde_json::Value::Bool(true));
let edit_predictions_provider = all_language_settings(file, cx).inline_completions.provider;
let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
let copilot_enabled = edit_predictions_provider
== language::language_settings::InlineCompletionProvider::Copilot;
== language::language_settings::EditPredictionProvider::Copilot;
let copilot_enabled_for_language = self
.buffer
.read(cx)
.settings_at(0, cx)
.show_inline_completions;
.show_edit_predictions;
let project = project.read(cx);
telemetry::event!(

View file

@ -35,7 +35,7 @@ pub struct EditorSettings {
pub auto_signature_help: bool,
pub show_signature_help_after_edits: bool,
pub jupyter: Jupyter,
pub show_inline_completions_in_menu: bool,
pub show_edit_predictions_in_menu: bool,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@ -372,7 +372,7 @@ pub struct EditorSettingsContent {
/// Only has an effect if edit prediction provider supports it.
///
/// Default: true
pub show_inline_completions_in_menu: Option<bool>,
pub show_edit_predictions_in_menu: Option<bool>,
/// Jupyter REPL settings.
pub jupyter: Option<JupyterContent>,

View file

@ -1159,7 +1159,7 @@ fn test_fold_at_level(cx: &mut TestAppContext) {
});
_ = editor.update(cx, |editor, window, cx| {
editor.fold_at_level(&FoldAtLevel { level: 2 }, window, cx);
editor.fold_at_level(&FoldAtLevel(2), window, cx);
assert_eq!(
editor.display_text(cx),
"
@ -1183,7 +1183,7 @@ fn test_fold_at_level(cx: &mut TestAppContext) {
.unindent(),
);
editor.fold_at_level(&FoldAtLevel { level: 1 }, window, cx);
editor.fold_at_level(&FoldAtLevel(1), window, cx);
assert_eq!(
editor.display_text(cx),
"
@ -1198,7 +1198,7 @@ fn test_fold_at_level(cx: &mut TestAppContext) {
);
editor.unfold_all(&UnfoldAll, window, cx);
editor.fold_at_level(&FoldAtLevel { level: 0 }, window, cx);
editor.fold_at_level(&FoldAtLevel(0), window, cx);
assert_eq!(
editor.display_text(cx),
"

View file

@ -475,8 +475,8 @@ impl EditorElement {
}
});
register_action(editor, window, Editor::show_signature_help);
register_action(editor, window, Editor::next_inline_completion);
register_action(editor, window, Editor::previous_inline_completion);
register_action(editor, window, Editor::next_edit_prediction);
register_action(editor, window, Editor::previous_edit_prediction);
register_action(editor, window, Editor::show_inline_completion);
register_action(editor, window, Editor::context_menu_first);
register_action(editor, window, Editor::context_menu_prev);
@ -486,7 +486,7 @@ impl EditorElement {
register_action(editor, window, Editor::unique_lines_case_insensitive);
register_action(editor, window, Editor::unique_lines_case_sensitive);
register_action(editor, window, Editor::accept_partial_inline_completion);
register_action(editor, window, Editor::accept_inline_completion);
register_action(editor, window, Editor::accept_edit_prediction);
register_action(editor, window, Editor::revert_file);
register_action(editor, window, Editor::revert_selected_hunks);
register_action(editor, window, Editor::apply_all_diff_hunks);
@ -3197,7 +3197,7 @@ impl EditorElement {
#[cfg(target_os = "macos")]
{
// let bindings = window.bindings_for_action_in(
// &crate::AcceptInlineCompletion,
// &crate::AcceptEditPrediction,
// &self.editor.focus_handle(cx),
// );
@ -5770,7 +5770,7 @@ fn inline_completion_accept_indicator(
}
}
} else {
let bindings = window.bindings_for_action_in(&crate::AcceptInlineCompletion, &focus_handle);
let bindings = window.bindings_for_action_in(&crate::AcceptEditPrediction, &focus_handle);
if let Some(keystroke) = bindings
.last()
.and_then(|binding| binding.keystrokes().first())

View file

@ -1,6 +1,6 @@
use gpui::{prelude::*, Entity};
use indoc::indoc;
use inline_completion::InlineCompletionProvider;
use inline_completion::EditPredictionProvider;
use language::{Language, LanguageConfig};
use multi_buffer::{Anchor, MultiBufferSnapshot, ToPoint};
use project::Project;
@ -315,7 +315,7 @@ fn assert_editor_active_move_completion(
fn accept_completion(cx: &mut EditorTestContext) {
cx.update_editor(|editor, window, cx| {
editor.accept_inline_completion(&crate::AcceptInlineCompletion, window, cx)
editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
})
}
@ -345,7 +345,7 @@ fn assign_editor_completion_provider(
cx: &mut EditorTestContext,
) {
cx.update_editor(|editor, window, cx| {
editor.set_inline_completion_provider(Some(provider), window, cx);
editor.set_edit_prediction_provider(Some(provider), window, cx);
})
}
@ -363,7 +363,7 @@ impl FakeInlineCompletionProvider {
}
}
impl InlineCompletionProvider for FakeInlineCompletionProvider {
impl EditPredictionProvider for FakeInlineCompletionProvider {
fn name() -> &'static str {
"fake-completion-provider"
}

View file

@ -38,7 +38,7 @@ impl DataCollectionState {
}
}
pub trait InlineCompletionProvider: 'static + Sized {
pub trait EditPredictionProvider: 'static + Sized {
fn name() -> &'static str;
fn display_name() -> &'static str;
fn show_completions_in_menu() -> bool;
@ -126,7 +126,7 @@ pub trait InlineCompletionProviderHandle {
impl<T> InlineCompletionProviderHandle for Entity<T>
where
T: InlineCompletionProvider,
T: EditPredictionProvider,
{
fn name(&self) -> &'static str {
T::name()

View file

@ -1,7 +1,7 @@
use anyhow::Result;
use client::UserStore;
use copilot::{Copilot, Status};
use editor::{actions::ShowInlineCompletion, scroll::Autoscroll, Editor};
use editor::{actions::ShowEditPrediction, scroll::Autoscroll, Editor};
use feature_flags::{
FeatureFlagAppExt, PredictEditsFeatureFlag, PredictEditsRateCompletionsFeatureFlag,
};
@ -13,9 +13,7 @@ use gpui::{
};
use indoc::indoc;
use language::{
language_settings::{
self, all_language_settings, AllLanguageSettings, InlineCompletionProvider,
},
language_settings::{self, all_language_settings, AllLanguageSettings, EditPredictionProvider},
File, Language,
};
use regex::Regex;
@ -37,7 +35,7 @@ use zed_actions::OpenBrowser;
use zeta::RateCompletionModal;
actions!(zeta, [RateCompletions]);
actions!(inline_completion, [ToggleMenu]);
actions!(edit_prediction, [ToggleMenu]);
const COPILOT_SETTINGS_URL: &str = "https://github.com/settings/copilot";
@ -49,7 +47,7 @@ pub struct InlineCompletionButton {
editor_focus_handle: Option<FocusHandle>,
language: Option<Arc<Language>>,
file: Option<Arc<dyn File>>,
inline_completion_provider: Option<Arc<dyn inline_completion::InlineCompletionProviderHandle>>,
edit_prediction_provider: Option<Arc<dyn inline_completion::InlineCompletionProviderHandle>>,
fs: Arc<dyn Fs>,
workspace: WeakEntity<Workspace>,
user_store: Entity<UserStore>,
@ -67,10 +65,10 @@ impl Render for InlineCompletionButton {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let all_language_settings = all_language_settings(None, cx);
match all_language_settings.inline_completions.provider {
InlineCompletionProvider::None => div(),
match all_language_settings.edit_predictions.provider {
EditPredictionProvider::None => div(),
InlineCompletionProvider::Copilot => {
EditPredictionProvider::Copilot => {
let Some(copilot) = Copilot::global(cx) else {
return div();
};
@ -146,7 +144,7 @@ impl Render for InlineCompletionButton {
)
}
InlineCompletionProvider::Supermaven => {
EditPredictionProvider::Supermaven => {
let Some(supermaven) = Supermaven::global(cx) else {
return div();
};
@ -196,7 +194,7 @@ impl Render for InlineCompletionButton {
set_completion_provider(
fs.clone(),
cx,
InlineCompletionProvider::Copilot,
EditPredictionProvider::Copilot,
)
},
)
@ -226,7 +224,7 @@ impl Render for InlineCompletionButton {
);
}
InlineCompletionProvider::Zed => {
EditPredictionProvider::Zed => {
if !cx.has_flag::<PredictEditsFeatureFlag>() {
return div();
}
@ -307,7 +305,7 @@ impl Render for InlineCompletionButton {
.with_handle(self.popover_menu_handle.clone());
let is_refreshing = self
.inline_completion_provider
.edit_prediction_provider
.as_ref()
.map_or(false, |provider| provider.is_refreshing(cx));
@ -352,7 +350,7 @@ impl InlineCompletionButton {
editor_focus_handle: None,
language: None,
file: None,
inline_completion_provider: None,
edit_prediction_provider: None,
popover_menu_handle,
workspace,
fs,
@ -375,11 +373,7 @@ impl InlineCompletionButton {
.entry("Use Supermaven", None, {
let fs = fs.clone();
move |_window, cx| {
set_completion_provider(
fs.clone(),
cx,
InlineCompletionProvider::Supermaven,
)
set_completion_provider(fs.clone(), cx, EditPredictionProvider::Supermaven)
}
})
})
@ -394,7 +388,7 @@ impl InlineCompletionButton {
let fs = fs.clone();
let language_enabled =
language_settings::language_settings(Some(language.name()), None, cx)
.show_inline_completions;
.show_edit_predictions;
menu = menu.toggleable_entry(
language.name(),
@ -418,7 +412,7 @@ impl InlineCompletionButton {
);
menu = menu.separator().header("Privacy Settings");
if let Some(provider) = &self.inline_completion_provider {
if let Some(provider) = &self.edit_prediction_provider {
let data_collection = provider.data_collection_state(cx);
if data_collection.is_supported() {
let provider = provider.clone();
@ -491,12 +485,12 @@ impl InlineCompletionButton {
.separator()
.entry(
"Predict Edit at Cursor",
Some(Box::new(ShowInlineCompletion)),
Some(Box::new(ShowEditPrediction)),
{
let editor_focus_handle = editor_focus_handle.clone();
move |window, cx| {
editor_focus_handle.dispatch_action(&ShowInlineCompletion, window, cx);
editor_focus_handle.dispatch_action(&ShowEditPrediction, window, cx);
}
},
)
@ -579,7 +573,7 @@ impl InlineCompletionButton {
.unwrap_or(true),
)
};
self.inline_completion_provider = editor.inline_completion_provider();
self.edit_prediction_provider = editor.edit_prediction_provider();
self.language = language.cloned();
self.file = file;
self.editor_focus_handle = Some(editor.focus_handle(cx));
@ -664,7 +658,7 @@ async fn open_disabled_globs_setting_in_editor(
// Ensure that we always have "inline_completions { "disabled_globs": [] }"
let edits = settings.edits_for_update::<AllLanguageSettings>(&text, |file| {
file.inline_completions
file.edit_predictions
.get_or_insert_with(Default::default)
.disabled_globs
.get_or_insert_with(Vec::new);
@ -696,17 +690,17 @@ async fn open_disabled_globs_setting_in_editor(
}
fn toggle_inline_completions_globally(fs: Arc<dyn Fs>, cx: &mut App) {
let show_inline_completions = all_language_settings(None, cx).show_inline_completions(None, cx);
let show_edit_predictions = all_language_settings(None, cx).show_inline_completions(None, cx);
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
file.defaults.show_inline_completions = Some(!show_inline_completions)
file.defaults.show_edit_predictions = Some(!show_edit_predictions)
});
}
fn set_completion_provider(fs: Arc<dyn Fs>, cx: &mut App, provider: InlineCompletionProvider) {
fn set_completion_provider(fs: Arc<dyn Fs>, cx: &mut App, provider: EditPredictionProvider) {
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
file.features
.get_or_insert(Default::default())
.inline_completion_provider = Some(provider);
.edit_prediction_provider = Some(provider);
});
}
@ -715,13 +709,13 @@ fn toggle_show_inline_completions_for_language(
fs: Arc<dyn Fs>,
cx: &mut App,
) {
let show_inline_completions =
let show_edit_predictions =
all_language_settings(None, cx).show_inline_completions(Some(&language), cx);
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
file.languages
.entry(language.name())
.or_default()
.show_inline_completions = Some(!show_inline_completions);
.show_edit_predictions = Some(!show_edit_predictions);
});
}
@ -729,6 +723,6 @@ fn hide_copilot(fs: Arc<dyn Fs>, cx: &mut App) {
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
file.features
.get_or_insert(Default::default())
.inline_completion_provider = Some(InlineCompletionProvider::None);
.edit_prediction_provider = Some(EditPredictionProvider::None);
});
}

View file

@ -60,7 +60,7 @@ pub fn all_language_settings<'a>(
#[derive(Debug, Clone)]
pub struct AllLanguageSettings {
/// The edit prediction settings.
pub inline_completions: InlineCompletionSettings,
pub edit_predictions: EditPredictionSettings,
defaults: LanguageSettings,
languages: HashMap<LanguageName, LanguageSettings>,
pub(crate) file_types: HashMap<Arc<str>, GlobSet>,
@ -110,11 +110,11 @@ pub struct LanguageSettings {
/// - `"..."` - A placeholder to refer to the **rest** of the registered language servers for this language.
pub language_servers: Vec<String>,
/// Controls whether edit predictions are shown immediately (true)
/// or manually by triggering `editor::ShowInlineCompletion` (false).
pub show_inline_completions: bool,
/// or manually by triggering `editor::ShowEditPrediction` (false).
pub show_edit_predictions: bool,
/// Controls whether edit predictions are shown in the given language
/// scopes.
pub inline_completions_disabled_in: Vec<String>,
pub edit_predictions_disabled_in: Vec<String>,
/// Whether to show tabs and spaces in the editor.
pub show_whitespaces: ShowWhitespaceSetting,
/// Whether to start a new line with a comment when a previous line is a comment as well.
@ -198,7 +198,7 @@ impl LanguageSettings {
/// The provider that supplies edit predictions.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum InlineCompletionProvider {
pub enum EditPredictionProvider {
None,
#[default]
Copilot,
@ -206,13 +206,13 @@ pub enum InlineCompletionProvider {
Zed,
}
impl InlineCompletionProvider {
impl EditPredictionProvider {
pub fn is_zed(&self) -> bool {
match self {
InlineCompletionProvider::Zed => true,
InlineCompletionProvider::None
| InlineCompletionProvider::Copilot
| InlineCompletionProvider::Supermaven => false,
EditPredictionProvider::Zed => true,
EditPredictionProvider::None
| EditPredictionProvider::Copilot
| EditPredictionProvider::Supermaven => false,
}
}
}
@ -220,9 +220,9 @@ impl InlineCompletionProvider {
/// The settings for edit predictions, such as [GitHub Copilot](https://github.com/features/copilot)
/// or [Supermaven](https://supermaven.com).
#[derive(Clone, Debug, Default)]
pub struct InlineCompletionSettings {
pub struct EditPredictionSettings {
/// The provider that supplies edit predictions.
pub provider: InlineCompletionProvider,
pub provider: EditPredictionProvider,
/// A list of globs representing files that edit predictions should be disabled for.
pub disabled_globs: Vec<GlobMatcher>,
/// When to show edit predictions previews in buffer.
@ -248,7 +248,7 @@ pub struct AllLanguageSettingsContent {
pub features: Option<FeaturesContent>,
/// The edit prediction settings.
#[serde(default)]
pub inline_completions: Option<InlineCompletionSettingsContent>,
pub edit_predictions: Option<InlineCompletionSettingsContent>,
/// The default language settings.
#[serde(flatten)]
pub defaults: LanguageSettingsContent,
@ -347,11 +347,11 @@ pub struct LanguageSettingsContent {
#[serde(default)]
pub language_servers: Option<Vec<String>>,
/// Controls whether edit predictions are shown immediately (true)
/// or manually by triggering `editor::ShowInlineCompletion` (false).
/// or manually by triggering `editor::ShowEditPrediction` (false).
///
/// Default: true
#[serde(default)]
pub show_inline_completions: Option<bool>,
pub show_edit_predictions: Option<bool>,
/// Controls whether edit predictions are shown in the given language
/// scopes.
///
@ -359,7 +359,7 @@ pub struct LanguageSettingsContent {
///
/// Default: []
#[serde(default)]
pub inline_completions_disabled_in: Option<Vec<String>>,
pub edit_predictions_disabled_in: Option<Vec<String>>,
/// Whether to show tabs and spaces in the editor.
#[serde(default)]
pub show_whitespaces: Option<ShowWhitespaceSetting>,
@ -442,7 +442,7 @@ pub struct FeaturesContent {
/// Whether the GitHub Copilot feature is enabled.
pub copilot: Option<bool>,
/// Determines which edit prediction provider to use.
pub inline_completion_provider: Option<InlineCompletionProvider>,
pub edit_prediction_provider: Option<EditPredictionProvider>,
}
/// Controls the soft-wrapping behavior in the editor.
@ -906,7 +906,7 @@ impl AllLanguageSettings {
/// Returns whether edit predictions are enabled for the given path.
pub fn inline_completions_enabled_for_path(&self, path: &Path) -> bool {
!self
.inline_completions
.edit_predictions
.disabled_globs
.iter()
.any(|glob| glob.is_match(path))
@ -915,12 +915,12 @@ impl AllLanguageSettings {
/// Returns whether edit predictions are enabled for the given language and path.
pub fn show_inline_completions(&self, language: Option<&Arc<Language>>, cx: &App) -> bool {
self.language(None, language.map(|l| l.name()).as_ref(), cx)
.show_inline_completions
.show_edit_predictions
}
/// Returns the edit predictions preview mode for the given language and path.
pub fn inline_completions_preview_mode(&self) -> InlineCompletionPreviewMode {
self.inline_completions.inline_preview
self.edit_predictions.inline_preview
}
}
@ -1015,18 +1015,18 @@ impl settings::Settings for AllLanguageSettings {
}
let mut copilot_enabled = default_value.features.as_ref().and_then(|f| f.copilot);
let mut inline_completion_provider = default_value
let mut edit_prediction_provider = default_value
.features
.as_ref()
.and_then(|f| f.inline_completion_provider);
.and_then(|f| f.edit_prediction_provider);
let mut inline_completions_preview = default_value
.inline_completions
.edit_predictions
.as_ref()
.map(|inline_completions| inline_completions.inline_preview)
.ok_or_else(Self::missing_default)?;
let mut completion_globs: HashSet<&String> = default_value
.inline_completions
.edit_predictions
.as_ref()
.and_then(|c| c.disabled_globs.as_ref())
.map(|globs| globs.iter().collect())
@ -1051,12 +1051,12 @@ impl settings::Settings for AllLanguageSettings {
if let Some(provider) = user_settings
.features
.as_ref()
.and_then(|f| f.inline_completion_provider)
.and_then(|f| f.edit_prediction_provider)
{
inline_completion_provider = Some(provider);
edit_prediction_provider = Some(provider);
}
if let Some(inline_completions) = user_settings.inline_completions.as_ref() {
if let Some(inline_completions) = user_settings.edit_predictions.as_ref() {
inline_completions_preview = inline_completions.inline_preview;
if let Some(disabled_globs) = inline_completions.disabled_globs.as_ref() {
@ -1102,13 +1102,13 @@ impl settings::Settings for AllLanguageSettings {
}
Ok(Self {
inline_completions: InlineCompletionSettings {
provider: if let Some(provider) = inline_completion_provider {
edit_predictions: EditPredictionSettings {
provider: if let Some(provider) = edit_prediction_provider {
provider
} else if copilot_enabled.unwrap_or(true) {
InlineCompletionProvider::Copilot
EditPredictionProvider::Copilot
} else {
InlineCompletionProvider::None
EditPredictionProvider::None
},
disabled_globs: completion_globs
.iter()
@ -1219,12 +1219,12 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent
);
merge(&mut settings.language_servers, src.language_servers.clone());
merge(
&mut settings.show_inline_completions,
src.show_inline_completions,
&mut settings.show_edit_predictions,
src.show_edit_predictions,
);
merge(
&mut settings.inline_completions_disabled_in,
src.inline_completions_disabled_in.clone(),
&mut settings.edit_predictions_disabled_in,
src.edit_predictions_disabled_in.clone(),
);
merge(&mut settings.show_whitespaces, src.show_whitespaces);
merge(

View file

@ -0,0 +1,22 @@
[package]
name = "migrator"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/migrator.rs"
doctest = false
[dependencies]
collections.workspace = true
tree-sitter-json.workspace = true
tree-sitter.workspace = true
convert_case.workspace = true
[dev-dependencies]
pretty_assertions.workspace = true

1
crates/migrator/LICENSE-GPL Symbolic link
View file

@ -0,0 +1 @@
../../LICENSE-GPL

View file

@ -0,0 +1,863 @@
use collections::HashMap;
use convert_case::{Case, Casing};
use std::{cmp::Reverse, ops::Range, sync::LazyLock};
use tree_sitter::{Query, QueryMatch};
fn migrate(text: &str, patterns: MigrationPatterns, query: &Query) -> Option<String> {
let mut parser = tree_sitter::Parser::new();
parser
.set_language(&tree_sitter_json::LANGUAGE.into())
.unwrap();
let syntax_tree = parser.parse(&text, None).unwrap();
let mut cursor = tree_sitter::QueryCursor::new();
let matches = cursor.matches(query, syntax_tree.root_node(), text.as_bytes());
let mut edits = vec![];
for mat in matches {
if let Some((_, callback)) = patterns.get(mat.pattern_index) {
edits.extend(callback(&text, &mat, query));
}
}
edits.sort_by_key(|(range, _)| (range.start, Reverse(range.end)));
edits.dedup_by(|(range_b, _), (range_a, _)| {
range_a.contains(&range_b.start) || range_a.contains(&range_b.end)
});
if edits.is_empty() {
None
} else {
let mut text = text.to_string();
for (range, replacement) in edits.into_iter().rev() {
text.replace_range(range, &replacement);
}
Some(text)
}
}
pub fn migrate_keymap(text: &str) -> Option<String> {
let transformed_text = migrate(
text,
KEYMAP_MIGRATION_TRANSFORMATION_PATTERNS,
&KEYMAP_MIGRATION_TRANSFORMATION_QUERY,
);
let replacement_text = migrate(
&transformed_text.as_ref().unwrap_or(&text.to_string()),
KEYMAP_MIGRATION_REPLACEMENT_PATTERNS,
&KEYMAP_MIGRATION_REPLACEMENT_QUERY,
);
replacement_text.or(transformed_text)
}
pub fn migrate_settings(text: &str) -> Option<String> {
migrate(
&text,
SETTINGS_MIGRATION_PATTERNS,
&SETTINGS_MIGRATION_QUERY,
)
}
type MigrationPatterns = &'static [(
&'static str,
fn(&str, &QueryMatch, &Query) -> Option<(Range<usize>, String)>,
)];
static KEYMAP_MIGRATION_TRANSFORMATION_PATTERNS: MigrationPatterns = &[
(ACTION_ARRAY_PATTERN, replace_array_with_single_string),
(
ACTION_ARGUMENT_OBJECT_PATTERN,
replace_action_argument_object_with_single_value,
),
(ACTION_STRING_PATTERN, rename_string_action),
(CONTEXT_PREDICATE_PATTERN, rename_context_key),
];
static KEYMAP_MIGRATION_TRANSFORMATION_QUERY: LazyLock<Query> = LazyLock::new(|| {
Query::new(
&tree_sitter_json::LANGUAGE.into(),
&KEYMAP_MIGRATION_TRANSFORMATION_PATTERNS
.iter()
.map(|pattern| pattern.0)
.collect::<String>(),
)
.unwrap()
});
const ACTION_ARRAY_PATTERN: &str = r#"(document
(array
(object
(pair
key: (string (string_content) @name)
value: (
(object
(pair
key: (string)
value: ((array
. (string (string_content) @action_name)
. (string (string_content) @argument)
.)) @array
)
)
)
)
)
)
(#eq? @name "bindings")
)"#;
fn replace_array_with_single_string(
contents: &str,
mat: &QueryMatch,
query: &Query,
) -> Option<(Range<usize>, String)> {
let array_ix = query.capture_index_for_name("array").unwrap();
let action_name_ix = query.capture_index_for_name("action_name").unwrap();
let argument_ix = query.capture_index_for_name("argument").unwrap();
let action_name = contents.get(
mat.nodes_for_capture_index(action_name_ix)
.next()?
.byte_range(),
)?;
let argument = contents.get(
mat.nodes_for_capture_index(argument_ix)
.next()?
.byte_range(),
)?;
let replacement = TRANSFORM_ARRAY.get(&(action_name, argument))?;
let replacement_as_string = format!("\"{replacement}\"");
let range_to_replace = mat.nodes_for_capture_index(array_ix).next()?.byte_range();
Some((range_to_replace, replacement_as_string))
}
#[rustfmt::skip]
static TRANSFORM_ARRAY: LazyLock<HashMap<(&str, &str), &str>> = LazyLock::new(|| {
HashMap::from_iter([
// activate
(("workspace::ActivatePaneInDirection", "Up"), "workspace::ActivatePaneUp"),
(("workspace::ActivatePaneInDirection", "Down"), "workspace::ActivatePaneDown"),
(("workspace::ActivatePaneInDirection", "Left"), "workspace::ActivatePaneLeft"),
(("workspace::ActivatePaneInDirection", "Right"), "workspace::ActivatePaneRight"),
// swap
(("workspace::SwapPaneInDirection", "Up"), "workspace::SwapPaneUp"),
(("workspace::SwapPaneInDirection", "Down"), "workspace::SwapPaneDown"),
(("workspace::SwapPaneInDirection", "Left"), "workspace::SwapPaneLeft"),
(("workspace::SwapPaneInDirection", "Right"), "workspace::SwapPaneRight"),
// menu
(("app_menu::NavigateApplicationMenuInDirection", "Left"), "app_menu::ActivateMenuLeft"),
(("app_menu::NavigateApplicationMenuInDirection", "Right"), "app_menu::ActivateMenuRight"),
// vim push
(("vim::PushOperator", "Change"), "vim::PushChange"),
(("vim::PushOperator", "Delete"), "vim::PushDelete"),
(("vim::PushOperator", "Yank"), "vim::PushYank"),
(("vim::PushOperator", "Replace"), "vim::PushReplace"),
(("vim::PushOperator", "DeleteSurrounds"), "vim::PushDeleteSurrounds"),
(("vim::PushOperator", "Mark"), "vim::PushMark"),
(("vim::PushOperator", "Indent"), "vim::PushIndent"),
(("vim::PushOperator", "Outdent"), "vim::PushOutdent"),
(("vim::PushOperator", "AutoIndent"), "vim::PushAutoIndent"),
(("vim::PushOperator", "Rewrap"), "vim::PushRewrap"),
(("vim::PushOperator", "ShellCommand"), "vim::PushShellCommand"),
(("vim::PushOperator", "Lowercase"), "vim::PushLowercase"),
(("vim::PushOperator", "Uppercase"), "vim::PushUppercase"),
(("vim::PushOperator", "OppositeCase"), "vim::PushOppositeCase"),
(("vim::PushOperator", "Register"), "vim::PushRegister"),
(("vim::PushOperator", "RecordRegister"), "vim::PushRecordRegister"),
(("vim::PushOperator", "ReplayRegister"), "vim::PushReplayRegister"),
(("vim::PushOperator", "ReplaceWithRegister"), "vim::PushReplaceWithRegister"),
(("vim::PushOperator", "ToggleComments"), "vim::PushToggleComments"),
// vim switch
(("vim::SwitchMode", "Normal"), "vim::SwitchToNormalMode"),
(("vim::SwitchMode", "Insert"), "vim::SwitchToInsertMode"),
(("vim::SwitchMode", "Replace"), "vim::SwitchToReplaceMode"),
(("vim::SwitchMode", "Visual"), "vim::SwitchToVisualMode"),
(("vim::SwitchMode", "VisualLine"), "vim::SwitchToVisualLineMode"),
(("vim::SwitchMode", "VisualBlock"), "vim::SwitchToVisualBlockMode"),
(("vim::SwitchMode", "HelixNormal"), "vim::SwitchToHelixNormalMode"),
// vim resize
(("vim::ResizePane", "Widen"), "vim::ResizePaneRight"),
(("vim::ResizePane", "Narrow"), "vim::ResizePaneLeft"),
(("vim::ResizePane", "Shorten"), "vim::ResizePaneDown"),
(("vim::ResizePane", "Lengthen"), "vim::ResizePaneUp"),
])
});
const ACTION_ARGUMENT_OBJECT_PATTERN: &str = r#"(document
(array
(object
(pair
key: (string (string_content) @name)
value: (
(object
(pair
key: (string)
value: ((array
. (string (string_content) @action_name)
. (object
(pair
key: (string (string_content) @action_key)
value: (_) @argument))
. ) @array
))
)
)
)
)
)
(#eq? @name "bindings")
)"#;
/// [ "editor::FoldAtLevel", { "level": 1 } ] -> [ "editor::FoldAtLevel", 1 ]
fn replace_action_argument_object_with_single_value(
contents: &str,
mat: &QueryMatch,
query: &Query,
) -> Option<(Range<usize>, String)> {
let array_ix = query.capture_index_for_name("array").unwrap();
let action_name_ix = query.capture_index_for_name("action_name").unwrap();
let action_key_ix = query.capture_index_for_name("action_key").unwrap();
let argument_ix = query.capture_index_for_name("argument").unwrap();
let action_name = contents.get(
mat.nodes_for_capture_index(action_name_ix)
.next()?
.byte_range(),
)?;
let action_key = contents.get(
mat.nodes_for_capture_index(action_key_ix)
.next()?
.byte_range(),
)?;
let argument = contents.get(
mat.nodes_for_capture_index(argument_ix)
.next()?
.byte_range(),
)?;
let new_action_name = UNWRAP_OBJECTS.get(&action_name)?.get(&action_key)?;
let range_to_replace = mat.nodes_for_capture_index(array_ix).next()?.byte_range();
let replacement = format!("[\"{}\", {}]", new_action_name, argument);
Some((range_to_replace, replacement))
}
// "ctrl-k ctrl-1": [ "editor::PushOperator", { "Object": {} } ] -> [ "editor::vim::PushObject", {} ]
static UNWRAP_OBJECTS: LazyLock<HashMap<&str, HashMap<&str, &str>>> = LazyLock::new(|| {
HashMap::from_iter([
(
"editor::FoldAtLevel",
HashMap::from_iter([("level", "editor::FoldAtLevel")]),
),
(
"vim::PushOperator",
HashMap::from_iter([
("Object", "vim::PushObject"),
("FindForward", "vim::PushFindForward"),
("FindBackward", "vim::PushFindBackward"),
("Sneak", "vim::PushSneak"),
("SneakBackward", "vim::PushSneakBackward"),
("AddSurrounds", "vim::PushAddSurrounds"),
("ChangeSurrounds", "vim::PushChangeSurrounds"),
("Jump", "vim::PushJump"),
("Digraph", "vim::PushDigraph"),
("Literal", "vim::PushLiteral"),
]),
),
])
});
static KEYMAP_MIGRATION_REPLACEMENT_PATTERNS: MigrationPatterns = &[(
ACTION_ARGUMENT_SNAKE_CASE_PATTERN,
action_argument_snake_case,
)];
static KEYMAP_MIGRATION_REPLACEMENT_QUERY: LazyLock<Query> = LazyLock::new(|| {
Query::new(
&tree_sitter_json::LANGUAGE.into(),
&KEYMAP_MIGRATION_REPLACEMENT_PATTERNS
.iter()
.map(|pattern| pattern.0)
.collect::<String>(),
)
.unwrap()
});
const ACTION_STRING_PATTERN: &str = r#"(document
(array
(object
(pair
key: (string (string_content) @name)
value: (
(object
(pair
key: (string)
value: (string (string_content) @action_name)
)
)
)
)
)
)
(#eq? @name "bindings")
)"#;
fn rename_string_action(
contents: &str,
mat: &QueryMatch,
query: &Query,
) -> Option<(Range<usize>, String)> {
let action_name_ix = query.capture_index_for_name("action_name").unwrap();
let action_name_range = mat
.nodes_for_capture_index(action_name_ix)
.next()?
.byte_range();
let action_name = contents.get(action_name_range.clone())?;
let new_action_name = STRING_REPLACE.get(&action_name)?;
Some((action_name_range, new_action_name.to_string()))
}
// "ctrl-k ctrl-1": "inline_completion::ToggleMenu" -> "edit_prediction::ToggleMenu"
#[rustfmt::skip]
static STRING_REPLACE: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
HashMap::from_iter([
("inline_completion::ToggleMenu", "edit_prediction::ToggleMenu"),
("editor::NextInlineCompletion", "editor::NextEditPrediction"),
("editor::PreviousInlineCompletion", "editor::PreviousEditPrediction"),
("editor::AcceptPartialInlineCompletion", "editor::AcceptPartialEditPrediction"),
("editor::ShowInlineCompletion", "editor::ShowEditPrediction"),
("editor::AcceptInlineCompletion", "editor::AcceptEditPrediction"),
("editor::ToggleInlineCompletions", "editor::ToggleEditPrediction"),
])
});
const CONTEXT_PREDICATE_PATTERN: &str = r#"
(array
(object
(pair
key: (string (string_content) @name)
value: (string (string_content) @context_predicate)
)
)
)
(#eq? @name "context")
"#;
fn rename_context_key(
contents: &str,
mat: &QueryMatch,
query: &Query,
) -> Option<(Range<usize>, String)> {
let context_predicate_ix = query.capture_index_for_name("context_predicate").unwrap();
let context_predicate_range = mat
.nodes_for_capture_index(context_predicate_ix)
.next()?
.byte_range();
let old_predicate = contents.get(context_predicate_range.clone())?.to_string();
let mut new_predicate = old_predicate.to_string();
for (old_key, new_key) in CONTEXT_REPLACE.iter() {
new_predicate = new_predicate.replace(old_key, new_key);
}
if new_predicate != old_predicate {
Some((context_predicate_range, new_predicate.to_string()))
} else {
None
}
}
const ACTION_ARGUMENT_SNAKE_CASE_PATTERN: &str = r#"(document
(array
(object
(pair
key: (string (string_content) @name)
value: (
(object
(pair
key: (string)
value: ((array
. (string (string_content) @action_name)
. (object
(pair
key: (string (string_content) @argument_key)
value: (_) @argument_value))
. ) @array
))
)
)
)
)
)
(#eq? @name "bindings")
)"#;
fn is_snake_case(text: &str) -> bool {
text == text.to_case(Case::Snake)
}
fn to_snake_case(text: &str) -> String {
text.to_case(Case::Snake)
}
/// [ "editor::FoldAtLevel", { "SomeKey": "Value" } ] -> [ "editor::FoldAtLevel", { "some_key" : "value" } ]
fn action_argument_snake_case(
contents: &str,
mat: &QueryMatch,
query: &Query,
) -> Option<(Range<usize>, String)> {
let array_ix = query.capture_index_for_name("array").unwrap();
let action_name_ix = query.capture_index_for_name("action_name").unwrap();
let argument_key_ix = query.capture_index_for_name("argument_key").unwrap();
let argument_value_ix = query.capture_index_for_name("argument_value").unwrap();
let action_name = contents.get(
mat.nodes_for_capture_index(action_name_ix)
.next()?
.byte_range(),
)?;
let argument_key = contents.get(
mat.nodes_for_capture_index(argument_key_ix)
.next()?
.byte_range(),
)?;
let argument_value_node = mat.nodes_for_capture_index(argument_value_ix).next()?;
let argument_value = contents.get(argument_value_node.byte_range())?;
let mut needs_replacement = false;
let mut new_key = argument_key.to_string();
if !is_snake_case(argument_key) {
new_key = to_snake_case(argument_key);
needs_replacement = true;
}
let mut new_value = argument_value.to_string();
if argument_value_node.kind() == "string" {
let inner_value = argument_value.trim_matches('"');
if !is_snake_case(inner_value) {
new_value = format!("\"{}\"", to_snake_case(inner_value));
needs_replacement = true;
}
}
if !needs_replacement {
return None;
}
let range_to_replace = mat.nodes_for_capture_index(array_ix).next()?.byte_range();
let replacement = format!(
"[\"{}\", {{ \"{}\": {} }}]",
action_name, new_key, new_value
);
Some((range_to_replace, replacement))
}
// "context": "Editor && inline_completion && !showing_completions" -> "Editor && edit_prediction && !showing_completions"
pub static CONTEXT_REPLACE: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
HashMap::from_iter([
("inline_completion", "edit_prediction"),
(
"inline_completion_requires_modifier",
"edit_prediction_requires_modifier",
),
])
});
static SETTINGS_MIGRATION_PATTERNS: MigrationPatterns = &[
(SETTINGS_STRING_REPLACE_QUERY, replace_setting_name),
(SETTINGS_REPLACE_NESTED_KEY, replace_setting_nested_key),
(
SETTINGS_REPLACE_IN_LANGUAGES_QUERY,
replace_setting_in_languages,
),
];
static SETTINGS_MIGRATION_QUERY: LazyLock<Query> = LazyLock::new(|| {
Query::new(
&tree_sitter_json::LANGUAGE.into(),
&SETTINGS_MIGRATION_PATTERNS
.iter()
.map(|pattern| pattern.0)
.collect::<String>(),
)
.unwrap()
});
static SETTINGS_STRING_REPLACE_QUERY: &str = r#"(document
(object
(pair
key: (string (string_content) @name)
value: (_)
)
)
)"#;
fn replace_setting_name(
contents: &str,
mat: &QueryMatch,
query: &Query,
) -> Option<(Range<usize>, String)> {
let setting_capture_ix = query.capture_index_for_name("name").unwrap();
let setting_name_range = mat
.nodes_for_capture_index(setting_capture_ix)
.next()?
.byte_range();
let setting_name = contents.get(setting_name_range.clone())?;
let new_setting_name = SETTINGS_STRING_REPLACE.get(&setting_name)?;
Some((setting_name_range, new_setting_name.to_string()))
}
#[rustfmt::skip]
pub static SETTINGS_STRING_REPLACE: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
HashMap::from_iter([
("show_inline_completions_in_menu", "show_edit_predictions_in_menu"),
("show_inline_completions", "show_edit_predictions"),
("inline_completions_disabled_in", "edit_predictions_disabled_in"),
("inline_completions", "edit_predictions")
])
});
static SETTINGS_REPLACE_NESTED_KEY: &str = r#"
(object
(pair
key: (string (string_content) @parent_key)
value: (object
(pair
key: (string (string_content) @setting_name)
value: (_) @value
)
)
)
)
"#;
fn replace_setting_nested_key(
contents: &str,
mat: &QueryMatch,
query: &Query,
) -> Option<(Range<usize>, String)> {
let parent_object_capture_ix = query.capture_index_for_name("parent_key").unwrap();
let parent_object_range = mat
.nodes_for_capture_index(parent_object_capture_ix)
.next()?
.byte_range();
let parent_object_name = contents.get(parent_object_range.clone())?;
let setting_name_ix = query.capture_index_for_name("setting_name").unwrap();
let setting_range = mat
.nodes_for_capture_index(setting_name_ix)
.next()?
.byte_range();
let setting_name = contents.get(setting_range.clone())?;
let new_setting_name = SETTINGS_NESTED_STRING_REPLACE
.get(&parent_object_name)?
.get(setting_name)?;
Some((setting_range, new_setting_name.to_string()))
}
// "features": {
// "inline_completion_provider": "copilot"
// },
pub static SETTINGS_NESTED_STRING_REPLACE: LazyLock<
HashMap<&'static str, HashMap<&'static str, &'static str>>,
> = LazyLock::new(|| {
HashMap::from_iter([(
"features",
HashMap::from_iter([("inline_completion_provider", "edit_prediction_provider")]),
)])
});
static SETTINGS_REPLACE_IN_LANGUAGES_QUERY: &str = r#"
(object
(pair
key: (string (string_content) @languages)
value: (object
(pair
key: (string)
value: (object
(pair
key: (string (string_content) @setting_name)
value: (_) @value
)
)
))
)
)
(#eq? @languages "languages")
"#;
fn replace_setting_in_languages(
contents: &str,
mat: &QueryMatch,
query: &Query,
) -> Option<(Range<usize>, String)> {
let setting_capture_ix = query.capture_index_for_name("setting_name").unwrap();
let setting_name_range = mat
.nodes_for_capture_index(setting_capture_ix)
.next()?
.byte_range();
let setting_name = contents.get(setting_name_range.clone())?;
let new_setting_name = LANGUAGE_SETTINGS_REPLACE.get(&setting_name)?;
Some((setting_name_range, new_setting_name.to_string()))
}
#[rustfmt::skip]
static LANGUAGE_SETTINGS_REPLACE: LazyLock<
HashMap<&'static str, &'static str>,
> = LazyLock::new(|| {
HashMap::from_iter([
("show_inline_completions", "show_edit_predictions"),
("inline_completions_disabled_in", "edit_predictions_disabled_in"),
])
});
#[cfg(test)]
mod tests {
use super::*;
fn assert_migrate_keymap(input: &str, output: Option<&str>) {
let migrated = migrate_keymap(&input);
pretty_assertions::assert_eq!(migrated.as_deref(), output);
}
fn assert_migrate_settings(input: &str, output: Option<&str>) {
let migrated = migrate_settings(&input);
pretty_assertions::assert_eq!(migrated.as_deref(), output);
}
#[test]
fn test_replace_array_with_single_string() {
assert_migrate_keymap(
r#"
[
{
"bindings": {
"cmd-1": ["workspace::ActivatePaneInDirection", "Up"]
}
}
]
"#,
Some(
r#"
[
{
"bindings": {
"cmd-1": "workspace::ActivatePaneUp"
}
}
]
"#,
),
)
}
#[test]
fn test_replace_action_argument_object_with_single_value() {
assert_migrate_keymap(
r#"
[
{
"bindings": {
"cmd-1": ["editor::FoldAtLevel", { "level": 1 }]
}
}
]
"#,
Some(
r#"
[
{
"bindings": {
"cmd-1": ["editor::FoldAtLevel", 1]
}
}
]
"#,
),
)
}
#[test]
fn test_replace_action_argument_object_with_single_value_2() {
assert_migrate_keymap(
r#"
[
{
"bindings": {
"cmd-1": ["vim::PushOperator", { "Object": { "some" : "value" } }]
}
}
]
"#,
Some(
r#"
[
{
"bindings": {
"cmd-1": ["vim::PushObject", { "some" : "value" }]
}
}
]
"#,
),
)
}
#[test]
fn test_rename_string_action() {
assert_migrate_keymap(
r#"
[
{
"bindings": {
"cmd-1": "inline_completion::ToggleMenu"
}
}
]
"#,
Some(
r#"
[
{
"bindings": {
"cmd-1": "edit_prediction::ToggleMenu"
}
}
]
"#,
),
)
}
#[test]
fn test_rename_context_key() {
assert_migrate_keymap(
r#"
[
{
"context": "Editor && inline_completion && !showing_completions"
}
]
"#,
Some(
r#"
[
{
"context": "Editor && edit_prediction && !showing_completions"
}
]
"#,
),
)
}
#[test]
fn test_action_argument_snake_case() {
// First performs transformations, then replacements
assert_migrate_keymap(
r#"
[
{
"bindings": {
"cmd-1": ["vim::PushOperator", { "Object": { "SomeKey": "Value" } }],
"cmd-2": ["vim::SomeOtherAction", { "OtherKey": "Value" }],
"cmd-3": ["vim::SomeDifferentAction", { "OtherKey": true }],
"cmd-4": ["vim::OneMore", { "OtherKey": 4 }]
}
}
]
"#,
Some(
r#"
[
{
"bindings": {
"cmd-1": ["vim::PushObject", { "some_key": "value" }],
"cmd-2": ["vim::SomeOtherAction", { "other_key": "value" }],
"cmd-3": ["vim::SomeDifferentAction", { "other_key": true }],
"cmd-4": ["vim::OneMore", { "other_key": 4 }]
}
}
]
"#,
),
)
}
#[test]
fn test_replace_setting_name() {
assert_migrate_settings(
r#"
{
"show_inline_completions_in_menu": true,
"show_inline_completions": true,
"inline_completions_disabled_in": ["string"],
"inline_completions": { "some" : "value" }
}
"#,
Some(
r#"
{
"show_edit_predictions_in_menu": true,
"show_edit_predictions": true,
"edit_predictions_disabled_in": ["string"],
"edit_predictions": { "some" : "value" }
}
"#,
),
)
}
#[test]
fn test_nested_string_replace_for_settings() {
assert_migrate_settings(
r#"
{
"features": {
"inline_completion_provider": "zed"
},
}
"#,
Some(
r#"
{
"features": {
"edit_prediction_provider": "zed"
},
}
"#,
),
)
}
#[test]
fn test_replace_settings_in_languages() {
assert_migrate_settings(
r#"
{
"languages": {
"Astro": {
"show_inline_completions": true
}
}
}
"#,
Some(
r#"
{
"languages": {
"Astro": {
"show_edit_predictions": true
}
}
}
"#,
),
)
}
}

View file

@ -26,6 +26,7 @@ actions!(picker, [ConfirmCompletion]);
/// ConfirmInput is an alternative editor action which - instead of selecting active picker entry - treats pickers editor input literally,
/// performing some kind of action on it.
#[derive(Clone, PartialEq, Deserialize, JsonSchema, Default)]
#[serde(deny_unknown_fields)]
pub struct ConfirmInput {
pub secondary: bool,
}

View file

@ -162,12 +162,14 @@ struct EntryDetails {
}
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
struct Delete {
#[serde(default)]
pub skip_prompt: bool,
}
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
struct Trash {
#[serde(default)]
pub skip_prompt: bool,

View file

@ -44,6 +44,7 @@ use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar, WithResults};
const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50;
#[derive(PartialEq, Clone, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Deploy {
#[serde(default = "util::serde::default_true")]
pub focus: bool,

View file

@ -35,6 +35,7 @@ smallvec.workspace = true
tree-sitter-json.workspace = true
tree-sitter.workspace = true
util.workspace = true
migrator.workspace = true
[dev-dependencies]
fs = { workspace = true, features = ["test-support"] }

View file

@ -1,22 +1,24 @@
use std::rc::Rc;
use crate::{settings_store::parse_json_with_comments, SettingsAssets};
use anyhow::anyhow;
use anyhow::{anyhow, Context as _, Result};
use collections::{HashMap, IndexMap};
use fs::Fs;
use gpui::{
Action, ActionBuildError, App, InvalidKeystrokeError, KeyBinding, KeyBindingContextPredicate,
NoAction, SharedString, KEYSTROKE_PARSE_EXPECTED_MESSAGE,
};
use migrator::migrate_keymap;
use schemars::{
gen::{SchemaGenerator, SchemaSettings},
schema::{ArrayValidation, InstanceType, Schema, SchemaObject, SubschemaValidation},
JsonSchema,
};
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fmt::Write;
use std::rc::Rc;
use std::{fmt::Write, sync::Arc};
use util::{asset_str, markdown::MarkdownString};
use crate::{settings_store::parse_json_with_comments, SettingsAssets};
// Note that the doc comments on these are shown by json-language-server when editing the keymap, so
// they should be considered user-facing documentation. Documentation is not handled well with
// schemars-0.8 - when there are newlines, it is rendered as plaintext (see
@ -28,12 +30,12 @@ use util::{asset_str, markdown::MarkdownString};
/// Keymap configuration consisting of sections. Each section may have a context predicate which
/// determines whether its bindings are used.
#[derive(Debug, Deserialize, Default, Clone, JsonSchema)]
#[derive(Debug, Deserialize, Default, Clone, JsonSchema, Serialize)]
#[serde(transparent)]
pub struct KeymapFile(Vec<KeymapSection>);
/// Keymap section which binds keystrokes to actions.
#[derive(Debug, Deserialize, Default, Clone, JsonSchema)]
#[derive(Debug, Deserialize, Default, Clone, JsonSchema, Serialize)]
pub struct KeymapSection {
/// Determines when these bindings are active. When just a name is provided, like `Editor` or
/// `Workspace`, the bindings will be active in that context. Boolean expressions like `X && Y`,
@ -78,9 +80,9 @@ impl KeymapSection {
/// Unlike the other json types involved in keymaps (including actions), this doc-comment will not
/// be included in the generated JSON schema, as it manually defines its `JsonSchema` impl. The
/// actual schema used for it is automatically generated in `KeymapFile::generate_json_schema`.
#[derive(Debug, Deserialize, Default, Clone)]
#[derive(Debug, Deserialize, Default, Clone, Serialize)]
#[serde(transparent)]
pub struct KeymapAction(Value);
pub struct KeymapAction(pub(crate) Value);
impl std::fmt::Display for KeymapAction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -114,9 +116,11 @@ impl JsonSchema for KeymapAction {
pub enum KeymapFileLoadResult {
Success {
key_bindings: Vec<KeyBinding>,
keymap_file: KeymapFile,
},
SomeFailedToLoad {
key_bindings: Vec<KeyBinding>,
keymap_file: KeymapFile,
error_message: MarkdownString,
},
JsonParseFailure {
@ -150,6 +154,7 @@ impl KeymapFile {
KeymapFileLoadResult::SomeFailedToLoad {
key_bindings,
error_message,
..
} if key_bindings.is_empty() => Err(anyhow!(
"Error loading built-in keymap \"{asset_path}\": {error_message}"
)),
@ -164,7 +169,7 @@ impl KeymapFile {
#[cfg(feature = "test-support")]
pub fn load_panic_on_failure(content: &str, cx: &App) -> Vec<KeyBinding> {
match Self::load(content, cx) {
KeymapFileLoadResult::Success { key_bindings } => key_bindings,
KeymapFileLoadResult::Success { key_bindings, .. } => key_bindings,
KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => {
panic!("{error_message}");
}
@ -180,6 +185,7 @@ impl KeymapFile {
if content.is_empty() {
return KeymapFileLoadResult::Success {
key_bindings: Vec::new(),
keymap_file: KeymapFile(Vec::new()),
};
}
let keymap_file = match parse_json_with_comments::<Self>(content) {
@ -266,7 +272,10 @@ impl KeymapFile {
}
if errors.is_empty() {
KeymapFileLoadResult::Success { key_bindings }
KeymapFileLoadResult::Success {
key_bindings,
keymap_file,
}
} else {
let mut error_message = "Errors in user keymap file.\n".to_owned();
for (context, section_errors) in errors {
@ -284,6 +293,7 @@ impl KeymapFile {
}
KeymapFileLoadResult::SomeFailedToLoad {
key_bindings,
keymap_file,
error_message: MarkdownString(error_message),
}
}
@ -551,6 +561,55 @@ impl KeymapFile {
pub fn sections(&self) -> impl DoubleEndedIterator<Item = &KeymapSection> {
self.0.iter()
}
async fn load_keymap_file(fs: &Arc<dyn Fs>) -> Result<String> {
match fs.load(paths::keymap_file()).await {
result @ Ok(_) => result,
Err(err) => {
if let Some(e) = err.downcast_ref::<std::io::Error>() {
if e.kind() == std::io::ErrorKind::NotFound {
return Ok(crate::initial_keymap_content().to_string());
}
}
Err(err)
}
}
}
pub fn should_migrate_keymap(keymap_file: Self) -> bool {
let Ok(old_text) = serde_json::to_string(&keymap_file) else {
return false;
};
migrate_keymap(&old_text).is_some()
}
pub async fn migrate_keymap(fs: Arc<dyn Fs>) -> Result<()> {
let old_text = Self::load_keymap_file(&fs).await?;
let Some(new_text) = migrate_keymap(&old_text) else {
return Ok(());
};
let initial_path = paths::keymap_file().as_path();
if fs.is_file(initial_path).await {
let backup_path = paths::home_dir().join(".zed_keymap_backup");
fs.atomic_write(backup_path, old_text)
.await
.with_context(|| {
"Failed to create settings backup in home directory".to_string()
})?;
let resolved_path = fs.canonicalize(initial_path).await.with_context(|| {
format!("Failed to canonicalize keymap path {:?}", initial_path)
})?;
fs.atomic_write(resolved_path.clone(), new_text)
.await
.with_context(|| format!("Failed to write keymap to file {:?}", resolved_path))?;
} else {
fs.atomic_write(initial_path.to_path_buf(), new_text)
.await
.with_context(|| format!("Failed to write keymap to file {:?}", initial_path))?;
}
Ok(())
}
}
// Double quotes a string and wraps it in backticks for markdown inline code..
@ -560,7 +619,7 @@ fn inline_code_string(text: &str) -> MarkdownString {
#[cfg(test)]
mod tests {
use crate::KeymapFile;
use super::KeymapFile;
#[test]
fn can_deserialize_keymap_with_trailing_comma() {

View file

@ -81,7 +81,7 @@ pub fn watch_config_file(
pub fn handle_settings_file_changes(
mut user_settings_file_rx: mpsc::UnboundedReceiver<String>,
cx: &mut App,
settings_changed: impl Fn(Option<anyhow::Error>, &mut App) + 'static,
settings_changed: impl Fn(Result<serde_json::Value, anyhow::Error>, &mut App) + 'static,
) {
let user_settings_content = cx
.background_executor()
@ -92,7 +92,7 @@ pub fn handle_settings_file_changes(
if let Err(err) = &result {
log::error!("Failed to load user settings: {err}");
}
settings_changed(result.err(), cx);
settings_changed(result, cx);
});
cx.spawn(move |cx| async move {
while let Some(user_settings_content) = user_settings_file_rx.next().await {
@ -101,7 +101,7 @@ pub fn handle_settings_file_changes(
if let Err(err) = &result {
log::error!("Failed to load user settings: {err}");
}
settings_changed(result.err(), cx);
settings_changed(result, cx);
cx.refresh_windows();
});
if result.is_err() {

View file

@ -4,6 +4,7 @@ use ec4rs::{ConfigParser, PropertiesSource, Section};
use fs::Fs;
use futures::{channel::mpsc, future::LocalBoxFuture, FutureExt, StreamExt};
use gpui::{App, AsyncApp, BorrowAppContext, Global, Task, UpdateGlobal};
use migrator::migrate_settings;
use paths::{local_settings_file_relative_path, EDITORCONFIG_NAME};
use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
@ -17,7 +18,9 @@ use std::{
sync::{Arc, LazyLock},
};
use tree_sitter::Query;
use util::{merge_non_null_json_value_into, RangeExt, ResultExt as _};
use util::RangeExt;
use util::{merge_non_null_json_value_into, ResultExt as _};
pub type EditorconfigProperties = ec4rs::Properties;
@ -544,7 +547,11 @@ impl SettingsStore {
}
/// Sets the user settings via a JSON string.
pub fn set_user_settings(&mut self, user_settings_content: &str, cx: &mut App) -> Result<()> {
pub fn set_user_settings(
&mut self,
user_settings_content: &str,
cx: &mut App,
) -> Result<serde_json::Value> {
let settings: serde_json::Value = if user_settings_content.is_empty() {
parse_json_with_comments("{}")?
} else {
@ -552,9 +559,9 @@ impl SettingsStore {
};
anyhow::ensure!(settings.is_object(), "settings must be an object");
self.raw_user_settings = settings;
self.raw_user_settings = settings.clone();
self.recompute_values(None, cx)?;
Ok(())
Ok(settings)
}
pub fn set_server_settings(
@ -988,6 +995,52 @@ impl SettingsStore {
properties.use_fallbacks();
Some(properties)
}
pub fn should_migrate_settings(settings: &serde_json::Value) -> bool {
let Ok(old_text) = serde_json::to_string(settings) else {
return false;
};
migrate_settings(&old_text).is_some()
}
pub fn migrate_settings(&self, fs: Arc<dyn Fs>) {
self.setting_file_updates_tx
.unbounded_send(Box::new(move |_: AsyncApp| {
async move {
let old_text = Self::load_settings(&fs).await?;
let Some(new_text) = migrate_settings(&old_text) else {
return anyhow::Ok(());
};
let initial_path = paths::settings_file().as_path();
if fs.is_file(initial_path).await {
let backup_path = paths::home_dir().join(".zed_settings_backup");
fs.atomic_write(backup_path, old_text)
.await
.with_context(|| {
"Failed to create settings backup in home directory".to_string()
})?;
let resolved_path =
fs.canonicalize(initial_path).await.with_context(|| {
format!("Failed to canonicalize settings path {:?}", initial_path)
})?;
fs.atomic_write(resolved_path.clone(), new_text)
.await
.with_context(|| {
format!("Failed to write settings to file {:?}", resolved_path)
})?;
} else {
fs.atomic_write(initial_path.to_path_buf(), new_text)
.await
.with_context(|| {
format!("Failed to write settings to file {:?}", initial_path)
})?;
}
anyhow::Ok(())
}
.boxed_local()
}))
.ok();
}
}
#[derive(Debug, Clone, PartialEq)]
@ -1235,7 +1288,9 @@ fn replace_value_in_json_text(
let found_key = text
.get(key_range.clone())
.map(|key_text| key_text == format!("\"{}\"", key_path[depth]))
.map(|key_text| {
depth < key_path.len() && key_text == format!("\"{}\"", key_path[depth])
})
.unwrap_or(false);
if found_key {

View file

@ -31,16 +31,16 @@ pub fn init(client: Arc<Client>, cx: &mut App) {
let supermaven = cx.new(|_| Supermaven::Starting);
Supermaven::set_global(supermaven.clone(), cx);
let mut provider = all_language_settings(None, cx).inline_completions.provider;
if provider == language::language_settings::InlineCompletionProvider::Supermaven {
let mut provider = all_language_settings(None, cx).edit_predictions.provider;
if provider == language::language_settings::EditPredictionProvider::Supermaven {
supermaven.update(cx, |supermaven, cx| supermaven.start(client.clone(), cx));
}
cx.observe_global::<SettingsStore>(move |cx| {
let new_provider = all_language_settings(None, cx).inline_completions.provider;
let new_provider = all_language_settings(None, cx).edit_predictions.provider;
if new_provider != provider {
provider = new_provider;
if provider == language::language_settings::InlineCompletionProvider::Supermaven {
if provider == language::language_settings::EditPredictionProvider::Supermaven {
supermaven.update(cx, |supermaven, cx| supermaven.start(client.clone(), cx));
} else {
supermaven.update(cx, |supermaven, _cx| supermaven.stop());

View file

@ -2,7 +2,7 @@ use crate::{Supermaven, SupermavenCompletionStateId};
use anyhow::Result;
use futures::StreamExt as _;
use gpui::{App, Context, Entity, EntityId, Task};
use inline_completion::{Direction, InlineCompletion, InlineCompletionProvider};
use inline_completion::{Direction, EditPredictionProvider, InlineCompletion};
use language::{Anchor, Buffer, BufferSnapshot};
use project::Project;
use std::{
@ -97,7 +97,7 @@ fn completion_from_diff(
}
}
impl InlineCompletionProvider for SupermavenCompletionProvider {
impl EditPredictionProvider for SupermavenCompletionProvider {
fn name() -> &'static str {
"supermaven"
}

View file

@ -25,6 +25,7 @@ use workspace::{
const PANEL_WIDTH_REMS: f32 = 28.;
#[derive(PartialEq, Clone, Deserialize, JsonSchema, Default)]
#[serde(deny_unknown_fields)]
pub struct Toggle {
#[serde(default)]
pub select_last: bool,

View file

@ -35,10 +35,11 @@ use workspace::{
item::SerializableItem,
move_active_item, move_item, pane,
ui::IconName,
ActivateNextPane, ActivatePane, ActivatePaneInDirection, ActivatePreviousPane,
DraggedSelection, DraggedTab, ItemId, MoveItemToPane, MoveItemToPaneInDirection, NewTerminal,
Pane, PaneGroup, SplitDirection, SplitDown, SplitLeft, SplitRight, SplitUp,
SwapPaneInDirection, ToggleZoom, Workspace,
ActivateNextPane, ActivatePane, ActivatePaneDown, ActivatePaneLeft, ActivatePaneRight,
ActivatePaneUp, ActivatePreviousPane, DraggedSelection, DraggedTab, ItemId, MoveItemToPane,
MoveItemToPaneInDirection, NewTerminal, Pane, PaneGroup, SplitDirection, SplitDown, SplitLeft,
SplitRight, SplitUp, SwapPaneDown, SwapPaneLeft, SwapPaneRight, SwapPaneUp, ToggleZoom,
Workspace,
};
use anyhow::{anyhow, Context as _, Result};
@ -889,6 +890,37 @@ impl TerminalPanel {
is_enabled_in_workspace(workspace.read(cx), cx)
})
}
fn activate_pane_in_direction(
&mut self,
direction: SplitDirection,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(pane) = self
.center
.find_pane_in_direction(&self.active_pane, direction, cx)
{
window.focus(&pane.focus_handle(cx));
} else {
self.workspace
.update(cx, |workspace, cx| {
workspace.activate_pane_in_direction(direction, window, cx)
})
.ok();
}
}
fn swap_pane_in_direction(&mut self, direction: SplitDirection, cx: &mut Context<Self>) {
if let Some(to) = self
.center
.find_pane_in_direction(&self.active_pane, direction, cx)
.cloned()
{
self.center.swap(&self.active_pane, &to);
cx.notify();
}
}
}
fn is_enabled_in_workspace(workspace: &Workspace, cx: &App) -> bool {
@ -1145,24 +1177,28 @@ impl Render for TerminalPanel {
.ok()
.map(|div| {
div.on_action({
cx.listener(
|terminal_panel, action: &ActivatePaneInDirection, window, cx| {
if let Some(pane) = terminal_panel.center.find_pane_in_direction(
&terminal_panel.active_pane,
action.0,
cx,
) {
window.focus(&pane.focus_handle(cx));
} else {
terminal_panel
.workspace
.update(cx, |workspace, cx| {
workspace.activate_pane_in_direction(action.0, window, cx)
})
.ok();
}
},
)
cx.listener(|terminal_panel, _: &ActivatePaneLeft, window, cx| {
terminal_panel.activate_pane_in_direction(SplitDirection::Left, window, cx);
})
})
.on_action({
cx.listener(|terminal_panel, _: &ActivatePaneRight, window, cx| {
terminal_panel.activate_pane_in_direction(
SplitDirection::Right,
window,
cx,
);
})
})
.on_action({
cx.listener(|terminal_panel, _: &ActivatePaneUp, window, cx| {
terminal_panel.activate_pane_in_direction(SplitDirection::Up, window, cx);
})
})
.on_action({
cx.listener(|terminal_panel, _: &ActivatePaneDown, window, cx| {
terminal_panel.activate_pane_in_direction(SplitDirection::Down, window, cx);
})
})
.on_action(
cx.listener(|terminal_panel, _action: &ActivateNextPane, window, cx| {
@ -1210,18 +1246,18 @@ impl Render for TerminalPanel {
}
}),
)
.on_action(
cx.listener(|terminal_panel, action: &SwapPaneInDirection, _, cx| {
if let Some(to) = terminal_panel
.center
.find_pane_in_direction(&terminal_panel.active_pane, action.0, cx)
.cloned()
{
terminal_panel.center.swap(&terminal_panel.active_pane, &to);
cx.notify();
}
}),
)
.on_action(cx.listener(|terminal_panel, _: &SwapPaneLeft, _, cx| {
terminal_panel.swap_pane_in_direction(SplitDirection::Left, cx);
}))
.on_action(cx.listener(|terminal_panel, _: &SwapPaneRight, _, cx| {
terminal_panel.swap_pane_in_direction(SplitDirection::Right, cx);
}))
.on_action(cx.listener(|terminal_panel, _: &SwapPaneUp, _, cx| {
terminal_panel.swap_pane_in_direction(SplitDirection::Up, cx);
}))
.on_action(cx.listener(|terminal_panel, _: &SwapPaneDown, _, cx| {
terminal_panel.swap_pane_in_direction(SplitDirection::Down, cx);
}))
.on_action(
cx.listener(|terminal_panel, action: &MoveItemToPane, window, cx| {
let Some(&target_pane) =

View file

@ -1,19 +1,31 @@
use gpui::{impl_actions, Entity, OwnedMenu, OwnedMenuItem};
use gpui::{Entity, OwnedMenu, OwnedMenuItem};
#[cfg(not(target_os = "macos"))]
use gpui::{actions, impl_actions};
#[cfg(not(target_os = "macos"))]
use schemars::JsonSchema;
#[cfg(not(target_os = "macos"))]
use serde::Deserialize;
use smallvec::SmallVec;
use ui::{prelude::*, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip};
impl_actions!(
app_menu,
[OpenApplicationMenu, NavigateApplicationMenuInDirection]
);
#[cfg(not(target_os = "macos"))]
impl_actions!(app_menu, [OpenApplicationMenu]);
#[cfg(not(target_os = "macos"))]
actions!(app_menu, [ActivateMenuRight, ActivateMenuLeft]);
#[cfg(not(target_os = "macos"))]
#[derive(Clone, Deserialize, JsonSchema, PartialEq, Default)]
pub struct OpenApplicationMenu(String);
#[derive(Clone, Deserialize, JsonSchema, PartialEq, Default)]
pub struct NavigateApplicationMenuInDirection(String);
#[cfg(not(target_os = "macos"))]
pub enum ActivateDirection {
Left,
Right,
}
#[derive(Clone)]
struct MenuEntry {
@ -190,7 +202,7 @@ impl ApplicationMenu {
#[cfg(not(target_os = "macos"))]
pub fn navigate_menus_in_direction(
&mut self,
action: &NavigateApplicationMenuInDirection,
direction: ActivateDirection,
window: &mut Window,
cx: &mut Context<Self>,
) {
@ -202,22 +214,21 @@ impl ApplicationMenu {
return;
};
let next_index = match action.0.as_str() {
"Left" => {
let next_index = match direction {
ActivateDirection::Left => {
if current_index == 0 {
self.entries.len() - 1
} else {
current_index - 1
}
}
"Right" => {
ActivateDirection::Right => {
if current_index == self.entries.len() - 1 {
0
} else {
current_index + 1
}
}
_ => return,
};
self.entries[current_index].handle.hide(cx);

View file

@ -9,7 +9,9 @@ mod stories;
use crate::application_menu::ApplicationMenu;
#[cfg(not(target_os = "macos"))]
use crate::application_menu::{NavigateApplicationMenuInDirection, OpenApplicationMenu};
use crate::application_menu::{
ActivateDirection, ActivateMenuLeft, ActivateMenuRight, OpenApplicationMenu,
};
use crate::platforms::{platform_linux, platform_mac, platform_windows};
use auto_update::AutoUpdateStatus;
@ -78,22 +80,36 @@ pub fn init(cx: &mut App) {
});
#[cfg(not(target_os = "macos"))]
workspace.register_action(
|workspace, action: &NavigateApplicationMenuInDirection, window, cx| {
if let Some(titlebar) = workspace
.titlebar_item()
.and_then(|item| item.downcast::<TitleBar>().ok())
{
titlebar.update(cx, |titlebar, cx| {
if let Some(ref menu) = titlebar.application_menu {
menu.update(cx, |menu, cx| {
menu.navigate_menus_in_direction(action, window, cx)
});
}
});
}
},
);
workspace.register_action(|workspace, _: &ActivateMenuRight, window, cx| {
if let Some(titlebar) = workspace
.titlebar_item()
.and_then(|item| item.downcast::<TitleBar>().ok())
{
titlebar.update(cx, |titlebar, cx| {
if let Some(ref menu) = titlebar.application_menu {
menu.update(cx, |menu, cx| {
menu.navigate_menus_in_direction(ActivateDirection::Right, window, cx)
});
}
});
}
});
#[cfg(not(target_os = "macos"))]
workspace.register_action(|workspace, _: &ActivateMenuLeft, window, cx| {
if let Some(titlebar) = workspace
.titlebar_item()
.and_then(|item| item.downcast::<TitleBar>().ok())
{
titlebar.update(cx, |titlebar, cx| {
if let Some(ref menu) = titlebar.application_menu {
menu.update(cx, |menu, cx| {
menu.navigate_menus_in_direction(ActivateDirection::Left, window, cx)
});
}
});
}
});
})
.detach();
}

View file

@ -141,105 +141,105 @@ pub enum Motion {
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct NextWordStart {
#[serde(default)]
ignore_punctuation: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct NextWordEnd {
#[serde(default)]
ignore_punctuation: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct PreviousWordStart {
#[serde(default)]
ignore_punctuation: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct PreviousWordEnd {
#[serde(default)]
ignore_punctuation: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub(crate) struct NextSubwordStart {
#[serde(default)]
pub(crate) ignore_punctuation: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub(crate) struct NextSubwordEnd {
#[serde(default)]
pub(crate) ignore_punctuation: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub(crate) struct PreviousSubwordStart {
#[serde(default)]
pub(crate) ignore_punctuation: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub(crate) struct PreviousSubwordEnd {
#[serde(default)]
pub(crate) ignore_punctuation: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub(crate) struct Up {
#[serde(default)]
pub(crate) display_lines: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub(crate) struct Down {
#[serde(default)]
pub(crate) display_lines: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct FirstNonWhitespace {
#[serde(default)]
display_lines: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct EndOfLine {
#[serde(default)]
display_lines: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct StartOfLine {
#[serde(default)]
pub(crate) display_lines: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct UnmatchedForward {
#[serde(default)]
char: char,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct UnmatchedBackward {
#[serde(default)]
char: char,

View file

@ -8,14 +8,14 @@ use std::ops::Range;
use crate::{state::Mode, Vim};
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct Increment {
#[serde(default)]
step: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct Decrement {
#[serde(default)]
step: bool,

View file

@ -13,7 +13,7 @@ use crate::{
};
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct Paste {
#[serde(default)]
before: bool,

View file

@ -16,7 +16,7 @@ use crate::{
};
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub(crate) struct MoveToNext {
#[serde(default = "default_true")]
case_sensitive: bool,
@ -27,7 +27,7 @@ pub(crate) struct MoveToNext {
}
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub(crate) struct MoveToPrev {
#[serde(default = "default_true")]
case_sensitive: bool,
@ -38,6 +38,7 @@ pub(crate) struct MoveToPrev {
}
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
pub(crate) struct Search {
#[serde(default)]
backwards: bool,
@ -46,6 +47,7 @@ pub(crate) struct Search {
}
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct FindCommand {
pub query: String,
pub backwards: bool,

View file

@ -19,6 +19,7 @@ use serde::Deserialize;
use ui::Context;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum Object {
Word { ignore_punctuation: bool },
Subword { ignore_punctuation: bool },
@ -44,20 +45,20 @@ pub enum Object {
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct Word {
#[serde(default)]
ignore_punctuation: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct Subword {
#[serde(default)]
ignore_punctuation: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct IndentObj {
#[serde(default)]
include_below: bool,

View file

@ -10,7 +10,6 @@ use gpui::{
Action, App, BorrowAppContext, ClipboardEntry, ClipboardItem, Entity, Global, WeakEntity,
};
use language::Point;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
use std::borrow::BorrowMut;
@ -18,7 +17,7 @@ use std::{fmt::Display, ops::Range, sync::Arc};
use ui::{Context, SharedString};
use workspace::searchable::Direction;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, JsonSchema, Serialize)]
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum Mode {
Normal,
Insert,
@ -59,7 +58,7 @@ impl Default for Mode {
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, JsonSchema)]
#[derive(Clone, Debug, PartialEq)]
pub enum Operator {
Change,
Delete,
@ -82,7 +81,6 @@ pub enum Operator {
},
AddSurrounds {
// Typically no need to configure this as `SendKeystrokes` can be used - see #23088.
#[serde(skip)]
target: Option<SurroundsType>,
},
ChangeSurrounds {

View file

@ -554,11 +554,7 @@ mod test {
use gpui::KeyBinding;
use indoc::indoc;
use crate::{
state::{Mode, Operator},
test::VimTestContext,
PushOperator,
};
use crate::{state::Mode, test::VimTestContext, PushAddSurrounds};
#[gpui::test]
async fn test_add_surrounds(cx: &mut gpui::TestAppContext) {
@ -749,7 +745,7 @@ mod test {
cx.update(|_, cx| {
cx.bind_keys([KeyBinding::new(
"shift-s",
PushOperator(Operator::AddSurrounds { target: None }),
PushAddSurrounds {},
Some("vim_mode == visual"),
)])
});

View file

@ -17,12 +17,7 @@ use indoc::indoc;
use search::BufferSearchBar;
use workspace::WorkspaceSettings;
use crate::{
insert::NormalBefore,
motion,
state::{Mode, Operator},
PushOperator,
};
use crate::{insert::NormalBefore, motion, state::Mode, PushSneak, PushSneakBackward};
#[gpui::test]
async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
@ -1347,17 +1342,17 @@ async fn test_sneak(cx: &mut gpui::TestAppContext) {
cx.bind_keys([
KeyBinding::new(
"s",
PushOperator(Operator::Sneak { first_char: None }),
PushSneak { first_char: None },
Some("vim_mode == normal"),
),
KeyBinding::new(
"S",
PushOperator(Operator::SneakBackward { first_char: None }),
PushSneakBackward { first_char: None },
Some("vim_mode == normal"),
),
KeyBinding::new(
"S",
PushOperator(Operator::SneakBackward { first_char: None }),
PushSneakBackward { first_char: None },
Some("vim_mode == visual"),
),
])

View file

@ -35,6 +35,7 @@ use language::{CursorShape, Point, Selection, SelectionGoal, TransactionId};
pub use mode_indicator::ModeIndicator;
use motion::Motion;
use normal::search::SearchSubmit;
use object::Object;
use schemars::JsonSchema;
use serde::Deserialize;
use serde_derive::Serialize;
@ -45,24 +46,10 @@ use surrounds::SurroundsType;
use theme::ThemeSettings;
use ui::{px, IntoElement, SharedString};
use vim_mode_setting::VimModeSetting;
use workspace::{self, Pane, ResizeIntent, Workspace};
use workspace::{self, Pane, Workspace};
use crate::state::ReplayableAction;
/// Used to resize the current pane
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
pub struct ResizePane(pub ResizeIntent);
/// An Action to Switch between modes
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
pub struct SwitchMode(pub Mode);
/// PushOperator is used to put vim into a "minor" mode,
/// where it's waiting for a specific next set of keystrokes.
/// For example 'd' needs a motion to complete.
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
pub struct PushOperator(pub Operator);
/// Number is used to manage vim's count. Pushing a digit
/// multiplies the current value by 10 and adds the digit.
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
@ -71,29 +58,126 @@ struct Number(usize);
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
struct SelectRegister(String);
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct PushObject {
around: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct PushFindForward {
before: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct PushFindBackward {
after: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct PushSneak {
first_char: Option<char>,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct PushSneakBackward {
first_char: Option<char>,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct PushAddSurrounds {}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct PushChangeSurrounds {
target: Option<Object>,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct PushJump {
line: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct PushDigraph {
first_char: Option<char>,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct PushLiteral {
prefix: Option<String>,
}
actions!(
vim,
[
SwitchToNormalMode,
SwitchToInsertMode,
SwitchToReplaceMode,
SwitchToVisualMode,
SwitchToVisualLineMode,
SwitchToVisualBlockMode,
SwitchToHelixNormalMode,
ClearOperators,
Tab,
Enter,
InnerObject,
FindForward,
FindBackward,
MaximizePane,
OpenDefaultKeymap,
ResetPaneSizes,
Sneak,
SneakBackward,
ResizePaneRight,
ResizePaneLeft,
ResizePaneUp,
ResizePaneDown,
PushChange,
PushDelete,
PushYank,
PushReplace,
PushDeleteSurrounds,
PushMark,
PushIndent,
PushOutdent,
PushAutoIndent,
PushRewrap,
PushShellCommand,
PushLowercase,
PushUppercase,
PushOppositeCase,
PushRegister,
PushRecordRegister,
PushReplayRegister,
PushReplaceWithRegister,
PushToggleComments,
]
);
// in the workspace namespace so it's not filtered out when vim is disabled.
actions!(workspace, [ToggleVimMode]);
actions!(workspace, [ToggleVimMode,]);
impl_actions!(
vim,
[ResizePane, SwitchMode, PushOperator, Number, SelectRegister]
[
Number,
SelectRegister,
PushObject,
PushFindForward,
PushFindBackward,
PushSneak,
PushSneakBackward,
PushAddSurrounds,
PushChangeSurrounds,
PushJump,
PushDigraph,
PushLiteral
]
);
/// Initializes the `vim` crate.
@ -142,7 +226,7 @@ pub fn init(cx: &mut App) {
workspace.resize_pane(Axis::Vertical, desired_size - size.size.height, window, cx)
});
workspace.register_action(|workspace, action: &ResizePane, window, cx| {
workspace.register_action(|workspace, _: &ResizePaneRight, window, cx| {
let count = Vim::take_count(cx).unwrap_or(1) as f32;
let theme = ThemeSettings::get_global(cx);
let Ok(font_id) = window.text_system().font_id(&theme.buffer_font) else {
@ -154,16 +238,36 @@ pub fn init(cx: &mut App) {
else {
return;
};
let height = theme.buffer_font_size() * theme.buffer_line_height.value();
workspace.resize_pane(Axis::Horizontal, width.width * count, window, cx);
});
let (axis, amount) = match action.0 {
ResizeIntent::Lengthen => (Axis::Vertical, height),
ResizeIntent::Shorten => (Axis::Vertical, height * -1.),
ResizeIntent::Widen => (Axis::Horizontal, width.width),
ResizeIntent::Narrow => (Axis::Horizontal, width.width * -1.),
workspace.register_action(|workspace, _: &ResizePaneLeft, window, cx| {
let count = Vim::take_count(cx).unwrap_or(1) as f32;
let theme = ThemeSettings::get_global(cx);
let Ok(font_id) = window.text_system().font_id(&theme.buffer_font) else {
return;
};
let Ok(width) = window
.text_system()
.advance(font_id, theme.buffer_font_size(), 'm')
else {
return;
};
workspace.resize_pane(Axis::Horizontal, -width.width * count, window, cx);
});
workspace.resize_pane(axis, amount * count, window, cx);
workspace.register_action(|workspace, _: &ResizePaneUp, window, cx| {
let count = Vim::take_count(cx).unwrap_or(1) as f32;
let theme = ThemeSettings::get_global(cx);
let height = theme.buffer_font_size() * theme.buffer_line_height.value();
workspace.resize_pane(Axis::Vertical, height * count, window, cx);
});
workspace.register_action(|workspace, _: &ResizePaneDown, window, cx| {
let count = Vim::take_count(cx).unwrap_or(1) as f32;
let theme = ThemeSettings::get_global(cx);
let height = theme.buffer_font_size() * theme.buffer_line_height.value();
workspace.resize_pane(Axis::Vertical, -height * count, window, cx);
});
workspace.register_action(|workspace, _: &SearchSubmit, window, cx| {
@ -330,12 +434,212 @@ impl Vim {
});
vim.update(cx, |_, cx| {
Vim::action(editor, cx, |vim, action: &SwitchMode, window, cx| {
vim.switch_mode(action.0, false, window, cx)
Vim::action(editor, cx, |vim, _: &SwitchToNormalMode, window, cx| {
vim.switch_mode(Mode::Normal, false, window, cx)
});
Vim::action(editor, cx, |vim, action: &PushOperator, window, cx| {
vim.push_operator(action.0.clone(), window, cx)
Vim::action(editor, cx, |vim, _: &SwitchToInsertMode, window, cx| {
vim.switch_mode(Mode::Insert, false, window, cx)
});
Vim::action(editor, cx, |vim, _: &SwitchToReplaceMode, window, cx| {
vim.switch_mode(Mode::Replace, false, window, cx)
});
Vim::action(editor, cx, |vim, _: &SwitchToVisualMode, window, cx| {
vim.switch_mode(Mode::Visual, false, window, cx)
});
Vim::action(editor, cx, |vim, _: &SwitchToVisualLineMode, window, cx| {
vim.switch_mode(Mode::VisualLine, false, window, cx)
});
Vim::action(
editor,
cx,
|vim, _: &SwitchToVisualBlockMode, window, cx| {
vim.switch_mode(Mode::VisualBlock, false, window, cx)
},
);
Vim::action(
editor,
cx,
|vim, _: &SwitchToHelixNormalMode, window, cx| {
vim.switch_mode(Mode::HelixNormal, false, window, cx)
},
);
Vim::action(editor, cx, |vim, action: &PushObject, window, cx| {
vim.push_operator(
Operator::Object {
around: action.around,
},
window,
cx,
)
});
Vim::action(editor, cx, |vim, action: &PushFindForward, window, cx| {
vim.push_operator(
Operator::FindForward {
before: action.before,
},
window,
cx,
)
});
Vim::action(editor, cx, |vim, action: &PushFindBackward, window, cx| {
vim.push_operator(
Operator::FindBackward {
after: action.after,
},
window,
cx,
)
});
Vim::action(editor, cx, |vim, action: &PushSneak, window, cx| {
vim.push_operator(
Operator::Sneak {
first_char: action.first_char,
},
window,
cx,
)
});
Vim::action(editor, cx, |vim, action: &PushSneakBackward, window, cx| {
vim.push_operator(
Operator::SneakBackward {
first_char: action.first_char,
},
window,
cx,
)
});
Vim::action(editor, cx, |vim, _: &PushAddSurrounds, window, cx| {
vim.push_operator(Operator::AddSurrounds { target: None }, window, cx)
});
Vim::action(
editor,
cx,
|vim, action: &PushChangeSurrounds, window, cx| {
vim.push_operator(
Operator::ChangeSurrounds {
target: action.target,
},
window,
cx,
)
},
);
Vim::action(editor, cx, |vim, action: &PushJump, window, cx| {
vim.push_operator(Operator::Jump { line: action.line }, window, cx)
});
Vim::action(editor, cx, |vim, action: &PushDigraph, window, cx| {
vim.push_operator(
Operator::Digraph {
first_char: action.first_char,
},
window,
cx,
)
});
Vim::action(editor, cx, |vim, action: &PushLiteral, window, cx| {
vim.push_operator(
Operator::Literal {
prefix: action.prefix.clone(),
},
window,
cx,
)
});
Vim::action(editor, cx, |vim, _: &PushChange, window, cx| {
vim.push_operator(Operator::Change, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushDelete, window, cx| {
vim.push_operator(Operator::Delete, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushYank, window, cx| {
vim.push_operator(Operator::Yank, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushReplace, window, cx| {
vim.push_operator(Operator::Replace, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushDeleteSurrounds, window, cx| {
vim.push_operator(Operator::DeleteSurrounds, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushMark, window, cx| {
vim.push_operator(Operator::Mark, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushIndent, window, cx| {
vim.push_operator(Operator::Indent, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushOutdent, window, cx| {
vim.push_operator(Operator::Outdent, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushAutoIndent, window, cx| {
vim.push_operator(Operator::AutoIndent, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushRewrap, window, cx| {
vim.push_operator(Operator::Rewrap, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushShellCommand, window, cx| {
vim.push_operator(Operator::ShellCommand, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushLowercase, window, cx| {
vim.push_operator(Operator::Lowercase, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushUppercase, window, cx| {
vim.push_operator(Operator::Uppercase, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushOppositeCase, window, cx| {
vim.push_operator(Operator::OppositeCase, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushRegister, window, cx| {
vim.push_operator(Operator::Register, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushRecordRegister, window, cx| {
vim.push_operator(Operator::RecordRegister, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushReplayRegister, window, cx| {
vim.push_operator(Operator::ReplayRegister, window, cx)
});
Vim::action(
editor,
cx,
|vim, _: &PushReplaceWithRegister, window, cx| {
vim.push_operator(Operator::ReplaceWithRegister, window, cx)
},
);
Vim::action(editor, cx, |vim, _: &PushToggleComments, window, cx| {
vim.push_operator(Operator::ToggleComments, window, cx)
});
Vim::action(editor, cx, |vim, _: &ClearOperators, window, cx| {
@ -1275,8 +1579,8 @@ impl Vim {
if self.mode == Mode::Normal {
self.update_editor(window, cx, |_, editor, window, cx| {
editor.accept_inline_completion(
&editor::actions::AcceptInlineCompletion {},
editor.accept_edit_prediction(
&editor::actions::AcceptEditPrediction {},
window,
cx,
);

View file

@ -72,7 +72,7 @@ impl DraggedSelection {
}
#[derive(Clone, Copy, PartialEq, Debug, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all = "snake_case")]
pub enum SaveIntent {
/// write all files (even if unchanged)
/// prompt before overwriting on-disk changes
@ -96,13 +96,13 @@ pub enum SaveIntent {
pub struct ActivateItem(pub usize);
#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct CloseActiveItem {
pub save_intent: Option<SaveIntent>,
}
#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct CloseInactiveItems {
pub save_intent: Option<SaveIntent>,
#[serde(default)]
@ -110,7 +110,7 @@ pub struct CloseInactiveItems {
}
#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct CloseAllItems {
pub save_intent: Option<SaveIntent>,
#[serde(default)]
@ -118,34 +118,35 @@ pub struct CloseAllItems {
}
#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct CloseCleanItems {
#[serde(default)]
pub close_pinned: bool,
}
#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct CloseItemsToTheRight {
#[serde(default)]
pub close_pinned: bool,
}
#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct CloseItemsToTheLeft {
#[serde(default)]
pub close_pinned: bool,
}
#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct RevealInProjectPanel {
#[serde(skip)]
pub entry_id: Option<u64>,
}
#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
#[serde(deny_unknown_fields)]
pub struct DeploySearch {
#[serde(default)]
pub replace_enabled: bool,

View file

@ -725,6 +725,7 @@ impl PaneAxis {
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SplitDirection {
Up,
Down,
@ -807,14 +808,6 @@ impl SplitDirection {
}
}
#[derive(Clone, Copy, Debug, Deserialize, JsonSchema, PartialEq)]
pub enum ResizeIntent {
Lengthen,
Shorten,
Widen,
Narrow,
}
mod element {
use std::mem;
use std::{cell::RefCell, iter, rc::Rc, sync::Arc};

View file

@ -170,12 +170,7 @@ pub struct OpenPaths {
pub struct ActivatePane(pub usize);
#[derive(Clone, Deserialize, PartialEq, JsonSchema)]
pub struct ActivatePaneInDirection(pub SplitDirection);
#[derive(Clone, Deserialize, PartialEq, JsonSchema)]
pub struct SwapPaneInDirection(pub SplitDirection);
#[derive(Clone, Deserialize, PartialEq, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct MoveItemToPane {
pub destination: usize,
#[serde(default = "default_true")]
@ -183,6 +178,7 @@ pub struct MoveItemToPane {
}
#[derive(Clone, Deserialize, PartialEq, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct MoveItemToPaneInDirection {
pub direction: SplitDirection,
#[serde(default = "default_true")]
@ -190,25 +186,25 @@ pub struct MoveItemToPaneInDirection {
}
#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct SaveAll {
pub save_intent: Option<SaveIntent>,
}
#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct Save {
pub save_intent: Option<SaveIntent>,
}
#[derive(Clone, PartialEq, Debug, Deserialize, Default, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct CloseAllItemsAndPanes {
pub save_intent: Option<SaveIntent>,
}
#[derive(Clone, PartialEq, Debug, Deserialize, Default, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct CloseInactiveTabsAndPanes {
pub save_intent: Option<SaveIntent>,
}
@ -217,6 +213,7 @@ pub struct CloseInactiveTabsAndPanes {
pub struct SendKeystrokes(pub String);
#[derive(Clone, Deserialize, PartialEq, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Reload {
pub binary_path: Option<PathBuf>,
}
@ -235,7 +232,6 @@ impl_actions!(
workspace,
[
ActivatePane,
ActivatePaneInDirection,
CloseAllItemsAndPanes,
CloseInactiveTabsAndPanes,
MoveItemToPane,
@ -244,11 +240,24 @@ impl_actions!(
Reload,
Save,
SaveAll,
SwapPaneInDirection,
SendKeystrokes,
]
);
actions!(
workspace,
[
ActivatePaneLeft,
ActivatePaneRight,
ActivatePaneUp,
ActivatePaneDown,
SwapPaneLeft,
SwapPaneRight,
SwapPaneUp,
SwapPaneDown,
]
);
#[derive(PartialEq, Eq, Debug)]
pub enum CloseIntent {
/// Quit the program entirely.
@ -301,6 +310,7 @@ impl PartialEq for Toast {
}
#[derive(Debug, Default, Clone, Deserialize, PartialEq, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct OpenTerminal {
pub working_directory: PathBuf,
}
@ -4821,29 +4831,38 @@ impl Workspace {
workspace.activate_previous_window(cx)
}),
)
.on_action(
cx.listener(|workspace, action: &ActivatePaneInDirection, window, cx| {
workspace.activate_pane_in_direction(action.0, window, cx)
}),
)
.on_action(cx.listener(|workspace, _: &ActivatePaneLeft, window, cx| {
workspace.activate_pane_in_direction(SplitDirection::Left, window, cx)
}))
.on_action(cx.listener(|workspace, _: &ActivatePaneRight, window, cx| {
workspace.activate_pane_in_direction(SplitDirection::Right, window, cx)
}))
.on_action(cx.listener(|workspace, _: &ActivatePaneUp, window, cx| {
workspace.activate_pane_in_direction(SplitDirection::Up, window, cx)
}))
.on_action(cx.listener(|workspace, _: &ActivatePaneDown, window, cx| {
workspace.activate_pane_in_direction(SplitDirection::Down, window, cx)
}))
.on_action(cx.listener(|workspace, _: &ActivateNextPane, window, cx| {
workspace.activate_next_pane(window, cx)
}))
.on_action(
cx.listener(|workspace, action: &ActivatePaneInDirection, window, cx| {
workspace.activate_pane_in_direction(action.0, window, cx)
}),
)
.on_action(cx.listener(
|workspace, action: &MoveItemToPaneInDirection, window, cx| {
workspace.move_item_to_pane_in_direction(action, window, cx)
},
))
.on_action(
cx.listener(|workspace, action: &SwapPaneInDirection, _, cx| {
workspace.swap_pane_in_direction(action.0, cx)
}),
)
.on_action(cx.listener(|workspace, _: &SwapPaneLeft, _, cx| {
workspace.swap_pane_in_direction(SplitDirection::Left, cx)
}))
.on_action(cx.listener(|workspace, _: &SwapPaneRight, _, cx| {
workspace.swap_pane_in_direction(SplitDirection::Right, cx)
}))
.on_action(cx.listener(|workspace, _: &SwapPaneUp, _, cx| {
workspace.swap_pane_in_direction(SplitDirection::Up, cx)
}))
.on_action(cx.listener(|workspace, _: &SwapPaneDown, _, cx| {
workspace.swap_pane_in_direction(SplitDirection::Down, cx)
}))
.on_action(cx.listener(|this, _: &ToggleLeftDock, window, cx| {
this.toggle_dock(DockPosition::Left, window, cx);
}))

View file

@ -20,6 +20,7 @@ use command_palette_hooks::CommandPaletteFilter;
use editor::ProposedChangesEditorToolbar;
use editor::{scroll::Autoscroll, Editor, MultiBuffer};
use feature_flags::{FeatureFlagAppExt, FeatureFlagViewExt, GitUiFeatureFlag};
use fs::Fs;
use futures::{channel::mpsc, select_biased, StreamExt};
use gpui::{
actions, point, px, Action, App, AppContext as _, AsyncApp, Context, DismissEvent, Element,
@ -1144,18 +1145,34 @@ pub fn handle_keymap_file_changes(
cx.update(|cx| {
let load_result = KeymapFile::load(&user_keymap_content, cx);
match load_result {
KeymapFileLoadResult::Success { key_bindings } => {
KeymapFileLoadResult::Success {
key_bindings,
keymap_file,
} => {
reload_keymaps(cx, key_bindings);
dismiss_app_notification(&notification_id, cx);
show_keymap_migration_notification_if_needed(
keymap_file,
notification_id.clone(),
cx,
);
}
KeymapFileLoadResult::SomeFailedToLoad {
key_bindings,
keymap_file,
error_message,
} => {
if !key_bindings.is_empty() {
reload_keymaps(cx, key_bindings);
}
show_keymap_file_load_error(notification_id.clone(), error_message, cx)
dismiss_app_notification(&notification_id, cx);
if !show_keymap_migration_notification_if_needed(
keymap_file,
notification_id.clone(),
cx,
) {
show_keymap_file_load_error(notification_id.clone(), error_message, cx);
}
}
KeymapFileLoadResult::JsonParseFailure { error } => {
show_keymap_file_json_error(notification_id.clone(), &error, cx)
@ -1187,6 +1204,61 @@ fn show_keymap_file_json_error(
});
}
fn show_keymap_migration_notification_if_needed(
keymap_file: KeymapFile,
notification_id: NotificationId,
cx: &mut App,
) -> bool {
if !KeymapFile::should_migrate_keymap(keymap_file) {
return false;
}
show_app_notification(notification_id, cx, move |cx| {
cx.new(move |_cx| {
let message = "A newer version of Zed has simplified several keymaps. Your existing keymaps may be deprecated. You can migrate them by clicking below. A backup will be created in your home directory.";
let button_text = "Backup and Migrate Keymap";
MessageNotification::new_from_builder(move |_, _| {
gpui::div().text_xs().child(message).into_any()
})
.primary_message(button_text)
.primary_on_click(move |_, cx| {
let fs = <dyn Fs>::global(cx);
cx.spawn(move |weak_notification, mut cx| async move {
KeymapFile::migrate_keymap(fs).await.ok();
weak_notification.update(&mut cx, |_, cx| {
cx.emit(DismissEvent);
}).ok();
}).detach();
})
})
});
return true;
}
fn show_settings_migration_notification_if_needed(
notification_id: NotificationId,
settings: serde_json::Value,
cx: &mut App,
) {
if !SettingsStore::should_migrate_settings(&settings) {
return;
}
show_app_notification(notification_id, cx, move |cx| {
cx.new(move |_cx| {
let message = "A newer version of Zed has updated some settings. Your existing settings may be deprecated. You can migrate them by clicking below. A backup will be created in your home directory.";
let button_text = "Backup and Migrate Settings";
MessageNotification::new_from_builder(move |_, _| {
gpui::div().text_xs().child(message).into_any()
})
.primary_message(button_text)
.primary_on_click(move |_, cx| {
let fs = <dyn Fs>::global(cx);
cx.update_global(|store: &mut SettingsStore, _| store.migrate_settings(fs));
cx.emit(DismissEvent);
})
})
});
}
fn show_keymap_file_load_error(
notification_id: NotificationId,
markdown_error_message: MarkdownString,
@ -1259,12 +1331,12 @@ pub fn load_default_keymap(cx: &mut App) {
}
}
pub fn handle_settings_changed(error: Option<anyhow::Error>, cx: &mut App) {
pub fn handle_settings_changed(result: Result<serde_json::Value, anyhow::Error>, cx: &mut App) {
struct SettingsParseErrorNotification;
let id = NotificationId::unique::<SettingsParseErrorNotification>();
match error {
Some(error) => {
match result {
Err(error) => {
if let Some(InvalidSettingsError::LocalSettings { .. }) =
error.downcast_ref::<InvalidSettingsError>()
{
@ -1283,7 +1355,10 @@ pub fn handle_settings_changed(error: Option<anyhow::Error>, cx: &mut App) {
})
});
}
None => dismiss_app_notification(&id, cx),
Ok(settings) => {
dismiss_app_notification(&id, cx);
show_settings_migration_notification_if_needed(id, settings, cx);
}
}
}
@ -3925,24 +4000,28 @@ mod tests {
"vim::FindCommand"
| "vim::Literal"
| "vim::ResizePane"
| "vim::SwitchMode"
| "vim::PushOperator"
| "vim::PushObject"
| "vim::PushFindForward"
| "vim::PushFindBackward"
| "vim::PushSneak"
| "vim::PushSneakBackward"
| "vim::PushChangeSurrounds"
| "vim::PushJump"
| "vim::PushDigraph"
| "vim::PushLiteral"
| "vim::Number"
| "vim::SelectRegister"
| "terminal::SendText"
| "terminal::SendKeystroke"
| "app_menu::OpenApplicationMenu"
| "app_menu::NavigateApplicationMenuInDirection"
| "picker::ConfirmInput"
| "editor::HandleInput"
| "editor::FoldAtLevel"
| "pane::ActivateItem"
| "workspace::ActivatePane"
| "workspace::ActivatePaneInDirection"
| "workspace::MoveItemToPane"
| "workspace::MoveItemToPaneInDirection"
| "workspace::OpenTerminal"
| "workspace::SwapPaneInDirection"
| "workspace::SendKeystrokes"
| "zed::OpenBrowser"
| "zed::OpenZedUrl" => {}

View file

@ -4,7 +4,7 @@ use copilot::{Copilot, CopilotCompletionProvider};
use editor::{Editor, EditorMode};
use feature_flags::{FeatureFlagAppExt, PredictEditsFeatureFlag};
use gpui::{AnyWindowHandle, App, AppContext, Context, Entity, WeakEntity};
use language::language_settings::{all_language_settings, InlineCompletionProvider};
use language::language_settings::{all_language_settings, EditPredictionProvider};
use settings::SettingsStore;
use std::{cell::RefCell, rc::Rc, sync::Arc};
use supermaven::{Supermaven, SupermavenCompletionProvider};
@ -41,8 +41,8 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
editors
.borrow_mut()
.insert(editor_handle, window.window_handle());
let provider = all_language_settings(None, cx).inline_completions.provider;
assign_inline_completion_provider(
let provider = all_language_settings(None, cx).edit_predictions.provider;
assign_edit_prediction_provider(
editor,
provider,
&client,
@ -54,11 +54,11 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
})
.detach();
let mut provider = all_language_settings(None, cx).inline_completions.provider;
let mut provider = all_language_settings(None, cx).edit_predictions.provider;
for (editor, window) in editors.borrow().iter() {
_ = window.update(cx, |_window, window, cx| {
_ = editor.update(cx, |editor, cx| {
assign_inline_completion_provider(
assign_edit_prediction_provider(
editor,
provider,
&client,
@ -79,8 +79,8 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
let client = client.clone();
let user_store = user_store.clone();
move |active, cx| {
let provider = all_language_settings(None, cx).inline_completions.provider;
assign_inline_completion_providers(&editors, provider, &client, user_store.clone(), cx);
let provider = all_language_settings(None, cx).edit_predictions.provider;
assign_edit_prediction_providers(&editors, provider, &client, user_store.clone(), cx);
if active && !cx.is_action_available(&zeta::ClearHistory) {
cx.on_action(clear_zeta_edit_history);
}
@ -93,7 +93,7 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
let client = client.clone();
let user_store = user_store.clone();
move |cx| {
let new_provider = all_language_settings(None, cx).inline_completions.provider;
let new_provider = all_language_settings(None, cx).edit_predictions.provider;
if new_provider != provider {
let tos_accepted = user_store
@ -109,7 +109,7 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
);
provider = new_provider;
assign_inline_completion_providers(
assign_edit_prediction_providers(
&editors,
provider,
&client,
@ -119,7 +119,7 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
if !tos_accepted {
match provider {
InlineCompletionProvider::Zed => {
EditPredictionProvider::Zed => {
let Some(window) = cx.active_window() else {
return;
};
@ -133,9 +133,9 @@ pub fn init(client: Arc<Client>, user_store: Entity<UserStore>, cx: &mut App) {
})
.ok();
}
InlineCompletionProvider::None
| InlineCompletionProvider::Copilot
| InlineCompletionProvider::Supermaven => {}
EditPredictionProvider::None
| EditPredictionProvider::Copilot
| EditPredictionProvider::Supermaven => {}
}
}
}
@ -150,9 +150,9 @@ fn clear_zeta_edit_history(_: &zeta::ClearHistory, cx: &mut App) {
}
}
fn assign_inline_completion_providers(
fn assign_edit_prediction_providers(
editors: &Rc<RefCell<HashMap<WeakEntity<Editor>, AnyWindowHandle>>>,
provider: InlineCompletionProvider,
provider: EditPredictionProvider,
client: &Arc<Client>,
user_store: Entity<UserStore>,
cx: &mut App,
@ -160,7 +160,7 @@ fn assign_inline_completion_providers(
for (editor, window) in editors.borrow().iter() {
_ = window.update(cx, |_window, window, cx| {
_ = editor.update(cx, |editor, cx| {
assign_inline_completion_provider(
assign_edit_prediction_provider(
editor,
provider,
&client,
@ -187,7 +187,7 @@ fn register_backward_compatible_actions(editor: &mut Editor, cx: &mut Context<Ed
editor
.register_action(cx.listener(
|editor, _: &copilot::NextSuggestion, window: &mut Window, cx: &mut Context<Editor>| {
editor.next_inline_completion(&Default::default(), window, cx);
editor.next_edit_prediction(&Default::default(), window, cx);
},
))
.detach();
@ -197,7 +197,7 @@ fn register_backward_compatible_actions(editor: &mut Editor, cx: &mut Context<Ed
_: &copilot::PreviousSuggestion,
window: &mut Window,
cx: &mut Context<Editor>| {
editor.previous_inline_completion(&Default::default(), window, cx);
editor.previous_edit_prediction(&Default::default(), window, cx);
},
))
.detach();
@ -213,9 +213,9 @@ fn register_backward_compatible_actions(editor: &mut Editor, cx: &mut Context<Ed
.detach();
}
fn assign_inline_completion_provider(
fn assign_edit_prediction_provider(
editor: &mut Editor,
provider: InlineCompletionProvider,
provider: EditPredictionProvider,
client: &Arc<Client>,
user_store: Entity<UserStore>,
window: &mut Window,
@ -225,8 +225,8 @@ fn assign_inline_completion_provider(
let singleton_buffer = editor.buffer().read(cx).as_singleton();
match provider {
InlineCompletionProvider::None => {}
InlineCompletionProvider::Copilot => {
EditPredictionProvider::None => {}
EditPredictionProvider::Copilot => {
if let Some(copilot) = Copilot::global(cx) {
if let Some(buffer) = singleton_buffer {
if buffer.read(cx).file().is_some() {
@ -236,16 +236,16 @@ fn assign_inline_completion_provider(
}
}
let provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
editor.set_inline_completion_provider(Some(provider), window, cx);
editor.set_edit_prediction_provider(Some(provider), window, cx);
}
}
InlineCompletionProvider::Supermaven => {
EditPredictionProvider::Supermaven => {
if let Some(supermaven) = Supermaven::global(cx) {
let provider = cx.new(|_| SupermavenCompletionProvider::new(supermaven));
editor.set_inline_completion_provider(Some(provider), window, cx);
editor.set_edit_prediction_provider(Some(provider), window, cx);
}
}
InlineCompletionProvider::Zed => {
EditPredictionProvider::Zed => {
if cx.has_flag::<PredictEditsFeatureFlag>()
|| (cfg!(debug_assertions) && client.status().borrow().is_connected())
{
@ -280,7 +280,7 @@ fn assign_inline_completion_provider(
let provider =
cx.new(|_| zeta::ZetaInlineCompletionProvider::new(zeta, data_collection));
editor.set_inline_completion_provider(Some(provider), window, cx);
editor.set_edit_prediction_provider(Some(provider), window, cx);
}
}
}

View file

@ -301,14 +301,14 @@ impl Render for QuickActionBar {
.toggleable(IconPosition::Start, inline_completion_enabled && show_inline_completions)
.disabled(!inline_completion_enabled)
.action(Some(
editor::actions::ToggleInlineCompletions.boxed_clone(),
editor::actions::ToggleEditPrediction.boxed_clone(),
)).handler({
let editor = editor.clone();
move |window, cx| {
editor
.update(cx, |editor, cx| {
editor.toggle_inline_completions(
&editor::actions::ToggleInlineCompletions,
&editor::actions::ToggleEditPrediction,
window,
cx,
);

View file

@ -12,11 +12,13 @@ use serde::{Deserialize, Serialize};
pub fn init() {}
#[derive(Clone, PartialEq, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct OpenBrowser {
pub url: String,
}
#[derive(Clone, PartialEq, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct OpenZedUrl {
pub url: String,
}
@ -69,6 +71,7 @@ pub mod theme_selector {
use serde::Deserialize;
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Toggle {
/// A list of theme names to filter the theme selector down to.
pub themes_filter: Option<Vec<String>>,
@ -83,6 +86,7 @@ pub mod icon_theme_selector {
use serde::Deserialize;
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Toggle {
/// A list of icon theme names to filter the theme selector down to.
pub themes_filter: Option<Vec<String>>,
@ -99,6 +103,7 @@ pub mod assistant {
actions!(assistant, [ToggleFocus, DeployPromptLibrary]);
#[derive(Clone, Default, Deserialize, PartialEq, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct InlineAssist {
pub prompt: Option<String>,
}
@ -107,6 +112,7 @@ pub mod assistant {
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct OpenRecent {
#[serde(default)]
pub create_new_window: bool,
@ -154,6 +160,7 @@ impl Spawn {
/// Rerun the last task.
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Rerun {
/// Controls whether the task context is reevaluated prior to execution of a task.
/// If it is not, environment variables such as ZED_COLUMN, ZED_FILE are gonna be the same as in the last execution of a task

View file

@ -5,7 +5,7 @@ use feature_flags::{
FeatureFlagAppExt as _, PredictEditsFeatureFlag, PredictEditsRateCompletionsFeatureFlag,
};
use gpui::actions;
use language::language_settings::{AllLanguageSettings, InlineCompletionProvider};
use language::language_settings::{AllLanguageSettings, EditPredictionProvider};
use settings::update_settings_file;
use ui::App;
use workspace::Workspace;
@ -44,7 +44,7 @@ pub fn init(cx: &mut App) {
move |file, _| {
file.features
.get_or_insert(Default::default())
.inline_completion_provider = Some(InlineCompletionProvider::None)
.edit_prediction_provider = Some(EditPredictionProvider::None)
},
);

View file

@ -1,7 +1,7 @@
use chrono::Utc;
use feature_flags::{FeatureFlagAppExt as _, PredictEditsFeatureFlag};
use gpui::Subscription;
use language::language_settings::{all_language_settings, InlineCompletionProvider};
use language::language_settings::{all_language_settings, EditPredictionProvider};
use settings::SettingsStore;
use ui::{prelude::*, ButtonLike, Tooltip};
use util::ResultExt;
@ -11,7 +11,7 @@ use crate::onboarding_event;
/// Prompts the user to try Zed's Edit Prediction feature
pub struct ZedPredictBanner {
dismissed: bool,
provider: InlineCompletionProvider,
provider: EditPredictionProvider,
_subscription: Subscription,
}
@ -19,7 +19,7 @@ impl ZedPredictBanner {
pub fn new(cx: &mut Context<Self>) -> Self {
Self {
dismissed: get_dismissed(),
provider: all_language_settings(None, cx).inline_completions.provider,
provider: all_language_settings(None, cx).edit_predictions.provider,
_subscription: cx.observe_global::<SettingsStore>(Self::handle_settings_changed),
}
}
@ -29,7 +29,7 @@ impl ZedPredictBanner {
}
fn handle_settings_changed(&mut self, cx: &mut Context<Self>) {
let new_provider = all_language_settings(None, cx).inline_completions.provider;
let new_provider = all_language_settings(None, cx).edit_predictions.provider;
if new_provider == self.provider {
return;

View file

@ -9,7 +9,7 @@ use gpui::{
ease_in_out, svg, Animation, AnimationExt as _, ClickEvent, DismissEvent, Entity, EventEmitter,
FocusHandle, Focusable, MouseDownEvent, Render,
};
use language::language_settings::{AllLanguageSettings, InlineCompletionProvider};
use language::language_settings::{AllLanguageSettings, EditPredictionProvider};
use settings::{update_settings_file, Settings};
use ui::{prelude::*, Checkbox, TintColor};
use util::ResultExt;
@ -105,7 +105,7 @@ impl ZedPredictModal {
update_settings_file::<AllLanguageSettings>(this.fs.clone(), cx, move |file, _| {
file.features
.get_or_insert(Default::default())
.inline_completion_provider = Some(InlineCompletionProvider::Zed);
.edit_prediction_provider = Some(EditPredictionProvider::Zed);
});
cx.emit(DismissEvent);

View file

@ -1500,7 +1500,7 @@ impl ZetaInlineCompletionProvider {
}
}
impl inline_completion::InlineCompletionProvider for ZetaInlineCompletionProvider {
impl inline_completion::EditPredictionProvider for ZetaInlineCompletionProvider {
fn name() -> &'static str {
"zed-predict"
}