代码编辑器缩进与空格处理完全指南

缩进是代码可读性的基础。不同语言、不同团队可能采用不同的缩进策略,编辑器需要灵活支持这些配置,并提供良好的可视化体验。

缩进类型

常见的缩进类型有两种:

  • 制表符(Tab):使用ASCII tab字符,可以自定义显示宽度
  • 空格(Space):使用空格字符,通常是2或4个空格

缩进配置

编辑器需要支持灵活的缩进配置:

/* 缩进配置 */
interface IndentConfig {
    useTab: boolean;           /* true=Tab, false=空格 */
    tabSize: number;            /* Tab显示宽度 */
    indentSize: number;         /* 缩进宽度 */
    autoIndent: boolean;        /* 自动缩进 */
}

const defaultConfig: IndentConfig = {
    useTab: false,
    tabSize: 4,
    indentSize: 4,
    autoIndent: true
};

/* 检测文件使用的缩进类型 */
function detectIndent(text: string): IndentConfig {
    const lines = text.split('\n');
    let tabCount = 0;
    let space2Count = 0;
    let space4Count = 0;

    for (const line of lines) {
        /* 检查行首的缩进 */
        const match = line.match(/^(\s+)/);
        if (!match) continue;

        const indent = match[1];
        if (includes(indent, '\t')) tabCount++;
        else if (indent.length % 2 === 0 && indent[0] === ' ') {
            if (indent.length === 2) space2Count++;
            else if (indent.length % 4 === 0) space4Count++;
        }
    }

    /* 返回检测结果 */
    if (tabCount > space2Count && tabCount > space4Count) {
        return { useTab: true, tabSize: 4, indentSize: 4, autoIndent: true };
    } else if (space4Count >= space2Count) {
        return { useTab: false, tabSize: 4, indentSize: 4, autoIndent: true };
    }
    return defaultConfig;
}

Tab转换为空格

在编辑时处理Tab和空格的转换:

/* Tab转空格 */
function tabToSpaces(text: string, tabSize: number): string {
    return text.replace(/\t/g, ' '.repeat(tabSize));
}

/* 空格转Tab */
function spacesToTab(text: string, tabSize: number): string {
    let result = '';
    let spaces = 0;

    for (const char of text) {
        if (char === ' ') {
            spaces++;
            if (spaces === tabSize) {
                result += '\t';
                spaces = 0;
            }
        } else {
            if (spaces > 0) {
                result += ' '.repeat(spaces);
                spaces = 0;
            }
            result += char;
        }
    }

    if (spaces > 0) {
        result += ' '.repeat(spaces);
    }
    return result;
}

自动缩进

按Enter时自动继承或增加缩进:

/* 计算新行的缩进 */
function calculateIndent(
    currentLine: string,
    config: IndentConfig
): string {
    /* 获取当前行的缩进 */
    const match = currentLine.match(^(\s*));
    let indent = match ? match[1] : '';

    /* 如果是Tab模式,转换为Tab */
    if (config.useTab) {
        /* 检查是否需要增加缩进(大括号结尾) */
        if (/[{[\(]$/.test(currentLine.trim())) {
            const currentIndent = getIndentCount(indent, config);
            indent = makeIndent(currentIndent + 1, config);
        }
    } else {
        /* 空格模式 */
        if (/[{[\(]$/.test(currentLine.trim())) {
            const currentIndent = indent.length;
            indent = ' '.repeat(currentIndent + config.indentSize);
        }
    }

    return indent;
}

function makeIndent(level: number, config: IndentConfig): string {
    if (config.useTab) {
        return '\t'.repeat(level);
    }
    return ' '.repeat(level * config.indentSize);
}

function getIndentCount(indent: string, config: IndentConfig): number {
    if (config.useTab) {
        return (indent.match(/\t/g) || []).length;
    }
    return Math.floor(indent.length / config.indentSize);
}

可视化空白字符

让用户看到不可见字符:

/* 可视化渲染配置 */
interface WhitespaceRender {
    showSpaces: boolean;
    showTabs: boolean;
    showLineEndings: boolean;
}

/* 空白字符渲染器 */
class WhitespaceRenderer {
    render(text: string, render: WhitespaceRender): string {
        let result = text;

        if (render.showTabs) {
            /* 将Tab渲染为箭头符号 */
            result = result.replace(
                /\t/g,
                '→' .repeat(3)
            );
        }

        if (render.showSpaces) {
            /* 将尾部空格渲染为点 */
            result = result.replace(
                / +$/gm,
                (match) => '·'.repeat(match.length)
            );
        }

        if (render.showLineEndings) {
            /* 显示行尾符号 */
            result = result.replace(
                /\n/g,
                '↵\n'
            );
        }

        return result;
    }
}

智能缩进检测

根据语言特性自动调整缩进:

/* 语言特定的缩进规则 */
const languageIndentRules = {
    'python': {
        useTab: false,
        indentSize: 4,
        getSmartIndent: (line) => {
            /* Python: 缩进跟随上一行 */
            const match = line.match(/^(\s*)/);
            return match ? match[1].length : 0;
        }
    },
    'javascript': {
        useTab: false,
        indentSize: 2,
        getSmartIndent: (line) => {
            /* JS: 大括号不增加缩进 */
            const trimmed = line.trim();
            const baseIndent = getBaseIndent(line);

            if (/[{[\(]$/.test(trimmed)) {
                return baseIndent + 2;
            }
            if (/[}\])]$/.test(trimmed)) {
                return Math.max(0, baseIndent - 2);
            }
            return baseIndent;
        }
    }
};

用户偏好

提供可视化选项让用户选择是否显示空白字符,同时在状态栏显示当前的缩进类型。

总结

  • 支持Tab和空格两种缩进类型
  • 提供自动检测文件缩进类型的功能
  • 自动缩进根据语言特性智能调整
  • 可视化空白字符提升可读性
  • 支持语言特定的缩进规则