I've attached a new patch for named buffers. There's no longer a
paste stack. There's a name-tree which stores all the buffers, and a
time-tree which just stores unsticky buffers. The time-tree only
needs to store unsticky buffers since they're the only ones which can
be aged away.
I was going to use an actual time value for the time-tree, but there
can be duplicate times if you setb quickly, which could happen in a
script, and that would screw things up. But there's no need to store
a time value. We just need something that shows relative age, so I
made the crtime field a u_int, and new unsticky buffers have it set 1
higher than the current highest.
There is no most-recent-buffer pointer, which you mentioned before.
Since there's a time-tree, RB_MAX will retrieve the most recent
unsticky buffer, so I saw no need to track it separately.
I've added -s and -u flags to setb to make a buffer sticky or unsticky.
I've also added -n to rename a buffer. For example: setb -n newname -b oldname
I've kept the following features from the original patch:
> When in copy mode, pressing the double-quote character allows you to
> enter a buffer name. Once you've made a selection, typing "myBuf and
> then hitting Enter will save your selection to the buffer named myBuf.
> You can hit Ctrl-a instead of Enter to append. Both of these key
> bindings are only set by default in vi mode.
> I added the ability to do prefix-P to more quickly enter a buffer name
> for pasting.
diff --git a/cmd-capture-pane.c b/cmd-capture-pane.c
index e157e3c..2defab0 100644
--- a/cmd-capture-pane.c
+++ b/cmd-capture-pane.c
@@ -38,7 +38,7 @@ char *cmd_capture_pane_history(struct args *, struct cmd_q *,
const struct cmd_entry cmd_capture_pane_entry = {
"capture-pane", "capturep",
"ab:CeE:JpPqS:t:", 0, 0,
- "[-aCeJpPq] [-b buffer-index] [-E end-line] [-S start-line]"
+ "[-aCeJpPq] " CMD_BUFFER_USAGE " [-E end-line] [-S start-line]"
CMD_TARGET_PANE_USAGE,
0,
NULL,
@@ -165,7 +165,7 @@ cmd_capture_pane_exec(struct cmd *self, struct cmd_q *cmdq)
struct client *c;
struct window_pane *wp;
char *buf, *cause;
- int buffer;
+ const char *bufname;
u_int limit;
size_t len;
@@ -193,24 +193,17 @@ cmd_capture_pane_exec(struct cmd *self, struct cmd_q *cmdq)
server_push_stdout(c);
} else {
limit = options_get_number(&global_options, "buffer-limit");
- if (!args_has(args, 'b')) {
- paste_add(&global_buffers, buf, len, limit);
- return (CMD_RETURN_NORMAL);
- }
+ bufname = NULL;
+ if (args_has(args, 'b'))
+ bufname = args_get(args, 'b');
- buffer = args_strtonum(args, 'b', 0, INT_MAX, &cause);
- if (cause != NULL) {
- cmdq_error(cmdq, "buffer %s", cause);
+ if (paste_set(
+ &global_buffers, buf, len, limit, bufname, &cause) != 0) {
+ cmdq_error(cmdq, "%s", cause);
free(buf);
free(cause);
return (CMD_RETURN_ERROR);
}
-
- if (paste_replace(&global_buffers, buffer, buf, len) != 0) {
- cmdq_error(cmdq, "no buffer %d", buffer);
- free(buf);
- return (CMD_RETURN_ERROR);
- }
}
return (CMD_RETURN_NORMAL);
diff --git a/cmd-choose-buffer.c b/cmd-choose-buffer.c
index 64edc84..9532f0a 100644
--- a/cmd-choose-buffer.c
+++ b/cmd-choose-buffer.c
@@ -75,19 +75,19 @@ cmd_choose_buffer_exec(struct cmd *self, struct cmd_q *cmdq)
action = xstrdup("paste-buffer -b '%%'");
idx = 0;
- while ((pb = paste_walk_stack(&global_buffers, &idx)) != NULL) {
+ RB_FOREACH(pb, paste_name_tree, &global_buffers.name_tree) {
cdata = window_choose_data_create(TREE_OTHER, c, c->session);
- cdata->idx = idx - 1;
+ cdata->idx = idx;
cdata->ft_template = xstrdup(template);
- format_add(cdata->ft, "line", "%u", idx - 1);
format_paste_buffer(cdata->ft, pb, utf8flag);
- xasprintf(&action_data, "%u", idx - 1);
+ xasprintf(&action_data, "%s", pb->name);
cdata->command = cmd_template_replace(action, action_data, 1);
free(action_data);
window_choose_add(wl->window->active, cdata);
+ idx++;
}
free(action);
diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c
index 759d578..0acecc5 100644
--- a/cmd-command-prompt.c
+++ b/cmd-command-prompt.c
@@ -76,6 +76,9 @@ cmd_command_prompt_key_binding(struct cmd *self, int key)
self->args = args_create(1, "select-window -t ':%%'");
args_set(self->args, 'p', "index");
break;
+ case 'P':
+ self->args = args_create(1, "paste-buffer -b '%%'");
+ break;
default:
self->args = args_create(0);
break;
diff --git a/cmd-delete-buffer.c b/cmd-delete-buffer.c
index b8f55db..436ec25 100644
--- a/cmd-delete-buffer.c
+++ b/cmd-delete-buffer.c
@@ -41,23 +41,16 @@ enum cmd_retval
cmd_delete_buffer_exec(struct cmd *self, struct cmd_q *cmdq)
{
struct args *args = self->args;
- char *cause;
- int buffer;
+ const char *bufname;
if (!args_has(args, 'b')) {
paste_free_top(&global_buffers);
return (CMD_RETURN_NORMAL);
}
- buffer = args_strtonum(args, 'b', 0, INT_MAX, &cause);
- if (cause != NULL) {
- cmdq_error(cmdq, "buffer %s", cause);
- free(cause);
- return (CMD_RETURN_ERROR);
- }
-
- if (paste_free_index(&global_buffers, buffer) != 0) {
- cmdq_error(cmdq, "no buffer %d", buffer);
+ bufname = args_get(args, 'b');
+ if (paste_free_name(&global_buffers, bufname) != 0) {
+ cmdq_error(cmdq, "no buffer %s", bufname);
return (CMD_RETURN_ERROR);
}
diff --git a/cmd-list-buffers.c b/cmd-list-buffers.c
index 53f6598..8bc4fcb 100644
--- a/cmd-list-buffers.c
+++ b/cmd-list-buffers.c
@@ -44,17 +44,14 @@ cmd_list_buffers_exec(unused struct cmd *self, struct cmd_q *cmdq)
struct args *args = self->args;
struct paste_buffer *pb;
struct format_tree *ft;
- u_int idx;
char *line;
const char *template;
if ((template = args_get(args, 'F')) == NULL)
template = LIST_BUFFERS_TEMPLATE;
- idx = 0;
- while ((pb = paste_walk_stack(&global_buffers, &idx)) != NULL) {
+ RB_FOREACH(pb, paste_name_tree, &global_buffers.name_tree) {
ft = format_create();
- format_add(ft, "line", "%u", idx - 1);
format_paste_buffer(ft, pb, 0);
line = format_expand(ft, template);
diff --git a/cmd-load-buffer.c b/cmd-load-buffer.c
index 2cb01b9..3ea6589 100644
--- a/cmd-load-buffer.c
+++ b/cmd-load-buffer.c
@@ -50,30 +50,20 @@ cmd_load_buffer_exec(struct cmd *self, struct cmd_q *cmdq)
struct client *c = cmdq->client;
struct session *s;
FILE *f;
- const char *path;
+ const char *path, *bufname;
char *pdata, *new_pdata, *cause;
size_t psize;
u_int limit;
- int ch, error, buffer, *buffer_ptr, cwd, fd;
-
- if (!args_has(args, 'b'))
- buffer = -1;
- else {
- buffer = args_strtonum(args, 'b', 0, INT_MAX, &cause);
- if (cause != NULL) {
- cmdq_error(cmdq, "buffer %s", cause);
- free(cause);
- return (CMD_RETURN_ERROR);
- }
- }
+ int ch, error, cwd, fd;
+
+ bufname = NULL;
+ if (args_has(args, 'b'))
+ bufname = args_get(args, 'b');
path = args->argv[0];
if (strcmp(path, "-") == 0) {
- buffer_ptr = xmalloc(sizeof *buffer_ptr);
- *buffer_ptr = buffer;
-
error = server_set_stdin_callback(c, cmd_load_buffer_callback,
- buffer_ptr, &cause);
+ bufname, &cause);
if (error != 0) {
cmdq_error(cmdq, "%s: %s", path, cause);
free(cause);
@@ -118,13 +108,12 @@ cmd_load_buffer_exec(struct cmd *self, struct cmd_q *cmdq)
fclose(f);
limit = options_get_number(&global_options, "buffer-limit");
- if (buffer == -1) {
- paste_add(&global_buffers, pdata, psize, limit);
- return (CMD_RETURN_NORMAL);
- }
- if (paste_replace(&global_buffers, buffer, pdata, psize) != 0) {
- cmdq_error(cmdq, "no buffer %d", buffer);
+
+ if(paste_set(
+ &global_buffers, pdata, psize, limit, bufname, &cause) != 0) {
+ cmdq_error(cmdq, "%s", cause);
free(pdata);
+ free(cause);
return (CMD_RETURN_ERROR);
}
@@ -140,10 +129,10 @@ error:
void
cmd_load_buffer_callback(struct client *c, int closed, void *data)
{
- int *buffer = data;
- char *pdata;
- size_t psize;
- u_int limit;
+ char *pdata, *cause;
+ const char *bufname = data;
+ size_t psize;
+ u_int limit;
if (!closed)
return;
@@ -154,26 +143,24 @@ cmd_load_buffer_callback(struct client *c, int closed, void *data)
return;
psize = EVBUFFER_LENGTH(c->stdin_data);
- if (psize == 0 || (pdata = malloc(psize + 1)) == NULL) {
- free(data);
+ if (psize == 0 || (pdata = malloc(psize + 1)) == NULL)
goto out;
- }
+
memcpy(pdata, EVBUFFER_DATA(c->stdin_data), psize);
pdata[psize] = '\0';
evbuffer_drain(c->stdin_data, psize);
limit = options_get_number(&global_options, "buffer-limit");
- if (*buffer == -1)
- paste_add(&global_buffers, pdata, psize, limit);
- else if (paste_replace(&global_buffers, *buffer, pdata, psize) != 0) {
+
+ if (paste_set(&global_buffers,
+ pdata, psize, limit, bufname, &cause) != 0) {
/* No context so can't use server_client_msg_error. */
- evbuffer_add_printf(c->stderr_data, "no buffer %d\n", *buffer);
+ evbuffer_add_printf(c->stderr_data, "%s", cause);
server_push_stderr(c);
free(pdata);
+ free(cause);
}
- free(data);
-
out:
cmdq_continue(c->cmdq);
}
diff --git a/cmd-paste-buffer.c b/cmd-paste-buffer.c
index 5305b7e..af0c1f0 100644
--- a/cmd-paste-buffer.c
+++ b/cmd-paste-buffer.c
@@ -35,7 +35,7 @@ void cmd_paste_buffer_filter(struct window_pane *,
const struct cmd_entry cmd_paste_buffer_entry = {
"paste-buffer", "pasteb",
"db:prs:t:", 0, 0,
- "[-dpr] [-s separator] [-b buffer-index] " CMD_TARGET_PANE_USAGE,
+ "[-dpr] [-s separator] " CMD_BUFFER_USAGE " " CMD_TARGET_PANE_USAGE,
0,
NULL,
cmd_paste_buffer_exec
@@ -48,31 +48,22 @@ cmd_paste_buffer_exec(struct cmd *self, struct cmd_q *cmdq)
struct window_pane *wp;
struct session *s;
struct paste_buffer *pb;
- const char *sepstr;
- char *cause;
- int buffer;
+ const char *sepstr, *bufname;
int pflag;
if (cmd_find_pane(cmdq, args_get(args, 't'), &s, &wp) == NULL)
return (CMD_RETURN_ERROR);
- if (!args_has(args, 'b'))
- buffer = -1;
- else {
- buffer = args_strtonum(args, 'b', 0, INT_MAX, &cause);
- if (cause != NULL) {
- cmdq_error(cmdq, "buffer %s", cause);
- free(cause);
- return (CMD_RETURN_ERROR);
- }
- }
+ bufname = NULL;
+ if (args_has(args, 'b'))
+ bufname = args_get(args, 'b');
- if (buffer == -1)
+ if (bufname == NULL)
pb = paste_get_top(&global_buffers);
else {
- pb = paste_get_index(&global_buffers, buffer);
+ pb = paste_get_name(&global_buffers, bufname);
if (pb == NULL) {
- cmdq_error(cmdq, "no buffer %d", buffer);
+ cmdq_error(cmdq, "no buffer %s", bufname);
return (CMD_RETURN_ERROR);
}
}
@@ -91,10 +82,10 @@ cmd_paste_buffer_exec(struct cmd *self, struct cmd_q *cmdq)
/* Delete the buffer if -d. */
if (args_has(args, 'd')) {
- if (buffer == -1)
+ if (bufname == NULL)
paste_free_top(&global_buffers);
else
- paste_free_index(&global_buffers, buffer);
+ paste_free_name(&global_buffers, bufname);
}
return (CMD_RETURN_NORMAL);
diff --git a/cmd-save-buffer.c b/cmd-save-buffer.c
index f08f80c..0ce0238 100644
--- a/cmd-save-buffer.c
+++ b/cmd-save-buffer.c
@@ -58,10 +58,10 @@ cmd_save_buffer_exec(struct cmd *self, struct cmd_q *cmdq)
struct client *c = cmdq->client;
struct session *s;
struct paste_buffer *pb;
- const char *path;
- char *cause, *start, *end, *msg;
+ const char *path, *bufname;
+ char *start, *end, *msg;
size_t size, used, msglen;
- int cwd, fd, buffer;
+ int cwd, fd;
FILE *f;
if (!args_has(args, 'b')) {
@@ -70,16 +70,10 @@ cmd_save_buffer_exec(struct cmd *self, struct cmd_q *cmdq)
return (CMD_RETURN_ERROR);
}
} else {
- buffer = args_strtonum(args, 'b', 0, INT_MAX, &cause);
- if (cause != NULL) {
- cmdq_error(cmdq, "buffer %s", cause);
- free(cause);
- return (CMD_RETURN_ERROR);
- }
-
- pb = paste_get_index(&global_buffers, buffer);
+ bufname = args_get(args, 'b');
+ pb = paste_get_name(&global_buffers, bufname);
if (pb == NULL) {
- cmdq_error(cmdq, "no buffer %d", buffer);
+ cmdq_error(cmdq, "no buffer %s", bufname);
return (CMD_RETURN_ERROR);
}
}
diff --git a/cmd-set-buffer.c b/cmd-set-buffer.c
index e7f9b95..61895f9 100644
--- a/cmd-set-buffer.c
+++ b/cmd-set-buffer.c
@@ -31,8 +31,8 @@ enum cmd_retval cmd_set_buffer_exec(struct cmd *, struct cmd_q *);
const struct cmd_entry cmd_set_buffer_entry = {
"set-buffer", "setb",
- "ab:", 1, 1,
- "[-a] " CMD_BUFFER_USAGE " data",
+ "ab:n:su", 0, 1,
+ "[-asu] [-n new_buffer_name] " CMD_BUFFER_USAGE " data",
0,
NULL,
cmd_set_buffer_exec
@@ -45,36 +45,105 @@ cmd_set_buffer_exec(struct cmd *self, struct cmd_q *cmdq)
struct paste_buffer *pb;
u_int limit;
char *pdata, *cause;
+ u_char sticky;
+ const char *bufname;
size_t psize, newsize;
- int buffer;
+ bufname = NULL;
limit = options_get_number(&global_options, "buffer-limit");
- psize = 0;
- pdata = NULL;
- pb = NULL;
- buffer = -1;
+ if (args_has(args, 'n')) {
+ if (args_has(args, 's') || args_has(args, 'u')) {
+ cmdq_error(cmdq, "specify only 1 of n, s, or u flags");
+ return (CMD_RETURN_ERROR);
+ }
- if ((newsize = strlen(args->argv[0])) == 0)
- return (CMD_RETURN_NORMAL);
+ if (args->argc > 0) {
+ cmdq_error(cmdq, "don't provide data with n flag");
+ return (CMD_RETURN_ERROR);
+ }
- if (args_has(args, 'b')) {
- buffer = args_strtonum(args, 'b', 0, INT_MAX, &cause);
- if (cause != NULL) {
- cmdq_error(cmdq, "buffer %s", cause);
+ if (args_has(args, 'b'))
+ bufname = args_get(args, 'b');
+
+ if (bufname == NULL) {
+ pb = paste_get_top(&global_buffers);
+ if (pb == NULL) {
+ cmdq_error(cmdq, "no buffer");
+ return (CMD_RETURN_ERROR);
+ }
+ bufname = pb->name;
+ }
+
+ if (paste_rename(&global_buffers,
+ bufname, args_get(args, 'n'), &cause) != 0) {
+ cmdq_error(cmdq, "%s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
- pb = paste_get_index(&global_buffers, buffer);
+
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (args_has(args, 's') && args_has(args, 'u')) {
+ cmdq_error(cmdq, "specify only 1 of s or u flags");
+ return (CMD_RETURN_ERROR);
+ }
+
+ if (args_has(args, 's') || args_has(args, 'u')) {
+ sticky = 0;
+ if (args_has(args, 's'))
+ sticky = 1;
+
+ if (args->argc > 0) {
+ cmdq_error(cmdq, "don't provide data with s or u flags");
+ return (CMD_RETURN_ERROR);
+ }
+
+ if (args_has(args, 'b'))
+ bufname = args_get(args, 'b');
+
+ if (bufname == NULL)
+ pb = paste_get_top(&global_buffers);
+ else
+ pb = paste_get_name(&global_buffers, bufname);
+
if (pb == NULL) {
- cmdq_error(cmdq, "no buffer %d", buffer);
+ if (bufname == NULL)
+ bufname = "";
+ cmdq_error(cmdq, "no buffer %s", bufname);
return (CMD_RETURN_ERROR);
}
+
+ if (sticky == 0)
+ paste_make_unsticky(&global_buffers, pb->name, limit);
+ else
+ paste_make_sticky(&global_buffers, pb->name);
+
+ return (CMD_RETURN_NORMAL);
+ }
+
+ if (args->argc != 1) {
+ cmdq_error(cmdq, "no data specified");
+ return (CMD_RETURN_ERROR);
+ }
+
+ psize = 0;
+ pdata = NULL;
+
+ pb = NULL;
+
+ if ((newsize = strlen(args->argv[0])) == 0)
+ return (CMD_RETURN_NORMAL);
+
+ if (args_has(args, 'b')) {
+ bufname = args_get(args, 'b');
+ pb = paste_get_name(&global_buffers, bufname);
} else if (args_has(args, 'a')) {
pb = paste_get_top(&global_buffers);
if (pb != NULL)
- buffer = 0;
+ bufname = pb->name;
}
if (args_has(args, 'a') && pb != NULL) {
@@ -87,10 +156,13 @@ cmd_set_buffer_exec(struct cmd *self, struct cmd_q *cmdq)
memcpy(pdata + psize, args->argv[0], newsize);
psize += newsize;
- if (buffer == -1)
- paste_add(&global_buffers, pdata, psize, limit);
- else
- paste_replace(&global_buffers, buffer, pdata, psize);
+ if (paste_set(&global_buffers,
+ pdata, psize, limit, bufname, &cause) != 0) {
+ cmdq_error(cmdq, "%s", cause);
+ free(pdata);
+ free(cause);
+ return (CMD_RETURN_ERROR);
+ }
return (CMD_RETURN_NORMAL);
}
diff --git a/format.c b/format.c
index 6f988b9..6bef9ab 100644
--- a/format.c
+++ b/format.c
@@ -610,6 +610,9 @@ format_paste_buffer(struct format_tree *ft, struct paste_buffer *pb,
char *s;
format_add(ft, "buffer_size", "%zu", pb->size);
+ format_add(ft, "buffer_name", "%s", pb->name);
+ format_add(ft, "buffer_stickiness", "%c",
+ pb->sticky == 0 ? 'U' : 'S');
s = paste_make_sample(pb, utf8flag);
format_add(ft, "buffer_sample", "%s", s);
diff --git a/key-bindings.c b/key-bindings.c
index f725508..6a7d14c 100644
--- a/key-bindings.c
+++ b/key-bindings.c
@@ -130,6 +130,7 @@ key_bindings_init(void)
{ '?', 0, &cmd_list_keys_entry },
{ 'D', 0, &cmd_choose_client_entry },
{ 'L', 0, &cmd_switch_client_entry },
+ { 'P', 0, &cmd_command_prompt_entry },
{ '[', 0, &cmd_copy_mode_entry },
{ '\'', 0, &cmd_command_prompt_entry },
{ '\002', /* C-b */ 0, &cmd_send_prefix_entry },
diff --git a/mode-key.c b/mode-key.c
index 57be2d8..b9ad8e4 100644
--- a/mode-key.c
+++ b/mode-key.c
@@ -51,6 +51,7 @@ const struct mode_key_cmdstr mode_key_cmdstr_edit[] = {
{ MODEKEYEDIT_DELETEWORD, "delete-word" },
{ MODEKEYEDIT_ENDOFLINE, "end-of-line" },
{ MODEKEYEDIT_ENTER, "enter" },
+ { MODEKEYEDIT_ENTERAPPEND, "enter-append" },
{ MODEKEYEDIT_HISTORYDOWN, "history-down" },
{ MODEKEYEDIT_HISTORYUP, "history-up" },
{ MODEKEYEDIT_NEXTSPACE, "next-space" },
@@ -141,6 +142,7 @@ const struct mode_key_cmdstr mode_key_cmdstr_copy[] = {
{ MODEKEYCOPY_SEARCHREVERSE, "search-reverse" },
{ MODEKEYCOPY_SEARCHUP, "search-backward" },
{ MODEKEYCOPY_SELECTLINE, "select-line" },
+ { MODEKEYCOPY_STARTNAMEDBUFFER, "start-named-buffer" },
{ MODEKEYCOPY_STARTNUMBERPREFIX, "start-number-prefix" },
{ MODEKEYCOPY_STARTOFLINE, "start-of-line" },
{ MODEKEYCOPY_STARTSELECTION, "begin-selection" },
@@ -160,6 +162,7 @@ const struct mode_key_entry mode_key_vi_edit[] = {
{ '\033' /* Escape */, 0, MODEKEYEDIT_SWITCHMODE },
{ '\n', 0, MODEKEYEDIT_ENTER },
{ '\r', 0, MODEKEYEDIT_ENTER },
+ { '\001' /* C-a */, 0, MODEKEYEDIT_ENTERAPPEND },
{ KEYC_BSPACE, 0, MODEKEYEDIT_BACKSPACE },
{ KEYC_DC, 0, MODEKEYEDIT_DELETE },
{ KEYC_DOWN, 0, MODEKEYEDIT_HISTORYDOWN },
@@ -257,6 +260,7 @@ struct mode_key_tree mode_key_tree_vi_choice;
/* vi copy mode keys. */
const struct mode_key_entry mode_key_vi_copy[] = {
{ ' ', 0, MODEKEYCOPY_STARTSELECTION },
+ { '"', 0, MODEKEYCOPY_STARTNAMEDBUFFER },
{ '$', 0, MODEKEYCOPY_ENDOFLINE },
{ ',', 0, MODEKEYCOPY_JUMPREVERSE },
{ ';', 0, MODEKEYCOPY_JUMPAGAIN },
diff --git a/paste.c b/paste.c
index 00f37b9..e4875c0 100644
--- a/paste.c
+++ b/paste.c
@@ -21,6 +21,7 @@
#include <stdlib.h>
#include <string.h>
+#include <ctype.h>
#include "tmux.h"
@@ -29,124 +30,104 @@
* string!
*/
-/* Return each item of the stack in turn. */
-struct paste_buffer *
-paste_walk_stack(struct paste_stack *ps, u_int *idx)
-{
- struct paste_buffer *pb;
+int compare_buffer_names(const struct paste_buffer *, const struct paste_buffer *);
+int compare_buffer_times(const struct paste_buffer *, const struct paste_buffer *);
- pb = paste_get_index(ps, *idx);
- (*idx)++;
- return (pb);
-}
+RB_GENERATE(paste_name_tree, paste_buffer, name_entry, compare_buffer_names);
+RB_GENERATE(paste_time_tree, paste_buffer, time_entry, compare_buffer_times);
-/* Get the top item on the stack. */
-struct paste_buffer *
-paste_get_top(struct paste_stack *ps)
-{
- if (ARRAY_LENGTH(ps) == 0)
- return (NULL);
- return (ARRAY_FIRST(ps));
-}
-/* Get an item by its index. */
+/* Get the most recent unsticky buffer */
struct paste_buffer *
-paste_get_index(struct paste_stack *ps, u_int idx)
+paste_get_top(struct paste_store *ps)
{
- if (idx >= ARRAY_LENGTH(ps))
- return (NULL);
- return (ARRAY_ITEM(ps, idx));
+ return (RB_MAX(paste_time_tree, &ps->time_tree));
}
-/* Free the top item on the stack. */
+/* Free the most recent unsticky buffer */
int
-paste_free_top(struct paste_stack *ps)
+paste_free_top(struct paste_store *ps)
{
struct paste_buffer *pb;
- if (ARRAY_LENGTH(ps) == 0)
+ pb = paste_get_top(ps);
+ if (pb == NULL)
return (-1);
- pb = ARRAY_FIRST(ps);
- ARRAY_REMOVE(ps, 0);
-
- free(pb->data);
- free(pb);
-
- return (0);
+ return (paste_free_name(ps, pb->name));
}
-/* Free an item by index. */
+/* Free an item by name. */
int
-paste_free_index(struct paste_stack *ps, u_int idx)
+paste_free_name(struct paste_store *ps, const char *name)
{
- struct paste_buffer *pb;
+ struct paste_buffer *pb, pbfind;
- if (idx >= ARRAY_LENGTH(ps))
+ if (name == NULL || *name == '\0')
return (-1);
- pb = ARRAY_ITEM(ps, idx);
- ARRAY_REMOVE(ps, idx);
+ pbfind.name = name;
+ pb = RB_FIND(paste_name_tree, &ps->name_tree, &pbfind);
+
+ if (pb == NULL)
+ return (-1);
+
+ RB_REMOVE(paste_name_tree, &ps->name_tree, pb);
+ if (pb->sticky == 0) {
+ RB_REMOVE(paste_time_tree, &ps->time_tree, pb);
+ ps->num_unsticky--;
+ }
free(pb->data);
+ free(pb->name);
free(pb);
-
return (0);
}
/*
- * Add an item onto the top of the stack, freeing the bottom if at limit. Note
+ * Add an unsticky buffer, freeing the oldest unsticky item if at limit. Note
* that the caller is responsible for allocating data.
*/
void
-paste_add(struct paste_stack *ps, char *data, size_t size, u_int limit)
+paste_add(struct paste_store *ps, char *data, size_t size, u_int limit)
{
struct paste_buffer *pb;
+ u_int next_crtime;
if (size == 0)
return;
- while (ARRAY_LENGTH(ps) >= limit) {
- pb = ARRAY_LAST(ps);
- free(pb->data);
- free(pb);
- ARRAY_TRUNC(ps, 1);
+ while (ps->num_unsticky >= limit) {
+ pb = RB_MIN(paste_time_tree, &ps->time_tree);
+ paste_free_name(ps, pb->name);
}
- pb = xmalloc(sizeof *pb);
- ARRAY_INSERT(ps, 0, pb);
-
- pb->data = data;
- pb->size = size;
-}
-
-
-/*
- * Replace an item on the stack. Note that the caller is responsible for
- * allocating data.
- */
-int
-paste_replace(struct paste_stack *ps, u_int idx, char *data, size_t size)
-{
- struct paste_buffer *pb;
-
- if (size == 0) {
- free(data);
- return (0);
+ next_crtime = 0;
+ if (ps->num_unsticky > 0) {
+ pb = RB_MAX(paste_time_tree, &ps->time_tree);
+ next_crtime = pb->crtime + 1;
}
- if (idx >= ARRAY_LENGTH(ps))
- return (-1);
-
- pb = ARRAY_ITEM(ps, idx);
- free(pb->data);
+ pb = xmalloc(sizeof *pb);
+ pb->name = NULL;
+ do {
+ free(pb->name);
+ xasprintf(&pb->name, "buffer%04u", ps->unsticky_idx);
+ ps->unsticky_idx++;
+ } while (paste_get_name(ps, pb->name) != NULL);
pb->data = data;
pb->size = size;
+ pb->sticky = 0;
+ pb->crtime = next_crtime;
+
+ RB_INSERT(paste_name_tree, &ps->name_tree, pb);
+ RB_INSERT(paste_time_tree, &ps->time_tree, pb);
+ ps->num_unsticky++;
- return (0);
}
+
/* Convert start of buffer into a nice string. */
char *
paste_make_sample(struct paste_buffer *pb, int utf8flag)
@@ -195,3 +176,194 @@ paste_send_pane(struct paste_buffer *pb, struct window_pane *wp,
if (bracket)
bufferevent_write(wp->event, "\033[201~", 6);
}
+
+int
+compare_buffer_names(const struct paste_buffer *a,
+ const struct paste_buffer *b)
+{
+ return (strcmp(a->name, b->name));
+}
+
+
+int
+compare_buffer_times(const struct paste_buffer *a,
+ const struct paste_buffer *b)
+{
+ return (a->crtime < b->crtime ? -1 : a->crtime > b->crtime);
+}
+
+/* Get an item by its name. */
+struct paste_buffer *
+paste_get_name(struct paste_store *ps, const char *name)
+{
+ struct paste_buffer pbfind;
+
+ if (name == NULL || *name == '\0')
+ return (NULL);
+
+ pbfind.name = name;
+ return (RB_FIND(paste_name_tree, &ps->name_tree, &pbfind));
+}
+
+int
+paste_rename(struct paste_store *ps, const char *oldname,
+ const char *newname, char **cause)
+{
+ const char *c;
+ struct paste_buffer *pb;
+
+ *cause = NULL;
+
+ if (oldname == NULL || *oldname == '\0') {
+ *cause = xstrdup("no buffer");
+ return (-1);
+ }
+
+ if (newname == NULL || *newname == '\0') {
+ *cause = xstrdup("new name is empty");
+ return (-1);
+ }
+
+ if ((pb = paste_get_name(ps, oldname)) == NULL) {
+ xasprintf(cause, "no buffer %s", oldname);
+ return (-1);
+ }
+
+ if (paste_get_name(ps, newname) != NULL) {
+ *cause = xstrdup("buffer with new name already exists");
+ return (-1);
+ }
+
+ if (strlen(newname) > 16) {
+ *cause = xstrdup("buffer name too long");
+ return (-1);
+ }
+
+ for (c = newname; *c != '\0'; c++) {
+ if (!isprint(*c) || isspace(*c)) {
+ *cause = xstrdup("bad characters in buffer name");
+ return (-1);
+ }
+ }
+
+ RB_REMOVE(paste_name_tree, &ps->name_tree, pb);
+ free(pb->name);
+
+ pb->name = xstrdup(newname);
+ RB_INSERT(paste_name_tree, &ps->name_tree, pb);
+ return (0);
+}
+
+/*
+ * Add or Replace an item in the store. Note that the caller is responsible for
+ * allocating data.
+ */
+int
+paste_set(struct paste_store *ps, char *data, size_t size, u_int limit,
+ const char *name, char **cause)
+{
+ struct paste_buffer *pb;
+ const char *c;
+
+
+ if (cause != NULL)
+ *cause = NULL;
+
+ if (size == 0) {
+ free(data);
+ return (0);
+ }
+
+ if (name == NULL) {
+ paste_add(ps, data, size, limit);
+ return (0);
+ }
+
+
+ pb = paste_get_name(ps, name);
+
+ if (pb != NULL) {
+ free(pb->data);
+ pb->data = data;
+ pb->size = size;
+ return (0);
+ }
+
+
+ if (*name == '\0') {
+ if (cause != NULL)
+ *cause = xstrdup("empty buffer name");
+ return (-1);
+ }
+
+ if (strlen(name) > 16) {
+ if (cause != NULL)
+ *cause = xstrdup("buffer name too long");
+ return (-1);
+ }
+
+ for (c = name; *c != '\0'; c++) {
+ if (!isprint(*c) || isspace(*c)) {
+ if (cause != NULL)
+ *cause = xstrdup("bad characters in buffer name");
+ return (-1);
+ }
+ }
+
+ pb = xmalloc(sizeof *pb);
+ pb->data = data;
+ pb->size = size;
+ pb->name = xstrdup(name);
+ pb->sticky = 1;
+
+ RB_INSERT(paste_name_tree, &ps->name_tree, pb);
+
+ return (0);
+}
+
+void
+paste_make_sticky(struct paste_store *ps, const char *name)
+{
+ struct paste_buffer *pb;
+
+ if ((pb = paste_get_name(ps, name)) == NULL)
+ return;
+
+ if (pb->sticky == 1)
+ return;
+
+ RB_REMOVE(paste_time_tree, &ps->time_tree, pb);
+ ps->num_unsticky--;
+
+ pb->sticky = 1;
+}
+
+void
+paste_make_unsticky(struct paste_store *ps, const char *name, u_int limit)
+{
+ struct paste_buffer *pb, *tmppb;
+ u_int next_crtime;
+
+ if ((pb = paste_get_name(ps, name)) == NULL)
+ return;
+
+ if (pb->sticky == 0)
+ return;
+
+ while (ps->num_unsticky >= limit) {
+ tmppb = RB_MIN(paste_time_tree, &ps->time_tree);
+ paste_free_name(ps, tmppb->name);
+ }
+
+ next_crtime = 0;
+ if (ps->num_unsticky > 0) {
+ tmppb = RB_MAX(paste_time_tree, &ps->time_tree);
+ next_crtime = tmppb->crtime + 1;
+ }
+
+ pb->sticky = 0;
+ pb->crtime = next_crtime;
+
+ RB_INSERT(paste_time_tree, &ps->time_tree, pb);
+ ps->num_unsticky++;
+}
diff --git a/server.c b/server.c
index c8bc7ed..60717f6 100644
--- a/server.c
+++ b/server.c
@@ -50,7 +50,7 @@ int server_shutdown;
struct event server_ev_accept;
struct event server_ev_second;
-struct paste_stack global_buffers;
+struct paste_store global_buffers;
int server_create_socket(void);
void server_loop(void);
@@ -146,7 +146,10 @@ server_start(int lockfd, char *lockfile)
RB_INIT(&sessions);
RB_INIT(&dead_sessions);
TAILQ_INIT(&session_groups);
- ARRAY_INIT(&global_buffers);
+ global_buffers.unsticky_idx = 0;
+ global_buffers.num_unsticky = 0;
+ RB_INIT(&global_buffers.name_tree);
+ RB_INIT(&global_buffers.time_tree);
mode_key_init_trees();
key_bindings_init();
utf8_build();
diff --git a/tmux.h b/tmux.h
index 6f29126..9f89a09 100644
--- a/tmux.h
+++ b/tmux.h
@@ -82,7 +82,7 @@ extern char **environ;
/* Default template for choose-buffer. */
#define CHOOSE_BUFFER_TEMPLATE \
- "#{line}: #{buffer_size} bytes: #{buffer_sample}"
+ "#{buffer_name}: #{buffer_size} bytes: #{buffer_sample}"
/* Default template for choose-client. */
#define CHOOSE_CLIENT_TEMPLATE \
@@ -115,7 +115,8 @@ extern char **environ;
/* Default template for list-buffers. */
#define LIST_BUFFERS_TEMPLATE \
- "#{line}: #{buffer_size} bytes: \"#{buffer_sample}\""
+ "#{buffer_name}: #{buffer_size} bytes: " \
+ "\"#{buffer_sample}\""
/* Default template for list-clients. */
#define LIST_CLIENTS_TEMPLATE \
@@ -496,6 +497,7 @@ enum mode_key_cmd {
MODEKEYEDIT_DELETEWORD,
MODEKEYEDIT_ENDOFLINE,
MODEKEYEDIT_ENTER,
+ MODEKEYEDIT_ENTERAPPEND,
MODEKEYEDIT_HISTORYDOWN,
MODEKEYEDIT_HISTORYUP,
MODEKEYEDIT_NEXTSPACE,
@@ -579,6 +581,7 @@ enum mode_key_cmd {
MODEKEYCOPY_SEARCHREVERSE,
MODEKEYCOPY_SEARCHUP,
MODEKEYCOPY_SELECTLINE,
+ MODEKEYCOPY_STARTNAMEDBUFFER,
MODEKEYCOPY_STARTNUMBERPREFIX,
MODEKEYCOPY_STARTOFLINE,
MODEKEYCOPY_STARTSELECTION,
@@ -1032,9 +1035,21 @@ struct layout_cell {
/* Paste buffer. */
struct paste_buffer {
char *data;
+ char *name;
+ u_char sticky;
size_t size;
+ u_int crtime;
+
+ RB_ENTRY(paste_buffer) name_entry;
+ RB_ENTRY(paste_buffer) time_entry;
+};
+
+struct paste_store {
+ u_int unsticky_idx, num_unsticky;
+ RB_HEAD(paste_name_tree, paste_buffer) name_tree;
+ RB_HEAD(paste_time_tree, paste_buffer) time_tree;
+
};
-ARRAY_DECL(paste_stack, struct paste_buffer *);
/* Environment variable. */
struct environ_entry {
@@ -1494,7 +1509,7 @@ RB_HEAD(format_tree, format_entry);
#define CMD_SRCDST_WINDOW_USAGE "[-s src-window] [-t dst-window]"
#define CMD_SRCDST_SESSION_USAGE "[-s src-session] [-t dst-session]"
#define CMD_SRCDST_CLIENT_USAGE "[-s src-client] [-t dst-client]"
-#define CMD_BUFFER_USAGE "[-b buffer-index]"
+#define CMD_BUFFER_USAGE "[-b buffer-name]"
/* tmux.c */
extern struct options global_options;
@@ -1706,14 +1721,20 @@ void tty_keys_free(struct tty *);
int tty_keys_next(struct tty *);
/* paste.c */
-struct paste_buffer *paste_walk_stack(struct paste_stack *, u_int *);
-struct paste_buffer *paste_get_top(struct paste_stack *);
-struct paste_buffer *paste_get_index(struct paste_stack *, u_int);
-int paste_free_top(struct paste_stack *);
-int paste_free_index(struct paste_stack *, u_int);
-void paste_add(struct paste_stack *, char *, size_t, u_int);
-int paste_replace(struct paste_stack *, u_int, char *, size_t);
+RB_PROTOTYPE(paste_name_tree, paste_buffer, name_entry, compare_buffer_names);
+RB_PROTOTYPE(paste_time_tree, paste_buffer, time_entry, compare_buffer_times);
+struct paste_buffer *paste_get_top(struct paste_store *);
+struct paste_buffer *paste_get_name(struct paste_store *, const char *);
+int paste_free_top(struct paste_store *);
+int paste_free_name(struct paste_store *, const char *);
+void paste_add(struct paste_store *, char *, size_t, u_int);
+int paste_rename(struct paste_store *, const char *,
+ const char *, char **);
+int paste_set(struct paste_store *, char *, size_t, u_int,
+ const char *, char **);
char *paste_make_sample(struct paste_buffer *, int);
+void paste_make_sticky(struct paste_store *, const char *);
+void paste_make_unsticky(struct paste_store *, const char *, u_int);
void paste_send_pane(struct paste_buffer *, struct window_pane *,
const char *, int);
@@ -1883,7 +1904,7 @@ const char *key_string_lookup_key(int);
/* server.c */
extern struct clients clients;
extern struct clients dead_clients;
-extern struct paste_stack global_buffers;
+extern struct paste_store global_buffers;
int server_start(int, char *);
void server_update_socket(void);
void server_add_accept(int);
diff --git a/window-copy.c b/window-copy.c
index afa6d4d..4d8505c 100644
--- a/window-copy.c
+++ b/window-copy.c
@@ -54,11 +54,12 @@ void window_copy_update_cursor(struct window_pane *, u_int, u_int);
void window_copy_start_selection(struct window_pane *);
int window_copy_update_selection(struct window_pane *, int);
void *window_copy_get_selection(struct window_pane *, size_t *);
-void window_copy_copy_buffer(struct window_pane *, int, void *, size_t);
+void window_copy_copy_buffer(
+ struct window_pane *, const char *, void *, size_t);
void window_copy_copy_pipe(
- struct window_pane *, struct session *, int, const char *);
-void window_copy_copy_selection(struct window_pane *, int);
-void window_copy_append_selection(struct window_pane *, int);
+ struct window_pane *, struct session *, const char *, const char *);
+void window_copy_copy_selection(struct window_pane *, const char *);
+void window_copy_append_selection(struct window_pane *, const char *);
void window_copy_clear_selection(struct window_pane *);
void window_copy_copy_line(
struct window_pane *, char **, size_t *, u_int, u_int, u_int);
@@ -94,6 +95,7 @@ const struct window_mode window_copy_mode = {
enum window_copy_input_type {
WINDOW_COPY_OFF,
+ WINDOW_COPY_NAMEDBUFFER,
WINDOW_COPY_NUMERICPREFIX,
WINDOW_COPY_SEARCHUP,
WINDOW_COPY_SEARCHDOWN,
@@ -417,7 +419,7 @@ window_copy_key(struct window_pane *wp, struct session *sess, int key)
switch (cmd) {
case MODEKEYCOPY_APPENDSELECTION:
if (sess != NULL) {
- window_copy_append_selection(wp, data->numprefix);
+ window_copy_append_selection(wp, NULL);
window_pane_reset_mode(wp);
return;
}
@@ -543,7 +545,7 @@ window_copy_key(struct window_pane *wp, struct session *sess, int key)
if (sess != NULL &&
(cmd == MODEKEYCOPY_COPYLINE ||
cmd == MODEKEYCOPY_COPYENDOFLINE)) {
- window_copy_copy_selection(wp, -1);
+ window_copy_copy_selection(wp, NULL);
window_pane_reset_mode(wp);
return;
}
@@ -554,14 +556,14 @@ window_copy_key(struct window_pane *wp, struct session *sess, int key)
break;
case MODEKEYCOPY_COPYPIPE:
if (sess != NULL) {
- window_copy_copy_pipe(wp, sess, data->numprefix, arg);
+ window_copy_copy_pipe(wp, sess, NULL, arg);
window_pane_reset_mode(wp);
return;
}
break;
case MODEKEYCOPY_COPYSELECTION:
if (sess != NULL) {
- window_copy_copy_selection(wp, data->numprefix);
+ window_copy_copy_selection(wp, NULL);
window_pane_reset_mode(wp);
return;
}
@@ -676,6 +678,7 @@ window_copy_key(struct window_pane *wp, struct session *sess, int key)
case WINDOW_COPY_JUMPBACK:
case WINDOW_COPY_JUMPTOFORWARD:
case WINDOW_COPY_JUMPTOBACK:
+ case WINDOW_COPY_NAMEDBUFFER:
case WINDOW_COPY_NUMERICPREFIX:
break;
case WINDOW_COPY_SEARCHUP:
@@ -711,6 +714,11 @@ window_copy_key(struct window_pane *wp, struct session *sess, int key)
data->inputprompt = "Goto Line";
*data->inputstr = '\0';
goto input_on;
+ case MODEKEYCOPY_STARTNAMEDBUFFER:
+ data->inputtype = WINDOW_COPY_NAMEDBUFFER;
+ data->inputprompt = "Buffer";
+ *data->inputstr = '\0';
+ goto input_on;
case MODEKEYCOPY_STARTNUMBERPREFIX:
key &= KEYC_MASK_KEY;
if (key >= '0' && key <= '9') {
@@ -814,6 +822,11 @@ window_copy_key_input(struct window_pane *wp, int key)
data->searchtype = data->inputtype;
data->searchstr = xstrdup(data->inputstr);
break;
+ case WINDOW_COPY_NAMEDBUFFER:
+ window_copy_copy_selection(wp, data->inputstr);
+ *data->inputstr = '\0';
+ window_pane_reset_mode(wp);
+ return (0);
case WINDOW_COPY_GOTOLINE:
window_copy_goto_line(wp, data->inputstr);
*data->inputstr = '\0';
@@ -821,6 +834,17 @@ window_copy_key_input(struct window_pane *wp, int key)
}
data->numprefix = -1;
return (1);
+ case MODEKEYEDIT_ENTERAPPEND:
+ switch (data->inputtype) {
+ case WINDOW_COPY_NAMEDBUFFER:
+ window_copy_append_selection(wp, data->inputstr);
+ *data->inputstr = '\0';
+ window_pane_reset_mode(wp);
+ return (0);
+ default:
+ break;
+ }
+ break;
case MODEKEY_OTHER:
if (key < 32 || key > 126)
break;
@@ -918,7 +942,7 @@ reset_mode:
s->mode &= ~MODE_MOUSE_BUTTON;
s->mode |= MODE_MOUSE_STANDARD;
if (sess != NULL) {
- window_copy_copy_selection(wp, -1);
+ window_copy_copy_selection(wp, NULL);
window_pane_reset_mode(wp);
}
}
@@ -1452,10 +1476,11 @@ window_copy_get_selection(struct window_pane *wp, size_t *len)
}
void
-window_copy_copy_buffer(struct window_pane *wp, int idx, void *buf, size_t len)
+window_copy_copy_buffer(
+ struct window_pane *wp, const char *bufname, void *buf, size_t len)
{
- u_int limit;
- struct screen_write_ctx ctx;
+ u_int limit;
+ struct screen_write_ctx ctx;
if (options_get_number(&global_options, "set-clipboard")) {
screen_write_start(&ctx, wp, NULL);
@@ -1463,16 +1488,18 @@ window_copy_copy_buffer(struct window_pane *wp, int idx, void *buf, size_t len)
screen_write_stop(&ctx);
}
- if (idx == -1) {
- limit = options_get_number(&global_options, "buffer-limit");
- paste_add(&global_buffers, buf, len, limit);
- } else if (paste_replace(&global_buffers, idx, buf, len) != 0)
+ limit = options_get_number(&global_options, "buffer-limit");
+
+ if (paste_set(
+ &global_buffers, buf, len, limit, bufname, NULL) != 0)
free(buf);
+
+ return;
}
void
-window_copy_copy_pipe(
- struct window_pane *wp, struct session *sess, int idx, const char *arg)
+window_copy_copy_pipe(struct window_pane *wp, struct session *sess,
+ const char *bufname, const char *arg)
{
void *buf;
size_t len;
@@ -1486,11 +1513,11 @@ window_copy_copy_pipe(
job = job_run(arg, sess, NULL, NULL, NULL);
bufferevent_write(job->event, buf, len);
- window_copy_copy_buffer(wp, idx, buf, len);
+ window_copy_copy_buffer(wp, bufname, buf, len);
}
void
-window_copy_copy_selection(struct window_pane *wp, int idx)
+window_copy_copy_selection(struct window_pane *wp, const char *bufname)
{
void* buf;
size_t len;
@@ -1499,17 +1526,17 @@ window_copy_copy_selection(struct window_pane *wp, int idx)
if (buf == NULL)
return;
- window_copy_copy_buffer(wp, idx, buf, len);
+ window_copy_copy_buffer(wp, bufname, buf, len);
}
void
-window_copy_append_selection(struct window_pane *wp, int idx)
+window_copy_append_selection(struct window_pane *wp, const char *bufname)
{
- char *buf;
- struct paste_buffer *pb;
- size_t len;
- u_int limit;
- struct screen_write_ctx ctx;
+ char *buf;
+ struct paste_buffer *pb;
+ size_t len;
+ u_int limit;
+ struct screen_write_ctx ctx;
buf = window_copy_get_selection(wp, &len);
if (buf == NULL)
@@ -1521,16 +1548,17 @@ window_copy_append_selection(struct window_pane *wp, int idx)
screen_write_stop(&ctx);
}
- if (idx == -1)
- idx = 0;
+ limit = options_get_number(&global_options, "buffer-limit");
- if (idx == 0 && paste_get_top(&global_buffers) == NULL) {
- limit = options_get_number(&global_options, "buffer-limit");
- paste_add(&global_buffers, buf, len, limit);
- return;
+ if (bufname == NULL || *bufname == '\0') {
+ pb = paste_get_top(&global_buffers);
+ if (pb != NULL)
+ bufname = pb->name;
+ } else {
+ pb = paste_get_name(&global_buffers, bufname);
}
- pb = paste_get_index(&global_buffers, idx);
+
if (pb != NULL) {
buf = xrealloc(buf, 1, len + pb->size);
memmove(buf + pb->size, buf, len);
@@ -1538,7 +1566,8 @@ window_copy_append_selection(struct window_pane *wp, int idx)
len += pb->size;
}
- if (paste_replace(&global_buffers, idx, buf, len) != 0)
+ if (paste_set(
+ &global_buffers, buf, len, limit, bufname, NULL) != 0)
free(buf);
}
------------------------------------------------------------------------------
Start Your Social Network Today - Download eXo Platform
Build your Enterprise Intranet with eXo Platform Software
Java Based Open Source Intranet - Social, Extensible, Cloud Ready
Get Started Now And Turn Your Intranet Into A Collaboration Platform
http://p.sf.net/sfu/ExoPlatform
_______________________________________________
tmux-users mailing list
tmux-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/tmux-users