@@ -198,26 +198,29 @@ impl CommandPaletteDelegate {
) {
self.updating_matches.take();
- let mut intercept_result = CommandPaletteInterceptor::try_global(cx)
- .and_then(|interceptor| interceptor.intercept(&query, cx));
+ let mut intercept_results = CommandPaletteInterceptor::try_global(cx)
+ .map(|interceptor| interceptor.intercept(&query, cx))
+ .unwrap_or_default();
if parse_zed_link(&query, cx).is_some() {
- intercept_result = Some(CommandInterceptResult {
+ intercept_results = vec![CommandInterceptResult {
action: OpenZedUrl { url: query.clone() }.boxed_clone(),
string: query.clone(),
positions: vec![],
- })
+ }]
}
- if let Some(CommandInterceptResult {
+ let mut new_matches = Vec::new();
+
+ for CommandInterceptResult {
action,
string,
positions,
- }) = intercept_result
+ } in intercept_results
{
if let Some(idx) = matches
.iter()
- .position(|m| commands[m.candidate_id].action.type_id() == action.type_id())
+ .position(|m| commands[m.candidate_id].action.partial_eq(&*action))
{
matches.remove(idx);
}
@@ -225,18 +228,16 @@ impl CommandPaletteDelegate {
name: string.clone(),
action,
});
- matches.insert(
- 0,
- StringMatch {
- candidate_id: commands.len() - 1,
- string,
- positions,
- score: 0.0,
- },
- )
+ new_matches.push(StringMatch {
+ candidate_id: commands.len() - 1,
+ string,
+ positions,
+ score: 0.0,
+ })
}
+ new_matches.append(&mut matches);
self.commands = commands;
- self.matches = matches;
+ self.matches = new_matches;
if self.matches.is_empty() {
self.selected_ix = 0;
} else {
@@ -108,7 +108,7 @@ pub struct CommandInterceptResult {
/// An interceptor for the command palette.
#[derive(Default)]
pub struct CommandPaletteInterceptor(
- Option<Box<dyn Fn(&str, &App) -> Option<CommandInterceptResult>>>,
+ Option<Box<dyn Fn(&str, &App) -> Vec<CommandInterceptResult>>>,
);
#[derive(Default)]
@@ -132,10 +132,12 @@ impl CommandPaletteInterceptor {
}
/// Intercepts the given query from the command palette.
- pub fn intercept(&self, query: &str, cx: &App) -> Option<CommandInterceptResult> {
- let handler = self.0.as_ref()?;
-
- (handler)(query, cx)
+ pub fn intercept(&self, query: &str, cx: &App) -> Vec<CommandInterceptResult> {
+ if let Some(handler) = self.0.as_ref() {
+ (handler)(query, cx)
+ } else {
+ Vec::new()
+ }
}
/// Clears the global interceptor.
@@ -146,7 +148,7 @@ impl CommandPaletteInterceptor {
/// Sets the global interceptor.
///
/// This will override the previous interceptor, if it exists.
- pub fn set(&mut self, handler: Box<dyn Fn(&str, &App) -> Option<CommandInterceptResult>>) {
+ pub fn set(&mut self, handler: Box<dyn Fn(&str, &App) -> Vec<CommandInterceptResult>>) {
self.0 = Some(handler);
}
}
@@ -8,6 +8,7 @@ use editor::{
Bias, Editor, ToPoint,
};
use gpui::{actions, impl_internal_actions, Action, App, Context, Global, Window};
+use itertools::Itertools;
use language::Point;
use multi_buffer::MultiBufferRow;
use regex::Regex;
@@ -64,6 +65,95 @@ pub struct WithCount {
action: WrappedAction,
}
+#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
+pub enum VimOption {
+ Wrap(bool),
+ Number(bool),
+ RelativeNumber(bool),
+}
+
+impl VimOption {
+ fn possible_commands(query: &str) -> Vec<CommandInterceptResult> {
+ let mut prefix_of_options = Vec::new();
+ let mut options = query.split(" ").collect::<Vec<_>>();
+ let prefix = options.pop().unwrap_or_default();
+ for option in options {
+ if let Some(opt) = Self::from(option) {
+ prefix_of_options.push(opt)
+ } else {
+ return vec![];
+ }
+ }
+
+ Self::possibilities(&prefix)
+ .map(|possible| {
+ let mut options = prefix_of_options.clone();
+ options.push(possible);
+
+ CommandInterceptResult {
+ string: format!(
+ "set {}",
+ options.iter().map(|opt| opt.to_string()).join(" ")
+ ),
+ action: VimSet { options }.boxed_clone(),
+ positions: vec![],
+ }
+ })
+ .collect()
+ }
+
+ fn possibilities(query: &str) -> impl Iterator<Item = Self> + '_ {
+ [
+ (None, VimOption::Wrap(true)),
+ (None, VimOption::Wrap(false)),
+ (None, VimOption::Number(true)),
+ (None, VimOption::Number(false)),
+ (None, VimOption::RelativeNumber(true)),
+ (None, VimOption::RelativeNumber(false)),
+ (Some("rnu"), VimOption::RelativeNumber(true)),
+ (Some("nornu"), VimOption::RelativeNumber(false)),
+ ]
+ .into_iter()
+ .filter(move |(prefix, option)| prefix.unwrap_or(option.to_string()).starts_with(query))
+ .map(|(_, option)| option)
+ }
+
+ fn from(option: &str) -> Option<Self> {
+ match option {
+ "wrap" => Some(Self::Wrap(true)),
+ "nowrap" => Some(Self::Wrap(false)),
+
+ "number" => Some(Self::Number(true)),
+ "nu" => Some(Self::Number(true)),
+ "nonumber" => Some(Self::Number(false)),
+ "nonu" => Some(Self::Number(false)),
+
+ "relativenumber" => Some(Self::RelativeNumber(true)),
+ "rnu" => Some(Self::RelativeNumber(true)),
+ "norelativenumber" => Some(Self::RelativeNumber(false)),
+ "nornu" => Some(Self::RelativeNumber(false)),
+
+ _ => None,
+ }
+ }
+
+ fn to_string(&self) -> &'static str {
+ match self {
+ VimOption::Wrap(true) => "wrap",
+ VimOption::Wrap(false) => "nowrap",
+ VimOption::Number(true) => "number",
+ VimOption::Number(false) => "nonumber",
+ VimOption::RelativeNumber(true) => "relativenumber",
+ VimOption::RelativeNumber(false) => "norelativenumber",
+ }
+ }
+}
+
+#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
+pub struct VimSet {
+ options: Vec<VimOption>,
+}
+
#[derive(Debug)]
struct WrappedAction(Box<dyn Action>);
@@ -76,7 +166,8 @@ impl_internal_actions!(
WithRange,
WithCount,
OnMatchingLines,
- ShellExec
+ ShellExec,
+ VimSet,
]
);
@@ -100,6 +191,26 @@ impl Deref for WrappedAction {
}
pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
+ // Vim::action(editor, cx, |vim, action: &StartOfLine, window, cx| {
+ Vim::action(editor, cx, |vim, action: &VimSet, window, cx| {
+ for option in action.options.iter() {
+ vim.update_editor(window, cx, |_, editor, _, cx| match option {
+ VimOption::Wrap(true) => {
+ editor
+ .set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
+ }
+ VimOption::Wrap(false) => {
+ editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx);
+ }
+ VimOption::Number(enabled) => {
+ editor.set_show_line_numbers(*enabled, cx);
+ }
+ VimOption::RelativeNumber(enabled) => {
+ editor.set_relative_line_number(Some(*enabled), cx);
+ }
+ });
+ }
+ });
Vim::action(editor, cx, |vim, _: &VisualCommand, window, cx| {
let Some(workspace) = vim.workspace(window) else {
return;
@@ -808,7 +919,7 @@ fn wrap_count(action: Box<dyn Action>, range: &CommandRange) -> Option<Box<dyn A
})
}
-pub fn command_interceptor(mut input: &str, cx: &App) -> Option<CommandInterceptResult> {
+pub fn command_interceptor(mut input: &str, cx: &App) -> Vec<CommandInterceptResult> {
// NOTE: We also need to support passing arguments to commands like :w
// (ideally with filename autocompletion).
while input.starts_with(':') {
@@ -834,6 +945,8 @@ pub fn command_interceptor(mut input: &str, cx: &App) -> Option<CommandIntercept
}
.boxed_clone(),
)
+ } else if query.starts_with("se ") || query.starts_with("set ") {
+ return VimOption::possible_commands(query.split_once(" ").unwrap().1);
} else if query.starts_with('s') {
let mut substitute = "substitute".chars().peekable();
let mut query = query.chars().peekable();
@@ -886,11 +999,11 @@ pub fn command_interceptor(mut input: &str, cx: &App) -> Option<CommandIntercept
if let Some(action) = action {
let string = input.to_string();
let positions = generate_positions(&string, &(range_prefix + query));
- return Some(CommandInterceptResult {
+ return vec![CommandInterceptResult {
action,
string,
positions,
- });
+ }];
}
for command in commands(cx).iter() {
@@ -901,14 +1014,14 @@ pub fn command_interceptor(mut input: &str, cx: &App) -> Option<CommandIntercept
}
let positions = generate_positions(&string, &(range_prefix + query));
- return Some(CommandInterceptResult {
+ return vec![CommandInterceptResult {
action,
string,
positions,
- });
+ }];
}
}
- None
+ return Vec::default();
}
fn generate_positions(string: &str, query: &str) -> Vec<usize> {
@@ -982,7 +1095,12 @@ impl OnMatchingLines {
let command: String = chars.collect();
- let action = WrappedAction(command_interceptor(&command, cx)?.action);
+ let action = WrappedAction(
+ command_interceptor(&command, cx)
+ .first()?
+ .action
+ .boxed_clone(),
+ );
Some(Self {
range,