开发一个 vscode 插件,提升 markdown 排版可读性🌿

简介: 开发一个 vscode 插件,提升 markdown 排版可读性🌿

背景介绍


不知道大家有没有被吐槽过文档格式看着不舒服,格式乱七八糟呢,比如说给大家看一组排版的对比:


  • 不好的案例:我看你这鸡蛋不错,给我来10kg.
  • 好的案例:我看你这鸡蛋不错,给我来 10 kg。


标点符号使用错误,没有空格,看着让人很不舒服😭,其实从这一个案例可以看出,规范整齐的排版对文章或者文档的阅读体验有很大的提升🔥。


而且作为一个分享者,排版的整齐就更加重要了,可以显著提升读者体验,所以我也有预谋的想要提升我的文章质量,但是人工去查排版是否规范存在很多问题:


  • 人力成本高
  • 无法保证 100% 都核查到


于是我想通过自动化的手段去提高文档的阅读体验,想到的办法就是通过 vscode 插件🌟,于是就诞生了我的最新插件: markdown-format 「已经可以在 vscode 插件商店搜索到了~肯定还有 bug,欢迎大家帮忙测试」。


插件中的文档排版规范来自于:译文排版规则指北


方案实现


网络异常,图片无法展示
|


创建一个 vscode-extension 项目


首先还是熟悉的流程,用脚手架创建一个项目:官方文档


安装 vscode 插件开发脚手架:


npm install -g yo generator-code


输入yo code初始化代码


网络异常,图片无法展示
|


之后我们先去配置一下 package.json 文件:


"activationEvents": [
    "onCommand:markdown-format.format"
  ],
  "main": "./extension.js",
  "contributes": {
    "commands": [
      {
        "command": "markdown-format.format",
        "title": "格式化markdown"
      }
    ],
    "menus": {
      "explorer/context": [
        {
          "command": "markdown-format.format",
          "when": "filesExplorerFocus",
          "group": "navigation@1"
        }
      ]
    }
  },


主要配置了一个 format 命令,触发时机是右键点击文件。


网络异常,图片无法展示
|


判断文件类型是否合规


下一步要去判断文件类型是否为 markdown,不是 markdown则不进行操作。


const isMarkdown = (path) => {
  if (isDir(path)) {
    return false;
  }
  if (extname(path) != '.md') {
    return false;
  }
  return true;
}


词法分析


下一步就是词法分析,把 markdown 文本解析为 token 序列,其中 token 序列分为以下几种类型:


  • 空格 SPACE
  • 换行 ENTER
  • 半角符号 HALF
  • 全角符号 FULL
  • 中文 CHINESE
  • 数字 NUMBER
  • 英文 ENGLISH


如果不了解编译原理的可以去阅读我之前的文章:前端学编译原理(一):编译引论

这里其实也就是遍历字符串的操作


function lexicalAnalysis(content) {
  const tokenList = [];
  let currentStr = '';
  let currentType = '';
  const handleChar = (char, type) => {
    if (currentType == type) {
      currentStr += char;
    } else {
      tokenList.push({
        type: currentType,
        content: currentStr
      })
      currentStr = char;
      currentType = type;
    }
  }
  for (const char of content) {
    if (char == ' ') {
      handleChar(char, 'SPACE');
    } else if (char == '\n') {
      handleChar(char, 'ENTER');
    } else if (char.match(/[\x21-\x2f\x3a-\x40\x5b-\x60\x7B-\x7F]/) || char.charCodeAt() === 8212) {
      //半角 和破折号 ——
      handleChar(char, 'HALF');
    } else if (char.charCodeAt() === 12290 || (char.charCodeAt() > 65280 && char.charCodeAt() < 65375)) {
      //全角 (处理一下。) TODO: 为什么句号的charcode这么奇怪
      handleChar(char, 'FULL');
    } else if (char.match(/[\u4e00-\u9fa5]/)) {
      //中文
      handleChar(char, 'CHINESE')
    } else if (char.match(/[0-9]/)) {
      handleChar(char, 'NUMBER');
    } else {
      handleChar(char, 'ENGLISH');
    }
  }
  tokenList.push({
    type: currentType,
    content: currentStr
  })
  return tokenList;
}


这里给大家看一下 Token 序列的格式是什么样的


markdown 内容为:


你是`asdas`寒——草.w  
主要是123ni.D...  
啊。。。上不去github


Token 序列为:


0: {type: '', content: ''}
1: {type: 'CHINESE', content: '你是'}
2: {type: 'HALF', content: '`'}
3: {type: 'ENGLISH', content: 'asdas'}
4: {type: 'HALF', content: '`'}
5: {type: 'CHINESE', content: '寒'}
6: {type: 'HALF', content: '——'}
7: {type: 'CHINESE', content: '草'}
8: {type: 'HALF', content: '.'}
9: {type: 'ENGLISH', content: 'w'}
10: {type: 'SPACE', content: '  '}
11: {type: 'ENTER', content: '\n'}
12: {type: 'CHINESE', content: '主要是'}
13: {type: 'NUMBER', content: '123'}
14: {type: 'ENGLISH', content: 'ni'}
15: {type: 'HALF', content: '.'}
16: {type: 'ENGLISH', content: 'D'}
17: {type: 'HALF', content: '...'}
18: {type: 'SPACE', content: '  '}
19: {type: 'ENTER', content: '\n'}
20: {type: 'CHINESE', content: '啊'}
21: {type: 'FULL', content: '。。。'}
22: {type: 'CHINESE', content: '上不去'}
23: {type: 'ENGLISH', content: 'github'}


处理 Token 序列


这里我打算直接基于这个 Token 序列进行操作之后再组合在一起,下面我按照每条规则单独来说


中英文之间需要增加空格 中文与数字之间要有空格


  • 错误:我喜欢javascript的灵活性
  • 正确:我喜欢 javascript 的灵活性


这里的思路为:


  • 如果我现在的 token 类型是中文并且下一个 token 的类型是数字或者英文,则加入空格
  • 如果我现在的 token 类型是中文并且接下来的内容是 markdown 语法中的代码行高亮,加粗,斜体标识且接着数字或者英文,则也加入空格
  • 如果现在匹配到的是数字或者英文,且下一个 token 的类型是中文,则加入空格
  • 如果现在匹配到的是 markdown 语法中的代码行高亮,加粗,斜体标识,且前一位是英文或者数字,后一位是中文,则插入空格


if(tokenList[i].type == 'CHINESE') {
  if(tokenList[i+1] && ['ENGLISH', 'NUMBER'].includes(tokenList[i+1].type)) {
    resList.push(SPACE_TOKEN);
  }
  if(tokenList[i+2] && ['`', '**', '*'].includes(tokenList[i+1].content) && ['ENGLISH', 'NUMBER'].includes(tokenList[i+2].type)){
    resList.push(SPACE_TOKEN);
  }
}
// 后
if(['ENGLISH', 'NUMBER'].includes(tokenList[i].type)) {
  if(tokenList[i+1] && tokenList[i+1].type === 'CHINESE') {
    resList.push(SPACE_TOKEN);
  }
}
if(['`', '**', '*'].includes(tokenList[i].content)) {
  if(resList[resList.length - 2]&&['ENGLISH', 'NUMBER'].includes(resList[resList.length - 2].type) && tokenList[i+1] && tokenList[i+1].type === 'CHINESE') {
    resList.push(SPACE_TOKEN);
  }
}


数字与单位之间需要增加空格


  • 错误:这筐鸡蛋有 5kg。
  • 正确:这筐鸡蛋有 5 kg。

这里的思路为:

这个就是让数字和英文之间加上空格,不多介绍了「但是存在问题


TODO:这里存在一定的误差,我在想是否要维护一个单位列表


if(tokenList[i].type === 'NUMBER' && tokenList[i+1] && tokenList[i+1].type === 'ENGLISH') {
  resList.push(SPACE_TOKEN);
}

全角标点与其他字符之间不加空格


  • 错误:我喜欢你 , 你是小仙女。
  • 正确:我喜欢你,你是小仙女。

这里的思路为:

  • 如果当前字符是空格,且后一个字符是全角标点时,删除此空格
  • 如果当前字符是空格,且前一个字符是全角标点时,删除此空格


if(resList[resList.length - 1].type == 'SPACE'&&tokenList[i+1]&&tokenList[i+1].type === 'FULL') {
  resList.pop();
}
// -- 避免不一致的情况
if(resList[resList.length - 1]== 'SPACE'&&resList[resList.length - 2]&&resList[resList.length - 2].type === 'FULL') {
  resList.pop();
}


不重复使用标点符号

  • 错误:你是卧底?!!!
  • 正确:你是卧底?!


这里只对 ?,!,【,】进行处理


if(tokenList[i].type === 'FULL') {
  resList.pop();
  let end = '';
  let str = '';
  for(const char of tokenList[i].content) {
    if(end == char && ["?", "!", "【", "】"].includes(char)){
      continue;
    } else {
      str += char;
    }
  }
  resList.push({
    type: 'FULL',
    content: str
  });
}


省略号


  • 错误:这里有西红柿,土豆,芹菜。。。
  • 正确:这里有西红柿,土豆,芹菜……


省略号应该显示为:……


if(resList[resList.length - 2] && ['CHINESE', 'ENGLISH'].includes(resList[resList.length - 2].type) && tokenList[i].content.match(/^[。\.]{2,}$/)){
  resList.pop();
  resList.push({
    type: 'HALF',
    content: '……'
  })
}


中文接的是全角字符


  • 错误:你好,我叫寒草.
  • 正确:你好,我叫寒草。


暂时支持切换的标点:


  • 。.
  • ,,
  • ??
  • !!
  • ;;
  • ::


考虑到闭合标签存在一些特殊情况,暂未做处理


if(['.', '?', '!', ';', ':'].includes(tokenList[i].content)) {
  if(resList[resList.length - 2] && resList[resList.length - 2].type == 'CHINESE') {
    resList.pop();
    resList.push({
      type: 'FULL',
      content: punctuationMap.get(tokenList[i].content)
    });
  }
}


英文接的是半角字符


  • 错误:I am hancao。
  • 正确:I am hancao.


英文与中文同理~


if(['。', '?', '!', ';', ':'].includes(tokenList[i].content)) {
  if(resList[resList.length - 2] && resList[resList.length - 2].type == 'ENGLISH') {
    resList.pop();
    resList.push({
      type: 'HALF',
      content: punctuationMap.get(tokenList[i].content)
    });
  }
}


功能演示


格式化前


网络异常,图片无法展示
|


格式化后


网络异常,图片无法展示
|


结束语


网络异常,图片无法展示
|


目前肯定有一定的 bug,我也会在自己的使用中不断完善


如果大家喜欢我的文章,点赞关注就是对我最大的支持🌿,感谢~


写在最后


你让我的世界五彩斑斓


class World {
  constructor(name) {
    this.isColorful = false;
    this.owner = name;
  }
  add(somebody) {
    if(somebody === 'YOU' && this.owner === '寒草') {
      this.isColorful = true;
    }
  }
}


相关文章
|
6月前
|
人工智能 前端开发 开发工具
9.2K Star!微信排版从未如此简单,这款开源神器让Markdown飞入公众号!
一款9.2K Star的开源神器,让微信公众号排版变得简单高效!支持Markdown语法,实时预览、多图床混搭、AI智能排版、自定义主题样式等功能一应俱全。通过沉浸式双栏编辑、七图床混合编排、AI写作助手和主题定制工坊等核心功能,彻底解放技术创作者的生产力。无论是技术博客迁移、多平台发布还是企业定制,都能满足需求。三步上手:在线体验、本地部署、公众号对接。项目地址:https://github.com/doocs/md
738 4
|
存储 安全 数据安全/隐私保护
Django 后端架构开发:富文本编辑器权限管理与 UEditor 、Wiki接入,实现 Markdown 文本编辑器
Django 后端架构开发:富文本编辑器权限管理与 UEditor 、Wiki接入,实现 Markdown 文本编辑器
626 0
|
存储 移动开发 编解码
基于HTML5开发的Markdown在线编辑器
Markdown是一种轻量级标记语言,以其简洁易读的格式而备受程序员和作者们的青睐。随着互联网的发展,越来越多的在线Markdown编辑器应运而生,为用户提供了更加便捷、高效的写作和编辑环境。本文将探讨基于HTML5开发的Markdown在线编辑器的设计原理、功能特点以及技术优势。
362 4
|
存储 移动开发 编解码
基于HTML5开发的Markdown在线编辑器
Markdown是一种轻量级标记语言,以其简洁易读的格式而备受程序员和作者们的青睐。随着互联网的发展,越来越多的在线Markdown编辑器应运而生,为用户提供了更加便捷、高效的写作和编辑环境。本文将探讨基于HTML5开发的Markdown在线编辑器的设计原理、功能特点以及技术优势。
228 1
基于HTML5开发的Markdown在线编辑器
|
人工智能 前端开发 开发者
编辑技能:使用 Markdown 标记语言仅仅靠键盘也能完成排版,鼠标兄弟可以一边歇着了
Markdown 是一种标记语言,对于 SaaS 服务商和 AI 提示词编写者来说是必备技能。它使用简单的符号如 `#`, `-`, `&gt;` 进行文本排版,无需复杂富文本编辑器。通过键盘输入即可实现标题、列表等样式,比 WPS 或 Word 更便捷。Markdown 广泛应用于云平台社区、问答网站、GitHub 项目文档及开源程序。了解基础后,适应不同平台的自定义语法相对容易,且有多种 Markdown 客户端工具可供选择,如 Typora、MarkText 和 Obsidian。Visual Studio Code 配合插件也是强大工具,适合撰写公众号推文等。
222 4
编辑技能:使用 Markdown 标记语言仅仅靠键盘也能完成排版,鼠标兄弟可以一边歇着了
|
前端开发
VSCode中自带插件Emmet的用法
Emmet 是一个强大的工具,集成在 Visual Studio Code (VSCode) 中,可以大大提高编写 HTML 和 CSS 的效率。以下是如何使用 Emmet 插件的一些基本方法
213 4
|
传感器 前端开发 JavaScript
前端开发者必备的VS Code插件推荐
前端开发者必备的VS Code插件推荐
|
开发者
Markdown:解放排版,简洁高效的文字创作神器!
Markdown 是一种轻量级标记语言,以易读易写著称,常用于生成 HTML 页面。其简洁的语法加速了排版,尤其在写作、博客和文档领域广泛应用。虽然不擅长复杂排版,但能轻松实现字体大小调整、插入表格、图片和超链接等。Markdown 通过键盘快捷操作,避免了 Word 等软件的繁琐设置。本文将深入讲解 Markdown 语法,助你提升效率。Markdown 适合快速学习,兼容各种文本编辑器,支持导出多种格式,广泛应用于 GitHub 和多个在线平台。
477 0
|
API 开发工具 C++
【专栏:工具与技巧篇】使用代码编辑器(VS Code/Sublime Text)提升开发效率
【4月更文挑战第30天】在VS Code与Sublime Text两大流行代码编辑器中,开发者可借助其高效特性提升编程效率。VS Code拥有丰富的插件生态、内置Git集成、强大的调试工具、智能提示和多文件导航功能。Sublime Text则以其轻量级、快速响应、多光标编辑及自定义构建系统见长。学习编辑器的键盘快捷键、自定义配置、使用内置终端以及键绑定和宏,将助开发者进一步提高开发效率。选择适合自己的编辑器并不断适应新技术是提升开发工作流的关键。
566 1
|
存储 Shell 网络安全
【vscode】在本地加载远端环境并开发
【vscode】在本地加载远端环境并开发
838 0

热门文章

最新文章