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:
parent
5d120a2f36
commit
80202d2679
@ -86,6 +86,9 @@ Detailed list of changes
|
|||||||
|
|
||||||
- Fix a regression in the previous release that broke :opt:`active_tab_foreground` (:iss:`4620`)
|
- 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
|
- 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
|
- A new option :opt:`narrow_symbols` to turn off opportunistic wide rendering of private use codepoints
|
||||||
|
|||||||
@ -987,7 +987,7 @@ class HistoryBuf:
|
|||||||
def as_text(self, callback: Callable[[str], None], as_ansi: bool, insert_wrap_markers: bool) -> None:
|
def as_text(self, callback: Callable[[str], None], as_ansi: bool, insert_wrap_markers: bool) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def pagerhist_as_text(self) -> str:
|
def pagerhist_as_text(self, upto_output_start: bool = False) -> str:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def pagerhist_as_bytes(self) -> bytes:
|
def pagerhist_as_bytes(self) -> bytes:
|
||||||
@ -1137,7 +1137,7 @@ class Screen:
|
|||||||
as_text_non_visual = as_text
|
as_text_non_visual = as_text
|
||||||
as_text_alternate = 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
|
pass
|
||||||
|
|
||||||
def scroll_until_cursor_prompt(self) -> None:
|
def scroll_until_cursor_prompt(self) -> None:
|
||||||
|
|||||||
@ -418,8 +418,21 @@ pagerhist_write(HistoryBuf *self, PyObject *what) {
|
|||||||
Py_RETURN_NONE;
|
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*
|
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
|
#define ph self->pagerhist
|
||||||
if (!ph || !ringbuf_bytes_used(ph->ringbuf)) return PyBytes_FromStringAndSize("", 0);
|
if (!ph || !ringbuf_bytes_used(ph->ringbuf)) return PyBytes_FromStringAndSize("", 0);
|
||||||
pagerhist_ensure_start_is_valid_utf8(ph);
|
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);
|
uint8_t *buf = (uint8_t*)PyBytes_AS_STRING(ans);
|
||||||
ringbuf_memcpy_from(buf, ph->ringbuf, sz);
|
ringbuf_memcpy_from(buf, ph->ringbuf, sz);
|
||||||
if (!l.attrs.continued) buf[sz-1] = '\n';
|
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;
|
return ans;
|
||||||
#undef ph
|
#undef ph
|
||||||
}
|
}
|
||||||
@ -501,8 +521,8 @@ static PyMethodDef methods[] = {
|
|||||||
METHOD(as_ansi, METH_O)
|
METHOD(as_ansi, METH_O)
|
||||||
METHODB(pagerhist_write, METH_O),
|
METHODB(pagerhist_write, METH_O),
|
||||||
METHODB(pagerhist_rewrap, METH_O),
|
METHODB(pagerhist_rewrap, METH_O),
|
||||||
METHODB(pagerhist_as_text, METH_NOARGS),
|
METHODB(pagerhist_as_text, METH_VARARGS),
|
||||||
METHODB(pagerhist_as_bytes, METH_NOARGS),
|
METHODB(pagerhist_as_bytes, METH_VARARGS),
|
||||||
METHODB(as_text, METH_VARARGS),
|
METHODB(as_text, METH_VARARGS),
|
||||||
METHOD(dirty_lines, METH_NOARGS)
|
METHOD(dirty_lines, METH_NOARGS)
|
||||||
METHOD(push, METH_VARARGS)
|
METHOD(push, METH_VARARGS)
|
||||||
|
|||||||
@ -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
|
(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
|
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
|
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
|
text. A value of zero or less disables this feature. The maximum allowed size is
|
||||||
4GB. Note that on config reload if this
|
4GB. Note that on config reload if this
|
||||||
is changed it will only affect newly created windows, not existing ones.
|
is changed it will only affect newly created windows, not existing ones.
|
||||||
|
|||||||
@ -2736,6 +2736,7 @@ typedef struct OutputOffset {
|
|||||||
Screen *screen;
|
Screen *screen;
|
||||||
int start;
|
int start;
|
||||||
unsigned num_lines;
|
unsigned num_lines;
|
||||||
|
bool reached_upper_limit;
|
||||||
} OutputOffset;
|
} OutputOffset;
|
||||||
|
|
||||||
static Line*
|
static Line*
|
||||||
@ -2788,7 +2789,10 @@ find_cmd_output(Screen *self, OutputOffset *oo, index_type start_screen_y, unsig
|
|||||||
}
|
}
|
||||||
y1--;
|
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;
|
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);
|
PyErr_Format(PyExc_KeyError, "%u is not a valid type of command", which);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (found) return as_text_generic(as_text_args, &oo, get_line_from_offset, oo.num_lines, &self->as_ansi_buf);
|
if (found) {
|
||||||
Py_RETURN_NONE;
|
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
|
bool
|
||||||
|
|||||||
@ -157,6 +157,14 @@ def call_watchers(windowref: Callable[[], Optional['Window']], which: str, data:
|
|||||||
add_timer(callback, 0, False)
|
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(
|
def as_text(
|
||||||
screen: Screen,
|
screen: Screen,
|
||||||
as_ansi: bool = False,
|
as_ansi: bool = False,
|
||||||
@ -186,13 +194,8 @@ def as_text(
|
|||||||
ctext += f'\x1b[{code} q'
|
ctext += f'\x1b[{code} q'
|
||||||
|
|
||||||
if add_history:
|
if add_history:
|
||||||
h: List[str] = []
|
pht = pagerhist(screen, as_ansi, add_wrap_markers)
|
||||||
pht = screen.historybuf.pagerhist_as_text()
|
h: List[str] = [pht] if pht else []
|
||||||
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))
|
|
||||||
screen.historybuf.as_text(h.append, as_ansi, add_wrap_markers)
|
screen.historybuf.as_text(h.append, as_ansi, add_wrap_markers)
|
||||||
if h:
|
if h:
|
||||||
if not screen.linebuf.is_continued(0):
|
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:
|
def cmd_output(self, which: CommandOutput = CommandOutput.last_run, as_ansi: bool = False, add_wrap_markers: bool = False) -> str:
|
||||||
lines: List[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)
|
return ''.join(lines)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from kitty.fast_data_types import (
|
|||||||
DECAWM, DECCOLM, DECOM, IRM, Cursor, parse_bytes
|
DECAWM, DECCOLM, DECOM, IRM, Cursor, parse_bytes
|
||||||
)
|
)
|
||||||
from kitty.marks import marker_from_function, marker_from_regex
|
from kitty.marks import marker_from_function, marker_from_regex
|
||||||
|
from kitty.window import pagerhist
|
||||||
|
|
||||||
from . import BaseTest
|
from . import BaseTest
|
||||||
|
|
||||||
@ -998,7 +999,10 @@ class TestScreen(BaseTest):
|
|||||||
|
|
||||||
def lco(as_ansi=False):
|
def lco(as_ansi=False):
|
||||||
a = []
|
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)
|
return ''.join(a)
|
||||||
|
|
||||||
def fco():
|
def fco():
|
||||||
@ -1076,3 +1080,9 @@ class TestScreen(BaseTest):
|
|||||||
self.ae(lvco(), '0\n1\n2')
|
self.ae(lvco(), '0\n1\n2')
|
||||||
s.scroll_to_prompt(1)
|
s.scroll_to_prompt(1)
|
||||||
self.ae(lvco(), '0x\n1x')
|
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)))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user