Skip to content

[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

Closed
wants to merge 22 commits into from

Conversation

dzhou121
Copy link
Contributor

This enables the feature to allow external ui to draw cmdline, and this is part of PR #5686

@dzhou121 dzhou121 mentioned this pull request Feb 23, 2017
@marvim marvim added the RFC label Feb 23, 2017
@dzhou121
Copy link
Contributor Author

Hi @justinmk Can you please revert this PR and I'll re-submit it; messed up the rebase.

@andreypopp
Copy link
Contributor

@dzhou121 I believe if you force push into your feature/external-cmdline branch — then this PR will be updated.

@dzhou121 dzhou121 force-pushed the feature/external-cmdline branch from b2bd484 to 132a988 Compare February 24, 2017 07:53
@dzhou121
Copy link
Contributor Author

@andreypopp Thanks. It worked.

Copy link
Member

@teto teto left a 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 👍

Enter the cmdline.

["cmdline_leave"]
Leave the cmdline.
Copy link
Member

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]

Copy link
Member

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.

["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.

Copy link
Member

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.

Copy link
Member

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.

@justinmk
Copy link
Member

justinmk commented Mar 1, 2017

I believe we should externalize showcmd along with cmdline.

Agreed, but let's wait until later for that.

@dzhou121
Copy link
Contributor Author

dzhou121 commented Mar 2, 2017

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()) {
Copy link
Member

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() )

}
msg_no_more = false;
} else {
char_u *p;
Copy link
Member

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.

if (cmdline_external) {
Array args = ARRAY_DICT_INIT;
ADD(args, STRING_OBJ(cstr_to_string((char *)(ccline.cmdbuff))));
ADD(args, INTEGER_OBJ(ccline.cmdpos));
Copy link
Contributor

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.

Copy link
Member

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

@dzhou121 dzhou121 force-pushed the feature/external-cmdline branch from 594781b to 3f8ca52 Compare April 28, 2017 05:52
["cmdline_leave"]
Leave the cmdline.

["cmdline_show", content, pos, firstc, prompt]
Copy link
Contributor

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)?

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))));
Copy link
Member

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)

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor

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) }.

Copy link
Member

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?

@@ -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)
Copy link
Member

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Member

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.

Copy link
Member

@justinmk justinmk Apr 28, 2017

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?

Copy link
Member

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.

Copy link
Member

@justinmk justinmk Apr 28, 2017

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?

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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.

}
if (ccline.cmdprompt != NULL) {
ADD(args, STRING_OBJ(cstr_to_string((char *)(ccline.cmdprompt))));
} else {
Copy link
Member

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.

@bfredl
Copy link
Member

bfredl commented Apr 28, 2017

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 highlight_changed( {"Groupname": {attrs}, ...) ) so the ui can get the attributes without blocking calls. Though it is not a urgent problem for this PR as it doesn't apply to Normal (we already send those attrs), but it is something to be aware of when making the decision.

return command_line_enter(firstc, count, indent);
if (ui_is_external(kUICmdline)) {
Array args = ARRAY_DICT_INIT;
ui_event("cmdline_enter", args);
Copy link
Member

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).

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Member

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.

Copy link
Member

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)

char_u *p = command_line_enter(firstc, count, indent);
if (ui_is_external(kUICmdline)) {
Array args = ARRAY_DICT_INIT;
ui_event("cmdline_leave", args);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cmdline_hide

@bfredl
Copy link
Member

bfredl commented May 11, 2017

Please add the ui events to src/nvim/api/ui_events.in.h, following the pattern for popupmenu. Then you could use autogenerated ui_call_cmdline_char(c, shift) et al

@dzhou121 dzhou121 force-pushed the feature/external-cmdline branch from 67e4984 to 0e68949 Compare May 11, 2017 06:12
@@ -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);
Copy link
Member

@bfredl bfredl May 11, 2017

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
    };
}

@bfredl
Copy link
Member

bfredl commented May 11, 2017

I still think removing enter and adding level to show and hide is better. Consider what happens if an UI connects in the middle of the nested cmdline. Either we (1) do nothing, and then it will at least get a consistent state of the nested cmdline or (2) try to bring it up to date, in case

cmdline_show(..., level=1)
cmdline_show(..., level=2)

is both simpler and more correct than cmdline_enter(); cmdline_show(); cmdline_enter(); cmdline_show(). Also it could be just sent to all UIs as it won't mess up any state machine, (and similarly be sent on redraw!)

return (String) {
.data = xmemdupz(buf, 1),
.size = (c != NUL) ? 1 : 0
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 space indent

@@ -2706,6 +2754,9 @@ static void cursorcmd(void)

void gotocmdline(int clr)
{
if (ui_is_external(kUICmdline)) {
return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 space

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)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 space

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)
Copy link
Member

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)

Copy link
Member

@bfredl bfredl May 12, 2017

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.

@ZyX-I
Copy link
Contributor

ZyX-I commented May 11, 2017

Need to check how external cmdline behaves when Ex mode is active (after Q and gQ and typing a number of commands, ends with visual).

@bfredl
Copy link
Member

bfredl commented May 11, 2017

cmdline_update to me seems a purely regression in clarity. What was wrong with what was before? Different events should have different names. cmdline_char is special and should be a separate event, but it could have a better name, like cmdline_char_special or _overlay.

@dzhou121 dzhou121 force-pushed the feature/external-cmdline branch from accf92c to 132cde6 Compare June 26, 2017 10:20
@dzhou121
Copy link
Contributor Author

@ZyX-I I've added simple cmdline_function_show cmdline_function_hide to indicate we are inside/outside ex_function.

I tried to let cmdline_function_show to show the whole function content. The content itself is simple because it's all stored in newlines, but we also need the indent for each line of the content to make the output formatted correctly, and potentially color in the line as well, which would be complex. So I only emit a simple indicator for entering the function and the GUI needs to store the state by itself.

@bfredl
Copy link
Member

bfredl commented Jul 2, 2017

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.

@Shougo
Copy link
Contributor

Shougo commented Jul 3, 2017

gonvim supports the feature.
dzhou121/gonvim#48

@dzhou121
I think the screenshot is useful.

@dzhou121
Copy link
Contributor Author

dzhou121 commented Jul 3, 2017

Screencast for cmdline and wildmenu
cmdline mov

@Chillee
Copy link

Chillee commented Jul 3, 2017

@dzhou121 Can this display :reg for example?

@dzhou121
Copy link
Contributor Author

dzhou121 commented Jul 3, 2017

@Chillee No it can't. This PR only externalises commands you type in.

@Chillee
Copy link

Chillee commented Jul 3, 2017

@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.

@justinmk justinmk added the ui-extensibility UI extensibility, events, protocol, externalized UI label Jul 4, 2017
@bfredl
Copy link
Member

bfredl commented Aug 16, 2017

As a headsup, I'm looking into combining this with colored commandline.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ui-extensibility UI extensibility, events, protocol, externalized UI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants