vim: :set support (#24209)
Closes #21147 Release Notes: - vim: First version of `:set` with support for `[no]wrap`, `[no]number`, `[no]relativenumber` --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
2e7bb11b7d
commit
37785a54d5
3 changed files with 152 additions and 31 deletions
|
@ -198,26 +198,29 @@ impl CommandPaletteDelegate {
|
||||||
) {
|
) {
|
||||||
self.updating_matches.take();
|
self.updating_matches.take();
|
||||||
|
|
||||||
let mut intercept_result = CommandPaletteInterceptor::try_global(cx)
|
let mut intercept_results = CommandPaletteInterceptor::try_global(cx)
|
||||||
.and_then(|interceptor| interceptor.intercept(&query, cx));
|
.map(|interceptor| interceptor.intercept(&query, cx))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
if parse_zed_link(&query, cx).is_some() {
|
if parse_zed_link(&query, cx).is_some() {
|
||||||
intercept_result = Some(CommandInterceptResult {
|
intercept_results = vec![CommandInterceptResult {
|
||||||
action: OpenZedUrl { url: query.clone() }.boxed_clone(),
|
action: OpenZedUrl { url: query.clone() }.boxed_clone(),
|
||||||
string: query.clone(),
|
string: query.clone(),
|
||||||
positions: vec![],
|
positions: vec![],
|
||||||
})
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(CommandInterceptResult {
|
let mut new_matches = Vec::new();
|
||||||
|
|
||||||
|
for CommandInterceptResult {
|
||||||
action,
|
action,
|
||||||
string,
|
string,
|
||||||
positions,
|
positions,
|
||||||
}) = intercept_result
|
} in intercept_results
|
||||||
{
|
{
|
||||||
if let Some(idx) = matches
|
if let Some(idx) = matches
|
||||||
.iter()
|
.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);
|
matches.remove(idx);
|
||||||
}
|
}
|
||||||
|
@ -225,18 +228,16 @@ impl CommandPaletteDelegate {
|
||||||
name: string.clone(),
|
name: string.clone(),
|
||||||
action,
|
action,
|
||||||
});
|
});
|
||||||
matches.insert(
|
new_matches.push(StringMatch {
|
||||||
0,
|
candidate_id: commands.len() - 1,
|
||||||
StringMatch {
|
string,
|
||||||
candidate_id: commands.len() - 1,
|
positions,
|
||||||
string,
|
score: 0.0,
|
||||||
positions,
|
})
|
||||||
score: 0.0,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
new_matches.append(&mut matches);
|
||||||
self.commands = commands;
|
self.commands = commands;
|
||||||
self.matches = matches;
|
self.matches = new_matches;
|
||||||
if self.matches.is_empty() {
|
if self.matches.is_empty() {
|
||||||
self.selected_ix = 0;
|
self.selected_ix = 0;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -108,7 +108,7 @@ pub struct CommandInterceptResult {
|
||||||
/// An interceptor for the command palette.
|
/// An interceptor for the command palette.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct CommandPaletteInterceptor(
|
pub struct CommandPaletteInterceptor(
|
||||||
Option<Box<dyn Fn(&str, &App) -> Option<CommandInterceptResult>>>,
|
Option<Box<dyn Fn(&str, &App) -> Vec<CommandInterceptResult>>>,
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -132,10 +132,12 @@ impl CommandPaletteInterceptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Intercepts the given query from the command palette.
|
/// Intercepts the given query from the command palette.
|
||||||
pub fn intercept(&self, query: &str, cx: &App) -> Option<CommandInterceptResult> {
|
pub fn intercept(&self, query: &str, cx: &App) -> Vec<CommandInterceptResult> {
|
||||||
let handler = self.0.as_ref()?;
|
if let Some(handler) = self.0.as_ref() {
|
||||||
|
(handler)(query, cx)
|
||||||
(handler)(query, cx)
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears the global interceptor.
|
/// Clears the global interceptor.
|
||||||
|
@ -146,7 +148,7 @@ impl CommandPaletteInterceptor {
|
||||||
/// Sets the global interceptor.
|
/// Sets the global interceptor.
|
||||||
///
|
///
|
||||||
/// This will override the previous interceptor, if it exists.
|
/// 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);
|
self.0 = Some(handler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ use editor::{
|
||||||
Bias, Editor, ToPoint,
|
Bias, Editor, ToPoint,
|
||||||
};
|
};
|
||||||
use gpui::{actions, impl_internal_actions, Action, App, Context, Global, Window};
|
use gpui::{actions, impl_internal_actions, Action, App, Context, Global, Window};
|
||||||
|
use itertools::Itertools;
|
||||||
use language::Point;
|
use language::Point;
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
@ -64,6 +65,95 @@ pub struct WithCount {
|
||||||
action: WrappedAction,
|
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)]
|
#[derive(Debug)]
|
||||||
struct WrappedAction(Box<dyn Action>);
|
struct WrappedAction(Box<dyn Action>);
|
||||||
|
|
||||||
|
@ -76,7 +166,8 @@ impl_internal_actions!(
|
||||||
WithRange,
|
WithRange,
|
||||||
WithCount,
|
WithCount,
|
||||||
OnMatchingLines,
|
OnMatchingLines,
|
||||||
ShellExec
|
ShellExec,
|
||||||
|
VimSet,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -100,6 +191,26 @@ impl Deref for WrappedAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
|
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| {
|
Vim::action(editor, cx, |vim, _: &VisualCommand, window, cx| {
|
||||||
let Some(workspace) = vim.workspace(window) else {
|
let Some(workspace) = vim.workspace(window) else {
|
||||||
return;
|
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
|
// NOTE: We also need to support passing arguments to commands like :w
|
||||||
// (ideally with filename autocompletion).
|
// (ideally with filename autocompletion).
|
||||||
while input.starts_with(':') {
|
while input.starts_with(':') {
|
||||||
|
@ -834,6 +945,8 @@ pub fn command_interceptor(mut input: &str, cx: &App) -> Option<CommandIntercept
|
||||||
}
|
}
|
||||||
.boxed_clone(),
|
.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') {
|
} else if query.starts_with('s') {
|
||||||
let mut substitute = "substitute".chars().peekable();
|
let mut substitute = "substitute".chars().peekable();
|
||||||
let mut query = query.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 {
|
if let Some(action) = action {
|
||||||
let string = input.to_string();
|
let string = input.to_string();
|
||||||
let positions = generate_positions(&string, &(range_prefix + query));
|
let positions = generate_positions(&string, &(range_prefix + query));
|
||||||
return Some(CommandInterceptResult {
|
return vec![CommandInterceptResult {
|
||||||
action,
|
action,
|
||||||
string,
|
string,
|
||||||
positions,
|
positions,
|
||||||
});
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
for command in commands(cx).iter() {
|
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));
|
let positions = generate_positions(&string, &(range_prefix + query));
|
||||||
|
|
||||||
return Some(CommandInterceptResult {
|
return vec![CommandInterceptResult {
|
||||||
action,
|
action,
|
||||||
string,
|
string,
|
||||||
positions,
|
positions,
|
||||||
});
|
}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
return Vec::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_positions(string: &str, query: &str) -> Vec<usize> {
|
fn generate_positions(string: &str, query: &str) -> Vec<usize> {
|
||||||
|
@ -982,7 +1095,12 @@ impl OnMatchingLines {
|
||||||
|
|
||||||
let command: String = chars.collect();
|
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 {
|
Some(Self {
|
||||||
range,
|
range,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue