# How to Configure File Type Detection and Associated Settings in Flow Control

> Learn how to configure file type detection in Flow Control. Customize settings by creating .conf files or let the editor automatically detect types using extensions and shebangs.

- Repository: [CJ van den Berg/flow](https://github.com/neurocyte/flow)
- Tags: how-to-guide
- Published: 2026-03-08

---

**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`:

```zig
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:

```zig
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:

```bash
~/.config/flow/file_type/<type>.conf

```

For example, [`rust.conf`](https://github.com/neurocyte/flow/blob/main/rust.conf) or [`python.conf`](https://github.com/neurocyte/flow/blob/main/python.conf).

### Configuration Format

The `.conf` files use a simple `key = value` syntax with bracket notation for arrays:

```text

# ~/.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 type
- **`description`**: Human-readable label
- **`icon`**: Unicode character or emoji for UI display
- **`color`**: Hex color code for theming
- **`language_server`**: Array of command arguments for LSP startup
- **`formatter`**: 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:

```zig
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:

```zig
pub const python = .{
    .language_server = .{ "uvx", "ty", "server" },
    .formatter = .{ "ruff", "format", "-" },
};

```

These are collected at compile time:

```zig
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:

```zig
// 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

```zig
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:

```zig
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:

```zig
// 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>.conf` files using simple `key = value` syntax, overriding built-in LSP and formatter settings.
- **Default bindings** for language servers and formatters are defined in `src/file_type_lsp.zig` and merged with user configs at runtime.
- **Programmatic access** is available through `file_type_config.get` for resolution and `file_type_config.guess_file_type` for detection.
- **UI integration** includes the `:change_file_type` command and palette for interactive selection, with icons and colors rendered via `src/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.