How to Configure File Type Detection and Associated Settings in Flow Control
Developers configure file type detection in Flow Control by placing <type>.conf files in ~/.config/flow/file_type/ to override built-in defaults, while the editor automatically detects types via file_type_config.guess_file_type using file extensions and shebang heuristics.
Flow Control (neurocyte/flow) implements a data-driven file-type system that determines how files are interpreted for syntax highlighting, language-server integration, and formatting. Understanding how to configure file type detection allows developers to customize editor behavior for any language or file format.
Architecture Overview
The file-type system consists of several coordinated components:
| Component | Purpose | Source File |
|---|---|---|
file_type_config.zig |
Core API for loading, caching, guessing, and exposing file-type objects | src/file_type_config.zig |
file_type_lsp.zig |
Built-in language-server and formatter bindings | src/file_type_lsp.zig |
editor.zig |
Detects and assigns file types when opening buffers | src/editor.zig |
| File Type Palette | UI overlay for browsing and switching types | src/tui/mode/overlay/file_type_palette.zig |
How Automatic File Type Detection Works
Flow Control uses a two-stage detection strategy implemented in src/file_type_config.zig.
First-Line Heuristics
The system first examines the file's content for shebangs or language markers via guess_first_line:
fn guess(file_path: ?[]const u8, content: []const u8) ?@This() {
if (guess_first_line(content)) |ft| return ft;
// ... extension matching
}
This detects executable scripts regardless of file extension.
Extension-Based Matching
If first-line detection fails, Flow iterates through all registered file types and matches against extensions:
for (get_all_names()) |file_type_name| {
const file_type = get(file_type_name) catch unreachable orelse unreachable;
if (file_path) |fp|
if (syntax.FileType.match_file_type(file_type.extensions orelse continue, fp))
return file_type;
}
The match_file_type function in the syntax module compares the file path against the type's extension list.
Configuring File Types via User Overrides
Developers can override or extend any file type by creating configuration files in the user config directory.
Configuration File Location
Place files at:
~/.config/flow/file_type/<type>.conf
For example, rust.conf or python.conf.
Configuration Format
The .conf files use a simple key = value syntax with bracket notation for arrays:
# ~/.config/flow/file_type/rust.conf
name = "rust"
description = "Rust"
icon = "🦀"
color = 0xDEA584
language_server = ["rust-analyzer"]
formatter = ["rustfmt"]
Available fields include:
name: Identifier for the file typedescription: Human-readable labelicon: Unicode character or emoji for UI displaycolor: Hex color code for theminglanguage_server: Array of command arguments for LSP startupformatter: Array of command arguments for formatting
Loading Mechanism
When file_type_config.get is called, it checks for user configurations before falling back to built-ins:
const file_name = try get_config_file_path(cache_allocator, file_type_name);
defer cache_allocator.free(file_name);
const file: ?std.fs.File = std.fs.openFileAbsolute(file_name, .{ .mode = .read_only }) catch null;
if (file) |f| {
// Parse user-provided *.conf file
// ...
} else {
// Fall back to built-in definition
break :file_type if (syntax.FileType.get_by_name_static(file_type_name))
|ft| from_file_type(ft) else null;
}
Default LSP and Formatter Bindings
Built-in language server and formatter configurations reside in src/file_type_lsp.zig.
Static Defaults
The file defines defaults for common languages:
pub const python = .{
.language_server = .{ "uvx", "ty", "server" },
.formatter = .{ "ruff", "format", "-" },
};
These are collected at compile time:
const static_file_type_lsp_defaults_list = load_file_type_lsp_defaults(@import("file_type_lsp.zig"));
const static_file_type_lsp_defaults = std.StaticStringMap(LspDefaults)
.initComptime(static_file_type_lsp_defaults_list);
Merging with User Configuration
When creating a file type configuration, from_file_type merges built-in LSP defaults with syntax definitions:
// In from_file_type (lines 25-40 of file_type_config.zig)
const lsp_defaults = file_type_lsp.static_file_type_lsp_defaults.get(file_type.name) orelse .{};
return .{
.name = file_type.name,
.language_server = lsp_defaults.language_server,
.formatter = lsp_defaults.formatter,
// ... other fields
};
User overrides in .conf files take precedence over these defaults.
Programmatic Access to File Types
Developers can interact with the file-type system programmatically in Zig.
Resolving a File Type by Name
const ftc = @import("file_type_config");
// Resolve a user-provided name
if (ftc.get("python")) |maybe_ft| {
const ft = maybe_ft orelse return error.UnknownFileType;
std.debug.print("LSP: {s}\n", .{ft.language_server.?});
std.debug.print("Formatter: {s}\n", .{ft.formatter.?});
}
Generating Default Configuration
To obtain a template for editing:
pub fn get_default(allocator: std.mem.Allocator, file_type_name: []const u8) ![]const u8 {
const file_type = syntax.FileType.get_by_name_static(file_type_name) orelse return error.UnknownFileType;
const config = from_file_type(file_type);
var content: std.Io.Writer.Allocating = .init(allocator);
defer content.deinit();
root.write_config_to_writer(@This(), config, &content.writer) catch {};
return content.toOwnedSlice();
}
This generates the text representation used when creating new .conf files.
Opening Buffers with Explicit Types
When opening a file, the editor accepts an optional file type override:
// In src/editor.zig (lines 735-762)
editor.open_buffer("/tmp/example", buffer, .{ .file_type = "markdown" });
If no type is specified, Flow calls file_type_config.guess_file_type to detect based on path and content.
Summary
- Automatic detection relies on
file_type_config.guess_file_type, which checks shebangs first, then file extensions against built-in definitions. - User customization happens via
~/.config/flow/file_type/<type>.conffiles using simplekey = valuesyntax, overriding built-in LSP and formatter settings. - Default bindings for language servers and formatters are defined in
src/file_type_lsp.zigand merged with user configs at runtime. - Programmatic access is available through
file_type_config.getfor resolution andfile_type_config.guess_file_typefor detection. - UI integration includes the
:change_file_typecommand and palette for interactive selection, with icons and colors rendered viasrc/tui/status/tabs.zig.
Frequently Asked Questions
How does Flow Control detect file types for files without extensions?
Flow Control uses first-line heuristics via guess_first_line in src/file_type_config.zig. This function examines the file's content for shebangs (e.g., #!/usr/bin/env python) or language-specific markers, allowing it to identify executable scripts and configuration files even when they lack standard extensions.
Where should I place custom file type configuration files?
Place custom configurations in ~/.config/flow/file_type/<type>.conf, where <type> is the identifier of the file type you want to override or create. Flow automatically loads these files at runtime, merging your custom language_server, formatter, icon, and color settings with the built-in defaults defined in src/file_type_lsp.zig.
Can I override the language server for a specific file type?
Yes. Create or edit a .conf file for the target type and specify the language_server array. For example, to use a different Python language server, create ~/.config/flow/file_type/python.conf with language_server = ["pylsp"]. This overrides the default ["uvx", "ty", "server"] defined in src/file_type_lsp.zig.
How do I force a specific file type when opening a file?
Use the :change_file_type command (bound to Ctrl-t by default) to open the file-type palette and select the desired type interactively. Programmatically, you can pass an explicit file_type option to editor.open_buffer in src/editor.zig, which bypasses automatic detection and assigns the specified type directly to the buffer.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →