Show feedback to user when trying to quit with dirty buffers

This commit is contained in:
Igor Támara 2025-10-03 16:07:03 -05:00 committed by CJ van den Berg
parent 552417091d
commit d3e601e774
4 changed files with 58 additions and 13 deletions

29
help.md
View file

@ -22,15 +22,23 @@ ctrl+n/ctrl+p or f3/shift+f3 to jump through the matches. Press Enter
to exit find mode at the current match or Escape to return to your to exit find mode at the current match or Escape to return to your
starting point. starting point.
## Messages and logs
Messages of issues regarding tasks that are not accomplished are
reported to the log view, to open it, use ctrl+shift+p > View log. For
example, when you try to close a buffer that is not saved, or try to
exit Flow without all the buffers saved, it will be reported in the log.
## Input Modes ## Input Modes
Flow Control supports multiple input modes that may be changed Flow Control supports multiple input modes that may be changed
interactively at runtime. The current input mode (and some other interactively at runtime. The current input mode (and some other
settings) is persisted in the configuration file automatically. settings) is persisted in the configuration file automatically.
- f4 => Cycle major input modes (flow, vim, ...) - f4 => Cycle major input modes (flow, emacs, vim, helix,...)
The current input mode is displayed in the at the left side of the statusbar. The current input mode is displayed at the left side of the statusbar.
- ctrl+shift+p or alt+x => Show the command palette - ctrl+shift+p or alt+x => Show the command palette
@ -102,15 +110,26 @@ list of keybindings for this mode.
The vim modes, shown as NORMAL, INSERT or VISUAL in the status bar, The vim modes, shown as NORMAL, INSERT or VISUAL in the status bar,
follow the basic modal editing style of vim. The basics follow vim follow the basic modal editing style of vim. The basics follow vim
closely, but more advanced vim functions (e.g. macrosand registers) closely, but more advanced vim functions (e.g. macros and registers)
are not supported (yet). Keybindings from flow mode that do not conflict are not supported (yet). Keybindings from flow mode that do not conflict
with vim keybindings also work in vim mode. with vim keybindings also work in vim mode.
## Helix mode
The Helix modes, shown as NOR, INS or SEL in the status bar, follow
the basic modal editing style of Helix. The basics are being adapted
closely, more advanced functions (e.g. surround, macros,
selections, registers) are not supported (yet). Usual keybinding
with LSPs are used for tasks like 'go to definition', 'go to reference'
and 'inline documentation' featuring inline diagnostics. Keybindings
from flow mode that do not conflict with helix keybindings also work in
helix mode.
(work in progress) (work in progress)
### Mouse Commands ### Mouse Commands
Mouse commands are not rebindable and are not listed in the command palette. Mouse commands are NOT rebindable and are not listed in the command palette.
- Left Click => - Left Click =>
Clear all cursors and selections and the place cursor at the mouse pointer Clear all cursors and selections and the place cursor at the mouse pointer
@ -170,7 +189,7 @@ animation_min_lag 0
animation_max_lag 150 animation_max_lag 150
``` ```
Most of these options are fairly self explanitory. Most of these options are fairly self explanatory.
`theme`, `input_mode` and `show_whitespace` are automatically `theme`, `input_mode` and `show_whitespace` are automatically
persisted when changed interactively with keybindings. persisted when changed interactively with keybindings.

View file

@ -134,6 +134,19 @@ pub fn is_dirty(self: *const Self) bool {
return false; return false;
} }
pub fn number_of_dirties(self: *const Self) usize {
var dirties: usize = 0;
var i = self.buffers.iterator();
while (i.next()) |p| {
const buffer = p.value_ptr.*;
if (!buffer.is_ephemeral() and buffer.is_dirty()) {
dirties += 1;
}
}
return dirties;
}
pub fn is_buffer_dirty(self: *const Self, file_path: []const u8) bool { pub fn is_buffer_dirty(self: *const Self, file_path: []const u8) bool {
return if (self.buffers.get(file_path)) |buffer| buffer.is_dirty() else false; return if (self.buffers.get(file_path)) |buffer| buffer.is_dirty() else false;
} }
@ -181,18 +194,18 @@ pub fn delete_others(self: *Self, protected: *Buffer) void {
} }
pub fn close_others(self: *Self, protected: *Buffer) usize { pub fn close_others(self: *Self, protected: *Buffer) usize {
var removed: usize = 0; var remaining: usize = 0;
var i = self.buffers.iterator(); var i = self.buffers.iterator();
if (self.is_dirty()) return 0;
while (i.next()) |p| { while (i.next()) |p| {
const buffer = p.value_ptr.*; const buffer = p.value_ptr.*;
if (buffer != protected) { if (buffer != protected) {
if (self.buffers.remove(buffer.get_file_path())) if (buffer.is_ephemeral() or !buffer.is_dirty()) {
removed += 1; _ = self.buffers.remove(buffer.get_file_path());
buffer.deinit(); buffer.deinit();
} else remaining += 1;
} }
} }
return removed; return remaining;
} }
pub fn buffer_from_ref(self: *Self, buffer_ref: usize) ?*Buffer { pub fn buffer_from_ref(self: *Self, buffer_ref: usize) ?*Buffer {

View file

@ -273,7 +273,13 @@ const cmds = struct {
const Result = command.Result; const Result = command.Result;
pub fn quit(self: *Self, _: Ctx) Result { pub fn quit(self: *Self, _: Ctx) Result {
try self.check_all_not_dirty(); const logger = log.logger("buffer");
defer logger.deinit();
self.check_all_not_dirty() catch |err| {
const dirties = self.buffer_manager.number_of_dirties();
logger.print("There are {} unsaved buffer(s), use 'quit without saving' if not needed to save them", .{dirties});
return err;
};
try tp.self_pid().send("quit"); try tp.self_pid().send("quit");
} }
pub const quit_meta: Meta = .{ .description = "Quit" }; pub const quit_meta: Meta = .{ .description = "Quit" };

View file

@ -163,9 +163,16 @@ const cmds_ = struct {
pub const @"bco!_meta": Meta = .{ .description = "bco! (Close other buffers/tabs forcefully, ignoring changes)" }; pub const @"bco!_meta": Meta = .{ .description = "bco! (Close other buffers/tabs forcefully, ignoring changes)" };
pub fn bco(_: *void, _: Ctx) Result { pub fn bco(_: *void, _: Ctx) Result {
const logger = log.logger("helix-mode");
defer logger.deinit();
const mv = tui.mainview() orelse return; const mv = tui.mainview() orelse return;
const bm = tui.get_buffer_manager() orelse return; const bm = tui.get_buffer_manager() orelse return;
if (mv.get_active_buffer()) |buffer| _ = bm.close_others(buffer); if (mv.get_active_buffer()) |buffer| {
const remaining = bm.close_others(buffer);
if (remaining > 0) {
logger.print("{} unsaved buffer(s) remaining", .{remaining});
}
}
} }
pub const bco_meta: Meta = .{ .description = "bco (Close other buffers/tabs, except this one)" }; pub const bco_meta: Meta = .{ .description = "bco (Close other buffers/tabs, except this one)" };