Compare commits
2 Commits
064102b786
...
800a4e354c
| Author | SHA1 | Date | |
|---|---|---|---|
|
800a4e354c
|
|||
|
9f1ef95f46
|
@@ -289,6 +289,7 @@ enum action
|
||||
ACTION_BUFFER_LIST_SCROLL_BOTTOM,
|
||||
ACTION_SUBMIT,
|
||||
ACTION_COMPLETE,
|
||||
ACTION_COMPLETE_BACKWARD,
|
||||
ACTION_HISTORY_PREVIOUS,
|
||||
ACTION_HISTORY_NEXT,
|
||||
ACTION_HISTORY_FIRST,
|
||||
@@ -421,7 +422,8 @@ struct log_view
|
||||
char *buffer_name; ///< Stamp: the buffer we want logs for
|
||||
|
||||
bool visible; ///< We see the log rather than backlog
|
||||
ARRAY (struct buffer_line *, lines) ///< Log split into display lines
|
||||
struct buffer_line **lines; ///< Log split into display lines
|
||||
size_t lines_len; ///< Number of lines
|
||||
int offset; ///< XUI pixels above the bottom
|
||||
};
|
||||
|
||||
@@ -499,10 +501,17 @@ static struct
|
||||
struct poller_timer date_change_event;
|
||||
|
||||
struct line_editor editor; ///< Message editor
|
||||
uint64_t editor_revision; ///< Changes with contents or caret
|
||||
char *editor_running_for; ///< Buffer name being edited externally
|
||||
char *editor_filename; ///< File being edited by the user
|
||||
bool editor_shows_transcript; ///< External editor shows transcript
|
||||
|
||||
struct strv completions; ///< Stem followed by possible matches
|
||||
size_t completions_at; ///< Currently selected completion
|
||||
uint32_t completions_start; ///< Start of replacement range
|
||||
uint32_t completions_end; ///< End of replacement range
|
||||
uint64_t completions_revision; ///< Request/menu editor revision stamp
|
||||
|
||||
struct binding *keys; ///< Parsed xC-style key bindings
|
||||
size_t keys_len;
|
||||
|
||||
@@ -872,6 +881,7 @@ app_on_date_change_timer (void *user_data)
|
||||
static void
|
||||
app_editor_changed (void)
|
||||
{
|
||||
g.editor_revision++;
|
||||
xui_invalidate ();
|
||||
}
|
||||
|
||||
@@ -922,18 +932,29 @@ app_editor_text (void)
|
||||
static void
|
||||
app_editor_set (const char *text)
|
||||
{
|
||||
line_editor_free (&g.editor);
|
||||
app_editor_free ();
|
||||
memset (&g.editor, 0, sizeof g.editor);
|
||||
line_editor_start (&g.editor, 0);
|
||||
app_editor_init ();
|
||||
app_editor_changed ();
|
||||
if (!text)
|
||||
return;
|
||||
|
||||
const char *input = text ? text : "";
|
||||
struct utf8_iter iter = { .s = input, .len = strlen (input) };
|
||||
struct utf8_iter iter = utf8_iter_make (text);
|
||||
int32_t codepoint = 0;
|
||||
size_t codepoint_len = 0;
|
||||
while ((codepoint = utf8_iter_next (&iter, &codepoint_len)) >= 0)
|
||||
app_editor_insert (codepoint);
|
||||
g.editor.on_changed = app_editor_changed;
|
||||
xui_invalidate ();
|
||||
}
|
||||
|
||||
static void
|
||||
app_editor_set_at (const char *text, size_t position)
|
||||
{
|
||||
app_editor_set (text);
|
||||
g.editor.point = 0;
|
||||
|
||||
struct utf8_iter iter = { .s = text, .len = position };
|
||||
while (utf8_iter_next (&iter, NULL) >= 0)
|
||||
g.editor.point++;
|
||||
}
|
||||
|
||||
// --- Relay -------------------------------------------------------------------
|
||||
@@ -986,6 +1007,81 @@ relay_send (struct relay_command_message *m, uint32_t *command_seq)
|
||||
|
||||
// ~~~ Completion request ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
static void
|
||||
completion_menu_reset (void)
|
||||
{
|
||||
strv_free (&g.completions);
|
||||
g.completions = (struct strv) {};
|
||||
}
|
||||
|
||||
static size_t
|
||||
completion_replace (const char *current,
|
||||
size_t start, size_t end, const char *word, bool append_space)
|
||||
{
|
||||
struct str completed = str_make ();
|
||||
str_append_data (&completed, current, start);
|
||||
str_append (&completed, word);
|
||||
if (append_space)
|
||||
str_append_c (&completed, ' ');
|
||||
size_t caret = completed.len;
|
||||
str_append (&completed, current + end);
|
||||
app_editor_set_at (completed.str, caret);
|
||||
str_free (&completed);
|
||||
return caret;
|
||||
}
|
||||
|
||||
static void
|
||||
completion_menu_apply (size_t word)
|
||||
{
|
||||
hard_assert (word < g.completions.len);
|
||||
|
||||
char *current = app_editor_text ();
|
||||
g.completions_at = word;
|
||||
g.completions_end = completion_replace (current,
|
||||
g.completions_start, g.completions_end,
|
||||
g.completions.vector[word], !!word);
|
||||
g.completions_revision = g.editor_revision;
|
||||
free (current);
|
||||
}
|
||||
|
||||
static bool
|
||||
completion_menu_cycle (bool backwards)
|
||||
{
|
||||
if (g.completions.len < 2 || g.completions_revision != g.editor_revision)
|
||||
return false;
|
||||
|
||||
completion_menu_apply (backwards
|
||||
? (g.completions_at ? g.completions_at - 1 : g.completions.len - 1)
|
||||
: (g.completions_at + 1) % g.completions.len);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
completion_apply_response (const char *current, uint32_t start, uint32_t end,
|
||||
const struct str *completions, uint32_t completions_len)
|
||||
{
|
||||
completion_menu_reset ();
|
||||
if (start > end || end > strlen (current) || !completions_len)
|
||||
return false;
|
||||
|
||||
if (completions_len == 1)
|
||||
{
|
||||
completion_replace (current, start, end, completions[0].str, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
g.completions_start = start;
|
||||
g.completions_end = end;
|
||||
g.completions = strv_make ();
|
||||
for (uint32_t i = 0; i < completions_len; i++)
|
||||
strv_append (&g.completions, completions[i].str);
|
||||
|
||||
completion_menu_apply (0);
|
||||
return false;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static void
|
||||
completion_request_start (struct completion_request *self, uint32_t request_seq,
|
||||
char *text_owned, const char *buffer_name, uint32_t position)
|
||||
@@ -1001,11 +1097,9 @@ completion_request_start (struct completion_request *self, uint32_t request_seq,
|
||||
static void
|
||||
completion_request_reset (struct completion_request *self)
|
||||
{
|
||||
self->pending = false;
|
||||
self->request_seq = 0;
|
||||
self->position = 0;
|
||||
cstr_set (&self->text, NULL);
|
||||
cstr_set (&self->buffer_name, NULL);
|
||||
free (self->text);
|
||||
free (self->buffer_name);
|
||||
memset (self, 0, sizeof *self);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -1013,39 +1107,26 @@ app_process_completion_response
|
||||
(const struct relay_response_data_buffer_complete *response)
|
||||
{
|
||||
char *current = app_editor_text ();
|
||||
bool current_request = g.buffer_current
|
||||
&& g.completion.buffer_name
|
||||
&& !strcmp (g.buffer_current->buffer_name, g.completion.buffer_name)
|
||||
&& !strcmp (current, g.completion.text)
|
||||
&& app_editor_position () == g.completion.position;
|
||||
|
||||
if (current_request
|
||||
&& response->start <= g.completion.position
|
||||
&& g.completion.position <= strlen (current))
|
||||
if (g.buffer_current && g.completion.buffer_name
|
||||
&& !strcmp (g.buffer_current->buffer_name, g.completion.buffer_name)
|
||||
&& !strcmp (current, g.completion.text)
|
||||
&& app_editor_position () == g.completion.position
|
||||
&& g.completions_revision == g.editor_revision)
|
||||
{
|
||||
if (response->completions_len)
|
||||
{
|
||||
struct str completed = str_make ();
|
||||
str_append_data (&completed, current, response->start);
|
||||
str_append_str (&completed, &response->completions[0]);
|
||||
if (response->completions_len == 1)
|
||||
str_append_c (&completed, ' ');
|
||||
str_append (&completed, current + g.completion.position);
|
||||
app_editor_set (completed.str);
|
||||
str_free (&completed);
|
||||
}
|
||||
if (response->completions_len != 1)
|
||||
if (!completion_apply_response (current,
|
||||
response->start, g.completion.position,
|
||||
response->completions, response->completions_len))
|
||||
g_xui.ui->beep ();
|
||||
}
|
||||
else if (current_request)
|
||||
g_xui.ui->beep ();
|
||||
|
||||
free (current);
|
||||
completion_request_reset (&g.completion);
|
||||
}
|
||||
|
||||
// ~~~ Log request ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
static bool app_launch_external_editor (const char *template_name,
|
||||
const char *contents, size_t contents_len, bool viewing_transcript);
|
||||
|
||||
static void
|
||||
log_view_request_start (struct log_view *self, uint32_t request_seq,
|
||||
const char *buffer_name)
|
||||
@@ -1072,7 +1153,7 @@ log_view_hide (struct log_view *self)
|
||||
buffer_line_destroy (self->lines[i]);
|
||||
free (self->lines);
|
||||
self->lines = NULL;
|
||||
self->lines_len = self->lines_alloc = 0;
|
||||
self->lines_len = 0;
|
||||
self->offset = 0;
|
||||
}
|
||||
|
||||
@@ -1084,19 +1165,18 @@ log_view_free (struct log_view *self)
|
||||
}
|
||||
|
||||
static void
|
||||
log_view_show (struct log_view *self, char *text_owned)
|
||||
log_view_show (struct log_view *self, const char *text)
|
||||
{
|
||||
log_view_hide (self);
|
||||
|
||||
struct strv lines = strv_make ();
|
||||
cstr_split (text_owned, "\r\n", true, &lines);
|
||||
free (text_owned);
|
||||
ARRAY_INIT (self->lines);
|
||||
cstr_split (text, "\r\n", true, &lines);
|
||||
self->lines = xcalloc (lines.len, sizeof *self->lines);
|
||||
for (size_t i = 0; i < lines.len; i++)
|
||||
{
|
||||
struct buffer_line *line = xcalloc (1, sizeof *line);
|
||||
buffer_line_add_item (line, lines.vector[i], -1, -1, 0);
|
||||
buffer_line_finalize (line);
|
||||
ARRAY_RESERVE (self->lines, 1);
|
||||
self->lines[self->lines_len++] = line;
|
||||
}
|
||||
strv_free (&lines);
|
||||
@@ -1109,10 +1189,15 @@ app_process_log_response (const struct relay_response_data_buffer_log *response)
|
||||
if (g.buffer_current && g.log.buffer_name
|
||||
&& !strcmp (g.buffer_current->buffer_name, g.log.buffer_name))
|
||||
{
|
||||
log_view_show (&g.log,
|
||||
utf8_validate ((const char *) response->log, response->log_len)
|
||||
? xstrndup ((const char *) response->log, response->log_len)
|
||||
: xstrdup ("Log is not valid UTF-8."));
|
||||
// We're not that performant with layouting.
|
||||
const char *log = (const char *) response->log;
|
||||
if (response->log_len > 1 << 20)
|
||||
app_launch_external_editor
|
||||
("log.XXXXXX.txt", log, response->log_len, true);
|
||||
else if (!utf8_validate (log, response->log_len))
|
||||
print_error ("log is not valid UTF-8");
|
||||
else
|
||||
log_view_show (&g.log, log);
|
||||
}
|
||||
log_view_request_reset (&g.log);
|
||||
g.log.offset = 0;
|
||||
@@ -1179,6 +1264,7 @@ app_reset_relay_state (void)
|
||||
|
||||
log_view_hide (&g.log);
|
||||
app_editor_set ("");
|
||||
completion_menu_reset ();
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -3400,9 +3486,15 @@ app_switch_buffer_number (int number)
|
||||
}
|
||||
|
||||
static bool
|
||||
app_complete (void)
|
||||
app_complete (bool backwards)
|
||||
{
|
||||
if (!g.buffer_current || g.completion.pending)
|
||||
if (g.completion.pending)
|
||||
return false;
|
||||
if (completion_menu_cycle (backwards))
|
||||
return true;
|
||||
|
||||
completion_menu_reset ();
|
||||
if (!g.buffer_current)
|
||||
return false;
|
||||
|
||||
char *text = app_editor_text ();
|
||||
@@ -3429,6 +3521,7 @@ app_complete (void)
|
||||
|
||||
completion_request_start (&g.completion, request_seq, text,
|
||||
g.buffer_current->buffer_name, position);
|
||||
g.completions_revision = g.editor_revision;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3711,42 +3804,25 @@ transcript_text_visit (enum transcript_visit_kind kind,
|
||||
}
|
||||
|
||||
static bool
|
||||
app_launch_external_editor (bool view_transcript)
|
||||
app_launch_external_editor (const char *template_name,
|
||||
const char *contents, size_t contents_len, bool viewing_transcript)
|
||||
{
|
||||
if (g.editor_running_for || !g.buffer_current)
|
||||
return false;
|
||||
|
||||
char *contents = NULL;
|
||||
size_t contents_len = 0;
|
||||
const char *template_name = "input.XXXXXX.txt";
|
||||
if (view_transcript)
|
||||
{
|
||||
struct str text = str_make ();
|
||||
app_walk_transcript (time (NULL), transcript_text_visit, &text);
|
||||
contents_len = text.len;
|
||||
contents = str_steal (&text);
|
||||
template_name = "transcript.XXXXXX.txt";
|
||||
}
|
||||
else
|
||||
{
|
||||
contents = app_editor_text ();
|
||||
contents_len = strlen (contents);
|
||||
}
|
||||
|
||||
struct error *err = NULL;
|
||||
char *filename = app_external_editor_make_file (template_name,
|
||||
contents, contents_len, &err);
|
||||
free (contents);
|
||||
if (!filename)
|
||||
{
|
||||
print_error ("failed to prepare input for editing: %s", err->message);
|
||||
print_error ("failed to prepare editor contents: %s", err->message);
|
||||
error_free (err);
|
||||
return false;
|
||||
}
|
||||
|
||||
g.editor_running_for = xstrdup (g.buffer_current->buffer_name);
|
||||
g.editor_filename = filename;
|
||||
g.editor_shows_transcript = view_transcript;
|
||||
g.editor_shows_transcript = viewing_transcript;
|
||||
xui_invalidate ();
|
||||
|
||||
if (!app_external_editor_launch (&err))
|
||||
@@ -3759,6 +3835,27 @@ app_launch_external_editor (bool view_transcript)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
app_launch_input_editor (void)
|
||||
{
|
||||
char *contents = app_editor_text ();
|
||||
bool result = app_launch_external_editor ("input.XXXXXX.txt",
|
||||
contents, strlen (contents), false);
|
||||
free (contents);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool
|
||||
app_launch_transcript_editor (void)
|
||||
{
|
||||
struct str text = str_make ();
|
||||
app_walk_transcript (time (NULL), transcript_text_visit, &text);
|
||||
bool result = app_launch_external_editor ("transcript.XXXXXX.txt",
|
||||
text.str, text.len, true);
|
||||
str_free (&text);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ~~~ Action and mouse dispatch ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
static const enum line_editor_action g_editor_actions[] =
|
||||
@@ -3791,7 +3888,11 @@ app_process_editor_action (enum action action)
|
||||
enum line_editor_action editor_action = g_editor_actions[index];
|
||||
bool handled = line_editor_action (&g.editor, editor_action);
|
||||
if (handled)
|
||||
{
|
||||
// Motions do not invoke the editor's change callback.
|
||||
g.editor_revision++;
|
||||
xui_invalidate ();
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
@@ -3853,9 +3954,9 @@ app_process_action (enum action action)
|
||||
case ACTION_LOG:
|
||||
return app_toggle_log ();
|
||||
case ACTION_EDIT_INPUT:
|
||||
return app_launch_external_editor (false);
|
||||
return app_launch_input_editor ();
|
||||
case ACTION_VIEW_TRANSCRIPT:
|
||||
return app_launch_external_editor (true);
|
||||
return app_launch_transcript_editor ();
|
||||
case ACTION_OPEN:
|
||||
return app_open_last_link ();
|
||||
case ACTION_FORMAT:
|
||||
@@ -3890,7 +3991,9 @@ app_process_action (enum action action)
|
||||
app_submit_input ();
|
||||
return true;
|
||||
case ACTION_COMPLETE:
|
||||
return app_complete ();
|
||||
return app_complete (false);
|
||||
case ACTION_COMPLETE_BACKWARD:
|
||||
return app_complete (true);
|
||||
case ACTION_HISTORY_PREVIOUS:
|
||||
return app_history (-1);
|
||||
case ACTION_HISTORY_NEXT:
|
||||
@@ -4045,6 +4148,7 @@ g_default_bindings[] =
|
||||
{ "Enter", ACTION_SUBMIT },
|
||||
{ "C-j", ACTION_SUBMIT },
|
||||
{ "Tab", ACTION_COMPLETE }, // Readline
|
||||
{ "S-Tab", ACTION_COMPLETE_BACKWARD },
|
||||
{ "M-p", ACTION_HISTORY_PREVIOUS }, // xP/xC
|
||||
{ "Up", ACTION_HISTORY_PREVIOUS }, // Readline
|
||||
{ "M-n", ACTION_HISTORY_NEXT }, // xP/xC
|
||||
|
||||
Reference in New Issue
Block a user