More sophisticated OSC 133 parsing with support for secondary prompts

This commit is contained in:
Kovid Goyal 2021-10-22 11:54:54 +05:30
parent 9b957a1fdb
commit 729cea88f3
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
6 changed files with 72 additions and 49 deletions

View file

@ -148,12 +148,16 @@ Notes for shell developers
The protocol used for marking the prompt is very simple. You should consider
adding it to your shell as a builtin. Many modern terminals make use of it, for
example: kitty, iTerm2, WezTerm
example: kitty, iTerm2, WezTerm, DomTerm
Just before starting to draw the prompt send the escape code::
Just before starting to draw the PS1 prompt send the escape code::
<OSC>133;A<ST>
Just before starting to draw the PS2 prompt send the escape code::
<OSC>133;A;k=s<ST>
Just before running a command/program, send the escape code::
<OSC>133;C<ST>

View file

@ -164,12 +164,12 @@ typedef struct {
hyperlink_id_type hyperlink_id;
} CPUCell;
typedef enum { UNKNOWN_PROMPT_KIND = 0, PROMPT_START = 1, SECONDARY_PROMPT = 2, OUTPUT_START = 3 } PromptKind;
typedef union LineAttrs {
struct {
uint8_t continued : 1;
uint8_t has_dirty_text : 1;
uint8_t is_prompt_start : 1;
uint8_t is_output_start : 1;
PromptKind prompt_kind : 2;
};
uint8_t val;
} LineAttrs ;

View file

@ -57,20 +57,6 @@ linebuf_mark_line_as_not_continued(LineBuf *self, index_type y) {
self->line_attrs[y].continued = false;
}
void
linebuf_mark_line_as_prompt_start(LineBuf *self, index_type y) {
self->line_attrs[y].is_prompt_start = true;
self->line_attrs[y].is_output_start = false;
}
void
linebuf_mark_line_as_output_start(LineBuf *self, index_type y) {
self->line_attrs[y].is_prompt_start = false;
self->line_attrs[y].is_output_start = true;
}
static PyObject*
clear(LineBuf *self, PyObject *a UNUSED) {
#define clear_doc "Clear all lines in this LineBuf"

View file

@ -105,8 +105,6 @@ void linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *, index_type *, H
void linebuf_mark_line_dirty(LineBuf *self, index_type y);
void linebuf_mark_line_clean(LineBuf *self, index_type y);
void linebuf_mark_line_as_not_continued(LineBuf *self, index_type y);
void linebuf_mark_line_as_prompt_start(LineBuf *self, index_type y);
void linebuf_mark_line_as_output_start(LineBuf *self, index_type y);
unsigned int linebuf_char_width_at(LineBuf *self, index_type x, index_type y);
void linebuf_refresh_sprite_positions(LineBuf *self);
void historybuf_add_line(HistoryBuf *self, const Line *line, ANSIBuf*);

View file

@ -157,7 +157,7 @@ screen_reset(Screen *self) {
memset(self->main_key_encoding_flags, 0, sizeof(self->main_key_encoding_flags));
memset(self->alt_key_encoding_flags, 0, sizeof(self->alt_key_encoding_flags));
self->display_window_number = 0;
self->prompt_redraw_settings.val = 0;
self->prompt_settings.val = 0;
self->last_graphic_char = 0;
self->main_savepoint.is_valid = false;
self->alt_savepoint.is_valid = false;
@ -284,17 +284,25 @@ index_selection(const Screen *self, Selections *selections, bool up) {
static void
prevent_current_prompt_from_rewrapping(Screen *self) {
if (!self->prompt_redraw_settings.redraws_prompts_at_all) return;
if (!self->prompt_settings.redraws_prompts_at_all) return;
int y = self->cursor->y;
while (y >= 0) {
linebuf_init_line(self->main_linebuf, y);
Line *line = self->linebuf->line;
if (line->attrs.is_output_start) return;
if (line->attrs.is_prompt_start) break;
switch (line->attrs.prompt_kind) {
case UNKNOWN_PROMPT_KIND:
break;
case PROMPT_START:
case SECONDARY_PROMPT:
goto found;
break;
case OUTPUT_START:
return;
}
y--;
}
found:
if (y < 0) return;
if (!self->prompt_redraw_settings.redraws_multiline_prompts && self->cursor->y > (unsigned) y) return; // bash does not redraw multiline prompts
// we have identified a prompt at which the cursor is present, the shell
// will redraw this prompt. However when doing so it gets confused if the
// cursor vertical position relative to the first prompt line changes. This
@ -322,7 +330,7 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) {
bool is_main = self->linebuf == self->main_linebuf;
index_type num_content_lines_before, num_content_lines_after;
bool dummy_output_inserted = false;
if (is_main && self->cursor->x == 0 && self->cursor->y < self->lines && self->linebuf->line_attrs[self->cursor->y].is_output_start) {
if (is_main && self->cursor->x == 0 && self->cursor->y < self->lines && self->linebuf->line_attrs[self->cursor->y].prompt_kind == OUTPUT_START) {
linebuf_init_line(self->linebuf, self->cursor->y);
if (!self->linebuf->line->cpu_cells[0].ch) {
// we have a blank output start line, we need it to be preserved by
@ -1447,8 +1455,15 @@ screen_cursor_at_a_shell_prompt(const Screen *self) {
if (self->cursor->y >= self->lines || self->linebuf != self->main_linebuf || !screen_is_cursor_visible(self)) return -1;
for (index_type y=self->cursor->y + 1; y-- > 0; ) {
linebuf_init_line(self->linebuf, y);
if (self->linebuf->line->attrs.is_output_start) return -1;
if (self->linebuf->line->attrs.is_prompt_start) return y;
switch(self->linebuf->line->attrs.prompt_kind) {
case OUTPUT_START:
return -1;
case PROMPT_START:
case SECONDARY_PROMPT:
return y;
case UNKNOWN_PROMPT_KIND:
break;
}
}
return -1;
}
@ -1909,6 +1924,15 @@ file_transmission(Screen *self, PyObject *data) {
CALLBACK("file_transmission", "O", data);
}
static void
parse_prompt_mark(Screen *self, PyObject *parts, PromptKind *pk) {
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(parts); i++) {
PyObject *token = PyList_GET_ITEM(parts, i);
if (PyUnicode_CompareWithASCIIString(token, "k=s")) *pk = SECONDARY_PROMPT;
else if (PyUnicode_CompareWithASCIIString(token, "redraw=0")) self->prompt_settings.redraws_prompts_at_all = 0;
}
}
void
shell_prompt_marking(Screen *self, PyObject *data) {
if (PyUnicode_READY(data) != 0) { PyErr_Clear(); return; }
@ -1916,20 +1940,21 @@ shell_prompt_marking(Screen *self, PyObject *data) {
Py_UCS4 ch = PyUnicode_READ_CHAR(data, 0);
switch (ch) {
case 'A': {
linebuf_mark_line_as_prompt_start(self->linebuf, self->cursor->y);
size_t l = PyUnicode_GET_LENGTH(data);
self->prompt_redraw_settings.redraws_multiline_prompts = 1;
self->prompt_redraw_settings.redraws_prompts_at_all = 1;
if (l > 8) {
const char *buf = PyUnicode_AsUTF8(data);
if (buf) {
if (strstr(buf, ";does_not_redraw_multiline_prompt")) self->prompt_redraw_settings.redraws_multiline_prompts = 0;
if (strstr(buf, ";does_not_redraw_prompts")) self->prompt_redraw_settings.redraws_prompts_at_all = 0;
PromptKind pk = PROMPT_START;
self->prompt_settings.redraws_prompts_at_all = 1;
if (PyUnicode_FindChar(data, ';', 0, PyUnicode_GET_LENGTH(data), 1)) {
DECREF_AFTER_FUNCTION PyObject *sep = PyUnicode_FromString(";");
if (sep) {
DECREF_AFTER_FUNCTION PyObject *parts = PyUnicode_Split(data, sep, -1);
if (parts) parse_prompt_mark(self, parts, &pk);
}
}
if (PyErr_Occurred()) PyErr_Print();
self->linebuf->line_attrs[self->cursor->y].prompt_kind = pk;
} break;
case 'C':
linebuf_mark_line_as_output_start(self->linebuf, self->cursor->y); break;
self->linebuf->line_attrs[self->cursor->y].prompt_kind = OUTPUT_START;
break;
}
}
if (global_state.debug_rendering) {
@ -1946,16 +1971,16 @@ screen_history_scroll_to_prompt(Screen *self, int num_of_prompts_to_jump) {
num_of_prompts_to_jump = num_of_prompts_to_jump < 0 ? -num_of_prompts_to_jump : num_of_prompts_to_jump;
int y = -self->scrolled_by;
#define ensure_y_ok if (y >= (int)self->lines || -y > (int)self->historybuf->count) return false;
#define move_y_to_start_of_promt while (-y + 1 <= (int)self->historybuf->count && range_line_(self, y - 1)->attrs.is_prompt_start) y--;
#define move_y_to_end_of_promt while (y + 1 < (int)self->lines && range_line_(self, y + 1)->attrs.is_prompt_start) y++;
#define move_y_to_start_of_promt while (-y + 1 <= (int)self->historybuf->count && range_line_(self, y - 1)->attrs.prompt_kind == PROMPT_START) y--;
#define move_y_to_end_of_promt while (y + 1 < (int)self->lines && range_line_(self, y + 1)->attrs.prompt_kind == PROMPT_START) y++;
ensure_y_ok;
if (range_line_(self, y)->attrs.is_prompt_start) {
if (range_line_(self, y)->attrs.prompt_kind == PROMPT_START) {
if (delta < 0) { move_y_to_start_of_promt; } else { move_y_to_end_of_promt; }
}
while (num_of_prompts_to_jump) {
y += delta;
ensure_y_ok;
if (range_line_(self, y)->attrs.is_prompt_start) {
if (range_line_(self, y)->attrs.prompt_kind == PROMPT_START) {
num_of_prompts_to_jump--;
if (delta < 0) { move_y_to_start_of_promt; } else { move_y_to_end_of_promt; }
}
@ -2583,8 +2608,8 @@ last_cmd_output(Screen *self, PyObject *args) {
const int limit = -self->historybuf->count;
while (y >= limit) {
Line *line = range_line_(self, y);
if (line->attrs.is_prompt_start) prompt_pos = y;
if (line->attrs.is_output_start) {
if (line->attrs.prompt_kind == PROMPT_START) prompt_pos = y;
if (line->attrs.prompt_kind == OUTPUT_START) {
oo.start = y;
num_lines = prompt_pos - y;
break;
@ -2599,7 +2624,7 @@ last_cmd_output(Screen *self, PyObject *args) {
// so find the first one
while (oo.start > limit) {
Line *line = range_line_(self, oo.start - 1);
if (!line->attrs.is_output_start) break;
if (line->attrs.prompt_kind != OUTPUT_START) break;
oo.start--; num_lines++;
}
}
@ -3519,8 +3544,19 @@ dump_lines_with_attrs(Screen *self, PyObject *accum) {
PyObject_CallFunctionObjArgs(accum, t);
Py_DECREF(t);
}
if (line->attrs.is_prompt_start) PyObject_CallFunction(accum, "s", "\x1b[32mprompt \x1b[39m");
if (line->attrs.is_output_start) PyObject_CallFunction(accum, "s", "\x1b[33moutput \x1b[39m");
switch (line->attrs.prompt_kind) {
case UNKNOWN_PROMPT_KIND:
break;
case PROMPT_START:
PyObject_CallFunction(accum, "s", "\x1b[32mprompt \x1b[39m");
break;
case SECONDARY_PROMPT:
PyObject_CallFunction(accum, "s", "\x1b[32msecondary_prompt \x1b[39m");
break;
case OUTPUT_START:
PyObject_CallFunction(accum, "s", "\x1b[33moutput \x1b[39m");
break;
}
if (line->attrs.continued) PyObject_CallFunction(accum, "s", "continued ");
if (line->attrs.has_dirty_text) PyObject_CallFunction(accum, "s", "dirty ");
PyObject_CallFunction(accum, "s", "\n");

View file

@ -133,11 +133,10 @@ typedef struct {
} ignore_bells;
union {
struct {
unsigned int redraws_multiline_prompts: 1;
unsigned int redraws_prompts_at_all: 1;
};
unsigned int val;
} prompt_redraw_settings;
} prompt_settings;
unsigned int display_window_number;
} Screen;