From 2d1df86a461f9056aa138ff8ae160ba47e2112b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Sun, 9 Aug 2026 13:30:22 +0200 Subject: [PATCH] xC + frontends: merge M-! into M-a + reversed M-A Both WeeChat and irssi merge these two by prioritising highlights when you press M-a. --- NEWS | 3 ++ xA/xA.go | 50 ++++++++++++++++++------------ xC.adoc | 6 ++-- xC.c | 78 +++++++++++++++++++++-------------------------- xF.c | 31 +++++++++++++------ xM/main.swift | 34 +++++++++++++-------- xP/public/xP.js | 28 +++++++++-------- xT/xT.cpp | 42 +++++++++++++++---------- xW/xW-resources.h | 4 +-- xW/xW.cpp | 31 +++++++++++-------- xW/xW.rc | 2 +- 11 files changed, 180 insertions(+), 129 deletions(-) diff --git a/NEWS b/NEWS index 7a577de..3257af0 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,8 @@ Unreleased + * xC + all frontends: M-a now prioritizes highlights, newly added M-A looks + for buffers in reverse order, and the old M-! binding has been removed + * xF: reached near feature parity with xC and xP * xP: fixed channel title layout for narrow browser windows diff --git a/xA/xA.go b/xA/xA.go index c211a08..596dd78 100644 --- a/xA/xA.go +++ b/xA/xA.go @@ -1420,7 +1420,7 @@ func (l *customLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// rotatedBuffers returns buffer indexes starting with the current buffer. +// rotatedBuffers returns buffer indexes starting after the current buffer. func rotatedBuffers() []int { r, start := make([]int, len(buffers)), 0 for i := range buffers { @@ -1458,6 +1458,29 @@ func actionBufferLast() { } } +func actionBufferActivity(backwards bool) { + r := rotatedBuffers() + if len(r) == 0 { + return + } + r = r[:len(r)-1] + if backwards { + slices.Reverse(r) + } + for _, i := range r { + if buffers[i].highlighted { + bufferActivate(buffers[i].bufferName) + return + } + } + for _, i := range r { + if buffers[i].newMessages != 0 { + bufferActivate(buffers[i].bufferName) + return + } + } +} + var shortcuts = map[desktop.CustomShortcut]func(){ { KeyName: fyne.KeyPageUp, @@ -1487,28 +1510,17 @@ var shortcuts = map[desktop.CustomShortcut]func(){ KeyName: fyne.KeyTab, Modifier: fyne.KeyModifierAlt, }: actionBufferLast, - { - // XXX: This makes an assumption on the keyboard layout (we want '!'). - KeyName: fyne.Key1, - Modifier: fyne.KeyModifierAlt | fyne.KeyModifierShift, - }: func() { - for _, i := range rotatedBuffers() { - if buffers[i].highlighted { - bufferActivate(buffers[i].bufferName) - break - } - } - }, { KeyName: fyne.KeyA, Modifier: fyne.KeyModifierAlt, }: func() { - for _, i := range rotatedBuffers() { - if buffers[i].newMessages != 0 { - bufferActivate(buffers[i].bufferName) - break - } - } + actionBufferActivity(false) + }, + { + KeyName: fyne.KeyA, + Modifier: fyne.KeyModifierAlt | fyne.KeyModifierShift, + }: func() { + actionBufferActivity(true) }, { KeyName: fyne.KeyH, diff --git a/xC.adoc b/xC.adoc index 23ff37e..68a8d33 100644 --- a/xC.adoc +++ b/xC.adoc @@ -56,10 +56,10 @@ This is a list of all local specialties and their respective function names: *M-0*, *M-1*, ..., *M-9*: *goto-buffer*:: Go to the N-th buffer (normally sets a repeat counter). Since there is no buffer number zero, *M-0* goes to the tenth one. -*M-!*: *goto-highlight*:: - Go to the first following buffer with an unseen highlight. *M-a*: *goto-activity*:: - Go to the first following buffer with unseen activity. +*M-A*: *goto-backtivity*:: + Go to the first following/preceding buffer with an unseen highlight, + or, if there are none, with unseen activity. *PageUp*: *display-backlog*:: Show the in-memory backlog for this buffer using *general.pager*, which is almost certainly the *less*(1) program. diff --git a/xC.c b/xC.c index 6e84d91..d3a9012 100644 --- a/xC.c +++ b/xC.c @@ -5105,23 +5105,20 @@ buffer_at_index (struct app_context *ctx, int n) } static struct buffer * -buffer_next (struct app_context *ctx, int count) +buffer_after (struct app_context *ctx, struct buffer *iter, bool backwards) { - struct buffer *new_buffer = ctx->current_buffer; - while (count-- > 0) - if (!(new_buffer = new_buffer->next)) - new_buffer = ctx->buffers; - return new_buffer; + return backwards + ? (iter->prev ? iter->prev : ctx->buffers_tail) + : (iter->next ? iter->next : ctx->buffers); } static struct buffer * -buffer_previous (struct app_context *ctx, int count) +buffer_step (struct app_context *ctx, int count, bool backwards) { - struct buffer *new_buffer = ctx->current_buffer; + struct buffer *iter = ctx->current_buffer; while (count-- > 0) - if (!(new_buffer = new_buffer->prev)) - new_buffer = ctx->buffers_tail; - return new_buffer; + iter = buffer_after (ctx, iter, backwards); + return iter; } static bool @@ -5177,7 +5174,7 @@ buffer_remove_safe (struct app_context *ctx, struct buffer *buffer) if (buffer == ctx->current_buffer) buffer_activate (ctx, ctx->last_buffer ? ctx->last_buffer - : buffer_next (ctx, 1)); + : buffer_step (ctx, 1, false)); buffer_remove (ctx, buffer); } @@ -14283,7 +14280,7 @@ static bool on_previous_buffer (int key, void *user_data) { (void) key; - buffer_activate (user_data, buffer_previous (user_data, 1)); + buffer_activate (user_data, buffer_step (user_data, 1, true)); return true; } @@ -14291,7 +14288,7 @@ static bool on_next_buffer (int key, void *user_data) { (void) key; - buffer_activate (user_data, buffer_next (user_data, 1)); + buffer_activate (user_data, buffer_step (user_data, 1, false)); return true; } @@ -14308,41 +14305,36 @@ on_switch_buffer (int key, void *user_data) } static bool -on_goto_highlight (int key, void *user_data) +goto_activity (struct app_context *ctx, bool backwards) { - (void) key; - - struct app_context *ctx = user_data; - struct buffer *iter = ctx->current_buffer;; - do - { - if (!(iter = iter->next)) - iter = ctx->buffers; - if (iter == ctx->current_buffer) - return false; - } - while (!iter->highlighted); - buffer_activate (ctx, iter); - return true; + struct buffer *iter = ctx->current_buffer, *stop = iter; + while ((iter = buffer_after (ctx, iter, backwards)) != stop) + if (iter->highlighted) + { + buffer_activate (ctx, iter); + return true; + } + while ((iter = buffer_after (ctx, iter, backwards)) != stop) + if (iter->new_messages_count != iter->new_unimportant_count) + { + buffer_activate (ctx, iter); + return true; + } + return false; } static bool on_goto_activity (int key, void *user_data) { (void) key; + return goto_activity (user_data, false); +} - struct app_context *ctx = user_data; - struct buffer *iter = ctx->current_buffer; - do - { - if (!(iter = iter->next)) - iter = ctx->buffers; - if (iter == ctx->current_buffer) - return false; - } - while (iter->new_messages_count == iter->new_unimportant_count); - buffer_activate (ctx, iter); - return true; +static bool +on_goto_backtivity (int key, void *user_data) +{ + (void) key; + return goto_activity (user_data, true); } static bool @@ -14610,8 +14602,8 @@ app_input_init (struct input *self) XX ("next-buffer", "Next buffer", on_next_buffer) XX ("goto-buffer", "Go to buffer", on_goto_buffer) XX ("switch-buffer", "Switch buffer", on_switch_buffer) - XX ("goto-highlight", "Go to highlight", on_goto_highlight) XX ("goto-activity", "Go to activity", on_goto_activity) + XX ("goto-backtivity", "Go to activity", on_goto_backtivity) XX ("move-buffer-left", "Move buffer left", on_move_buffer_left) XX ("move-buffer-right", "Move buffer right", on_move_buffer_right) XX ("display-backlog", "Show backlog", on_display_backlog) @@ -14638,8 +14630,8 @@ app_input_init (struct input *self) input_bind (self, "M-Tab", "switch-buffer"); input_bind (self, "M-/", "switch-buffer"); - input_bind (self, "M-!", "goto-highlight"); input_bind (self, "M-a", "goto-activity"); + input_bind (self, "M-A", "goto-backtivity"); input_bind (self, "M-m", "insert-attribute"); input_bind (self, "M-h", "display-full-log"); input_bind (self, "M-H", "toggle-unimportant"); diff --git a/xF.c b/xF.c index 1e1b085..69450e4 100644 --- a/xF.c +++ b/xF.c @@ -258,8 +258,8 @@ enum action ACTION_BUFFER_PREVIOUS, ACTION_BUFFER_NEXT, ACTION_BUFFER_LAST, - ACTION_BUFFER_HIGHLIGHT, ACTION_BUFFER_ACTIVITY, + ACTION_BUFFER_BACKTIVITY, ACTION_BUFFER_1, ACTION_BUFFER_2, ACTION_BUFFER_3, @@ -3376,16 +3376,29 @@ app_switch_buffer (int direction) app_send_buffer_activate (next); } +static struct buffer * +buffer_after (struct buffer *b, bool backwards) +{ + return backwards + ? (b->prev ? b->prev : g.buffers_tail) + : (b->next ? b->next : g.buffers); +} + static bool -app_switch_buffer_matching (bool highlighted) +app_goto_activity (bool backwards) { if (!g.buffers || !g.buffer_current) return false; struct buffer *b = g.buffer_current, *stop = b; - while ((b = b->next ? b->next : g.buffers) != stop) - if ((highlighted && b->highlighted) - || (!highlighted && b->new_messages)) + while ((b = buffer_after (b, backwards)) != stop) + if (b->highlighted) + { + app_send_buffer_activate (b); + return true; + } + while ((b = buffer_after (b, backwards)) != stop) + if (b->new_messages) { app_send_buffer_activate (b); return true; @@ -3832,10 +3845,10 @@ app_process_action (enum action action) return false; app_send_buffer_activate (g.buffer_last); return true; - case ACTION_BUFFER_HIGHLIGHT: - return app_switch_buffer_matching (true); case ACTION_BUFFER_ACTIVITY: - return app_switch_buffer_matching (false); + return app_goto_activity (false); + case ACTION_BUFFER_BACKTIVITY: + return app_goto_activity (true); case ACTION_BUFFER_1: case ACTION_BUFFER_2: case ACTION_BUFFER_3: @@ -4025,8 +4038,8 @@ g_default_bindings[] = { "F6", ACTION_BUFFER_NEXT }, // WeeChat { "M-Tab", ACTION_BUFFER_LAST }, // xC { "M-/", ACTION_BUFFER_LAST }, // WeeChat - { "M-!", ACTION_BUFFER_HIGHLIGHT }, // xC { "M-a", ACTION_BUFFER_ACTIVITY }, // xC + { "M-A", ACTION_BUFFER_BACKTIVITY }, // xC { "M-H", ACTION_TOGGLE_UNIMPORTANT }, // xC { "M-h", ACTION_LOG }, // xC { "M-e", ACTION_EDIT_INPUT }, // xC diff --git a/xM/main.swift b/xM/main.swift index be8ca0e..71183ad 100644 --- a/xM/main.swift +++ b/xM/main.swift @@ -1210,7 +1210,7 @@ class WindowDelegate: NSObject, NSWindowDelegate { refreshBufferList() } - // Buffer indexes rotated to start after the current buffer. + // Buffers rotated to start after the current buffer. func rotatedBuffers() -> Array { guard let i = relayBuffers.firstIndex( where: { $0.bufferName == relayBufferCurrent }) else { @@ -1239,22 +1239,32 @@ class WindowDelegate: NSObject, NSWindowDelegate { } } - @objc func actionGotoHighlight() { - for b in rotatedBuffers() { + func gotoActivity(backwards: Bool) { + var candidates = rotatedBuffers() + candidates.popLast() + if backwards { + candidates.reverse() + } + for b in candidates { if b.highlighted { bufferActivate(name: b.bufferName) return } } + for b in candidates { + if b.newMessages != 0 { + bufferActivate(name: b.bufferName) + return + } + } } @objc func actionGotoActivity() { - for b in rotatedBuffers() { - if b.newMessages != 0 { - bufferActivate(name: b.bufferName) - return - } - } + gotoActivity(backwards: false) + } + + @objc func actionGotoBacktivity() { + gotoActivity(backwards: true) } @objc func actionToggleUnimportant() { @@ -1314,12 +1324,12 @@ pushAccelerator("Switch buffer", // TODO(p): Remove .command, and ignore these with the right Option key. bufferMenu.addItem(NSMenuItem.separator()) -pushAccelerator("Go to highlight", - #selector(WindowDelegate.actionGotoHighlight), - "!", [.command, .option]) pushAccelerator("Go to activity", #selector(WindowDelegate.actionGotoActivity), "a", [.command, .option]) +pushAccelerator("Go to activity backwards", + #selector(WindowDelegate.actionGotoBacktivity), + "a", [.shift, .command, .option]) bufferMenu.addItem(NSMenuItem.separator()) pushAccelerator("Toggle unimportant", diff --git a/xP/public/xP.js b/xP/public/xP.js index c3213a1..21fc8a6 100644 --- a/xP/public/xP.js +++ b/xP/public/xP.js @@ -1234,10 +1234,22 @@ document.addEventListener('keydown', event => { return // Rotate names so that the current buffer comes first. - let names = [...buffers.keys()] + const names = [...buffers.keys()] names.push.apply(names, names.splice(0, names.findIndex(name => name == bufferCurrent))) + const gotoActivity = backwards => { + const candidates = names.slice(1) + if (backwards) + candidates.reverse() + for (const name of candidates) + if (buffers.get(name).highlighted) + return bufferActivate(name) + for (const name of candidates) + if (buffers.get(name).newMessages) + return bufferActivate(name) + } + switch (event.key) { case 'h': bufferToggleLog() @@ -1247,18 +1259,10 @@ document.addEventListener('keydown', event => { bufferToggleUnimportant(bufferCurrent) break case 'a': - for (const name of names.slice(1)) - if (buffers.get(name).newMessages) { - bufferActivate(name) - break - } + gotoActivity(false) break - case '!': - for (const name of names.slice(1)) - if (buffers.get(name).highlighted) { - bufferActivate(name) - break - } + case 'A': + gotoActivity(true) break case 'Tab': case '/': diff --git a/xT/xT.cpp b/xT/xT.cpp index f5e2751..57936b9 100644 --- a/xT/xT.cpp +++ b/xT/xT.cpp @@ -19,6 +19,7 @@ #include "xC-proto.cpp" #include "config.h" +#include #include #include #include @@ -1622,6 +1623,22 @@ rotated_buffers() return rotated; } +static void +goto_activity_bidirectional(bool backwards) +{ + auto candidates = rotated_buffers(); + if (!candidates.empty()) + candidates.pop_back(); + if (backwards) + std::reverse(candidates.begin(), candidates.end()); + for (auto i : candidates) + if (auto &b = g.buffers[i]; b.highlighted) + return buffer_activate(b.buffer_name); + for (auto i : candidates) + if (auto &b = g.buffers[i]; b.new_messages) + return buffer_activate(b.buffer_name); +} + static void bind_shortcuts() { @@ -1641,24 +1658,16 @@ bind_shortcuts() if (auto b = buffer_by_name(g.buffer_last)) buffer_activate(b->buffer_name); }; - auto goto_highlight = [] { - for (auto i : rotated_buffers()) - if (g.buffers[i].highlighted) { - buffer_activate(g.buffers[i].buffer_name); - break; - } - }; - auto goto_activity = [] { - for (auto i : rotated_buffers()) - if (g.buffers[i].new_messages) { - buffer_activate(g.buffers[i].buffer_name); - break; - } - }; auto toggle_unimportant = [] { if (auto b = buffer_by_name(g.buffer_current)) buffer_toggle_unimportant(b->buffer_name); }; + auto goto_activity = [] { + goto_activity_bidirectional(false); + }; + auto goto_backtivity = [] { + goto_activity_bidirectional(true); + }; new QShortcut(QKeyCombination(Qt::ControlModifier, Qt::Key_Tab), g.wMain, switch_buffer); @@ -1682,8 +1691,9 @@ bind_shortcuts() new QShortcut(QKeyCombination(Qt::AltModifier, Qt::Key_A), g.wMain, goto_activity); - new QShortcut(QKeyCombination(Qt::AltModifier, Qt::Key_Exclam), - g.wMain, goto_highlight); + new QShortcut( + QKeyCombination(Qt::AltModifier | Qt::ShiftModifier, Qt::Key_A), + g.wMain, goto_backtivity); new QShortcut(QKeyCombination(Qt::AltModifier, Qt::Key_H), g.wMain, toggle_unimportant); } diff --git a/xW/xW-resources.h b/xW/xW-resources.h index 55d5590..1bba617 100644 --- a/xW/xW-resources.h +++ b/xW/xW-resources.h @@ -7,8 +7,8 @@ #define ID_PREVIOUS_BUFFER 11 #define ID_NEXT_BUFFER 12 #define ID_SWITCH_BUFFER 13 -#define ID_GOTO_HIGHLIGHT 14 -#define ID_GOTO_ACTIVITY 15 +#define ID_GOTO_ACTIVITY 14 +#define ID_GOTO_BACKTIVITY 15 #define ID_TOGGLE_UNIMPORTANT 16 #define ID_DISPLAY_FULL_LOG 17 diff --git a/xW/xW.cpp b/xW/xW.cpp index d5e541d..707cb92 100644 --- a/xW/xW.cpp +++ b/xW/xW.cpp @@ -1615,6 +1615,21 @@ process_bufferlist_notification(WORD code) } } +static void +goto_activity(std::vector candidates, bool backwards) +{ + if (!candidates.empty()) + candidates.pop_back(); + if (backwards) + std::reverse(candidates.begin(), candidates.end()); + for (auto i : candidates) + if (auto &b = g.buffers[i]; b.highlighted) + return buffer_activate(b.buffer_name); + for (auto i : candidates) + if (auto &b = g.buffers[i]; b.new_messages) + return buffer_activate(b.buffer_name); +} + static void process_accelerator(WORD id) { @@ -1645,19 +1660,11 @@ process_accelerator(WORD id) if (!g.buffer_last.empty()) buffer_activate(g.buffer_last); return; - case ID_GOTO_HIGHLIGHT: - for (auto i : rotated) - if (g.buffers[i].highlighted) { - buffer_activate(g.buffers[i].buffer_name); - break; - } - return; case ID_GOTO_ACTIVITY: - for (auto i : rotated) - if (g.buffers[i].new_messages) { - buffer_activate(g.buffers[i].buffer_name); - break; - } + goto_activity(rotated, false); + return; + case ID_GOTO_BACKTIVITY: + goto_activity(rotated, true); return; case ID_TOGGLE_UNIMPORTANT: if (b) diff --git a/xW/xW.rc b/xW/xW.rc index 34d956c..7c91bb0 100644 --- a/xW/xW.rc +++ b/xW/xW.rc @@ -25,8 +25,8 @@ BEGIN VK_TAB, ID_SWITCH_BUFFER, CONTROL, VIRTKEY // These are proper, but llvm-rc won't accept them (GitHub #64002). #ifndef __clang__ - "!", ID_GOTO_HIGHLIGHT, ALT "a", ID_GOTO_ACTIVITY, ALT + "A", ID_GOTO_BACKTIVITY, ALT "H", ID_TOGGLE_UNIMPORTANT, ALT "h", ID_DISPLAY_FULL_LOG, ALT #endif