-
-
Notifications
You must be signed in to change notification settings - Fork 6.3k
[RFC] allow external ui to draw cmdline #6162
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
Conversation
Hi @justinmk Can you please revert this PR and I'll re-submit it; messed up the rebase. |
@dzhou121 I believe if you force push into your |
b2bd484
to
132a988
Compare
@andreypopp Thanks. It worked. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tried for fun to set ui->cmdline_external = true;
in tui.c. The line that would be used by command line still shows up. Is it hard to remove since it would appear in remote UIs ? it is used to display errors so I can see why it would require more work.
I believe we should externalize showcmd
along with cmdline
.
EDIT: exciting PR 👍
runtime/doc/msgpack_rpc.txt
Outdated
Enter the cmdline. | ||
|
||
["cmdline_leave"] | ||
Leave the cmdline. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these 2 events needed ? since we already have ["mode_change", mode]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would argue they are not synonymous. If we get mode_change
to support cmdline, it should indicate cmdline regardless of cmdline_external
. However these events are emitted only when cmdline_external
, and the ui thus should draw the cmdline, though they should be called show
and hide
in consistency with popupmenu.
runtime/doc/msgpack_rpc.txt
Outdated
["cmdline_firstc", firstc] | ||
The first character of the command, which could be : / ? etc. With | ||
the firstc, you know wheither it's a command or a search. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't this be deduced from ["cmdline", content, pos] as well ? or given as a parameter of ["cmdline", content, pos,submode={search/reverse-search/...}]
. In fact all the events could be merged into that single one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Merging with show into a single ["cmdline_show", content, pos, firstc, prompt]
makes sense but I still think there should be a separate cmdline_pos
, to easier avoid unnecessary redraws and it would also make the api consistent with popupmenu.
Agreed, but let's wait until later for that. |
The cmdline is also used for displaying :echo :echoerror etc as well. So I think we hide the cmdline only when all elements that need it are external. |
src/nvim/eval.c
Outdated
@@ -12231,19 +12231,23 @@ static void get_user_input(typval_T *argvars, typval_T *rettv, int inputdialog) | |||
if (prompt != NULL) { | |||
/* Only the part of the message after the last NL is considered as | |||
* prompt for the command line */ | |||
p = vim_strrchr(prompt, '\n'); | |||
if (p == NULL) | |||
if (cmdline_get_external()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this logic should be moved to getcmdline_prompt
. It would seem cleaner to have all display logic in ex_getln.c
and not in eval.c
(and remove the need for get_cmdline_external()
)
src/nvim/ex_getln.c
Outdated
} | ||
msg_no_more = false; | ||
} else { | ||
char_u *p; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of modifying the text this could be a separate event ["cmdline_char", char, shift]
, This would allow the gui to change the display by showing it in a different color etc.
src/nvim/ex_getln.c
Outdated
if (cmdline_external) { | ||
Array args = ARRAY_DICT_INIT; | ||
ADD(args, STRING_OBJ(cstr_to_string((char *)(ccline.cmdbuff)))); | ||
ADD(args, INTEGER_OBJ(ccline.cmdpos)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bfredl It looks like interaction with #6364 is going to be pretty straightforward: move color_cmdline
call to the start of the function, stuff the results here. Only need to agree now how these results are transferred.
The only thing which bothers me is coloring of arabic shaping: I have no tests for this (if I am not mistaking, new Vim tests were not ported) and not sure what I need to do in first place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At least to start with I think we can assume one single word (sequence of codepoints being shaped together) of arabic text will have just one color. So at the end of the if arabic_char()
block we could check if the byte index is equal to or greater than the end of the current highlight chunk and then "flush" the arshape_buf so far with msg_outtrans_len_attr
594781b
to
3f8ca52
Compare
runtime/doc/msgpack_rpc.txt
Outdated
["cmdline_leave"] | ||
Leave the cmdline. | ||
|
||
["cmdline_show", content, pos, firstc, prompt] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still, what about making content
be a list of pairs [highlight group, string]
(always one item content=[["Normal", text]]
in this PR because cmdline highlighting PR is not finished yet)?
src/nvim/ex_getln.c
Outdated
ADD(args, ARRAY_OBJ(content)); | ||
ADD(args, INTEGER_OBJ(ccline.cmdpos)); | ||
if (ccline.cmdfirstc != NUL) { | ||
ADD(args, STRING_OBJ(cstr_to_string((char *)(&ccline.cmdfirstc)))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not safe, cmdfirstc is an int
. (it works accidentally on a LE platform as long as firstc is ASCII)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
firstc is only : / ? etc which is defined by nvim. So there's no so many of them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still, it will break on BE. We should avoid tricks like this. If we assue ASCII use buf[0] = (char)ccline.cmdfirstc; buf[1] = NUL
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One could avoid buf with (String) { .size = 1, .data = xmemdupz((char []) { (char)ccline.cmdfirstc }, 1) }
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At that point, wouldn't introducing char_to_string()
be better?
src/nvim/ex_getln.c
Outdated
@@ -2263,20 +2280,56 @@ static void draw_cmdline(int start, int len) | |||
msg_outtrans_len(ccline.cmdbuff + start, len); | |||
} | |||
|
|||
void ui_ext_cmdline_char(int c, int shift) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should probably just be inline
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is short, and not longer than the other branch in the calling function, we should not "hide away" ext ui code specifically, it is as important as the other branch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be helpful for all of the UI widget events to live in ui_ext_<event_name>
functions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? "cmdline_char"
(or ui_event("cmdline_char"
to be even preciser) is already greppable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the pattern "certain classes of code must be in a separate function, even if it is very short and integral with the surrounding code" I have never heard of.
The API functions and foo_event
handlers are somewhat analogous. "Externalized widgets" are special and should be called out. Regularity is really helpful. And given that these functions are part of a class as you noted, formalizing the name under a psuedo-namespace natural.
Most logic isn't trivially assignable to a class or pattern. Why not take the few opportunities that we can?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consistency is good. But "consistency" is not an argument for doing X consistently over doing Y consistently. And we consistently try to not extract short functions that only will be called once and are heavily coupled to their surroundings.
And marshallers are not equivalent to extracting functions. Extracting functions manually adds yet another layer, located in a different place, one must analyse where values can be passed trough or module-globals maybe are used. Marshallers are sematically equal to calling ui_event
directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The converse is probably not.
And this is not true. ui_event
(only used for external stuff, not for TUI) is easy to grep for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't disagree, and I strongly agree about avoiding needless indirection/abstraction. I see this case as an exception, it's a way of labeling an important special class of functionality. I'm not concerned about grepping.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe my point is better illustrated by an example:
/// Undisplay the popup menu (later).
void pum_undisplay(void)
{
pum_is_visible = false;
pum_array = NULL;
if (pum_external) {
Array args = ARRAY_DICT_INIT;
ui_event("popupmenu_hide", args);
} else {
redraw_all_later(SOME_VALID);
redraw_tabline = true;
status_redraw_all();
}
}
The relevant abstraction here is already pum_undisplay()
. If we add yet another layer, the actual ui_event
(or maybe later ui_popupmenu_hide()
auto generated wrapper), will be doubly divorced from the true source of the ui change, the calls to pum_undisplay()
. In both this and the cmdline_putchar
case that will be a very short function one is forced to analyze separately (from the true abstraction point) to make sure it don't have extra conditions etc. On the other side, ui_ext_tabline_show
and cmdline_show
makes sense as there actually are some logic going on. Also, it would make sense to extract the external logic in pum_display
to one function, even if it might result in two different events. To put it bluntly, I don't see how a bad pattern becomes a good pattern just because one uses it consistently.
src/nvim/ex_getln.c
Outdated
} | ||
if (ccline.cmdprompt != NULL) { | ||
ADD(args, STRING_OBJ(cstr_to_string((char *)(ccline.cmdprompt)))); | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not needed, cstr_to_string
is NULL safe.
There is a slight discrepancy, for the grid we don't send hi group names, rather the actual attributes. We can establish the convention to use group names in the extended ui, but then we need to also send updates |
src/nvim/ex_getln.c
Outdated
return command_line_enter(firstc, count, indent); | ||
if (ui_is_external(kUICmdline)) { | ||
Array args = ARRAY_DICT_INIT; | ||
ui_event("cmdline_enter", args); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
won't a cmdline_show
be sent right afterwards anyway? (so this event can be removed).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A separate cmdline_enter
event can be helpful in this scenario:
:<C-R>=1+2<CR><CR>
The events would be cmdline_enter
cmdline_enter
cmdline_leave
cmdline_leave
If we only have cmdline_show, the events will be:
cmdline_show
cmdline_show
cmdline_leave
You wouldn't know when you entered the "sub" cmdline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the ui is really supposed to keep track of the mode stack-machine, it should just be told what to draw right now. When entering the sub cmdline it will see cmdline_show
with the new relevant attributes. When leaving, it will see cmdline_hide
immediately followed by cmdline_show
for the outer command line. In the end it will always see consistent state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can leave the UI to decide whether they want to implement this complexity. What's the drawbacks to enable this new event?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Separation of concerns. We do want richer mode change events, but that's relevant not only for ui, and it doesn't belong in ext_cmdline
. These events should just tell the ui the current cmdline state to draw.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's the limitation of tui that we only draw the current state. But UI is cable to draw all the states. (they can ignore this anyway) I personally would love to see both of the commands at the same time. Isn't it the purpose of this PR to make the cmdline more presentable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, but that can be done in a state-less way by adding a level
field to show
and hide
. If an ui attaches in the middle of cmdline, we do not need to "simulate" enter events for it to get a consistent world view. That will also incidentally work better with cmdline window. Then the first-level cmdline (if it ever was shown) will be hidden (as it is now drawn in a window), and if the user does :
again, only the second-level cmdline will be shown.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(though it should probably be fixed in the TUI also, just print a new msgline, but that's another PR)
src/nvim/ex_getln.c
Outdated
char_u *p = command_line_enter(firstc, count, indent); | ||
if (ui_is_external(kUICmdline)) { | ||
Array args = ARRAY_DICT_INIT; | ||
ui_event("cmdline_leave", args); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cmdline_hide
Please add the ui events to |
67e4984
to
0e68949
Compare
src/nvim/ex_getln.c
Outdated
@@ -2325,7 +2310,7 @@ void putcmdline(int c, int shift) | |||
draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos); | |||
msg_no_more = FALSE; | |||
} else { | |||
ui_ext_cmdline_char(c, shift); | |||
ui_call_cmdline_char(cstr_to_string((char *)(&c)), shift); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cchar_to_string((char)c)
would be better, maybe (add to api/private/helpers.c
)
/// Allocates a String consisting of a single char. Does not support multibyte
/// characters. The resulting string is also NUL-terminated, to facilitate
/// interoperating with code using C strings.
///
/// @param char the char to convert
/// @return the resulting String, if the input char was NUL, an
/// empty String is returned
String cchar_to_string(char c)
{
char buf[] = { c, NUL };
return (String) {
.data = xmemdupz(buf, 1),
.size = (c != NUL) ? 1 : 0
};
}
I still think removing
is both simpler and more correct than |
src/nvim/api/private/helpers.c
Outdated
return (String) { | ||
.data = xmemdupz(buf, 1), | ||
.size = (c != NUL) ? 1 : 0 | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 space indent
src/nvim/ex_getln.c
Outdated
@@ -2706,6 +2754,9 @@ static void cursorcmd(void) | |||
|
|||
void gotocmdline(int clr) | |||
{ | |||
if (ui_is_external(kUICmdline)) { | |||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 space
src/nvim/ex_getln.c
Outdated
ADD(text, STRING_OBJ(cstr_to_string("Normal"))); | ||
ADD(text, STRING_OBJ(cstr_to_string((char *)(ccline.cmdbuff)))); | ||
ADD(content, ARRAY_OBJ(text)); | ||
ui_call_cmdline_show(content, ccline.cmdpos, cchar_to_string((char)ccline.cmdfirstc), cstr_to_string((char *)(ccline.cmdprompt))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 space
src/nvim/api/ui_events.in.h
Outdated
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; | ||
void cmdline_pos(Integer pos, Integer level) | ||
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; | ||
void cmdline_char(String c, Integer shift) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can cmdline_pos and cmdline_char be combined into a general cmdline_update event (which may send other updates as well, in a map-like form)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@justinmk What was the point of this suggestion? UI events are already prefectly extensible by adding new events, and new fields to extisting events. The only effect this has is that ext ui contributors would have to write a lot of dict packing code that the metadata mechanism could have autogenerated for them... The point of #6618 was exactly to move away from ADD("x", ...); ADD("y", ...); ui_event()
style code, so I wonder why we want to cancel the effect of that PR by recreating ui_event()
as an inner platform...
Also, from the ui implementers view, the metadata cmdline_pos(Integer pos, Integer level)
is more or less self-documenting, while cmdline_update(Dictionary)
is positively useless.
Need to check how external cmdline behaves when Ex mode is active (after |
|
accf92c
to
132cde6
Compare
@ZyX-I I've added simple I tried to let |
Maybe not call it "function" but something more generic, I would expect it to be usable also for other multiline constructs. And as more arguments can be added later on, it is fine leaving it without arguments, we can add the full multiline contents with correct indent in a later PR. |
gonvim supports the feature. @dzhou121 |
@dzhou121 Can this display |
@Chillee No it can't. This PR only externalises commands you type in. |
@dzhou121 So are you storing the history manually in gonvim for that screenshot? Or is that related to another API call? Looking forward getting to this getting merged. It'll help a lot with neovim integration in VsCodeVim. |
As a headsup, I'm looking into combining this with colored commandline. |
This enables the feature to allow external ui to draw cmdline, and this is part of PR #5686