Sunday, May 8, 2022

V2EX - 技术

V2EX - 技术


我的 Vim 自动补全配置变迁史

Posted: 08 May 2022 05:38 AM PDT

原文: https://reorx.com/blog/the-history-of-my-vim-completion-config/


Vim 是我系统学习的第一个终端编辑器,从学生时代至今,我几乎每天都会使用到它(长时间写前端代码时除外)。

自动补全( auto completion )大概是每个 Vim 用户在掌握了基本用法后,第一个想要进阶配置的功能。这篇文章记录了从 2017 年至今,我的 Vim 自动补全配置的每次变更,从中窥见 Vim 生态发展的一角,也纪念这些曾经给我带来过便利,最终在技术发展中被轮替的插件。

Before 2017

2017 年我从 Vim 切换到 Neovim(下文简称 nvim ),除了增加 nvim 特殊的 init.vim,基本沿用了以往的配置和插件。

彼时我使用的语言以 Python 为主,自动补全插件为 jedi-vim

我对 jedi-vim 的了解最早可以追溯到 2012 年,那时还没有 LSP 的概念。开发者们针对自己的需求,编写如语法增强、文档查看、自动补全等各类插件,非常零散。jedi-vim 对这些插件的功能进行了重构和集成,提供了开箱即用的统一解决方案,一经推出便广受好评,成为使用 Vim 进行 Python 开发的标配。在后来的十年里,它的初心始终不变,得到持续的维护并沿用至今。

2017

还是 2017 年,在切换到 nvim 后不久,我发现了 deoplete 插件,经过一番尝试将 jedi-vim 替换成了 deoplete + deoplete-jedi 。

Commit: 0760ba6f7d11526e38e15b36a0d1db8709834825

use deoplete, remove jedi-vim

committed on Jun 28, 2017

-Plug 'davidhalter/jedi-vim', { 'for': 'python' } +Plug 'Shougo/deoplete.nvim', { 'do': ':UpdateRemotePlugins' } +Plug 'zchee/deoplete-jedi', { 'for': 'python' } 

deoplete 的目标是提供一个通用的异步自动补全框架,这在设计理念上是一个巨大的进步。jedi-vim 虽然开箱即用,但却是一堆粘合在一起的 spaghetti code ,不仅随着项目功能的增加变得越发庞大和迟缓(这是我想要离开 jedi-vim 的主要原因,文件一大各种操作都变得肉眼可见的慢),代码的可读性也非常糟糕,难以维护和参与。而 deoplete 本身并不提供针对任何语言的分析能力,只专注于与 nvim 的整合和 completion source 的调度,并且利用 nvim 的异步功能(后来 vim 8 也推出了自己的 async 接口),大大提升了补全的流畅度。

但 deoplete 也有着自身的局限性。首先配置变得复杂且麻烦,用户得理解其架构和设计,学会如何通过 deoplete 对接编程语言的 completion source 。为了使检查结果的提示贴合自己的使用习惯,还要再去学习 completion source 的配置,每个语言的实现不同,配置也不一样。

当时我却没有料到,配置复杂的问题在 LSP 时代不仅没能得到解决,反而变本加厉,直到本文完成时也依旧是使用者的巨大痛点

deoplete 的第二个问题是,它只专注在 completion ,缺少对于 go to definition 和显示 function siguature 等功能的支持,这对于从 jedi-vim 的 all-in-one 体验切换过来的我,显然是个巨大的落差。好在我找到了其他插件来解决这些问题。

对于 "go to definition",通过装回 jedi-vim 并打开无补全模式可以解决。这样既可以使用 jedi 提供的 go to definition 等辅助功能,也不会与 deoplete 的补全产生冲突。

Plug 'davidhalter/jedi-vim', { 'for': 'python' }  " jedi (only for go to definition) let g:jedi#completions_enabled = 0 

对于 "function signature",我找到了 deoplete 作者的另一个插件 echodoc 来实现。它将函数的签名信息显示在 cmd 区域,规避了 deoplete 占用 completeopt 导致编辑界面无法显示补全菜单以外的其他信息的问题。

Plug 'Shougo/echodoc.vim'  " echodoc set noshowmode let g:echodoc#enable_at_startup=1 

2018

2018 年是里程碑式的一年,Language Server Protocol 的生态逐渐成熟,新的补全工具涌现。我对 LSP 感到相当兴奋和好奇,迫不及待地从 deoplete 更换到了对 LSP 有更好支持的 ncm2

Commit: 7a1442c2334673ac17162c101663e220ef43a3c8

nvim: update completion plugins (a lot!)

  • move and reorg completion plugins definitions and configurations
  • use LSP completion instead of deoplete
  • remove eslint from ale_linters
  • enable virtualenv display for airline
  • still working on passing settings to pyls through LanguageClient-neovim

committed on Dec 7, 2018

-Plug 'Shougo/deoplete.nvim', { 'do': ':UpdateRemotePlugins' } -Plug 'zchee/deoplete-jedi', { 'for': 'python' } +Plug 'ervandew/supertab' +Plug 'ncm2/ncm2' +Plug 'roxma/nvim-yarp' +Plug 'autozimu/LanguageClient-neovim', { 'branch': 'next', 'do': 'bash install.sh', } 

从设计理念上看,ncm2 与 deoplete 并无差别,都是通用的异步自动补全框架,唯有与静态分析器的集成方式不同,deoplete 是自己的私有协议,ncm2 则拥抱了更加通用的业界标准 LSP 。

我为 deoplete 的作者感到惋惜,他在 LSP 还不够成熟的时期,自己设计了与静态分析器的集成协议,构建了一个完整的补全插件生态[^1]。还写了很多小巧实用的插件,代码也非常优美,让人感到赏心悦目。但因为 LSP 的发展和新一代更加 LSP native 的补全插件的涌现,它已不再是当下的第一选择,势必因为历史包袱而逐渐被淘汰。

说回 ncm2 ,其实它也有许多瑕疵,印象中配置过程比 deoplete 还要痛苦,但当时已经是让 nvim 用上 LSP 的最好插件了。之后我对 JetBrains 和 VSCode 的使用频率变高,疏于对 nvim 插件的持续跟进,ncm2 于是一直服役到 2021 年。

ncm2 出现后没过多久,coc 也诞生了,在 2019 年成为最受人关注的 vim 补全插件,国内也看到很多文章(似乎作者就是国内开发者)。当时我简单了解后因为它是 nodejs 实现的,就放弃了尝试的念头。开玩笑,jedi 这么一大坨 Python 就慢成这样,nodejs 岂不是更糟糕吗。没想到 coc 一直流行到现在,这似乎再次证明了一个论点,即易用和功能全面才是软件流行的第一因素,无论它的实现有多么不优雅、效率有多么低,只要是能用的、可接受的就行,用户在使用体验上得到满足后,对于小问题的容忍度是相当高的。

2021

2021 年的某一天,因为 ncm2 长期存在的一个小问题(现在已经忘了),我一气之下再次打开了 deoplete 的项目页面,惊喜地发现它已经完善了对 LSP 的支持,于是立刻就开始迁移,换回了我更欣赏且代码品质更胜一筹的 deoplete 。

Commit: cd044fcda603ad5b9ee16bd4d7d7873c9ade9a31

nvim: rework on languageserver & python completion

committed on Mar 26, 2021

-Plug 'ervandew/supertab' -Plug 'ncm2/ncm2' -Plug 'roxma/nvim-yarp' -Plug 'autozimu/LanguageClient-neovim', { 'branch': 'next', 'do': 'bash install.sh', +Plug 'Shougo/deoplete.nvim', { 'do': ':UpdateRemotePlugins' } +Plug 'prabirshrestha/vim-lsp' +Plug 'mattn/vim-lsp-settings' +Plug 'lighttiger2505/deoplete-vim-lsp' 

这次变更除了换回 deoplete ,还去掉了陪伴多年的 supertab ,在抄了一段看不懂的配置后,实现了我更为习惯的 tab 键触发补全的方式。

2022

在咖啡馆结束了一天的主要工作后,看着好友 @iwendellsun 流畅的 vim 操作,我问起了它的 nvim 自动补全配置,果然有许多我从未听过的东西。于是趁此机会赶紧向他请教,在他的指导下完成了 2022 年的配置升级。

Commit: 3de43d030ca40b498911c6752a7396af38202fe6

nvim: use nvim-cmp for completion

committed on May 08, 2022

-Plug 'Shougo/deoplete.nvim', { 'do': ':UpdateRemotePlugins' } -Plug 'prabirshrestha/vim-lsp' -Plug 'mattn/vim-lsp-settings' -Plug 'lighttiger2505/deoplete-vim-lsp' -Plug 'w0rp/ale' -Plug 'rhysd/vim-lsp-ale' +Plug 'williamboman/nvim-lsp-installer' +Plug 'neovim/nvim-lspconfig' +Plug 'hrsh7th/cmp-nvim-lsp' +Plug 'hrsh7th/cmp-buffer' +Plug 'hrsh7th/cmp-path' +Plug 'hrsh7th/cmp-cmdline' +Plug 'hrsh7th/nvim-cmp' 

这次变更分以下几个方面:

  1. 补全框架从 deoplete 变为 nvim-cmp,我还没细看,不过据说它就是现在的 meta & state of the art.
  2. LSP 集成从 vim-lsp 换成了 nvim-lspconfig 。迟来的官方出品。
  3. 去掉了 ale 和 vim-lsp-ale 。nvim-cmp 可以将 LSP client 返回的错误提示直接在行内显示,不需要再依赖 ALE 这个 linter 框架了。
  4. Last but not least, 这些插件的配置语法几乎都是用 Lua 写的,这让用了 10 年 Vimscript 的我感到极度陌生和恐慌。

相比之前的变更,这是唯一一次生搬硬套而非全部理解的,这次我主要是想快速上车,免得被社区发展抛在了后面,现在实在没有太多精力可以悠闲地慢慢尝试。虽然有人指导免去了初次上手的痛苦,但可以预见的是,想要让这套插件和我的编程习惯完美契合,还有许多坑等着我去折腾呢。

参考链接:

参考配置:

结语

Vim 的 LSP 插件生态还有许多有待优化的空间,开发者们对生产力的追求是永无止境的,下一个 5 年编辑器的体验会有着怎样激动人心的变化,我对此充满期待。

Windows Sandbox 无法联网

Posted: 08 May 2022 05:34 AM PDT

使用 Clash for Windows 的时候打开 Windows Sandbox 会使其无法连网,关闭 cfw 又可以了,有什么办法可以在开着 cfw 的情况下让 Windows Sandbox 正常连网吗?

字节的监控告警工程师是做什么的,稍微有点好奇。

Posted: 08 May 2022 03:27 AM PDT

今天在网上闲逛的时候,看到字节的内推招聘下有一个告警监控的岗位( JD 如下),

想询问下这个岗位的"具体"工作场景,因为我理解:字节细分这个岗位是为了更基础通用的提供基础能力吗?

有没有懂的老哥举个具体例子?

职位描述 1 、完善 监控告警、系统可观测性、问题定位等中台系统建设,支撑抖音 /火山 /直播 /国际化产品等业务; 2 、深入业务优化告警规则,开发多样的异常发现系统,优化异常触达的 准召率、发现时间、恢复时间等 指标; 3 、告警分析引擎开发、提高服务治理和运维效率; 4 、支持分布式服务异常排查、构建系统异常应对平台,提高事故处理效率。

ps: 无任何恶意,我对这个岗位也挺感兴趣的,所以想了解下是不是我理解的开发各种监控工具的岗位。

杭州,想评 E 类人才(已买房,纯粹为了福利),请问除了软考高级还有什么其他现实可行的方法?

Posted: 08 May 2022 03:21 AM PDT

已知道的是大厂 50W 年薪。还有什么其他的?比如出书这种。
如果选择软考高级,目前比较推崇的是信息系统项目管理师,因为专业性低,适合大部分人考(还有宝妈考,绝,这东西应该管管),个人感觉比较水。其他四种高级资格真的更难通过吗,哪种论文最好写。

关于 HashMap 中 tableSizeFor 方法的性能问题

Posted: 08 May 2022 02:41 AM PDT

最近看 HashMap 的源码。有一个 tableSizeFor 方法,原来在 JDK7 的时候,这个方法叫做 roundUpToPowerOf2

    static int roundUpToPowerOf2(int cap) {         return cap >= MAXIMUM_CAPACITY                 ? MAXIMUM_CAPACITY                 : (cap > 1) ? Integer.highestOneBit((cap - 1) << 1) : 1;     } 

在 JDK8 的时候给改成了这样:

    static int tableSizeFor(int cap) {         int n = cap - 1;         n |= n >>> 1;         n |= n >>> 2;         n |= n >>> 4;         n |= n >>> 8;         n |= n >>> 16;         return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;     } 

但是在 JDK11 的时候,对这块又进行了一个优化,可以参考 JDK-8203279, 给改成了下面的代码:

static int tableSizeFor(int cap) {    int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; } 

这个方法的用途其实没有变: 都是计算返回大于等于 cap 且与之最接近的一个 2 的次幂值。 为了验证这个方法的性能,我用 JHM 跑了一下基准测试,测试代码:

@BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 1s @Fork(1) @State(Scope.Thread)  public class TableSizeForTest {      static final int MAXIMUM_CAPACITY = 1 << 30;      static final int roundSize = 100_000_000;      public static void main(String[] args) throws RunnerException {         Options opt = new OptionsBuilder()                 .include(TableSizeForTest.class.getSimpleName())                 .build();         new Runner(opt).run();     }      @Benchmark     public void jdk8() {         for (int i = 1; i <= roundSize; i++) {             tableSizeFor(i);         }     }      @Benchmark     public void jdk7() {         for (int i = 1; i <= roundSize; i++) {             roundUpToPowerOf2(i);         }     }      @Benchmark     public void jdk11() {         for (int i = 1; i <= roundSize; i++) {             tableSizeFor_JDK11(i);         }     }    // 字数原因不贴 tableSizeFor 方法了 } 

最后结果如下:

Benchmark               Mode  Cnt          Score          Error  Units TableSizeForTest.jdk11  avgt    5   88015092.847 ± 57126449.190  ns/op TableSizeForTest.jdk7   avgt    5          0.397 ±        0.042  ns/op TableSizeForTest.jdk8   avgt    5  123458345.756 ±  2528234.087  ns/op 

可以看到,jdk11 确实比 jdk8 的性能提高了不少,但是 相比于 jdk7 时使用的 roundUpToPowerOf2 方法,差距也太大了,有大佬能给说一下这是我测试的问题,还是说确实就是这样?

高德 web API 的 callba 应该怎么用?

Posted: 08 May 2022 12:34 AM PDT

API 接口 https://lbs.amap.com/api/webservice/guide/api/georegeo

返回数据

{"status":"1","info":"OK","infocode":"10000","count":"1","geocodes":[{"formatted_address":"北京市朝阳区阜通东大街 6 号","country":"中国","province":"北京市","citycode":"010","city":"北京市","district":"朝阳区","township":[],"neighborhood":{"name":[],"type":[]},"building":{"name":[],"type":[]},"adcode":"110105","street":"阜通东大街","number":"6 号","location":"116.482086,39.990496","level":"门牌号"}]} 

比如我的 callback=getGPS

$.get('url', getGPS) ???

刚学到回调函数这,回调函数就是写好一个函数,当作参数传给另一个函数调用,那这个 callback 在这个 api 中有什么意义?应该怎么使用?

我现在直接

                   var results = $.get(url, getGeocodes);                      function getGeocodes() {                         var tude = results.responseJSON.geocodes[0].location;                         $("#id_detect_coordinate").val(tude);                     }                 } 

我没有填那个 callback,直接拿到返回数据, 所以那个 callback 到底有什么用?正确使用方法是什么?

No comments:

Post a Comment