-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Command expansion v2 #11164
base: master
Are you sure you want to change the base?
Command expansion v2 #11164
Conversation
92beac3
to
21f1bb9
Compare
726f874
to
c3eff3e
Compare
I think this is good for review (lints should be good now) |
Rather than tying this to Just like Perform all the replacements for the variables first and then evaluate the I am working on #11149 to try to simplify the handling of args, and the way this is currently implemented would require a lot of changes for whichever would get merged second. Mostly with this pr's logic needing a complete rewrite. |
Yup but isn't shellword parsing done at each keypress for completion,... ? Would replacing before parsing using shellwords mean executing %sh at each keypress ? (I mean, each key typed after the %sh{...} |
Yeah, currently this pr touches Unless im missing something, this can be done with interpreting the args as the text input, |
It worked like this in the original PR but shellwords messed up with spaces inside variables expansion (eg: %sh{there are some spaces}) so I added a lil exception inside shellword to ignore spaces (actually, ignore everything) inside of a %{} but the expansion is only done when the command is ran |
Ah, I see. With the changes I am making for the args I changed the pub enum MappableCommand {
Typable {
name: String,
args: String,
doc: String,
},
Static {
name: &'static str,
fun: fn(cx: &mut Context),
doc: &'static str,
},
} Instead of the current pub fn execute(&self, cx: &mut Context) {
match &self {
Self::Typable { name, args, doc: _ } => {
if let Some(command) = typed::TYPABLE_COMMAND_MAP.get(name.as_str()) {
let mut cx = compositor::Context {
editor: cx.editor,
jobs: cx.jobs,
scroll: None,
};
if let Err(err) =
(command.fun)(&mut cx, Args::from(args), PromptEvent::Validate)
{
cx.editor.set_error(format!("{err}"));
}
}
}
Self::Static { fun, .. } => (fun)(cx),
}
} If we were to treat the expanding as a fancy replace, we can just replace the This could then be some rough form of the replacing: fn expand_variables<'a>(editor: &Editor, args: &'a str) -> anyhow::Result<Cow<'a, str>> {
let (view, doc) = current_ref!(editor);
let mut expanded = String::with_capacity(args.len());
let mut var = Tendril::new_const();
let mut chars = args.chars().peekable();
while let Some(c) = chars.next() {
if c == '%' {
if let Some('{') = chars.peek() {
chars.next(); // consume '{'
while let Some(&ch) = chars.peek() {
if ch == '}' {
chars.next(); // consume '}'
break;
}
var.push(ch);
chars.next();
}
match var.as_str() {
"basename" => {
let replacement = doc
.path()
.and_then(|it| it.file_name().and_then(|it| it.to_str()))
.unwrap();
expanded.push_str(replacement);
}
"filename" => {
let replacement = doc
.path()
.and_then(|path| path.parent())
.unwrap()
.to_str()
.unwrap();
expanded.push_str(replacement);
}
"dirname" => {
let replacement = doc
.path()
.and_then(|p| p.parent())
.and_then(std::path::Path::to_str)
.unwrap();
expanded.push_str(replacement);
}
"cwd" => {
let dir = helix_stdx::env::current_working_dir();
let replacement = dir.to_str().unwrap();
expanded.push_str(replacement);
}
"linenumber" => {
let replacement = (doc
.selection(view.id)
.primary()
.cursor_line(doc.text().slice(..))
+ 1)
.to_string();
expanded.push_str(&replacement);
}
"selection" => {
let replacement = doc
.selection(view.id)
.primary()
.fragment(doc.text().slice(..));
expanded.push_str(&replacement);
}
unknown => bail!("unknown variable `{unknown}`"),
}
// Clear for potential further variables to expand.
var.clear();
} else {
expanded.push(c);
}
} else {
expanded.push(c);
}
}
//... `%sh` stuff
Ok(expanded.into())
} to use it like : match expand_variables(cx.editor, args) {
Ok(args) => {
if let Err(err) =
(command.fun)(&mut cx, Args::from(&args), PromptEvent::Validate)
{
cx.editor.set_error(format!("{err}"));
}
}
Err(err) => cx.editor.set_error(format!("{err}")),
}; |
Make sense, it's much clearer this way to me Shall I update this PR to be rebased on yours or shall we directly integrate command expansions inside your refactoring ? |
I'm not sure what changes will be proposed for my |
Don't completions need this ? |
You mean for the |
Nope, I am talking about the code inside the (If the user type And if shellwords is messing up spaces inside of expansions I think it can mess up the completion part. But there is no reason a variable name would contain space like |
Yeah, I believe you can provide a |editor: &Editor, input: &str| {
let shellwords = Shellwords::from(input);
let command = shellwords.command();
if command.is_empty()
|| (shellwords.args().next().is_none() && !shellwords.ends_with_whitespace())
{
fuzzy_match(
input,
TYPABLE_COMMAND_LIST.iter().map(|command| command.name),
false,
)
.into_iter()
.map(|(name, _)| (0.., name.into()))
.collect()
} else {
// Otherwise, use the command's completer and the last shellword
// as completion input.
let (word, len) = shellwords
.args()
.last()
.map_or(("", 0), |last| (last, last.len()));
TYPABLE_COMMAND_MAP
.get(command)
.map(|tc| tc.completer_for_argument_number(argument_number_of(&shellwords)))
.map_or_else(Vec::new, |completer| {
completer(editor, word)
.into_iter()
.map(|(range, file)| {
let file = shellwords::escape(file);
// offset ranges to input
let offset = input.len() - len;
let range = (range.start + offset)..;
(range, file)
})
.collect()
})
}
}, // completion For instance this is how |
And perhaps its actually not desired to parse in a way that respects whitespace? With the If you wrote like let (word, len) = shellwords
.args()
// Special case for `%sh{...}` completions so that final `}` is excluded from matches.
// NOTE: User would have to be aware of how to enter input so that final `}` is not touching
// anything else.
.filter(|arg| *arg != "}")
.last()
.map_or(("", 0), |last| (last, last.len())); |
Is there a way we can increase the pop-up size? I'm attempting to get git log in a large pop-up instead of small one. |
Wouldn't more advanced solutions better suit your usecase ? (like a floating pane in zellij containing it (you can open it from helix) or something like that ?) But it should be possible, but not easy as this PR do not touch the TUI code part. We are using code already written and well integrated into helix. I don't know how hard it can be, and I think it's quite out of the scope of this PR. |
I did a test with and it is returning;
So it didn't evaluate the shell script. |
Yep, atm the PR does not handle completions inside of %sh, but I am waiting an answer from core maintainers about which direction we should follow with @RoloEdits as I don't want to waste my time writing code on this if it's going to be removed and re-written anyway haha |
But you should get the desired behavior using |
This was just an example. What I need is:
I moved back to the previous PR. It was working there. |
I don't think the core maintainers will allow this patch. Such kind of extensions will be covered with the Scheme language paradigm. I'd a similar issue where I wanted the current document's indentation size passed to formatter. Most likely when #10389 is fully ready probably. Though I wish patches like yours are merged along with #11149 |
I like those changes |
I think these are more descriptive:
Reasons:
|
But in all honesty, discussions over these details may actually delay this PR so it won't be in the next release. What about if we merge it as-is, and then make further enhancements down the line if necessary? |
No matter what, this PR won't be included in the next release, as it depends on #11149, which will be merged after the next release. I really like I do think that: filename Is great. But yes I think everyone will have a different opinion and those names could even change after this PR so it's not that important |
Yes, if you look at https://github.com/helix-editor/helix/blob/changelog/CHANGELOG.md it says the first of the year. As not even one maintainer has left any reviews here yet, it wont be included for sure. Any big features will have to wait till next release cycle. And also, naming is hard. To me these can be succinct as the context is already set. These aren't from an unknown scope. Its all around the current buffer. (current buffer) file etc. Im sure this can change later on, but as far as I know, only one other variable is being worked on that is not in some way tied to the current buffer(with them being either a leaf or a stem to the buffer), that being |
I don't think the names should be completely self-descriptive. After all, there's documentation with the full meaning if one's unsure about the meaning of any. And conciseness has value, although I'm not sure how much these substitutions will be used "live" instead of living in a keybinding/command in the config file. |
`patchy` is a tool which makes it easy to declaratively manage personal forks by automatically merging pull requests. Check it out here: https://github.com/NikitaRevenco/patchy
`patchy` is a tool which makes it easy to declaratively manage personal forks by automatically merging pull requests. Check it out here: https://github.com/NikitaRevenco/patchy
`patchy` is a tool which makes it easy to declaratively manage personal forks by automatically merging pull requests. Check it out here: https://github.com/NikitaRevenco/patchy
`patchy` is a tool which makes it easy to declaratively manage personal forks by automatically merging pull requests. Check it out here: https://github.com/NikitaRevenco/patchy
`patchy` is a tool which makes it easy to declaratively manage personal forks by automatically merging pull requests. Check it out here: https://github.com/NikitaRevenco/patchy
#11149 was merged into master, so this can now be rebased off of master. Should be pretty straight forward as the changes were already made. Mainly, for - acc.push_str(&helix_core::shellwords::unescape(separator));
+ acc.push_str(&shellwords::unescape(separator)); And for - .map_or(&self.signature.var_args, |completer| completer)
+ .unwrap_or(&self.signature.var_args) |
`patchy` is a tool which makes it easy to declaratively manage personal forks by automatically merging pull requests. Check it out here: https://github.com/NikitaRevenco/patchy
`patchy` is a tool which makes it easy to declaratively manage personal forks by automatically merging pull requests. Check it out here: https://github.com/NikitaRevenco/patchy
`patchy` is a tool which makes it easy to declaratively manage personal forks by automatically merging pull requests. Check it out here: https://github.com/NikitaRevenco/patchy
Ah, yes, I forgot to say something here: the maintainers felt more and more like they wanted a different API. The new work is being tracked here #12441. Most of what the old work had done was liked, which is the bulk of the work, just wanted some more rules around escaping and more eager approach rather than a lazy one. Hopefully this can get merged soon. Sorry for the trouble on this. |
No problem :) |
Lucky I think the way that this PR is now setup it shouldn't be bad to merge. I will provide suggestions for conflict fixes when the time comes. |
For those already using this PR, I wrote a small utility gh-permalink that copies the github permalink of your current line. |
No way! I wrote the same thing a couple days ago, seems like we all are wanting this PR for similar reasons:
|
Hi !
We talked about command expansions inside of #6979, but I created a new PR as the original PR reached a "stopping" point as the PR became inactive, mainly because the OP is currently busy.
Into this new PR , i rebased #6979 on master, and made some changes as @ksdrar told me to ;) (I think whitespaces are fixed as i handle completions inside of the shellwords parsing phase)
If you think creating a new PR is not a good idea i can of course close this one, but I think it would be great to finally get this feature !
Current variables:
%{basename}
or%{b}
%{dirname}
or%{d}
%{cwd}
%{repo}
cwd
if not inside a VCS repository%{filename}
or%{f}
%{filename:rel}
cwd
(will give absolute path if the file is not a child of the current working directory)%{filename:repo_rel}
repo
(will give absolute path if the file is not a child of the VCS directory or the cwd)%{ext}
%{lang}
%{linenumber}
%{cursorcolumn}
%{selection}
%sh{cmd}
cmd
with the default shell and returns the command output, if any.