代码编辑器代码格式化器设计完全指南

代码格式化是现代编辑器的核心功能之一。良好的格式化功能可以让代码保持一致的风格,提升可读性。本文将介绍代码编辑器中格式化器的设计与实现。

格式化器概述

一个完整的代码格式化器通常包含以下功能:

  • 缩进处理:根据语法结构添加或修正缩进
  • 换行处理:控制单行代码的最大长度,超长自动换行
  • 空格处理:运算符周围添加空格、移除多余空格
  • 对齐处理:对齐赋值号、参数、数组元素等
  • 括号处理:控制花括号位置、换行风格

解析器设计

格式化之前,需要先解析代码生成抽象语法树(AST)。这里以简化版为例:

/* formatter_ast.h - 语法树定义 */
#ifndef FORMATTER_AST_H
#define FORMATTER_AST_H

typedef enum {
    NODE_PROGRAM,
    NODE_FUNCTION,
    NODE_BLOCK,
    NODE_IF,
    NODE_FOR,
    NODE_WHILE,
    NODE_RETURN,
    NODE_EXPRESSION,
    NODE_ASSIGNMENT,
    NODE_BINARY_OP,
    NODE_UNARY_OP,
    NODE_CALL,
    NODE_VARIABLE,
    NODE_LITERAL
} NodeType;

typedef struct ASTNode {
    NodeType type;
    char *value;
    struct ASTNode **children;
    size_t child_count;
    int line;
    int column;
} ASTNode;

/* 解析源代码生成 AST */
ASTNode *parse(const char *source);

/* 释放 AST */
void ast_free(ASTNode *node);

#endif

格式化选项配置

允许用户自定义格式化行为:

/* formatter_options.h - 格式化选项 */
#ifndef FORMATTER_OPTIONS_H
#define FORMATTER_OPTIONS_H

typedef struct {
    /* 缩进设置 */
    int indent_size;           /* 缩进宽度,默认 4 */
    int tab_size;              /* Tab 宽度,默认 4 */
    int use_spaces;            /* 使用空格缩进,默认 1 */
    int indent_with_tabs;      /* 使用 Tab 缩进,默认 0 */

    /* 行宽设置 */
    int print_width;          /* 最大行宽,默认 80 */

    /* 空格设置 */
    int spaces_around_operators;    /* 运算符周围空格 */
    int spaces_around_brackets;     /* 括号周围空格 */
    int space_before_paren;         /* 函数圆括号前空格 */
    int align_var_decl;             /* 变量声明对齐 */

    /* 括号风格 */
    int brace_style;         /* 0: K&R, 1: Allman, 2: GNU */

    /* 其他 */
    int insert_final_newline;      /* 文件末尾插入新行 */
    int trim_trailing_whitespace;  /* 去除末尾空白 */
} FormatterOptions;

void options_init(FormatterOptions *opts);
int options_load_from_file(FormatterOptions *opts, const char *path);

#endif

核心格式化逻辑

/* formatter.c - 格式化器核心实现 */
#include 
#include 
#include 
#include 
#include "formatter_options.h"
#include "formatter_ast.h"

typedef struct FormatterContext {
    FormatterOptions *opts;
    char *output;
    size_t output_size;
    size_t output_capacity;
    int current_indent;
    int current_column;
} FormatterContext;

/* 输出格式化文本 */
static void format_append(FormatterContext *ctx, const char *fmt, ...) {
    va_list args;
    char buffer[1024];

    va_start(args, fmt);
    int len = vsnprintf(buffer, sizeof(buffer), fmt, args);
    va_end(args);

    /* 扩容检查 */
    while (ctx->output_size + len + 1 > ctx->output_capacity) {
        ctx->output_capacity *= 2;
        ctx->output = realloc(ctx->output, ctx->output_capacity);
    }

    memcpy(ctx->output + ctx->output_size, buffer, len);
    ctx->output_size += len;
    ctx->output[ctx->output_size] = '\0';
    ctx->current_column += len;
}

/* 输出缩进 */
static void format_indent(FormatterContext *ctx) {
    int indent = ctx->current_indent;
    if (ctx->opts->use_spaces) {
        for (int i = 0; i < indent * ctx->opts->indent_size; i++) {
            format_append(ctx, " ");
        }
    } else {
        format_append(ctx, "\t");
    }
}

处理花括号风格

/* 处理花括号换行风格 */
static void format_open_brace(FormatterContext *ctx, int *need_newline) {
    switch (ctx->opts->brace_style) {
        case 0: /* K&R 风格 - 同行 */
            format_append(ctx, " {\n");
            ctx->current_indent++;
            ctx->current_column = 0;
            *need_newline = 0;
            break;

        case 1: /* Allman 风格 - 独立行 */
            format_append(ctx, "\n");
            format_indent(ctx);
            format_append(ctx, "{\n");
            ctx->current_indent++;
            ctx->current_column = 0;
            *need_newline = 0;
            break;

        case 2: /* GNU 风格 - 独立行 + 缩进 */
            format_append(ctx, " {\n");
            format_indent(ctx);
            ctx->current_indent++;
            ctx->current_column = 0;
            *need_newline = 0;
            break;
    }
}

static void format_close_brace(FormatterContext *ctx) {
    ctx->current_indent--;
    format_append(ctx, "\n");
    format_indent(ctx);
    format_append(ctx, "}");
    ctx->current_column = 1;
}

运算符空格处理

/* 格式化二元运算符 */
static void format_binary_op(FormatterContext *ctx, const char *op) {
    if (ctx->opts->spaces_around_operators) {
        format_append(ctx, " %s ", op);
        ctx->current_column += strlen(op) + 2;
    } else {
        format_append(ctx, "%s", op);
        ctx->current_column += strlen(op);
    }
}

/* 检查是否需要换行 */
static int should_break_line(FormatterContext *ctx, int estimated_len) {
    if (ctx->current_column + estimated_len > ctx->opts->print_width) {
        return 1;
    }
    return 0;
}

/* 换行并缩进 */
static void format_break_line(FormatterContext *ctx) {
    format_append(ctx, "\n");
    format_indent(ctx);
    ctx->current_column = ctx->current_indent * ctx->opts->indent_size;
}

主格式化入口

/* 格式化整个程序 */
char *format_code(const char *source, FormatterOptions *opts) {
    /* 1. 解析源代码为 AST */
    ASTNode *ast = parse(source);
    if (!ast) return NULL;

    /* 2. 初始化格式化上下文 */
    FormatterContext ctx;
    ctx.opts = opts;
    ctx.current_indent = 0;
    ctx.current_column = 0;
    ctx.output_capacity = 4096;
    ctx.output_size = 0;
    ctx.output = malloc(ctx.output_capacity);
    ctx.output[0] = '\0';

    /* 3. 遍历 AST 生成格式化后的代码 */
    format_node(&ctx, ast);

    /* 4. 处理末尾 */
    if (opts->insert_final_newline && ctx.output_size > 0) {
        if (ctx.output[ctx.output_size - 1] != '\n') {
            format_append(&ctx, "\n");
        }
    }

    /* 5. 清理 */
    ast_free(ast);

    return ctx.output;
}

/* 格式化单个节点 */
static void format_node(FormatterContext *ctx, ASTNode *node) {
    if (!node) return;

    switch (node->type) {
        case NODE_BLOCK:
            format_block(ctx, node);
            break;
        case NODE_IF:
            format_if(ctx, node);
            break;
        case NODE_FOR:
        case NODE_WHILE:
            format_loop(ctx, node);
            break;
        case NODE_FUNCTION:
            format_function(ctx, node);
            break;
        /* ... 其他节点类型 ... */
    }
}

格式化 API 设计

/* formatter_api.h - 对外 API */
#ifndef FORMATTER_API_H
#define FORMATTER_API_H

#include "formatter_options.h"

/* 格式化代码字符串 */
char *format(const char *source, FormatterOptions *opts);

/* 格式化文件 */
int format_file(const char *input_path,
              const char *output_path,
              FormatterOptions *opts);

/* 原地格式化(修改原文件)*/
int format_file_inplace(const char *path,
                          FormatterOptions *opts);

#endif

实现建议

  • 使用增量解析,只重新格式化修改的部分
  • 支持格式化前后的 diff 预览
  • 提供 "保存时格式化" 选项
  • 支持针对不同语言使用不同的格式化规则
  • 考虑集成 Prettier、clang-format 等成熟工具

总结

代码格式化器是编辑器中复杂度较高的功能模块。它需要先解析代码为语法树,然后根据用户配置逐个处理节点,最后输出格式化的代码。本文介绍的设计方案涵盖了核心的数据结构、配置选项和处理逻辑,可以作为实现格式化器的基础。