How DesktopCommanderMCP's Line Ending Handler Normalizes CRLF and LF Across Operating Systems

DesktopCommanderMCP employs a two-step round-trip process in src/ui/file-preview/src/markdown/editor.ts to detect the original line ending style, normalize content to LF for the Tiptap editor, and restore CRLF or LF on save, ensuring cross-platform compatibility without altering file formats.

DesktopCommanderMCP's markdown editor must seamlessly handle files from Windows, macOS, and Linux systems, each potentially utilizing different line ending conventions. The application preserves the original CRLF (\r\n) or LF (\n) format while providing a consistent editing experience through the Tiptap editor. This is achieved through a sophisticated line ending normalization system implemented in the editor's core preprocessing module.

The Round-Trip Normalization Strategy

The normalization architecture follows a detect-normalize-restore pattern that operates transparently during the editing lifecycle. This approach ensures that Tiptap—which internally prefers LF line endings for reliable parsing—can process content without corrupting the original file's line ending convention.

Step 1: Detect and Normalize on Input

When a file is loaded into the editor, the preprocessForEditor function analyzes the raw content to determine the existing line ending style. As implemented in src/ui/file-preview/src/markdown/editor.ts (lines 28-33), the code checks for the presence of CRLF sequences:

const eol: '\r\n' | '\n' = input.includes('\r\n') ? '\r\n' : '\n';
const lf = eol === '\r\n' ? input.replace(/\r\n/g, '\n') : input;

This detection logic stores the original EOL (End Of Line) convention in a RoundTripContext object (lines 48-49). If CRLF is detected, the entire document is converted to LF format before being passed to Tiptap, preventing parsing inconsistencies that arise from mixed line endings.

Step 2: Restore Original EOL on Output

After editing completes and Tiptap serializes the markdown, the applyPostProcess function reverses the transformation. The serialized string—which always contains LF line endings at this stage—is converted back to the original format when necessary:

if (context.eol === '\r\n') {
    out = out.replace(/\n/g, '\r\n');
}

This ensures that Windows files retain their CRLF endings while Unix/Linux files preserve their LF format, regardless of the operating system hosting the editor or the intermediate processing steps.

Implementation Examples

The editor.ts module exports utility functions that handle normalization transparently. Developers integrating custom editing features can leverage these utilities to ensure line ending preservation.

Normalizing Input Before Editing

To prepare content for the editor while capturing line ending metadata:

import { preprocessForEditor } from './editor';

// Original content with Windows line endings
const raw = "title: Example\r\n\r\ncontent line 1\r\ncontent line 2\r\n";

// Preprocess – converts to LF for Tiptap, saves original EOL
const { editorInput, context } = preprocessForEditor(raw);
console.log(editorInput); // LF-only string
console.log(context.eol); // '\r\n'

Restoring Line Endings After Editing

To finalize content and restore the original format before saving:

import { applyPostProcess } from './editor';

// Assume `serialized` is the LF-only output from Tiptap
const restored = applyPostProcess(serialized, context);
console.log(restored.includes('\r\n')); // true if original used CRLF

Key Source Files

The line ending normalization system relies on the following components within the markdown editor subsystem:

Summary

  • Detection First: The system analyzes incoming content for CRLF sequences before any processing occurs to determine the original format.
  • LF Normalization: Content is always converted to LF format for internal Tiptap operations to ensure parser compatibility and prevent data corruption.
  • Context Preservation: The RoundTripContext object stores the original line ending style (\r\n or \n) throughout the editing session at lines 48-49.
  • Format Restoration: The post-processing step reverts the serialized output to the original CRLF or LF format based on the stored context.
  • Cross-Platform Safety: Files edited on any operating system maintain their original line ending conventions, preventing unintentional format changes in version control systems.

Frequently Asked Questions

Why does DesktopCommanderMCP need to normalize line endings?

Tiptap's markdown parser does not reliably retain CRLF sequences, which can lead to inconsistent formatting or data corruption when editing Windows-style files. By normalizing to LF internally, the editor ensures stable parsing while the round-trip handler preserves the original file format according to the repository's source code.

How does the editor detect the original line ending style?

The detection occurs in preprocessForEditor through a simple string inclusion check: input.includes('\r\n'). If this returns true, the system assumes CRLF format; otherwise, it defaults to LF. This heuristic accurately identifies the line ending convention used in the vast majority of text files across operating systems.

Will editing a file change its line endings?

No. The applyPostProcess function guarantees that files retain their original line ending convention. A file originally using CRLF will be saved with CRLF, and an LF file will remain LF, regardless of the modifications made during editing or the host operating system's default line ending style.

Where is the line ending state stored during editing?

The state persists in the RoundTripContext object, which is passed between the preprocessing and post-processing stages. This context stores the eol property ('\r\n' or '\n') at lines 48-49 of src/ui/file-preview/src/markdown/editor.ts, ensuring the information survives the entire editing lifecycle from file open to file save.

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 →