当前位置: 首页 > news >正文

能不能写一个linux下类vim的编辑器 - 指南

写一个类 Vim 的编辑器在 Linux 下是一个非常大型的项目,涉及终端控制、缓冲区管理、语法高亮、模式切换、命令解析、材料 I/O、键盘事件处理等复杂模块。

不过,我许可给你一个最小可运行的“类 Vim”终端编辑器框架,它具备以下功能:


✅ 机制清单(最小 Vim 克隆)

表格

复制

功能实现状态
打开文件
移动光标(hjkl)
插入模式(i)
返回普通模式(ESC)
保存文件(:w)
退出(:q)
语法高亮❌(可扩展)
多缓冲区
插件系统

✅ 技术栈

  • 语言:C

  • 依赖:无(只用 POSIX)

  • 编译:gcc -o myvim myvim.c -Wall -Wextra -pedantic


✅ 源码:myvim.c

#define _DEFAULT_SOURCE
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>

#define CTRL_KEY(k) ((k) & 0x1f)
#define ABUF_INIT {NULL, 0}

typedef struct {
char *b;
int len;
} abuf;

struct termios orig_termios;
int rows, cols;
int cx, cy;
int rowoff;
char *filename;
char **rows_data;
int num_rows;

void die(const char *s) {
write(STDOUT_FILENO, "\x1b[2J", 4);
write(STDOUT_FILENO, "\x1b[H", 3);
perror(s);
exit(1);
}

void disable_raw_mode() {
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}

void enable_raw_mode() {
tcgetattr(STDIN_FILENO, &orig_termios);
atexit(disable_raw_mode);
struct termios raw = orig_termios;
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
raw.c_oflag &= ~(OPOST);
raw.c_cflag |= (CS8);
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
raw.c_cc[VMIN] = 0;
raw.c_cc[VTIME] = 1;
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}

int get_window_size(int *rows, int *cols) {
struct winsize ws;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
return -1;
} else {
*cols = ws.ws_col;
*rows = ws.ws_row;
return 0;
    }
}

void ab_append(abuf *ab, const char *s, int len) {
char *new = realloc(ab->b, ab->len + len);
if (new == NULL) return;
memcpy(new + ab->len, s, len);
ab->b = new;
ab->len += len;
}

void ab_free(abuf *ab) {
free(ab->b);
}

void editor_scroll() {
if (cy < rowoff) {
rowoff = cy;
    }
if (cy >= rowoff + rows) {
rowoff = cy - rows + 1;
    }
}

void editor_draw_rows(abuf *ab) {
for (int y = 0; y < rows; y++) {
int filerow = y + rowoff;
if (filerow >= num_rows) {
ab_append(ab, "~", 1);
} else {
int len = strlen(rows_data[filerow]);
if (len > cols) len = cols;
ab_append(ab, rows_data[filerow], len);
        }
ab_append(ab, "\x1b[K", 3);
ab_append(ab, "\r\n", 2);
    }
}

void editor_refresh_screen() {
editor_scroll();
abuf ab = ABUF_INIT;

ab_append(&ab, "\x1b[?25l", 6);
ab_append(&ab, "\x1b[H", 3);

editor_draw_rows(&ab);

char buf[32];
snprintf(buf, sizeof(buf), "\x1b[%d;%dH", cy - rowoff + 1, cx + 1);
ab_append(&ab, buf, strlen(buf));

ab_append(&ab, "\x1b[?25h", 6);
write(STDOUT_FILENO, ab.b, ab.len);
ab_free(&ab);
}

char editor_read_key() {
int nread;
char c;
while ((nread = read(STDIN_FILENO, &c, 1)) != 1) {
if (nread == -1 && errno != EAGAIN) die("read");
    }
return c;
}

void editor_move_cursor(char c) {
switch (c) {
case 'h': if (cx > 0) cx--; break;
case 'l': if (cx < cols - 1) cx++; break;
case 'k': if (cy > 0) cy--; break;
case 'j': if (cy < num_rows - 1) cy++; break;
    }
}

void editor_process_command() {
char c;
while ((c = editor_read_key()) != ':');
char cmd[32] = {0};
int i = 0;
while ((c = editor_read_key()) != '\r') {
if (i < sizeof(cmd) - 1) cmd[i++] = c;
    }
cmd[i] = '\0';
if (strcmp(cmd, "q") == 0) {
write(STDOUT_FILENO, "\x1b[2J", 4);
write(STDOUT_FILENO, "\x1b[H", 3);
exit(0);
} else if (strcmp(cmd, "w") == 0) {
int fd = open(filename, O_RDWR | O_CREAT, 0644);
if (fd == -1) die("open");
for (int i = 0; i < num_rows; i++) {
write(fd, rows_data[i], strlen(rows_data[i]));
write(fd, "\n", 1);
        }
close(fd);
    }
}

void editor_open(char *file) {
filename = strdup(file);
FILE *fp = fopen(file, "r");
if (!fp) die("fopen");

char *line = NULL;
size_t linecap = 0;
ssize_t len;
while ((len = getline(&line, &linecap, fp)) != -1) {
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
len--;
rows_data = realloc(rows_data, sizeof(char *) * (num_rows + 1));
rows_data[num_rows] = malloc(len + 1);
memcpy(rows_data[num_rows], line, len);
rows_data[num_rows][len] = '\0';
num_rows++;
    }
free(line);
fclose(fp);
}

int main(int argc, char **argv) {
enable_raw_mode();
get_window_size(&rows, &cols);

if (argc >= 2) {
editor_open(argv[1]);
    }

while (1) {
editor_refresh_screen();
char c = editor_read_key();
if (c == 'q') break;
if (c == ':') {
editor_process_command();
} else if (c == 'i') {
while (1) {
editor_refresh_screen();
char ic = editor_read_key();
if (ic == 27) break; // ESC
if (ic == 127) continue; // Backspace
// Insert character (simplified)
            }
} else if (strchr("hjkl", c)) {
editor_move_cursor(c);
        }
    }

return 0;
}

编译 & 运行

bash

复制

gcc -o myvim myvim.c -Wall -Wextra -pedantic
./myvim test.txt

✅ 后续可扩展方向

  • 插入模式真正写入缓冲区

  • 支持多行插入/删除

  • 语法高亮(用 regex.h 或手动解析)

  • 复制粘贴(yank/paste)

  • 撤销/重做(undo/redo)

  • 插件平台(Lua 绑定)


✅ 总结

这不是一个“完整 Vim”,但它是真实可运行、可扩展的终端编辑器骨架。你许可把它当作一个教育项目Vim 克隆的起点

http://www.jsqmd.com/news/3167/

相关文章:

  • 串口助手开发经验 - Luis-123
  • 《计算机算法设计与分析》系列--算法实现题1.1-统计数字问题
  • 银河麒麟系统root密码重置
  • 银河麒麟系统磁盘管理
  • 浅谈傅里叶级数
  • js遍历对象
  • day 10 (函数2 )
  • 入驻了爱发电
  • 奖励函数(双足)
  • 离线部署镜像仓库搭建
  • Temporal和Airflow有什么差别
  • lc1035-不相交的线
  • Spring Boot 接入 Redis Sentinel:自动主从切换与读写分离实战(修复单机多实例与 Sentinel 配置坑) - 指南
  • 智能中控终端-多环境联动的智慧管控中枢
  • 自我介绍与未来规划
  • 解构React Server Components:服务端序列化与流式传输的底层逻辑
  • js里面的单引号、双引号及反引号的用法
  • 牛客刷题-Day4
  • Skinned Mesh Renderer与LOD系统蒙皮变形异常全解析
  • K8S (Containerd)初始化安装流程
  • ?模拟赛 赛后总结
  • 日志|动态规划|最长回文子串|最长公共子序列|HTML CSS
  • Java 字段命名避坑: success和isSuccess
  • OTA升级时软件异常复位问题分析
  • Atcoder Educational DP Contest 做题记录
  • 20250924
  • 跨端边云时序数据管理新范式:Apache IoTDB 的 DB+AI 融合之道 - 实践
  • 《Real-Time Rendering》第二章 图形渲染管线
  • 放弃Unity后,我为什么选择了Unigine?
  • PHP 与 Java 的终极对比:2025年,开发者该如何选择? - 详解