Fix show_last_command_output not working when the output is stored partially in the scrollback pager history buffer

Fixes #4435
This commit is contained in:
Kovid Goyal 2022-02-15 13:04:02 +05:30
parent 5d120a2f36
commit 80202d2679
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
7 changed files with 66 additions and 18 deletions

View File

@ -86,6 +86,9 @@ Detailed list of changes
- Fix a regression in the previous release that broke :opt:`active_tab_foreground` (:iss:`4620`)
- Fix :ac:`show_last_command_output` not working when the output is stored
partially in the scrollback pager history buffer (:iss:`4435`)
- Improve CWD detection when there are multiple foreground processes in the TTY process group
- A new option :opt:`narrow_symbols` to turn off opportunistic wide rendering of private use codepoints

View File

@ -987,7 +987,7 @@ class HistoryBuf:
def as_text(self, callback: Callable[[str], None], as_ansi: bool, insert_wrap_markers: bool) -> None:
pass
def pagerhist_as_text(self) -> str:
def pagerhist_as_text(self, upto_output_start: bool = False) -> str:
pass
def pagerhist_as_bytes(self) -> bytes:
@ -1137,7 +1137,7 @@ class Screen:
as_text_non_visual = as_text
as_text_alternate = as_text
def cmd_output(self, which: int, callback: Callable[[str], None], as_ansi: bool, insert_wrap_markers: bool) -> None:
def cmd_output(self, which: int, callback: Callable[[str], None], as_ansi: bool, insert_wrap_markers: bool) -> bool:
pass
def scroll_until_cursor_prompt(self) -> None:

View File

@ -418,8 +418,21 @@ pagerhist_write(HistoryBuf *self, PyObject *what) {
Py_RETURN_NONE;
}
static const uint8_t*
reverse_find(const uint8_t *haystack, size_t haystack_sz, const uint8_t *needle) {
const size_t needle_sz = strlen((const char*)needle);
if (!needle_sz || needle_sz > haystack_sz) return NULL;
const uint8_t *p = haystack + haystack_sz - (needle_sz - 1);
while (--p >= haystack) {
if (*p == needle[0] && memcmp(p, needle, MIN(needle_sz, haystack_sz - (p - haystack))) == 0) return p;
}
return NULL;
}
static PyObject*
pagerhist_as_bytes(HistoryBuf *self, PyObject *args UNUSED) {
pagerhist_as_bytes(HistoryBuf *self, PyObject *args) {
int upto_output_start = 0;
if (!PyArg_ParseTuple(args, "|p", &upto_output_start)) return NULL;
#define ph self->pagerhist
if (!ph || !ringbuf_bytes_used(ph->ringbuf)) return PyBytes_FromStringAndSize("", 0);
pagerhist_ensure_start_is_valid_utf8(ph);
@ -433,6 +446,13 @@ pagerhist_as_bytes(HistoryBuf *self, PyObject *args UNUSED) {
uint8_t *buf = (uint8_t*)PyBytes_AS_STRING(ans);
ringbuf_memcpy_from(buf, ph->ringbuf, sz);
if (!l.attrs.continued) buf[sz-1] = '\n';
if (upto_output_start) {
const uint8_t *p = reverse_find(buf, sz, (const uint8_t*)"\x1b]133;C\x1b\\");
if (p) {
PyObject *t = PyBytes_FromStringAndSize((const char*)p, sz - (p - buf));
Py_DECREF(ans); ans = t;
}
}
return ans;
#undef ph
}
@ -501,8 +521,8 @@ static PyMethodDef methods[] = {
METHOD(as_ansi, METH_O)
METHODB(pagerhist_write, METH_O),
METHODB(pagerhist_rewrap, METH_O),
METHODB(pagerhist_as_text, METH_NOARGS),
METHODB(pagerhist_as_bytes, METH_NOARGS),
METHODB(pagerhist_as_text, METH_VARARGS),
METHODB(pagerhist_as_bytes, METH_VARARGS),
METHODB(as_text, METH_VARARGS),
METHOD(dirty_lines, METH_NOARGS)
METHOD(push, METH_VARARGS)

View File

@ -326,7 +326,7 @@ Separate scrollback history size, used only for browsing the scrollback buffer
(in MB). This separate buffer is not available for interactive scrolling but
will be piped to the pager program when viewing scrollback buffer in a separate
window. The current implementation stores the data in UTF-8, so approximatively
10000 lines per megabyte at 100 chars per line, for pure ASCII text, unformatted
10000 lines per megabyte at 100 chars per line, for pure ASCII, unformatted
text. A value of zero or less disables this feature. The maximum allowed size is
4GB. Note that on config reload if this
is changed it will only affect newly created windows, not existing ones.

View File

@ -2736,6 +2736,7 @@ typedef struct OutputOffset {
Screen *screen;
int start;
unsigned num_lines;
bool reached_upper_limit;
} OutputOffset;
static Line*
@ -2788,7 +2789,10 @@ find_cmd_output(Screen *self, OutputOffset *oo, index_type start_screen_y, unsig
}
y1--;
}
if (y1 < upward_limit) start = upward_limit;
if (y1 < upward_limit) {
oo->reached_upper_limit = true;
start = upward_limit;
}
found_output = true; found_prompt = true;
}
@ -2848,8 +2852,12 @@ cmd_output(Screen *self, PyObject *args) {
PyErr_Format(PyExc_KeyError, "%u is not a valid type of command", which);
return NULL;
}
if (found) return as_text_generic(as_text_args, &oo, get_line_from_offset, oo.num_lines, &self->as_ansi_buf);
Py_RETURN_NONE;
if (found) {
DECREF_AFTER_FUNCTION PyObject *ret = as_text_generic(as_text_args, &oo, get_line_from_offset, oo.num_lines, &self->as_ansi_buf);
if (!ret) return NULL;
}
if (oo.reached_upper_limit && self->linebuf == self->main_linebuf && OPT(scrollback_pager_history_size) > 0) Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
bool

View File

@ -157,6 +157,14 @@ def call_watchers(windowref: Callable[[], Optional['Window']], which: str, data:
add_timer(callback, 0, False)
def pagerhist(screen: Screen, as_ansi: bool = False, add_wrap_markers: bool = True, upto_output_start: bool = False) -> str:
pht = screen.historybuf.pagerhist_as_text(upto_output_start)
if pht and (not as_ansi or not add_wrap_markers):
sanitizer = text_sanitizer(as_ansi, add_wrap_markers)
pht = sanitizer(pht)
return pht
def as_text(
screen: Screen,
as_ansi: bool = False,
@ -186,13 +194,8 @@ def as_text(
ctext += f'\x1b[{code} q'
if add_history:
h: List[str] = []
pht = screen.historybuf.pagerhist_as_text()
if pht:
h.append(pht)
if h and (not as_ansi or not add_wrap_markers):
sanitizer = text_sanitizer(as_ansi, add_wrap_markers)
h = list(map(sanitizer, h))
pht = pagerhist(screen, as_ansi, add_wrap_markers)
h: List[str] = [pht] if pht else []
screen.historybuf.as_text(h.append, as_ansi, add_wrap_markers)
if h:
if not screen.linebuf.is_continued(0):
@ -1064,7 +1067,11 @@ class Window:
def cmd_output(self, which: CommandOutput = CommandOutput.last_run, as_ansi: bool = False, add_wrap_markers: bool = False) -> str:
lines: List[str] = []
self.screen.cmd_output(which, lines.append, as_ansi, add_wrap_markers)
search_in_pager_hist = self.screen.cmd_output(which, lines.append, as_ansi, add_wrap_markers)
if search_in_pager_hist:
pht = pagerhist(self.screen, as_ansi, add_wrap_markers, True)
if pht:
lines.insert(0, pht)
return ''.join(lines)
@property

View File

@ -5,6 +5,7 @@ from kitty.fast_data_types import (
DECAWM, DECCOLM, DECOM, IRM, Cursor, parse_bytes
)
from kitty.marks import marker_from_function, marker_from_regex
from kitty.window import pagerhist
from . import BaseTest
@ -998,7 +999,10 @@ class TestScreen(BaseTest):
def lco(as_ansi=False):
a = []
s.cmd_output(0, a.append, as_ansi)
if s.cmd_output(0, a.append, as_ansi):
pht = pagerhist(s, as_ansi=as_ansi, upto_output_start=True)
if pht:
a.insert(0, pht)
return ''.join(a)
def fco():
@ -1076,3 +1080,9 @@ class TestScreen(BaseTest):
self.ae(lvco(), '0\n1\n2')
s.scroll_to_prompt(1)
self.ae(lvco(), '0x\n1x')
# last command output from pager history
s = self.create_screen()
draw_prompt('p1')
draw_output(30)
self.ae(tuple(map(int, lco()[len('\x1b]133;C\x1b\\'):].split())), tuple(range(0, 30)))