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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ language servers, or the font, create:
~/.config/1coder/config.toml
```

On Windows: `%APPDATA%\1coder\config.toml`.
(`$XDG_CONFIG_HOME/1coder/config.toml` when that variable is set.)

A full example with every default binding is in
Expand Down
3 changes: 2 additions & 1 deletion config/config.example.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Example 1coder config — copy to ~/.config/1coder/config.toml and edit.
# Example 1coder config — copy to ~/.config/1coder/config.toml and edit
# (Windows: %APPDATA%\1coder\config.toml).
#
# The editor ships with these defaults compiled in. You do not need this file
# for them to work. Keep only the keys you want to change, or start from this
Expand Down
11 changes: 11 additions & 0 deletions core/config/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -273,12 +273,23 @@ void ParseLspTable(Arena *arena, const TomlValue *table, Config *config, ErrorLo

String8 ConfigDefaultPath(Arena *arena) {
if (!arena) return String8{};

// Explicit XDG wins on every platform when set.
String8 xdg = OsGetEnv(arena, Str8Lit("XDG_CONFIG_HOME"));
if (xdg.size > 0) return OsPathJoin(arena, xdg, Str8Lit("1coder/config.toml"));

#if defined(_WIN32)
// Native Windows convention: %APPDATA%\1coder\config.toml
String8 appdata = OsGetEnv(arena, Str8Lit("APPDATA"));
if (appdata.size > 0) return OsPathJoin(arena, appdata, Str8Lit("1coder/config.toml"));
String8 home = HomeDirectory(arena);
if (home.size == 0) return String8{};
return OsPathJoin(arena, home, Str8Lit("AppData/Roaming/1coder/config.toml"));
#else
String8 home = HomeDirectory(arena);
if (home.size == 0) return String8{};
return OsPathJoin(arena, home, Str8Lit(".config/1coder/config.toml"));
#endif
}

ConfigLoadResult ConfigParse(Arena *arena, String8 text, String8 path) {
Expand Down
3 changes: 2 additions & 1 deletion core/config/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ struct ConfigLoadResult {
bool ok;
};

// $XDG_CONFIG_HOME/1coder/config.toml, else ~/.config/1coder/config.toml.
// $XDG_CONFIG_HOME/1coder/config.toml when set; else ~/.config/1coder/config.toml
// on Unix, or %APPDATA%\1coder\config.toml on Windows.
[[nodiscard]] String8 ConfigDefaultPath(Arena *arena);

// Parse TOML text into a Config. Does not touch the editor.
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ the editor properly, or for changing it.

## Using it

- [Configuration](config.md) — `~/.config/1coder/config.toml`, reload, error log
- [Configuration](config.md) — user config path, reload, error log
- [Keybindings](keybindings.md) — the complete key reference
- [Language server protocol](lsp.md) — auto-detected servers, navigation, completion
- [The command window](commands.md) — `:` syntax, and how to add a command
Expand Down
4 changes: 3 additions & 1 deletion docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ startup and again when you run `:config-reload`.
## Location

1. `$XDG_CONFIG_HOME/1coder/config.toml` if `XDG_CONFIG_HOME` is set
2. otherwise `~/.config/1coder/config.toml` (`%USERPROFILE%\.config\1coder\config.toml` on Windows)
2. otherwise:
- Unix / macOS: `~/.config/1coder/config.toml`
- Windows: `%APPDATA%\1coder\config.toml` (typically `C:\Users\<you>\AppData\Roaming\1coder\config.toml`)

Missing file → defaults, no error.

Expand Down
92 changes: 92 additions & 0 deletions tests/test_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,68 @@ struct ScopedFixtureDir {
~ScopedFixtureDir() { (void)OsDirDeleteRecursive(path); }
};

struct ScopedEnvVar {
Arena *arena;
const char *name;
bool had_old_value;
String8 old_value;

ScopedEnvVar(Arena *arena_, const char *name_, String8 value) : arena(arena_), name(name_) {
const char *old = getenv(name);
had_old_value = old != nullptr;
if (had_old_value) old_value = PushStr8Copy(arena, Str8C(old));
Set(value);
}

~ScopedEnvVar() {
if (had_old_value) {
Set(old_value);
} else {
#if defined(_WIN32)
CHECK(SetEnvironmentVariableA(name, nullptr) != 0);
#else
CHECK(unsetenv(name) == 0);
#endif
}
}

void Set(String8 value) {
#if defined(_WIN32)
CHECK(SetEnvironmentVariableA(name, PushCStr(arena, value)) != 0);
#else
CHECK(setenv(name, PushCStr(arena, value), 1) == 0);
#endif
}

};

struct ScopedEnvUnset {
Arena *arena;
const char *name;
bool had_old_value;
String8 old_value;

ScopedEnvUnset(Arena *arena_, const char *name_) : arena(arena_), name(name_) {
const char *old = getenv(name);
had_old_value = old != nullptr;
if (had_old_value) old_value = PushStr8Copy(arena, Str8C(old));
#if defined(_WIN32)
CHECK(SetEnvironmentVariableA(name, nullptr) != 0);
#else
CHECK(unsetenv(name) == 0);
#endif
}

~ScopedEnvUnset() {
if (!had_old_value) return;
#if defined(_WIN32)
CHECK(SetEnvironmentVariableA(name, PushCStr(arena, old_value)) != 0);
#else
CHECK(setenv(name, PushCStr(arena, old_value), 1) == 0);
#endif
}
};

} // namespace

TEST(toml_parses_tables_strings_and_arrays) {
Expand Down Expand Up @@ -180,3 +242,33 @@ TEST(config_example_file_parses) {
CHECK(loaded.ok);
CHECK(loaded.config.binding_count > 100);
}

TEST(config_default_path_platform_location) {
ArenaScope scope;
ScopedFixtureDir dir(scope.arena, "default_path");

{
ScopedEnvVar xdg(scope.arena, "XDG_CONFIG_HOME", dir.path);
String8 path = ConfigDefaultPath(scope.arena);
String8 expect = OsPathJoin(scope.arena, dir.path, Str8Lit("1coder/config.toml"));
CHECK_STR(path, expect);
}

#if defined(_WIN32)
{
ScopedEnvUnset xdg(scope.arena, "XDG_CONFIG_HOME");
ScopedEnvVar appdata(scope.arena, "APPDATA", dir.path);
String8 path = ConfigDefaultPath(scope.arena);
String8 expect = OsPathJoin(scope.arena, dir.path, Str8Lit("1coder/config.toml"));
CHECK_STR(path, expect);
}
#else
{
ScopedEnvUnset xdg(scope.arena, "XDG_CONFIG_HOME");
ScopedEnvVar home(scope.arena, "HOME", dir.path);
String8 path = ConfigDefaultPath(scope.arena);
String8 expect = OsPathJoin(scope.arena, dir.path, Str8Lit(".config/1coder/config.toml"));
CHECK_STR(path, expect);
}
#endif
}
Loading