How fzf Handles Unicode Normalization with the `--literal` Option

The --literal flag disables fzf's default Unicode normalization, forcing exact code-point matching for accented characters instead of treating "é" and "e" as equivalent.

By default, fzf normalizes Latin-script characters so that searches ignore diacritics and accent marks. This behavior is controlled by the Normalize option, which defaults to true in the standard configuration. When you invoke fzf with the --literal flag, you disable this normalization pipeline, ensuring that only exact Unicode matches are returned.

How fzf Unicode Normalization Works by Default

Out of the box, fzf treats accented characters as equivalent to their base forms. This is enabled by the default value of Normalize set in src/options.go:

// Default options
func defaultOptions() *Options {
    return &Options{
        Normalize:   true,
        // ... other defaults
    }
}

The actual mapping of Unicode code points occurs in src/algo/normalize.go. This file contains a static table that maps accented Latin characters to their ASCII equivalents. For example, the rune 0x00E9 ("é") maps to 'e'. During the matching process, fzf consults this table to convert characters before comparison, allowing "cafe" to match "café".

How the --literal Flag Disables Normalization

When you pass --literal on the command line, fzf's option parser in src/options.go (lines 2679-2682) explicitly sets Normalize to false:

case "--literal":
    opts.Normalize = false          // disable normalization
case "--no-literal":
    opts.Normalize = true           // keep the default behaviour

Conversely, --no-literal (or simply omitting the flag) maintains the default true value. This boolean flag is then propagated through the entire search pipeline, determining whether the normalization lookup table is consulted during string comparison.

The Technical Flow from Flag to Match

The Normalize boolean flows through three critical stages before affecting the final match result.

Option Parsing in src/options.go

As shown above, the command-line parser sets opts.Normalize based on the presence of --literal. This value remains attached to the options structure throughout the program's execution.

Pattern Construction in src/pattern.go

The BuildPattern function receives the normalize boolean and stores it inside each Pattern object (lines 77-84). When the pattern is prepared for matching, this flag is passed to the low-level matching algorithms. At line 457, the pattern invokes the matching functions with the normalize parameter:

res, pos := pfun(caseSensitive, normalize, forward, part.text, pattern, withPos, slab)

Algorithm Execution in src/algo/algo.go

Inside the core algorithms (such as FuzzyMatchV2), the normalize flag determines whether each rune is processed through normalizeRune. At lines 492-496, the code checks this flag before conversion:

if normalize {
    char = normalizeRune(char)
}

If normalize is false (due to --literal), the original Unicode code point is used for comparison, bypassing the mapping defined in src/algo/normalize.go.

Practical Examples

The difference between normalized and literal matching is best demonstrated with accented text:

Flag Normalization Example query "café" in list Matching behaviour
default (no flag) enabled cafe café matches (diacritic ignored)
--literal disabled cafe café does not match (exact Unicode code points required)
--literal disabled café café matches (exact literal)

Command-line examples:


# List contains "café"

printf "café\ncafe\n" | fzf --filter cafe  # default: normalizes, both lines match

# Preserve diacritics – literal mode

printf "café\ncafe\n" | fzf --filter cafe --literal

# Only the exact "café" line is returned when searching for "café",

# and "cafe" will not match "café"

Scripting context:


# Script that needs exact Unicode matching

search_term="café"
matches=$(printf "café\ncafe\n" | fzf --filter --literal --query "$search_term")
echo "$matches"   # → prints only "café"

Summary

  • Default behavior: fzf enables Unicode normalization via the Normalize option (default true), mapping accented Latin characters to their ASCII equivalents during matching.
  • The --literal flag: Parsed in src/options.go, this flag sets opts.Normalize = false, disabling the normalization pipeline.
  • Propagation: The boolean flows through src/pattern.go into Pattern objects, then into the core algorithms in src/algo/algo.go.
  • Matching impact: When normalization is disabled, normalizeRune is bypassed, forcing exact Unicode code-point comparison. This means "e" will not match "é" when --literal is active.

Frequently Asked Questions

What is Unicode normalization in fzf?

Unicode normalization in fzf refers to the process of converting accented Latin characters (such as "é", "ñ", or "ü") to their base ASCII equivalents before performing string matching. This allows users to search for "cafe" and still match "café" without typing the accent. This behavior is controlled by the Normalize option and implemented through a static mapping table in src/algo/normalize.go.

Does --literal affect case sensitivity?

No, --literal only controls Unicode normalization (accented character handling). Case sensitivity in fzf is managed separately by the Case option, typically controlled via --case-sensitive or --ignore-case flags. You can use --literal together with case sensitivity options to achieve exact Unicode matching while still ignoring or respecting letter case as needed.

Which characters are affected by fzf's normalization?

fzf's normalization primarily affects Latin-script characters with diacritics and accent marks. The mapping table in src/algo/normalize.go includes conversions for characters such as "é" (U+00E9) to "e", "ñ" (U+00F1) to "n", and various other accented vowels and consonants. Characters outside the Latin script (such as Cyrillic, CJK characters, or emoji) are not modified by this normalization process.

How do I enable normalization after using --literal?

If you have previously used --literal in a script or alias and want to re-enable normalization, simply remove the --literal flag or explicitly add --no-literal. The --no-literal flag explicitly sets opts.Normalize = true, restoring the default behavior where accented characters are mapped to their base forms during matching.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →