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