Compare commits
10 commits
934cfcd9e2
...
44356e5d30
| Author | SHA1 | Date | |
|---|---|---|---|
| 44356e5d30 | |||
| c9dc713537 | |||
| 6312d44d15 | |||
| 70b60a15fa | |||
| 1317de3f72 | |||
| 377b8ba6b3 | |||
| cdfa463ce7 | |||
| 7b6e75cc9d | |||
| 919c87e988 | |||
| f55885c48e |
8 changed files with 171 additions and 94 deletions
98
README.md
98
README.md
|
|
@ -8,13 +8,14 @@ and is my daily driver for almost everything.
|
|||
|
||||
# Features
|
||||
|
||||
- **Lightning Fast** TUI with ≤6ms frame times, **low latency** input handling
|
||||
and smooth **animated scrolling**
|
||||
- **Lightning Fast** TUI with ≤6ms frame times, **low latency** input
|
||||
handling and smooth **animated scrolling**
|
||||
- Intuitive UI with **tabs**, **scrollbars** and **palettes** with full
|
||||
**mouse** support for all UI elements
|
||||
- Support for more than **70 programming languages**, **zero
|
||||
configuration** needed, via **tree-sitter** powered syntax highlighting
|
||||
- **Language Server Protocol** pre configured support for most language servers
|
||||
- **Language Server Protocol** pre configured support for most language
|
||||
servers
|
||||
- Powerful **multi-cursor** editing and integrated **clipboard history**
|
||||
- Powerful configurable keybinding system that supports **modal** and
|
||||
**non-modal** editing styles
|
||||
|
|
@ -27,26 +28,29 @@ and is my daily driver for almost everything.
|
|||
- Hybrid rope/piece-table buffer system, edit **very large files** with
|
||||
**thousands of cursors**
|
||||
- Infinite **undo** (at least until you run out of ram)
|
||||
- Full **unicode** support, including support for the kitty text sizing protocol
|
||||
- Full **unicode** support, including support for the kitty text sizing
|
||||
protocol
|
||||
- Plenty of **themes** included and support for vscode themes via the
|
||||
flow-themes project
|
||||
- Runs on **Linux, FreeBSD, MacOS, Windows and Android** (under termux) with
|
||||
easy **cross-compilation** to all supported targets
|
||||
- Runs on **Linux, FreeBSD, MacOS, Windows and Android** (under termux)
|
||||
with easy **cross-compilation** to all supported targets
|
||||
|
||||
|
||||
# Requirements
|
||||
|
||||
- A modern terminal with **24bit color** and, ideally, **kitty keyboard
|
||||
protocol** support. **Kitty**, **Foot** and **Ghostty** are the recommended
|
||||
terminals at this time. **Zellij** also works well. Most other terminals will
|
||||
work, but likely with reduced functionality.
|
||||
- **NerdFont** support. Either via terminal font fallback or a patched font.
|
||||
protocol** support. **Kitty**, **Foot** and **Ghostty** are the
|
||||
recommended terminals at this time. **Zellij** also works well. Most
|
||||
other terminals will work, but likely with reduced functionality.
|
||||
- **NerdFont** support. Either via terminal font fallback or a patched
|
||||
font.
|
||||
- A **UTF-8** locale
|
||||
|
||||
|
||||
# Roadmap
|
||||
|
||||
See our [devlog](https://flow-control.dev/devlog/2025/) for on-going updates from the development team.
|
||||
See our [devlog](https://flow-control.dev/devlog/2025/) for on-going
|
||||
updates from the development team.
|
||||
|
||||
## In Development
|
||||
|
||||
|
|
@ -63,8 +67,8 @@ See our [devlog](https://flow-control.dev/devlog/2025/) for on-going updates fro
|
|||
|
||||
# Download / Install
|
||||
|
||||
There is an [installation guide](https://flow-control.dev/installation) on the
|
||||
main website, and source, release and nightly build binary
|
||||
There is an [installation guide](https://flow-control.dev/installation) on
|
||||
the main website, and source, release and nightly build binary
|
||||
[downloads](https://flow-control.dev/downloads).
|
||||
|
||||
Or check your favorite local system package repository.
|
||||
|
|
@ -82,9 +86,9 @@ Flow builds with zig 0.15.2 at this time. Build with:
|
|||
zig build -Doptimize=ReleaseSafe
|
||||
```
|
||||
|
||||
Zig will by default build a binary optimized for your specific CPU. If you get
|
||||
illegal instruction errors add `-Dcpu=baseline` to the build command to produce
|
||||
a binary with generic CPU support.
|
||||
Zig will by default build a binary optimized for your specific CPU. If you
|
||||
get illegal instruction errors add `-Dcpu=baseline` to the build command to
|
||||
produce a binary with generic CPU support.
|
||||
|
||||
|
||||
Thanks to Zig you may also cross-compile from any host to pretty much any
|
||||
|
|
@ -105,8 +109,8 @@ The output binary is:
|
|||
zig-out/bin/flow
|
||||
```
|
||||
|
||||
It is statically built (by default) and contains all the required tree-sitter parsers
|
||||
and queries. No additional runtime files are required.
|
||||
It is statically built (by default) and contains all the required
|
||||
tree-sitter parsers and queries. No additional runtime files are required.
|
||||
|
||||
|
||||
# Running Flow Control
|
||||
|
|
@ -125,8 +129,9 @@ Or if you prefer, let zig install it in your home directory:
|
|||
zig build -Doptimize=ReleaseSafe --prefix ~/.local
|
||||
```
|
||||
|
||||
Flow Control is a single statically linked binary. No further runtime files are
|
||||
required. You may install it on another system by simply copying the binary.
|
||||
Flow Control is a single statically linked binary. No further runtime files
|
||||
are required. You may install it on another system by simply copying the
|
||||
binary.
|
||||
|
||||
```shell
|
||||
scp zig-out/bin/flow root@otherhost:/usr/local/bin
|
||||
|
|
@ -138,8 +143,9 @@ Files to load may be specifed on the command line:
|
|||
flow fileA.zig fileB.zig
|
||||
```
|
||||
|
||||
The last file will be opened and the previous files will be placed in reverse
|
||||
order at the top of the recent files list. Switch to recent files with Ctrl-e.
|
||||
The last file will be opened and the previous files will be placed in
|
||||
reverse order at the top of the recent files list. Switch to recent files
|
||||
with Ctrl-e.
|
||||
|
||||
Common target line specifiers are supported too:
|
||||
|
||||
|
|
@ -171,13 +177,13 @@ See `flow --help` for the full list of command line options.
|
|||
A basic user manual is available inside flow. You can open it with the
|
||||
`Open help` command (F1).
|
||||
|
||||
It is also available in the website [documentation](https://flow-control.dev/docs/)
|
||||
section.
|
||||
It is also available in the website
|
||||
[documentation](https://flow-control.dev/docs/) section.
|
||||
|
||||
## Development Resources
|
||||
|
||||
Additional [developer](https://flow-control.dev/docs/#resources) resources can
|
||||
be found on the Flow Control website at.
|
||||
Additional [developer](https://flow-control.dev/docs/#resources) resources
|
||||
can be found on the Flow Control website at.
|
||||
|
||||
There is also an AI generated developer guide at
|
||||
[](https://deepwiki.com/neurocyte/flow).
|
||||
|
|
@ -186,23 +192,23 @@ Accuracy may vary. Check details against the referenced source code.
|
|||
|
||||
# Configuration
|
||||
|
||||
Configuration is mostly dynamically maintained with various commands in the UI.
|
||||
It is stored under the standard user configuration path. Usually
|
||||
`~/.config/flow` on Linux. %APPDATA%\Roaming\flow on Windows. Somewhere magical
|
||||
on MacOS.
|
||||
Configuration is mostly dynamically maintained with various commands in the
|
||||
UI. It is stored under the standard user configuration path. Usually
|
||||
`~/.config/flow` on Linux. %APPDATA%\Roaming\flow on Windows. Somewhere
|
||||
magical on MacOS.
|
||||
|
||||
There are commands to open the various configuration files, so you don't have to
|
||||
manually find them. Look for commands starting with `Edit` in the command
|
||||
palette.
|
||||
There are commands to open the various configuration files, so you don't
|
||||
have to manually find them. Look for commands starting with `Edit` in the
|
||||
command palette.
|
||||
|
||||
File types may be configured with the `Edit file type configuration` command.
|
||||
You can also create a new file type by adding a new `.conf` file to the
|
||||
`file_type` directory. Have a look at an existing file type to see what options
|
||||
are available.
|
||||
File types may be configured with the `Edit file type configuration`
|
||||
command. You can also create a new file type by adding a new `.conf` file
|
||||
to the `file_type` directory. Have a look at an existing file type to see
|
||||
what options are available.
|
||||
|
||||
Logs, traces and per-project most recently used file lists are stored in the
|
||||
standard user application state directory. Usually `~/.local/state/flow` on
|
||||
Linux and %APPDATA%\Roaming\flow on Windows.
|
||||
Logs, traces and per-project most recently used file lists are stored in
|
||||
the standard user application state directory. Usually
|
||||
`~/.local/state/flow` on Linux and %APPDATA%\Roaming\flow on Windows.
|
||||
|
||||
|
||||
# Key bindings and commands
|
||||
|
|
@ -213,16 +219,16 @@ Press `ctrl+shift+p` or `alt+x` to show the command palette.
|
|||
Press `ctrl+F2` to see a full list of all current keybindings and commands.
|
||||
|
||||
Run the `Edit keybindings` command to save the current keybinding mode to a
|
||||
file and open it for editing. Save your customized keybinds under a new name
|
||||
in the same directory to create an entirely new keybinding mode. Keybinding
|
||||
changes will take effect on restart.
|
||||
file and open it for editing. Save your customized keybinds under a new
|
||||
name in the same directory to create an entirely new keybinding mode.
|
||||
Keybinding changes will take effect on restart.
|
||||
|
||||
|
||||
# Terminal configuration
|
||||
|
||||
Kitty, Ghostty and most other terminals have default keybindings that conflict
|
||||
with common editor commands. I highly recommend rebinding them to keys that are
|
||||
not generally used anywhere else.
|
||||
Kitty, Ghostty and most other terminals have default keybindings that
|
||||
conflict with common editor commands. I highly recommend rebinding them to
|
||||
keys that are not generally used anywhere else.
|
||||
|
||||
For Kitty rebinding `kitty_mod` is usually enough:
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
pub fn reflow(allocator: std.mem.Allocator, text: []const u8, width: usize) error{ OutOfMemory, WriteFailed }![]u8 {
|
||||
const prefix = detect_prefix(text);
|
||||
const words = try split_words(allocator, text, prefix.len);
|
||||
const len = text.len;
|
||||
const trailing_ln: bool = (len > 0 and text[len - 1] == '\n');
|
||||
const input = if (trailing_ln) text[0 .. len - 1] else text;
|
||||
const prefix = detect_prefix(input);
|
||||
const words = try split_words(allocator, input, prefix.len);
|
||||
defer allocator.free(words);
|
||||
var output: std.Io.Writer.Allocating = .init(allocator);
|
||||
const writer = &output.writer;
|
||||
|
||||
std.log.info("reflow @{d}", .{width});
|
||||
|
||||
var first = true;
|
||||
var line_len: usize = 0;
|
||||
for (words) |word| {
|
||||
|
|
@ -16,30 +21,35 @@ pub fn reflow(allocator: std.mem.Allocator, text: []const u8, width: usize) erro
|
|||
.begin => {
|
||||
if (first) {
|
||||
try writer.writeAll(prefix.first);
|
||||
line_len += prefix.first.len;
|
||||
first = false;
|
||||
} else {
|
||||
try writer.writeAll(prefix.continuation);
|
||||
line_len += prefix.continuation.len;
|
||||
var pad = prefix.first.len - prefix.continuation.len;
|
||||
while (pad > 0) : (pad -= 1)
|
||||
while (pad > 0) : (pad -= 1) {
|
||||
try writer.writeByte(' ');
|
||||
line_len += 1;
|
||||
}
|
||||
}
|
||||
line_len += prefix.len;
|
||||
continue :blk .words;
|
||||
},
|
||||
.words => {
|
||||
if (line_len > prefix.len and line_len + word.len + 1 >= width - 1) {
|
||||
try writer.writeByte('\n');
|
||||
line_len = 0;
|
||||
continue :blk .begin;
|
||||
}
|
||||
if (line_len > prefix.len)
|
||||
if (line_len > prefix.len) {
|
||||
if (line_len + word.len + 1 >= width) {
|
||||
try writer.writeByte('\n');
|
||||
line_len = 0;
|
||||
continue :blk .begin;
|
||||
}
|
||||
try writer.writeByte(' ');
|
||||
line_len += 1;
|
||||
}
|
||||
try writer.writeAll(word);
|
||||
line_len += word.len;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (trailing_ln) try writer.writeByte('\n');
|
||||
return output.toOwnedSlice();
|
||||
}
|
||||
|
||||
|
|
@ -61,10 +71,10 @@ fn detect_prefix(text: []const u8) Prefix {
|
|||
const line1 = lines.next() orelse return .{};
|
||||
var prefix: []const u8 = line1;
|
||||
var count: usize = 0;
|
||||
while (lines.next()) |line| {
|
||||
while (lines.next()) |line| if (line.len > 0) {
|
||||
prefix = lcp(prefix, line);
|
||||
count += 1;
|
||||
}
|
||||
};
|
||||
if (count < 1) return .{
|
||||
.len = 0,
|
||||
.first = &.{},
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ follow_cursor_on_buffer_switch: bool = false, //scroll cursor into view on buffe
|
|||
default_cursor: CursorShape = .default,
|
||||
modes_can_change_cursor: bool = true,
|
||||
enable_auto_save: bool = false,
|
||||
auto_save_mode: AutoSaveMode = .on_focus_change,
|
||||
limit_auto_save_file_types: ?[]const []const u8 = null, // null means *all*
|
||||
enable_prefix_keyhints: bool = true,
|
||||
enable_auto_find: bool = true,
|
||||
|
|
@ -45,7 +46,7 @@ auto_run_commands: ?[]const []const u8 = &.{"save_session_quiet"}, // a list of
|
|||
indent_size: usize = 4,
|
||||
tab_width: usize = 8,
|
||||
indent_mode: IndentMode = .auto,
|
||||
reflow_width: usize = 80,
|
||||
reflow_width: usize = 76,
|
||||
|
||||
top_bar: []const u8 = "tabs",
|
||||
bottom_bar: []const u8 = "mode file log selection diagnostics keybind branch linenumber clock spacer",
|
||||
|
|
@ -149,6 +150,12 @@ pub const WhitespaceMode = enum {
|
|||
none,
|
||||
};
|
||||
|
||||
pub const AutoSaveMode = enum {
|
||||
on_input_idle,
|
||||
on_focus_change,
|
||||
on_document_change,
|
||||
};
|
||||
|
||||
pub const CursorShape = enum {
|
||||
default,
|
||||
block_blink,
|
||||
|
|
|
|||
|
|
@ -649,7 +649,7 @@ pub const Editor = struct {
|
|||
self.highlight_references_pending.deinit(self.allocator);
|
||||
self.handlers.deinit();
|
||||
self.logger.deinit();
|
||||
if (self.buffer) |p| self.buffer_manager.retire(p, meta.written());
|
||||
if (self.buffer) |p| self.retire_buffer(p, meta.written());
|
||||
}
|
||||
|
||||
pub fn need_render(_: *Self) void {
|
||||
|
|
@ -697,7 +697,6 @@ pub const Editor = struct {
|
|||
defer frame.deinit();
|
||||
break :blk try self.buffer_manager.open_file(file_path);
|
||||
};
|
||||
if (tui.config().enable_auto_save) buffer.enable_auto_save();
|
||||
return self.open_buffer(file_path, buffer, null);
|
||||
}
|
||||
|
||||
|
|
@ -713,7 +712,7 @@ pub const Editor = struct {
|
|||
fn open_buffer(self: *Self, file_path: []const u8, new_buf: *Buffer, file_type_: ?[]const u8) !void {
|
||||
const frame = tracy.initZone(@src(), .{ .name = "open_buffer" });
|
||||
defer frame.deinit();
|
||||
errdefer self.buffer_manager.retire(new_buf, null);
|
||||
errdefer self.retire_buffer(new_buf, null);
|
||||
self.cancel_all_selections();
|
||||
self.get_primary().reset();
|
||||
self.file_path = try self.allocator.dupe(u8, file_path);
|
||||
|
|
@ -755,8 +754,6 @@ pub const Editor = struct {
|
|||
self.checked_formatter = false;
|
||||
self.formatter = null;
|
||||
|
||||
self.maybe_enable_auto_save();
|
||||
|
||||
const syn = blk: {
|
||||
const frame_ = tracy.initZone(@src(), .{ .name = "create" });
|
||||
defer frame_.deinit();
|
||||
|
|
@ -793,7 +790,6 @@ pub const Editor = struct {
|
|||
buffer.file_type_icon = fti;
|
||||
buffer.file_type_color = ftc;
|
||||
}
|
||||
const auto_save = if (self.buffer) |b| b.is_auto_save() and !b.is_ephemeral() else false;
|
||||
|
||||
if (buffer_meta) |meta| {
|
||||
const frame_ = tracy.initZone(@src(), .{ .name = "extract_state" });
|
||||
|
|
@ -801,24 +797,29 @@ pub const Editor = struct {
|
|||
var iter = meta;
|
||||
try self.extract_state(&iter, .none);
|
||||
}
|
||||
|
||||
const auto_save = if (self.buffer) |b|
|
||||
self.maybe_enable_buffer_auto_save(b)
|
||||
else
|
||||
false;
|
||||
|
||||
try self.send_editor_open(file_path, new_buf.file_exists, ftn, fti, ftc, auto_save);
|
||||
}
|
||||
|
||||
fn maybe_enable_auto_save(self: *Self) void {
|
||||
const buffer = self.buffer orelse return;
|
||||
if (self.restored_state) return;
|
||||
buffer.disable_auto_save();
|
||||
if (!tui.config().enable_auto_save) return;
|
||||
const self_file_type = self.file_type orelse return;
|
||||
fn maybe_enable_buffer_auto_save(self: *Self, buffer: *Buffer) bool {
|
||||
if (self.restored_state) return false;
|
||||
if (!tui.config().enable_auto_save) return false;
|
||||
const self_file_type = self.file_type orelse return false;
|
||||
|
||||
enable: {
|
||||
const file_types = tui.config().limit_auto_save_file_types orelse break :enable;
|
||||
for (file_types) |file_type|
|
||||
if (std.mem.eql(u8, file_type, self_file_type.name))
|
||||
break :enable;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
buffer.enable_auto_save();
|
||||
return !buffer.is_ephemeral();
|
||||
}
|
||||
|
||||
fn detect_indent_mode(self: *Self, content: []const u8) void {
|
||||
|
|
@ -853,11 +854,16 @@ pub const Editor = struct {
|
|||
}
|
||||
pub const set_editor_tab_width_meta: Meta = .{ .arguments = &.{.integer} };
|
||||
|
||||
fn retire_buffer(self: *const Self, buffer: *Buffer, meta: ?[]const u8) void {
|
||||
self.buffer_manager.retire(buffer, meta);
|
||||
auto_save_buffer(buffer, .on_focus_change);
|
||||
}
|
||||
|
||||
fn close(self: *Self) !void {
|
||||
var meta: std.Io.Writer.Allocating = .init(self.allocator);
|
||||
defer meta.deinit();
|
||||
self.write_state(&meta.writer) catch {};
|
||||
if (self.buffer) |b_mut| self.buffer_manager.retire(b_mut, meta.written());
|
||||
if (self.buffer) |b_mut| self.retire_buffer(b_mut, meta.written());
|
||||
self.cancel_all_selections();
|
||||
self.buffer = null;
|
||||
self.plane.erase();
|
||||
|
|
@ -2020,10 +2026,11 @@ pub const Editor = struct {
|
|||
|
||||
fn send_editor_update(self: *const Self, old_root: ?Buffer.Root, new_root: ?Buffer.Root, eol_mode: Buffer.EolMode) !void {
|
||||
_ = try self.handlers.msg(.{ "E", "update" });
|
||||
if (self.buffer) |buffer| if (self.syntax) |_| if (self.file_path) |file_path| if (old_root != null and new_root != null)
|
||||
project_manager.did_change(file_path, buffer.lsp_version, try text_from_root(new_root, eol_mode), try text_from_root(old_root, eol_mode), eol_mode) catch {};
|
||||
if (self.buffer) |b| if (b.is_auto_save() and !b.is_ephemeral())
|
||||
tp.self_pid().send(.{ "cmd", "save_file", .{} }) catch {};
|
||||
if (self.buffer) |buffer| {
|
||||
if (self.syntax) |_| if (self.file_path) |file_path| if (old_root != null and new_root != null)
|
||||
project_manager.did_change(file_path, buffer.lsp_version, try text_from_root(new_root, eol_mode), try text_from_root(old_root, eol_mode), eol_mode) catch {};
|
||||
auto_save_buffer(buffer, .on_document_change);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vcs_content_update(self: *const Self) !void {
|
||||
|
|
@ -5547,7 +5554,10 @@ pub const Editor = struct {
|
|||
buffer.disable_auto_save();
|
||||
self.send_editor_auto_save(buffer.is_auto_save()) catch {};
|
||||
if (buffer.is_auto_save())
|
||||
tp.self_pid().send(.{ "cmd", "save_file", .{} }) catch {};
|
||||
std.log.info("enabled auto save {t}", .{tui.config().auto_save_mode})
|
||||
else
|
||||
std.log.info("disabled auto save", .{});
|
||||
auto_save_buffer(buffer, .on_document_change);
|
||||
}
|
||||
pub const toggle_auto_save_meta: Meta = .{ .description = "Toggle auto save" };
|
||||
|
||||
|
|
@ -7135,6 +7145,7 @@ pub const EditorWidget = struct {
|
|||
if (self.focused) self.commands.unregister();
|
||||
self.focused = false;
|
||||
command.executeName("enter_mode_default", .{}) catch {};
|
||||
if (self.editor.buffer) |b| auto_save_buffer(b, .on_focus_change);
|
||||
}
|
||||
|
||||
pub fn update(self: *Self) void {
|
||||
|
|
@ -7213,6 +7224,7 @@ pub const EditorWidget = struct {
|
|||
}
|
||||
},
|
||||
};
|
||||
if (self.editor.buffer) |b| auto_save_buffer(b, .on_input_idle);
|
||||
return false;
|
||||
} else if (try m.match(.{ "whitespace_mode", tp.extract(&whitespace_mode) })) {
|
||||
self.editor.render_whitespace = whitespace_mode;
|
||||
|
|
@ -7428,3 +7440,24 @@ fn ViewMap(T: type, default: T) type {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn auto_save_buffer(b: *Buffer, comptime event: @import("config").AutoSaveMode) void {
|
||||
const auto_save = switch (tui.config().auto_save_mode) {
|
||||
.on_input_idle => comptime switch (event) {
|
||||
.on_input_idle, .on_focus_change => true,
|
||||
.on_document_change => false,
|
||||
},
|
||||
.on_document_change => comptime switch (event) {
|
||||
.on_document_change => true,
|
||||
.on_input_idle, .on_focus_change => false,
|
||||
},
|
||||
.on_focus_change => comptime switch (event) {
|
||||
.on_focus_change => true,
|
||||
.on_input_idle, .on_document_change => false,
|
||||
},
|
||||
};
|
||||
if (auto_save and b.is_auto_save() and !b.is_ephemeral() and b.is_dirty()) {
|
||||
tp.self_pid().send(.{ "cmd", "save_buffer", .{ b.get_file_path(), "auto_save" } }) catch {};
|
||||
std.log.debug("auto save {t} {s}", .{ event, b.get_file_path() });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -188,10 +188,18 @@ pub fn receive(self: *Self, from_: tp.pid_ref, m: tp.message) error{Exit}!bool {
|
|||
} else if (try m.match(.{ "navigate_complete", tp.extract(&path), tp.extract(&goto_args), tp.null_, tp.null_ })) {
|
||||
cmds.navigate_complete(self, null, path, goto_args, null, null, null) catch |e| return tp.exit_error(e, @errorReturnTrace());
|
||||
return true;
|
||||
} else if (try m.match(.{"focus_out"})) {
|
||||
self.process_focus_out() catch |e| return tp.exit_error(e, @errorReturnTrace());
|
||||
}
|
||||
return if (try self.floating_views.send(from_, m)) true else self.widgets.send(from_, m);
|
||||
}
|
||||
|
||||
fn process_focus_out(self: *Self) error{OutOfMemory}!void {
|
||||
const buffers = try self.buffer_manager.list_unordered(self.allocator);
|
||||
defer self.allocator.free(buffers);
|
||||
for (buffers) |b| ed.auto_save_buffer(b, .on_focus_change);
|
||||
}
|
||||
|
||||
pub fn update(self: *Self) void {
|
||||
self.widgets.update();
|
||||
self.floating_views.update();
|
||||
|
|
@ -769,8 +777,11 @@ const cmds = struct {
|
|||
pub const create_new_file_meta: Meta = .{ .description = "New file" };
|
||||
|
||||
pub fn save_buffer(self: *Self, ctx: Ctx) Result {
|
||||
var auto_save: bool = false;
|
||||
var file_path: []const u8 = undefined;
|
||||
if (!(ctx.args.match(.{tp.extract(&file_path)}) catch false))
|
||||
if (ctx.args.match(.{ tp.extract(&file_path), "auto_save" }) catch false) {
|
||||
auto_save = true;
|
||||
} else if (!(ctx.args.match(.{tp.extract(&file_path)}) catch false))
|
||||
return error.InvalidSaveBufferArgument;
|
||||
|
||||
const buffer = self.buffer_manager.get_buffer_for_file(file_path) orelse return;
|
||||
|
|
@ -786,7 +797,10 @@ const cmds = struct {
|
|||
const logger = log.logger("buffer");
|
||||
defer logger.deinit();
|
||||
if (buffer.is_ephemeral()) return logger.print_err("save", "ephemeral buffer, use save as", .{});
|
||||
if (!buffer.is_dirty()) return logger.print("no changes to save", .{});
|
||||
if (!buffer.is_dirty()) {
|
||||
if (!auto_save) logger.print("no changes to save", .{});
|
||||
return;
|
||||
}
|
||||
try buffer.store_to_file_and_clean(file_path);
|
||||
}
|
||||
pub const save_buffer_meta: Meta = .{ .arguments = &.{.string} };
|
||||
|
|
|
|||
|
|
@ -83,6 +83,10 @@ pub fn layout(_: *Self, _: *ButtonType) Widget.Layout {
|
|||
}
|
||||
|
||||
pub fn render(self: *Self, btn: *ButtonType, theme: *const Widget.Theme) bool {
|
||||
const auto_save = if (self.auto_save) switch (tui.config().auto_save_mode) {
|
||||
.on_input_idle, .on_document_change => true,
|
||||
.on_focus_change => false,
|
||||
} else false;
|
||||
const style_base = theme.statusbar;
|
||||
const style_label = if (btn.active) theme.editor_cursor else style_base;
|
||||
btn.plane.set_base_style(theme.editor);
|
||||
|
|
@ -99,9 +103,9 @@ pub fn render(self: *Self, btn: *ButtonType, theme: *const Widget.Theme) bool {
|
|||
if (tui.mini_mode()) |_|
|
||||
render_mini_mode(&btn.plane, theme)
|
||||
else if (self.detailed)
|
||||
self.render_detailed(&btn.plane, theme)
|
||||
self.render_detailed(&btn.plane, theme, auto_save)
|
||||
else
|
||||
self.render_normal(&btn.plane, theme);
|
||||
self.render_normal(&btn.plane, theme, auto_save);
|
||||
self.render_terminal_title();
|
||||
return false;
|
||||
}
|
||||
|
|
@ -135,19 +139,19 @@ fn render_mini_mode(plane: *Plane, theme: *const Widget.Theme) void {
|
|||
// Content save check
|
||||
// Content save cog
|
||||
// Content save all
|
||||
fn render_normal(self: *Self, plane: *Plane, theme: *const Widget.Theme) void {
|
||||
fn render_normal(self: *Self, plane: *Plane, theme: *const Widget.Theme, auto_save: bool) void {
|
||||
plane.on_styles(styles.italic);
|
||||
_ = plane.putstr(" ") catch {};
|
||||
if (self.file_icon.len > 0 and tui.config().show_fileicons) {
|
||||
self.render_file_icon(plane, theme);
|
||||
_ = plane.print(" ", .{}) catch {};
|
||||
}
|
||||
_ = plane.putstr(if (!self.file_exists) " " else if (self.auto_save) " " else if (self.file_dirty) " " else "") catch {};
|
||||
_ = plane.putstr(if (!self.file_exists) " " else if (auto_save) " " else if (self.file_dirty) " " else "") catch {};
|
||||
_ = plane.print("{s}", .{self.name}) catch {};
|
||||
return;
|
||||
}
|
||||
|
||||
fn render_detailed(self: *Self, plane: *Plane, theme: *const Widget.Theme) void {
|
||||
fn render_detailed(self: *Self, plane: *Plane, theme: *const Widget.Theme, auto_save: bool) void {
|
||||
plane.on_styles(styles.italic);
|
||||
_ = plane.putstr(" ") catch {};
|
||||
if (self.file_icon.len > 0 and tui.config().show_fileicons) {
|
||||
|
|
@ -167,7 +171,7 @@ fn render_detailed(self: *Self, plane: *Plane, theme: *const Widget.Theme) void
|
|||
.tabs => "[⭾ = ␉]",
|
||||
};
|
||||
|
||||
_ = plane.putstr(if (!self.file_exists) "" else if (self.auto_save) "" else if (self.file_dirty) "" else "") catch {};
|
||||
_ = plane.putstr(if (!self.file_exists) "" else if (auto_save) "" else if (self.file_dirty) "" else "") catch {};
|
||||
_ = plane.print(" {s}:{d}:{d}", .{ self.name, self.line + 1, self.column + 1 }) catch {};
|
||||
_ = plane.print(" of {d} lines", .{self.lines}) catch {};
|
||||
if (self.file_type.len > 0)
|
||||
|
|
|
|||
|
|
@ -902,7 +902,10 @@ const Tab = struct {
|
|||
const buffer_manager = tui.get_buffer_manager() orelse @panic("tabs no buffer manager");
|
||||
const buffer_ = buffer_manager.buffer_from_ref(self.buffer_ref);
|
||||
const is_dirty = if (buffer_) |buffer| buffer.is_dirty() else false;
|
||||
const auto_save = if (buffer_) |buffer| buffer.is_auto_save() else false;
|
||||
const auto_save = if (buffer_) |buffer| if (buffer.is_auto_save()) switch (tui.config().auto_save_mode) {
|
||||
.on_input_idle, .on_document_change => true,
|
||||
.on_focus_change => false,
|
||||
} else false else false;
|
||||
self.render_padding(plane, .left);
|
||||
if (self.tab_style.file_type_icon) if (buffer_) |buffer| if (buffer.file_type_icon) |icon| {
|
||||
const color_: ?u24 = if (buffer.file_type_color) |color| if (!(color == 0xFFFFFF or color == 0x000000 or color == 0x000001)) color else null else null;
|
||||
|
|
|
|||
|
|
@ -499,12 +499,6 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void {
|
|||
if (try m.match(.{"mouse_leave"}))
|
||||
return;
|
||||
|
||||
if (try m.match(.{"focus_in"}))
|
||||
return;
|
||||
|
||||
if (try m.match(.{"focus_out"}))
|
||||
return;
|
||||
|
||||
if (try m.match(.{ "K", tp.more }))
|
||||
return;
|
||||
|
||||
|
|
@ -514,6 +508,12 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void {
|
|||
if (try self.send_widgets(from, m))
|
||||
return;
|
||||
|
||||
if (try m.match(.{"focus_in"}))
|
||||
return;
|
||||
|
||||
if (try m.match(.{"focus_out"}))
|
||||
return;
|
||||
|
||||
if (try m.match(.{ "exit", tp.more })) {
|
||||
if (try m.match(.{ tp.string, "normal" }) or
|
||||
try m.match(.{ tp.string, "timeout_error", 125, "Operation aborted." }) or
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue