mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-03-25 09:05:47 -04:00
Merge branch 'pipe-command-output'
This commit is contained in:
commit
0a302265ec
12 changed files with 246 additions and 51 deletions
|
|
@ -58,6 +58,7 @@
|
||||||
of floating windows to be constrained to multiples of the cell size.
|
of floating windows to be constrained to multiples of the cell size.
|
||||||
* Support for custom (i.e. other than ctrl/shift/alt/super) modifiers
|
* Support for custom (i.e. other than ctrl/shift/alt/super) modifiers
|
||||||
in key bindings ([#1348][1348]).
|
in key bindings ([#1348][1348]).
|
||||||
|
* `pipe-command-output` key binding.
|
||||||
|
|
||||||
[1348]: https://codeberg.org/dnkl/foot/issues/1348
|
[1348]: https://codeberg.org/dnkl/foot/issues/1348
|
||||||
|
|
||||||
|
|
|
||||||
36
README.md
36
README.md
|
|
@ -359,6 +359,42 @@ See the
|
||||||
[wiki](https://codeberg.org/dnkl/foot/wiki#user-content-jumping-between-prompts)
|
[wiki](https://codeberg.org/dnkl/foot/wiki#user-content-jumping-between-prompts)
|
||||||
for details, and examples for other shells.
|
for details, and examples for other shells.
|
||||||
|
|
||||||
|
### Piping last command’s output
|
||||||
|
|
||||||
|
The key binding `pipe-command-output` can pipe the last command’s
|
||||||
|
output to an application of your choice (similar to the other `pipe-*`
|
||||||
|
key bindings):
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[key-bindings]
|
||||||
|
pipe-command-output=[sh -c "f=$(mktemp); cat - > $f; footclient emacsclient -nw $f; rm $f"] Control+Shift+g
|
||||||
|
```
|
||||||
|
|
||||||
|
When pressing <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>g</kbd>, the last
|
||||||
|
command’s output is written to a temporary file, then an emacsclient
|
||||||
|
is started in a new footclient instance. The temporary file is removed
|
||||||
|
after the footclient instance has closed.
|
||||||
|
|
||||||
|
For this to work, the shell must emit an OSC-133;C (`\E]133;C\E\\`)
|
||||||
|
sequence before command output starts, and an OSC-133;D
|
||||||
|
(`\E]133;D\E\\`) when the command output ends.
|
||||||
|
|
||||||
|
In fish, one way to do this is to add `preexec` and `postexec` hooks:
|
||||||
|
|
||||||
|
```fish
|
||||||
|
function foot_cmd_start --on-event fish_preexec
|
||||||
|
echo -en "\e]133;C\e\\"
|
||||||
|
end
|
||||||
|
|
||||||
|
function foot_cmd_end --on-event fish_postexec
|
||||||
|
echo -en "\e]133;D\e\\"
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
See the
|
||||||
|
[wiki](https://codeberg.org/dnkl/foot/wiki#user-content-piping-last-commands-output)
|
||||||
|
for details, and examples for other shells
|
||||||
|
|
||||||
|
|
||||||
## Alt/meta
|
## Alt/meta
|
||||||
|
|
||||||
|
|
|
||||||
1
config.c
1
config.c
|
|
@ -111,6 +111,7 @@ static const char *const binding_action_map[] = {
|
||||||
[BIND_ACTION_PIPE_SCROLLBACK] = "pipe-scrollback",
|
[BIND_ACTION_PIPE_SCROLLBACK] = "pipe-scrollback",
|
||||||
[BIND_ACTION_PIPE_VIEW] = "pipe-visible",
|
[BIND_ACTION_PIPE_VIEW] = "pipe-visible",
|
||||||
[BIND_ACTION_PIPE_SELECTED] = "pipe-selected",
|
[BIND_ACTION_PIPE_SELECTED] = "pipe-selected",
|
||||||
|
[BIND_ACTION_PIPE_COMMAND_OUTPUT] = "pipe-command-output",
|
||||||
[BIND_ACTION_SHOW_URLS_COPY] = "show-urls-copy",
|
[BIND_ACTION_SHOW_URLS_COPY] = "show-urls-copy",
|
||||||
[BIND_ACTION_SHOW_URLS_LAUNCH] = "show-urls-launch",
|
[BIND_ACTION_SHOW_URLS_LAUNCH] = "show-urls-launch",
|
||||||
[BIND_ACTION_SHOW_URLS_PERSISTENT] = "show-urls-persistent",
|
[BIND_ACTION_SHOW_URLS_PERSISTENT] = "show-urls-persistent",
|
||||||
|
|
|
||||||
|
|
@ -424,6 +424,38 @@ See the wiki
|
||||||
(https://codeberg.org/dnkl/foot/wiki#user-content-jumping-between-prompts)
|
(https://codeberg.org/dnkl/foot/wiki#user-content-jumping-between-prompts)
|
||||||
for details, and examples for other shells.
|
for details, and examples for other shells.
|
||||||
|
|
||||||
|
## Piping last command’s output
|
||||||
|
|
||||||
|
The key binding *pipe-command-output* can pipe the last command’s
|
||||||
|
output to an application of your choice (similar to the other
|
||||||
|
*pipe-\** key bindings):
|
||||||
|
|
||||||
|
*\[key-bindings\]++
|
||||||
|
pipe-command-output=[sh -c "f=$(mktemp); cat - > $f; footclient emacsclient -nw $f; rm $f"] Control+Shift+g*
|
||||||
|
|
||||||
|
When pressing *ctrl*+*shift*+*g*, the last command’s output is written
|
||||||
|
to a temporary file, then an emacsclient is started in a new
|
||||||
|
footclient instance. The temporary file is removed after the
|
||||||
|
footclient instance has closed.
|
||||||
|
|
||||||
|
For this to work, the shell must emit an OSC-133;C (*\\E]133;C\\E\\\\*)
|
||||||
|
sequence before command output starts, and an OSC-133;D
|
||||||
|
(*\\E]133;D\\E\\\\*) when the command output ends.
|
||||||
|
|
||||||
|
In fish, one way to do this is to add _preexec_ and _postexec_ hooks:
|
||||||
|
|
||||||
|
*function foot_cmd_start --on-event fish_preexec
|
||||||
|
echo -en "\\e]133;C\\e\\\\"
|
||||||
|
end*
|
||||||
|
|
||||||
|
*function foot_cmd_end --on-event fish_postexec
|
||||||
|
echo -en "\\e]133;D\\e\\\\"
|
||||||
|
end*
|
||||||
|
|
||||||
|
See the wiki
|
||||||
|
(https://codeberg.org/dnkl/foot/wiki#user-content-piping-last-commands-output)
|
||||||
|
for details, and examples for other shells
|
||||||
|
|
||||||
# TERMINFO
|
# TERMINFO
|
||||||
|
|
||||||
Client applications use the terminfo identifier specified by the
|
Client applications use the terminfo identifier specified by the
|
||||||
|
|
|
||||||
|
|
@ -844,11 +844,12 @@ e.g. *search-start=none*.
|
||||||
*fullscreen*
|
*fullscreen*
|
||||||
Toggles the fullscreen state. Default: _none_.
|
Toggles the fullscreen state. Default: _none_.
|
||||||
|
|
||||||
*pipe-visible*, *pipe-scrollback*, *pipe-selected*
|
*pipe-visible*, *pipe-scrollback*, *pipe-selected*, *pipe-command-output*
|
||||||
Pipes the currently visible text, the entire scrollback, or the
|
Pipes the currently visible text, the entire scrollback, the
|
||||||
currently selected text to an external tool. The syntax for this
|
currently selected text, or the last command's output to an
|
||||||
option is a bit special; the first part of the value is the
|
external tool. The syntax for this option is a bit special; the
|
||||||
command to execute enclosed in "[]", followed by the binding(s).
|
first part of the value is the command to execute enclosed in
|
||||||
|
"[]", followed by the binding(s).
|
||||||
|
|
||||||
You can configure multiple pipes as long as the command strings
|
You can configure multiple pipes as long as the command strings
|
||||||
are different and the key bindings are unique.
|
are different and the key bindings are unique.
|
||||||
|
|
@ -856,10 +857,17 @@ e.g. *search-start=none*.
|
||||||
Note that the command is *not* automatically run inside a shell;
|
Note that the command is *not* automatically run inside a shell;
|
||||||
use *sh -c "command line"* if you need that.
|
use *sh -c "command line"* if you need that.
|
||||||
|
|
||||||
Example:
|
Example #1:
|
||||||
*pipe-visible=[sh -c "xurls | uniq | tac | fuzzel | xargs -r
|
|
||||||
|
# Extract currently visible URLs, let user choose one (via
|
||||||
|
fuzzel), then launch firefox with the selected URL++
|
||||||
|
*pipe-visible=[sh -c "xurls | uniq | tac | fuzzel | xargs -r
|
||||||
firefox"] Control+Print*
|
firefox"] Control+Print*
|
||||||
|
|
||||||
|
Example #2:
|
||||||
|
# Write scrollback content to /tmp/foot-scrollback.txt++
|
||||||
|
*pipe-scrollback=[sh -c "cat - > /tmp/foot-scrollback.txt"]
|
||||||
|
Control+Shift+Print*
|
||||||
Default: _none_
|
Default: _none_
|
||||||
|
|
||||||
*show-urls-launch*
|
*show-urls-launch*
|
||||||
|
|
|
||||||
1
foot.ini
1
foot.ini
|
|
@ -158,6 +158,7 @@
|
||||||
# pipe-visible=[sh -c "xurls | fuzzel | xargs -r firefox"] none
|
# pipe-visible=[sh -c "xurls | fuzzel | xargs -r firefox"] none
|
||||||
# pipe-scrollback=[sh -c "xurls | fuzzel | xargs -r firefox"] none
|
# pipe-scrollback=[sh -c "xurls | fuzzel | xargs -r firefox"] none
|
||||||
# pipe-selected=[xargs -r firefox] none
|
# pipe-selected=[xargs -r firefox] none
|
||||||
|
# pipe-command-output=[sh -c "cat - > /tmp/foot-cmd-out.txt"] none # Write output of last command to /tmp/foot-cmd-out.txt (requires shell integration)
|
||||||
# show-urls-launch=Control+Shift+o
|
# show-urls-launch=Control+Shift+o
|
||||||
# show-urls-copy=none
|
# show-urls-copy=none
|
||||||
# show-urls-persistent=none
|
# show-urls-persistent=none
|
||||||
|
|
|
||||||
70
grid.c
70
grid.c
|
|
@ -1,5 +1,6 @@
|
||||||
#include "grid.h"
|
#include "grid.h"
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
|
@ -231,7 +232,7 @@ grid_snapshot(const struct grid *grid)
|
||||||
clone_row->cells = xmalloc(grid->num_cols * sizeof(clone_row->cells[0]));
|
clone_row->cells = xmalloc(grid->num_cols * sizeof(clone_row->cells[0]));
|
||||||
clone_row->linebreak = row->linebreak;
|
clone_row->linebreak = row->linebreak;
|
||||||
clone_row->dirty = row->dirty;
|
clone_row->dirty = row->dirty;
|
||||||
clone_row->prompt_marker = row->prompt_marker;
|
clone_row->shell_integration = row->shell_integration;
|
||||||
|
|
||||||
for (int c = 0; c < grid->num_cols; c++)
|
for (int c = 0; c < grid->num_cols; c++)
|
||||||
clone_row->cells[c] = row->cells[c];
|
clone_row->cells[c] = row->cells[c];
|
||||||
|
|
@ -366,7 +367,9 @@ grid_row_alloc(int cols, bool initialize)
|
||||||
row->dirty = false;
|
row->dirty = false;
|
||||||
row->linebreak = false;
|
row->linebreak = false;
|
||||||
row->extra = NULL;
|
row->extra = NULL;
|
||||||
row->prompt_marker = false;
|
row->shell_integration.prompt_marker = false;
|
||||||
|
row->shell_integration.cmd_start = -1;
|
||||||
|
row->shell_integration.cmd_end = -1;
|
||||||
|
|
||||||
if (initialize) {
|
if (initialize) {
|
||||||
row->cells = xcalloc(cols, sizeof(row->cells[0]));
|
row->cells = xcalloc(cols, sizeof(row->cells[0]));
|
||||||
|
|
@ -425,7 +428,9 @@ grid_resize_without_reflow(
|
||||||
|
|
||||||
new_row->dirty = old_row->dirty;
|
new_row->dirty = old_row->dirty;
|
||||||
new_row->linebreak = false;
|
new_row->linebreak = false;
|
||||||
new_row->prompt_marker = old_row->prompt_marker;
|
new_row->shell_integration.prompt_marker = old_row->shell_integration.prompt_marker;
|
||||||
|
new_row->shell_integration.cmd_start = min(old_row->shell_integration.cmd_start, new_cols - 1);
|
||||||
|
new_row->shell_integration.cmd_end = min(old_row->shell_integration.cmd_end, new_cols - 1);
|
||||||
|
|
||||||
if (new_cols > old_cols) {
|
if (new_cols > old_cols) {
|
||||||
/* Clear "new" columns */
|
/* Clear "new" columns */
|
||||||
|
|
@ -587,7 +592,9 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row,
|
||||||
/* Scrollback is full, need to reuse a row */
|
/* Scrollback is full, need to reuse a row */
|
||||||
grid_row_reset_extra(new_row);
|
grid_row_reset_extra(new_row);
|
||||||
new_row->linebreak = false;
|
new_row->linebreak = false;
|
||||||
new_row->prompt_marker = false;
|
new_row->shell_integration.prompt_marker = false;
|
||||||
|
new_row->shell_integration.cmd_start = -1;
|
||||||
|
new_row->shell_integration.cmd_end = -1;
|
||||||
|
|
||||||
tll_foreach(old_grid->sixel_images, it) {
|
tll_foreach(old_grid->sixel_images, it) {
|
||||||
if (it->item.pos.row == *row_idx) {
|
if (it->item.pos.row == *row_idx) {
|
||||||
|
|
@ -831,35 +838,26 @@ grid_resize_and_reflow(
|
||||||
int end;
|
int end;
|
||||||
bool tp_break = false;
|
bool tp_break = false;
|
||||||
bool uri_break = false;
|
bool uri_break = false;
|
||||||
|
bool ftcs_break = false;
|
||||||
|
|
||||||
/*
|
/* Figure out where to end this chunk */
|
||||||
* Set end-coordinate for this chunk, by finding the next
|
{
|
||||||
* point-of-interest on this row.
|
const int uri_col = range != range_terminator
|
||||||
*
|
? ((range->start >= start ? range->start : range->end) + 1)
|
||||||
* If there are no more tracking points, or URI ranges,
|
: INT_MAX;
|
||||||
* the end-coordinate will be at the end of the row,
|
const int tp_col = tp != NULL ? tp->col + 1 : INT_MAX;
|
||||||
*/
|
const int ftcs_col = old_row->shell_integration.cmd_start >= start
|
||||||
if (range != range_terminator) {
|
? old_row->shell_integration.cmd_start + 1
|
||||||
int uri_col = (range->start >= start ? range->start : range->end) + 1;
|
: old_row->shell_integration.cmd_end >= start
|
||||||
|
? old_row->shell_integration.cmd_end + 1
|
||||||
|
: INT_MAX;
|
||||||
|
|
||||||
if (tp != NULL) {
|
end = min(col_count, min(min(tp_col, uri_col), ftcs_col));
|
||||||
int tp_col = tp->col + 1;
|
|
||||||
end = min(tp_col, uri_col);
|
|
||||||
|
|
||||||
tp_break = end == tp_col;
|
uri_break = end == uri_col;
|
||||||
uri_break = end == uri_col;
|
tp_break = end == tp_col;
|
||||||
LOG_DBG("tp+uri break at %d (%d, %d)", end, tp_col, uri_col);
|
ftcs_break = end == ftcs_col;
|
||||||
} else {
|
}
|
||||||
end = uri_col;
|
|
||||||
uri_break = true;
|
|
||||||
LOG_DBG("uri break at %d", end);
|
|
||||||
}
|
|
||||||
} else if (tp != NULL) {
|
|
||||||
end = tp->col + 1;
|
|
||||||
tp_break = true;
|
|
||||||
LOG_DBG("TP break at %d", end);
|
|
||||||
} else
|
|
||||||
end = col_count;
|
|
||||||
|
|
||||||
int cols = end - start;
|
int cols = end - start;
|
||||||
xassert(cols > 0);
|
xassert(cols > 0);
|
||||||
|
|
@ -920,7 +918,7 @@ grid_resize_and_reflow(
|
||||||
xassert(from + amount <= old_cols);
|
xassert(from + amount <= old_cols);
|
||||||
|
|
||||||
if (from == 0)
|
if (from == 0)
|
||||||
new_row->prompt_marker = old_row->prompt_marker;
|
new_row->shell_integration.prompt_marker = old_row->shell_integration.prompt_marker;
|
||||||
|
|
||||||
memcpy(
|
memcpy(
|
||||||
&new_row->cells[new_col_idx], &old_row->cells[from],
|
&new_row->cells[new_col_idx], &old_row->cells[from],
|
||||||
|
|
@ -979,6 +977,16 @@ grid_resize_and_reflow(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ftcs_break) {
|
||||||
|
xassert(old_row->shell_integration.cmd_start == start + cols - 1 ||
|
||||||
|
old_row->shell_integration.cmd_end == start + cols - 1);
|
||||||
|
|
||||||
|
if (old_row->shell_integration.cmd_start == start + cols - 1)
|
||||||
|
new_row->shell_integration.cmd_start = new_col_idx - 1;
|
||||||
|
if (old_row->shell_integration.cmd_end == start + cols - 1)
|
||||||
|
new_row->shell_integration.cmd_end = new_col_idx - 1;
|
||||||
|
}
|
||||||
|
|
||||||
left -= cols;
|
left -= cols;
|
||||||
start += cols;
|
start += cols;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
input.c
11
input.c
|
|
@ -227,7 +227,8 @@ execute_binding(struct seat *seat, struct terminal *term,
|
||||||
break;
|
break;
|
||||||
/* FALLTHROUGH */
|
/* FALLTHROUGH */
|
||||||
case BIND_ACTION_PIPE_VIEW:
|
case BIND_ACTION_PIPE_VIEW:
|
||||||
case BIND_ACTION_PIPE_SELECTED: {
|
case BIND_ACTION_PIPE_SELECTED:
|
||||||
|
case BIND_ACTION_PIPE_COMMAND_OUTPUT: {
|
||||||
if (binding->aux->type != BINDING_AUX_PIPE)
|
if (binding->aux->type != BINDING_AUX_PIPE)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
|
@ -269,6 +270,10 @@ execute_binding(struct seat *seat, struct terminal *term,
|
||||||
len = text != NULL ? strlen(text) : 0;
|
len = text != NULL ? strlen(text) : 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case BIND_ACTION_PIPE_COMMAND_OUTPUT:
|
||||||
|
success = term_command_output_to_text(term, &text, &len);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
BUG("Unhandled action type");
|
BUG("Unhandled action type");
|
||||||
success = false;
|
success = false;
|
||||||
|
|
@ -377,7 +382,7 @@ execute_binding(struct seat *seat, struct terminal *term,
|
||||||
const struct row *row = grid->rows[r_abs];
|
const struct row *row = grid->rows[r_abs];
|
||||||
xassert(row != NULL);
|
xassert(row != NULL);
|
||||||
|
|
||||||
if (!row->prompt_marker)
|
if (!row->shell_integration.prompt_marker)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
grid->view = r_abs;
|
grid->view = r_abs;
|
||||||
|
|
@ -409,7 +414,7 @@ execute_binding(struct seat *seat, struct terminal *term,
|
||||||
const struct row *row = grid->rows[r_abs];
|
const struct row *row = grid->rows[r_abs];
|
||||||
xassert(row != NULL);
|
xassert(row != NULL);
|
||||||
|
|
||||||
if (!row->prompt_marker) {
|
if (!row->shell_integration.prompt_marker) {
|
||||||
if (r_abs == grid->offset + term->rows - 1) {
|
if (r_abs == grid->offset + term->rows - 1) {
|
||||||
/* We’ve reached the bottom of the scrollback */
|
/* We’ve reached the bottom of the scrollback */
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ enum bind_action_normal {
|
||||||
BIND_ACTION_PIPE_SCROLLBACK,
|
BIND_ACTION_PIPE_SCROLLBACK,
|
||||||
BIND_ACTION_PIPE_VIEW,
|
BIND_ACTION_PIPE_VIEW,
|
||||||
BIND_ACTION_PIPE_SELECTED,
|
BIND_ACTION_PIPE_SELECTED,
|
||||||
|
BIND_ACTION_PIPE_COMMAND_OUTPUT,
|
||||||
BIND_ACTION_SHOW_URLS_COPY,
|
BIND_ACTION_SHOW_URLS_COPY,
|
||||||
BIND_ACTION_SHOW_URLS_LAUNCH,
|
BIND_ACTION_SHOW_URLS_LAUNCH,
|
||||||
BIND_ACTION_SHOW_URLS_PERSISTENT,
|
BIND_ACTION_SHOW_URLS_PERSISTENT,
|
||||||
|
|
|
||||||
12
osc.c
12
osc.c
|
|
@ -893,7 +893,7 @@ osc_dispatch(struct terminal *term)
|
||||||
term->grid->cursor.point.row,
|
term->grid->cursor.point.row,
|
||||||
term->grid->cursor.point.col);
|
term->grid->cursor.point.col);
|
||||||
|
|
||||||
term->grid->cur_row->prompt_marker = true;
|
term->grid->cur_row->shell_integration.prompt_marker = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'B':
|
case 'B':
|
||||||
|
|
@ -901,11 +901,17 @@ osc_dispatch(struct terminal *term)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'C':
|
case 'C':
|
||||||
LOG_DBG("FTCS_COMMAND_EXECUTED");
|
LOG_DBG("FTCS_COMMAND_EXECUTED: %dx%d",
|
||||||
|
term->grid->cursor.point.row,
|
||||||
|
term->grid->cursor.point.col);
|
||||||
|
term->grid->cur_row->shell_integration.cmd_start = term->grid->cursor.point.col;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'D':
|
case 'D':
|
||||||
LOG_DBG("FTCS_COMMAND_FINISHED");
|
LOG_DBG("FTCS_COMMAND_FINISHED: %dx%d",
|
||||||
|
term->grid->cursor.point.row,
|
||||||
|
term->grid->cursor.point.col);
|
||||||
|
term->grid->cur_row->shell_integration.cmd_end = term->grid->cursor.point.col;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
101
terminal.c
101
terminal.c
|
|
@ -1825,7 +1825,9 @@ erase_line(struct terminal *term, struct row *row)
|
||||||
{
|
{
|
||||||
erase_cell_range(term, row, 0, term->cols - 1);
|
erase_cell_range(term, row, 0, term->cols - 1);
|
||||||
row->linebreak = false;
|
row->linebreak = false;
|
||||||
row->prompt_marker = false;
|
row->shell_integration.prompt_marker = false;
|
||||||
|
row->shell_integration.cmd_start = -1;
|
||||||
|
row->shell_integration.cmd_end = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
@ -3611,7 +3613,7 @@ term_surface_kind(const struct terminal *term, const struct wl_surface *surface)
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
rows_to_text(const struct terminal *term, int start, int end,
|
rows_to_text(const struct terminal *term, int start, int end,
|
||||||
char **text, size_t *len)
|
int col_start, int col_end, char **text, size_t *len)
|
||||||
{
|
{
|
||||||
struct extraction_context *ctx = extract_begin(SELECTION_NONE, true);
|
struct extraction_context *ctx = extract_begin(SELECTION_NONE, true);
|
||||||
if (ctx == NULL)
|
if (ctx == NULL)
|
||||||
|
|
@ -3624,15 +3626,20 @@ rows_to_text(const struct terminal *term, int start, int end,
|
||||||
const struct row *row = term->grid->rows[r];
|
const struct row *row = term->grid->rows[r];
|
||||||
xassert(row != NULL);
|
xassert(row != NULL);
|
||||||
|
|
||||||
for (int c = 0; c < term->cols; c++)
|
const int c_end = r == end ? col_end : term->cols;
|
||||||
|
|
||||||
|
for (int c = col_start; c < c_end; c++) {
|
||||||
if (!extract_one(term, row, &row->cells[c], c, ctx))
|
if (!extract_one(term, row, &row->cells[c], c, ctx))
|
||||||
goto out;
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
if (r == end)
|
if (r == end)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
r++;
|
r++;
|
||||||
r &= grid_rows - 1;
|
r &= grid_rows - 1;
|
||||||
|
|
||||||
|
col_start = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
|
|
@ -3664,7 +3671,7 @@ term_scrollback_to_text(const struct terminal *term, char **text, size_t *len)
|
||||||
end += term->grid->num_rows;
|
end += term->grid->num_rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
return rows_to_text(term, start, end, text, len);
|
return rows_to_text(term, start, end, 0, term->cols, text, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|
@ -3672,7 +3679,91 @@ term_view_to_text(const struct terminal *term, char **text, size_t *len)
|
||||||
{
|
{
|
||||||
int start = grid_row_absolute_in_view(term->grid, 0);
|
int start = grid_row_absolute_in_view(term->grid, 0);
|
||||||
int end = grid_row_absolute_in_view(term->grid, term->rows - 1);
|
int end = grid_row_absolute_in_view(term->grid, term->rows - 1);
|
||||||
return rows_to_text(term, start, end, text, len);
|
return rows_to_text(term, start, end, 0, term->cols, text, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
term_command_output_to_text(const struct terminal *term, char **text, size_t *len)
|
||||||
|
{
|
||||||
|
int start_row = -1;
|
||||||
|
int end_row = -1;
|
||||||
|
int start_col = -1;
|
||||||
|
int end_col = -1;
|
||||||
|
|
||||||
|
const struct grid *grid = term->grid;
|
||||||
|
const int sb_end = grid_row_absolute(grid, term->rows - 1);
|
||||||
|
const int sb_start = (sb_end + 1) & (grid->num_rows - 1);
|
||||||
|
int r = sb_end;
|
||||||
|
|
||||||
|
while (start_row < 0) {
|
||||||
|
const struct row *row = grid->rows[r];
|
||||||
|
if (row == NULL)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (row->shell_integration.cmd_end >= 0) {
|
||||||
|
end_row = r;
|
||||||
|
end_col = row->shell_integration.cmd_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end_row >= 0 && row->shell_integration.cmd_start >= 0) {
|
||||||
|
start_row = r;
|
||||||
|
start_col = row->shell_integration.cmd_start;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r == sb_start)
|
||||||
|
break;
|
||||||
|
|
||||||
|
r = (r - 1 + grid->num_rows) & (grid->num_rows - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start_row < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool ret = rows_to_text(term, start_row, end_row, start_col, end_col, text, len);
|
||||||
|
if (!ret)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the FTCS_COMMAND_FINISHED marker was emitted at the *first*
|
||||||
|
* column, then the *entire* previous line is part of the command
|
||||||
|
* output. *Including* the newline, if any.
|
||||||
|
*
|
||||||
|
* Since rows_to_text() doesn’t extract the column
|
||||||
|
* FTCS_COMMAND_FINISHED was emitted at (that would be wrong -
|
||||||
|
* FTCS_COMMAND_FINISHED is emitted *after* the command output,
|
||||||
|
* not at its last character), the extraction logic will not see
|
||||||
|
* the last newline (this is true for all non-line-wise selection
|
||||||
|
* types), and the extracted text will *not* end with a newline.
|
||||||
|
*
|
||||||
|
* Here we try to compensate for that. Note that if ‘end_col’ is
|
||||||
|
* not 0, then the command output only covers a partial row, and
|
||||||
|
* thus we do *not* want to append a newline.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (end_col > 0) {
|
||||||
|
/* Command output covers partial row - don’t append newline */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int next_to_last_row = (end_row - 1 + grid->num_rows) & (grid->num_rows - 1);
|
||||||
|
const struct row *row = grid->rows[next_to_last_row];
|
||||||
|
|
||||||
|
/* Add newline if last row has a hard linebreak */
|
||||||
|
if (row->linebreak) {
|
||||||
|
char *new_text = xrealloc(*text, *len + 1 + 1);
|
||||||
|
|
||||||
|
if (new_text == NULL) {
|
||||||
|
/* Ignore failure - use text as is (without inserting newline) */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
*text = new_text;
|
||||||
|
(*len)++;
|
||||||
|
(*text)[*len - 1] = '\n';
|
||||||
|
(*text)[*len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|
|
||||||
|
|
@ -121,8 +121,11 @@ struct row {
|
||||||
bool dirty;
|
bool dirty;
|
||||||
bool linebreak;
|
bool linebreak;
|
||||||
|
|
||||||
/* Shell integration */
|
struct {
|
||||||
bool prompt_marker;
|
bool prompt_marker;
|
||||||
|
int cmd_start; /* Column, -1 if unset */
|
||||||
|
int cmd_end; /* Column, -1 if unset */
|
||||||
|
} shell_integration;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sixel {
|
struct sixel {
|
||||||
|
|
@ -844,6 +847,8 @@ bool term_scrollback_to_text(
|
||||||
const struct terminal *term, char **text, size_t *len);
|
const struct terminal *term, char **text, size_t *len);
|
||||||
bool term_view_to_text(
|
bool term_view_to_text(
|
||||||
const struct terminal *term, char **text, size_t *len);
|
const struct terminal *term, char **text, size_t *len);
|
||||||
|
bool term_command_output_to_text(
|
||||||
|
const struct terminal *term, char **text, size_t *len);
|
||||||
|
|
||||||
bool term_ime_is_enabled(const struct terminal *term);
|
bool term_ime_is_enabled(const struct terminal *term);
|
||||||
void term_ime_enable(struct terminal *term);
|
void term_ime_enable(struct terminal *term);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue