Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ fancy-cat <path-to-pdf> <optional-page-number>

### Commands

fancy-cat uses a modal interface similar to Neovim. There are two modes: view mode and command mode. To enter command mode you type `:` by default (this can be changed in the config file)
fancy-cat uses a modal interface similar to Neovim. There are two modes: view mode and command mode. To enter command mode you type `:` by default (this can be changed in the config file).

Documentation on the available commands can be found [here](./docs/commands.md)
Documentation on the available commands can be found [here](./docs/commands.md).

### Configuration

fancy-cat can be configured through a JSON config file located at `~/.config/fancy-cat/config.json`. The file is automatically created on the first run with default settings.
fancy-cat can be configured through a JSON configuration file located in one of several locations (primary `$XDG_CONFIG_HOME/fancy-cat/config.json`, fallback `$HOME/.config/fancy-cat/config.json`, legacy `$HOME/.fancy-cat`). An empty configuration file is automatically created in the primary or fallback location on the first run.

The default `config.json` and documentation can be found [here](./docs/config.md)
An example `config.json` and documentation can be found [here](./docs/config.md).

## Installation

Expand Down
57 changes: 51 additions & 6 deletions docs/config.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
# Configuration

On startup, fancy-cat looks for a configuration file at:
On startup, fancy-cat looks for a configuration file in the following locations:

**Primary**

```
$XDG_CONFIG_HOME/fancy-cat/config.json
```

**Fallback**

```
$HOME/.config/fancy-cat/config.json
```
~/.config/fancy-cat/config.json

**Legacy**

```
$HOME/.fancy-cat
```

If no configuration file is found, fancy-cat creates an empty one. Since fancy-cat comes with sensible defaults, you only need to add the options you want to change.
If no configuration file is found in any of these locations, fancy-cat creates an empty configuration file in the primary or fallback location.

## Defaults

Below is an example configuration file that replicates the default settings. You can use it as a starting point for your customizations:
Because fancy-cat provides sensible defaults, you only need to specify the options you wish to override. Below is an example configuration file that replicates the default settings. You can use this example as a starting point for your customizations:

```json
{
Expand All @@ -29,7 +43,9 @@ Below is an example configuration file that replicates the default settings. You
"full_screen": { "key": "f"},
"enter_command_mode": { "key": ":" },
"exit_command_mode": { "key": "escape" },
"execute_command": { "key": "enter" }
"execute_command": { "key": "enter" },
"history_back": { "key": "up" },
"history_forward": { "key": "down" }
},
"FileMonitor": {
"enabled": true,
Expand All @@ -46,7 +62,8 @@ Below is an example configuration file that replicates the default settings. You
"scroll_step": 100.0,
"retry_delay": 0.2,
"timeout": 5.0,
"dpi": 96.0
"dpi": 96.0,
"history": 1000
},
"StatusBar": {
"enabled": true,
Expand Down Expand Up @@ -77,6 +94,7 @@ The rest of this reference provides detailed explanations for each configuration
- [File Monitor](#file-monitor)
- [General](#general)
- [Color](#color)
- [History](#history)
- [Status Bar](#status-bar)
- [Style](#style)
- [Underline](#underline)
Expand Down Expand Up @@ -110,6 +128,8 @@ The `KeyMap` section defines keybindings for various actions.
| `enter_command_mode` | Enter command mode |
| `exit_command_mode` | Exit command mode |
| `execute_command` | Execute the entered command |
| `history_back` | Go back one command in history |
| `history_forward` | Go forward one command in history |

### Keybindings

Expand Down Expand Up @@ -191,6 +211,7 @@ The `General` section includes various display and timing settings.
| `dpi` | Float | Resolution used for 100% zoom calculation |
| `retry_delay` | Float (seconds) | Delay before retrying to load a document or render a page |
| `timeout` | Float (seconds) | Maximum time to keep retrying before giving up on loading a document or rendering a page |
| `history` | Integer | Maximum number of entries in command history |

>[!TIP]
>The color replacement feature works by replacing white and black with custom colors, which also affects the full color range depending on contrast. By default, `white` is set to black (`#000000`) and `black` is set to white (`#ffffff`). For a seamless look, try setting `white` to match your terminal’s background color and `black` to match the foreground (text) color.
Expand All @@ -204,6 +225,30 @@ The following color formats are supported:
| `"#RRGGBB"` or `"0xRRGGBB"` | `RR`, `GG`, and `BB` are two-digit hexadecimal values |
| `{ "rgb": [R, G, B] }` | `R`, `G`, and `B` are integers between 0 and 255 |

### History

To ensure persistence across sessions, fancy-cat saves its command history in one of the following locations:

**Primary**

```
$XDG_STATE_HOME/fancy-cat/history
```

**Fallback**

```
$HOME/.local/state/fancy-cat/history
```

**Legacy**

```
$HOME/.fancy-cat_history
```
>[!NOTE]
>The legacy location is only used if the [configuration file](#configuration) itself is located at `$HOME/.fancy-cat`.

---

## Status Bar
Expand Down
13 changes: 9 additions & 4 deletions src/Context.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const Config = @import("config/Config.zig");
const DocumentHandler = @import("handlers/DocumentHandler.zig");
const Cache = @import("./Cache.zig");
const ReloadIndicatorTimer = @import("services/ReloadIndicatorTimer.zig");
const History = @import("services/History.zig");

pub const panic = vaxis.panic_handler;

Expand Down Expand Up @@ -38,6 +39,7 @@ pub const Context = struct {
watcher_thread: ?std.Thread,
config: *Config,
current_mode: Mode,
history: History,
reload_page: bool,
cache: Cache,
should_check_cache: bool,
Expand All @@ -54,7 +56,7 @@ pub const Context = struct {

const config = try allocator.create(Config);
errdefer allocator.destroy(config);
config.* = try Config.init(allocator);
config.* = Config.init(allocator);
errdefer config.deinit();

var document_handler = try DocumentHandler.init(allocator, path, initial_page, config);
Expand All @@ -70,6 +72,7 @@ pub const Context = struct {
const buf = try allocator.alloc(u8, 4096);
const tty = try vaxis.Tty.init(buf);
const reload_indicator_timer = ReloadIndicatorTimer.init(config);
const history = History.init(allocator, config);

return .{
.allocator = allocator,
Expand All @@ -85,6 +88,7 @@ pub const Context = struct {
.watcher_thread = null,
.config = config,
.current_mode = undefined,
.history = history,
.reload_page = true,
.cache = Cache.init(allocator, config, vx, &tty),
.should_check_cache = config.cache.enabled,
Expand All @@ -107,14 +111,15 @@ pub const Context = struct {

if (self.page_info_text.len > 0) self.allocator.free(self.page_info_text);

self.config.deinit();
self.allocator.destroy(self.config);
self.arena.deinit();
self.reload_indicator_timer.deinit();
self.history.deinit();
self.cache.deinit();
self.document_handler.deinit();
self.vx.deinit(self.allocator, self.tty.writer());
self.tty.deinit();
self.config.deinit();
self.allocator.destroy(self.config);
self.arena.deinit();
self.allocator.free(self.buf);
}

Expand Down
51 changes: 32 additions & 19 deletions src/config/Config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ pub const KeyMap = struct {
enter_command_mode: vaxis.Key = .{ .codepoint = ':' },
exit_command_mode: vaxis.Key = .{ .codepoint = vaxis.Key.escape },
execute_command: vaxis.Key = .{ .codepoint = vaxis.Key.enter },
history_back: vaxis.Key = .{ .codepoint = vaxis.Key.up },
history_forward: vaxis.Key = .{ .codepoint = vaxis.Key.down },

pub fn parse(val: std.json.Value, allocator: std.mem.Allocator) KeyMap {
var keymap = KeyMap{};
Expand Down Expand Up @@ -87,6 +89,8 @@ pub const General = struct {
timeout: f32 = 5.0,
// resolution
dpi: f32 = 96.0,
// whole number (possibly 0)
history: u32 = 1000,

pub fn parse(val: std.json.Value, allocator: std.mem.Allocator) General {
var general = General{};
Expand Down Expand Up @@ -116,6 +120,7 @@ pub const General = struct {
general.retry_delay = parseType(f32, val.object, "retry_delay", allocator, general.retry_delay);
general.timeout = parseType(f32, val.object, "timeout", allocator, general.timeout);
general.dpi = parseType(f32, val.object, "dpi", allocator, general.dpi);
general.history = parseType(u32, val.object, "history", allocator, general.history);

return general;
}
Expand Down Expand Up @@ -211,34 +216,42 @@ general: General = .{},
status_bar: StatusBar = .{},
cache: Cache = .{},

pub fn init(allocator: std.mem.Allocator) !Self {
legacy_path: bool = false,

pub fn init(allocator: std.mem.Allocator) Self {
var self = Self{ .arena = std.heap.ArenaAllocator.init(allocator) };
const arena_allocator = self.arena.allocator();

const home = std.process.getEnvVarOwned(allocator, "HOME") catch return self;
defer allocator.free(home);

var config_dir_buf: [std.fs.max_path_bytes]u8 = undefined;
const config_dir = std.fmt.bufPrint(&config_dir_buf, "{s}/.config/fancy-cat", .{home}) catch return self;

std.fs.makeDirAbsolute(config_dir) catch {};

var config_path_buf: [std.fs.max_path_bytes]u8 = undefined;
const config_path = std.fmt.bufPrint(&config_path_buf, "{s}/config.json", .{config_dir}) catch return self;

const file = std.fs.openFileAbsolute(config_path, .{ .mode = .read_only }) catch |err| {
if (err == error.FileNotFound) {
const newf = std.fs.createFileAbsolute(config_path, .{}) catch return self;
newf.close();
var path: []u8 = "";
const xdg_config_home = std.process.getEnvVarOwned(allocator, "XDG_CONFIG_HOME") catch null;
if (xdg_config_home) |x| {
path = std.fmt.allocPrint(allocator, "{s}/fancy-cat/config.json", .{x}) catch return self;
allocator.free(x);
} else path = std.fmt.allocPrint(allocator, "{s}/.config/fancy-cat/config.json", .{home}) catch return self;
defer allocator.free(path);

var content = std.fs.cwd().readFileAlloc(allocator, path, 1024 * 1024) catch null;
if (content == null) {
const legacy_path = std.fmt.allocPrint(allocator, "{s}/.fancy-cat", .{home}) catch return self;
defer allocator.free(legacy_path);

content = std.fs.cwd().readFileAlloc(allocator, legacy_path, 1024 * 1024) catch null;
if (content == null) {
if (std.fs.path.dirname(path)) |dir| std.fs.cwd().makePath(dir) catch {};
const file = std.fs.createFileAbsolute(path, .{}) catch return self;
file.close();
return self;
}
return self;
};
defer file.close();
self.legacy_path = true;
}
defer allocator.free(content.?);

const content = file.readToEndAlloc(arena_allocator, 1024 * 1024) catch return self;
if (content.len == 0) return self;
if (content.?.len == 0) return self;

var parsed = std.json.parseFromSlice(std.json.Value, arena_allocator, content, .{}) catch return self;
var parsed = std.json.parseFromSlice(std.json.Value, arena_allocator, content.?, .{}) catch return self;
defer parsed.deinit();

if (parsed.value.object.get("KeyMap")) |key_map| self.key_map = KeyMap.parse(key_map, arena_allocator);
Expand Down
Loading