[
  {
    "path": ".all-contributorsrc",
    "content": "{\n  \"projectName\": \"coc.nvim\",\n  \"projectOwner\": \"neoclide\",\n  \"repoType\": \"github\",\n  \"repoHost\": \"https://github.com\",\n  \"files\": [\n    \"README.md\"\n  ],\n  \"imageSize\": 50,\n  \"commit\": false,\n  \"commitConvention\": \"angular\",\n  \"contributors\": [\n    {\n      \"login\": \"chemzqm\",\n      \"name\": \"Qiming zhao\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/251450?v=4\",\n      \"profile\": \"https://github.com/chemzqm\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"fannheyward\",\n      \"name\": \"Heyward Fann\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/345274?v=4\",\n      \"profile\": \"https://fann.im/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"weirongxu\",\n      \"name\": \"Raidou\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1709861?v=4\",\n      \"profile\": \"https://github.com/weirongxu\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"kevinhwang91\",\n      \"name\": \"kevinhwang91\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/17562139?v=4\",\n      \"profile\": \"https://github.com/kevinhwang91\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"iamcco\",\n      \"name\": \"年糕小豆汤\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/5492542?v=4\",\n      \"profile\": \"http://yuuko.cn/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Avi-D-coder\",\n      \"name\": \"Avi Dessauer\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/29133776?v=4\",\n      \"profile\": \"https://github.com/Avi-D-coder\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"voldikss\",\n      \"name\": \"最上川\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/20282795?v=4\",\n      \"profile\": \"https://github.com/voldikss\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"yatli\",\n      \"name\": \"Yatao Li\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/20684720?v=4\",\n      \"profile\": \"https://www.microsoft.com/en-us/research/people/yatli/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"xiyaowong\",\n      \"name\": \"wongxy\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/47070852?v=4\",\n      \"profile\": \"https://github.com/xiyaowong\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"sam-mccall\",\n      \"name\": \"Sam McCall\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/548993?v=4\",\n      \"profile\": \"https://github.com/sam-mccall\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"pappasam\",\n      \"name\": \"Samuel Roeca\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/3723671?v=4\",\n      \"profile\": \"https://samroeca.com/pages/about.html#about\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"amiralies\",\n      \"name\": \"Amirali Esmaeili\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/13261088?v=4\",\n      \"profile\": \"https://github.com/amiralies\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"jrowlingson\",\n      \"name\": \"Jack Rowlingson\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/3051781?v=4\",\n      \"profile\": \"https://bit.ly/3cLKGE4\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"tomtomjhj\",\n      \"name\": \"Jaehwang Jung\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/19489738?v=4\",\n      \"profile\": \"https://github.com/tomtomjhj\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"antoinemadec\",\n      \"name\": \"Antoine\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/10830594?v=4\",\n      \"profile\": \"https://github.com/antoinemadec\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"cosminadrianpopescu\",\n      \"name\": \"Cosmin Popescu\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/5187873?v=4\",\n      \"profile\": \"https://github.com/cosminadrianpopescu\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"xuanduc987\",\n      \"name\": \"Duc Nghiem Xuan\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1186411?v=4\",\n      \"profile\": \"https://ducnx.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"oblitum\",\n      \"name\": \"Francisco Lopes\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1269815?v=4\",\n      \"profile\": \"https://nosubstance.me/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"daquexian\",\n      \"name\": \"daquexian\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/11607199?v=4\",\n      \"profile\": \"https://github.com/daquexian\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"dependabot[bot]\",\n      \"name\": \"dependabot[bot]\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/in/29110?v=4\",\n      \"profile\": \"https://github.com/apps/dependabot\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"greenkeeper[bot]\",\n      \"name\": \"greenkeeper[bot]\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/in/505?v=4\",\n      \"profile\": \"https://github.com/apps/greenkeeper\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"ckipp01\",\n      \"name\": \"Chris Kipp\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/13974112?v=4\",\n      \"profile\": \"https://chris-kipp.io/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"dmitmel\",\n      \"name\": \"Dmytro Meleshko\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/15367354?v=4\",\n      \"profile\": \"https://dmitmel.github.io/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"kirillbobyrev\",\n      \"name\": \"Kirill Bobyrev\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/3352968?v=4\",\n      \"profile\": \"https://github.com/kirillbobyrev\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"gbcreation\",\n      \"name\": \"Gontran Baerts\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/454315?v=4\",\n      \"profile\": \"https://github.com/gbcreation\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"andys8\",\n      \"name\": \"Andy\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/13085980?v=4\",\n      \"profile\": \"https://andys8.de/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"GopherJ\",\n      \"name\": \"Cheng JIANG\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/33961674?v=4\",\n      \"profile\": \"https://www.alexcj96.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"cpearce-py\",\n      \"name\": \"Corin\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/53532946?v=4\",\n      \"profile\": \"https://github.com/cpearce-py\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"wodesuck\",\n      \"name\": \"Daniel Zhang\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/3124581?v=4\",\n      \"profile\": \"https://github.com/wodesuck\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Ferdi265\",\n      \"name\": \"Ferdinand Bachmann\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/4077106?v=4\",\n      \"profile\": \"https://github.com/Ferdi265\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"gou4shi1\",\n      \"name\": \"Guangqing Chen\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/16915589?v=4\",\n      \"profile\": \"https://goushi.me/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"iamruinous\",\n      \"name\": \"Jade Meskill\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/2108?v=4\",\n      \"profile\": \"http://jademeskill.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"jpoppe\",\n      \"name\": \"Jasper Poppe\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/65505?v=4\",\n      \"profile\": \"https://github.com/jpoppe\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"jean\",\n      \"name\": \"Jean Jordaan\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/84800?v=4\",\n      \"profile\": \"https://github.com/jean\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"kidonng\",\n      \"name\": \"Kid\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/44045911?v=4\",\n      \"profile\": \"https://xuann.wang/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Kavantix\",\n      \"name\": \"Pieter van Loon\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/6243755?v=4\",\n      \"profile\": \"https://github.com/Kavantix\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"rliebz\",\n      \"name\": \"Robert Liebowitz\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/5321575?v=4\",\n      \"profile\": \"https://github.com/rliebz\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"megalithic\",\n      \"name\": \"Seth Messer\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/3678?v=4\",\n      \"profile\": \"https://megalithic.io/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"UncleBill\",\n      \"name\": \"UncleBill\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1141198?v=4\",\n      \"profile\": \"https://github.com/UncleBill\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"ZSaberLv0\",\n      \"name\": \"ZERO\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/6846867?v=4\",\n      \"profile\": \"http://zsaber.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"fsouza\",\n      \"name\": \"fsouza\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/108725?v=4\",\n      \"profile\": \"https://fsouza.blog/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"onichandame\",\n      \"name\": \"XiaoZhang\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/23728505?v=4\",\n      \"profile\": \"https://onichandame.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"whyreal\",\n      \"name\": \"whyreal\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/2084642?v=4\",\n      \"profile\": \"https://github.com/whyreal\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"yehuohan\",\n      \"name\": \"yehuohan\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/17680752?v=4\",\n      \"profile\": \"https://github.com/yehuohan\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Bakudankun\",\n      \"name\": \"バクダンくん\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/4504807?v=4\",\n      \"profile\": \"http://www.bakudan.farm/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"glepnir\",\n      \"name\": \"Raphael\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/41671631?v=4\",\n      \"profile\": \"https://blog.gopherhub.org/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"tbodt\",\n      \"name\": \"tbodt\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/5678977?v=4\",\n      \"profile\": \"https://tbodt.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"aaronmcdaid\",\n      \"name\": \"Aaron McDaid\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/64350?v=4\",\n      \"profile\": \"https://aaronmcdaid.github.io/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"versi786\",\n      \"name\": \"Aasif Versi\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/7347942?v=4\",\n      \"profile\": \"https://github.com/versi786\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"abnerf\",\n      \"name\": \"Abner Silva\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/56300?v=4\",\n      \"profile\": \"https://github.com/abnerf\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"sheerun\",\n      \"name\": \"Adam Stankiewicz\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/292365?v=4\",\n      \"profile\": \"http://sheerun.net/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"adamansky\",\n      \"name\": \"Adamansky Anton\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/496683?v=4\",\n      \"profile\": \"https://wirow.io/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"ahmedelgabri\",\n      \"name\": \"Ahmed El Gabri\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/63876?v=4\",\n      \"profile\": \"https://gabri.me/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"theg4sh\",\n      \"name\": \"Alexandr Kondratev\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/5094691?v=4\",\n      \"profile\": \"http://theg4sh.ru/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"andrewkshim\",\n      \"name\": \"Andrew Shim\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1403410?v=4\",\n      \"profile\": \"https://github.com/andrewkshim\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"alindeman\",\n      \"name\": \"Andy Lindeman\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/395621?v=4\",\n      \"profile\": \"http://andylindeman.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Augustin82\",\n      \"name\": \"Augustin\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/2370810?v=4\",\n      \"profile\": \"https://github.com/Augustin82\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Eijebong\",\n      \"name\": \"Bastien Orivel\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/3650385?v=4\",\n      \"profile\": \"https://bananium.fr/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"ayroblu\",\n      \"name\": \"Ben Lu\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/4915682?v=4\",\n      \"profile\": \"https://github.com/ayroblu\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"vantreeseba\",\n      \"name\": \"Ben\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/316782?v=4\",\n      \"profile\": \"https://github.com/vantreeseba\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"bmon\",\n      \"name\": \"Brendan Roy\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/2115272?v=4\",\n      \"profile\": \"https://github.com/bmon\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"brianembry\",\n      \"name\": \"brianembry\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/35347666?v=4\",\n      \"profile\": \"https://github.com/brianembry\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"b-\",\n      \"name\": \"br\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/284789?v=4\",\n      \"profile\": \"https://keybase.io/bri_\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"casonadams\",\n      \"name\": \"Cason Adams\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/17597548?v=4\",\n      \"profile\": \"https://github.com/casonadams\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"y9c\",\n      \"name\": \"Chang Y\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/5415510?v=4\",\n      \"profile\": \"https://github.com/y9c\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"yous\",\n      \"name\": \"Chayoung You\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/853977?v=4\",\n      \"profile\": \"https://yous.be/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"chenlijun99\",\n      \"name\": \"Chen Lijun\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/20483759?v=4\",\n      \"profile\": \"https://github.com/chenlijun99\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"beeender\",\n      \"name\": \"Chen Mulong\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/449296?v=4\",\n      \"profile\": \"https://github.com/beeender\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"rsrchboy\",\n      \"name\": \"Chris Weyl\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/59620?v=4\",\n      \"profile\": \"http://weyl.io/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"dezza\",\n      \"name\": \"dezza\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/402927?v=4\",\n      \"profile\": \"https://github.com/dezza\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"ceedubs\",\n      \"name\": \"Cody Allen\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/977929?v=4\",\n      \"profile\": \"https://github.com/ceedubs\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"pyrho\",\n      \"name\": \"Damien Rajon\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/145502?v=4\",\n      \"profile\": \"https://www.25.wf/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"daern91\",\n      \"name\": \"Daniel Eriksson\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/6084427?v=4\",\n      \"profile\": \"https://github.com/daern91\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"danjenson\",\n      \"name\": \"Daniel Jenson\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/4793438?v=4\",\n      \"profile\": \"https://github.com/danjenson\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"davidmh\",\n      \"name\": \"David Mejorado\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/594302?v=4\",\n      \"profile\": \"https://github.com/davidmh\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"pderichai\",\n      \"name\": \"Deric Pang\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/13430946?v=4\",\n      \"profile\": \"https://github.com/pderichai\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"miyatsu\",\n      \"name\": \"Ding Tao\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/12852587?v=4\",\n      \"profile\": \"https://www.dingtao.org/blog\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"doronbehar\",\n      \"name\": \"Doron Behar\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/10998835?v=4\",\n      \"profile\": \"https://github.com/doronbehar\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"kovetskiy\",\n      \"name\": \"Egor Kovetskiy\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/8445924?v=4\",\n      \"profile\": \"https://github.com/kovetskiy\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"elkowar\",\n      \"name\": \"ElKowar\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/5300871?v=4\",\n      \"profile\": \"https://github.com/elkowar\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"demelev\",\n      \"name\": \"Emeliov Dmitrii\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/3952209?v=4\",\n      \"profile\": \"https://github.com/demelev\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"sawmurai\",\n      \"name\": \"Fabian Becker\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/6454986?v=4\",\n      \"profile\": \"https://github.com/sawmurai\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"FallenWarrior2k\",\n      \"name\": \"FallenWarrior2k\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/20320149?v=4\",\n      \"profile\": \"https://github.com/FallenWarrior2k\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"fnune\",\n      \"name\": \"Fausto Núñez Alberro\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/16181067?v=4\",\n      \"profile\": \"https://fnune.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"FelipeCRamos\",\n      \"name\": \"Felipe Ramos\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/7572843?v=4\",\n      \"profile\": \"https://github.com/FelipeCRamos\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"frbor\",\n      \"name\": \"Fredrik Borg\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/2320183?v=4\",\n      \"profile\": \"https://github.com/frbor\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"gavsim\",\n      \"name\": \"Gavin Sim\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/812273?v=4\",\n      \"profile\": \"http://www.gavinsim.co.uk/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"gibfahn\",\n      \"name\": \"Gibson Fahnestock\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/15943089?v=4\",\n      \"profile\": \"https://fahn.co/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"giovannigiordano\",\n      \"name\": \"Giovanni Giordano\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/15145952?v=4\",\n      \"profile\": \"https://github.com/giovannigiordano\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"qubbit\",\n      \"name\": \"Gopal Adhikari\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1987473?v=4\",\n      \"profile\": \"https://github.com/qubbit\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"hanh090\",\n      \"name\": \"Hanh Le\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/3643657?v=4\",\n      \"profile\": \"https://github.com/hanh090\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"hedyhli\",\n      \"name\": \"hedy\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/50042066?v=4\",\n      \"profile\": \"https://github.com/hedyhli\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"hendriklammers\",\n      \"name\": \"Hendrik Lammers\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/754556?v=4\",\n      \"profile\": \"https://www.hendriklammers.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"henrybarreto\",\n      \"name\": \"Henry Barreto\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/23109089?v=4\",\n      \"profile\": \"https://github.com/henrybarreto\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"WhyNotHugo\",\n      \"name\": \"Hugo\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/730811?v=4\",\n      \"profile\": \"https://hugo.barrera.io/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"jackieli-tes\",\n      \"name\": \"Jackie Li\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/64778297?v=4\",\n      \"profile\": \"https://github.com/jackieli-tes\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"MrQubo\",\n      \"name\": \"Jakub Nowak\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/16545322?v=4\",\n      \"profile\": \"https://github.com/MrQubo\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"euoia\",\n      \"name\": \"James Pickard\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1271216?v=4\",\n      \"profile\": \"https://github.com/euoia\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"jsfaint\",\n      \"name\": \"Jia Sui\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/571829?v=4\",\n      \"profile\": \"https://github.com/jsfaint\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"expipiplus1\",\n      \"name\": \"Ellie Hermaszewska\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/857308?v=4\",\n      \"profile\": \"https://github.com/expipiplus1\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"cincodenada\",\n      \"name\": \"Joel Bradshaw\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/479715?v=4\",\n      \"profile\": \"https://cincodenada.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"irizwaririz\",\n      \"name\": \"John Carlo Roberto\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/10111643?v=4\",\n      \"profile\": \"https://github.com/irizwaririz\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Jomik\",\n      \"name\": \"Jonas Holst Damtoft\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/699655?v=4\",\n      \"profile\": \"https://github.com/Jomik\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"jdlehman\",\n      \"name\": \"Jonathan Lehman\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/3144695?v=4\",\n      \"profile\": \"http://inlehmansterms.net/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"JoosepAlviste\",\n      \"name\": \"Joosep Alviste\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/9450943?v=4\",\n      \"profile\": \"https://joosep.xyz/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"josa42\",\n      \"name\": \"Josa Gesell\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/423234?v=4\",\n      \"profile\": \"https://github.com/josa42\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"joshuarubin\",\n      \"name\": \"Joshua Rubin\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/194275?v=4\",\n      \"profile\": \"https://jawa.dev/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"perrin4869\",\n      \"name\": \"Julian Grinblat\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/5774716?v=4\",\n      \"profile\": \"https://github.com/perrin4869\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"valentjn\",\n      \"name\": \"Julian Valentin\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/19839841?v=4\",\n      \"profile\": \"https://valentjn.github.io/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"KabbAmine\",\n      \"name\": \"KabbAmine\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/5658084?v=4\",\n      \"profile\": \"https://kabbamine.github.io/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"acro5piano\",\n      \"name\": \"Kay Gosho\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/10719495?v=4\",\n      \"profile\": \"https://moncargo.io/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"hkennyv\",\n      \"name\": \"Kenny Huynh\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/29909203?v=4\",\n      \"profile\": \"https://kennyvh.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"kevinrambaud\",\n      \"name\": \"Kevin Rambaud\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/7501477?v=4\",\n      \"profile\": \"https://github.com/kevinrambaud\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"kiancross\",\n      \"name\": \"Kian Cross\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/11011464?v=4\",\n      \"profile\": \"https://github.com/kiancross\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"kristijanhusak\",\n      \"name\": \"Kristijan Husak\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1782860?v=4\",\n      \"profile\": \"https://ko-fi.com/kristijanhusak\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"NullVoxPopuli\",\n      \"name\": \"NullVoxPopuli\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/199018?v=4\",\n      \"profile\": \"https://github.com/NullVoxPopuli\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"lassepe\",\n      \"name\": \"Lasse Peters\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/10076790?v=4\",\n      \"profile\": \"https://github.com/lassepe\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Linerre\",\n      \"name\": \"Noel Errenil\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/49512984?v=4\",\n      \"profile\": \"https://github.com/Linerre\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"LinArcX\",\n      \"name\": \"LinArcX\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/10884422?v=4\",\n      \"profile\": \"https://github.com/LinArcX\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"liuchengxu\",\n      \"name\": \"Liu-Cheng Xu\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/8850248?v=4\",\n      \"profile\": \"https://paypal.me/liuchengxu\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"foxtrot\",\n      \"name\": \"Marc\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/4153572?v=4\",\n      \"profile\": \"https://malloc.me/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"mgaw\",\n      \"name\": \"Marius Gawrisch\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/2177016?v=4\",\n      \"profile\": \"https://github.com/mgaw\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"mhintz\",\n      \"name\": \"Mark Hintz\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/2789742?v=4\",\n      \"profile\": \"http://www.markhz.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"MatElGran\",\n      \"name\": \"Mathieu Le Tiec\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1052778?v=4\",\n      \"profile\": \"https://github.com/MatElGran\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"matt-fff\",\n      \"name\": \"Matt White\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/8656127?v=4\",\n      \"profile\": \"https://matt-w.net/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"ml-evs\",\n      \"name\": \"Matthew Evans\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/7916000?v=4\",\n      \"profile\": \"https://github.com/ml-evs\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Me1onRind\",\n      \"name\": \"Me1onRind\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/19531270?v=4\",\n      \"profile\": \"https://github.com/Me1onRind\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Qyriad\",\n      \"name\": \"Qyriad\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1542224?v=4\",\n      \"profile\": \"https://github.com/Qyriad\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"leonardssh\",\n      \"name\": \"Narcis B.\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/35312043?v=4\",\n      \"profile\": \"https://leo.is-a.dev/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Neur1n\",\n      \"name\": \"Neur1n\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/17579247?v=4\",\n      \"profile\": \"https://github.com/Neur1n\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"nicoder\",\n      \"name\": \"Nicolas Dermine\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/365210?v=4\",\n      \"profile\": \"https://github.com/nicoder\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"NoahTheDuke\",\n      \"name\": \"Noah\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/603677?v=4\",\n      \"profile\": \"https://github.com/NoahTheDuke\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"IndexXuan\",\n      \"name\": \"PENG Rui\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/6322673?v=4\",\n      \"profile\": \"https://github.com/IndexXuan\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"paco0x\",\n      \"name\": \"Paco\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/6123425?v=4\",\n      \"profile\": \"https://liaoph.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"peng1999\",\n      \"name\": \"Peng Guanwen\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/12483662?v=4\",\n      \"profile\": \"https://github.com/peng1999\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"ilAYAli\",\n      \"name\": \"Petter Wahlman\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1106732?v=4\",\n      \"profile\": \"https://www.twitter.com/badeip\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"pvonmoradi\",\n      \"name\": \"Pooya Moradi\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1058151?v=4\",\n      \"profile\": \"https://github.com/pvonmoradi\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"QuadeMorrison\",\n      \"name\": \"Quade Morrison\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/10917383?v=4\",\n      \"profile\": \"https://github.com/QuadeMorrison\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"vogler\",\n      \"name\": \"Ralf Vogler\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/493741?v=4\",\n      \"profile\": \"https://github.com/vogler\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"crccw\",\n      \"name\": \"Ran Chen\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/41463?v=4\",\n      \"profile\": \"https://github.com/crccw\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"bigardone\",\n      \"name\": \"Ricardo García Vega\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1090272?v=4\",\n      \"profile\": \"https://bigardone.dev/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"nomasprime\",\n      \"name\": \"Rick Jones\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/140855?v=4\",\n      \"profile\": \"https://github.com/nomasprime\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"rschristian\",\n      \"name\": \"Ryan Christian\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/33403762?v=4\",\n      \"profile\": \"https://github.com/rschristian\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"winterbesos\",\n      \"name\": \"Salo\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/4694263?v=4\",\n      \"profile\": \"http://salo.so/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Hazelfire\",\n      \"name\": \"Sam Nolan\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/13807753?v=4\",\n      \"profile\": \"https://github.com/Hazelfire\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"rickysaurav\",\n      \"name\": \"Saurav\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/13986039?v=4\",\n      \"profile\": \"https://github.com/rickysaurav\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"smackesey\",\n      \"name\": \"Sean Mackesey\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1531373?v=4\",\n      \"profile\": \"https://github.com/smackesey\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"sheeldotme\",\n      \"name\": \"Sheel Patel\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/6991406?v=4\",\n      \"profile\": \"https://github.com/sheeldotme\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"solomonwzs\",\n      \"name\": \"Solomon Ng\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/907942?v=4\",\n      \"profile\": \"https://github.com/solomonwzs\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"kadimisetty\",\n      \"name\": \"Sri Kadimisetty\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/535947?v=4\",\n      \"profile\": \"https://github.com/kadimisetty\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"stephenprater\",\n      \"name\": \"Stephen Prater\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/149870?v=4\",\n      \"profile\": \"https://github.com/stephenprater\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"kibs\",\n      \"name\": \"Sune Kibsgaard\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/14085?v=4\",\n      \"profile\": \"https://kibs.dk/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Aquaakuma\",\n      \"name\": \"Aquaakuma\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/31891793?v=4\",\n      \"profile\": \"https://github.com/Aquaakuma\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"coil398\",\n      \"name\": \"Takumi Kawase\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/7694377?v=4\",\n      \"profile\": \"https://github.com/coil398\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"theblobscp\",\n      \"name\": \"The Blob SCP\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/81673375?v=4\",\n      \"profile\": \"https://github.com/theblobscp\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"przepompownia\",\n      \"name\": \"Tomasz N\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/11404453?v=4\",\n      \"profile\": \"https://github.com/przepompownia\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"gasuketsu\",\n      \"name\": \"Tomoyuki Harada\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/15703757?v=4\",\n      \"profile\": \"https://github.com/gasuketsu\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"tonyfettes\",\n      \"name\": \"Tony Fettes\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/29998228?v=4\",\n      \"profile\": \"https://github.com/tonyfettes\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"tony\",\n      \"name\": \"Tony Narlock\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/26336?v=4\",\n      \"profile\": \"https://www.git-pull.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"wwwjfy\",\n      \"name\": \"Tony Wang\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/126527?v=4\",\n      \"profile\": \"https://blog.wwwjfy.net/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Varal7\",\n      \"name\": \"Victor Quach\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/8019486?v=4\",\n      \"profile\": \"https://github.com/Varal7\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"whisperity\",\n      \"name\": \"Whisperity\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1969470?v=4\",\n      \"profile\": \"https://github.com/whisperity\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"willtrnr\",\n      \"name\": \"William Turner\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1878110?v=4\",\n      \"profile\": \"https://github.com/willtrnr\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"damnever\",\n      \"name\": \"Xiaochao Dong\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/6223594?v=4\",\n      \"profile\": \"https://drafts.damnever.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"hyhugh\",\n      \"name\": \"Hugh Hou\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/16500351?v=4\",\n      \"profile\": \"https://github.com/hyhugh\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"jackielii\",\n      \"name\": \"Jackie Li\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/360983?v=4\",\n      \"profile\": \"https://github.com/jackielii\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"TheConfuZzledDude\",\n      \"name\": \"Zachary Freed\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/3160203?v=4\",\n      \"profile\": \"https://github.com/TheConfuZzledDude\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"akiyosi\",\n      \"name\": \"akiyosi\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/8478977?v=4\",\n      \"profile\": \"https://github.com/akiyosi\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"alexjg\",\n      \"name\": \"alexjg\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/224635?v=4\",\n      \"profile\": \"https://github.com/alexjg\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"aste4\",\n      \"name\": \"aste4\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/47511385?v=4\",\n      \"profile\": \"https://github.com/aste4\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"clyfish\",\n      \"name\": \"clyfish\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/541215?v=4\",\n      \"profile\": \"https://github.com/clyfish\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"dev7ba\",\n      \"name\": \"dev7ba\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/93706552?v=4\",\n      \"profile\": \"https://github.com/dev7ba\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"diartyz\",\n      \"name\": \"diartyz\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/4486152?v=4\",\n      \"profile\": \"https://github.com/diartyz\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"doza-daniel\",\n      \"name\": \"doza-daniel\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/13752683?v=4\",\n      \"profile\": \"https://github.com/doza-daniel\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"equal-l2\",\n      \"name\": \"equal-l2\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/8597717?v=4\",\n      \"profile\": \"https://github.com/equal-l2\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"FongHou\",\n      \"name\": \"fong\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/13973254?v=4\",\n      \"profile\": \"https://github.com/FongHou\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"hexh250786313\",\n      \"name\": \"hexh\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/26080416?v=4\",\n      \"profile\": \"https://blog.hexuhua.vercel.app/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"hhiraba\",\n      \"name\": \"hhiraba\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/4624806?v=4\",\n      \"profile\": \"https://github.com/hhiraba\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"ic-768\",\n      \"name\": \"ic-768\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/83115125?v=4\",\n      \"profile\": \"https://github.com/ic-768\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"javiertury\",\n      \"name\": \"javiertury\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1520320?v=4\",\n      \"profile\": \"https://github.com/javiertury\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"seiyeah78\",\n      \"name\": \"karasu\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/6185139?v=4\",\n      \"profile\": \"https://github.com/seiyeah78\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"kevineato\",\n      \"name\": \"kevineato\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/13666221?v=4\",\n      \"profile\": \"https://github.com/kevineato\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"m4c0\",\n      \"name\": \"Eduardo Costa\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1664510?v=4\",\n      \"profile\": \"https://github.com/m4c0\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"micchy326\",\n      \"name\": \"micchy326\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/23257067?v=4\",\n      \"profile\": \"https://github.com/micchy326\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"midchildan\",\n      \"name\": \"midchildan\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/7343721?v=4\",\n      \"profile\": \"https://keybase.io/midchildan\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"minefuto\",\n      \"name\": \"minefuto\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/46558834?v=4\",\n      \"profile\": \"https://github.com/minefuto\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"miyanokomiya\",\n      \"name\": \"miyanokomiya\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/20733354?v=4\",\n      \"profile\": \"https://twitter.com/robokomy\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"miyaviee\",\n      \"name\": \"miyaviee\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/15247561?v=4\",\n      \"profile\": \"https://github.com/miyaviee\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"monkoose\",\n      \"name\": \"monkoose\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/6261276?v=4\",\n      \"profile\": \"https://github.com/monkoose\",\n      \"contributions\": [\n        \"code\",\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"mujx\",\n      \"name\": \"mujx\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/6430350?v=4\",\n      \"profile\": \"https://github.com/mujx\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"mvilim\",\n      \"name\": \"mvilim\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/40682862?v=4\",\n      \"profile\": \"https://github.com/mvilim\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"naruaway\",\n      \"name\": \"naruaway\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/2931577?v=4\",\n      \"profile\": \"https://naruaway.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"piersy\",\n      \"name\": \"piersy\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/5087847?v=4\",\n      \"profile\": \"https://github.com/piersy\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"ryantig\",\n      \"name\": \"ryantig\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/324810?v=4\",\n      \"profile\": \"https://github.com/ryantig\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"rydesun\",\n      \"name\": \"rydesun\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/19602440?v=4\",\n      \"profile\": \"https://catcat.cc/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"sc00ter\",\n      \"name\": \"sc00ter\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1271025?v=4\",\n      \"profile\": \"https://github.com/sc00ter\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"smhc\",\n      \"name\": \"smhc\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/6404304?v=4\",\n      \"profile\": \"https://github.com/smhc\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"stkaplan\",\n      \"name\": \"Sam Kaplan\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/594990?v=4\",\n      \"profile\": \"https://github.com/stkaplan\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"tasuten\",\n      \"name\": \"tasuten\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1623176?v=4\",\n      \"profile\": \"https://github.com/tasuten\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"todesking\",\n      \"name\": \"todesking\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/112881?v=4\",\n      \"profile\": \"http://todesking.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"typicode\",\n      \"name\": \"typicode\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/5502029?v=4\",\n      \"profile\": \"https://github.com/typicode\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"LiMingFei56\",\n      \"name\": \"李鸣飞\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/8553407?v=4\",\n      \"profile\": \"https://limingfei56.github.io/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"eltociear\",\n      \"name\": \"Ikko Ashimine\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/22633385?v=4\",\n      \"profile\": \"https://bandism.net/\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"rammiah\",\n      \"name\": \"Rammiah\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/26727562?v=4\",\n      \"profile\": \"https://github.com/rammiah\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"lambdalisue\",\n      \"name\": \"Alisue\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/546312?v=4\",\n      \"profile\": \"https://keybase.io/lambdalisue\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"bigshans\",\n      \"name\": \"bigshans\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/26884666?v=4\",\n      \"profile\": \"http://bigshans.github.io\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"rob-3\",\n      \"name\": \"Robert Boyd III\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/24816247?v=4\",\n      \"profile\": \"https://github.com/rob-3\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"creasty\",\n      \"name\": \"Yuki Iwanaga\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1695538?v=4\",\n      \"profile\": \"https://creasty.com\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"springhack\",\n      \"name\": \"SpringHack\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/2389889?v=4\",\n      \"profile\": \"https://www.dosk.win/\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"lmburns\",\n      \"name\": \"Lucas Burns\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/44355502?v=4\",\n      \"profile\": \"http://git.lmburns.com\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"qiqiboy\",\n      \"name\": \"qiqiboy\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/3774036?v=4\",\n      \"profile\": \"http://qiqi.boy.im\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"timsu92\",\n      \"name\": \"timsu92\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/33785401?v=4\",\n      \"profile\": \"https://github.com/timsu92\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"sartak\",\n      \"name\": \"Shawn M Moore\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/45430?v=4\",\n      \"profile\": \"https://sartak.org\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"aauren\",\n      \"name\": \"Aaron U'Ren\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1392295?v=4\",\n      \"profile\": \"https://github.com/aauren\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"SirCharlieMars\",\n      \"name\": \"SeniorMars\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/31679231?v=4\",\n      \"profile\": \"https://github.com/SirCharlieMars\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"CollieIsCute\",\n      \"name\": \"牧羊犬真Q\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/43088530?v=4\",\n      \"profile\": \"https://github.com/CollieIsCute\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"geraldspreer\",\n      \"name\": \"geraldspreer\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1745692?v=4\",\n      \"profile\": \"http://geraldspreer.com\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"3ximus\",\n      \"name\": \"Fabio\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/9083012?v=4\",\n      \"profile\": \"http://3ximus.github.io/cv\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"skysky97\",\n      \"name\": \"Li Yunting\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/18086458?v=4\",\n      \"profile\": \"https://github.com/skysky97\",\n      \"contributions\": [\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"LebJe\",\n      \"name\": \"Jeff L.\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/51171427?v=4\",\n      \"profile\": \"https://github.com/LebJe\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"mcmire\",\n      \"name\": \"Elliot Winkler\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/7371?v=4\",\n      \"profile\": \"https://hachyderm.io/@mcmire\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"asmodeus812\",\n      \"name\": \"Svetlozar Iliev\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/15955811?v=4\",\n      \"profile\": \"http://www.lebstertm.com\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"43081j\",\n      \"name\": \"James Garbutt\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/5677153?v=4\",\n      \"profile\": \"http://43081j.com/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Kaiser-Yang\",\n      \"name\": \"Qingzhou Yue\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/58209855?v=4\",\n      \"profile\": \"https://github.com/Kaiser-Yang\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"de-vri-es\",\n      \"name\": \"Maarten de Vries\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/786213?v=4\",\n      \"profile\": \"https://www.linkedin.com/in/de-vries-maarten/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"A4-Tacks\",\n      \"name\": \"A4-Tacks\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/102709083?v=4\",\n      \"profile\": \"https://github.com/A4-Tacks\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"zhixiao-zhang\",\n      \"name\": \"forceofsystem\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/89405463?v=4\",\n      \"profile\": \"https://github.com/zhixiao-zhang\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"statiolake\",\n      \"name\": \"lake\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/20490597?v=4\",\n      \"profile\": \"https://github.com/statiolake\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"davidosomething\",\n      \"name\": \"David O'Trakoun\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/609213?v=4\",\n      \"profile\": \"https://www.davidosomething.com/\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"aispeaking\",\n      \"name\": \"aispeaking\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/139532597?v=4\",\n      \"profile\": \"https://github.com/aispeaking\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"cclauss\",\n      \"name\": \"Christian Clauss\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/3709715?v=4\",\n      \"profile\": \"https://github.com/cclauss\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"mehalter\",\n      \"name\": \"Micah Halter\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1591837?v=4\",\n      \"profile\": \"http://mehalter.com\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"cridemichel\",\n      \"name\": \"Cristiano De Michele\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/15322138?v=4\",\n      \"profile\": \"https://github.com/cridemichel\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"YongJieYongJie\",\n      \"name\": \"Yong Jie\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/14101781?v=4\",\n      \"profile\": \"https://yongjie.codes/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"hackergrrl\",\n      \"name\": \"Kira Oakley\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/489362?v=4\",\n      \"profile\": \"http://eight45.net\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"merwan\",\n      \"name\": \"Merouane Atig\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/222879?v=4\",\n      \"profile\": \"https://merwan.github.io\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"gera2ld\",\n      \"name\": \"Gerald\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/3139113?v=4\",\n      \"profile\": \"https://gera2ld.space/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"V-Mann-Nick\",\n      \"name\": \"Nicklas Sedlock\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/47660390?v=4\",\n      \"profile\": \"https://nicklas.sedlock.xyz/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"tcx4c70\",\n      \"name\": \"Adam Tao\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/16728230?v=4\",\n      \"profile\": \"https://github.com/tcx4c70\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"itsf4llofstars\",\n      \"name\": \"itsf4llofstars\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/90528743?v=4\",\n      \"profile\": \"https://github.com/itsf4llofstars\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"brainwo\",\n      \"name\": \"Brian Wo\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/45139213?v=4\",\n      \"profile\": \"https://github.com/brainwo\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"wsdjeg\",\n      \"name\": \"Eric Wong\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/13142418?v=4\",\n      \"profile\": \"https://wsdjeg.net/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"oxalica\",\n      \"name\": \"oxalica\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/14816024?v=4\",\n      \"profile\": \"https://github.com/oxalica\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"laktak\",\n      \"name\": \"Christian Zangl\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/959858?v=4\",\n      \"profile\": \"https://github.com/laktak\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"zoumi\",\n      \"name\": \"zoumi\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/5162901?v=4\",\n      \"profile\": \"https://github.com/zoumi\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"atitcreate\",\n      \"name\": \"atitcreate\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/40348360?v=4\",\n      \"profile\": \"https://github.com/atitcreate\",\n      \"contributions\": [\n        \"code\"\n      ]\n    }\n  ],\n  \"contributorsPerLine\": 7,\n  \"skipCi\": true,\n  \"commitType\": \"docs\"\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nend_of_line = lf\ncharset = utf-8\n\n[*.{js,ts}]\nindent_style = space\nindent_size = 2\ntrim_trailing_whitespace = true\nmax_line_length = 120\n\n[*.json]\nindent_style = space\nindent_size = 2\ntrim_trailing_whitespace = true\n\n[*.vim]\nindent_style = space\nindent_size = 2\ntrim_trailing_whitespace = true\nmax_line_length = 120\n\n\n[*.lua]\nindent_size = 2\nmax_line_length = 120\nalign_call_args = only_not_exist_cross_row_expression\nalign_table_field_to_first_field = true\nlocal_assign_continuation_align_to_first_expression = true\nkeep_one_space_between_table_and_bracket = false\nquote_style = single\nremove_empty_header_and_footer_lines_in_function = true\nremove_expression_list_finish_comma= true\n"
  },
  {
    "path": ".github/.codecov.yml",
    "content": "coverage:\n  status:\n    patch: off\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\nopen_collective: cocnvim\npatreon: chemzqm\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\n---\n\n<!--\n**Warning: We will close the bug issue without the issue template and the reproduce ways.**\n\nIf you have question, please ask at https://github.com/neoclide/coc.nvim/discussions\n\nIf the problem related to specific language server, please checkout: https://git.io/fjCEM\n\nIf your have performance issue, checkout: https://git.io/fjCEX & https://git.io/Jfe00\n-->\n\n## Result from CocInfo\n\n<!--Run `:CocInfo` command and paste the content below.-->\n\n## Describe the bug\n\nA clear and concise description of what the bug is.\n\n## Reproduce the bug\n\n**We will close your issue when you don't provide minimal vimrc and we can't\nreproduce it**\n\n- Create file `mini.vim` with：\n\n  ```vim\n  set nocompatible\n  set runtimepath^=/path/to/coc.nvim\n  filetype plugin indent on\n  syntax on\n  set hidden\n  ```\n\n- Start (neo)vim with command: `vim -u mini.vim`\n\n- Operate vim.\n\n## Screenshots (optional)\n\nIf applicable, add screenshots to help explain your problem.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: Dev\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\n\njobs:\n  test:\n    if: github.event.pull_request.draft == false\n    timeout-minutes: 60\n    runs-on: ubuntu-latest\n\n    strategy:\n      fail-fast: false\n      matrix:\n        versions:\n          - neovim: \"stable\"\n            vim: \"v9.0.0438\"\n          - neovim: \"nightly\"\n            vim: \"v9.1.1365\"\n        node:\n          - \"20\"\n        include:\n          # only enable coverage on the fastest job\n          - node: \"20\"\n            ENABLE_CODE_COVERAGE: true\n\n    env:\n      NODE_ENV: test\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 2\n\n      - name: Setup Node.js ${{ matrix.node }}\n        uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.node }}\n          cache: \"npm\"\n\n      - name: Setup python3\n        uses: actions/setup-python@v5\n        with:\n          python-version: \"3.x\"\n      - run: pip install pynvim\n\n      - name: Setup vim\n        uses: rhysd/action-setup-vim@v1\n        id: vim\n        with:\n          version: ${{ matrix.versions.vim }}\n\n      - name: Setup neovim\n        id: nvim\n        uses: rhysd/action-setup-vim@v1\n        with:\n          neovim: true\n          version: ${{ matrix.versions.neovim }}\n\n      - name: Install Dependencies\n        run: |\n          npm i -g bytes\n          npm ci\n          sudo apt-get install -y ripgrep exuberant-ctags\n          rg --version\n          ctags --version\n          vim --version\n          nvim --version\n\n      - name: Run jest\n        env:\n          VIM_COMMAND: ${{ steps.vim.outputs.executable }}\n          NVIM_COMMAND: ${{ steps.nvim.outputs.executable }}\n        run: |\n          node --max-old-space-size=4096 --expose-gc ./node_modules/.bin/jest --maxWorkers=2 --coverage --forceExit\n\n      - name: Codecov\n        uses: codecov/codecov-action@v4\n        if: ${{ matrix.ENABLE_CODE_COVERAGE }}\n        with:\n          token: ${{ secrets.CODECOV_TOKEN }}\n          fail_ci_if_error: false\n          verbose: true\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: Lint\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\n\njobs:\n  lint:\n    if: github.event.pull_request.draft == false\n    name: Lint\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          cache: \"npm\"\n\n      - name: Install Dependencies\n        run: npm install --frozen-lockfile\n\n      - name: Check Types by TSC\n        run: npm run lint:typecheck\n\n      - name: Lint ESLint\n        run: npm run lint\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Publish Release Task\n\non:\n  schedule:\n    - cron: '0 16 * * *'  # UTC时间16:00（对应北京时间+8时区的0点）\n\njobs:\n  publish-release:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n        with:\n          ref: master\n          token: ${{ secrets.GITHUB_TOKEN }}\n          persist-credentials: true\n\n      - name: Install Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          cache: 'npm'\n\n      - name: Setup python3\n        uses: actions/setup-python@v5\n        with:\n          python-version: \"3.x\"\n      - run: pip install pynvim\n\n      - name: Setup vim\n        uses: rhysd/action-setup-vim@v1\n        id: vim\n        with:\n          version: v9.0.0815\n\n      - name: Setup neovim\n        id: nvim\n        uses: rhysd/action-setup-vim@v1\n        with:\n          neovim: true\n          version: stable\n\n      - name: Install Dependencies\n        env:\n          VIM_COMMAND: ${{ steps.vim.outputs.executable }}\n          NVIM_COMMAND: ${{ steps.nvim.outputs.executable }}\n        run: |\n          npm i -g bytes\n          npm ci\n          NODE_ENV=production node esbuild.js\n          sudo apt-get install -y ripgrep exuberant-ctags\n          rg --version\n          ctags --version\n          vim --version\n          nvim --version\n\n      - name: Execute release.sh\n        run: |\n          chmod +x ./release.sh\n          ./release.sh\n"
  },
  {
    "path": ".gitignore",
    "content": "lib\n.cache\n*.map\ncoverage\n__pycache__\n.pyc\n.log\nbuild\ndoc/tags\ntypings/package.json\nnode_modules\npublish.sh\n!src/__tests__/tags\nsrc/__tests__/extensions/db.json\n"
  },
  {
    "path": ".ignore",
    "content": "lib\n"
  },
  {
    "path": ".npmignore",
    "content": "*.map\n.cache\nlib/extensions\nlib/__tests__\nplugin\nautoload\nrplugin\nsrc\n.github\nbuild\ncoverage\ndata\ntslint.json\ntsconfig.json\n.zip\n.DS_Store\n"
  },
  {
    "path": ".prettierignore",
    "content": "src/\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"bracketSpacing\": false,\n  \"arrowParens\": \"avoid\",\n  \"printWidth\": 120,\n  \"singleQuote\": true,\n  \"trailingComma\": \"none\",\n  \"tabWidth\": 2,\n  \"proseWrap\": \"never\",\n  \"semi\": false\n}\n"
  },
  {
    "path": ".swcrc",
    "content": "{\n  \"sourceMaps\": false,\n  \"module\": {\n    \"type\": \"es6\"\n  },\n  \"env\": {\n    \"targets\": {\n      \"node\": \"14\"\n    }\n  },\n  \"jsc\": {\n    \"parser\": {\n      \"syntax\": \"typescript\",\n      \"tsx\": false,\n      \"dynamicImport\": false,\n      \"decorators\": false\n    },\n    \"loose\": true\n  }\n}\n"
  },
  {
    "path": ".vim/coc-settings.json",
    "content": "{\n  \"eslint.validate\": [\"typescript\"],\n  \"eslint.lintTask.options\": [\".\"],\n  \"sumneko-lua.enableNvimLuaDev\": true,\n  \"javascript.format.semicolons\": \"remove\",\n  \"typescript.format.semicolons\": \"remove\",\n  \"typescript.preferences.importModuleSpecifier\": \"relative\",\n  \"typescript.preferences.importModuleSpecifierEnding\": \"minimal\",\n  \"typescript.preferences.quoteStyle\": \"single\",\n  \"typescript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions\": false,\n  \"typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces\": true,\n  \"Lua.diagnostics.disable\": [\n    \"empty-block\"\n  ]\n}\n"
  },
  {
    "path": "Backers.md",
    "content": "# Backers\n\n❤️ coc.nvim? Help us keep it alive by [donating funds](https://www.bountysource.com/teams/coc-nvim)😘!\n\n<a href=\"https://github.com/oblitum\" target=\"_blank\" title=\"oblitum\">\n  <img src=\"https://github.com/oblitum.png?size=64\" width=\"64\" height=\"64\" alt=\"oblitum\">\n</a>\n<a href=\"https://github.com/free-easy\" target=\"_blank\" title=\"free-easy\">\n  <img src=\"https://github.com/free-easy.png?size=64\" width=\"64\" height=\"64\" alt=\"free-easy\">\n</a>\n<a href=\"https://github.com/ruanyl\" target=\"_blank\" title=\"ruanyl\">\n  <img src=\"https://github.com/ruanyl.png?size=64\" width=\"64\" height=\"64\" alt=\"ruanyl\">\n</a>\n<a href=\"https://github.com/robjuffermans\" target=\"_blank\" title=\"robjuffermans\">\n  <img src=\"https://github.com/robjuffermans.png?size=64\" width=\"64\" height=\"64\" alt=\"robjuffermans\">\n</a>\n<a href=\"https://github.com/iamcco\" target=\"_blank\" title=\"iamcco\">\n  <img src=\"https://github.com/iamcco.png?size=64\" width=\"64\" height=\"64\" alt=\"iamcco\">\n</a>\n<a href=\"https://github.com/phcerdan\" target=\"_blank\" title=\"phcerdan\">\n  <img src=\"https://github.com/phcerdan.png?size=64\" width=\"64\" height=\"64\" alt=\"phcerdan\">\n</a>\n<a href=\"https://github.com/sarene\" target=\"_blank\" title=\"sarene\">\n  <img src=\"https://github.com/sarene.png?size=64\" width=\"64\" height=\"64\" alt=\"sarene\">\n</a>\n<a href=\"https://github.com/robtrac\" target=\"_blank\" title=\"robtrac\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals89_puer8v.png\" width=\"64\" height=\"64\" alt=\"robtrac\">\n</a>\n<a href=\"https://github.com/raidou\" target=\"_blank\" title=\"raidou\">\n  <img src=\"https://github.com/raidou.png?size=64\" width=\"64\" height=\"64\" alt=\"raidou\">\n</a>\n<a href=\"https://github.com/tomspeak\" target=\"_blank\" title=\"tomspeak\">\n  <img src=\"https://github.com/tomspeak.png?size=64\" width=\"64\" height=\"64\" alt=\"tomspeak\">\n</a>\n<a href=\"https://github.com/taigacute\" target=\"_blank\" title=\"taigacute\">\n  <img src=\"https://github.com/taigacute.png?size=64\" width=\"64\" height=\"64\" alt=\"taigacute\">\n</a>\n<a href=\"https://github.com/weirongxu\" target=\"_blank\" title=\"weirongxu\">\n  <img src=\"https://github.com/weirongxu.png?size=64\" width=\"64\" height=\"64\" alt=\"weirongxu\">\n</a>\n<a href=\"https://github.com/tbo\" target=\"_blank\" title=\"tbo\">\n  <img src=\"https://github.com/tbo.png?size=64\" width=\"64\" height=\"64\" alt=\"tbo\">\n</a>\n<a href=\"https://github.com/darthShadow\" target=\"_blank\" title=\"darthShadow\">\n  <img src=\"https://github.com/darthShadow.png?size=64\" width=\"64\" height=\"64\" alt=\"darthShadow\">\n</a>\n<a href=\"https://github.com/yatli\" target=\"_blank\" title=\"yatli\">\n   <img src=\"https://github.com/yatli.png?size=64\" width=\"64\" height=\"64\" alt=\"yatli\">\n </a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/gravatar/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/f8fbc5df2432deac7557cf5e111439f2\" width=\"64\" height=\"64\" alt=\"Matt Greer\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://avatars0.githubusercontent.com/u/2914269?v=4&s=100&s=400\" width=\"64\" height=\"64\" alt=\"malob\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/gravatar/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/a8b8103b9131cdf694bea446881c05fb\" width=\"64\" height=\"64\" alt=\"Emigre\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals27_bjhsl8.png\" width=\"64\" height=\"64\" alt=\"OkanEsen\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals57_yatmux.png\" width=\"64\" height=\"64\" alt=\"Lennaert Meijvogel\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://avatars2.githubusercontent.com/u/557201?s=400&u=ac96c9da87099c27f094eec935a627cb32fdfdf2&v=4&s=400\" width=\"64\" height=\"64\" alt=\"Nils Landt\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals10_mjtuws.png\" width=\"64\" height=\"64\" alt=\"dlants\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals45_ecgl95.png\" width=\"64\" height=\"64\" alt=\"RCVU\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals71_wi5cvo.png\" width=\"64\" height=\"64\" alt=\"yatli\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/gravatar/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/2986e67e29cf2ad3de088f9f8bc131cf\" width=\"64\" height=\"64\" alt=\"mikker\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/gravatar/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/8703a88e1c178112625bcb6970ed40e4\" width=\"64\" height=\"64\" alt=\"Velovix\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals51_byhedz.png\" width=\"64\" height=\"64\" alt=\"stCarolas\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals67_rzqguf.png\" width=\"64\" height=\"64\" alt=\"Robbie Clarken\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/svdunc4lofagkaeobpar.png\" width=\"64\" height=\"64\" alt=\"hallettj\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://avatars0.githubusercontent.com/u/6803419?v=4&s=100&s=400\" width=\"64\" height=\"64\" alt=\"appelgriebsch\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals75_a0xqeq.png\" width=\"64\" height=\"64\" alt=\"cosminadrianpopescu\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://avatars3.githubusercontent.com/u/301015?v=4&s=100&s=400\" width=\"64\" height=\"64\" alt=\"partizan\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals24_s1h7ax.png\" width=\"64\" height=\"64\" alt=\"ksaldana1\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals63_olgqd6.png\" width=\"64\" height=\"64\" alt=\"jesperryom\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals70_t5kjmo.png\" width=\"64\" height=\"64\" alt=\"JackCA\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals38_vwccce.png\" width=\"64\" height=\"64\" alt=\"peymanmortazavi\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals92_htl0if.png\" width=\"64\" height=\"64\" alt=\"jonaustin\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals33_ch4hs0.png\" width=\"64\" height=\"64\" alt=\"Yuriy Ivanyuk\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals26_knlvug.png\" width=\"64\" height=\"64\" alt=\"abenz1267\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals100_g8py5g.png\" width=\"64\" height=\"64\" alt=\"Sh3Rm4n\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals14_bnuacq.png\" width=\"64\" height=\"64\" alt=\"mwcz\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals78_hleldd.png\" width=\"64\" height=\"64\" alt=\"Philipp-M\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals37_sikg8d.png\" width=\"64\" height=\"64\" alt=\"gvelchuru\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals62_hxul6y.png\" width=\"64\" height=\"64\" alt=\"JSamir\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals19_zafwti.png\" width=\"64\" height=\"64\" alt=\"toby de havilland\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals97_iuw00n.png\" width=\"64\" height=\"64\" alt=\"viniciusarcanjo\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals70_t5kjmo.png\" width=\"64\" height=\"64\" alt=\"Mike Hearn\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals87_vnmrie.png\" width=\"64\" height=\"64\" alt=\"darsto\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://avatars2.githubusercontent.com/u/145502?v=4&s=100&s=400\" width=\"64\" height=\"64\" alt=\"pyrho\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals102_hqrga7.png\" width=\"64\" height=\"64\" alt=\"Frydac\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals90_qlafi0.png\" width=\"64\" height=\"64\" alt=\"gsa9\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals16_qlob5k.png\" width=\"64\" height=\"64\" alt=\"_andys8\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals27_bjhsl8.png\" width=\"64\" height=\"64\" alt=\"iago-lito\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals44_xa5xwi.png\" width=\"64\" height=\"64\" alt=\"ddaletski\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals83_ryixly.png\" width=\"64\" height=\"64\" alt=\"jonatan-branting\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://avatars3.githubusercontent.com/u/8683947?v=4&s=100&s=400\" width=\"64\" height=\"64\" alt=\"yutakatay\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals87_vnmrie.png\" width=\"64\" height=\"64\" alt=\"kevinrambaud\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals76_g3jfjp.png\" width=\"64\" height=\"64\" alt=\"tomaskallup\">\n</a>\n<a href=\"#Backers\">\n  <img src=\"https://cloudinary-a.akamaihd.net/bountysource/image/upload/d_noaoqqwxegvmulwus0un.png,c_pad,w_400,h_400,b_white/Bountysource_Animals46_qe2ye0.png\" width=\"64\" height=\"64\" alt=\"LewisSteele\">\n</a>\n\n## 微信扫码赞助者\n\n- free-easy\n- sarene\n- tomspeak\n- robtrac\n- 葫芦小金刚\n- leo 陶\n- 飞翔的白斩鸡\n- mark_ll\n- 火冷\n- Solomon\n- 李宇星\n- Yus\n- IndexXuan\n- Sniper\n- 陈达野\n- 胖听\n- Jimmy\n- lightxue\n- 小亦俊\n- 周慎敏\n- 凤鸣\n- Wilson\n- Abel\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\n## How do I... <a name=\"toc\"></a>\n\n- [Use This Guide](#introduction)?\n- Make Something? 🤓👩🏽‍💻📜🍳\n  - [Project Setup](#project-setup)\n  - [Contribute Documentation](#contribute-documentation)\n  - [Contribute Code](#contribute-code)\n- Manage Something ✅🙆🏼💃👔\n  - [Provide Support on Issues](#provide-support-on-issues)\n  - [Review Pull Requests](#review-pull-requests)\n  - [Join the Project Team](#join-the-project-team)\n\n## Introduction\n\nThank you so much for your interest in contributing!. All types of contributions are encouraged and valued. See the [table of contents](#toc) for different ways to help and details about how this project handles them!📝\n\nThe [Project Team](#join-the-project-team) looks forward to your contributions. 🙌🏾✨\n\n## Project Setup\n\nSo you wanna contribute some code! That's great! This project uses GitHub Pull Requests to manage contributions, so [read up on how to fork a GitHub project and file a PR](https://guides.github.com/activities/forking) if you've never done it before.\n\nIf this seems like a lot or you aren't able to do all this setup, you might also be able to [edit the files directly](https://help.github.com/articles/editing-files-in-another-user-s-repository/) without having to do any of this setup. Yes, [even code](#contribute-code).\n\nIf you want to go the usual route and run the project locally, though:\n\n- [Install Node.js](https://nodejs.org/en/download/)\n- [Fork the project](https://guides.github.com/activities/forking/#fork)\n\nThen in your terminal:\n\n- Add coc.nvim to your vim's rtp by `set runtimepath^=/path/to/coc.nvim`\n- `cd path/to/your/coc.nvim`\n- `npm install`\n- Install [coc-tsserver](https://github.com/neoclide/coc-tsserver) by\n  `:CocInstall coc-tsserver` in your vim\n- Install [coc-eslint](https://github.com/neoclide/coc-eslint) by\n  `:CocInstall coc-eslint` in your vim.\n\nAnd you should be ready to go!\n\n## Contribute Documentation\n\nDocumentation is a super important, critical part of this project. Docs are how we keep track of what we're doing, how, and why. It's how we stay on the same page about our policies. And it's how we tell others everything they need in order to be able to use this project -- or contribute to it. So thank you in advance.\n\nDocumentation contributions of any size are welcome! Feel free to file a PR even if you're just rewording a sentence to be more clear, or fixing a spelling mistake!\n\nTo contribute documentation:\n\n- [Set up the project](#project-setup).\n- Edit or add any relevant documentation.\n- Make sure your changes are formatted correctly and consistently with the rest of the documentation.\n- Re-read what you wrote, and run a spellchecker on it to make sure you didn't miss anything.\n- In your commit message(s), begin the first line with `docs:`. For example: `docs: Adding a doc contrib section to CONTRIBUTING.md`.\n- Write clear, concise commit message(s) using [conventional-changelog format](https://github.com/conventional-changelog/conventional-changelog-angular/blob/master/convention.md). Documentation commits should use `docs(<component>): <message>`.\n- Go to https://github.com/neoclide/coc.nvim/pulls and open a new pull request with your changes.\n- If your PR is connected to an open issue, add a line in your PR's description that says `Fixes: #123`, where `#123` is the number of the issue you're fixing.\n\n## Contribute Code\n\nWe like code commits a lot! They're super handy, and they keep the project going and doing the work it needs to do to be useful to others.\n\nCode contributions of just about any size are acceptable!\n\nThe main difference between code contributions and documentation contributions is that contributing code requires inclusion of relevant tests for the code being added or changed. Contributions without accompanying tests will be held off until a test is added, unless the maintainers consider the specific tests to be either impossible, or way too much of a burden for such a contribution.\n\nTo contribute code:\n\n- [Set up the project](#project-setup).\n- Make any necessary changes to the source code.\n- Include any [additional documentation](#contribute-documentation) the changes might need.\n- Make sure the code doesn't have lint issue by command `npm run lint` in your\n  terminal.\n- Write tests that verify that your contribution works as expected when necessary.\n- Make sure all tests passed by command `npm test` in your terminal.\n- Write clear, concise commit message(s) using [conventional-changelog format](https://github.com/conventional-changelog/conventional-changelog-angular/blob/master/convention.md).\n- Dependency updates, additions, or removals must be in individual commits, and the message must use the format: `<prefix>(deps): PKG@VERSION`, where `<prefix>` is any of the usual `conventional-changelog` prefixes, at your discretion.\n- Go to https://github.com/neoclide/coc.nvim/pulls and open a new pull request with your changes.\n- If your PR is connected to an open issue, add a line in your PR's description that says `Fixes: #123`, where `#123` is the number of the issue you're fixing.\n\nOnce you've filed the PR:\n\n- Barring special circumstances, maintainers will not review PRs until all checks pass (Travis, AppVeyor, etc).\n- One or more maintainers will use GitHub's review feature to review your PR.\n- If the maintainer asks for any changes, edit your changes, push, and ask for another review. Additional tags (such as `needs-tests`) will be added depending on the review.\n- If the maintainer decides not to pass on your PR, they will thank you for the contribution and explain why they won't be accepting the changes. Please don't feel offended. We still really appreciate you taking the time to do it, and we don't take that lightly. 💚\n- If your PR gets accepted, it will be marked as such, and merged into the `latest` branch soon after. Your contribution will be distributed to the masses next time the maintainers tag a release\n\n## Provide Support on Issues\n\n[Needs Collaborator](#join-the-project-team): none\n\nHelping out other users with their questions is a really awesome way of contributing to any community. It's not uncommon for most of the issues on an open source projects being support-related questions by users trying to understand something they ran into, or find their way around a known bug.\n\nSometimes, the `support` label will be added to things that turn out to actually be other things, like bugs or feature requests. In that case, suss out the details with the person who filed the original issue, add a comment explaining what the bug is, and change the label from `support` to `bug` or `feature`. If you can't do this yourself, @mention a maintainer so they can do it.\n\nIn order to help other folks out with their questions:\n\n- Go to the issue tracker and [filter open issues by the `support` label](https://github.com/neoclide/coc.nvim/issues?q=is%3Aopen+is%3Aissue+label%3Asupport).\n- Read through the list until you find something that you're familiar enough with to give an answer to.\n- Respond to the issue with whatever details are needed to clarify the question, or get more details about what's going on.\n- Once the discussion wraps up and things are clarified, either close the issue, or ask the original issue filer (or a maintainer) to close it for you.\n\nSome notes on picking up support issues:\n\n- Avoid responding to issues you don't know you can answer accurately.\n- As much as possible, try to refer to past issues with accepted answers. Link to them from your replies with the `#123` format.\n- Be kind and patient with users -- often, folks who have run into confusing things might be upset or impatient. This is ok. Try to understand where they're coming from, and if you're too uncomfortable with the tone, feel free to stay away or withdraw from the issue. (note: if the user is outright hostile or is violating the CoC, [refer to the Code of Conduct](CODE_OF_CONDUCT.md) to resolve the conflict).\n\n## Review Pull Requests\n\n[Needs Collaborator](#join-the-project-team): Issue Tracker\n\nWhile anyone can comment on a PR, add feedback, etc, PRs are only _approved_ by team members with Issue Tracker or higher permissions.\n\nPR reviews use [GitHub's own review feature](https://help.github.com/articles/about-pull-request-reviews/), which manages comments, approval, and review iteration.\n\nSome notes:\n\n- You may ask for minor changes (\"nitpicks\"), but consider whether they are really blockers to merging: try to err on the side of \"approve, with comments\".\n- _ALL PULL REQUESTS_ should be covered by a test: either by a previously-failing test, an existing test that covers the entire functionality of the submitted code, or new tests to verify any new/changed behavior. All tests must also pass and follow established conventions. Test coverage should not drop, unless the specific case is considered reasonable by maintainers.\n- Please make sure you're familiar with the code or documentation being updated, unless it's a minor change (spellchecking, minor formatting, etc). You may @mention another project member who you think is better suited for the review, but still provide a non-approving review of your own.\n- Be extra kind: people who submit code/doc contributions are putting themselves in a pretty vulnerable position, and have put time and care into what they've done (even if that's not obvious to you!) -- always respond with respect, be understanding, but don't feel like you need to sacrifice your standards for their sake, either. Just don't be a jerk about it?\n\n## Join the Project Team\n\n### Ways to Join\n\nThere are many ways to contribute! Most of them don't require any official status unless otherwise noted. That said, there's a couple of positions that grant special repository abilities, and this section describes how they're granted and what they do.\n\nAll of the below positions are granted based on the project team's needs, as well as their consensus opinion about whether they would like to work with the person and think that they would fit well into that position. The process is relatively informal, and it's likely that people who express interest in participating can just be granted the permissions they'd like.\n\nYou can spot a collaborator on the repo by looking for the `[Collaborator]` or `[Owner]` tags next to their names.\n\n| Permission    | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |\n| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| Issue Tracker | Granted to contributors who express a strong interest in spending time on the project's issue tracker. These tasks are mainly labeling issues, cleaning up old ones, and [reviewing pull requests](#review-pull-requests), as well as all the usual things non-team-member contributors can do. Issue handlers should not merge pull requests, tag releases, or directly commit code themselves: that should still be done through the usual pull request process. Becoming an Issue Handler means the project team trusts you to understand enough of the team's process and context to implement it on the issue tracker. |\n| Committer     | Granted to contributors who want to handle the actual pull request merges, tagging new versions, etc. Committers should have a good level of familiarity with the codebase, and enough context to understand the implications of various changes, as well as a good sense of the will and expectations of the project team.                                                                                                                                                                                                                                                                                                                                              |\n| Admin/Owner   | Granted to people ultimately responsible for the project, its community, etc.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright (c) <2022> <chemzqm@gmail.com>\n\n\"Anti 996\" License Version 1.0 (Draft)\n\nPermission is hereby granted to any individual or legal entity\nobtaining a copy of this licensed work (including the source code,\ndocumentation and/or related items, hereinafter collectively referred\nto as the \"licensed work\"), free of charge, to deal with the licensed\nwork for any purpose, including without limitation, the rights to use,\nreproduce, modify, prepare derivative works of, distribute, publish\nand sublicense the licensed work, subject to the following conditions:\n\n1. The individual or the legal entity must conspicuously display,\n   without modification, this License and the notice on each redistributed\n   or derivative copy of the Licensed Work.\n\n2. The individual or the legal entity must strictly comply with all\n   applicable laws, regulations, rules and standards of the jurisdiction\n   relating to labor and employment where the individual is physically\n   located or where the individual was born or naturalized; or where the\n   legal entity is registered or is operating (whichever is stricter). In\n   case that the jurisdiction has no such laws, regulations, rules and\n   standards or its laws, regulations, rules and standards are\n   unenforceable, the individual or the legal entity are required to\n   comply with Core International Labor Standards.\n\n3. The individual or the legal entity shall not induce, suggest or force\n   its employee(s), whether full-time or part-time, or its independent\n   contractor(s), in any methods, to agree in oral or written form, to\n   directly or indirectly restrict, weaken or relinquish his or her\n   rights or remedies under such laws, regulations, rules and standards\n   relating to labor and employment as mentioned above, no matter whether\n   such written or oral agreements are enforceable under the laws of the\n   said jurisdiction, nor shall such individual or the legal entity\n   limit, in any methods, the rights of its employee(s) or independent\n   contractor(s) from reporting or complaining to the copyright holder or\n   relevant authorities monitoring the compliance of the license about\n   its violation(s) of the said license.\n\nTHE LICENSED WORK IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM,\nDAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\nOTHERWISE, ARISING FROM, OUT OF OR IN ANY WAY CONNECTION WITH THE\nLICENSED WORK OR THE USE OR OTHER DEALINGS IN THE LICENSED WORK.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <a href=\"https://www.vim.org/scripts/script.php?script_id=5779\">\n    <img alt=\"Logo\" src=\"https://github.com/neoclide/coc.nvim/assets/251450/9c2bc011-35f0-4ef5-93ba-bc3f17e65bb7\" height=\"240\" />\n  </a>\n  <p align=\"center\">Make your Vim/Neovim as smart as VS Code</p>\n  <p align=\"center\">\n    <a href=\"LICENSE.md\"><img alt=\"Software License\" src=\"https://img.shields.io/badge/license-Anti%20996-brightgreen.svg?style=flat-square\"></a>\n    <a href=\"https://github.com/neoclide/coc.nvim/actions\"><img alt=\"Actions\" src=\"https://img.shields.io/github/actions/workflow/status/neoclide/coc.nvim/ci.yml?style=flat-square&branch=master\"></a>\n    <a href=\"https://codecov.io/gh/neoclide/coc.nvim\"><img alt=\"Codecov Coverage Status\" src=\"https://img.shields.io/codecov/c/github/neoclide/coc.nvim.svg?style=flat-square\"></a>\n    <a href=\"doc/coc.txt\"><img alt=\"Doc\" src=\"https://img.shields.io/badge/doc-%3Ah%20coc.txt-brightgreen.svg?style=flat-square\"></a>\n    <a href=\"https://deepwiki.com/neoclide/coc.nvim\"><img src=\"https://deepwiki.com/badge.svg\" alt=\"Ask DeepWiki\"></a>\n  </p>\n</p>\n\n---\n\n<img alt=\"Custom coc popup menu with snippet support\" src=\"https://github.com/neoclide/coc.nvim/assets/251450/05f60ab8-dcb1-40f7-9e4a-3c03f5db5398\" width=\"60%\" />\n\n_Custom popup menu with snippet support_\n\n## Why?\n\n- 🚀 **Fast**: separated NodeJS process that does not slow down Vim most of the time.\n- 💎 **Reliable**: typed language, tested with CI.\n- 🌟 **Featured**: all LSP 3.16 features are supported, see `:h coc-lsp`.\n- ❤️ **Flexible**: [configured like VS Code](https://github.com/neoclide/coc.nvim/wiki/Using-the-configuration-file), [Coc extensions function similarly to VS Code extensions](https://github.com/neoclide/coc.nvim/wiki/Using-coc-extensions)\n\n## Quick Start\n\nMake sure use Vim >= 9.0.0438 or Neovim >= 0.8.0.\n\nInstall [nodejs](https://nodejs.org/en/download/) >= 16.18.0:\n\n```bash\ncurl -sL install-node.vercel.app/lts | bash\n```\n\nFor [vim-plug](https://github.com/junegunn/vim-plug) users:\n\n```vim\n\" Use release branch (recommended)\nPlug 'neoclide/coc.nvim', {'branch': 'release'}\n\n\" Or build from source code by using npm\nPlug 'neoclide/coc.nvim', {'branch': 'master', 'do': 'npm ci'}\n```\n\nin your `.vimrc` or `init.vim`, then restart Vim and run `:PlugInstall`.\n\nCheckout [Install\ncoc.nvim](https://github.com/neoclide/coc.nvim/wiki/Install-coc.nvim) for\nmore info.\n\nYou **have to** install coc extensions or configure language servers for\nLSP support.\n\nInstall extensions like this:\n\n    :CocInstall coc-json coc-tsserver\n\nOr you can configure a language server in your `coc-settings.json`(open it using `:CocConfig`) like this:\n\n```json\n{\n  \"languageserver\": {\n    \"go\": {\n      \"command\": \"gopls\",\n      \"rootPatterns\": [\"go.mod\"],\n      \"trace.server\": \"verbose\",\n      \"filetypes\": [\"go\"]\n    }\n  }\n}\n```\n\nCheckout the wiki for more details:\n\n- [Completion with sources](https://github.com/neoclide/coc.nvim/wiki/Completion-with-sources)\n- [Using the configuration file](https://github.com/neoclide/coc.nvim/wiki/Using-the-configuration-file)\n- [Using coc extensions](https://github.com/neoclide/coc.nvim/wiki/Using-coc-extensions)\n- [Configure language servers](https://github.com/neoclide/coc.nvim/wiki/Language-servers)\n- [F.A.Q](https://github.com/neoclide/coc.nvim/wiki/F.A.Q)\n\nCheckout `:h coc-nvim` for Vim interface.\n\n## Example Vim configuration\n\nConfiguration is required to make coc.nvim easier to work with, since it\ndoesn't change your key-mappings or Vim options. This is done as much as\npossible to avoid conflict with your other plugins.\n\n**❗️Important**: Some Vim plugins can change your key mappings. Please use\ncommand like`:verbose imap <tab>` to make sure that your keymap has taken effect.\n\n```vim\n\" https://raw.githubusercontent.com/neoclide/coc.nvim/master/doc/coc-example-config.vim\n\n\" May need for Vim (not Neovim) since coc.nvim calculates byte offset by count\n\" utf-8 byte sequence\nset encoding=utf-8\n\" Some servers have issues with backup files, see #649\nset nobackup\nset nowritebackup\n\n\" Having longer updatetime (default is 4000 ms = 4s) leads to noticeable\n\" delays and poor user experience\nset updatetime=300\n\n\" Always show the signcolumn, otherwise it would shift the text each time\n\" diagnostics appear/become resolved\nset signcolumn=yes\n\n\" Use tab for trigger completion with characters ahead and navigate\n\" NOTE: There's always complete item selected by default, you may want to enable\n\" no select by `\"suggest.noselect\": true` in your configuration file\n\" NOTE: Use command ':verbose imap <tab>' to make sure tab is not mapped by\n\" other plugin before putting this into your config\ninoremap <silent><expr> <TAB>\n      \\ coc#pum#visible() ? coc#pum#next(1) :\n      \\ CheckBackspace() ? \"\\<Tab>\" :\n      \\ coc#refresh()\ninoremap <expr><S-TAB> coc#pum#visible() ? coc#pum#prev(1) : \"\\<C-h>\"\n\n\" Make <CR> to accept selected completion item or notify coc.nvim to format\n\" <C-g>u breaks current undo, please make your own choice\ninoremap <silent><expr> <CR> coc#pum#visible() ? coc#pum#confirm()\n                              \\: \"\\<C-g>u\\<CR>\\<c-r>=coc#on_enter()\\<CR>\"\n\nfunction! CheckBackspace() abort\n  let col = col('.') - 1\n  return !col || getline('.')[col - 1]  =~# '\\s'\nendfunction\n\n\" Use <c-space> to trigger completion\nif has('nvim')\n  inoremap <silent><expr> <c-space> coc#refresh()\nelse\n  inoremap <silent><expr> <c-@> coc#refresh()\nendif\n\n\" Use `[g` and `]g` to navigate diagnostics\n\" Use `:CocDiagnostics` to get all diagnostics of current buffer in location list\nnmap <silent><nowait> [g <Plug>(coc-diagnostic-prev)\nnmap <silent><nowait> ]g <Plug>(coc-diagnostic-next)\n\n\" GoTo code navigation\nnmap <silent><nowait> gd <Plug>(coc-definition)\nnmap <silent><nowait> gy <Plug>(coc-type-definition)\nnmap <silent><nowait> gi <Plug>(coc-implementation)\nnmap <silent><nowait> gr <Plug>(coc-references)\n\n\" Use K to show documentation in preview window\nnnoremap <silent> K :call ShowDocumentation()<CR>\n\nfunction! ShowDocumentation()\n  if CocAction('hasProvider', 'hover')\n    call CocActionAsync('doHover')\n  else\n    call feedkeys('K', 'in')\n  endif\nendfunction\n\n\" Highlight the symbol and its references when holding the cursor\nautocmd CursorHold * silent call CocActionAsync('highlight')\n\n\" Symbol renaming\nnmap <leader>rn <Plug>(coc-rename)\n\n\" Formatting selected code\nxmap <leader>f  <Plug>(coc-format-selected)\nnmap <leader>f  <Plug>(coc-format-selected)\n\naugroup mygroup\n  autocmd!\n  \" Setup formatexpr specified filetype(s)\n  autocmd FileType typescript,json setl formatexpr=CocAction('formatSelected')\naugroup end\n\n\" Applying code actions to the selected code block\n\" Example: `<leader>aap` for current paragraph\nxmap <leader>a  <Plug>(coc-codeaction-selected)\nnmap <leader>a  <Plug>(coc-codeaction-selected)\n\n\" Remap keys for applying code actions at the cursor position\nnmap <leader>ac  <Plug>(coc-codeaction-cursor)\n\" Remap keys for apply code actions affect whole buffer\nnmap <leader>as  <Plug>(coc-codeaction-source)\n\" Apply the most preferred quickfix action to fix diagnostic on the current line\nnmap <leader>qf  <Plug>(coc-fix-current)\n\n\" Remap keys for applying refactor code actions\nnmap <silent> <leader>re <Plug>(coc-codeaction-refactor)\nxmap <silent> <leader>r  <Plug>(coc-codeaction-refactor-selected)\nnmap <silent> <leader>r  <Plug>(coc-codeaction-refactor-selected)\n\n\" Run the Code Lens action on the current line\nnmap <leader>cl  <Plug>(coc-codelens-action)\n\n\" Map function and class text objects\n\" NOTE: Requires 'textDocument.documentSymbol' support from the language server\nxmap if <Plug>(coc-funcobj-i)\nomap if <Plug>(coc-funcobj-i)\nxmap af <Plug>(coc-funcobj-a)\nomap af <Plug>(coc-funcobj-a)\nxmap ic <Plug>(coc-classobj-i)\nomap ic <Plug>(coc-classobj-i)\nxmap ac <Plug>(coc-classobj-a)\nomap ac <Plug>(coc-classobj-a)\n\n\" Remap <C-f> and <C-b> to scroll float windows/popups\nif has('nvim-0.4.0') || has('patch-8.2.0750')\n  nnoremap <silent><nowait><expr> <C-f> coc#float#has_scroll() ? coc#float#scroll(1) : \"\\<C-f>\"\n  nnoremap <silent><nowait><expr> <C-b> coc#float#has_scroll() ? coc#float#scroll(0) : \"\\<C-b>\"\n  inoremap <silent><nowait><expr> <C-f> coc#float#has_scroll() ? \"\\<c-r>=coc#float#scroll(1)\\<cr>\" : \"\\<Right>\"\n  inoremap <silent><nowait><expr> <C-b> coc#float#has_scroll() ? \"\\<c-r>=coc#float#scroll(0)\\<cr>\" : \"\\<Left>\"\n  vnoremap <silent><nowait><expr> <C-f> coc#float#has_scroll() ? coc#float#scroll(1) : \"\\<C-f>\"\n  vnoremap <silent><nowait><expr> <C-b> coc#float#has_scroll() ? coc#float#scroll(0) : \"\\<C-b>\"\nendif\n\n\" Use CTRL-S for selections ranges\n\" Requires 'textDocument/selectionRange' support of language server\nnmap <silent> <C-s> <Plug>(coc-range-select)\nxmap <silent> <C-s> <Plug>(coc-range-select)\n\n\" Add `:Format` command to format current buffer\ncommand! -nargs=0 Format :call CocActionAsync('format')\n\n\" Add `:Fold` command to fold current buffer\ncommand! -nargs=? Fold :call     CocAction('fold', <f-args>)\n\n\" Add `:OR` command for organize imports of the current buffer\ncommand! -nargs=0 OR   :call     CocActionAsync('runCommand', 'editor.action.organizeImport')\n\n\" Add (Neo)Vim's native statusline support\n\" NOTE: Please see `:h coc-status` for integrations with external plugins that\n\" provide custom statusline: lightline.vim, vim-airline\nset statusline^=%{coc#status()}%{get(b:,'coc_current_function','')}\n\n\" Mappings for CoCList\n\" Show all diagnostics\nnnoremap <silent><nowait> <space>a  :<C-u>CocList diagnostics<cr>\n\" Manage extensions\nnnoremap <silent><nowait> <space>e  :<C-u>CocList extensions<cr>\n\" Show commands\nnnoremap <silent><nowait> <space>c  :<C-u>CocList commands<cr>\n\" Find symbol of current document\nnnoremap <silent><nowait> <space>o  :<C-u>CocList outline<cr>\n\" Search workspace symbols\nnnoremap <silent><nowait> <space>s  :<C-u>CocList -I symbols<cr>\n\" Do default action for next item\nnnoremap <silent><nowait> <space>j  :<C-u>CocNext<CR>\n\" Do default action for previous item\nnnoremap <silent><nowait> <space>k  :<C-u>CocPrev<CR>\n\" Resume latest coc list\nnnoremap <silent><nowait> <space>p  :<C-u>CocListResume<CR>\n```\n\n## Example Lua configuration\n\nNOTE: This only works in Neovim 0.7.0dev+.\n\n```lua\n-- https://raw.githubusercontent.com/neoclide/coc.nvim/master/doc/coc-example-config.lua\n\n-- Some servers have issues with backup files, see #649\nvim.opt.backup = false\nvim.opt.writebackup = false\n\n-- Having longer updatetime (default is 4000 ms = 4s) leads to noticeable\n-- delays and poor user experience\nvim.opt.updatetime = 300\n\n-- Always show the signcolumn, otherwise it would shift the text each time\n-- diagnostics appeared/became resolved\nvim.opt.signcolumn = \"yes\"\n\nlocal keyset = vim.keymap.set\n-- Autocomplete\nfunction _G.check_back_space()\n    local col = vim.fn.col('.') - 1\n    return col == 0 or vim.fn.getline('.'):sub(col, col):match('%s') ~= nil\nend\n\n-- Use Tab for trigger completion with characters ahead and navigate\n-- NOTE: There's always a completion item selected by default, you may want to enable\n-- no select by setting `\"suggest.noselect\": true` in your configuration file\n-- NOTE: Use command ':verbose imap <tab>' to make sure Tab is not mapped by\n-- other plugins before putting this into your config\nlocal opts = {silent = true, noremap = true, expr = true, replace_keycodes = false}\nkeyset(\"i\", \"<TAB>\", 'coc#pum#visible() ? coc#pum#next(1) : v:lua.check_back_space() ? \"<TAB>\" : coc#refresh()', opts)\nkeyset(\"i\", \"<S-TAB>\", [[coc#pum#visible() ? coc#pum#prev(1) : \"\\<C-h>\"]], opts)\n\n-- Make <CR> to accept selected completion item or notify coc.nvim to format\n-- <C-g>u breaks current undo, please make your own choice\nkeyset(\"i\", \"<cr>\", [[coc#pum#visible() ? coc#pum#confirm() : \"\\<C-g>u\\<CR>\\<c-r>=coc#on_enter()\\<CR>\"]], opts)\n\n-- Use <c-j> to trigger snippets\nkeyset(\"i\", \"<c-j>\", \"<Plug>(coc-snippets-expand-jump)\")\n-- Use <c-space> to trigger completion\nkeyset(\"i\", \"<c-space>\", \"coc#refresh()\", {silent = true, expr = true})\n\n-- Use `[g` and `]g` to navigate diagnostics\n-- Use `:CocDiagnostics` to get all diagnostics of current buffer in location list\nkeyset(\"n\", \"[g\", \"<Plug>(coc-diagnostic-prev)\", {silent = true})\nkeyset(\"n\", \"]g\", \"<Plug>(coc-diagnostic-next)\", {silent = true})\n\n-- GoTo code navigation\nkeyset(\"n\", \"gd\", \"<Plug>(coc-definition)\", {silent = true})\nkeyset(\"n\", \"gy\", \"<Plug>(coc-type-definition)\", {silent = true})\nkeyset(\"n\", \"gi\", \"<Plug>(coc-implementation)\", {silent = true})\nkeyset(\"n\", \"gr\", \"<Plug>(coc-references)\", {silent = true})\n\n\n-- Use K to show documentation in preview window\nfunction _G.show_docs()\n    local cw = vim.fn.expand('<cword>')\n    if vim.fn.index({'vim', 'help'}, vim.bo.filetype) >= 0 then\n        vim.api.nvim_command('h ' .. cw)\n    elseif vim.api.nvim_eval('coc#rpc#ready()') then\n        vim.fn.CocActionAsync('doHover')\n    else\n        vim.api.nvim_command('!' .. vim.o.keywordprg .. ' ' .. cw)\n    end\nend\nkeyset(\"n\", \"K\", '<CMD>lua _G.show_docs()<CR>', {silent = true})\n\n\n-- Highlight the symbol and its references on a CursorHold event(cursor is idle)\nvim.api.nvim_create_augroup(\"CocGroup\", {})\nvim.api.nvim_create_autocmd(\"CursorHold\", {\n    group = \"CocGroup\",\n    command = \"silent call CocActionAsync('highlight')\",\n    desc = \"Highlight symbol under cursor on CursorHold\"\n})\n\n\n-- Symbol renaming\nkeyset(\"n\", \"<leader>rn\", \"<Plug>(coc-rename)\", {silent = true})\n\n\n-- Formatting selected code\nkeyset(\"x\", \"<leader>f\", \"<Plug>(coc-format-selected)\", {silent = true})\nkeyset(\"n\", \"<leader>f\", \"<Plug>(coc-format-selected)\", {silent = true})\n\n\n-- Setup formatexpr specified filetype(s)\nvim.api.nvim_create_autocmd(\"FileType\", {\n    group = \"CocGroup\",\n    pattern = \"typescript,json\",\n    command = \"setl formatexpr=CocAction('formatSelected')\",\n    desc = \"Setup formatexpr specified filetype(s).\"\n})\n\n-- Apply codeAction to the selected region\n-- Example: `<leader>aap` for current paragraph\nlocal opts = {silent = true, nowait = true}\nkeyset(\"x\", \"<leader>a\", \"<Plug>(coc-codeaction-selected)\", opts)\nkeyset(\"n\", \"<leader>a\", \"<Plug>(coc-codeaction-selected)\", opts)\n\n-- Remap keys for apply code actions at the cursor position.\nkeyset(\"n\", \"<leader>ac\", \"<Plug>(coc-codeaction-cursor)\", opts)\n-- Remap keys for apply source code actions for current file.\nkeyset(\"n\", \"<leader>as\", \"<Plug>(coc-codeaction-source)\", opts)\n-- Apply the most preferred quickfix action on the current line.\nkeyset(\"n\", \"<leader>qf\", \"<Plug>(coc-fix-current)\", opts)\n\n-- Remap keys for apply refactor code actions.\nkeyset(\"n\", \"<leader>re\", \"<Plug>(coc-codeaction-refactor)\", { silent = true })\nkeyset(\"x\", \"<leader>r\", \"<Plug>(coc-codeaction-refactor-selected)\", { silent = true })\nkeyset(\"n\", \"<leader>r\", \"<Plug>(coc-codeaction-refactor-selected)\", { silent = true })\n\n-- Run the Code Lens actions on the current line\nkeyset(\"n\", \"<leader>cl\", \"<Plug>(coc-codelens-action)\", opts)\n\n\n-- Map function and class text objects\n-- NOTE: Requires 'textDocument.documentSymbol' support from the language server\nkeyset(\"x\", \"if\", \"<Plug>(coc-funcobj-i)\", opts)\nkeyset(\"o\", \"if\", \"<Plug>(coc-funcobj-i)\", opts)\nkeyset(\"x\", \"af\", \"<Plug>(coc-funcobj-a)\", opts)\nkeyset(\"o\", \"af\", \"<Plug>(coc-funcobj-a)\", opts)\nkeyset(\"x\", \"ic\", \"<Plug>(coc-classobj-i)\", opts)\nkeyset(\"o\", \"ic\", \"<Plug>(coc-classobj-i)\", opts)\nkeyset(\"x\", \"ac\", \"<Plug>(coc-classobj-a)\", opts)\nkeyset(\"o\", \"ac\", \"<Plug>(coc-classobj-a)\", opts)\n\n\n-- Remap <C-f> and <C-b> to scroll float windows/popups\n---@diagnostic disable-next-line: redefined-local\nlocal opts = {silent = true, nowait = true, expr = true}\nkeyset(\"n\", \"<C-f>\", 'coc#float#has_scroll() ? coc#float#scroll(1) : \"<C-f>\"', opts)\nkeyset(\"n\", \"<C-b>\", 'coc#float#has_scroll() ? coc#float#scroll(0) : \"<C-b>\"', opts)\nkeyset(\"i\", \"<C-f>\",\n       'coc#float#has_scroll() ? \"<c-r>=coc#float#scroll(1)<cr>\" : \"<Right>\"', opts)\nkeyset(\"i\", \"<C-b>\",\n       'coc#float#has_scroll() ? \"<c-r>=coc#float#scroll(0)<cr>\" : \"<Left>\"', opts)\nkeyset(\"v\", \"<C-f>\", 'coc#float#has_scroll() ? coc#float#scroll(1) : \"<C-f>\"', opts)\nkeyset(\"v\", \"<C-b>\", 'coc#float#has_scroll() ? coc#float#scroll(0) : \"<C-b>\"', opts)\n\n\n-- Use CTRL-S for selections ranges\n-- Requires 'textDocument/selectionRange' support of language server\nkeyset(\"n\", \"<C-s>\", \"<Plug>(coc-range-select)\", {silent = true})\nkeyset(\"x\", \"<C-s>\", \"<Plug>(coc-range-select)\", {silent = true})\n\n\n-- Add `:Format` command to format current buffer\nvim.api.nvim_create_user_command(\"Format\", \"call CocAction('format')\", {})\n\n-- \" Add `:Fold` command to fold current buffer\nvim.api.nvim_create_user_command(\"Fold\", \"call CocAction('fold', <f-args>)\", {nargs = '?'})\n\n-- Add `:OR` command for organize imports of the current buffer\nvim.api.nvim_create_user_command(\"OR\", \"call CocActionAsync('runCommand', 'editor.action.organizeImport')\", {})\n\n-- Add (Neo)Vim's native statusline support\n-- NOTE: Please see `:h coc-status` for integrations with external plugins that\n-- provide custom statusline: lightline.vim, vim-airline\nvim.opt.statusline:prepend(\"%{coc#status()}%{get(b:,'coc_current_function','')}\")\n\n-- Mappings for CoCList\n-- code actions and coc stuff\n---@diagnostic disable-next-line: redefined-local\nlocal opts = {silent = true, nowait = true}\n-- Show all diagnostics\nkeyset(\"n\", \"<space>a\", \":<C-u>CocList diagnostics<cr>\", opts)\n-- Manage extensions\nkeyset(\"n\", \"<space>e\", \":<C-u>CocList extensions<cr>\", opts)\n-- Show commands\nkeyset(\"n\", \"<space>c\", \":<C-u>CocList commands<cr>\", opts)\n-- Find symbol of current document\nkeyset(\"n\", \"<space>o\", \":<C-u>CocList outline<cr>\", opts)\n-- Search workspace symbols\nkeyset(\"n\", \"<space>s\", \":<C-u>CocList -I symbols<cr>\", opts)\n-- Do default action for next item\nkeyset(\"n\", \"<space>j\", \":<C-u>CocNext<cr>\", opts)\n-- Do default action for previous item\nkeyset(\"n\", \"<space>k\", \":<C-u>CocPrev<cr>\", opts)\n-- Resume latest coc list\nkeyset(\"n\", \"<space>p\", \":<C-u>CocListResume<cr>\", opts)\n```\n\n## Articles\n\n- [coc.nvim 插件体系介绍](https://zhuanlan.zhihu.com/p/65524706)\n- [CocList 入坑指南](https://zhuanlan.zhihu.com/p/71846145)\n- [Create coc.nvim extension to improve Vim experience](https://medium.com/@chemzqm/create-coc-nvim-extension-to-improve-vim-experience-4461df269173)\n- [How to write a coc.nvim extension (and why)](https://samroeca.com/coc-plugin.html)\n\n## Troubleshooting\n\nTry these steps if you experience problems with coc.nvim:\n\n- Ensure your Vim version >= 8.0 using `:version`\n- If a service failed to start, use `:CocInfo` or `:checkhealth` if you use Neovim\n- Checkout the log of coc.nvim with `:CocOpenLog`\n- If you have issues with the language server, it's recommended to [checkout\n  the language server output](https://github.com/neoclide/coc.nvim/wiki/Debug-language-server#using-output-channel)\n\n## Feedback\n\n- Have a question? Start a discussion on [GitHub Discussions](https://github.com/neoclide/coc.nvim/discussions).\n- File a bug in [GitHub Issues](https://github.com/neoclide/coc.nvim/issues).\n\n## Backers\n\n[Become a backer](https://opencollective.com/cocnvim#backer) and get your image on our README on GitHub with a link to your site.\n\n<a href=\"https://opencollective.com/cocnvim/backer/0/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/0/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/1/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/1/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/2/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/2/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/3/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/3/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/4/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/4/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/5/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/5/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/6/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/6/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/7/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/7/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/8/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/8/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/9/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/9/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/10/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/10/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/11/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/11/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/12/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/12/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/13/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/13/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/14/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/14/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/15/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/15/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/16/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/16/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/17/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/17/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/18/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/18/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/19/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/19/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/20/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/20/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/21/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/21/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/22/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/22/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/23/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/23/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/24/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/24/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/25/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/25/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/26/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/26/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/27/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/27/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/28/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/28/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/29/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/29/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/30/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/30/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/31/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/31/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/32/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/32/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/33/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/33/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/34/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/34/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/35/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/35/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/36/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/36/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/37/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/37/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/38/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/38/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/39/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/39/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/40/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/40/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/41/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/41/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/42/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/42/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/43/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/43/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/44/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/44/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/45/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/45/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/46/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/46/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/47/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/47/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/48/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/48/avatar.svg?requireActive=false\"></a>\n<a href=\"https://opencollective.com/cocnvim/backer/49/website?requireActive=false\" target=\"_blank\"><img src=\"https://opencollective.com/cocnvim/backer/49/avatar.svg?requireActive=false\"></a>\n\n<a href=\"https://opencollective.com/cocnvim#backer\" target=\"_blank\"><img src=\"https://images.opencollective.com/static/images/become_backer.svg\"></a>\n\n## Contributors\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tbody>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/chemzqm\"><img src=\"https://avatars.githubusercontent.com/u/251450?v=4?s=50\" width=\"50px;\" alt=\"Qiming zhao\"/><br /><sub><b>Qiming zhao</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=chemzqm\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://fann.im/\"><img src=\"https://avatars.githubusercontent.com/u/345274?v=4?s=50\" width=\"50px;\" alt=\"Heyward Fann\"/><br /><sub><b>Heyward Fann</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=fannheyward\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/weirongxu\"><img src=\"https://avatars.githubusercontent.com/u/1709861?v=4?s=50\" width=\"50px;\" alt=\"Raidou\"/><br /><sub><b>Raidou</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=weirongxu\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/kevinhwang91\"><img src=\"https://avatars.githubusercontent.com/u/17562139?v=4?s=50\" width=\"50px;\" alt=\"kevinhwang91\"/><br /><sub><b>kevinhwang91</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=kevinhwang91\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://yuuko.cn/\"><img src=\"https://avatars.githubusercontent.com/u/5492542?v=4?s=50\" width=\"50px;\" alt=\"年糕小豆汤\"/><br /><sub><b>年糕小豆汤</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=iamcco\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Avi-D-coder\"><img src=\"https://avatars.githubusercontent.com/u/29133776?v=4?s=50\" width=\"50px;\" alt=\"Avi Dessauer\"/><br /><sub><b>Avi Dessauer</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=Avi-D-coder\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/voldikss\"><img src=\"https://avatars.githubusercontent.com/u/20282795?v=4?s=50\" width=\"50px;\" alt=\"最上川\"/><br /><sub><b>最上川</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=voldikss\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://www.microsoft.com/en-us/research/people/yatli/\"><img src=\"https://avatars.githubusercontent.com/u/20684720?v=4?s=50\" width=\"50px;\" alt=\"Yatao Li\"/><br /><sub><b>Yatao Li</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=yatli\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/xiyaowong\"><img src=\"https://avatars.githubusercontent.com/u/47070852?v=4?s=50\" width=\"50px;\" alt=\"wongxy\"/><br /><sub><b>wongxy</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=xiyaowong\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/sam-mccall\"><img src=\"https://avatars.githubusercontent.com/u/548993?v=4?s=50\" width=\"50px;\" alt=\"Sam McCall\"/><br /><sub><b>Sam McCall</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=sam-mccall\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://samroeca.com/pages/about.html#about\"><img src=\"https://avatars.githubusercontent.com/u/3723671?v=4?s=50\" width=\"50px;\" alt=\"Samuel Roeca\"/><br /><sub><b>Samuel Roeca</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=pappasam\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/amiralies\"><img src=\"https://avatars.githubusercontent.com/u/13261088?v=4?s=50\" width=\"50px;\" alt=\"Amirali Esmaeili\"/><br /><sub><b>Amirali Esmaeili</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=amiralies\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://bit.ly/3cLKGE4\"><img src=\"https://avatars.githubusercontent.com/u/3051781?v=4?s=50\" width=\"50px;\" alt=\"Jack Rowlingson\"/><br /><sub><b>Jack Rowlingson</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=jrowlingson\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/tomtomjhj\"><img src=\"https://avatars.githubusercontent.com/u/19489738?v=4?s=50\" width=\"50px;\" alt=\"Jaehwang Jung\"/><br /><sub><b>Jaehwang Jung</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=tomtomjhj\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/antoinemadec\"><img src=\"https://avatars.githubusercontent.com/u/10830594?v=4?s=50\" width=\"50px;\" alt=\"Antoine\"/><br /><sub><b>Antoine</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=antoinemadec\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/cosminadrianpopescu\"><img src=\"https://avatars.githubusercontent.com/u/5187873?v=4?s=50\" width=\"50px;\" alt=\"Cosmin Popescu\"/><br /><sub><b>Cosmin Popescu</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=cosminadrianpopescu\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://ducnx.com/\"><img src=\"https://avatars.githubusercontent.com/u/1186411?v=4?s=50\" width=\"50px;\" alt=\"Duc Nghiem Xuan\"/><br /><sub><b>Duc Nghiem Xuan</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=xuanduc987\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://nosubstance.me/\"><img src=\"https://avatars.githubusercontent.com/u/1269815?v=4?s=50\" width=\"50px;\" alt=\"Francisco Lopes\"/><br /><sub><b>Francisco Lopes</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=oblitum\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/daquexian\"><img src=\"https://avatars.githubusercontent.com/u/11607199?v=4?s=50\" width=\"50px;\" alt=\"daquexian\"/><br /><sub><b>daquexian</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=daquexian\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/apps/dependabot\"><img src=\"https://avatars.githubusercontent.com/in/29110?v=4?s=50\" width=\"50px;\" alt=\"dependabot[bot]\"/><br /><sub><b>dependabot[bot]</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=dependabot[bot]\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/apps/greenkeeper\"><img src=\"https://avatars.githubusercontent.com/in/505?v=4?s=50\" width=\"50px;\" alt=\"greenkeeper[bot]\"/><br /><sub><b>greenkeeper[bot]</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=greenkeeper[bot]\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://chris-kipp.io/\"><img src=\"https://avatars.githubusercontent.com/u/13974112?v=4?s=50\" width=\"50px;\" alt=\"Chris Kipp\"/><br /><sub><b>Chris Kipp</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=ckipp01\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://dmitmel.github.io/\"><img src=\"https://avatars.githubusercontent.com/u/15367354?v=4?s=50\" width=\"50px;\" alt=\"Dmytro Meleshko\"/><br /><sub><b>Dmytro Meleshko</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=dmitmel\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/kirillbobyrev\"><img src=\"https://avatars.githubusercontent.com/u/3352968?v=4?s=50\" width=\"50px;\" alt=\"Kirill Bobyrev\"/><br /><sub><b>Kirill Bobyrev</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=kirillbobyrev\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/gbcreation\"><img src=\"https://avatars.githubusercontent.com/u/454315?v=4?s=50\" width=\"50px;\" alt=\"Gontran Baerts\"/><br /><sub><b>Gontran Baerts</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=gbcreation\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://andys8.de/\"><img src=\"https://avatars.githubusercontent.com/u/13085980?v=4?s=50\" width=\"50px;\" alt=\"Andy\"/><br /><sub><b>Andy</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=andys8\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://www.alexcj96.com/\"><img src=\"https://avatars.githubusercontent.com/u/33961674?v=4?s=50\" width=\"50px;\" alt=\"Cheng JIANG\"/><br /><sub><b>Cheng JIANG</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=GopherJ\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/cpearce-py\"><img src=\"https://avatars.githubusercontent.com/u/53532946?v=4?s=50\" width=\"50px;\" alt=\"Corin\"/><br /><sub><b>Corin</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=cpearce-py\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/wodesuck\"><img src=\"https://avatars.githubusercontent.com/u/3124581?v=4?s=50\" width=\"50px;\" alt=\"Daniel Zhang\"/><br /><sub><b>Daniel Zhang</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=wodesuck\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Ferdi265\"><img src=\"https://avatars.githubusercontent.com/u/4077106?v=4?s=50\" width=\"50px;\" alt=\"Ferdinand Bachmann\"/><br /><sub><b>Ferdinand Bachmann</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=Ferdi265\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://goushi.me/\"><img src=\"https://avatars.githubusercontent.com/u/16915589?v=4?s=50\" width=\"50px;\" alt=\"Guangqing Chen\"/><br /><sub><b>Guangqing Chen</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=gou4shi1\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://jademeskill.com/\"><img src=\"https://avatars.githubusercontent.com/u/2108?v=4?s=50\" width=\"50px;\" alt=\"Jade Meskill\"/><br /><sub><b>Jade Meskill</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=iamruinous\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/jpoppe\"><img src=\"https://avatars.githubusercontent.com/u/65505?v=4?s=50\" width=\"50px;\" alt=\"Jasper Poppe\"/><br /><sub><b>Jasper Poppe</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=jpoppe\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/jean\"><img src=\"https://avatars.githubusercontent.com/u/84800?v=4?s=50\" width=\"50px;\" alt=\"Jean Jordaan\"/><br /><sub><b>Jean Jordaan</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=jean\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://xuann.wang/\"><img src=\"https://avatars.githubusercontent.com/u/44045911?v=4?s=50\" width=\"50px;\" alt=\"Kid\"/><br /><sub><b>Kid</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=kidonng\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Kavantix\"><img src=\"https://avatars.githubusercontent.com/u/6243755?v=4?s=50\" width=\"50px;\" alt=\"Pieter van Loon\"/><br /><sub><b>Pieter van Loon</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=Kavantix\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/rliebz\"><img src=\"https://avatars.githubusercontent.com/u/5321575?v=4?s=50\" width=\"50px;\" alt=\"Robert Liebowitz\"/><br /><sub><b>Robert Liebowitz</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=rliebz\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://megalithic.io/\"><img src=\"https://avatars.githubusercontent.com/u/3678?v=4?s=50\" width=\"50px;\" alt=\"Seth Messer\"/><br /><sub><b>Seth Messer</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=megalithic\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/UncleBill\"><img src=\"https://avatars.githubusercontent.com/u/1141198?v=4?s=50\" width=\"50px;\" alt=\"UncleBill\"/><br /><sub><b>UncleBill</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=UncleBill\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://zsaber.com/\"><img src=\"https://avatars.githubusercontent.com/u/6846867?v=4?s=50\" width=\"50px;\" alt=\"ZERO\"/><br /><sub><b>ZERO</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=ZSaberLv0\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://fsouza.blog/\"><img src=\"https://avatars.githubusercontent.com/u/108725?v=4?s=50\" width=\"50px;\" alt=\"fsouza\"/><br /><sub><b>fsouza</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=fsouza\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://onichandame.com/\"><img src=\"https://avatars.githubusercontent.com/u/23728505?v=4?s=50\" width=\"50px;\" alt=\"XiaoZhang\"/><br /><sub><b>XiaoZhang</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=onichandame\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/whyreal\"><img src=\"https://avatars.githubusercontent.com/u/2084642?v=4?s=50\" width=\"50px;\" alt=\"whyreal\"/><br /><sub><b>whyreal</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=whyreal\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/yehuohan\"><img src=\"https://avatars.githubusercontent.com/u/17680752?v=4?s=50\" width=\"50px;\" alt=\"yehuohan\"/><br /><sub><b>yehuohan</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=yehuohan\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://www.bakudan.farm/\"><img src=\"https://avatars.githubusercontent.com/u/4504807?v=4?s=50\" width=\"50px;\" alt=\"バクダンくん\"/><br /><sub><b>バクダンくん</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=Bakudankun\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://blog.gopherhub.org/\"><img src=\"https://avatars.githubusercontent.com/u/41671631?v=4?s=50\" width=\"50px;\" alt=\"Raphael\"/><br /><sub><b>Raphael</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=glepnir\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://tbodt.com/\"><img src=\"https://avatars.githubusercontent.com/u/5678977?v=4?s=50\" width=\"50px;\" alt=\"tbodt\"/><br /><sub><b>tbodt</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=tbodt\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://aaronmcdaid.github.io/\"><img src=\"https://avatars.githubusercontent.com/u/64350?v=4?s=50\" width=\"50px;\" alt=\"Aaron McDaid\"/><br /><sub><b>Aaron McDaid</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=aaronmcdaid\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/versi786\"><img src=\"https://avatars.githubusercontent.com/u/7347942?v=4?s=50\" width=\"50px;\" alt=\"Aasif Versi\"/><br /><sub><b>Aasif Versi</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=versi786\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/abnerf\"><img src=\"https://avatars.githubusercontent.com/u/56300?v=4?s=50\" width=\"50px;\" alt=\"Abner Silva\"/><br /><sub><b>Abner Silva</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=abnerf\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://sheerun.net/\"><img src=\"https://avatars.githubusercontent.com/u/292365?v=4?s=50\" width=\"50px;\" alt=\"Adam Stankiewicz\"/><br /><sub><b>Adam Stankiewicz</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=sheerun\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://wirow.io/\"><img src=\"https://avatars.githubusercontent.com/u/496683?v=4?s=50\" width=\"50px;\" alt=\"Adamansky Anton\"/><br /><sub><b>Adamansky Anton</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=adamansky\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://gabri.me/\"><img src=\"https://avatars.githubusercontent.com/u/63876?v=4?s=50\" width=\"50px;\" alt=\"Ahmed El Gabri\"/><br /><sub><b>Ahmed El Gabri</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=ahmedelgabri\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://theg4sh.ru/\"><img src=\"https://avatars.githubusercontent.com/u/5094691?v=4?s=50\" width=\"50px;\" alt=\"Alexandr Kondratev\"/><br /><sub><b>Alexandr Kondratev</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=theg4sh\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/andrewkshim\"><img src=\"https://avatars.githubusercontent.com/u/1403410?v=4?s=50\" width=\"50px;\" alt=\"Andrew Shim\"/><br /><sub><b>Andrew Shim</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=andrewkshim\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://andylindeman.com/\"><img src=\"https://avatars.githubusercontent.com/u/395621?v=4?s=50\" width=\"50px;\" alt=\"Andy Lindeman\"/><br /><sub><b>Andy Lindeman</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=alindeman\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Augustin82\"><img src=\"https://avatars.githubusercontent.com/u/2370810?v=4?s=50\" width=\"50px;\" alt=\"Augustin\"/><br /><sub><b>Augustin</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=Augustin82\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://bananium.fr/\"><img src=\"https://avatars.githubusercontent.com/u/3650385?v=4?s=50\" width=\"50px;\" alt=\"Bastien Orivel\"/><br /><sub><b>Bastien Orivel</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=Eijebong\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/ayroblu\"><img src=\"https://avatars.githubusercontent.com/u/4915682?v=4?s=50\" width=\"50px;\" alt=\"Ben Lu\"/><br /><sub><b>Ben Lu</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=ayroblu\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/vantreeseba\"><img src=\"https://avatars.githubusercontent.com/u/316782?v=4?s=50\" width=\"50px;\" alt=\"Ben\"/><br /><sub><b>Ben</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=vantreeseba\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/bmon\"><img src=\"https://avatars.githubusercontent.com/u/2115272?v=4?s=50\" width=\"50px;\" alt=\"Brendan Roy\"/><br /><sub><b>Brendan Roy</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=bmon\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/brianembry\"><img src=\"https://avatars.githubusercontent.com/u/35347666?v=4?s=50\" width=\"50px;\" alt=\"brianembry\"/><br /><sub><b>brianembry</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=brianembry\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://keybase.io/bri_\"><img src=\"https://avatars.githubusercontent.com/u/284789?v=4?s=50\" width=\"50px;\" alt=\"br\"/><br /><sub><b>br</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=b-\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/casonadams\"><img src=\"https://avatars.githubusercontent.com/u/17597548?v=4?s=50\" width=\"50px;\" alt=\"Cason Adams\"/><br /><sub><b>Cason Adams</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=casonadams\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/y9c\"><img src=\"https://avatars.githubusercontent.com/u/5415510?v=4?s=50\" width=\"50px;\" alt=\"Chang Y\"/><br /><sub><b>Chang Y</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=y9c\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://yous.be/\"><img src=\"https://avatars.githubusercontent.com/u/853977?v=4?s=50\" width=\"50px;\" alt=\"Chayoung You\"/><br /><sub><b>Chayoung You</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=yous\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/chenlijun99\"><img src=\"https://avatars.githubusercontent.com/u/20483759?v=4?s=50\" width=\"50px;\" alt=\"Chen Lijun\"/><br /><sub><b>Chen Lijun</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=chenlijun99\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/beeender\"><img src=\"https://avatars.githubusercontent.com/u/449296?v=4?s=50\" width=\"50px;\" alt=\"Chen Mulong\"/><br /><sub><b>Chen Mulong</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=beeender\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://weyl.io/\"><img src=\"https://avatars.githubusercontent.com/u/59620?v=4?s=50\" width=\"50px;\" alt=\"Chris Weyl\"/><br /><sub><b>Chris Weyl</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=rsrchboy\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/dezza\"><img src=\"https://avatars.githubusercontent.com/u/402927?v=4?s=50\" width=\"50px;\" alt=\"dezza\"/><br /><sub><b>dezza</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=dezza\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/ceedubs\"><img src=\"https://avatars.githubusercontent.com/u/977929?v=4?s=50\" width=\"50px;\" alt=\"Cody Allen\"/><br /><sub><b>Cody Allen</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=ceedubs\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://www.25.wf/\"><img src=\"https://avatars.githubusercontent.com/u/145502?v=4?s=50\" width=\"50px;\" alt=\"Damien Rajon\"/><br /><sub><b>Damien Rajon</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=pyrho\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/daern91\"><img src=\"https://avatars.githubusercontent.com/u/6084427?v=4?s=50\" width=\"50px;\" alt=\"Daniel Eriksson\"/><br /><sub><b>Daniel Eriksson</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=daern91\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/danjenson\"><img src=\"https://avatars.githubusercontent.com/u/4793438?v=4?s=50\" width=\"50px;\" alt=\"Daniel Jenson\"/><br /><sub><b>Daniel Jenson</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=danjenson\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/davidmh\"><img src=\"https://avatars.githubusercontent.com/u/594302?v=4?s=50\" width=\"50px;\" alt=\"David Mejorado\"/><br /><sub><b>David Mejorado</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=davidmh\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/pderichai\"><img src=\"https://avatars.githubusercontent.com/u/13430946?v=4?s=50\" width=\"50px;\" alt=\"Deric Pang\"/><br /><sub><b>Deric Pang</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=pderichai\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://www.dingtao.org/blog\"><img src=\"https://avatars.githubusercontent.com/u/12852587?v=4?s=50\" width=\"50px;\" alt=\"Ding Tao\"/><br /><sub><b>Ding Tao</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=miyatsu\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/doronbehar\"><img src=\"https://avatars.githubusercontent.com/u/10998835?v=4?s=50\" width=\"50px;\" alt=\"Doron Behar\"/><br /><sub><b>Doron Behar</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=doronbehar\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/kovetskiy\"><img src=\"https://avatars.githubusercontent.com/u/8445924?v=4?s=50\" width=\"50px;\" alt=\"Egor Kovetskiy\"/><br /><sub><b>Egor Kovetskiy</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=kovetskiy\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/elkowar\"><img src=\"https://avatars.githubusercontent.com/u/5300871?v=4?s=50\" width=\"50px;\" alt=\"ElKowar\"/><br /><sub><b>ElKowar</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=elkowar\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/demelev\"><img src=\"https://avatars.githubusercontent.com/u/3952209?v=4?s=50\" width=\"50px;\" alt=\"Emeliov Dmitrii\"/><br /><sub><b>Emeliov Dmitrii</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=demelev\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/sawmurai\"><img src=\"https://avatars.githubusercontent.com/u/6454986?v=4?s=50\" width=\"50px;\" alt=\"Fabian Becker\"/><br /><sub><b>Fabian Becker</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=sawmurai\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/FallenWarrior2k\"><img src=\"https://avatars.githubusercontent.com/u/20320149?v=4?s=50\" width=\"50px;\" alt=\"FallenWarrior2k\"/><br /><sub><b>FallenWarrior2k</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=FallenWarrior2k\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://fnune.com/\"><img src=\"https://avatars.githubusercontent.com/u/16181067?v=4?s=50\" width=\"50px;\" alt=\"Fausto Núñez Alberro\"/><br /><sub><b>Fausto Núñez Alberro</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=fnune\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/FelipeCRamos\"><img src=\"https://avatars.githubusercontent.com/u/7572843?v=4?s=50\" width=\"50px;\" alt=\"Felipe Ramos\"/><br /><sub><b>Felipe Ramos</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=FelipeCRamos\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/frbor\"><img src=\"https://avatars.githubusercontent.com/u/2320183?v=4?s=50\" width=\"50px;\" alt=\"Fredrik Borg\"/><br /><sub><b>Fredrik Borg</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=frbor\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://www.gavinsim.co.uk/\"><img src=\"https://avatars.githubusercontent.com/u/812273?v=4?s=50\" width=\"50px;\" alt=\"Gavin Sim\"/><br /><sub><b>Gavin Sim</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=gavsim\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://fahn.co/\"><img src=\"https://avatars.githubusercontent.com/u/15943089?v=4?s=50\" width=\"50px;\" alt=\"Gibson Fahnestock\"/><br /><sub><b>Gibson Fahnestock</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=gibfahn\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/giovannigiordano\"><img src=\"https://avatars.githubusercontent.com/u/15145952?v=4?s=50\" width=\"50px;\" alt=\"Giovanni Giordano\"/><br /><sub><b>Giovanni Giordano</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=giovannigiordano\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/qubbit\"><img src=\"https://avatars.githubusercontent.com/u/1987473?v=4?s=50\" width=\"50px;\" alt=\"Gopal Adhikari\"/><br /><sub><b>Gopal Adhikari</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=qubbit\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/hanh090\"><img src=\"https://avatars.githubusercontent.com/u/3643657?v=4?s=50\" width=\"50px;\" alt=\"Hanh Le\"/><br /><sub><b>Hanh Le</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=hanh090\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/hedyhli\"><img src=\"https://avatars.githubusercontent.com/u/50042066?v=4?s=50\" width=\"50px;\" alt=\"hedy\"/><br /><sub><b>hedy</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=hedyhli\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://www.hendriklammers.com/\"><img src=\"https://avatars.githubusercontent.com/u/754556?v=4?s=50\" width=\"50px;\" alt=\"Hendrik Lammers\"/><br /><sub><b>Hendrik Lammers</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=hendriklammers\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/henrybarreto\"><img src=\"https://avatars.githubusercontent.com/u/23109089?v=4?s=50\" width=\"50px;\" alt=\"Henry Barreto\"/><br /><sub><b>Henry Barreto</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=henrybarreto\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://hugo.barrera.io/\"><img src=\"https://avatars.githubusercontent.com/u/730811?v=4?s=50\" width=\"50px;\" alt=\"Hugo\"/><br /><sub><b>Hugo</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=WhyNotHugo\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/jackieli-tes\"><img src=\"https://avatars.githubusercontent.com/u/64778297?v=4?s=50\" width=\"50px;\" alt=\"Jackie Li\"/><br /><sub><b>Jackie Li</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=jackieli-tes\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/MrQubo\"><img src=\"https://avatars.githubusercontent.com/u/16545322?v=4?s=50\" width=\"50px;\" alt=\"Jakub Nowak\"/><br /><sub><b>Jakub Nowak</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=MrQubo\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/euoia\"><img src=\"https://avatars.githubusercontent.com/u/1271216?v=4?s=50\" width=\"50px;\" alt=\"James Pickard\"/><br /><sub><b>James Pickard</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=euoia\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/jsfaint\"><img src=\"https://avatars.githubusercontent.com/u/571829?v=4?s=50\" width=\"50px;\" alt=\"Jia Sui\"/><br /><sub><b>Jia Sui</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=jsfaint\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/expipiplus1\"><img src=\"https://avatars.githubusercontent.com/u/857308?v=4?s=50\" width=\"50px;\" alt=\"Ellie Hermaszewska\"/><br /><sub><b>Ellie Hermaszewska</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=expipiplus1\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://cincodenada.com/\"><img src=\"https://avatars.githubusercontent.com/u/479715?v=4?s=50\" width=\"50px;\" alt=\"Joel Bradshaw\"/><br /><sub><b>Joel Bradshaw</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=cincodenada\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/irizwaririz\"><img src=\"https://avatars.githubusercontent.com/u/10111643?v=4?s=50\" width=\"50px;\" alt=\"John Carlo Roberto\"/><br /><sub><b>John Carlo Roberto</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=irizwaririz\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Jomik\"><img src=\"https://avatars.githubusercontent.com/u/699655?v=4?s=50\" width=\"50px;\" alt=\"Jonas Holst Damtoft\"/><br /><sub><b>Jonas Holst Damtoft</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=Jomik\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://inlehmansterms.net/\"><img src=\"https://avatars.githubusercontent.com/u/3144695?v=4?s=50\" width=\"50px;\" alt=\"Jonathan Lehman\"/><br /><sub><b>Jonathan Lehman</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=jdlehman\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://joosep.xyz/\"><img src=\"https://avatars.githubusercontent.com/u/9450943?v=4?s=50\" width=\"50px;\" alt=\"Joosep Alviste\"/><br /><sub><b>Joosep Alviste</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=JoosepAlviste\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/josa42\"><img src=\"https://avatars.githubusercontent.com/u/423234?v=4?s=50\" width=\"50px;\" alt=\"Josa Gesell\"/><br /><sub><b>Josa Gesell</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=josa42\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://jawa.dev/\"><img src=\"https://avatars.githubusercontent.com/u/194275?v=4?s=50\" width=\"50px;\" alt=\"Joshua Rubin\"/><br /><sub><b>Joshua Rubin</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=joshuarubin\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/perrin4869\"><img src=\"https://avatars.githubusercontent.com/u/5774716?v=4?s=50\" width=\"50px;\" alt=\"Julian Grinblat\"/><br /><sub><b>Julian Grinblat</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=perrin4869\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://valentjn.github.io/\"><img src=\"https://avatars.githubusercontent.com/u/19839841?v=4?s=50\" width=\"50px;\" alt=\"Julian Valentin\"/><br /><sub><b>Julian Valentin</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=valentjn\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://kabbamine.github.io/\"><img src=\"https://avatars.githubusercontent.com/u/5658084?v=4?s=50\" width=\"50px;\" alt=\"KabbAmine\"/><br /><sub><b>KabbAmine</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=KabbAmine\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://moncargo.io/\"><img src=\"https://avatars.githubusercontent.com/u/10719495?v=4?s=50\" width=\"50px;\" alt=\"Kay Gosho\"/><br /><sub><b>Kay Gosho</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=acro5piano\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://kennyvh.com/\"><img src=\"https://avatars.githubusercontent.com/u/29909203?v=4?s=50\" width=\"50px;\" alt=\"Kenny Huynh\"/><br /><sub><b>Kenny Huynh</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=hkennyv\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/kevinrambaud\"><img src=\"https://avatars.githubusercontent.com/u/7501477?v=4?s=50\" width=\"50px;\" alt=\"Kevin Rambaud\"/><br /><sub><b>Kevin Rambaud</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=kevinrambaud\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/kiancross\"><img src=\"https://avatars.githubusercontent.com/u/11011464?v=4?s=50\" width=\"50px;\" alt=\"Kian Cross\"/><br /><sub><b>Kian Cross</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=kiancross\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://ko-fi.com/kristijanhusak\"><img src=\"https://avatars.githubusercontent.com/u/1782860?v=4?s=50\" width=\"50px;\" alt=\"Kristijan Husak\"/><br /><sub><b>Kristijan Husak</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=kristijanhusak\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/NullVoxPopuli\"><img src=\"https://avatars.githubusercontent.com/u/199018?v=4?s=50\" width=\"50px;\" alt=\"NullVoxPopuli\"/><br /><sub><b>NullVoxPopuli</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=NullVoxPopuli\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/lassepe\"><img src=\"https://avatars.githubusercontent.com/u/10076790?v=4?s=50\" width=\"50px;\" alt=\"Lasse Peters\"/><br /><sub><b>Lasse Peters</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=lassepe\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Linerre\"><img src=\"https://avatars.githubusercontent.com/u/49512984?v=4?s=50\" width=\"50px;\" alt=\"Noel Errenil\"/><br /><sub><b>Noel Errenil</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=Linerre\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/LinArcX\"><img src=\"https://avatars.githubusercontent.com/u/10884422?v=4?s=50\" width=\"50px;\" alt=\"LinArcX\"/><br /><sub><b>LinArcX</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=LinArcX\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://paypal.me/liuchengxu\"><img src=\"https://avatars.githubusercontent.com/u/8850248?v=4?s=50\" width=\"50px;\" alt=\"Liu-Cheng Xu\"/><br /><sub><b>Liu-Cheng Xu</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=liuchengxu\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://malloc.me/\"><img src=\"https://avatars.githubusercontent.com/u/4153572?v=4?s=50\" width=\"50px;\" alt=\"Marc\"/><br /><sub><b>Marc</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=foxtrot\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/mgaw\"><img src=\"https://avatars.githubusercontent.com/u/2177016?v=4?s=50\" width=\"50px;\" alt=\"Marius Gawrisch\"/><br /><sub><b>Marius Gawrisch</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=mgaw\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://www.markhz.com/\"><img src=\"https://avatars.githubusercontent.com/u/2789742?v=4?s=50\" width=\"50px;\" alt=\"Mark Hintz\"/><br /><sub><b>Mark Hintz</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=mhintz\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/MatElGran\"><img src=\"https://avatars.githubusercontent.com/u/1052778?v=4?s=50\" width=\"50px;\" alt=\"Mathieu Le Tiec\"/><br /><sub><b>Mathieu Le Tiec</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=MatElGran\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://matt-w.net/\"><img src=\"https://avatars.githubusercontent.com/u/8656127?v=4?s=50\" width=\"50px;\" alt=\"Matt White\"/><br /><sub><b>Matt White</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=matt-fff\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/ml-evs\"><img src=\"https://avatars.githubusercontent.com/u/7916000?v=4?s=50\" width=\"50px;\" alt=\"Matthew Evans\"/><br /><sub><b>Matthew Evans</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=ml-evs\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Me1onRind\"><img src=\"https://avatars.githubusercontent.com/u/19531270?v=4?s=50\" width=\"50px;\" alt=\"Me1onRind\"/><br /><sub><b>Me1onRind</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=Me1onRind\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Qyriad\"><img src=\"https://avatars.githubusercontent.com/u/1542224?v=4?s=50\" width=\"50px;\" alt=\"Qyriad\"/><br /><sub><b>Qyriad</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=Qyriad\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://leo.is-a.dev/\"><img src=\"https://avatars.githubusercontent.com/u/35312043?v=4?s=50\" width=\"50px;\" alt=\"Narcis B.\"/><br /><sub><b>Narcis B.</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=leonardssh\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Neur1n\"><img src=\"https://avatars.githubusercontent.com/u/17579247?v=4?s=50\" width=\"50px;\" alt=\"Neur1n\"/><br /><sub><b>Neur1n</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=Neur1n\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/nicoder\"><img src=\"https://avatars.githubusercontent.com/u/365210?v=4?s=50\" width=\"50px;\" alt=\"Nicolas Dermine\"/><br /><sub><b>Nicolas Dermine</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=nicoder\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/NoahTheDuke\"><img src=\"https://avatars.githubusercontent.com/u/603677?v=4?s=50\" width=\"50px;\" alt=\"Noah\"/><br /><sub><b>Noah</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=NoahTheDuke\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/IndexXuan\"><img src=\"https://avatars.githubusercontent.com/u/6322673?v=4?s=50\" width=\"50px;\" alt=\"PENG Rui\"/><br /><sub><b>PENG Rui</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=IndexXuan\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://liaoph.com/\"><img src=\"https://avatars.githubusercontent.com/u/6123425?v=4?s=50\" width=\"50px;\" alt=\"Paco\"/><br /><sub><b>Paco</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=paco0x\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/peng1999\"><img src=\"https://avatars.githubusercontent.com/u/12483662?v=4?s=50\" width=\"50px;\" alt=\"Peng Guanwen\"/><br /><sub><b>Peng Guanwen</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=peng1999\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://www.twitter.com/badeip\"><img src=\"https://avatars.githubusercontent.com/u/1106732?v=4?s=50\" width=\"50px;\" alt=\"Petter Wahlman\"/><br /><sub><b>Petter Wahlman</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=ilAYAli\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/pvonmoradi\"><img src=\"https://avatars.githubusercontent.com/u/1058151?v=4?s=50\" width=\"50px;\" alt=\"Pooya Moradi\"/><br /><sub><b>Pooya Moradi</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=pvonmoradi\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/QuadeMorrison\"><img src=\"https://avatars.githubusercontent.com/u/10917383?v=4?s=50\" width=\"50px;\" alt=\"Quade Morrison\"/><br /><sub><b>Quade Morrison</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=QuadeMorrison\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/vogler\"><img src=\"https://avatars.githubusercontent.com/u/493741?v=4?s=50\" width=\"50px;\" alt=\"Ralf Vogler\"/><br /><sub><b>Ralf Vogler</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=vogler\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/crccw\"><img src=\"https://avatars.githubusercontent.com/u/41463?v=4?s=50\" width=\"50px;\" alt=\"Ran Chen\"/><br /><sub><b>Ran Chen</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=crccw\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://bigardone.dev/\"><img src=\"https://avatars.githubusercontent.com/u/1090272?v=4?s=50\" width=\"50px;\" alt=\"Ricardo García Vega\"/><br /><sub><b>Ricardo García Vega</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=bigardone\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/nomasprime\"><img src=\"https://avatars.githubusercontent.com/u/140855?v=4?s=50\" width=\"50px;\" alt=\"Rick Jones\"/><br /><sub><b>Rick Jones</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=nomasprime\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/rschristian\"><img src=\"https://avatars.githubusercontent.com/u/33403762?v=4?s=50\" width=\"50px;\" alt=\"Ryan Christian\"/><br /><sub><b>Ryan Christian</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=rschristian\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://salo.so/\"><img src=\"https://avatars.githubusercontent.com/u/4694263?v=4?s=50\" width=\"50px;\" alt=\"Salo\"/><br /><sub><b>Salo</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=winterbesos\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Hazelfire\"><img src=\"https://avatars.githubusercontent.com/u/13807753?v=4?s=50\" width=\"50px;\" alt=\"Sam Nolan\"/><br /><sub><b>Sam Nolan</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=Hazelfire\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/rickysaurav\"><img src=\"https://avatars.githubusercontent.com/u/13986039?v=4?s=50\" width=\"50px;\" alt=\"Saurav\"/><br /><sub><b>Saurav</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=rickysaurav\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/smackesey\"><img src=\"https://avatars.githubusercontent.com/u/1531373?v=4?s=50\" width=\"50px;\" alt=\"Sean Mackesey\"/><br /><sub><b>Sean Mackesey</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=smackesey\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/sheeldotme\"><img src=\"https://avatars.githubusercontent.com/u/6991406?v=4?s=50\" width=\"50px;\" alt=\"Sheel Patel\"/><br /><sub><b>Sheel Patel</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=sheeldotme\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/solomonwzs\"><img src=\"https://avatars.githubusercontent.com/u/907942?v=4?s=50\" width=\"50px;\" alt=\"Solomon Ng\"/><br /><sub><b>Solomon Ng</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=solomonwzs\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/kadimisetty\"><img src=\"https://avatars.githubusercontent.com/u/535947?v=4?s=50\" width=\"50px;\" alt=\"Sri Kadimisetty\"/><br /><sub><b>Sri Kadimisetty</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=kadimisetty\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/stephenprater\"><img src=\"https://avatars.githubusercontent.com/u/149870?v=4?s=50\" width=\"50px;\" alt=\"Stephen Prater\"/><br /><sub><b>Stephen Prater</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=stephenprater\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://kibs.dk/\"><img src=\"https://avatars.githubusercontent.com/u/14085?v=4?s=50\" width=\"50px;\" alt=\"Sune Kibsgaard\"/><br /><sub><b>Sune Kibsgaard</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=kibs\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Aquaakuma\"><img src=\"https://avatars.githubusercontent.com/u/31891793?v=4?s=50\" width=\"50px;\" alt=\"Aquaakuma\"/><br /><sub><b>Aquaakuma</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=Aquaakuma\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/coil398\"><img src=\"https://avatars.githubusercontent.com/u/7694377?v=4?s=50\" width=\"50px;\" alt=\"Takumi Kawase\"/><br /><sub><b>Takumi Kawase</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=coil398\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/theblobscp\"><img src=\"https://avatars.githubusercontent.com/u/81673375?v=4?s=50\" width=\"50px;\" alt=\"The Blob SCP\"/><br /><sub><b>The Blob SCP</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=theblobscp\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/przepompownia\"><img src=\"https://avatars.githubusercontent.com/u/11404453?v=4?s=50\" width=\"50px;\" alt=\"Tomasz N\"/><br /><sub><b>Tomasz N</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=przepompownia\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/gasuketsu\"><img src=\"https://avatars.githubusercontent.com/u/15703757?v=4?s=50\" width=\"50px;\" alt=\"Tomoyuki Harada\"/><br /><sub><b>Tomoyuki Harada</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=gasuketsu\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/tonyfettes\"><img src=\"https://avatars.githubusercontent.com/u/29998228?v=4?s=50\" width=\"50px;\" alt=\"Tony Fettes\"/><br /><sub><b>Tony Fettes</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=tonyfettes\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://www.git-pull.com/\"><img src=\"https://avatars.githubusercontent.com/u/26336?v=4?s=50\" width=\"50px;\" alt=\"Tony Narlock\"/><br /><sub><b>Tony Narlock</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=tony\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://blog.wwwjfy.net/\"><img src=\"https://avatars.githubusercontent.com/u/126527?v=4?s=50\" width=\"50px;\" alt=\"Tony Wang\"/><br /><sub><b>Tony Wang</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=wwwjfy\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Varal7\"><img src=\"https://avatars.githubusercontent.com/u/8019486?v=4?s=50\" width=\"50px;\" alt=\"Victor Quach\"/><br /><sub><b>Victor Quach</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=Varal7\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/whisperity\"><img src=\"https://avatars.githubusercontent.com/u/1969470?v=4?s=50\" width=\"50px;\" alt=\"Whisperity\"/><br /><sub><b>Whisperity</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=whisperity\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/willtrnr\"><img src=\"https://avatars.githubusercontent.com/u/1878110?v=4?s=50\" width=\"50px;\" alt=\"William Turner\"/><br /><sub><b>William Turner</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=willtrnr\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://drafts.damnever.com/\"><img src=\"https://avatars.githubusercontent.com/u/6223594?v=4?s=50\" width=\"50px;\" alt=\"Xiaochao Dong\"/><br /><sub><b>Xiaochao Dong</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=damnever\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/hyhugh\"><img src=\"https://avatars.githubusercontent.com/u/16500351?v=4?s=50\" width=\"50px;\" alt=\"Hugh Hou\"/><br /><sub><b>Hugh Hou</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=hyhugh\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/jackielii\"><img src=\"https://avatars.githubusercontent.com/u/360983?v=4?s=50\" width=\"50px;\" alt=\"Jackie Li\"/><br /><sub><b>Jackie Li</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=jackielii\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/TheConfuZzledDude\"><img src=\"https://avatars.githubusercontent.com/u/3160203?v=4?s=50\" width=\"50px;\" alt=\"Zachary Freed\"/><br /><sub><b>Zachary Freed</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=TheConfuZzledDude\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/akiyosi\"><img src=\"https://avatars.githubusercontent.com/u/8478977?v=4?s=50\" width=\"50px;\" alt=\"akiyosi\"/><br /><sub><b>akiyosi</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=akiyosi\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/alexjg\"><img src=\"https://avatars.githubusercontent.com/u/224635?v=4?s=50\" width=\"50px;\" alt=\"alexjg\"/><br /><sub><b>alexjg</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=alexjg\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/aste4\"><img src=\"https://avatars.githubusercontent.com/u/47511385?v=4?s=50\" width=\"50px;\" alt=\"aste4\"/><br /><sub><b>aste4</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=aste4\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/clyfish\"><img src=\"https://avatars.githubusercontent.com/u/541215?v=4?s=50\" width=\"50px;\" alt=\"clyfish\"/><br /><sub><b>clyfish</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=clyfish\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/dev7ba\"><img src=\"https://avatars.githubusercontent.com/u/93706552?v=4?s=50\" width=\"50px;\" alt=\"dev7ba\"/><br /><sub><b>dev7ba</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=dev7ba\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/diartyz\"><img src=\"https://avatars.githubusercontent.com/u/4486152?v=4?s=50\" width=\"50px;\" alt=\"diartyz\"/><br /><sub><b>diartyz</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=diartyz\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/doza-daniel\"><img src=\"https://avatars.githubusercontent.com/u/13752683?v=4?s=50\" width=\"50px;\" alt=\"doza-daniel\"/><br /><sub><b>doza-daniel</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=doza-daniel\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/equal-l2\"><img src=\"https://avatars.githubusercontent.com/u/8597717?v=4?s=50\" width=\"50px;\" alt=\"equal-l2\"/><br /><sub><b>equal-l2</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=equal-l2\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/FongHou\"><img src=\"https://avatars.githubusercontent.com/u/13973254?v=4?s=50\" width=\"50px;\" alt=\"fong\"/><br /><sub><b>fong</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=FongHou\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://blog.hexuhua.vercel.app/\"><img src=\"https://avatars.githubusercontent.com/u/26080416?v=4?s=50\" width=\"50px;\" alt=\"hexh\"/><br /><sub><b>hexh</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=hexh250786313\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/hhiraba\"><img src=\"https://avatars.githubusercontent.com/u/4624806?v=4?s=50\" width=\"50px;\" alt=\"hhiraba\"/><br /><sub><b>hhiraba</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=hhiraba\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/ic-768\"><img src=\"https://avatars.githubusercontent.com/u/83115125?v=4?s=50\" width=\"50px;\" alt=\"ic-768\"/><br /><sub><b>ic-768</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=ic-768\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/javiertury\"><img src=\"https://avatars.githubusercontent.com/u/1520320?v=4?s=50\" width=\"50px;\" alt=\"javiertury\"/><br /><sub><b>javiertury</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=javiertury\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/seiyeah78\"><img src=\"https://avatars.githubusercontent.com/u/6185139?v=4?s=50\" width=\"50px;\" alt=\"karasu\"/><br /><sub><b>karasu</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=seiyeah78\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/kevineato\"><img src=\"https://avatars.githubusercontent.com/u/13666221?v=4?s=50\" width=\"50px;\" alt=\"kevineato\"/><br /><sub><b>kevineato</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=kevineato\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/m4c0\"><img src=\"https://avatars.githubusercontent.com/u/1664510?v=4?s=50\" width=\"50px;\" alt=\"Eduardo Costa\"/><br /><sub><b>Eduardo Costa</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=m4c0\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/micchy326\"><img src=\"https://avatars.githubusercontent.com/u/23257067?v=4?s=50\" width=\"50px;\" alt=\"micchy326\"/><br /><sub><b>micchy326</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=micchy326\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://keybase.io/midchildan\"><img src=\"https://avatars.githubusercontent.com/u/7343721?v=4?s=50\" width=\"50px;\" alt=\"midchildan\"/><br /><sub><b>midchildan</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=midchildan\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/minefuto\"><img src=\"https://avatars.githubusercontent.com/u/46558834?v=4?s=50\" width=\"50px;\" alt=\"minefuto\"/><br /><sub><b>minefuto</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=minefuto\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://twitter.com/robokomy\"><img src=\"https://avatars.githubusercontent.com/u/20733354?v=4?s=50\" width=\"50px;\" alt=\"miyanokomiya\"/><br /><sub><b>miyanokomiya</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=miyanokomiya\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/miyaviee\"><img src=\"https://avatars.githubusercontent.com/u/15247561?v=4?s=50\" width=\"50px;\" alt=\"miyaviee\"/><br /><sub><b>miyaviee</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=miyaviee\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/monkoose\"><img src=\"https://avatars.githubusercontent.com/u/6261276?v=4?s=50\" width=\"50px;\" alt=\"monkoose\"/><br /><sub><b>monkoose</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=monkoose\" title=\"Code\">💻</a> <a href=\"https://github.com/neoclide/coc.nvim/issues?q=author%3Amonkoose\" title=\"Bug reports\">🐛</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/mujx\"><img src=\"https://avatars.githubusercontent.com/u/6430350?v=4?s=50\" width=\"50px;\" alt=\"mujx\"/><br /><sub><b>mujx</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=mujx\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/mvilim\"><img src=\"https://avatars.githubusercontent.com/u/40682862?v=4?s=50\" width=\"50px;\" alt=\"mvilim\"/><br /><sub><b>mvilim</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=mvilim\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://naruaway.com/\"><img src=\"https://avatars.githubusercontent.com/u/2931577?v=4?s=50\" width=\"50px;\" alt=\"naruaway\"/><br /><sub><b>naruaway</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=naruaway\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/piersy\"><img src=\"https://avatars.githubusercontent.com/u/5087847?v=4?s=50\" width=\"50px;\" alt=\"piersy\"/><br /><sub><b>piersy</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=piersy\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/ryantig\"><img src=\"https://avatars.githubusercontent.com/u/324810?v=4?s=50\" width=\"50px;\" alt=\"ryantig\"/><br /><sub><b>ryantig</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=ryantig\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://catcat.cc/\"><img src=\"https://avatars.githubusercontent.com/u/19602440?v=4?s=50\" width=\"50px;\" alt=\"rydesun\"/><br /><sub><b>rydesun</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=rydesun\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/sc00ter\"><img src=\"https://avatars.githubusercontent.com/u/1271025?v=4?s=50\" width=\"50px;\" alt=\"sc00ter\"/><br /><sub><b>sc00ter</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=sc00ter\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/smhc\"><img src=\"https://avatars.githubusercontent.com/u/6404304?v=4?s=50\" width=\"50px;\" alt=\"smhc\"/><br /><sub><b>smhc</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=smhc\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/stkaplan\"><img src=\"https://avatars.githubusercontent.com/u/594990?v=4?s=50\" width=\"50px;\" alt=\"Sam Kaplan\"/><br /><sub><b>Sam Kaplan</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=stkaplan\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/tasuten\"><img src=\"https://avatars.githubusercontent.com/u/1623176?v=4?s=50\" width=\"50px;\" alt=\"tasuten\"/><br /><sub><b>tasuten</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=tasuten\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://todesking.com/\"><img src=\"https://avatars.githubusercontent.com/u/112881?v=4?s=50\" width=\"50px;\" alt=\"todesking\"/><br /><sub><b>todesking</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=todesking\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/typicode\"><img src=\"https://avatars.githubusercontent.com/u/5502029?v=4?s=50\" width=\"50px;\" alt=\"typicode\"/><br /><sub><b>typicode</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=typicode\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://limingfei56.github.io/\"><img src=\"https://avatars.githubusercontent.com/u/8553407?v=4?s=50\" width=\"50px;\" alt=\"李鸣飞\"/><br /><sub><b>李鸣飞</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=LiMingFei56\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://bandism.net/\"><img src=\"https://avatars.githubusercontent.com/u/22633385?v=4?s=50\" width=\"50px;\" alt=\"Ikko Ashimine\"/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=eltociear\" title=\"Documentation\">📖</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/rammiah\"><img src=\"https://avatars.githubusercontent.com/u/26727562?v=4?s=50\" width=\"50px;\" alt=\"Rammiah\"/><br /><sub><b>Rammiah</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/issues?q=author%3Arammiah\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://keybase.io/lambdalisue\"><img src=\"https://avatars.githubusercontent.com/u/546312?v=4?s=50\" width=\"50px;\" alt=\"Alisue\"/><br /><sub><b>Alisue</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/issues?q=author%3Alambdalisue\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://bigshans.github.io\"><img src=\"https://avatars.githubusercontent.com/u/26884666?v=4?s=50\" width=\"50px;\" alt=\"bigshans\"/><br /><sub><b>bigshans</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=bigshans\" title=\"Documentation\">📖</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/rob-3\"><img src=\"https://avatars.githubusercontent.com/u/24816247?v=4?s=50\" width=\"50px;\" alt=\"Robert Boyd III\"/><br /><sub><b>Robert Boyd III</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/issues?q=author%3Arob-3\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://creasty.com\"><img src=\"https://avatars.githubusercontent.com/u/1695538?v=4?s=50\" width=\"50px;\" alt=\"Yuki Iwanaga\"/><br /><sub><b>Yuki Iwanaga</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=creasty\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://www.dosk.win/\"><img src=\"https://avatars.githubusercontent.com/u/2389889?v=4?s=50\" width=\"50px;\" alt=\"SpringHack\"/><br /><sub><b>SpringHack</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/issues?q=author%3Aspringhack\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://git.lmburns.com\"><img src=\"https://avatars.githubusercontent.com/u/44355502?v=4?s=50\" width=\"50px;\" alt=\"Lucas Burns\"/><br /><sub><b>Lucas Burns</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=lmburns\" title=\"Documentation\">📖</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://qiqi.boy.im\"><img src=\"https://avatars.githubusercontent.com/u/3774036?v=4?s=50\" width=\"50px;\" alt=\"qiqiboy\"/><br /><sub><b>qiqiboy</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=qiqiboy\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/timsu92\"><img src=\"https://avatars.githubusercontent.com/u/33785401?v=4?s=50\" width=\"50px;\" alt=\"timsu92\"/><br /><sub><b>timsu92</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=timsu92\" title=\"Documentation\">📖</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://sartak.org\"><img src=\"https://avatars.githubusercontent.com/u/45430?v=4?s=50\" width=\"50px;\" alt=\"Shawn M Moore\"/><br /><sub><b>Shawn M Moore</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=sartak\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/aauren\"><img src=\"https://avatars.githubusercontent.com/u/1392295?v=4?s=50\" width=\"50px;\" alt=\"Aaron U'Ren\"/><br /><sub><b>Aaron U'Ren</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/issues?q=author%3Aaauren\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/SirCharlieMars\"><img src=\"https://avatars.githubusercontent.com/u/31679231?v=4?s=50\" width=\"50px;\" alt=\"SeniorMars\"/><br /><sub><b>SeniorMars</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=SirCharlieMars\" title=\"Documentation\">📖</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/CollieIsCute\"><img src=\"https://avatars.githubusercontent.com/u/43088530?v=4?s=50\" width=\"50px;\" alt=\"牧羊犬真Q\"/><br /><sub><b>牧羊犬真Q</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=CollieIsCute\" title=\"Documentation\">📖</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://geraldspreer.com\"><img src=\"https://avatars.githubusercontent.com/u/1745692?v=4?s=50\" width=\"50px;\" alt=\"geraldspreer\"/><br /><sub><b>geraldspreer</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=geraldspreer\" title=\"Documentation\">📖</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://3ximus.github.io/cv\"><img src=\"https://avatars.githubusercontent.com/u/9083012?v=4?s=50\" width=\"50px;\" alt=\"Fabio\"/><br /><sub><b>Fabio</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=3ximus\" title=\"Documentation\">📖</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/skysky97\"><img src=\"https://avatars.githubusercontent.com/u/18086458?v=4?s=50\" width=\"50px;\" alt=\"Li Yunting\"/><br /><sub><b>Li Yunting</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/issues?q=author%3Askysky97\" title=\"Bug reports\">🐛</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/LebJe\"><img src=\"https://avatars.githubusercontent.com/u/51171427?v=4?s=50\" width=\"50px;\" alt=\"Jeff L.\"/><br /><sub><b>Jeff L.</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=LebJe\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://hachyderm.io/@mcmire\"><img src=\"https://avatars.githubusercontent.com/u/7371?v=4?s=50\" width=\"50px;\" alt=\"Elliot Winkler\"/><br /><sub><b>Elliot Winkler</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=mcmire\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://www.lebstertm.com\"><img src=\"https://avatars.githubusercontent.com/u/15955811?v=4?s=50\" width=\"50px;\" alt=\"Svetlozar Iliev\"/><br /><sub><b>Svetlozar Iliev</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=asmodeus812\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://43081j.com/\"><img src=\"https://avatars.githubusercontent.com/u/5677153?v=4?s=50\" width=\"50px;\" alt=\"James Garbutt\"/><br /><sub><b>James Garbutt</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=43081j\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/Kaiser-Yang\"><img src=\"https://avatars.githubusercontent.com/u/58209855?v=4?s=50\" width=\"50px;\" alt=\"Qingzhou Yue\"/><br /><sub><b>Qingzhou Yue</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=Kaiser-Yang\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://www.linkedin.com/in/de-vries-maarten/\"><img src=\"https://avatars.githubusercontent.com/u/786213?v=4?s=50\" width=\"50px;\" alt=\"Maarten de Vries\"/><br /><sub><b>Maarten de Vries</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=de-vri-es\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/A4-Tacks\"><img src=\"https://avatars.githubusercontent.com/u/102709083?v=4?s=50\" width=\"50px;\" alt=\"A4-Tacks\"/><br /><sub><b>A4-Tacks</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=A4-Tacks\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/zhixiao-zhang\"><img src=\"https://avatars.githubusercontent.com/u/89405463?v=4?s=50\" width=\"50px;\" alt=\"forceofsystem\"/><br /><sub><b>forceofsystem</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=zhixiao-zhang\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/statiolake\"><img src=\"https://avatars.githubusercontent.com/u/20490597?v=4?s=50\" width=\"50px;\" alt=\"lake\"/><br /><sub><b>lake</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=statiolake\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://www.davidosomething.com/\"><img src=\"https://avatars.githubusercontent.com/u/609213?v=4?s=50\" width=\"50px;\" alt=\"David O'Trakoun\"/><br /><sub><b>David O'Trakoun</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=davidosomething\" title=\"Documentation\">📖</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/aispeaking\"><img src=\"https://avatars.githubusercontent.com/u/139532597?v=4?s=50\" width=\"50px;\" alt=\"aispeaking\"/><br /><sub><b>aispeaking</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=aispeaking\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/cclauss\"><img src=\"https://avatars.githubusercontent.com/u/3709715?v=4?s=50\" width=\"50px;\" alt=\"Christian Clauss\"/><br /><sub><b>Christian Clauss</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=cclauss\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://mehalter.com\"><img src=\"https://avatars.githubusercontent.com/u/1591837?v=4?s=50\" width=\"50px;\" alt=\"Micah Halter\"/><br /><sub><b>Micah Halter</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=mehalter\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/cridemichel\"><img src=\"https://avatars.githubusercontent.com/u/15322138?v=4?s=50\" width=\"50px;\" alt=\"Cristiano De Michele\"/><br /><sub><b>Cristiano De Michele</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=cridemichel\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://yongjie.codes/\"><img src=\"https://avatars.githubusercontent.com/u/14101781?v=4?s=50\" width=\"50px;\" alt=\"Yong Jie\"/><br /><sub><b>Yong Jie</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=YongJieYongJie\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"http://eight45.net\"><img src=\"https://avatars.githubusercontent.com/u/489362?v=4?s=50\" width=\"50px;\" alt=\"Kira Oakley\"/><br /><sub><b>Kira Oakley</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=hackergrrl\" title=\"Documentation\">📖</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://merwan.github.io\"><img src=\"https://avatars.githubusercontent.com/u/222879?v=4?s=50\" width=\"50px;\" alt=\"Merouane Atig\"/><br /><sub><b>Merouane Atig</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=merwan\" title=\"Documentation\">📖</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://gera2ld.space/\"><img src=\"https://avatars.githubusercontent.com/u/3139113?v=4?s=50\" width=\"50px;\" alt=\"Gerald\"/><br /><sub><b>Gerald</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=gera2ld\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://nicklas.sedlock.xyz/\"><img src=\"https://avatars.githubusercontent.com/u/47660390?v=4?s=50\" width=\"50px;\" alt=\"Nicklas Sedlock\"/><br /><sub><b>Nicklas Sedlock</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=V-Mann-Nick\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/tcx4c70\"><img src=\"https://avatars.githubusercontent.com/u/16728230?v=4?s=50\" width=\"50px;\" alt=\"Adam Tao\"/><br /><sub><b>Adam Tao</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=tcx4c70\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/itsf4llofstars\"><img src=\"https://avatars.githubusercontent.com/u/90528743?v=4?s=50\" width=\"50px;\" alt=\"itsf4llofstars\"/><br /><sub><b>itsf4llofstars</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=itsf4llofstars\" title=\"Documentation\">📖</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/brainwo\"><img src=\"https://avatars.githubusercontent.com/u/45139213?v=4?s=50\" width=\"50px;\" alt=\"Brian Wo\"/><br /><sub><b>Brian Wo</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=brainwo\" title=\"Documentation\">📖</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://wsdjeg.net/\"><img src=\"https://avatars.githubusercontent.com/u/13142418?v=4?s=50\" width=\"50px;\" alt=\"Eric Wong\"/><br /><sub><b>Eric Wong</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=wsdjeg\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/oxalica\"><img src=\"https://avatars.githubusercontent.com/u/14816024?v=4?s=50\" width=\"50px;\" alt=\"oxalica\"/><br /><sub><b>oxalica</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=oxalica\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/laktak\"><img src=\"https://avatars.githubusercontent.com/u/959858?v=4?s=50\" width=\"50px;\" alt=\"Christian Zangl\"/><br /><sub><b>Christian Zangl</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=laktak\" title=\"Code\">💻</a></td>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/zoumi\"><img src=\"https://avatars.githubusercontent.com/u/5162901?v=4?s=50\" width=\"50px;\" alt=\"zoumi\"/><br /><sub><b>zoumi</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=zoumi\" title=\"Code\">💻</a></td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" width=\"14.28%\"><a href=\"https://github.com/atitcreate\"><img src=\"https://avatars.githubusercontent.com/u/40348360?v=4?s=50\" width=\"50px;\" alt=\"atitcreate\"/><br /><sub><b>atitcreate</b></sub></a><br /><a href=\"https://github.com/neoclide/coc.nvim/commits?author=atitcreate\" title=\"Code\">💻</a></td>\n    </tr>\n  </tbody>\n</table>\n\n<!-- markdownlint-restore -->\n<!-- prettier-ignore-end -->\n\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<!-- markdownlint-restore -->\n<!-- prettier-ignore-end -->\n\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://allcontributors.org) specification.\nContributions of any kind are welcome!\n\n## License\n\n[Anti 996](./LICENSE.md)\n"
  },
  {
    "path": "autoload/coc/api.vim",
    "content": "if has('nvim')\n  finish\nendif\nvim9script\nscriptencoding utf-8\n\nvar namespace_id: number = 1\nfinal namespace_cache: dict<any> = {}\nvar max_src_id: number = 1000\n# bufnr => max textprop id\nfinal buffer_id: dict<any> = {}\n# srcId => list of types\nfinal id_types: dict<any> = {}\nvar tab_id: number = 1\nfinal listener_map: dict<any> = {}\nconst prop_offset: number = get(g:, 'coc_text_prop_offset', 1000)\nconst keymap_arguments: list<string> = ['nowait', 'silent', 'script', 'expr', 'unique', 'special']\nconst known_types = ['Number', 'String', 'Funcref', 'List', 'Dictionary', 'Float', 'Boolean', 'None', 'Job', 'Channel', 'Blob']\nconst scopes = ['global', 'local']\n# Boolean options of vim 9.1.1134\nconst boolean_options: list<string> = ['allowrevins', 'arabic', 'arabicshape', 'autochdir', 'autoindent', 'autoread', 'autoshelldir', 'autowrite', 'autowriteall', 'backup', 'balloonevalterm', 'binary', 'bomb', 'breakindent', 'buflisted', 'cdhome', 'cindent', 'compatible', 'confirm', 'copyindent', 'cursorbind', 'cursorcolumn', 'cursorline', 'delcombine', 'diff', 'digraph', 'edcompatible', 'emoji', 'endoffile', 'endofline', 'equalalways', 'errorbells', 'esckeys', 'expandtab', 'exrc', 'fileignorecase', 'fixendofline', 'foldenable', 'fsync', 'gdefault', 'hidden', 'hkmap', 'hkmapp', 'hlsearch', 'icon', 'ignorecase', 'imcmdline', 'imdisable', 'incsearch', 'infercase', 'insertmode', 'joinspaces', 'langnoremap', 'langremap', 'lazyredraw', 'linebreak', 'lisp', 'list', 'loadplugins', 'magic', 'modeline', 'modelineexpr', 'modifiable', 'modified', 'more', 'number', 'paste', 'preserveindent', 'previewwindow', 'prompt', 'readonly', 'relativenumber', 'remap', 'revins', 'rightleft', 'ruler', 'scrollbind', 'secure', 'shelltemp', 'shiftround', 'shortname', 'showcmd', 'showfulltag', 'showmatch', 'showmode', 'smartcase', 'smartindent', 'smarttab', 'smoothscroll', 'spell', 'splitbelow', 'splitright', 'startofline', 'swapfile', 'tagbsearch', 'tagrelative', 'tagstack', 'termbidi', 'termguicolors', 'terse', 'textauto', 'textmode', 'tildeop', 'timeout', 'title', 'ttimeout', 'ttybuiltin', 'ttyfast', 'undofile', 'visualbell', 'warn', 'weirdinvert', 'wildignorecase', 'wildmenu', 'winfixbuf', 'winfixheight', 'winfixwidth', 'wrap', 'wrapscan', 'write', 'writeany', 'writebackup', 'xtermcodes']\nconst window_options = keys(getwinvar(0, '&'))\nconst buffer_options = keys(getbufvar(bufnr('%'), '&'))\nvar group_id: number = 1\n# id => name\nfinal groups_map: dict<any> = {}\nvar autocmd_id: number = 1\nfinal autocmds_map: dict<any> = {}\n\nconst API_FUNCTIONS = [\n  'eval',\n  'command',\n  'feedkeys',\n  'command_output',\n  'exec',\n  'input',\n  'create_buf',\n  'strwidth',\n  'out_write',\n  'err_write',\n  'err_writeln',\n  'set_option',\n  'set_var',\n  'set_keymap',\n  'set_option_value',\n  'set_current_line',\n  'set_current_dir',\n  'set_current_buf',\n  'set_current_win',\n  'set_current_tabpage',\n  'get_option',\n  'get_api_info',\n  'get_current_line',\n  'get_var',\n  'get_vvar',\n  'get_current_buf',\n  'get_current_win',\n  'get_current_tabpage',\n  'get_mode',\n  'get_namespaces',\n  'get_option_value',\n  'del_var',\n  'del_keymap',\n  'del_current_line',\n  'del_autocmd',\n  'list_wins',\n  'list_bufs',\n  'list_runtime_paths',\n  'list_tabpages',\n  'call_atomic',\n  'call_function',\n  'call_dict_function',\n  'create_namespace',\n  'create_augroup',\n  'create_autocmd',\n  'buf_set_option',\n  'buf_get_option',\n  'buf_get_changedtick',\n  'buf_is_valid',\n  'buf_is_loaded',\n  'buf_get_mark',\n  'buf_add_highlight',\n  'buf_clear_namespace',\n  'buf_line_count',\n  'buf_attach',\n  'buf_detach',\n  'buf_get_lines',\n  'buf_set_lines',\n  'buf_set_name',\n  'buf_get_name',\n  'buf_get_var',\n  'buf_set_var',\n  'buf_del_var',\n  'buf_set_keymap',\n  'buf_del_keymap',\n  'win_get_buf',\n  'win_set_buf',\n  'win_get_position',\n  'win_set_height',\n  'win_get_height',\n  'win_set_width',\n  'win_get_width',\n  'win_set_cursor',\n  'win_get_cursor',\n  'win_set_option',\n  'win_get_option',\n  'win_get_var',\n  'win_set_var',\n  'win_del_var',\n  'win_is_valid',\n  'win_get_number',\n  'win_get_tabpage',\n  'win_close',\n  'tabpage_get_number',\n  'tabpage_list_wins',\n  'tabpage_get_var',\n  'tabpage_set_var',\n  'tabpage_del_var',\n  'tabpage_is_valid',\n  'tabpage_get_win',\n]\n\n# helper {{\n# Create a window with bufnr for execute win_execute\ndef CreatePopup(bufnr: number): number\n  noa const id = popup_create(bufnr, {\n      \\ 'line': 1,\n      \\ 'col': &columns,\n      \\ 'maxwidth': 1,\n      \\ 'maxheight': 1,\n      \\ })\n  popup_hide(id)\n  return id\nenddef\n\ndef CheckBufnr(bufnr: number): void\n  if bufnr != 0 && !bufexists(bufnr)\n    throw $'Invalid buffer id: {bufnr}'\n  endif\nenddef\n\ndef CheckWinid(winid: number): void\n  if winid != 0 && empty(getwininfo(winid))\n    throw $'Invalid window id: {winid}'\n  endif\nenddef\n\ndef GetValidBufnr(id: number): number\n  if id == 0\n    return bufnr('%')\n  endif\n  if !bufexists(id)\n    throw $'Invalid buffer id: {id}'\n  endif\n  return id\nenddef\n\ndef GetValidWinid(id: number): number\n  if id == 0\n    return win_getid()\n  endif\n  if empty(getwininfo(id))\n    throw $'Invalid window id: {id}'\n  endif\n  return id\nenddef\n\ndef CheckKey(dict: dict<any>, key: string): void\n  if !has_key(dict, key)\n    throw $'Key not found: {key}'\n  endif\nenddef\n\n# TextChanged and callback not fired when using channel on vim.\nexport def OnTextChange(bufnr: number): void\n  const event = mode() ==# 'i' ? 'TextChangedI' : 'TextChanged'\n  if bufnr('%') == bufnr\n    coc#compat#execute($'doautocmd <nomodeline> {event}')\n  else\n    BufExecute(bufnr, [$'legacy doautocmd <nomodeline> {event}'])\n  endif\nenddef\n\ndef Pick(target: dict<any>, source: dict<any>, keys: list<string>): void\n  for key in keys\n    if has_key(source, key)\n      target[key] = source[key]\n    endif\n  endfor\nenddef\n\n# execute command for bufnr\nexport def BufExecute(bufnr: number, cmds: list<string>, silent = 'silent'): void\n  var winid = get(win_findbuf(bufnr), 0, -1)\n  var need_close: bool = false\n  if winid == -1\n    winid = CreatePopup(bufnr)\n    need_close = true\n  endif\n  win_execute(winid, cmds, silent)\n  if need_close\n    noa popup_close(winid)\n  endif\nenddef\n\ndef BufLineCount(bufnr: number): number\n  const info = get(getbufinfo(bufnr), 0, null)\n  if empty(info)\n    throw $'Invalid buffer id: {bufnr}'\n  endif\n  return info.loaded == 0 ? 0 : info.linecount\nenddef\n\ndef IsPopup(winid: number): bool\n  return index(popup_list(), winid) != -1\nenddef\n\ndef TabIdNr(tid: number): number\n  if tid == 0\n    return tabpagenr()\n  endif\n  var result: any = null\n  for nr in range(1, tabpagenr('$'))\n    if gettabvar(nr, '__coc_tid', null) == tid\n      result = nr\n    endif\n  endfor\n  if result == null\n    throw $'Invalid tabpage id: {tid}'\n  endif\n  return result\nenddef\n\nexport def TabNrId(nr: number): number\n  var tid = gettabvar(nr, '__coc_tid', -1)\n  if tid == -1\n    tid = tab_id\n    settabvar(nr, '__coc_tid', tid)\n    tab_id += 1\n  endif\n  return tid\nenddef\n\ndef WinTabnr(winid: number): number\n  const info = getwininfo(winid)\n  if empty(info)\n    throw $'Invalid window id: {winid}'\n  endif\n  return info[0]['tabnr']\nenddef\n\ndef DeferExecute(cmd: string): void\n  def RunExecute(): void\n    if cmd =~# '^redraw'\n      if index(['c', 'r'], mode()) == -1\n        execute cmd\n      endif\n    elseif cmd =~# '^echo'\n      execute cmd\n    else\n      silent! execute $'legacy {cmd}'\n    endif\n  enddef\n  timer_start(0, (..._) => RunExecute())\nenddef\n\ndef InspectType(val: any): string\n  return get(known_types, type(val), 'Unknown')\nenddef\n\ndef EscapeSpace(text: string): string\n  return substitute(text, ' ', '<space>', 'g')\nenddef\n\n# See :h option-backslash\ndef EscapeOptionValue(value: any): string\n  if type(value) == v:t_string\n    return substitute(value, '\\( \\|\\\\\\)', '\\\\\\1', 'g')\n  endif\n  return string(value)\nenddef\n\n# Check the type like nvim, currently bool option only\ndef CheckOptionValue(name: string, value: any): void\n  if index(boolean_options, name) != -1 && type(value) != v:t_bool\n    throw $\"Invalid value for option '{name}': expected boolean, got {tolower(InspectType(value))} {value}\"\n  endif\nenddef\n\ndef CheckScopeOption(opts: dict<any>): void\n  if has_key(opts, 'scope') && has_key(opts, 'buf')\n    throw \"Can't use both scope and buf\"\n  endif\n  if has_key(opts, 'buf') && has_key(opts, 'win')\n    throw \"Can't use both buf and win\"\n  endif\n  if has_key(opts, 'scope') && index(scopes, opts.scope) == -1\n    throw \"Invalid 'scope': expected 'local' or 'global'\"\n  endif\n  if has_key(opts, 'buf') && type(opts.buf) != v:t_number\n    throw $\"Invalid 'buf': expected Number, got {InspectType(opts.buf)}\"\n  endif\n  if has_key(opts, 'win') && type(opts.win) != v:t_number\n    throw $\"Invalid 'win': expected Number, got {InspectType(opts.win)}\"\n  endif\nenddef\n\ndef CreateModePrefix(mode: string, opts: dict<any>): string\n  if mode ==# '!'\n    return 'map!'\n  endif\n  return get(opts, 'noremap', 0) ?  $'{mode}noremap' : $'{mode}map'\nenddef\n\ndef CreateArguments(opts: dict<any>): string\n  var arguments = ''\n  for key in keys(opts)\n    if opts[key] == true && index(keymap_arguments, key) != -1\n      arguments ..= $'<{key}>'\n    endif\n  endfor\n  return arguments\nenddef\n\nexport def GeneratePropId(bufnr: number): number\n  const max: number = get(buffer_id, bufnr, prop_offset)\n  const id: number = max + 1\n  buffer_id[bufnr] = id\n  return id\nenddef\n\nexport def GetNamespaceTypes(ns: number): list<string>\n  if ns == -1\n    return values(id_types)->flattennew(1)\n  endif\n  return get(id_types, ns, [])\nenddef\n\nexport def CreateType(ns: number, hl: string, opts: dict<any>): string\n  const type: string = $'{hl}_{ns}'\n  final types: list<string> = get(id_types, ns, [])\n  if index(types, type) == -1\n    add(types, type)\n    id_types[ns] = types\n    if empty(prop_type_get(type))\n      final type_option: dict<any> = {'highlight': hl}\n      if !hlexists(hl)\n        execute $'highlight default {hl} ctermfg=NONE'\n      endif\n      const hl_mode: string = get(opts, 'hl_mode', 'combine')\n      if hl_mode !=# 'combine'\n        type_option['override'] = 1\n        type_option['combine'] = 0\n      endif\n      # vim not throw for unknown properties\n      prop_type_add(type, extend(type_option, opts))\n    endif\n  endif\n  return type\nenddef\n\ndef OnBufferChange(bufnr: number, start: number, end: number, added: number, bufchanges: list<any>): void\n  const new_len = end - start + added\n  const lines: list<string> = new_len > 0 ? getbufline(bufnr, start, start + new_len - 1) : []\n  coc#rpc#notify('vim_buf_change_event', [bufnr, getbufvar(bufnr, 'changedtick'), start - 1, end - 1, lines])\nenddef\n\nexport def DetachListener(bufnr: number): bool\n  const id: number = get(listener_map, bufnr, 0)\n  if id != 0\n    remove(listener_map, bufnr)\n    return listener_remove(id) != 0\n  endif\n  return false\nenddef\n\n# echo single line message with highlight\nexport def EchoHl(message: string, hl: string): void\n  const escaped = substitute(message, \"'\", \"''\", 'g')\n  DeferExecute($\"echohl {hl} | echo '{escaped}' | echohl None\")\nenddef\n\ndef ChangeBufferLines(bufnr: number, start_row: number, start_col: number, end_row: number, end_col: number, replacement: list<string>): void\n  const lines = getbufline(bufnr, start_row + 1, end_row + 1)\n  const total = len(lines)\n  final new_lines = []\n  const before = strpart(lines[0], 0, start_col)\n  const after = strpart(lines[total - 1], end_col)\n  const last = len(replacement) - 1\n  for idx in range(0, last)\n    var line = replacement[idx]\n    if idx == 0\n      line = before .. line\n    endif\n    if idx == last\n      line = line .. after\n    endif\n    new_lines->add(line)\n  endfor\n  const del = end_row - (start_row - 1)\n  if del == last + 1\n    setbufline(bufnr, start_row + 1, new_lines)\n  else\n    if len(new_lines) > 0\n      appendbufline(bufnr, start_row, new_lines)\n    endif\n    if del > 0\n      const lnum = start_row + len(new_lines) + 1\n      deletebufline(bufnr, lnum, lnum + del - 1)\n    endif\n  endif\nenddef\n\n# make sure inserted space last.\ndef SortProp(a: dict<any>, b: dict<any>): number\n  if a.col != b.col\n    return a.col > b.col ? 1 : -1\n  endif\n  if has_key(a, 'text') && has_key(b, 'text')\n    return a.text ==# ' ' ? 1 : -1\n  endif\n  return 0\nenddef\n\ndef ReplaceBufLines(bufnr: number, start_row: number, end_row: number, replacement: list<string>): void\n  if start_row != end_row\n    deletebufline(bufnr, start_row + 1, end_row)\n  endif\n  if !empty(replacement)\n    const new_lines = replacement[0 : -2]\n    if !empty(new_lines)\n      appendbufline(bufnr, start_row, new_lines)\n    endif\n  endif\nenddef\n\n# Change buffer texts with text properties keeped.\nexport def SetBufferText(bufnr: number, start_row: number, start_col: number, end_row: number, end_col: number, replacement: list<string>): void\n  # Improve speed for replace lines\n  if start_col == 0 && end_col == 0 && (empty(replacement) || replacement[len(replacement) - 1] == '')\n    ReplaceBufLines(bufnr, start_row, end_row, replacement)\n  else\n    const lines = getbufline(bufnr, start_row + 1, end_row + 1)\n    final new_props = []\n    const props = prop_list(start_row + 1, {\n      'bufnr': bufnr,\n      'end_lnum': end_row + 1\n    })\n    const total = len(props)\n    const replace = empty(replacement) ? [''] : replacement\n    if total > 0\n      var idx = 0\n      while idx != total\n        const prop = props[idx]\n        if !prop.start || !prop.end || has_key(prop, 'text_align')\n          idx += 1\n          continue\n        endif\n        if prop.lnum > start_row + 1 || prop.col + get(prop, 'length', 0) > start_col + 1\n          break\n        endif\n        new_props->add(prop)\n        idx += 1\n      endwhile\n      const rl = len(replace)\n      if idx != total\n        # new - old\n        const line_delta = start_row + rl - 1 - end_row\n        var col_delta = 0\n        if rl > 1\n          col_delta = strlen(replace[rl - 1]) - end_col\n        else\n          col_delta = start_col + strlen(replace[0]) - end_col\n        endif\n        while idx != total\n          var prop = props[idx]\n          if prop.lnum < end_row + 1 || prop.col < end_col + 1 || !prop.start || !prop.end || has_key(prop, 'text_align')\n            idx += 1\n            continue\n          endif\n          if prop.lnum > end_row + 1\n            break\n          endif\n          prop = copy(prop)\n          prop.lnum += line_delta\n          prop.col += col_delta\n          new_props->add(prop)\n          idx += 1\n        endwhile\n      endif\n    endif\n    ChangeBufferLines(bufnr, start_row, start_col, end_row, end_col, replace)\n    for prop in sort(new_props, SortProp)\n      const has_text = has_key(prop, 'text')\n      const id = get(prop, 'id', -1)\n      if id < 0 && !has_text\n        # prop.id < 0 should be vtext, but text not exists on old vim, can't handle\n        continue\n      endif\n      final opts = {'bufnr': bufnr, 'type': prop.type}\n      if id > 0\n        opts.id = prop.id\n        opts.length = get(prop, 'length', 0)\n      else\n        Pick(opts, prop, ['text', 'text_wrap'])\n      endif\n      prop_add(prop.lnum, prop.col, opts)\n    endfor\n  endif\nenddef\n\n# Change lines only\nexport def SetBufferLines(bufnr: number, start_line: number, end_line: number, replacement: list<string>): void\n  const delCount = end_line - (start_line - 1)\n  const total = len(replacement)\n  if delCount == total\n    const currentLines = getbufline(bufnr, start_line, start_line + delCount)\n    for idx in range(0, delCount - 1)\n      if currentLines[idx] !=# replacement[idx]\n        setbufline(bufnr, start_line + idx, replacement[idx])\n      endif\n    endfor\n  else\n    if total > 0\n      appendbufline(bufnr, start_line - 1, replacement)\n    endif\n    if delCount > 0\n      const start = start_line + total\n      silent deletebufline(bufnr, start, start + delCount - 1)\n    endif\n  endif\nenddef\n# }}\"\n\n# nvim client methods {{\nexport def Set_current_dir(dir: string): any\n  execute $'legacy cd {fnameescape(dir)}'\n  return null\nenddef\n\nexport def Set_var(name: string, value: any): any\n  g:[name] = value\n  return null\nenddef\n\nexport def Del_var(name: string): any\n  CheckKey(g:, name)\n  remove(g:, name)\n  return null\nenddef\n\nexport def Set_option(name: string, value: any, local: bool = false): any\n  CheckOptionValue(name, value)\n  if index(boolean_options, name) != -1\n    if value\n      execute $'legacy set{local ? 'l' : ''} {name}'\n    else\n      execute $'legacy set{local ? 'l' : ''} no{name}'\n    endif\n  else\n    execute $\"legacy set{local ? 'l' : ''} {name}={EscapeOptionValue(value)}\"\n  endif\n  return null\nenddef\n\nexport def Get_option(name: string): any\n  return eval($'&{name}')\nenddef\n\nexport def Set_current_buf(bufnr: number): any\n  CheckBufnr(bufnr)\n  # autocmd could fail when not use legacy.\n  execute $'legacy buffer {bufnr}'\n  return null\nenddef\n\nexport def Set_current_win(winid: number): any\n  CheckWinid(winid)\n  win_gotoid(winid)\n  return null\nenddef\n\nexport def Set_current_tabpage(tid: number): any\n  const nr = TabIdNr(tid)\n  execute $'legacy normal! {nr}gt'\n  return null\nenddef\n\nexport def List_wins(): list<number>\n  return getwininfo()->map((_, info) => info.winid)\nenddef\n\nexport def Call_atomic(calls: list<any>): list<any>\n  final results: list<any> = []\n  for i in range(len(calls))\n    const key: string = calls[i][0]\n    const name: string = $\"{toupper(key[5])}{strpart(key, 6)}\"\n    try\n      const result = call(name, get(calls[i], 1, []))\n      add(results, result)\n    catch /.*/\n      return [results, [i, $'VimException({InspectType(v:exception)})', $'{v:exception} on function coc#api#{name}']]\n    endtry\n  endfor\n  return [results, null]\nenddef\n\nexport def Set_client_info(..._): any\n  # not supported\n  return null\nenddef\n\nexport def Subscribe(..._): any\n  # not supported\n  return null\nenddef\n\nexport def Unsubscribe(..._): any\n  # not supported\n  return null\nenddef\n\n# Not return on notification for possible void function call.\nexport def Call_function(method: string, args: list<any>, notify: bool = false): any\n  if method ==# 'execute'\n    return call('coc#compat#execute', args)\n  elseif method ==# 'eval'\n    return Eval(args[0])\n  elseif method ==# 'win_execute'\n    return call('coc#compat#win_execute', args)\n  elseif !notify\n    return call(method, args)\n  endif\n  call call(method, args)\n  return null\nenddef\n\nexport def Call_dict_function(dict: any, method: string, args: list<any>): any\n  if type(dict) == v:t_string\n    return call(method, args, Eval(dict))\n  endif\n  return call(method, args, dict)\nenddef\n\n# Use the legacy eval, could be called by Call\nexport function Eval(expr) abort\n  legacy return coc#compat#eval(a:expr)\nendfunction\n\nexport def Command(command: string): any\n  # command that could cause cursor vanish\n  if command =~# '^\\(echo\\|redraw\\|sign\\)'\n    DeferExecute(command)\n  else\n    # Use legacy command not work for command like autocmd\n    coc#compat#execute(command)\n    # The error is set by python script, since vim not give error on python command failure\n    if strpart(command, 0, 2) ==# 'py'\n      const errmsg: string = get(g:, 'errmsg', '')\n      if !empty(errmsg)\n        remove(g:, 'errmsg')\n        throw $'Python error {errmsg}'\n      endif\n    endif\n  endif\n  return null\nenddef\n\nexport def Get_api_info(): any\n  const functions: list<string> = map(copy(API_FUNCTIONS), (_, val) => $'nvim_{val}')\n  const channel: any = coc#rpc#get_channel()\n  if empty(channel)\n    throw 'Unable to get channel'\n  endif\n  return [ch_info(channel)['id'], {'functions': functions}]\nenddef\n\nexport def List_bufs(): list<number>\n  return getbufinfo()->map((_, info) => info.bufnr)\nenddef\n\nexport def Feedkeys(keys: string, mode: string, escape_csi: any = false): any\n  feedkeys(keys, mode)\n  return null\nenddef\n\nexport def List_runtime_paths(): list<string>\n  return map(globpath(&runtimepath, '', 0, 1), (_, val) => coc#util#win32unix_to_node(val))\nenddef\n\nexport def Command_output(cmd: string): string\n  const output = coc#compat#execute(cmd, 'silent')\n  # The same as nvim.\n  if cmd =~# '^echo'\n    return trim(output, \"\\r\\n\")\n  endif\n  return output\nenddef\n\nexport def Exec(code: string, output: bool): string\n  if output\n    return Command_output(code)\n  endif\n  coc#compat#execute(code)\n  return ''\nenddef\n\n# Queues raw user-input, <\" is special. To input a literal \"<\", send <LT>.\nexport def Input(keys: string): any\n  const escaped: string = substitute(keys, '<', '\\\\<', 'g')\n  feedkeys(eval($'\"{escaped}\"'), 'n')\n  return null\nenddef\n\nexport def Create_buf(listed: bool, scratch: bool): number\n  const bufnr: number = bufadd('')\n  setbufvar(bufnr, '&buflisted', listed ? 1 : 0)\n  if scratch\n    setbufvar(bufnr, '&modeline', 0)\n    setbufvar(bufnr, '&buftype', 'nofile')\n    setbufvar(bufnr, '&swapfile', 0)\n  endif\n  bufload(bufnr)\n  return bufnr\nenddef\n\nexport def Get_current_line(): string\n  return getline('.')\nenddef\n\nexport def Set_current_line(line: string): any\n  setline('.', line)\n  OnTextChange(bufnr('%'))\n  return null\nenddef\n\nexport def Del_current_line(): any\n  deletebufline('%', line('.'))\n  OnTextChange(bufnr('%'))\n  return null\nenddef\n\nexport def Get_var(var: string): any\n  CheckKey(g:, var)\n  return g:[var]\nenddef\n\nexport def Get_vvar(var: string): any\n  return eval($'v:{var}')\nenddef\n\nexport def Get_current_buf(): number\n  return bufnr('%')\nenddef\n\nexport def Get_current_win(): number\n  return win_getid()\nenddef\n\nexport def Get_current_tabpage(): number\n  return TabNrId(tabpagenr())\nenddef\n\nexport def List_tabpages(): list<number>\n  final ids = []\n  for nr in range(1, tabpagenr('$'))\n    add(ids, TabNrId(nr))\n  endfor\n  return ids\nenddef\n\nexport def Get_mode(): dict<any>\n  const m: string = mode()\n  return {'blocking': m =~# '^r' ? true : false, 'mode': m}\nenddef\n\nexport def Strwidth(str: string): number\n  return strwidth(str)\nenddef\n\nexport def Out_write(str: string): any\n  echon str\n  DeferExecute('redraw')\n  return null\nenddef\n\nexport def Err_write(str: string): any\n  # Err_write texts are cached by node-client\n  return null\nenddef\n\nexport def Err_writeln(str: string): any\n  echohl ErrorMsg\n  echom str\n  echohl None\n  DeferExecute('redraw')\n  return null\nenddef\n\nexport def Create_namespace(name: string): number\n  if empty(name)\n    const id = namespace_id\n    namespace_id += 1\n    return id\n  endif\n  var id = get(namespace_cache, name, 0)\n  if id == 0\n    id = namespace_id\n    namespace_id += 1\n    namespace_cache[name] = id\n  endif\n  return id\nenddef\n\nexport def Get_namespaces(): dict<any>\n  return copy(namespace_cache)\nenddef\n\nexport def Set_keymap(mode: string, lhs: string, rhs: string, opts: dict<any>): any\n  const modekey: string = CreateModePrefix(mode, opts)\n  const arguments: string = CreateArguments(opts)\n  const escaped: string = empty(rhs) ? '<Nop>' : EscapeSpace(rhs)\n  coc#compat#execute($'{modekey} {arguments} {EscapeSpace(lhs)} {escaped}')\n  return null\nenddef\n\nexport def Del_keymap(mode: string, lhs: string): any\n  const escaped = substitute(lhs, ' ', '<space>', 'g')\n  execute $'legacy silent {mode}unmap {escaped}'\n  return null\nenddef\n\nexport def Set_option_value(name: string, value: any, opts: dict<any>): any\n  CheckScopeOption(opts)\n  const winid: number = get(opts, 'win', -1)\n  const bufnr: number = get(opts, 'buf', -1)\n  const scope: string = get(opts, 'scope', 'global')\n  if bufnr != -1\n    Buf_set_option(bufnr, name, value)\n  elseif winid != -1\n    Win_set_option(winid, name, value)\n  else\n    if scope ==# 'global'\n      Set_option(name, value)\n    else\n      Set_option(name, value, true)\n    endif\n  endif\n  return null\nenddef\n\nexport def Get_option_value(name: string, opts: dict<any> = {}): any\n  CheckScopeOption(opts)\n  const winid: number = get(opts, 'win', -1)\n  const bufnr: number = get(opts, 'buf', -1)\n  const scope: string = get(opts, 'scope', 'global')\n  var result: any = null\n  if bufnr != -1\n    result = Buf_get_option(bufnr, name)\n  elseif winid != -1\n    result = Win_get_option(winid, name)\n  else\n    if scope ==# 'global'\n      result = eval($'&{name}')\n    else\n      result = gettabwinvar(tabpagenr(), 0, '&' .. name, null)\n      if result == null\n        result = Buf_get_option(bufnr('%'), name)\n      endif\n    endif\n  endif\n  return result\nenddef\n\nexport def Create_augroup(name: string, option: dict<any> = {}): number\n  const clear: bool = get(option, 'clear', true)\n  if clear\n    execute $'augroup {name} | autocmd! | augroup END'\n  else\n    execute $'augroup {name} | augroup END'\n  endif\n  const id = group_id\n  groups_map[id] = name\n  group_id += 1\n  return id\nenddef\n\nexport def Create_autocmd(event: any, option: dict<any> = {}): number\n  final opt: dict<any> = { event: event }\n  if has_key(option, 'group')\n    if type(option.group) == v:t_number\n      if !has_key(groups_map, option.group)\n        throw $'Invalid group {option.group}'\n      endif\n      opt.group = groups_map[option.group]\n    elseif type(option.group) == v:t_string\n      opt.group = option.group\n    else\n      throw $'Invalid group {option.group}'\n    endif\n  endif\n  if get(option, 'nested', false) == true\n    opt.nested = true\n  endif\n  if get(option, 'once', false) == true\n    opt.once = true\n  endif\n  if has_key(option, 'pattern')\n    opt.pattern = option.pattern\n  else\n    # nvim add it automatically\n    opt.pattern = '*'\n  endif\n  if has_key(option, 'buffer')\n    opt.bufnr = option.buffer\n  endif\n  if has_key(option, 'command')\n    opt.cmd = $'legacy {option.command}'\n  endif\n  call autocmd_add([extend({'replace': get(option, 'replace', false)}, opt)])\n  const id = autocmd_id\n  autocmds_map[id] = opt\n  autocmd_id += 1\n  return id\nenddef\n\nexport def Del_autocmd(id: number): bool\n  if !has_key(autocmds_map, id)\n    return true\n  endif\n  final opt: dict<any> = autocmds_map[id]\n  # vim add autocmd when cmd exists\n  remove(opt, 'cmd')\n  remove(autocmds_map, id)\n  return autocmd_delete([opt])\nenddef\n# }}\n\n# buffer methods {{\nexport def Buf_set_option(id: number, name: string, value: any): any\n  const bufnr = GetValidBufnr(id)\n  CheckOptionValue(name, value)\n  if index(buffer_options, name) == -1\n    throw $\"Invalid buffer option name: {name}\"\n  endif\n  setbufvar(bufnr, $'&{name}', value)\n  return null\nenddef\n\nexport def Buf_get_option(id: number, name: string): any\n  const bufnr = GetValidBufnr(id)\n  if index(buffer_options, name) == -1\n    throw $\"Invalid buffer option name: {name}\"\n  endif\n  return getbufvar(bufnr, $'&{name}')\nenddef\n\nexport def Buf_get_changedtick(id: number): number\n  const bufnr = GetValidBufnr(id)\n  return getbufvar(bufnr, 'changedtick')\nenddef\n\nexport def Buf_is_valid(bufnr: number): bool\n  return bufexists(bufnr)\nenddef\n\nexport def Buf_is_loaded(bufnr: number): bool\n  return bufloaded(bufnr)\nenddef\n\nexport def Buf_get_mark(id: number, name: string): list<number>\n  const bufnr = GetValidBufnr(id)\n  const marks: list<any> = getmarklist(bufnr)\n  for item in marks\n    if item['mark'] ==# $\"'{name}\"\n      const pos: list<number> = item['pos']\n      return [pos[1], pos[2] - 1]\n    endif\n  endfor\n  return [0, 0]\nenddef\n\nexport def Buf_add_highlight(id: number, srcId: number, hlGroup: string, line: number, colStart: number, colEnd: number, propTypeOpts: dict<any> = {}): any\n  const bufnr = GetValidBufnr(id)\n  var sourceId: number\n  if srcId == 0\n    max_src_id += 1\n    sourceId = max_src_id\n  else\n    sourceId = srcId\n  endif\n  Buf_add_highlight1(bufnr, sourceId, hlGroup, line, colStart, colEnd, propTypeOpts)\n  return sourceId\nenddef\n\n# To be called directly for better performance\n# 0 based line, colStart, colEnd, see `:h prop_type_add` for propTypeOpts\nexport def Buf_add_highlight1(bufnr: number, srcId: number, hlGroup: string, line: number, colStart: number, colEnd: number, propTypeOpts: dict<any> = {}): void\n  const columnEnd: number = colEnd == -1 ? strlen(get(getbufline(bufnr, line + 1), 0, '')) + 1 : colEnd + 1\n  if columnEnd <= colStart\n    return\n  endif\n  const propType: string = CreateType(srcId, hlGroup, propTypeOpts)\n  const propId: number = GeneratePropId(bufnr)\n  try\n    prop_add(line + 1, colStart + 1, {'bufnr': bufnr, 'type': propType, 'id': propId, 'end_col': columnEnd})\n  catch /^Vim\\%((\\a\\+)\\)\\=:\\(E967\\|E964\\)/\n    # ignore 967\n  endtry\nenddef\n\nexport def Buf_clear_namespace(id: number, srcId: number, startLine: number, endLine: number): any\n  const bufnr = GetValidBufnr(id)\n  const start = startLine + 1\n  const end = endLine == -1 ? BufLineCount(bufnr) : endLine\n  if srcId == -1\n    if has_key(buffer_id, bufnr)\n      remove(buffer_id, bufnr)\n    endif\n    prop_clear(start, end, {'bufnr': bufnr})\n  else\n    const types = get(id_types, srcId, [])\n    if !empty(types)\n      try\n        prop_remove({'bufnr': bufnr, 'all': true, 'types': types}, start, end)\n      catch /^Vim\\%((\\a\\+)\\)\\=:E968/\n        # ignore 968\n      endtry\n    endif\n  endif\n  return null\nenddef\n\nexport def Buf_line_count(bufnr: number): number\n  if bufnr == 0\n    return line('$')\n  endif\n  return BufLineCount(bufnr)\nenddef\n\nexport def Buf_attach(id: number = 0, ..._): bool\n  const bufnr = GetValidBufnr(id)\n  # listener not removed on e!\n  DetachListener(bufnr)\n  const result = listener_add(OnBufferChange, bufnr)\n  if result != 0\n    listener_map[bufnr] = result\n    return true\n  endif\n  return false\nenddef\n\nexport def Buf_detach(id: number): bool\n  const bufnr = GetValidBufnr(id)\n  return DetachListener(bufnr)\nenddef\n\nexport def Buf_flush(id: any): void\n  if type(id) == v:t_number && has_key(listener_map, id)\n    listener_flush(id)\n  endif\nenddef\n\nexport def Buf_get_lines(id: number, start: number, end: number, strict: bool = false): list<string>\n  const bufnr = GetValidBufnr(id)\n  const len = BufLineCount(bufnr)\n  const s = start < 0 ? len + start + 2 : start + 1\n  const e = end < 0 ? len + end + 1 : end\n  if strict && e > len\n    throw $'Index out of bounds {end}'\n  endif\n  return getbufline(bufnr, s, e)\nenddef\n\nexport def Buf_set_lines(id: number, start: number, end: number, strict: bool = false, replacement: list<string> = []): any\n  const bufnr = GetValidBufnr(id)\n  const len = BufLineCount(bufnr)\n  const startLnum = start < 0 ? len + start + 2 : start + 1\n  var endLnum = end < 0 ? len + end + 1 : end\n  if endLnum > len\n    if strict\n      throw $'Index out of bounds {end}'\n    else\n      endLnum = len\n    endif\n  endif\n  const view = bufnr == bufnr('%') ? winsaveview() : null\n  SetBufferLines(bufnr, startLnum, endLnum, replacement)\n  if view != null\n    winrestview(view)\n  endif\n  OnTextChange(bufnr)\n  return null\nenddef\n\nexport def Buf_set_name(id: number, name: string): any\n  const bufnr = GetValidBufnr(id)\n  BufExecute(bufnr, ['legacy silent noa 0file', $'legacy file {fnameescape(name)}'])\n  return null\nenddef\n\nexport def Buf_get_name(id: number): string\n  return GetValidBufnr(id)->bufname()\nenddef\n\nexport def Buf_get_var(id: number, name: string): any\n  const bufnr = GetValidBufnr(id)\n  const dict: dict<any> = getbufvar(bufnr, '')\n  CheckKey(dict, name)\n  return dict[name]\nenddef\n\nexport def Buf_set_var(id: number, name: string, val: any): any\n  const bufnr = GetValidBufnr(id)\n  setbufvar(bufnr, name, val)\n  return null\nenddef\n\nexport def Buf_del_var(id: number, name: string): any\n  const bufnr = GetValidBufnr(id)\n  final bufvars = getbufvar(bufnr, '')\n  CheckKey(bufvars, name)\n  remove(bufvars, name)\n  return null\nenddef\n\nexport def Buf_set_keymap(id: number, mode: string, lhs: string, rhs: string, opts: dict<any>): any\n  const bufnr = GetValidBufnr(id)\n  const prefix = CreateModePrefix(mode, opts)\n  const arguments = CreateArguments(opts)\n  const escaped = empty(rhs) ? '<Nop>' : EscapeSpace(rhs)\n  BufExecute(bufnr, [$'legacy {prefix} {arguments}<buffer> {EscapeSpace(lhs)} {escaped}'])\n  return null\nenddef\n\nexport def Buf_del_keymap(id: number, mode: string, lhs: string): any\n  const bufnr = GetValidBufnr(id)\n  const escaped = substitute(lhs, ' ', '<space>', 'g')\n  BufExecute(bufnr, [$'legacy silent {mode}unmap <buffer> {escaped}'])\n  return null\nenddef\n\nexport def Buf_set_text(id: number, start_row: number, start_col: number, end_row: number, end_col: number, replacement: list<string>): void\n  const bufnr = GetValidBufnr(id)\n  const len = BufLineCount(bufnr)\n  if start_row >= len\n    throw $'Start row out of bounds {start_row}'\n  endif\n  if end_row >= len\n    throw $'End row out of bounds {end_row}'\n  endif\n  SetBufferText(bufnr, start_row, start_col, end_row, end_col, replacement)\nenddef\n# }}\n\n# window methods {{\nexport def Win_get_buf(id: number): number\n  return GetValidWinid(id)->winbufnr()\nenddef\n\nexport def Win_set_buf(id: number, bufnr: number): any\n  const winid = GetValidWinid(id)\n  CheckBufnr(bufnr)\n  win_execute(winid, $'legacy buffer {bufnr}')\n  return null\nenddef\n\nexport def Win_get_position(id: number): list<number>\n  const winid = GetValidWinid(id)\n  const [row, col] = win_screenpos(winid)\n  if row == 0 && col == 0\n    throw $'Invalid window {winid}'\n  endif\n  return [row - 1, col - 1]\nenddef\n\nexport def Win_set_height(id: number, height: number): any\n  const winid = GetValidWinid(id)\n  if IsPopup(winid)\n    popup_move(winid, {'maxheight': height, 'minheight': height})\n  else\n    win_execute(winid, $'legacy resize {height}')\n  endif\n  return null\nenddef\n\nexport def Win_get_height(id: number): number\n  const winid = GetValidWinid(id)\n  if IsPopup(winid)\n    return popup_getpos(winid)['height']\n  endif\n  return winheight(winid)\nenddef\n\nexport def Win_set_width(id: number, width: number): any\n  const winid = GetValidWinid(id)\n  if IsPopup(winid)\n    popup_move(winid, {'maxwidth': width, 'minwidth': width})\n  else\n    win_execute(winid, $'legacy vertical resize {width}')\n  endif\n  return null\nenddef\n\nexport def Win_get_width(id: number): number\n  const winid = GetValidWinid(id)\n  if IsPopup(winid)\n    return popup_getpos(winid)['width']\n  endif\n  return winwidth(winid)\nenddef\n\nexport def Win_set_cursor(id: number, pos: list<number>): any\n  const winid = GetValidWinid(id)\n  win_execute(winid, $'cursor({pos[0]}, {pos[1] + 1})')\n  return null\nenddef\n\nexport def Win_get_cursor(id: number): list<number>\n  const winid = GetValidWinid(id)\n  const result = getcurpos(winid)\n  if result[1] == 0\n    return [1, 0]\n  endif\n  return [result[1], result[2] - 1]\nenddef\n\nexport def Win_set_option(id: number, name: string, value: any): any\n  const winid = GetValidWinid(id)\n  CheckOptionValue(name, value)\n  const tabnr = WinTabnr(winid)\n  if index(window_options, name) == -1\n    throw $\"Invalid window option name: {name}\"\n  endif\n  settabwinvar(tabnr, winid, $'&{name}', value)\n  return null\nenddef\n\nexport def Win_get_option(id: number, name: string, ..._): any\n  const winid = GetValidWinid(id)\n  const tabnr = WinTabnr(winid)\n  if index(window_options, name) == -1\n    throw $\"Invalid window option name: {name}\"\n  endif\n  return gettabwinvar(tabnr, winid, '&' .. name)\nenddef\n\nexport def Win_get_var(id: number, name: string, ..._): any\n  const winid = GetValidWinid(id)\n  const tabnr = WinTabnr(winid)\n  const vars = gettabwinvar(tabnr, winid, '')\n  CheckKey(vars, name)\n  return get(vars, name, null)\nenddef\n\nexport def Win_set_var(id: number, name: string, value: any): any\n  const winid = GetValidWinid(id)\n  const tabnr = WinTabnr(winid)\n  settabwinvar(tabnr, winid, name, value)\n  return null\nenddef\n\nexport def Win_del_var(id: number, name: string): any\n  const winid = GetValidWinid(id)\n  const tabnr = WinTabnr(winid)\n  const vars: dict<any> = gettabwinvar(tabnr, winid, '')\n  CheckKey(vars, name)\n  win_execute(winid, 'remove(w:, \"' .. name .. '\")')\n  return null\nenddef\n\nexport def Win_is_valid(id: number): bool\n  const winid = id == 0 ? win_getid() : id\n  return empty(getwininfo(winid)) == 0\nenddef\n\nexport def Win_get_number(id: number): number\n  const winid = GetValidWinid(id)\n  const info = getwininfo(winid)\n  # Note: vim return 0 for popup\n  return info[0]['winnr']\nenddef\n\n# Not work for popup since vim gives 0 for tabnr\nexport def Win_get_tabpage(id: number): number\n  return GetValidWinid(id)->WinTabnr()->TabNrId()\nenddef\n\nexport def Win_close(id: number, force: bool = false): any\n  const winid = GetValidWinid(id)\n  if IsPopup(winid)\n    popup_close(winid)\n  else\n    win_execute(winid, $'legacy close{force ? '!' : ''}')\n  endif\n  return null\nenddef\n# }}\n\n# tabpage methods {{\nexport def Tabpage_get_number(tid: number): number\n  return TabIdNr(tid)\nenddef\n\nexport def Tabpage_list_wins(tid: number): list<number>\n  return TabIdNr(tid)->gettabinfo()[0].windows\nenddef\n\nexport def Tabpage_get_var(tid: number, name: string): any\n  const nr = TabIdNr(tid)\n  const dict = gettabvar(nr, '')\n  CheckKey(dict, name)\n  return get(dict, name, null)\nenddef\n\nexport def Tabpage_set_var(tid: number, name: string, value: any): any\n  const nr = TabIdNr(tid)\n  settabvar(nr, name, value)\n  return null\nenddef\n\nexport def Tabpage_del_var(tid: number, name: string): any\n  const nr = TabIdNr(tid)\n  final dict = gettabvar(nr, '')\n  CheckKey(dict, name)\n  remove(dict, name)\n  return null\nenddef\n\nexport def Tabpage_is_valid(tid: number): bool\n  for nr in range(1, tabpagenr('$'))\n    if gettabvar(nr, '__coc_tid', -1) == tid\n      return true\n    endif\n  endfor\n  return false\nenddef\n\nexport def Tabpage_get_win(tid: number): number\n  const nr = TabIdNr(tid)\n  return win_getid(tabpagewinnr(nr), nr)\nenddef\n\nexport def Tabpage_ids(): void\n  for nr in range(1, tabpagenr('$'))\n    if gettabvar(nr, '__coc_tid', -1) == -1\n      settabvar(nr, '__coc_tid', tab_id)\n      tab_id += 1\n    endif\n  endfor\nenddef\n# }}\n\n# Used by node-client request, function needed to catch error\n# Must use coc#api# prefix to avoid call global function\nexport function Call(method, args) abort\n  let err = v:null\n  let result = v:null\n  try\n    let result = call($'coc#api#{toupper(a:method[0])}{strpart(a:method, 1)}', a:args)\n    call coc#api#Buf_flush(bufnr('%'))\n  catch /.*/\n    let err =  v:exception .. \" - on request \\\"\" .. a:method .. \"\\\" \\n\" .. v:throwpoint\n    let result = v:null\n  endtry\n  return [err, result]\nendfunction\n\n# Used by node-client notification, function needed to catch error\nexport function Notify(method, args) abort\n  try\n    if a:method ==# 'call_function'\n      call coc#api#Call_function(a:args[0], a:args[1], v:true)\n    else\n      let fname = $'coc#api#{toupper(a:method[0])}{strpart(a:method, 1)}'\n      call call(fname, a:args)\n    endif\n    call coc#api#Buf_flush(bufnr('%'))\n  catch /.*/\n    call coc#rpc#notify('nvim_error_event', [0, v:exception .. \" - on notification \\\"\" .. a:method .. \"\\\" \\n\" .. v:throwpoint])\n  endtry\n  return v:null\nendfunction\n\n# Could be called by other plugin\nconst call_function =<< trim END\n  function! coc#api#call(method, args) abort\n    return coc#api#Call(a:method, a:args)\n  endfunction\nEND\n\nexecute $'legacy execute \"{join(call_function, '\\n')}\"'\n\ndefcompile\n# vim: set sw=2 ts=2 sts=2 et tw=78 foldmarker={{,}} foldmethod=marker foldlevel=0:\n"
  },
  {
    "path": "autoload/coc/client.vim",
    "content": "scriptencoding utf-8\nlet s:is_vim = !has('nvim')\nlet s:clients = {}\n\nif get(g:, 'node_client_debug', 0)\n  echohl WarningMsg | echo '[coc.nvim] Enable g:node_client_debug could impact your vim experience' | echohl None\n  let $NODE_CLIENT_LOG_LEVEL = 'debug'\n  if exists('$NODE_CLIENT_LOG_FILE')\n    let s:logfile = resolve($NODE_CLIENT_LOG_FILE)\n  else\n    let s:logfile = tempname()\n    let $NODE_CLIENT_LOG_FILE = s:logfile\n  endif\nendif\n\n\" create a client\nfunction! coc#client#create(name, command)\n  let client = {}\n  let client['command'] = a:command\n  let client['name'] = a:name\n  let client['running'] = 0\n  let client['async_req_id'] = 1\n  let client['async_callbacks'] = {}\n  \" vim only\n  let client['channel'] = v:null\n  \" neovim only\n  let client['chan_id'] = 0\n  let client['start'] = function('s:start', [], client)\n  let client['request'] = function('s:request', [], client)\n  let client['notify'] = function('s:notify', [], client)\n  let client['request_async'] = function('s:request_async', [], client)\n  let client['on_async_response'] = function('s:on_async_response', [], client)\n  let s:clients[a:name] = client\n  return client\nendfunction\n\nfunction! s:start() dict\n  if self.running | return | endif\n  if !isdirectory(getcwd())\n    echoerr '[coc.nvim] Current cwd is not a valid directory.'\n    return\n  endif\n  let tmpdir = fnamemodify(tempname(), ':p:h')\n  let env = { 'NODE_NO_WARNINGS': '1', 'TMPDIR': coc#util#win32unix_to_node(tmpdir)}\n  if s:is_vim\n    let env['VIM_NODE_RPC'] = 1\n    if get(g:, 'node_client_debug', 0) || $COC_VIM_CHANNEL_ENABLE == '1'\n      let file = tmpdir . '/coc.log'\n      call ch_logfile(file, 'w')\n      echohl MoreMsg | echo '[coc.nvim] channel log to '.file | echohl None\n    endif\n    let options = {\n          \\ 'noblock': 1,\n          \\ 'in_mode': 'json',\n          \\ 'out_mode': 'json',\n          \\ 'err_mode': 'nl',\n          \\ 'err_cb': {channel, message -> s:on_stderr(self.name, split(message, \"\\n\"))},\n          \\ 'exit_cb': {channel, code -> s:on_exit(self.name, code)},\n          \\ 'env': env\n          \\}\n    let job = job_start(self.command, options)\n    let status = job_status(job)\n    if status !=# 'run'\n      let self.running = 0\n      echohl Error | echom 'Failed to start '.self.name.' service' | echohl None\n      return\n    endif\n    let self['channel'] = job_getchannel(job)\n  else\n    let opts = {\n          \\ 'rpc': 1,\n          \\ 'on_stderr': {channel, msgs -> s:on_stderr(self.name, msgs)},\n          \\ 'on_exit': {channel, code -> s:on_exit(self.name, code)},\n          \\ 'env': env\n          \\ }\n    let chan_id = jobstart(self.command, opts)\n    if chan_id <= 0\n      echohl Error | echom 'Failed to start '.self.name.' service' | echohl None\n      return\n    endif\n    let self['chan_id'] = chan_id\n  endif\n  let self['running'] = 1\nendfunction\n\nfunction! s:on_stderr(name, msgs)\n  if get(g:, 'coc_vim_leaving', 0) | return | endif\n  let data = filter(copy(a:msgs), '!empty(v:val)')\n  if empty(data) | return | endif\n  let client = a:name ==# 'coc' ? '[coc.nvim]' : '['.a:name.']'\n  let data[0] = client.': '.data[0]\n  if a:name ==# 'coc' && len(filter(copy(data), 'v:val =~# \"SyntaxError: \"'))\n    call coc#client#check_version()\n    return\n  endif\n  if get(g:, 'coc_disable_uncaught_error', 0) | return | endif\n  call s:on_error(a:name, data)\nendfunction\n\nfunction! coc#client#check_version() abort\n  if (has_key(g:, 'coc_node_path'))\n    let node = expand(g:coc_node_path)\n  else\n    let node = $COC_NODE_PATH == '' ? 'node' : $COC_NODE_PATH\n  endif\n  let cmd = node . ' --version'\n  let output = system(cmd)\n  let msgs = []\n  if v:shell_error\n    let msgs = ['Unexpected result from \"'.cmd.'\"'] + split(output, '\\n')\n  else\n    let ms = matchlist(output, 'v\\(\\d\\+\\).\\(\\d\\+\\).\\(\\d\\+\\)')\n    if empty(ms)\n      let msgs = ['Unable to get node version by \"'.cmd.'\" please install NodeJS from https://nodejs.org/en/download/']\n    elseif str2nr(ms[1]) < 16 || (str2nr(ms[1]) == 16 && str2nr(ms[2]) < 18)\n      let msgs = ['Current Node.js version '.trim(output).' < 16.18.0 ', 'Please upgrade your Node.js']\n    endif\n  endif\n  if !empty(msgs)\n    call s:on_error('coc', msgs)\n  endif\nendfunction\n\nfunction! s:on_exit(name, code) abort\n  if get(g:, 'coc_vim_leaving', 0) | return | endif\n  let client = get(s:clients, a:name, v:null)\n  if empty(client) | return | endif\n  if client['running'] != 1 | return | endif\n  let client['running'] = 0\n  let client['chan_id'] = 0\n  let client['channel'] = v:null\n  let client['async_req_id'] = 1\n  if a:code != 0 && a:code != 143 && a:code != -1\n    echohl Error | echom 'client '.a:name. ' abnormal exit with: '.a:code | echohl None\n  endif\nendfunction\n\nfunction! coc#client#get_client(name) abort\n  return get(s:clients, a:name, v:null)\nendfunction\n\nfunction! coc#client#get_channel(client)\n  if s:is_vim\n    return a:client['channel']\n  endif\n  return a:client['chan_id']\nendfunction\n\nfunction! s:request(method, args) dict\n  let channel = coc#client#get_channel(self)\n  if empty(channel) | return '' | endif\n  try\n    if s:is_vim\n      let res = ch_evalexpr(channel, [a:method, a:args], {'timeout': 60 * 1000})\n      if type(res) == 1 && res ==# ''\n        throw 'request '.a:method. ' '.string(a:args).' timeout after 60s'\n      endif\n      let [l:errmsg, res] =  res\n      if !empty(l:errmsg)\n        throw 'Error on \"'.a:method.'\" request: '.l:errmsg\n      else\n        return res\n      endif\n    else\n      return call('rpcrequest', [channel, a:method] + a:args)\n    endif\n  catch /.*/\n    if v:exception =~# 'E475'\n      if get(g:, 'coc_vim_leaving', 0) | return | endif\n      echohl Error | echom '['.self.name.'] server connection lost' | echohl None\n      let name = self.name\n      call s:on_exit(name, 0)\n      execute 'silent do User ConnectionLost'.toupper(name[0]).name[1:]\n    elseif v:exception =~# 'E12'\n      \" neovim's bug, ignore it\n    else\n      if s:is_vim\n        throw v:exception\n      else\n        throw 'Error on request: '.v:exception\n      endif\n    endif\n  endtry\nendfunction\n\nfunction! s:notify(method, args) dict\n  let channel = coc#client#get_channel(self)\n  if empty(channel)\n    return ''\n  endif\n  try\n    if s:is_vim\n      call ch_sendraw(channel, json_encode([0, [a:method, a:args]]).\"\\n\")\n    else\n      call call('rpcnotify', [channel, a:method] + a:args)\n    endif\n  catch /.*/\n    if v:exception =~# 'E475'\n      if get(g:, 'coc_vim_leaving', 0)\n        return\n      endif\n      echohl Error | echom '['.self.name.'] server connection lost' | echohl None\n      let name = self.name\n      call s:on_exit(name, 0)\n      execute 'silent do User ConnectionLost'.toupper(name[0]).name[1:]\n    elseif v:exception =~# 'E12'\n      \" neovim's bug, ignore it\n    else\n      echohl Error | echo 'Error on notify ('.a:method.'): '.v:exception | echohl None\n    endif\n  endtry\nendfunction\n\nfunction! s:request_async(method, args, cb) dict\n  let channel = coc#client#get_channel(self)\n  if empty(channel) | return '' | endif\n  if type(a:cb) != 2\n    echohl Error | echom '['.self['name'].'] Callback should be function' | echohl None\n    return\n  endif\n  let id = self.async_req_id\n  let self.async_req_id = id + 1\n  let self.async_callbacks[id] = a:cb\n  call self['notify']('nvim_async_request_event', [id, a:method, a:args])\nendfunction\n\nfunction! s:on_async_response(id, resp, isErr) dict\n  let Callback = get(self.async_callbacks, a:id, v:null)\n  if empty(Callback)\n    \" should not happen\n    echohl Error | echom 'callback not found' | echohl None\n    return\n  endif\n  call remove(self.async_callbacks, a:id)\n  if a:isErr\n    call call(Callback, [a:resp, v:null])\n  else\n    call call(Callback, [v:null, a:resp])\n  endif\nendfunction\n\nfunction! coc#client#is_running(name) abort\n  let client = get(s:clients, a:name, v:null)\n  if empty(client) | return 0 | endif\n  if !client['running'] | return 0 | endif\n  try\n    if s:is_vim\n      let status = job_status(ch_getjob(client['channel']))\n      return status ==# 'run'\n    else\n      let chan_id = client['chan_id']\n      let [code] = jobwait([chan_id], 10)\n      return code == -1\n    endif\n  catch /.*/\n    return 0\n  endtry\nendfunction\n\nfunction! coc#client#stop(name) abort\n  let client = get(s:clients, a:name, v:null)\n  if empty(client) | return 1 | endif\n  let running = coc#client#is_running(a:name)\n  if !running\n    echohl WarningMsg | echom 'client '.a:name. ' not running.' | echohl None\n    return 1\n  endif\n  if s:is_vim\n    call job_stop(ch_getjob(client['channel']), 'term')\n  else\n    call jobstop(client['chan_id'])\n  endif\n  sleep 200m\n  if coc#client#is_running(a:name)\n    echohl Error | echom 'client '.a:name. ' stop failed.' | echohl None\n    return 0\n  endif\n  call s:on_exit(a:name, 0)\n  echohl MoreMsg | echom 'client '.a:name.' stopped!' | echohl None\n  return 1\nendfunction\n\nfunction! coc#client#kill(name) abort\n  let client = get(s:clients, a:name, v:null)\n  if empty(client) | return 1 | endif\n  let running = coc#client#is_running(a:name)\n  if empty(client) || exists('$COC_NVIM_REMOTE_ADDRESS')\n    return 1\n  endif\n  if running\n    if s:is_vim\n      call job_stop(ch_getjob(client['channel']), 'kill')\n    else\n      call jobstop(client['chan_id'])\n    endif\n  endif\nendfunction\n\nfunction! coc#client#request(name, method, args)\n  let client = get(s:clients, a:name, v:null)\n  if !empty(client)\n    return client['request'](a:method, a:args)\n  endif\nendfunction\n\nfunction! coc#client#notify(name, method, args)\n  let client = get(s:clients, a:name, v:null)\n  if !empty(client)\n    call client['notify'](a:method, a:args)\n  endif\nendfunction\n\nfunction! coc#client#request_async(name, method, args, cb)\n  let client = get(s:clients, a:name, v:null)\n  if !empty(client)\n    call client['request_async'](a:method, a:args, a:cb)\n  endif\nendfunction\n\nfunction! coc#client#on_response(name, id, resp, isErr)\n  let client = get(s:clients, a:name, v:null)\n  if !empty(client)\n    call client['on_async_response'](a:id, a:resp, a:isErr)\n  endif\nendfunction\n\nfunction! coc#client#restart(name) abort\n  let stopped = coc#client#stop(a:name)\n  if !stopped | return | endif\n  let client = get(s:clients, a:name, v:null)\n  if !empty(client)\n    call client['start']()\n  endif\nendfunction\n\nfunction! coc#client#restart_all()\n  for key in keys(s:clients)\n    call coc#client#restart(key)\n  endfor\nendfunction\n\nfunction! coc#client#open_log()\n  if !get(g:, 'node_client_debug', 0)\n    throw '[coc.nvim] use let g:node_client_debug = 1 in your vimrc to enable debug mode.'\n    return\n  endif\n  execute 'vs '.s:logfile\nendfunction\n\nfunction! coc#client#get_log()\n  if !get(g:, 'node_client_debug', 0)\n    throw '[coc.nvim] use let g:node_client_debug = 1 in your vimrc to enable debug mode.'\n    return ''\n  endif\n  return s:logfile\nendfunction\n\nfunction! s:on_error(name, msgs) abort\n  echohl ErrorMsg\n  echo join(a:msgs, \"\\n\")\n  echohl None\n  let client = get(s:clients, a:name, v:null)\n  if !empty(client)\n    let errors = get(client, 'stderr', [])\n    call extend(errors, a:msgs)\n    let client['stderr'] = errors\n  endif\nendfunction\n"
  },
  {
    "path": "autoload/coc/color.vim",
    "content": "scriptencoding utf-8\n\nlet s:activate = \"\"\nlet s:quit = \"\"\nif has(\"gui_macvim\") && has('gui_running')\n  let s:app = \"MacVim\"\nelseif $TERM_PROGRAM ==# \"Apple_Terminal\"\n  let s:app = \"Terminal\"\nelseif $TERM_PROGRAM ==# \"iTerm.app\"\n  let s:app = \"iTerm2\"\nelseif has('mac')\n  let s:app = \"System Events\"\n  let s:quit = \"quit\"\n  let s:activate = 'activate'\nendif\n\nlet s:patterns = {}\nlet s:patterns['hex']      = '\\v#?(\\x{2})(\\x{2})(\\x{2})'\nlet s:patterns['shortHex'] = '\\v#(\\x{1})(\\x{1})(\\x{1})'\n\nlet s:xterm_colors = {\n    \\ '0':   '#000000', '1':   '#800000', '2':   '#008000', '3':   '#808000', '4':   '#000080',\n    \\ '5':   '#800080', '6':   '#008080', '7':   '#c0c0c0', '8':   '#808080', '9':   '#ff0000',\n    \\ '10':  '#00ff00', '11':  '#ffff00', '12':  '#0000ff', '13':  '#ff00ff', '14':  '#00ffff',\n    \\ '15':  '#ffffff', '16':  '#000000', '17':  '#00005f', '18':  '#000087', '19':  '#0000af',\n    \\ '20':  '#0000df', '21':  '#0000ff', '22':  '#005f00', '23':  '#005f5f', '24':  '#005f87',\n    \\ '25':  '#005faf', '26':  '#005fdf', '27':  '#005fff', '28':  '#008700', '29':  '#00875f',\n    \\ '30':  '#008787', '31':  '#0087af', '32':  '#0087df', '33':  '#0087ff', '34':  '#00af00',\n    \\ '35':  '#00af5f', '36':  '#00af87', '37':  '#00afaf', '38':  '#00afdf', '39':  '#00afff',\n    \\ '40':  '#00df00', '41':  '#00df5f', '42':  '#00df87', '43':  '#00dfaf', '44':  '#00dfdf',\n    \\ '45':  '#00dfff', '46':  '#00ff00', '47':  '#00ff5f', '48':  '#00ff87', '49':  '#00ffaf',\n    \\ '50':  '#00ffdf', '51':  '#00ffff', '52':  '#5f0000', '53':  '#5f005f', '54':  '#5f0087',\n    \\ '55':  '#5f00af', '56':  '#5f00df', '57':  '#5f00ff', '58':  '#5f5f00', '59':  '#5f5f5f',\n    \\ '60':  '#5f5f87', '61':  '#5f5faf', '62':  '#5f5fdf', '63':  '#5f5fff', '64':  '#5f8700',\n    \\ '65':  '#5f875f', '66':  '#5f8787', '67':  '#5f87af', '68':  '#5f87df', '69':  '#5f87ff',\n    \\ '70':  '#5faf00', '71':  '#5faf5f', '72':  '#5faf87', '73':  '#5fafaf', '74':  '#5fafdf',\n    \\ '75':  '#5fafff', '76':  '#5fdf00', '77':  '#5fdf5f', '78':  '#5fdf87', '79':  '#5fdfaf',\n    \\ '80':  '#5fdfdf', '81':  '#5fdfff', '82':  '#5fff00', '83':  '#5fff5f', '84':  '#5fff87',\n    \\ '85':  '#5fffaf', '86':  '#5fffdf', '87':  '#5fffff', '88':  '#870000', '89':  '#87005f',\n    \\ '90':  '#870087', '91':  '#8700af', '92':  '#8700df', '93':  '#8700ff', '94':  '#875f00',\n    \\ '95':  '#875f5f', '96':  '#875f87', '97':  '#875faf', '98':  '#875fdf', '99':  '#875fff',\n    \\ '100': '#878700', '101': '#87875f', '102': '#878787', '103': '#8787af', '104': '#8787df',\n    \\ '105': '#8787ff', '106': '#87af00', '107': '#87af5f', '108': '#87af87', '109': '#87afaf',\n    \\ '110': '#87afdf', '111': '#87afff', '112': '#87df00', '113': '#87df5f', '114': '#87df87',\n    \\ '115': '#87dfaf', '116': '#87dfdf', '117': '#87dfff', '118': '#87ff00', '119': '#87ff5f',\n    \\ '120': '#87ff87', '121': '#87ffaf', '122': '#87ffdf', '123': '#87ffff', '124': '#af0000',\n    \\ '125': '#af005f', '126': '#af0087', '127': '#af00af', '128': '#af00df', '129': '#af00ff',\n    \\ '130': '#af5f00', '131': '#af5f5f', '132': '#af5f87', '133': '#af5faf', '134': '#af5fdf',\n    \\ '135': '#af5fff', '136': '#af8700', '137': '#af875f', '138': '#af8787', '139': '#af87af',\n    \\ '140': '#af87df', '141': '#af87ff', '142': '#afaf00', '143': '#afaf5f', '144': '#afaf87',\n    \\ '145': '#afafaf', '146': '#afafdf', '147': '#afafff', '148': '#afdf00', '149': '#afdf5f',\n    \\ '150': '#afdf87', '151': '#afdfaf', '152': '#afdfdf', '153': '#afdfff', '154': '#afff00',\n    \\ '155': '#afff5f', '156': '#afff87', '157': '#afffaf', '158': '#afffdf', '159': '#afffff',\n    \\ '160': '#df0000', '161': '#df005f', '162': '#df0087', '163': '#df00af', '164': '#df00df',\n    \\ '165': '#df00ff', '166': '#df5f00', '167': '#df5f5f', '168': '#df5f87', '169': '#df5faf',\n    \\ '170': '#df5fdf', '171': '#df5fff', '172': '#df8700', '173': '#df875f', '174': '#df8787',\n    \\ '175': '#df87af', '176': '#df87df', '177': '#df87ff', '178': '#dfaf00', '179': '#dfaf5f',\n    \\ '180': '#dfaf87', '181': '#dfafaf', '182': '#dfafdf', '183': '#dfafff', '184': '#dfdf00',\n    \\ '185': '#dfdf5f', '186': '#dfdf87', '187': '#dfdfaf', '188': '#dfdfdf', '189': '#dfdfff',\n    \\ '190': '#dfff00', '191': '#dfff5f', '192': '#dfff87', '193': '#dfffaf', '194': '#dfffdf',\n    \\ '195': '#dfffff', '196': '#ff0000', '197': '#ff005f', '198': '#ff0087', '199': '#ff00af',\n    \\ '200': '#ff00df', '201': '#ff00ff', '202': '#ff5f00', '203': '#ff5f5f', '204': '#ff5f87',\n    \\ '205': '#ff5faf', '206': '#ff5fdf', '207': '#ff5fff', '208': '#ff8700', '209': '#ff875f',\n    \\ '210': '#ff8787', '211': '#ff87af', '212': '#ff87df', '213': '#ff87ff', '214': '#ffaf00',\n    \\ '215': '#ffaf5f', '216': '#ffaf87', '217': '#ffafaf', '218': '#ffafdf', '219': '#ffafff',\n    \\ '220': '#ffdf00', '221': '#ffdf5f', '222': '#ffdf87', '223': '#ffdfaf', '224': '#ffdfdf',\n    \\ '225': '#ffdfff', '226': '#ffff00', '227': '#ffff5f', '228': '#ffff87', '229': '#ffffaf',\n    \\ '230': '#ffffdf', '231': '#ffffff', '232': '#080808', '233': '#121212', '234': '#1c1c1c',\n    \\ '235': '#262626', '236': '#303030', '237': '#3a3a3a', '238': '#444444', '239': '#4e4e4e',\n    \\ '240': '#585858', '241': '#606060', '242': '#666666', '243': '#767676', '244': '#808080',\n    \\ '245': '#8a8a8a', '246': '#949494', '247': '#9e9e9e', '248': '#a8a8a8', '249': '#b2b2b2',\n    \\ '250': '#bcbcbc', '251': '#c6c6c6', '252': '#d0d0d0', '253': '#dadada', '254': '#e4e4e4',\n    \\ '255': '#eeeeee'}\n\nlet s:xterm_16colors = {\n\\ 'black':          '#000000',\n\\ 'darkblue':       '#00008B',\n\\ 'darkgreen':      '#00CD00',\n\\ 'darkcyan':       '#00CDCD',\n\\ 'darkred':        '#CD0000',\n\\ 'darkmagenta':    '#8B008B',\n\\ 'brown':          '#CDCD00',\n\\ 'darkyellow':     '#CDCD00',\n\\ 'lightgrey':      '#E5E5E5',\n\\ 'lightgray':      '#E5E5E5',\n\\ 'gray':           '#E5E5E5',\n\\ 'grey':           '#E5E5E5',\n\\ 'darkgrey':       '#7F7F7F',\n\\ 'darkgray':       '#7F7F7F',\n\\ 'blue':           '#5C5CFF',\n\\ 'lightblue':      '#5C5CFF',\n\\ 'green':          '#00FF00',\n\\ 'lightgreen':     '#00FF00',\n\\ 'cyan':           '#00FFFF',\n\\ 'lightcyan':      '#00FFFF',\n\\ 'red':            '#FF0000',\n\\ 'lightred':       '#FF0000',\n\\ 'magenta':        '#FF00FF',\n\\ 'lightmagenta':   '#FF00FF',\n\\ 'yellow':         '#FFFF00',\n\\ 'lightyellow':    '#FFFF00',\n\\ 'white':          '#FFFFFF',\n\\ }\n\nlet s:w3c_color_names = {\n\\ 'aliceblue': '#F0F8FF',\n\\ 'antiquewhite': '#FAEBD7',\n\\ 'aqua': '#00FFFF',\n\\ 'aquamarine': '#7FFFD4',\n\\ 'azure': '#F0FFFF',\n\\ 'beige': '#F5F5DC',\n\\ 'bisque': '#FFE4C4',\n\\ 'black': '#000000',\n\\ 'blanchedalmond': '#FFEBCD',\n\\ 'blue': '#0000FF',\n\\ 'blueviolet': '#8A2BE2',\n\\ 'brown': '#A52A2A',\n\\ 'burlywood': '#DEB887',\n\\ 'cadetblue': '#5F9EA0',\n\\ 'chartreuse': '#7FFF00',\n\\ 'chocolate': '#D2691E',\n\\ 'coral': '#FF7F50',\n\\ 'cornflowerblue': '#6495ED',\n\\ 'cornsilk': '#FFF8DC',\n\\ 'crimson': '#DC143C',\n\\ 'cyan': '#00FFFF',\n\\ 'darkblue': '#00008B',\n\\ 'darkcyan': '#008B8B',\n\\ 'darkgoldenrod': '#B8860B',\n\\ 'darkgray': '#A9A9A9',\n\\ 'darkgreen': '#006400',\n\\ 'darkkhaki': '#BDB76B',\n\\ 'darkmagenta': '#8B008B',\n\\ 'darkolivegreen': '#556B2F',\n\\ 'darkorange': '#FF8C00',\n\\ 'darkorchid': '#9932CC',\n\\ 'darkred': '#8B0000',\n\\ 'darksalmon': '#E9967A',\n\\ 'darkseagreen': '#8FBC8F',\n\\ 'darkslateblue': '#483D8B',\n\\ 'darkslategray': '#2F4F4F',\n\\ 'darkturquoise': '#00CED1',\n\\ 'darkviolet': '#9400D3',\n\\ 'deeppink': '#FF1493',\n\\ 'deepskyblue': '#00BFFF',\n\\ 'dimgray': '#696969',\n\\ 'dodgerblue': '#1E90FF',\n\\ 'firebrick': '#B22222',\n\\ 'floralwhite': '#FFFAF0',\n\\ 'forestgreen': '#228B22',\n\\ 'fuchsia': '#FF00FF',\n\\ 'gainsboro': '#DCDCDC',\n\\ 'ghostwhite': '#F8F8FF',\n\\ 'gold': '#FFD700',\n\\ 'goldenrod': '#DAA520',\n\\ 'gray': '#808080',\n\\ 'green': '#008000',\n\\ 'greenyellow': '#ADFF2F',\n\\ 'honeydew': '#F0FFF0',\n\\ 'hotpink': '#FF69B4',\n\\ 'indianred': '#CD5C5C',\n\\ 'indigo': '#4B0082',\n\\ 'ivory': '#FFFFF0',\n\\ 'khaki': '#F0E68C',\n\\ 'lavender': '#E6E6FA',\n\\ 'lavenderblush': '#FFF0F5',\n\\ 'lawngreen': '#7CFC00',\n\\ 'lemonchiffon': '#FFFACD',\n\\ 'lightblue': '#ADD8E6',\n\\ 'lightcoral': '#F08080',\n\\ 'lightcyan': '#E0FFFF',\n\\ 'lightgoldenrodyellow': '#FAFAD2',\n\\ 'lightgray': '#D3D3D3',\n\\ 'lightgreen': '#90EE90',\n\\ 'lightpink': '#FFB6C1',\n\\ 'lightsalmon': '#FFA07A',\n\\ 'lightseagreen': '#20B2AA',\n\\ 'lightskyblue': '#87CEFA',\n\\ 'lightslategray': '#778899',\n\\ 'lightsteelblue': '#B0C4DE',\n\\ 'lightyellow': '#FFFFE0',\n\\ 'lime': '#00FF00',\n\\ 'limegreen': '#32CD32',\n\\ 'linen': '#FAF0E6',\n\\ 'magenta': '#FF00FF',\n\\ 'maroon': '#800000',\n\\ 'mediumaquamarine': '#66CDAA',\n\\ 'mediumblue': '#0000CD',\n\\ 'mediumorchid': '#BA55D3',\n\\ 'mediumpurple': '#9370D8',\n\\ 'mediumseagreen': '#3CB371',\n\\ 'mediumslateblue': '#7B68EE',\n\\ 'mediumspringgreen': '#00FA9A',\n\\ 'mediumturquoise': '#48D1CC',\n\\ 'mediumvioletred': '#C71585',\n\\ 'midnightblue': '#191970',\n\\ 'mintcream': '#F5FFFA',\n\\ 'mistyrose': '#FFE4E1',\n\\ 'moccasin': '#FFE4B5',\n\\ 'navajowhite': '#FFDEAD',\n\\ 'navy': '#000080',\n\\ 'oldlace': '#FDF5E6',\n\\ 'olive': '#808000',\n\\ 'olivedrab': '#6B8E23',\n\\ 'orange': '#FFA500',\n\\ 'orangered': '#FF4500',\n\\ 'orchid': '#DA70D6',\n\\ 'palegoldenrod': '#EEE8AA',\n\\ 'palegreen': '#98FB98',\n\\ 'paleturquoise': '#AFEEEE',\n\\ 'palevioletred': '#D87093',\n\\ 'papayawhip': '#FFEFD5',\n\\ 'peachpuff': '#FFDAB9',\n\\ 'peru': '#CD853F',\n\\ 'pink': '#FFC0CB',\n\\ 'plum': '#DDA0DD',\n\\ 'powderblue': '#B0E0E6',\n\\ 'purple': '#800080',\n\\ 'red': '#FF0000',\n\\ 'rosybrown': '#BC8F8F',\n\\ 'royalblue': '#4169E1',\n\\ 'saddlebrown': '#8B4513',\n\\ 'salmon': '#FA8072',\n\\ 'sandybrown': '#F4A460',\n\\ 'seagreen': '#2E8B57',\n\\ 'seashell': '#FFF5EE',\n\\ 'sienna': '#A0522D',\n\\ 'silver': '#C0C0C0',\n\\ 'skyblue': '#87CEEB',\n\\ 'slateblue': '#6A5ACD',\n\\ 'slategray': '#708090',\n\\ 'snow': '#FFFAFA',\n\\ 'springgreen': '#00FF7F',\n\\ 'steelblue': '#4682B4',\n\\ 'tan': '#D2B48C',\n\\ 'teal': '#008080',\n\\ 'thistle': '#D8BFD8',\n\\ 'tomato': '#FF6347',\n\\ 'turquoise': '#40E0D0',\n\\ 'violet': '#EE82EE',\n\\ 'wheat': '#F5DEB3',\n\\ 'white': '#FFFFFF',\n\\ 'whitesmoke': '#F5F5F5',\n\\ 'yellow': '#FFFF00',\n\\ 'yellowgreen': '#9ACD32'\n\\ }\n\n\" Returns an approximate grey index for the given grey level\nfun! s:grey_number(x)\n  if &t_Co == 88\n    if a:x < 23\n      return 0\n    elseif a:x < 69\n      return 1\n    elseif a:x < 103\n      return 2\n    elseif a:x < 127\n      return 3\n    elseif a:x < 150\n      return 4\n    elseif a:x < 173\n      return 5\n    elseif a:x < 196\n      return 6\n    elseif a:x < 219\n      return 7\n    elseif a:x < 243\n      return 8\n    else\n      return 9\n    endif\n  else\n    if a:x < 14\n      return 0\n    else\n      let l:n = (a:x - 8) / 10\n      let l:m = (a:x - 8) % 10\n      if l:m < 5\n        return l:n\n      else\n        return l:n + 1\n      endif\n    endif\n  endif\nendfun\n\n\" Returns the actual grey level represented by the grey index\nfun! s:grey_level(n)\n  if &t_Co == 88\n    if a:n == 0\n      return 0\n    elseif a:n == 1\n      return 46\n    elseif a:n == 2\n      return 92\n    elseif a:n == 3\n      return 115\n    elseif a:n == 4\n      return 139\n    elseif a:n == 5\n      return 162\n    elseif a:n == 6\n      return 185\n    elseif a:n == 7\n      return 208\n    elseif a:n == 8\n      return 231\n    else\n      return 255\n    endif\n  else\n    if a:n == 0\n      return 0\n    else\n      return 8 + (a:n * 10)\n    endif\n  endif\nendfun\n\n\" Returns the palette index for the given grey index\nfun! s:grey_colour(n)\n  if &t_Co == 88\n    if a:n == 0\n      return 16\n    elseif a:n == 9\n      return 79\n    else\n      return 79 + a:n\n    endif\n  else\n    if a:n == 0\n      return 16\n    elseif a:n == 25\n      return 231\n    else\n      return 231 + a:n\n    endif\n  endif\nendfun\n\n\" Returns an approximate colour index for the given colour level\nfun! s:rgb_number(x)\n  if &t_Co == 88\n    if a:x < 69\n      return 0\n    elseif a:x < 172\n      return 1\n    elseif a:x < 230\n      return 2\n    else\n      return 3\n    endif\n  else\n    if a:x < 75\n      return 0\n    else\n      let l:n = (a:x - 55) / 40\n      let l:m = (a:x - 55) % 40\n      if l:m < 20\n        return l:n\n      else\n        return l:n + 1\n      endif\n    endif\n  endif\nendfun\n\n\" Returns the palette index for the given R/G/B colour indices\nfun! s:rgb_colour(x, y, z)\n  if &t_Co == 88\n    return 16 + (a:x * 16) + (a:y * 4) + a:z\n  else\n    return 16 + (a:x * 36) + (a:y * 6) + a:z\n  endif\nendfun\n\n\" Returns the actual colour level for the given colour index\nfun! s:rgb_level(n)\n  if &t_Co == 88\n    if a:n == 0\n      return 0\n    elseif a:n == 1\n      return 139\n    elseif a:n == 2\n      return 205\n    else\n      return 255\n    endif\n  else\n    if a:n == 0\n      return 0\n    else\n      return 55 + (a:n * 40)\n    endif\n  endif\nendfun\n\n\" Returns the palette index to approximate the given R/G/B colour levels\nfun! s:colour(r, g, b)\n  \" Get the closest grey\n  let l:gx = s:grey_number(a:r)\n  let l:gy = s:grey_number(a:g)\n  let l:gz = s:grey_number(a:b)\n\n  \" Get the closest colour\n  let l:x = s:rgb_number(a:r)\n  let l:y = s:rgb_number(a:g)\n  let l:z = s:rgb_number(a:b)\n\n  if l:gx == l:gy && l:gy == l:gz\n    \" There are two possibilities\n    let l:dgr = s:grey_level(l:gx) - a:r\n    let l:dgg = s:grey_level(l:gy) - a:g\n    let l:dgb = s:grey_level(l:gz) - a:b\n    let l:dgrey = (l:dgr * l:dgr) + (l:dgg * l:dgg) + (l:dgb * l:dgb)\n    let l:dr = s:rgb_level(l:gx) - a:r\n    let l:dg = s:rgb_level(l:gy) - a:g\n    let l:db = s:rgb_level(l:gz) - a:b\n    let l:drgb = (l:dr * l:dr) + (l:dg * l:dg) + (l:db * l:db)\n    if l:dgrey < l:drgb\n      \" Use the grey\n      return s:grey_colour(l:gx)\n    else\n      \" Use the colour\n      return s:rgb_colour(l:x, l:y, l:z)\n    endif\n  else\n    \" Only one possibility\n    return s:rgb_colour(l:x, l:y, l:z)\n  endif\nendfun\n\nfunction! coc#color#term2rgb(term) abort\n  if a:term < 0 || a:term > 255\n    return '#000000'\n  endif\n  return s:xterm_colors[a:term]\nendfunction\n\nfunction! coc#color#rgb2term(rgb)\n  let l:r = (\"0x\" . strpart(a:rgb, 0, 2)) + 0\n  let l:g = (\"0x\" . strpart(a:rgb, 2, 2)) + 0\n  let l:b = (\"0x\" . strpart(a:rgb, 4, 2)) + 0\n  return s:colour(l:r, l:g, l:b)\nendfunction\n\nfunction! coc#color#rgbToHex(...)\n  let [r, g, b] = ( a:0==1 ? a:1 : a:000 )\n  let num = printf('%02x', float2nr(r)) . ''\n        \\ . printf('%02x', float2nr(g)) . ''\n        \\ . printf('%02x', float2nr(b)) . ''\n  return '#' . num\nendfunction\n\nfunction! coc#color#hexToRgb(color)\n  if type(a:color) == 2\n    let color = printf('%x', a:color)\n  else\n    let color = a:color\n  end\n  let matches = matchlist(color, s:patterns['hex'])\n  let factor  = 0x1\n  if empty(matches)\n    let matches = matchlist(color, s:patterns['shortHex'])\n    let factor  = 0x10\n    end\n    if len(matches) < 4\n      echohl Error\n      echom 'Couldnt parse ' . string(color) . ' ' .  string(matches)\n      echohl None\n      return\n    end\n    let r = str2nr(matches[1], 16) * factor\n    let g = str2nr(matches[2], 16) * factor\n    let b = str2nr(matches[3], 16) * factor\n  return [r, g, b]\nendfunction\n\nfunction! coc#color#lighten(color, ...)\n  let amount = a:0 ?\n        \\(type(a:1) < 2 ?\n        \\str2float(a:1) : a:1 )\n        \\: 5\n  let rgb = coc#color#hexToRgb(a:color)\n  let rgb = map(rgb, 'v:val + amount*(255 - v:val)/255')\n  let rgb = map(rgb, 'v:val > 255.0 ? 255.0 : v:val')\n  let rgb = map(rgb, 'float2nr(v:val)')\n  let hex = coc#color#rgbToHex(rgb)\n  return hex\nendfunction\n\nfunction! coc#color#darken(color, ...)\n  let amount = a:0 ?\n        \\(type(a:1) < 2 ?\n        \\str2float(a:1) : a:1 )\n        \\: 5.0\n  let rgb = coc#color#hexToRgb(a:color)\n  let rgb = map(rgb, 'v:val - amount*v:val/255')\n  let rgb = map(rgb, 'v:val < 0.0 ? 0.0 : v:val')\n  let rgb = map(rgb, 'float2nr(v:val)')\n  let hex = coc#color#rgbToHex(rgb)\n  return hex\nendfu\n\nfunction! coc#color#luminance(rgb) abort\n  let vals = []\n  for val in a:rgb\n    let val = (val + 0.0)/255\n    if val <= 0.03928\n      call add(vals, val/12.92)\n    else\n      call add(vals, pow((val + 0.055)/1.055, 2.4))\n    endif\n  endfor\n  return vals[0] * 0.2126 + vals[1] * 0.7152 + vals[2] * 0.0722\nendfunction\n\nfunction! coc#color#contrast(rgb1, rgb2) abort\n  let lnum1 = coc#color#luminance(a:rgb1)\n  let lnum2 = coc#color#luminance(a:rgb2)\n  let brightest = lnum1 > lnum2 ? lnum1 : lnum2\n  let darkest = lnum1 < lnum2 ? lnum1 : lnum2\n  return (brightest + 0.05) / (darkest + 0.05)\nendfunction\n\nfunction! coc#color#hex_contrast(hex1, hex2) abort\n  return  coc#color#contrast(coc#color#hexToRgb(a:hex1), coc#color#hexToRgb(a:hex2))\nendfunction\n\nfunction! coc#color#nameToHex(name, term) abort\n  if a:term\n    return has_key(s:xterm_16colors, a:name) ? s:xterm_16colors[a:name] : v:null\n  endif\n  return has_key(s:w3c_color_names, a:name) ? s:w3c_color_names[a:name] : v:null\nendfunction\n\n\" [r, g, b] ['255', '255', '255']\n\" return ['65535', '65535', '65535'] or return v:false to cancel\nfunction! coc#color#pick_color(default_color)\n  if has('mac')\n    let default_color = map(a:default_color, {idx, val -> str2nr(val) * 65535 / 255 })\n    \" This is the AppleScript magic:\n    let ascrpt = ['-e \"tell application \\\"' . s:app . '\\\"\"',\n          \\ '-e \"' . s:activate . '\"',\n          \\ \"-e \\\"set AppleScript's text item delimiters to {\\\\\\\",\\\\\\\"}\\\"\",\n          \\ '-e \"set theColor to (choose color default color {' . default_color[0] . \", \" . default_color[1] . \", \" . default_color[2] . '}) as text\"',\n          \\ '-e \"' . s:quit . '\"',\n          \\ '-e \"end tell\"',\n          \\ '-e \"return theColor\"']\n    let res = trim(system(\"osascript \" . join(ascrpt, ' ') . \" 2>/dev/null\"))\n    if empty(res)\n      return v:false\n    else\n      return split(trim(res), ',')\n    endif\n  endif\n\n  let hex_color = printf('#%02x%02x%02x', a:default_color[0], a:default_color[1], a:default_color[2])\n\n  if has('unix')\n    if executable('zenity')\n      let res = trim(system('zenity --title=\"Select a color\" --color-selection --color=\"' . hex_color . '\" 2> /dev/null'))\n      if empty(res)\n        return v:false\n      else\n        \" res format is rgb(255,255,255)\n        return map(split(res[4:-2], ','), {idx, val -> string(str2nr(trim(val)) * 65535 / 255)})\n      endif\n    endif\n  endif\n\n  let rgb = v:false\n  if !has('python')\n    echohl Error | echom 'python support required, checkout :echo has(''python'')' | echohl None\n    return\n  endif\n  try\n    execute 'py import gtk'\n  catch /.*/\n    echohl Error | echom 'python gtk module not found' | echohl None\n    return\n  endtry\npython << endpython\n\nimport vim\nimport gtk, sys\n\n# message strings\nwnd_title_insert = \"Insert a color\"\n\ncsd = gtk.ColorSelectionDialog(wnd_title_insert)\ncs = csd.colorsel\n\ncs.set_current_color(gtk.gdk.color_parse(vim.eval(\"hex_color\")))\n\ncs.set_current_alpha(65535)\ncs.set_has_opacity_control(False)\n# cs.set_has_palette(int(vim.eval(\"s:display_palette\")))\n\nif csd.run()==gtk.RESPONSE_OK:\n    c = cs.get_current_color()\n    s = [str(int(c.red)),',',str(int(c.green)),',',str(int(c.blue))]\n    thecolor = ''.join(s)\n    vim.command(\":let rgb = split('%s',',')\" % thecolor)\n\ncsd.destroy()\n\nendpython\n  return rgb\nendfunction\n"
  },
  {
    "path": "autoload/coc/compat.vim",
    "content": "scriptencoding utf-8\nlet s:is_vim = !has('nvim')\n\n\" first window id for bufnr\n\" builtin bufwinid returns window of current tab only\nfunction! coc#compat#buf_win_id(bufnr) abort\n  return get(win_findbuf(a:bufnr), 0, -1)\nendfunction\n\nfunction! coc#compat#buf_set_lines(bufnr, start, end, replacement) abort\n  if bufloaded(a:bufnr)\n    call coc#compat#call('buf_set_lines', [a:bufnr, a:start, a:end, 0, a:replacement])\n  endif\nendfunction\n\nfunction! coc#compat#buf_line_count(bufnr) abort\n  if !bufloaded(a:bufnr)\n    return 0\n  endif\n  return coc#compat#call('buf_line_count', [a:bufnr])\nendfunction\n\n\" remove keymap for bufnr, not throw error\nfunction! coc#compat#buf_del_keymap(bufnr, mode, lhs) abort\n  if a:bufnr != 0 && !bufexists(a:bufnr)\n    return\n  endif\n  try\n    call coc#compat#call('buf_del_keymap', [a:bufnr, a:mode, a:lhs])\n  catch /E31/\n    \" ignore keymap doesn't exist\n  endtry\nendfunction\n\nfunction! coc#compat#buf_add_keymap(bufnr, mode, lhs, rhs, opts) abort\n  if a:bufnr != 0 && !bufexists(a:bufnr)\n    return\n  endif\n  call coc#compat#call('buf_set_keymap', [a:bufnr, a:mode, a:lhs, a:rhs, a:opts])\nendfunction\n\nfunction! coc#compat#clear_matches(winid) abort\n  silent! call clearmatches(a:winid)\nendfunction\n\nfunction! coc#compat#matchaddpos(group, pos, priority, winid) abort\n  let curr = win_getid()\n  if curr == a:winid\n    call matchaddpos(a:group, a:pos, a:priority, -1)\n  else\n    call matchaddpos(a:group, a:pos, a:priority, -1, {'window': a:winid})\n  endif\nendfunction\n\n\" hlGroup, pos, priority\nfunction! coc#compat#matchaddgroups(winid, groups) abort\n  for group in a:groups\n    call matchaddpos(group['hlGroup'], [group['pos']], group['priority'], -1, {'window': a:winid})\n  endfor\nendfunction\n\n\" Delete var, not throw version.\nfunction! coc#compat#del_var(name) abort\n  if s:is_vim\n    execute 'unlet! g:' . a:name\n  else\n    silent! call nvim_del_var(a:name)\n  endif\nendfunction\n\nfunction! coc#compat#tabnr_id(tabnr) abort\n  if s:is_vim\n    return coc#api#TabNrId(a:tabnr)\n  endif\n  return nvim_list_tabpages()[a:tabnr - 1]\nendfunction\n\nfunction! coc#compat#list_runtime_paths() abort\n  return coc#compat#call('list_runtime_paths', [])\nendfunction\n\nfunction! coc#compat#buf_execute(bufnr, cmds, ...) abort\n  let silent = get(a:, 1, 'silent')\n  if s:is_vim\n    let cmds = copy(a:cmds)->map({_, val -> 'legacy ' . val})\n    call coc#api#BufExecute(a:bufnr, cmds, silent)\n  else\n  endif\nendfunction\n\nfunction coc#compat#execute(command, ...) abort\n  return execute(a:command, get(a:, 1, 'silent'))\nendfunction\n\nfunction! coc#compat#eval(expr) abort\n  return eval(a:expr)\nendfunction\n\nfunction coc#compat#win_execute(id, command, ...) abort\n  return win_execute(a:id, a:command, get(a:, 1, 'silent'))\nendfunction\n\n\" call api function on vim or neovim\nfunction! coc#compat#call(fname, args) abort\n  if s:is_vim\n    return call('coc#api#' . toupper(a:fname[0]) . a:fname[1:], a:args)\n  endif\n  return call('nvim_' . a:fname, a:args)\nendfunction\n\nfunction! coc#compat#send_error(fname, tracestack) abort\n    let msg = v:exception .. ' - on \"' .. a:fname .. '\"'\n    if a:tracestack\n      let msg = msg .. \" \\n\" .. v:throwpoint\n    endif\n    call coc#rpc#notify('nvim_error_event', [0, msg])\nendfunction\n\" vim: set sw=2 ts=2 sts=2 et tw=78 foldlevel=0:\n"
  },
  {
    "path": "autoload/coc/cursor.vim",
    "content": "scriptencoding utf-8\n\n\" Position of cursor relative to screen cell\nfunction! coc#cursor#screen_pos() abort\n  let nr = winnr()\n  let [row, col] = win_screenpos(nr)\n  return [row + winline() - 2, col + wincol() - 2]\nendfunction\n\nfunction! coc#cursor#move_by_col(delta)\n  let pos = getcurpos()\n  call cursor(pos[1], pos[2] + a:delta)\nendfunction\n\n\" Get cursor position.\nfunction! coc#cursor#position()\n  let line = getline('.')\n  return [line('.') - 1, coc#string#character_index(line, col('.') - 1)]\nendfunction\n\n\" Move cursor to position.\nfunction! coc#cursor#move_to(line, character) abort\n  let content = getline(a:line + 1)\n  call cursor(a:line + 1, coc#string#byte_index(content, a:character) + 1)\nendfunction\n\n\" Character offset of current cursor, vim provide bytes offset only.\nfunction! coc#cursor#char_offset() abort\n  let offset = 0\n  let lnum = line('.')\n  for i in range(1, lnum)\n    if i == lnum\n      let offset += strchars(strpart(getline('.'), 0, col('.')-1))\n    else\n      let offset += strchars(getline(i)) + 1\n    endif\n  endfor\n  return offset\nendfunction\n\n\" Returns latest selection range\nfunction! coc#cursor#get_selection(char) abort\n  let m = a:char ? 'char' : visualmode()\n  if empty(m)\n    return v:null\n  endif\n  let [_, sl, sc, soff] = getpos(m ==# 'char' ? \"'[\" : \"'<\")\n  let [_, el, ec, eoff] = getpos(m ==# 'char' ? \"']\" : \"'>\")\n  let start_idx = coc#string#character_index(getline(sl), sc - 1)\n  if m ==# 'V'\n    return [sl - 1, start_idx, el, 0]\n  endif\n  let line = getline(el)\n  let end_idx = coc#string#character_index(line, ec - 1)\n  if m !=# 'char'\n    if &selection ==# 'exclusive' && !(sl == el && start_idx == end_idx)\n      let end_idx = end_idx - 1\n    endif\n    let end_idx = end_idx == coc#string#character_length(line) ? end_idx : end_idx + 1\n  endif\n  return [sl - 1, start_idx, el - 1, end_idx]\nendfunction\n"
  },
  {
    "path": "autoload/coc/dialog.vim",
    "content": "scriptencoding utf-8\nlet s:is_vim = !has('nvim')\nlet s:root = expand('<sfile>:h:h:h')\nlet s:prompt_win_bufnr = 0\nlet s:list_win_bufnr = 0\nlet s:prompt_win_width = get(g:, 'coc_prompt_win_width', 32)\nlet s:frames = ['·  ', '·· ', '···', ' ··', '  ·', '   ']\nlet s:sign_group = 'PopUpCocDialog'\nlet s:detail_bufnr = 0\nlet s:term_support = s:is_vim ? has('terminal') : 1\n\n\" Float window aside pum\nfunction! coc#dialog#create_pum_float(lines, config) abort\n  let winid = coc#float#get_float_by_kind('pumdetail')\n  if empty(a:lines) || !coc#pum#visible()\n    if winid\n      call coc#float#close(winid)\n    endif\n    return\n  endif\n  let pumbounding = coc#pum#info()\n  let border = get(a:config, 'border', [])\n  let pw = pumbounding['width'] + (pumbounding['border'] ? 0 : get(pumbounding, 'scrollbar', 0))\n  let rp = &columns - pumbounding['col'] - pw\n  let showRight = pumbounding['col'] > rp ? 0 : 1\n  let maxWidth = showRight ? min([rp - 1, a:config['maxWidth']]) : min([pumbounding['col'] - 1, a:config['maxWidth']])\n  let bh = get(border, 0 ,0) + get(border, 2, 0)\n  let maxHeight = &lines - pumbounding['row'] - &cmdheight - 1 - bh\n  if maxWidth <= 2 || maxHeight < 1\n    return v:null\n  endif\n  let width = 0\n  for line in a:lines\n    let dw = max([1, strdisplaywidth(line)])\n    let width = max([width, dw + 2])\n  endfor\n  let width = width < maxWidth ? width : maxWidth\n  let ch = coc#string#content_height(a:lines, width - 2)\n  let height = ch < maxHeight ? ch : maxHeight\n  let lines = map(a:lines, {_, s -> s =~# '^─' ? repeat('─', width - 2 + (s:is_vim && ch > height ? -1 : 0)) : s})\n  let opts = {\n        \\ 'lines': lines,\n        \\ 'highlights': get(a:config, 'highlights', []),\n        \\ 'relative': 'editor',\n        \\ 'col': showRight ? pumbounding['col'] + pw : pumbounding['col'] - width,\n        \\ 'row': pumbounding['row'],\n        \\ 'height': height,\n        \\ 'width': width - 2 + (s:is_vim && ch > height ? -1 : 0),\n        \\ 'scrollinside': showRight ? 0 : 1,\n        \\ 'codes': get(a:config, 'codes', []),\n        \\ }\n  for key in ['border', 'highlight', 'borderhighlight', 'winblend', 'focusable', 'shadow', 'rounded', 'title']\n    if has_key(a:config, key)\n      let opts[key] = a:config[key]\n    endif\n  endfor\n  call s:close_auto_hide_wins(winid)\n  let result = coc#float#create_float_win(winid, s:detail_bufnr, opts)\n  if empty(result)\n    return\n  endif\n  let s:detail_bufnr = result[1]\n  call setwinvar(result[0], 'kind', 'pumdetail')\n  if !s:is_vim\n    call coc#float#nvim_scrollbar(result[0])\n  endif\nendfunction\n\n\" Float window below/above cursor\nfunction! coc#dialog#create_cursor_float(winid, bufnr, lines, config) abort\n  if coc#prompt#activated()\n    return v:null\n  endif\n  let pumAlignTop = get(a:config, 'pumAlignTop', 0)\n  let modes = get(a:config, 'modes', ['n', 'i', 'ic', 's'])\n  let mode = mode()\n  let currbuf = bufnr('%')\n  let pos = [line('.'), col('.')]\n  if index(modes, mode) == -1\n    return v:null\n  endif\n  let dimension = coc#dialog#get_config_cursor(a:lines, a:config)\n  if empty(dimension)\n    return v:null\n  endif\n  if coc#pum#visible() && ((pumAlignTop && dimension['row'] <0)|| (!pumAlignTop && dimension['row'] > 0))\n    return v:null\n  endif\n  let width = dimension['width']\n  let lines = map(a:lines, {_, s -> s =~# '^─' ? repeat('─', width) : s})\n  let config = extend(extend({'lines': lines, 'relative': 'cursor'}, a:config), dimension)\n  call s:close_auto_hide_wins(a:winid)\n  let res = coc#float#create_float_win(a:winid, a:bufnr, config)\n  if empty(res)\n    return v:null\n  endif\n  let alignTop = dimension['row'] < 0\n  let winid = res[0]\n  let bufnr = res[1]\n  call win_execute(winid, 'setl nonumber')\n  if s:is_vim\n    call timer_start(0, { -> execute('redraw')})\n  else\n    redraw\n    call coc#float#nvim_scrollbar(winid)\n  endif\n  return [currbuf, pos, winid, bufnr, alignTop]\nendfunction\n\n\" Use terminal buffer\nfunction! coc#dialog#_create_prompt_vim(title, default, opts) abort\n  execute 'hi link CocPopupTerminal '.get(a:opts, 'highlight', 'CocFloating')\n  let node =  expand(get(g:, 'coc_node_path', 'node'))\n  let placeHolder = get(a:opts, 'placeHolder', '')\n  let opt = {\n        \\ 'term_rows': 1,\n        \\ 'hidden': 1,\n        \\ 'term_finish': 'close',\n        \\ 'norestore': 1,\n        \\ 'tty_type': 'conpty',\n        \\ 'term_highlight': 'CocPopupTerminal'\n        \\ }\n  let bufnr = term_start([node, s:root . '/bin/prompt.js', a:default, empty(placeHolder) ? '' : placeHolder], opt)\n  call term_setapi(bufnr, 'Coc')\n  call setbufvar(bufnr, 'current', type(a:default) == v:t_string ? a:default : '')\n  let res = s:create_prompt_win(bufnr, a:title, a:default, a:opts)\n  if empty(res)\n    return\n  endif\n  let winid = res[0]\n  \" call win_gotoid(winid)\n  call coc#util#do_autocmd('CocOpenFloatPrompt')\n  let pos = popup_getpos(winid)\n  \" width height row col\n  let dimension = [pos['width'], pos['height'], pos['line'] - 1, pos['col'] - 1]\n  return [bufnr, winid, dimension]\nendfunction\n\n\" Use normal buffer on neovim\nfunction! coc#dialog#_create_prompt_nvim(title, default, opts) abort\n  let result = s:create_prompt_win(s:prompt_win_bufnr, a:title, a:default, a:opts)\n  if empty(result)\n    return\n  endif\n  let winid = result[0]\n  let s:prompt_win_bufnr = result[1]\n  let bufnr = s:prompt_win_bufnr\n  call sign_unplace(s:sign_group, { 'buffer': s:prompt_win_bufnr })\n  call nvim_set_current_win(winid)\n  inoremap <buffer> <C-a> <Home>\n  inoremap <buffer><expr><C-e> pumvisible() ? \"\\<C-e>\" : \"\\<End>\"\n  exe 'imap <silent><nowait><buffer> <esc> <esc><esc>'\n  exe 'nnoremap <silent><buffer> <esc> :call coc#float#close('.winid.')<CR>'\n  exe 'inoremap <silent><expr><nowait><buffer> <cr> \"\\<C-r>=coc#dialog#prompt_insert()\\<cr>\\<esc>\"'\n  if get(a:opts, 'list', 0)\n    for key in ['<C-j>', '<C-k>', '<C-n>', '<C-p>', '<up>', '<down>', '<C-f>', '<C-b>', '<C-space>']\n      let escaped = key ==# '<C-space>' ? '\\<C-@\\>' : substitute(key, '\\(<\\|>\\)', '\\\\\\1', 'g')\n      exe 'inoremap <nowait><buffer> '.key.' <Cmd>call coc#rpc#notify(\"PromptKeyPress\", ['.bufnr.', \"'.escaped.'\"])<CR>'\n    endfor\n  endif\n  let mode = mode()\n  if mode ==# 'n'\n    call feedkeys('A', 'int')\n  elseif mode ==# 'i'\n    call feedkeys(\"\\<end>\", 'int')\n  else\n    call feedkeys(\"\\<esc>A\", 'int')\n  endif\n  let placeHolder = get(a:opts, 'placeHolder', '')\n  if empty(a:default) && !empty(placeHolder)\n    let src_id = coc#highlight#create_namespace('input-box')\n    call nvim_buf_set_extmark(bufnr, src_id, 0, 0, {\n          \\ 'virt_text': [[placeHolder, 'CocInputBoxVirtualText']],\n          \\ 'virt_text_pos': 'overlay',\n          \\ })\n  endif\n  call coc#util#do_autocmd('CocOpenFloatPrompt')\n  let id = coc#float#get_related(winid, 'border')\n  let pos = nvim_win_get_position(id)\n  let dimension = [nvim_win_get_width(id), nvim_win_get_height(id), pos[0], pos[1]]\n  return [bufnr, winid, dimension]\nendfunction\n\n\" Create float window for input\nfunction! coc#dialog#create_prompt_win(title, default, opts) abort\n  call s:close_auto_hide_wins()\n  if s:is_vim\n    if !s:term_support\n      \" use popup_menu or inputlist instead\n      let pickItems = get(a:opts, 'quickpick', [])\n      if len(pickItems) > 0\n        call coc#ui#quickpick(a:title, pickItems, {err, res -> s:on_quickpick_selected(err, res)})\n      else\n        call inputsave()\n        let value = input(a:title.':', a:default)\n        call inputrestore()\n        if empty(value)\n          \" Cancel\n          call timer_start(50, { -> coc#rpc#notify('CocAutocmd', ['BufWinLeave', -1, -1])})\n        else\n          call timer_start(50, { -> coc#rpc#notify('PromptInsert', [value, -1])})\n        endif\n      endif\n      return [-1, -1, [0, 0, 0, 0]]\n    endif\n    return coc#dialog#_create_prompt_vim(a:title, a:default, a:opts)\n  endif\n  return  coc#dialog#_create_prompt_nvim(a:title, a:default, a:opts)\nendfunction\n\n\" Create list window under target window\nfunction! coc#dialog#create_list(target, dimension, opts) abort\n  if a:target < 0\n    return [-1, -1]\n  endif\n  let maxHeight = get(a:opts, 'maxHeight', 30)\n  let height = get(a:opts, 'linecount', 1)\n  let height = min([maxHeight, height, &lines - &cmdheight - 1 - a:dimension['row'] + a:dimension['height']])\n  let chars = get(a:opts, 'rounded', 1) ? ['╯', '╰'] : ['┘', '└']\n  let width = a:dimension['width'] - 2\n  let config = extend(copy(a:opts), {\n      \\ 'relative': 'editor',\n      \\ 'row': a:dimension['row'] + a:dimension['height'],\n      \\ 'col': a:dimension['col'],\n      \\ 'width': width,\n      \\ 'height': height,\n      \\ 'border': [1, 1, 1, 1],\n      \\ 'scrollinside': 1,\n      \\ 'borderchars': extend(['─', '│', '─', '│', '├', '┤'], chars)\n      \\ })\n  let bufnr = 0\n  let result = coc#float#create_float_win(0, s:list_win_bufnr, config)\n  if empty(result)\n    return\n  endif\n  let winid = result[0]\n  call coc#float#add_related(winid, a:target)\n  call setwinvar(winid, 'auto_height', get(a:opts, 'autoHeight', 1))\n  call setwinvar(winid, 'core_width', width)\n  call setwinvar(winid, 'max_height', maxHeight)\n  call setwinvar(winid, 'target_winid', a:target)\n  call setwinvar(winid, 'kind', 'list')\n  call coc#dialog#check_scroll_vim(a:target)\n  return result\nendfunction\n\n\" Create menu picker for pick single item\nfunction! coc#dialog#create_menu(lines, config) abort\n  call s:close_auto_hide_wins()\n  let highlight = get(a:config, 'highlight', 'CocFloating')\n  let borderhighlight = get(a:config, 'borderhighlight', [highlight])\n  let relative = get(a:config, 'relative', 'cursor')\n  let lines = a:lines\n  let content = get(a:config, 'content', '')\n  let maxWidth = get(a:config, 'maxWidth', 80)\n  let highlights = get(a:config, 'highlights', [])\n  let contentCount = 0\n  if !empty(content)\n    let contentLines = coc#string#reflow(split(content, '\\r\\?\\n'), maxWidth)\n    let contentCount = len(contentLines)\n    let lines = extend(contentLines, lines)\n    if !empty(highlights)\n      for item in highlights\n        let item['lnum'] = item['lnum'] + contentCount\n      endfor\n    endif\n  endif\n  let opts = {\n    \\ 'lines': lines,\n    \\ 'highlight': highlight,\n    \\ 'title': get(a:config, 'title', ''),\n    \\ 'borderhighlight': borderhighlight,\n    \\ 'maxWidth': maxWidth,\n    \\ 'maxHeight': get(a:config, 'maxHeight', 80),\n    \\ 'rounded': get(a:config, 'rounded', 0),\n    \\ 'border': [1, 1, 1, 1],\n    \\ 'highlights': highlights,\n    \\ 'relative': relative,\n    \\ }\n  if relative ==# 'editor'\n    let dimension = coc#dialog#get_config_editor(lines, opts)\n  else\n    let dimension = coc#dialog#get_config_cursor(lines, opts)\n  endif\n  call extend(opts, dimension)\n  let ids = coc#float#create_float_win(0, s:prompt_win_bufnr, opts)\n  if empty(ids)\n    return\n  endif\n  let s:prompt_win_bufnr = ids[1]\n  call coc#dialog#set_cursor(ids[0], ids[1], contentCount + 1)\n  redraw\n  if !s:is_vim\n    call coc#float#nvim_scrollbar(ids[0])\n  endif\n  return [ids[0], ids[1], contentCount]\nendfunction\n\n\" Create dialog at center of screen\nfunction! coc#dialog#create_dialog(lines, config) abort\n  call s:close_auto_hide_wins()\n  \" dialog always have borders\n  let title = get(a:config, 'title', '')\n  let buttons = get(a:config, 'buttons', [])\n  let highlight = get(a:config, 'highlight', 'CocFloating')\n  let borderhighlight = get(a:config, 'borderhighlight', [highlight])\n  let opts = {\n    \\ 'title': title,\n    \\ 'rounded': get(a:config, 'rounded', 0),\n    \\ 'relative': 'editor',\n    \\ 'border': [1,1,1,1],\n    \\ 'close': get(a:config, 'close', 1),\n    \\ 'highlight': highlight,\n    \\ 'highlights': get(a:config, 'highlights', []),\n    \\ 'buttons': buttons,\n    \\ 'borderhighlight': borderhighlight,\n    \\ 'getchar': get(a:config, 'getchar', 0)\n    \\ }\n  call extend(opts, coc#dialog#get_config_editor(a:lines, a:config))\n  let bufnr = coc#float#create_buf(0, a:lines)\n  let res =  coc#float#create_float_win(0, bufnr, opts)\n  if empty(res)\n    return\n  endif\n  if get(a:config, 'cursorline', 0)\n    call coc#dialog#place_sign(bufnr, 1)\n  endif\n  if !s:is_vim\n    redraw\n    call coc#float#nvim_scrollbar(res[0])\n  endif\n  return res\nendfunction\n\nfunction! coc#dialog#prompt_confirm(title, cb) abort\n  call s:close_auto_hide_wins()\n  if s:is_vim && exists('*popup_dialog')\n    try\n      call popup_dialog(a:title. ' (y/n)?', {\n        \\ 'highlight': 'Normal',\n        \\ 'filter': 'popup_filter_yesno',\n        \\ 'callback': {id, res -> a:cb(v:null, res)},\n        \\ 'borderchars': get(g:, 'coc_borderchars', ['─', '│', '─', '│', '╭', '╮', '╯', '╰']),\n        \\ 'borderhighlight': ['MoreMsg']\n        \\ })\n    catch /.*/\n      call a:cb(v:exception)\n    endtry\n    return\n  endif\n  let text = ' '. a:title . ' (y/n)? '\n  let maxWidth = coc#math#min(78, &columns - 2)\n  let width = coc#math#min(maxWidth, strdisplaywidth(text))\n  let maxHeight = &lines - &cmdheight - 1\n  let height = coc#math#min(maxHeight, float2nr(ceil(str2float(string(strdisplaywidth(text)))/width)))\n  let arr =  coc#float#create_float_win(0, s:prompt_win_bufnr, {\n        \\ 'col': &columns/2 - width/2 - 1,\n        \\ 'row': maxHeight/2 - height/2 - 1,\n        \\ 'width': width,\n        \\ 'height': height,\n        \\ 'border': [1,1,1,1],\n        \\ 'focusable': v:false,\n        \\ 'relative': 'editor',\n        \\ 'highlight': 'Normal',\n        \\ 'borderhighlight': 'MoreMsg',\n        \\ 'style': 'minimal',\n        \\ 'lines': [text],\n        \\ })\n  if empty(arr)\n    call a:cb('Window create failed!')\n    return\n  endif\n  let winid = arr[0]\n  let s:prompt_win_bufnr = arr[1]\n  call sign_unplace(s:sign_group, { 'buffer': s:prompt_win_bufnr })\n  let res = 0\n  redraw\n  \" same result as vim\n  while 1\n    let key = nr2char(getchar())\n    if key == \"\\<C-c>\"\n      let res = -1\n      break\n    elseif key == \"\\<esc>\" || key == 'n' || key == 'N'\n      let res = 0\n      break\n    elseif key == 'y' || key == 'Y'\n      let res = 1\n      break\n    endif\n  endw\n  call coc#float#close(winid)\n  call a:cb(v:null, res)\nendfunction\n\n\" works on neovim only\nfunction! coc#dialog#get_prompt_win() abort\n  if s:prompt_win_bufnr == 0\n    return -1\n  endif\n  return get(win_findbuf(s:prompt_win_bufnr), 0, -1)\nendfunction\n\nfunction! coc#dialog#get_config_editor(lines, config) abort\n  let title = get(a:config, 'title', '')\n  let maxheight = min([get(a:config, 'maxHeight', 78), &lines - &cmdheight - 6])\n  let maxwidth = min([get(a:config, 'maxWidth', 78), &columns - 2])\n  let buttons = get(a:config, 'buttons', [])\n  let minwidth = s:min_btns_width(buttons)\n  if maxheight <= 0 || maxwidth <= 0 || minwidth > maxwidth\n    throw 'Not enough spaces for float window'\n  endif\n  let ch = 0\n  let width = min([strdisplaywidth(title) + 1, maxwidth])\n  for line in a:lines\n    let dw = max([1, strdisplaywidth(line)])\n    if dw < maxwidth && dw > width\n      let width = dw\n    elseif dw >= maxwidth\n      let width = maxwidth\n    endif\n    let ch += float2nr(ceil(str2float(string(dw))/maxwidth))\n  endfor\n  let width = max([minwidth, width])\n  let height = coc#math#min(ch ,maxheight)\n  return {\n      \\ 'row': &lines/2 - (height + 4)/2,\n      \\ 'col': &columns/2 - (width + 2)/2,\n      \\ 'width': width,\n      \\ 'height': height,\n      \\ }\nendfunction\n\nfunction! coc#dialog#prompt_insert() abort\n  let value = getline('.')\n  call coc#rpc#notify('PromptInsert', [value, bufnr('%')])\n  return ''\nendfunction\n\n\" Dimension of window with lines relative to cursor\n\" Width & height excludes border & padding\nfunction! coc#dialog#get_config_cursor(lines, config) abort\n  let preferTop = get(a:config, 'preferTop', 0)\n  let title = get(a:config, 'title', '')\n  let border = get(a:config, 'border', [])\n  if empty(border) && len(title)\n    let border = [1, 1, 1, 1]\n  endif\n  let bh = get(border, 0, 0) + get(border, 2, 0)\n  let vh = &lines - &cmdheight - 1\n  if vh <= 0\n    return v:null\n  endif\n  let maxWidth = coc#math#min(get(a:config, 'maxWidth', &columns - 1), &columns - 1)\n  if maxWidth < 3\n    return v:null\n  endif\n  let maxHeight = coc#math#min(get(a:config, 'maxHeight', vh), vh)\n  let width = coc#math#min(40, strdisplaywidth(title)) + 3\n  for line in a:lines\n    let dw = max([1, strdisplaywidth(line)])\n    let width = max([width, dw + 2])\n  endfor\n  let width = coc#math#min(maxWidth, width)\n  let ch = coc#string#content_height(a:lines, width - 2)\n  let [lineIdx, colIdx] = coc#cursor#screen_pos()\n  \" How much we should move left\n  let offsetX = coc#math#min(get(a:config, 'offsetX', 0), colIdx)\n  let showTop = 0\n  let hb = vh - lineIdx -1\n  if lineIdx > bh + 2 && (preferTop || (lineIdx > hb && hb < ch + bh))\n    let showTop = 1\n  endif\n  let height = coc#math#min(maxHeight, ch + bh, showTop ? lineIdx - 1 : hb)\n  if height <= bh\n    return v:null\n  endif\n  let col = - max([offsetX, colIdx - (&columns - 1 - width)])\n  let row = showTop ? - height + bh : 1\n  return {\n        \\ 'row': row,\n        \\ 'col': col,\n        \\ 'width': width - 2,\n        \\ 'height': height - bh\n        \\ }\nendfunction\n\nfunction! coc#dialog#change_border_hl(winid, hlgroup) abort\n  if !hlexists(a:hlgroup)\n    return\n  endif\n  if s:is_vim\n    if coc#float#valid(a:winid)\n      call popup_setoptions(a:winid, {'borderhighlight': repeat([a:hlgroup], 4)})\n      redraw\n    endif\n  else\n    let winid = coc#float#get_related(a:winid, 'border')\n    if winid > 0\n      call setwinvar(winid, '&winhl', 'Normal:'.a:hlgroup)\n    endif\n  endif\nendfunction\n\nfunction! coc#dialog#change_title(winid, title) abort\n  if s:is_vim\n    if coc#float#valid(a:winid)\n      call popup_setoptions(a:winid, {'title': a:title})\n      redraw\n    endif\n  else\n    let winid = coc#float#get_related(a:winid, 'border')\n    if winid > 0\n      let bufnr = winbufnr(winid)\n      let line = getbufline(bufnr, 1)[0]\n      let top = strcharpart(line, 0, 1)\n            \\.repeat('─', strchars(line) - 2)\n            \\.strcharpart(line, strchars(line) - 1, 1)\n      if !empty(a:title)\n        let top = coc#string#compose(top, 1, a:title.' ')\n      endif\n      call nvim_buf_set_lines(bufnr, 0, 1, v:false, [top])\n    endif\n  endif\nendfunction\n\nfunction! coc#dialog#change_input_value(winid, bufnr, value) abort\n  if !coc#float#valid(a:winid)\n    return\n  endif\n  if win_getid() != a:winid\n    call win_gotoid(a:winid)\n  endif\n  if s:is_vim\n    if !s:term_support\n      call term_sendkeys(a:bufnr, \"\\<C-u>\\<C-k>\".a:value)\n    endif\n    \" call timer_start(3000, { -> term_sendkeys(bufnr, \"\\<C-u>\\<C-k>abcd\")})\n  else\n    let mode = mode()\n    if mode ==# 'i'\n      call feedkeys(\"\\<end>\", 'int')\n    else\n      call feedkeys(\"\\<esc>A\", 'int')\n    endif\n    \" Use complete to replace text before\n    let saved_completeopt = &completeopt\n    if saved_completeopt =~ 'menuone'\n      noa set completeopt=menu\n    endif\n    noa call complete(1, [{ 'empty': 1, 'word': a:value }])\n    call feedkeys(\"\\<C-x>\\<C-z>\", 'in')\n    execute 'noa set completeopt='.saved_completeopt\n  endif\nendfunction\n\nfunction! coc#dialog#change_loading(winid, loading) abort\n  if coc#float#valid(a:winid)\n    let winid = coc#float#get_related(a:winid, 'loading')\n    if !a:loading && winid > 0\n      call coc#float#close(winid)\n    endif\n    if a:loading && winid == 0\n      let bufnr = s:create_loading_buf()\n      if s:is_vim\n        let pos = popup_getpos(a:winid)\n        let winid = popup_create(bufnr, {\n            \\ 'line': pos['line'] + 1,\n            \\ 'col': pos['col'] + pos['width'] - 4,\n            \\ 'maxheight': 1,\n            \\ 'maxwidth': 3,\n            \\ 'zindex': 999,\n            \\ 'highlight': get(popup_getoptions(a:winid), 'highlight', 'CocFloating')\n            \\ })\n      else\n        let pos = nvim_win_get_position(a:winid)\n        let width = nvim_win_get_width(a:winid)\n        let opts = {\n            \\ 'relative': 'editor',\n            \\ 'row': pos[0],\n            \\ 'col': pos[1] + width - 3,\n            \\ 'focusable': v:false,\n            \\ 'width': 3,\n            \\ 'height': 1,\n            \\ 'style': 'minimal',\n            \\ 'zindex': 900,\n            \\ }\n        let winid = nvim_open_win(bufnr, v:false, opts)\n        call setwinvar(winid, '&winhl', getwinvar(a:winid, '&winhl'))\n      endif\n      call setwinvar(winid, 'kind', 'loading')\n      call setbufvar(bufnr, 'target_winid', a:winid)\n      call setbufvar(bufnr, 'popup', winid)\n      call coc#float#add_related(winid, a:winid)\n    endif\n  endif\nendfunction\n\n\" Update list with new lines and highlights\nfunction! coc#dialog#update_list(winid, bufnr, lines, highlights) abort\n  if coc#window#tabnr(a:winid) == tabpagenr()\n    if getwinvar(a:winid, 'auto_height', 0)\n      let row = coc#float#get_row(a:winid)\n      let width = getwinvar(a:winid, 'core_width', 80)\n      let height = s:get_height(a:lines, width)\n      let height = min([getwinvar(a:winid, 'max_height', 10), height, &lines - &cmdheight - 1 - row])\n      let curr = s:is_vim ? popup_getpos(a:winid)['core_height'] : nvim_win_get_height(a:winid)\n      let delta = height - curr\n      if delta != 0\n        call coc#float#change_height(a:winid, delta)\n      endif\n    endif\n    call coc#compat#buf_set_lines(a:bufnr, 0, -1, a:lines)\n    call coc#highlight#add_highlights(a:winid, [], a:highlights)\n    if s:is_vim\n      let target = getwinvar(a:winid, 'target_winid', -1)\n      if target != -1\n        call coc#dialog#check_scroll_vim(target)\n      endif\n      call win_execute(a:winid, 'exe 1')\n    endif\n  endif\nendfunction\n\n\" Fix width of prompt window same as list window on scrollbar change\nfunction! coc#dialog#check_scroll_vim(winid) abort\n  if s:is_vim && coc#float#valid(a:winid)\n    let winid = coc#float#get_related(a:winid, 'list')\n    if winid\n      redraw\n      let pos = popup_getpos(winid)\n      let width = pos['width'] + (pos['scrollbar'] ? 1 : 0)\n      if width != popup_getpos(a:winid)['width']\n        call popup_move(a:winid, {\n            \\ 'minwidth': width - 2,\n            \\ 'maxwidth': width - 2,\n            \\ })\n      endif\n    endif\n  endif\nendfunction\n\nfunction! coc#dialog#set_cursor(winid, bufnr, line) abort\n  if a:winid >= 0\n    if s:is_vim\n      call win_execute(a:winid, 'exe ' . max([a:line, 1]), 'silent!')\n      call popup_setoptions(a:winid, {'cursorline' : 1})\n      call popup_setoptions(a:winid, {'cursorline' : 0})\n    else\n      call nvim_win_set_cursor(a:winid, [max([a:line, 1]), 0])\n    endif\n    call coc#dialog#place_sign(a:bufnr, a:line)\n  endif\nendfunction\n\nfunction! coc#dialog#place_sign(bufnr, line) abort\n  call sign_unplace(s:sign_group, { 'buffer': a:bufnr })\n  if a:line > 0\n    call sign_place(6, s:sign_group, 'CocCurrentLine', a:bufnr, {'lnum': a:line})\n  endif\nendfunction\n\nfunction! s:create_prompt_win(bufnr, title, default, opts) abort\n  let config = s:get_prompt_dimension(a:title, a:default, a:opts)\n  return coc#float#create_float_win(0, a:bufnr, extend(config, {\n        \\ 'style': 'minimal',\n        \\ 'border': get(a:opts, 'border', [1,1,1,1]),\n        \\ 'rounded': get(a:opts, 'rounded', 1),\n        \\ 'prompt': 1,\n        \\ 'title': a:title,\n        \\ 'lines': s:is_vim ? v:null : [a:default],\n        \\ 'highlight': get(a:opts, 'highlight', 'CocFloating'),\n        \\ 'borderhighlight': [get(a:opts, 'borderhighlight', 'CocFloatBorder')],\n        \\ }))\nendfunction\n\n\" Could be center(with optional marginTop) or cursor\nfunction! s:get_prompt_dimension(title, default, opts) abort\n  let relative = get(a:opts, 'position', 'cursor') ==# 'cursor' ? 'cursor' : 'editor'\n  let curr = win_screenpos(winnr())[1] + wincol() - 2\n  let minWidth = get(a:opts, 'minWidth', s:prompt_win_width)\n  let width = min([max([strwidth(a:default) + 2, strwidth(a:title) + 2, minWidth]), &columns - 2])\n  if get(a:opts, 'maxWidth', 0)\n    let width = min([width, a:opts['maxWidth']])\n  endif\n  if relative ==# 'cursor'\n    let [lineIdx, colIdx] = coc#cursor#screen_pos()\n    if width == &columns - 2\n      let col = 0 - curr\n    else\n      let col = curr + width <= &columns - 2 ? 0 : curr + width - &columns + 2\n    endif\n    let config = {\n        \\ 'row': lineIdx == 0 ? 1 : 0,\n        \\ 'col': colIdx == 0 ? 0 : col - 1,\n        \\ }\n  else\n    let marginTop = get(a:opts, 'marginTop', v:null)\n    if marginTop is v:null\n      let row = (&lines - &cmdheight - 2) / 2\n    else\n      let row = marginTop < 2 ? 1 : min([marginTop, &columns - &cmdheight])\n    endif\n    let config = {\n          \\ 'col': float2nr((&columns - width) / 2),\n          \\ 'row': row - s:is_vim,\n          \\ }\n  endif\n  return extend(config, {'relative': relative, 'width': width, 'height': 1})\nendfunction\n\nfunction! s:min_btns_width(buttons) abort\n  if empty(a:buttons)\n    return 0\n  endif\n  let minwidth = len(a:buttons)*3 - 1\n  for txt in a:buttons\n    let minwidth = minwidth + strdisplaywidth(txt)\n  endfor\n  return minwidth\nendfunction\n\n\" Close windows that should auto hide\nfunction! s:close_auto_hide_wins(...) abort\n  let winids = coc#float#get_float_win_list()\n  let except = get(a:, 1, 0)\n  for id in winids\n    if except && id == except\n      continue\n    endif\n    if getwinvar(id, 'autohide', 0)\n      call coc#float#close(id)\n    endif\n  endfor\nendfunction\n\nfunction! s:create_loading_buf() abort\n  let bufnr = coc#float#create_buf(0)\n  call s:change_loading_buf(bufnr, 0)\n  return bufnr\nendfunction\n\nfunction! s:get_height(lines, width) abort\n  let height = 0\n  for line in a:lines\n    let height += float2nr(strdisplaywidth(line) / a:width) + 1\n  endfor\n  return max([1, height])\nendfunction\n\nfunction! s:change_loading_buf(bufnr, idx) abort\n  if bufloaded(a:bufnr)\n    let target = getbufvar(a:bufnr, 'target_winid', v:null)\n    if !empty(target) && !coc#float#valid(target)\n      call coc#float#close(getbufvar(a:bufnr, 'popup'))\n      return\n    endif\n    let line = get(s:frames, a:idx, '   ')\n    call setbufline(a:bufnr, 1, line)\n    call coc#highlight#add_highlight(a:bufnr, -1, 'CocNotificationProgress', 0, 0, -1)\n    let idx = a:idx == len(s:frames) - 1 ? 0 : a:idx + 1\n    call timer_start(100, { -> s:change_loading_buf(a:bufnr, idx)})\n  endif\nendfunction\n\nfunction! s:on_quickpick_selected(errMsg, res) abort\n  if !empty(a:errMsg)\n    throw a:errMsg\n  endif\n  call timer_start(50, { -> coc#rpc#notify('InputListSelect', [a:res - 1])})\nendfunction\n"
  },
  {
    "path": "autoload/coc/dict.vim",
    "content": "scriptencoding utf-8\n\nfunction! coc#dict#equal(one, two) abort\n  for key in keys(a:one)\n    if a:one[key] != a:two[key]\n      return 0\n    endif\n  endfor\n  return 1\nendfunction\n\n\" Return new dict with keys removed\nfunction! coc#dict#omit(dict, keys) abort\n  let res = {}\n  for key in keys(a:dict)\n    if index(a:keys, key) == -1\n      let res[key] = a:dict[key]\n    endif\n  endfor\n  return res\nendfunction\n\n\" Return new dict with keys only\nfunction! coc#dict#pick(dict, keys) abort\n  let res = {}\n  for key in keys(a:dict)\n    if index(a:keys, key) != -1\n      let res[key] = a:dict[key]\n    endif\n  endfor\n  return res\nendfunction\n"
  },
  {
    "path": "autoload/coc/float.vim",
    "content": "scriptencoding utf-8\nlet s:is_vim = !has('nvim')\nlet s:borderchars = get(g:, 'coc_borderchars', ['─', '│', '─', '│', '┌', '┐', '┘', '└'])\nlet s:rounded_borderchars = s:borderchars[0:3] + ['╭', '╮', '╯', '╰']\nlet s:borderjoinchars = get(g:, 'coc_border_joinchars', ['┬', '┤', '┴', '├'])\nlet s:pad_bufnr = -1\n\n\" Check visible float/popup exists.\nfunction! coc#float#has_float(...) abort\n  return len(coc#float#get_float_win_list(get(a:, 1, 0))) > 0\nendfunction\n\nfunction! coc#float#close_all(...) abort\n  let winids = coc#float#get_float_win_list(get(a:, 1, 0))\n  for id in winids\n    try\n      call coc#float#close(id)\n    catch /E5555:/\n      \" ignore\n    endtry\n  endfor\n  return ''\nendfunction\n\nfunction! coc#float#jump() abort\n  if !s:is_vim\n    let winids = coc#float#get_float_win_list()\n    if !empty(winids)\n      call win_gotoid(winids[0])\n    endif\n  endif\nendfunction\n\nfunction! coc#float#valid(winid) abort\n  if a:winid <= 0\n    return 0\n  endif\n  if !s:is_vim\n    if !nvim_win_is_valid(a:winid)\n      return 0\n    endif\n    return !empty(nvim_win_get_config(a:winid)['relative'])\n  endif\n  try\n    return !empty(popup_getpos(a:winid))\n  catch /^Vim\\%((\\a\\+)\\)\\=:E993/\n    \" not a popup window\n    return 0\n  endtry\nendfunction\n\nfunction! coc#float#get_height(winid) abort\n  if !s:is_vim\n    let borderwin = coc#float#get_related(a:winid, 'border')\n    if borderwin\n      return nvim_win_get_height(borderwin)\n    endif\n    return nvim_win_get_height(a:winid)\n  endif\n  return get(popup_getpos(a:winid), 'height', 0)\nendfunction\n\nfunction! coc#float#change_height(winid, delta) abort\n  if s:is_vim\n    let curr = get(popup_getpos(a:winid), 'core_height', v:null)\n    if curr isnot v:null\n      call popup_move(a:winid, {\n          \\ 'maxheight': max([1, curr + a:delta]),\n          \\ 'minheight': max([1, curr + a:delta]),\n          \\ })\n    endif\n  else\n    let winids = copy(coc#window#get_var(a:winid, 'related', []))\n    call filter(winids, 'index([\"border\",\"pad\",\"scrollbar\"],coc#window#get_var(v:val,\"kind\",\"\")) >= 0')\n    call add(winids, a:winid)\n    for winid in winids\n      if coc#window#get_var(winid, 'kind', '') ==# 'border'\n        let bufnr = winbufnr(winid)\n        if a:delta > 0\n          call appendbufline(bufnr, 1, repeat(getbufline(bufnr, 2), a:delta))\n        else\n          call deletebufline(bufnr, 2, 2 - a:delta - 1)\n        endif\n      endif\n      let height = nvim_win_get_height(winid)\n      call nvim_win_set_height(winid, max([1, height + a:delta]))\n    endfor\n  endif\nendfunction\n\n\" create or config float window, returns [winid, bufnr], config including:\n\" - relative:  could be 'editor' 'cursor'\n\" - row: line count relative to editor/cursor, nagetive number means abover cursor.\n\" - col: column count relative to editor/cursor, nagetive number means left of cursor.\n\" - width: content width without border and title.\n\" - height: content height without border and title.\n\" - lines: (optional) lines to insert, default to v:null.\n\" - title: (optional) title.\n\" - border: (optional) border as number list, like [1, 1, 1 ,1].\n\" - cursorline: (optional) enable cursorline when is 1.\n\" - autohide: (optional) window should be closed on CursorMoved when is 1.\n\" - highlight: (optional) highlight of window, default to 'CocFloating'\n\" - borderhighlight: (optional) should be array or string for border highlights,\n\"   highlight all borders with first value.\n\" - close: (optional) show close button when is 1.\n\" - highlights: (optional) highlight items.\n\" - buttons: (optional) array of button text for create buttons at bottom.\n\" - codes: (optional) list of CodeBlock.\n\" - winblend: (optional) winblend option for float window, neovim only.\n\" - shadow:  (optional) use shadow as border style, neovim only.\n\" - focusable:  (optional) neovim only, default to true.\n\" - scrollinside: (optional) neovim only, create scrollbar inside window.\n\" - rounded: (optional) use rounded borderchars, ignored when borderchars exists.\n\" - zindex: (optional) zindex of window, default 50.\n\" - borderchars: (optional) borderchars, should be length of 8\n\" - nopad: (optional) not add pad when 1\n\" - filter: (optional) filter property on vim9.\n\" - index: (optional) line index\nfunction! coc#float#create_float_win(winid, bufnr, config) abort\n  let lines = get(a:config, 'lines', v:null)\n  let bufnr = a:bufnr\n  try\n    let bufnr = coc#float#create_buf(a:bufnr, lines, get(a:config, 'bufhidden', 'hide'))\n  catch /E523:/\n    \" happens when using getchar() #3921\n    return []\n  endtry\n\n  \" Calculate position when relative is editor\n  if get(a:config, 'relative', '') ==# 'editor'\n    let top = get(a:config, 'top', v:null)\n    let bottom = get(a:config, 'bottom', v:null)\n    let left = get(a:config, 'left', v:null)\n    let right = get(a:config, 'right', v:null)\n\n    if top isnot v:null || bottom isnot v:null || left isnot v:null || right isnot v:null\n      let height = &lines\n      let width = &columns\n\n      \" Calculate row\n      let calc_row = a:config.row\n      if bottom isnot v:null\n        let calc_row = height - bottom - a:config.height - 2\n      elseif top isnot v:null\n        let calc_row = top\n      endif\n\n      \" Calculate col\n      let calc_col = a:config.col\n      if right isnot v:null\n        let calc_col = width - right - a:config.width - 3\n      elseif left isnot v:null\n        let calc_col = left\n      endif\n\n      \" Check if window would overlap cursor position\n      let pos = screenpos(0, line('.'), col('.'))\n      let currow = pos.row - 1\n      let curcol = pos.col - 1\n      let win_top = calc_row\n      let win_bottom = win_top + a:config.height + 2\n      let win_left = calc_col\n      let win_right = win_left + a:config.width + 3\n\n      \" If window would overlap cursor, switch to cursor relative\n      if currow >= win_top && currow <= win_bottom && curcol >= win_left && curcol <= win_right\n        let a:config.relative = 'cursor'\n      else\n        let a:config.row = calc_row\n        let a:config.col = calc_col\n      endif\n    endif\n  endif\n  let lnum = max([1, get(a:config, 'index', 0) + 1])\n  let zindex = get(a:config, 'zindex', 50)\n  \" use exists\n  if a:winid && coc#float#valid(a:winid)\n    if s:is_vim\n      let [line, col] = s:popup_position(a:config)\n      let opts = {\n            \\ 'firstline': 1,\n            \\ 'line': line,\n            \\ 'col': col,\n            \\ 'minwidth': a:config['width'],\n            \\ 'minheight': a:config['height'],\n            \\ 'maxwidth': a:config['width'],\n            \\ 'maxheight': a:config['height'],\n            \\ 'title': get(a:config, 'title', ''),\n            \\ 'highlight': get(a:config, 'highlight', 'CocFloating'),\n            \\ 'borderhighlight':  [s:get_borderhighlight(a:config)],\n            \\ }\n      if !s:empty_border(get(a:config, 'border', []))\n        let opts['border'] = a:config['border']\n      endif\n      call popup_setoptions(a:winid, opts)\n      call win_execute(a:winid, 'exe '.lnum)\n      call coc#float#vim_buttons(a:winid, a:config)\n      call s:add_highlights(a:winid, a:config, 0)\n      return [a:winid, winbufnr(a:winid)]\n    else\n      let config = s:convert_config_nvim(a:config, 0)\n      let hlgroup = get(a:config, 'highlight', 'CocFloating')\n      let current = getwinvar(a:winid, '&winhl', '')\n      let winhl = coc#util#merge_winhl(current, [['Normal', hlgroup], ['FoldColumn', hlgroup]])\n      if winhl !=# current\n        call setwinvar(a:winid, '&winhl', winhl)\n      endif\n      call nvim_win_set_buf(a:winid, bufnr)\n      call nvim_win_set_config(a:winid, config)\n      call nvim_win_set_cursor(a:winid, [lnum, 0])\n      call coc#float#nvim_create_related(a:winid, config, a:config)\n      call s:add_highlights(a:winid, a:config, 0)\n      return [a:winid, bufnr]\n    endif\n  endif\n  let winid = 0\n  if s:is_vim\n    let [line, col] = s:popup_position(a:config)\n    let title = get(a:config, 'title', '')\n    let buttons = get(a:config, 'buttons', [])\n    let hlgroup = get(a:config, 'highlight',  'CocFloating')\n    let nopad = get(a:config, 'nopad', 0)\n    let border = s:empty_border(get(a:config, 'border', [])) ? [0, 0, 0, 0] : a:config['border']\n    let opts = {\n          \\ 'title': title,\n          \\ 'line': line,\n          \\ 'col': col,\n          \\ 'fixed': 1,\n          \\ 'padding': [0, !nopad && !border[1], 0, !nopad && !border[3]],\n          \\ 'borderchars': s:get_borderchars(a:config),\n          \\ 'highlight': hlgroup,\n          \\ 'minwidth': a:config['width'],\n          \\ 'minheight': a:config['height'],\n          \\ 'maxwidth': a:config['width'],\n          \\ 'maxheight': a:config['height'],\n          \\ 'close': get(a:config, 'close', 0) ? 'button' : 'none',\n          \\ 'border': border,\n          \\ 'zindex': zindex,\n          \\ 'callback': { -> coc#float#on_close(winid)},\n          \\ 'borderhighlight': [s:get_borderhighlight(a:config)],\n          \\ 'scrollbarhighlight': 'CocFloatSbar',\n          \\ 'thumbhighlight': 'CocFloatThumb',\n          \\ }\n    if type(get(a:config, 'filter', v:null)) == v:t_func\n      let opts['filter'] = get(a:config, 'filter', v:null)\n    endif\n    noa let winid = popup_create(bufnr, opts)\n    call s:set_float_defaults(winid, a:config)\n    call win_execute(winid, 'exe '.lnum)\n    call coc#float#vim_buttons(winid, a:config)\n  else\n    let config = s:convert_config_nvim(a:config, 1)\n    noa let winid = nvim_open_win(bufnr, 0, config)\n    if winid is 0\n      return []\n    endif\n    \" cursorline highlight not work on old neovim\n    call s:set_float_defaults(winid, a:config)\n    call nvim_win_set_cursor(winid, [lnum, 0])\n    call coc#float#nvim_create_related(winid, config, a:config)\n    call coc#float#nvim_set_winblend(winid, get(a:config, 'winblend', v:null))\n  endif\n  call s:add_highlights(winid, a:config, 1)\n  let g:coc_last_float_win = winid\n  call coc#util#do_autocmd('CocOpenFloat')\n  return [winid, bufnr]\nendfunction\n\nfunction! coc#float#nvim_create_related(winid, config, opts) abort\n  let related = getwinvar(a:winid, 'related', [])\n  let exists = !empty(related)\n  let border = get(a:opts, 'border', [])\n  let borderhighlight = s:get_borderhighlight(a:opts)\n  let buttons = get(a:opts, 'buttons', [])\n  let pad = !get(a:opts, 'nopad', 0) && (empty(border) || get(border, 1, 0) == 0)\n  let shadow = get(a:opts, 'shadow', 0)\n  if get(a:opts, 'close', 0)\n    call coc#float#nvim_close_btn(a:config, a:winid, border, borderhighlight, related)\n  elseif exists\n    call coc#float#close_related(a:winid, 'close')\n  endif\n  if !empty(buttons)\n    call coc#float#nvim_buttons(a:config, a:winid, buttons, get(a:opts, 'getchar', 0), get(border, 2, 0), pad, borderhighlight, shadow, related)\n  elseif exists\n    call coc#float#close_related(a:winid, 'buttons')\n  endif\n  if !s:empty_border(border)\n    let borderchars = s:get_borderchars(a:opts)\n    call coc#float#nvim_border_win(a:config, borderchars, a:winid, border, get(a:opts, 'title', ''), !empty(buttons), borderhighlight, shadow, related)\n  elseif exists\n    call coc#float#close_related(a:winid, 'border')\n  endif\n  \" Check right border\n  if pad\n    call coc#float#nvim_right_pad(a:config, a:winid, shadow, related)\n  elseif exists\n    call coc#float#close_related(a:winid, 'pad')\n  endif\n  call setwinvar(a:winid, 'related', filter(related, 'nvim_win_is_valid(v:val)'))\nendfunction\n\n\" border window for neovim, content config with border\nfunction! coc#float#nvim_border_win(config, borderchars, winid, border, title, hasbtn, hlgroup, shadow, related) abort\n  let winid = coc#float#get_related(a:winid, 'border')\n  let row = a:border[0] ? a:config['row'] - 1 : a:config['row']\n  let col = a:border[3] ? a:config['col'] - 1 : a:config['col']\n  let width = a:config['width'] + a:border[1] + a:border[3]\n  let height = a:config['height'] + a:border[0] + a:border[2] + (a:hasbtn ? 2 : 0)\n  let lines = coc#float#create_border_lines(a:border, a:borderchars, a:title, a:config['width'], a:config['height'], a:hasbtn)\n  let bufnr = winid ? winbufnr(winid) : 0\n  let bufnr = coc#float#create_buf(bufnr, lines)\n  let opt = {\n        \\ 'relative': a:config['relative'],\n        \\ 'width': width,\n        \\ 'height': height,\n        \\ 'row': row,\n        \\ 'col': col,\n        \\ 'focusable': v:false,\n        \\ 'style': 'minimal',\n        \\ }\n  if has_key(a:config, 'zindex')\n    let opt['zindex'] = a:config['zindex']\n  endif\n  if a:shadow && !a:hasbtn && a:border[2]\n    let opt['border'] = 'shadow'\n  endif\n  if winid\n    call nvim_win_set_config(winid, opt)\n    call setwinvar(winid, '&winhl', 'Normal:'.a:hlgroup)\n  else\n    noa let winid = nvim_open_win(bufnr, 0, opt)\n    call setwinvar(winid, 'delta', -1)\n    let winhl = 'Normal:'.a:hlgroup\n    call s:nvim_add_related(winid, a:winid, 'border', winhl, a:related)\n  endif\nendfunction\n\n\" neovim only\nfunction! coc#float#nvim_close_btn(config, winid, border, hlgroup, related) abort\n  let winid = coc#float#get_related(a:winid, 'close')\n  let config = {\n        \\ 'relative': a:config['relative'],\n        \\ 'width': 1,\n        \\ 'height': 1,\n        \\ 'row': get(a:border, 0, 0) ? a:config['row'] - 1 : a:config['row'],\n        \\ 'col': a:config['col'] + a:config['width'],\n        \\ 'focusable': v:true,\n        \\ 'style': 'minimal',\n        \\ }\n  if has_key(a:config, 'zindex')\n    let config['zindex'] = a:config['zindex'] + 2\n  endif\n  if winid\n    call nvim_win_set_config(winid, coc#dict#pick(config, ['relative', 'row', 'col']))\n  else\n    let bufnr = coc#float#create_buf(0, ['X'])\n    noa let winid = nvim_open_win(bufnr, 0, config)\n    let winhl = 'Normal:'.a:hlgroup\n    call setwinvar(winid, 'delta', -1)\n    call s:nvim_add_related(winid, a:winid, 'close', winhl, a:related)\n  endif\nendfunction\n\n\" Create padding window by config of current window & border config\nfunction! coc#float#nvim_right_pad(config, winid, shadow, related) abort\n  let winid = coc#float#get_related(a:winid, 'pad')\n  let config = {\n        \\ 'relative': a:config['relative'],\n        \\ 'width': 1,\n        \\ 'height': a:config['height'],\n        \\ 'row': a:config['row'],\n        \\ 'col': a:config['col'] + a:config['width'],\n        \\ 'focusable': v:false,\n        \\ 'style': 'minimal',\n        \\ }\n  if has_key(a:config, 'zindex')\n    let config['zindex'] = a:config['zindex'] + 1\n  endif\n  if a:shadow\n    let config['border'] = 'shadow'\n  endif\n  if winid && nvim_win_is_valid(winid)\n    call nvim_win_set_config(winid, coc#dict#pick(config, ['relative', 'row', 'col']))\n    call nvim_win_set_height(winid, config['height'])\n    return\n  endif\n  let s:pad_bufnr = bufloaded(s:pad_bufnr) ? s:pad_bufnr : coc#float#create_buf(0, repeat([''], &lines), 'hide')\n  noa let winid = nvim_open_win(s:pad_bufnr, 0, config)\n  call s:nvim_add_related(winid, a:winid, 'pad', '', a:related)\nendfunction\n\n\" draw buttons window for window with config\nfunction! coc#float#nvim_buttons(config, winid, buttons, getchar, borderbottom, pad, borderhighlight, shadow, related) abort\n  let winid = coc#float#get_related(a:winid, 'buttons')\n  let width = a:config['width'] + (a:pad ? 1 : 0)\n  let config = {\n        \\ 'row': a:config['row'] + a:config['height'],\n        \\ 'col': a:config['col'],\n        \\ 'width': width,\n        \\ 'height': 2 + (a:borderbottom ? 1 : 0),\n        \\ 'relative': a:config['relative'],\n        \\ 'focusable': 1,\n        \\ 'style': 'minimal',\n        \\ 'zindex': 300,\n        \\ }\n  if a:shadow\n    let config['border'] = 'shadow'\n  endif\n  if winid\n    let bufnr = winbufnr(winid)\n    call s:create_btns_buffer(bufnr, width, a:buttons, a:borderbottom)\n    call nvim_win_set_config(winid, config)\n  else\n    let bufnr = s:create_btns_buffer(0, width, a:buttons, a:borderbottom)\n    noa let winid = nvim_open_win(bufnr, 0, config)\n    if winid\n      call s:nvim_add_related(winid, a:winid, 'buttons', '', a:related)\n      call s:nvim_create_keymap(winid)\n    endif\n  endif\n  if bufnr\n    call nvim_buf_clear_namespace(bufnr, -1, 0, -1)\n    call nvim_buf_add_highlight(bufnr, 1, a:borderhighlight, 0, 0, -1)\n    if a:borderbottom\n      call nvim_buf_add_highlight(bufnr, 1, a:borderhighlight, 2, 0, -1)\n    endif\n    let vcols = getbufvar(bufnr, 'vcols', [])\n    \" TODO need change vol to col\n    for col in vcols\n      call nvim_buf_add_highlight(bufnr, 1, a:borderhighlight, 1, col, col + 3)\n    endfor\n    if a:getchar\n      let keys = s:gen_filter_keys(getbufline(bufnr, 2)[0])\n      call matchaddpos('MoreMsg', map(keys[0], \"[2,v:val]\"), 99, -1, {'window': winid})\n      call timer_start(10, {-> coc#float#getchar(winid, keys[1])})\n    endif\n  endif\nendfunction\n\nfunction! coc#float#getchar(winid, keys) abort\n  let ch = coc#prompt#getc()\n  let target = getwinvar(a:winid, 'target_winid', 0)\n  if ch ==# \"\\<esc>\"\n    call coc#float#close(target)\n    return\n  endif\n  if ch ==# \"\\<LeftMouse>\"\n    if getwinvar(v:mouse_winid, 'kind', '') ==# 'close'\n      call coc#float#close(target)\n      return\n    endif\n    if v:mouse_winid == a:winid && v:mouse_lnum == 2\n      let vcols = getbufvar(winbufnr(a:winid), 'vcols', [])\n      let col = v:mouse_col - 1\n      if index(vcols, col) < 0\n        let filtered = filter(vcols, 'v:val < col')\n        call coc#rpc#notify('FloatBtnClick', [winbufnr(target), len(filtered)])\n        call coc#float#close(target)\n        return\n      endif\n    endif\n  else\n    let idx = index(a:keys, ch)\n    if idx >= 0\n      call coc#rpc#notify('FloatBtnClick', [winbufnr(target), idx])\n      call coc#float#close(target)\n      return\n    endif\n  endif\n  call coc#float#getchar(a:winid, a:keys)\nendfunction\n\n\" Create or refresh scrollbar for winid\n\" Need called on create, config, buffer change, scrolled\nfunction! coc#float#nvim_scrollbar(winid) abort\n  if s:is_vim\n    return\n  endif\n  let winids = nvim_tabpage_list_wins(nvim_get_current_tabpage())\n  if index(winids, a:winid) == -1\n    return\n  endif\n  let config = nvim_win_get_config(a:winid)\n  let [row, column] = nvim_win_get_position(a:winid)\n  let relative = 'editor'\n  if row == 0 && column == 0\n    \" fix bad value when ext_multigrid is enabled. https://github.com/neovim/neovim/issues/11935\n    let [row, column] = [config.row, config.col]\n    let relative = config.relative\n  endif\n  let width = nvim_win_get_width(a:winid)\n  let height = nvim_win_get_height(a:winid)\n  let bufnr = winbufnr(a:winid)\n  let cw = getwinvar(a:winid, '&foldcolumn', 0) ? width - 1 : width\n  let ch = coc#float#content_height(bufnr, cw, getwinvar(a:winid, '&wrap'))\n  let closewin = coc#float#get_related(a:winid, 'close')\n  let border = getwinvar(a:winid, 'border', [])\n  let scrollinside = getwinvar(a:winid, 'scrollinside', 0) && get(border, 1, 0)\n  let winblend = getwinvar(a:winid, '&winblend', 0)\n  let move_down = closewin && !get(border, 0, 0)\n  let id = coc#float#get_related(a:winid, 'scrollbar')\n  if ch <= height || height <= 1\n    \" no scrollbar, remove exists\n    if id\n      call s:close_win(id, 1)\n    endif\n    return\n  endif\n  if move_down\n    let height = height - 1\n  endif\n  call coc#float#close_related(a:winid, 'pad')\n  let sbuf = id ? winbufnr(id) : 0\n  let sbuf = coc#float#create_buf(sbuf, repeat([' '], height))\n  let opts = {\n        \\ 'row': move_down ? row + 1 : row,\n        \\ 'col': column + width - scrollinside,\n        \\ 'relative': relative,\n        \\ 'width': 1,\n        \\ 'height': height,\n        \\ 'focusable': v:false,\n        \\ 'style': 'minimal',\n        \\ }\n  if has_key(config, 'zindex')\n    let opts['zindex'] = config['zindex'] + 2\n  endif\n  if s:has_shadow(config)\n    let opts['border'] = 'shadow'\n  endif\n  if id\n    call nvim_win_set_config(id, opts)\n  else\n    noa let id = nvim_open_win(sbuf, 0 , opts)\n    if id == 0\n      return\n    endif\n    if winblend\n      call setwinvar(id, '&winblend', winblend)\n    endif\n    call setwinvar(id, 'kind', 'scrollbar')\n    call setwinvar(id, 'target_winid', a:winid)\n    call coc#float#add_related(id, a:winid)\n  endif\n  if !scrollinside\n    call coc#float#nvim_scroll_adjust(a:winid)\n  endif\n  \" The min with height - 1 ensures that the scrollbar never takes up the full height.\n  \" If ch <= height we never reach this point, so we always want an actual scrollbar here.\n  \" The height of the scrollbar needs to be an integer to conform to the terminal grid.\n  \" Rounding down could result in gaps appearing when using coc#float#scroll(1),\n  \" meaning a situation where the lower end of the scrollbar is above a certain position,\n  \" and after calling coc#float#scroll(1) the upper end of the scrollbar is below this position,\n  \" so the position is never part of the scrollbar,\n  \" giving the appearance that some of the text in the float is skipped.\n  \" Rounding up ensures that no such gaps can appear.\n  let thumb_height = min([height - 1, float2nr(ceil(height * (height + 0.0)/ch))])\n  let wininfo = getwininfo(a:winid)[0]\n  let start = 0\n  if wininfo['topline'] != 1\n    \" needed for correct getwininfo\n    let firstline = wininfo['topline']\n    let lastline = s:nvim_get_botline(firstline, height, cw, bufnr)\n    let linecount = nvim_buf_line_count(winbufnr(a:winid))\n    if lastline >= linecount\n      let start = height - thumb_height\n    else\n      let start = max([1, float2nr(round((height - thumb_height + 0.0)*(firstline - 1.0)/(ch - height)))])\n    endif\n  endif\n  \" add highlights\n  call nvim_buf_clear_namespace(sbuf, -1, 0, -1)\n  for idx in range(0, height - 1)\n    if idx >= start && idx < start + thumb_height\n      call nvim_buf_add_highlight(sbuf, -1, 'CocFloatThumb', idx, 0, 1)\n    else\n      call nvim_buf_add_highlight(sbuf, -1, 'CocFloatSbar', idx, 0, 1)\n    endif\n  endfor\nendfunction\n\nfunction! coc#float#create_border_lines(border, borderchars, title, width, height, hasbtn) abort\n  let borderchars = a:borderchars\n  let list = []\n  if a:border[0]\n    let top = (a:border[3] ?  borderchars[4]: '')\n          \\.repeat(borderchars[0], a:width)\n          \\.(a:border[1] ? borderchars[5] : '')\n    if !empty(a:title)\n      let top = coc#string#compose(top, 1, a:title.' ')\n    endif\n    call add(list, top)\n  endif\n  let mid = (a:border[3] ?  borderchars[3]: '')\n        \\.repeat(' ', a:width)\n        \\.(a:border[1] ? borderchars[1] : '')\n  call extend(list, repeat([mid], a:height + (a:hasbtn ? 2 : 0)))\n  if a:hasbtn\n    let list[len(list) - 2] = (a:border[3] ?  s:borderjoinchars[3]: '')\n        \\.repeat(' ', a:width)\n        \\.(a:border[1] ? s:borderjoinchars[1] : '')\n  endif\n  if a:border[2]\n    let bot = (a:border[3] ?  borderchars[7]: '')\n          \\.repeat(borderchars[2], a:width)\n          \\.(a:border[1] ? borderchars[6] : '')\n    call add(list, bot)\n  endif\n  return list\nendfunction\n\n\" Close float window by id\nfunction! coc#float#close(winid, ...) abort\n  let noautocmd = get(a:, 1, 0)\n  if a:winid >= 0\n    call coc#float#close_related(a:winid)\n    call s:close_win(a:winid, noautocmd)\n  endif\n  return 1\nendfunction\n\n\" Get visible float windows\nfunction! coc#float#get_float_win_list(...) abort\n  let res = []\n  let list_all = get(a:, 1, 0)\n  if s:is_vim\n    return filter(popup_list(), 'popup_getpos(v:val)[\"visible\"]'.(list_all ? '' : '&& getwinvar(v:val, \"float\", 0)'))\n  else\n    let res = []\n    for id in nvim_list_wins()\n      let config = nvim_win_get_config(id)\n      if empty(config) || empty(config['relative'])\n        continue\n      endif\n      \" ignore border & button window & others\n      if list_all == 0 && !getwinvar(id, 'float', 0)\n        continue\n      endif\n      call add(res, id)\n    endfor\n    return res\n  endif\n  return []\nendfunction\n\nfunction! coc#float#get_float_by_kind(kind) abort\n  if s:is_vim\n    return get(filter(popup_list(), 'popup_getpos(v:val)[\"visible\"] && getwinvar(v:val, \"kind\", \"\") ==# \"'.a:kind.'\"'), 0, 0)\n  else\n    let res = []\n    for winid in nvim_list_wins()\n      let config = nvim_win_get_config(winid)\n      if !empty(config['relative']) && getwinvar(winid, 'kind', '') ==# a:kind\n        return winid\n      endif\n    endfor\n  endif\n  return 0\nendfunction\n\n\" Check if a float window is scrollable\nfunction! coc#float#scrollable(winid) abort\n  let bufnr = winbufnr(a:winid)\n  if bufnr == -1\n    return 0\n  endif\n  if s:is_vim\n    let pos = popup_getpos(a:winid)\n    if get(pos, 'scrollbar', 0)\n      return 1\n    endif\n    let ch = coc#float#content_height(bufnr, pos['core_width'], getwinvar(a:winid, '&wrap'))\n    return ch > pos['core_height']\n  else\n    let height = nvim_win_get_height(a:winid)\n    let width = nvim_win_get_width(a:winid)\n    if width > 1 && getwinvar(a:winid, '&foldcolumn', 0)\n      \" since we use foldcolumn for left padding\n      let width = width - 1\n    endif\n    let ch = coc#float#content_height(bufnr, width, getwinvar(a:winid, '&wrap'))\n    return ch > height\n  endif\nendfunction\n\nfunction! coc#float#has_scroll() abort\n  let win_ids = filter(coc#float#get_float_win_list(), 'coc#float#scrollable(v:val)')\n  return !empty(win_ids)\nendfunction\n\nfunction! coc#float#scroll(forward, ...)\n  let amount = get(a:, 1, 0)\n  let winids = filter(coc#float#get_float_win_list(), 'coc#float#scrollable(v:val) && getwinvar(v:val,\"kind\",\"\") !=# \"pum\"')\n  if empty(winids)\n    return mode() =~ '^i' || mode() ==# 'v' ? \"\" : \"\\<Ignore>\"\n  endif\n  for winid in winids\n    call s:scroll_win(winid, a:forward, amount)\n  endfor\n  return mode() =~ '^i' || mode() ==# 'v' ? \"\" : \"\\<Ignore>\"\nendfunction\n\nfunction! coc#float#scroll_win(winid, forward, amount) abort\n  let opts = coc#float#get_options(a:winid)\n  let lines = getbufline(winbufnr(a:winid), 1, '$')\n  let maxfirst = s:max_firstline(lines, opts['height'], opts['width'])\n  let topline = opts['topline']\n  let height = opts['height']\n  let width = opts['width']\n  let scrolloff = getwinvar(a:winid, '&scrolloff', 0)\n  if a:forward && topline >= maxfirst\n    return\n  endif\n  if !a:forward && topline == 1\n    return\n  endif\n  if a:amount == 0\n    let topline = s:get_topline(opts['topline'], lines, a:forward, height, width)\n  else\n    let topline = topline + (a:forward ? a:amount : - a:amount)\n  endif\n  let topline = a:forward ? min([maxfirst, topline]) : max([1, topline])\n  let lnum = s:get_cursorline(topline, lines, scrolloff, width, height)\n  call s:win_setview(a:winid, topline, lnum)\n  let top = coc#float#get_options(a:winid)['topline']\n  \" not changed\n  if top == opts['topline']\n    if a:forward\n      call s:win_setview(a:winid, topline + 1, lnum + 1)\n    else\n      call s:win_setview(a:winid, topline - 1, lnum - 1)\n    endif\n  endif\nendfunction\n\nfunction! coc#float#content_height(bufnr, width, wrap) abort\n  if !bufloaded(a:bufnr)\n    return 0\n  endif\n  if !a:wrap\n    return coc#compat#buf_line_count(a:bufnr)\n  endif\n  let lines = s:is_vim ? getbufline(a:bufnr, 1, '$') : nvim_buf_get_lines(a:bufnr, 0, -1, 0)\n  return coc#string#content_height(lines, a:width)\nendfunction\n\nfunction! coc#float#nvim_refresh_scrollbar(winid) abort\n  let id = coc#float#get_related(a:winid, 'scrollbar')\n  if id && nvim_win_is_valid(id)\n    call coc#float#nvim_scrollbar(a:winid)\n  endif\nendfunction\n\nfunction! coc#float#on_close(winid) abort\n  let winids = coc#float#get_float_win_list()\n  for winid in winids\n    let target = getwinvar(winid, 'target_winid', -1)\n    if target == a:winid\n      call coc#float#close(winid)\n    endif\n  endfor\nendfunction\n\n\" Close related windows, or specific kind\nfunction! coc#float#close_related(winid, ...) abort\n  if !coc#float#valid(a:winid)\n    return\n  endif\n  let timer = coc#window#get_var(a:winid, 'timer', 0)\n  if timer\n    call timer_stop(timer)\n  endif\n  let kind = get(a:, 1, '')\n  let winids = coc#window#get_var(a:winid, 'related', [])\n  for id in winids\n    let curr = coc#window#get_var(id, 'kind', '')\n    if empty(kind) || curr ==# kind\n      if curr == 'list'\n        call coc#float#close(id, 1)\n      elseif s:is_vim\n        \" vim doesn't throw\n        noa call popup_close(id)\n      else\n        silent! noa call nvim_win_close(id, 1)\n      endif\n    endif\n  endfor\nendfunction\n\n\" Close related windows if target window is not visible.\nfunction! coc#float#check_related() abort\n  let invalids = []\n  let ids = coc#float#get_float_win_list(1)\n  for id in ids\n    let target = getwinvar(id, 'target_winid', 0)\n    if target && index(ids, target) == -1\n      call add(invalids, id)\n    endif\n  endfor\n  for id in invalids\n    call coc#float#close(id)\n  endfor\nendfunction\n\n\" Show float window/popup for user confirm.\n\" Create buttons popup on vim\nfunction! coc#float#vim_buttons(winid, config) abort\n  let related = getwinvar(a:winid, 'related', [])\n  let winid = coc#float#get_related(a:winid, 'buttons')\n  let btns = get(a:config, 'buttons', [])\n  if empty(btns)\n    if winid\n      call s:close_win(winid, 1)\n      \" fix padding\n      let opts = popup_getoptions(a:winid)\n      let padding = get(opts, 'padding', v:null)\n      if !empty(padding)\n        let padding[2] = padding[2] - 2\n      endif\n      call popup_setoptions(a:winid, {'padding': padding})\n    endif\n    return\n  endif\n  let border = get(a:config, 'border', v:null)\n  if !winid\n    \" adjusting popup padding\n    let opts = popup_getoptions(a:winid)\n    let padding = get(opts, 'padding', v:null)\n    if type(padding) == 7\n      let padding = [0, 0, 2, 0]\n    elseif len(padding) == 0\n      let padding = [1, 1, 3, 1]\n    else\n      let padding[2] = padding[2] + 2\n    endif\n    call popup_setoptions(a:winid, {'padding': padding})\n  endif\n  let borderhighlight = get(get(a:config, 'borderhighlight', []), 0, '')\n  let pos = popup_getpos(a:winid)\n  let bw = empty(border) ? 0 : get(border, 1, 0) + get(border, 3, 0)\n  let borderbottom = empty(border) ? 0 : get(border, 2, 0)\n  let borderleft = empty(border) ? 0 : get(border, 3, 0)\n  let width = pos['width'] - bw + get(pos, 'scrollbar', 0)\n  let bufnr = s:create_btns_buffer(winid ? winbufnr(winid): 0,width, btns, borderbottom)\n  let height = 2 + (borderbottom ? 1 : 0)\n  let keys = s:gen_filter_keys(getbufline(bufnr, 2)[0])\n  let options = {\n        \\ 'filter': {id, key -> coc#float#vim_filter(id, key, keys[1])},\n        \\ 'highlight': get(opts, 'highlight', 'CocFloating')\n        \\ }\n  let config = {\n        \\ 'line': pos['line'] + pos['height'] - height,\n        \\ 'col': pos['col'] + borderleft,\n        \\ 'minwidth': width,\n        \\ 'minheight': height,\n        \\ 'maxwidth': width,\n        \\ 'maxheight': height,\n        \\ }\n  if winid != 0\n    call popup_move(winid, config)\n    call popup_setoptions(winid, options)\n    call win_execute(winid, 'call clearmatches()')\n  else\n    let options = extend({\n          \\ 'filtermode': 'nvi',\n          \\ 'padding': [0, 0, 0, 0],\n          \\ 'fixed': 1,\n          \\ 'zindex': 99,\n          \\ }, options)\n    call extend(options, config)\n    let winid = popup_create(bufnr, options)\n  endif\n  if winid != 0\n    if !empty(borderhighlight)\n      call coc#highlight#add_highlight(bufnr, -1, borderhighlight, 0, 0, -1)\n      call coc#highlight#add_highlight(bufnr, -1, borderhighlight, 2, 0, -1)\n      call win_execute(winid, 'call matchadd(\"'.borderhighlight.'\", \"'.s:borderchars[1].'\")')\n    endif\n    call setwinvar(winid, 'kind', 'buttons')\n    call setwinvar(winid, 'target_winid', a:winid)\n    call add(related, winid)\n    call setwinvar(a:winid, 'related', related)\n    call matchaddpos('MoreMsg', map(keys[0], \"[2,v:val]\"), 99, -1, {'window': winid})\n  endif\nendfunction\n\nfunction! coc#float#nvim_float_click() abort\n  let kind = getwinvar(win_getid(), 'kind', '')\n  if kind == 'buttons'\n    if line('.') != 2\n      return\n    endif\n    let vw = strdisplaywidth(strpart(getline('.'), 0, col('.') - 1))\n    let vcols = getbufvar(bufnr('%'), 'vcols', [])\n    if index(vcols, vw) >= 0\n      return\n    endif\n    let idx = 0\n    if !empty(vcols)\n      let filtered = filter(vcols, 'v:val < vw')\n      let idx = idx + len(filtered)\n    endif\n    let winid = win_getid()\n    let target = getwinvar(winid, 'target_winid', 0)\n    if target\n      call coc#rpc#notify('FloatBtnClick', [winbufnr(target), idx])\n      call coc#float#close(target)\n    endif\n  elseif kind == 'close'\n    let target = getwinvar(win_getid(), 'target_winid', 0)\n    call coc#float#close(target)\n  endif\nendfunction\n\n\" Add <LeftRelease> mapping if necessary\nfunction! coc#float#nvim_win_enter(winid) abort\n  let kind = getwinvar(a:winid, 'kind', '')\n  if kind == 'buttons' || kind == 'close'\n    if empty(maparg('<LeftRelease>', 'n'))\n      nnoremap <buffer><silent> <LeftRelease> :call coc#float#nvim_float_click()<CR>\n    endif\n  endif\nendfunction\n\nfunction! coc#float#vim_filter(winid, key, keys) abort\n  let key = tolower(a:key)\n  let idx = index(a:keys, key)\n  let target = getwinvar(a:winid, 'target_winid', 0)\n  if target && idx >= 0\n    call coc#rpc#notify('FloatBtnClick', [winbufnr(target), idx])\n    call coc#float#close(target)\n    return 1\n  endif\n  return 0\nendfunction\n\nfunction! coc#float#get_related(winid, kind, ...) abort\n  if coc#float#valid(a:winid)\n    for winid in coc#window#get_var(a:winid, 'related', [])\n      if coc#window#get_var(winid, 'kind', '') ==# a:kind\n        return winid\n      endif\n    endfor\n  endif\n  return get(a:, 1, 0)\nendfunction\n\nfunction! coc#float#get_row(winid) abort\n  let winid = s:is_vim ? a:winid : coc#float#get_related(a:winid, 'border', a:winid)\n  if coc#float#valid(winid)\n    if s:is_vim\n      let pos = popup_getpos(winid)\n      return pos['line'] - 1\n    endif\n    let pos = nvim_win_get_position(winid)\n    return pos[0]\n  endif\nendfunction\n\n\" Create temporarily buffer with optional lines and &bufhidden\nfunction! coc#float#create_buf(bufnr, ...) abort\n  if a:bufnr > 0 && bufloaded(a:bufnr)\n    let bufnr = a:bufnr\n  else\n    if s:is_vim\n      noa let bufnr = bufadd('')\n      noa call bufload(bufnr)\n      call setbufvar(bufnr, '&buflisted', 0)\n      call setbufvar(bufnr, '&modeline', 0)\n      call setbufvar(bufnr, '&buftype', 'nofile')\n      call setbufvar(bufnr, '&swapfile', 0)\n    else\n      noa let bufnr = nvim_create_buf(v:false, v:true)\n    endif\n    let bufhidden = get(a:, 2, 'wipe')\n    call setbufvar(bufnr, '&bufhidden', bufhidden)\n    call setbufvar(bufnr, '&undolevels', -1)\n    \" neovim's bug\n    call setbufvar(bufnr, '&modifiable', 1)\n  endif\n  let lines = get(a:, 1, v:null)\n  if type(lines) == v:t_list\n    if s:is_vim\n      silent noa call setbufline(bufnr, 1, lines)\n      silent noa call deletebufline(bufnr, len(lines) + 1, '$')\n    else\n      call nvim_buf_set_lines(bufnr, 0, -1, v:false, lines)\n    endif\n  endif\n  return bufnr\nendfunction\n\n\" Change border window & close window when scrollbar is shown.\nfunction! coc#float#nvim_scroll_adjust(winid) abort\n  let winid = coc#float#get_related(a:winid, 'border')\n  if !winid\n    return\n  endif\n  let bufnr = winbufnr(winid)\n  let lines = nvim_buf_get_lines(bufnr, 0, -1, 0)\n  if len(lines) >= 2\n    let cw = nvim_win_get_width(a:winid)\n    let width = nvim_win_get_width(winid)\n    if width - cw != 1 + (strcharpart(lines[1], 0, 1) ==# s:borderchars[3] ? 1 : 0)\n      return\n    endif\n    call nvim_win_set_width(winid, width + 1)\n    let lastline = len(lines) - 1\n    for i in range(0, lastline)\n      let line = lines[i]\n      if i == 0\n        let add = s:borderchars[0]\n      elseif i == lastline\n        let add = s:borderchars[2]\n      else\n        let add = ' '\n      endif\n      let prev = strcharpart(line, 0, strchars(line) - 1)\n      let lines[i] = prev . add . coc#string#last_character(line)\n    endfor\n    call nvim_buf_set_lines(bufnr, 0, -1, 0, lines)\n    \" Move right close button\n    if coc#window#get_var(a:winid, 'right', 0) == 0\n      let id = coc#float#get_related(a:winid, 'close')\n      if id\n        let [row, col] = nvim_win_get_position(id)\n        call nvim_win_set_config(id, {\n              \\ 'relative': 'editor',\n              \\ 'row': row,\n              \\ 'col': col + 1,\n              \\ })\n      endif\n    else\n      \" Move winid and all related left by 1\n      let winids = [a:winid] + coc#window#get_var(a:winid, 'related', [])\n      for winid in winids\n        if nvim_win_is_valid(winid)\n          if coc#window#get_var(winid, 'kind', '') != 'close'\n            let config = nvim_win_get_config(winid)\n            let [row, column] = [config.row, config.col]\n            call nvim_win_set_config(winid, {\n                  \\ 'row': row,\n                  \\ 'col': column - 1,\n                  \\ 'relative': 'editor',\n                  \\ })\n          endif\n        endif\n      endfor\n    endif\n  endif\nendfunction\n\nfunction! coc#float#nvim_set_winblend(winid, winblend) abort\n  if a:winblend is v:null\n    return\n  endif\n  call coc#window#set_var(a:winid, '&winblend', a:winblend)\n  for winid in coc#window#get_var(a:winid, 'related', [])\n    call coc#window#set_var(winid, '&winblend', a:winblend)\n  endfor\nendfunction\n\nfunction! s:popup_visible(id) abort\n  let pos = popup_getpos(a:id)\n  if !empty(pos) && get(pos, 'visible', 0)\n    return 1\n  endif\n  return 0\nendfunction\n\nfunction! s:convert_config_nvim(config, create) abort\n  let valids = ['relative', 'win', 'anchor', 'width', 'height', 'bufpos', 'col', 'row', 'focusable']\n  let result = coc#dict#pick(a:config, valids)\n  let border = get(a:config, 'border', [])\n  if !s:empty_border(border)\n    if result['relative'] ==# 'cursor' && result['row'] < 0\n      \" move top when has bottom border\n      if get(border, 2, 0)\n        let result['row'] = result['row'] - 1\n      endif\n    else\n      \" move down when has top border\n      if get(border, 0, 0) && !get(a:config, 'prompt', 0)\n        let result['row'] = result['row'] + 1\n      endif\n    endif\n    \" move right when has left border\n    if get(border, 3, 0)\n      let result['col'] = result['col'] + 1\n    endif\n    let result['width'] = float2nr(result['width'] + 1 - get(border,3, 0))\n  else\n    let result['width'] = float2nr(result['width'] + (get(a:config, 'nopad', 0) ? 0 : 1))\n  endif\n  if get(a:config, 'shadow', 0) && a:create\n    if empty(get(a:config, 'buttons', v:null)) && empty(get(border, 2, 0))\n      let result['border'] = 'shadow'\n    endif\n  endif\n  let result['zindex'] = get(a:config, 'zindex', 50)\n  let result['height'] = float2nr(result['height'])\n  return result\nendfunction\n\nfunction! s:create_btns_buffer(bufnr, width, buttons, borderbottom) abort\n  let n = len(a:buttons)\n  let spaces = a:width - n + 1\n  let tw = 0\n  for txt in a:buttons\n    let tw += strdisplaywidth(txt)\n  endfor\n  if spaces < tw\n    throw 'window is too small for buttons.'\n  endif\n  let ds = (spaces - tw)/n\n  let dl = ds/2\n  let dr = ds%2 == 0 ? ds/2 : ds/2 + 1\n  let btnline = ''\n  let idxes = []\n  for idx in range(0, n - 1)\n    let txt = toupper(a:buttons[idx][0]).a:buttons[idx][1:]\n    let btnline .= repeat(' ', dl).txt.repeat(' ', dr)\n    if idx != n - 1\n      call add(idxes, strdisplaywidth(btnline))\n      let btnline .= s:borderchars[1]\n    endif\n  endfor\n  let lines = [repeat(s:borderchars[0], a:width), btnline]\n  if a:borderbottom\n    call add(lines, repeat(s:borderchars[0], a:width))\n  endif\n  for idx in idxes\n    let lines[0] = strcharpart(lines[0], 0, idx).s:borderjoinchars[0].strcharpart(lines[0], idx + 1)\n    if a:borderbottom\n      let lines[2] = strcharpart(lines[0], 0, idx).s:borderjoinchars[2].strcharpart(lines[0], idx + 1)\n    endif\n  endfor\n  let bufnr = coc#float#create_buf(a:bufnr, lines)\n  call setbufvar(bufnr, 'vcols', idxes)\n  return bufnr\nendfunction\n\nfunction! s:gen_filter_keys(line) abort\n  let cols = []\n  let used = []\n  let next = 1\n  for idx in  range(0, strchars(a:line) - 1)\n    let ch = strcharpart(a:line, idx, 1)\n    let nr = char2nr(ch)\n    if next\n      if (nr >= 65 && nr <= 90) || (nr >= 97 && nr <= 122)\n        let lc = tolower(ch)\n        if index(used, lc) < 0 && (!s:is_vim || empty(maparg(lc, 'n')))\n          let col = len(strcharpart(a:line, 0, idx)) + 1\n          call add(used, lc)\n          call add(cols, col)\n          let next = 0\n        endif\n      endif\n    else\n      if ch == s:borderchars[1]\n        let next = 1\n      endif\n    endif\n  endfor\n  return [cols, used]\nendfunction\n\nfunction! s:close_win(winid, noautocmd) abort\n  if a:winid <= 0\n    return\n  endif\n  \" vim not throw for none exists winid\n  if s:is_vim\n    let prefix = a:noautocmd ? 'noa ': ''\n    exe prefix.'call popup_close('.a:winid.')'\n  else\n    if nvim_win_is_valid(a:winid)\n      let prefix = a:noautocmd ? 'noa ': ''\n      exe prefix.'call nvim_win_close('.a:winid.', 1)'\n    endif\n  endif\nendfunction\n\nfunction! s:nvim_create_keymap(winid) abort\n  let bufnr = winbufnr(a:winid)\n  call nvim_buf_set_keymap(bufnr, 'n', '<LeftRelease>', ':call coc#float#nvim_float_click()<CR>', {\n        \\ 'silent': v:true,\n        \\ 'nowait': v:true\n        \\ })\nendfunction\n\n\" getwininfo is buggy on neovim, use topline, width & height should for content\nfunction! s:nvim_get_botline(topline, height, width, bufnr) abort\n  let lines = getbufline(a:bufnr, a:topline, a:topline + a:height - 1)\n  let botline = a:topline\n  let count = 0\n  for i in range(0, len(lines) - 1)\n    let w = max([1, strdisplaywidth(lines[i])])\n    let lh = float2nr(ceil(str2float(string(w))/a:width))\n    let count = count + lh\n    let botline = a:topline + i\n    if count >= a:height\n      break\n    endif\n  endfor\n  return botline\nendfunction\n\n\" get popup position for vim8 based on config of neovim float window\nfunction! s:popup_position(config) abort\n  let relative = get(a:config, 'relative', 'editor')\n  let border = get(a:config, 'border', [0, 0, 0, 0])\n  let delta = get(border, 0, 0)  + get(border, 2, 0)\n  if relative ==# 'cursor'\n    if a:config['row'] < 0\n      let delta = - delta\n    elseif a:config['row'] == 0\n      let delta = - get(border, 0, 0)\n    else\n      let delta = 0\n    endif\n    return [s:popup_cursor(a:config['row'] + delta), s:popup_cursor(a:config['col'])]\n  endif\n  return [a:config['row'] + 1, a:config['col'] + 1]\nendfunction\n\nfunction! coc#float#add_related(winid, target) abort\n  let arr = coc#window#get_var(a:target, 'related', [])\n  if index(arr, a:winid) >= 0\n    return\n  endif\n  call add(arr, a:winid)\n  call coc#window#set_var(a:target, 'related', arr)\nendfunction\n\nfunction! coc#float#get_wininfo(winid) abort\n  if !coc#float#valid(a:winid)\n    throw 'Not valid float window: '.a:winid\n  endif\n  if s:is_vim\n    let pos = popup_getpos(a:winid)\n    return {'topline': pos['firstline'], 'botline': pos['lastline']}\n  endif\n  let info = getwininfo(a:winid)[0]\n  return {'topline': info['topline'], 'botline': info['botline']}\nendfunction\n\nfunction! s:popup_cursor(n) abort\n  if a:n == 0\n    return 'cursor'\n  endif\n  if a:n < 0\n    return 'cursor'.a:n\n  endif\n  return 'cursor+'.a:n\nendfunction\n\n\" max firstline of lines, height > 0, width > 0\nfunction! s:max_firstline(lines, height, width) abort\n  let max = len(a:lines)\n  let remain = a:height\n  for line in reverse(copy(a:lines))\n    let w = max([1, strdisplaywidth(line)])\n    let dh = float2nr(ceil(str2float(string(w))/a:width))\n    if remain - dh < 0\n      break\n    endif\n    let remain = remain - dh\n    let max = max - 1\n  endfor\n  return min([len(a:lines), max + 1])\nendfunction\n\n\" Get best lnum by topline\nfunction! s:get_cursorline(topline, lines, scrolloff, width, height) abort\n  let lastline = len(a:lines)\n  if a:topline == lastline\n    return lastline\n  endif\n  let bottomline = a:topline\n  let used = 0\n  for lnum in range(a:topline, lastline)\n    let w = max([1, strdisplaywidth(a:lines[lnum - 1])])\n    let dh = float2nr(ceil(str2float(string(w))/a:width))\n    if used + dh >= a:height || lnum == lastline\n      let bottomline = lnum\n      break\n    endif\n    let used += dh\n  endfor\n  let cursorline = a:topline + a:scrolloff\n  if cursorline + a:scrolloff > bottomline\n    \" unable to satisfy scrolloff\n    let cursorline = (a:topline + bottomline)/2\n  endif\n  return cursorline\nendfunction\n\n\" Get firstline for full scroll\nfunction! s:get_topline(topline, lines, forward, height, width) abort\n  let used = 0\n  let lnums = a:forward ? range(a:topline, len(a:lines)) : reverse(range(1, a:topline))\n  let topline = a:forward ? len(a:lines) : 1\n  for lnum in lnums\n    let w = max([1, strdisplaywidth(a:lines[lnum - 1])])\n    let dh = float2nr(ceil(str2float(string(w))/a:width))\n    if used + dh >= a:height\n      let topline = lnum\n      break\n    endif\n    let used += dh\n  endfor\n  if topline == a:topline\n    if a:forward\n      let topline = min([len(a:lines), topline + 1])\n    else\n      let topline = max([1, topline - 1])\n    endif\n  endif\n  return topline\nendfunction\n\n\" topline content_height content_width\nfunction! coc#float#get_options(winid) abort\n  if s:is_vim\n    let pos = popup_getpos(a:winid)\n    return {\n      \\ 'topline': pos['firstline'],\n      \\ 'width': pos['core_width'],\n      \\ 'height': pos['core_height']\n      \\ }\n  else\n    let width = nvim_win_get_width(a:winid)\n    if coc#window#get_var(a:winid, '&foldcolumn', 0)\n      let width = width - 1\n    endif\n    let info = getwininfo(a:winid)[0]\n    return {\n      \\ 'topline': info['topline'],\n      \\ 'height': nvim_win_get_height(a:winid),\n      \\ 'width': width\n      \\ }\n  endif\nendfunction\n\nfunction! s:win_setview(winid, topline, lnum) abort\n  if s:is_vim\n    call win_execute(a:winid, 'exe '.a:lnum)\n    call popup_setoptions(a:winid, { 'firstline': a:topline })\n  else\n    call win_execute(a:winid, 'call winrestview({\"lnum\":'.a:lnum.',\"topline\":'.a:topline.'})')\n    call timer_start(1, { -> coc#float#nvim_refresh_scrollbar(a:winid) })\n  endif\nendfunction\n\nfunction! s:set_float_defaults(winid, config) abort\n  if !s:is_vim\n    let hlgroup = get(a:config, 'highlight', 'CocFloating')\n    call setwinvar(a:winid, '&winhl', 'Normal:'.hlgroup.',FoldColumn:'.hlgroup)\n    call setwinvar(a:winid, 'border', get(a:config, 'border', []))\n    call setwinvar(a:winid, 'scrollinside', get(a:config, 'scrollinside', 0))\n    call setwinvar(a:winid, '&foldcolumn', s:nvim_get_foldcolumn(a:config))\n    call setwinvar(a:winid, '&signcolumn', 'no')\n    call setwinvar(a:winid, '&cursorcolumn', 0)\n  else\n    call setwinvar(a:winid, '&foldcolumn', 0)\n  endif\n  if exists('&statuscolumn')\n    call setwinvar(a:winid, '&statuscolumn', '')\n  endif\n  if !s:is_vim\n    call setwinvar(a:winid, '&number', 0)\n    call setwinvar(a:winid, '&relativenumber', 0)\n    call setwinvar(a:winid, '&cursorline', 0)\n  endif\n  call setwinvar(a:winid, '&foldenable', 0)\n  call setwinvar(a:winid, '&colorcolumn', '')\n  call setwinvar(a:winid, '&spell', 0)\n  call setwinvar(a:winid, '&linebreak', 1)\n  call setwinvar(a:winid, '&conceallevel', 0)\n  call setwinvar(a:winid, '&list', 0)\n  call setwinvar(a:winid, '&wrap', !get(a:config, 'cursorline', 0))\n  call setwinvar(a:winid, '&scrolloff', 0)\n  call setwinvar(a:winid, '&showbreak', 'NONE')\n  call win_execute(a:winid, 'setl fillchars+=eob:\\ ')\n  if get(a:config, 'autohide', 0)\n    call setwinvar(a:winid, 'autohide', 1)\n  endif\n  call setwinvar(a:winid, 'float', 1)\nendfunction\n\nfunction! s:nvim_add_related(winid, target, kind, winhl, related) abort\n  if a:winid <= 0\n    return\n  endif\n  if exists('&statuscolumn')\n    call setwinvar(a:winid, '&statuscolumn', '')\n  endif\n  let winhl = empty(a:winhl) ? coc#window#get_var(a:target, '&winhl', '') : a:winhl\n  call setwinvar(a:winid, '&winhl', winhl)\n  call setwinvar(a:winid, 'target_winid', a:target)\n  call setwinvar(a:winid, 'kind', a:kind)\n  call add(a:related, a:winid)\nendfunction\n\nfunction! s:nvim_get_foldcolumn(config) abort\n  let nopad = get(a:config, 'nopad', 0)\n  if nopad\n    return 0\n  endif\n  let border = get(a:config, 'border', v:null)\n  if border is 1 || (type(border) == v:t_list && get(border, 3, 0) == 1)\n    return 0\n  endif\n  return 1\nendfunction\n\nfunction! s:add_highlights(winid, config, create) abort\n  let codes = get(a:config, 'codes', [])\n  let highlights = get(a:config, 'highlights', [])\n  if empty(codes) && empty(highlights) && a:create\n    return\n  endif\n  let bgGroup = get(a:config, 'highlight', 'CocFloating')\n  for obj in codes\n    let hlGroup = get(obj, 'hlGroup', v:null)\n    if !empty(hlGroup)\n      let obj['hlGroup'] = coc#hlgroup#compose_hlgroup(hlGroup, bgGroup)\n    endif\n  endfor\n  call coc#highlight#add_highlights(a:winid, codes, highlights)\nendfunction\n\nfunction! s:empty_border(border) abort\n  if empty(a:border) || empty(filter(copy(a:border), 'v:val != 0'))\n    return 1\n  endif\n  return 0\nendfunction\n\nfunction! s:get_borderchars(config) abort\n  let borderchars = get(a:config, 'borderchars', [])\n  if !empty(borderchars)\n    return borderchars\n  endif\n  return get(a:config, 'rounded', 0) ? s:rounded_borderchars : s:borderchars\nendfunction\n\nfunction! s:scroll_win(winid, forward, amount) abort\n  if s:is_vim\n    call coc#float#scroll_win(a:winid, a:forward, a:amount)\n  else\n    call timer_start(0, { -> coc#float#scroll_win(a:winid, a:forward, a:amount)})\n  endif\nendfunction\n\nfunction! s:get_borderhighlight(config) abort\n  let hlgroup = get(a:config, 'highlight', 'CocFloating')\n  let borderhighlight = get(a:config, 'borderhighlight', 'CocFloatBorder')\n  let highlight = type(borderhighlight) == 3 ? borderhighlight[0] : borderhighlight\n  return coc#hlgroup#compose_hlgroup(highlight, hlgroup)\nendfunction\n\nfunction! s:has_shadow(config) abort\n  let border = get(a:config, 'border', [])\n  let filtered = filter(copy(border), 'type(v:val) == 3 && get(v:val, 1, \"\") ==# \"FloatShadow\"')\n  return len(filtered) > 0\nendfunction\n"
  },
  {
    "path": "autoload/coc/highlight.vim",
    "content": "scriptencoding utf-8\nlet s:is_vim = !has('nvim')\n\nlet s:func_map = {\n    \\ 'del_markers': 'coc#vim9#Del_markers',\n    \\ 'buffer_update': 'coc#vim9#Buffer_update',\n    \\ 'update_highlights': 'coc#vim9#Update_highlights',\n    \\ 'set_highlights': 'coc#vim9#Set_highlights',\n    \\ 'clear_highlights': 'coc#vim9#Clear_highlights',\n    \\ 'add_highlight': 'coc#vim9#Add_highlight',\n    \\ 'clear_all': 'coc#vim9#Clear_all',\n    \\ 'highlight_ranges': 'coc#vim9#Highlight_ranges',\n    \\ }\n\nfunction! s:call(name, args) abort\n  try\n    if s:is_vim\n      call call(s:func_map[a:name], a:args)\n    else\n      call call(v:lua.require('coc.highlight')[a:name], a:args)\n    endif\n  catch /.*/\n    \" Need try catch here on vim9\n    let name = s:is_vim ? get(s:func_map, a:name, a:name) : 'coc.highlight.' . a:name\n    call coc#compat#send_error(name, s:is_vim)\n  endtry\nendfunction\n\n\" Update highlights of whole buffer, bufnr: number, key: any, highlights: list<any>, priority: number = 10, changedtick: any = null\nfunction! coc#highlight#buffer_update(...) abort\n  call s:call('buffer_update', a:000)\nendfunction\n\n\" Update highlights, id: number, key: string, highlights: list<any>, start: number = 0, end: number = -1, priority: any = null\nfunction! coc#highlight#update_highlights(...) abort\n  call s:call('update_highlights', a:000)\nendfunction\n\n\" Add multiple highlights, bufnr: number, key: any, highlights: list<any>, priority: number = 0\nfunction! coc#highlight#set(...) abort\n  call s:call('set_highlights', a:000)\nendfunction\n\n\" bufnr: number, ids: list<number>\nfunction! coc#highlight#del_markers(...) abort\n  call s:call('del_markers', a:000)\nendfunction\n\n\" id: number, key: any, start_line: number = 0, end_line: number = -1\nfunction! coc#highlight#clear_highlight(...) abort\n  call s:call('clear_highlights', a:000)\nendfunction\n\n\" Add single highlight, id: number, src_id: number, hl_group: string, line: number, col_start: number, col_end: number, opts: dict<any> = {}\nfunction! coc#highlight#add_highlight(...) abort\n  call s:call('add_highlight', a:000)\nendfunction\n\n\" Clear all extmark or textprop highlights of coc.nvim\nfunction! coc#highlight#clear_all() abort\n  call s:call('clear_all', [])\nendfunction\n\n\" highlight LSP ranges. id: number, key: any, hlGroup: string, ranges: list<any>, opts: dict<any> = {}\nfunction! coc#highlight#ranges(...) abort\n  call s:call('highlight_ranges', a:000)\nendfunction\n\n\" Get list of highlights, bufnr, key, [start, end] 0 based line index.\nfunction! coc#highlight#get_highlights(bufnr, key, ...) abort\n  if s:is_vim\n    return coc#vim9#Get_highlights(a:bufnr, a:key, get(a:, 1, 0), get(a:, 2, -1))\n  endif\n  return v:lua.require('coc.highlight').get_highlights(a:bufnr, a:key, get(a:, 1, 0), get(a:, 2, -1))\nendfunction\n\n\" highlight buffer in winid with CodeBlock and HighlightItems\n\" export interface HighlightItem {\n\"   lnum: number // 0 based\n\"   hlGroup: string\n\"   colStart: number // 0 based\n\"   colEnd: number\n\" }\n\" export interface CodeBlock {\n\"   filetype?: string\n\"   hlGroup?: string\n\"   startLine: number // 0 based\n\"   endLine: number\n\" }\nfunction! coc#highlight#add_highlights(winid, codes, highlights) abort\n  \" clear highlights\n  let bufnr = winbufnr(a:winid)\n  let kind = getwinvar(a:winid, 'kind', '')\n  if kind !=# 'pum'\n    call win_execute(a:winid, 'syntax clear')\n    if !empty(a:codes)\n      call coc#highlight#highlight_lines(a:winid, a:codes)\n    endif\n  endif\n  call coc#highlight#buffer_update(bufnr, -1, a:highlights)\nendfunction\n\n\" Add highlights to line groups of winid, support hlGroup and filetype\n\" config should have startLine, endLine (0 based, end excluded) and filetype or hlGroup\n\" endLine should > startLine and endLine is excluded\nfunction! coc#highlight#highlight_lines(winid, blocks) abort\n  let region_id = 1\n  let defined = []\n  let cmds = []\n  for config in a:blocks\n    let start = config['startLine'] + 1\n    let end = config['endLine'] == -1 ? len(getbufline(winbufnr(a:winid), 1, '$')) + 1 : config['endLine'] + 1\n    let filetype = get(config, 'filetype', '')\n    let hlGroup = get(config, 'hlGroup', '')\n    if !empty(hlGroup)\n      call add(cmds, 'syntax region '.hlGroup.' start=/\\%'.start.'l/ end=/\\%'.end.'l/')\n    else\n      let filetype = matchstr(filetype, '\\v^\\w+')\n      if empty(filetype) || filetype == 'txt' || index(get(g:, 'coc_markdown_disabled_languages', []), filetype) != -1\n        continue\n      endif\n      if index(defined, filetype) == -1\n        call add(cmds, 'syntax include @'.toupper(filetype).' syntax/'.filetype.'.vim')\n        call add(cmds, 'unlet! b:current_syntax')\n        call add(defined, filetype)\n      endif\n      call add(cmds, 'syntax region CodeBlock'.region_id.' start=/\\%'.start.'l/ end=/\\%'.end.'l/ contains=@'.toupper(filetype).' keepend')\n      let region_id = region_id + 1\n    endif\n  endfor\n  if !empty(cmds)\n    call win_execute(a:winid, cmds, 'silent!')\n  endif\nendfunction\n\n\" add matches for window. winid, bufnr, ranges, hlGroup, priority\nfunction! coc#highlight#match_ranges(winid, bufnr, ranges, hlGroup, priority) abort\n  if s:is_vim\n    return coc#vim9#Match_ranges(a:winid, a:bufnr, a:ranges, a:hlGroup, a:priority)\n  endif\n  return v:lua.require('coc.highlight').match_ranges(a:winid, a:bufnr, a:ranges, a:hlGroup, a:priority)\nendfunction\n\n\" Clear matches by hlGroup regexp, used by extension\nfunction! coc#highlight#clear_match_group(winid, match) abort\n  call coc#window#clear_match_group(a:winid, a:match)\nendfunction\n\n\" Clear matches by match ids, use 0 for current win.\nfunction! coc#highlight#clear_matches(winid, ids)\n  call coc#window#clear_matches(a:winid, a:ids)\nendfunction\n\nfunction! coc#highlight#create_namespace(key) abort\n  if type(a:key) == v:t_number\n    return a:key\n  endif\n  return coc#compat#call('create_namespace', ['coc-'. a:key])\nendfunction\n"
  },
  {
    "path": "autoload/coc/hlgroup.vim",
    "content": "scriptencoding utf-8\n\nfunction! coc#hlgroup#valid(hlGroup) abort\n  return hlexists(a:hlGroup) && execute('hi '.a:hlGroup, 'silent!') !~# ' cleared$'\nendfunction\n\nfunction! coc#hlgroup#compose(fg, bg) abort\n  let fgId = synIDtrans(hlID(a:fg))\n  let bgId = synIDtrans(hlID(a:bg))\n  let isGuiReversed = synIDattr(fgId, 'reverse', 'gui') !=# '1' || synIDattr(bgId, 'reverse', 'gui') !=# '1'\n  let guifg = isGuiReversed ? synIDattr(fgId, 'fg', 'gui') : synIDattr(fgId, 'bg', 'gui')\n  let guibg = isGuiReversed ? synIDattr(bgId, 'bg', 'gui') : synIDattr(bgId, 'fg', 'gui')\n  let isCtermReversed = synIDattr(fgId, 'reverse', 'cterm') !=# '1' || synIDattr(bgId, 'reverse', 'cterm') !=# '1'\n  let ctermfg = isCtermReversed ? synIDattr(fgId, 'fg', 'cterm') : synIDattr(fgId, 'bg', 'cterm')\n  let ctermbg = isCtermReversed ? synIDattr(bgId, 'bg', 'cterm') : synIDattr(bgId, 'fg', 'cterm')\n  let bold = synIDattr(fgId, 'bold') ==# '1'\n  let italic = synIDattr(fgId, 'italic') ==# '1'\n  let underline = synIDattr(fgId, 'underline') ==# '1'\n  let cmd = ''\n  if !empty(guifg)\n    let cmd .= ' guifg=' . guifg\n  endif\n  if !empty(ctermfg)\n    let cmd .= ' ctermfg=' . ctermfg\n  endif\n  if !empty(guibg)\n    let cmd .= ' guibg=' . guibg\n  endif\n  if !empty(ctermbg)\n    let cmd .= ' ctermbg=' . ctermbg\n  endif\n  if bold\n    let cmd .= ' cterm=bold gui=bold'\n  elseif italic\n    let cmd .= ' cterm=italic gui=italic'\n  elseif underline\n    let cmd .= ' cterm=underline gui=underline'\n  endif\n  return cmd\nendfunction\n\n\" Compose hlGroups with foreground and background colors.\nfunction! coc#hlgroup#compose_hlgroup(fgGroup, bgGroup) abort\n  let hlGroup = 'Fg'.a:fgGroup.'Bg'.a:bgGroup\n  if a:fgGroup ==# a:bgGroup\n    return a:fgGroup\n  endif\n  if coc#hlgroup#valid(hlGroup)\n    return hlGroup\n  endif\n  let cmd = coc#hlgroup#compose(a:fgGroup, a:bgGroup)\n  if empty(cmd)\n      return 'Normal'\n  endif\n  execute 'silent hi ' . hlGroup . cmd\n  return hlGroup\nendfunction\n\n\" hlGroup id, key => 'fg' | 'bg', kind => 'cterm' | 'gui'\nfunction! coc#hlgroup#get_color(id, key, kind) abort\n  if synIDattr(a:id, 'reverse', a:kind) !=# '1'\n    return synIDattr(a:id, a:key, a:kind)\n  endif\n  return  synIDattr(a:id, a:key ==# 'bg' ? 'fg' : 'bg', a:kind)\nendfunction\n\nfunction! coc#hlgroup#get_hl_command(id, key, cterm, gui) abort\n  let cterm = coc#hlgroup#get_color(a:id, a:key, 'cterm')\n  let gui = coc#hlgroup#get_color(a:id, a:key, 'gui')\n  let cmd = ' cterm'.a:key.'=' . (empty(cterm) ? a:cterm : cterm)\n  let cmd .= ' gui'.a:key.'=' . (empty(gui) ? a:gui : gui)\n  return cmd\nendfunction\n\nfunction! coc#hlgroup#get_hex_color(id, kind, fallback) abort\n  let term_colors = s:use_term_colors()\n  let attr = coc#hlgroup#get_color(a:id, a:kind, term_colors ? 'cterm' : 'gui')\n  let hex = s:to_hex_color(attr, term_colors)\n  if empty(hex) && !term_colors\n    let attr = coc#hlgroup#get_color(a:id, a:kind, 'cterm')\n    let hex = s:to_hex_color(attr, 1)\n  endif\n  return empty(hex) ? a:fallback : hex\nendfunction\n\nfunction! coc#hlgroup#get_contrast(group1, group2) abort\n  let normal = coc#hlgroup#get_hex_color(synIDtrans(hlID('Normal')), 'bg', '#000000')\n  let bg1 = coc#hlgroup#get_hex_color(synIDtrans(hlID(a:group1)), 'bg', normal)\n  let bg2 = coc#hlgroup#get_hex_color(synIDtrans(hlID(a:group2)), 'bg', normal)\n  return coc#color#hex_contrast(bg1, bg2)\nendfunction\n\n\" Darken or lighten background\nfunction! coc#hlgroup#create_bg_command(group, amount) abort\n  let id = synIDtrans(hlID(a:group))\n  let normal = coc#hlgroup#get_hex_color(synIDtrans(hlID('Normal')), 'bg', &background ==# 'dark' ? '#282828' : '#fefefe')\n  let bg = coc#hlgroup#get_hex_color(id, 'bg', normal)\n  let hex = a:amount > 0 ? coc#color#darken(bg, a:amount) : coc#color#lighten(bg, -a:amount)\n\n  let ctermbg = coc#color#rgb2term(strpart(hex, 1))\n  if s:use_term_colors() && !s:check_ctermbg(id, ctermbg) && abs(a:amount) < 20.0\n    return coc#hlgroup#create_bg_command(a:group, a:amount * 2)\n  endif\n  return 'ctermbg=' . ctermbg.' guibg=' . hex\nendfunction\n\n\nfunction! s:check_ctermbg(id, cterm) abort\n  let attr = coc#hlgroup#get_color(a:id, 'bg', 'cterm')\n  if empty(attr)\n    let attr = coc#hlgroup#get_color(synIDtrans(hlID('Normal')), 'bg', 'cterm')\n  endif\n  if attr ==# a:cterm\n    return 0\n  endif\n  return 1\nendfunction\n\nfunction! s:to_hex_color(color, term) abort\n  if empty(a:color)\n    return ''\n  endif\n  if a:color =~# '^#\\x\\+$'\n    return a:color\n  endif\n  if a:term && a:color =~# '^\\d\\+$'\n    return coc#color#term2rgb(a:color)\n  endif\n  let hex = coc#color#nameToHex(tolower(a:color), a:term)\n  return empty(hex) ? '' : hex\nendfunction\n\n\" Can't use script variable as nvim change it after VimEnter\nfunction! s:use_term_colors() abort\n  return &termguicolors == 0 && !has('gui_running')\nendfunction\n"
  },
  {
    "path": "autoload/coc/inline.vim",
    "content": "scriptencoding utf-8\nlet s:is_vim = !has('nvim')\nlet s:inline_ns = coc#highlight#create_namespace('inlineSuggest')\nlet s:is_supported = has('patch-9.0.0185') || has('nvim-0.7')\nlet s:hl_group = 'CocInlineVirtualText'\nlet s:annot_hlgroup = 'CocInlineAnnotation'\n\nfunction! coc#inline#visible() abort\n  return coc#vtext#exists(bufnr('%'), s:inline_ns)\nendfunction\n\nfunction! coc#inline#trigger(...) abort\n  call coc#inline#clear()\n  call CocActionAsync('inlineTrigger', bufnr('%'), get(a:, 1))\n  return ''\nendfunction\n\nfunction! coc#inline#cancel() abort\n  call coc#inline#clear()\n  call CocActionAsync('inlineCancel')\n  return ''\nendfunction\n\nfunction! coc#inline#accept(...) abort\n  if coc#inline#visible()\n    call CocActionAsync('inlineAccept', bufnr('%'), get(a:, 1, 'all'))\n  endif\n  return ''\nendfunction\n\nfunction! coc#inline#next() abort\n  if coc#inline#visible()\n    call CocActionAsync('inlineNext', bufnr('%'))\n  endif\n  return ''\nendfunction\n\nfunction! coc#inline#prev() abort\n  if coc#inline#visible()\n    call CocActionAsync('inlinePrev', bufnr('%'))\n  endif\n  return ''\nendfunction\n\nfunction! coc#inline#clear(...) abort\n  let bufnr = get(a:, 1, bufnr('%'))\n  call coc#compat#call('buf_clear_namespace', [bufnr, s:inline_ns, 0, -1])\nendfunction\n\nfunction! coc#inline#_insert(bufnr, lineidx, col, lines, annot) abort\n  if !s:is_supported || bufnr('%') != a:bufnr || mode() !~ '^i' || col('.') != a:col\n    return v:false\n  endif\n  call coc#inline#clear(a:bufnr)\n  call coc#pum#clear_vtext()\n\n  if empty(get(a:lines, 0, ''))\n    return v:false\n  endif\n\n  let option = {\n      \\ 'col': a:col,\n      \\ 'hl_mode': 'replace',\n      \\ }\n  let blocks = [[a:lines[0], s:hl_group]]\n  if !empty(a:annot)\n    let blocks += [[' '], [a:annot, s:annot_hlgroup]]\n  endif\n  if len(a:lines) > 1\n    let option['virt_lines'] = map(a:lines[1:], {idx, line -> [[line, s:hl_group]]})\n  endif\n  call coc#vtext#add(a:bufnr, s:inline_ns, a:lineidx, blocks, option)\n  return v:true\nendfunction\n"
  },
  {
    "path": "autoload/coc/list.vim",
    "content": "scriptencoding utf-8\nlet s:is_vim = !has('nvim')\nlet s:prefix = '[List Preview]'\nlet s:sign_group = 'CocList'\n\" filetype detect could be slow.\nlet s:filetype_map = {\n      \\ 'c': 'c',\n      \\ 'py': 'python',\n      \\ 'vim': 'vim',\n      \\ 'ts': 'typescript',\n      \\ 'js': 'javascript',\n      \\ 'html': 'html',\n      \\ 'css': 'css'\n      \\ }\nlet s:pwinid = -1\nlet s:pbufnr = -1\nlet s:sign_range = 'CocCursorLine'\nlet s:sign_popup_range = 'PopUpCocList'\nlet s:current_line_hl = 'CocListCurrent'\n\nfunction! coc#list#getchar() abort\n  return coc#prompt#getchar()\nendfunction\n\nfunction! coc#list#setlines(bufnr, lines, append)\n  if a:append\n    silent call appendbufline(a:bufnr, '$', a:lines)\n  else\n    if exists('*deletebufline')\n      silent call deletebufline(a:bufnr, len(a:lines) + 1, '$')\n    else\n      let n = len(a:lines) + 1\n      let saved_reg = @\"\n      silent execute n.',$d'\n      let @\" = saved_reg\n    endif\n    silent call setbufline(a:bufnr, 1, a:lines)\n  endif\nendfunction\n\nfunction! coc#list#options(...)\n  let list = ['--top', '--tab', '--buffer', '--workspace-folder', '--normal', '--no-sort',\n   \\ '--input=', '--strict', '--regex', '--interactive', '--number-select',\n   \\ '--auto-preview', '--ignore-case', '--no-quit', '--first', '--reverse', '--height=']\n  if get(g:, 'coc_enabled', 0)\n    let names = coc#rpc#request('listNames', [])\n    call extend(list, names)\n  endif\n  return join(list, \"\\n\")\nendfunction\n\nfunction! coc#list#names(...) abort\n  let names = coc#rpc#request('listNames', [])\n  return join(names, \"\\n\")\nendfunction\n\nfunction! coc#list#status(name)\n  if !exists('b:list_status') | return '' | endif\n  return get(b:list_status, a:name, '')\nendfunction\n\nfunction! coc#list#create(position, height, name, numberSelect)\n  if a:position ==# 'tab'\n    call coc#ui#safe_open('silent tabe', 'list:///'.a:name)\n  else\n    call s:save_views(-1)\n    let height = max([1, a:height])\n    let cmd = 'silent keepalt '.(a:position ==# 'top' ? '' : 'botright').height.'sp'\n    call coc#ui#safe_open(cmd, 'list:///'.a:name)\n    call s:set_height(height)\n    call s:restore_views()\n  endif\n  if a:numberSelect\n    setl norelativenumber\n    setl number\n  else\n    setl nonumber\n    setl norelativenumber\n  endif\n  if exists('&winfixbuf')\n    setl winfixbuf\n  endif\n  setl colorcolumn=\"\"\n  return [bufnr('%'), win_getid(), tabpagenr()]\nendfunction\n\n\" close list windows\nfunction! coc#list#clean_up() abort\n  for i in range(1, winnr('$'))\n    let bufname = bufname(winbufnr(i))\n    if bufname =~# 'list://'\n      execute i.'close!'\n    endif\n  endfor\nendfunction\n\nfunction! coc#list#setup(source)\n  let b:list_status = {}\n  setl buftype=nofile nobuflisted nofen nowrap\n  setl norelativenumber bufhidden=wipe nocursorline winfixheight\n  setl tabstop=1 nolist nocursorcolumn undolevels=-1\n  setl signcolumn=auto\n  setl foldcolumn=0\n  if exists('&cursorlineopt')\n    setl cursorlineopt=both\n  endif\n  if s:is_vim\n    setl nocursorline\n  else\n    setl cursorline\n    setl winhighlight=CursorLine:CocListLine\n  endif\n  setl scrolloff=0\n  setl filetype=list\n  syntax case ignore\n  let source = a:source[8:]\n  let name = toupper(source[0]).source[1:]\n  execute 'syntax match Coc'.name.'Line /\\v^.*$/'\n  if !s:is_vim\n    \" Repeat press <C-f> and <C-b> would invoke <esc> on vim\n    nnoremap <silent><nowait><buffer> <esc> <C-w>c\n  endif\nendfunction\n\nfunction! coc#list#close(winid, position, target_win, saved_height) abort\n  let tabnr = coc#window#tabnr(a:winid)\n  if a:position ==# 'tab'\n    if tabnr != -1\n      call coc#list#close_preview(tabnr, 0)\n    endif\n    call coc#window#close(a:winid)\n  else\n    call s:save_views(a:winid)\n    if tabnr != -1\n      call coc#list#close_preview(tabnr, 0)\n    endif\n    if type(a:target_win) == v:t_number\n      call win_gotoid(a:target_win)\n    endif\n    call coc#window#close(a:winid)\n    call s:restore_views()\n    if type(a:saved_height) == v:t_number\n      call coc#window#set_height(a:target_win, a:saved_height)\n    endif\n    \" call coc#rpc#notify('Log', [\"close\", a:target_win, v])\n  endif\nendfunction\n\nfunction! coc#list#select(bufnr, line) abort\n  if s:is_vim && !empty(a:bufnr) && bufloaded(a:bufnr)\n    call sign_unplace(s:sign_group, { 'buffer': a:bufnr })\n    if a:line > 0\n      call sign_place(6, s:sign_group, s:current_line_hl, a:bufnr, {'lnum': a:line})\n    endif\n  endif\nendfunction\n\n\" Check if previewwindow exists on current tab.\nfunction! coc#list#has_preview()\n  if s:pwinid != -1 && coc#window#visible(s:pwinid)\n    return 1\n  endif\n  for i in range(1, winnr('$'))\n    let preview = getwinvar(i, 'previewwindow', getwinvar(i, '&previewwindow', 0))\n    if preview\n      return i\n    endif\n  endfor\n  return 0\nendfunction\n\n\" Get previewwindow from tabnr, use 0 for current tab\nfunction! coc#list#get_preview(...) abort\n  if s:pwinid != -1 && coc#window#visible(s:pwinid)\n    return s:pwinid\n  endif\n  let tabnr = get(a:, 1, 0) == 0 ? tabpagenr() : a:1\n  let info = gettabinfo(tabnr)\n  if !empty(info)\n    for win in info[0]['windows']\n      if gettabwinvar(tabnr, win, 'previewwindow', 0)\n        return win\n      endif\n    endfor\n  endif\n  return -1\nendfunction\n\nfunction! coc#list#scroll_preview(dir, floatPreview) abort\n  let winid = coc#list#get_preview()\n  if winid == -1\n    return\n  endif\n  if a:floatPreview\n    let forward = a:dir ==# 'up' ? 0 : 1\n    let amount = 1\n    if s:is_vim\n      call coc#float#scroll_win(winid, forward, amount)\n    else\n      call timer_start(0, { -> coc#float#scroll_win(winid, forward, amount)})\n    endif\n    return\n  endif\n  call win_execute(winid, \"normal! \".(a:dir ==# 'up' ? \"\\<C-u>\" : \"\\<C-d>\"))\nendfunction\n\nfunction! coc#list#close_preview(...) abort\n  let tabnr = get(a:, 1, tabpagenr())\n  let winid = coc#list#get_preview(tabnr)\n  if winid != -1\n    let keep = get(a:, 2, 1) && tabnr == tabpagenr() && !coc#window#is_float(winid)\n    if keep\n      call s:save_views(winid)\n    endif\n    call coc#window#close(winid)\n    if keep\n      call s:restore_views()\n    endif\n  endif\nendfunction\n\nfunction! s:get_preview_lines(lines, config) abort\n  if empty(a:lines)\n    if get(a:config, 'scheme', 'file') !=# 'file'\n      let bufnr = s:load_buffer(get(a:config, 'name', ''))\n      return bufnr == 0 ? [''] : getbufline(bufnr, 1, '$')\n    else\n      return ['']\n    endif\n  endif\n  return a:lines\nendfunction\n\nfunction! coc#list#float_preview(lines, config) abort\n  let position = get(a:config, 'position', 'bottom')\n  if position ==# 'tab'\n    throw 'unable to use float preview'\n  endif\n  let remain = 0\n  let winrow = win_screenpos(winnr())[0]\n  if position ==# 'bottom'\n    let remain = winrow - 2\n  else\n    let winbottom = winrow + winheight(winnr())\n    let remain = &lines - &cmdheight - 1 - winbottom\n  endif\n  let lines = s:get_preview_lines(a:lines, a:config)\n  let height = s:get_preview_height(lines, a:config)\n  let height = min([remain, height + 2])\n  if height < 0\n    return\n  endif\n  let row = position ==# 'bottom' ? winrow - 3 - height : winrow + winheight(winnr())\n  let title = fnamemodify(get(a:config, 'name', ''), ':.')\n  let total = get(get(b:, 'list_status', {}), 'total', 0)\n  if !empty(total)\n    let title .= ' ('.line('.').'/'.total.')'\n  endif\n  let lnum = min([get(a:config, 'lnum', 1), len(lines)])\n  let opts = {\n      \\ 'relative': 'editor',\n      \\ 'width': winwidth(winnr()) - 2,\n      \\ 'borderhighlight': 'MoreMsg',\n      \\ 'highlight': 'Normal',\n      \\ 'height': height,\n      \\ 'col': 0,\n      \\ 'index': lnum - 1,\n      \\ 'row': row,\n      \\ 'border': [1,1,1,1],\n      \\ 'rounded': 1,\n      \\ 'lines': lines,\n      \\ 'scrollinside': 1,\n      \\ 'title': title,\n      \\ }\n  let result = coc#float#create_float_win(s:pwinid, s:pbufnr, opts)\n  if empty(result)\n    return\n  endif\n  let s:pwinid = result[0]\n  let s:pbufnr = result[1]\n  call setwinvar(s:pwinid, 'previewwindow', 1)\n  let topline = s:get_topline(a:config, lnum, height)\n  call coc#window#restview(s:pwinid, lnum, topline)\n  call s:preview_highlights(s:pwinid, s:pbufnr, a:config, 1)\nendfunction\n\n\" Improve preview performance by reused window & buffer.\n\" lines - list of lines\n\" config.position - could be 'bottom' 'top' 'tab'.\n\" config.winid - id of original window.\n\" config.name - (optional )name of preview buffer.\n\" config.splitRight - (optional) split to right when 1.\n\" config.lnum - (optional) current line number\n\" config.filetype - (optional) filetype of lines.\n\" config.range - (optional) highlight range. with hlGroup.\n\" config.hlGroup - (optional) highlight group.\n\" config.maxHeight - (optional) max height of window, valid for 'bottom' & 'top' position.\nfunction! coc#list#preview(lines, config) abort\n  let lines = s:get_preview_lines(a:lines, a:config)\n  let winid = coc#list#get_preview(0)\n  let bufnr = winid == -1 ? 0 : winbufnr(winid)\n  \" Try reuse buffer & window\n  let bufnr = coc#float#create_buf(bufnr, lines)\n  if bufnr == 0\n    return\n  endif\n  let lnum = get(a:config, 'lnum', 1)\n  let position = get(a:config, 'position', 'bottom')\n  let original = get(a:config, 'winid', -1)\n  if winid == -1\n    let change = position != 'tab' && get(a:config, 'splitRight', 0)\n    let curr = win_getid()\n    if change\n      if original && win_id2win(original)\n        noa call win_gotoid(original)\n      else\n        noa wincmd t\n      endif\n      execute 'noa belowright vert sb '.bufnr\n      let winid = win_getid()\n    elseif position == 'tab' || get(a:config, 'splitRight', 0)\n      execute 'noa belowright vert sb '.bufnr\n      let winid = win_getid()\n    else\n      let mod = position == 'top' ? 'below' : 'above'\n      let height = s:get_preview_height(lines, a:config)\n      call s:save_views(-1)\n      execute 'noa '.mod.' sb +resize\\ '.height.' '.bufnr\n      call s:restore_views()\n      let winid = win_getid()\n    endif\n    call setbufvar(bufnr, '&synmaxcol', 500)\n    noa call winrestview({\"lnum\": lnum ,\"topline\":s:get_topline(a:config, lnum, winheight(winid))})\n    call s:set_preview_options(winid)\n    noa call win_gotoid(curr)\n  else\n    let height = s:get_preview_height(lines, a:config)\n    if height > 0\n      if s:is_vim\n        let curr = win_getid()\n        noa call win_gotoid(winid)\n        execute 'silent! noa resize '.height\n        noa call win_gotoid(curr)\n      else\n        call s:save_views(winid)\n        call nvim_win_set_height(winid, height)\n        call s:restore_views()\n      endif\n    endif\n    call coc#window#restview(winid, lnum, s:get_topline(a:config, lnum, height))\n  endif\n  call s:preview_highlights(winid, bufnr, a:config, 0)\nendfunction\n\nfunction! s:preview_highlights(winid, bufnr, config, float) abort\n  let name = fnamemodify(get(a:config, 'name', ''), ':.')\n  let newname = s:prefix.' '.name\n  if newname !=# bufname(a:bufnr)\n    if s:is_vim\n      call win_execute(a:winid, 'noa file '.fnameescape(newname), 'silent!')\n    else\n      silent! noa call nvim_buf_set_name(a:bufnr, newname)\n    endif\n  endif\n\n  let filetype = get(a:config, 'filetype', '')\n  let extname = matchstr(name, '\\.\\zs[^.]\\+$')\n  if empty(filetype) && !empty(extname)\n    let filetype = get(s:filetype_map, extname, '')\n  endif\n  \" highlights\n  let sign_group = s:is_vim && a:float ? s:sign_popup_range : s:sign_range\n  call win_execute(a:winid, ['syntax clear', 'call clearmatches()'])\n  call sign_unplace(sign_group, {'buffer': a:bufnr})\n  let lnum = get(a:config, 'lnum', 1)\n  if !empty(filetype)\n    if get(g:, 'coc_list_preview_filetype', 0)\n      call win_execute(a:winid, 'setf '.filetype)\n    else\n      let start = max([0, lnum - 300])\n      let end = min([coc#compat#buf_line_count(a:bufnr), lnum + 300])\n      call coc#highlight#highlight_lines(a:winid, [{'filetype': filetype, 'startLine': start, 'endLine': end}])\n      call win_execute(a:winid, 'syn sync fromstart')\n    endif\n  else\n    call win_execute(a:winid, 'filetype detect')\n    let ft = getbufvar(a:bufnr, '&filetype', '')\n    if !empty(extname) && !empty(ft)\n      let s:filetype_map[extname] = ft\n    endif\n  endif\n  \" selection range\n  let targetRange = get(a:config, 'targetRange', v:null)\n  if !empty(targetRange)\n    for lnum in range(targetRange['start']['line'] + 1, targetRange['end']['line'] + 1)\n      call sign_place(0, sign_group, s:current_line_hl, a:bufnr, {'lnum': lnum})\n    endfor\n  endif\n  let range = get(a:config, 'range', v:null)\n  if !empty(range)\n    let hlGroup = get(a:config, 'hlGroup', 'Search')\n    call coc#highlight#match_ranges(a:winid, a:bufnr, [range], hlGroup, 10)\n  endif\nendfunction\n\nfunction! s:get_preview_height(lines, config) abort\n  if get(a:config, 'splitRight', 0) || get(a:config, 'position', 'bottom') == 'tab'\n    return 0\n  endif\n  let height = min([get(a:config, 'maxHeight', 10), len(a:lines), &lines - &cmdheight - 2])\n  return height\nendfunction\n\nfunction! s:load_buffer(name) abort\n  if exists('*bufadd') && exists('*bufload')\n    let bufnr = bufadd(a:name)\n    call bufload(bufnr)\n    return bufnr\n  endif\n  return 0\nendfunction\n\nfunction! s:get_topline(config, lnum, winheight) abort\n  let toplineStyle = get(a:config, 'toplineStyle', 'offset')\n  if toplineStyle == 'middle'\n    return max([1, a:lnum - a:winheight/2])\n  endif\n  let toplineOffset = get(a:config, 'toplineOffset', 3)\n  return max([1, a:lnum - toplineOffset])\nendfunction\n\nfunction! s:set_preview_options(winid) abort\n  call setwinvar(a:winid, '&foldmethod', 'manual')\n  call setwinvar(a:winid, '&foldenable', 0)\n  call setwinvar(a:winid, '&signcolumn', 'no')\n  call setwinvar(a:winid, '&number', 1)\n  call setwinvar(a:winid, '&cursorline', 0)\n  call setwinvar(a:winid, '&relativenumber', 0)\n  call setwinvar(a:winid, 'previewwindow', 1)\nendfunction\n\n\" save views on current tabpage\nfunction! s:save_views(exclude) abort\n  \" Not work as expected when cursor becomes hidden\n  if s:is_vim\n    return\n  endif\n  for nr in range(1, winnr('$'))\n    let winid = win_getid(nr)\n    if winid != a:exclude && getwinvar(nr, 'previewwindow', 0) == 0 && !coc#window#is_float(winid)\n      call win_execute(winid, 'let w:coc_list_saved_view = winsaveview()')\n    endif\n  endfor\nendfunction\n\nfunction! s:restore_views() abort\n  if s:is_vim\n    return\n  endif\n  for nr in range(1, winnr('$'))\n    let saved = getwinvar(nr, 'coc_list_saved_view', v:null)\n    if !empty(saved)\n      let winid = win_getid(nr)\n      call win_execute(winid, 'noa call winrestview(w:coc_list_saved_view) | unlet w:coc_list_saved_view')\n    endif\n  endfor\nendfunction\n\nfunction! s:set_height(height) abort\n  let curr = winheight(0)\n  if curr != a:height\n    execute 'resize '.a:height\n  endif\nendfunction\n"
  },
  {
    "path": "autoload/coc/math.vim",
    "content": "\n\" support for float values\nfunction! coc#math#min(first, ...) abort\n  let val = a:first\n  for i in range(0, len(a:000) - 1)\n    if a:000[i] < val\n      let val = a:000[i]\n    endif\n  endfor\n  return val\nendfunction\n"
  },
  {
    "path": "autoload/coc/notify.vim",
    "content": "scriptencoding utf-8\nlet s:is_vim = !has('nvim')\nlet s:utf = has('nvim') || &encoding =~# '^utf'\nlet s:error_icon = get(g:, 'coc_notify_error_icon', s:utf ? \"\\uf057\" : 'E')\nlet s:warning_icon = get(g:, 'coc_notify_warning_icon', s:utf ? \"\\u26a0\" : 'W')\nlet s:info_icon = get(g:, 'coc_notify_info_icon', s:utf ? \"\\uf06a\" : 'I')\nlet s:interval = get(g:, 'coc_notify_interval', s:is_vim ? 50 : 20)\nlet s:phl = 'CocNotificationProgress'\nlet s:progress_char = '─'\nlet s:duration = 300.0\nlet s:winids = []\nlet s:fn_keys = [\"\\<F1>\",\"\\<F2>\",\"\\<F3>\",\"\\<F4>\",\"\\<F5>\",\"\\<F6>\",\"\\<F7>\",\"\\<F8>\",\"\\<F9>\"]\n\n\" Valid notify winids on current tab\nfunction! coc#notify#win_list() abort\n  call filter(s:winids, 'coc#float#valid(v:val)')\n  return filter(copy(s:winids), '!empty(getwinvar(v:val,\"float\"))')\nendfunction\n\nfunction! coc#notify#close_all() abort\n  for winid in coc#notify#win_list()\n    call coc#notify#close(winid)\n  endfor\nendfunction\n\n\" Do action for winid or first notify window with actions.\nfunction! coc#notify#do_action(...) abort\n  let winids = a:0 > 0 ? a:000 : coc#notify#win_list()\n  for winid in winids\n    if coc#float#valid(winid) && getwinvar(winid, 'closing', 0) != 1\n      let actions = getwinvar(winid, 'actions', [])\n      if !empty(actions)\n        let items = map(copy(actions), '(v:key + 1).\". \".v:val')\n        let msg = join(getbufline(winbufnr(winid), 1, '$'), ' ')\n        call coc#ui#quickpick(msg, items, {err, res -> s:on_action(err, res, winid) })\n        break\n      endif\n    endif\n  endfor\nendfunction\n\n\" Copy notification contents\nfunction! coc#notify#copy() abort\n  let lines = []\n  for winid in coc#notify#win_list()\n    let key = getwinvar(winid, 'key', v:null)\n    if type(key) == v:t_string\n      call extend(lines, json_decode(key)['lines'])\n    endif\n  endfor\n  if empty(lines)\n    echohl WarningMsg | echon 'No content to copy' | echohl None\n    return\n  endif\n  call setreg('*', join(lines, \"\\n\"))\nendfunction\n\n\" Show source name in window\nfunction! coc#notify#show_sources() abort\n  if !exists('*getbufline') || !exists('*appendbufline')\n    throw \"getbufline and appendbufline functions required, please upgrade your vim.\"\n  endif\n  let winids = filter(coc#notify#win_list(), 'coc#window#get_var(v:val,\"closing\") != 1')\n  for winid in winids\n    let key = getwinvar(winid, 'key', v:null)\n    if type(key) == v:t_string\n      let bufnr = winbufnr(winid)\n      let obj = json_decode(key)\n      let sourcename = get(obj, 'source', '')\n      let lnum = get(obj, 'kind', '') ==# 'progress' ? 1 : 0\n      let content = get(getbufline(bufnr, lnum + 1), 0, '')\n      if empty(sourcename) || content ==# sourcename\n        continue\n      endif\n      call appendbufline(bufnr, lnum, sourcename)\n      call coc#highlight#add_highlight(bufnr, -1, 'Title', lnum, 0, -1)\n      call coc#float#scroll_win(winid, 0, 1)\n    endif\n  endfor\n  redra\nendfunction\n\nfunction! coc#notify#close_by_source(source) abort\n  let winids = filter(coc#notify#win_list(), 'coc#window#get_var(v:val,\"closing\") != 1')\n  for winid in winids\n    let key = getwinvar(winid, 'key', v:null)\n    if type(key) == v:t_string\n      let obj = json_decode(key)\n      if get(obj, 'source', '') ==# a:source\n        call coc#notify#close(winid)\n      endif\n    endif\n  endfor\nendfunction\n\n\" Cancel auto hide\nfunction! coc#notify#keep() abort\n  for winid in coc#notify#win_list()\n    call s:cancel(winid, 'close_timer')\n  endfor\nendfunction\n\n\" borderhighlight - border highlight [string]\n\" maxWidth - max content width, default 60 [number]\n\" minWidth - minimal width [number]\n\" maxHeight - max content height, default 10 [number]\n\" highlight - default highlight [string]\n\" winblend - winblend [number]\n\" timeout - auto close timeout, default 5000 [number]\n\" title - title text\n\" marginRight - margin right, default 10 [number]\n\" focusable - focusable [number]\n\" source -  source name [string]\n\" kind - kind for create icon [string]\n\" actions - action names [string[]]\n\" close - close button [boolean]\nfunction! coc#notify#create(lines, config) abort\n  let actions = get(a:config, 'actions', [])\n  if s:is_vim\n    let actions = map(actions, 'v:val. \"<F\".(v:key + 1).\">\"')\n  endif\n  let key = json_encode(extend({'lines': a:lines}, a:config))\n  let winid = s:find_win(key)\n  let kind = get(a:config, 'kind', '')\n  let row = 0\n  \" Close duplicated window\n  if winid != -1\n    let row = getwinvar(winid, 'top', 0)\n    call filter(s:winids, 'v:val != '.winid)\n    call coc#float#close(winid, 1)\n  endif\n  let opts = coc#dict#pick(a:config, ['highlight', 'borderhighlight', 'focusable', 'shadow', 'close'])\n  let border = has_key(opts, 'borderhighlight') ? [1, 1, 1, 1] : []\n  let icon = s:get_icon(kind, get(a:config, 'highlight', 'CocFloating'))\n  let margin = get(a:config, 'marginRight', 10)\n  let maxWidth = min([&columns - margin - 2,  get(a:config, 'maxWidth', 80)])\n  if maxWidth <= 0\n    throw 'No enough spaces for notification'\n  endif\n  let lines = map(copy(a:lines), 'tr(v:val, \"\\t\", \" \")')\n  if has_key(a:config, 'title')\n    if !empty(border)\n      let opts['title'] = a:config['title']\n    else\n      let lines = [a:config['title']] + lines\n    endif\n  endif\n  let width = max(map(copy(lines), 'strwidth(v:val)')) + (empty(icon) ? 1 : 3)\n  if width > maxWidth\n    let lines = coc#string#reflow(lines, maxWidth)\n    let width = max(map(copy(lines), 'strwidth(v:val)')) + (empty(icon) ? 1 : 3)\n  endif\n  let highlights = []\n  if !empty(icon)\n    let ic = icon['text']\n    if empty(lines)\n      call add(lines, ic)\n    else\n      let lines[0] = ic.' '.lines[0]\n    endif\n    call add(highlights, {'lnum': 0, 'hlGroup': icon['hl'], 'colStart': 0, 'colEnd': strlen(ic)})\n  endif\n  let actionText = join(actions, ' ')\n  call map(lines, 'v:key == 0 ? v:val : repeat(\" \", '.(empty(icon) ? 0 : 2).').v:val')\n  let minWidth = get(a:config, 'minWidth', kind ==# 'progress' ? 30 : 10)\n  let width = max(extend(map(lines + [get(opts, 'title', '').'   '], 'strwidth(v:val)'), [minWidth, strwidth(actionText) + 1]))\n  let width = min([maxWidth, width])\n  let height = min([get(a:config, 'maxHeight', 3), len(lines)])\n  if kind ==# 'progress'\n    let lines = [repeat(s:progress_char, width)] + lines\n    let height = height + 1\n  endif\n  if !empty(actions)\n    let before = max([width - strdisplaywidth(actionText), 0])\n    let lines = lines + [repeat(' ', before).actionText]\n    let height = height + 1\n    call s:add_action_highlights(before, height - 1, highlights, actions)\n    if s:is_vim\n      let opts['filter'] = function('s:NotifyFilter', [len(actions)])\n    endif\n  endif\n  if row == 0\n    let wintop = coc#notify#get_top()\n    let row = wintop - height - (empty(border) ? 0 : 2) - 1\n    if !s:is_vim && !empty(border)\n      let row = row + 1\n    endif\n  endif\n  let col = &columns - margin - width\n  if s:is_vim && !empty(border)\n    let col = col - 2\n  endif\n  let winblend = 60\n  \" Avoid animate for transparent background.\n  if get(a:config, 'winblend', 30) == 0 && empty(synIDattr(synIDtrans(hlID(get(opts, 'highlight', 'CocFloating'))), 'bg', 'gui'))\n    let winblend = 0\n  endif\n  call extend(opts, {\n      \\ 'relative': 'editor',\n      \\ 'width': width,\n      \\ 'height': height,\n      \\ 'col': col,\n      \\ 'row': row + 1,\n      \\ 'lines': lines,\n      \\ 'rounded': 1,\n      \\ 'highlights': highlights,\n      \\ 'winblend': winblend,\n      \\ 'close': s:is_vim,\n      \\ 'border': border,\n      \\ 'bufhidden': 'wipe',\n      \\ })\n  let result = coc#float#create_float_win(0, 0, opts)\n  if empty(result)\n    return\n  endif\n  let winid = result[0]\n  let bufnr = result[1]\n  call setwinvar(winid, 'right', 1)\n  call setwinvar(winid, 'kind', 'notification')\n  call setwinvar(winid, 'top', row)\n  call setwinvar(winid, 'key', key)\n  call setwinvar(winid, 'actions', actions)\n  call setwinvar(winid, 'source', get(a:config, 'source', ''))\n  call setwinvar(winid, 'borders', !empty(border))\n  call coc#float#nvim_scrollbar(winid)\n  call add(s:winids, winid)\n  let from = {'row': opts['row'], 'winblend': opts['winblend']}\n  let to = {'row': row, 'winblend': get(a:config, 'winblend', 30)}\n  call timer_start(s:interval, { -> s:animate(winid, from, to, 0)})\n  if kind ==# 'progress'\n    call timer_start(s:interval, { -> s:progress(winid, width, 0, -1)})\n  endif\n  if !s:is_vim\n    call coc#compat#buf_add_keymap(bufnr, 'n', '<LeftRelease>', ':call coc#notify#nvim_click('.winid.')<CR>', {\n        \\ 'silent': v:true,\n        \\ 'nowait': v:true\n        \\ })\n  endif\n  \" Enable auto close\n  if empty(actions) && kind !=# 'progress'\n    let timer = timer_start(get(a:config, 'timeout', 10000), { -> coc#notify#close(winid)})\n    call setwinvar(winid, 'close_timer', timer)\n  endif\n  return [winid, bufnr]\nendfunction\n\nfunction! coc#notify#nvim_click(winid) abort\n  if getwinvar(a:winid, 'closing', 0)\n    return\n  endif\n  call s:cancel(a:winid, 'close_timer')\n  let actions = getwinvar(a:winid, 'actions', [])\n  if !empty(actions)\n    let hls = filter(coc#highlight#get_highlights(bufnr('%'), -1), \"v:val[0] ==# 'CocNotificationButton'\")\n    if empty(hls)\n      \" Something went wrong.\n      return\n    endif\n    if line('.') != hls[0][1] + 1\n      return\n    endif\n    let col = col('.')\n    let line = getline('.')\n    for idx in range(0, len(hls) - 1)\n      let item = hls[idx]\n      let start_idx = coc#string#byte_index(line, item[2])\n      let end_idx = coc#string#byte_index(line, item[3])\n      if col > start_idx && col <= end_idx\n        call coc#notify#choose(a:winid, idx)\n      endif\n    endfor\n  endif\nendfunction\n\nfunction! coc#notify#on_close(winid) abort\n  if index(s:winids, a:winid) >= 0\n    call filter(s:winids, 'v:val != '.a:winid)\n    call coc#notify#reflow()\n  endif\nendfunction\n\nfunction! coc#notify#get_top() abort\n  let mintop = min(map(coc#notify#win_list(), 'coc#notify#get_win_top(v:val)'))\n  if mintop != 0\n    return mintop\n  endif\n  return &lines - &cmdheight - (&laststatus == 0 ? 0 : 1 )\nendfunction\n\nfunction! coc#notify#get_win_top(winid) abort\n  let row = getwinvar(a:winid, 'top', 0)\n  if row == 0\n    return row\n  endif\n  return row - (s:is_vim ? 0 : getwinvar(a:winid, 'borders', 0))\nendfunction\n\n\" Close with timer\nfunction! coc#notify#close(winid) abort\n  if !coc#float#valid(a:winid) || coc#window#get_var(a:winid, 'closing', 0) == 1\n    return\n  endif\n  if !coc#window#visible(a:winid)\n    call coc#float#close(a:winid, 1)\n    return\n  endif\n  let row = coc#window#get_var(a:winid, 'top')\n  if type(row) != v:t_number\n    call coc#float#close(a:winid)\n    return\n  endif\n  call coc#window#set_var(a:winid, 'closing', 1)\n  call s:cancel(a:winid)\n  let winblend = coc#window#get_var(a:winid, 'winblend', 0)\n  let curr = s:is_vim ? {'row': row} : {'winblend': winblend}\n  let dest = s:is_vim ? {'row': row + 1} : {'winblend': winblend == 0 ? 0 : 60}\n  call s:animate(a:winid, curr, dest, 0, 1)\nendfunction\n\nfunction! s:add_action_highlights(before, lnum, highlights, actions) abort\n  let colStart = a:before\n  for text in a:actions\n    let w = strwidth(text)\n    let len = s:is_vim ? stridx(text, '<') : 0\n    call add(a:highlights, {\n        \\ 'lnum': a:lnum,\n        \\ 'hlGroup': s:is_vim ? 'CocNotificationKey' : 'CocNotificationButton',\n        \\ 'colStart': colStart + len,\n        \\ 'colEnd': colStart + w\n        \\ })\n    let colStart = colStart + w + 1\n  endfor\nendfunction\n\nfunction! s:on_action(err, idx, winid) abort\n  if !empty(a:err)\n    throw a:err\n  endif\n  if a:idx > 0\n    call coc#notify#choose(a:winid, a:idx - 1)\n  endif\nendfunction\n\nfunction! s:cancel(winid, ...) abort\n  let name = get(a:, 1, 'timer')\n  let timer = coc#window#get_var(a:winid, name)\n  if !empty(timer)\n    call timer_stop(timer)\n    call win_execute(a:winid, 'unlet w:timer', 'silent!')\n  endif\nendfunction\n\nfunction! s:progress(winid, total, curr, index) abort\n  if !coc#float#valid(a:winid)\n    return\n  endif\n  if coc#window#visible(a:winid)\n    let total = a:total\n    let idx = float2nr(a:curr/5.0)%total\n    let option = coc#float#get_options(a:winid)\n    let width = option['width']\n    if idx != a:index\n      \" update percent & message\n      let bufnr = winbufnr(a:winid)\n      let percent = coc#window#get_var(a:winid, 'percent')\n      let lines = []\n      if !empty(percent)\n        let line = repeat(s:progress_char, width - 4).printf('%4s', percent)\n        let total = width - 4\n        call add(lines, line)\n      else\n        call add(lines, repeat(s:progress_char, width))\n      endif\n      let message = coc#window#get_var(a:winid, 'message')\n      if !empty(message)\n        let lines = lines + coc#string#reflow(split(message, '\\v\\r?\\n'), width)\n      endif\n      if s:is_vim\n        noa call setbufline(bufnr, 1, lines)\n        noa call deletebufline(bufnr, len(lines) + 1, '$')\n      else\n        call nvim_buf_set_lines(bufnr, 0, -1, v:false, lines)\n      endif\n      let height = option['height']\n      let delta = len(lines) - height\n      if delta > 0 && height < 3\n        call coc#float#change_height(a:winid, min([delta, 3 - height]))\n        let tabnr = coc#window#tabnr(a:winid)\n        call coc#notify#reflow(tabnr)\n        if len(lines) > 3\n          call coc#float#nvim_scrollbar(a:winid)\n        endif\n      endif\n      let bytes = strlen(s:progress_char)\n      call coc#highlight#clear_highlight(bufnr, -1, 0, 1)\n      let colStart = bytes * idx\n      if idx + 4 <= total\n        let colEnd = bytes * (idx + 4)\n        call coc#highlight#add_highlight(bufnr, -1, s:phl, 0, colStart, colEnd)\n      else\n        let colEnd = bytes * total\n        call coc#highlight#add_highlight(bufnr, -1, s:phl, 0, colStart, colEnd)\n        call coc#highlight#add_highlight(bufnr, -1, s:phl, 0, 0, bytes * (idx + 4 - total))\n      endif\n    endif\n    call timer_start(s:interval, { -> s:progress(a:winid, total, a:curr + 1, idx)})\n  else\n    \" Not block CursorHold event\n    call timer_start(&updatetime + 100, { -> s:progress(a:winid, a:total, a:curr, a:index)})\n  endif\nendfunction\n\n\" Optional row & winblend\nfunction! s:config_win(winid, props) abort\n  let change_row = has_key(a:props, 'row')\n  if s:is_vim\n    if change_row\n      call popup_move(a:winid, {'line': a:props['row'] + 1})\n    endif\n  else\n    if change_row\n      let [row, column] = nvim_win_get_position(a:winid)\n      call nvim_win_set_config(a:winid, {\n          \\ 'row': a:props['row'],\n          \\ 'col': column,\n          \\ 'relative': 'editor',\n          \\ })\n      call s:nvim_move_related(a:winid, a:props['row'])\n    endif\n    call coc#float#nvim_set_winblend(a:winid, get(a:props, 'winblend', v:null))\n    call coc#float#nvim_refresh_scrollbar(a:winid)\n  endif\nendfunction\n\nfunction! s:nvim_move_related(winid, row) abort\n  let winids = coc#window#get_var(a:winid, 'related')\n  if empty(winids)\n    return\n  endif\n  for winid in winids\n    if nvim_win_is_valid(winid)\n      let [row, column] = nvim_win_get_position(winid)\n      let delta = coc#window#get_var(winid, 'delta', 0)\n      call nvim_win_set_config(winid, {\n          \\ 'row': a:row + delta,\n          \\ 'col': column,\n          \\ 'relative': 'editor',\n          \\ })\n    endif\n  endfor\nendfunction\n\nfunction! s:animate(winid, from, to, prev, ...) abort\n  if !coc#float#valid(a:winid)\n    return\n  endif\n  let curr = a:prev + s:interval\n  let percent = coc#math#min(curr / s:duration, 1)\n  let props = s:get_props(a:from, a:to, percent)\n  call s:config_win(a:winid, props)\n  let close = get(a:, 1, 0)\n  if percent < 1\n    call timer_start(s:interval, { -> s:animate(a:winid, a:from, a:to, curr, close)})\n  elseif close\n    call filter(s:winids, 'v:val != '.a:winid)\n    let tabnr = coc#window#tabnr(a:winid)\n    if tabnr != -1\n      call coc#float#close(a:winid, 1)\n      call coc#notify#reflow(tabnr)\n    endif\n  endif\nendfunction\n\nfunction! coc#notify#reflow(...) abort\n  let tabnr = get(a:, 1, tabpagenr())\n  let winids = filter(copy(s:winids), 'coc#window#tabnr(v:val) == '.tabnr.' && coc#window#get_var(v:val,\"closing\") != 1')\n  if empty(winids)\n    return\n  endif\n  let animate = tabnr == tabpagenr()\n  let wins = map(copy(winids), {_, val -> {\n        \\ 'winid': val,\n        \\ 'row': coc#window#get_var(val,'top',0),\n        \\ 'top': coc#window#get_var(val,'top',0) - (s:is_vim ? 0 : coc#window#get_var(val, 'borders', 0)),\n        \\ 'height': coc#float#get_height(val),\n        \\ }})\n  call sort(wins, {a, b -> b['top'] - a['top']})\n  let bottom = &lines - &cmdheight - (&laststatus == 0 ? 0 : 1 )\n  let moved = 0\n  for item in wins\n    let winid = item['winid']\n    let delta = bottom - (item['top'] + item['height'] + 1)\n    if delta != 0\n      call s:cancel(winid)\n      let dest = item['row'] + delta\n      call coc#window#set_var(winid, 'top', dest)\n      if animate\n        call s:move_win_timer(winid, {'row': item['row']}, {'row': dest}, 0)\n      else\n        call s:config_win(winid, {'row': dest})\n      endif\n      let moved = moved + delta\n    endif\n    let bottom = item['top'] + delta\n  endfor\nendfunction\n\nfunction! s:move_win_timer(winid, from, to, curr) abort\n  if !coc#float#valid(a:winid)\n    return\n  endif\n  if coc#window#get_var(a:winid, 'closing', 0) == 1\n    return\n  endif\n  let percent = coc#math#min(a:curr / s:duration, 1)\n  let next = a:curr + s:interval\n  if a:curr > 0\n    call s:config_win(a:winid, s:get_props(a:from, a:to, percent))\n  endif\n  if percent < 1\n    let timer = timer_start(s:interval, { -> s:move_win_timer(a:winid, a:from, a:to, next)})\n    call coc#window#set_var(a:winid, 'timer', timer)\n  endif\nendfunction\n\nfunction! s:find_win(key) abort\n  for winid in coc#notify#win_list()\n    if getwinvar(winid, 'key', '') ==# a:key\n      return winid\n    endif\n  endfor\n  return -1\nendfunction\n\nfunction! s:get_icon(kind, bg) abort\n  if a:kind ==# 'info'\n    return {'text': s:info_icon, 'hl': coc#hlgroup#compose_hlgroup('CocInfoSign', a:bg)}\n  endif\n  if a:kind ==# 'warning'\n    return {'text': s:warning_icon, 'hl': coc#hlgroup#compose_hlgroup('CocWarningSign', a:bg)}\n  endif\n  if a:kind ==# 'error'\n    return {'text': s:error_icon, 'hl': coc#hlgroup#compose_hlgroup('CocErrorSign', a:bg)}\n  endif\n  return v:null\nendfunction\n\n\" percent should be float\nfunction! s:get_props(from, to, percent) abort\n  let obj = {}\n  for key in keys(a:from)\n    let changed = a:to[key] - a:from[key]\n    if !s:is_vim && key ==# 'row'\n      \" Could be float\n      let obj[key] = a:from[key] + changed * a:percent\n    else\n      let obj[key] = a:from[key] + float2nr(round(changed * a:percent))\n    endif\n  endfor\n  return obj\nendfunction\n\nfunction! coc#notify#choose(winid, idx) abort\n  call s:cancel(a:winid, 'close_timer')\n  call coc#rpc#notify('FloatBtnClick', [winbufnr(a:winid), a:idx])\n  call coc#notify#close(a:winid)\nendfunction\n\nfunction! s:NotifyFilter(count, winid, key) abort\n  let max = min([a:count, 9])\n  for idx in range(1, max)\n    if a:key == s:fn_keys[idx - 1]\n      call coc#notify#choose(a:winid, idx - 1)\n      return 1\n    endif\n  endfor\n  return 0\nendfunction\n"
  },
  {
    "path": "autoload/coc/prompt.vim",
    "content": "scriptencoding utf-8\nlet s:is_vim = !has('nvim')\nlet s:activated = 0\nlet s:session_names = []\nlet s:saved_ve = &t_ve\nlet s:saved_cursor = &guicursor\nlet s:gui = has('gui_running') || has('nvim')\n\nlet s:char_map = {\n      \\ \"\\<Plug>\": '<plug>',\n      \\ \"\\<Esc>\": '<esc>',\n      \\ \"\\<Tab>\": '<tab>',\n      \\ \"\\<S-Tab>\": '<s-tab>',\n      \\ \"\\<bs>\": '<bs>',\n      \\ \"\\<right>\": '<right>',\n      \\ \"\\<left>\": '<left>',\n      \\ \"\\<up>\": '<up>',\n      \\ \"\\<down>\": '<down>',\n      \\ \"\\<home>\": '<home>',\n      \\ \"\\<end>\": '<end>',\n      \\ \"\\<cr>\": '<cr>',\n      \\ \"\\<PageUp>\":'<PageUp>' ,\n      \\ \"\\<PageDown>\":'<PageDown>' ,\n      \\ \"\\<FocusGained>\":'<FocusGained>',\n      \\ \"\\<FocusLost>\":'<FocusLost>',\n      \\ \"\\<ScrollWheelUp>\": '<ScrollWheelUp>',\n      \\ \"\\<ScrollWheelDown>\": '<ScrollWheelDown>',\n      \\ \"\\<LeftMouse>\": '<LeftMouse>',\n      \\ \"\\<LeftDrag>\": '<LeftDrag>',\n      \\ \"\\<LeftRelease>\": '<LeftRelease>',\n      \\ \"\\<2-LeftMouse>\": '<2-LeftMouse>',\n      \\ \"\\<C-space>\": '<C-space>',\n      \\ \"\\<C-_>\": '<C-_>',\n      \\ \"\\<C-a>\": '<C-a>',\n      \\ \"\\<C-b>\": '<C-b>',\n      \\ \"\\<C-c>\": '<C-c>',\n      \\ \"\\<C-d>\": '<C-d>',\n      \\ \"\\<C-e>\": '<C-e>',\n      \\ \"\\<C-f>\": '<C-f>',\n      \\ \"\\<C-g>\": '<C-g>',\n      \\ \"\\<C-h>\": '<C-h>',\n      \\ \"\\<C-j>\": '<C-j>',\n      \\ \"\\<C-k>\": '<C-k>',\n      \\ \"\\<C-l>\": '<C-l>',\n      \\ \"\\<C-n>\": '<C-n>',\n      \\ \"\\<C-o>\": '<C-o>',\n      \\ \"\\<C-p>\": '<C-p>',\n      \\ \"\\<C-q>\": '<C-q>',\n      \\ \"\\<C-r>\": '<C-r>',\n      \\ \"\\<C-s>\": '<C-s>',\n      \\ \"\\<C-t>\": '<C-t>',\n      \\ \"\\<C-u>\": '<C-u>',\n      \\ \"\\<C-v>\": '<C-v>',\n      \\ \"\\<C-w>\": '<C-w>',\n      \\ \"\\<C-x>\": '<C-x>',\n      \\ \"\\<C-y>\": '<C-y>',\n      \\ \"\\<C-z>\": '<C-z>',\n      \\ \"\\<A-a>\": '<A-a>',\n      \\ \"\\<A-b>\": '<A-b>',\n      \\ \"\\<A-c>\": '<A-c>',\n      \\ \"\\<A-d>\": '<A-d>',\n      \\ \"\\<A-e>\": '<A-e>',\n      \\ \"\\<A-f>\": '<A-f>',\n      \\ \"\\<A-g>\": '<A-g>',\n      \\ \"\\<A-h>\": '<A-h>',\n      \\ \"\\<A-i>\": '<A-i>',\n      \\ \"\\<A-j>\": '<A-j>',\n      \\ \"\\<A-k>\": '<A-k>',\n      \\ \"\\<A-l>\": '<A-l>',\n      \\ \"\\<A-m>\": '<A-m>',\n      \\ \"\\<A-n>\": '<A-n>',\n      \\ \"\\<A-o>\": '<A-o>',\n      \\ \"\\<A-p>\": '<A-p>',\n      \\ \"\\<A-q>\": '<A-q>',\n      \\ \"\\<A-r>\": '<A-r>',\n      \\ \"\\<A-s>\": '<A-s>',\n      \\ \"\\<A-t>\": '<A-t>',\n      \\ \"\\<A-u>\": '<A-u>',\n      \\ \"\\<A-v>\": '<A-v>',\n      \\ \"\\<A-w>\": '<A-w>',\n      \\ \"\\<A-x>\": '<A-x>',\n      \\ \"\\<A-y>\": '<A-y>',\n      \\ \"\\<A-z>\": '<A-z>',\n      \\ \"\\<A-bs>\": '<A-bs>',\n      \\ }\n\nfunction! coc#prompt#getc() abort\n  let c = getchar()\n  return type(c) is 0 ? nr2char(c) : c\nendfunction\n\nfunction! coc#prompt#getchar() abort\n  let input = coc#prompt#getc()\n  if 1 != &iminsert\n    return input\n  endif\n  \"a language keymap is activated, so input must be resolved to the mapped values.\n  let partial_keymap = mapcheck(input, 'l')\n  while partial_keymap !=# ''\n    let dict = maparg(input, 'l', 0, 1)\n    if empty(dict) || get(dict, 'expr', 0)\n      return input\n    endif\n    let full_keymap = get(dict, 'rhs', '')\n    if full_keymap ==# \"\" && len(input) >= 3 \"HACK: assume there are no keymaps longer than 3.\n      return input\n    elseif full_keymap ==# partial_keymap\n      return full_keymap\n    endif\n    let c = coc#prompt#getc()\n    if c ==# \"\\<Esc>\" || c ==# \"\\<CR>\"\n      \"if the short sequence has a valid mapping, return that.\n      if !empty(full_keymap)\n        return full_keymap\n      endif\n      return input\n    endif\n    let input .= c\n    let partial_keymap = mapcheck(input, 'l')\n  endwhile\n  return input\nendfunction\n\nfunction! coc#prompt#start_prompt(session) abort\n  let s:session_names = s:filter(s:session_names, a:session)\n  call add(s:session_names, a:session)\n  if s:activated | return | endif\n  if s:is_vim\n    call s:start_prompt_vim()\n  else\n    call s:start_prompt()\n  endif\nendfunction\n\nfunction! s:start_prompt_vim() abort\n  call timer_start(10, {-> s:start_prompt()})\nendfunction\n\nfunction! s:start_prompt()\n  if s:activated | return | endif\n  if !get(g:, 'coc_disable_transparent_cursor', 0)\n    if s:gui\n      if !s:is_vim && !empty(s:saved_cursor)\n        set guicursor+=a:ver1-CocCursorTransparent/lCursor\n      endif\n    elseif s:is_vim\n      set t_ve=\n    endif\n  endif\n  let s:activated = 1\n  try\n    while s:activated\n      let ch = coc#prompt#getchar()\n      if ch ==# \"\\<FocusLost>\" || ch ==# \"\\<FocusGained>\" || ch ==# \"\\<CursorHold>\"\n        continue\n      else\n        let curr = s:current_session()\n        let mapped = get(s:char_map, ch, ch)\n        if !empty(curr)\n          call coc#rpc#notify('InputChar', [curr, mapped, getcharmod()])\n        endif\n        if mapped == '<esc>'\n          let s:session_names = []\n          call s:reset()\n          break\n        endif\n      endif\n    endwhile\n  catch /^Vim:Interrupt$/\n    let s:activated = 0\n    call coc#rpc#notify('InputChar', [s:current_session(), '<esc>', 0])\n    let s:session_names = []\n    call s:reset()\n    return\n  endtry\n  let s:activated = 0\nendfunction\n\nfunction! coc#prompt#stop_prompt(session)\n  let s:session_names = s:filter(s:session_names, a:session)\n  if len(s:session_names)\n    return\n  endif\n  if s:activated\n    let s:activated = 0\n    call s:reset()\n    call feedkeys(\"\\<esc>\", 'int')\n  endif\nendfunction\n\nfunction! coc#prompt#activated() abort\n  return s:activated\nendfunction\n\nfunction! s:reset() abort\n  if !get(g:, 'coc_disable_transparent_cursor',0)\n    \" neovim has bug with revert empty &guicursor\n    if s:gui && !empty(s:saved_cursor)\n      if !s:is_vim\n        set guicursor+=a:ver1-Cursor/lCursor\n        let &guicursor = s:saved_cursor\n      endif\n    elseif s:is_vim\n      let &t_ve = s:saved_ve\n    endif\n  endif\n  echo \"\"\nendfunction\n\nfunction! s:current_session() abort\n  if empty(s:session_names)\n    return v:null\n  endif\n  return s:session_names[len(s:session_names) - 1]\nendfunction\n\nfunction! s:filter(list, id) abort\n  return filter(copy(a:list), 'v:val !=# a:id')\nendfunction\n"
  },
  {
    "path": "autoload/coc/pum.vim",
    "content": "scriptencoding utf-8\nlet s:is_vim = !has('nvim')\nlet s:pum_bufnr = 0\nlet s:pum_winid = -1\nlet s:pum_index = -1\nlet s:pum_size = 0\n\" word of complete item inserted\nlet s:inserted = 0\nlet s:virtual_text = 0\nlet s:virtual_text_ns = coc#highlight#create_namespace('pum-virtual')\n\" bufnr, &indentkeys\nlet s:saved_indenetkeys = []\nlet s:saved_textwidth = []\nlet s:prop_id = 0\nlet s:reversed = 0\nlet s:check_hl_group = 0\nlet s:start_col = -1\n\nif s:is_vim\n  if empty(prop_type_get('CocPumVirtualText'))\n    call prop_type_add('CocPumVirtualText', {'highlight': 'CocPumVirtualText'})\n  endif\nendif\n\nfunction! coc#pum#has_item_selected() abort\n    return coc#pum#visible() && s:pum_index != -1\nendfunction\n\nfunction! coc#pum#visible() abort\n  if s:pum_winid == -1\n    return 0\n  endif\n  \" getwinvar check current tab only.\n  return getwinvar(s:pum_winid, 'float', 0) == 1\nendfunction\n\nfunction! coc#pum#winid() abort\n  return s:pum_winid\nendfunction\n\nfunction! coc#pum#close_detail() abort\n  let winid = coc#float#get_float_by_kind('pumdetail')\n  if winid\n    call coc#float#close(winid, 1)\n  endif\nendfunction\n\n\" kind, and skipRequest (default to false)\nfunction! coc#pum#close(...) abort\n  if coc#pum#visible()\n    let inserted = 0\n    let kind = get(a:, 1, '')\n    if kind ==# 'cancel'\n      let input = getwinvar(s:pum_winid, 'input', '')\n      let s:pum_index = -1\n      let inserted = s:insert_word(input, 1)\n      call s:on_pum_change(0)\n    elseif kind ==# 'confirm'\n      let words = getwinvar(s:pum_winid, 'words', [])\n      if s:pum_index >= 0\n        let word = get(words, s:pum_index, '')\n        let inserted = s:insert_word(word, 1)\n        \" have to restore here, so that TextChangedI can trigger indent.\n        call s:restore_indentkeys()\n      endif\n    endif\n    call s:close_pum()\n    if !get(a:, 2, 0)\n      \" Needed to wait TextChangedI fired\n      if inserted\n        call timer_start(0, {-> coc#rpc#request('stopCompletion', [kind])})\n      else\n        call coc#rpc#request('stopCompletion', [kind])\n      endif\n    endif\n  endif\n  return ''\nendfunction\n\nfunction! coc#pum#select_confirm() abort\n  if coc#pum#visible()\n    if s:pum_index < 0\n      let s:pum_index = 0\n      call s:on_pum_change(0)\n    endif\n    \" Avoid change of text not allowed\n    return \"\\<C-r>=coc#pum#close('confirm')\\<CR>\"\n  endif\n  return ''\nendfunction\n\nfunction! coc#pum#_close() abort\n  if coc#pum#visible()\n    call s:close_pum()\n    if s:is_vim\n      call timer_start(0, {-> execute('redraw')})\n    endif\n  endif\nendfunction\n\nfunction! coc#pum#_one_more() abort\n  if coc#pum#visible()\n    let parts = getwinvar(s:pum_winid, 'parts', [])\n    let start = strlen(parts[0])\n    let input = strpart(getline('.'), start, col('.') - 1 - start)\n    let words = getwinvar(s:pum_winid, 'words', [])\n    let word = get(words, s:pum_index == -1 ? 0 : s:pum_index, '')\n    if !empty(word) && strcharpart(word, 0, strchars(input)) ==# input\n      let ch = strcharpart(word, strchars(input), 1)\n      if !empty(ch)\n        call feedkeys(ch, \"nt\")\n      endif\n    endif\n  endif\n  return ''\nendfunction\n\nfunction! coc#pum#_insert() abort\n  if coc#pum#visible()\n    if s:pum_index >= 0\n      let words = getwinvar(s:pum_winid, 'words', [])\n      let word = get(words, s:pum_index, '')\n      call s:insert_word(word, 1)\n      call s:restore_indentkeys()\n    endif\n    doautocmd <nomodeline> TextChangedI\n    call s:close_pum()\n    call timer_start(0, {-> coc#rpc#request('stopCompletion', [''])})\n  endif\n  return ''\nendfunction\n\nfunction! coc#pum#insert() abort\n  return \"\\<C-r>=coc#pum#_insert()\\<CR>\"\nendfunction\n\n\" Add one more character from the matched complete item(or first one),\n\" the word should starts with input, the same as vim's CTRL-L behavior.\nfunction! coc#pum#one_more() abort\n  return \"\\<C-r>=coc#pum#_one_more()\\<CR>\"\nendfunction\n\nfunction! coc#pum#next(insert) abort\n  return \"\\<C-r>=coc#pum#_navigate(1,\".a:insert.\")\\<CR>\"\nendfunction\n\nfunction! coc#pum#prev(insert) abort\n  return \"\\<C-r>=coc#pum#_navigate(0,\".a:insert.\")\\<CR>\"\nendfunction\n\nfunction! coc#pum#stop() abort\n  return \"\\<C-r>=coc#pum#close()\\<CR>\"\nendfunction\n\nfunction! coc#pum#cancel() abort\n  return \"\\<C-r>=coc#pum#close('cancel')\\<CR>\"\nendfunction\n\nfunction! coc#pum#confirm() abort\n  return \"\\<C-r>=coc#pum#close('confirm')\\<CR>\"\nendfunction\n\nfunction! coc#pum#select(index, insert, confirm) abort\n  if coc#pum#visible()\n    if a:index == -1\n      call coc#pum#close('cancel')\n      return ''\n    endif\n    if a:index < 0 || a:index >= s:pum_size\n      throw 'index out of range ' . a:index\n    endif\n    if a:confirm\n      if s:pum_index != a:index\n        let s:pum_index = a:index\n        let s:inserted = 1\n        call s:on_pum_change(0)\n      endif\n      call coc#pum#close('confirm')\n    else\n      call s:select_by_index(a:index, a:insert)\n    endif\n  endif\n  return ''\nendfunction\n\nfunction! coc#pum#info() abort\n  let bufnr = winbufnr(s:pum_winid)\n  let words = getwinvar(s:pum_winid, 'words', [])\n  let word = s:pum_index < 0 ? '' : get(words, s:pum_index, '')\n  let base = {\n        \\ 'word': word,\n        \\ 'index': s:pum_index,\n        \\ 'size': s:pum_size,\n        \\ 'startcol': s:start_col,\n        \\ 'inserted': s:pum_index >=0 && s:inserted ? v:true : v:false,\n        \\ 'reversed': s:reversed ? v:true : v:false,\n        \\ }\n  if s:is_vim\n    let pos = popup_getpos(s:pum_winid)\n    let border = has_key(popup_getoptions(s:pum_winid), 'border')\n    let add = pos['scrollbar'] && border ? 1 : 0\n    return extend(base, {\n          \\ 'scrollbar': pos['scrollbar'],\n          \\ 'row': pos['line'] - 1,\n          \\ 'col': pos['col'] - 1,\n          \\ 'width': pos['width'] + add,\n          \\ 'height': pos['height'],\n          \\ 'border': border,\n          \\ })\n  else\n    let scrollbar = coc#float#get_related(s:pum_winid, 'scrollbar')\n    let winid = coc#float#get_related(s:pum_winid, 'border', s:pum_winid)\n    let pos = nvim_win_get_position(winid)\n    return extend(base, {\n          \\ 'scrollbar': scrollbar && nvim_win_is_valid(scrollbar) ? 1 : 0,\n          \\ 'row': pos[0],\n          \\ 'col': pos[1],\n          \\ 'width': nvim_win_get_width(winid),\n          \\ 'height': nvim_win_get_height(winid),\n          \\ 'border': winid != s:pum_winid,\n          \\ })\n  endif\nendfunction\n\nfunction! coc#pum#scroll(forward) abort\n  if coc#pum#visible()\n    let height = s:get_height(s:pum_winid)\n    if s:pum_size > height\n      call timer_start(1, { -> s:scroll_pum(a:forward, height, s:pum_size)})\n    endif\n  endif\n  \" Required on old version vim/neovim.\n  return \"\\<Ignore>\"\nendfunction\n\nfunction! s:get_height(winid) abort\n  if s:is_vim\n    return get(popup_getpos(a:winid), 'core_height', 0)\n  endif\n  return nvim_win_get_height(a:winid)\nendfunction\n\nfunction! s:scroll_pum(forward, height, size) abort\n  let topline = s:get_topline(s:pum_winid)\n  if !a:forward && topline == 1\n    if s:pum_index >= 0\n      call s:select_line(s:pum_winid, 1)\n      call s:on_pum_change(1)\n    endif\n    return\n  endif\n  if a:forward && topline + a:height - 1 >= a:size\n    if s:pum_index >= 0\n      call s:select_line(s:pum_winid, a:size)\n      call s:on_pum_change(1)\n    endif\n    return\n  endif\n  call coc#float#scroll_win(s:pum_winid, a:forward, a:height)\n  if s:pum_index >= 0\n    let lnum = s:pum_index + 1\n    let topline = s:get_topline(s:pum_winid)\n    if lnum >= topline && lnum <= topline + a:height - 1\n      return\n    endif\n    call s:select_line(s:pum_winid, topline)\n    call s:on_pum_change(1)\n  endif\nendfunction\n\nfunction! s:get_topline(winid) abort\n  if s:is_vim\n    let pos = popup_getpos(a:winid)\n    return pos['firstline']\n  endif\n  let info = getwininfo(a:winid)[0]\n  return info['topline']\nendfunction\n\nfunction! coc#pum#_navigate(next, insert) abort\n  if coc#pum#visible()\n    call s:save_indentkeys()\n    let index = s:get_index(a:next)\n    call s:select_by_index(index, a:insert)\n    call coc#rpc#notify('PumNavigate', [bufnr('%')])\n  endif\n  return ''\nendfunction\n\nfunction! s:select_by_index(index, insert) abort\n  let lnum = a:index == -1 ? 0 : s:index_to_lnum(a:index)\n  call s:set_cursor(s:pum_winid, lnum)\n  if !s:is_vim\n    call coc#float#nvim_scrollbar(s:pum_winid)\n  endif\n  if a:insert\n    let s:inserted = a:index >= 0\n    if a:index < 0\n      let input = getwinvar(s:pum_winid, 'input', '')\n      call s:insert_word(input, 0)\n      call coc#pum#close_detail()\n    else\n      let words = getwinvar(s:pum_winid, 'words', [])\n      let word = get(words, a:index, '')\n      call s:insert_word(word, 0)\n    endif\n  endif\n  call s:on_pum_change(1)\nendfunction\n\nfunction! s:get_index(next) abort\n  if a:next\n    let index = s:pum_index + 1 == s:pum_size ? -1 : s:pum_index + 1\n  else\n    let index = s:pum_index == -1 ? s:pum_size - 1 : s:pum_index - 1\n  endif\n  return index\nendfunction\n\nfunction! s:insert_word(word, finish) abort\n  if s:start_col != -1 && mode() ==# 'i'\n    \" Not insert same characters\n    let inserted = strpart(getline('.'), s:start_col, col('.') - 1)\n    if inserted !=# a:word\n      \" avoid auto wrap using 'textwidth'\n      if !a:finish && &textwidth > 0\n        let textwidth = &textwidth\n        noa setl textwidth=0\n        call timer_start(0, { -> execute('noa setl textwidth='.textwidth)})\n      endif\n      let saved_completeopt = &completeopt\n      noa set completeopt=noinsert,noselect\n      noa call complete(s:start_col + 1, [{ 'empty': v:true, 'word': a:word }])\n      noa call feedkeys(\"\\<C-n>\\<C-x>\\<C-z>\", 'in')\n      call timer_start(0, { -> execute('noa set completeopt='.saved_completeopt)})\n      return 1\n    endif\n  endif\n  return 0\nendfunction\n\n\" Replace from col to cursor col with new characters\nfunction! coc#pum#replace(col, insert, delta) abort\n  if a:delta == 1\n    call feedkeys(\"\\<right>\", 'in')\n  endif\n  let saved_completeopt = &completeopt\n  noa set completeopt=noinsert,noselect\n  noa call complete(a:col, [{ 'empty': v:true, 'word': a:insert }])\n  noa call feedkeys(\"\\<C-n>\\<C-x>\\<C-z>\", 'n')\nexecute 'noa set completeopt='.saved_completeopt\nendfunction\n\n\" create or update pum with lines, CompleteOption and config.\n\" return winid & dimension\nfunction! coc#pum#create(lines, opt, config) abort\n  if mode() !=# 'i' || a:opt['line'] != line('.')\n    return\n  endif\n  let len = col('.') - a:opt['col'] - 1\n  if len < 0\n    return\n  endif\n  let input = len == 0 ? '' : strpart(getline('.'), a:opt['col'], len)\n  if input !=# a:opt['input']\n    return\n  endif\n  let config = s:get_pum_dimension(a:lines, a:opt['col'], a:config)\n  if empty(config)\n    return\n  endif\n  let s:reversed = get(a:config, 'reverse', 0) && config['row'] < 0\n  let s:virtual_text = get(a:opt, 'virtualText', v:false)\n  let s:pum_size = len(a:lines)\n  let s:pum_index = a:opt['index']\n  let lnum = s:index_to_lnum(s:pum_index)\n  call extend(config, {\n        \\ 'lines': s:reversed ? reverse(copy(a:lines)) : a:lines,\n        \\ 'relative': 'cursor',\n        \\ 'nopad': 1,\n        \\ 'cursorline': 1,\n        \\ 'index': lnum - 1,\n        \\ 'focusable': v:false\n        \\ })\n  call extend(config, coc#dict#pick(a:config, ['highlight', 'rounded', 'highlights', 'winblend', 'shadow', 'border', 'borderhighlight', 'title']))\n  if s:reversed\n    for item in config['highlights']\n      let item['lnum'] = s:pum_size - item['lnum'] - 1\n    endfor\n  endif\n  if empty(get(config, 'winblend', 0)) && exists('&pumblend')\n    let config['winblend'] = &pumblend\n  endif\n  let result =  coc#float#create_float_win(s:pum_winid, s:pum_bufnr, config)\n  if empty(result)\n    return\n  endif\n  let s:inserted = 0\n  let s:pum_winid = result[0]\n  let s:pum_bufnr = result[1]\n  let s:start_col = a:opt['startcol']\n  call setwinvar(s:pum_winid, 'above', config['row'] < 0)\n  let firstline = s:get_firstline(lnum, s:pum_size, config['height'])\n  if s:is_vim\n    call popup_setoptions(s:pum_winid, { 'firstline': firstline })\n  else\n    call win_execute(s:pum_winid, 'call winrestview({\"lnum\":'.lnum.',\"topline\":'.firstline.'})')\n  endif\n  call coc#dialog#place_sign(s:pum_bufnr, s:pum_index == -1 ? 0 : lnum)\n  \" content before col and content after cursor\n  let linetext = getline('.')\n  let parts = [strpart(linetext, 0, s:start_col), strpart(linetext, col('.') - 1)]\n  let input = strpart(getline('.'), s:start_col, col('.') - 1 - s:start_col)\n  call setwinvar(s:pum_winid, 'input', input)\n  call setwinvar(s:pum_winid, 'parts', parts)\n  call setwinvar(s:pum_winid, 'words', a:opt['words'])\n  call setwinvar(s:pum_winid, 'kind', 'pum')\n  if !s:is_vim\n    if s:pum_size > config['height']\n      call timer_start(0,{ -> coc#float#nvim_scrollbar(s:pum_winid)})\n    else\n      call coc#float#close_related(s:pum_winid, 'scrollbar')\n    endif\n  endif\n  call s:on_pum_change(0)\nendfunction\n\nfunction! s:save_indentkeys() abort\n  let bufnr = bufnr('%')\n  if !empty(&indentexpr) && get(s:saved_indenetkeys, 0, 0) != bufnr\n    let s:saved_indenetkeys = [bufnr, &indentkeys]\n    execute 'setl indentkeys='\n  endif\nendfunction\n\nfunction! s:get_firstline(lnum, total, height) abort\n  if a:lnum <= a:height\n    return 1\n  endif\n  return min([a:total - a:height + 1, a:lnum  - (a:height*2/3)])\nendfunction\n\nfunction! s:on_pum_change(move) abort\n  if s:virtual_text\n    if s:inserted\n      call coc#pum#clear_vtext()\n    else\n      call s:insert_virtual_text()\n    endif\n  endif\n  let ev = extend(coc#pum#info(), {'move': a:move ? v:true : v:false})\n  call coc#rpc#notify('CocAutocmd', ['MenuPopupChanged', ev, win_screenpos(winnr())[0] + winline() - 2])\nendfunction\n\nfunction! s:index_to_lnum(index) abort\n  if s:reversed\n    if a:index <= 0\n      return s:pum_size\n    endif\n    return s:pum_size - a:index\n  endif\n  return max([1, a:index + 1])\nendfunction\n\nfunction! s:get_pum_dimension(lines, col, config) abort\n  let linecount = len(a:lines)\n  let [lineIdx, colIdx] = coc#cursor#screen_pos()\n  let bh = empty(get(a:config, 'border', [])) ? 0 : 2\n  let columns = &columns\n  let pumwidth = max([15, exists('&pumwidth') ? &pumwidth : 0])\n  let width = min([columns, max([pumwidth, a:config['width']])])\n  let vh = &lines - &cmdheight - 1 - !empty(&tabline)\n  if vh <= 0\n    return v:null\n  endif\n  let pumheight = empty(&pumheight) ? vh : &pumheight\n  let showTop = getwinvar(s:pum_winid, 'above', v:null)\n  if type(showTop) != v:t_number\n    if vh - lineIdx - bh - 1 < min([pumheight, linecount]) && vh - lineIdx < min([10, vh/2])\n      let showTop = 1\n    else\n      let showTop = 0\n    endif\n  endif\n  let height = showTop ? min([lineIdx - bh - !empty(&tabline), linecount, pumheight]) : min([vh - lineIdx - bh - 1, linecount, pumheight])\n  if height <= 0\n    return v:null\n  endif\n  \" should use strdiplaywidth here\n  let text = strpart(getline('.'), a:col, col('.') - 1 - a:col)\n  let col = - strdisplaywidth(text, a:col) - 1\n  let row = showTop ? - height : 1\n  let delta = colIdx + col\n  if width > pumwidth && delta + width > columns\n    let width = max([columns - delta, pumwidth])\n  endif\n  if delta < 0\n    let col = col - delta\n  elseif delta + width > columns\n    let col = max([-colIdx, col - (delta + width - columns)])\n  endif\n  return {\n        \\ 'row': row,\n        \\ 'col': col,\n        \\ 'width': width,\n        \\ 'height': height\n        \\ }\nendfunction\n\n\" can't use coc#dialog#set_cursor on vim8, don't know why\nfunction! s:set_cursor(winid, line) abort\n  if s:is_vim\n    let pos = popup_getpos(a:winid)\n    let core_height = pos['core_height']\n    let lastline = pos['firstline'] + core_height - 1\n    if a:line > lastline\n      call popup_setoptions(a:winid, {\n            \\ 'firstline': pos['firstline'] + a:line - lastline,\n            \\ })\n    elseif a:line < pos['firstline']\n      if s:reversed\n        call popup_setoptions(a:winid, {\n              \\ 'firstline': a:line == 0 ? s:pum_size - core_height + 1 : a:line - core_height + 1,\n              \\ })\n      else\n        call popup_setoptions(a:winid, {\n              \\ 'firstline': max([1, a:line]),\n              \\ })\n      endif\n    endif\n  endif\n  call s:select_line(a:winid, a:line)\nendfunction\n\nfunction! s:select_line(winid, line) abort\n  let s:pum_index = s:reversed ? (a:line == 0 ? -1 : s:pum_size - a:line) : a:line - 1\n  let lnum = s:reversed ? (a:line == 0 ? s:pum_size : a:line) : max([1, a:line])\n  if s:is_vim\n    call win_execute(a:winid, 'exe '.lnum)\n  else\n    call nvim_win_set_cursor(a:winid, [lnum, 0])\n  endif\n  call coc#dialog#place_sign(s:pum_bufnr, a:line == 0 ? 0 : lnum)\nendfunction\n\nfunction! s:insert_virtual_text() abort\n  let bufnr = bufnr('%')\n  if !s:virtual_text || s:pum_index < 0\n    call coc#pum#clear_vtext()\n  else\n    \" Check if could create\n    let insert = ''\n    let line = line('.') - 1\n    let words = getwinvar(s:pum_winid, 'words', [])\n    let word = get(words, s:pum_index, '')\n    let input = strpart(getline('.'), s:start_col, col('.') - 1 - s:start_col)\n    if strlen(word) > strlen(input) && strcharpart(word, 0, strchars(input)) ==# input\n      let insert = strcharpart(word, strchars(input))\n    endif\n    if s:is_vim\n      if s:prop_id != 0\n        call prop_remove({'id': s:prop_id}, line + 1, line + 1)\n      endif\n      if !empty(insert)\n        let s:prop_id = prop_add(line + 1, col('.'), {\n            \\ 'text': insert,\n            \\ 'type': 'CocPumVirtualText'\n            \\ })\n      endif\n    else\n      call nvim_buf_clear_namespace(bufnr, s:virtual_text_ns, line, line + 1)\n      if !empty(insert)\n        let opts = {\n            \\ 'hl_mode': 'combine',\n            \\ 'virt_text': [[insert, 'CocPumVirtualText']],\n            \\ 'virt_text_pos': 'overlay',\n            \\ 'virt_text_win_col': virtcol('.') - 1,\n            \\ }\n        call nvim_buf_set_extmark(bufnr, s:virtual_text_ns, line, col('.') - 1, opts)\n      endif\n    endif\n  endif\nendfunction\n\nfunction! coc#pum#clear_vtext() abort\n  if s:is_vim\n    if s:prop_id != 0\n      call prop_remove({'id': s:prop_id})\n    endif\n    let s:prop_id = 0\n  else\n    call nvim_buf_clear_namespace(bufnr('%'), s:virtual_text_ns, 0, -1)\n  endif\nendfunction\n\nfunction! s:close_pum() abort\n  call coc#pum#clear_vtext()\n  call coc#float#close(s:pum_winid, 1)\n  let s:pum_winid = 0\n  let s:pum_size = 0\n  let winid = coc#float#get_float_by_kind('pumdetail')\n  if winid\n    call coc#float#close(winid, 1)\n  endif\n  call s:restore_indentkeys()\nendfunction\n\nfunction! s:restore_indentkeys() abort\n  if get(s:saved_indenetkeys, 0, 0) == bufnr('%')\n    call setbufvar(s:saved_indenetkeys[0], '&indentkeys', get(s:saved_indenetkeys, 1, ''))\n    let s:saved_indenetkeys = []\n  endif\nendfunction\n"
  },
  {
    "path": "autoload/coc/rpc.vim",
    "content": "scriptencoding utf-8\nlet s:is_win = has(\"win32\") || has(\"win64\")\nlet s:client = v:null\nlet s:name = 'coc'\nlet s:is_vim = !has('nvim')\nlet s:chan_id = 0\nlet s:root = expand('<sfile>:h:h:h')\n\nfunction! coc#rpc#start_server()\n  let test = get(g:, 'coc_node_env', '') ==# 'test'\n  if test && !s:is_vim && !exists('$COC_NVIM_REMOTE_ADDRESS')\n    \" server already started, chan_id could be available later\n    let s:client = coc#client#create(s:name, [])\n    let s:client['running'] = s:chan_id != 0\n    let s:client['chan_id'] = s:chan_id\n    return\n  endif\n  if exists('$COC_NVIM_REMOTE_ADDRESS')\n    let address = $COC_NVIM_REMOTE_ADDRESS\n    if s:is_vim\n      let s:client = coc#client#create(s:name, [])\n      \" TODO don't know if vim support named pipe on windows.\n      let address = address =~# ':\\d\\+$' ? address : 'unix:'.address\n      let channel = ch_open(address, {\n          \\ 'mode': 'json',\n          \\ 'close_cb': {channel -> s:on_channel_close()},\n          \\ 'noblock': 1,\n          \\ 'timeout': 1000,\n          \\ })\n      if ch_status(channel) == 'open'\n        let s:client['running'] = 1\n        let s:client['channel'] = channel\n      endif\n    else\n      let s:client = coc#client#create(s:name, [])\n      try\n        let mode = address =~# ':\\d\\+$' ? 'tcp' : 'pipe'\n        let chan_id = sockconnect(mode, address, { 'rpc': 1 })\n        if chan_id > 0\n          let s:client['running'] = 1\n          let s:client['chan_id'] = chan_id\n        endif\n      catch /connection\\ refused/\n        \" ignore\n      endtry\n    endif\n    if !s:client['running']\n      echohl Error | echom '[coc.nvim] Unable connect to '.address.' from variable $COC_NVIM_REMOTE_ADDRESS' | echohl None\n    elseif !test\n      let logfile = exists('$NVIM_COC_LOG_FILE') ? $NVIM_COC_LOG_FILE : ''\n      let loglevel = exists('$NVIM_COC_LOG_LEVEL') ? $NVIM_COC_LOG_LEVEL : ''\n      let runtimepath = join(coc#compat#list_runtime_paths(), \",\")\n      let data = [coc#util#win32unix_to_node(s:root), coc#util#get_data_home(), coc#util#get_config_home(), logfile, loglevel, runtimepath]\n      if s:is_vim\n        call ch_sendraw(s:client['channel'], json_encode(data).\"\\n\")\n      else\n        call call('rpcnotify', [s:client['chan_id'], 'init'] + data)\n      endif\n    endif\n    return\n  endif\n  if empty(s:client)\n    let cmd = coc#util#job_command()\n    if empty(cmd) | return | endif\n    let $COC_VIMCONFIG = coc#util#get_config_home()\n    let $COC_DATA_HOME = coc#util#get_data_home()\n    let s:client = coc#client#create(s:name, cmd)\n  endif\n  if !coc#client#is_running('coc')\n    call s:client['start']()\n  endif\n  call s:check_vim_enter()\nendfunction\n\nfunction! coc#rpc#started() abort\n  return !empty(s:client)\nendfunction\n\nfunction! coc#rpc#ready()\n  if empty(s:client) || s:client['running'] == 0\n    return 0\n  endif\n  return 1\nendfunction\n\n\" Used for test on neovim only\nfunction! coc#rpc#set_channel(chan_id) abort\n  let s:chan_id = a:chan_id\n  let s:client['running'] = a:chan_id != 0\n  let s:client['chan_id'] = a:chan_id\nendfunction\n\nfunction! coc#rpc#get_channel() abort\n  if empty(s:client)\n    return v:null\n  endif\n  return coc#client#get_channel(s:client)\nendfunction\n\nfunction! coc#rpc#kill()\n  let pid = get(g:, 'coc_process_pid', 0)\n  if !pid | return | endif\n  if s:is_win\n    call system('taskkill /PID '.pid)\n  else\n    call system('kill -9 '.pid)\n  endif\nendfunction\n\nfunction! coc#rpc#show_errors()\n  let client = coc#client#get_client('coc')\n  if !empty(client)\n    let lines = get(client, 'stderr', [])\n    keepalt new +setlocal\\ buftype=nofile [Stderr of coc.nvim]\n    setl noswapfile wrap bufhidden=wipe nobuflisted nospell\n    call append(0, lines)\n    exe \"normal! z\" . len(lines) . \"\\<cr>\"\n    exe \"normal! gg\"\n  endif\nendfunction\n\nfunction! coc#rpc#stop()\n  if empty(s:client)\n    return\n  endif\n  try\n    if s:is_vim\n      call job_stop(ch_getjob(s:client['channel']), 'term')\n    else\n      call jobstop(s:client['chan_id'])\n    endif\n  catch /.*/\n    \" ignore\n  endtry\nendfunction\n\nfunction! coc#rpc#restart()\n  if empty(s:client)\n    call coc#rpc#start_server()\n  else\n    call coc#highlight#clear_all()\n    call coc#ui#sign_unplace()\n    call coc#float#close_all()\n    call coc#clearGroups('coc_dynamic_')\n    call coc#rpc#request('detach', [])\n    if !empty(get(g:, 'coc_status', ''))\n      unlet g:coc_status\n    endif\n    let g:coc_service_initialized = 0\n    sleep 100m\n    if exists('$COC_NVIM_REMOTE_ADDRESS')\n      call coc#rpc#close_connection()\n      sleep 100m\n      call coc#rpc#start_server()\n    else\n      let s:client['command'] = coc#util#job_command()\n      call coc#client#restart(s:name)\n      call s:check_vim_enter()\n    endif\n    echohl MoreMsg | echom 'starting coc.nvim service' | echohl None\n  endif\nendfunction\n\nfunction! coc#rpc#close_connection() abort\n  let channel = coc#rpc#get_channel()\n  if channel == v:null\n    return\n  endif\n  if s:is_vim\n    \" Unlike neovim, vim not close the socket as expected.\n    call ch_close(channel)\n  else\n    call chanclose(channel)\n  endif\n  let s:client['running'] = 0\n  let s:client['channel'] = v:null\n  let s:client['chan_id'] = 0\nendfunction\n\nfunction! coc#rpc#request(method, args) abort\n  if !coc#rpc#ready()\n    return ''\n  endif\n  return s:client['request'](a:method, a:args)\nendfunction\n\nfunction! coc#rpc#notify(method, args) abort\n  if !coc#rpc#ready()\n    return ''\n  endif\n  call s:client['notify'](a:method, a:args)\n  return ''\nendfunction\n\nfunction! coc#rpc#request_async(method, args, cb) abort\n  if !coc#rpc#ready()\n    return call(a:cb, ['coc.nvim service not started.'])\n  endif\n  call s:client['request_async'](a:method, a:args, a:cb)\nendfunction\n\n\" receive async response\nfunction! coc#rpc#async_response(id, resp, isErr) abort\n  if empty(s:client)\n    return\n  endif\n  call coc#client#on_response(s:name, a:id, a:resp, a:isErr)\nendfunction\n\n\" send async response to server\nfunction! coc#rpc#async_request(id, method, args)\n  let l:Cb = {err, ... -> coc#rpc#notify('nvim_async_response_event', [a:id, err, get(a:000, 0, v:null)])}\n  let args = a:args + [l:Cb]\n  try\n    call call(a:method, args)\n  catch /.*/\n    call coc#rpc#notify('nvim_async_response_event', [a:id, v:exception, v:null])\n  endtry\nendfunction\n\nfunction! s:check_vim_enter() abort\n  if s:client['running'] && v:vim_did_enter\n    call coc#rpc#notify('VimEnter', [join(coc#compat#list_runtime_paths(), \",\")])\n  endif\nendfunction\n\n\" Used on vim and remote address only\nfunction! s:on_channel_close() abort\n  if get(g:, 'coc_node_env', '') !=# 'test'\n    echohl Error | echom '[coc.nvim] channel closed' | echohl None\n  endif\n  if !empty(s:client)\n    let s:client['running'] = 0\n    let s:client['channel'] = v:null\n    let s:client['async_req_id'] = 1\n  endif\nendfunction\n"
  },
  {
    "path": "autoload/coc/snippet.vim",
    "content": "scriptencoding utf-8\nlet s:is_vim = !has('nvim')\nlet s:map_next = 1\nlet s:map_prev = 1\n\nfunction! coc#snippet#_select_mappings(bufnr)\n  if !get(g:, 'coc_selectmode_mapping', 1)\n    return\n  endif\n\n  redir => mappings\n    silent! smap\n  redir END\n\n  for map in map(filter(split(mappings, '\\n'),\n        \\ \"v:val !~# '^s' && v:val !~# '^\\\\a*\\\\s*<\\\\S\\\\+>'\"),\n        \\ \"matchstr(v:val, '^\\\\a*\\\\s*\\\\zs\\\\S\\\\+')\")\n    silent! execute 'sunmap' map\n    call coc#compat#buf_del_keymap(a:bufnr, 's', map)\n  endfor\n\n  \" same behaviour of ultisnips\n  snoremap <silent> <BS> <c-g>c\n  snoremap <silent> <DEL> <c-g>c\n  snoremap <silent> <c-h> <c-g>c\n  snoremap <c-r> <c-g>\"_c<c-r>\nendfunction\n\nfunction! coc#snippet#show_choices(lnum, col, position, input) abort\n  call coc#snippet#move(a:position)\n  call CocActionAsync('startCompletion', {\n          \\ 'source': '$words',\n          \\ 'col': a:col\n          \\ })\n  redraw\nendfunction\n\nfunction! coc#snippet#enable(...)\n  let bufnr = get(a:, 1, bufnr('%'))\n  if getbufvar(bufnr, 'coc_snippet_active', 0) == 1\n    return\n  endif\n  let complete = get(a:, 2, 0)\n  call setbufvar(bufnr, 'coc_snippet_active', 1)\n  call coc#snippet#_select_mappings(bufnr)\n  let nextkey = get(g:, 'coc_snippet_next', '<C-j>')\n  let prevkey = get(g:, 'coc_snippet_prev', '<C-k>')\n  if maparg(nextkey, 'i') =~# 'snippet'\n    let s:map_next = 0\n  endif\n  if maparg(prevkey, 'i') =~# 'snippet'\n    let s:map_prev = 0\n  endif\n  if !empty(nextkey)\n    if s:map_next\n      call s:buf_add_keymap(bufnr, 'i', nextkey, \"<Cmd>:call coc#snippet#jump(1, \".complete.\")<cr>\")\n    endif\n    call s:buf_add_keymap(bufnr, 's', nextkey, \"<Cmd>:call coc#snippet#jump(1, \".complete.\")<cr>\")\n  endif\n  if !empty(prevkey)\n    if s:map_prev\n      call s:buf_add_keymap(bufnr, 'i', prevkey, \"<Cmd>:call coc#snippet#jump(0, \".complete.\")<cr>\")\n    endif\n    call s:buf_add_keymap(bufnr, 's', prevkey, \"<Cmd>:call coc#snippet#jump(0, \".complete.\")<cr>\")\n  endif\nendfunction\n\nfunction! coc#snippet#disable(...)\n  let bufnr = get(a:, 1, bufnr('%'))\n  if getbufvar(bufnr, 'coc_snippet_active', 0) == 0\n    return\n  endif\n  call setbufvar(bufnr, 'coc_snippet_active', 0)\n  let nextkey = get(g:, 'coc_snippet_next', '<C-j>')\n  let prevkey = get(g:, 'coc_snippet_prev', '<C-k>')\n  if s:map_next\n    call coc#compat#buf_del_keymap(bufnr, 'i', nextkey)\n  endif\n  if s:map_prev\n    call coc#compat#buf_del_keymap(bufnr, 'i', prevkey)\n  endif\n  call coc#compat#buf_del_keymap(bufnr, 's', nextkey)\n  call coc#compat#buf_del_keymap(bufnr, 's', prevkey)\nendfunction\n\nfunction! coc#snippet#prev() abort\n  call coc#rpc#request('snippetPrev', [])\n  return ''\nendfunction\n\nfunction! coc#snippet#next() abort\n  call coc#rpc#request('snippetNext', [])\n  return ''\nendfunction\n\nfunction! coc#snippet#jump(direction, complete) abort\n  if a:direction == 1 && a:complete\n    if pumvisible()\n      let pre = exists('*complete_info') && complete_info()['selected'] == -1 ? \"\\<C-n>\" : ''\n      call feedkeys(pre.\"\\<C-y>\", 'in')\n      return ''\n    endif\n    if coc#pum#visible()\n      \" Discard the return value, otherwise weird characters will be inserted\n      call coc#pum#close('confirm')\n      return ''\n    endif\n  endif\n  call coc#pum#close()\n  call coc#rpc#request(a:direction == 1 ? 'snippetNext' : 'snippetPrev', [])\n  return ''\nendfunction\n\nfunction! coc#snippet#select(start, end, text) abort\n  if coc#pum#visible()\n    call coc#pum#close()\n  endif\n  if mode() ==? 's'\n    call feedkeys(\"\\<Esc>\", 'in')\n  endif\n  if &selection ==# 'exclusive'\n    let cursor = coc#snippet#to_cursor(a:start)\n    call cursor([cursor[0], cursor[1]])\n    let cmd = ''\n    let cmd .= mode()[0] ==# 'i' ? \"\\<Esc>\".(col('.') == 1 ? '' : 'l') : ''\n    let cmd .= printf('zvv%s', strchars(a:text) . 'l')\n    let cmd .= \"\\<C-g>\"\n  else\n    let cursor = coc#snippet#to_cursor(a:end)\n    call cursor([cursor[0], cursor[1] - 1])\n    let len = strchars(a:text) - 1\n    let cmd = ''\n    let cmd .= mode()[0] ==# 'i' ? \"\\<Esc>\".(col('.') == 1 ? '' : 'l') : ''\n    let cmd .= printf('zvv%s', len > 0 ? len . 'h' : '')\n    let cmd .= \"o\\<C-g>\"\n  endif\n  if s:is_vim\n    \" Can't use 't' since the code of <esc> can be changed.\n    call feedkeys(cmd, 'n')\n  else\n    call feedkeys(cmd, 'nt')\n  endif\nendfunction\n\nfunction! coc#snippet#move(position) abort\n  let m = mode()\n  if m ==? 's'\n    call feedkeys(\"\\<Esc>\", 'in')\n  endif\n  let pos = coc#snippet#to_cursor(a:position)\n  call cursor(pos)\n  if pos[1] > strlen(getline(pos[0]))\n    startinsert!\n  else\n    startinsert\n  endif\nendfunction\n\nfunction! coc#snippet#to_cursor(position) abort\n  let line = getline(a:position.line + 1)\n  if line is v:null\n    return [a:position.line + 1, a:position.character + 1]\n  endif\n  return [a:position.line + 1, coc#string#byte_index(line, a:position.character) + 1]\nendfunction\n\nfunction! s:buf_add_keymap(bufnr, mode, lhs, rhs) abort\n  let opts = {'nowait': v:true, 'silent': v:true}\n  call coc#compat#buf_add_keymap(a:bufnr, a:mode, a:lhs, a:rhs, opts)\nendfunction\n"
  },
  {
    "path": "autoload/coc/string.vim",
    "content": "scriptencoding utf-8\n\nfunction! coc#string#last_character(line) abort\n  return strcharpart(a:line, strchars(a:line) - 1, 1)\nendfunction\n\n\" Get utf16 code unit index from col (0 based)\nfunction! coc#string#character_index(line, byteIdx) abort\n  if a:byteIdx <= 0\n    return 0\n  endif\n  let i = 0\n  for char in split(strpart(a:line, 0, a:byteIdx), '\\zs')\n    let i += char2nr(char) > 65535 ? 2 : 1\n  endfor\n  return i\nendfunction\n\n\" Convert utf16 character index to byte index\nfunction! coc#string#byte_index(line, character) abort\n  if a:character <= 0\n    return 0\n  endif\n  \" code unit index\n  let i = 0\n  let len = 0\n  for char in split(a:line, '\\zs')\n    let i += char2nr(char) > 65535 ? 2 : 1\n    let len += strlen(char)\n    if i >= a:character\n      break\n    endif\n  endfor\n  return len\nendfunction\n\nfunction! coc#string#character_length(text) abort\n  let i = 0\n  for char in split(a:text, '\\zs')\n    let i += char2nr(char) > 65535 ? 2 : 1\n  endfor\n  return i\nendfunction\n\nfunction! coc#string#reflow(lines, width) abort\n  let lines = []\n  let currlen = 0\n  let parts = []\n  for line in a:lines\n    for part in split(line, '\\s\\+')\n      let w = strwidth(part)\n      if currlen + w + 1 >= a:width\n        if len(parts) > 0\n          call add(lines, join(parts, ' '))\n        endif\n        if w >= a:width\n          call add(lines, part)\n          let currlen = 0\n          let parts = []\n        else\n          let currlen = w\n          let parts = [part]\n        endif\n        continue\n      endif\n      call add(parts, part)\n      let currlen = currlen + w + 1\n    endfor\n  endfor\n  if len(parts) > 0\n    call add(lines, join(parts, ' '))\n  endif\n  return empty(lines) ? [''] : lines\nendfunction\n\n\" Used when 'wrap' and 'linebreak' is enabled\nfunction! coc#string#content_height(lines, width) abort\n  let len = 0\n  let pattern = empty(&breakat) ? '.\\zs' : '['.substitute(&breakat, '\\([\\[\\]\\-]\\)', '\\\\\\1', 'g').']\\zs'\n  for line in a:lines\n    if strwidth(line) <= a:width\n      let len += 1\n    else\n      let currlen = 0\n      for part in split(line, pattern)\n        let wl = strwidth(part)\n        if currlen == 0 && wl > 0\n          let len += 1\n        endif\n        let delta = currlen + wl - a:width\n        if delta >= 0\n          let len = len + (delta > 0)\n          let currlen = delta == 0 ? 0 : wl\n          if wl >= a:width\n            let currlen = wl%a:width\n            let len += float2nr(ceil(wl/(a:width + 0.0))) - (currlen == 0)\n          endif\n        else\n          let currlen = currlen + wl\n        endif\n      endfor\n    endif\n  endfor\n  return len\nendfunction\n\n\" insert inserted to line at position, use ... when result is too long\n\" line should only contains character has strwidth equals 1\nfunction! coc#string#compose(line, position, inserted) abort\n  let width = strwidth(a:line)\n  let text = a:inserted\n  let res = a:line\n  let need_truncate = a:position + strwidth(text) + 1 > width\n  if need_truncate\n    let remain = width - a:position - 3\n    if remain < 2\n      \" use text for full line, use first & end of a:line, ignore position\n      let res = strcharpart(a:line, 0, 1)\n      let w = strwidth(res)\n      for i in range(strchars(text))\n        let c = strcharpart(text, i, 1)\n        let a = strwidth(c)\n        if w + a <= width - 1\n          let w = w + a\n          let res = res . c\n        endif\n      endfor\n      let res = res.strcharpart(a:line, w)\n    else\n      let res = strcharpart(a:line, 0, a:position)\n      let w = strwidth(res)\n      for i in range(strchars(text))\n        let c = strcharpart(text, i, 1)\n        let a = strwidth(c)\n        if w + a <= width - 3\n          let w = w + a\n          let res = res . c\n        endif\n      endfor\n      let res = res.'..'\n      let w = w + 2\n      let res = res . strcharpart(a:line, w)\n    endif\n  else\n    let first = strcharpart(a:line, 0, a:position)\n    let res = first . text . strcharpart(a:line, a:position + strwidth(text))\n  endif\n  return res\nendfunction\n"
  },
  {
    "path": "autoload/coc/task.vim",
    "content": "\" ============================================================================\n\" Description: Manage long running tasks.\n\" Author: Qiming Zhao <chemzqm@gmail.com>\n\" Licence: Anti 966 licence\n\" Version: 0.1\n\" Last Modified:  Dec 12, 2020\n\" ============================================================================\nscriptencoding utf-8\n\nlet s:is_vim = !has('nvim')\nlet s:running_task = {}\n\" neovim emit strings that part of lines.\nlet s:out_remain_text = {}\nlet s:err_remain_text = {}\n\nfunction! coc#task#start(id, opts)\n  if coc#task#running(a:id)\n    call coc#task#stop(a:id)\n  endif\n  let cmd = [a:opts['cmd']] + get(a:opts, 'args', [])\n  let cwd = get(a:opts, 'cwd', getcwd())\n  let env = get(a:opts, 'env', {})\n  \" cmd args cwd pty\n  if s:is_vim\n    let options = {\n          \\ 'cwd': cwd,\n          \\ 'noblock' : 1,\n          \\ 'err_mode': 'nl',\n          \\ 'out_mode': 'nl',\n          \\ 'err_cb': {channel, message -> s:on_stderr(a:id, [message])},\n          \\ 'out_cb': {channel, message -> s:on_stdout(a:id, [message])},\n          \\ 'exit_cb': {channel, code -> s:on_exit(a:id, code)},\n          \\ 'env': env,\n          \\}\n    if get(a:opts, 'pty', 0)\n      let options['pty'] = 1\n    endif\n    let job = job_start(cmd, options)\n    let status = job_status(job)\n    if status !=# 'run'\n      echohl Error | echom 'Failed to start '.a:id.' task' | echohl None\n      return v:false\n    endif\n    let s:running_task[a:id] = job\n  else\n    let options = {\n          \\ 'cwd': cwd,\n          \\ 'on_stderr': {channel, msgs -> s:on_stderr(a:id, msgs)},\n          \\ 'on_stdout': {channel, msgs -> s:on_stdout(a:id, msgs)},\n          \\ 'on_exit': {channel, code -> s:on_exit(a:id, code)},\n          \\ 'detach': get(a:opts, 'detach', 0),\n          \\ 'env': env,\n          \\}\n    if get(a:opts, 'pty', 0)\n      let options['pty'] = 1\n    endif\n    let chan_id = jobstart(cmd, options)\n    if chan_id <= 0\n      echohl Error | echom 'Failed to start '.a:id.' task' | echohl None\n      return v:false\n    endif\n    let s:running_task[a:id] = chan_id\n  endif\n  return v:true\nendfunction\n\nfunction! coc#task#stop(id)\n  let job = get(s:running_task, a:id, v:null)\n  if !job | return | endif\n  if s:is_vim\n    call job_stop(job, 'term')\n  else\n    call jobstop(job)\n  endif\n  sleep 50m\n  let running = coc#task#running(a:id)\n  if running\n    echohl Error | echom 'job '.a:id. ' stop failed.' | echohl None\n  endif\nendfunction\n\nfunction! s:on_exit(id, code) abort\n  if get(g:, 'coc_vim_leaving', 0) | return | endif\n  if !s:is_vim\n    let s:out_remain_text[a:id] = ''\n    let s:err_remain_text[a:id] = ''\n  endif\n  if has_key(s:running_task, a:id)\n    call remove(s:running_task, a:id)\n  endif\n  call coc#rpc#notify('TaskExit', [a:id, a:code])\nendfunction\n\nfunction! s:on_stderr(id, msgs)\n  if get(g:, 'coc_vim_leaving', 0) | return | endif\n  if empty(a:msgs)\n    return\n  endif\n  if s:is_vim\n    call coc#rpc#notify('TaskStderr', [a:id, a:msgs])\n  else\n    let remain = get(s:err_remain_text, a:id, '')\n    let eof = (a:msgs == [''])\n    let msgs = copy(a:msgs)\n    if len(remain) > 0\n      if msgs[0] == ''\n        let msgs[0] = remain\n      else\n        let msgs[0] = remain . msgs[0]\n      endif\n    endif\n    let last = msgs[len(msgs) - 1]\n    let s:err_remain_text[a:id] = len(last) > 0 ? last : ''\n    \" all lines from 0 to n - 2\n    if len(msgs) > 1\n      call coc#rpc#notify('TaskStderr', [a:id, msgs[:len(msgs)-2]])\n    elseif eof && len(msgs[0]) > 0\n      call coc#rpc#notify('TaskStderr', [a:id, msgs])\n    endif\n  endif\nendfunction\n\nfunction! s:on_stdout(id, msgs)\n  if empty(a:msgs)\n    return\n  endif\n  if s:is_vim\n    call coc#rpc#notify('TaskStdout', [a:id, a:msgs])\n  else\n    let remain = get(s:out_remain_text, a:id, '')\n    let eof = (a:msgs == [''])\n    let msgs = copy(a:msgs)\n    if len(remain) > 0\n      if msgs[0] == ''\n        let msgs[0] = remain\n      else\n        let msgs[0] = remain . msgs[0]\n      endif\n    endif\n    let last = msgs[len(msgs) - 1]\n    let s:out_remain_text[a:id] = len(last) > 0 ? last : ''\n    \" all lines from 0 to n - 2\n    if len(msgs) > 1\n      call coc#rpc#notify('TaskStdout', [a:id, msgs[:len(msgs)-2]])\n    elseif eof && len(msgs[0]) > 0\n      call coc#rpc#notify('TaskStdout', [a:id, msgs])\n    endif\n  endif\nendfunction\n\nfunction! coc#task#running(id)\n  if !has_key(s:running_task, a:id) == 1\n    return v:false\n  endif\n  let job = s:running_task[a:id]\n  if s:is_vim\n    let status = job_status(job)\n    return status ==# 'run'\n  endif\n  let [code] = jobwait([job], 10)\n  return code == -1\nendfunction\n"
  },
  {
    "path": "autoload/coc/terminal.vim",
    "content": "scriptencoding utf-8\nlet s:is_vim = !has('nvim')\nlet s:channel_map = {}\nlet s:is_win = has('win32') || has('win64')\n\n\" start terminal, return [bufnr, pid]\nfunction! coc#terminal#start(cmd, cwd, env, strict) abort\n  if s:is_vim && !has('terminal')\n    throw 'terminal feature not supported by current vim.'\n  endif\n  let cwd = empty(a:cwd) ? getcwd() : a:cwd\n  execute 'belowright '.get(g:, 'coc_terminal_height', 8).'new +setl\\ buftype=nofile'\n  setl winfixheight\n  setl norelativenumber\n  setl nonumber\n  setl bufhidden=hide\n  if exists('&winfixbuf')\n    setl winfixbuf\n  endif\n  if exists('#User#CocTerminalOpen')\n    exe 'doautocmd <nomodeline> User CocTerminalOpen'\n  endif\n  let bufnr = bufnr('%')\n  let env = {}\n  let original = {}\n  if !empty(a:env)\n    \" use env option when possible\n    if s:is_vim\n      let env = copy(a:env)\n    elseif exists('*setenv')\n      for key in keys(a:env)\n        let original[key] = getenv(key)\n        call setenv(key, a:env[key])\n      endfor\n    endif\n  endif\n\n  function! s:OnExit(status) closure\n    call coc#rpc#notify('CocAutocmd', ['TermExit', bufnr, a:status])\n    if a:status == 0\n      execute 'silent! bd! '.bufnr\n    endif\n  endfunction\n\n  if s:is_vim\n    let res = term_start(a:cmd, {\n          \\ 'cwd': cwd,\n          \\ 'term_kill': s:is_win ? 'kill' : 'term',\n          \\ 'term_finish': 'close',\n          \\ 'exit_cb': {job, status -> s:OnExit(status)},\n          \\ 'curwin': 1,\n          \\ 'env': env,\n          \\})\n    if res == 0\n      throw 'create terminal job failed'\n    endif\n    let job = term_getjob(bufnr)\n    let s:channel_map[bufnr] = job_getchannel(job)\n    wincmd p\n    return [bufnr, job_info(job).process]\n  else\n    let job_id = termopen(a:cmd, {\n          \\ 'cwd': cwd,\n          \\ 'pty': v:true,\n          \\ 'on_exit': {job, status -> s:OnExit(status)},\n          \\ 'env': env,\n          \\ 'clear_env': a:strict ? v:true : v:false\n          \\ })\n    if !empty(original) && exists('*setenv')\n      for key in keys(original)\n        call setenv(key, original[key])\n      endfor\n    endif\n    if job_id == 0\n      throw 'create terminal job failed'\n    endif\n    wincmd p\n    let s:channel_map[bufnr] = job_id\n    return [bufnr, jobpid(job_id)]\n  endif\nendfunction\n\nfunction! coc#terminal#send(bufnr, text, add_new_line) abort\n  let chan = get(s:channel_map, a:bufnr, v:null)\n  if empty(chan) | return| endif\n  if s:is_vim\n    if !a:add_new_line\n      call ch_sendraw(chan, a:text)\n    else\n      call ch_sendraw(chan, a:text.(s:is_win ? \"\\r\\n\" : \"\\n\"))\n    endif\n  else\n    let lines = split(a:text, '\\v\\r?\\n')\n    if a:add_new_line && !empty(lines[len(lines) - 1])\n      if s:is_win\n        call add(lines, \"\\r\\n\")\n      else\n        call add(lines, '')\n      endif\n    endif\n    call chansend(chan, lines)\n    let winid = bufwinid(a:bufnr)\n    if winid != -1\n      call win_execute(winid, 'noa normal! G')\n    endif\n  endif\nendfunction\n\nfunction! coc#terminal#close(bufnr) abort\n  if !s:is_vim\n    let job_id = get(s:channel_map, a:bufnr, 0)\n    if !empty(job_id)\n      silent! call chanclose(job_id)\n    endif\n  endif\n  exe 'silent! bd! '.a:bufnr\nendfunction\n\nfunction! coc#terminal#show(bufnr, opts) abort\n  if !bufloaded(a:bufnr)\n    return v:false\n  endif\n  let winids = win_findbuf(a:bufnr)\n  if index(winids, win_getid()) != -1\n    execute 'normal! G'\n    return v:true\n  endif\n  let curr_winid = -1\n  for winid in winids\n    if get(get(getwininfo(winid), 0, {}), 'tabnr', 0) == tabpagenr()\n      let curr_winid = winid\n    else\n      call coc#window#close(winid)\n    endif\n  endfor\n  let height = get(a:opts, 'height', 8)\n  if curr_winid == -1\n    execute 'below '.a:bufnr.'sb'\n    execute 'resize '.height\n    call coc#util#do_autocmd('CocTerminalOpen')\n  else\n    call win_gotoid(curr_winid)\n  endif\n  execute 'normal! G'\n  if get(a:opts, 'preserveFocus', v:false)\n    execute 'wincmd p'\n  endif\n  return v:true\nendfunction\n"
  },
  {
    "path": "autoload/coc/text.vim",
    "content": "vim9script\n\nexport def LinesEqual(one: list<string>, two: list<string>): bool\n  if len(one) != len(two)\n    return false\n  endif\n  for i in range(0, len(one) - 1)\n    if one[i] !=# two[i]\n      return false\n    endif\n  endfor\n  return true\nenddef\n\n# Slice like javascript by character index\nexport def Slice(str: string, start_idx: number, end_idx: any = null): string\n  if end_idx == null\n    return str[start_idx : ]\n  endif\n  if start_idx >= end_idx\n    return ''\n  endif\n  return str[start_idx : end_idx - 1]\nenddef\n\n# Function to check if a string starts with a given prefix\nexport def StartsWith(str: string, prefix: string): bool\n  return str =~# '^' .. prefix\nenddef\n\n# Function to check if a string ends with a given suffix\nexport def EndsWith(str: string, suffix: string): bool\n  return str =~# suffix .. '$'\nenddef\n\n# UTF16 character index in line to byte index.\nexport def Byte_index(line: string, character: number): number\n  if character == 0\n    return 0\n  endif\n  var i = 0\n  var len = 0\n  for char in split(line, '\\zs')\n    i += char2nr(char) > 65535 ? 2 : 1\n    len += strlen(char)\n    if i >= character\n      break\n    endif\n  endfor\n  return len\nenddef\n\n# Character index of current vim encoding.\nexport def Char_index(line: string, colIdx: number): number\n  return strpart(line, 0, colIdx)->strchars()\nenddef\n\n# Using character indexes\nexport def LcsDiff(str1: string, str2: string): list<dict<any>>\n  def Lcs(a: string, b: string): string\n    var matrix = []\n    for i in range(0, strchars(a))\n      matrix[i] = []\n      for j in range(0, strchars(b))\n        if i == 0 || j == 0\n          matrix[i][j] = 0\n        elseif a[i] == b[j]\n          matrix[i][j] = matrix[i - 1][j - 1] + 1\n        else\n          matrix[i][j] = max([matrix[i - 1][j], matrix[i][j - 1]])\n        endif\n      endfor\n    endfor\n    var result = ''\n    var i = strchars(a) - 1\n    var j = strchars(b) - 1\n    while i >= 0 && j >= 0\n      if a[i] == b[j]\n        result = a[i] .. result\n        i -= 1\n        j -= 1\n      elseif matrix[i - 1][j] > matrix[i][j - 1]\n        i -= 1\n      else\n        j -= 1\n      endif\n    endwhile\n    return result\n  enddef\n  const len1 = strchars(str1)\n  const len2 = strchars(str2)\n  var common = Lcs(str1, str2)\n  var result = []\n  var i1 = 0\n  var i2 = 0\n  var ic = 0\n  while ic < strchars(common)\n    # 处理str1中不在公共序列的部分\n    while i1 < len1 && str1[i1] != common[ic]\n      result->add({type: '-', char: str1[i1]})\n      i1 += 1\n    endwhile\n    # 处理str2中不在公共序列的部分\n    while i2 < len2 && str2[i2] != common[ic]\n      result->add({type: '+', char: str2[i2]})\n      i2 += 1\n    endwhile\n    # 添加公共字符\n    if ic < strchars(common)\n      result->add({type: '=', char: common[ic]})\n      i1 += 1\n      i2 += 1\n      ic += 1\n    endif\n  endwhile\n  # 处理剩余字符\n  while i1 < len1\n    result->add({type: '-', char: str1[i1]})\n    i1 += 1\n  endwhile\n  while i2 < len2\n    result->add({type: '+', char: str2[i2]})\n    i2 += 1\n  endwhile\n  return result\nenddef\n\n# Get the single changed part, by character index of cursor.\ndef SimpleStringDiff(oldStr: string, newStr: string, charIdx: number = -1): dict<any>\n  var suffixLen = 0\n  const old_length = strchars(oldStr)\n  const new_length = strchars(newStr)\n  var maxSuffixLen = 0\n  if charIdx >= 0\n    maxSuffixLen = min([old_length, new_length - charIdx])\n    while suffixLen < maxSuffixLen\n      if strcharpart(oldStr, old_length - suffixLen - 1, 1) !=\n         strcharpart(newStr, new_length - suffixLen - 1, 1)\n        break\n      endif\n      suffixLen += 1\n    endwhile\n  else\n    maxSuffixLen = min([old_length, new_length])\n    while suffixLen < maxSuffixLen\n      if strcharpart(oldStr, old_length - suffixLen - 1, 1) !=\n         strcharpart(newStr, new_length - suffixLen - 1, 1)\n        break\n      endif\n      suffixLen += 1\n    endwhile\n  endif\n  var prefixLen = 0\n  var remainingLen = min([old_length - suffixLen, new_length - suffixLen])\n  while prefixLen < remainingLen\n    if strcharpart(oldStr, prefixLen, 1) != strcharpart(newStr, prefixLen, 1)\n      break\n    endif\n    prefixLen += 1\n  endwhile\n  # Reduce suffixLen\n  if suffixLen == new_length - charIdx\n    const max = min([old_length, new_length]) - prefixLen - suffixLen\n    var i = 0\n    while i < max\n      if strcharpart(oldStr, old_length - suffixLen - 1, 1) !=\n         strcharpart(newStr, new_length - suffixLen - 1, 1)\n        break\n      endif\n      suffixLen += 1\n      i += 1\n    endwhile\n  endif\n  const endIndex = old_length - suffixLen\n  echo suffixLen\n  return {\n    oldStart: prefixLen,\n    oldEnd: endIndex,\n    newText: Slice(newStr, prefixLen, new_length - suffixLen),\n  }\nenddef\n\n# Search for new start position of diff in new string\nexport def SearchChangePosition(newStr: string, oldStr: string, diff: dict<any>): number\n  var result = -1\n  const delta = diff.oldEnd - diff.oldStart\n  const oldText = Slice(oldStr, diff.oldStart, diff.oldEnd)\n  def CheckPosition(idx: number): bool\n    if delta == 0 || Slice(newStr, idx, idx + delta) ==# oldText\n      result = idx\n      return true\n    endif\n    return false\n  enddef\n  if Slice(oldStr, 0, diff.oldStart) ==# Slice(newStr, 0, diff.oldStart) && CheckPosition(diff.oldStart)\n    return result\n  endif\n  const diffs = LcsDiff(oldStr, newStr)\n  # oldStr index\n  var used = 0\n  # newStr index\n  var index = 0\n  # Until used reached diff.oldStart\n  var i = 0\n  for d in diffs\n    if d.type ==# '-'\n      used += 1\n    elseif d.type ==# '+'\n      index += 1\n    else\n      used += 1\n      index += 1\n    endif\n    if used == diff.oldStart && CheckPosition(index)\n      break\n    endif\n  endfor\n  return result\nenddef\n\n# 0 based start index and end index\nexport def SimpleApplyDiff(text: string, startIdx: number, endIdx: number, insert: string): string\n  return Slice(text, 0, startIdx) .. insert .. Slice(text, endIdx)\nenddef\n\n# Apply change from original to current for newText\nexport def DiffApply(original: string, current: string, newText: string, colIdx: number): any\n  if original ==# current\n    return newText\n  endif\n  const charIdx = colIdx == -1 ? -1 : Char_index(current, colIdx)\n  const diff = SimpleStringDiff(original, current, charIdx)\n  const delta = diff.oldEnd - diff.oldStart\n  const idx = SearchChangePosition(newText, original, diff)\n  if idx == -1\n    return null\n  endif\n  return SimpleApplyDiff(newText, idx, idx + delta, diff.newText)\nenddef\n"
  },
  {
    "path": "autoload/coc/ui.vim",
    "content": "let s:is_vim = !has('nvim')\nlet s:is_win = has('win32') || has('win64')\nlet s:is_mac = has('mac')\nlet s:root = expand('<sfile>:h:h:h')\nlet s:sign_api = exists('*sign_getplaced') && exists('*sign_place')\nlet s:sign_groups = []\nlet s:outline_preview_bufnr = 0\nlet s:is_win32unix = has('win32unix')\n\n\" Check <Tab> and <CR>\nfunction! coc#ui#check_pum_keymappings(trigger) abort\n  if get(g:, 'coc_disable_mappings_check', 0) == 1\n    return\n  endif\n  if a:trigger !=# 'none'\n    for key in ['<cr>', '<tab>', '<c-y>', '<s-tab>']\n      let arg = maparg(key, 'i', 0, 1)\n      if get(arg, 'expr', 0)\n        let rhs = get(arg, 'rhs', '')\n        if rhs =~# '\\<pumvisible()' && rhs !~# '\\<coc#pum#visible()'\n          let rhs = substitute(rhs, '\\Cpumvisible()', 'coc#pum#visible()', 'g')\n          let rhs = substitute(rhs, '\\c\"\\\\<C-n>\"', 'coc#pum#next(1)', '')\n          let rhs = substitute(rhs, '\\c\"\\\\<C-p>\"', 'coc#pum#prev(1)', '')\n          let rhs = substitute(rhs, '\\c\"\\\\<C-y>\"', 'coc#pum#confirm()', '')\n          execute 'inoremap <silent><nowait><expr> '.arg['lhs'].' '.rhs\n        endif\n      endif\n    endfor\n  endif\nendfunction\n\nfunction! coc#ui#quickpick(title, items, cb) abort\n  if exists('*popup_menu')\n    function! s:QuickpickHandler(id, result) closure\n      call a:cb(v:null, a:result)\n    endfunction\n    function! s:QuickpickFilter(id, key) closure\n      for i in range(1, len(a:items))\n        if a:key == string(i)\n          call popup_close(a:id, i)\n          return 1\n        endif\n      endfor\n      \" No shortcut, pass to generic filter\n      return popup_filter_menu(a:id, a:key)\n    endfunction\n    try\n      call popup_menu(a:items, {\n        \\ 'title': a:title,\n        \\ 'filter': function('s:QuickpickFilter'),\n        \\ 'callback': function('s:QuickpickHandler'),\n        \\ })\n      redraw\n    catch /.*/\n      call a:cb(v:exception)\n    endtry\n  else\n    let res = inputlist([a:title] + map(range(1, len(a:items)), 'v:val . \". \" . a:items[v:val - 1]'))\n    call a:cb(v:null, res)\n  endif\nendfunction\n\n\" cmd, cwd\nfunction! coc#ui#open_terminal(opts) abort\n  if s:is_vim && !exists('*term_start')\n    echohl WarningMsg | echon \"Your vim doesn't have terminal support!\" | echohl None\n    return\n  endif\n  if get(a:opts, 'position', 'bottom') ==# 'bottom'\n    let p = '5new'\n  else\n    let p = 'vnew'\n  endif\n  execute 'belowright '.p.' +setl\\ buftype=nofile '\n  setl buftype=nofile\n  setl winfixheight\n  setl norelativenumber\n  setl nonumber\n  setl bufhidden=wipe\n  if exists('#User#CocTerminalOpen')\n    exe 'doautocmd <nomodeline> User CocTerminalOpen'\n  endif\n  let cmd = get(a:opts, 'cmd', '')\n  let autoclose = get(a:opts, 'autoclose', 1)\n  if empty(cmd)\n    throw 'command required!'\n  endif\n  let cwd = get(a:opts, 'cwd', getcwd())\n  let keepfocus = get(a:opts, 'keepfocus', 0)\n  let bufnr = bufnr('%')\n  let Callback = get(a:opts, 'Callback', v:null)\n\n  function! s:OnExit(status) closure\n    let content = join(getbufline(bufnr, 1, '$'), \"\\n\")\n    if a:status == 0 && autoclose == 1\n      execute 'silent! bd! '.bufnr\n    endif\n    if !empty(Callback)\n      call call(Callback, [a:status, bufnr, content])\n    endif\n  endfunction\n\n  if s:is_vim\n    if s:is_win\n      let cmd = ['cmd.exe', '/C', cmd]\n    endif\n    call term_start(cmd, {\n          \\ 'cwd': cwd,\n          \\ 'term_finish': 'close',\n          \\ 'exit_cb': {job, status -> s:OnExit(status)},\n          \\ 'curwin': 1,\n          \\})\n  else\n    call termopen(cmd, {\n          \\ 'cwd': cwd,\n          \\ 'on_exit': {job, status -> s:OnExit(status)},\n          \\})\n  endif\n  if keepfocus\n    wincmd p\n  endif\n  return bufnr\nendfunction\n\n\" run command in terminal\nfunction! coc#ui#run_terminal(opts, cb)\n  let cmd = get(a:opts, 'cmd', '')\n  if empty(cmd)\n    return a:cb('command required for terminal')\n  endif\n  let opts = {\n        \\ 'cmd': cmd,\n        \\ 'cwd': empty(get(a:opts, 'cwd', '')) ? getcwd() : a:opts['cwd'],\n        \\ 'keepfocus': get(a:opts, 'keepfocus', 0),\n        \\ 'Callback': {status, bufnr, content -> a:cb(v:null, {'success': status == 0 ? v:true : v:false, 'bufnr': bufnr, 'content': content})}\n        \\}\n  call coc#ui#open_terminal(opts)\nendfunction\n\nfunction! coc#ui#fix() abort\n  let file = s:root .. '/esbuild.js'\n  if filereadable(file)\n    let opts = {\n          \\ 'cmd': 'npm ci',\n          \\ 'cwd': s:root,\n          \\ 'keepfocus': 1,\n          \\ 'Callback': {_ -> execute('CocRestart')}\n          \\}\n    call coc#ui#open_terminal(opts)\n  endif\nendfunction\n\nfunction! coc#ui#echo_hover(msg)\n  echohl MoreMsg\n  echo a:msg\n  echohl None\n  let g:coc_last_hover_message = a:msg\nendfunction\n\nfunction! coc#ui#echo_messages(hl, msgs)\n  if a:hl !~# 'Error' && (mode() !~# '\\v^(i|n)$')\n    return\n  endif\n  let msgs = filter(copy(a:msgs), '!empty(v:val)')\n  if empty(msgs)\n    return\n  endif\n  execute 'echohl '.a:hl\n  echom join(msgs, \"\\n\")\n  echohl None\nendfunction\n\nfunction! coc#ui#preview_info(lines, filetype, ...) abort\n  pclose\n  keepalt new +setlocal\\ previewwindow|setlocal\\ buftype=nofile|setlocal\\ noswapfile|setlocal\\ wrap [Document]\n  setl bufhidden=wipe\n  setl nobuflisted\n  setl nospell\n  exe 'setl filetype='.a:filetype\n  setl conceallevel=0\n  setl nofoldenable\n  for command in a:000\n    execute command\n  endfor\n  call append(0, a:lines)\n  exe \"normal! z\" . len(a:lines) . \"\\<cr>\"\n  exe \"normal! gg\"\n  wincmd p\nendfunction\n\nfunction! coc#ui#open_files(files)\n  let bufnrs = []\n  \" added on latest vim8\n  for filepath in a:files\n    let file = fnamemodify(coc#util#node_to_win32unix(filepath), ':.')\n    if bufloaded(file)\n      call add(bufnrs, bufnr(file))\n    else\n      let bufnr = bufadd(file)\n      call bufload(file)\n      call add(bufnrs, bufnr)\n      call setbufvar(bufnr, '&buflisted', 1)\n    endif\n  endfor\n  doautocmd BufEnter\n  return bufnrs\nendfunction\n\nfunction! coc#ui#echo_lines(lines)\n  echo join(a:lines, \"\\n\")\nendfunction\n\nfunction! coc#ui#echo_signatures(signatures) abort\n  if pumvisible() | return | endif\n  echo \"\"\n  for i in range(len(a:signatures))\n    call s:echo_signature(a:signatures[i])\n    if i != len(a:signatures) - 1\n      echon \"\\n\"\n    endif\n  endfor\nendfunction\n\nfunction! s:echo_signature(parts)\n  for part in a:parts\n    let hl = get(part, 'type', 'Normal')\n    let text = get(part, 'text', '')\n    if !empty(text)\n      execute 'echohl '.hl\n      execute \"echon '\".substitute(text, \"'\", \"''\", 'g').\"'\"\n      echohl None\n    endif\n  endfor\nendfunction\n\nfunction! coc#ui#iterm_open(dir)\n  return s:osascript(\n      \\ 'if application \"iTerm2\" is not running',\n      \\   'error',\n      \\ 'end if') && s:osascript(\n      \\ 'tell application \"iTerm2\"',\n      \\   'tell current window',\n      \\     'create tab with default profile',\n      \\     'tell current session',\n      \\       'write text \"cd ' . a:dir . '\"',\n      \\       'write text \"clear\"',\n      \\       'activate',\n      \\     'end tell',\n      \\   'end tell',\n      \\ 'end tell')\nendfunction\n\nfunction! s:osascript(...) abort\n  let args = join(map(copy(a:000), '\" -e \".shellescape(v:val)'), '')\n  call  s:system('osascript'. args)\n  return !v:shell_error\nendfunction\n\nfunction! s:system(cmd)\n  let output = system(a:cmd)\n  if v:shell_error && output !=# \"\"\n    echohl Error | echom output | echohl None\n    return\n  endif\n  return output\nendfunction\n\nfunction! coc#ui#set_lines(bufnr, changedtick, original, replacement, start, end, changes, cursor, col, linecount) abort\n  try\n    if s:is_vim\n      call coc#vim9#Set_lines(a:bufnr, a:changedtick, a:original, a:replacement, a:start, a:end, a:changes, a:cursor, a:col, a:linecount)\n    else\n      call v:lua.require('coc.text').set_lines(a:bufnr, a:changedtick, a:original, a:replacement, a:start, a:end, a:changes, a:cursor, a:col, a:linecount)\n    endif\n  catch /.*/\n    \" Need try catch here on vim9\n    call coc#compat#send_error('coc#ui#set_lines', s:is_vim)\n  endtry\nendfunction\n\nfunction! coc#ui#change_lines(bufnr, list) abort\n  if !bufloaded(a:bufnr)\n    return v:null\n  endif\n  undojoin\n  for [lnum, line] in a:list\n    call setbufline(a:bufnr, lnum + 1, line)\n  endfor\nendfunction\n\nfunction! coc#ui#open_url(url)\n  if isdirectory(a:url) && $TERM_PROGRAM ==# \"iTerm.app\"\n    call coc#ui#iterm_open(a:url)\n    return\n  endif\n  if !empty(get(g:, 'coc_open_url_command', ''))\n    call system(g:coc_open_url_command.' '.a:url)\n    return\n  endif\n  if has('mac') && executable('open')\n    call system('open \"'.a:url.'\"')\n    return\n  endif\n  if executable('xdg-open')\n    call system('xdg-open \"'.a:url.'\"')\n    return\n  endif\n  call system('cmd /c start \"\" /b '. substitute(a:url, '&', '^&', 'g'))\n  if v:shell_error\n    echohl Error | echom 'Failed to open '.a:url | echohl None\n    return\n  endif\nendfunction\n\nfunction! coc#ui#rename_file(oldPath, newPath, write) abort\n  let oldPath = coc#util#node_to_win32unix(a:oldPath)\n  let newPath =  coc#util#node_to_win32unix(a:newPath)\n  let bufnr = bufnr(oldPath)\n  if bufnr == -1\n    throw 'Unable to get bufnr of '.oldPath\n  endif\n  if oldPath =~? newPath && (s:is_mac || s:is_win || s:is_win32unix)\n    return coc#ui#safe_rename(bufnr, oldPath, newPath, a:write)\n  endif\n  if bufloaded(newPath)\n    execute 'silent bdelete! '.bufnr(newPath)\n  endif\n  \" TODO use nvim_buf_set_name instead\n  let current = bufnr == bufnr('%')\n  let bufname = fnamemodify(newPath, \":~:.\")\n  let filepath = fnamemodify(bufname(bufnr), '%:p')\n  let winid = coc#compat#buf_win_id(bufnr)\n  let curr = -1\n  if winid == -1\n    let curr = win_getid()\n    let file = fnamemodify(bufname(bufnr), ':.')\n    execute 'keepalt tab drop '.fnameescape(bufname(bufnr))\n    let winid = win_getid()\n  endif\n  call win_execute(winid, 'keepalt file '.fnameescape(bufname), 'silent')\n  call win_execute(winid, 'doautocmd BufEnter')\n  if a:write\n    call win_execute(winid, 'noa write!', 'silent')\n    call delete(filepath, '')\n  endif\n  if curr != -1\n    call win_gotoid(curr)\n  endif\n  return bufnr\nendfunction\n\n\" System is case in sensitive and newPath have different case.\nfunction! coc#ui#safe_rename(bufnr, oldPath, newPath, write) abort\n  let winid = win_getid()\n  let lines = getbufline(a:bufnr, 1, '$')\n  execute 'keepalt tab drop '.fnameescape(fnamemodify(a:oldPath, ':.'))\n  let view = winsaveview()\n  execute 'keepalt bwipeout! '.a:bufnr\n  if a:write\n    call delete(a:oldPath, '')\n  endif\n  execute 'keepalt edit '.fnameescape(fnamemodify(a:newPath, ':~:.'))\n  let bufnr = bufnr('%')\n  call coc#compat#buf_set_lines(bufnr, 0, -1, lines)\n  if a:write\n    execute 'noa write'\n  endif\n  call winrestview(view)\n  call win_gotoid(winid)\n  return bufnr\nendfunction\n\nfunction! coc#ui#sign_unplace() abort\n  if exists('*sign_unplace')\n    for group in s:sign_groups\n      call sign_unplace(group)\n    endfor\n  endif\nendfunction\n\nfunction! coc#ui#update_signs(bufnr, group, signs) abort\n  if !s:sign_api || !bufloaded(a:bufnr)\n    return\n  endif\n  call sign_unplace(a:group, {'buffer': a:bufnr})\n  for def in a:signs\n    let opts = {'lnum': def['lnum']}\n    if has_key(def, 'priority')\n      let opts['priority'] = def['priority']\n    endif\n    call sign_place(0, a:group, def['name'], a:bufnr, opts)\n  endfor\nendfunction\n\nfunction! coc#ui#outline_preview(config) abort\n  let view_id = get(w:, 'cocViewId', '')\n  if view_id !=# 'OUTLINE'\n    return\n  endif\n  let wininfo = get(getwininfo(win_getid()), 0, v:null)\n  if empty(wininfo)\n    return\n  endif\n  let border = get(a:config, 'border', v:true)\n  let th = &lines - &cmdheight - 2\n  let range = a:config['range']\n  let height = min([range['end']['line'] - range['start']['line'] + 1, th - 4])\n  let to_left = &columns - wininfo['wincol'] - wininfo['width'] < wininfo['wincol']\n  let start_lnum = range['start']['line'] + 1\n  let end_lnum = range['end']['line'] + 1 - start_lnum > &lines ? start_lnum + &lines : range['end']['line'] + 1\n  let lines = getbufline(a:config['bufnr'], start_lnum, end_lnum)\n  let content_width = max(map(copy(lines), 'strdisplaywidth(v:val)'))\n  let width = min([content_width, a:config['maxWidth'], to_left ? wininfo['wincol'] - 3 : &columns - wininfo['wincol'] - wininfo['width']])\n  let filetype = getbufvar(a:config['bufnr'], '&filetype')\n  let cursor_row = coc#cursor#screen_pos()[0]\n  let config = {\n      \\ 'relative': 'editor',\n      \\ 'row': cursor_row - 1 + height < th ? cursor_row - (border ? 1 : 0) : th - height - (border ? 1 : -1),\n      \\ 'col': to_left ? wininfo['wincol'] - 4 - width : wininfo['wincol'] + wininfo['width'],\n      \\ 'width': width,\n      \\ 'height': height,\n      \\ 'lines': lines,\n      \\ 'border': border ? [1,1,1,1] : v:null,\n      \\ 'rounded': get(a:config, 'rounded', 1) ? 1 : 0,\n      \\ 'winblend': a:config['winblend'],\n      \\ 'highlight': a:config['highlight'],\n      \\ 'borderhighlight': a:config['borderhighlight'],\n      \\ }\n  let winid = coc#float#get_float_by_kind('outline-preview')\n  let result = coc#float#create_float_win(winid, s:outline_preview_bufnr, config)\n  if empty(result)\n    return v:null\n  endif\n  call setwinvar(result[0], 'kind', 'outline-preview')\n  let s:outline_preview_bufnr = result[1]\n  if !empty(filetype)\n    call win_execute(result[0], 'setfiletype '.filetype)\n  endif\n  return result[1]\nendfunction\n\nfunction! coc#ui#outline_close_preview() abort\n  let winid = coc#float#get_float_by_kind('outline-preview')\n  if winid\n    call coc#float#close(winid)\n  endif\nendfunction\n\n\" Ignore error from autocmd when file opened\nfunction! coc#ui#safe_open(cmd, file) abort\n  let bufname = fnameescape(a:file)\n  try\n    execute 'silent! '. a:cmd.' '.bufname\n  catch /.*/\n    if bufname('%') != bufname\n      throw 'Error on open '. v:exception\n    endif\n  endtry\nendfunction\n\n\" Use noa to setloclist, avoid BufWinEnter autocmd\nfunction! coc#ui#setloclist(nr, items, action, title) abort\n  let items = s:is_win32unix ? map(copy(a:items), 's:convert_qfitem(v:val)'): a:items\n  if a:action ==# ' '\n    let title = get(getloclist(a:nr, {'title': 1}), 'title', '')\n    let action = title ==# a:title ? 'r' : ' '\n    noa call setloclist(a:nr, [], action, {'title': a:title, 'items': items})\n  else\n    noa call setloclist(a:nr, [], a:action, {'title': a:title, 'items': items})\n  endif\nendfunction\n\nfunction! s:convert_qfitem(item) abort\n  let result = copy(a:item)\n  if has_key(result, 'filename')\n    let result['filename'] = coc#util#node_to_win32unix(result['filename'])\n  endif\n  return result\nendfunction\n\nfunction! coc#ui#get_mouse() abort\n  if get(g:, 'coc_node_env', '') ==# 'test'\n    return get(g:, 'mouse_position', [win_getid(), line('.'), col('.')])\n  endif\n  return [v:mouse_winid,v:mouse_lnum,v:mouse_col]\nendfunction\n\n\" viewId - identifier of tree view\n\" bufnr - bufnr tree view\n\" winid - winid of tree view\n\" bufname -  bufname of tree view\n\" command - split command\n\" optional options - bufhidden, canSelectMany, winfixwidth\nfunction! coc#ui#create_tree(opts) abort\n  let viewId = a:opts['viewId']\n  let bufname = a:opts['bufname']\n  let tabid = coc#compat#tabnr_id(tabpagenr())\n  let winid = s:get_tree_winid(a:opts)\n  let bufnr = a:opts['bufnr']\n  if !bufloaded(bufnr)\n    let bufnr = -1\n  endif\n  if winid != -1\n    call win_gotoid(winid)\n    if bufnr('%') == bufnr\n      return [bufnr, winid, tabid]\n    elseif bufnr != -1\n      execute 'silent keepalt buffer '.bufnr\n    else\n      execute 'silent keepalt edit +setl\\ buftype=nofile '.bufname\n      call s:set_tree_defaults(a:opts)\n    endif\n  else\n    \" need to split\n    let cmd = get(a:opts, 'command', 'belowright 30vs')\n    execute 'silent keepalt '.cmd.' +setl\\ buftype=nofile '.bufname\n    call s:set_tree_defaults(a:opts)\n    let winid = win_getid()\n  endif\n  let w:cocViewId = viewId\n  return [winbufnr(winid), winid, tabid]\nendfunction\n\n\" valid window id or -1\nfunction! s:get_tree_winid(opts) abort\n  let viewId = a:opts['viewId']\n  let winid = a:opts['winid']\n  if winid != -1 && coc#window#visible(winid)\n    return winid\n  endif\n  if winid != -1\n    call win_execute(winid, 'noa close!', 'silent!')\n  endif\n  return coc#window#find('cocViewId', viewId)\nendfunction\n\nfunction! s:set_tree_defaults(opts) abort\n  let bufhidden = get(a:opts, 'bufhidden', 'wipe')\n  let signcolumn = get(a:opts, 'canSelectMany', v:false) ? 'yes' : 'no'\n  let winfixwidth = get(a:opts, 'winfixwidth', v:false) ? ' winfixwidth' : ''\n  execute 'setl bufhidden='.bufhidden.' signcolumn='.signcolumn.winfixwidth\n  setl nolist nonumber norelativenumber foldcolumn=0\n  setl nocursorline nobuflisted wrap undolevels=-1 filetype=coctree nomodifiable noswapfile\nendfunction\n"
  },
  {
    "path": "autoload/coc/util.vim",
    "content": "scriptencoding utf-8\nlet s:root = expand('<sfile>:h:h:h')\nlet s:is_win = has('win32') || has('win64')\nlet s:is_vim = !has('nvim')\nlet s:vim_api_version = 38\nlet s:is_win32unix = has('win32unix')\nlet s:win32unix_prefix = ''\nlet s:win32unix_fix_home = 0\nif s:is_win32unix\n  let home = expand('$HOME')\n  if strpart(home, 0, 6) ==# '/home/'\n    let s:win32unix_fix_home = 1\n    let s:win32unix_prefix = '/'\n  elseif strpart(home, 0, 3) =~# '^/\\w/'\n    let s:win32unix_prefix = '/'\n  else\n    let s:win32unix_prefix = matchstr(home, '^\\/\\w\\+\\/')\n  endif\nendif\nlet s:win32unix_prefix_len = strlen(s:win32unix_prefix)\n\nfunction! coc#util#merge_winhl(curr, hls) abort\n  let highlightMap = {}\n  for parts in map(split(a:curr, ','), 'split(v:val, \":\")')\n    if len(parts) == 2\n      let highlightMap[parts[0]] = parts[1]\n    endif\n  endfor\n  for item in a:hls\n    let highlightMap[item[0]] = item[1]\n  endfor\n  return join(map(items(highlightMap), 'v:val[0].\":\".v:val[1]'), ',')\nendfunction\n\nfunction! coc#util#api_version() abort\n  return s:vim_api_version\nendfunction\n\nfunction! coc#util#semantic_hlgroups() abort\n  let res = split(execute('hi'), \"\\n\")\n  let filtered = filter(res, \"v:val =~# '^CocSem' && v:val !~# ' cleared$'\")\n  return map(filtered, \"matchstr(v:val,'\\\\v^CocSem\\\\w+')\")\nendfunction\n\n\" get cursor position\nfunction! coc#util#cursor()\n  return [line('.') - 1, coc#string#character_length(strpart(getline('.'), 0, col('.') - 1))]\nendfunction\n\nfunction! coc#util#change_info() abort\n  return {'lnum': line('.'), 'col': col('.'), 'line': getline('.'), 'changedtick': b:changedtick}\nendfunction\n\nfunction! coc#util#jumpTo(line, character) abort\n  echohl WarningMsg | echon 'coc#util#jumpTo is deprecated, use coc#cursor#move_to instead.' | echohl None\n  call coc#cursor#move_to(a:line, a:character)\nendfunction\n\nfunction! coc#util#root_patterns() abort\n  return coc#rpc#request('rootPatterns', [bufnr('%')])\nendfunction\n\nfunction! coc#util#get_config(key) abort\n  return coc#rpc#request('getConfig', [a:key])\nendfunction\n\nfunction! coc#util#open_terminal(opts) abort\n  return coc#ui#open_terminal(a:opts)\nendfunction\n\nfunction! coc#util#synname() abort\n  return synIDattr(synID(line('.'), col('.') - 1, 1), 'name')\nendfunction\n\nfunction! coc#util#version()\n  if s:is_vim\n    return string(v:versionlong)\n  endif\n  let c = execute('silent version')\n  let lines = split(matchstr(c,  'NVIM v\\zs[^\\n-]*'))\n  return lines[0]\nendfunction\n\nfunction! coc#util#check_refresh(bufnr)\n  if !bufloaded(a:bufnr)\n    return 0\n  endif\n  if getbufvar(a:bufnr, 'coc_diagnostic_disable', 0)\n    return 0\n  endif\n  return 1\nendfunction\n\nfunction! coc#util#diagnostic_info(bufnr, checkInsert) abort\n  let checked = coc#util#check_refresh(a:bufnr)\n  if !checked\n    return v:null\n  endif\n  if a:checkInsert && mode() =~# '^i'\n    return v:null\n  endif\n  let locationlist = ''\n  let winid = -1\n  for info in getwininfo()\n    if info['bufnr'] == a:bufnr\n      let winid = info['winid']\n      let locationlist = get(getloclist(winid, {'title': 1}), 'title', '')\n      break\n    endif\n  endfor\n  return {\n      \\ 'bufnr': bufnr('%'),\n      \\ 'winid': winid,\n      \\ 'lnum': winid == -1 ? -1 : coc#window#get_cursor(winid)[0],\n      \\ 'locationlist': locationlist\n      \\ }\nendfunction\n\nfunction! coc#util#job_command()\n  if (has_key(g:, 'coc_node_path'))\n    let node = expand(g:coc_node_path)\n  else\n    let node = $COC_NODE_PATH == '' ? 'node' : $COC_NODE_PATH\n  endif\n  if !executable(node)\n    echohl Error | echom '[coc.nvim] \"'.node.'\" is not executable, checkout https://nodejs.org/en/download/' | echohl None\n    return\n  endif\n  if !filereadable(s:root.'/build/index.js')\n    if isdirectory(s:root.'/src')\n      echohl Error | echom '[coc.nvim] build/index.js not found, please install dependencies and compile coc.nvim by: npm ci' | echohl None\n    else\n      echohl Error | echon '[coc.nvim] your coc.nvim is broken.' | echohl None\n    endif\n    return\n  endif\n\n  let default = ['--no-warnings']\n  return [node] + get(g:, 'coc_node_args', default) + [s:root.'/build/index.js']\nendfunction\n\nfunction! coc#util#open_file(cmd, file)\n  let file = coc#util#node_to_win32unix(a:file)\n  execute a:cmd .' '.fnameescape(fnamemodify(file, ':~:.'))\n  return bufnr('%')\nendfunction\n\nfunction! coc#util#jump(cmd, filepath, ...) abort\n  if a:cmd != 'pedit'\n    silent! normal! m'\n  endif\n  let path = coc#util#node_to_win32unix(a:filepath)\n  let file = fnamemodify(path, \":~:.\")\n  if a:cmd ==# 'pedit'\n    let extra = empty(get(a:, 1, [])) ? '' : '+'.(a:1[0] + 1)\n    exe 'pedit '.extra.' '.fnameescape(file)\n    return\n  elseif a:cmd ==# 'drop'\n    let dstbuf = bufadd(path)\n    if bufnr('%') != dstbuf\n      let binfo = getbufinfo(dstbuf)\n      if len(binfo) == 1 && empty(binfo[0].windows)\n        execute 'buffer '.dstbuf\n        let &buflisted = 1\n      else\n        let saved = &wildignore\n        set wildignore=\n        execute 'drop '.fnameescape(file)\n        execute 'set wildignore='.saved\n      endif\n    endif\n  elseif a:cmd ==# 'edit' && bufloaded(file)\n    exe 'b '.bufnr(file)\n  else\n    call s:safer_open(a:cmd, file)\n  endif\n  if !empty(get(a:, 1, []))\n    let line = getline(a:1[0] + 1)\n    let col = coc#string#byte_index(line, a:1[1]) + 1\n    call cursor(a:1[0] + 1, col)\n  endif\n  if &filetype ==# ''\n    filetype detect\n  endif\n  if s:is_vim\n    redraw\n  endif\nendfunction\n\nfunction! s:safer_open(cmd, file) abort\n  \" How to support :pedit and :drop?\n  let is_supported_cmd = index([\"edit\", \"split\", \"vsplit\", \"tabe\"], a:cmd) >= 0\n\n  \" Use special handling only for URI.\n  let looks_like_uri = match(a:file, \"^.*://\") >= 0\n\n  if looks_like_uri && is_supported_cmd && has('win32') && exists('*bufadd')\n    \" Workaround a bug for Win32 paths.\n    \"\n    \" reference:\n    \" - https://github.com/vim/vim/issues/541\n    \" - https://github.com/neoclide/coc-java/issues/82\n    \" - https://github.com/vim-jp/issues/issues/6\n    let buf = bufadd(a:file)\n    if a:cmd != 'edit'\n      \" Open split, tab, etc. by a:cmd.\n      execute a:cmd\n    endif\n    \" Set current buffer to the file\n    exe 'keepjumps buffer ' . buf\n  else\n    if a:cmd =~# 'drop'\n      if a:cmd ==# 'tab drop' && bufexists(a:file)\n        let bufnr = bufnr(a:file)\n        if bufnr == bufnr('%')\n          return\n        endif\n        let winid = coc#window#buf_winid(bufnr)\n        if winid != -1\n          call win_gotoid(winid)\n          return\n        endif\n      endif\n      let saved = &wildignore\n      set wildignore=\n      let l:old_page_idx = tabpagenr()\n      let l:old_page_cnt = tabpagenr('$')\n      execute 'noautocmd '.a:cmd.' '.fnameescape(a:file)\n      if tabpagenr('$') > l:old_page_cnt\n        doautocmd TabNew\n        doautocmd BufNew\n        doautocmd BufAdd\n      endif\n      let l:new_page_idx = tabpagenr()\n      if l:new_page_idx != l:old_page_idx\n        exec 'noautocmd tabnext '.l:old_page_idx\n        doautocmd TabLeave\n        doautocmd BufLeave\n        exec 'noautocmd tabnext '.l:new_page_idx\n      endif\n      doautocmd TabEnter\n      doautocmd BufReadPre\n      doautocmd BufReadPost\n      doautocmd BufEnter\n      if l:new_page_idx != l:old_page_idx\n        doautocmd BufWinEnter\n      endif\n      execute 'set wildignore='.saved\n    else\n      execute a:cmd.' '.fnameescape(a:file)\n    endif\n  endif\nendfunction\n\nfunction! coc#util#variables(bufnr) abort\n  let info = getbufinfo(a:bufnr)\n  let variables = empty(info) ? {} : copy(info[0]['variables'])\n  for key in keys(variables)\n    if key !~# '\\v^coc'\n      unlet variables[key]\n    endif\n  endfor\n  return variables\nendfunction\n\nfunction! coc#util#with_callback(method, args, cb)\n  function! s:Cb() closure\n    try\n      let res = call(a:method, a:args)\n      call a:cb(v:null, res)\n    catch /.*/\n      call a:cb(v:exception)\n    endtry\n  endfunction\n  let timeout = s:is_vim ? 10 : 0\n  call timer_start(timeout, {-> s:Cb() })\nendfunction\n\nfunction! coc#util#timer(method, args)\n  call timer_start(0, { -> s:Call(a:method, a:args)})\nendfunction\n\nfunction! s:Call(method, args)\n  try\n    call call(a:method, a:args)\n    \" don't redraw for command-line/prompt mode\n    if mode() !~# '^[cr]'\n      redraw\n    endif\n  catch /.*/\n    return 0\n  endtry\nendfunction\n\n\" Global vim information\nfunction! coc#util#vim_info()\n  return {\n        \\ 'root': coc#util#win32unix_to_node(s:root),\n        \\ 'apiversion': s:vim_api_version,\n        \\ 'mode': mode(),\n        \\ 'config': get(g:, 'coc_user_config', {}),\n        \\ 'floating': !s:is_vim && exists('*nvim_open_win') ? v:true : v:false,\n        \\ 'extensionRoot': coc#util#extension_root(),\n        \\ 'globalExtensions': get(g:, 'coc_global_extensions', []),\n        \\ 'lines': &lines,\n        \\ 'columns': &columns,\n        \\ 'cmdheight': &cmdheight,\n        \\ 'pid': coc#util#getpid(),\n        \\ 'filetypeMap': get(g:, 'coc_filetype_map', {}),\n        \\ 'version': coc#util#version(),\n        \\ 'pumevent': 1,\n        \\ 'dialog': !s:is_vim || has('popupwin') ? v:true : v:false,\n        \\ 'terminal': !s:is_vim || has('terminal') ? v:true : v:false,\n        \\ 'unixPrefix': s:win32unix_prefix,\n        \\ 'jumpAutocmd': coc#util#check_jump_autocmd(),\n        \\ 'isVim': s:is_vim ? v:true : v:false,\n        \\ 'isCygwin': s:is_win32unix ? v:true : v:false,\n        \\ 'isMacvim': has('gui_macvim') ? v:true : v:false,\n        \\ 'isiTerm': $TERM_PROGRAM ==# \"iTerm.app\",\n        \\ 'colorscheme': get(g:, 'colors_name', ''),\n        \\ 'workspaceFolders': get(g:, 'WorkspaceFolders', v:null),\n        \\ 'background': &background,\n        \\ 'runtimepath': join(coc#compat#list_runtime_paths(), ','),\n        \\ 'locationlist': get(g:,'coc_enable_locationlist', 1),\n        \\ 'progpath': v:progpath,\n        \\ 'guicursor': &guicursor,\n        \\ 'pumwidth': exists('&pumwidth') ? &pumwidth : 15,\n        \\ 'tabCount': tabpagenr('$'),\n        \\ 'vimCommands': get(g:, 'coc_vim_commands', []),\n        \\ 'virtualText': v:true,\n        \\ 'sign': exists('*sign_place') && exists('*sign_unplace'),\n        \\ 'ambiguousIsNarrow': &ambiwidth ==# 'single' ? v:true : v:false,\n        \\ 'textprop': has('textprop') ? v:true : v:false,\n        \\ 'semanticHighlights': coc#util#semantic_hlgroups()\n        \\}\nendfunction\n\nfunction! coc#util#check_jump_autocmd() abort\n  let autocmd_event = 'User'\n  let autocmd_group = 'CocJumpPlaceholder'\n  if exists('#' . autocmd_event . '#' . autocmd_group)\n    let content = execute('autocmd ' . autocmd_event . ' ' . autocmd_group)\n    if content =~# 'showSignatureHelp'\n      return v:true\n    endif\n  endif\n  return v:false\nendfunction\n\nfunction! coc#util#all_state()\n  return {\n        \\ 'bufnr': bufnr('%'),\n        \\ 'winid': win_getid(),\n        \\ 'bufnrs': map(getbufinfo({'bufloaded': 1}),'v:val[\"bufnr\"]'),\n        \\ 'winids': map(getwininfo(),'v:val[\"winid\"]'),\n        \\ }\nendfunction\n\nfunction! coc#util#install() abort\n  call coc#ui#open_terminal({\n        \\ 'cwd': s:root,\n        \\ 'cmd': 'npm ci',\n        \\ 'autoclose': 0,\n        \\ })\nendfunction\n\nfunction! coc#util#extension_root() abort\n  return coc#util#get_data_home().'/extensions'\nendfunction\n\nfunction! coc#util#update_extensions(...) abort\n  let async = get(a:, 1, 0)\n  if async\n    call coc#rpc#notify('updateExtensions', [])\n  else\n    call coc#rpc#request('updateExtensions', [v:true])\n  endif\nendfunction\n\nfunction! coc#util#install_extension(args) abort\n  let names = filter(copy(a:args), 'v:val !~# \"^-\"')\n  let isRequest = index(a:args, '-sync') != -1\n  if isRequest\n    call coc#rpc#request('installExtensions', names)\n  else\n    call coc#rpc#notify('installExtensions', names)\n  endif\nendfunction\n\nfunction! coc#util#do_autocmd(name) abort\n  if exists('#User#'.a:name)\n    exe 'doautocmd <nomodeline> User '.a:name\n  endif\nendfunction\n\nfunction! coc#util#refactor_foldlevel(lnum) abort\n  if a:lnum <= 2 | return 0 | endif\n  let line = getline(a:lnum)\n  if line =~# '^\\%u3000\\s*$' | return 0 | endif\n  return 1\nendfunction\n\nfunction! coc#util#refactor_fold_text(lnum) abort\n  let range = ''\n  let info = get(b:line_infos, a:lnum, [])\n  if !empty(info)\n    let range = info[0].':'.info[1]\n  endif\n  return trim(getline(a:lnum)[3:]).' '.range\nendfunction\n\n\" get tabsize & expandtab option\nfunction! coc#util#get_format_opts(bufnr) abort\n  let bufnr = a:bufnr && bufloaded(a:bufnr) ? a:bufnr : bufnr('%')\n  let tabsize = getbufvar(bufnr, '&shiftwidth')\n  if tabsize == 0\n    let tabsize = getbufvar(bufnr, '&tabstop')\n  endif\n  return {\n      \\ 'tabsize': tabsize,\n      \\ 'expandtab': getbufvar(bufnr, '&expandtab'),\n      \\ 'insertFinalNewline': getbufvar(bufnr, '&eol'),\n      \\ 'trimTrailingWhitespace': getbufvar(bufnr, 'coc_trim_trailing_whitespace', 0),\n      \\ 'trimFinalNewlines': getbufvar(bufnr, 'coc_trim_final_newlines', 0)\n      \\ }\nendfunction\n\nfunction! coc#util#get_editoroption(winid) abort\n  let info = get(getwininfo(a:winid), 0, v:null)\n  if empty(info) || coc#window#is_float(a:winid)\n    return v:null\n  endif\n  let bufnr = info['bufnr']\n  let buftype = getbufvar(bufnr, '&buftype')\n  \" avoid window for other purpose.\n  if buftype !=# '' && buftype !=# 'acwrite'\n    return v:null\n  endif\n  return {\n        \\ 'bufnr': bufnr,\n        \\ 'winid': a:winid,\n        \\ 'tabpageid': coc#compat#tabnr_id(info['tabnr']),\n        \\ 'winnr': winnr(),\n        \\ 'visibleRanges': s:visible_ranges(a:winid),\n        \\ 'formatOptions': coc#util#get_format_opts(bufnr),\n        \\ }\nendfunction\n\nfunction! coc#util#get_loaded_bufs() abort\n  return map(getbufinfo({'bufloaded': 1}),'v:val[\"bufnr\"]')\nendfunction\n\nfunction! coc#util#editor_infos() abort\n  let result = []\n  for info in getwininfo()\n    if !coc#window#is_float(info['winid'])\n      let bufnr = info['bufnr']\n      let buftype = getbufvar(bufnr, '&buftype')\n      if buftype !=# '' && buftype !=# 'acwrite'\n        continue\n      endif\n      call add(result, {\n          \\ 'winid': info['winid'],\n          \\ 'bufnr': bufnr,\n          \\ 'tabid': coc#compat#tabnr_id(info['tabnr']),\n          \\ 'fullpath': coc#util#get_fullpath(bufnr),\n          \\ })\n    endif\n  endfor\n  return result\nendfunction\n\nfunction! coc#util#getpid()\n  if !s:is_win32unix\n    return getpid()\n  endif\n  let cmd = 'cat /proc/' . getpid() . '/winpid'\n  return substitute(system(cmd), '\\v\\n', '', 'gi')\nendfunction\n\nfunction! coc#util#get_bufoptions(bufnr, max) abort\n  if !bufloaded(a:bufnr)\n    return v:null\n  endif\n  let bufname = bufname(a:bufnr)\n  let buftype = getbufvar(a:bufnr, '&buftype')\n  let commandline = get(getbufinfo(a:bufnr)[0], 'command', 0) || bufname(a:bufnr) == '[Command Line]'\n  let size = coc#util#bufsize(a:bufnr)\n  let lines = v:null\n  if getbufvar(a:bufnr, 'coc_enabled', 1)\n        \\ && (buftype == '' || buftype == 'acwrite' || getbufvar(a:bufnr, 'coc_force_attach', 0))\n        \\ && size != -2\n        \\ && size < a:max\n    let lines = getbufline(a:bufnr, 1, '$')\n  endif\n  return {\n        \\ 'bufnr': a:bufnr,\n        \\ 'commandline': commandline,\n        \\ 'size': size,\n        \\ 'lines': lines,\n        \\ 'winid': bufwinid(a:bufnr),\n        \\ 'winids': win_findbuf(a:bufnr),\n        \\ 'bufname': bufname,\n        \\ 'buftype': buftype,\n        \\ 'previewwindow': v:false,\n        \\ 'eol': getbufvar(a:bufnr, '&eol'),\n        \\ 'variables': coc#util#variables(a:bufnr),\n        \\ 'filetype': getbufvar(a:bufnr, '&filetype'),\n        \\ 'lisp': getbufvar(a:bufnr, '&lisp'),\n        \\ 'iskeyword': getbufvar(a:bufnr, '&iskeyword'),\n        \\ 'changedtick': getbufvar(a:bufnr, 'changedtick'),\n        \\ 'fullpath': coc#util#get_fullpath(a:bufnr)\n        \\}\nendfunction\n\n\" Get fullpath for NodeJs of current buffer or bufnr\nfunction! coc#util#get_fullpath(...) abort\n  let nr = a:0 == 0 ? bufnr('%') : a:1\n  if !bufloaded(nr)\n    return ''\n  endif\n  if s:is_vim && getbufvar(nr, '&buftype') ==# 'terminal'\n    let job = term_getjob(nr)\n    let pid = job_info(job)->get('process', 0)\n    let cwd = fnamemodify(getcwd(), ':~')\n    return 'term://' . cwd . '//' . pid . ':' . substitute(bufname(nr), '^!', '', '')\n  endif\n  let name = bufname(nr)\n  return empty(name) ? '' : coc#util#win32unix_to_node(fnamemodify(name, ':p'))\nendfunction\n\nfunction! coc#util#bufsize(bufnr) abort\n  if bufnr('%') == a:bufnr\n    return line2byte(line(\"$\") + 1)\n  endif\n  let bufname = bufname(a:bufnr)\n  if !getbufvar(a:bufnr, '&modified') && filereadable(bufname)\n    return getfsize(bufname)\n  endif\n  return strlen(join(getbufline(a:bufnr, 1, '$'), '\\n'))\nendfunction\n\nfunction! coc#util#get_config_home(...)\n  let skip_convert = get(a:, 1, 0)\n  let dir = ''\n  if !empty(get(g:, 'coc_config_home', ''))\n    let dir = resolve(expand(g:coc_config_home))\n  else\n    if exists('$VIMCONFIG')\n      let dir =  resolve($VIMCONFIG)\n    else\n      if s:is_vim\n        if s:is_win || s:is_win32unix\n          let dir = s:resolve($HOME, \"vimfiles\")\n        else\n          if isdirectory(s:resolve($HOME, '.vim'))\n            let dir = s:resolve($HOME, '.vim')\n          else\n            if exists('$XDG_CONFIG_HOME') && isdirectory(resolve($XDG_CONFIG_HOME))\n              let dir = s:resolve($XDG_CONFIG_HOME, 'vim')\n            else\n              let dir = s:resolve($HOME, '.config/vim')\n            endif\n          endif\n        endif\n      else\n        let appname = empty($NVIM_APPNAME) ? 'nvim' : $NVIM_APPNAME\n        if exists('$XDG_CONFIG_HOME')\n          let dir = s:resolve($XDG_CONFIG_HOME, appname)\n        else\n          if s:is_win\n            let dir = s:resolve($HOME, 'AppData/Local/'.appname)\n          else\n            let dir = s:resolve($HOME, '.config/'.appname)\n          endif\n        endif\n      endif\n    endif\n  endif\n  return skip_convert ? coc#util#fix_home(dir) : coc#util#win32unix_to_node(dir)\nendfunction\n\nfunction! coc#util#get_data_home()\n  if get(g:, 'coc_node_env', '') ==# 'test' && !empty($COC_DATA_HOME)\n    return coc#util#win32unix_to_node($COC_DATA_HOME)\n  endif\n  if !empty(get(g:, 'coc_data_home', ''))\n    let dir = resolve(expand(g:coc_data_home))\n  else\n    if exists('$XDG_CONFIG_HOME') && isdirectory(resolve($XDG_CONFIG_HOME))\n      let dir = s:resolve($XDG_CONFIG_HOME, 'coc')\n    else\n      if s:is_win || s:is_win32unix\n        let dir = resolve(expand('~/AppData/Local/coc'))\n      else\n        let dir = resolve(expand('~/.config/coc'))\n      endif\n    endif\n  endif\n  let dir = coc#util#fix_home(dir)\n  if !isdirectory(dir)\n    call coc#notify#create(['creating coc.nvim data directory: '.dir], {\n          \\ 'borderhighlight': 'CocInfoSign',\n          \\ 'timeout': 5000,\n          \\ 'kind': 'info',\n          \\ })\n    call mkdir(dir, \"p\", 0755)\n  endif\n  return coc#util#win32unix_to_node(dir)\nendfunction\n\n\" Get the fixed home dir on mysys2, use user's home\n\" /home/YourName => /c/User/YourName\nfunction! coc#util#fix_home(filepath) abort\n  if s:win32unix_fix_home && strpart(a:filepath, 0, 6) ==# '/home/'\n    return substitute(a:filepath, '^/home', '/c/User', '')\n  endif\n  return a:filepath\nendfunction\n\n\" /cygdrive/c/Users/YourName\n\" /mnt/c/Users/YourName\n\" /c/Users/YourName\nfunction! coc#util#win32unix_to_node(filepath) abort\n  if s:is_win32unix\n    let fullpath = coc#util#fix_home(a:filepath)\n    if strpart(fullpath, 0, s:win32unix_prefix_len) ==# s:win32unix_prefix\n      let part = strpart(fullpath, s:win32unix_prefix_len)\n      return toupper(part[0]) . ':' . substitute(part[1:], '/', '\\', 'g')\n    endif\n  endif\n  return a:filepath\nendfunction\n\nfunction! coc#util#node_to_win32unix(filepath) abort\n  if s:is_win32unix && a:filepath =~# '^\\w:\\\\'\n    let part = tolower(a:filepath[0]) . a:filepath[2:]\n    return s:win32unix_prefix . substitute(part, '\\\\', '/', 'g')\n  endif\n  return a:filepath\nendfunction\n\nfunction! coc#util#get_complete_option()\n  let pos = getcurpos()\n  let line = getline(pos[1])\n  let input = matchstr(strpart(line, 0, pos[2] - 1), '\\k*$')\n  let col = pos[2] - strlen(input)\n  let position = {\n      \\ 'line': line('.')-1,\n      \\ 'character': coc#string#character_length(strpart(getline('.'), 0, col('.') - 1))\n      \\ }\n  let word = matchstr(strpart(line, col - 1), '^\\k\\+')\n  let followWord = len(word) > 0 ? strcharpart(word, strchars(input)) : ''\n  return {\n        \\ 'word': word,\n        \\ 'followWord': followWord,\n        \\ 'position': position,\n        \\ 'input': empty(input) ? '' : input,\n        \\ 'line': line,\n        \\ 'filetype': &filetype,\n        \\ 'filepath': expand('%:p'),\n        \\ 'bufnr': bufnr('%'),\n        \\ 'linenr': pos[1],\n        \\ 'colnr' : pos[2],\n        \\ 'col': col - 1,\n        \\ 'changedtick': b:changedtick,\n        \\}\nendfunction\n\nfunction! coc#util#get_changedtick(bufnr) abort\n  if s:is_vim && bufloaded(a:bufnr)\n    call listener_flush(a:bufnr)\n  endif\n  return getbufvar(a:bufnr, 'changedtick')\nendfunction\n\n\" Get the valid position from line, character of current buffer\nfunction! coc#util#valid_position(line, character) abort\n  let total = line('$') - 1\n  if a:line > total\n    return [total, 0]\n  endif\n  let max = max([0, coc#string#character_length(getline(a:line + 1)) - (mode() ==# 'n' ? 1 : 0)])\n  return a:character > max ? [a:line, max] : [a:line, a:character]\nendfunction\n\nfunction! s:visible_ranges(winid) abort\n  let info = getwininfo(a:winid)[0]\n  let res = []\n  if !has_key(info, 'topline') || !has_key(info, 'botline')\n    return res\n  endif\n  let begin = 0\n  let curr = info['topline']\n  let max = info['botline']\n  if win_getid() != a:winid\n    return [[curr, max]]\n  endif\n  while curr <= max\n    let closedend = foldclosedend(curr)\n    if closedend == -1\n      let begin = begin == 0 ? curr : begin\n      if curr == max\n        call add(res, [begin, curr])\n      endif\n      let curr = curr + 1\n    else\n      if begin != 0\n        call add(res, [begin, curr - 1])\n        let begin = closedend + 1\n      endif\n      let curr = closedend + 1\n    endif\n  endwhile\n  return res\nendfunction\n\n\" for avoid bug with vim&neovim https://github.com/neoclide/coc.nvim/discussions/5287\nfunction! s:resolve(path, part) abort\n  return resolve(a:path . '/' . a:part)\nendfunction\n"
  },
  {
    "path": "autoload/coc/vim9.vim",
    "content": "vim9script\nscriptencoding utf-8\n\nconst default_priority = 1024\nconst priorities = {\n  'CocListSearch': 2048,\n  'CocSearch': 2048,\n}\nconst diagnostic_hlgroups = ['CocUnusedHighlight', 'CocDeprecatedHighlight', 'CocHintHighlight', 'CocInfoHighlight', 'CocWarningHighlight', 'CocErrorHighlight']\nconst maxCount = get(g:, 'coc_highlight_maximum_count', 500)\nconst maxTimePerBatchMs = 16\nvar maxEditCount = get(g:, 'coc_edits_maximum_count', 200)\nvar saved_event_ignore: string = ''\n\ndef Is_timeout(start_time: list<any>, max: number): bool\n  return (start_time->reltime()->reltimefloat()) * 1000 > max\nenddef\n\n# Some hlGroups have higher priorities.\ndef Get_priority(hlGroup: string, priority: any): number\n  if has_key(priorities, hlGroup)\n    return priorities[hlGroup]\n  endif\n  if type(priority) != v:t_number\n    return default_priority\n  endif\n  const idx = index(diagnostic_hlgroups, hlGroup)\n  if idx != -1\n    return priority + idx\n  endif\n  return priority\nenddef\n\ndef Convert_item(item: any): list<any>\n  if type(item) == v:t_list\n    return item\n  endif\n  # hlGroup, lnum, colStart, colEnd, combine, start_incl, end_incl\n  const combine = has_key(priorities, item.hlGroup) ? 1 : get(item, 'combine', 0)\n  const start_incl = get(item, 'start_incl', 0)\n  const end_incl = get(item, 'end_incl', 0)\n  return [item.hlGroup, item.lnum, item.colStart, item.colEnd, combine, start_incl, end_incl]\nenddef\n\n# Check if lines synchronized as expected\nexport def Check_sha256(bufnr: number, expected: string): bool\n  if !exists('*sha256')\n    return true\n  endif\n  return getbufline(bufnr, 1, '$')->join(\"\\n\")->sha256() ==# expected\nenddef\n\ndef Create_namespace(key: any): number\n  if type(key) == v:t_number\n    if key == -1\n      return coc#api#Create_namespace('anonymous')\n    endif\n    return key\n  endif\n  if type(key) != v:t_string\n    throw 'Expect number or string for namespace key.'\n  endif\n  return coc#api#Create_namespace($'coc-{key}')\nenddef\n\nexport def Clear_highlights(id: number, key: any, start_line: number = 0, end_line: number = -1): void\n  const buf = id == 0 ? bufnr('%') : id\n  if bufloaded(buf)\n    const ns = Create_namespace(key)\n    coc#api#Buf_clear_namespace(buf, ns, start_line, end_line)\n  endif\nenddef\n\nexport def Add_highlight(id: number, key: any, hl_group: string, line: number, col_start: number, col_end: number, opts: dict<any> = {}): void\n  const buf = id == 0 ? bufnr('%') : id\n  if bufloaded(buf)\n    const ns = Create_namespace(key)\n    coc#api#Buf_add_highlight1(buf, ns, hl_group, line, col_start, col_end, opts)\n  endif\nenddef\n\nexport def Clear_all(): void\n  const namespaces = coc#api#Get_namespaces()\n  const bufnrs = getbufinfo({'bufloaded': 1})->mapnew((_, o: dict<any>): number => o.bufnr)\n  for ns in values(namespaces)\n    for bufnr in bufnrs\n      coc#api#Buf_clear_namespace(bufnr, ns, 0, -1)\n    endfor\n  endfor\nenddef\n\n# From `coc#highlight#set(`:\n#   type HighlightItem = [hlGroup, lnum, colStart, colEnd, combine?, start_incl?, end_incl?]\n# From `src/core/highlights.ts`:\n#   type HighlightItemDef = [string, number, number, number, number?, number?, number?]\n# type HighlightItem = list<any>\n# type HighlightItemList = list<HighlightItem>\n# NOTE: Can't use type on vim9.0.0438\nexport def Set_highlights(bufnr: number, key: any, highlights: list<any>, priority: any = null): void\n  if bufloaded(bufnr)\n    const changedtick = getbufvar(bufnr, 'changedtick', 0)\n    const ns = Create_namespace(key)\n    Add_highlights_timer(bufnr, ns, highlights, priority, changedtick)\n  endif\nenddef\n\ndef Add_highlights_timer(bufnr: number, ns: number, highlights: list<any>, priority: any, changedtick: number): void\n  if !bufloaded(bufnr) || getbufvar(bufnr, 'changedtick', 0) != changedtick\n    return\n  endif\n  const total = len(highlights)\n  const start_time = reltime()\n  var end_idx = 0\n  for i in range(0, total - 1, maxCount)\n    end_idx = i + maxCount - 1\n    const hls = highlights[i : end_idx]\n    Add_highlights(bufnr, ns, hls, priority)\n    if Is_timeout(start_time, maxTimePerBatchMs)\n      break\n    endif\n  endfor\n  if end_idx < total - 1\n    const next = highlights[end_idx + 1 : ]\n    timer_start(10,  (_) => Add_highlights_timer(bufnr, ns, next, priority, changedtick))\n  endif\nenddef\n\ndef Add_highlights(bufnr: number, ns: number, highlights: list<any>, priority: any): void\n  final types = coc#api#GetNamespaceTypes(ns)->copy()\n  for highlightItem in highlights\n    const item = Convert_item(highlightItem)\n    var [ hlGroup: string, lnum: number, colStart: number, colEnd: number; _ ] = item\n    if colEnd == -1\n      colEnd = getbufline(bufnr, lnum + 1)->get(0, '')->strlen()\n    endif\n    const type: string = $'{hlGroup}_{ns}'\n    if index(types, type) == -1\n      const opts: dict<any> = {\n        'priority': Get_priority(hlGroup, priority),\n        'hl_mode': get(item, 4, 1) ? 'combine' : 'override',\n        'start_incl': get(item, 5, 0),\n        'end_incl': get(item, 6, 0),\n      }\n      coc#api#CreateType(ns, hlGroup, opts)\n      add(types, type)\n    endif\n    const propId: number = coc#api#GeneratePropId(bufnr)\n    try\n      prop_add(lnum + 1, colStart + 1, {'bufnr': bufnr, 'type': type, 'id': propId, 'end_col': colEnd + 1})\n    catch /^Vim\\%((\\a\\+)\\)\\=:\\(E967\\|E964\\)/\n      # ignore 967\n    endtry\n  endfor\nenddef\n\nexport def Update_highlights(id: number, key: string, highlights: list<any>, start: number = 0, end: number = -1, priority: any = null, changedtick: any = null): void\n  const bufnr = id == 0 ? bufnr('%') : id\n  if bufloaded(bufnr)\n    const tick = getbufvar(bufnr, 'changedtick')\n    if type(changedtick) == v:t_number && changedtick != tick\n      return\n    endif\n    const ns = Create_namespace(key)\n    coc#api#Buf_clear_namespace(bufnr, ns, start, end)\n    Add_highlights_timer(bufnr, ns, highlights, priority, tick)\n  endif\nenddef\n\n# key could be -1 or string or number\nexport def Buffer_update(bufnr: number, key: any, highlights: list<any>, priority: any = null, changedtick: any = null): void\n  if bufloaded(bufnr)\n    const ns = Create_namespace(key)\n    coc#api#Buf_clear_namespace(bufnr, ns, 0, -1)\n    if empty(highlights)\n      return\n    endif\n    const tick = getbufvar(bufnr, 'changedtick', 0)\n    if type(changedtick) == v:t_number && tick != changedtick\n      return\n    endif\n    # highlight current region first\n    const winid = bufwinid(bufnr)\n    if winid == -1\n      Add_highlights_timer(bufnr, ns, highlights, priority, tick)\n    else\n      const info = getwininfo(winid)->get(0, {})\n      const topline = info.topline\n      const botline = info.botline\n      if topline <= 5\n        Add_highlights_timer(bufnr, ns, highlights, priority, tick)\n      else\n        final curr_hls = []\n        final other_hls = []\n        for hl in highlights\n          const lnum = type(hl) == v:t_list ? hl[1] + 1 : hl.lnum + 1\n          if lnum >= topline && lnum <= botline\n            add(curr_hls, hl)\n          else\n            add(other_hls, hl)\n          endif\n        endfor\n        const hls = extend(curr_hls, other_hls)\n        Add_highlights_timer(bufnr, ns, hls, priority, tick)\n      endif\n    endif\n  endif\nenddef\n\nexport def Highlight_ranges(id: number, key: any, hlGroup: string, ranges: list<any>, opts: dict<any> = {}): void\n  const bufnr = id == 0 ? bufnr('%') : id\n  if bufloaded(bufnr)\n    final highlights: list<any> = []\n    if get(opts, 'clear', false) == true\n      const ns = Create_namespace(key)\n      coc#api#Buf_clear_namespace(bufnr, ns, 0, -1)\n    endif\n    for range in ranges\n      const start_pos = range.start\n      const end_pos = range.end\n      const lines = getbufline(bufnr, start_pos.line + 1, end_pos.line + 1)\n      for index in range(start_pos.line, end_pos.line)\n        const line = get(lines, index - start_pos.line, '')\n        if len(line) == 0\n          continue\n        endif\n        const colStart = index == start_pos.line ? coc#text#Byte_index(line, start_pos.character) : 0\n        const colEnd = index == end_pos.line ? coc#text#Byte_index(line, end_pos.character) : strlen(line)\n        if colStart >= colEnd\n          continue\n        endif\n        const combine = get(opts, 'combine', false) ? 1 : 0\n        const start_incl = get(opts, 'start_incl', false) ? 1 : 0\n        const end_incl = get(opts, 'end_incl', false) ? 1 : 0\n        add(highlights, [hlGroup, index, colStart, colEnd, combine, start_incl, end_incl])\n      endfor\n    endfor\n    const priority = has_key(opts, 'priority') ? opts.priority : 4096\n    Set_highlights(bufnr, key, highlights, priority)\n  endif\nenddef\n\nexport def Match_ranges(id: number, buf: number, ranges: list<any>, hlGroup: string, priority: any = 99): list<number>\n  const winid = id == 0 ? win_getid() : id\n  const bufnr = buf == 0 ? winbufnr(winid) : buf\n  if empty(getwininfo(winid)) || (buf != 0 && winbufnr(winid) != buf)\n    return []\n  endif\n  final ids = []\n  final pos = []\n  for range in ranges\n    const start_pos = range.start\n    const end_pos = range.end\n    const lines = getbufline(bufnr, start_pos.line + 1, end_pos.line + 1)\n    for index in range(start_pos.line, end_pos.line)\n      const line = get(lines, index - start_pos.line, '')\n      if len(line) == 0\n        continue\n      endif\n      const colStart = index == start_pos.line ? coc#text#Byte_index(line, start_pos.character) : 0\n      const colEnd = index == end_pos.line ? coc#text#Byte_index(line, end_pos.character) : strlen(line)\n      if colStart >= colEnd\n        continue\n      endif\n      add(pos, [index + 1, colStart + 1, colEnd - colStart])\n    endfor\n  endfor\n  const count = len(pos)\n  if count > 0\n    const pr = type(priority) == v:t_number ? priority : 99\n    const opts = {'window': winid}\n    if count < 9 || has('patch-9.0.0622')\n      ids->add(matchaddpos(hlGroup, pos, pr, -1, opts))\n    else\n      # limit to 8 each time\n      for i in range(0, count - 1, 8)\n        const end = min([i + 8, count]) - 1\n        ids->add(matchaddpos(hlGroup, pos[i : end], pr, -1, opts))\n      endfor\n    endif\n  endif\n  return ids\nenddef\n\n# key could be string or number, use -1 for all highlights.\nexport def Get_highlights(bufnr: number, key: any, start: number, end: number): list<any>\n  if !bufloaded(bufnr)\n    return []\n  endif\n  const ns = type(key) == v:t_number ? key : Create_namespace(key)\n  const types: list<string> = coc#api#GetNamespaceTypes(ns)\n  if empty(types)\n    return []\n  endif\n  final res: list<any> = []\n  const endLnum: number = end == -1 ? -1 : end + 1\n  for prop in prop_list(start + 1, {'bufnr': bufnr, 'types': types, 'end_lnum': endLnum})\n    if prop.start == 0 || prop.end == 0\n      # multi line textprop are not supported, simply ignore it\n      continue\n    endif\n    const startCol: number = prop.col - 1\n    const endCol: number = startCol + prop.length\n    const hl = prop_type_get(prop.type)->get('highlight', '')\n    add(res, [ hl, prop.lnum - 1, startCol, endCol, prop.id ])\n  endfor\n  return res\nenddef\n\nexport def Del_markers(bufnr: number, key: any, ids: list<number>): void\n  if bufloaded(bufnr)\n    for id in ids\n      prop_remove({'bufnr': bufnr, 'id': id})\n    endfor\n  endif\nenddef\n\n# Can't use strdisplaywidth as it doesn't support bufnr\ndef Calc_padding_size(bufnr: number, indent: string): number\n  const tabSize: number = getbufvar(bufnr, '&shiftwidth') ?? getbufvar(bufnr, '&tabstop', 8)\n  var padding: number = 0\n  for character in indent\n    if character == \"\\t\"\n      padding += tabSize - (padding % tabSize)\n    else\n      padding += 1\n    endif\n  endfor\n  return padding\nenddef\n\ndef Add_vtext_item(bufnr: number, ns: number, opts: dict<any>, pre: string, priority: number): void\n  var propColumn: number = get(opts, 'col', 0)\n  const align = get(opts, 'text_align', 'after')\n  const line = opts.line\n  const blocks = opts.blocks\n  var blockList: list<list<string>> = blocks\n  const virt_lines = get(opts, 'virt_lines', [])\n  var isAboveBelow = align ==# 'above' || align ==# 'below'\n  if !empty(blocks) && isAboveBelow\n    # only first highlight can be used\n    const highlightGroup: string = blocks[0][1]\n    const text: string = blocks->mapnew((_, block: list<string>): string => block[0])->join('')\n    blockList = [[text, highlightGroup]]\n    propColumn = 0\n  endif\n  var first: bool = true\n  final base: dict<any> = { 'priority': priority }\n  if propColumn == 0 && align != 'overlay'\n    base.text_align = align\n  endif\n  if has_key(opts, 'text_wrap')\n    base.text_wrap = opts.text_wrap\n  endif\n  var before: string = ''\n  for blockItem in blockList\n    const text = empty(before) ? blockItem[0] : $'{before}{blockItem[0]}'\n    const highlightGroup: string = get(blockItem, 1, '')\n    if empty(highlightGroup)\n      # should be spaces\n      before = text\n      continue\n    endif\n    before = ''\n    const type: string = coc#api#CreateType(ns, highlightGroup, opts)\n    final propOpts: dict<any> = extend({ 'text': text, 'type': type, 'bufnr': bufnr }, base)\n    if first && propColumn == 0\n      # add a whitespace, same as neovim.\n      if align ==# 'after'\n        propOpts.text_padding_left = 1\n      elseif !empty(pre) && isAboveBelow\n        propOpts.text_padding_left = Calc_padding_size(bufnr, pre)\n      endif\n    endif\n    prop_add(line + 1, propColumn, propOpts)\n    first = false\n  endfor\n  for item_list in virt_lines\n    for [text, highlightGroup] in item_list\n      const type: string = coc#api#CreateType(ns, highlightGroup, opts)\n      final propOpts: dict<any> = { 'text': text, 'type': type, 'bufnr': bufnr, 'text_align': 'below'}\n      prop_add(line + 1, 0, propOpts)\n    endfor\n  endfor\nenddef\n\nexport def Add_vtext(bufnr: number, ns: number, line: number, blocks: list<list<string>>, opts: dict<any>): void\n  var propIndent: string = ''\n  if get(opts, 'indent', false)\n    propIndent = matchstr(get(getbufline(bufnr, line + 1), 0, ''), '^\\s\\+')\n  endif\n  final conf = {'line': line, 'blocks': blocks}\n  Add_vtext_item(bufnr, ns, extend(conf, opts), propIndent, get(opts, 'priority', 0))\nenddef\n\ndef Add_vtext_items(bufnr: number, ns: number, items: list<any>, indent: bool, priority: number): void\n  const length = len(items)\n  if length > 0\n    var buflines: list<string> = []\n    var start = 0\n    var propIndent: string = ''\n    if indent\n      start = items[0].line\n      const endLine = items[length - 1].line\n      buflines = getbufline(bufnr, start + 1, endLine + 1)\n    endif\n    for item in items\n      if indent\n        propIndent = matchstr(buflines[item.line - start], '^\\s\\+')\n      endif\n      Add_vtext_item(bufnr, ns, item, propIndent, priority)\n    endfor\n  endif\nenddef\n\ndef Add_vtexts_timer(bufnr: number, ns: number, items: list<any>, indent: bool,\n            priority: number, changedtick: number): void\n  if !bufloaded(bufnr) || getbufvar(bufnr, 'changedtick', 0) != changedtick\n    return\n  endif\n  if len(items) > maxCount\n    const hls = items[ : maxCount - 1]\n    const next = items[maxCount : ]\n    Add_vtext_items(bufnr, ns, hls, indent, priority)\n    timer_start(10,  (_) => Add_vtexts_timer(bufnr, ns, next, indent, priority, changedtick))\n  else\n    Add_vtext_items(bufnr, ns, items, indent, priority)\n  endif\nenddef\n\nexport def Set_virtual_texts(bufnr: number, ns: number, items: list<any>, indent: bool, priority: number): void\n  if bufloaded(bufnr)\n    const changedtick = getbufvar(bufnr, 'changedtick', 0)\n    Add_vtexts_timer(bufnr, ns, items, indent, priority, changedtick)\n  endif\nenddef\n\n# Apply many text changes while preserve text props can be slow,\ndef Apply_changes(bufnr: number, changes: list<any>): void\n  const start_time = reltime()\n  const total = len(changes)\n  var timeout: bool = false\n  var i = total - 1\n  while i >= 0\n    const item = changes[i]\n    # item is null for some unknown reason\n    if !empty(item)\n      coc#api#SetBufferText(bufnr, item[1], item[2], item[3], item[4], item[0])\n    endif\n    i -= 1\n  endwhile\n  const duration = (start_time->reltime()->reltimefloat()) * 1000\n  if duration > 200\n    maxEditCount = maxEditCount / 2\n    coc#api#EchoHl($'Text edits cost {float2nr(duration)}ms, consider configure g:coc_edits_maximum_count < {total}', 'WarningMsg')\n  endif\nenddef\n\n# Replace text before cursor at current line, insert should not includes line break.\n# 0 based start col\nexport def Set_lines(bufnr: number, changedtick: number, original: list<string>, replacement: list<string>,\n    start: number, end: number, changes: any, cursor: any, col: any, linecount: number): void\n  if bufloaded(bufnr)\n    const current = bufnr == bufnr('%')\n    const view = current ? winsaveview() : null\n    var start_row: number = start\n    var end_row: number = end\n    var replace = copy(replacement)\n    var finished: bool = false\n    var change_list = copy(changes)\n    var delta: number = 0\n    if current && type(col) == v:t_number\n      delta = col('.') - col\n    endif\n    if changedtick != getbufvar(bufnr, 'changedtick') && end_row > start_row\n      const line_delta = bufnr->getbufinfo()->get(0).linecount - linecount\n      if line_delta == 0\n        # Check current line change first\n        const curr_lines = getbufline(bufnr, start_row + 1, end_row)\n        const pos = getpos('.')\n        const row = current ? pos[1] - start_row - 1 : -1\n        for idx in range(0, len(curr_lines) - 1)\n          var oldStr = get(original, idx, '')\n          var newStr = get(curr_lines, idx, '')\n          var replaceStr = get(replace, idx, null)\n          var colIdx = idx == row ? pos[2] - 1 : -1\n          if oldStr !=# newStr && replaceStr != null\n            if replaceStr ==# oldStr\n              replaceStr = newStr\n            else\n              replaceStr = coc#text#DiffApply(oldStr, newStr, replaceStr, colIdx)\n            endif\n            if replaceStr != null\n              replace[idx] = replaceStr\n            endif\n            change_list = []\n          endif\n        endfor\n      else\n        # Check if change lines before or after\n        # Consider changed before first\n        if coc#text#LinesEqual(replace, getbufline(bufnr, start_row + 1 + line_delta, end_row + line_delta))\n          start_row += line_delta\n          end_row += line_delta\n          change_list = []\n        elseif !coc#text#LinesEqual(replace, getbufline(bufnr, start_row + 1, end_row))\n          return\n        endif\n      endif\n    endif\n    if !empty(change_list) && len(change_list) <= maxEditCount\n      Apply_changes(bufnr, change_list)\n    else\n      coc#api#SetBufferLines(bufnr, start_row + 1, end_row, replace)\n    endif\n    if current\n      winrestview(view)\n    endif\n    coc#api#OnTextChange(bufnr)\n    if !empty(cursor) && current\n      cursor(cursor[0], cursor[1] + delta)\n    endif\n  endif\nenddef\n\ndefcompile\n"
  },
  {
    "path": "autoload/coc/vtext.vim",
    "content": "let s:is_vim = !has('nvim')\n\n\" Add multiple virtual texts, use timer when needed.\n\" bufnr - The buffer number\n\" ns - Id created by Nvim_create_namespace()\n\" items - list of item:\n\"   item.line - Zero based line number\n\"   item.blocks - List with [text, hl_group]\n\"   item.hl_mode - Default to 'combine'.\n\"   item.col - vim & nvim >= 0.10.0, default to 0.\n\"   item.virt_text_win_col - neovim only.\n\"   item.text_align - Could be 'after' 'right' 'below' 'above'.\n\"   item.text_wrap - Could be 'wrap' and 'truncate', vim9 only.\n\" indent - Prepend indent of current line when true\n\" priority - Highlight priority\nfunction! coc#vtext#set(bufnr, ns, items, indent, priority) abort\n  try\n    if s:is_vim\n      call coc#vim9#Set_virtual_texts(a:bufnr, a:ns, a:items, a:indent, a:priority)\n    else\n      call v:lua.require('coc.vtext').set(a:bufnr, a:ns, a:items, a:indent, a:priority)\n    endif\n  catch /.*/\n    call coc#compat#send_error('coc#vtext#set', s:is_vim)\n  endtry\nendfunction\n\n\" Check virtual text of namespace exists\nfunction! coc#vtext#exists(bufnr, ns) abort\n  if s:is_vim\n    let types = coc#api#GetNamespaceTypes(a:ns)\n    if empty(types)\n      return 0\n    endif\n    return !empty(prop_list(1, {'bufnr': a:bufnr, 'types': types, 'end_lnum': -1}))\n  endif\n  return !empty(nvim_buf_get_extmarks(a:bufnr, a:ns, [0, 0], [-1, -1], {}))\nendfunction\n\n\" This function is called by buffer.setVirtualText\n\" ns - Id created by coc#highlight#create_namespace()\n\" line - Zero based line number\n\" blocks - List with [text, hl_group]\n\" opts.hl_mode - Default to 'combine'.\n\" opts.col - vim & nvim >= 0.10.0, default to 0.\n\" opts.virt_text_win_col - neovim only.\n\" opts.text_align - Could be 'after' 'right' 'below' 'above', converted on neovim.\n\" opts.text_wrap - Could be 'wrap' and 'truncate', vim9 only.\n\" opts.indent - add indent when using 'above' and 'below' as text_align\nfunction! coc#vtext#add(bufnr, ns, line, blocks, opts) abort\n  try\n    if s:is_vim\n      call coc#vim9#Add_vtext(a:bufnr, a:ns, a:line, a:blocks, a:opts)\n    else\n      call v:lua.require('coc.vtext').add(a:bufnr, a:ns, a:line, a:blocks, a:opts)\n    endif\n  catch /.*/\n    call coc#compat#send_error('coc#vtext#add', s:is_vim)\n  endtry\nendfunction\n"
  },
  {
    "path": "autoload/coc/window.vim",
    "content": "let g:coc_max_treeview_width = get(g:, 'coc_max_treeview_width', 40)\nlet s:is_vim = !has('nvim')\n\n\" Get tabpagenr of winid, return -1 if window doesn't exist\nfunction! coc#window#tabnr(winid) abort\n  \" getwininfo not work with popup on vim\n  if s:is_vim && index(popup_list(), a:winid) != -1\n    call win_execute(a:winid, 'let g:__coc_tabnr = tabpagenr()')\n    let nr = g:__coc_tabnr\n    unlet g:__coc_tabnr\n    return nr\n  endif\n  let info = getwininfo(a:winid)\n  return empty(info) ? -1 : info[0]['tabnr']\nendfunction\n\n\" (1, 0) based line, column\nfunction! coc#window#get_cursor(winid) abort\n  if exists('*nvim_win_get_cursor')\n    return nvim_win_get_cursor(a:winid)\n  endif\n  let pos = getcurpos(a:winid)\n  return [pos[1], pos[2] - 1]\nendfunction\n\n\" Check if winid visible on current tabpage\nfunction! coc#window#visible(winid) abort\n  if s:is_vim\n    if coc#window#tabnr(a:winid) != tabpagenr()\n      return 0\n    endif\n    \" Check possible hidden popup\n    try\n      return get(popup_getpos(a:winid), 'visible', 0) == 1\n    catch /^Vim\\%((\\a\\+)\\)\\=:E993/\n      return 1\n    endtry\n  else\n    if !nvim_win_is_valid(a:winid)\n      return 0\n    endif\n    return coc#window#tabnr(a:winid) == tabpagenr()\n  endif\nendfunction\n\n\" winid is popup and shown\nfunction! s:visible_popup(winid) abort\n  if index(popup_list(), a:winid) != -1\n    return get(popup_getpos(a:winid), 'visible', 0) == 1\n  endif\n  return 0\nendfunction\n\n\" Return default or v:null when name or window doesn't exist,\n\" 'getwinvar' only works on window of current tab\nfunction! coc#window#get_var(winid, name, ...) abort\n  let tabnr = coc#window#tabnr(a:winid)\n  if tabnr == -1\n    return get(a:, 1, v:null)\n  endif\n  return gettabwinvar(tabnr, a:winid, a:name, get(a:, 1, v:null))\nendfunction\n\n\" Not throw like setwinvar\nfunction! coc#window#set_var(winid, name, value) abort\n  let tabnr = coc#window#tabnr(a:winid)\n  if tabnr == -1\n    return\n  endif\n  call settabwinvar(tabnr, a:winid, a:name, a:value)\nendfunction\n\nfunction! coc#window#is_float(winid) abort\n  if s:is_vim\n    return index(popup_list(), a:winid) != -1\n  else\n    if nvim_win_is_valid(a:winid)\n      let config = nvim_win_get_config(a:winid)\n      return !empty(get(config, 'relative', ''))\n    endif\n  endif\n  return 0\nendfunction\n\n\" Reset current lnum & topline of window\nfunction! coc#window#restview(winid, lnum, topline) abort\n  if empty(getwininfo(a:winid))\n    return\n  endif\n  if s:is_vim && s:visible_popup(a:winid)\n    call popup_setoptions(a:winid, {'firstline': a:topline})\n    return\n  endif\n  call win_execute(a:winid, ['noa call winrestview({\"lnum\":'.a:lnum.',\"topline\":'.a:topline.'})'])\nendfunction\n\nfunction! coc#window#set_height(winid, height) abort\n  if empty(getwininfo(a:winid))\n    return\n  endif\n  if !s:is_vim\n    call nvim_win_set_height(a:winid, a:height)\n  else\n    if coc#window#is_float(a:winid)\n      call popup_move(a:winid, {'minheight': a:height, 'maxheight': a:height})\n    else\n      call win_execute(a:winid, 'noa resize '.a:height)\n    endif\n  endif\nendfunction\n\nfunction! coc#window#adjust_width(winid) abort\n  let bufnr = winbufnr(a:winid)\n  if bufloaded(bufnr)\n    let maxwidth = 0\n    let lines = getbufline(bufnr, 1, '$')\n    if len(lines) > 2\n      call win_execute(a:winid, 'setl nowrap')\n      for line in lines\n        let w = strwidth(line)\n        if w > maxwidth\n          let maxwidth = w\n        endif\n      endfor\n    endif\n    if maxwidth > winwidth(a:winid)\n      call win_execute(a:winid, 'vertical resize '.min([maxwidth, g:coc_max_treeview_width]))\n    endif\n  endif\nendfunction\n\n\" Get single window by window variable, current tab only\nfunction! coc#window#find(key, val) abort\n  for i in range(1, winnr('$'))\n    let res = getwinvar(i, a:key)\n    if res == a:val\n      return win_getid(i)\n    endif\n  endfor\n  return -1\nendfunction\n\n\" Visible buffer numbers\nfunction! coc#window#bufnrs() abort\n  let winids = map(getwininfo(), 'v:val[\"winid\"]')\n  return uniq(map(winids, 'winbufnr(v:val)'))\nendfunction\n\nfunction! coc#window#buf_winid(bufnr) abort\n  let winids = map(getwininfo(), 'v:val[\"winid\"]')\n  for winid in winids\n    if winbufnr(winid) == a:bufnr\n      return winid\n    endif\n  endfor\n  return -1\nendfunction\n\n\" Avoid errors\nfunction! coc#window#close(winid) abort\n  if empty(a:winid) || a:winid == -1\n    return\n  endif\n  if coc#window#is_float(a:winid)\n    call coc#float#close(a:winid)\n    return\n  endif\n  call win_execute(a:winid, 'noa close!', 'silent!')\nendfunction\n\nfunction! coc#window#visible_range(winid) abort\n  let winid = a:winid == 0 ? win_getid() : a:winid\n  let info = get(getwininfo(winid), 0, v:null)\n  if empty(info)\n    return v:null\n  endif\n  return [info['topline'], info['botline']]\nendfunction\n\nfunction! coc#window#visible_ranges(bufnr) abort\n  let wins = gettabinfo(tabpagenr())[0]['windows']\n  let res = []\n  for id in wins\n    let info = getwininfo(id)[0]\n    if info['bufnr'] == a:bufnr\n      call add(res, [info['topline'], info['botline']])\n    endif\n  endfor\n  return res\nendfunction\n\n\" Clear matches by hlGroup regexp.\nfunction! coc#window#clear_match_group(winid, match) abort\n  let winid = a:winid == 0 ? win_getid() : a:winid\n  if !empty(getwininfo(winid))\n    let arr = filter(getmatches(winid), 'v:val[\"group\"] =~# \"'.a:match.'\"')\n    for item in arr\n      call matchdelete(item['id'], winid)\n    endfor\n  endif\nendfunction\n\n\" Clear matches by match ids, use 0 for current win.\nfunction! coc#window#clear_matches(winid, ids) abort\n  let winid = a:winid == 0 ? win_getid() : a:winid\n  if !empty(getwininfo(winid))\n    for id in a:ids\n      try\n        call matchdelete(id, winid)\n      catch /^Vim\\%((\\a\\+)\\)\\=:E803/\n        \" ignore\n      endtry\n    endfor\n  endif\nendfunction\n"
  },
  {
    "path": "autoload/coc.vim",
    "content": "scriptencoding utf-8\nlet g:coc_user_config = get(g:, 'coc_user_config', {})\nlet g:coc_global_extensions = get(g:, 'coc_global_extensions', [])\nlet g:coc_selected_text = ''\nlet g:coc_vim_commands = []\nlet s:watched_keys = []\nlet s:is_vim = !has('nvim')\nlet s:utf = has('nvim') || &encoding =~# '^utf'\nlet s:error_sign = get(g:, 'coc_status_error_sign', has('mac') && s:utf ? \"\\u274c \" : 'E ')\nlet s:warning_sign = get(g:, 'coc_status_warning_sign', has('mac') && s:utf ? \"\\u26a0\\ufe0f \" : 'W ')\nlet s:select_api = exists('*nvim_select_popupmenu_item')\nlet s:callbacks = {}\nlet s:fns = ['init', 'complete', 'should_complete', 'refresh', 'get_startcol', 'on_complete', 'on_enter']\nlet s:all_fns = s:fns + map(copy(s:fns), 'toupper(strpart(v:val, 0, 1)) . strpart(v:val, 1)')\n\nfunction! coc#expandable() abort\n  return coc#rpc#request('snippetCheck', [1, 0])\nendfunction\n\nfunction! coc#jumpable() abort\n  return coc#rpc#request('snippetCheck', [0, 1])\nendfunction\n\nfunction! coc#expandableOrJumpable() abort\n  return coc#rpc#request('snippetCheck', [1, 1])\nendfunction\n\n\" Only clear augroup starts with coc\nfunction! coc#clearGroups(prefix) abort\n  for group in getcompletion('coc', 'augroup')\n    if group =~# '^' . a:prefix\n      execute 'autocmd! ' . group\n    endif\n  endfor\nendfunction\n\n\" add vim command to CocCommand list\nfunction! coc#add_command(id, cmd, ...)\n  let config = {'id':a:id, 'cmd':a:cmd, 'title': get(a:,1,'')}\n  call add(g:coc_vim_commands, config)\n  if !coc#rpc#ready() | return | endif\n  call coc#rpc#notify('addCommand', [config])\nendfunction\n\nfunction! coc#on_enter()\n  call coc#rpc#notify('CocAutocmd', ['Enter', bufnr('%')])\n  return ''\nendfunction\n\nfunction! coc#_insert_key(method, key, ...) abort\n  let prefix = ''\n  if get(a:, 1, 1)\n    if coc#pum#visible()\n      let prefix = \"\\<C-r>=coc#pum#close()\\<CR>\"\n    elseif pumvisible()\n      let prefix = \"\\<C-x>\\<C-z>\"\n    endif\n  endif\n  return prefix.\"\\<c-r>=coc#rpc#\".a:method.\"('doKeymap', ['\".a:key.\"'])\\<CR>\"\nendfunction\n\n\" used for statusline\nfunction! coc#status(...)\n  let info = get(b:, 'coc_diagnostic_info', {})\n  let msgs = []\n  if !empty(info) && get(info, 'error', 0)\n    call add(msgs, s:error_sign . info['error'])\n  endif\n  if !empty(info) && get(info, 'warning', 0)\n    call add(msgs, s:warning_sign . info['warning'])\n  endif\n  let status = get(g:, 'coc_status', '')\n  if get(a:, 1, 0)\n    let status = substitute(status, '%', '%%', 'g')\n  endif\n  return trim(join(msgs, ' ') . ' ' . status)\nendfunction\n\nfunction! coc#config(section, value)\n  let g:coc_user_config[a:section] = a:value\n  call coc#rpc#notify('updateConfig', [a:section, a:value])\nendfunction\n\n\" Deprecated, use variable instead.\nfunction! coc#add_extension(...)\n  if a:0 == 0 | return | endif\n  call extend(g:coc_global_extensions, a:000)\nendfunction\n\nfunction! coc#_watch(key)\n  if s:is_vim | return | endif\n  if index(s:watched_keys, a:key) == -1\n    call add(s:watched_keys, a:key)\n    call dictwatcheradd(g:, a:key, function('s:GlobalChange'))\n  endif\nendfunction\n\nfunction! coc#_unwatch(key)\n  if s:is_vim | return | endif\n  let idx = index(s:watched_keys, a:key)\n  if idx != -1\n    call remove(s:watched_keys, idx)\n    call dictwatcherdel(g:, a:key, function('s:GlobalChange'))\n  endif\nendfunction\n\nfunction! s:GlobalChange(dict, key, val)\n  call coc#rpc#notify('GlobalChange', [a:key, get(a:val, 'old', v:null), get(a:val, 'new', v:null)])\nendfunction\n\nfunction! coc#on_notify(id, method, Cb)\n  let key = a:id. '-'.a:method\n  let s:callbacks[key] = a:Cb\n  call coc#rpc#notify('registerNotification', [a:id, a:method])\nendfunction\n\nfunction! coc#do_notify(id, method, result)\n  let key = a:id. '-'.a:method\n  let Fn = s:callbacks[key]\n  if !empty(Fn)\n    call Fn(a:result)\n  endif\nendfunction\n\nfunction! coc#start(...)\n  call CocActionAsync('startCompletion', get(a:, 1, {}))\n  return ''\nendfunction\n\n\" Could be used by coc extensions\nfunction! coc#_cancel(...)\n  call coc#pum#close()\nendfunction\n\nfunction! coc#refresh() abort\n  return \"\\<c-r>=coc#start()\\<CR>\"\nendfunction\n\nfunction! coc#_select_confirm() abort\n  return \"\\<C-r>=coc#pum#select_confirm()\\<CR>\"\nendfunction\n\nfunction! coc#_suggest_variables() abort\n  return {\n      \\ 'disable': get(b:, 'coc_suggest_disable', 0),\n      \\ 'disabled_sources': get(b:, 'coc_disabled_sources', []),\n      \\ 'blacklist': get(b:, 'coc_suggest_blacklist', []),\n      \\ }\nendfunction\n\nfunction! coc#_remote_fns(name)\n  let res = []\n  for fn in s:all_fns\n    if exists('*coc#source#'.a:name.'#'.fn)\n      call add(res, fn)\n    endif\n  endfor\n  return res\nendfunction\n\nfunction! coc#_do_complete(name, opt, cb) abort\n  let method = get(a:opt, 'vim9', v:false) ? 'Complete' : 'complete'\n  let handler = 'coc#source#'.a:name.'#'.method\n  let l:Cb = {res -> a:cb(v:null, res)}\n  let args = [a:opt, l:Cb]\n  call call(handler, args)\nendfunction\n"
  },
  {
    "path": "autoload/health/coc.vim",
    "content": "scriptencoding utf-8\nlet s:root = expand('<sfile>:h:h:h')\n\nfunction! s:report_ok(report) abort\n  if has('nvim-0.10')\n    call v:lua.vim.health.ok(a:report)\n  else\n    call health#report_ok(a:report)\n  endif\nendfunction\n\nfunction! s:report_error(report, advises) abort\n  if has('nvim-0.10')\n    call v:lua.vim.health.error(a:report, a:advises)\n  else\n    call health#report_error(a:report, a:advises)\n  endif\nendfunction\n\nfunction! s:report_warn(report) abort\n  if has('nvim-0.10')\n    call v:lua.vim.health.warn(a:report)\n  else\n    call health#report_warn(a:report)\n  endif\nendfunction\n\nfunction! s:checkVim(test, name, patchlevel) abort\n  if a:test\n    if !has(a:patchlevel)\n      call s:report_error(a:name . ' version not satisfied, ' . a:patchlevel . ' and above required')\n      return 0\n    else\n      call s:report_ok(a:name . ' version satisfied')\n      return 1\n    endif\n  endif\n  return 0\nendfunction\n\nfunction! s:checkEnvironment() abort\n  let valid\n    \\ = s:checkVim(has('nvim'), 'nvim', 'nvim-0.8.0')\n    \\ + s:checkVim(!has('nvim'), 'vim', 'patch-9.0.0438')\n  let node = get(g:, 'coc_node_path', $COC_NODE_PATH == '' ? 'node' : $COC_NODE_PATH)\n  if !executable(node)\n    let valid = 0\n    call s:report_error('Executable node.js not found, install node.js from http://nodejs.org/')\n  endif\n  let output = system(node . ' --version')\n  if v:shell_error && output !=# \"\"\n    let valid = 0\n    call s:report_error(output)\n  endif\n  let ms = matchlist(output, 'v\\(\\d\\+\\).\\(\\d\\+\\).\\(\\d\\+\\)')\n  if empty(ms)\n    let valid = 0\n    call s:report_error('Unable to detect version of node, make sure your node executable is http://nodejs.org/')\n  elseif str2nr(ms[1]) < 16 || (str2nr(ms[1]) == 16 && str2nr(ms[2]) < 18)\n    let valid = 0\n    call s:report_warn('Node.js version '.trim(output).' < 16.18.0, please upgrade node.js')\n  endif\n  if valid\n    call s:report_ok('Environment check passed')\n  endif\n  if has('pythonx')\n    try\n      silent pyx print(\"\")\n    catch /.*/\n      call s:report_warn('pyx command not work, some extensions may fail to work, checkout \":h pythonx\"')\n      if has('nvim')\n        call s:report_warn('Install pynvim by command: `pip install pynvim --upgrade`')\n      endif\n    endtry\n  endif\n  return valid\nendfunction\n\nfunction! s:checkCommand()\n  let file = s:root.'/build/index.js'\n  if filereadable(file)\n    call s:report_ok('Javascript bundle build/index.js found')\n  else\n    call s:report_error('Javascript entry not found, please compile coc.nvim by esbuild.')\n  endif\nendfunction\n\nfunction! s:checkAutocmd()\n  let cmds = ['CursorHold', 'CursorHoldI', 'CursorMovedI', 'InsertCharPre', 'TextChangedI']\n  for cmd in cmds\n    let lines = split(execute('verbose autocmd '.cmd), '\\n')\n    let n = 0\n    for line in lines\n      if line =~# 'CocAction(' && n < len(lines) - 1\n        let next = lines[n + 1]\n        let ms = matchlist(next, 'Last set from \\(.*\\)')\n        if !empty(ms)\n          call s:report_warn('Use CocActionAsync to replace CocAction for better performance on '.cmd)\n          call s:report_warn('Checkout the file '.ms[1])\n        endif\n      endif\n      let n = n + 1\n    endfor\n  endfor\nendfunction\n\nfunction! s:checkInitialize() abort\n  if get(g:, 'coc_start_at_startup', 1) == 0\n    call s:report_warn('coc.nvim was disabled on startup, run :CocStart to start manually')\n    return 1\n  endif\n  if coc#client#is_running('coc')\n    call s:report_ok('Service started')\n    return 1\n  endif\n  call s:report_error('service could not be initialized', [\n        \\ 'Use command \":messages\" to get error messages.',\n        \\ 'Open an issue at https://github.com/neoclide/coc.nvim/issues for feedback.'\n        \\])\n  return 0\nendfunction\n\nfunction! health#coc#check() abort\n    call s:checkEnvironment()\n    call s:checkCommand()\n    call s:checkInitialize()\n    call s:checkAutocmd()\nendfunction\n"
  },
  {
    "path": "bin/prompt.js",
    "content": "/*\n * Used for prompt popup on vim\n */\nconst readline = require(\"readline\")\nconst rl = readline.createInterface({\n  input: process.stdin,\n  output: process.stdout,\n  terminal: true,\n  escapeCodeTimeout: 0,\n  prompt: ''\n})\n\nlet value = process.argv[2]\nlet placeholder = process.argv[3]\nlet clear = false\nif (value) {\n  rl.write(value)\n} else if (placeholder) {\n  clear = true\n  rl.write('\\x1B[90m' + placeholder + '\\x1B[39m')\n  rl.write('', {ctrl: true, name: 'a'})\n}\nrl.on('line', input => {\n  send(['confirm', clear ? '' : input])\n  process.exit()\n})\n\nlet original_ttyWrite = rl._ttyWrite\nrl._ttyWrite = function (code, key) {\n  if (key.name === 'enter') {\n    send(['send', '<C-j>'])\n    return ''\n  }\n  original_ttyWrite.apply(rl, arguments)\n  if (clear && rl.line.includes('\\x1B')) {\n    clear = false\n    rl.write('', {ctrl: true, name: 'k'})\n    return\n  }\n  send(['change', rl.line])\n}\n\nfunction createSequences(str) {\n  return '\\x1b]51;' + str + '\\x07'\n}\n\nfunction send(args) {\n  process.stdout.write(createSequences(JSON.stringify(['call', 'CocPopupCallback', args])))\n}\n\nprocess.stdin.on('keypress', (e, key) => {\n  if (key) {\n    let k = getKey(key)\n    if (k == '<bs>') {\n      return\n    }\n    if (k == '<esc>') {\n      send(['exit', ''])\n      process.exit()\n    }\n    if (k) {\n      send(['send', k])\n      return\n    }\n  }\n})\n\nfunction getKey(key) {\n  if (key.ctrl === true) {\n    if (key.name == 'n') {\n      return '<C-n>'\n    }\n    if (key.name == 'p') {\n      return '<C-p>'\n    }\n    if (key.name == 'j') {\n      return '<C-j>'\n    }\n    if (key.name == 'k') {\n      return '<C-k>'\n    }\n    if (key.name == 'f') {\n      return '<C-f>'\n    }\n    if (key.name == 't') {\n      return '<C-t>'\n    }\n    if (key.name == 'b') {\n      return '<C-b>'\n    }\n    if (key.sequence == '\\x00') {\n      return '<C-@>'\n    }\n  }\n  if (key.sequence == '\\u001b') {\n    return '<esc>'\n  }\n  if (key.sequence == '\\r') {\n    return '<cr>'\n  }\n  if (key.sequence == '\\t') {\n    return key.shift ? '<s-tab>' : '<tab>'\n  }\n  if (key.name == 'up') {\n    return '<up>'\n  }\n  if (key.name == 'down') {\n    return '<down>'\n  }\n  return ''\n}\n"
  },
  {
    "path": "bin/terminateProcess.sh",
    "content": "#!/bin/bash\nterminateTree() {\n    for cpid in $(pgrep -P $1); do\n        terminateTree $cpid\n    done\n    kill -9 $1 > /dev/null 2>&1\n}\n\nfor pid in $*; do\n    terminateTree $pid\ndone\n\n"
  },
  {
    "path": "data/schema.json",
    "content": "{\n  \"description\": \"Configuration file for coc.nvim\",\n  \"additionalProperties\": false,\n  \"definitions\": {\n    \"floatConfig\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"border\": {\n          \"type\": \"boolean\",\n          \"default\": false,\n          \"description\": \"Set to true to use borders.\"\n        },\n        \"rounded\": {\n          \"type\": \"boolean\",\n          \"default\": false,\n          \"description\": \"Use rounded borders when border is true.\"\n        },\n        \"highlight\": {\n          \"type\": \"string\",\n          \"default\": \"CocFloating\",\n          \"description\": \"Background highlight group of float window.\"\n        },\n        \"title\": {\n          \"type\": \"string\",\n          \"default\": \"\",\n          \"description\": \"Title used by float window.\"\n        },\n        \"borderhighlight\": {\n          \"type\": \"string\",\n          \"default\": \"CocFloatBorder\",\n          \"description\": \"Border highlight group of float window.\"\n        },\n        \"close\": {\n          \"type\": \"boolean\",\n          \"default\": false,\n          \"description\": \"Set to true to draw close icon\"\n        },\n        \"maxWidth\": {\n          \"type\": \"integer\",\n          \"description\": \"Maximum width of float window, include border.\"\n        },\n        \"maxHeight\": {\n          \"type\": \"integer\",\n          \"minimum\": 2,\n          \"description\": \"Maximum height of float window, include border.\"\n        },\n        \"focusable\": {\n          \"type\": \"boolean\",\n          \"default\": true,\n          \"description\": \"Enable focus by user actions (wincmds, mouse events), neovim only.\"\n        },\n        \"shadow\": {\n          \"type\": \"boolean\",\n          \"default\": false,\n          \"description\": \"Drop shadow effect by blending with the background, neovim only.\"\n        },\n        \"winblend\": {\n          \"type\": \"integer\",\n          \"default\": 0,\n          \"minimum\": 0,\n          \"maximum\": 100,\n          \"description\": \"Enables pseudo-transparency by set 'winblend' option of window, neovim only.\"\n        },\n        \"position\": {\n          \"type\": \"string\",\n          \"default\": \"auto\",\n          \"description\": \"Controls how floating windows are positioned. When set to `'fixed'`, the window will be positioned according to the `top`, `bottom`, `left`, and `right` settings. When set to `'auto'`, the window follows the default position.\",\n          \"enum\": [\"fixed\", \"auto\"]\n        },\n        \"top\": {\n          \"type\": \"number\",\n          \"description\": \"Distance from the top of the editor window in characters. Only takes effect when `position` is set to `'fixed'`. Will be ignored if `bottom` is set.\"\n        },\n        \"bottom\": {\n          \"type\": \"number\",\n          \"description\": \"Distance from the bottom of the editor window in characters. Only takes effect when `position` is set to `'fixed'`. Takes precedence over `top` if both are set.\"\n        },\n        \"left\": {\n          \"type\": \"number\",\n          \"description\": \"Distance from the left edge of the editor window in characters. Only takes effect when `position` is set to `'fixed'`. Will be ignored if `right` is set.\"\n        },\n        \"right\": {\n          \"type\": \"number\",\n          \"description\": \"Distance from the right edge of the editor window in characters. Only takes effect when `position` is set to `'fixed'`. Takes precedence over `left` if both are set.\"\n        }\n      }\n    },\n    \"languageserver.enable\": {\n      \"type\": \"boolean\",\n      \"description\": \"Enable the languageserver, restart coc.nvim required after change.\",\n      \"default\": true\n    },\n    \"languageserver.filetypes\": {\n      \"type\": \"array\",\n      \"default\": [],\n      \"description\": \"Supported filetypes, add * in array for all filetypes.\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"languageserver.maxRestartCount\": {\n      \"type\": \"integer\",\n      \"default\": 4,\n      \"minimum\": 1,\n      \"description\": \"Maximum restart count when server closed in the last 3 minutes.\"\n    },\n    \"languageserver.cwd\": {\n      \"type\": \"string\",\n      \"default\": \"\",\n      \"description\": \"Working directory of languageserver, absolute path or relative to workspace folder, use workspace root by default\"\n    },\n    \"languageserver.settings\": {\n      \"type\": \"object\",\n      \"default\": {},\n      \"description\": \"Settings of languageserver\"\n    },\n    \"languageserver.initializationOptions\": {\n      \"type\": \"object\",\n      \"default\": {},\n      \"description\": \"initializationOptions passed to languageserver\"\n    },\n    \"languageserver.env\": {\n      \"type\": \"object\",\n      \"default\": null,\n      \"description\": \"Environment variables for child process.\"\n    },\n    \"languageserver.stdioEncoding\": {\n      \"type\": \"string\",\n      \"default\": \"utf8\",\n      \"description\": \"Encoding used for stdio of child process.\"\n    },\n    \"languageserver.rootPatterns\": {\n      \"type\": \"array\",\n      \"default\": [],\n      \"description\": \"Root patterns used to resolve rootPath from current file, default to workspace root\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"languageserver.requireRootPattern\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"If true, doesn't start server when root pattern not found.\"\n    },\n    \"languageserver.ignoredRootPaths\": {\n      \"type\": \"array\",\n      \"default\": [],\n      \"description\": \"Absolute root paths that language server should not use as rootPath, higher priority than rootPatterns.\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"languageserver.additionalSchemes\": {\n      \"type\": \"array\",\n      \"default\": [],\n      \"description\": \"Additional URI schemes, default schemes including file & untitled.\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"languageserver.revealOutputChannelOn\": {\n      \"type\": \"string\",\n      \"default\": \"never\",\n      \"description\": \"Configure message level to show the output channel buffer\",\n      \"enum\": [\"info\", \"warn\", \"error\", \"never\"]\n    },\n    \"languageserver.progressOnInitialization\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Enable progress report on languageserver initialize.\"\n    },\n    \"languageserver.trace.server\": {\n      \"type\": \"string\",\n      \"default\": \"off\",\n      \"enum\": [\"off\", \"messages\", \"verbose\"],\n      \"description\": \"Trace level of communication between server and client\"\n    },\n    \"languageserver.trace.server.verbosity\": {\n      \"type\": \"string\",\n      \"default\": \"off\",\n      \"enum\": [\"off\", \"messages\", \"compact\", \"verbose\"],\n      \"description\": \"Trace level of communication between server and client\"\n    },\n    \"languageserver.trace.server.format\": {\n      \"type\": \"string\",\n      \"default\": \"text\",\n      \"enum\": [\"text\", \"json\"],\n      \"description\": \"Text format of trace messages.\"\n    },\n    \"languageserver.disableDynamicRegister\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Disable dynamic registerCapability feature for this languageserver to avoid duplicate feature registration.\"\n    },\n    \"languageserver.disableSnippetCompletion\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"description\": \"Disable completion snippet feature for this languageserver, the languageserver may not respect it.\"\n    },\n    \"languageserver.disabledFeatures\": {\n      \"type\": \"array\",\n      \"default\": [],\n      \"description\": \"Disabled features for this languageserver.\",\n      \"items\": {\n        \"type\": \"string\",\n        \"enum\": [\n          \"completion\",\n          \"configuration\",\n          \"workspaceFolders\",\n          \"diagnostics\",\n          \"willSave\",\n          \"willSaveUntil\",\n          \"didSaveTextDocument\",\n          \"fileSystemWatcher\",\n          \"hover\",\n          \"signatureHelp\",\n          \"definition\",\n          \"references\",\n          \"documentHighlight\",\n          \"documentSymbol\",\n          \"workspaceSymbol\",\n          \"codeAction\",\n          \"codeLens\",\n          \"formatting\",\n          \"documentFormatting\",\n          \"documentRangeFormatting\",\n          \"documentOnTypeFormatting\",\n          \"rename\",\n          \"documentLink\",\n          \"executeCommand\",\n          \"pullConfiguration\",\n          \"typeDefinition\",\n          \"implementation\",\n          \"declaration\",\n          \"color\",\n          \"foldingRange\",\n          \"selectionRange\",\n          \"progress\",\n          \"callHierarchy\",\n          \"linkedEditing\",\n          \"inlayHint\",\n          \"inlineValue\",\n          \"typeHierarchy\",\n          \"pullDiagnostic\",\n          \"fileEvents\",\n          \"semanticTokens\"\n        ]\n      }\n    },\n    \"languageserver.formatterPriority\": {\n      \"type\": \"integer\",\n      \"default\": 0,\n      \"description\": \"Priority of this languageserver's formatter.\"\n    },\n    \"languageServerSocket\": {\n      \"type\": \"object\",\n      \"required\": [\"port\", \"filetypes\"],\n      \"additionalProperties\": false,\n      \"properties\": {\n        \"port\": {\n          \"type\": \"integer\",\n          \"description\": \"Port number of socket server\"\n        },\n        \"host\": {\n          \"type\": \"string\",\n          \"default\": \"127.0.0.1\",\n          \"description\": \"Host of server\"\n        },\n        \"enable\": {\n          \"$ref\": \"#/definitions/languageserver.enable\"\n        },\n        \"disableSnippetCompletion\": {\n          \"$ref\": \"#/definitions/languageserver.disableSnippetCompletion\"\n        },\n        \"disableDynamicRegister\": {\n          \"$ref\": \"#/definitions/languageserver.disableDynamicRegister\"\n        },\n        \"disabledFeatures\": {\n          \"$ref\": \"#/definitions/languageserver.disabledFeatures\"\n        },\n        \"formatterPriority\": {\n          \"$ref\": \"#/definitions/languageserver.formatterPriority\"\n        },\n        \"rootPatterns\": {\n          \"$ref\": \"#/definitions/languageserver.rootPatterns\"\n        },\n        \"requireRootPattern\": {\n          \"$ref\": \"#/definitions/languageserver.requireRootPattern\"\n        },\n        \"ignoredRootPaths\": {\n          \"$ref\": \"#/definitions/languageserver.ignoredRootPaths\"\n        },\n        \"maxRestartCount\": {\n          \"$ref\": \"#/definitions/languageserver.maxRestartCount\"\n        },\n        \"filetypes\": {\n          \"$ref\": \"#/definitions/languageserver.filetypes\"\n        },\n        \"additionalSchemes\": {\n          \"$ref\": \"#/definitions/languageserver.additionalSchemes\"\n        },\n        \"revealOutputChannelOn\": {\n          \"$ref\": \"#/definitions/languageserver.revealOutputChannelOn\"\n        },\n        \"progressOnInitialization\": {\n          \"$ref\": \"#/definitions/languageserver.progressOnInitialization\"\n        },\n        \"initializationOptions\": {\n          \"$ref\": \"#/definitions/languageserver.initializationOptions\"\n        },\n        \"settings\": {\n          \"$ref\": \"#/definitions/languageserver.settings\"\n        },\n        \"stdioEncoding\": {\n          \"$ref\": \"#/definitions/languageserver.stdioEncoding\"\n        },\n        \"trace.server\": {\n          \"$ref\": \"#/definitions/languageserver.trace.server\"\n        },\n        \"trace.server.verbosity\": {\n          \"$ref\": \"#/definitions/languageserver.trace.server.verbosity\"\n        },\n        \"trace.server.format\": {\n          \"$ref\": \"#/definitions/languageserver.trace.server.format\"\n        }\n      }\n    },\n    \"languageServerModule\": {\n      \"type\": \"object\",\n      \"required\": [\"module\", \"filetypes\"],\n      \"additionalProperties\": false,\n      \"properties\": {\n        \"module\": {\n          \"type\": \"string\",\n          \"default\": \"\",\n          \"description\": \"Absolute path of Javascript file, should works in IPC mode\"\n        },\n        \"args\": {\n          \"type\": \"array\",\n          \"default\": [],\n          \"description\": \"Extra arguments of module\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"runtime\": {\n          \"type\": \"string\",\n          \"default\": \"\",\n          \"description\": \"Absolute path of node runtime.\"\n        },\n        \"execArgv\": {\n          \"type\": \"array\",\n          \"default\": [],\n          \"description\": \"ARGV passed to node when using module, normally used for debugging, ex: [\\\"--nolazy\\\", \\\"--inspect-brk=6045\\\"]\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"transport\": {\n          \"type\": \"string\",\n          \"default\": \"ipc\",\n          \"description\": \"Transport kind used by server, could be 'ipc', 'stdio', 'socket' and 'pipe'\",\n          \"enum\": [\"ipc\", \"stdio\", \"socket\", \"pipe\"]\n        },\n        \"transportPort\": {\n          \"type\": \"integer\",\n          \"description\": \"Port number used when transport is 'socket'\"\n        },\n        \"enable\": {\n          \"$ref\": \"#/definitions/languageserver.enable\"\n        },\n        \"disableSnippetCompletion\": {\n          \"$ref\": \"#/definitions/languageserver.disableSnippetCompletion\"\n        },\n        \"disableDynamicRegister\": {\n          \"$ref\": \"#/definitions/languageserver.disableDynamicRegister\"\n        },\n        \"disabledFeatures\": {\n          \"$ref\": \"#/definitions/languageserver.disabledFeatures\"\n        },\n        \"formatterPriority\": {\n          \"$ref\": \"#/definitions/languageserver.formatterPriority\"\n        },\n        \"rootPatterns\": {\n          \"$ref\": \"#/definitions/languageserver.rootPatterns\"\n        },\n        \"requireRootPattern\": {\n          \"$ref\": \"#/definitions/languageserver.requireRootPattern\"\n        },\n        \"ignoredRootPaths\": {\n          \"$ref\": \"#/definitions/languageserver.ignoredRootPaths\"\n        },\n        \"maxRestartCount\": {\n          \"$ref\": \"#/definitions/languageserver.maxRestartCount\"\n        },\n        \"filetypes\": {\n          \"$ref\": \"#/definitions/languageserver.filetypes\"\n        },\n        \"additionalSchemes\": {\n          \"$ref\": \"#/definitions/languageserver.additionalSchemes\"\n        },\n        \"revealOutputChannelOn\": {\n          \"$ref\": \"#/definitions/languageserver.revealOutputChannelOn\"\n        },\n        \"progressOnInitialization\": {\n          \"$ref\": \"#/definitions/languageserver.progressOnInitialization\"\n        },\n        \"initializationOptions\": {\n          \"$ref\": \"#/definitions/languageserver.initializationOptions\"\n        },\n        \"settings\": {\n          \"$ref\": \"#/definitions/languageserver.settings\"\n        },\n        \"stdioEncoding\": {\n          \"$ref\": \"#/definitions/languageserver.stdioEncoding\"\n        },\n        \"trace.server\": {\n          \"$ref\": \"#/definitions/languageserver.trace.server\"\n        },\n        \"trace.server.verbosity\": {\n          \"$ref\": \"#/definitions/languageserver.trace.server.verbosity\"\n        },\n        \"trace.server.format\": {\n          \"$ref\": \"#/definitions/languageserver.trace.server.format\"\n        }\n      }\n    },\n    \"languageServerCommand\": {\n      \"type\": \"object\",\n      \"required\": [\"command\", \"filetypes\"],\n      \"additionalProperties\": false,\n      \"properties\": {\n        \"command\": {\n          \"type\": \"string\",\n          \"default\": \"\",\n          \"description\": \"Executable in $PATH to start languageserver, should not used with module\"\n        },\n        \"args\": {\n          \"type\": \"array\",\n          \"default\": [],\n          \"description\": \"Arguments of command\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"detached\": {\n          \"type\": \"boolean\",\n          \"default\": false,\n          \"description\": \"Detach the languageserver process\"\n        },\n        \"shell\": {\n          \"type\": [\"boolean\", \"string\"],\n          \"default\": false,\n          \"description\": \"If true runs command inside of a shell, always true on windows.\"\n        },\n        \"enable\": {\n          \"$ref\": \"#/definitions/languageserver.enable\"\n        },\n        \"env\": {\n          \"$ref\": \"#/definitions/languageserver.env\"\n        },\n        \"disableSnippetCompletion\": {\n          \"$ref\": \"#/definitions/languageserver.disableSnippetCompletion\"\n        },\n        \"disableDynamicRegister\": {\n          \"$ref\": \"#/definitions/languageserver.disableDynamicRegister\"\n        },\n        \"disabledFeatures\": {\n          \"$ref\": \"#/definitions/languageserver.disabledFeatures\"\n        },\n        \"formatterPriority\": {\n          \"$ref\": \"#/definitions/languageserver.formatterPriority\"\n        },\n        \"rootPatterns\": {\n          \"$ref\": \"#/definitions/languageserver.rootPatterns\"\n        },\n        \"requireRootPattern\": {\n          \"$ref\": \"#/definitions/languageserver.requireRootPattern\"\n        },\n        \"ignoredRootPaths\": {\n          \"$ref\": \"#/definitions/languageserver.ignoredRootPaths\"\n        },\n        \"maxRestartCount\": {\n          \"$ref\": \"#/definitions/languageserver.maxRestartCount\"\n        },\n        \"filetypes\": {\n          \"$ref\": \"#/definitions/languageserver.filetypes\"\n        },\n        \"additionalSchemes\": {\n          \"$ref\": \"#/definitions/languageserver.additionalSchemes\"\n        },\n        \"revealOutputChannelOn\": {\n          \"$ref\": \"#/definitions/languageserver.revealOutputChannelOn\"\n        },\n        \"progressOnInitialization\": {\n          \"$ref\": \"#/definitions/languageserver.progressOnInitialization\"\n        },\n        \"initializationOptions\": {\n          \"$ref\": \"#/definitions/languageserver.initializationOptions\"\n        },\n        \"settings\": {\n          \"$ref\": \"#/definitions/languageserver.settings\"\n        },\n        \"stdioEncoding\": {\n          \"$ref\": \"#/definitions/languageserver.stdioEncoding\"\n        },\n        \"trace.server\": {\n          \"$ref\": \"#/definitions/languageserver.trace.server\"\n        },\n        \"trace.server.verbosity\": {\n          \"$ref\": \"#/definitions/languageserver.trace.server.verbosity\"\n        },\n        \"trace.server.format\": {\n          \"$ref\": \"#/definitions/languageserver.trace.server.format\"\n        }\n      }\n    }\n  },\n  \"properties\": {\n    \"callHierarchy.enableTooltip\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": true,\n      \"description\": \"Enable tooltip to show relative filepath of call hierarchy.\"\n    },\n    \"callHierarchy.openCommand\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"edit\",\n      \"description\": \"Open command for callHierarchy tree view.\"\n    },\n    \"callHierarchy.splitCommand\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"botright 30vs\",\n      \"description\": \"Window split command used by callHierarchy tree view.\"\n    },\n    \"coc.preferences.rootPatterns\": {\n      \"type\": [\"array\", \"null\"],\n      \"default\": null,\n      \"scope\": \"application\",\n      \"description\": \"Root patterns to resolve workspaceFolder from parent folders of opened files, resolved from up to down.\",\n      \"deprecationMessage\": \"Use 'workspace.rootPatterns' instead.\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"coc.preferences.bracketEnterImprove\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Improve enter inside bracket `<> {} [] ()` by add new empty line below and place cursor to it. Works with `coc#on_enter()`\",\n      \"default\": true\n    },\n    \"coc.preferences.currentFunctionSymbolAutoUpdate\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Automatically update the value of b:coc_current_function on CursorMove event\",\n      \"default\": false\n    },\n    \"coc.preferences.currentFunctionSymbolDebounceTime\": {\n      \"type\": \"number\",\n      \"scope\": \"application\",\n      \"description\": \"Set debounce timer for the update of b:coc_current_function on CursorMove event\",\n      \"default\": 300\n    },\n    \"coc.preferences.enableLinkedEditing\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"default\": false,\n      \"description\": \"Enable linked editing support.\"\n    },\n    \"coc.preferences.enableMarkdown\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"description\": \"Tell the language server that markdown text format is supported, note that markdown text may not rendered as expected.\",\n      \"default\": true\n    },\n    \"coc.preferences.enableMessageDialog\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": false,\n      \"deprecationMessage\": \"Use configuration 'coc.preferences.messageDialogKind' instead.\",\n      \"description\": \"Enable interactive messages in notification dialog, or fallback to native vim confirm action.\"\n    },\n    \"coc.preferences.messageDialogKind\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"confirm\",\n      \"description\": \"Method to use when showing interactive messages to the user\",\n      \"enum\": [\"notification\", \"menu\", \"confirm\"]\n    },\n    \"coc.preferences.messageReportKind\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"echo\",\n      \"description\": \"Method to use when printing or showing plain user messages\",\n      \"enum\": [\"notification\", \"echo\"]\n    },\n    \"coc.preferences.excludeImageLinksInMarkdownDocument\": {\n      \"type\": \"boolean\",\n      \"description\": \"Exclude image links from markdown text in float window.\",\n      \"scope\": \"application\",\n      \"default\": true\n    },\n    \"coc.preferences.enableGFMBreaksInMarkdownDocument\": {\n      \"type\": \"boolean\",\n      \"description\": \"Exclude GFM breaks in markdown document.\",\n      \"scope\": \"application\",\n      \"default\": true\n    },\n    \"coc.preferences.extensionUpdateCheck\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"never\",\n      \"description\": \"Interval for check extension update, could be daily, weekly, never\",\n      \"deprecationMessage\": \"Use configuration 'extensions.updateCheck' instead.\",\n      \"enum\": [\"daily\", \"weekly\", \"never\"]\n    },\n    \"coc.preferences.extensionUpdateUIInTab\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": false,\n      \"deprecationMessage\": \"Use configuration 'extensions.updateUIInTab' instead.\",\n      \"description\": \"Display extension updating UI in new vim tab\"\n    },\n    \"coc.preferences.silentAutoupdate\": {\n      \"type\": \"boolean\",\n      \"description\": \"Not open split window with update status when performing auto update.\",\n      \"deprecationMessage\": \"Use configuration 'extensions.silentAutoupdate' instead.\",\n      \"scope\": \"application\",\n      \"default\": true\n    },\n    \"coc.preferences.floatActions\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"description\": \"Set to false to disable float/popup support for actions menu.\",\n      \"default\": true\n    },\n    \"coc.preferences.autoApplySingleQuickfix\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"description\": \"Automatically apply the single quickfix action .\",\n      \"default\": true\n    },\n    \"coc.preferences.formatOnSave\": {\n      \"type\": \"boolean\",\n      \"description\": \"Set to true to enable formatting on save.\",\n      \"scope\": \"language-overridable\",\n      \"default\": false\n    },\n    \"coc.preferences.formatOnSaveTimeout\": {\n      \"type\": \"integer\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"How long before the format command run on save times out.\",\n      \"default\": 500,\n      \"minimum\": 200,\n      \"maximum\": 5000\n    },\n    \"coc.preferences.formatOnSaveFiletypes\": {\n      \"type\": [\"null\", \"array\"],\n      \"scope\": \"resource\",\n      \"default\": null,\n      \"description\": \"Filetypes that should run format on save.\",\n      \"deprecationMessage\": \"Use 'coc.preferences.formatOnSave' as language override configuration instead, see :h coc-configuration-scope\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"coc.preferences.formatOnType\": {\n      \"type\": \"boolean\",\n      \"description\": \"Set to true to enable formatting on typing.\",\n      \"scope\": \"language-overridable\",\n      \"default\": false\n    },\n    \"coc.preferences.formatOnTypeFiletypes\": {\n      \"type\": [\"null\", \"array\"],\n      \"default\": null,\n      \"scope\": \"resource\",\n      \"description\": \"Filetypes that should run format on typing, only works when `coc.preferences.formatOnType` is `true`\",\n      \"deprecationMessage\": \"Use 'coc.preferences.formatOnType' as language override configuration instead, see :h coc-configuration-scope\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"coc.preferences.formatterExtension\": {\n      \"type\": [\"null\", \"string\"],\n      \"default\": null,\n      \"scope\": \"language-overridable\",\n      \"description\": \"Extension used for formatting documents. When set to null, the formatter with highest priority is used.\"\n    },\n    \"coc.preferences.jumpCommand\": {\n      \"anyOf\": [\n        {\n          \"type\": \"string\",\n          \"enum\": [\"edit\", \"split\", \"vsplit\", \"tabe\", \"drop\", \"tab drop\", \"pedit\"]\n        },\n        {\"type\": \"string\", \"minimum\": 1}\n      ],\n      \"scope\": \"application\",\n      \"description\": \"Command used for location jump, like goto definition, goto references etc. Can be also a custom command that gives file as an argument.\",\n      \"default\": \"edit\"\n    },\n    \"coc.preferences.maxFileSize\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"10MB\",\n      \"description\": \"Maximum file size in bytes that coc.nvim should handle, default '10MB'\"\n    },\n    \"coc.preferences.messageLevel\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"description\": \"Message level for filter echoed messages, could be 'more', 'warning' and 'error'\",\n      \"default\": \"more\",\n      \"enum\": [\"more\", \"warning\", \"error\"]\n    },\n    \"coc.preferences.promptInput\": {\n      \"type\": \"boolean\",\n      \"description\": \"Use prompt buffer in float window for user input.\",\n      \"scope\": \"application\",\n      \"default\": true\n    },\n    \"coc.preferences.renameFillCurrent\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": true,\n      \"description\": \"Disable to stop Refactor-Rename float/popup window from populating with old name in the New Name field.\"\n    },\n    \"coc.preferences.useQuickfixForLocations\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"description\": \"Use vim's quickfix list for jump locations,\\n need restart on change.\",\n      \"default\": false\n    },\n    \"coc.preferences.watchmanPath\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"deprecationMessage\": \"Use configuration \\\"fileSystemWatch.watchmanPath\\\" instead.\",\n      \"description\": \"executable path for https://facebook.github.io/watchman/, detected from $PATH by default\",\n      \"default\": null\n    },\n    \"coc.preferences.willSaveHandlerTimeout\": {\n      \"type\": \"integer\",\n      \"scope\": \"application\",\n      \"default\": 500,\n      \"minimum\": 200,\n      \"maximum\": 5000,\n      \"description\": \"Will save handler timeout\"\n    },\n    \"coc.preferences.tagDefinitionTimeout\": {\n      \"type\": \"integer\",\n      \"scope\": \"application\",\n      \"default\": 0,\n      \"description\": \"The timeout of CocTagFunc, default is infinity\"\n    },\n    \"coc.source.around.disableSyntaxes\": {\n      \"type\": \"array\",\n      \"default\": [],\n      \"scope\": \"application\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"coc.source.around.enable\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": true\n    },\n    \"coc.source.around.priority\": {\n      \"type\": \"integer\",\n      \"scope\": \"application\",\n      \"default\": 1\n    },\n    \"coc.source.around.shortcut\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"A\"\n    },\n    \"coc.source.buffer.disableSyntaxes\": {\n      \"type\": \"array\",\n      \"default\": [],\n      \"scope\": \"application\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"coc.source.buffer.enable\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": true\n    },\n    \"coc.source.buffer.ignoreGitignore\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"scope\": \"application\",\n      \"description\": \"Ignore git ignored files for buffer words\"\n    },\n    \"coc.source.buffer.priority\": {\n      \"type\": \"integer\",\n      \"scope\": \"application\",\n      \"default\": 1\n    },\n    \"coc.source.buffer.shortcut\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"B\"\n    },\n    \"coc.source.file.disableSyntaxes\": {\n      \"type\": \"array\",\n      \"default\": [],\n      \"scope\": \"application\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"coc.source.file.enable\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": true\n    },\n    \"coc.source.file.ignoreHidden\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": true,\n      \"description\": \"Ignore completion for hidden files\"\n    },\n    \"coc.source.file.ignorePatterns\": {\n      \"type\": \"array\",\n      \"scope\": \"application\",\n      \"default\": [],\n      \"description\": \"Ignore patterns of matcher\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"coc.source.file.priority\": {\n      \"type\": \"integer\",\n      \"scope\": \"application\",\n      \"default\": 10\n    },\n    \"coc.source.file.shortcut\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"F\"\n    },\n    \"coc.source.file.triggerCharacters\": {\n      \"type\": \"array\",\n      \"default\": [\"/\", \"\\\\\"],\n      \"scope\": \"application\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"coc.source.file.trimSameExts\": {\n      \"type\": \"array\",\n      \"scope\": \"application\",\n      \"default\": [\".ts\", \".js\"],\n      \"description\": \"Trim same extension on file completion\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"codeLens.enable\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Enable codeLens feature, require neovim with set virtual text feature.\",\n      \"default\": false\n    },\n    \"codeLens.display\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"default\": true,\n      \"description\": \"Display codeLens.\"\n    },\n    \"codeLens.position\": {\n      \"type\": \"string\",\n      \"scope\": \"language-overridable\",\n      \"enum\": [\"top\", \"eol\", \"right_align\"],\n      \"description\": \"Display position of codeLens virtual text.\",\n      \"default\": \"top\"\n    },\n    \"codeLens.separator\": {\n      \"type\": \"string\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Separator text for codeLens in virtual text\",\n      \"default\": \"\"\n    },\n    \"codeLens.subseparator\": {\n      \"type\": \"string\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Subseparator between codeLenses in virtual text\",\n      \"default\": \" | \"\n    },\n    \"colors.enable\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Enable colors highlight feature, for terminal vim, 'termguicolors' option should be enabled and the terminal support gui colors.\",\n      \"default\": false\n    },\n    \"colors.filetypes\": {\n      \"type\": [\"array\", \"null\"],\n      \"default\": null,\n      \"scope\": \"resource\",\n      \"deprecationMessage\": \"Use colors.enable as language override configuration instead, see :h coc-configuration-scope\",\n      \"description\": \"Filetypes that should be enabled for colors highlight feature, use \\\"*\\\" for all filetypes.\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"colors.highlightPriority\": {\n      \"type\": \"integer\",\n      \"scope\": \"application\",\n      \"description\": \"Priority for colors highlights, works on vim8 and neovim >= 0.6.0\",\n      \"default\": 1000,\n      \"maximum\": 4096\n    },\n    \"cursors.cancelKey\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"<esc>\",\n      \"description\": \"Key used for cancel cursors session.\"\n    },\n    \"cursors.nextKey\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"<C-n>\",\n      \"description\": \"Key used for jump to next cursors position.\"\n    },\n    \"cursors.previousKey\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"<C-p>\",\n      \"description\": \"Key used for jump to previous cursors position.\"\n    },\n    \"cursors.wrapscan\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": true,\n      \"description\": \"Searches wrap around the first or last cursors range.\"\n    },\n    \"diagnostic.autoRefresh\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Enable automatically refresh diagnostics, use diagnosticRefresh action when it's disabled.\",\n      \"default\": true\n    },\n    \"diagnostic.checkCurrentLine\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"When enabled, show all diagnostics of current line if there are none at the current position.\",\n      \"default\": false\n    },\n    \"diagnostic.displayByAle\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Use Ale, coc-diagnostics-shim.nvim, or other provider to display diagnostics in vim. This setting will disable diagnostic display using coc's handler. A restart required on change.\",\n      \"default\": false\n    },\n    \"diagnostic.displayByVimDiagnostic\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Display diagnostics with nvim's `vim.diagnostic`. This setting will disable diagnostic display using coc's handler. A restart required on change. Neovim only.\",\n      \"default\": false\n    },\n    \"diagnostic.enable\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Set to false to disable diagnostic display\",\n      \"default\": true\n    },\n    \"diagnostic.enableHighlightLineNumber\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": true,\n      \"description\": \"Enable highlighting line numbers for diagnostics, only works with neovim.\"\n    },\n    \"diagnostic.enableMessage\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"always\",\n      \"description\": \"When to enable show messages of diagnostics.\",\n      \"enum\": [\"always\", \"jump\", \"never\"]\n    },\n    \"diagnostic.enableSign\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"default\": true,\n      \"description\": \"Enable signs for diagnostics.\"\n    },\n    \"diagnostic.errorSign\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"description\": \"Text of error sign\",\n      \"default\": \">>\"\n    },\n    \"diagnostic.filetypeMap\": {\n      \"type\": \"object\",\n      \"scope\": \"application\",\n      \"description\": \"A map between buffer filetype and the filetype assigned to diagnostics. To syntax highlight diagnostics with their parent buffer type use `\\\"default\\\": \\\"bufferType\\\"`\",\n      \"default\": {}\n    },\n    \"diagnostic.floatConfig\": {\n      \"type\": \"object\",\n      \"scope\": \"application\",\n      \"description\": \"Configure float window style of diagnostic message.\",\n      \"allOf\": [{\"$ref\": \"#/definitions/floatConfig\"}],\n      \"additionalProperties\": false,\n      \"properties\": {\n        \"border\": {},\n        \"rounded\": {},\n        \"highlight\": {},\n        \"borderhighlight\": {},\n        \"title\": {},\n        \"close\": {},\n        \"maxHeight\": {},\n        \"maxWidth\": {},\n        \"winblend\": {},\n        \"focusable\": {},\n        \"shadow\": {},\n        \"position\": {},\n        \"top\": {},\n        \"bottom\": {},\n        \"left\": {},\n        \"right\": {}\n      }\n    },\n    \"diagnostic.format\": {\n      \"type\": \"string\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Define the diagnostic format that shown in float window or echoed, available parts: source, code, severity, message\",\n      \"default\": \"%message (%source%code)\"\n    },\n    \"diagnostic.highlightLimit\": {\n      \"type\": \"integer\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Limit count for highlighted diagnostics, too many diagnostic highlights could make vim stop responding\",\n      \"default\": 1000\n    },\n    \"diagnostic.highlightPriority\": {\n      \"type\": \"integer\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Priority for diagnostic highlights, works on vim8 and neovim >= 0.6.0\",\n      \"default\": 4096,\n      \"maximum\": 4096,\n      \"minimum\": 110\n    },\n    \"diagnostic.hintSign\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"description\": \"Text of hint sign\",\n      \"default\": \">>\"\n    },\n    \"diagnostic.infoSign\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"description\": \"Text of info sign\",\n      \"default\": \">>\"\n    },\n    \"diagnostic.level\": {\n      \"type\": \"string\",\n      \"scope\": \"resource\",\n      \"description\": \"Used for filter diagnostics by diagnostic severity.\",\n      \"default\": \"hint\",\n      \"enum\": [\"hint\", \"information\", \"warning\", \"error\"]\n    },\n    \"diagnostic.locationlistLevel\": {\n      \"type\": [\"string\", \"null\"],\n      \"scope\": \"language-overridable\",\n      \"description\": \"Filter diagnostics in locationlist.\",\n      \"default\": null,\n      \"enum\": [\"hint\", \"information\", \"warning\", \"error\"]\n    },\n    \"diagnostic.locationlistUpdate\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Update locationlist on diagnostics change, only works with locationlist opened by :CocDiagnostics command and first window of associated buffer.\",\n      \"default\": true\n    },\n    \"diagnostic.messageDelay\": {\n      \"type\": \"integer\",\n      \"scope\": \"application\",\n      \"description\": \"How long to wait (in milliseconds) before displaying the diagnostic message with echo or float\",\n      \"default\": 200\n    },\n    \"diagnostic.messageLevel\": {\n      \"type\": [\"string\", \"null\"],\n      \"scope\": \"language-overridable\",\n      \"description\": \"Filter diagnostic message in float window/popup.\",\n      \"default\": null,\n      \"enum\": [\"hint\", \"information\", \"warning\", \"error\"]\n    },\n    \"diagnostic.messageTarget\": {\n      \"type\": \"string\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Diagnostic message target.\",\n      \"default\": \"float\",\n      \"enum\": [\"echo\", \"float\"]\n    },\n    \"diagnostic.refreshOnInsertMode\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Enable diagnostic refresh on insert mode, default false.\",\n      \"default\": false\n    },\n    \"diagnostic.showDeprecated\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"scope\": \"language-overridable\",\n      \"description\": \"Show diagnostics with deprecated tag.\"\n    },\n    \"diagnostic.showUnused\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"scope\": \"language-overridable\",\n      \"description\": \"Show diagnostics with unused tag, affects highlight, sign, virtual text, message\"\n    },\n    \"diagnostic.signLevel\": {\n      \"type\": [\"string\", \"null\"],\n      \"scope\": \"language-overridable\",\n      \"description\": \"Filter diagnostics displayed in signcolumn.\",\n      \"default\": null,\n      \"enum\": [\"hint\", \"information\", \"warning\", \"error\"]\n    },\n    \"diagnostic.signPriority\": {\n      \"type\": \"integer\",\n      \"scope\": \"resource\",\n      \"description\": \"Priority of diagnostic signs, default to 10\",\n      \"default\": 10\n    },\n    \"diagnostic.virtualText\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Use virtual text to display diagnostics.\",\n      \"default\": false\n    },\n    \"diagnostic.virtualTextAlign\": {\n      \"type\": \"string\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Position of virtual text, default 'after'\",\n      \"default\": \"after\",\n      \"enum\": [\"after\", \"right\", \"below\"]\n    },\n    \"diagnostic.virtualTextCurrentLineOnly\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Only show virtualText diagnostic on current cursor line\",\n      \"default\": true\n    },\n    \"diagnostic.virtualTextFormat\": {\n      \"type\": \"string\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Define the virtual text diagnostic format, available parts: source, code, severity, message\",\n      \"default\": \"%message\"\n    },\n    \"diagnostic.virtualTextLevel\": {\n      \"type\": [\"string\", \"null\"],\n      \"scope\": \"language-overridable\",\n      \"description\": \"Filter diagnostic message in virtual text by level\",\n      \"default\": null,\n      \"enum\": [\"hint\", \"information\", \"warning\", \"error\"]\n    },\n    \"diagnostic.virtualTextLimitInOneLine\": {\n      \"type\": \"integer\",\n      \"scope\": \"language-overridable\",\n      \"minimum\": 1,\n      \"description\": \"The maximum number of diagnostic messages to display in one line\",\n      \"default\": 999\n    },\n    \"diagnostic.virtualTextLineSeparator\": {\n      \"type\": \"string\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"The text that will mark a line end from the diagnostic message\",\n      \"default\": \" \\\\ \"\n    },\n    \"diagnostic.virtualTextLines\": {\n      \"type\": \"integer\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"The number of non empty lines from a diagnostic to display\",\n      \"default\": 3\n    },\n    \"diagnostic.virtualTextPrefix\": {\n      \"type\": \"string\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"The prefix added virtual text diagnostics\",\n      \"default\": \" \"\n    },\n    \"diagnostic.virtualTextWinCol\": {\n      \"type\": [\"integer\", \"null\"],\n      \"scope\": \"language-overridable\",\n      \"description\": \"Window column number to align virtual text, neovim only.\",\n      \"default\": null\n    },\n    \"diagnostic.warningSign\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"description\": \"Text of warning sign\",\n      \"default\": \"⚠\"\n    },\n    \"diagnostic.showRelatedInformation\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"scope\": \"language-overridable\",\n      \"description\": \"Display related information in the diagnostic floating window.\"\n    },\n    \"dialog.confirmKey\": {\n      \"type\": \"string\",\n      \"default\": \"<cr>\",\n      \"scope\": \"application\",\n      \"description\": \"Confirm key for confirm selection used by menu and picker, you can always use <esc> to cancel.\"\n    },\n    \"dialog.floatBorderHighlight\": {\n      \"type\": [\"string\", \"null\"],\n      \"default\": null,\n      \"scope\": \"application\",\n      \"description\": \"Highlight group for border of dialog window/popup, default to 'CocFloatBorder'\"\n    },\n    \"dialog.floatHighlight\": {\n      \"type\": [\"string\", \"null\"],\n      \"default\": null,\n      \"scope\": \"application\",\n      \"description\": \"Highlight group for dialog window/popup, default to 'CocFloating'\"\n    },\n    \"dialog.maxHeight\": {\n      \"type\": \"integer\",\n      \"default\": 30,\n      \"scope\": \"application\",\n      \"description\": \"Maximum height of dialog window.\"\n    },\n    \"dialog.maxWidth\": {\n      \"type\": \"integer\",\n      \"default\": 80,\n      \"scope\": \"application\",\n      \"description\": \"Maximum width of dialog window.\"\n    },\n    \"dialog.pickerButtonShortcut\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"scope\": \"application\",\n      \"description\": \"Show shortcut in buttons of picker dialog window/popup, used when dialog.pickerButtons is true.\"\n    },\n    \"dialog.pickerButtons\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"scope\": \"application\",\n      \"description\": \"Show buttons for picker dialog window/popup.\"\n    },\n    \"dialog.rounded\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"scope\": \"application\",\n      \"description\": \"use rounded border for dialog window.\"\n    },\n    \"dialog.shortcutHighlight\": {\n      \"type\": \"string\",\n      \"default\": \"MoreMsg\",\n      \"scope\": \"application\",\n      \"description\": \"Highlight group for shortcut character in menu dialog, default to 'MoreMsg'\"\n    },\n    \"documentHighlight.priority\": {\n      \"type\": \"integer\",\n      \"default\": -1,\n      \"scope\": \"resource\",\n      \"description\": \"Match priority used by document highlight, see ':h matchadd'.\"\n    },\n    \"documentHighlight.limit\": {\n      \"type\": \"integer\",\n      \"default\": 200,\n      \"scope\": \"resource\",\n      \"description\": \"Limit the highlights added by matchaddpos, too many positions could cause vim slow.\"\n    },\n    \"documentHighlight.timeout\": {\n      \"type\": \"integer\",\n      \"default\": 300,\n      \"minimum\": 200,\n      \"maximum\": 5000,\n      \"scope\": \"resource\",\n      \"description\": \"Timeout for document highlight, in milliseconds.\"\n    },\n    \"editor.autocmdTimeout\": {\n      \"type\": \"integer\",\n      \"default\": 1000,\n      \"minimum\": 100,\n      \"maximum\": 5000,\n      \"scope\": \"application\",\n      \"description\": \"Timeout for execute request autocmd registered by coc extensions.\"\n    },\n    \"editor.codeActionsOnSave\": {\n      \"additionalProperties\": {\n        \"type\": [\"string\", \"boolean\"],\n        \"enum\": [\"always\", \"never\", true, false]\n      },\n      \"type\": \"object\",\n      \"default\": {},\n      \"scope\": \"language-overridable\",\n      \"description\": \"Run Code Actions for the buffer on save, normally source actions, Example: `\\\"source.organizeImports\\\": \\\"always\\\"\"\n    },\n    \"fileSystemWatch.watchmanPath\": {\n      \"type\": [\"null\", \"string\"],\n      \"scope\": \"application\",\n      \"description\": \"executable path for https://facebook.github.io/watchman/, detected from $PATH by default\",\n      \"default\": null\n    },\n    \"fileSystemWatch.enable\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"scope\": \"application\",\n      \"description\": \"Enable file system watch support for workspace folders.\"\n    },\n    \"fileSystemWatch.ignoredFolders\": {\n      \"type\": \"array\",\n      \"default\": [\"/private/tmp\", \"/\", \"${tmpdir}\"],\n      \"scope\": \"application\",\n      \"description\": \"List of folders that should not be watched for file changes, environment variables and minimatch patterns can be used.\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"floatFactory.floatConfig\": {\n      \"type\": \"object\",\n      \"scope\": \"application\",\n      \"description\": \"Configure default style float window/popup created by float factory (created around cursor and automatically closed)\",\n      \"allOf\": [{\"$ref\": \"#/definitions/floatConfig\"}],\n      \"additionalProperties\": false,\n      \"properties\": {\n        \"border\": {},\n        \"rounded\": {},\n        \"highlight\": {},\n        \"borderhighlight\": {},\n        \"title\": {},\n        \"close\": {},\n        \"maxWidth\": {},\n        \"maxHeight\": {},\n        \"winblend\": {},\n        \"focusable\": {},\n        \"shadow\": {},\n        \"position\": {},\n        \"top\": {},\n        \"bottom\": {},\n        \"left\": {},\n        \"right\": {}\n      }\n    },\n    \"hover.autoHide\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": true,\n      \"description\": \"Automatically hide hover float window on CursorMove or InsertEnter.\"\n    },\n    \"hover.floatConfig\": {\n      \"type\": \"object\",\n      \"scope\": \"application\",\n      \"description\": \"Configure float window style of hover documents.\",\n      \"allOf\": [{\"$ref\": \"#/definitions/floatConfig\"}],\n      \"additionalProperties\": false,\n      \"properties\": {\n        \"border\": {},\n        \"rounded\": {},\n        \"highlight\": {},\n        \"borderhighlight\": {},\n        \"title\": {},\n        \"close\": {},\n        \"maxHeight\": {},\n        \"maxWidth\": {},\n        \"winblend\": {},\n        \"focusable\": {},\n        \"shadow\": {},\n        \"position\": {},\n        \"top\": {},\n        \"bottom\": {},\n        \"left\": {},\n        \"right\": {}\n      }\n    },\n    \"hover.previewMaxHeight\": {\n      \"type\": \"integer\",\n      \"scope\": \"resource\",\n      \"default\": 12,\n      \"description\": \"Max height of preview window for hover.\"\n    },\n    \"hover.target\": {\n      \"type\": \"string\",\n      \"default\": \"float\",\n      \"scope\": \"resource\",\n      \"description\": \"Target to show hover information, default is floating window when possible.\",\n      \"enum\": [\"preview\", \"echo\", \"float\"]\n    },\n    \"http.proxy\": {\n      \"type\": \"string\",\n      \"default\": \"\",\n      \"pattern\": \"^https?://([^:]*(:[^@]*)?@)?([^:]+|\\\\[[:0-9a-fA-F]+\\\\])(:\\\\d+)?/?$|^$\",\n      \"description\": \"The proxy setting to use. If not set, will be inherited from the `http_proxy` and `https_proxy` environment variables.\",\n      \"scope\": \"application\"\n    },\n    \"http.proxyAuthorization\": {\n      \"type\": [\"null\", \"string\"],\n      \"description\": \"The value to send as the `Proxy-Authorization` header for every network request.\",\n      \"default\": null,\n      \"scope\": \"application\"\n    },\n    \"http.proxyCA\": {\n      \"type\": \"string\",\n      \"description\": \"CA (file) to use as Certificate Authority\",\n      \"default\": null,\n      \"scope\": \"application\"\n    },\n    \"http.proxyStrictSSL\": {\n      \"type\": \"boolean\",\n      \"description\": \"Controls whether the proxy server certificate should be verified against the list of supplied CAs\",\n      \"default\": true,\n      \"scope\": \"application\"\n    },\n    \"extensions.updateCheck\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"never\",\n      \"description\": \"Interval time for check extension update, could be daily, weekly, never\",\n      \"enum\": [\"daily\", \"weekly\", \"never\"]\n    },\n    \"extensions.recommendations\": {\n      \"type\": \"array\",\n      \"scope\": \"resource\",\n      \"description\": \"List of extensions recommended for installation in the current project\",\n      \"default\": [],\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"extensions.silentAutoupdate\": {\n      \"type\": \"boolean\",\n      \"description\": \"Not open split window with update status when performing auto update.\",\n      \"scope\": \"application\",\n      \"default\": true\n    },\n    \"extensions.updateUIInTab\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": false,\n      \"description\": \"Display extension updating UI in new vim tab\"\n    },\n    \"inlayHint.enable\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"scope\": \"language-overridable\",\n      \"description\": \"Enable inlay hint support\"\n    },\n    \"inlayHint.position\": {\n      \"type\": \"string\",\n      \"default\": \"inline\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Controls where to show inlay hints: inline in the text, or at the end of the line\",\n      \"enum\": [\"inline\", \"eol\"]\n    },\n    \"inlayHint.enableParameter\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"default\": true,\n      \"description\": \"Enable inlay hints for parameters.\"\n    },\n    \"inlayHint.display\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"default\": true,\n      \"description\": \"Display inlay hints.\"\n    },\n    \"inlayHint.filetypes\": {\n      \"type\": [\"array\", \"null\"],\n      \"scope\": \"application\",\n      \"description\": \"Filetypes that enable inlayHint, all filetypes are enabled by default\",\n      \"deprecationMessage\": \"Use inlayHint.enable with language scope instead, see :h coc-configuration-scope\",\n      \"default\": null,\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"inlayHint.refreshOnInsertMode\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"scope\": \"language-overridable\",\n      \"description\": \"Refresh inlayHints on insert mode.\"\n    },\n    \"inlayHint.maximumLength\": {\n      \"type\": \"integer\",\n      \"default\": 0,\n      \"minimum\": 0,\n      \"scope\": \"language-overridable\",\n      \"description\": \"Maximum overall length of inlay hints, for a single line, before they get truncated by the editor. Set to `0`to disable truncation.\"\n    },\n    \"links.enable\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Enable document links\",\n      \"default\": true\n    },\n    \"links.tooltip\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"description\": \"Show tooltip of link under cursor on CursorHold.\",\n      \"default\": false\n    },\n    \"links.highlight\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"description\": \"Use CocLink highlight group to highlight links\",\n      \"default\": false\n    },\n    \"list.floatPreview\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"scope\": \"application\",\n      \"description\": \"Enable preview with float window/popup, default: `false`\"\n    },\n    \"list.alignColumns\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"scope\": \"application\",\n      \"description\": \"Whether to align lists in columns, default: `false`\"\n    },\n    \"list.extendedSearchMode\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": true,\n      \"description\": \"Enable extended search mode which allows multiple search patterns delimited by spaces.\"\n    },\n    \"list.height\": {\n      \"type\": \"integer\",\n      \"scope\": \"application\",\n      \"default\": 10,\n      \"description\": \"Height of split list window.\"\n    },\n    \"list.indicator\": {\n      \"type\": \"string\",\n      \"default\": \">\",\n      \"scope\": \"application\",\n      \"description\": \"The character used as first character in prompt line.\"\n    },\n    \"list.insertMappings\": {\n      \"type\": \"object\",\n      \"scope\": \"application\",\n      \"default\": {},\n      \"description\": \"Custom keymappings on insert mode.\"\n    },\n    \"list.interactiveDebounceTime\": {\n      \"type\": \"integer\",\n      \"default\": 100,\n      \"scope\": \"application\",\n      \"description\": \"Debounce time for input change on interactive mode.\"\n    },\n    \"list.limitLines\": {\n      \"type\": [\"number\", \"null\"],\n      \"scope\": \"application\",\n      \"default\": null,\n      \"description\": \"Limit lines for list buffer.\"\n    },\n    \"list.maxPreviewHeight\": {\n      \"type\": \"integer\",\n      \"scope\": \"application\",\n      \"default\": 12,\n      \"description\": \"Max height for preview window of list.\"\n    },\n    \"list.menuAction\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"scope\": \"application\",\n      \"description\": \"Use menu picker instead of confirm() for choose action.\"\n    },\n    \"list.nextKeymap\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"<C-j>\",\n      \"description\": \"Key used for select next line on insert mode.\"\n    },\n    \"list.normalMappings\": {\n      \"type\": \"object\",\n      \"scope\": \"application\",\n      \"default\": {},\n      \"description\": \"Custom keymappings on normal mode.\"\n    },\n    \"list.previewHighlightGroup\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"Search\",\n      \"description\": \"Highlight group used for highlight the range in preview window.\"\n    },\n    \"list.previewSplitRight\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": false,\n      \"description\": \"Use vsplit for preview window.\"\n    },\n    \"list.previewToplineOffset\": {\n      \"type\": \"integer\",\n      \"scope\": \"application\",\n      \"default\": 3,\n      \"description\": \"Topline offset for list previews\"\n    },\n    \"list.previewToplineStyle\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"offset\",\n      \"description\": \"Topline style for list previews\",\n      \"enum\": [\"offset\", \"middle\"]\n    },\n    \"list.previousKeymap\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"<C-k>\",\n      \"description\": \"Key used for select previous line on insert mode.\"\n    },\n    \"list.selectedSignText\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"*\",\n      \"description\": \"Sign text for selected lines.\"\n    },\n    \"list.signOffset\": {\n      \"type\": \"integer\",\n      \"scope\": \"application\",\n      \"default\": 900,\n      \"description\": \"Sign offset of list, should be different from other plugins.\"\n    },\n    \"list.smartCase\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"scope\": \"application\",\n      \"description\": \"Use smartcase match for fuzzy match and strict match, --ignore-case will be ignored, may not affect interactive list.\"\n    },\n    \"list.source.diagnostics.includeCode\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"description\": \"Whether to show the diagnostic code in the list.\",\n      \"default\": true\n    },\n    \"list.source.diagnostics.pathFormat\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"description\": \"Decide how the filepath is shown in the list.\",\n      \"enum\": [\"full\", \"short\", \"filename\", \"hidden\"],\n      \"default\": \"full\"\n    },\n    \"list.source.outline.ctagsFiletypes\": {\n      \"type\": \"array\",\n      \"scope\": \"application\",\n      \"default\": [],\n      \"description\": \"Filetypes that should use ctags for outline instead of language server.\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"list.source.symbols.excludes\": {\n      \"type\": \"array\",\n      \"scope\": \"application\",\n      \"default\": [],\n      \"description\": \"Patterns of minimatch for filepath to exclude from symbols list.\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"list.statusLineSegments\": {\n      \"type\": [\"array\", \"null\"],\n      \"scope\": \"application\",\n      \"default\": [\n        \"%#CocListMode#-- %{coc#list#status(\\\"mode\\\")} --%*\",\n        \"%{coc#list#status(\\\"loading\\\")}\",\n        \"%{coc#list#status(\\\"args\\\")}\",\n        \"(%L/%{coc#list#status(\\\"total\\\")})\",\n        \"%=\",\n        \"%#CocListPath# %{coc#list#status(\\\"cwd\\\")} %l/%L%*\"\n      ],\n      \"items\": {\n        \"types\": \"string\"\n      },\n      \"description\": \"An array of statusline segments that will be used to draw the status line for list windows.\"\n    },\n    \"notification.statusLineProgress\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"scope\": \"application\",\n      \"description\": \"Show progress notification in status line, instead of float window/popup.\"\n    },\n    \"notification.border\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"scope\": \"application\",\n      \"description\": \"Enable rounded border for notification windows.\"\n    },\n    \"notification.disabledProgressSources\": {\n      \"type\": \"array\",\n      \"default\": [],\n      \"scope\": \"application\",\n      \"description\": \"Sources that should be disabled for message progress, use * to disable all message only progresses\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"notification.focusable\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"scope\": \"application\",\n      \"description\": \"Enable focus by user actions (wincmds, mouse events), neovim only.\"\n    },\n    \"notification.highlightGroup\": {\n      \"type\": \"string\",\n      \"default\": \"Normal\",\n      \"scope\": \"application\",\n      \"description\": \"Highlight group of notification dialog.\"\n    },\n    \"notification.marginRight\": {\n      \"type\": \"integer\",\n      \"default\": 10,\n      \"scope\": \"application\",\n      \"description\": \"Margin right to the right of editor window.\"\n    },\n    \"notification.maxHeight\": {\n      \"type\": \"integer\",\n      \"default\": 10,\n      \"scope\": \"application\",\n      \"description\": \"Maximum content height of notification dialog.\"\n    },\n    \"notification.maxWidth\": {\n      \"type\": \"integer\",\n      \"default\": 60,\n      \"scope\": \"application\",\n      \"description\": \"Maximum content width of notification dialog.\"\n    },\n    \"notification.minProgressWidth\": {\n      \"type\": \"integer\",\n      \"default\": 30,\n      \"scope\": \"application\",\n      \"description\": \"Minimal with of progress notification.\"\n    },\n    \"notification.timeout\": {\n      \"type\": \"integer\",\n      \"default\": 10000,\n      \"scope\": \"application\",\n      \"description\": \"Timeout for auto close notifications, in milliseconds.\"\n    },\n    \"notification.winblend\": {\n      \"type\": \"integer\",\n      \"default\": 30,\n      \"minimum\": 0,\n      \"maximum\": 100,\n      \"scope\": \"application\",\n      \"description\": \"Winblend option of notification window, neovim only.\"\n    },\n    \"npm.binPath\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"npm\",\n      \"description\": \"Command or absolute path to npm or yarn.\"\n    },\n    \"outline.autoHide\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": false,\n      \"description\": \"Automatically close the outline window when an item is clicked.\"\n    },\n    \"outline.autoPreview\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": false,\n      \"description\": \"Enable auto preview on cursor move.\"\n    },\n    \"outline.autoWidth\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": true,\n      \"description\": \"Automatically increase window width to avoid wrapped lines.\"\n    },\n    \"outline.checkBufferSwitch\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": true,\n      \"description\": \"Recreate outline view after user changed to another buffer on current tab.\"\n    },\n    \"outline.codeActionKinds\": {\n      \"type\": \"array\",\n      \"scope\": \"application\",\n      \"default\": [\"\", \"quickfix\", \"refactor\"],\n      \"description\": \"Filter code actions in actions menu by kinds.\",\n      \"items\": {\n        \"type\": \"string\",\n        \"enum\": [\"\", \"quickfix\", \"refactor\", \"source\"]\n      }\n    },\n    \"outline.detailAsDescription\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": true,\n      \"description\": \"Show detail as description aside with label, when false detail will be shown in tooltip on cursor hold.\"\n    },\n    \"outline.expandLevel\": {\n      \"type\": \"integer\",\n      \"scope\": \"application\",\n      \"default\": 1,\n      \"description\": \"Expand level of tree nodes.\"\n    },\n    \"outline.followCursor\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": true,\n      \"description\": \"Reveal item in outline tree on cursor hold.\"\n    },\n    \"outline.keepWindow\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": false,\n      \"description\": \"Jump back to original window after outline is shown.\"\n    },\n    \"outline.previewBorder\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": true,\n      \"description\": \"Use border for preview window.\"\n    },\n    \"outline.previewBorderHighlightGroup\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"Normal\",\n      \"description\": \"Border highlight group of preview window.\"\n    },\n    \"outline.previewBorderRounded\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": false,\n      \"description\": \"Use rounded border for preview window.\"\n    },\n    \"outline.previewHighlightGroup\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"Normal\",\n      \"description\": \"Highlight group of preview window.\"\n    },\n    \"outline.previewMaxWidth\": {\n      \"type\": \"integer\",\n      \"scope\": \"application\",\n      \"default\": 80,\n      \"description\": \"Max width of preview window.\"\n    },\n    \"outline.previewWinblend\": {\n      \"type\": \"integer\",\n      \"scope\": \"application\",\n      \"default\": 0,\n      \"minimum\": 0,\n      \"maximum\": 100,\n      \"description\": \"Enables pseudo-transparency by set 'winblend' option of window, neovim only.\"\n    },\n    \"outline.showLineNumber\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": true,\n      \"description\": \"Show line number of symbols.\"\n    },\n    \"outline.sortBy\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"category\",\n      \"description\": \"Sort method for symbols.\",\n      \"enum\": [\"position\", \"name\", \"category\"]\n    },\n    \"outline.splitCommand\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"botright 30vs\",\n      \"description\": \"Window split command used by outline.\"\n    },\n    \"outline.switchSortKey\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"<C-s>\",\n      \"description\": \"The key used to switch sort method for symbols provider of current tree view.\"\n    },\n    \"outline.togglePreviewKey\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"p\",\n      \"description\": \"The key used to toggle auto preview feature.\"\n    },\n    \"pullDiagnostic.ignored\": {\n      \"type\": \"array\",\n      \"default\": [],\n      \"scope\": \"application\",\n      \"description\": \"Minimatch patterns to match full filepath that should be ignored for pullDiagnostic.\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"pullDiagnostic.onChange\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"scope\": \"language-overridable\",\n      \"description\": \"Whether to pull for diagnostics on document change.\"\n    },\n    \"pullDiagnostic.onSave\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"scope\": \"language-overridable\",\n      \"description\": \"Whether to pull for diagnostics on document save.\"\n    },\n    \"pullDiagnostic.workspace\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"scope\": \"application\",\n      \"description\": \"Whether to pull for workspace diagnostics when possible.\"\n    },\n    \"refactor.afterContext\": {\n      \"type\": \"integer\",\n      \"scope\": \"application\",\n      \"default\": 3,\n      \"description\": \"Print num lines of trailing context after each match.\"\n    },\n    \"refactor.beforeContext\": {\n      \"type\": \"integer\",\n      \"scope\": \"application\",\n      \"default\": 3,\n      \"description\": \"Print num lines of leading context before each match.\"\n    },\n    \"refactor.openCommand\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"description\": \"Open command for refactor window.\",\n      \"default\": \"vsplit\"\n    },\n    \"refactor.saveToFile\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"description\": \"Save changed buffer to file when write refactor buffer with ':noa wa' command.\",\n      \"default\": true\n    },\n    \"refactor.showMenu\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"<Tab>\",\n      \"description\": \"Refactor buffer local mapping to bring up menu for this chunk.\"\n    },\n    \"semanticTokens.combinedModifiers\": {\n      \"type\": \"array\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Semantic token modifiers that should have highlight combined with syntax highlights.\",\n      \"default\": [\"deprecated\"],\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"semanticTokens.enable\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"scope\": \"language-overridable\",\n      \"description\": \"Enable semantic tokens support\"\n    },\n    \"semanticTokens.filetypes\": {\n      \"type\": [\"array\", \"null\"],\n      \"scope\": \"resource\",\n      \"description\": \"Filetypes that enable semantic tokens highlighting or [\\\"*\\\"] for any filetype\",\n      \"deprecationMessage\": \"Use semanticTokens.enable configuration with language scope instead, see :h coc-configuration-scope\",\n      \"default\": null,\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"semanticTokens.highlightPriority\": {\n      \"type\": \"integer\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Priority for semantic tokens highlight.\",\n      \"default\": 2048,\n      \"maximum\": 4096\n    },\n    \"semanticTokens.incrementTypes\": {\n      \"type\": \"array\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Semantic token types that should increase highlight when insert at the start and end position of token.\",\n      \"default\": [\"variable\", \"string\", \"parameter\"],\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"signature.enable\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Enable show signature help when trigger character typed.\",\n      \"default\": true\n    },\n    \"signature.floatConfig\": {\n      \"type\": \"object\",\n      \"scope\": \"application\",\n      \"description\": \"Configure float window style of signature documents.\",\n      \"allOf\": [{\"$ref\": \"#/definitions/floatConfig\"}],\n      \"additionalProperties\": false,\n      \"properties\": {\n        \"border\": {},\n        \"rounded\": {},\n        \"highlight\": {},\n        \"borderhighlight\": {},\n        \"title\": {},\n        \"close\": {},\n        \"maxHeight\": {},\n        \"maxWidth\": {},\n        \"winblend\": {},\n        \"focusable\": {},\n        \"shadow\": {},\n        \"position\": {},\n        \"top\": {},\n        \"bottom\": {},\n        \"left\": {},\n        \"right\": {}\n      }\n    },\n    \"signature.hideOnTextChange\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Hide signature float window when text changed on insert mode.\",\n      \"default\": false\n    },\n    \"signature.preferShownAbove\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"description\": \"Show signature help float window above cursor when possible, require restart service on change.\",\n      \"default\": true\n    },\n    \"signature.target\": {\n      \"type\": \"string\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Target of signature help, use float when possible by default.\",\n      \"default\": \"float\",\n      \"enum\": [\"float\", \"echo\"]\n    },\n    \"signature.triggerSignatureWait\": {\n      \"type\": \"integer\",\n      \"scope\": \"language-overridable\",\n      \"default\": 500,\n      \"minimum\": 200,\n      \"maximum\": 1000,\n      \"description\": \"Timeout for trigger signature help, in milliseconds.\"\n    },\n    \"snippet.highlight\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"description\": \"Use highlight group 'CocSnippetVisual' to highlight placeholders with same index of current one.\",\n      \"default\": false\n    },\n    \"snippet.nextPlaceholderOnDelete\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"description\": \"Automatically jump to the next placeholder when the current one is completely deleted.\",\n      \"default\": false\n    },\n    \"snippet.statusText\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"SNIP\",\n      \"description\": \"Text shown in statusline to indicate snippet session is activated.\"\n    },\n    \"suggest.acceptSuggestionOnCommitCharacter\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"scope\": \"language-overridable\",\n      \"description\": \"Controls whether suggestions should be accepted on commit characters. For example, in JavaScript, the semi-colon (`;`) can be a commit character that accepts a suggestion and types that character.\"\n    },\n    \"suggest.asciiCharactersOnly\": {\n      \"type\": \"boolean\",\n      \"description\": \"Trigger suggest with ASCII characters only\",\n      \"scope\": \"language-overridable\",\n      \"default\": false\n    },\n    \"suggest.segmenterLocales\": {\n      \"type\": [\"string\", \"null\"],\n      \"default\": \"\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Locales used for divide sentence into segments for around and buffer source, works when NodeJS built with intl support, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter/Segmenter#parameters, default empty string means auto detect, use null to disable this feature.\"\n    },\n    \"suggest.asciiMatch\": {\n      \"type\": \"boolean\",\n      \"description\": \"Convert unicode characters to ascii for match\",\n      \"scope\": \"language-overridable\",\n      \"default\": true\n    },\n    \"suggest.autoTrigger\": {\n      \"type\": \"string\",\n      \"scope\": \"language-overridable\",\n      \"default\": \"always\",\n      \"description\": \"How should completion be triggered\",\n      \"enum\": [\"always\", \"trigger\", \"none\"]\n    },\n    \"suggest.reTriggerAfterIndent\": {\n      \"type\": \"boolean\",\n      \"description\": \"Re-Trigger completion after indent changes\",\n      \"scope\": \"language-overridable\",\n      \"default\": true\n    },\n    \"suggest.completionItemKindLabels\": {\n      \"type\": \"object\",\n      \"default\": {},\n      \"scope\": \"application\",\n      \"description\": \"Set custom labels to completion items' kinds.\",\n      \"properties\": {\n        \"text\": {\"type\": \"string\"},\n        \"method\": {\"type\": \"string\"},\n        \"function\": {\"type\": \"string\"},\n        \"constructor\": {\"type\": \"string\"},\n        \"field\": {\"type\": \"string\"},\n        \"variable\": {\"type\": \"string\"},\n        \"class\": {\"type\": \"string\"},\n        \"interface\": {\"type\": \"string\"},\n        \"module\": {\"type\": \"string\"},\n        \"property\": {\"type\": \"string\"},\n        \"unit\": {\"type\": \"string\"},\n        \"value\": {\"type\": \"string\"},\n        \"enum\": {\"type\": \"string\"},\n        \"keyword\": {\"type\": \"string\"},\n        \"snippet\": {\"type\": \"string\"},\n        \"color\": {\"type\": \"string\"},\n        \"file\": {\"type\": \"string\"},\n        \"reference\": {\"type\": \"string\"},\n        \"folder\": {\"type\": \"string\"},\n        \"enumMember\": {\"type\": \"string\"},\n        \"constant\": {\"type\": \"string\"},\n        \"struct\": {\"type\": \"string\"},\n        \"event\": {\"type\": \"string\"},\n        \"operator\": {\"type\": \"string\"},\n        \"typeParameter\": {\"type\": \"string\"},\n        \"default\": {\"type\": \"string\"}\n      },\n      \"additionalProperties\": false\n    },\n    \"suggest.defaultSortMethod\": {\n      \"type\": \"string\",\n      \"description\": \"Default sorting behavior for suggested completion items.\",\n      \"default\": \"length\",\n      \"scope\": \"language-overridable\",\n      \"enum\": [\"length\", \"alphabetical\", \"none\"]\n    },\n    \"suggest.detailField\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"preview\",\n      \"description\": \"Where to show the detail text of CompleteItem from LS.\",\n      \"enum\": [\"abbr\", \"preview\"]\n    },\n    \"suggest.detailMaxLength\": {\n      \"type\": \"integer\",\n      \"scope\": \"application\",\n      \"description\": \"Max length of detail that should be shown in popup menu.\",\n      \"deprecationMessage\": \"Use suggest.labelMaxLength instead.\",\n      \"default\": 100\n    },\n    \"suggest.enableFloat\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Enable float window with documentation aside with popupmenu.\",\n      \"default\": true\n    },\n    \"suggest.enablePreselect\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"description\": \"Enable preselect feature of LSP, works when suggest.noselect is false.\",\n      \"default\": true\n    },\n    \"suggest.filterGraceful\": {\n      \"type\": \"boolean\",\n      \"description\": \"Controls whether filtering and sorting suggestions accounts for small typos.\",\n      \"scope\": \"language-overridable\",\n      \"default\": true\n    },\n    \"suggest.filterOnBackspace\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"description\": \"Filter complete items on backspace.\",\n      \"default\": true\n    },\n    \"suggest.floatConfig\": {\n      \"type\": \"object\",\n      \"scope\": \"application\",\n      \"description\": \"Configure style of popup menu and documentation window of completion.\",\n      \"allOf\": [{\"$ref\": \"#/definitions/floatConfig\"}],\n      \"additionalProperties\": false,\n      \"properties\": {\n        \"border\": {},\n        \"rounded\": {},\n        \"highlight\": {},\n        \"borderhighlight\": {},\n        \"title\": {},\n        \"maxWidth\": {},\n        \"winblend\": {},\n        \"shadow\": {}\n      }\n    },\n    \"suggest.formatItems\": {\n      \"type\": \"array\",\n      \"scope\": \"application\",\n      \"items\": {\n        \"enum\": [\"abbr\", \"menu\", \"kind\", \"shortcut\"]\n      },\n      \"contains\": {\n        \"enum\": [\"abbr\"]\n      },\n      \"uniqueItems\": true,\n      \"description\": \"Items shown in popup menu in order.\",\n      \"default\": [\"abbr\", \"menu\", \"kind\", \"shortcut\"]\n    },\n    \"suggest.highPrioritySourceLimit\": {\n      \"type\": \"integer\",\n      \"minimum\": 1,\n      \"maximum\": 100,\n      \"scope\": \"language-overridable\",\n      \"description\": \"Max items count for source priority bigger than or equal to 90.\"\n    },\n    \"suggest.insertMode\": {\n      \"type\": \"string\",\n      \"scope\": \"language-overridable\",\n      \"default\": \"replace\",\n      \"description\": \"Controls whether words are overwritten when accepting completions.\",\n      \"enum\": [\"insert\", \"replace\"]\n    },\n    \"suggest.ignoreRegexps\": {\n      \"type\": \"array\",\n      \"scope\": \"language-overridable\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"description\": \"Regexps to ignore when trigger suggest\",\n      \"default\": []\n    },\n    \"suggest.invalidInsertCharacters\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"scope\": \"application\",\n      \"description\": \"Invalid character for strip valid word when inserting text of complete item.\",\n      \"default\": [\"\\r\", \"\\n\"]\n    },\n    \"suggest.labelMaxLength\": {\n      \"type\": \"integer\",\n      \"scope\": \"application\",\n      \"description\": \"Max length of abbr that shown as label of complete item.\",\n      \"default\": 200\n    },\n    \"suggest.languageSourcePriority\": {\n      \"type\": \"integer\",\n      \"default\": 99,\n      \"scope\": \"language-overridable\",\n      \"description\": \"Priority of language sources.\"\n    },\n    \"suggest.localityBonus\": {\n      \"type\": \"boolean\",\n      \"description\": \"Controls whether sorting favors words that appear close to the cursor.\",\n      \"scope\": \"language-overridable\",\n      \"default\": true\n    },\n    \"suggest.lowPrioritySourceLimit\": {\n      \"type\": \"integer\",\n      \"minimum\": 1,\n      \"maximum\": 100,\n      \"scope\": \"language-overridable\",\n      \"description\": \"Max items count for source priority lower than 90.\"\n    },\n    \"suggest.maxCompleteItemCount\": {\n      \"type\": \"integer\",\n      \"default\": 256,\n      \"scope\": \"language-overridable\",\n      \"description\": \"Maximum number of complete items shown in vim\"\n    },\n    \"suggest.minTriggerInputLength\": {\n      \"type\": \"integer\",\n      \"default\": 1,\n      \"scope\": \"language-overridable\",\n      \"description\": \"Minimal input length for trigger completion, default 1\"\n    },\n    \"suggest.noselect\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"description\": \"Not make vim select first item on popupmenu shown\",\n      \"default\": false\n    },\n    \"suggest.preferCompleteThanJumpPlaceholder\": {\n      \"type\": \"boolean\",\n      \"description\": \"Confirm completion instead of jump to next placeholder when completion is activated.\",\n      \"scope\": \"resource\",\n      \"default\": false\n    },\n    \"suggest.pumFloatConfig\": {\n      \"type\": [\"object\", \"null\"],\n      \"scope\": \"application\",\n      \"description\": \"Configure style of popup menu, suggest.floatConfig is used when not specified.\",\n      \"allOf\": [{\"$ref\": \"#/definitions/floatConfig\"}],\n      \"additionalProperties\": false,\n      \"default\": null,\n      \"properties\": {\n        \"title\": {},\n        \"border\": {},\n        \"rounded\": {},\n        \"highlight\": {},\n        \"borderhighlight\": {},\n        \"winblend\": {},\n        \"shadow\": {}\n      }\n    },\n    \"suggest.removeDuplicateItems\": {\n      \"type\": \"boolean\",\n      \"description\": \"Remove completion items with duplicated word for all sources, snippet items are excluded.\",\n      \"scope\": \"language-overridable\",\n      \"default\": false\n    },\n    \"suggest.removeCurrentWord\": {\n      \"type\": \"boolean\",\n      \"description\": \"Remove word item (from around and buffer source) that is identical to current input\",\n      \"scope\": \"language-overridable\",\n      \"default\": false\n    },\n    \"suggest.reversePumAboveCursor\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"description\": \"Reverse order of complete items when pum shown above cursor.\",\n      \"default\": false\n    },\n    \"suggest.selection\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"first\",\n      \"description\": \"Controls how suggestions are pre-selected when showing the suggest list.\",\n      \"enum\": [\"first\", \"recentlyUsed\", \"recentlyUsedByPrefix\"]\n    },\n    \"suggest.snippetIndicator\": {\n      \"type\": \"string\",\n      \"default\": \"~\",\n      \"scope\": \"application\",\n      \"description\": \"The character used in abbr of complete item to indicate the item could be expand as snippet.\"\n    },\n    \"suggest.snippetsSupport\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Set to false to disable snippets support of completion.\",\n      \"default\": true\n    },\n    \"suggest.timeout\": {\n      \"type\": \"integer\",\n      \"default\": 5000,\n      \"minimum\": 500,\n      \"maximum\": 15000,\n      \"scope\": \"language-overridable\",\n      \"description\": \"Timeout for completion, in milliseconds.\"\n    },\n    \"suggest.triggerAfterInsertEnter\": {\n      \"type\": \"boolean\",\n      \"description\": \"Trigger completion after InsertEnter, auto trigger should be 'always' to enable this option\",\n      \"scope\": \"language-overridable\",\n      \"default\": false\n    },\n    \"suggest.triggerCompletionWait\": {\n      \"type\": \"integer\",\n      \"default\": 0,\n      \"minimum\": 0,\n      \"maximum\": 10,\n      \"scope\": \"language-overridable\",\n      \"description\": \"Wait time between text change and completion start, completion is canceled when text changed during wait.\"\n    },\n    \"suggest.virtualText\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"description\": \"Show virtual text for insert word of the selected item if any\",\n      \"default\": false\n    },\n    \"inlineSuggest.autoTrigger\": {\n      \"type\": \"boolean\",\n      \"scope\": \"language-overridable\",\n      \"description\": \"Enable automatically trigger inline completion on insert mode cursor hold.\",\n      \"default\": true\n    },\n    \"inlineSuggest.triggerCompletionWait\": {\n      \"type\": \"integer\",\n      \"default\": 0,\n      \"minimum\": 0,\n      \"maximum\": 1000,\n      \"scope\": \"language-overridable\",\n      \"description\": \"Wait time in milliseconds between text change and trigger inline completion.\"\n    },\n    \"tree.closedIcon\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"+\",\n      \"description\": \"Closed icon of tree view.\"\n    },\n    \"tree.key.actions\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"<tab>\",\n      \"description\": \"Trigger key to invoke actions.\"\n    },\n    \"tree.key.activeFilter\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"f\",\n      \"description\": \"Trigger key active filter.\"\n    },\n    \"tree.key.close\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"<esc>\",\n      \"description\": \"Trigger key to dispose the tree and close tree window.\"\n    },\n    \"tree.key.collapseAll\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"M\",\n      \"description\": \"Trigger key to collapse all tree node.\"\n    },\n    \"tree.key.invoke\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"<cr>\",\n      \"description\": \"Trigger key to invoke default command of current node or selection.\"\n    },\n    \"tree.key.selectNext\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"<C-j>\",\n      \"description\": \"Trigger key to select next item during filter.\"\n    },\n    \"tree.key.selectPrevious\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"<C-k>\",\n      \"description\": \"Trigger key to select previous item during filter.\"\n    },\n    \"tree.key.toggle\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"t\",\n      \"description\": \"Trigger key to toggle expand state of tree node, does nothing with leaf node.\"\n    },\n    \"tree.key.toggleSelection\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"<space>\",\n      \"description\": \"Trigger key to select/unselect item\"\n    },\n    \"tree.openedIcon\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"-\",\n      \"description\": \"Opened icon of tree view.\"\n    },\n    \"typeHierarchy.enableTooltip\": {\n      \"type\": \"boolean\",\n      \"scope\": \"application\",\n      \"default\": true,\n      \"description\": \"Enable tooltip to show relative filepath of type hierarchy.\"\n    },\n    \"typeHierarchy.openCommand\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"edit\",\n      \"description\": \"Open command for type hierarchy tree view.\"\n    },\n    \"typeHierarchy.splitCommand\": {\n      \"type\": \"string\",\n      \"scope\": \"application\",\n      \"default\": \"botright 30vs\",\n      \"description\": \"Window split command used by type hierarchy tree view.\"\n    },\n    \"workspace.rootPatterns\": {\n      \"type\": \"array\",\n      \"default\": [\".git\", \".hg\", \".projections.json\"],\n      \"scope\": \"application\",\n      \"description\": \"Root patterns to resolve workspaceFolder from parent folders of opened files, resolved from up to down.\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"workspace.bottomUpFiletypes\": {\n      \"type\": \"array\",\n      \"default\": [],\n      \"scope\": \"application\",\n      \"description\": \"Filetypes that should have workspace folder should resolved from base directory of file, or [\\\"*\\\"] for any filetype.\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"workspace.ignoredFiletypes\": {\n      \"type\": \"array\",\n      \"default\": [],\n      \"scope\": \"application\",\n      \"description\": \"Filetypes that should be ignored for workspace folder resolve.\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"workspace.ignoredFolders\": {\n      \"type\": \"array\",\n      \"default\": [\"$HOME\", \"$HOME/.cargo/**\", \"$HOME/.rustup/**\", \"$HOME/pkg/mod/**\", \"$HOMEBREW_PREFIX/**\"],\n      \"scope\": \"application\",\n      \"description\": \"List of folders that should not be resolved as workspace folder, environment variables and minimatch patterns can be used.\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"workspace.openOutputCommand\": {\n      \"type\": \"string\",\n      \"default\": \"vs\",\n      \"scope\": \"resource\",\n      \"description\": \"Command used to open output channel.\"\n    },\n    \"workspace.openResourceCommand\": {\n      \"type\": \"string\",\n      \"default\": \"tab drop\",\n      \"scope\": \"application\",\n      \"description\": \"Command to open files that not loaded, load files as hidden buffers when empty.\"\n    },\n    \"workspace.workspaceFolderCheckCwd\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"scope\": \"application\",\n      \"description\": \"Whether the current working directory should be used first when checking patterns match for workspace folder.\"\n    },\n    \"workspace.workspaceFolderFallbackCwd\": {\n      \"type\": \"boolean\",\n      \"default\": true,\n      \"scope\": \"application\",\n      \"description\": \"Use current working directory as workspace folder when no root patterns resolved.\"\n    },\n    \"workspace.removeEmptyWorkspaceFolder\": {\n      \"type\": \"boolean\",\n      \"default\": false,\n      \"scope\": \"application\",\n      \"description\": \"Automatically remove the workspace folder when no buffer associated with it.\"\n    },\n    \"languageserver\": {\n      \"type\": \"object\",\n      \"default\": {},\n      \"scope\": \"resource\",\n      \"description\": \"Dictionary of languageservers, key is used as id of languageserver, restart coc.nvim required after change.\",\n      \"patternProperties\": {\n        \"^[_a-zA-Z]+$\": {\n          \"oneOf\": [\n            {\n              \"$ref\": \"#/definitions/languageServerModule\"\n            },\n            {\n              \"$ref\": \"#/definitions/languageServerCommand\"\n            },\n            {\n              \"$ref\": \"#/definitions/languageServerSocket\"\n            }\n          ]\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "doc/coc-api.txt",
    "content": "*coc-api.txt*                                  NodeJS client for Vim & Neovim.\n\nCONTENTS\n\nVim sources \t\t\t\t\t|coc-api-vim-source|\nExtension introduction \t\t\t\t|coc-api-intro|\nExtension package json \t\t\t\t|coc-api-json|\nSingle file extensions \t\t\t\t|coc-api-single|\nCreate custom Extensions \t\t\t|coc-api-extension|\nDebug extensions \t\t\t\t|coc-api-debug|\n\n==============================================================================\n\nThe guide for extend coc.nvim by create vim completion sources and coc.nvim\nextensions.\n\n------------------------------------------------------------------------------\nVIM SOURCES \t\t\t\t\t*coc-api-vim-source*\n\nDuring initialization, coc.nvim searches vim's |runtimepath| for file pattern\n`autoload/coc/source/${name}.vim`, matched files would be loaded as vim\ncompletion sources.\n\nNote: LSP completion features like `TextEdit`, `additionalTextEdits`,\n`command` are not supported by vim sources, use the NodeJS API\n`languages.registerCompletionItemProvider` for LSP completion.\n\nFor example, create a file `autoload/coc/source/email.vim` inside your plugin\nfolder.  With code:\n>\n  \" vim source for emails\n  function! coc#source#email#init() abort\n    return {\n      \\ 'priority': 9,\n      \\ 'shortcut': 'Email',\n      \\ 'triggerCharacters': ['@']\n      \\}\n  endfunction\n\n  function! coc#source#email#complete(option, cb) abort\n    let items = ['foo@gmail.com', 'bar@yahoo.com']\n    call a:cb(items)\n  endfunction\n<\n`init` and `complete` are required functions for vim sources, error message\nwill be shown when not exists.\n\nvim9script can be also used on vim9 (not supported on neovim), the function\nfirst letter need to be uppercased, like:\n>\n  vim9script\n  export def Init(): dict<any>\n    return {\n      priority: 9,\n      shortcut: 'Email',\n      triggerCharacters: ['@']\n    }\n  enddef\n\n  export def Complete(option: dict<any>, Callback: func(list<any>))\n    const items = ['foo@gmail.com', 'bar@yahoo.com']\n    Callback(items)\n  enddef\n<\nSource option: ~\n\n  The source option object is returned by `coc#source#{name}#init`\n  function, available properties:\n\n  • shortcut: The shortcut characters shown in popup menu, first three\n    characters from the source name would be used when not exists.\n  • priority: The priority of source, default to `9`.\n  • filetypes: Array of filetype names this source should be triggered\n    by. Available for all filetypes when not exists.\n  • firstMatch: When is truthy value, only the completion item that has the\n    first letter matching the user input will be shown.\n  • triggerCharacters: Trigger characters for this source, default to `[]`.\n  • triggerOnly: The source should only be triggered by trigger characters,\n    when trigger characters is false or empty, the source would only be\n    triggered by api |coc#start()|.\n  • isSnippet: All complete items returned by `complete` are snippets,\n    which would have snippet indicator text added to the label in popup\n    menu.  The \"isSnippet\" property of completion item override this\n    option.\n\n  All options are optional.\n\nSource configurations: ~\n\n  Vim sources register |coc-configuration| for allow the user to customize the\n  source behavior.\n\n  • `coc.source.${name}.enable` Enable the source, default to `true`.\n  • `coc.source.${name}.disableSyntaxes` Disabled syntax names when trigger\n    completion.\n  • `coc.source.${name}.firstMatch` Default to \"firstMatch\" of source option.\n  • `coc.source.${name}.priority` Default to \"priority\" of source option.\n  • `coc.source.${name}.shortcut` Default to \"shortcut\" of source option.\n  • `coc.source.${name}.filetypes` Default to \"filetypes\" of source option.\n\nComplete function: ~\n\n  The complete function is called with complete option as the first argument\n  and a callback function as the second argument, the callback function should\n  be called with list of complete item or `v:null` synchronously or\n  asynchronously.\n\n  Note: synchronously compute complete items blocks vim's operation.\n  Note: Error during completion is not thrown, use |:CocOpenLog| to check the\n  error log.\n\n  Complete option have following properties:\n\n  • bufnr: Current buffer number.\n  • line: Content line when trigger completion.\n  • col: Start col of completion, start col of the keywords before cursor by\n    default, 0 based.\n  • input: Input text between start col and cursor col.\n  • filetype: Filetype of current buffer.\n  • filepath: Fullpath of current buffer.\n  • changedtick: b:changedtick value when trigger completion.\n  • triggerCharacter: The character which trigger the completion, could be\n    empty string.\n  • colnr: Cursor col when trigger completion, 1 based.\n  • linenr: Line number of cursor, 1 based.\n\n  Complete items extends vim's |complete-items| with the following properties:\n\n  • deprecated: The complete item would be rendered with strike through\n    highlight when truthy.\n  • labelDetails: Additional details for a completion item label, which have\n    optional `detail` and/or `description` text.\n  • sortText: A string that should be used when comparing this item with other\n    items, word is used when not exists.\n  • filterText: A string that should be used when filtering a set of\n    complete items, word is used when not exists.\n  • insertText: The text to insert, could be textmate snippet text, word is\n    used when not exists.\n  • isSnippet: The text to insert is snippet when is truthy value, when\n    truthy and `on_complete` not provided by vim source, the `insertText` is\n    expanded as textmate snippet when confirm completion.\n  • documentation: Array of `Documentation`, which provide `filetype` and\n    `content` text to be displayed in preview window.\n\n  Only the \"word\" property is mandatory for complete items.\n\nOptional functions: ~\n\n  The vim source could provide some optional functions which would be invoked\n  by coc.nvim:\n\n  • `coc#source#{name}#get_startcol(option)` Used to alter the start col of\n    completion, the returned col must <= current cursor col.\n  • `coc#source#{name}#on_complete(item)` Called with selected complete item\n    when user confirm the completion by |coc#pum#confirm()| or\n    |coc#pum#select_confirm()|. Normally used for apply necessary edits to the\n    buffer.\n  • `coc#source#{name}#on_enter(option)` Called on |BufEnter| with option\n    contains:\n    • bufnr: The buffer number.\n    • uri: The uri text of buffer.\n    • languageId: The mapped filetype of buffer, see |coc-document-filetype|.\n  • `coc#source#{name}#refresh()` Called when the user trigger refresh action\n    for the source.\n\n------------------------------------------------------------------------------\nEXTENSION INTRODUCTION \t\t\t\t*coc-api-intro*\n\nEvery extension of coc.nvim has a JavaScript entry file, that file is loaded\nby NodeJS API `vm.runInContext` with an identical global context (like iframe\nin browser).\n\nThe JavaScript entry file should be a CommonJS module with `activate` method\nexported, and `require('coc.nvim')` can be used to access modules exported by\ncoc.nvim, for example:\n>\n  const {window} = require('coc.nvim')\n  exports.activate = async context => {\n    window.showInformationMessage('extension activated')\n  }\n<\nWhen `exports.deactivate` is exported from the JavaScript entry file as a\nfunction, it would be called on extension deactivate.\n\nLimitation of extension context: ~\n\nSome methods/properties provided by NodeJS can't be used inside extension\ncontext, including:\n\n  • `process.reallyExit()`\n  • `process.abort()`\n  • `process.setuid()`\n  • `process.setgid()`\n  • `process.setgroups()`\n  • `process._fatalException()`\n  • `process.exit()`\n  • `process.kill()`\n  • `process.umask()` Could only be used to get umask value.\n  • `process.chdir()` Could be called, but no effect at all.\n\nSome globals may can't be accessed directly, for example `TextDecoder`,\n`TextEncoder`, use `globalThis` like `globalThis.TextDecoder` to access them.\n\n\t\t\t\t\t\t*coc-api-console*\n\nStdin and stdout of the NodeJS process is used for communication between vim\nand NodeJS process, use the methods related to `process.stdin` and\n`process.stdout` may cause unexpected behavior.  However, some methods of\n`console` are provided for debugging purpose.\n\nMessages from `console` of extension would be redirected to the log file\n|:CocOpenLog|. Available methods:\n\n  • `debug(...args: any[])` Write debug message to the log file.\n  • `log(...args: any[])` Write info message to the log file.\n  • `info(...args: any[])` Write info message to the log file.\n  • `error(...args: any[])` Write error message to the log file.\n  • `warn(...args: any[])` Write warning message to the log file.\n\nCheck the full NodeJS API interfaces at:\nhttps://github.com/neoclide/coc.nvim/blob/master/typings/index.d.ts\n\n------------------------------------------------------------------------------\nEXTENSION PACKAGE JSON \t\t\t\t*coc-api-json*\n\nThe package.json file inside extension root defines the meta data of the\nextension. For example:\n>\n  {\n    \"name\": \"coc-my-extension\",\n    \"version\": \"1.0.0\",\n    \"main\": \"lib/index.js\",\n    \"engines\": {\n      \"coc\": \"^0.0.82\"\n    },\n    \"activationEvents\": [\n      \"*\",\n    ],\n    \"contributes\": {\n      \"rootPatterns\": [{\n        \"filetype\": \"myfiletype\",\n        \"patterns\": [\n          \"project_root.json\"\n        ]\n      }],\n      \"commands\": [{\n        \"title\": \"My command\",\n        \"category\": \"myextension\",\n        \"id\": \"myextension.myCommand\"\n      }],\n      \"configuration\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"myextension.enable\": {\n            \"type\": \"boolean\",\n            \"default\": true,\n            \"scope\": \"resource\",\n            \"description\": \"Enable running of my extension.\"\n          }\n        }\n      }\n    }\n  }\n<\nRequired properties of package.json:\n\n  • name: The unique name of extension, to publish the extension, the name\n    should not be taken by exists packages at https://www.npmjs.com/\n  • version: The semver version of extension.\n  • engines: Should have `coc` property with minimal required coc.nvim version.\n\nThe `main` property contains the relative filepath of the javascript entry\nfile, `index.js` would be used when not exists.\n\nThe `activationEvents` property tell coc.nvim when to activate the extension,\nwhen the property not exists or `*` is included, the extension would be\nactivated during coc.nvim initialize.  Other possible events:\n\n  • onLanguage: Activate the extension when document of specific languageId\n    exists, ex: `\"onLanguage:vim\"` activate the extension when there's buffer with\n    languageId as vim loaded.\n  • onFileSystem: Activate the extension when document with custom schema\n    loaded, ex: `\"onFileSystem:fugitive\"` activate the extension when there's\n    buffer with schema `fugitive` loaded.\n  • onCommand: activate the extension when specific command invoked by user,\n    ex: `\"onCommand:tsserver.reloadProjects\"`\n  • workspaceContains: activate the extension when the glob pattern match one\n    of the file in current workspace folder, ex:\n    `\"workspaceContains:**/package.json\"`\n\nOptional `contributes` property contains the meta data that contributed to\ncoc.nvim, including:\n\n  • rootPatterns: The patterns to resolve |coc-workspace-folders| for\n    associated filetype.\n  • commands: List of commands with `id` and `title` that can be invoked by\n    |:CocCommand|.\n  • configuration: Contains `properties` object or a list of configurations\n    that each one provide `properties` objects which define the configuration\n    properties contributed by this extension.\n\nThe `contributes` property could also contains other properties that used by\nother extensions, for example: the `jsonValidation` property could be used by\ncoc-json.\n\nIt's recommended to install `coc-json` for json intellisense support.\n\n------------------------------------------------------------------------------\nSINGLE FILE EXTENSIONS \t\t\t\t*coc-api-single*\n\nThe easiest way to access the NodeJS API is make use of single file\nextensions.\n\nAll Javascript files that ends with `.js` inside the folder \"coc-extensions\"\nunder |g:coc_config_home| are considered as coc extensions.\n\nThe javascript files would be loaded during coc.nvim initialize by default.\n\nTo contribute extension meta data, create file `${name}.json` aside with\n`${name}.js`, the json file works the same as package.json of extension\n|coc-api-json|, except that only `activationEvents` and `contributes`\nproperties are used.\n\nSingle file extensions can't be managed by extensions list.\n\n------------------------------------------------------------------------------\nCREATE CUSTOM EXTENSIONS \t\t\t*coc-api-extension*\n\nTo make an extension installable by |:CocInstall|, the easiest way is make use\nof https://github.com/fannheyward/create-coc-extension. Simply run command\n>\n\tnpm init coc-extension [extension-name]\n<\nor\n>\n\tyarn create coc-extension [extension-name]\n<\nin terminal and you will be prompted for create a javascript/typescript\nextension step by step.\n\nTo manually create an extension, follow these step:\n\n  • Create an empty folder and goto that folder.\n  • Create the package.json file |coc-api-json|.\n  • Create a javascript file with name `index.js` and write code.\n  • Add the created folder to your vim's runtimepath by\n    add `set runtimepath^=/path/to/folder` in your vimrc.\n\nRecommended steps:\n\n  • Install types of NodeJS and coc.nvim by terminal command\n    `npm install @types/node@latest coc.nvim` in extension folder.\n  • Bundle the javascript files when using multiple node dependencies by\n    esbuild to save the time of installation.  A typical build script looks\n    like: >\n\n      async function start() {\n        await require('esbuild').build({\n          entryPoints: ['src/index.ts'],\n          bundle: true,\n          minify: process.env.NODE_ENV === 'production',\n          sourcemap: process.env.NODE_ENV === 'development',\n          mainFields: ['module', 'main'],\n          external: ['coc.nvim'],\n          platform: 'node',\n          target: 'node16.18',\n          outfile: 'lib/index.js'\n        })\n      }\n\n      start().catch(e => {\n        console.error(e)\n      })\n<\n------------------------------------------------------------------------------\nDEBUG EXTENSIONS \t\t\t\t*coc-api-debug*\n\n\t\t\t\t\t\t*coc-api-channel*\nChannel errors: ~\n\nChannel feature on vim9 is used by coc.nvim to communicate between vim and\nNodeJS, the error messages caused by channel commands are not displayed on the\nscreen. Most of the time the error should be caught by coc.nvim and can be\nchecked by |CocOpenLog|.  But for some API functions including `callVim()`\n`exVim()` and `evalVim()`, the errors only update the |v:errmsg| and appears\nin vim's channel log, which can be checked by use |g:node_client_debug| or\nset environment variable `$COC_VIM_CHANNEL_ENABLE` to `\"1\"`.\n\nUncaught errors: ~\n\nWhen an uncaught error raised on the NodeJS process, the error message would\nbe send to vim through stderr, and echoed by vim (unless\n|g:coc_disable_uncaught_error| is enabled).\n\nThe error messages are not stored by vim's message history, use\n|:CocPrintErrors| to show previous errors.\n\nWhen error happens on the vim side, the promise would be rejected when sending\nrequest to vim, for notifications, vim would send `nvim_error_event` to the\nNodeJS process, and the node-client would create error log for it (could be\nopened by |:CocOpenLog|).\n\nUse the log file: ~\n\n• Configure `NVIM_COC_LOG_LEVEL` to `trace` in vimrc:\n  `let $NVIM_COC_LOG_LEVEL='trace'`\n• Configure `NVIM_COC_LOG_FILE` to a fixed in vimrc:\n  `let $NVIM_COC_LOG_FILE=/tmp/coc.log`, otherwise it would be different for\n  each vim instance.\n• Use |coc-api-console| to add console statements in javascript/typescript\ncode and compile the extension when needed.\n• Tail the log file by `tail` command and make the issue happen.\n\nAdd source map support: ~\n\nWhen the javascript code is bundled by esbuild, it would be useful to have\ncorrect source map support for the error stack.\n\n• Install global source-map-support by `npm install -g source-map-support`\n• Find out the npm root by `npm root -g`\n• Load source-map-support with coc.nvim by append arguments to node in vimrc:\n  `let g:coc_node_args = ['-r', '/path/to/npm/root/source-map-support/register']`\n  Replace the part `/path/to/npm/root` with result from `npm root -g` terminal\n  command.\n\nNote: the source-map-support module slows down the coc.nvim initialization.\n\nDebug javascript code with chrome: ~\n\n• Add `let g:coc_node_args = ['--nolazy', '--inspect-brk=5858']`\n• Open vim and you will get the error message indicate that the debugger is\n  listening.\n• Open Chrome browser with url chrome://inspect/#devices, configure\n  the `Target discovery settings` and you will get the remote target to\n  inspect.\n• Click the inspect link to open the devtools.\n• Click the sources label to debug javascript code.\n\nOther debugger clients can be used as well, see:\nhttps://nodejs.org/en/docs/guides/debugging-getting-started/\n\n==============================================================================\nvim:tw=78:sta:noet:ts=8:sts=0:ft=help:fen:\n"
  },
  {
    "path": "doc/coc-config.txt",
    "content": "*coc-config.txt*\t\t\t\tNodeJS client for Vim & Neovim.\n\nCONTENTS\n\nCore features\n\tWorkspace \t\t\t\t|coc-config-workspace|\n\tFile system watch \t\t\t|coc-config-fileSystemWatch|\n\tExtensions \t\t\t\t|coc-config-extensions|\n\tPreferences \t\t\t\t|coc-config-preferences|\n\tEditor \t\t\t\t\t|coc-config-editor|\n\tFloat factory \t\t\t\t|coc-config-floatFactory|\n\tFloat \t\t\t\t\t|coc-config-float|\n\tTree \t\t\t\t\t|coc-config-tree|\n\tDialog \t\t\t\t\t|coc-config-dialog|\n\tHttp \t\t\t\t\t|coc-config-http|\n\tNpm \t\t\t\t\t|coc-config-npm|\n\tLanguage server \t\t\t|coc-config-languageserver|\n\nLSP features\n\tCall hierarchy \t\t\t\t|coc-config-callHierarchy|\n\tCodeLens \t\t\t\t|coc-config-codeLens|\n\tColors \t\t\t\t\t|coc-config-colors|\n\tCompletion \t\t\t\t|coc-config-suggest|\n\tInline completion \t\t\t|coc-config-inlineSuggest|\n\tCursors \t\t\t\t|coc-config-cursors|\n\tDiagnostics \t\t\t\t|coc-config-diagnostic|\n\tDocument highlight \t\t\t|coc-config-documentHighlight|\n\tHover \t\t\t\t\t|coc-config-hover|\n\tInlay hint \t\t\t\t|coc-config-inlayHint|\n\tLinks \t\t\t\t\t|coc-config-links|\n\tList \t\t\t\t\t|coc-config-list|\n\tNotification \t\t\t\t|coc-config-notification|\n\tOutline \t\t\t\t|coc-config-outline|\n\tPull diagnostics \t\t\t|coc-config-pullDiagnostic|\n\tRefactor \t\t\t\t|coc-config-refactor|\n\tSemantic tokens \t\t\t|coc-config-semanticTokens|\n\tSignature \t\t\t\t|coc-config-signature|\n\tType hierarchy\t\t\t\t|coc-config-typeHierarchy|\n\n==============================================================================\nBUILTIN CONFIGURATIONS \t\t\t\t\t*coc-config*\n\nBuiltin configurations of coc.nvim, it's recommended to use `coc-json`\nextension for completion and validation support.\n\n==============================================================================\n\nCORE FEATURES\n\nConfigurations of builtin features.\n\n------------------------------------------------------------------------------\nWORKSPACE\n\t\t\t\t\t\t\t*coc-config-workspace*\n\"workspace.rootPatterns\"\t\t\t\t*coc-config-workspace-rootPatterns*\n\n\tRoot patterns to resolve workspaceFolder from parent folders of opened\n\tfiles, resolved from up to down.\n\n\tScope: `application`, default: `[\".git\",\".hg\",\".projections.json\"]`\n\n\"workspace.bottomUpFiletypes\"\t\t\t\t*coc-config-workspace-bottomUpFiletypes*\n\n\tFiletypes that should have workspace folder should resolved from base \n\tdirectory of file, or [\"*\"] for any filetype.\n\n\tScope: `application`, default: `[]`\n\n\"workspace.ignoredFiletypes\"\t\t\t\t*coc-config-workspace-ignoredFiletypes*\n\n\tFiletypes that should be ignored for workspace folder resolve.\n\n\tScope: `resource`, default: `[]`\n\n\"workspace.ignoredFolders\"\t\t\t\t*coc-config-workspace-ignoredFolders*\n\n\tList of folders that should not be resolved as workspace folder,\n\tenvironment variables and minimatch patterns can be used.\n\n\tScope: `application`, default: `[\"$HOME\"]`\n\n\"workspace.openOutputCommand\"\t\t\t\t*coc-config-workspace-openOutputCommand*\n\n\tCommand used to open output channel.\n\n\tScope: `resource`, default: `\"vs\"`\n\n\"workspace.openResourceCommand\"\t\t\t\t*coc-config-workspace-openResourceCommand*\n\n\tCommand to open files that not loaded, load files as hidden buffers\n\twhen empty.\n\n\tScope: `application`, default: `\"tab drop\"`\n\n\"workspace.workspaceFolderCheckCwd\"\t\t\t*coc-config-workspace-workspaceFolderCheckCwd*\n\n\tWhether the current working directory should be used first when\n\tchecking patterns match for workspace folder.\n\n\tScope: `application`, default: `true`\n\n\"workspace.workspaceFolderFallbackCwd\"\t\t\t*coc-config-workspace-workspaceFolderFallbackCwd*\n\n\tUse current working directory as workspace folder when no root\n\tpatterns resolved.\n\n\tScope: `application`, default: `true`\n\n\"workspace.removeEmptyWorkspaceFolder\"\t\t\t*coc-config-workspace-removeEmptyWorkspaceFolder*\n\n\tAutomatically remove the workspace folder when no buffer associated\n\twith it.\n\n\tScope: `application`, default: `false`\n\n------------------------------------------------------------------------------\nFILESYSTEMWATCH \t\t\t\t\t*coc-config-fileSystemWatch*\n\n\"fileSystemWatch.watchmanPath\" \t\t\t\t*coc-config-filesystemwatch-watchmanPath*\n\n\texecutable path for https://facebook.github.io/watchman/, detected\n\tfrom $PATH by default\t\n\t\n\tScope: `application`, default: `null`\n\n\"fileSystemWatch.enable\" \t\t\t\t*coc-config-filesystemwatch-enable*\n\n\tEnable file system watch support for workspace folders.\n\t\n\tScope: `application`, default: `true`\n\n\"fileSystemWatch.ignoredFolders\" \t\t\t*coc-config-filesystemwatch-ignoredFolders*\n\n\tList of folders that should not be watched for file changes,\n\tenvironment variables  and minimatch patterns\n\tcan be used.\n\n\tScope: `application`, default: `[\"/private/tmp\", \"/\", \"${tmpdir}\"]`\n\n------------------------------------------------------------------------------\nEXTENSIONS\n\t\t\t\t\t\t\t*coc-config-extensions*\n\"extensions.updateCheck\" \t\t\t\t*coc-config-extensions-updateCheck*\n\n\tInterval for check extension update, could be \"daily\", \"weekly\" or\n\t\"never\"\n\n\tScope: `application`, default: `\"never\"`\n\n\"extensions.silentAutoupdate\" \t\t\t\t*coc-config-extensions-silentAutoupdate*\n\n\tNot open split window with update status when performing auto update.\n\n\tScope: `application`, default: `true`\n\n\"extensions.updateUIInTab\" \t\t\t\t*coc-config-extensions-updateUIInTab*\n\n\tOpen `CocUpdate` UI in new tab.\n\n\tScope: `application`, default: `false`\n\n\"extensions.recommendations\" \t\t\t\t*coc-config-extensions-recommendations*\n\n\tList of extensions recommended for installation in the current project.\n\tOnly works as workspace folder configuration.\n\n\tScope: `resource`, default: `[]`\n\n------------------------------------------------------------------------------\nPREFERENCES\n\t\t\t\t\t\t\t*coc-config-preferences*\n\"coc.preferences.bracketEnterImprove\"\t\t\t*coc-preferences-bracketEnterImprove*\n\n\tImprove enter inside bracket `<> {} [] ()` by add new empty line below\n\tand place cursor to it. Works with `coc#on_enter()`\n\n\tScope: `language-overridable`, default: `true`\n\n\"coc.preferences.currentFunctionSymbolAutoUpdate\"\t*coc-preferences-currentFunctionSymbolAutoUpdate*\n\n\tAutomatically update the value of b:coc_current_function on CursorMove\n\tevent\n\n\tScope: `language-overridable`, default: `false`\n\n\"coc.preferences.currentFunctionSymbolDebounceTime\"\t*coc-preferences-currentFunctionSymbolDebounceTime*\n\n\tSet debounce timer for the update of b:coc_current_function on CursorMove\n\tevent\n\n\tScope: `application`, default: `300`\n\n\"coc.preferences.enableLinkedEditing\"\t\t\t*coc-preferences-enableLinkedEditing*\n\n\tEnable linked editing support.\n\n\tScope: `language-overridable`, default: `false`\n\n\"coc.preferences.enableMarkdown\"\t\t\t*coc-preferences-enableMarkdown*\n\n\tTell the language server that markdown text format is supported, note\n\tthat markdown text may not rendered as expected.\n\n\tScope: `application`, default: `true`\n\n\"coc.preferences.enableMessageDialog\"\t\t\t*coc-preferences-enableMessageDialog*\n\n\tEnable messages shown in notification dialog. Deprecated, prefer configuration 'coc.preferences.messageDialogKind' instead.\n\n\tScope: `application`, default: `false`\n\n\"coc.preferences.messageDialogKind\"\t\t\t*coc-preferences-messageDialogKind*\n\n\tConfigure the type of user interaction when an interactive message dialog occurs with more than zero actions to trigger.\n\n\tScope: `application`, default: `confirm`\n\n\"coc.preferences.messageReportKind\"\t\t\t*coc-preferences-messageReportKind*\n\n\tConfigure the type of user interaction when a non-interactive message dialog occurs with zero actions to trigger.\n\n\tScope: `application`, default: `echo`\n\n\"coc.preferences.excludeImageLinksInMarkdownDocument\"\t*coc-preferences-excludeImageLinksInMarkdownDocument*\n\n\tExclude image links from markdown text in float window.\n\n\tScope: `application`, default: `true`\n\n\"coc.preferences.enableGFMBreaksInMarkdownDocument\"\t*coc-preferences-enableGFMBreaksInmakrdownDocument*\n\n\tExclude GFM breaks in markdown document.\n\n\tScope: `application`, default: `true`\n\n\"coc.preferences.floatActions\"\t\t\t\t*coc-preferences-floatActions*\n\n\tSet to false to disable float/popup support for actions menu.\n\n\tScope: `application`, default: `true`\n\n\"coc.preferences.formatOnSave\"\t\t\t\t*coc-preferences-formatOnSave*\n\n\tSet to true to enable formatting on save.\n\n\tScope: `language-overridable`, default: `false`\n\n\"coc.preferences.formatOnSaveTimeout\"\t\t\t*coc-preferences-formatOnSaveTimeout*\n\n\tHow long before the format command run on save will time out.\n\n\tScope: `language-overridable`, default: `200`\n\n\"coc.preferences.formatOnType\"\t\t\t\t*coc-preferences-formatOnType*\n\n\tSet to true to enable formatting on typing\n\n\tScope: `language-overridable`, default: `false`\n\n\"coc.preferences.formatterExtension\" \t\t\t*coc-preferences-formatterExtension*\n\n\tExtension used for formatting documents. When set to null, the\n\tformatter with highest priority is used.\n\n\tScope: `language-overridable`, default: `null`\n\n\"coc.preferences.jumpCommand\"\t\t\t\t*coc-preferences-jumpCommand*\n\n\tCommand used for location jump, like goto definition, goto references \n\tetc. Can be also a custom command that gives file as an argument.\n\n\tScope: `application`, default: `\"edit\"`\n\n\"coc.preferences.maxFileSize\"\t\t\t\t*coc-preferences-maxFileSize*\n\n\tMaximum file size in bytes that coc.nvim should handle, default\n\t'10MB'.\n\n\tScope: `application`, default: `\"10MB\"`\n\n\"coc.preferences.messageLevel\"\t\t\t\t*coc-preferences-messageLevel*\n\n\tMessage level for filter echoed messages, could be 'more', 'warning'\n\tand 'error'\n\n\tScope: `application`, default: `\"more\"`\n\n\"coc.preferences.promptInput\"\t\t\t\t*coc-preferences-promptInput*\n\n\tUse prompt buffer in float window for user input.\n\n\tScope: `application`, default: `true`\n\n\"coc.preferences.renameFillCurrent\"\t\t\t*coc-preferences-renameFillCurrent*\n\n\tDisable to stop Refactor-Rename float/popup window from populating\n\twith old name in the New Name field.\n\n\tScope: `application`, default: `true`\n\n\"coc.preferences.silentAutoupdate\"\t\t\t*coc-preferences-silentAutoupdate*\n\n\tNot open split window with update status when performing auto update.\n\n\tScope: `application`, default: `true`\n\n\"coc.preferences.useQuickfixForLocations\"\t\t*coc-preferences-useQuickfixForLocations*\n\n\tUse vim's quickfix list for jump locations, need restart on change.\n\n\tScope: `application`, default: `false`\n\n\"coc.preferences.watchmanPath\"\t\t\t\t*coc-preferences-watchmanPath*\n\n\texecutable path for https://facebook.github.io/watchman/, detected\n\tfrom $PATH by default. Required by features which need file watch\n\tto work, eg: update import path on file move.\n\n\tScope: `application`, default: `null`\n\n\"coc.preferences.willSaveHandlerTimeout\"\t\t*coc-preferences-willSaveHandlerTimeout*\n\n\tWill save handler timeout.\n\n\tScope: `application`, default: `500`\n\n\"coc.preferences.tagDefinitionTimeout\" \t\t\t*coc-preferences-tagDefinitionTimeout*\n\n\tThe timeout of CocTagFunc.\n\tScope: `application`, default: `0`\n\n------------------------------------------------------------------------------\nEDITOR\n\t\t\t\t\t\t\t*coc-config-editor*\n\t\t\t\t\t\t\t*coc-config-editor-codeActionsOnSave*\n\n\"editor.codeActionsOnSave\"\n\n\tRun Code Actions for the buffer on save, normally source actions,\n\tExample: `\\\"source.organizeImports\\\": \\\"always\\\"`,\n\t|coc-preferences-willSaveHandlerTimeout| is used for timeout control.\n\n\tScope: `language-overridable`, default: `{}`\n\n\t\t\t\t\t\t\t*coc-config-editor-autocmdTimeout*\n\n\"editor.autocmdTimeout\"\n\n\tTimeout for execute request autocmd registered by coc extensions.\n\n\tScope: `application`, default: `1000`\n\n------------------------------------------------------------------------------\nFLOATfACTORY\n\t\t\t\t\t\t\t*coc-config-floatFactory*\n\"floatFactory.floatConfig\"\t\t\t\t*coc-config-floatFactory-floatConfig*\n\n\tConfigure default float window/popup style created by float factory\n\t(created around cursor and automatically closed),\n\tsee |coc-config-float| for supported properties.\n\n\tScope: `application`, default: `null`\n\n\n------------------------------------------------------------------------------\nFLOAT CONFIGURATION\n\t\t\t\t\t\t\t*coc-config-float*\n\nUsed by `floatFactory.floatConfig`, `suggest.floatConfig`,\n`diagnostic.floatConfig`, `signature.floatConfig` and `hover.floatConfig`,\nfollowing properties are supported:\n\n\t- \"border\": Change to `true` to enable border.\n\t- \"rounded\": Use rounded borders when border is `true`.\n\t- \"highlight\": Background highlight group of float window, default:\n\t  `\"CocFloating\"`.\n\t- \"title\": Title text used by float window, default: `\"\"`.\n\t- \"borderhighlight\": Border highlight group of float window, default:\n\t  `\"CocFloatBorder\"`.\n\t- \"close\": Set to `true` to draw close icon.\n\t- \"maxWidth\": Maximum width of float window, contains border.\n\t- \"maxHeight\": Maximum height of float window, contains border.\n\t- \"winblend\": Set 'winblend' option of window, neovim only, default:\n\t  `0`.\n\t- \"focusable\": Set to false to make window not focusable, neovim only.\n\t- \"shadow\": Set to true to enable shadow, neovim only.\n\t- \"position\": Controls how floating windows are positioned. When set\n\t  to `'fixed'`, the window will be positioned according to the `top`,\n\t  `bottom`, `left`, and `right` settings. When set to `'auto'`, the\n\t  window follows the default position.\n\t- \"top\": Distance from the top of the editor window in characters.\n\t  Only takes effect when `position` is set to `'fixed'`. Will be\n\t  ignored if `bottom` is set.\n\t- \"bottom\": Distance from the bottom of the editor window in\n\t  characters. Only takes effect when `position` is set to `'fixed'`.\n\t  Takes precedence over `top` if both are set.\n\t- \"left\": Distance from the left edge of the editor window in\n\t  characters. Only takes effect when `position` is set to `'fixed'`.\n\t  Will be ignored if `right` is set.\n\t- \"right\": \"Distance from the right edge of the editor window in\n\t  characters. Only takes effect when `position` is set to `'fixed'`.\n\t  Takes precedence over `left` if both are set.\"\n\n------------------------------------------------------------------------------\nTREE\n\t\t\t\t\t\t\t*coc-config-tree*\n\nConfigurations for tree view.\n\n\"tree.openedIcon\"\t\t\t\t\t*coc-config-tree-openedIcon*\n\n\tOpened icon of tree view.\n\n\tScope: `application`, default: `\"-\"`\n\n\"tree.closedIcon\"\t\t\t\t\t*coc-config-tree-closedIcon*\n\n\tClosed icon of tree view.\n\n\tScope: `application`, default: `\"+\"`\n\n\"tree.key.actions\"\t\t\t\t\t*coc-config-tree-key-actions*\n\n\tTrigger key to invoke actions.\n\n\tScope: `application`, default: `\"<tab>\"`\n\n\"tree.key.activeFilter\"\t\t\t\t\t*coc-config-tree-key-activeFilter*\n\n\tTrigger key active filter.\n\n\tScope: `application`, default: `\"f\"`\n\n\"tree.key.close\"\t\t\t\t\t*coc-config-tree-key-close*\n\n\tTrigger key to dispose the tree and close tree window.\n\n\tScope: `application`, default: `\"<esc>\"`\n\n\"tree.key.collapseAll\"\t\t\t\t\t*coc-config-tree-key-collapseAll*\n\n\tTrigger key to collapse all tree node.\n\n\tScope: `application`, default: `\"M\"`\n\n\"tree.key.invoke\"\t\t\t\t\t*coc-config-tree-key-invoke*\n\n\tTrigger key to invoke default command of current node or selection.\n\n\tScope: `application`, default: `\"<cr>\"`\n\n\"tree.key.selectNext\"\t\t\t\t\t*coc-config-tree-key-selectNext*\n\n\tTrigger key to select next item during filter.\n\n\tScope: `application`, default: `\"<C-j>\"`\n\n\"tree.key.selectPrevious\"\t\t\t\t*coc-config-tree-key-selectPrevious*\n\n\tTrigger key to select previous item during filter.\n\n\tScope: `application`, default: `\"<C-k>\"`\n\n\"tree.key.toggle\"\t\t\t\t\t*coc-config-tree-key-toggle*\n\n\tTrigger key to toggle expand state of tree node, does nothing with leaf \n\tnode.\n\n\tScope: `application`, default: `\"t\"`\n\n\"tree.key.toggleSelection\"\t\t\t\t*coc-config-tree-key-toggleSelection*\n\n\tTrigger key to select/unselect item.\n\n\tScope: `application`, default: `\"<space>\"`\n\n------------------------------------------------------------------------------\nDIALOG\n\t\t\t\t\t\t\t*coc-config-dialog*\nConfigurations for dialog windows.\n\n\"dialog.confirmKey\"\t\t\t\t\t*coc-config-dialog-confirmKey*\n\n\tConfirm key for confirm selection used by menu and picker, you can\n\talways use <esc> to cancel.\n\n\tScope: `application`, default: `\"<cr>\"`\n\n\"dialog.floatBorderHighlight\"\t\t\t\t*coc-config-dialog-floatBorderHighlight*\n\n\tHighlight group for border of dialog window/popup, use 'CocFloating'\n\twhen not specified.\n\n\tScope: `application`, default: `null`\n\n\"dialog.floatHighlight\"\t\t\t\t\t*coc-config-dialog-floatHighlight*\n\n\tHighlight group for dialog window/popup, use 'CocFloating' when not\n\tspecified.\n\n\tScope: `application`, default: `null`\n\n\"dialog.maxHeight\"\t\t\t\t\t*coc-config-dialog-maxHeight*\n\n\tMaximum height of dialog window, for quickpick, it's content window's\n\theight.\n\n\tScope: `application`, default: `30`\n\n\"dialog.maxWidth\"\t\t\t\t\t*coc-config-dialog-maxWidth*\n\n\tMaximum width of dialog window.\n\n\tScope: `application`, default: `80`\n\n\"dialog.pickerButtonShortcut\"\t\t\t\t*coc-config-dialog-pickerButtonShortcut*\n\n\tShow shortcut in buttons of picker dialog window/popup, used when dialog\n\t.pickerButtons is true.\n\n\tScope: `application`, default: `true`\n\n\"dialog.pickerButtons\"\t\t\t\t\t*coc-config-dialog-pickerButtons*\n\n\tShow buttons for picker dialog window/popup.\n\n\tScope: `application`, default: `true`\n\n\"dialog.rounded\"\t\t\t\t\t*coc-config-dialog-rounded*\n\n\tuse rounded border for dialog window.\n\n\tScope: `application`, default: `true`\n\n\"dialog.shortcutHighlight\"\t\t\t\t*coc-config-dialog-shortcutHighlight*\n\n\tHighlight group for shortcut character in menu dialog.\n\n\tScope: `application`, default: `\"MoreMsg\"`\n\n------------------------------------------------------------------------------\nHTTP PROXY\n\t\t\t\t\t\t\t*coc-config-http*\n\nConfigurations for http requests, used by coc.nvim and some coc extensions.\n\n\"http.proxy\"\t\t\t\t\t\t*coc-config-http-proxy*\n\n\tThe proxy setting to use. If not set, will be inherited from the `\n\thttp_proxy` and `https_proxy` environment variables.\n\n\tScope: `application`, default: `\"\"`\n\n\"http.proxyAuthorization\"\t\t\t\t*coc-config-http-proxyAuthorization*\n\n\tThe value to send as the `Proxy-Authorization` header for every network\n\trequest.\n\n\tScope: `application`, default: `null`\n\n\"http.proxyCA\"\t\t\t\t\t\t*coc-config-http-proxyCA*\n\n\tCA (file) to use as Certificate Authority>\n\n\tScope: `application`, default: `null`\n\n\"http.proxyStrictSSL\"\t\t\t\t\t*coc-config-http-proxyStrictSSL*\n\n\tControls whether the proxy server certificate should be verified\n\tagainst the list of supplied CAs.\n\n\tScope: `application`, default: `true`\n\n------------------------------------------------------------------------------\nNPM\n\t\t\t\t\t\t\t*coc-config-npm*\n\"npm.binPath\"\t\t\t\t\t\t*coc-config-npm-binPath*\n\n\tCommand or absolute path to npm or yarn for global extension\n\tinstall/uninstall.\n\n\tScope: `application`, default: `\"npm\"`\n\n------------------------------------------------------------------------------\nLANGUAGESERVER\n\t\t\t\t\t\t\t*coc-config-languageserver*\n\n\tDictionary of Language Servers, key is the ID of corresponding server,\n\tand value is configuration of languageserver. Default: `{}`\n\n\tProperties of languageserver configuration:\n\n\t- \"enable\": Change to `false` to disable that languageserver.\n\n\t- \"filetypes\": Supported filetypes, add * in array for all filetypes.\n\t  Note: it's required for start the languageserver, please make sure\n\t  your filetype is expected by `:CocCommand document.echoFiletype` command\n\n\t- 'maxRestartCount': Maximum restart count when server closed in the\n\t  last 3 minutes, default to `4`.\n\n\t- \"additionalSchemes\": Additional URI schemes, default schemes\n\t  including file & untitled.\n\t  Note: you have to setup vim provide content for custom URI as well.\n\n\t- \"cwd\": Working directory used to start languageserver, vim's cwd is\n\t  used by default.\n\n\t- \"env\": Environment variables for child process.\n\n\t- \"settings\": Settings for languageserver, received on server\n\t  initialization.\n\n\t- \"trace.server\": Trace level of communication between server and\n\t  client that showed with output channel, open output channel by\n\t  command `:CocCommand workspace.showOutput`\n\n\t- \"stdioEncoding\": Encoding used for stdio of child process.\n\n\t- \"initializationOptions\": Initialization options passed to\n\t  languageserver (it's deprecated)\n\n\t- \"rootPatterns\": Root patterns used to resolve rootPath from current\n\t  file.\n\n\t- \"requireRootPattern\": If true, doesn't start server when root\n\t  pattern not found.\n\n\t- \"ignoredRootPaths\": Absolute root paths that language server should\n\t  not use as rootPath, higher priority than rootPatterns.\n\n\t- \"disableDynamicRegister\": Disable dynamic registerCapability feature\n\t  for this languageserver to avoid duplicated feature registration.\n\n\t- \"disableSnippetCompletion\": Disable snippet completion feature for\n\t  this languageserver.\n\n\t- \"disabledFeatures\": Disable features for this languageserver,\n\t  valid keys: >\n\n\t  [\"completion\", \"configuration\", \"workspaceFolders\", \"diagnostics\",\n\t  \"willSave\", \"willSaveUntil\", \"didSaveTextDocument\",\n\t  \"fileSystemWatcher\", \"hover\", \"signatureHelp\", \"definition\",\n\t  \"references\", \"documentHighlight\", \"documentSymbol\",\n\t  \"workspaceSymbol\", \"codeAction\", \"codeLens\", \"formatting\",\n\t  \"documentFormatting\", \"documentRangeFormatting\",\n\t  \"documentOnTypeFormatting\", \"rename\", \"documentLink\",\n\t  \"executeCommand\", \"pullConfiguration\", \"typeDefinition\",\n\t  \"implementation\", \"declaration\", \"color\", \"foldingRange\",\n\t  \"selectionRange\", \"progress\", \"callHierarchy\", \"linkedEditing\",\n\t  \"fileEvents\", \"semanticTokens\"]\n<\n\t- \"formatterPriority\": Priority of this languageserver's formatter.\n\n\t- \"revealOutputChannelOn\": Configure message level to show the output\n\t  channel buffer.\n\n\t- \"progressOnInitialization\": Enable progress report on languageserver\n\t  initialize.\n\nLanguage server start with command: ~\n\n\tAdditional fields can be used for a command languageserver:\n\n\t- \"command\": Executable program name in $PATH or absolute path of\n\t  executable used for start languageserver.\n\n\t- \"args\": Command line arguments of command.\n\n\t- \"detached\": Detach language server when is true.\n\n\t- \"shell\": Use shell for server process, default: `false`\n\nLanguage server start with module: ~\n\n\tAdditional fields can be used for a languageserver started by node\n\tmodule:\n\n\t- \"module\": Absolute filepath of Javascript file.\n\n\t- \"args\": Extra arguments used on fork Javascript module.\n\n\t- \"runtime\": Absolute path of node runtime, node runtime of coc.nvim\n\t  is used by default.\n\n\t- \"execArgv\": ARGV passed to node on fork, normally used for\n\t  debugging, example: `[\"--nolazy\", \"--inspect-brk=6045\"]`\n\n\t- \"transport\": Transport kind used by server, could be 'ipc', 'stdio',\n\t  'socket' and 'pipe'. 'ipc' is used by default (recommended).\n\n\t- \"transportPort\": Port number used when transport is 'socket'.\n\nLanguage server use initialized socket server: ~\n\n\t- \"port\": Port number of socket server.\n\n\t- \"host\": Host of socket server, default to `127.0.0.1`.\n\n\n==============================================================================\n\nLSP FEATURES\n\nConfigurations for features provided by language server.\n\n------------------------------------------------------------------------------\n\nCALLHIERARCHY\n\t\t\t\t\t\t\t*coc-config-callHierarchy*\n\"callHierarchy.enableTooltip\"\t\t\t\t*coc-config-callHierarchy-enableTooltip*\n\n\tEnable tooltip to show relative filepath of call hierarchy item.\n\n\tScope: `application`, default: `true`\n\n\"callHierarchy.openCommand\"\t\t\t\t*coc-config-callHierarchy-openCommand*\n\n\tOpen command for call hierarchy tree view.\n\n\tScope: `application`, default: `\"edit\"`\n\n\"callHierarchy.splitCommand\"\t\t\t\t*coc-config-callHierarchy-splitCommand*\n\n\tWindow split command used by call hierarchy tree view.\n\n\tScope: `application`, default: `\"botright 30vs\"`\n\n------------------------------------------------------------------------------\nCODELENS\n\t\t\t\t\t\t\t*coc-config-codeLens*\n\"codeLens.enable\"\t\t\t\t\t*coc-config-codeLens-enable*\n\n\tEnable codeLens feature, require neovim with set virtual text feature.\n\n\tScope: `language-overridable`, default: `false`\n\n\"codeLens.display\"\t\t\t\t\t*coc-config-codeLens-display*\n\n\tDisplay codeLens. Toggle with :CocCommand document.toggleCodeLens\n\n\tScope: `language-overridable`, default: `true`\n\n\"codeLens.position\"\t\t\t\t\t*coc-config-codeLens-position*\n\n\tDisplay position of codeLens virtual text.\n\n\tScope: `resource`, default: `\"top\"`\n\n\"codeLens.separator\"\t\t\t\t\t*coc-config-codeLens-separator*\n\n\tSeparator text for codeLens in virtual text.\n\n\tScope: `resource`, default: `\"\"`\n\n\"codeLens.subseparator\"\t\t\t\t\t*coc-config-codeLens-subseparator*\n\n\tSubseparator between codeLenses in virtual text.\n\n\tScope: `resource`, default: `\" | \"`\n\n------------------------------------------------------------------------------\nCOLORS\n\t\t\t\t\t\t\t*coc-config-colors*\n\"colors.enable\"\t\t\t\t\t\t*coc-config-colors-enable*\n\n\tEnable colors highlight feature, for terminal vim, 'termguicolors'\n\toption should be enabled and the terminal support gui colors.\n\n\tScope: `language-overridable`, default: `false`\n\n\"colors.highlightPriority\"\t\t\t\t*coc-config-colors-highlightPriority*\n\n\tPriority for colors highlights, works on vim8 and neovim >= 0.6.0.\n\n\tScope: `application`, default: `1000`\n\n------------------------------------------------------------------------------\nCURSORS\n\t\t\t\t\t\t\t*coc-config-cursors*\n\"cursors.cancelKey\"\t\t\t\t\t*coc-config-cursors-cancelKey*\n\n\tKey used for cancel cursors session.\n\n\tScope: `application`, default: `\"<esc>\"`\n\n\"cursors.nextKey\"\t\t\t\t\t*coc-config-cursors-nextKey*\n\n\tKey used for jump to next cursors position.\n\n\tScope: `application`, default: `\"<C-n>\"`\n\n\"cursors.previousKey\"\t\t\t\t\t*coc-config-cursors-previousKey*\n\n\tKey used for jump to previous cursors position.\n\n\tScope: `application`, default: `\"<C-p>\"`\n\n\"cursors.wrapscan\"\t\t\t\t\t*coc-config-cursors-wrapscan*\n\n\tSearches wrap around the first or last cursors range.\n\n\tScope: `application`, default: `true`\n\n------------------------------------------------------------------------------\nDIAGNOSTIC\n\t\t\t\t\t\t\t*coc-config-diagnostic*\n\"diagnostic.autoRefresh\"\t\t\t\t*coc-config-diagnostic-autoRefresh*\n\n\tEnable automatically refresh diagnostics, use diagnosticRefresh action\n\twhen it's disabled.\n\n\tScope: `language-overridable`, default: `true`\n\n\"diagnostic.checkCurrentLine\"\t\t\t\t*coc-config-diagnostic-checkCurrentLine*\n\n\tWhen enabled, show all diagnostics of current line if there are none at \n\tthe current position.\n\n\tScope: `language-overridable`, default: `false`\n\n\"diagnostic.displayByAle\"\t\t\t\t*coc-config-diagnostic-displayByAle*\n\n\tUse Ale, coc-diagnostics-shim.nvim, or other provider to display\n\tdiagnostics in vim. This setting will disable diagnostic display using\n\tcoc's handler. A restart required on change.\n\n\tScope: `language-overridable`, default: `false`\n\n\"diagnostic.displayByVimDiagnostic\"\t\t\t*coc-config-diagnostic-displayByVimDiagnostic*\n\n\tSet diagnostics to nvim's `vim.diagnostic`, and prevent coc.nvim's\n\thandler to display in virtualText/floating window etc.\n\n\tScope: `language-overridable`, default: `false`\n\n\"diagnostic.enable\"\t\t\t\t\t*coc-config-diagnostic-enable*\n\n\tSet to false to disable diagnostic display.\n\n\tScope: `language-overridable`, default: `true`\n\n\"diagnostic.enableHighlightLineNumber\"\t\t\t*coc-config-diagnostic-enableHighlightLineNumber*\n\n\tEnable highlighting line numbers for diagnostics, only works with neovim.\n\n\tScope: `application`, default: `true`\n\n\"diagnostic.enableMessage\"\t\t\t\t*coc-config-diagnostic-enableMessage*\n\n\tWhen to enable show messages of diagnostics.\n\n\tScope: `application`, default: `\"always\"`\n\n\"diagnostic.enableSign\"\t\t\t\t\t*coc-config-diagnostic-enableSign*\n\n\tEnable signs for diagnostics.\n\n\tScope: `language-overridable`, default: `true`\n\n\"diagnostic.errorSign\"\t\t\t\t\t*coc-config-diagnostic-errorSign*\n\n\tText of error sign.\n\n\tScope: `application`, default: `\">>\"`\n\n\"diagnostic.filetypeMap\"\t\t\t\t*coc-config-diagnostic-filetypeMap*\n\n\tA map between buffer filetype and the filetype assigned to diagnostics.\n\tTo syntax highlight diagnostics with their parent buffer type use `\"\n\tdefault\": \"bufferType\"`.\n\n\tScope: `application`, default: `{}`\n\n\"diagnostic.floatConfig\"\t\t\t\t*coc-config-diagnostic-floatConfig*\n\n\tConfiguration of floating window/popup for diagnostic messages, see\n\t|coc-config-float|.\n\n\tScope: `application`, default: `null`\n\n\"diagnostic.format\"\t\t\t\t\t*coc-config-diagnostic-format*\n\n\tDefine the diagnostic format that shown in float window or echoed,\n\tavailable parts: source, code, severity, message.\n\n\tScope: `language-overridable`, default: `\"%message (%source%code)\"`\n\n\"diagnostic.highlightLimit\"\t\t\t\t*coc-config-diagnostic-highlightLimit*\n\n\tLimit count for highlighted diagnostics, too many diagnostic highlights\n\tcould make vim stop responding.\n\n\tScope: `language-overridable`, default: `1000`\n\n\"diagnostic.highlightPriority\"\t\t\t\t*coc-config-diagnostic-highlightPriority*\n\n\tPriority for diagnostic highlights, works on vim8 and neovim >= 0.6.0.\n\n\tScope: `language-overridable`, default: `4096`\n\n\"diagnostic.hintSign\"\t\t\t\t\t*coc-config-diagnostic-hintSign*\n\n\tText of hint sign.\n\n\tScope: `application`, default: `\">>\"`\n\n\"diagnostic.infoSign\"\t\t\t\t\t*coc-config-diagnostic-infoSign*\n\n\tText of info sign.\n\n\tScope: `application`, default: `\">>\"`\n\n\"diagnostic.level\"\t\t\t\t\t*coc-config-diagnostic-level*\n\n\tUsed for filter diagnostics by diagnostic severity.\n\n\tScope: `resource`, default: `\"hint\"`\n\n\"diagnostic.locationlistLevel\"\t\t\t\t*coc-config-diagnostic-locationlistLevel*\n\n\tFilter diagnostics in locationlist.\n\n\tScope: `language-overridable`, default: `null`\n\n\"diagnostic.locationlistUpdate\"\t\t\t\t*coc-config-diagnostic-locationlistUpdate*\n\n\tUpdate locationlist on diagnostics change, only works with locationlist\n\topened by :CocDiagnostics command and first window of associated buffer.\n\n\tScope: `language-overridable`, default: `true`\n\n\"diagnostic.messageDelay\"\t\t\t\t*coc-config-diagnostic-messageDelay*\n\n\tHow long to wait (in milliseconds) before displaying the diagnostic\n\tmessage with echo or float\n\n\tScope: `application`, default: `200`\n\n\"diagnostic.messageLevel\"\t\t\t\t*coc-config-diagnostic-messageLevel*\n\n\tFilter diagnostic message in float window/popup.\n\n\tScope: `language-overridable`, default: `null`\n\n\"diagnostic.messageTarget\"\t\t\t\t*coc-config-diagnostic-messageTarget*\n\n\tDiagnostic message target.\n\n\tScope: `language-overridable`, default: `\"float\"`\n\n\"diagnostic.refreshOnInsertMode\"\t\t\t*coc-config-diagnostic-refreshOnInsertMode*\n\n\tEnable diagnostic refresh on insert mode, default false.\n\n\tScope: `language-overridable`, default: `false`\n\n\"diagnostic.showDeprecated\"\t\t\t\t*coc-config-diagnostic-showDeprecated*\n\n\tShow diagnostics with deprecated tag.\n\n\tScope: `language-overridable`, default: `true`\n\n\"diagnostic.showUnused\"\t\t\t\t\t*coc-config-diagnostic-showUnused*\n\n\tShow diagnostics with unused tag, affects highlight, sign, virtual\n\ttext , message.\n\n\tScope: `language-overridable`, default: `true`\n\n\"diagnostic.signLevel\"\t\t\t\t\t*coc-config-diagnostic-signLevel*\n\n\tFilter diagnostics displayed in signcolumn.\n\n\tScope: `language-overridable`, default: `null`\n\n\"diagnostic.signPriority\"\t\t\t\t*coc-config-diagnostic-signPriority*\n\n\tPriority of diagnostic signs.\n\n\tScope: `resource`, default: `10`\n\n\"diagnostic.virtualText\"\t\t\t\t*coc-config-diagnostic-virtualText*\n\n\tUse virtual text to display diagnostics.\n\n\tScope: `language-overridable`, default: `false`\n\n\"diagnostic.virtualTextAlign\"\t\t\t\t*coc-config-diagnostic-virtualTextAlign*\n\n\tPosition of virtual text.\n\n\tScope: `language-overridable`, default: `\"after\"`\n\n\"diagnostic.virtualTextCurrentLineOnly\"\t\t\t*coc-config-diagnostic-virtualTextCurrentLineOnly*\n\n\tOnly show virtualText diagnostic on current cursor line.\n\n\tScope: `language-overridable`, default: `true`\n\n\"diagnostic.virtualTextFormat\"\t\t\t\t*coc-config-diagnostic-virtualTextFormat*\n\n\tDefine the virtual text diagnostic format, available parts: source, code\n\t, severity, message.\n\n\tScope: `language-overridable`, default: `\"%message\"`\n\n\"diagnostic.virtualTextLevel\"\t\t\t\t*coc-config-diagnostic-virtualTextLevel*\n\n\tFilter diagnostic message in virtual text by level.\n\n\tScope: `language-overridable`, default: `null`\n\n\"diagnostic.virtualTextLimitInOneLine\"\t\t\t*coc-config-diagnostic-virtualTextLimitInOneLine*\n\n\tThe maximum number of diagnostic messages to display in one line.\n\n\tScope: `language-overridable`, default: `999`\n\n\"diagnostic.virtualTextLineSeparator\"\t\t\t*coc-config-diagnostic-virtualTextLineSeparator*\n\n\tThe text that will mark a line end from the diagnostic message.\n\n\tScope: `language-overridable`, default: `\" \\ \"`\n\n\"diagnostic.virtualTextLines\"\t\t\t\t*coc-config-diagnostic-virtualTextLines*\n\n\tThe number of non empty lines from a diagnostic to display.\n\n\tScope: `language-overridable`, default: `3`\n\n\"diagnostic.virtualTextPrefix\"\t\t\t\t*coc-config-diagnostic-virtualTextPrefix*\n\n\tThe prefix added virtual text diagnostics.\n\n\tScope: `language-overridable`, default: `\" \"`\n\n\"diagnostic.virtualTextWinCol\"\t\t\t\t*coc-config-diagnostic-virtualTextWinCol*\n\n\tWindow column number to align virtual text, neovim only.\n\n\tScope: `language-overridable`, default: `null`\n\n\"diagnostic.warningSign\"\t\t\t\t*coc-config-diagnostic-warningSign*\n\n\tText of warning sign.\n\n\tScope: `application`, default: `\"⚠\"`\n\n\"diagnostic.showRelatedInformation\"\t\t\t\t*coc-config-diagnostic-showRelatedInformation*\n\n\tDisplay related information in the diagnostic floating window.\n\n\tScope: `language-overridable`, default: `true`\n\n------------------------------------------------------------------------------\nDOCUMENTHIGHLIGHT\n\t\t\t\t\t\t\t*coc-config-documentHighlight*\n\"documentHighlight.priority\"\t\t\t\t*coc-config-documentHighlight-priority*\n\n\tMatch priority used by document highlight, see ':h matchadd'.\n\n\tScope: `resource`, default: `-1`\n\n\"documentHighlight.limit\"\t\t\t\t*coc-config-documentHighlight-limit*\n\n\tLimit the highlights added by matchaddpos, too many positions could\n\tcause vim slow.\n\n\tScope: `resource`, default: `100`\n\n\"documentHighlight.timeout\"\t\t\t\t*coc-config-documentHighlight-timeout*\n\n\tTimeout for document highlight, in milliseconds.\n\n\tScope: `resource`, default: `300`\n\n------------------------------------------------------------------------------\nHOVER\n\t\t\t\t\t\t\t*coc-config-hover*\n\"hover.autoHide\"\t\t\t\t\t*coc-config-hover-autoHide*\n\n\tAutomatically hide hover float window on CursorMove or InsertEnter.\n\n\tScope: `application`, default: `true`\n\n\"hover.floatConfig\"\t\t\t\t\t*coc-config-hover-floatConfig*\n\n\tConfiguration of floating window/popup for hover documents, see\n\t|coc-config-float|.\n\n\tScope: `application`, default: `null`\n\n\"hover.previewMaxHeight\"\t\t\t\t*coc-config-hover-previewMaxHeight*\n\n\tMax height of preview window for hover.\n\n\tScope: `resource`, default: `12`\n\n\"hover.target\"\t\t\t\t\t\t*coc-config-hover-target*\n\n\tTarget to show hover information, could be `float`, `echo` or\n\t`preview`.\n\n\tScope: `resource`, default: `float`\n\n------------------------------------------------------------------------------\nINLAYHINT\n\t\t\t\t\t\t\t*coc-config-inlayHint*\n\"inlayHint.enable\"\t\t\t\t\t*coc-config-inlayHint-enable*\n\n\tEnable inlay hint support.\n\n\tScope: `language-overridable`, default: `true`\n\n\"inlayHint.enableParameter\"\t\t\t\t*coc-config-inlayHint-enableParameter*\n\n\tEnable inlay hints for parameters.\n\n\tScope: `language-overridable`, default: `true`\n\n\"inlayHint.display\"\t\t\t\t\t*coc-config-inlayHint-display*\n\n\tDisplay inlay hints. Toggle with :CocCommand document.toggleInlayHint\n\n\tScope: `language-overridable`, default: `true`\n\n\"inlayHint.refreshOnInsertMode\"\t\t\t\t*coc-config-inlayHint-refreshOnInsertMode*\n\n\tRefresh inlayHints on insert mode.\n\n\tScope: `language-overridable`, default: `false`\n\n\"inlayHint.position\"\t\t\t\t\t*coc-config-inlayHint-position*\n\n\tControls the position of inlay hint, supports `inline` and `eol`.\n\n\tScope: `language-overridable`, default: `inline`\n\n------------------------------------------------------------------------------\nLINK\n\t\t\t\t\t\t\t*coc-config-links*\n\"links.enable\" \t\t\t\t\t\t*coc-config-links-enable*\n\n\tEnable document links.\n\n\tScope: `language-overridable`, default: `true`\n\n\"links.highlight\"\t\t\t\t\t*coc-config-links-highlight*\n\n\tUse CocLink highlight group to highlight links.\n\n\tScope: `application`, default: `false`\n\n\"links.tooltip\"\t\t\t\t\t\t*coc-config-links-tooltip*\n\n\tShow tooltip of link under cursor on CursorHold.\n\n\tScope: `application`, default: `false`\n\n------------------------------------------------------------------------------\nLIST\n\t\t\t\t\t\t\t*coc-config-list*\n\"list.alignColumns\"\t\t\t\t\t*coc-config-list-alignColumns*\n\n\tWhether to align lists in columns.\n\n\tScope: `application`, default: `false`\n\n\"list.extendedSearchMode\"\t\t\t\t*coc-config-list-extendedSearchMode*\n\n\tEnable extended search mode which allows multiple search patterns\n\tdelimited by spaces.\n\n\tScope: `application`, default: `true`\n\n\"list.floatPreview\" \t\t\t\t\t*coc-config-list-floatPreview*\n\n\tEnable preview with float window/popup, default: `false`.\n\n\tScope: `application`, default: `false`\n\n\"list.height\"\t\t\t\t\t\t*coc-config-list-height*\n\n\tHeight of split list window.\n\n\tScope: `application`, default: `10`\n\n\"list.indicator\"\t\t\t\t\t*coc-config-list-indicator*\n\n\tThe character used as first character in prompt line.\n\n\tScope: `application`, default: `\">\"`\n\n\"list.insertMappings\"\t\t\t\t\t*coc-config-list-insertMappings*\n\n\tCustom keymappings on insert mode.\n\n\tScope: `application`, default: `{}`\n\n\"list.interactiveDebounceTime\"\t\t\t\t*coc-config-list-interactiveDebounceTime*\n\n\tDebounce time for input change on interactive mode.\n\n\tScope: `application`, default: `100`\n\n\"list.limitLines\"\t\t\t\t\t*coc-config-list-limitLines*\n\n\tLimit lines for list buffer.\n\n\tScope: `application`, default: `null`\n\n\"list.maxPreviewHeight\"\t\t\t\t\t*coc-config-list-maxPreviewHeight*\n\n\tMax height for preview window of list.\n\n\tScope: `application`, default: `12`\n\n\"list.menuAction\"\t\t\t\t\t*coc-config-list-menuAction*\n\n\tUse menu picker instead of confirm() for choose action.\n\n\tScope: `application`, default: `false`\n\n\"list.nextKeymap\"\t\t\t\t\t*coc-config-list-nextKeymap*\n\n\tKey used for select next line on insert mode.\n\n\tScope: `application`, default: `\"<C-j>\"`\n\n\"list.normalMappings\"\t\t\t\t\t*coc-config-list-normalMappings*\n\n\tCustom keymappings on normal mode.\n\n\tScope: `application`, default: `{}`\n\n\"list.previewHighlightGroup\"\t\t\t\t*coc-config-list-previewHighlightGroup*\n\n\tHighlight group used for highlight the range in preview window.\n\n\tScope: `application`, default: `\"Search\"`\n\n\"list.previewSplitRight\"\t\t\t\t*coc-config-list-previewSplitRight*\n\n\tUse vsplit for preview window.\n\n\tScope: `application`, default: `false`\n\n\"list.previewToplineOffset\"\t\t\t\t*coc-config-list-previewToplineOffset*\n\n\tTopline offset for list previews\n\n\tScope: `application`, default: `3`\n\n\"list.previewToplineStyle\"\t\t\t\t*coc-config-list-previewToplineStyle*\n\n\tTopline style for list previews, could be \"offset\" or \"middle\".\n\n\tScope: `application`, default: `\"offset\"`\n\n\"list.previousKeymap\"\t\t\t\t\t*coc-config-list-previousKeymap*\n\n\tKey used for select previous line on insert mode.\n\n\tScope: `application`, default: `\"<C-k>\"`\n\n\"list.selectedSignText\"\t\t\t\t\t*coc-config-list-selectedSignText*\n\n\tSign text for selected lines.\n\n\tScope: `application`, default: `\"*\"`\n\n\"list.signOffset\"\t\t\t\t\t*coc-config-list-signOffset*\n\n\tSign offset of list, should be different from other plugins.\n\n\tScope: `application`, default: `900`\n\n\"list.smartCase\"\t\t\t\t\t*coc-config-list-smartCase*\n\n\tUse smartcase match for fuzzy match and strict match, --ignore-case\n\twill be ignored, may not affect interactive list.\n\n\tScope: `application`, default: `false`\n\n\"list.source.diagnostics.includeCode\"\t\t\t*coc-config-list-source-diagnostics-includeCode*\n\n\tWhether to show the diagnostic code in the list.\n\n\tScope: `application`, default: `true`\n\n\"list.source.diagnostics.pathFormat\"\t\t\t*coc-config-list-source-diagnostics-pathFormat*\n\n\tDecide how the filepath is shown in the list.\n\n\tScope: `application`, default: `\"full\"`\n\n\"list.source.outline.ctagsFiletypes\"\t\t\t*coc-config-list-source-outline-ctagsFiletypes*\n\n\tFiletypes that should use ctags for outline instead of language server.\n\n\tScope: `application`, default: `[]`\n\n\"list.source.symbols.excludes\"\t\t\t\t*coc-config-list-source-symbols-excludes*\n\n\tPatterns of minimatch for filepath to exclude from symbols list.\n\n\tScope: `application`, default: `[]`\n\n\"list.statusLineSegments\"\t\t\t\t*coc-config-list-statusLineSegments*\n\n\tAn array of statusline segments that will be used to draw the status \n\tline for list windows.\n\n\tScope: `application`.\n\n------------------------------------------------------------------------------\nNOTIFICATION\n\t\t\t\t\t\t\t*coc-config-notification*\n\"notification.border\"\t\t\t\t\t*coc-config-notification-border*\n\n\tEnable rounded border for notification windows.\n\n\tScope: `application`, default: `true`\n\n\"notification.disabledProgressSources\"\t\t\t*coc-config-notification-disabledProgressSources*\n\n\tSources that should be disabled for message progress, use * to disable\n\tall progresses.\n\n\tScope: `application`, default: `[]`\n\n\"notification.focusable\"\t\t\t\t*coc-config-notification-focusable*\n\n\tEnable focus by user actions (wincmds, mouse events), neovim only.\n\n\tScope: `application`, default: `true`\n\n\"notification.highlightGroup\"\t\t\t\t*coc-config-notification-highlightGroup*\n\n\tHighlight group of notification dialog.\n\n\tScope: `application`, default: `\"Normal\"`\n\n\"notification.marginRight\"\t\t\t\t*coc-config-notification-marginRight*\n\n\tMargin right to the right of editor window.\n\n\tScope: `application`, default: `10`\n\n\"notification.maxHeight\"\t\t\t\t*coc-config-notification-maxHeight*\n\n\tMaximum content height of notification dialog.\n\n\tScope: `application`, default: `10`\n\n\"notification.maxWidth\"\t\t\t\t\t*coc-config-notification-maxWidth*\n\n\tMaximum content width of notification dialog.\n\n\tScope: `application`, default: `60`\n\n\"notification.minProgressWidth\"\t\t\t\t*coc-config-notification-minProgressWidth*\n\n\tMinimal with of progress notification.\n\n\tScope: `application`, default: `30`\n\n\"notification.statusLineProgress\"\t\t\t*coc-config-notification-statusLineProgress*\n\n\tShow progress notification in status line, instead of use float\n\twindow/popup.\n\n\"notification.timeout\"\t\t\t\t\t*coc-config-notification-timeout*\n\n\tTimeout for auto close notifications, in milliseconds.\n\n\tScope: `application`, default: `10000`\n\n\"notification.winblend\"\t\t\t\t\t*coc-config-notification-winblend*\n\n\tWinblend option of notification window, neovim only.\n\n\tScope: `application`, default: `30`\n\n------------------------------------------------------------------------------\nOUTLINE\n\t\t\t\t\t\t\t*coc-config-outline*\n\"outline.autoPreview\"\t\t\t\t\t*coc-config-outline-autoPreview*\n\n\tEnable auto preview on cursor move.\n\n\tScope: `application`, default: `false`\n\n\"outline.autoHide\"\t\t\t\t\t*coc-config-outline-autoHide*\n\n\tAutomatically hide the outline window when an item is clicked.\n\n\tScope: `application`, default: `false`\n\n\"outline.autoWidth\"\t\t\t\t\t*coc-config-outline-autoWidth*\n\n\tAutomatically increase window width to avoid wrapped lines.\n\n\tScope: `application`, default: `true`\n\n\"outline.checkBufferSwitch\"\t\t\t\t*coc-config-outline-checkBufferSwitch*\n\n\tRecreate outline view after user changed to another buffer on current \n\ttab.\n\n\tScope: `application`, default: `true`\n\n\"outline.codeActionKinds\"\t\t\t\t*coc-config-outline-codeActionKinds*\n\n\tFilter code actions in actions menu by kinds.\n\n\tScope: `application`, default: `[\"\",\"quickfix\",\"refactor\"]`\n\n\"outline.detailAsDescription\"\t\t\t\t*coc-config-outline-detailAsDescription*\n\n\tShow detail as description aside with label, when false detail will be\n\tshown in tooltip on cursor hold.\n\n\tScope: `application`, default: `true`\n\n\"outline.expandLevel\"\t\t\t\t\t*coc-config-outline-expandLevel*\n\n\tExpand level of tree nodes.\n\n\tScope: `application`, default: `1`\n\n\"outline.followCursor\"\t\t\t\t\t*coc-config-outline-followCursor*\n\n\tReveal item in outline tree on cursor hold.\n\n\tScope: `application`, default: `true`\n\n\"outline.keepWindow\"\t\t\t\t\t*coc-config-outline-keepWindow*\n\n\tJump back to original window after outline is shown.\n\n\tScope: `application`, default: `false`\n\n\"outline.previewBorder\"\t\t\t\t\t*coc-config-outline-previewBorder*\n\n\tUse border for preview window.\n\n\tScope: `application`, default: `true`\n\n\"outline.previewBorderHighlightGroup\"\t\t\t*coc-config-outline-previewBorderHighlightGroup*\n\n\tBorder highlight group of preview window.\n\n\tScope: `application`, default: `\"Normal\"`\n\n\"outline.previewBorderRounded\"\t\t\t\t*coc-config-outline-previewBorderRounded*\n\n\tUse rounded border for preview window.\n\n\tScope: `application`, default: `false`\n\n\"outline.previewHighlightGroup\"\t\t\t\t*coc-config-outline-previewHighlightGroup*\n\n\tHighlight group of preview window.\n\n\tScope: `application`, default: `\"Normal\"`\n\n\"outline.previewMaxWidth\"\t\t\t\t*coc-config-outline-previewMaxWidth*\n\n\tMax width of preview window.\n\n\tScope: `application`, default: `80`\n\n\"outline.previewWinblend\"\t\t\t\t*coc-config-outline-previewWinblend*\n\n\tEnables pseudo-transparency by set 'winblend' option of window, neovim\n\tonly.\n\n\tScope: `application`, default: `0`\n\n\"outline.showLineNumber\"\t\t\t\t*coc-config-outline-showLineNumber*\n\n\tShow line number of symbols.\n\n\tScope: `application`, default: `true`\n\n\"outline.sortBy\"\t\t\t\t\t*coc-config-outline-sortBy*\n\n\tDefault sort method for symbols outline.\n\n\tScope: `application`, default: `\"category\"`\n\n\"outline.splitCommand\"\t\t\t\t\t*coc-config-outline-splitCommand*\n\n\tWindow split command used by outline.\n\n\tScope: `application`, default: `\"botright 30vs\"`\n\n\"outline.switchSortKey\"\t\t\t\t\t*coc-config-outline-switchSortKey*\n\n\tThe key used to switch sort method for symbols provider of current\n\ttree view.\n\n\tScope: `application`, default: `\"<C-s>\"`\n\n\"outline.togglePreviewKey\"\t\t\t\t*coc-config-outline-togglePreviewKey*\n\n\tThe key used to toggle auto preview feature.\n\n\tScope: `application`, default: `\"p\"`\n\n------------------------------------------------------------------------------\nPULLDIAGNOSTIC\n\t\t\t\t\t\t\t*coc-config-pullDiagnostic*\n\"pullDiagnostic.ignored\"\t\t\t\t*coc-config-pullDiagnostic-ignored*\n\n\tMinimatch patterns to match full filepath that should be ignored for\n\tpullDiagnostic.\n\n\tScope: `application`, default: `[]`\n\n\"pullDiagnostic.onChange\"\t\t\t\t*coc-config-pullDiagnostic-onChange*\n\n\tWhether to pull for diagnostics on document change.\n\n\tScope: `language-overridable`, default: `true`\n\n\"pullDiagnostic.onSave\"\t\t\t\t\t*coc-config-pullDiagnostic-onSave*\n\n\tWhether to pull for diagnostics on document save.\n\n\tScope: `language-overridable`, default: `false`\n\n\"pullDiagnostic.workspace\"\t\t\t\t*coc-config-pullDiagnostic-workspace*\n\n\tWhether to pull for workspace diagnostics when possible.\n\n\tScope: `application`, default: `true`\n\n------------------------------------------------------------------------------\nREFACTOR\n\t\t\t\t\t\t\t*coc-config-refactor*\n\"refactor.afterContext\"\t\t\t\t\t*coc-config-refactor-afterContext*\n\n\tPrint num lines of trailing context after each match.\n\n\tScope: `application`, default: `3`\n\n\"refactor.beforeContext\"\t\t\t\t*coc-config-refactor-beforeContext*\n\n\tPrint num lines of leading context before each match.\n\n\tScope: `application`, default: `3`\n\n\"refactor.openCommand\"\t\t\t\t\t*coc-config-refactor-openCommand*\n\n\tOpen command for refactor window.\n\n\tScope: `application`, default: `\"vsplit\"`\n\n\"refactor.saveToFile\"\t\t\t\t\t*coc-config-refactor-saveToFile*\n\n\tSave changed buffer to file when write refactor buffer with ':noa wa'\n\tcommand.\n\n\tScope: `application`, default: `true`\n\n\"refactor.showMenu\"\t\t\t\t\t*coc-config-refactor-showMenu*\n\n\tRefactor buffer local mapping to bring up menu for this chunk.\n\n\tScope: `application`, default: `\"<Tab>\"`\n\n------------------------------------------------------------------------------\nSEMANTICTOKENS\n\t\t\t\t\t\t\t*coc-config-semanticTokens*\n\"semanticTokens.combinedModifiers\"\t\t\t*coc-config-semanticTokens-combinedModifiers*\n\n\tSemantic token modifiers that should have highlight combined with\n\tsyntax highlights.\n\n\tScope: `language-overridable`, default: `[\"deprecated\"]`\n\n\"semanticTokens.enable\"\t\t\t\t\t*coc-config-semanticTokens-enable*\n\n\tEnable semantic tokens support.\n\n\tScope: `language-overridable`, default: `false`\n\n\"semanticTokens.highlightPriority\"\t\t\t*coc-config-semanticTokens-highlightPriority*\n\n\tPriority for semantic tokens highlight.\n\n\tScope: `language-overridable`, default: `2048`\n\n\"semanticTokens.incrementTypes\"\t\t\t\t*coc-config-semanticTokens-incrementTypes*\n\n\tSemantic token types that should increase highlight when insert at the \n\tstart and end position of token.\n\n\tScope: `language-overridable`, default: `[\"variable\",\"string\",\"parameter\"]`\n\n------------------------------------------------------------------------------\nSIGNATURE\n\t\t\t\t\t\t\t*coc-config-signature*\n\"signature.enable\"\t\t\t\t\t*coc-config-signature-enable*\n\n\tEnable show signature help when trigger character typed.\n\n\tScope: `language-overridable`, default: `true`\n\n\"signature.floatConfig\"\t\t\t\t\t*coc-config-signature-floatConfig*\n\n\tConfiguration of floating window/popup for signature documents, see\n\t|coc-config-float|.\n\n\tScope: `application`, default: `null`\n\n\"signature.hideOnTextChange\"\t\t\t\t*coc-config-signature-hideOnTextChange*\n\n\tHide signature float window when text changed on insert mode.\n\n\tScope: `language-overridable`, default: `false`\n\n\"signature.preferShownAbove\"\t\t\t\t*coc-config-signature-preferShownAbove*\n\n\tShow signature help float window above cursor when possible, require\n\trestart coc.nvim on change.\n\n\tScope: `application`, default: `true`\n\n\"signature.target\"\t\t\t\t\t*coc-config-signature-target*\n\n\tTarget of signature help, use float when possible by default.\n\n\tScope: `language-overridable`, default: `\"float\"`\n\n\"signature.triggerSignatureWait\"\t\t\t*coc-config-signature-triggerSignatureWait*\n\n\tTimeout for trigger signature help, in milliseconds.\n\n\tScope: `language-overridable`, default: `500`\n\n------------------------------------------------------------------------------\nSNIPPET\n\t\t\t\t\t\t\t*coc-config-snippet*\n\"snippet.highlight\"\t\t\t\t\t*coc-config-snippet-highlight*\n\n\tUse highlight group 'CocSnippetVisual' to highlight placeholders with \n\tsame index of current one.\n\n\tScope: `resource`, default: `false`\n\n\"snippet.nextPlaceholderOnDelete\"\t\t\t*coc-config-snippet-nextPlaceholderOnDelete*\n\n\tAutomatically jump to the next placeholder when the current one is \n\tcompletely deleted.\n\n\tScope: `resource`, default: `false`\n\n\"snippet.statusText\"\t\t\t\t\t*coc-config-snippet-statusText*\n\n\tText shown in statusline to indicate snippet session is activated.\n\n\tScope: `application`, default: `\"SNIP\"`\n\n------------------------------------------------------------------------------\nSUGGEST\n\t\t\t\t\t\t\t*coc-config-suggest*\n\"suggest.acceptSuggestionOnCommitCharacter\"\t\t*coc-config-suggest-acceptSuggestionOnCommitCharacter*\n\n\tControls whether suggestions should be accepted on commit characters.\n\tFor example, in JavaScript, the semi-colon (`;`) can be a commit\n\tcharacter that accepts a suggestion and types that character.\n\n\tScope: `language-overridable`, default: `false`\n\n\"suggest.asciiCharactersOnly\"\t\t\t\t*coc-config-suggest-asciiCharactersOnly*\n\n\tTrigger suggest with ASCII characters only.\n\n\tScope: `language-overridable`, default: `false`\n\n\"suggest.segmenterLocales\" \t\t\t\t*coc-config-suggest-segmenterLocales*\n\n\tLocales used for divide sentence into segments for around and buffer\n\tsource, works when NodeJS built with intl support, see:\n\thttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter/Segmenter#parameters\n\tdefault empty string means auto detect, use null to disable this\n\tfeature.\n\n\tScope: `language-overridable`, default: `\"\"`\n\n\"suggest.asciiMatch\"\t\t\t\t\t*coc-config-suggest-asciiMatch*\n\n\tConvert unicode characters to ascii for match.\n\n\tScope: `language-overridable`, default: `true`\n\n\"suggest.autoTrigger\"\t\t\t\t\t*coc-config-suggest-autoTrigger*\n\n\tHow should completion be triggered, could be `\"always\"`, `\"trigger\"`\n\tor `\"none\"`.\n\n\tScope: `language-overridable`, default: `\"always\"`\n\n\"suggest.completionItemKindLabels\"\t\t\t*coc-config-suggest-completionItemKindLabels*\n\n\tSet custom labels to completion items' kinds.\n\tDefault value: >\n\t{\n\t  \"text\": \"v\",\n\t  \"method\": \"f\",\n\t  \"function\": \"f\",\n\t  \"constructor\": \"f\",\n\t  \"field\": \"m\",\n\t  \"variable\": \"v\",\n\t  \"class\": \"C\",\n\t  \"interface\": \"I\",\n\t  \"module\": \"M\",\n\t  \"property\": \"m\",\n\t  \"unit\": \"U\",\n\t  \"value\": \"v\",\n\t  \"enum\": \"E\",\n\t  \"keyword\": \"k\",\n\t  \"snippet\": \"S\",\n\t  \"color\": \"v\",\n\t  \"file\": \"F\",\n\t  \"reference\": \"r\",\n\t  \"folder\": \"F\",\n\t  \"enumMember\": \"m\",\n\t  \"constant\": \"v\",\n\t  \"struct\": \"S\",\n\t  \"event\": \"E\",\n\t  \"operator\": \"O\",\n\t  \"typeParameter\": \"T\",\n\t  \"default\": \"\"\n\t}\n<\n\tScope: `application`\n\n\"suggest.defaultSortMethod\"\t\t\t\t*coc-config-suggest-defaultSortMethod*\n\n\tDefault sorting behavior when trigger is empty, could be `\"length\"`,\n\t`\"alphabetical\"` or `\"none\"`.\n\n\tScope: `language-overridable`, default: `\"length\"`\n\n\"suggest.detailField\"\t\t\t\t\t*coc-config-suggest-detailField*\n\n\tWhere to show the detail text of CompleteItem from language server.\n\n\tScope: `application`, default: `\"preview\"`\n\n\"suggest.enableFloat\" \t\t\t\t\t*coc-config-suggest-enableFloat*\n\n\tEnable float window with documentation aside with popupmenu.\n\n\tScope: `language-overridable`, default: `true`\n\n\"suggest.enablePreselect\"\t\t\t\t*coc-config-suggest-enablePreselect*\n\n\tEnable preselect feature, works when |coc-config-suggest-noselect| is\n\tfalse.\n\n\tScope: `application`, default: `true`\n\n\"suggest.filterGraceful\" \t\t\t\t*coc-config-suggest-filterGraceful*\n\n\tControls whether filtering and sorting suggestions accounts for small\n\ttypos.\n\n\tScope: `language-overridable`, default: `true`\n\n\"suggest.filterOnBackspace\" \t\t\t\t*coc-config-suggest-filterOnBackspace*\n\n\tFilter complete items on backspace.\n\n\tScope: `language-overridable`, default: `true`\n\n\"suggest.floatConfig\"\t\t\t\t\t*coc-config-suggest-floatConfig*\n\n\tConfigure style of popup menu and documentation window for completion,\n\tsee |coc-config-float|.\n\n\tNote: some properties not work, including: \"focusable\", \"close\" and\n\t\"maxHeight\" (use 'pumheight' option for maximum height of popup menu).\n\n\tNote: \"maxWidth\" not works for popup menu, use\n\t|coc-config-suggest-labelMaxLength| instead.\n\n\"suggest.formatItems\"\t\t\t\t\t*coc-config-suggest-formatItems*\n\n\tItems shown in popup menu in order.\n\n\tScope: `application`, default: `[\"abbr\",\"menu\",\"kind\",\"shortcut\"]`\n\n\"suggest.highPrioritySourceLimit\"\t\t\t*coc-config-suggest-highPrioritySourceLimit*\n\n\tMax items count for source priority bigger than or equal to 90.\n\n\tScope: `language-overridable`, default: `null`\n\n\"suggest.insertMode\" \t\t\t\t\t*coc-config-suggest-insertMode*\n\n\tControls whether words are overwritten when accepting completions.\n\n\tScope: `language-overridable`, default: `“replace\"`\n\n\"suggest.ignoreRegexps\"\t\t\t\t\t*coc-config-suggest-ignoreRegexps*\n\n\tRegexps to ignore when trigger suggest.\n\n\tScope: `language-overridable`, default: `[]`\n\n\"suggest.invalidInsertCharacters\"\t\t\t*coc-config-suggest-invalidInsertCharacters*\n\n\tInvalid character for strip valid word when inserting text of complete\n\titem.\n\n\tScope: `application`, default: `[\"\\r\",\"\\n\"]`\n\n\"suggest.labelMaxLength\"\t\t\t\t*coc-config-suggest-labelMaxLength*\n\n\tMax length of abbr that shown as label of complete item.\n\n\tScope: `application`, default: `200`\n\n\"suggest.languageSourcePriority\"\t\t\t*coc-config-suggest-languageSourcePriority*\n\n\tPriority of language sources.\n\n\tScope: `language-overridable`, default: `99`\n\n\"suggest.localityBonus\"\t\t\t\t\t*coc-config-suggest-localityBonus*\n\n\tBoost suggestions that appear closer to the cursor position.\n\n\tScope: `language-overridable`, default: `true`\n\n\"suggest.lowPrioritySourceLimit\"\t\t\t*coc-config-suggest-lowPrioritySourceLimit*\n\n\tMax items count for source priority lower than 90.\n\n\tScope: `language-overridable`, default: `null`\n\n\"suggest.maxCompleteItemCount\"\t\t\t\t*coc-config-suggest-maxCompleteItemCount*\n\n\tMaximum number of complete items shown in vim.\n\n\tScope: `language-overridable`, default: `256`\n\n\"suggest.minTriggerInputLength\"\t\t\t\t*coc-config-suggest-minTriggerInputLength*\n\n\tMinimal input length for trigger completion.\n\n\tScope: `language-overridable`, default: `1`\n\n\"suggest.noselect\"\t\t\t\t\t*coc-config-suggest-noselect*\n\n\tNot make vim select first item on popupmenu shown.\n\n\tScope: `application`, default: `false`\n\n\"suggest.preferCompleteThanJumpPlaceholder\"\t\t*coc-config-suggest-preferCompleteThanJumpPlaceholder*\n\n\tConfirm completion instead of jump to next placeholder when completion\n\tis activated.\n\n\tScope: `resource`, default: `false`\n\n\"suggest.pumFloatConfig\"\t\t\t\t*coc-config-suggest-pumFloatConfig*\n\n\tConfigure style of popup menu, |coc-config-suggest-floatConfig| is\n\tused when not specified, see |coc-config-float|.\n\n\tAvailable properties: \"border\", \"rounded\", \"highlight\",\n\t\"borderhighlight\", \"winblend\", \"title\" and \"shadow\".\n\n\tNote: 'winblend' option is used for custom popup menu when not\n\tconfigured (neovim only), use 'pumwidth' for minimal width of popup\n\tmenu and 'pumheight' for maximum height of popup menu.\n\n\tScope: `application`, default: `null`\n\n\"suggest.removeDuplicateItems\"\t\t\t\t*coc-config-suggest-removeDuplicateItems*\n\n\tRemove completion items with duplicated word for all sources, snippet\n\titems are excluded.\n\n\tScope: `language-overridable`, default: `false`\n\n\"suggest.removeCurrentWord\"\t\t\t\t*coc-config-suggest-removeCurrentWord*\n\n\tRemove word item (from around and buffer source) that is identical to\n\tcurrent input\n\n\tScope: `language-overridable`, default: `false`\n\n\"suggest.reTriggerAfterIndent\"\t\t\t\t*coc-config-suggest-reTriggerAfterIndent*\n\n\tControls re-trigger or not after indent changes.\n\n\tScope: `application`, default: `true`\n\n\"suggest.reversePumAboveCursor\"\t\t\t\t*coc-config-suggest-reversePumAboveCursor*\n\n\tReverse order of complete items when pum shown above cursor.\n\n\tScope: `application`, default: `false`\n\n\"suggest.selection\"\t\t\t\t\t*coc-config-suggest-selection*\n\n\tControls how suggestions are pre-selected when showing the suggest list.\n\n\tScope: `application`, default: `\"first\"`\n\n\"suggest.snippetIndicator\"\t\t\t\t*coc-config-suggest-snippetIndicator*\n\n\tThe character used in abbr of complete item to indicate the item could\n\tbe expand as snippet.\n\n\tScope: `application`, default: `\"~\"`\n\n\"suggest.snippetsSupport\"\t\t\t\t*coc-config-suggest-snippetsSupport*\n\n\tSet to false to disable snippets support of completion.\n\n\tScope: `language-overridable`, default: `true`\n\n\"suggest.timeout\"\t\t\t\t\t*coc-config-suggest-timeout*\n\n\tTimeout for completion, in milliseconds.\n\n\tScope: `language-overridable`, default: `5000`\n\n\"suggest.triggerAfterInsertEnter\"\t\t\t*coc-config-suggest-triggerAfterInsertEnter*\n\n\tTrigger completion after InsertEnter, |coc-config-suggest-autoTrigger|\n\tshould be 'always' to enable this option\n\n\tScope: `language-overridable`, default: `false`\n\n\"suggest.triggerCompletionWait\"\t\t\t\t*coc-config-suggest-triggerCompletionWait*\n\n\tWait time between text change and completion start, cancel completion\n\twhen text changed during wait.\n\n\tScope: `language-overridable`, default: `0`\n\n\"suggest.virtualText\"\t\t\t\t\t*coc-config-suggest-virtualText*\n\n\tShow virtual text after cursor for insert word of current selected\n\tcomplete item.\n\n\tScope: `application`, default: `false`\n\n------------------------------------------------------------------------------\nINLINE COMPLETION\n\t\t\t\t\t\t\t*coc-config-inlineSuggest*\n\n\"inlineSuggest.autoTrigger\"\t\t\t\t*coc-config-inlineSuggest-autoTrigger*\n\n\tEnable automatically trigger inline completion after completion done\n\tor cursor hold.\n\n\tScope: `language-overridable`, default: `true`\n\n\"inlineSuggest.triggerCompletionWait\"   \t\t*coc-config-inlineSuggest-triggerCompletionWait*\n\n\tWait time in milliseconds between text synchronize and trigger inline\n\tcompletion.\n\n\tScope: `language-overridable`, default: `10`\n\n------------------------------------------------------------------------------\nTYPEHIERARCHY\n\t\t\t\t\t\t\t*coc-config-typeHierarchy*\n\"typeHierarchy.enableTooltip\"\t\t\t\t*coc-config-typeHierarchy-enableTooltip*\n\n\tEnable tooltip to show relative filepath of type hierarchy item.\n\n\tScope: `application`, default: `true`\n\n\"typeHierarchy.openCommand\"\t\t\t\t*coc-config-typeHierarchy-openCommand*\n\n\tOpen command for type hierarchy tree view.\n\n\tScope: `application`, default: `\"edit\"`\n\n\"typeHierarchy.splitCommand\"\t\t\t\t*coc-config-typeHierarchy-splitCommand*\n\n\tWindow split command used by type hierarchy tree view.\n\n\tScope: `application`, default: `\"botright 30vs\"`\n\n==============================================================================\nvim:tw=78:nosta:noet:ts=8:sts=0:ft=help:noet:fen:\n"
  },
  {
    "path": "doc/coc-example-config.lua",
    "content": "-- https://raw.githubusercontent.com/neoclide/coc.nvim/refs/heads/master/doc/coc-example-config.lua\n\n-- Some servers have issues with backup files, see #649\nvim.opt.backup = false\nvim.opt.writebackup = false\n\n-- Having longer updatetime (default is 4000 ms = 4s) leads to noticeable\n-- delays and poor user experience\nvim.opt.updatetime = 300\n\n-- Always show the signcolumn, otherwise it would shift the text each time\n-- diagnostics appeared/became resolved\nvim.opt.signcolumn = 'yes'\n\nlocal keyset = vim.keymap.set\n-- Autocomplete\nfunction _G.check_back_space()\n  local col = vim.fn.col('.') - 1\n  return col == 0 or vim.fn.getline('.'):sub(col, col):match('%s') ~= nil\nend\n\n-- Use Tab for trigger completion with characters ahead and navigate\n-- NOTE: There's always a completion item selected by default, you may want to enable\n-- no select by setting `\"suggest.noselect\": true` in your configuration file\n-- NOTE: Use command ':verbose imap <tab>' to make sure Tab is not mapped by\n-- other plugins before putting this into your config\nlocal opts = { silent = true, noremap = true, expr = true, replace_keycodes = false }\nkeyset('i', '<TAB>', 'coc#pum#visible() ? coc#pum#next(1) : v:lua.check_back_space() ? \"<TAB>\" : coc#refresh()', opts)\nkeyset('i', '<S-TAB>', [[coc#pum#visible() ? coc#pum#prev(1) : \"\\<C-h>\"]], opts)\n\n-- Make <CR> to accept selected completion item or notify coc.nvim to format\n-- <C-g>u breaks current undo, please make your own choice\nkeyset('i', '<cr>', [[coc#pum#visible() ? coc#pum#confirm() : \"\\<C-g>u\\<CR>\\<c-r>=coc#on_enter()\\<CR>\"]], opts)\n\n-- Use <c-j> to trigger snippets\nkeyset('i', '<c-j>', '<Plug>(coc-snippets-expand-jump)')\n-- Use <c-space> to trigger completion\nkeyset('i', '<c-space>', 'coc#refresh()', { silent = true, expr = true })\n\n-- Use `[g` and `]g` to navigate diagnostics\n-- Use `:CocDiagnostics` to get all diagnostics of current buffer in location list\nkeyset('n', '[g', '<Plug>(coc-diagnostic-prev)', { silent = true })\nkeyset('n', ']g', '<Plug>(coc-diagnostic-next)', { silent = true })\n\n-- GoTo code navigation\nkeyset('n', 'gd', '<Plug>(coc-definition)', { silent = true })\nkeyset('n', 'gy', '<Plug>(coc-type-definition)', { silent = true })\nkeyset('n', 'gi', '<Plug>(coc-implementation)', { silent = true })\nkeyset('n', 'gr', '<Plug>(coc-references)', { silent = true })\n\n\n-- Use K to show documentation in preview window\nfunction _G.show_docs()\n  local cw = vim.fn.expand('<cword>')\n  if vim.fn.index({ 'vim', 'help' }, vim.bo.filetype) >= 0 then\n    vim.api.nvim_command('h ' .. cw)\n  elseif vim.api.nvim_eval('coc#rpc#ready()') then\n    vim.fn.CocActionAsync('doHover')\n  else\n    vim.api.nvim_command('!' .. vim.o.keywordprg .. ' ' .. cw)\n  end\nend\n\nkeyset('n', 'K', '<CMD>lua _G.show_docs()<CR>', { silent = true })\n\n\n-- Highlight the symbol and its references on a CursorHold event(cursor is idle)\nvim.api.nvim_create_augroup('CocGroup', {})\nvim.api.nvim_create_autocmd('CursorHold', {\n  group = 'CocGroup',\n  command = \"silent call CocActionAsync('highlight')\",\n  desc = 'Highlight symbol under cursor on CursorHold'\n})\n\n\n-- Symbol renaming\nkeyset('n', '<leader>rn', '<Plug>(coc-rename)', { silent = true })\n\n\n-- Formatting selected code\nkeyset('x', '<leader>f', '<Plug>(coc-format-selected)', { silent = true })\nkeyset('n', '<leader>f', '<Plug>(coc-format-selected)', { silent = true })\n\n\n-- Setup formatexpr specified filetype(s)\nvim.api.nvim_create_autocmd('FileType', {\n  group = 'CocGroup',\n  pattern = 'typescript,json',\n  command = \"setl formatexpr=CocAction('formatSelected')\",\n  desc = 'Setup formatexpr specified filetype(s).'\n})\n\n-- Apply codeAction to the selected region\n-- Example: `<leader>aap` for current paragraph\nlocal opts = { silent = true, nowait = true }\nkeyset('x', '<leader>a', '<Plug>(coc-codeaction-selected)', opts)\nkeyset('n', '<leader>a', '<Plug>(coc-codeaction-selected)', opts)\n\n-- Remap keys for apply code actions at the cursor position.\nkeyset('n', '<leader>ac', '<Plug>(coc-codeaction-cursor)', opts)\n-- Remap keys for apply source code actions for current file.\nkeyset('n', '<leader>as', '<Plug>(coc-codeaction-source)', opts)\n-- Apply the most preferred quickfix action on the current line.\nkeyset('n', '<leader>qf', '<Plug>(coc-fix-current)', opts)\n\n-- Remap keys for apply refactor code actions.\nkeyset('n', '<leader>re', '<Plug>(coc-codeaction-refactor)', { silent = true })\nkeyset('x', '<leader>r', '<Plug>(coc-codeaction-refactor-selected)', { silent = true })\nkeyset('n', '<leader>r', '<Plug>(coc-codeaction-refactor-selected)', { silent = true })\n\n-- Run the Code Lens actions on the current line\nkeyset('n', '<leader>cl', '<Plug>(coc-codelens-action)', opts)\n\n\n-- Map function and class text objects\n-- NOTE: Requires 'textDocument.documentSymbol' support from the language server\nkeyset('x', 'if', '<Plug>(coc-funcobj-i)', opts)\nkeyset('o', 'if', '<Plug>(coc-funcobj-i)', opts)\nkeyset('x', 'af', '<Plug>(coc-funcobj-a)', opts)\nkeyset('o', 'af', '<Plug>(coc-funcobj-a)', opts)\nkeyset('x', 'ic', '<Plug>(coc-classobj-i)', opts)\nkeyset('o', 'ic', '<Plug>(coc-classobj-i)', opts)\nkeyset('x', 'ac', '<Plug>(coc-classobj-a)', opts)\nkeyset('o', 'ac', '<Plug>(coc-classobj-a)', opts)\n\n\n-- Remap <C-f> and <C-b> to scroll float windows/popups\n---@diagnostic disable-next-line: redefined-local\nlocal opts = { silent = true, nowait = true, expr = true }\nkeyset('n', '<C-f>', 'coc#float#has_scroll() ? coc#float#scroll(1) : \"<C-f>\"', opts)\nkeyset('n', '<C-b>', 'coc#float#has_scroll() ? coc#float#scroll(0) : \"<C-b>\"', opts)\nkeyset('i', '<C-f>',\n  'coc#float#has_scroll() ? \"<c-r>=coc#float#scroll(1)<cr>\" : \"<Right>\"', opts)\nkeyset('i', '<C-b>',\n  'coc#float#has_scroll() ? \"<c-r>=coc#float#scroll(0)<cr>\" : \"<Left>\"', opts)\nkeyset('v', '<C-f>', 'coc#float#has_scroll() ? coc#float#scroll(1) : \"<C-f>\"', opts)\nkeyset('v', '<C-b>', 'coc#float#has_scroll() ? coc#float#scroll(0) : \"<C-b>\"', opts)\n\n\n-- Use CTRL-S for selections ranges\n-- Requires 'textDocument/selectionRange' support of language server\nkeyset('n', '<C-s>', '<Plug>(coc-range-select)', { silent = true })\nkeyset('x', '<C-s>', '<Plug>(coc-range-select)', { silent = true })\n\n\n-- Add `:Format` command to format current buffer\nvim.api.nvim_create_user_command('Format', \"call CocAction('format')\", {})\n\n-- \" Add `:Fold` command to fold current buffer\nvim.api.nvim_create_user_command('Fold', \"call CocAction('fold', <f-args>)\", { nargs = '?' })\n\n-- Add `:OR` command for organize imports of the current buffer\nvim.api.nvim_create_user_command('OR', \"call CocActionAsync('runCommand', 'editor.action.organizeImport')\", {})\n\n-- Add (Neo)Vim's native statusline support\n-- NOTE: Please see `:h coc-status` for integrations with external plugins that\n-- provide custom statusline: lightline.vim, vim-airline\nvim.opt.statusline:prepend(\"%{coc#status()}%{get(b:,'coc_current_function','')}\")\n\n-- Mappings for CoCList\n-- code actions and coc stuff\n---@diagnostic disable-next-line: redefined-local\nlocal opts = { silent = true, nowait = true }\n-- Show all diagnostics\nkeyset('n', '<space>a', ':<C-u>CocList diagnostics<cr>', opts)\n-- Manage extensions\nkeyset('n', '<space>e', ':<C-u>CocList extensions<cr>', opts)\n-- Show commands\nkeyset('n', '<space>c', ':<C-u>CocList commands<cr>', opts)\n-- Find symbol of current document\nkeyset('n', '<space>o', ':<C-u>CocList outline<cr>', opts)\n-- Search workspace symbols\nkeyset('n', '<space>s', ':<C-u>CocList -I symbols<cr>', opts)\n-- Do default action for next item\nkeyset('n', '<space>j', ':<C-u>CocNext<cr>', opts)\n-- Do default action for previous item\nkeyset('n', '<space>k', ':<C-u>CocPrev<cr>', opts)\n-- Resume latest coc list\nkeyset('n', '<space>p', ':<C-u>CocListResume<cr>', opts)\n"
  },
  {
    "path": "doc/coc-example-config.vim",
    "content": "\" https://raw.githubusercontent.com/neoclide/coc.nvim/refs/heads/master/doc/coc-example-config.vim\n\n\" May need for Vim (not Neovim) since coc.nvim calculates byte offset by count\n\" utf-8 byte sequence\nset encoding=utf-8\n\" Some servers have issues with backup files, see #649\nset nobackup\nset nowritebackup\n\n\" Having longer updatetime (default is 4000 ms = 4s) leads to noticeable\n\" delays and poor user experience\nset updatetime=300\n\n\" Always show the signcolumn, otherwise it would shift the text each time\n\" diagnostics appear/become resolved\nset signcolumn=yes\n\n\" Use tab for trigger completion with characters ahead and navigate\n\" NOTE: There's always complete item selected by default, you may want to enable\n\" no select by `\"suggest.noselect\": true` in your configuration file\n\" NOTE: Use command ':verbose imap <tab>' to make sure tab is not mapped by\n\" other plugin before putting this into your config\ninoremap <silent><expr> <TAB>\n      \\ coc#pum#visible() ? coc#pum#next(1) :\n      \\ CheckBackspace() ? \"\\<Tab>\" :\n      \\ coc#refresh()\ninoremap <expr><S-TAB> coc#pum#visible() ? coc#pum#prev(1) : \"\\<C-h>\"\n\n\" Make <CR> to accept selected completion item or notify coc.nvim to format\n\" <C-g>u breaks current undo, please make your own choice\ninoremap <silent><expr> <CR> coc#pum#visible() ? coc#pum#confirm()\n                              \\: \"\\<C-g>u\\<CR>\\<c-r>=coc#on_enter()\\<CR>\"\n\nfunction! CheckBackspace() abort\n  let col = col('.') - 1\n  return !col || getline('.')[col - 1]  =~# '\\s'\nendfunction\n\n\" Use <c-space> to trigger completion\nif has('nvim')\n  inoremap <silent><expr> <c-space> coc#refresh()\nelse\n  inoremap <silent><expr> <c-@> coc#refresh()\nendif\n\n\" Use `[g` and `]g` to navigate diagnostics\n\" Use `:CocDiagnostics` to get all diagnostics of current buffer in location list\nnmap <silent> [g <Plug>(coc-diagnostic-prev)\nnmap <silent> ]g <Plug>(coc-diagnostic-next)\n\n\" GoTo code navigation\nnmap <silent> gd <Plug>(coc-definition)\nnmap <silent> gy <Plug>(coc-type-definition)\nnmap <silent> gi <Plug>(coc-implementation)\nnmap <silent> gr <Plug>(coc-references)\n\n\" Use K to show documentation in preview window\nnnoremap <silent> K :call ShowDocumentation()<CR>\n\nfunction! ShowDocumentation()\n  if CocAction('hasProvider', 'hover')\n    call CocActionAsync('doHover')\n  else\n    call feedkeys('K', 'in')\n  endif\nendfunction\n\n\" Highlight the symbol and its references when holding the cursor\nautocmd CursorHold * silent call CocActionAsync('highlight')\n\n\" Symbol renaming\nnmap <leader>rn <Plug>(coc-rename)\n\n\" Formatting selected code\nxmap <leader>f  <Plug>(coc-format-selected)\nnmap <leader>f  <Plug>(coc-format-selected)\n\naugroup mygroup\n  autocmd!\n  \" Setup formatexpr specified filetype(s)\n  autocmd FileType typescript,json setl formatexpr=CocAction('formatSelected')\naugroup end\n\n\" Applying code actions to the selected code block\n\" Example: `<leader>aap` for current paragraph\nxmap <leader>a  <Plug>(coc-codeaction-selected)\nnmap <leader>a  <Plug>(coc-codeaction-selected)\n\n\" Remap keys for applying code actions at the cursor position\nnmap <leader>ac  <Plug>(coc-codeaction-cursor)\n\" Remap keys for apply code actions affect whole buffer\nnmap <leader>as  <Plug>(coc-codeaction-source)\n\" Apply the most preferred quickfix action to fix diagnostic on the current line\nnmap <leader>qf  <Plug>(coc-fix-current)\n\n\" Remap keys for applying refactor code actions\nnmap <silent> <leader>re <Plug>(coc-codeaction-refactor)\nxmap <silent> <leader>r  <Plug>(coc-codeaction-refactor-selected)\nnmap <silent> <leader>r  <Plug>(coc-codeaction-refactor-selected)\n\n\" Run the Code Lens action on the current line\nnmap <leader>cl  <Plug>(coc-codelens-action)\n\n\" Map function and class text objects\n\" NOTE: Requires 'textDocument.documentSymbol' support from the language server\nxmap if <Plug>(coc-funcobj-i)\nomap if <Plug>(coc-funcobj-i)\nxmap af <Plug>(coc-funcobj-a)\nomap af <Plug>(coc-funcobj-a)\nxmap ic <Plug>(coc-classobj-i)\nomap ic <Plug>(coc-classobj-i)\nxmap ac <Plug>(coc-classobj-a)\nomap ac <Plug>(coc-classobj-a)\n\n\" Remap <C-f> and <C-b> to scroll float windows/popups\nnnoremap <silent><nowait><expr> <C-f> coc#float#has_scroll() ? coc#float#scroll(1) : \"\\<C-f>\"\nnnoremap <silent><nowait><expr> <C-b> coc#float#has_scroll() ? coc#float#scroll(0) : \"\\<C-b>\"\ninoremap <silent><nowait><expr> <C-f> coc#float#has_scroll() ? \"\\<c-r>=coc#float#scroll(1)\\<cr>\" : \"\\<Right>\"\ninoremap <silent><nowait><expr> <C-b> coc#float#has_scroll() ? \"\\<c-r>=coc#float#scroll(0)\\<cr>\" : \"\\<Left>\"\nvnoremap <silent><nowait><expr> <C-f> coc#float#has_scroll() ? coc#float#scroll(1) : \"\\<C-f>\"\nvnoremap <silent><nowait><expr> <C-b> coc#float#has_scroll() ? coc#float#scroll(0) : \"\\<C-b>\"\n\n\" Use CTRL-S for selections ranges\n\" Requires 'textDocument/selectionRange' support of language server\nnmap <silent> <C-s> <Plug>(coc-range-select)\nxmap <silent> <C-s> <Plug>(coc-range-select)\n\n\" Add `:Format` command to format current buffer\ncommand! -nargs=0 Format :call CocActionAsync('format')\n\n\" Add `:Fold` command to fold current buffer\ncommand! -nargs=? Fold :call     CocAction('fold', <f-args>)\n\n\" Add `:OR` command for organize imports of the current buffer\ncommand! -nargs=0 OR   :call     CocActionAsync('runCommand', 'editor.action.organizeImport')\n\n\" Add (Neo)Vim's native statusline support\n\" NOTE: Please see `:h coc-status` for integrations with external plugins that\n\" provide custom statusline: lightline.vim, vim-airline\nset statusline^=%{coc#status()}%{get(b:,'coc_current_function','')}\n\n\" Mappings for CoCList\n\" Show all diagnostics\nnnoremap <silent><nowait> <space>a  :<C-u>CocList diagnostics<cr>\n\" Manage extensions\nnnoremap <silent><nowait> <space>e  :<C-u>CocList extensions<cr>\n\" Show commands\nnnoremap <silent><nowait> <space>c  :<C-u>CocList commands<cr>\n\" Find symbol of current document\nnnoremap <silent><nowait> <space>o  :<C-u>CocList outline<cr>\n\" Search workspace symbols\nnnoremap <silent><nowait> <space>s  :<C-u>CocList -I symbols<cr>\n\" Do default action for next item\nnnoremap <silent><nowait> <space>j  :<C-u>CocNext<CR>\n\" Do default action for previous item\nnnoremap <silent><nowait> <space>k  :<C-u>CocPrev<CR>\n\" Resume latest coc list\nnnoremap <silent><nowait> <space>p  :<C-u>CocListResume<CR>\n"
  },
  {
    "path": "doc/coc.txt",
    "content": "*coc-nvim.txt*\t\t\t\t\tNodeJS client for Vim & Neovim.\n\nVersion: 0.0.82\nAuthor: Qiming Zhao <chemzqm at gmail.com>\n\nCONTENTS\t\t\t\t\t*coc-contents*\n\nIntroduction\t\t\t\t\t|coc-introduction|\nRequirements\t\t\t\t\t|coc-requirements|\nInstallation\t\t\t\t\t|coc-installation|\nFile system watch \t\t\t\t|coc-filesystemwatch|\nLanguage server \t\t\t\t|coc-languageserver|\nExtensions \t\t\t\t\t|coc-extensions|\nConfiguration\t\t\t\t\t|coc-configuration|\nFloating windows \t\t\t\t|coc-floating|\nLSP features \t\t\t\t\t|coc-lsp|\n  Document \t\t\t\t\t|coc-document|\n  Hover \t\t\t\t\t|coc-hover|\n  Completion\t\t\t\t\t|coc-completion|\n  Inline completion\t\t\t\t|coc-inlineCompletion|\n  Diagnostics \t\t\t\t\t|coc-diagnostics|\n  Pull diagnostics \t\t\t\t|coc-pullDiagnostics|\n  Locations \t\t\t\t\t|coc-locations|\n  Rename \t\t\t\t\t|coc-rename|\n  Signature help \t\t\t\t|coc-signature|\n  Inlay hint \t\t\t\t\t|coc-inlayHint|\n  Format \t\t\t\t\t|coc-format|\n  Code action \t\t\t\t\t|coc-code-actions|\n  Document highlights \t\t\t\t|coc-document-highlights|\n  Document colors \t\t\t\t|coc-document-colors|\n  Document links \t\t\t\t|coc-document-links|\n  Snippets \t\t\t\t\t|coc-snippets|\n  Workspace\t\t\t\t\t|coc-workspace|\n  Cursors \t\t\t\t\t|coc-cursors|\n  Outline \t\t\t\t\t|coc-outline|\n  Call hierarchy \t\t\t\t|coc-callHierarchy|\n  Type hierarchy \t\t\t\t|coc-typeHierarchy|\n  Semantic highlights \t\t\t\t|coc-semantic-highlights|\n  Fold \t\t\t\t\t\t|coc-fold|\n  Selection range \t\t\t\t|coc-selection-range|\n  Code Lens \t\t\t\t\t|coc-code-lens|\n  Linked editing \t\t\t\t|coc-linked-editing|\nInterface\t\t\t\t\t|coc-interface|\n  Key mappings\t\t\t\t\t|coc-key-mappings|\n  Variables\t\t\t\t\t|coc-variables|\n    Environment variables \t\t\t|coc-environment-variables|\n    Buffer variables \t\t\t\t|coc-buffer-variables|\n    Window variables \t\t\t\t|coc-window-variables|\n    Global variables \t\t\t\t|coc-global-variables|\n  Functions\t\t\t\t\t|coc-functions|\n  Commands\t\t\t\t\t|coc-commands|\n  Autocmds\t\t\t\t\t|coc-autocmds|\n  Highlights\t\t\t\t\t|coc-highlights|\nTree \t\t\t\t\t\t|coc-tree|\n  Tree mappings \t\t\t\t|coc-tree-mappings|\n  Tree filter \t\t\t\t\t|coc-tree-filter|\nList\t\t\t\t\t\t|coc-list|\n  List command\t\t\t\t\t|coc-list-command|\n  List command options \t\t\t\t|coc-list-options|\n  List configuration\t\t\t\t|coc-list-configuration|\n  List mappings\t\t\t\t\t|coc-list-mappings|\n  list sources\t\t\t\t\t|coc-list-sources|\nDialog \t\t\t\t\t\t|coc-dialog|\n  Dialog basic \t\t\t\t\t|coc-dialog-basic|\n  Dialog confirm  \t\t\t\t|coc-dialog-confirm|\n  Dialog input \t\t\t\t\t|coc-dialog-input|\n  Dialog menu \t\t\t\t\t|coc-dialog-menu|\n  Dialog picker                                 |coc-dialog-picker|\nNotification\t\t\t\t\t|coc-notification|\nStatusline integration\t\t\t\t|coc-status|\n  Manual\t\t\t\t\t|coc-status-manual|\n  Airline\t\t\t\t\t|coc-status-airline|\n  Lightline\t\t\t\t\t|coc-status-lightline|\nCreate plugins \t\t\t\t\t|coc-plugins|\nFAQ\t\t\t\t\t\t|coc-faq|\nChange log\t\t\t\t\t|coc-changelog|\n\n==============================================================================\nINTRODUCTION\t\t\t\t\t\t*coc-introduction*\n\nCoc.nvim enhances your (Neo)Vim to match the user experience provided by\nVSCode through a rich extension ecosystem and implemented the client features\nspecified by Language Server Protocol (3.17 for now), see |coc-lsp|.\n\nSome features (like completion and inline completion) automatically works by\ndefault, all of them can be disabled by |coc-configuration|.\n\nSome key features: ~\n\n  • Typescript APIs compatible with both Vim8 and Neovim.\n  • Loading VSCode-like extensions |coc-api-extension|.\n  • Configuring coc.nvim and its extensions with JSON configuration\n    |coc-configuration|.\n  • Configuring Language Servers that using Language Server Protocol (LSP)\n    |coc-config-languageserver|.\n\nIt is designed for best possible integration with other Vim plugins.\n\nNote: coc.nvim doesn't come with support for any specific language. You\nwill need to install coc.nvim extensions |coc-extensions| or set up the\nlanguage server by use |coc-config-languageserver|.\n\nNote: multiple language servers for same document is allowed, but you should\navoid configure same language server that already used by coc.nvim extension.\n\nNote: automatic completion plugins can't play nicely together, you can disable\nautomatic completion of coc.nvim by use `\"suggest.autoTrigger\": \"none\"` (or\n`\"suggest.autoTrigger\": \"trigger\"`) in your |coc-configuration|.\n\n==============================================================================\nREQUIREMENTS\t\t\t\t\t\t*coc-requirements*\n\nNeovim >= 0.8.0 or Vim >= 9.0.0483.\n\nNodeJS https://nodejs.org/ >= 16.18.0.\n\nFor neovim user, use command |:checkhealth| to check issue with current\nenvironment.\n\n==============================================================================\nINSTALLATION\t\t\t\t\t\t*coc-installation*\n\nIf you're using [vim-plug](https://github.com/junegunn/vim-plug), add this to\nyour `init.vim` or `.vimrc`: >\n\n  Plug 'neoclide/coc.nvim', {'branch': 'release'}\n<\nAnd run: >\n\n  :PlugInstall\n\nFor other plugin managers, make sure to use the release branch (unless you\nneed to build from typescript source code).\n\nTo use Vim's native |packages| on Linux or macOS, use script like: >\n\n  #!/bin/sh\n  # for vim8\n  mkdir -p ~/.vim/pack/coc/start\n  cd ~/.vim/pack/coc/start\n  curl --fail -L https://github.com/neoclide/coc.nvim/archive/release.tar.gz|tar xzfv -\n  vim -c 'helptags ~/.vim/pack/coc/start/doc|q'\n\n  # for neovim\n  mkdir -p ~/.local/share/nvim/site/pack/coc/start\n  cd ~/.local/share/nvim/site/pack/coc/start\n  curl --fail -L https://github.com/neoclide/coc.nvim/archive/release.tar.gz|tar xzfv -\n  nvim -c 'helptags ~/.local/share/nvim/site/pack/coc/start|q'\n\nwhen using source code of coc.nvim, you'll have to run `npm install` in project\nroot of coc.nvim.\n\n==============================================================================\nFile system watch\t\t\t\t\t*coc-filesystemwatch*\n\nWatchman https://facebook.github.io/watchman/ is used by coc.nvim to provide\nfile change detection to extensions and languageservers.  The watchman command\nis detected from your `$PATH`, the feature will silently fail when watchman\ncan't work.\n\nWatchman automatically watch |coc-workspace-folders| for file events by\ndefault.\n\nUse command: >\n\t:CocCommand workspace.showOutput watchman\n<\nto open output channel of watchman.\n\nUse configuration |coc-config-fileSystemWatch| to change behavior of file\nsystem watch.\n\nNote: The default filesystem watch limit can be easily exceeded for many\nprojects, checkout\nhttps://facebook.github.io/watchman/docs/install#system-specific-preparation\n\n==============================================================================\nLANGUAGESERVER \t\t\t\t\t\t*coc-languageserver*\n\nLanguage servers are services which provide LSP features, the servers are\nprovided by |coc-extensions| or |coc-config-languageserver|.\n\nTo get language server for your language, see:\nhttps://github.com/neoclide/coc.nvim/wiki/Language-servers\n\nTo debug language server, see:\nhttps://github.com/neoclide/coc.nvim/wiki/Debug-language-server\n\n==============================================================================\nEXTENSIONS\t\t\t\t\t\t*coc-extensions*\n\nCompare to |coc-config-languageserver| extensions are more powerful since they\ncould contribute json schemes, commands, and use middleware methods of\nlanguageserver to provide better results.  Extensions could provide more\nfeatures by make use of NodeJS and coc.nvim's API.\n\n\t\t\t\t\t\t\t*coc-extensions-folder*\nExtensions are loaded from `\"extensions\"` folder inside\n|coc#util#get_data_home()| and folders in 'runtimepath' when detected.\n\nUse `let $COC_NO_PLUGINS = '1'` in vimrc to disable the load of extensions.\n\nSee |coc-api-extension| for the guide to create coc.nvim extension.\n\nInstall extensions from git (not recommended): ~\n\n  • Download the source code.\n  • In project root, install dependencies and compile the code by `npm install`\n    (needed by most coc extensions).\n  • Add the project root to vim's runtimepath by `set runtimepath^=/path/to/project`\n\nPlugin manager like [vim-plug] can be used as well.\n\nNote: use coc.nvim extensions from source code requires install dependencies,\nwhich may take huge disk usage.\n\n\t\t\t\t\t\t\t*coc-extensions-npm*\nInstall global extensions from npm (recommended): ~\n\nUse |:CocInstall| to install coc extensions from vim's command line.\n\nTo make coc.nvim install extensions on startup, use |g:coc_global_extensions|.\n\nTo use package manager other than npm (like `yarn` or `pnpm`), use\n|coc-config-npm-binPath|.\n\nTo customize npm registry for coc.nvim add `coc.nvim:registry` in your\n`~/.npmrc`, like:\n>\n\tcoc.nvim:registry=https://registry.mycompany.org/\n<\nTo customize extension folder, configure |g:coc_data_home|.\n\nUninstall global extensions: ~\n\nUse |:CocUninstall|.\n\nUpdate global extensions: ~\n\nUse |:CocUpdate| or |:CocUpdateSync|.\n\nTo configure extension behavior, see |coc-config-extensions|.\n\nManage extensions: ~\n\nUse |coc-list-extensions| or |CocAction('extensionStats')| to get list of extensions.\n\n==============================================================================\nCONFIGURATION\t\t\t\t\t\t*coc-configuration*\n\nThe configuration of coc.nvim is stored in file named \"coc-settings.json\".\n\nConfiguration properties are contributed by coc.nvim itself and coc.nvim\nextensions.  See |coc-config| for builtin configurations.\n\nThe configuration files are all in JSON format (with comment supported), it's\nrecommended to enable JSON completion and validation by install the `coc-json`\nextension:\n>\n\t:CocInstall coc-json\n<\nTo fix the highlight of comment, use:\n>\n\tautocmd FileType json syntax match Comment +\\/\\/.\\+$+\n<\nin your vimrc.\n\nGlobal configuration file: ~\n\nCommand |:CocConfig| will open (create when necessary) a user settings\nfile in the folder returned by |coc#util#get_config_home()|.\n\nThe user configuration value could be overwritten by API |coc#config()| or\n|g:coc_user_config|.\n\nThe global configuration file can be created in another directory by setting\n|g:coc_config_home| in your vimrc like: >\n\n\tlet g:coc_config_home = '/path/to/folder'\n\nFolder configuration file: ~\n\nTo create a local configuration file for a specific workspace folder, use\n|:CocLocalConfig| to create and open `.vim/coc-settings.json` in current\nworkspace folder.\n\nFolder configuration would overwrite user configuration.\n\nNote: the configuration file won't work when the parent folder is not resolved\nas workspace folder, it's best practice to start vim inside workspace folder,\nsee |coc-workspace-folders|.\n\n\t\t\t\t\t\t\t*coc-configuration-expand*\nVariables expands: ~\n\nVariables would be expanded in string values of configuration, supported\nvariables:\n\n  • `${userHome}` the path of the user's home folder\n  • `${cwd}` current working directory of vim.\n\nYou can also reference environment variables through the `${env:name}` syntax\n(for example, `${env:USERNAME}`), no expand happens when env not exists.\n\nConfigurations that requires file paths (ex:\n|coc-config-workspace-ignoredFolders|) support expand `~` at the beginning of\nthe filepath to user's home and some additional variables:\n\n  • `${workspaceFolder}` the current opened file's workspace folder.\n  • `${workspaceFolderBasename}` the name of the workspace folder opened in\n    coc.nvim without any slashes (/).\n  • `${file}` the current opened file.\n  • `${fileDirname}` the current opened file's dirname.\n  • `${fileExtname}` the current opened file's extension.\n  • `${fileBasename}`  the current opened file's basename\n  • `${fileBasenameNoExtension}` the current opened file's basename with no file extension.\n\n\t\t\t\t\t\t\t*coc-configuration-scope*\nConfiguration scope: ~\n\nA configuration could be one of three different configuration scopes:\n\n  • `\"application\"` the configuration could only be used in user configuration\n    file.\n\n  • `\"resource\"` the configuration could be used in user and workspace folder\n    configuration file.\n\n  • `\"language-overridable\"` the configuration could be used in user and\n    workspace folder configuration file, and can be use used in language scoped\n    configuration section like `[typescript][json]`.  For example: >\n\n      // disable inlay hint for some languages\n      \"[rust][lua][c]\": {\n         \"inlayHint.enable\": false\n      }\n<\n==============================================================================\nFLOATING WINDOWS\t\t\t\t\t*coc-floating*\n\nFloating windows/popups are created by |api-floatwin| on neovim or |popupwin|\non vim.\n\t\t\t\t\t\t\t*coc-floating-scroll*\nScroll floating windows: ~\n\nSee |coc#float#has_scroll()| for example.\n\nNote: use |coc#pum#scroll()| for scroll popup menu.\n\n\t\t\t\t\t\t\t*coc-floating-close*\nClose floating windows: ~\n\nTo close all floating windows/popups use |coc#float#close_all()| or\n|popup_clear()| on vim.  Or you can use <CTRL-w>o on neovim which close all\nsplit windows as well.\n\nTo close single floating window/popup, use |coc#float#close()|.\n\n\t\t\t\t\t\t\t*coc-floating-focus*\nFocus floating windows: ~\n\nOn neovim, use <CTRL-w>w (or |<Plug>(coc-float-jump)|) could focus a floating\nwindow just created (if it's focusable).  It's not allowed to focus popups on\nvim, unless it's using a terminal buffer.\n\n\t\t\t\t\t\t\t*coc-floating-config*\nConfigure floating windows: ~\n\nTo set custom window options on floating window create, use autocmd\n|CocOpenFloat| or |CocOpenFloatPrompt|.\n\nRelated variables: ~\n\n  • |g:coc_last_float_win|\n  • |g:coc_borderchars|\n  • |g:coc_border_joinchars|\n  • |g:coc_markdown_disabled_languages|\n\nRelated highlight groups: ~\n\n  • |CocFloating| For floating window background.\n  • |CocFloatBorder| Default border highlight group of floating window.\n  • |CocFloatDividingLine| For dividing lines.\n  • |CocFloatActive| For active parts.\n  • |CocMenuSel| For selected line.\n\nTo customize floating windows used by popup menu, use:\n\n  • |coc-config-suggest-floatConfig|\n  • |coc-config-suggest-pumFloatConfig|\n\nFor floating windows created around cursor, like diagnostics, hover and\nsignature use |coc-config-floatFactory-floatConfig| for common float\nconfigurations. For further customization, use:\n\n  • |coc-config-diagnostic-floatConfig|\n  • |coc-config-signature-floatConfig|\n  • |coc-config-hover-floatConfig|\n\nFor customize dialog windows, use |coc-config-dialog|.\nFor customize notification windows, use |coc-config-notification|.\n\nConfigure |coc-preferences-enableMessageDialog| to show user non-interactive\nor interactive messages as notifications. To enable a more flexible user\ninteraction with the messages check |coc-preferences-messageDialogKind| and\n|coc-preferences-messageReportKind|\n\nUse |coc-preferences-messageDialogKind| to configure how interactive messages\nwhich require some user input are shown to the user this configuration\nsupersedes the |coc-preferences-enableMessageDialog|.\n\nUse |coc-preferences-messageReportKind| to configure how regular non-interactive\nmessages are reported to the user, that will tell coc how to show regular\nplain text messages to the user.\n\n==============================================================================\nLSP FEATURES \t\t\t\t\t\t*coc-lsp*\n\nMost features of LSP 3.17 are supported, checkout the specification at\nhttps://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/\n\nFeatures not supported: ~\n\n  • Telemetry.\n  • Inline values for debugger.\n  • Notebook document.\n\nLSP features only works with attached documents, see |coc-document-attached|.\n\nTo check exists providers of current buffer, use command\n`:CocCommand document.checkBuffer` or API |CocHasProvider()|.\n\nFor historic reason, some features automatically works by default, but some\nare not.\n\nFeatures automatically work by default: ~\n\n  • Trigger completion after text insert |coc-completion|.\n  • Trigger inline completion |coc-inlineCompletion|\n  • Diagnostics refresh |coc-diagnostics|.\n  • Pull diagnostics |coc-pullDiagnostics|.\n  • Trigger signature help |coc-signature|.\n  • Inlay hints |coc-inlayHint|\n\nMost features could be toggled by |coc-configuration| and some vim variables.\n\nTo disable all features that automatically work, use configuration:\n>\n    \"suggest.autoTrigger\": \"none\",\n    \"diagnostic.enable\": false,\n    \"pullDiagnostic.onChange\": false,\n    \"signature.enable\": false,\n    \"inlayHint.enable\": false,\n<\nFeatures require enabled by configuration: ~\n\n  • Semantic highlights |coc-semantic-highlights|.\n  • Document color highlights |coc-document-colors|.\n  • Code lens, |coc-code-lens|\n  • Linked editing, |coc-linked-editing|.\n  • Format on type, enabled by |coc-preferences-formatOnType|\n  • Format on save, enabled by |coc-preferences-formatOnSave|.\n\nFeatures requested by user: ~\n\n  • Locations related (including definitions, references etc.) |coc-locations|\n  • Invoke code action |coc-code-actions|.\n  • Show call hierarchy tree |coc-callHierarchy|.\n  • Show type hierarchy tree |coc-typeHierarchy|\n  • Format, range format and on type format |coc-format|.\n  • Highlight same symbol ranges |coc-document-highlights|.\n  • Outline of document symbols |coc-outline| and |coc-list-symbols|.\n  • Show hover information |coc-hover|.\n  • Rename symbol under cursor |coc-rename|.\n  • Open link under cursor |coc-document-links|.\n  • Selection range |coc-selection-range|\n  • Create folding ranges |coc-fold|.\n\nFor convenient, some actions have associated |coc-key-mappings| provided.\nPrefer |CocAction()| for more options.\n\nFeatures triggered by languageserver: ~\n\n  • Show message notification (use |coc-notification|).\n  • Show message request (use |coc-dialog-menu|).\n  • Log message notification (use `:CocCommand workspace.showOutput` to show\n  output).\n  • Show document request (opened by vim or your browser for url).\n  • Work done progress (use |coc-notification|).\n\nTo make coc.nvim provide LSP features for your languages, checkout\nhttps://github.com/neoclide/coc.nvim/wiki/Language-servers\n\nTo debug issues with languageserver, checkout\nhttps://github.com/neoclide/coc.nvim/wiki/Debug-language-server\n\n------------------------------------------------------------------------------\nDOCUMENT \t\t\t\t\t\t*coc-document*\n\nAn associated document is created on buffer create, and disposed on buffer\nunload.\n\nAttached document: ~\n\t\t\t\t\t\t\t*coc-document-attached*\n\nAn attached document means coc.nvim synchronize the lines of vim's buffer with\nassociated document automatically.\n\nOnly attached documents are synchronized with language servers and therefore\nLSP features could be provided for the attached buffer.\n\nThe buffer may not be attached by following reasons:\n\n  • The 'buftype' is neither <empty> nor 'acwrite', (could be bypassed by\n  |b:coc_force_attach|).\n  • Buffer variable |b:coc_enabled| is `0`.\n  • Byte length of buffer exceed |coc-preferences-maxFileSize|.\n  • Buffer is used for command line window.\n\nUse |CocAction('ensureDocument')| or `:CocCommand document.checkBuffer` to\ncheck attached state of current buffer.\n\nFiletype map: ~\n\t\t\t\t\t\t\t*coc-document-filetype*\n\nSome filetypes are mapped to others to match the languageId used by VSCode,\nincluding:\n\n  • javascript.jsx -> javascriptreact\n  • typescript.jsx -> typescriptreact\n  • typescript.tsx -> typescriptreact\n  • tex -> latex\n\nUse |g:coc_filetype_map| to create additional filetype maps.\n\nUse `:CocCommand document.echoFiletype` to echo mapped filetype of current\ndocument.\n\nNote: make sure use mapped filetypes for configurations that expect filetypes.\n\n------------------------------------------------------------------------------\nHOVER\t\t\t\t\t\t\t*coc-hover*\n\nHover feature provide information at a given text document position, normally\ninclude type information and documentation of current symbol.\n\nHover functions: ~\n\n  • |CocAction('doHover')| Show hover information at cursor position.\n  • |CocAction('definitionHover')||| Show hover information with definition\n  context at cursor position.\n  • |CocAction('getHover')| Get hover documentations at cursor position.\n\n\t\t\t\t\t\t\t*coc-hover-example*\nHover key-mapping example: ~\n>\n  nnoremap <silent> K :call ShowDocumentation()<CR>\n  \" Show hover when provider exists, fallback to vim's builtin behavior.\n  function! ShowDocumentation()\n    if CocAction('hasProvider', 'hover')\n      call CocActionAsync('definitionHover')\n    else\n      call feedkeys('K', 'in')\n    endif\n  endfunction\n<\n------------------------------------------------------------------------------\nCOMPLETION\t\t\t\t\t\t*coc-completion*\n\nVim's builtin completion is not used. The default completion works like\ncompletion in VSCode:\n\n  • Completion is automatically triggered by default.\n  • Selection is enabled by default, use |coc-config-suggest-noselect| to\n    disable default selection.\n  • When selection is enabled and no preselect item exists, the first complete\n    item will be selected (depends on |coc-config-suggest-selection|).\n  • Snippet expand and additional edits only work after confirm completion\n    |coc#pum#confirm()|.\n  • |'completeopt'| is not used and APIs of builtin popupmenu not work.\n\n\t\t\t\t\t\t\t*coc-completion-default*\nDefault Key-mappings: ~\n\nTo make the new completion works like the builtin completion, without any\nadditional configuration, the following key-mappings are used when the {lhs}\nis not mapped:\n\n  • `<C-n>` navigate to next complete item or inline complete item.\n  • `<C-p>` navigate to previous complete item or inline complete item.\n  • `<down>` navigate to next complete item (without word insert) or inline\n    complete item.\n  • `<up>` navigate to previous complete item (without word insert) or inline\n    complete item.\n  • `<C-e>` cancel the completion or inline completion.\n  • `<C-y>` confirm completion or accept current inline complete item.\n\nUse <PageDown> and <PageUp> to scroll: >\n\n  inoremap <silent><expr> <PageDown> coc#pum#visible() ? coc#pum#scroll(1) : \"\\<PageDown>\"\n  inoremap <silent><expr> <PageUp> coc#pum#visible() ? coc#pum#scroll(0) : \"\\<PageUp>\"\n<\nNote: <CR> and <Tab> are not remapped by coc.nvim.\n\n\t\t\t\t\t\t\t*coc-completion-variables*\nRelated variables: ~\n\n  • Disable completion for buffer: |b:coc_suggest_disable|\n  • Disable specific sources for buffer: |b:coc_disabled_sources|\n  • Disable words for completion: |b:coc_suggest_blacklist|\n  • Add additional keyword characters: |b:coc_additional_keywords|, the\n    buffer keyword characters are used for filter completion items instead of\n    trigger completion, see |'iskeyword'|.\n\n\t\t\t\t\t\t\t*coc-completion-functions*\nRelated functions: ~\n\n  • Trigger completion with options: |coc#start()|.\n  • Trigger completion refresh: |coc#refresh()|.\n  • Select and confirm completion: |coc#pum#select_confirm()|.\n  • Check if the custom popupmenu is visible: |coc#pum#visible()|.\n  • Select the next completion item: |coc#pum#next()|.\n  • Select the previous completion item: |coc#pum#prev()|.\n  • Cancel completion and reset trigger text: |coc#pum#cancel()|.\n  • Confirm completion: |coc#pum#confirm()|.\n  • Close the popupmenu only: |coc#pum#stop()|.\n  • Get information about the popupmenu: |coc#pum#info()|.\n  • Select specific completion item: |coc#pum#select()|.\n  • Insert word of selected item and finish completion: |coc#pum#insert()|.\n  • Insert one more character from current complete item: |coc#pum#one_more()|.\n  • Scroll popupmenu: |coc#pum#scroll()|.\n\n\t\t\t\t\t\t\t*coc-completion-customize*\nCustomize completion: ~\n\nUse |coc-config-suggest| to change the completion behavior.\n\nUse |'pumwidth'| for configure the minimal width of the popupmenu and |'pumheight'|\nfor its maximum height.\n\nRelated Highlight groups:\n\t|CocPum| \tfor highlight groups of customized pum.\n\t|CocSymbol| \tfor kind icons.\n\t|CocMenuSel| \tfor background highlight of selected item.\n\t|CocPumVirtualText| for virtual text when enabled by\n\t|coc-config-suggest-virtualText|.\n\nNote: background, border, title and winblend are configured by\n|coc-config-suggest-floatConfig|.\n\nExample user key-mappings: ~\n\t\t\t\t\t\t\t*coc-completion-example*\n\nNote: use command `:verbose imap` to check current insert key-mappings when\nyour key-mappings not work.\n\nUse <tab> and <S-tab> to navigate completion list: >\n\n  function! CheckBackspace() abort\n    let col = col('.') - 1\n    return !col || getline('.')[col - 1]  =~ '\\s'\n  endfunction\n\n  \" Insert <tab> when previous text is space, refresh completion if not.\n  inoremap <silent><expr> <TAB>\n\t\\ coc#pum#visible() ? coc#pum#next(1):\n\t\\ CheckBackspace() ? \"\\<Tab>\" :\n\t\\ coc#refresh()\n  inoremap <expr><S-TAB> coc#pum#visible() ? coc#pum#prev(1) : \"\\<C-h>\"\n\nUse <c-space> to trigger completion: >\n\n  if has('nvim')\n    inoremap <silent><expr> <c-space> coc#refresh()\n  else\n    inoremap <silent><expr> <c-@> coc#refresh()\n  endif\n<\nUse <CR> to confirm completion, use: >\n\n  inoremap <expr> <cr> coc#pum#visible() ? coc#pum#select_confirm() : \"\\<CR>\"\n<\nTo make <CR> to confirm selection of selected complete item or notify coc.nvim\nto format on enter, use: >\n\n  inoremap <silent><expr> <CR> coc#pum#visible() ? coc#pum#select_confirm()\n\t\t\t\t\\: \"\\<C-g>u\\<CR>\\<c-r>=coc#on_enter()\\<CR>\"\n\nMap <tab> for trigger completion, completion confirm, inline completion accept,\nsnippet expand and jump like VSCode: >\n\n  inoremap <silent><expr> <TAB>\n    \\ coc#pum#visible() ? coc#pum#select_confirm() :\n    \\ coc#inline#visible() ? coc#inline#accept() :\n    \\ coc#expandableOrJumpable() ?\n    \\ \"\\<C-r>=coc#rpc#request('doKeymap', ['snippets-expand-jump',''])\\<CR>\" :\n    \\ CheckBackspace() ? \"\\<TAB>\" :\n    \\ coc#refresh()\n\n  function! CheckBackspace() abort\n    let col = col('.') - 1\n    return !col || getline('.')[col - 1]  =~# '\\s'\n  endfunction\n\n  let g:coc_snippet_next = '<tab>'\n<\nNote: the `coc-snippets` extension is required for this to work.\n\n------------------------------------------------------------------------------\nINLINE COMPLETION\t\t\t\t\t*coc-inlineCompletion*\n\nInline Completion is a smart, lightweight alternative to traditional\nIntelliSense, offering faster, context-aware suggestions without disrupting\nyour workflow.  Inline completion is automatically triggered after document\ncontents synchronize on insert mode by default.\n\nUse command `:CocCommand document.checkInlineCompletion` to check inline\ncompletion feature of current buffer.\n\n\t\t\t\t\t\t\t*coc-inlineCompletion-default*\nDefault Key-mappings: ~\n\nInline completion and default completion shares default key mappings for\nnavigate and finish actions. see |coc-completion-default|.  To accept inline\ncompletion when pum is visible, finish the completion first or use\n|coc#inline#accept()|.\n\n\t\t\t\t\t\t\t*coc-inlineCompletion-functions*\nRelated functions: ~\n\n  • Trigger inline completion: |coc#inline#trigger()|.\n  • Check if inline completion visual text exists: |coc#inline#visible()|.\n  • Cancel inline completion: |coc#inline#cancel()|.\n  • Accept inline completion: |coc#inline#accept()|.\n  • Navigate to next: |coc#inline#next()|.\n  • Navigate to previous: |coc#inline#prev()|.\n\n\t\t\t\t\t\t\t*coc-inlineCompletion-customize*\nCustomize completion: ~\n\nUse |coc-config-inlineSuggest| to change the inline completion behavior.\nTo disable inline completion for special buffers, use language overridable\nconfiguration in coc-settings.json like: >\n\n\t\"[javascript][typescript]\": {\n\t\t\"inlineSuggest.autoTrigger\": false\n\t}\n<\nWhich disable auto trigger only, or use |b:coc_inline_disable| to disable\ntrigger inline completion completely.\n\nRelated Highlight groups:\n\t|CocInlineVirtualText| for virtual text highlight.\n\t|CocInlineAnnotation| for annotation highlight.\n\n------------------------------------------------------------------------------\nDIAGNOSTICS SUPPORT \t\t\t\t\t*coc-diagnostics*\n\nDiagnostics of coc.nvim are automatically refreshed to UI by default, checkout\n|coc-config-diagnostic| for available configurations.\n\nNote most language servers only send diagnostics for opened buffers for\nperformance reason, some lint tools could provide diagnostics for all files in\nworkspace.\n\nSee |coc-highlights-diagnostics| for diagnostic related highlight groups.\n\n\t\t\t\t\t\t\t*coc-diagnostics-refresh*\nChanges on diagnostics refresh ~\n\n  • Add highlights for diagnostic ranges and virtual text (when enabled on\n  neovim or vim >= 9.0.0067), see |coc-highlights-diagnostics|.\n  • Add diagnostic signs to 'signcolumn', use `set signcolumn=yes` to avoid\n  unnecessary UI refresh.\n  • Update variable |b:coc_diagnostic_info|.\n  • Refresh related |location-list| which was opened by |:CocDiagnostics|.\n\nDiagnostics are not refreshed when buffer is hidden, and refresh on insert\nmode is disabled by default.\n\nSee |coc-highlights-diagnostics| for highlight groups used by diagnostics.\n\n\t\t\t\t\t\t\t*coc-diagnostics-toggle*\nEnable and disable diagnostics ~\n\nUse |coc-config-diagnostic-enable| to toggle diagnostics feature.\n\nUse |CocAction('diagnosticToggle')| for enable/disable diagnostics feature.\n\nUse |CocAction('diagnosticToggleBuffer')| for enable/disable diagnostics of\ncurrent buffer.\n\nShow diagnostic messages ~\n\nDiagnostic messages would be automatically shown/hide when the diagnostics\nunder cursor position changed (use float window/popup when possible) by\ndefault.\n\nTo manually refresh diagnostics messages, use |<Plug>(coc-diagnostic-info)|\nand |CocAction('diagnosticPreview')|.\n\n\t\t\t\t\t\t\t*coc-diagnostics-jump*\nJump between diagnostics ~\n\nUse key-mappings:\n\n\t|<Plug>(coc-diagnostic-next)| jump to diagnostic after cursor position.\n\t|<Plug>(coc-diagnostic-prev)| jump to diagnostic before cursor position.\n\t|<Plug>(coc-diagnostic-next-error)| jump to next error.\n\t|<Plug>(coc-diagnostic-prev-error)| jump to previous error.\n\nDiagnostic may have related location, to jump to related location, use:\n>\n \t:CocCommand workspace.diagnosticRelated\n<\nCheck diagnostics ~\n\nUse |coc-list-diagnostics| to open |coc-list| with all available diagnostics.\nUse API |CocAction('diagnosticList')| to get list of all diagnostics.\n\nUse |:CocDiagnostics| to open vim's location list with diagnostics of current\nbuffer. To automatically close the location list window, use autocmd\n|CocDiagnosticChange| with |CocAction('diagnosticList')|.\n\n------------------------------------------------------------------------------\nPULL DIAGNOSTICS SUPPORT \t\t\t\t*coc-pullDiagnostics*\n\nDiagnostics are pulled for visible documents when supported by languageserver.\nPull for workspace diagnostics is also enabled by default.\n\nDocument diagnostics are pulled on change by default, and can be\nconfigured to be pulled on save.\n\nCheckout |coc-config-pullDiagnostic| for related configurations.\n\n------------------------------------------------------------------------------\nLOCATIONS SUPPORT \t\t\t\t\t*coc-locations*\n\nThere're different kinds of locations, including \"definitions\", \"declarations\",\n\"implementations\", \"typeDefinitions\" and \"references\".\n\nKey-mappings for invoke locations request ~\n\n  • |<Plug>(coc-definition)|\n  • |<Plug>(coc-declaration)|\n  • |<Plug>(coc-implementation)|\n  • |<Plug>(coc-type-definition)|\n  • |<Plug>(coc-references)|\n  • |<Plug>(coc-references-used)|\n\nError will be shown when the buffer not attached |coc-document-attached|.\nMessage will be shown when no result found.\n\nLocation jump behavior ~\n\nWhen there's only one location returned, the location is opened by command\nspecified by |coc-preferences-jumpCommand| (\"edit\" by default), context mark\nis added by |m'|, so you can jump back previous location by <C-o>.\n\nWhen multiple locations returned, |coc-list-location| is opened for preview\nand other further actions.\n\nTo use |coc-list-location| for single location as well, use\n|coc-locations-api| (instead key-mappings provided by coc.nvim).\n\nTo change default options of |coc-list-location| or use other plugin for\nlist of locations, see |g:coc_enable_locationlist|.\n\nTo use vim's quickfix for locations, use configuration\n|coc-preferences-useQuickfixForLocations|.\n\nTo use vim's tag list for definitions, use |CocTagFunc()|.\n\n\t\t\t\t\t\t\t*coc-locations-api*\nRelated APIs ~\n\n  • |CocAction('jumpDefinition')| Jump to definition locations.\n  • |CocAction('jumpDeclaration')| Jump to declaration locations.\n  • |CocAction('jumpImplementation')| Jump to implementation locations.\n  • |CocAction('jumpTypeDefinition')| Jump to type definition locations.\n  • |CocAction('jumpReferences')|| Jump to references.\n  • |CocAction('jumpUsed')| Jump to references without declarations.\n  • |CocAction('definitions')| Get definition list.\n  • |CocAction('declarations')| Get declaration list.\n  • |CocAction('implementations')| Get implementation list.\n  • |CocAction('typeDefinitions')| Get type definition list.\n  • |CocAction('references')| Get reference list.\n\nSend custom locations request to languageserver:\n\n  • |CocLocations()|\n  • |CocLocationsAsync()|\n\n------------------------------------------------------------------------------\nRENAME \t\t\t\t\t\t\t*coc-rename*\n\nRename provides workspace-wide rename of a symbol. Workspace edit\n|coc-workspace-edit| is requested and applied to related buffers when\nconfirmed.\n\nCheck if current buffer has rename provider with\n`:echo CocAction('hasProvider', 'rename')`\n\nRename key-mappings: ~\n\n  • |<Plug>(coc-rename)|\n\nRename functions: ~\n\n  • |CocAction('rename')| Rename the symbol under the cursor.\n  • |CocAction('refactor')| Open refactor buffer for all references (including\n    definitions), recommended for function signature refactor.\n\nRename local variable: ~\n\nUse command `:CocCommand document.renameCurrentWord` which uses |coc-cursors|\nto edit multiple locations at the same time and defaults to word extraction\nwhen rename provider doesn't exist.\n\nRename configuration: ~\n\nUse |coc-preferences-renameFillCurrent| to enable/disable populating prompt\nwindow with current variable name.\n\n------------------------------------------------------------------------------\nSIGNATURE HELP \t\t\t\t\t\t*coc-signature*\n\nSignature help for functions is shown automatically when user\ntypes trigger characters defined by the provider, which will use floating\nwindow/popup to show relevant documentation.\n\nUse |CocAction('showSignatureHelp')| to trigger signature help manually.\n\nNote: error will not be thrown when provider does not exist or nothing is returned\nby languageserver, use `echo CocAction('hasProvider', 'signature')` to check\nif a signature help provider exists.\n\nUse |coc-config-signature| to change default signature help behavior.\n\n|CocFloatActive| is used to highlight activated parameter part.\n\n------------------------------------------------------------------------------\nINLAY HINT \t\t\t\t\t\t*coc-inlayHint*\n\nInlay hint is enabled for all filetypes by default.  Inlay hint uses virtual\ntext feature of vim.  Vim9 or neovim >= 0.10 required to insert the virtual\ntext at correct position.\n\nNote: you may need configure extension or languageserver to make inlay hint\nworks.\n\nTo temporarily toggle inlay hint specific buffer, use command: >\n\n\t:CocCommand document.enableInlayHint {bufnr}\n\t:CocCommand document.disableInlayHint {bufnr}\n\t:CocCommand document.toggleInlayHint {bufnr}\n<\ncurrent bufnr is used when `{bufnr}` not specified.\n\nChange highlight group: ~\n\n  • |CocInlayHint|\n  • |CocInlayHintType|\n  • |CocInlayHintParameter|\n\nConfigure inlay hint support: ~\n\n|coc-config-inlayHint|\n\n------------------------------------------------------------------------------\nFORMAT \t\t\t\t\t\t\t*coc-format*\n\nSome tools may reload buffer from disk file during format, coc.nvim only\napply `TextEdit[]` to the document.\n\nDon't be confused with vim's indent feature, configure/fix the 'indentexpr' of\nyour buffer if the indent is wrong after character insert. (use\n|coc-format-ontype| might helps with the indent)\n\n\t\t\t\t\t\t\t*coc-format-options*\nFormat options: ~\n\nBuffer options that affect document format: 'eol', 'shiftwidth' and\n'expandtab'.\n\n  • |b:coc_trim_trailing_whitespace| Trim trailing whitespace on a line.\n  • |b:coc_trim_final_newlines| Trim all newlines after the final newline at\n    the end of the file.\n\nThose options are converted to `DocumentFormattingOptions` and transferred to\nlanguageservers before format.  Note: the languageservers may only support\nsome of those options.\n\n\t\t\t\t\t\t\t*coc-format-document*\nFormat full document: ~\n\nChoose \"editor.action.formatDocument\" from |coc-list-commands|.\nOr use |CocAction('format')|, you can create a command like: >\n\n\tcommand! -nargs=0 Format :call CocActionAsync('format')\n<\nto format current buffer.\n\n\t\t\t\t\t\t\t*coc-format-ontype*\nFormat on type: ~\n\nFormat on type could be enabled by |coc-preferences-formatOnType|.\n\nUse `:CocCommand document.checkBuffer` to check if `formatOnType` provider\nexists for current buffer.\n\nTo format on <CR>, create key-mapping of <CR> that uses |coc#on_enter()|.\n\nIf you don't like the behavior on type bracket characters, configure\n|coc-preferences-bracketEnterImprove||.\n\n\t\t\t\t\t\t\t*coc-format-selected*\nFormat selected code: ~\n\nUse 'formatexpr' for specific filetypes: >\n\n  autocmd FileType typescript,json setl formatexpr=CocAction('formatSelected')\n\nSo that |gq| could works for format range of lines.\n>\nSetup visual mode and operator key-mappings: >\n\n  xmap <leader>f  <Plug>(coc-format-selected)\n  nmap <leader>f  <Plug>(coc-format-selected)\n<\n\t\t\t\t\t\t\t*coc-format-onsave*\nFormat on save: ~\n\nTo enable format on save, use configuration |coc-preferences-formatOnSave|.\n\nOr create |BufWritePre| autocmd like: >\n\n\tautocmd BufWritePre * call CocAction('format')\n<\nNote the operation have to synchronized, avoid use |CocActionAsync()|.\nNote to skip the autocmd, use `:noa w` to save the buffer.\n\nThe operation on save will block your vim, to not block too long time, the\noperation will be canceled after 0.5s, configured by\n|coc-preferences-willSaveHandlerTimeout|\n\n------------------------------------------------------------------------------\nCODE ACTION \t\t\t\t\t\t*coc-code-actions*\n\nCode actions are used for ask languageserver to provide specific kind code\nchanges.\n\nPossible code action kinds:\n\n  • `quickfix` used for fix diagnostic(s).\n  • `refactor` used for code refactor.\n  • `source` code actions apply to the entire file.\n  • `organizeImport` organize import statements of current document.\n\nKey-mappings for code actions: ~\n\n  • |<Plug>(coc-fix-current)| Invoke quickfix action at current line if any.\n  • |<Plug>(coc-codeaction-cursor)| Choose code actions at cursor position.\n  • |<Plug>(coc-codeaction-line)| Choose code actions at current line.\n  • |<Plug>(coc-codeaction)| Choose code actions of current file.\n  • |<Plug>(coc-codeaction-source)| Choose source code action of current file.\n  • |<Plug>(coc-codeaction-selected)| Choose code actions from selected range.\n  • |<Plug>(coc-codeaction-refactor)| Choose refactor code action at cursor\n  position.\n  • |<Plug>(coc-codeaction-refactor-selected)| Choose refactor code action with\n  selected code.\n\nExcept for |<Plug>(coc-fix-current)| which invoke code action directly,\n|coc-dialog-menu| would be shown for pick specific code action.\n\nTo invoke organize import action, use command like:\n>\n\tcommand! -nargs=0 OR :call CocAction('organizeImport')\n\nSee |CocAction('organizeImport')| for details.\n\nRelated APIs ~\n\n  • |CocAction('codeActions')|\n  • |CocAction('organizeImport')|\n  • |CocAction('fixAll')|\n  • |CocAction('quickfixes')|\n  • |CocAction('doCodeAction')|\n  • |CocAction('doQuickfix')|\n  • |CocAction('codeActionRange')|\n\n------------------------------------------------------------------------------\nDOCUMENT HIGHLIGHTS \t\t\t\t\t*coc-document-highlights*\n\nDocument highlights is used for highlight same symbols of current document\nunder cursor.\n\nTo enable highlight on CursorHold, create an autocmd like this: >\n\n\tautocmd CursorHold * call CocActionAsync('highlight')\n<\nSee |coc-config-documentHighlight| for related configurations.\nSee |coc-highlights-document| for related highlight groups.\n\nNote error will not be thrown when provider not exists or nothing returned\nfrom languageserver with |CocAction('highlight')|\n\nInstall `coc-highlight` extension if you want to highlight same words under\ncursor without languageserver support.\n\nTo jump between previous/next symbol position, use\n`:CocCommand document.jumpToPrevSymbol` and\n`:CocCommand document.jumpToNextSymbol`\n\n------------------------------------------------------------------------------\nDOCUMENT COLORS \t\t\t\t\t*coc-document-colors*\n\nDocument colors added color highlights to vim buffers.  To enable document\ncolor highlights, use |coc-config-colors-enable|.\n\nNote: the highlights define gui colors only, make use you have 'termguicolors'\nenabled (and your terminal support gui colors) if you're using vim in\nterminal.\n\nTo pick a color from system color picker, use |CocAction('pickColor')| or\nchoose `editor.action.pickColor` from |:CocCommand|.\n\nNote: pick color may not work on your system.\n\nTo change color presentation, use |CocAction('colorPresentation')| or choose\n`editor.action.colorPresentation` from |:CocCommand|.\n\nTo toggle color highlight of current buffer, choose\n`document.toggleColors` from |:CocCommand|\n\nTo highlights colors without languageservers, install\nhttps://github.com/neoclide/coc-highlight\n\n==============================================================================\nDOCUMENT LINKS \t\t\t\t\t\t*coc-document-links*\n\nCheck if current buffer have documentLink provider by\n`:echo CocAction('hasProvider', 'documentLink')`\n\nHighlight and tooltip of the links could be configured by |coc-config-links|.\n\nUse |coc-list-links| to manage list of links in current document.\n\nDocument link key-mappings: ~\n\n|<Plug>(coc-openlink)|\n\nDocument link functions: ~\n\n  • |CocAction('openLink')| Open link under cursor.\n  • |CocAction('links')| Get link list of current buffer.\n\n------------------------------------------------------------------------------\nSNIPPETS SUPPORT \t\t\t\t\t*coc-snippets*\n\nSnippets engine of coc.nvim support both VSCode snippets and ultisnips\nsnippets format.\n\nThe completion items with snippet format has label ends with\n|coc-config-suggest-snippetIndicator| (`~` by default).\n\nConfirm the completion by |coc#pum#confirm()| or |coc#pum#select_confirm()| to\nexpand the snippet and execute other possible actions of selected complete\nitem.\n\nJump snippet placeholders: ~\n\n|g:coc_snippet_next| and |g:coc_snippet_prev| are used to jump placeholders on\nboth select mode and insert mode, which defaults to <C-j> and <C-k>.  Buffer\nkey-mappings are created on snippet activate, and removed on snippet\ndeactivate.\n\nDeactivate snippet session: ~\n\nA snippet session would be deactivated under the following conditions:\n\n  • The change affects the snippet and code outside.\n  • Autocmd |InsertEnter| triggered outside snippet.\n  • Jump to the final placeholder.\n\nUse |CocOpenLog| to checkout the cancel reason.\nUse |CocAction('snippetCancel')| for cancel snippet session manually.\n\nTo load and expand custom snippets, install `coc-snippets` extension by\ncommand: >\n\n\t:CocInstall coc-snippets\n<\nNested snippets: ~\n\nSnippets can be nested, when jump to tabstop of parent snippet, it's not\npossible to jump back again (works like UltiSnip).\n\nRelated configurations: ~\n\n  • |g:coc_snippet_prev|\n  • |g:coc_snippet_next|\n  • |g:coc_selectmode_mapping|\n  • |coc-config-suggest-snippetIndicator|\n  • |coc-config-suggest-preferCompleteThanJumpPlaceholder|\n  • |coc-config-snippet-highlight|\n  • |coc-config-snippet-statusText|\n  • |coc-config-snippet-nextPlaceholderOnDelete|\n\nRelated functions: ~\n\n  • |coc#snippet#next()|\n  • |coc#snippet#prev()|\n  • |coc#expandable()|\n  • |coc#jumpable()|\n  • |coc#expandableOrJumpable()|\n\nRelated variables, highlights and autocmds: ~\n\n  • |g:coc_selected_text| Used for replace `${VISUAL}` and `${TM_SELECTED_TEXT}`\n    placeholder of next expanded snippet.\n  • |b:coc_snippet_active| Check if snippet session is activated.\n  • |CocJumpPlaceholder| Autocmds triggered after placeholder jump.\n  • |CocSnippetVisual| For highlight of current placeholders when the\n    highlight is enabled.\n\n------------------------------------------------------------------------------\nWORKSPACE SUPPORT \t\t\t\t\t*coc-workspace*\n\n\t\t\t\t\t\t\t*coc-workspace-folders*\nWorkspace folders ~\n\nUnlike VSCode which prompt you to open folders, workspace folders of coc.nvim\nare resolved from filepath after document attached.\n\nA list of file/folder names is used for resolve workspace folder, the patterns\ncould comes from:\n\n  • |b:coc_root_patterns|\n  • \"rootPatterns\" field of languageserver in |coc-config-languageserver|.\n  • \"rootPatterns\" contributions from coc.nvim extensions.\n  • |coc-config-workspace-rootPatterns|\n\nWorkspace folder is resolved from cwd of vim first (by default) and then from\ntop directory to the parent directory of current filepath, when workspace\nfolder not resolved, current working directory is used if it's parent folder\nof current buffer.  Configurations are provided to change the default behavior:\n\n  • |coc-config-workspace-ignoredFiletypes|\n  • |coc-config-workspace-ignoredFolders|\n  • |coc-config-workspace-bottomUpFiletypes|\n  • |coc-config-workspace-workspaceFolderCheckCwd|\n  • |coc-config-workspace-workspaceFolderFallbackCwd|\n\nNote for performance reason, user's home directory would never considered as\nworkspace folder, which also means the languageserver that requires workspace\nfolder may not work when you start vim from home directory.\n\nTo preserve workspace folders across vim session, |g:WorkspaceFolders| is\nprovided.\n\nUse `:CocCommand workspace.workspaceFolders` to echo current workspaceFolders.\n\nTo manage current workspace folders, use |coc-list-folders|\n\nTo get related root patterns of current buffer, use |coc#util#root_patterns()|\n\n\t\t\t\t\t\t\t*coc-workspace-edits*\nWorkspace edit ~\n\nWorkspace edit is used to apply changes for multiple buffers(and/or files), the\nedit could contains document edits and file operations (including file create,\nfile/directory delete and file/directory rename).\n\nWhen the edit failed to apply, coc.nvim will revert the changes (including\ndocument edits and file operations) that previous made.\n\nFiles not loaded would be loaded by `tab drop` command, configured by\n|coc-config-workspace-openResourceCommand|.\n\nTo undo and redo workspace edit just applied, use command\n`:CocCommand workspace.undo` and `:CocCommand workspace.redo`\n\nTo inspect previous workspace edit, use command\n`:CocCommand workspace.inspectEdit`, in opened buffer, use <CR> for jump to\nchange position under cursor.\n\n\t\t\t\t\t\t\t*coc-workspace-rename*\nRename current file ~\n\nTo move or rename current file, it's recommended to use\n`:CocCommand workspace.renameCurrentFile` which makes vim reload current\nbuffer and send expected events to language servers.\n\t\t\t\t\t\t\t*coc-workspace-output*\nOutput channels ~\n\nOutput channels shows logs for user to inspect.  Use command\n`workspace.showOutput` to open output channel.\n\nBuiltin channels:\n\n- `watchman` shows watchman related logs.\n- `extensions` shows extension install and update related logs.\n\n|coc-languageserver| and |coc-extensions| could contribute output channels.\n\n------------------------------------------------------------------------------\nCURSORS SUPPORT\t\t\t\t\t\t*coc-cursors*\n\nMultiple cursors supported is added to allow edit multiple locations at once.\n\nCursors session could be started by following ways:\n\n  • Use command `:CocCommand document.renameCurrentWord` to rename variable\n    under cursor.\n  • Use |<Plug>(coc-refactor)| to open |coc-refactor-buffer|.\n  • Use |:CocSearch| to open searched locations with |coc-refactor-buffer|.\n  • Use cursors related key-mappings to add text range, including\n  |<Plug>(coc-cursors-operator)|, |<Plug>(coc-cursors-word)|,\n  |<Plug>(coc-cursors-position)| and |<Plug>(coc-cursors-range)|\n  • Ranges added by command `editor.action.addRanges` from coc extensions.\n\nDefault key-mappings when cursors activated:\n\n  • <esc> cancel cursors session.\n  • <C-n> jump to next cursors range.\n  • <C-p> jump to previous cursors range.\n\nUse |coc-config-cursors| to change cursors related key-mappings.\nUse highlight group |CocCursorRange| to change default range highlight.\nUse |b:coc_cursors_activated| to check if cursors session is activated.\n\n\t\t\t\t\t\t\t*coc-refactor-buffer*\n\nRefactor buffer is a specific buffer with |coc-cursors| enabled, normally\nopened by |CocAction('refactor')| and |:CocSearch|. When the refactor buffer\nsaved, related buffers and files would be changed at once, workspace edits\nwould be applied, which could be undo and redo, see |coc-workspace-edits|.\n\nCheck out |coc-config-refactor| for related configuration.\n\nUse <CR> to open buffer at current position in split window.\nUse <Tab> to invoke tab open or remove action with current code chunk.\n\n--------------------------------------------------------------------------------\nSYMBOLS OUTLINE\t\t\t\t\t\t*coc-outline*\n\nOutline is a split window with current document symbols rendered as\n|coc-tree|.\n\nTo show and hide outline of current window, use |CocAction('showOutline')| and\n|CocAction('hideOutline')|.\n\nOutline view has |w:cocViewId| set to \"OUTLINE\".\n\nFollowing outline features are supported:\n\n  • Start fuzzy filter by |coc-config-tree-key-activeFilter|.\n  • Automatic update after document change.\n  • Automatic reload when buffer in current window changed.\n  • Automatic follow cursor position by default.\n  • Different filter modes that can be changed on the fly\n  |coc-config-outline-switchSortKey|.\n  • Enable auto preview by |coc-config-outline-togglePreviewKey|.\n\nOutline would try to reload document symbols after 500ms when provider not\nregistered, which avoid the necessary to check provider existence.\n\nCheckout |coc-config-tree| and |coc-config-outline| for available\nconfigurations.\n\nCheckout |CocTree| and |CocSymbol| for customize highlights.\n\nUse configuration `\"suggest.completionItemKindLabels\"` for custom icons.\n\n\t\t\t\t\t\t\t*coc-outline-example*\n\nTo show outline for each tab automatically, use |autocmd|:\n>\n  autocmd VimEnter,Tabnew *\n\t  \\ if empty(&buftype) | call CocActionAsync('showOutline', 1) | endif\n<\nTo close outline when it's the last window automatically, use\n|autocmd| like:\n>\n  autocmd BufEnter * call CheckOutline()\n  function! CheckOutline() abort\n    if &filetype ==# 'coctree' && winnr('$') == 1\n      if tabpagenr('$') != 1\n        close\n      else\n        bdelete\n      endif\n    endif\n  endfunction\n<\nCreate a key-mapping to toggle outline, like:\n>\n  nnoremap <silent><nowait> <space>o  :call ToggleOutline()<CR>\n  function! ToggleOutline() abort\n    let winid = coc#window#find('cocViewId', 'OUTLINE')\n    if winid == -1\n      call CocActionAsync('showOutline', 1)\n    else\n      call coc#window#close(winid)\n    endif\n  endfunction\n<\n--------------------------------------------------------------------------------\nCALL HIERARCHY\t\t\t\t\t\t*coc-callHierarchy*\n\nA call hierarchy is a split |coc-tree| window with locations for incoming or\noutgoing calls of function under cursor position.\n\nCall hierarchy window is opened by |CocAction('showIncomingCalls')| and\n|CocAction('showOutgoingCalls')|.  The tree view window has |w:cocViewId| set\nto \"CALLS\".\n\nCall hierarchy is configured by |CocSymbol|, |coc-config-callHierarchy| and\n|coc-config-tree|.\n\nRelated ranges are highlighted with |CocSelectedRange| highlight group in\nopened buffer.\n\n|coc-dialog-menu| could be invoked by |coc-config-tree-key-actions| (default\nto <tab>).  Available actions:\n\n  • Dismiss.\n  • Open in new tab.\n  • Show Incoming Calls.\n  • Show Outgoing Calls.\n\nUse <CR> in call hierarchy tree to open location in original window.\n\n--------------------------------------------------------------------------------\nTYPE HIERARCHY\t\t\t\t\t\t*coc-typeHierarchy*\n\nA type hierarchy is a split |coc-tree| window with locations for super types\nor sub types from types at current position.\n\nType hierarchy window is opened by |CocAction('showSuperTypes')| and\n|CocAction('showSubTypes')|.  The tree view window has |w:cocViewId| set to\n\"TYPES\".\n\nType hierarchy is configured by |CocSymbol|, |coc-config-typeHierarchy| and\n|coc-config-tree|.\n\nActions are the same as |coc-callHierarchy|.\n\n--------------------------------------------------------------------------------\nSEMANTIC HIGHLIGHTS\t\t\t\t\t*coc-semantic-highlights*\n\nSemantic tokens are used to add additional color information to a buffer that\ndepends on language specific symbol information.\n\nUse |coc-config-semanticTokens-enable| to enable semantic tokens highlights.\n\nUse `:CocCommand semanticTokens.checkCurrent` to check semantic highlight\ninformation with current buffer.\n\nTo create custom highlights for symbol under cursor, follow these steps:\n\n  • Inspect semantic token by >\n\n  :CocCommand semanticTokens.inspect\n<\n  to check token type and token modifiers with current symbol.\n\n  • Create new highlight group by |highlight|, for example: >\n\n  :hi link CocSemDeclarationVariable MoreMsg\n<\n  • Refresh semantic highlight of current buffer by: >\n\n  :CocCommand semanticTokens.refreshCurrent\n<\n  • Clear semantic highlight by: >\n\n  \" Clear semantic tokens highlight of current buffer\n  :CocCommand semanticTokens.clearCurrent\n\n  \" Clear semantic tokens highlight for all buffers\n  :CocCommand semanticTokens.clearAll\n<\nSee |CocSem| to customize semantic token highlight groups.\n\nSee |coc-config-semanticTokens| for related configurations.\n\n--------------------------------------------------------------------------------\nFOLD \t\t\t\t\t\t\t*coc-fold*\n\nCheck if current buffer have fold provider by\n`:echo CocAction('hasProvider', 'foldingRange')`\n\nUse |CocAction('fold')| to create folds by request the languageserver and\ncreate manual folds on current window.\n\n--------------------------------------------------------------------------------\nSELECTION RANGE \t\t\t\t\t*coc-selection-range*\n\nSelect range forward or backward at cursor position.\n\nCheck if current buffer have selection range provider by\n`:echo CocAction('hasProvider', 'selectionRange')`\n\nSelection range key-mappings: ~\n\n  • |<Plug>(coc-range-select)| Select range forward.\n  • |<Plug>(coc-range-select-backward)| Select range backward.\n\nSelection range function: ~\n\n  • |CocAction('rangeSelect')| Visual select previous or next selection range\n\n--------------------------------------------------------------------------------\nCODE LENS \t\t\t\t\t\t*coc-code-lens*\n\nCode lens feature shows additional information above or after specific lines.\n\nCodeLens are not shown by default, use |coc-config-codeLens-enable| to enable,\nyou may also need enable codeLens feature by configure extension or\nlanguageserver.\n\nCheck if current buffer have code lens provider by\n`:echo CocAction('hasProvider', 'codeLens')`\n\nTo temporarily toggle codeLens of current buffer, use command\n`:CocCommand document.toggleCodeLens`\n\nTo invoke command from codeLens, use |<Plug>(coc-codelens-action)|.\n\nUse |CocCodeLens| for highlight of codeLens virtual text.\n\nCode lens are automatically requested on buffer create/change, checkout\n|coc-config-codeLens| for available configurations.\n\n--------------------------------------------------------------------------------\nLINKED EDITING \t\t\t\t\t\t*coc-linked-editing*\n\nLinked editing feature enables editing multiple linked ranges at the same time,\nfor example: html tags.  The linked editing ranges would be highlighted with\n|CocLinkedEditing| when activated.\n\nCheck if current buffer have linked editing provider by\n`:echo CocAction('hasProvider', 'linkedEditing')`\n\nLinked editing feature is disabled by default, use\n|coc-preferences-enableLinkedEditing| to enable.\n\n==============================================================================\nINTERFACE\t\t\t\t\t\t*coc-interface*\n\n--------------------------------------------------------------------------------\n\nKey mappings\t\t\t\t\t\t*coc-key-mappings*\n\nThere're some cases that local key-mappings are enabled for current buffer.\n\n  • Snippet jump key-mappings when snippet is activated: |g:coc_snippet_prev|\n    and |g:coc_snippet_next|.\n  • Cursor jump and cancel key-mappings when cursors is activated\n    |coc-config-cursors|.\n  • Dialog key-mappings for confirm and cancel dialog window\n    |coc-config-dialog|.\n  • Key-mappings for |CocList| buffer: |coc-list-mappings|.\n\nNote: Use |:verbose| command to check key-mappings that taking effect.\n\nNote: Use |noremap| with <Plug> will make the key-mapping not work at all.\n\nNote: <Plug> key-mappings are provided for convenient, use |CocActionAsync()| or\n|CocAction()| for more options.\n\nNormal mode key-mappings: ~\n\n*<Plug>(coc-diagnostic-info)* Show diagnostic message of current position by\ninvoke |CocAction('diagnosticInfo')|\n\n*<Plug>(coc-diagnostic-next)* Jump to next diagnostic position after current\ncursor position.\n\n*<Plug>(coc-diagnostic-prev)* Jump to previous diagnostic position before\ncurrent cursor position.\n\n*<Plug>(coc-diagnostic-next-error)* Jump to next diagnostic error position.\n\n*<Plug>(coc-diagnostic-prev-error)* Jump to previous diagnostic error position.\n\n*<Plug>(coc-definition)* Jump to definition(s) of current symbol by invoke\n|CocAction('jumpDefinition')|\n\n*<Plug>(coc-declaration)* Jump to declaration(s) of current symbol by invoke\n|CocAction('jumpDeclaration')|\n\n*<Plug>(coc-implementation)* Jump to implementation(s) of current symbol by\ninvoke |CocAction('jumpImplementation')|\n\n*<Plug>(coc-type-definition)* Jump to type definition(s) of current symbol by\ninvoke |CocAction('jumpTypeDefinition')|\n\n*<Plug>(coc-references)* Jump to references of current symbol by invoke\n|CocAction('jumpReferences')|\n\n*<Plug>(coc-references-used)* Jump to references of current symbol exclude\ndeclarations.\n\n*<Plug>(coc-format-selected)*\n\n\tFormat selected range, works on both |visual-mode| and |normal-mode|,\n\twhen used in normal mode, the selection works on the motion object.\n\n\tFor example: >\n\n\tvmap <leader>p  <Plug>(coc-format-selected)\n\tnmap <leader>p  <Plug>(coc-format-selected)\n<\n\tmakes `<leader>p` format the visually selected range, and you can use\n\t`<leader>pap` to format a paragraph.\n\n*<Plug>(coc-format)* Format the whole buffer by invoke |CocAction('format')|\n*<Plug>(coc-rename)* Rename symbol under cursor to a new word by invoke\n|CocAction('rename')|\n\n*<Plug>(coc-refactor)* Open refactor window for refactor of current symbol by\ninvoke |CocAction('refactor')|\n\n*<Plug>(coc-command-repeat)* Repeat latest |CocCommand|.\n\n*<Plug>(coc-codeaction)* Get and run code action(s) for current file, use\n|coc-codeaction-cursor| for same behavior as VSCode.\n\n*<Plug>(coc-codeaction-source)* Get and run source code action(s) for current\nfile.  The same as 'Source action...' in context menu of VSCode.\n\n*<Plug>(coc-codeaction-line)* Get and run code action(s) for current line.\n\n*<Plug>(coc-codeaction-cursor)* Get and run code action(s) using empty range\nat current cursor.\n\n*<Plug>(coc-codeaction-selected)* Get and run code action(s) with the selected\ncode.  Works on both |visual-mode| and |normal-mode|.\n\n*<Plug>(coc-codeaction-refactor)* Get and run refactor code action(s) at\ncurrent cursor, the same as refactor context menu in VSCode, disabled actions\nare not excluded.\n\n*<Plug>(coc-codeaction-refactor-selected)* Get and run refactor code action(s)\nwith selected code. Works on both |visual-mode| and |normal-mode|.\n\n*<Plug>(coc-openlink)* Open link under cursor by use |CocAction('openlink')|.\n\n*<Plug>(coc-codelens-action)* invoke command contributed by codeLens at the\ncurrent line.\n\n*<Plug>(coc-fix-current)* Try first quickfix action for diagnostics of current\nline.\n\n*<Plug>(coc-float-hide)* Hide all float windows/popups created by coc.nvim.\n\n*<Plug>(coc-float-jump)* Jump to first float window (neovim only), use\n|CTRL-W_p| for jump to previous window.\n\n*<Plug>(coc-range-select)*\n\n\tSelect next selection range.\n\tWorks on both |visual-mode| and |normal-mode|.\n\n\tNote: requires selection ranges feature of language server.\n\n*<Plug>(coc-funcobj-i)*\n\n\tSelect inside function. Recommend mapping:\n\tWorks on both |visual-mode| and |normal-mode|.\n  >\n\txmap if <Plug>(coc-funcobj-i)\n\tomap if <Plug>(coc-funcobj-i)\n<\n\tNote: Requires 'textDocument.documentSymbol' support from the language\n\tserver.\n\n*<Plug>(coc-funcobj-a)*\n\n\tSelect around function.  Works on both |visual-mode| and\n\t|normal-mode|.  Recommended mapping:\n>\n\txmap af <Plug>(coc-funcobj-a)\n\tomap af <Plug>(coc-funcobj-a)\n<\n\tNote: Requires 'textDocument.documentSymbol' support from the language\n\tserver.\n\n*<Plug>(coc-classobj-i)*\n\n\tSelect inside class/struct/interface.  Works on both |visual-mode| and\n\t|normal-mode|.  Recommended mapping:\n>\n\txmap ic <Plug>(coc-classobj-i)\n\tomap ic <Plug>(coc-classobj-i)\n<\n\tNote: Requires 'textDocument.documentSymbol' support from the language\n\tserver.\n\n*<Plug>(coc-classobj-a)*\n\n\tSelect around class/struct/interface.  Works on both |visual-mode| and\n\t|normal-mode|.  Recommended mapping:\n>\n\txmap ac <Plug>(coc-classobj-a)\n\tomap ac <Plug>(coc-classobj-a)\n<\n\tNote: Requires 'textDocument.documentSymbol' support from the language\n\tserver.\n\n\n*<Plug>(coc-cursors-operator)* Add text to cursors session by motion object.\n\n*<Plug>(coc-cursors-word)* Add current word to cursors session.\n\n*<Plug>(coc-cursors-position)* Add current position as empty range to cursors\nsession.\n\nVisual mode key-mappings: ~\n\n*<Plug>(coc-range-select-backward)*\n\n\tSelect previous selection range.\n\n\tNote: requires selection ranges feature of language server, like:\n\tcoc-tsserver, coc-python\n\n*<Plug>(coc-cursors-range)* Add selection to cursors session.\n\n==============================================================================\nVARIABLES\t\t\t\t\t\t*coc-variables*\n\nThe Variables used by coc.nvim.\n\n--------------------------------------------------------------------------------\n\nENVIRONMENT VARIABLES\t\t\t\t\t*coc-environment-variables*\n\n$COC_NODE_PATH \t\t\t\t\t\t*$COC_NODE_PATH*\n\n\tFull path of NodeJS executable to start the node process, `node`\n\tcommand is used when the variable not exists.\n\n$VIMCONFIG\n\n\tFull path of directory which used for contain the user configuration\n\tfile, not used when |g:coc_config_home| exists.\n\n$NODE_CLIENT_LOG_FILE\n\n\tFull path of client log file, used when |g:node_client_debug| enabled.\n\n$COC_VIM_CHANNEL_ENABLE \t\t\t\t*$COC_VIM_CHANNEL_ENABLE*\n\n\tEnable vim's channel log |ch_logfile()| when is \"1\".\n\n--------------------------------------------------------------------------------\n\nBUFFER VARIABLES\t\t\t\t\t*coc-buffer-variables*\n\nb:coc_enabled\t\t\t\t\t\t*b:coc_enabled*\n\n\tSet to `0` on buffer create if you don't want coc.nvim receive content\n\tfrom buffer. Normally used with |BufRead| autocmd, example:\n>\n\t\" Disable file with size > 1MB\n\tautocmd BufRead * if getfsize(expand('<afile>')) > 1024*1024 |\n\t\t\t\t\\ let b:coc_enabled=0 |\n\t\t\t\t\\ endif\n\nb:coc_disable_autoformat\t\t\t\t*b:coc_disable_autoformat*\n\n\tSet to `1` on buffer create if you don't want coc.nvim not format the\n\tbuffer automatically (when typing or saving the buffer). Normally used\n\twith |BufRead| autocmd, example:\n>\n\t\" Disable automatic format with file size > 1MB\n\tautocmd BufRead * if getfsize(expand('<afile>')) > 1024*1024 |\n\t\t\t\t\\ let b:coc_disable_autoformat = 1 |\n\t\t\t\t\\ endif\n<\nb:coc_force_attach\t\t\t\t\t*b:coc_force_attach*\n\n\tWhen is `1`, attach the buffer without check the 'buftype' option.\n\tShould be set on buffer create.\n\nb:coc_root_patterns\t\t\t\t\t*b:coc_root_patterns*\n\n\tRoot patterns used for resolving workspaceFolder for\n\tthe current file, will be used instead of\n\t`\"workspace.rootPatterns\"` setting. Example: >\n\n\tautocmd FileType python let b:coc_root_patterns =\n\t\t\t\t\\ ['.git', '.env']\n<\nb:coc_suggest_disable\t\t\t\t\t*b:coc_suggest_disable*\n\n\tDisable trigger completion of buffer. Example: >\n\n\t\" Disable completion for python\n\tautocmd FileType python let b:coc_suggest_disable = 1\n\nb:coc_inline_disable\t\t\t\t\t*b:coc_inline_disable*\n\n\tDisable trigger inline completion of buffer. Example: >\n\n\t\" Disable inline completion for python\n\tautocmd FileType python let b:coc_inline_disable = 1\n\nb:coc_disabled_sources \t\t\t\t\t*b:coc_disabled_sources*\n\n\tDisabled completion sources of current buffer. Example:\n>\n\tlet b:coc_disabled_sources = ['around', 'buffer', 'file']\n<\nb:coc_suggest_blacklist                 \t        *b:coc_suggest_blacklist*\n\n\tList of input words for which completion should not be triggered.\n\tExample: >\n\n\t\" Disable completion for 'end' in Lua files\n\tautocmd FileType lua let b:coc_suggest_blacklist = [\"end\"]\n\nb:coc_additional_keywords\t\t\t\t*b:coc_additional_keywords*\n\n\tAddition keyword characters for generate keywords. Example: >\n\n\t\" Add keyword characters for CSS\n\tautocmd FileType css let b:coc_additional_keywords = [\"-\"]\n\nb:coc_trim_trailing_whitespace\t\t\t\t*b:coc_trim_trailing_whitespace*\n\n\tTrim trailing whitespace on a line, default `0`.\n\tUse by \"FormattingOptions\" send to the server.\n\nb:coc_trim_final_newlines \t\t\t\t*b:coc_trim_final_newlines*\n\n\tTrim all newlines after the final newline at the end of the file.\n\tUse by \"FormattingOptions\" send to the server.\n\n\tOther buffer options that affect document format: 'eol', 'shiftwidth'\n\tand 'expandtab'.\n\n\tNote: language server may not respect format options.\n\n--------------------------------------------------------------------------------\n\nWINDOW VARIABLES \t\t\t\t\t*coc-window-variables*\n\nw:cocViewId \t\t\t\t\t\t*w:cocViewId*\n\n\tExists with tree view window to identify the kind of tree view.\n\n\tBuiltin in kinds:\n\t- 'OUTLINE' for outline tree view.\n\t- 'CALLS' for incoming calls and outgoing calls tree view.\n\t- 'TYPES' for type hierarchy tree view.\n\t\n\tExtensions could contribute other kinds of tree view.\n\n--------------------------------------------------------------------------------\n\nGLOBAL VARIABLES \t\t\t\t\t*coc-global-variables*\n\ng:coc_disable_startup_warning \t\t\t\t*g:coc_disable_startup_warning*\n\n\tDisable possible warning on startup for old vim/node version.\n\n\tDefault: 0\n\ng:coc_disable_mappings_check \t\t\t\t*g:coc_disable_mappings_check*\n\n\tDisable completion key-mappings check for `<tab>`, `<cr>`, `<c-y>`, `<s-tab>`.\n\n\tDefault: 0\n\ng:coc_disable_uncaught_error \t\t\t\t*g:coc_disable_uncaught_error*\n\n\tDisable uncaught error messages from node process of coc.nvim.\n\n\tDefault: 0\n\ng:coc_text_prop_offset \t\t\t\t\t*g:coc_text_prop_offset*\n\n\tStart |textprop| id offset of highlight namespaces on vim, change to\n\tother value to avoid conflict with other vim plugin.\n\n\tDefault: 1000\n\ng:coc_disable_transparent_cursor\t\t\t*g:coc_disable_transparent_cursor*\n\n\tDisable transparent cursor when CocList is activated.\n\tSet it to `1` if you have issue with transparent\n\tcursor.\n\n\tDefault: 0\n\ng:coc_start_at_startup\t\t\t\t\t*g:coc_start_at_startup*\n\n\tStart coc service on startup, use |CocStart| to start server when you\n\tset it to 0.\n\n\tDefault: 1\n\ng:coc_list_preview_filetype \t\t\t\t*g:coc_list_preview_filetype*\n\n\tEnable set filetype for buffer in the preview window of |CocList|.\n\ng:coc_global_extensions\t\t\t\t\t*g:coc_global_extensions*\n\n\tGlobal extension names to install when they aren't installed.\n>\n\tlet g:coc_global_extensions = ['coc-json', 'coc-git']\n<\n\tNote: coc.nvim will try to install extensions that are not installed\n\tin this list after initialization.\n\ng:coc_enable_locationlist\t\t\t\t*g:coc_enable_locationlist*\n\n\tUse location list of |coc-list| when jump to locations.\n\n\tSet it to 0 when you need customize behavior of location jump by use\n\t|CocLocationsChange| and |g:coc_jump_locations|\n\n\tIf you want use vim's quickfix list instead, add\n\t`\"coc.preferences.useQuickfixForLocations\": true` in your\n\tconfiguration file, this configuration would be ignored and no\n\t|CocLocationsChange| triggered.\n\n\tDefault: 1\n\ng:coc_snippet_next\t\t\t\t\t*g:coc_snippet_next*\n\n\tTrigger key for going to the next snippet position, applied in insert\n\tand select mode.\n\n\tOnly works when snippet session is activated.\n\n\tDefault: <C-j>\n\ng:coc_snippet_prev\t\t\t\t\t*g:coc_snippet_prev*\n\n\tTrigger key for going to the previous snippet position, applied in\n\tinsert and select mode.\n\n\tOnly works when snippet session is activated.\n\n\tDefault: <C-k>\n\ng:coc_selected_text \t\t\t\t\t*g:coc_selected_text*\n\n\tThe text used for replace `${VISUAL}` and `${TM_SELECTED_TEXT}`\n\tplaceholder of next expanded snippet.  Normally created by press\n\t`<Plug>(coc-snippets-select)` provided by coc-snippets extensions.\n\tThe variable is removed after snippet expand.\n\n\tNot exists by default.\n\ng:coc_filetype_map\t\t\t\t\t*g:coc_filetype_map*\n\n\tMap for document filetypes so the server could handle current document\n\tas another filetype, example: >\n\n\tlet g:coc_filetype_map = {\n\t\t\\ 'html.swig': 'html',\n\t\t\\ 'wxss': 'css',\n\t\t\\ }\n<\n\tDefault: {}\n\n\tSee |coc-document-filetype| for details.\n\ng:coc_selectmode_mapping\t\t\t\t*g:coc_selectmode_mapping*\n\n\tAdd key mappings for making snippet select mode easier. The same as\n\t|Ultisnip| does. >\n\n\tsnoremap <silent> <BS> <c-g>c\n\tsnoremap <silent> <DEL> <c-g>c\n\tsnoremap <silent> <c-h> <c-g>c\n\tsnoremap <c-r> <c-g>\"_c<c-r>\n<\n\tDefault: 1\n\ng:coc_node_path\t\t\t\t\t\t*g:coc_node_path*\n\n\tPath to node executable to start coc service, example: >\n\n\tlet g:coc_node_path = '/usr/local/opt/node@12/bin/node'\n<\n\tUse this when coc has problems with your system node,\n\n\tNote: you can use `~` as home directory.\n\ng:coc_node_args\t\t\t\t\t\t*g:coc_node_args*\n\n\tArguments passed to node when starting coc.nvim service.\n\n\tUseful for start coc.nvim in debug mode, example: >\n>\n\tlet g:coc_node_args = ['--nolazy', '--inspect-brk=6045']\n<\n\tDefault: []\n\ng:coc_status_error_sign\t\t\t\t\t*g:coc_status_error_sign*\n\n\tError character used by |coc#status()|, default: `E`\n\ng:coc_status_warning_sign\t\t\t\t*g:coc_status_warning_sign*\n\n\tWarning character used by |coc#status()|, default: `W`\n\ng:coc_quickfix_open_command\t\t\t\t*g:coc_quickfix_open_command*\n\n\tCommand used for open quickfix list.  To jump fist position after\n\tquickfix list opened, you can use:\n>\n\tlet g:coc_quickfix_open_command = 'copen|cfirst'\n<\n\tDefault: |copen|\n\ng:coc_open_url_command \t\t\t\t\t*g:coc_open_url_command*\n\n\tCommand used for open remote url, when not exists, coc.nvim will try\n\tto use \"open\", \"xdg-open\" on Mac and Linux, \"cmd /c start\" on windows.\n\ng:node_client_debug\t\t\t\t\t*g:node_client_debug*\n\n\tEnable debug mode of node client for check rpc messages between vim\n\tand coc.nvim.  Use environment variable `$NODE_CLIENT_LOG_FILE` to set\n\tthe log file or get the log file after coc.nvim started.\n\tOn vim9, this variable also enables vim's channel log, the log\n\tfilepath would be disposed on the screen after vim start.\n\n\tTo open the log file, use command: >\n\n\t:call coc#client#open_log()\n<\n\tDefault: `0`\n\ng:coc_user_config \t\t\t\t\t*g:coc_user_config*\n\n\tUser configuration which will be passed to coc.nvim process during\n\tinitialization, no effect when changed after coc.nvim started.  Prefer\n\t|coc#config| unless coc.nvim is lazy loaded.  Example: >\n\n\tlet g:coc_user_config = {}\n\tlet g:coc_user_config['suggest.timeout'] = 500\n\tlet g:coc_user_config['suggest.noselect'] = v:true\n<\n\tNote: those configuration would overwrite the configuration from the\n\tuser's settings file, unless you have to use some dynamic variables,\n\tusing the settings file is recommended.\n\ng:coc_config_home\t\t\t\t\t*g:coc_config_home*\n\n\tConfigure the directory which will be used to look for\n\tuser's `coc-settings.json`, default:\n\n\tWindows: `~/AppData/Local/nvim`\n\tOther: `~/.config/nvim`\n\ng:coc_data_home\t\t\t\t\t\t*g:coc_data_home*\n\n\tConfigure the directory which will be used to for data\n\tfiles(extensions, MRU and so on), default:\n\n\tWindows: `~/AppData/Local/coc`\n\tOther: `~/.config/coc`\n\ng:coc_terminal_height\t\t\t\t\t*g:coc_terminal_height*\n\n\tHeight of terminal window, default `8`.\n\ng:coc_markdown_disabled_languages  \t\t\t*g:coc_markdown_disabled_languages*\n\n\tFiletype list that should be disabled for highlight in markdown block,\n\tuseful to disable filetypes that could be slow with syntax\n\thighlighting, example: >\n\n\tlet g:coc_markdown_disabled_languages = ['html']\n\ng:coc_highlight_maximum_count \t\t\t\t*g:coc_highlight_maximum_count*\n\n\tWhen highlight items exceed maximum count, highlight items will be\n\tgrouped and added by using |timer_start| for better user experience.\n\n\tDefault `500`\n\ng:coc_default_semantic_highlight_groups \t\t*g:coc_default_semantic_highlight_groups*\n\n\tCreate default semantic highlight groups for |coc-semantic-highlights|\n\n\tDefault: `1`\n\ng:coc_max_treeview_width \t\t\t\t*g:coc_max_treeview_width*\n\n\tMaximum width of tree view when adjusted by auto width.\n\n\tDefault: `40`\n\ng:coc_borderchars \t\t\t\t\t*g:coc_borderchars*\n\n\tBorder characters used by border window, default to:\n>\n\t['─', '│', '─', '│', '┌', '┐', '┘', '└']\n<\n\tNote: you may need special font like Nerd font to show them.\n\ng:coc_border_joinchars \t\t\t\t\t*g:coc_border_joinchars*\n\n\tBorder join characters used by float window/popup, default to:\n>\n\t['┬', '┤', '┴', '├']\n<\n\tNote: you may need special font like Nerd font to show them.\n\ng:coc_prompt_win_width \t\t\t\t\t*g:coc_prompt_win_width*\n\n\tWidth of input prompt window, default `32`.\n\n\t\t\t\t\t\t\t*g:coc_notify*\ng:coc_notify_error_icon \t\t\t\t*g:coc_notify_error_icon*\n\n\tError icon for notification, default to: \n\ng:coc_notify_warning_icon \t\t\t\t*g:coc_notify_warning_icon*\n\n\tWarning icon for notification, default to: ⚠\n\ng:coc_notify_info_icon \t\t\t\t\t*g:coc_notify_info_icon*\n\n\tInfo icon for notification, default to: \n\n--------------------------------------------------------------------------------\n\nSome variables are provided by coc.nvim.\n\n\ng:WorkspaceFolders\t\t\t\t\t*g:WorkspaceFolders*\n\n\tCurrent workspace folders, used for restoring from a session file, add\n\t`set sessionoptions+=globals` to vimrc for restoring globals on\n\tsession load.\n\ng:coc_jump_locations\t\t\t\t\t*g:coc_jump_locations*\n\n\tThis variable would be set to jump locations when the\n\t|CocLocationsChange| autocmd is fired.\n\n\tEach location item contains:\n\n\t'filename': full file path.\n\t'lnum': line number (1 based).\n\t'col': column number(1 based).\n\t'text':  line content of location.\n\ng:coc_process_pid\t\t\t\t\t*g:coc_process_pid*\n\n\tProcess pid of coc.nvim service. If your vim doesn't kill coc.nvim\n\tprocess on exit, use:\n>\n\tautocmd VimLeavePre * if get(g:, 'coc_process_pid', 0)\n\t\t\\\t| call system('kill -9 '.g:coc_process_pid) | endif\n<\n\tin your vimrc.\n\ng:coc_service_initialized\t \t\t\t*g:coc_service_initialized*\n\n\tIs `1` when coc.nvim initialized, used with autocmd |CocNvimInit|.\n\ng:coc_status\t\t\t\t\t\t*g:coc_status*\n\n\tStatus string contributed by coc.nvim and extensions, used for status\n\tline.\n\n\tYou may need to escape `%` for your status line plugin.\n\tVimL: `substitute(g:coc_status, '%', '%%', 'g')`\n\tLua: `string.gsub(vim.g.coc_status, \"%%\", \"%%%%\")`\n\t\n\ng:coc_last_float_win\t\t\t\t\t*g:coc_last_float_win*\n\n\tWindow id of latest created float/popup window.\n\ng:coc_last_hover_message\t\t\t\t*g:coc_last_hover_message*\n\n\tLast message echoed from `doHover`, can be used in statusline.\n\n\tNote: not used when floating or preview window used for `doHover`.\n\nb:coc_snippet_active\t\t\t\t\t*b:coc_snippet_active*\n\n\tIs `1` when snippet session is activated, use |coc#jumpable| to check\n\tif it's possible to jump placeholder.\n\nb:coc_diagnostic_disable\t\t\t\t*b:coc_diagnostic_disable*\n\n\tDisable diagnostic support of current buffer.\n\nb:coc_diagnostic_info\t\t\t\t\t*b:coc_diagnostic_info*\n\n\tDiagnostic information of current buffer, the format would look like:\n\n\t`{'error': 0, 'warning': 0, 'information': 0, 'hint':0}`\n\n\tcan be used to customize statusline. See |coc-status|.\n\nb:coc_diagnostic_map\t\t\t\t\t*b:coc_diagnostic_map*\n\n\tDiagnostics of current buffer, the format is same as *diagnostic-structure*\n\nb:coc_current_function\t\t\t\t\t*b:coc_current_function*\n\n\tFunction string that current cursor in.\n\n\tEnable |coc-preferences-currentFunctionSymbolAutoUpdate| to update the\n\tvalue on CursorMove and use |coc-preferences-currentFunctionSymbolDebounceTime|\n\tto set the time to debounce the event.\n\nb:coc_cursors_activated\t\t\t\t\t*b:coc_cursors_activated*\n\n\tUse expression `get(b:, 'coc_cursors_activated',0)` to check if\n\tcursors session is activated for current buffer.\n\n--------------------------------------------------------------------------------\nFUNCTIONS\t\t\t\t\t\t*coc-functions*\n\nSome functions only work after the coc.nvim has been initialized.\n\nTo run a function on startup, use an autocmd like: >\n\n\tautocmd User CocNvimInit call CocAction('runCommand',\n\t\t\t\t\t\t\\ 'tsserver.watchBuild')\n<\ncoc#start([{option}]) \t\t\t\t\t*coc#start()*\n\n\tStart completion with optional {option}.  Option could contains:\n\n\t\t- `source` specific completion source name.\n\t\t- `col` the start column of completion (1 based).\n\n\tExample:  >\n\n\tinoremap <silent> <C-w> <C-R>=coc#start({'source': 'word'})<CR>\n<\n\tUse `:CocList sources` to get available sources.\n\ncoc#refresh()\t\t\t\t\t\t*coc#refresh()*\n\n\tStart or refresh completion at current cursor position, bind this to\n\t'imap' to trigger completion, example: >\n\n\tif has('nvim')\n\t  inoremap <silent><expr> <c-space> coc#refresh()\n\telse\n\t  inoremap <silent><expr> <c-@> coc#refresh()\n\tendif\n\ncoc#config({section}, {value})\t\t\t\t*coc#config()*\n\n\tChange user configuration, overwrite configurations from\n\tuser config file and default values. Example: >\n\n\tcall coc#config('coc.preferences', {\n\t\t\\ 'willSaveHandlerTimeout': 1000,\n\t\t\\})\n\tcall coc#config('languageserver', {\n\t\t\\ 'ccls': {\n\t\t\\   \"command\": \"ccls\",\n\t\t\\   \"trace.server\": \"verbose\",\n\t\t\\   \"filetypes\": [\"c\", \"cpp\", \"objc\", \"objcpp\"]\n\t\t\\ }\n\t\t\\})\n<\n\n\tNote: this function can be called multiple times.\n\tNote: this function can be called before coc.nvim started.\n\tNote: this function can work alongside the user configuration file,\n\tbut it's not recommended to use both.\n\tNote: use |g:coc_user_config| when you have coc.nvim lazy loaded.\n\ncoc#add_command({id}, {command}, [{title}])\t\t*coc#add_command()*\n\n\tAdd custom Vim command to commands list opened by\n\t`:CocList commands` .\n\n\tExample: >\n\n\tcall coc#add_command('mundoToggle', 'MundoToggle',\n\t\t\\ 'toggle mundo window')\n<\ncoc#expandable()\t\t\t\t\t*coc#expandable()*\n\n\tCheck if a snippet is expandable at the current position.\n\tRequires `coc-snippets` extension installed.\n\ncoc#jumpable()\t\t\t\t\t\t*coc#jumpable()*\n\n\tCheck if a snippet is jumpable at the current position.\n\ncoc#expandableOrJumpable()\t\t\t\t*coc#expandableOrJumpable()*\n\n\tCheck if a snippet is expandable or jumpable at the current position.\n\tRequires `coc-snippets` extension installed.\n\ncoc#on_enter()\t\t\t\t\t\t*coc#on_enter()*\n\n\tNotify coc.nvim that <CR> has been pressed.\n\n\tUsed for the format on type and improvement of brackets insert,\n\texample: >\n\n\t\" Confirm the completion when popupmenu is visible, insert <CR> and\n\t\" notify coc.nvim otherwise.\n\tinoremap <silent><expr> <CR> coc#pum#visible() ? coc#pum#confirm()\n                    \t          \\: \"\\<C-g>u\\<CR>\\<c-r>=coc#on_enter()\\<CR>\"\n<\n\tTo enable format on type, use |coc-preferences-formatOnType|\n\tconfiguration.\n\ncoc#status([{escape}])\t\t\t\t\t*coc#status()*\n\n\tReturn a status string that can be used in the status line, the status\n\tincludes diagnostic information from |b:coc_diagnostic_info| and\n\textension contributed statuses from |g:coc_status|. For statusline\n\tintegration, see |coc-status|.\n\n\tEscape '%' to '%%' when {escape} is truth value.\n\ncoc#util#api_version() \t\t\t\t\t*coc#util#api_version()*\n\n\tGet coc.nvim's vim API version number, start from `1`.\n\ncoc#util#job_command()\t\t\t\t\t*coc#util#job_command()*\n\n\tGet the job command used for starting the coc service.\n\ncoc#util#get_config_home()\t\t\t\t*coc#util#get_config_home()*\n\n\tGet the config directory that contains the user's coc-settings.json.\n\ncoc#util#get_data_home()\t\t\t\t*coc#util#get_data_home()*\n\n\tGet data home directory, return |g:coc_data_home| when defined, else\n\tuse $XDG_CONFIG_HOME/coc when $XDG_CONFIG_HOME exists, else fallback\n\tto `~/AppData/Local/coc` on windows and `~/.config/coc` on other\n\tsystems.\n\ncoc#util#extension_root()\t\t\t\t*coc#util#extension_root()*\n\n\tReturn extensions root of coc.nvim.\n\ncoc#util#root_patterns()\t\t\t\t*coc#util#root_patterns()*\n\n\tGet root patterns used for current document.\n\n\tResult could be something like: >\n\n\t{'global': ['.git', '.hg', '.projections.json'], 'buffer': [], 'server': v:null}\n<\ncoc#util#get_config({key})\t\t\t\t*coc#util#get_config()*\n\n\tGet configuration of current document (mostly defined in\n\tcoc-settings.json) by {key}, example: >\n\n\t:echo coc#util#get_config('coc.preferences')\n\ncoc#snippet#next() \t\t\t\t\t*coc#snippet#next()*\n\n\tJump to next placeholder, does nothing when |coc#jumpable| is 0.\n\ncoc#snippet#prev() \t\t\t\t\t*coc#snippet#prev()*\n\n\tJump to previous placeholder, does nothing when |coc#jumpable| is 0.\n\n\t\t\t\t\t\t\t*coc#pum*\ncoc#pum#visible() \t\t\t\t\t*coc#pum#visible()*\n\n\tCheck if customized popupmenu is visible like |pumvisible()| does.\n\tReturn 1 when popup menu is visible.\n\ncoc#pum#has_item_selected()\t\t\t\t*coc#pum#has_item_selected*\n\n\tCheck if there is a completion item selected in the popup menu. Return\n\tnumber `1` when completion item is selected.\n\ncoc#pum#next({insert}) \t\t\t\t\t*coc#pum#next()*\n\n\tSelect next item of customized popupmenu, insert word when {insert} is\n\t1.\n\n\tNote: this function should only be used in <expr> key-mappings.\n\ncoc#pum#prev({insert}) \t\t\t\t\t*coc#pum#prev()*\n\n\tSelect previous item of customized popupmenu, insert word when {insert}\n\tis truth value.\n\n\tNote: this function should only be used in <expr> key-mappings.\n\ncoc#pum#stop() \t\t\t\t\t\t*coc#pum#stop()*\n\n\tClose the customized popupmenu and stop the completion, works like\n\t<C-x><C-z> of vim.\n\n\tNote: this function should only be used in <expr> key-mappings.\n\ncoc#pum#cancel() \t\t\t\t\t*coc#pum#cancel()*\n\n\tClose the customized popupmenu and reset the trigger input before\n\tcursor when the trigger changed by pum navigation, like <C-e> of vim.\n\n\tNote: this function should only be used in <expr> key-mappings.\n\ncoc#pum#insert() \t\t\t\t\t*coc#pum#insert()*\n\n\tInsert word of current selected item and finish completion.  Unlike\n\t|coc#pum#confirm()|, no text edit would be applied and snippet would\n\tnot be expanded.\n\n\tNote: this function should only be used in <expr> key-mappings.\n\ncoc#pum#confirm() \t\t\t\t\t*coc#pum#confirm()*\n\n\tConfirm completion of the selected item (when possible) by insert the\n\t`word` of completion item when not inserted, and close the customized\n\tpopup menu, like <C-y> of vim.  Trigger the optional `onCompleteDone`\n\thandler of completion source after buffer text changed.\n\n\tNote: this function should only be used in <expr> key-mappings.\n\ncoc#pum#select_confirm()\t \t\t\t*coc#pum#select_confirm()*\n\n\tSelect the first completion item if no complete item is selected, then\n\tconfirm the completion like |coc#pum#confirm()|.\n\n\tNote: this function should only be used in <expr> key-mappings.\n\ncoc#pum#info() \t\t\t\t\t\t*coc#pum#info()*\n\n\tReturn information of the customized popupmenu, should only be used\n\twhen |coc#pum#visible()| is 1.\n\n\tResult contains:\n\t\tindex \t\tCurrent select item index, 0 based.\n\t\tscrollbar \tNon-zero if a scrollbar is displayed.\n\t\trow \t\tScreen row count, 0 based.\n\t\tcol \t\tScreen column count, 0 based.\n\t\twidth \t\tWidth of pum, including padding and border.\n\t\theight \t\tHeight of pum, including padding and border.\n\t\tsize \t\tCount of displayed complete items.\n\t\tinserted \tIs |v:true| when there is item inserted.\n\t\treversed \tIs |v:true| when pum shown above cursor and\n\t\t\t\tenable |suggest.reversePumAboveCursor|\n\ncoc#pum#select({index}, {insert}, {confirm}) \t\t*coc#pum#select()*\n\n\tSelects a complete item in the completion popupmenu, with optional\n\t{insert} and {confirm} action.  Return empty string.\n\n\tNote: when {insert} enabled or cancelled by `index = -1`, the current\n\tbuffer state could be not synchronized (to have better performance\n\twith dot repeat support |feedkeys()| with `\\<C-n>` is used to insert\n\tword when necessary). Use |timer_start()| afterwards to wait for the\n\tbuffer text synchronize and `stopCompletion` request finish when\n\tneeded.\n\n\tParameters: ~\n\t\t{index} \tIndex (zero-based) of the item to select.\n\t\t\t\tWhen is `-1` the completion is canceled.\n\t\t\t\tThrow error when index out of range.\n\t\t{insert} \tWhether the word of selected completion item\n\t\t\t\tshould be inserted to the buffer.\n\t\t{confirm} \tConfirm the completion and dismiss the\n\t\t\t\tpopupmenu, implies `insert = 1`.\n\ncoc#pum#one_more() \t\t\t\t\t*coc#pum#one_more()*\n\n\tInsert one more character from current complete item (first complete\n\titem when no complete item selected), works like <CTRL-L> of\n\t|popupmenu-keys|.  Note that the word of complete item should starts\n\twith current input.\n\n\tNothing happens when failed.\n\n\tNote: this function should only be used in <expr> key-mappings.\n\ncoc#pum#scroll({forward}) \t\t\t\t*coc#pum#scroll()*\n\n\tScroll the popupmenu forward or backward by page.\n\tTimer is used to make it works as {rhs} of key-mappings.\n\tReturn <Ignore>.\n\n\tParameters: ~\n\t\t{forward} \tScroll forward when none zero.\n\ncoc#inline#trigger([{option}]) \t\t\t\t*coc#inline#trigger()*\n\n\tParameters: ~\n\t\t{option} Optional option object. Which can have\n\t\tfollowing properties:\n\t\t- `provider` the provider name (the extension name or\n\t\t  Language Client id which register inline completion\n\t\t  provider), all valid providers will be requested in parallel\n\t\t  when not specified.\n\t\t- `silent` when truthy no message would be shown when no\n\t\t  inline completion items exists.\n\n\tReturn `\"\"`\n\ncoc#inline#visible() \t\t\t\t\t*coc#inline#visible()*\n\n\tReturn `1` when inline completion visual text exists for current\n\tbuffer.\n\ncoc#inline#cancel() \t\t\t\t\t*coc#inline#cancel()*\n\n\tCancel the inline completion request and clear inline completion\n\tvirtual text of current buffer.\n\tReturn `\"\"`.\n\n\tNote: this function uses |CocActionAsync|, use it in key-mappings\n\tinstead of API.\n\ncoc#inline#accept([{kind}]) \t\t\t\t\t*coc#inline#accept()*\n\n\tAccept current inline completion by insert text to current buffer when\n\tpossible, nothing happens when inline completion is not activated.\n\n\tParameters: ~\n\t\t{kind} \tOptional accept kind of inline completion, could be\n\t\tone of \"all\", \"word\", \"line\".  Default to \"all\" which means\n\t\taccept full insert text of complete item.\n\n\tNote: this function uses |CocActionAsync|, use it in key-mappings\n\tinstead of API.\n\ncoc#inline#next() \t\t\t\t\t*coc#inline#next()*\n\n\tNavigate to next inline complete item.\n\tReturn `\"\"`.\n\n\tNote: this function uses |CocActionAsync|, use it in key-mappings\n\tinstead of API.\n\ncoc#inline#prev() \t\t\t\t\t*coc#inline#prev()*\n\n\tNavigate to previous inline complete item.\n\tReturn `\"\"`.\n\n\tNote: this function uses |CocActionAsync|, use it in key-mappings\n\tinstead of API.\n\n\t\t\t\t\t\t\t*coc#notify*\ncoc#notify#close_all()\t\t\t\t\t*coc#notify#close_all()*\n\n\tClose all notification windows.\n\ncoc#notify#do_action([{winid}]) \t\t\t*coc#notify#do_action()*\n\n\tInvoke action for all notification windows, or particular window with\n\twinid.\n\ncoc#notify#copy() \t\t\t\t\t*coc#notify#copy()*\n\n\tCopy all content from notifications to system clipboard.\n\ncoc#notify#show_sources() \t\t\t\t*coc#notify#show_sources()*\n\n\tShow source name (extension name) in notification windows.\n\ncoc#notify#keep() \t\t\t\t\t*coc#notify#keep()*\n\n\tStop auto hide timer of notification windows.\n\ncoc#float#has_float([{all}]) \t\t\t\t*coc#float#has_float()*\n\n\tCheck if float window/popup exists, check coc.nvim's float\n\twindow/popup by default.\n\ncoc#float#close_all([{all}])\t\t\t\t*coc#float#close_all()*\n\n\tClose all float windows/popups created by coc.nvim, set {all} to `1`\n\tfor all float window/popups.\n\n\tReturn `\"\"`.\n\ncoc#float#close({winid}) \t\t\t\t*coc#float#close()*\n\n\tClose float window/popup with {winid}.\n\ncoc#float#has_scroll() \t\t\t\t\t*coc#float#has_scroll()*\n\n\tReturn `1` when there is scrollable float window/popup created by\n\tcoc.nvim.\n\n\tExample key-mappings:\n>\n\tnnoremap <silent><nowait><expr> <C-f> coc#float#has_scroll() ? coc#float#scroll(1) : \"\\<C-f>\"\n\tnnoremap <silent><nowait><expr> <C-b> coc#float#has_scroll() ? coc#float#scroll(0) : \"\\<C-b>\"\n\tinoremap <silent><nowait><expr> <C-f> coc#float#has_scroll() ? \"\\<c-r>=coc#float#scroll(1)\\<cr>\" : \"\\<Right>\"\n\tinoremap <silent><nowait><expr> <C-b> coc#float#has_scroll() ? \"\\<c-r>=coc#float#scroll(0)\\<cr>\" : \"\\<Left>\"\n\tvnoremap <silent><nowait><expr> <C-f> coc#float#has_scroll() ? coc#float#scroll(1) : \"\\<C-f>\"\n\tvnoremap <silent><nowait><expr> <C-b> coc#float#has_scroll() ? coc#float#scroll(0) : \"\\<C-b>\"\n<\ncoc#float#scroll({forward}, [{amount}])\t\t\t*coc#float#scroll()*\n\n\tScroll all scrollable float windows/popups, scroll backward when\n\t{forward} is not `1`. {amount} could be number or full page when\n\tomitted.  Popup menu is excluded.\n\ncoc#compat#call({name}, {args}) \t\t\t*coc#compat#call()*\n\t\n\tCall api function {name} with {args} list (starts with `nvim_`) on vim\n\tor neovim, use:\n\t>\n\t:echo coc#api#Get_api_info()[1]['functions']\n<\n\ton vim9 to get supported apis on vim.\n\nCocRequest({id}, {method}, [{params}])\t\t\t*CocRequest()*\n\n\tSend a request to language client of {id} with {method} and optional\n\t{params}. Example: >\n\n\tcall CocRequest('tslint', 'textDocument/tslint/allFixes',\n\t\t\\  {'textDocument': {'uri': 'file:///tmp'}})\n<\n\tVim error will be raised if the response contains an error.\n\n\t\t\t\t\t\t\t*CocRequestAsync()*\n\nCocRequestAsync({id}, {method}, [{params}, [{callback}]])\n\n\tSend async request to remote language server.\n\t{callback} function is called with error and response.\n\nCocNotify({id}, {method}, [{params}])\t\t\t*CocNotify()*\n\n\tSend notification to remote language server, example:\n>\n\tcall CocNotify('ccls', '$ccls/reload')\n<\n\t\t\t\t\t\t\t*CocRegisterNotification()*\n\nCocRegisterNotification({id}, {method}, {callback})\n\n\tRegister notification callback for specified client {id} and {method},\n\texample: >\n\n\tautocmd User CocNvimInit call CocRegisterNotification('ccls',\n\t\t\\ '$ccls/publishSemanticHighlight', function('s:Handler'))\n<\n\t{callback} is called with single param as notification result.\n\n\tNote: when register notification with same {id} and {method}, only the\n\tlater registered would work.\n\n\t\t\t\t\t\t\t*CocLocations()*\n\nCocLocations({id}, {method}, [{params}, {openCommand}])\n\n\tSend location request to language client of {id} with\n\t{method} and optional {params}. e.g.: >\n\n\tcall CocLocations('ccls', '$ccls/call',  {'callee': v:true})\n\n\tcall CocLocations('ccls', '$ccls/call',  {}, 'vsplit')\n<\n\t{openCommand}: optional command to open buffer, default to\n\t`coc.preferences.jumpCommand` , |:edit| by default.  When it's\n\t`v:false` locations list would always used.\n\n\t\t\t\t\t\t\t*CocLocationsAsync()*\n\nCocLocationsAsync({id}, {method}, [{params}, {openCommand}])\n\n\tSame as |CocLocations()|, but send notification to server instead\n\tof request.\n\n\nCocAction({action}, [...{args}])\t\t\t*CocAction()*\n\n\tRun {action} of coc with optional extra {args}.\n\n\tCheckout |coc-actions| for available actions.\n\n\tNote: it's recommended to use |CocActionAsync()| unless you have to\n\tblock your vim.\n\n\t\t\t\t\t\t\t*CocActionAsync()*\n\nCocActionAsync({action}, [...{args}, [{callback}]])\n\n\tCall CocAction by send notification to NodeJS process of coc.nvim.\n\n\tWhen callback function exists as the last argument, the callback\n\tfunction is called with `error` string as the first argument and\n\t`result` as the second argument.  When no callback exists, error\n\tmessage would be echoed.\n\n\tCheckout |coc-actions| for available actions.\n\n\tNote: callback function required to get the vim state after the\n\taction.\n\nCocHasProvider({feature}, [{bufnr}])\t\t\t*CocHasProvider()*\n\n\tCheck if provider exists for specified feature of current or\n\t{bufnr} buffer.\n\tSupported features:\n\n\t`rename` `onTypeEdit` `documentLink` `documentColor` `foldingRange`\n\t`format` `codeAction` `workspaceSymbols` `formatRange` `hover`\n\t`signature` `documentSymbol` `documentHighlight` `definition`\n\t`declaration` `typeDefinition` `reference` `implementation` `codeLens`\n\t`selectionRange` `formatOnType` `callHierarchy` `semanticTokens`\n\t`semanticTokensRange` `linkedEditing` `inlayHint` `inlineValue`\n\t`typeHierarchy`\n\nCocTagFunc({pattern}, {flags}, {info})\t\t\t*CocTagFunc()*\n\n\tUsed for vim's 'tagfunc' option, to make tag search by |CTRL-]| use\n\tcoc.nvim as provider, tag search would be performed when no result\n\tfrom coc.nvim.\n\n\tMake sure your vim support 'tagfunc' by\n>\n\t:echo exists('&tagfunc')\n<\n--------------------------------------------------------------------------------\n\t\t\t\t\t\t\t*coc-actions*\nAvailable Actions ~\n\nAcceptable {action} names for |CocAction()| and |CocActionAsync()|.\n\n\"addWorkspaceFolder\" {folder}\t\t\t\t*CocAction('addWorkspaceFolder')*\n\n\tAdd {folder} to workspace folders, {folder} should be exists directory\n\ton file system.\n\n\"removeWorkspaceFolder\" {folder}\t\t\t*CocAction('removeWorkspaceFolder')*\n\n\tRemove workspace fold {folder}, {folder} should be exists directory on\n\tfile system.\n\n\"ensureDocument\" [{bufnr}] \t\t\t\t*CocAction('ensureDocument')*\n\n\tEnsure current or specified document is attached to coc.nvim\n\t|coc-document-attached|, should be used when you need invoke action of\n\tcurrent document on buffer create.\n\n\tReturn |v:false| when document can't be attached.\n\n\"diagnosticList\"\t\t\t\t\t*CocAction('diagnosticList')*\n\n\tGet all diagnostic items of the current Neovim session.\n\n\"diagnosticInfo\" [{target}]\t\t\t\t*CocAction('diagnosticInfo')*\n\n\tShow diagnostic message at the current position, do not truncate.\n\n\tOptional {target} could be `float` or `echo`.\n\n\"diagnosticToggle\" [{enable}]\t \t\t\t*CocAction('diagnosticToggle')*\n\n\tEnable/disable diagnostics on the fly. This setting is ignored if\n\t`displayByAle` is enabled. You can toggle by specifying {enable}.\n\t{enable} can be 0 or 1\n\n\"diagnosticToggleBuffer\" [{bufnr}] [{enable}] \t\t*CocAction('diagnosticToggleBuffer')*\n\n\tToggle diagnostics for specific buffer, current buffer is used when\n\t{bufnr} not provided. 0 for current buffer\n\tYou can toggle by specifying {enable}. {enable} can be 0 or 1\n\n\tNote: this will only affect diagnostics shown in the UI, list of all\n\tdiagnostics won't change.\n\n\"diagnosticPreview\"\t\t\t\t\t*CocAction('diagnosticPreview')*\n\n\tShow diagnostics under current cursor in preview window.\n\n\"diagnosticRefresh\" [{bufnr}] \t\t\t\t*CocAction('diagnosticRefresh')*\n\n\tForce refresh diagnostics for special buffer with {bufnr} or all buffers\n\twhen {bufnr} doesn't exist, returns `v:null` before diagnostics are shown.\n\n\tNOTE: Will refresh in any mode.\n\n\tUseful when `diagnostic.autoRefresh` is `false`.\n\n\"sourceStat\"\t\t\t\t\t\t*CocAction('sourceStat')*\n\n\tget the list of completion source stats for the current buffer.\n\n\"toggleSource\" {source}\t\t\t\t\t*CocAction('toggleSource')*\n\n\tenable/disable {source}.\n\n\"definitions\" \t\t\t\t\t\t*CocAction('definitions')*\n\n\tGet definition locations of symbol under cursor.\n\tReturn LSP `Location[]`\n\n\"declarations\" \t\t\t\t\t\t*CocAction('declarations')*\n\n\tGet declaration location(s) of symbol under cursor.\n\tReturn LSP `Location | Location[] | LocationLink[]`\n\n\"implementations\" \t\t\t\t\t*CocAction('implementations')*\n\n\tGet implementation locations of symbol under cursor.\n\tReturn LSP `Location[]`\n\n\"typeDefinitions\" \t\t\t\t\t*CocAction('typeDefinitions')*\n\n\tGet type definition locations of symbol under cursor.\n\tReturn LSP `Location[]`\n\n\"references\" [{excludeDeclaration}] \t\t\t*CocAction('references')*\n\n\tGet references location list of symbol under cursor.\n\n\t{excludeDeclaration}: exclude declaration locations when not zero.\n\n\tReturn LSP `Location[]`\n\n\"jumpDefinition\" [{openCommand}]\t\t\t*CocAction('jumpDefinition')*\n\n\tjump to definition locations of the current symbol.\n\tReturn `v:false` when location not found.\n\n\t|coc-list-location| is used when more than one position is available,\n\tfor custom location list, use variable: |g:coc_enable_locationlist|.\n\n\tTo always use |coc-list-location|| for locations, use `v:false` for\n\t{openCommand}.\n\n\t{openCommand}: optional command to open buffer, default to\n\t`coc.preferences.jumpCommand` in `coc-settings.json`\n\n\"jumpDeclaration\" [{openCommand}]\t\t\t*CocAction('jumpDeclaration')*\n\n\tjump to declaration locations of the current symbol.\n\tReturn `v:false` when location not found.\n\n\tsame behavior as \"jumpDefinition\".\n\n\tWhen {openCommand} is `v:false`, location list would be always used.\n\n\"jumpImplementation\" [{openCommand}]\t\t\t*CocAction('jumpImplementation')*\n\n\tJump to implementation locations of the current symbol.\n\tReturn `v:false` when location not found.\n\n\tsame behavior as \"jumpDefinition\"\n\n\"jumpTypeDefinition\" [{openCommand}]\t\t\t*CocAction('jumpTypeDefinition')*\n\n\tJump to type definition locations of the current symbol.\n\tReturn `v:false` when location not found.\n\n\tsame behavior as \"jumpDefinition\"\n\n\"jumpReferences\" [{openCommand}]\t\t\t*CocAction('jumpReferences')*\n\n\tJump to references locations of the current symbol, use\n\t|CocAction('jumpUsed')| to exclude declaration locations.\n\n\tReturn `v:false` when location not found.\n\n\tsame behavior as \"jumpDefinition\"\n\n\"jumpUsed\" [{openCommand}] \t\t\t\t*CocAction('jumpUsed')*\n\n\tJump references locations without declarations.\n\n\tsame behavior as \"jumpDefinition\"\n\n\"getHover\" [{hoverLocation}]\t\t\t\t*CocAction('getHover')*\n\n\tGet documentation text array on {hoverLocation} or current position,\n\treturns array of string.\n\n\t{hoverLocation} could contains:\n\t• bufnr: optional buffer number.\n\t• line: 1 based line number.\n\t• col: 1 based col number\n\n\tThrow error when buffer with bufnr is not attached.\n\n\"doHover\" [{hoverTarget}]\t\t\t\t*CocAction('doHover')*\n\n\tShow documentation of  current symbol, return `v:false` when hover not\n\tfound.\n\n\t{hoverTarget}: optional specification for where to show hover info,\n\tdefaults to `coc.preferences.hoverTarget` in `coc-settings.json`.\n\tValid options: [\"preview\", \"echo\", \"float\"]\n\n\"definitionHover\" [{hoverTarget}]\t\t\t*CocAction('definitionHover')*\n\n\tSame as |CocAction('doHover')|, but includes definition contents from\n\tdefinition provider when possible.\n\n\"showSignatureHelp\"\t\t\t\t\t*CocAction('showSignatureHelp')*\n\n\tEcho signature help of current function, return `v:false` when\n\tsignature not found.  You may want to set up an autocmd like this: >\n\n\"getCurrentFunctionSymbol\"\t\t\t\t*CocAction('getCurrentFunctionSymbol')*\n\n\tReturn the function string that current cursor in.\n\n\"documentSymbols\" [{bufnr}]\t\t\t\t*CocAction('documentSymbols')*\n\n\tGet a list of symbols of current buffer or specific {bufnr}.\n\n\"rename\"\t\t\t\t\t\t*CocAction('rename')*\n\n\tRename the symbol under the cursor position, |coc-dialog-input| would\n\tbe shown for prompt a new name.\n\n\tShow error message when the provider not found or prepare rename\n\tfailed.\n\n\tThe buffers are not saved after apply workspace edits, use |:wa| to\n\tsave all buffers.  It's possible to undo/redo and inspect the changes,\n\tsee |coc-workspace-edits|.\n\n\tNote: coc.nvim supports rename for disk files, but your language server\n\tmay not.\n\n\"refactor\"\t\t\t\t\t\t*CocAction('refactor')*\n\n\tOpen |coc-refactor-buffer| with current symbol as activated cursor\n\tranges. Requires LSP rename support enabled with current buffer, use\n\t|:CocSearch| when rename support is not available.\n\n\"format\"\t\t\t\t\t\t*CocAction('format')*\n\n\tFormat current buffer using the language server.\n\tReturn `v:false` when format failed.\n\n\"formatSelected\" [{mode}]\t\t\t\t*CocAction('formatSelected')*\n\n\tFormat the selected range, {mode} should be one of visual mode: `v` ,\n\t`V`, `char`, `line`.\n\n\tWhen {mode} is omitted, it should be called using |formatexpr|.\n\n\"snippetCancel\"\t\t\t\t\t\t*CocAction('snippetCancel')*\n\n\tCancel current snippet session.\n\n\"snippetInsert\" {range} {snippet} [{mode}]\t\t*CocAction('snippetInsert')*\n\n\tInsert {snippet} text as specific {range} of current buffer.\n\n\t{range} should be valid LSP range like:\n>\n\t// all 0 based utf16 unit code index.\n\t{\"start\": {\"line\": 0, \"character\": 1}, \"end\": {\"line\": 0, \"character\": 3}}\n<\n\t{snippet} is the textmate format snippet text used by VSCode.\n\n\t{mode} could be 1 or 2, use 1 to disable format of snippet.\n\n\"selectionRanges\"\t\t\t\t\t*CocAction('selectionRanges')*\n\n\tGet selection ranges of current position from language server.\n\n\"services\"\t\t\t\t\t\t*CocAction('services')*\n\n\tGet an information list for all services.\n\n\"toggleService\" {serviceId}\t\t\t\t*CocAction('toggleService')*\n\n\tStart or stop a service.\n\n\"codeAction\" [{mode}] [{only}] [{include_disabled}]\t*CocAction('codeAction')*\n\n\tPrompt for a code action and do it.\n\n\t{mode} could be `currline` or `cursor` or result of |visualmode()|,\n\tcurrent buffer range is used when it's empty string.\n\n\t{only} can be title of a codeAction or list of CodeActionKind.\n\n\t{include_disabled} include disabled actions when is truth value.\n\n\"codeActionRange\" {start} {end} [{kind}]\t\t*CocAction('codeActionRange')*\n\n\tRun code action for range.\n\n\t{start} \tStart line number of range.\n\t{end} \t\tEnd line number of range.\n\t{kind} \t\tCode action kind, see |CocAction('codeActions')| for available\n\t\t\taction kind.\n\n\tCan be used to create commands like: >\n\n\tcommand! -nargs=* -range CocAction :call CocActionAsync('codeActionRange', <line1>, <line2>, <f-args>)\n\tcommand! -nargs=* -range CocFix    :call CocActionAsync('codeActionRange', <line1>, <line2>, 'quickfix')\n<\n\"codeLensAction\"\t\t\t\t\t*CocAction('codeLensAction')*\n\n\tInvoke the command for codeLens of current line (or the line that\n\tcontains codeLens just above). Prompt would be shown when multiple\n\tactions are available.\n\n\"commands\"\t\t\t\t\t\t*CocAction('commands')*\n\n\tGet a list of available service commands for the current buffer.\n\n\"runCommand\" [{name}] [...{args}]\t\t\t*CocAction('runCommand')*\n\n\tRun a global command provided by the language server. If {name} is not\n\tprovided, a prompt with a list of commands is shown to be selected.\n\n\t{args} are passed as arguments of command.\n\n\tYou can bind your custom command like so: >\n\n\tcommand! -nargs=0 OrganizeImport\n\t\t\\ :call CocActionAsync('runCommand', 'editor.action.organizeImport')\n<\n\"fold\" {{kind}}\t\t\t\t\t\t*CocAction('fold')*\n\n\tFold the current buffer, optionally use {kind} for specific\n\tFoldingRangeKind.\n\t{kind} could be 'comment', 'imports' or 'region'.\n\n\tReturn `v:false` when failed.\n\n\tYou can create a custom command like: >\n\n\tcommand! -nargs=? Fold \t\t:call CocAction('fold', <f-args>)\n<\n\"highlight\"\t\t\t\t\t\t*CocAction('highlight')*\n\n\tHighlight the symbols under the cursor.\n\n\"openLink\" [{command}]\t\t\t\t\t*CocAction('openLink')*\n\n\tOpen a link under the cursor with {command}.\n\t{command} default to `edit`.\n\n\tFile and URL links are supported, return `v:false` when failed.\n\n\tURI under cursor would be searched when no link returned from the\n\t\"documentLink\" provider.\n\n\tConfigure |g:coc_open_url_command| for custom command to open remote\n\turl.\n\n\"links\" \t\t\t\t\t\t*CocAction('links')*\n\n\tReturn document link list of current buffer.\n\n\"extensionStats\"\t\t\t\t\t*CocAction('extensionStats')*\n\n\tGet all extension states as a list. Including `id`, `root` and\n\t`state`.\n\n\tState could be `disabled`, `activated` and `loaded`.\n\n\"toggleExtension\" {id}\t\t\t\t\t*CocAction('toggleExtension')*\n\n\tEnable/disable an extension.\n\n\"uninstallExtension\" {id}\t\t\t\t*CocAction('uninstallExtension')*\n\n\tUninstall an extension.\n\n\"reloadExtension\" {id}\t\t\t\t\t*CocAction('reloadExtension')*\n\n\tReload an activated extension.\n\n\"activeExtension\" {id}\t\t\t\t\t*CocAction('activeExtension')*\n\n\tActivate extension of {id}.\n\n\"deactivateExtension\" {id}\t\t\t\t*CocAction('deactivateExtension')*\n\n\tDeactivate extension of {id}.\n\n\"pickColor\"\t\t\t\t\t\t*CocAction('pickColor')*\n\n\tChange the color at the current cursor position, requires\n\t`documentColor` provider |CocHasProvider|.\n\n\tNote: only works on mac or when you have python support on Vim and\n\thave the GTK module installed.\n\n\"colorPresentation\"\t\t\t\t\t*CocAction('colorPresentation')*\n\n\tChange the color presentation at the current color position, requires\n\t`documentColor` provider |CocHasProvider|.\n\n\"codeActions\" [{mode}] [{only}]\t\t\t\t*CocAction('codeActions')*\n\n\tGet codeActions list of current document.\n\n\t{mode} can be result of |visualmode()| for visual selected\n\trange.  When it's falsy value, current file is used as range.\n\n\t{only} can be array of codeActionKind, possible values including:\n\t - 'refactor': Base kind for refactoring actions\n\t - 'quickfix': base kind for quickfix actions\n\t - 'refactor.extract': Base kind for refactoring extraction actions\n\t - 'refactor.inline': Base kind for refactoring inline actions\n\t - 'refactor.rewrite': Base kind for refactoring rewrite actions\n\t - 'source': Base kind for source actions\n\t - 'source.organizeImports': Base kind for an organize imports source\n\t   action\n\t - 'source.fixAll': Base kind for auto-fix source actions\n\n\t{only} can also be string, which means filter by title of codeAction.\n\n\"organizeImport\" \t\t\t\t\t*CocAction('organizeImport')*\n\n\tRun organize import code action for current buffer.\n\tReturn `false` when the code action not exists.\n\n\"fixAll\" \t\t\t\t\t\t*CocAction('fixAll')*\n\n\tRun fixAll codeAction for current buffer.\n\tShow warning when codeAction not found.\n\n\n\"quickfixes\" [{visualmode}]\t\t\t\t*CocAction('quickfixes')*\n\n\tGet quickfix codeActions of current buffer.\n\n\tAdd {visualmode} as second argument get quickfix actions with range of\n\tlatest |visualmode()|\n\n\"doCodeAction\" {codeAction}\t\t\t\t*CocAction('doCodeAction')*\n\n\tDo a codeAction.\n\n\"doQuickfix\"\t\t\t\t\t\t*CocAction('doQuickfix')*\n\n\tDo the first preferred quickfix action on current line.\n\n\tThrow error when no quickfix action found.\n\n\"addRanges\" {ranges}\t\t\t\t\t*CocAction('addRanges')*\n\n\tRanges must be provided as array of range type: https://git.io/fjiEG\n\n\"getWordEdit\"\t\t\t\t\t\t*CocAction('getWordEdit')*\n\n\tGet workspaceEdit of current word, language server used when possible,\n\textract word from current buffer as fallback.\n\n\"getWorkspaceSymbols\" {input}\t\t\t\t*CocAction('getWorkspaceSymbols')*\n\n\tGet workspace symbols from {input}.\n\n\"resolveWorkspaceSymbol\" {symbol} \t\t\t*CocAction('resolveWorkspaceSymbol')*\n\n\tResolve location for workspace {symbol}.\n\n\"showOutline\" [{keep}]\t\t\t\t\t*CocAction('showOutline')*\n\n\tShow |coc-outline| for current buffer. Does nothing when outline\n\twindow already shown for current buffer.\n\n\t{keep} override `\"outline.keepWindow\"` configuration when specified.\n\tCould be 0 or 1.\n\n\tReturns after window is shown (document symbol request is still in\n\tprogress).\n\n\"hideOutline\" \t\t\t\t\t\t*CocAction('hideOutline')*\n\n\tClose |coc-outline| on current tab.  Throws vim error when it can't\n\tbe closed by vim.\n\n\"incomingCalls\" [{CallHierarchyItem}] \t\t\t*CocAction('incomingCalls')*\n\n\tRetrieve incoming calls from {CallHierarchyItem} or current position\n\twhen not provided.\n\n\"outgoingCalls\" [{CallHierarchyItem}] \t\t\t*CocAction('outgoingCalls')*\n\n\tRetrieve outgoing calls from {CallHierarchyItem} or current position\n\twhen not provided.\n\n\"showIncomingCalls\"  \t\t\t\t\t*CocAction('showIncomingCalls')*\n\n\tShow incoming calls of current function with |coc-tree|, see\n\t|coc-callHierarchy|\n\n\"showOutgoingCalls\"  \t\t\t\t\t*CocAction('showOutgoingCalls')*\n\n\tShow outgoing calls of current function with |coc-tree|.\n\n\"showSuperTypes\"  \t\t\t\t\t*CocAction('showSuperTypes')*\n\n\tShow super types of types under cursor with |coc-tree|, see\n\t|coc-typeHierarchy|.  A warning is shown when no types found under\n\tcursor.\n\n\"showSubTypes\"  \t\t\t\t\t*CocAction('showSubTypes')*\n\n\tShow sub types of types under cursor with |coc-tree|, see\n\t|coc-typeHierarchy|.  A warning is shown when no types found under\n\tcursor.\n\n\"semanticHighlight\" \t\t\t\t\t*CocAction('semanticHighlight')*\n\n\tRequest semantic tokens highlight for current buffer.\n\n\"inspectSemanticToken\" \t\t\t\t\t*CocAction('inspectSemanticToken')*\n\n\tInspect semantic token information at cursor position.\n\n\"rangeSelect\" {visualmode} {forward} \t\t \t*CocAction('rangeSelect')*\n\n\tVisual select previous or next selection range, requires\n\t`selectionRange` provider.\n\n\t{visualmode} should be result of {visualmode} or \"\" for current cursor\n\tposition.\n\t{forward} select backward when it's falsy value.\n\n\"sendRequest\" {id} {method} [{params}]\t\t \t*CocAction('sendRequest')*\n\n\tSend LSP request with {method} and {params} to the language server of\n\t{id} (check the id by `:CocList services`).\n\tCheckout the LSP specification at\n\thttps://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/\n\n\"sendNotification\" {id} {method} [{params}]\t\t*CocAction('sendNotification')*\n\n\tSend LSP notification to language server with {id}.\n\n--------------------------------------------------------------------------------\nCOMMANDS\t\t\t\t\t\t*coc-commands*\n\n:CocStart\t\t\t\t\t\t*:CocStart*\n\n\tStart the coc.nvim server, do nothing if the server already started.\n\n:CocRestart\t\t\t\t\t\t*:CocRestart*\n\n\tRestart coc.nvim service.\n\n\tUse this command when you want coc to start all over again.\n\n:CocPrintErrors\t\t\t\t\t\t*:CocPrintErrors*\n\n\tShow errors from stderr of NodeJS process in a split window.\n\n:CocDisable\t\t\t\t\t\t*:CocDisable*\n\n\tDisable handling vim events, useful for debug performance issues.\n\n\tTo disable dynamic autocmds registered by extensions, use:\n>\n\t:call coc#clearGroups('coc_dynamic_')\n<\n:CocEnable\t\t\t\t\t\t*:CocEnable*\n\n\tEnable handling vim events.\n\n:CocConfig\t\t\t\t\t\t*:CocConfig*\n\n\tEdit the user config file `coc-settings.json` in\n\t|coc#util#get_config_home()|\n\n:CocLocalConfig\t\t\t\t\t\t*:CocLocalConfig*\n\n\tEdit or create `.vim/coc-settings.json` in current workspace folder.\n\n:CocInstall [{option}] {name} ...\t\t\t*:CocInstall*\n\n\tInstall one or more coc extensions.\n\n\t{option}: could be `-sync` for use blocked process to download instead\n\tof terminal.\n\n\tExamples: >\n\n\t\" Install latest coc-omni\n\t:CocInstall coc-omni\n\t\" Install coc-omni 1.0.0\n\t:CocInstall coc-omni@1.0.0\n\t\" Install snippet extension from github\n\t:CocInstall https://github.com/dsznajder/vscode-es7-javascript-react-snippets\n<\n:CocUninstall {name}\t\t\t\t\t*:CocUninstall*\n\n\tUninstall an extension, use <tab> to complete the extension\n\tname.\n\n\tNote: the data create by extension is not cleaned up, you may have to\n\tmanually remove them.\n\n:CocUpdate\t\t\t\t\t\t*:CocUpdate*\n\n\tUpdate all coc extensions to the latest version.\n\n:CocUpdateSync\t\t\t\t\t\t*:CocUpdateSync*\n\n\tBlock version of update coc extensions.\n\n:CocCommand [{name}] [{args}] ... \t\t\t*:CocCommand*\n\n\tRun a command provided by coc.nvim or contributed by extensions, use\n\t`<tab>` for name completion.  Open |coc-list-commands| when {name} not\n\tprovided.\n\n\tNote: This command send notification to coc.nvim, to perform task after\n\tthe command use |CocAction('runCommand')| instead.\n\n\n:CocOpenLog\t\t\t\t\t\t*:CocOpenLog*\n\n\tOpen log file of coc.nvim.\n\n\tUse environmental variable `NVIM_COC_LOG_FILE` for fixed log file.\n\tNote: the log would be cleared when coc.nvim started.\n\n\tUse environment variable `NVIM_COC_LOG_LEVEL` to change log level\n\t(default 'info', could be 'all', 'trace', 'debug', 'info',\n\t'warn', 'error', 'off').\n\tUse shell command: >\n\n\texport NVIM_COC_LOG_LEVEL=debug\n<\n\tor add: >\n\n\tlet $NVIM_COC_LOG_LEVEL='debug'\n<\n\tto your `.vimrc`\n\n:CocInfo\t\t\t\t\t\t*:CocInfo*\n\n\tShow version and log information in a split window, useful for\n\tsubmitting a bug report.\n\n:CocDiagnostics\t[height] \t\t\t\t*:CocDiagnostics*\n\n\tOpen vim's |location-list| with diagnostics of current buffer. The\n\tlocation list is automatically updated by default.  When multiple\n\tlocation list are opened for one buffer, only first one would be\n\tautomatically updated.\n\n:CocSearch \t\t\t\t\t\t*:CocSearch*\n\n\tPerform search by ripgrep https://github.com/BurntSushi/ripgrep,\n\tand open |coc-refactor-buffer| with searched results.\n\n\tNote: the search is performed on your files, so normally you should\n\tsave your buffers before invoke this command.\n\n\tCommon arguments for ripgrep: ~\n\n\t`-e` `--regexp`: treat search pattern as regexp.\n\t`-F` `--fixed-strings`: treat search pattern as fixed string.\n\t`-L` `--follow`: follow symbolic links while traversing directories.\n\t`-g` `--glob` {GLOB}: Include or exclude files and directories for\n\tsearching that match the given glob.\n\t`--hidden`: Search hidden files and directories.\n\t`--no-ignore-vcs`:  Don't respect version control ignore files\n\t(.gitignore, etc.).\n\t`--no-ignore`: Don't respect ignore files (.gitignore, .ignore, etc.).\n\t`-w` `--word-regexp`: Only show matches surrounded by word boundaries.\n\t`-S` `--smart-case`: Searches case insensitively if the pattern is all\n\tlowercase. Search case sensitively otherwise.\n\t`--no-config`: Never read configuration files.\n\t`-x` `--line-regexp`: Only show matches surrounded by line boundaries.\n\n\tUse `:man 1 rg` in your terminal for more details.\n\n\tNote: By default, hidden files and directories are skipped.\n\n\tNote: By default, vcs ignore files including `.gitignore` and\n\t`.ignore` are respected\n\n\tEscape arguments: ~\n\n\t|<f-args>| is used to convert command line arguments to arguments of\n\trg, which means you have to escape space for single argument. For\n\texample, if you want to search `import { Neovim` , you have to use:\n>\n\t:CocSearch import\\ \\{\\ Neovim\n<\n\tThe escape for `{` is required because rg use regexp be default, or:\n>\n\t:CocSearch -F import\\ {\\ Neovim\n<\n\tfor strict match.\n\n\tChange and save: ~\n\n\tRefactor session is started with searched patterns highlighted, just\n\tchange the text and save refactor buffer to make changes across all\n\trelated files. You can make any kind of changes, including add lines\n\tand remove lines.\n\n:CocWatch [extension] \t\t\t\t\t*:CocWatch*\n\n\tWatch loaded [extension] for reload on file change, use <tab> for\n\tcomplete extension id.\n\n:CocOutline \t\t\t\t\t\t*:CocOutline*\n\n\tInvoke |CocAction('showOutline')| by notification.\n\n--------------------------------------------------------------------------------\nAUTOCMD\t\t\t\t\t\t\t*coc-autocmds*\n\n\t\t\t\t\t\t\t*CocLocationsChange*\n\n:autocmd User CocLocationsChange {command}\n\n\tFor building a custom view of locations, set\n\t|g:coc_enable_locationlist| to 0 and use this autocmd with with\n\t|g:coc_jump_locations|\n\n\tFor example, to disable auto preview of location list, use:\n>\n\tlet g:coc_enable_locationlist = 0\n\tautocmd User CocLocationsChange CocList --normal location\n<\n\t\t\t\t\t\t\t*CocNvimInit*\n:autocmd User CocNvimInit {command}\n\n\tTriggered after the coc services have started.\n\n\tIf you want to trigger an action of coc after Vim has started, this\n\tautocmd should be used because coc is always started asynchronously.\n\n\t\t\t\t\t\t\t*CocStatusChange*\n\n:autocmd User CocStatusChange {command}\n\n\tTriggered after |g:coc_status| changed, can be used for refresh\n\tstatusline.\n\n\t\t\t\t\t\t\t*CocDiagnosticChange*\n\n:autocmd User CocDiagnosticChange {command}\n\n\tTriggered after the diagnostic status has changed.\n\n\tCould be used for updating the statusline.\n\n\t\t\t\t\t\t\t*CocJumpPlaceholder*\n\n:autocmd User CocJumpPlaceholder {command}\n\n\tTriggered after cursor jump to a placeholder.\n\n\t\t\t\t\t\t\t*CocOpenFloat*\n\n:autocmd User CocOpenFloat {command}\n\n\tTriggered when a floating window is opened.  The window is not\n\tfocused, use |g:coc_last_float_win| to get window id.\n\n\t\t\t\t\t\t\t*CocOpenFloatPrompt*\n\n:autocmd User CocOpenFloatPrompt {command}\n\n\tTriggered when a floating prompt window is opened (triggered after\n\t|CocOpenFloat|).\n\n\t\t\t\t\t\t\t*CocTerminalOpen*\n:autocmd User CocTerminalOpen {command}\n\n\tTriggered when the terminal is shown, can be used for adjusting the\n\twindow height.\n\n--------------------------------------------------------------------------------\n\nHIGHLIGHTS\t\t\t\t\t\t*coc-highlights*\n\nThe best place to override the highlights is with a |ColorScheme| autocommand:\n>\n\t\" make error texts have a red color\n\tautocmd ColorScheme solarized\n              \\ highlight CocErrorHighlight ctermfg=Red  guifg=#ff0000\n\n<\nUse |:highlight| with group name to check current highlight.\n\nNote: don't use `:hi default` for overwriting the highlights.\n\nNote: user defined highlight commands should appear after the |:colorscheme|\ncommand and use |ColorScheme| autocmd to make sure customized highlights works\nafter color scheme change.\n\nMarkdown related ~\n\n*CocBold* for bold text.\n*CocItalic* for italic text.\n*CocUnderline* for underlined text.\n*CocStrikeThrough* for strikethrough text, like usage of deprecated API.\n*CocMarkdownCode* for inline code in markdown content.\n*CocMarkdownHeader* for markdown header in floating window/popup.\n*CocMarkdownLink* for markdown link text in floating window/popup.\n\nDiagnostics related ~\n\t\t\t\t\t\t\t*coc-highlights-diagnostics*\n\n*CocFadeOut* for faded out text, such as for highlighting unnecessary code.\n*CocErrorSign* for error signs.\n*CocWarningSign* for warning signs.\n*CocInfoSign* for information signs.\n*CocHintSign* for hint signs.\n*CocErrorVirtualText* for error virtual text.\n*CocWarningVirtualText* for warning virtual text.\n*CocInfoVirtualText* for information virtual text.\n*CocHintVirtualText* for hint virtual text.\n*CocErrorHighlight* for error code range.\n*CocWarningHighlight* for warning code range.\n*CocInfoHighlight* for information code range.\n*CocHintHighlight* for hint code range.\n*CocDeprecatedHighlight* for deprecated code range, links to\n|CocStrikeThrough| by default.\n*CocUnusedHighlight* for unnecessary code range, links to |CocFadeOut| by\ndefault.\n*CocErrorLine* line highlight of sign which contains error.\n*CocWarningLine* line highlight of sign which contains warning.\n*CocInfoLine* line highlight of sign which information.\n*CocHintLine* line highlight of sign which contains hint.\n\nHighlight with higher priority would overwrite highlight with lower priority.\nThe priority order:\n\n\t|CocUnusedHighlight| > |CocDeprecatedHighlight| > |CocErrorHighlight|\n\t> |CocWarningHighlight| > |CocInfoHighlight| > |CocHintHighlight|\n\nDocument highlight related ~\n\t\t\t\t\t\t\t*coc-highlights-document*\n\nHighlights used for highlighting same symbols in the buffer at the current\ncursor position.\n\n*CocHighlightText* default symbol highlight.\n*CocHighlightRead* for `Read` kind of document symbol.\n*CocHighlightWrite* for `Write` kind of document symbol.\n\nFloat window/popup related ~\n\t\t\t\t\t\t\t*coc-highlights-float*\n\n*CocFloating* default highlight group of floating windows/popups.\nDefault links to |NormalFloat| on neovim and |Pmenu| on vim.\n*CocFloatBorder* default border highlight group of floating windows/popups.\nDefault links to |FloatBorder| when exists and |CocFloating| when not.\nNote: only foreground color is used for border highlight.\n*CocFloatThumb* thumb highlight of scrollbar.\n*CocFloatSbar* Scrollbar highlight of floating window/popups.\n*CocFloatDividingLine* for dividing lines, links to |NonText| by default.\n*CocFloatActive* for activated text, links to |CocSearch| by default.\n*CocErrorFloat* for error text in floating windows/popups.\n*CocHintFloat* for hint text in floating windows/popups.\n\nInlay hint related ~\n\t\t\t\t\t\t\t*coc-highlights-inlayHint*\n\n*CocInlayHint* for highlight inlay hint virtual text block, default uses\nforeground from |CocHintSign| and background from |SignColumn|\n*CocInlayHintParameter* for parameter kind of inlay hint.\n*CocInlayHintType* for type kind of inlay hint.\n\nNotification window/popup related ~\n\nCocNotification \t\t\t\t\t*CocNotification*\n\n*CocNotificationProgress* for progress line in progress notification.\n*CocNotificationButton* for action buttons in notification window.\n*CocNotificationKey* for function keys which trigger actions in notification\npopups (vim9 only).\n*CocNotificationError* for highlight border of error notification.\n*CocNotificationWarning* for highlight border of warning notification.\n*CocNotificationInfo* for highlight border of info notification.\n\nList related ~\n\t\t\t\t\t\t\t*CocList*\n\n*CocSearch* for matched characters.\n*CocListLine* for current cursor line in list window and preview window.\n*CocListSearch* for matched characters.\n*CocListMode* for mode text in the statusline.\n*CocListPath* for cwd text in the statusline.\n*CocSelectedText* for sign text of selected lines (multiple selection only).\n*CocSelectedLine* for line highlight of selected lines (multiple selection only).\n\nTree view related ~\n\nCocTree \t\t\t\t\t\t*CocTree*\n\n*CocTreeTitle* for title in tree view.\n*CocTreeDescription* for description beside label.\n*CocTreeOpenClose* for open and close icon in tree view.\n*CocTreeSelected* for highlight lines contains selected node.\n\nPopup menu related ~\n\t\t\t\t\t\t\t*CocPum*\n*CocPumSearch* for matched input characters, linked to |CocSearch| by default.\n*CocPumDetail* for highlight label details that follows label (including\npossible detail and description).\n*CocPumMenu* for menu of complete item.\n*CocPumShortcut* for shortcut text of source.\n*CocPumDeprecated* for deprecated label.\n*CocPumVirtualText* for inserted virtual text from word of selected complete\nitem, enabled by |coc-config-suggest-virtualText|.\n\n\t\t\t\t\t\t\t*CocInline*\nInline completion related ~\n\n*CocInlineVirtualText* for virtual text of |coc-inlineCompletion|, defaulting\nto a medium gray.\n*CocInlineAnnotation* for annotation virtual text of |coc-inlineCompletion|,\ndefaulting to \"MoreMsg\".\n\nSymbol icons ~\n\nCocSymbol \t\t\t\t\t\t*CocSymbol*\n\nHighlight groups for symbol icons, including `CompletionItemKind` and\n`SymbolKind` of LSP.  The highlight groups link to related |nvim-treesitter|\nhighlight groups when possible and fallback to builtin highlight groups.\n\n*CocSymbolDefault* linked to |hl-MoreMsg| by default.\n*CocSymbolText*\n*CocSymbolUnit*\n*CocSymbolValue*\n*CocSymbolKeyword*\n*CocSymbolSnippet*\n*CocSymbolColor*\n*CocSymbolReference*\n*CocSymbolFolder*\n*CocSymbolFile*\n*CocSymbolModule*\n*CocSymbolNamespace*\n*CocSymbolPackage*\n*CocSymbolClass*\n*CocSymbolMethod*\n*CocSymbolProperty*\n*CocSymbolField*\n*CocSymbolConstructor*\n*CocSymbolEnum*\n*CocSymbolInterface*\n*CocSymbolFunction*\n*CocSymbolVariable*\n*CocSymbolConstant*\n*CocSymbolString*\n*CocSymbolNumber*\n*CocSymbolBoolean*\n*CocSymbolArray*\n*CocSymbolObject*\n*CocSymbolKey*\n*CocSymbolNull*\n*CocSymbolEnumMember*\n*CocSymbolStruct*\n*CocSymbolEvent*\n*CocSymbolOperator*\n*CocSymbolTypeParameter*\n\nNote: Use configuration |coc-config-suggest-completionItemKindLabels| for customized icon\ncharacters.\n\nSemantic token highlight groups ~\n\t\t\t\t\t\t\t\t*CocSem*\n\nSemantic highlight groups are starts with `CocSem` which link to related\n|nvim-treesitter| highlight groups when possible and fallback to builtin\nhighlight groups, use variable |g:coc_default_semantic_highlight_groups| to\ndisable creation of these highlight groups.\n\nThe highlight groups rules:\n>\n`CocSemType + type` for types\n`CocSemTypeMod + type + modifier` for modifier with type\n<\n\nOnly semantic tokens types and `deprecated` modifier have default\nhighlight groups.\n\nYou need create highlight groups for highlight other modifiers and/or specific\nmodifier with type, for example:\n>\n\t\" Add highlights for declaration modifier\n\thi link CocSemTypeModClassDeclaration ClassDeclaration\n<\nThe modifier highlight groups have higher priority.\n\nOthers ~\n\n*CocDisabled* highlight for disabled items, eg: menu item.\n*CocCodeLens* for virtual text of codeLens.\n*CocCursorRange* for highlight of activated cursors ranges.\n*CocLinkedEditing* for highlight of activated linked editing ranges.\n*CocHoverRange* for range of current hovered symbol.\n*CocMenuSel* for current menu item in menu dialog (should only provide\nbackground color).\n*CocSelectedRange* for highlight ranges of outgoing calls.\n*CocSnippetVisual* for highlight snippet placeholders.\n*CocLink* for highlight document links.\n*CocInputBoxVirtualText* for highlight placeholder of input box.\n\n==============================================================================\nTREE SUPPORT \t\t\t\t\t\t*coc-tree*\n\nTree view is used for render outline and call hierarchy, following features\nare supported:\n\n  • Data update while keep tree node open/close state.\n  • Auto refresh on load error.\n  • Click open/close icon to toggle collapse state.\n  • Click node to invoke default command.\n  • Show tooltip in float window on |CursorHold| when possible.\n  • Key-mappings support |coc-tree-mappings|\n  • Optional multiple selection.\n  • Optional node reveal support.\n  • Optional fuzzy filter support.\n  • Provide API `window.createTreeView` for extensions.\n\nCheck |coc-config-tree| for related configurations.\n\nThe filetype is `'coctree'`, which can be used to overwrite buffer and window\noptions.\n\nUse variable |w:cocViewId| to detect the kind of tree.\n\n--------------------------------------------------------------------------------\n\nTREE KEY MAPPINGS \t\t\t\t\t*coc-tree-mappings*\n\nDefault key-mappings are provided for 'coctree' buffer, which can be changed\nby configuration |coc-config-tree|.\n\n<space> - Select/unselect item, configured by `\"tree.key.toggleSelection\"`.\n<tab> \t- Invoke actions of current item, configured by `\"tree.key.actions\"`.\n<esc>   - Close tree window, configured by `\"tree.key.close\"`.\n<cr>    - Invoke command of current item,  configured by `\"tree.key.invoke\"`.\n<C-o>   - Move cursor to original window.\nf \t- Activate filter, configured by `\"tree.key.activeFilter\"`.\nt \t- Trigger key to toggle expand state of tree node, configured by\n\t`tree.key.toggle`.\nM \t- Collapse all tree node, configured by `\"tree.key.collapseAll\"`.\n\n--------------------------------------------------------------------------------\n\nTREE FILTER \t\t\t\t\t\t*coc-tree-filter*\n\nFilter mode is used for search for specific node by fuzzy filter, invoke the\nkey configured by `\"tree.key.activeFilter\"` to activate filter mode.\n\nNote: some tree views not have filter mode supported.\n\nWhen filter mode is activated, type normal character to insert filter input\nand following special keys are supported:\n\n<bs> \t- Delete last filter character when possible.\n<C-h> \t- Delete last filter character when possible.\n<C-u> \t- Clean up filter text.\n<C-p> \t- Navigate to previous filter text (stored on command invoke).\n<C-n> \t- Navigate to next filter text (stored on command invoke).\n<esc> \t- exit filter mode.\n<C-o> \t- exit filter mode.\n<up> or `\"tree.key.selectPrevious\"` \t- Select previous node.\n<down> or `\"tree.key.selectNext\"` \t- Select next node.\n<cr> or `\"key.key.invoke\"` \t- Invoke command of selected node.\n\n==============================================================================\nLIST SUPPORT\t\t\t\t\t\t*coc-list*\n\nBuilt-in list support to make working with lists of items easier.\n\nThe following features are supported:\n\n  • Insert & normal mode.\n  • Default key-mappings for insert & normal mode.\n  • Customize key-mappings for insert & normal mode.\n  • Commands for reopening & doing actions with a previous list.\n  • Different match modes.\n  • Interactive mode.\n  • Auto preview on cursor move.\n  • Number select support.\n  • Built-in actions for locations.\n  • Parse ANSI code.\n  • Mouse support.\n  • Select actions using <tab>.\n  • Multiple selections using <space> in normal mode.\n  • Select lines by visual selection.\n\nTo enable set filetype of preview window, use |g:coc_list_preview_filetype|.\n\n--------------------------------------------------------------------------------\n\nLIST COMMAND\t\t\t\t\t\t*coc-list-command*\n\n:CocList [{...options}] [{source}] [{...args}]\t\t*:CocList*\n\n\tOpen coc list of {source}, example: >\n\n\t:CocList --normal location\n<\n\tFor current jump locations.\n\n\tFor {options}, see |coc-list-options|.\n\n\tAlso check |coc-config-list| for list configuration.\n\n\t{args} are sent to source during the fetching of list.\n\tPress `?` on normal mode to get supported {args} of current\n\tlist.\n\n\tWhen {source} is empty, the lists source with list of sources is used.\n\n:CocListResume [{name}]\t\t\t\t\t*:CocListResume*\n\n\tReopen last opened list, input and cursor position will be preserved.\n\n:CocListCancel\t\t\t\t\t\t*:CocListCancel*\n\n\tClose list, useful when the list is not the current window.\n\n:CocPrev [{name}]\t\t\t\t\t*:CocPrev*\n\n\tInvoke default action for the previous item in the last {name} list.\n\n\tDoesn't open the list window if it's closed.\n\n:CocNext [{name}]\t\t\t\t\t*:CocNext*\n\n\tInvoke the default action for the next item in the last {name} list.\n\n\tDoesn't open the list window if it's closed.\n\n:CocFirst [{name}]\t\t\t\t\t*:CocFirst*\n\n\tInvoke default action for first item in the last {name} list.\n\n:CocLast [{name}]\t\t\t\t\t*:CocLast*\n\n\tInvoke default action for last item in the last {name} list.\n\n\t\t\t\t\t\t\t*coc-list-options*\nOptions of CocList command ~\n\n--top\n\n\tShow list as top window.\n\n--tab\n\n\tOpen list in new tabpage.\n\n--normal\n\n\tStart list in normal mode, recommended for short list.\n\n--no-sort\n\n\tDisable sort made by fuzzy score or most recently used, use it when\n\tit's already sorted.\n\n--input={input}\n\n\tSpecify the input on session start.\n\n--height={number}\n\n\tSpecify the height of list window, override configuration\n\t|coc-config-list-height|.  No effect when list opened in new tab by\n\t`--tab`.\n\n--strict, -S\n\n\tUse strict matching instead of fuzzy matching.\n\n--regex, -R\n\n\tUse regex matching instead of fuzzy matching.\n\n--ignore-case\n\n\tIgnore case when using strict matching or regex matching.\n\n--number-select, -N\n\n\tType a line number to select an item and invoke the default action on\n\tinsert mode. Type `0` to select the 10th line.\n\n--interactive, -I\n\n\tUse interactive mode, list items would be reloaded on input\n\tchange, filter and sort would be done by list implementation.\n\n\tNote: only works when the list support interactive mode.\n\n\tNote: filtering and sorting would be done by underlying task, which\n\tmeans options including `--strict`, `--no-sort`, `--regex`,\n\t`--ignore-case` would not work at all.\n\n--auto-preview, -A\n\n\tStart a preview for the current item on the visible list.\n\n--no-quit\n\n\tNot quit list session after invoke action.\n\n\tNote: you may need to refresh the list for current state.\n\n--first\n\n\tInvoke default action for first list item on list open.\n\tNothing happens when the list is empty.\n\n--reverse\n\n\tReverse the order of list items shown in the window, the bottom line\n\twould shown the first item.\n\n--------------------------------------------------------------------------------\n\nLIST CONFIGURATION\t\t\t\t\t*coc-list-configuration*\n\nUse `coc-settings.json` for configuration of lists.\n\nConfiguration of list starts with 'list.'.\n\nSee |coc-config-list| or type `list.` in your settings file to get completion\nlist (requires coc-json installed).\n\nFor configuration of a specified list, use section that starts with:\n`list.source.{name}`, where `{name}` is the name of list.\n\nChange default action: ~\n\nIf you want to use `tabe` as default action of symbols list, you can use:\n>\n\t// change default action of symbols\n\t\"list.source.symbols.defaultAction\": \"tabe\"\n<\nin your coc-settings.json\n\nChange default options: ~\n\nTo change |coc-list-options| for source with {name}, use\n`list.source.{name}.defaultOptions` configuration like: >\n\n\t// make symbols list use normal mode and interactive by default\n\t\"list.source.symbols.defaultOptions\": [\"--interactive\", \"--number-select\"],\n<\nNote: some list like symbols only work in interactive mode, you must\ninclude `--interactive` in `defaultOptions`.\n\nNote: default options will not be used when there're options passed\nwith |:CocList| command.\n\nChange default arguments: ~\n\nUse `list.source.{name}.defaultArgs` setting like: >\n\n\t// use regex match for grep source\n\t\"list.source.grep.defaultArgs\": [\"-regex\"],\n\nNote: default arguments used only when arguments from |:CocList| command is\nempty.\n\nNote: Type `?` on normal mode to get supported arguments of current list.\n\n--------------------------------------------------------------------------------\n\nLIST MAPPINGS\t\t\t\t\t\t*coc-list-mappings*\n\nDefault mappings on insert mode:\n\n<Esc>       - Cancel list session.\n<CR>        - Do default action with selected items or current item.\n<C-c>       - Stop loading task.\n<C-v>       - Paste text from system clipboard.\n<C-l>       - Reload list.\n<C-o>       - Change to normal mode.\n<Down>      - Select next line.\n<Up>        - Select previous line.\n<Left>      - Move cursor left.\n<Right>     - Move cursor right.\n<End>       - Move cursor to end of prompt.\n<C-e>       - Same as <End>.\n<Home>      - Move cursor to start of prompt.\n<C-a>       - Same as <Home>.\n<C-f>       - Scroll window forward.\n<C-b>       - Scroll window backward.\n<Backspace> - Remove previous character of cursor.\n<C-h>       - Remove previous character of cursor.\n<C-w>       - Remove previous word of cursor.\n<C-u>       - Remove characters before cursor.\n<C-n>       - Navigate to next input in history.\n<C-p>       - Navigate to previous input in history.\n<C-s>       - Switch matcher for filter items.\n<C-r>       - Insert content from vim's register.\n<Tab>       - Select action.\n\nDefault mappings on normal mode:\n\n<Esc>       - Cancel list session.\n<CR>        - Do default action with selected items or current item.\n<C-c>       - Stop source from fetching more items.\n<C-l>       - Reload list.\n<C-a>       - Mark all visible items selected.\n<C-o>       - Jump to original window on list create.\n<Tab>       - Select action.\n<C-e> \t    - Scroll preview window down.\n<C-y> \t    - Scroll preview window up.\n<Space>     - Toggle selection of current item.\ni,I,o,O,a,A - Change to insert mode.\np           - Preview action.\n:           - Cancel the prompt and enter command mode.\n?           - Show help of current list.\nt           - Do 'tabe' action.\nd           - Do 'drop' action.\ns           - Do 'split' action.\nr           - Do 'refactor' action.\n\nUse |coc-list-mappings-custom| to override default mappings.\n\n\t\t\t\t\t\t\t*coc-list-mappings-custom*\n\nConfigurations `\"list.normalMappings\"` and `\"list.insertMappings\"` are used\nfor customizing the list key-mappings, example: >\n\n\t\"list.insertMappings\": {\n\t\t\"<C-space>\": \"do:previewtoggle\",\n\t\t\"<C-_>\": \"do:help\"\n\t\t\"<C-r>\": \"do:refresh\",\n\t\t\"<C-f>\": \"feedkeys:\\\\<C-f>\",\n\t\t\"<C-b>\": \"feedkeys:\\\\<C-b>\",\n\t\t\"<C-n>\": \"normal:j\",\n\t\t\"<C-p>\": \"normal:k\",\n\t\t\"<C-t>\": \"action:tabe\",\n\t\t\"<C-x>\": \"call:MyFunc\",\n\t\t// paste yanked text to prompt\n\t\t\"<C-v>\": \"eval:@@\"\n\t}\n\t\"list.normalMappings\": {\n\t\t\"c\": \"expr:MyExprFunc\"\n\t\t\"d\": \"action:delete\"\n\t}\n<\nNote: you should only use mappings that start with `<C-` or `<A-` for insert\nmappings.\n\nNote: <Esc> can't be remapped for other actions.\n\nThe mapping expression should be `command:arguments`, available commands:\n\n'do' - special actions provided by coc list, including:\n\t'refresh'       - reload list.\n\t'selectall'     - mark all visible items selected.\n\t'switch'        - switch matcher used for filter items.\n\t'exit'          - exit list session.\n\t'stop'          - stop loading task.\n\t'cancel'        - cancel list session but leave list window open.\n\t'toggle'        - toggle selection of current item.\n\t'togglemode'    - toggle between insert and normal mode.\n\t'previous'      - move cursor to previous item.\n\t'next'          - move cursor to next item.\n\t'defaultaction' - do default action for selected item(s).\n\t'chooseaction'  - choose action for selected item(s).\n\t'jumpback'      - stop prompt and jump back to original window.\n\t'previewtoggle' - toggle preview window, requires preview action exists.\n\t'previewup'     - scroll preview window up.\n\t'previewdown'   - scroll preview window down.\n\t'help'          - show help.\n'prompt' - do prompt action, including:\n\t'previous' - change to previous input in history.\n\t'next'           - change to next input in history.\n\t'start'          - move cursor to start.\n\t'end'            - move cursor to end.\n\t'left'           - move cursor left.\n\t'right'          - move cursor right.\n\t'leftword'       - move cursor left by a word.\n\t'rightword'      - move cursor right by a word.\n\t'deleteforward'  - remove previous character.\n\t'deletebackward' - remove next character.\n\t'removetail'     - remove characters afterwards.\n\t'removeahead'    - remove character ahead.\n\t'removeword'     - remove word before cursor.\n\t'insertregister' - insert content from Vim register.\n\t'paste'          - append text from system clipboard to prompt.\n'eval'      - append text to prompt from result of VimL expression.\n'action'    - execute action of list, use <tab> to find available actions.\n'feedkeys'  - feedkeys to list window, use `\\\\` in JSON to escape special\n              characters.\n'feedkeys!' - feedkeys without remap.\n'normal'    - execute normal command in list window.\n'normal!'   - execute normal command without remap.\n'command'   - execute command.\n'call'      - call Vim function with |coc-list-context| as only argument.\n'expr'      - same as 'call' but expect the function return action name.\n\n\t\t\t\t\t\t\t*coc-list-context*\n\nContext argument contains the following properties:\n\n'name'    - name of the list, example: `'location'`.\n'args'    - arguments of the list.\n'input'   - current input of prompt.\n'winid'   - window id on list activated.\n'bufnr'   - buffer number on list activated.\n'targets' - list of selected targets, checkout |coc-list-target| for properties.\n\n\t\t\t\t\t\t\t*coc-list-target*\n\nTarget contains the following properties:\n\n'label'      - mandatory property that is shown in the buffer.\n'filtertext' - optional filter text used for filtering items.\n'location'   - optional location of item, check out https://bit.ly/2Rtb6Bo\n'data'       - optional additional properties.\n\n--------------------------------------------------------------------------------\n\nLIST SOURCES\t\t\t\t\t\t*coc-list-sources*\n\n--------------------------------------------------------------------------------\n\nlocation\t\t\t\t\t\t*coc-list-location*\n\n\tLast jump locations.\n\n\tActions:\n\n\t- 'preview' : preview location in preview window.\n\t- 'open': open location by use\n\t\t`\"coc.preferences.jumpCommand\"`, default action\n\t- 'tabe': Use |:tabe| to open location.\n\t- 'drop': Use |:drop| to open location.\n\t- 'vsplit': Use |:vsplit| to open location.\n\t- 'split': Use |:split| to open location.\n\t- 'quickfix': Add selected items to Vim's quickfix.\n\n\tTo customize filepath displayed in the list, user could inject\n\tjavascript global function `formatFilepath` which accept filepath and\n\tshould return string. ex:\n>\n\tconst path = require('path')\n\tglobal.formatFilepath = function (file) {\n\t  return file.startsWith('jdt:/') ? path.basename(file) : file\n\t}\n<\n\tsave the file to `~/custom.js` and make coc load it by add\n>\n\tlet g:coc_node_args = ['-r', expand('~/custom.js')]\n<\n\tto your vimrc.  `formatFilepath` works for |coc-list-symbols| as well.\n\nextensions\t\t\t\t\t\t*coc-list-extensions*\n\n\tManage coc.nvim extensions.\n\tFirst column in the list window represent the state of extension:\n\n\t- \"*\" means the extension is activated.\n\t- \"+\" means the extension package json is loaded, but not activated by\n\t  load the javascript file.\n\t- \"-\" means the extension is disabled by 'disable' action.\n\t- \"?\" means the extension is not recognized by coc.nvim.\n\n\tActions:\n\n\t- 'toggle' activate/deactivate extension, default action.\n\t- 'disable' disable extension.\n\t- 'enable' enable extension.\n\t- 'lock' lock/unlock extension to current version.\n\t- 'doc' view extension's README doc.\n\t- 'fix' fix dependencies in terminal buffer.\n\t- 'reload' reload extension.\n\t- 'uninstall' uninstall extension.\n\ndiagnostics\t\t\t\t\t\t*coc-list-diagnostics*\n\n\tAll diagnostics for the workspace.\n\n\tActions:\n\n\t- Same as |coc-list-location|\n\nfolders \t\t\t\t\t\t*coc-list-folders*\n\n\tManage current workspace folders of coc.nvim.\n\n\tActions:\n\n\t- 'edit' change the directory of workspace folder.\n\t- 'delete' remove selected workspace folder.\n\noutline\t\t\t\t\t\t\t*coc-list-outline*\n\n\tSymbols in the current document.\n\n\tActions:\n\n\t- Same as |coc-list-location|\n\nsymbols\t\t\t\t\t\t\t*coc-list-symbols*\n\n\tSearch workspace symbols.\n\n\tActions:\n\n\t- Same as |coc-list-location|\n\nservices\t\t\t\t\t\t*coc-list-services*\n\n\tManage registered services.\n\n\tActions:\n\n\t- 'toggle': toggle service state, default action.\n\ncommands\t\t\t\t\t\t*coc-list-commands*\n\n\tWorkspace commands.\n\n\tActions:\n\n\t- 'run': run selected command, default action.\n\n\tBuiltin commands:\n\t- document.checkBuffer\n\t- document.echoFiletype\n\t- document.jumpToNextSymbol\n\t- document.jumpToPrevSymbol\n\t- document.renameCurrentWord\n\t- document.showIncomingCalls\n\t- document.showOutgoingCalls\n\t- document.toggleCodeLens\n\t- document.toggleColors\n\t- document.toggleInlayHint\n\t- editor.action.colorPresentation\n\t- editor.action.formatDocument\n\t- editor.action.organizeImport\n\t- editor.action.pickColor\n\t- extensions.forceUpdateAll\n\t- extensions.toggleAutoUpdate\n\t- semanticTokens.checkCurrent\n\t- semanticTokens.clearAll\n\t- semanticTokens.clearCurrent\n\t- semanticTokens.inspect\n\t- semanticTokens.refreshCurrent\n\t- workspace.clearWatchman\n\t- workspace.diagnosticRelated\n\t- workspace.inspectEdit\n\t- workspace.undo\n\t- workspace.redo\n\t- workspace.renameCurrentFile\n\t- workspace.showOutput\n\t- workspace.workspaceFolders\n\nlinks\t\t\t\t\t\t\t*coc-list-links*\n\n\tLinks in the current document.\n\n\tActions:\n\n\t- 'open': open the link, default action.\n\t- 'jump': jump to link definition.\n\nsources\t\t\t\t\t\t\t*coc-list-completion-sources*\n\n\tAvailable completion sources.\n\n\tActions:\n\n\t- 'toggle': activate/deactivate source, default action.\n\t- 'refresh': refresh source.\n\t- 'open': open the file where source defined.\n\nlists\t\t\t\t\t\t\t*coc-list-lists*\n\n\tGet available lists.\n\n\tActions:\n\n\t- 'open': open selected list, default action.\n\n==============================================================================\n\nDIALOG SUPPORT\t\t\t\t\t\t*coc-dialog*\n\nDialog is special float window/popup that could response to user actions,\ndialog have close button, border, title (optional), bottom buttons(optional).\n\nNote bottom buttons work different on neovim and vim, on neovim you can\nclick the button since neovim allows focus of window, on vim you have to type\nhighlighted character to trigger button callback.\n\nSee |coc-config-dialog| for available configurations.\n\n--------------------------------------------------------------------------------\n\n\t\t\t\t\t\t\t*coc-dialog-basic*\n\nA basic dialog is create by Javascript API `window.showDialog` , which is just\nsome texts with optional buttons.\n\n--------------------------------------------------------------------------------\n\n\t\t\t\t\t\t\t*coc-dialog-confirm*\n\nA confirm dialog is used for user to confirm an action, normally created by\n`window.showPrompt()` Confirm dialog uses filter feature on vim8 and\n|getchar()| on Neovim.\n\nThe difference is you can operate vim on vim8, but not on neovim.\n\nSupported key-mappings:\n\n<C-c>         - force cancel, return -1 for callback.\n<esc>, n, N   - reject the action, return 0 for callback.\ny,Y           - accept the action, return 1 for callback.\n\n--------------------------------------------------------------------------------\n\n\t\t\t\t\t\t\t*coc-dialog-input*\n\nAn input dialog request user input with optional default value, normally\ncreated by `window.requestInput`, when `\"coc.preferences.promptInput\"` is\nfalse, vim's command line input prompt is used instead.\n\nOn neovim, it uses float window, on vim8, it opens terminal in popup.\n\nSupported key-mappings:\n\n<C-a>               - move cursor to first col.\n<C-e>               - move cursor to last col.\n<esc>               - cancel input, null is received by callback.\n<cr>                - accept current input selection of current item.\n\nQuickPick related (available when created by |coc-dialog-quickpick|).\n\n<C-f>               - scroll forward quickpick list.\n<C-b>               - scroll backward quickpick list.\n<C-j> <C-n> <down>  - move to next item in quickpick list.\n<C-k> <C-p> <up>    - move to previous item in quickpick list.\n<C-t> <C-space>     - toggle selection of current item in quickpick list when\ncanSelectMany is supported.\n\nNote on neovim, other insert mode key-mappings could work.\n\nNote not possible to configure key-mappings on vim8, to customize key-mappings\non neovim, use |CocOpenFloatPrompt| with current buffer.\n\n--------------------------------------------------------------------------------\n\n\t\t\t\t\t\t\t*coc-dialog-quickpick*\n\nA quickpick is a input dialog in the middle with a float window/popup contains\nfiltered list items.\n\nFuzzy filter is used by default.\n\nSee |coc-config-dialog| for available configurations.\n\nSee |coc-dialog-input| for available key-mappings.\n\n--------------------------------------------------------------------------------\n\n\t\t\t\t\t\t\t*coc-dialog-menu*\n\nA menu dialog is used for pick a single item from list of items, extensions\ncould use `window.showMenuPicker` to create menu dialog.\n\nSupported key-mappings:\n\n<Esc> <C-c>          - cancel selection.\n<cr>                 - confirm selection of current item, use\n|dialog.confirmKey| to override.\n1-9                  - select item with 1 based index.\ng                    - move to first item.\nG                    - move to last item.\nj <tab> <down> <C-n> - move to next item.\nk <s-tab> <up> <C-p> - move to previous item.\n<C-f>                - scroll forward.\n<C-b>                - scroll backward.\n\n--------------------------------------------------------------------------------\n\n\t\t\t\t\t\t\t*coc-dialog-picker*\n\nA picker dialog is used for single/multiple selection. On neovim, it's\npossible to toggle selection by mouse click inside the bracket. Extensions\ncould use `window.showPickerDialog` to create picker dialog.\n\nSupported key-mappings:\n\n<Esc> <C-c>          - cancel selection.\n<cr>                 - confirm selection of current item, use\n|dialog.confirmKey| to override.\n<space>              - toggle selection of current item.\ng                    - move to first item.\nG                    - move to last item.\nj <tab> <down> <C-n> - move to next item.\nk <s-tab> <up> <C-p> - move to previous item.\n<C-f>                - scroll forward.\n<C-b>                - scroll backward.\n\nWhen close button is clicked, the selection is canceled with undefined\nresult (same as <esc>).\n\nIt's recommended to use |coc-dialog-quickpick| for filter support.\n\n==============================================================================\n\nNOTIFICATION SUPPORT\t\t\t\t\t*coc-notification*\n\nNotification windows are created at the bottom right of the screen.\n\nNotifications are created by Javascript APIs: `window.showErrorMessage()`,\n`window.showWarningMessage()`, `window.showInformationMessage()`,\n`window.showNotification()` and `window.withProgress()`.\n\nPossible kind of notifications: 'error', 'warning', 'info' and 'progress'.\n\nMessage notifications (not progress) requires\n|coc-preferences-enableMessageDialog| to be `true`.\n\nMessage notifications without actions would be automatically closed after\nmilliseconds specified by |coc-config-notification-timeout|.\n\nUse |coc-config-notification-disabledProgressSources| to disable progress\nnotifications for specific sources.\n\nCustomize notifications: ~\n\n  • Customize icons: |g:coc_notify|\n  • Customize highlights: |CocNotification|\n  • Customize configurations: |coc-config-notification|\n\nRelated functions: ~\n\n  • |coc#notify#close_all()|\n  • |coc#notify#do_action()|\n  • |coc#notify#copy()|\n  • |coc#notify#show_sources()|\n  • |coc#notify#keep()|\n\n==============================================================================\n\nSTATUSLINE SUPPORT\t\t\t\t\t*coc-status*\n\nDiagnostics info and other status info contributed by extensions could be\nshown in statusline.\n\nThe easiest way is add `%{coc#status()}` to your 'statusline' option. Example: >\n\n\tset statusline^=%{coc#status()}\n<\nUse |CocStatusChange| autocmd for automatically refresh statusline: >\n\n\tautocmd User CocStatusChange redrawstatus\n<\n--------------------------------------------------------------------------------\n\n\t\t\t\t\t\t\t*coc-status-manual*\n\nCreate function like:\n>\n\tfunction! StatusDiagnostic() abort\n\t  let info = get(b:, 'coc_diagnostic_info', {})\n\t  if empty(info) | return '' | endif\n\t  let msgs = []\n\t  if get(info, 'error', 0)\n\t    call add(msgs, 'E' . info['error'])\n\t  endif\n\t  if get(info, 'warning', 0)\n\t    call add(msgs, 'W' . info['warning'])\n\t  endif\n\t  return join(msgs, ' ') . ' ' . get(g:, 'coc_status', '')\n\tendfunction\n<\nAdd `%{StatusDiagnostic()}` to your 'statusline' option.\n\n--------------------------------------------------------------------------------\n\n\t\t\t\t\t\t\t*coc-status-airline*\n\nWith vim-airline: https://github.com/vim-airline/vim-airline\n\nSee |airline-coc|\n\n------------------------------------------------------------------------------\n\t\t\t\t\t\t\t*coc-status-lightline*\n\nWith lightline.vim: https://github.com/itchyny/lightline.vim\n\nUse configuration like: >\n\n  let g:lightline = {\n\t\\ 'colorscheme': 'wombat',\n\t\\ 'active': {\n\t\\   'left': [ [ 'mode', 'paste' ],\n\t\\             [ 'cocstatus', 'readonly', 'filename', 'modified' ] ]\n\t\\ },\n\t\\ 'component_function': {\n\t\\   'cocstatus': 'coc#status'\n\t\\ },\n\t\\ }\n\n  \" Use autocmd to force lightline update.\n  autocmd User CocStatusChange,CocDiagnosticChange call lightline#update()\n<\n==============================================================================\nCREATE PLUGINS\t\t\t\t\t\t*coc-plugins*\n\nThere're different ways to extend coc.nvim:\n\n  • Create vim completion sources |coc-api-vim-source|.\n  • Create extensions |coc-api-extension|.\n  • Create single file extensions |coc-api-single-file|.\n  • Debug coc.nvim extension |coc-api-debug|.\n\n==============================================================================\nFAQ\t\t\t\t\t\t\t*coc-faq*\n\n------------------------------------------------------------------------------\n\nCheck out https://github.com/neoclide/coc.nvim/wiki/F.A.Q\n\n==============================================================================\nCHANGELOG\t\t\t\t\t\t*coc-changelog*\n\nSee ./history.md under project root.\n\n==============================================================================\nvim:tw=78:nosta:noet:ts=8:sts=0:ft=help:noet:fen:\n"
  },
  {
    "path": "esbuild.js",
    "content": "const cp = require('child_process')\nlet revision = 'master'\nif (process.env.NODE_ENV !== 'development') {\n  try {\n    let res = cp.execSync(`git log -1 --date=iso --pretty=format:'\"%h\",\"%ad\"'`, {encoding: 'utf8'})\n    revision = res.replaceAll('\"', '').replace(',', ' ')\n  } catch (e) {\n    // ignore\n  }\n}\n\nlet entryPlugin = {\n  name: 'entry',\n  setup(build) {\n    build.onResolve({filter: /^index\\.js$/}, args => {\n      return {\n        path: args.path,\n        namespace: 'entry-ns'\n      }\n    })\n    build.onLoad({filter: /.*/, namespace: 'entry-ns'}, () => {\n      let contents = `'use strict'\nif (global.__isMain) {\n  Object.defineProperty(console, 'log', {\n    value() {\n      if (logger) logger.info(...arguments)\n    }\n  })\n  const { createLogger } = require('./src/logger/index')\n  const logger = createLogger('server')\n  process.on('uncaughtException', function(err) {\n    let msg = 'Uncaught exception: ' + err.message\n    console.error(msg)\n    logger.error('uncaughtException', err.stack)\n  })\n  process.on('unhandledRejection', function(reason, p) {\n    if (reason instanceof Error) {\n      if (typeof reason.code === 'number') {\n        let msg = 'Unhandled response error ' + reason.code + ' from language server: ' + reason.message\n        if (reason.data != null) {\n          console.error(msg, reason.data)\n        } else {\n          console.error(msg)\n        }\n      } else {\n        console.error('UnhandledRejection: ' + reason.message + '\\\\n' + reason.stack)\n      }\n    } else {\n      console.error('UnhandledRejection: ' + reason)\n    }\n    logger.error('unhandledRejection ', p, reason)\n  })\n  const attach = require('./src/attach').default\n  attach({ reader: process.stdin, writer: process.stdout })\n} else {\n  const exports = require('./src/index')\n  const logger = require('./src/logger').logger\n  const attach = require('./src/attach').default\n  module.exports = {attach, exports, logger, loadExtension: (filepath, active) => {\n    return exports.extensions.manager.load(filepath, active)\n  }}\n}`\n      return {\n        contents,\n        resolveDir: __dirname\n      }\n    })\n  }\n}\n\nasync function start() {\n  await require('esbuild').build({\n    entryPoints: ['index.js'],\n    bundle: true,\n    sourcemap: process.env.NODE_ENV === 'development',\n    define: {\n      REVISION: '\"' + revision + '\"',\n      ESBUILD: 'true',\n      'process.env.COC_NVIM': '\"1\"',\n      'global.__TEST__': 'false'\n    },\n    mainFields: ['module', 'main'],\n    platform: 'node',\n    treeShaking: true,\n    target: 'node16.18',\n    plugins: [entryPlugin],\n    banner: {\n      js: `\"use strict\";\nglobal.__starttime = Date.now();\nglobal.__isMain = require.main === module;`\n    },\n    outfile: 'build/index.js'\n  })\n}\n\nstart().catch(e => {\n  console.error(e)\n})\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import {defineConfig, globalIgnores} from \"eslint/config\"\nimport jsdoc from \"eslint-plugin-jsdoc\"\nimport jest from \"eslint-plugin-jest\"\nimport typescriptEslint from \"@typescript-eslint/eslint-plugin\"\nimport globals from \"globals\"\nimport tsParser from \"@typescript-eslint/parser\"\nimport path from \"node:path\"\nimport {fileURLToPath} from \"node:url\"\nimport js from \"@eslint/js\"\nimport {FlatCompat} from \"@eslint/eslintrc\"\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = path.dirname(__filename)\nconst compat = new FlatCompat({\n  baseDirectory: __dirname,\n  recommendedConfig: js.configs.recommended,\n  allConfig: js.configs.all\n})\n\nexport default defineConfig([\n  globalIgnores([\"**/node_modules\", \"**/coverage\", \"**/build\", \"**/lib\", \"**/typings\"]),\n  {\n    files: ['**/*.ts'],\n    extends: compat.extends(\n      \"plugin:@typescript-eslint/recommended\",\n      \"plugin:@typescript-eslint/recommended-requiring-type-checking\",\n    ),\n\n    plugins: {\n      jsdoc,\n      jest,\n      \"@typescript-eslint\": typescriptEslint,\n    },\n\n    languageOptions: {\n      globals: {\n        ...globals.node,\n        ...jest.environments.globals.globals,\n      },\n\n      parser: tsParser,\n      ecmaVersion: 5,\n      sourceType: \"module\",\n\n      parserOptions: {\n        project: \"./tsconfig.json\",\n      },\n    },\n\n    settings: {},\n\n    rules: {\n      \"comma-dangle\": [0],\n      \"guard-for-in\": [0],\n      \"no-dupe-class-members\": [0],\n      \"prefer-spread\": [0],\n      \"prefer-rest-params\": [0],\n      \"func-names\": [0],\n      \"require-atomic-updates\": [0],\n      \"no-empty\": \"off\",\n      \"no-console\": \"off\",\n      \"linebreak-style\": [1, \"unix\"],\n      \"no-prototype-builtins\": [0],\n      \"no-unused-vars\": [0],\n      \"no-async-promise-executor\": [0],\n      \"constructor-super\": \"error\",\n      \"for-direction\": [\"error\"],\n      \"getter-return\": [\"error\"],\n      \"no-case-declarations\": [\"error\"],\n      \"no-class-assign\": [\"error\"],\n      \"no-compare-neg-zero\": [\"error\"],\n      \"no-cond-assign\": \"error\",\n      \"no-const-assign\": [\"error\"],\n      \"no-constant-condition\": [\"error\"],\n      \"no-control-regex\": [\"error\"],\n      \"no-debugger\": \"error\",\n      \"no-delete-var\": [\"error\"],\n      \"no-dupe-args\": [\"error\"],\n      \"no-dupe-keys\": [\"error\"],\n      \"no-duplicate-case\": [\"error\"],\n      \"no-empty-character-class\": [\"error\"],\n      \"no-empty-pattern\": [\"error\"],\n      \"no-ex-assign\": [\"error\"],\n      \"no-extra-boolean-cast\": [\"error\"],\n      \"no-extra-semi\": [\"error\"],\n      \"no-fallthrough\": \"off\",\n      \"no-func-assign\": [\"error\"],\n      \"no-global-assign\": [\"error\"],\n      \"no-inner-declarations\": [\"error\"],\n      \"no-invalid-regexp\": [\"error\"],\n      \"no-irregular-whitespace\": \"error\",\n      \"no-misleading-character-class\": [\"error\"],\n      \"no-mixed-spaces-and-tabs\": [\"error\"],\n      \"no-new-symbol\": [\"error\"],\n      \"no-obj-calls\": [\"error\"],\n      \"no-octal\": [\"error\"],\n      \"no-redeclare\": \"error\",\n      \"no-regex-spaces\": [\"error\"],\n      \"no-self-assign\": [\"error\"],\n      \"no-shadow-restricted-names\": [\"error\"],\n      \"no-sparse-arrays\": \"error\",\n      \"no-this-before-super\": [\"error\"],\n      \"no-undef\": [\"off\"],\n      \"no-unexpected-multiline\": [\"error\"],\n      \"no-unreachable\": [\"warn\"],\n      \"no-unsafe-finally\": \"error\",\n      \"no-unsafe-negation\": [\"error\"],\n      \"no-unused-labels\": \"error\",\n      \"no-useless-catch\": [\"error\"],\n      \"no-useless-escape\": [\"error\"],\n      \"no-with\": [\"error\"],\n      \"require-yield\": [\"error\"],\n      \"use-isnan\": \"error\",\n      \"valid-typeof\": \"off\",\n      \"jsdoc/tag-lines\": \"off\",\n      \"@typescript-eslint/no-unnecessary-boolean-literal-compare\": \"off\",\n      \"@typescript-eslint/no-unnecessary-type-assertion\": \"off\",\n      \"@typescript-eslint/prefer-string-starts-ends-with\": \"off\",\n      \"@typescript-eslint/prefer-regexp-exec\": \"off\",\n      \"@typescript-eslint/adjacent-overload-signatures\": \"error\",\n      \"@typescript-eslint/array-type\": \"off\",\n      \"@typescript-eslint/require-await\": \"off\",\n      \"@typescript-eslint/await-thenable\": \"error\",\n      \"@typescript-eslint/ban-types\": \"off\",\n      \"@typescript-eslint/no-unsafe-assignment\": \"off\",\n      \"@typescript-eslint/no-unsafe-member-access\": \"off\",\n      \"@typescript-eslint/no-unsafe-call\": \"off\",\n      \"@typescript-eslint/explicit-module-boundary-types\": \"off\",\n      \"@typescript-eslint/no-unsafe-return\": \"off\",\n      \"@typescript-eslint/no-non-null-assertion\": \"off\",\n      \"@typescript-eslint/restrict-plus-operands\": \"off\",\n      \"@typescript-eslint/restrict-template-expressions\": \"off\",\n      \"@typescript-eslint/consistent-type-assertions\": \"error\",\n      \"@typescript-eslint/consistent-type-definitions\": \"error\",\n\n      \"@typescript-eslint/explicit-member-accessibility\": [\"error\", {\n        accessibility: \"explicit\",\n\n        overrides: {\n          accessors: \"explicit\",\n          constructors: \"off\",\n        },\n      }],\n\n      \"@typescript-eslint/interface-name-prefix\": \"off\",\n      \"@typescript-eslint/member-delimiter-style\": \"off\",\n      \"@typescript-eslint/camelcase\": \"off\",\n      \"@typescript-eslint/member-ordering\": \"off\",\n      \"@typescript-eslint/no-base-to-string\": \"off\",\n      \"@typescript-eslint/no-empty-function\": \"off\",\n      \"@typescript-eslint/no-empty-interface\": \"off\",\n      \"@typescript-eslint/no-explicit-any\": \"off\",\n      \"@typescript-eslint/no-floating-promises\": \"error\",\n      \"@typescript-eslint/no-misused-promises\": \"off\",\n      \"@typescript-eslint/no-for-in-array\": \"error\",\n      \"@typescript-eslint/no-inferrable-types\": \"error\",\n      \"@typescript-eslint/no-misused-new\": \"error\",\n      \"@typescript-eslint/no-namespace\": \"off\",\n      \"@typescript-eslint/no-parameter-properties\": \"off\",\n      \"@typescript-eslint/no-redundant-type-constituents\": \"off\",\n      \"@typescript-eslint/no-unused-vars\": \"off\",\n      \"@typescript-eslint/no-unnecessary-qualifier\": \"error\",\n      \"@typescript-eslint/no-unsafe-enum-comparison\": \"off\",\n      \"@typescript-eslint/no-use-before-define\": \"off\",\n      \"@typescript-eslint/no-unsafe-argument\": \"off\",\n      \"@typescript-eslint/explicit-function-return-type\": \"off\",\n      \"@typescript-eslint/no-var-requires\": \"off\",\n      \"@typescript-eslint/prefer-for-of\": \"off\",\n      \"@typescript-eslint/prefer-function-type\": \"off\",\n      \"@typescript-eslint/prefer-namespace-keyword\": \"error\",\n      \"@typescript-eslint/quotes\": \"off\",\n      \"@typescript-eslint/no-require-imports\": \"off\",\n      \"@/semi\": [\"error\", \"never\"],\n\n      \"@typescript-eslint/triple-slash-reference\": [\"error\", {\n        path: \"always\",\n        types: \"prefer-import\",\n        lib: \"always\",\n      }],\n\n      \"@typescript-eslint/unbound-method\": \"off\",\n      \"@typescript-eslint/unified-signatures\": \"error\",\n      \"arrow-body-style\": \"off\",\n      \"arrow-parens\": [\"error\", \"as-needed\"],\n      camelcase: \"off\",\n      complexity: \"off\",\n      curly: \"off\",\n      \"dot-notation\": \"off\",\n      \"eol-last\": \"off\",\n      eqeqeq: [\"off\", \"always\"],\n\n      \"id-blacklist\": [\n        \"error\",\n        \"any\",\n        \"Number\",\n        \"number\",\n        \"String\",\n        \"string\",\n        \"Boolean\",\n        \"boolean\",\n        \"Undefined\",\n      ],\n\n      \"id-match\": \"error\",\n      \"jsdoc/check-alignment\": \"error\",\n      \"jsdoc/check-indentation\": \"error\",\n      \"jsdoc/tag-lines\": 1,\n      \"max-classes-per-file\": \"off\",\n      \"new-parens\": \"error\",\n      \"no-bitwise\": \"off\",\n      \"no-caller\": \"error\",\n      \"no-eval\": \"error\",\n      \"no-invalid-this\": \"off\",\n      \"no-magic-numbers\": \"off\",\n\n      \"no-multiple-empty-lines\": [\"error\", {\n        max: 1,\n      }],\n\n      \"no-new-wrappers\": \"error\",\n\n      \"no-shadow\": [\"off\", {\n        hoist: \"all\",\n      }],\n\n      \"no-template-curly-in-string\": \"off\",\n      \"no-throw-literal\": \"error\",\n      \"no-trailing-spaces\": \"error\",\n      \"no-undef-init\": \"error\",\n      \"no-underscore-dangle\": \"off\",\n      \"no-unused-expressions\": \"off\",\n      \"no-var\": \"error\",\n      \"no-void\": \"off\",\n      \"object-shorthand\": \"error\",\n      \"one-var\": [\"error\", \"never\"],\n      \"prefer-const\": \"off\",\n      \"prefer-template\": \"off\",\n      \"quote-props\": [\"error\", \"as-needed\"],\n      radix: \"error\",\n\n      \"space-before-function-paren\": [\"error\", {\n        anonymous: \"never\",\n        asyncArrow: \"always\",\n        named: \"never\",\n      }],\n\n      \"spaced-comment\": [\"error\", \"always\", {\n        markers: [\"/\"],\n      }],\n    },\n  }, {\n    files: ['src/__tests__/**/*.ts'],\n    rules: {\n      \"@typescript-eslint/no-unsafe-function-type\": \"off\"\n    }\n  }\n])\n"
  },
  {
    "path": "history.md",
    "content": "# Changelog\n\nNotable changes of coc.nvim:\n\n## 2025-11-22\n\n- Add configurable front end for reporting regular messages to the user, by using\n  'messageReportKind' which can be set to 'echo' or 'notification'.\n\n## 2025-07-30\n\n- Add configurable kind for dialog messages, through the use of the new configuration\n  'messageDialogKind' which can be set to 'menu', 'notification', or 'confirm'.\n\n## 2025-07-18\n\n- Add `extensionDependencies` support, declare dependencies on other extensions: `\"extensionDependencies\": [\"extension-1\", \"extension-2\"]`\n\n## 2025-07-17\n\n- Add notifications history, view with `:CocList notifications`\n\n## 2025-06-11\n\n- LSP 3.18 and latest vscode-languageclient features:\n    - SnippetTextEdit support\n        - Add `SnippetTextEdit` interface and namespace.\n        - The `TextDocumentEdit.edits` array now allows `SnippetTextEdit`.\n        - API `workspace.applyEdits()` now accepts `SnippetTextEdit`.\n        - Introduced `StringValue` to represent snippet strings (kind: 'snippet', value: string).\n    - Inline completion support, see `:h coc-inlineCompletion`\n        - Add `InlineCompletionItem` `InlineCompletionList` interface and namespace.\n        - Add enum `InlineCompletionTriggerKind`.\n        - Add interfaces `InlineCompletionContext` `InlineCompletionItemProvider`.\n        - Add method `languages.registerInlineCompletionItemProvider()`.\n        - Inline completion support of LanguageClient.\n        - Support `LanguageClient.getFeature('textDocument/inlineCompletion')`.\n        - Support inline completion middleware `Middleware.provideInlineCompletionItems`.\n    - Workspace‐edit metadata & applyEdit\n        - Add `metadata` to `ApplyWorkspaceEditParams`.\n        - Add `metadata` parameter to `workspace.applyEdit()`.\n    - Richer `ErrorHandler.error()` and `ErrorHandler.closed()` return types\n        - New interfaces `ErrorHandlerResult` and `CloseHandlerResult` (include\n            `action`, optional `message`, optional `handled` flag)\n        - `error()` and `closed()` may now return these richer results instead of bare enums.\n    - Trace & output‐channel improvements.\n        - Add `traceOutputChannel` to `LanguageClientOptions`.\n        - Middleware can now intercept all requests and notifications via\n            `sendRequest` and `sendNotification`.\n    - Delayed “didOpen” notifications\n        - Option `textSynchronization.delayOpenNotifications` was added to\n            `LanguageClientOptions` so that `didOpen` can wait until a document is\n            actually visible (or until another message is sent).\n    - Text‐document‐content provider support\n        - Registration type workspace/textDocumentContent to support custom‐\n            scheme content providers.\n        - Support `middleware.provideTextDocumentContent` of LanguageClient.\n        - Support `LanguageClient.getFeature('workspace/textDocumentContent')`.\n    - Support `transport` of `Executable` server option.  Transport could be\n      `pipe` and `socket`\n\n## 2025-06-02\n\n- Add function keys support to notification popups on vim9.\n- Use notification dialogs with actions (instead of menu picker) when\n  'enableMessageDialog' is enabled.\n\n## 2025-05-21\n\n- Perform format on save after execute `editor.codeActionsOnSave`, the same as\n  VSCode.\n\n## 2025-05-20\n\n- Add `winid` (current window ID) to `CursorHold` and `CursorHoldI` events handler.\n\n## 2025-05-19\n\n- Add `ModeChanged` event and `mode` property to `events`.\n\n## 2025-05-13\n\n- Change document highlight priority to -1 to avoid override search highlight\n  on vim9.\n- `start_incl` and `end_incl` options works on neovim.\n\n## 2025-05-08\n\n- For terminal created by `coc#ui#open_terminal`, close the terminal window on\n  terminal finish, make the behavior on vim9 the same as nvim.\n- Use lua and vim9 for highlight functions.\n\n## 2025-05-04\n\n- Execute python on snippet resolve, disable snippet synchronize on completion.\n- Change of none primary placeholder would not update placeholders with same\n  index, like UltiSnip does.\n- Add API `snippetManager.insertBufferSnippets()`.\n\n## 2025-05-03\n\n- The performance with popupmenu navigation on vim9 have improved, for some\n  cases, it's more than 10 times faster.\n- Break change: current line is not synchronized after use the pum API like\n  `coc#pum#select()`, see `:h coc#pum#select()`, functions used as expr\n  key-mappings should be not affected.\n- Break change: configuration `suggest.segmentChinese` replaced with\n  `suggest.segmenterLocales`, see `:h coc-config-suggest-segmenterLocales`.\n- Add `CompleteStart` event to `events` module.\n\n## 2025-04-25\n\n- Add `-level` argument support to diagnostics list.\n- Make lines event send before TextChange events on vim9.\n\n## 2025-04-23\n\n- Add configuration `inlayHint.maximumLength`, default to `0`\n\n## 2025-04-21\n\n- Add `WindowVisible` event to `events`.\n- Add `onVisible()` support to `BufferSyncItem`.\n- Improve inlay hint:\n    - Use lua and vim9 for virtual text api.\n    - Add `coc#vtext#set()` for set multiple virtual texts.\n    - Render all inlay hints for the first time.\n    - Use `WindowVisible` event.\n\n## 2025-04-18\n\n- Add `nvim.createAugroup()`, `nvim.createAutocmd()` and `nvim.deleteAutocmd()`.\n- Add `buffer` `once` and `nested` support to `workspace.registerAutocmd()`.\n- Not throw error from autocmd callback, log the error instead.\n- Add configuration `editor.autocmdTimeout`.\n\n## 2025-04-17\n\n- Support `$COC_VIM_CHANNEL_ENABLE` for enable channel log on vim9.\n- Add `nvim.callVim()`, `nvim.evalVim()` and `nvim.exVim()`.\n\n## 2025-04-15\n\n- Support 'title' for configuration `suggest.floatConfig` and `suggest.pumFloatConfig`.\n- Use timer for `CocStatusChange` autocmd to avoid cursor vanish caused by `redraws`.\n- Use vim9 script for api.vim and refactor related functions.\n- Add `coc#compat#call` for call api functions on vim or neovim.\n- Add `special` to interface `KeymapOption` (vim9 only).\n\n## 2025-04-06\n\n- Add `cmd` option to interface `KeymapOption`.\n- Add `KeymapOption` support to `workspace.registerLocalKeymap()`\n\n## 2025-04-04\n\n- Add `right_gravity` property to `VirtualTextOption`.\n\n## 2025-04-03\n\n- Add `disposables` argument to `workspace.registerAutocmd()`\n- Change behavior for failure autocmd request, echo message instead of throw\n  error.\n\n## 2025-04-02\n\n- Add method `window.getVisibleRanges()` to typings.\n- Break change: set `w:cocViewId` to upper case letters, see `:h w:cocViewId`\n\n## 2025-04-01\n\n- Add configuration `workspace.removeEmptyWorkspaceFolder` default to `false`.\n- Add configuration `editor.codeActionsOnSave`, similar to VSCode.\n\n## 2025-03-31\n\n- Change `placeHolder`to `placeholder` for `QuickPickOptions` like VSCode (old\n  option still works).\n- Change interface `DocumentSelector`, could also be `DocumentFilter` or\n  `string`, not only array of them.\n- Add `context.extensionUri` like VSCode.\n- Add `document` property to `DidChangeTextDocumentParams`, like VSCode.\n- Add `before()` and `after()` methods to `LinkedMap`, same as VSCode.\n- Add `onFocus` and `match()` to `DiagnosticPullOptions`.\n- Add `onFocus` to `DiagnosticPullMode` and export `DiagnosticPullMode`\n- Add interface `InlineValuesProvider`, `DiagnosticProvider` to typings.\n- Add missing properties to `LanguageClient` class, including\n  `createDefaultErrorHandler()`, `state` `middleware` `isInDebugMode`\n  `isRunning()` `dispose()` `getFeature()`\n\n## 2025-03-29\n\n- Add `bufnr` to `WinScrolled` event.\n\n## 2025-03-28\n\n- Improve vim9 highlight by vim9 script #5285.\n\n## 2025-03-27\n\n- Reworked snippets for UltiSnips options and actions support, see `:h coc-snippets` and #5282.\n\n## 2025-03-13\n\n- Add `coc.preferences.autoApplySingleQuickfix` configuration\n\n## 2025-03-07\n\n- Support `extensions.recommendations` configuration.\n- Support for UltiSnip options `t` `m` `s`.\n\n## 2025-03-05\n\n- Export method `workspace.fixWin32unixFilepath` for filepath convert.\n- Add commands `document.enableInlayHint` and `document.disableInlayHint`.\n- Refresh popup menu when completing incomplete sources.\n\n## 2025-03-04\n\n- Add VSCode command `workbench.action.openSettingsJson`.\n- Add `workspace.isTrusted` property.\n\n## 2025-03-03\n\n- Add command `workspace.openLocalConfig`.\n- Support vim built with win32unix enabled, including cygwin, git bash, WSL etc.\n\n## 2025-02-24\n\n- Configurations for file system watch, see `:h coc-config-fileSystemWatch`.\n\n## 2025-02-23\n\n- All global properties works with extensions #5222.\n- Return true or false for boolean option on vim (same as neovim).\n- Support completion sources using vim9script module.\n\n## 2025-02-22\n\n- QuickPick works with vim without terminal support.\n\n## 2025-02-21\n\n- To avoid unexpected signature help window close, signature help will be triggered after placeholder jump by default, when autocmd `CocJumpPlaceholder call CocActionAsync('showSignatureHelp')` not exists.\n- Support `global.formatFilepath` function for customize filepath displayed in symbols & location list.\n\n## 2025-02-20\n\nUse `extensions` section for extension related configurations. Deprecated configuration sections: `coc.preferences.extensionUpdateCheck`, `coc.preferences.extensionUpdateUIInTab` and `coc.preferences.silentAutoupdate`.\n\n## 2025-01-03\n\n- Add `diagnostic.displayByVimDiagnostic` configuration, set diagnostics to `vim.diagnostic` on nvim, and prevent coc.nvim's handler to display in virtualText/sign/floating etc.\n\n## 2024-12-10\n\n- Floating window can be set to fixed position, try `diagnostic.floatConfig`\n- `ensureDocument` and `hasProvider` support to accept specified bufnr\n\n## 2024-11-29\n\n- Increase `g:coc_highlight_maximum_count` default to 500 for better performance.\n- Add `uriConverter.code2Protocol` for extensions\n\n## 2024-10-25\n\n- Mention [davidosomething/coc-diagnostics-shim.nvim](https://github.com/davidosomething/coc-diagnostics-shim.nvim) as alternative to ALE for diagnostics display.\n\n## 2024-08-28\n\n- Add configuration `codeLens.display`\n\n## 2024-08-20\n\n- Add `CocAction('removeWorkspaceFolder')`.\n- Expanded the quick pick API in typings\n\n## 2024-08-12\n\n- Added `coc.preferences.formatterExtension` configuration\n\n## 2024-07-04\n\n- Added `NVIM_APPNAME` support\n\n## 2024-06-27\n\n- Added `inlayHint.position` configuration, with `inline` and `eol` options\n\n## 2024-06-20\n\n- Added `coc.preferences.extensionUpdateUIInTab` to open `CocUpdate` UI in tab\n\n## 2024-05-29\n\n- Break change: increase minimum vim/nvim version requirement\n  - vim 9.0.0438\n  - nvim 0.8.0\n\n## 2024-05-14\n\n- Added `suggest.reTriggerAfterIndent` to control re-trigger or not after indent changes\n\n## 2024-05-07\n\n- Allow `CocInstall` to install extension from Github in development mode\n\n## 2024-04-12\n\n- Change scope of codeLens configuration to `language-overridable`\n\n## 2024-03-26\n\n- Added new `--workspace-folder` argument for diagnostics lists\n- Added new `--buffer` argument for diagnostics lists\n\n## 2024-02-28\n\n- Increase `g:coc_highlight_maximum_count` default to 200\n- Break change: semanticTokens highlight groups changed:\n  - `CocSem + type` to `CocSemType + type`\n  - `CocSem + modifier + type` to `CocSemTypeMod + type + modifier`\n\n## 2024-03-06\n\n- add `outline.autoHide` configuration to automatically hide the outline window when an item is clicked\n\n## 2024-02-27\n\n- Add `g:coc_disable_mappings_check` to disable key-mappings checking\n- Add `suggest.chineseSegments` configuration to control whether to divide Chinese sentences into segments or not\n\n## 2023-09-02\n\n- Support `g:coc_list_preview_filetype`.\n\n## 2023-08-31\n\n- Minimal node version changed from 14.14.0 to 16.18.0.\n- Inlay hint support requires neovim >= 0.10.0.\n- Removed configurations:\n  - `inlayHint.subSeparator`\n  - `inlayHint.typeSeparator`\n  - `inlayHint.parameterSeparator`\n\n## 2023-01-30\n\n- Always show `cancellable` progress as notification without check\n  `notification.statusLineProgress`.\n\n## 2023-01-29\n\n- Exclude `source` actions when request code actions with range.\n- Any character can be used for channel name.\n\n## 2023-01-26\n\n- Add escape support to `coc#status()`.\n\n## 2023-01-24\n\n- Add `encoding` and `CancellationToken` support for `runCommand` function.\n\n## 2023-01-23\n\n- Make `vscode.open` command work with file uri.\n- Cancel option for `workspace.registerExprKeymap()`.\n- Support `suggest.filterOnBackspace` configuration.\n\n## 2023-01-22\n\n- `maxRestartCount` configuration for configured language server.\n\n## 2022-12-25\n\n- Create symbol tree from SymbolInformation list.\n\n## 2022-12-23\n\n- Support `URI` as param for API `workspace.jumpTo()`.\n\n## 2022-12-22\n\n- Support popup window for window related APIs.\n\n## 2022-12-21\n\n- When create `CocSem` highlight group, replace invalid character of token types\n  and token modifiers with underline.\n\n## 2022-12-20\n\n- Export `Buffer.setKeymap` and `Buffer.deleteKeymap` with vim and neovim support.\n- Make `workspace.registerLocalKeymap` accept bufnr argument.\n\n## 2022-12-12\n\n- Allow configuration of `window` scoped used by folder configuration file, like\n  VSCode.\n- Add location support for `getHover` action.\n- Use unique id for each tab on vim.\n- Chinese word segmentation for keywords.\n\n## 2022-12-05\n\n- Add `switchConsole` method to `LanguageClient`\n\n## 2022-12-03\n\n- Add configuration `suggest.insertMode`.\n\n## 2022-12-02\n\n- Expand variables for string configuration value.\n\n## 2022-11-30\n\n- File fragment support for `workspace.jumpTo()`.\n- Support `g:coc_open_url_command`.\n- Support `contributes.configuration` from extension as array.\n\n## 2022-11-29\n\n- Add documentations for develop of coc.nvim extensions.\n- Remove unused variable `g:coc_channel_timeout`.\n\n## 2022-11-28\n\n- Placeholder and update value support for `InputBox` and `QuickPick`.\n- `triggerOnly` option property for vim completion source.\n- Export `getExtensionById` from `extensions` module.\n\n## 2022-11-26\n\n- Use CTRL-R expression instead of timer for pum related functions:\n\n  - `coc#pum#insert()`\n  - `coc#pum#one_more()`\n  - `coc#pum#next()`\n  - `coc#pum#prev()`\n  - `coc#pum#stop()`\n  - `coc#pum#cancel()`\n  - `coc#pum#confirm()`\n\n## 2022-11-25\n\n- Avoid view change on list create.\n- Add configurations `links.enable` and `links.highlight`.\n- Use cursorline for list on neovim (to have correct highlight).\n- Fix highlight not work on neovim 0.5.0 by use `luaeval`.\n\n## 2022-11-22\n\n- Add command `document.toggleCodeLens`.\n\n## 2022-11-21\n\n- Add `CocAction('addWorkspaceFolder')`.\n\n## 2022-11-20\n\n- Support code lens feature on vim9.\n- `codeLens.subseparator` default changed to `|`, like VSCode.\n- Add configuration `coc.preferences.enableGFMBreaksInMarkdownDocument`, default to `true`\n- Add key-mappings `<Plug>(coc-codeaction-selected)` and `<Plug>(coc-codeaction-refactor-selected)`.\n\n## 2022-11-19\n\n- Create highlights after VimEnter.\n- Action 'organizeImport' return false instead of throw error when import code\n  action not found.\n\n## 2022-11-18\n\n- Throw error when rpc request error, instead of echo message.\n\n## 2022-11-13\n\n- Plugin emit ready after extensions activated.\n\n## 2022-11-12\n\n- Not cancel completion when request for in complete sources.\n\n## 2022-11-11\n\n- Support filter and display completion items with different start positions.\n- Remove configuration `suggest.fixInsertedWord`, insert word would always\n  be fixed.\n- Configuration `suggest.invalidInsertCharacters` default to line break\n  characters.\n\n## 2022-11-10\n\n- Not reset 'Search' highlight on float window as it could be used.\n- Note remap `<esc>` on float preview window.\n- Add new action `feedkeys!` to list.\n- Add new configuration `list.floatPreview`.\n\n## 2022-11-07\n\n- Add API `CocAction('snippetInsert')` for snippet insert from vim plugin.\n- Snippet support for vim source, snippet item should have `isSnippet` to be\n  `true` and `insertText` to be snippet text, when `on_complete` function exists,\n  the snippet expand should be handled completion source.\n\n## 2022-11-06\n\n- `window.createQuickPick()` API that show QuickPick by default, call `show()`\n- Fix change value property for QuickPick not works.\n\n## 2022-10-30\n\n- Add configuration `colors.enable`, mark `colors.filetypes` deprecated.\n- Add command `document.toggleColors` for toggle colors of current buffer.\n- Changed filter of completion to use code from VSCode.\n- Add configuration `suggest.filterGraceful`\n\n## 2022-10-39\n\n- Add configuration `suggest.enableFloat` back.\n\n## 2022-10-27\n\n- Use `workspace.rootPatterns` replace `coc.preferences.rootPatterns`, old\n  configuration still works when exists.\n- Store configurations with configuration registry.\n\n## 2022-10-25\n\n- Add `--height` support to `CocList`.\n\n## 2022-10-24\n\n- Use builtin static words source for snippet choices.\n- Remove configuration `\"snippet.choicesMenuPicker\"`\n- Remove unused internal functions `coc#complete_indent()` and\n  `coc#_do_complete()`\n\n## 2022-10-21\n\n- Consider utf-16 code unit instead of unicode code point.\n- Add `coc#string#character_index()` `coc#string#byte_index()` and\n  `coc#string#character_length()`.\n\n## 2022-10-20\n\n- Add `coc#pum#one_more()`\n\n## 2022-10-19\n\n- Trigger for trigger sources when no filter results available.\n\n## 2022-10-18\n\n- Change `suggest.maxCompleteItemCount` default to 256.\n\n## 2022-10-17\n\n- Set `g:coc_service_initialized` to `0` before service restart.\n- Show warning when diagnostic jump failed.\n- Use strwidth.wasm module for string display width.\n- Add API `workspace.getDisplayWidth`.\n\n## 2022-10-15\n\n- Add configuration `inlayHint.display`.\n\n## 2022-10-07\n\n- Use `CocFloatActive` for highlight active parameters.\n\n## 2022-09-28\n\n- Limit popupmenu width when exceed screen to &pumwidth, instead of change\n  completion column.\n- Make escape of `${name}` for ultisnip snippets the same behavior as\n  Ultisnip.vim.\n\n## 2022-09-27\n\n- Use fuzzy.wasm for native fuzzy match.\n- Add `binarySearch` and `isFalsyOrEmpty` functions for array.\n- `suggest.localityBonus` works like VSCode, using selection ranges.\n- Add and export `workspace.computeWordRanges`.\n- Rework keywords parse for better performance (parse changed lines only and use\n  yield to reduce iteration).\n\n## 2022-09-12\n\n- All configurations are now scoped #4185\n- No `onDidChangeConfiguration` event fired when workspace folder changed.\n- Deprecated configuration `suggest.detailMaxLength`, use `suggest.labelMaxLength` instead.\n- Deprecated configuration `inlayHint.filetypes`, use `inlayHint.enable` with scoped languages instead.\n- Deprecated configuration `semanticTokens.filetypes`, use `semanticTokens.enable` with scoped languages instead.\n- Use `workspaceFolderValue` instead of `workspaceValue` for `ConfigurationInspect` returned by `WorkspaceConfiguration.inspect()`.\n\n## 2022-09-04\n\n- Add configuration \"snippet.choicesMenuPicker\".\n\n## 2022-09-03\n\n- Send \"WinClosed\" event to node client.\n- Add `onDidFilterStateChange` and `onDidCursorMoved` to `TreeView`.\n- Support `autoPreview` for outline.\n\n## 2022-09-02\n\n- Support `diagnostic.virtualTextFormat`.\n- Add command `workspace.writeHeapSnapshot`.\n\n## 2022-09-01\n\n- Add configuration \"suggest.asciiMatch\"\n- Support `b:coc_force_attach`.\n\n## 2022-08-31\n\n- Add configuration \"suggest.reversePumAboveCursor\".\n- Use `DiagnosticSign*` highlight groups when possible.\n- Use `DiagnosticUnderline*` highlight groups when possible.\n\n## 2022-08-30\n\n- Export `LineBuilder` class.\n\n## 2022-08-29\n\n- Fix semanticTokens highlights unexpected cleared\n- Fix range of `doQuickfix` action.\n- Check reverse of `CocFloating`, use `border` and `Normal` highlight when reversed.\n- Make `CocInlayHint` use background of `SignColumn`.\n- Add command `document.toggleInlayHint`.\n\n## 2022-08-28\n\n- Make `CocMenuSel` use background of `PmenuSel`.\n- Snippet related configuration changed (old configuration still works until next release)\n  - \"coc.preferences.snippetStatusText\" -> \"snippet.statusText\"\n  - \"coc.preferences.snippetHighlight\" -> \"snippet.highlight\"\n  - \"coc.preferences.nextPlaceholderOnDelete\" -> \"snippet.nextPlaceholderOnDelete\"\n- Add configuration `\"list.smartCase\"`\n- Add configurations for inlay hint\n  - \"inlayHint.refreshOnInsertMode\"\n  - \"inlayHint.enableParameter\"\n  - \"inlayHint.typeSeparator\"\n  - \"inlayHint.parameterSeparator\"\n  - \"inlayHint.subSeparator\"\n\n## 2022-08-27\n\n- Avoid use `EasyMotion#is_active`, use autocmd to disable linting.\n- Show message when call hierarchy provider not found or bad position.\n\n## 2022-08-26\n\n- Remove `completeOpt` from `workspace.env`.\n- Add configuration `\"diagnostic.virtualTextAlign\"`.\n- Add warning when required features not compiled with vim.\n- Not echo error for semanticTokens request (log only).\n- Merge results form providers when possible.\n\n## 2022-08-24\n\n- Virtual text of suggest on vim9.\n- Virtual text of diagnostics on vim9.\n- Add configuration `inlayHint.filetypes`.\n- Inlay hint support on vim9.\n\n## 2022-08-23\n\n- Retry semanticTokens request on server cancel (LSP 3.17).\n- `RelativePattern` support for `workspace.createFileSystemWatcher()`.\n- `relativePatternSupport` for `DidChangeWatchedFiles` (LSP 3.17).\n- Not echo error on `doComplete()`.\n\n## 2022-08-21\n\n- Added `window.createFloatFactory()`, deprecated `FloatFactory` class.\n- Support `labelDetails` field of `CompleteItem`(LSP 3.17).\n- Added `triggerKind` to `CodeActionContext`, export `CodeActionTriggerKind`.\n\n## 2022-08-20\n\n- Support pull diagnostics `:h coc-pullDiagnostics`.\n- Break change: avoid extension overwrite builtin configuration defaults.\n- Change default value of configuration \"diagnostic.format\".\n- 'line' changes to 'currline' for `CocAction('codeAction')`.\n- Check NodeJS version on syntax error.\n\n## 2022-08-10\n\n- Change \"notification.highlightGroup\" default to \"Normal\".\n\n## 2022-08-07\n\n- Add configuration 'suggest.pumFloatConfig'.\n\n## 2022-08-04\n\n- Make diagnostic float window with the same background as CocFloating.\n\n## 2022-08-03\n\n- Add highlight group 'CocFloatingDividingLine'.\n\n## 2022-08-01\n\n- Use custom popup menu, #3862.\n- Use \"first\" instead of \"none\" for configuration `suggest.selection`.\n- Make \"first\" default for `suggest.selection`, like VSCode.\n- Add default blue color for hlgroup `CocMenuSel`.\n\n## 2022-06-14\n\n- Add highlight groups `CocListLine` and `CocListSearch`.\n\n## 2022-06-11\n\n- Add configuration \"notification.disabledProgressSources\"\n- Add \"rounded\" property to \"floatConfig\"\n\n## 2022-06-04\n\n- Add configuration `workspace.openOutputCommand`.\n- Log channel message of vim when `g:node_client_debug` enabled.\n\n## 2022-05-30\n\n- Disable `progressOnInitialization` for language client by default.\n\n## 2022-05-28\n\n- Support `repeat#set` for commands that make changes only.\n\n## 2022-05-24\n\n- Add transition and annotation support for `workspace.applyEdits()`.\n- Add command `workspace.undo` and `workspace.redo`.\n- Remove configuration `coc.preferences.promptWorkspaceEdit`.\n- Remove command `CocAction` and `CocFix`.\n\n## 2022-05-22\n\n- Check for previous position when not able to find completion match.\n- Add `content` support to `window.showMenuPicker()`\n\n## 2022-05-17\n\n- Add `QuickPick` module.\n- Add API `window.showQuickPick()` and `window.createQuickPick()`.\n\n## 2022-05-16\n\n- Add properties `title`, `loading` & `borderhighlight` to `InputBox`\n\n## 2022-05-14\n\n- Add `InputOption` support to `window.requestInput`\n- Add API `window.createInputBox()`.\n\n## 2022-05-13\n\n- Notification support like VSCode <https://github.com/neoclide/coc.nvim/discussions/3813>\n- Add configuration `notification.minProgressWidth`\n- Add configuration `notification.preferMenuPicker`\n- Support `source` in notification windows.\n\n## 2022-05-07\n\n- Show sort method as description in outline view.\n- Add configuration `outline.switchSortKey`, default to `<C-s>`.\n- Add configuration `outline.detailAsDescription`, default to `true`.\n- Add variable `g:coc_max_treeview_width`.\n- Add `position: 'center'` support to `window.showMenuPicker()`\n\n## 2022-05-06\n\n- Use menu for `window.showQuickpick()`.\n- Add configuration `outline.autoWidth`, default to `true`.\n\n## 2022-05-05\n\n- Add key bindings to dialog (created by `window.showDialog()`) on neovim.\n\n## 2022-05-04\n\n- Add `languages.registerInlayHintsProvider()` for inlay hint support.\n\n## 2022-04-25\n\n- Add `LinkedEditing` support\n\n## 2022-04-23\n\n- Add `WinScrolled` event to events.\n\n## 2022-04-20\n\n- Select recent item when input is empty and selection is `recentUsedByPrefix`.\n- Add `coc#snippet#prev()` and `coc#snippet#next()`.\n- Add command `document.checkBuffer`.\n- Add `region` param to `window.diffHighlights()`.\n\n## 2022-04-06\n\n- `workspace.onDidOpenTextDocument` fire `contentChanges` as empty array when\n  document changed with same lines.\n\n## 2022-04-04\n\n- Avoid `CompleteDone` cancel next completion.\n- Avoid indent change on `<C-n>` and `<C-p>` during completion.\n- Support `joinUndo` and `move` with `document.applyEdits()`.\n\n## 2022-04-02\n\n- Change `suggest.triggerCompletionWait` default to `0`.\n- Not trigger completion on `TextChangedP`.\n- Remove configuration `suggest.echodocSupport`.\n- Fix complettion triggered after `<C-e>`.\n\n## 2022-03-31\n\n- Check buffer rename on write.\n\n## 2022-03-30\n\n- Improve words parse performance.\n- Remove configurations `coc.source.around.firstMatch` and `coc.source.buffer.firstMatch`.\n- Fix `coc.source.buffer.ignoreGitignore` not works\n- Check document reload on detach.\n\n## 2022-03-29\n\n- Add menu actions to refactor buffer.\n\n## 2022-03-12\n\n- Avoid use `<sapce><bs>` for cancel completion.\n\n## 2022-03-05\n\n- Make `WinClosed` event fires on `CursorHold` to support vim8.\n- Add events `TabNew` and `TabClose`.\n- Make outline reuse TreeView buffer.\n\n## 2022-03-02\n\n- Add ultisnip option to `snippetManager.insertSnippet()` and\n  `snippetManager.resolveSnippet()`.\n- Support ultisnip regex option: `/a` (ascii option).\n- Support transform replacement of ultisnip, including:\n  - Variable placeholders, `$0`, `$1` etc.\n  - Escape sequence `\\u` `\\l` `\\U` `\\L` `\\E` `\\n` `\\t`\n  - Conditional replacement: `(?no:text:other text)`\n\n## 2022-02-28\n\n- Change `workspace.ignoredFiletypes` default value to `[]`\n\n## 2022-02-24\n\n- Add `window.activeTextEditor`, `window.visibleTextEditors`.\n- Add events `window.onDidChangeActiveTextEditor` `window.onDidChangeVisibleTextEditors`.\n- Add class `RelativePattern`.\n- Add `workspace.findFiles()`.\n\n## 2022-02-23\n\n- Add `workspace.openTextDocument()`\n- Add `Workspace.getRelativePath()`.\n- Add `window.terminals` `window.onDidOpenTerminal` `window.onDidCloseTerminal`\n  and `window.createTerminal`.\n- Add `exitStatus` property to `Terminal`.\n- Support `strictEnv` in `TerminalOptions` on neovim.\n- Deprecated warning for `workspace.createTerminal()`,\n  `workspace.onDidOpenTerminal` and `workspace.onDidCloseTerminal`\n\n## 2022-02-18\n\n- Clear all highlights created by coc.nvim before restart.\n- Support strike through for ansiparse.\n- Support `highlights` for `Documentation` in float window.\n\n## 2022-02-17\n\n- Change workspace configuration throw error when workspace folder can't be\n  resolved.\n- Remove configuration `diagnostic.highlightOffset`.\n\n## 2022-02-15\n\n- Add `events.race`.\n- Change default `suggest.triggerCompletionWait` to 50.\n- Support trigger completion after indent fix.\n\n## 2022-02-14\n\n- Add `pumvisible` property to events.\n\n## 2022-02-10\n\n- Add shortcut support for `window.showMenuPicker()`.\n- Add configuration `dialog.shortcutHighlight` for shortcut highlight.\n- Add configuration `list.menuAction` for choose action by menu picker.\n\n## 2022-02-09\n\n- Add error log to `nvim_error_event`.\n- Add `nvim.lua()` which replace `nvim.executeLua()` to typings.d.ts.\n\n## 2022-02-08\n\n- Support `MenuItem` with disabled property for `window.showMenuPicker`\n- Support show disabled code actions in menu picker.\n\n## 2022-02-07\n\n- Change `:CocLocalConfig` to open configuration file of current workspace\n  folder.\n\n## 2022-02-05\n\n- Support `version` from `textDocument/publishDiagnostics` notification's parameter.\n- Support `codeDescription` of diagnostics by add href to float window.\n- Support `showDocument` request from language server.\n- Support `label` from DocumentSymbolOptions in outline tree.\n- Support extra url use regexp under cursor with `openLink` action.\n- Support `activeParameter` from signature information.\n- Add `trimTrailingWhitespace`, `insertFinalNewline` and `trimFinalNewlines` to FormattingOptions.\n- Add configuration `links.tooltip`, default to `false`.\n\n## 2022-02-04\n\n- Add `--reverse` option to list.\n- Add `<esc>` key-mapping to cancel list in preview window (neovim only).\n\n## 2022-02-02\n\n- Remove `disableWorkspaceFolders` `disableDiagnostics` and `disableCompletion`\n  from language client option.\n- Add configuration `documentHighlight.timeout`.\n- Add `tabPersist` option to `ListAction`.\n- Add `refactor` to `LocationList`\n\n## 2022-01-30\n\n- Add configuration `diagnostics.virtualTextLevel`.\n- Remove configuration `suggest.numberSelect`\n\n## 2022-01-26\n\n- Use `nvim_buf_set_text` when possible to keep extmarks.\n\n## 2022-01-25\n\n- Not trigger completion when filtered is succeed.\n- Move methods `workspace.getSelectedRange` `workspace.selectRange` to `window`\n  module, show deprecated warning when using old methods.\n\n## 2022-01-23\n\n- Support semantic tokens highlights from range provider.\n\n## 2022-01-22\n\n- Not set `gravity` with api `nvim_buf_set_extmark` because highlight bug, wait neovim fix.\n- Support watch later created workspace folders for file events.\n\n## 2022-01-21\n\n- Changed semantic token highlight prefix from `CocSem_` to `CocSem`.\n- Changed semantic token highlight disabled by default, use configuration\n  `semanticTokens.filetypes`\n- Add configuration `semanticTokens.filetypes`.\n- Add configuration `semanticTokens.highlightPriority`.\n- Add configuration `semanticTokens.incrementTypes`.\n- Add configuration `semanticTokens.combinedModifiers`.\n- Add configuration `workspace.ignoredFolders`.\n- Add configuration `workspace.workspaceFolderFallbackCwd`.\n- Add command `semanticTokens.refreshCurrent`.\n- Add command `semanticTokens.inspect`.\n- Add action `inspectSemanticToken`.\n- Rework command `semanticTokens.checkCurrent` to show highlight information.\n- Support semantic tokens highlight group composed with type and modifier.\n\n## 2022-01-20\n\n- Remove deprecated method `workspace.resolveRootFolder`.\n\n## 2022-01-17\n\n- Extend `buffer.updateHighlights` to support `priority`, `combine`, `start_incl` and `end_incl`.\n- Add configuration `diagnostic.highlightPriority`.\n- Add configuration `colors.filetypes` and `colors.highlightPriority`.\n\n## 2022-01-16\n\n- Add configuration `codeLens.position`.\n\n## 2022-01-14\n\n- Add configuration `suggest.selection`.\n\n## 2022-01-13\n\n- `codeLens.separator` now defaults to `\"\"` and will be placed above lines on neovim >= 0.6.0 .\n- Add configurations 'diagnostic.locationlistLevel', 'diagnostic.signLevel', 'diagnostic.messageLevel'.\n\n## 2022-01-12\n\n- Add document.lineAt(), export TextLine class.\n- Upgrade node-client, support nvim.exec().\n- Add documentHighlight.priority configuration.\n\n## 2019-08-18 0.0.74\n\n- feat(cursors): support multiple cursors.\n- feat(extensions): install missing extensions by CocInstall.\n- feat(extensions): add command `extensions.forceUpdateAll`.\n- feat(completion): rework preselect feature.\n- feat(extension): use request for fetch package info.\n- feat(language-client): support disableDynamicRegister configuration.\n- feat(list): paste from vim register support on insert mode #1088.\n- feat(plugin): add CocHasProvider(), close #1087.\n- refactor(outline): not exclude variables and callback.\n- refactor(diagnostic): remove timeout on InsertLeave.\n\n## 2019-07-11 0.0.73\n\n- fix(completion): fix map of number select\n- fix(languages): fix cursor position with snippet\n- fix(completion): fix cursor position with additionalTextEdits\n- fix(position): fix rangeOverlap check #961\n- fix(list): not change guicursor when it's empty\n- fix(list): fix filter not work on loading\n- fix(list): fix custom location list command not work\n- fix(util): highlight & render on vim8\n- fix(handler): fix getCommands\n- fix(handler): not check lastInsert on trigger signatureHelp\n- fix(handler): fix check of signature help trigger\n- fix(language-client): configuration for configured server, closes #930\n- fix(diagnostic): clear diagnostics on filetype change\n- feat(plugin): add download & fetch modules\n- feat(plugin): add highlighter module\n- feat(refactor): add `<Plug>(coc-refactor)` for refactor window\n- feat(extension): use mv module for folder rename\n- feat(extension): support install tagged extension\n- feat(extension): support custom extension root `g:coc_extension_root`\n- feat(handler): close signature float window on ')'\n- feat(list): support `g:coc_quickfix_open_command`\n- feat(list): add eval action\n- feat(list): add --tab list option\n- feat(list): use highlighter module for showHelp\n- feat(terminal): add noa on window jump\n- feat(terminal): support vim8\n- feat(diagnostic): add diagnosticRelated support\n- feat(diagnostic): use text properties on vim8\n- feat(handler): improve signature float window\n\n## 2019-07-01\n\n- feat(plugin): add CocStatusChange autocmd\n- feat(extension): support both npm and yarn.\n- feat(plugin): work on vim 8.0\n- feat(extensions): add lock & doc actions to extension source\n- feat(extension): add proxy auth support (#920)\n- feat(source): not change startcol for file source\n- feat(completion): no numberSelect for number input\n- feat(extensions): Use yarn when npm not found\n- feat(completion): no popup for command line buffer\n- feat(plugin): support only for codeActions action\n- feat(task): debounce stdout\n- feat(plugin): add keymaps for selection ranges\n- feat(plugin): add function textobj\n- feat(list): restore window height, closes #905\n- feat(handler): support signature.floatTimeout\n- feat(configuration): support change of workspace configuration\n- feat(diagnostic): add keymaps for jump error diagnostics\n- feat(plugin): delay start on gvim, fix #659\n\n## 2019-06-15\n\n- feat(plugin): add popup support of vim\n- refactor(completion): improve float support\n- refactor(floating): remove unused code\n- refactor(workspace): replace find-up\n- refactor(handler): improve message for fold method\n- fix(virtualtext): invalid highlight tag (#874)\n- fix(snippets): fix plaintext check\n- fix(highlight): catch error of child_process.spawn\n- fix(highlight): use v:progpath, fix #871\n- fix(floatFactory): escape feedkeys\n- fix(handler): fix getCurrentFunctionSymbol not work\n\n## 2019-06-12\n\n- feat(document): add getVar method\n- fix(util): not break selection on message\n- fix(workspace): fix jumpTo not work on vim8\n- fix(completion): trigger completion with word character\n- refactor(handler): return boolean result\n- perf(workspace): improve jump performance\n- fix(util): Escape filename for jump (#862)\n- refactor(plugin): not show empty hover\n- feat(outline): ignore callback function\n- feat(workspace): support list of events with registerAutocmd\n- fix(workspace): fix jump with tab drop\n- refactor(language-client): change API of selectionRanges\n\n## 2019-06-09\n\n- **Break change** `CocHighlightText` link to `CursorColumn` by default.\n- **Break change** logger folder changed to `$XDG_RUNTIME_DIR` when exists.\n- Add `<PageUp>` and `<PageDown>` support for list, #825.\n- Add function `coc#add_command()`.\n- Add `disableDiagnostics` & `disableCompletion` to languageclient configuration.\n- Add `signature.triggerSignatureWait` configuration.\n- Add vim-repeat support for run command and quickfix.\n- Add preferred `codeAction` support.\n- Add `prompt.paste` action to list.\n- Add title as argument support for `codeAction` action.\n- Add `suggest.floatEnable` configuration.\n- Add `editor.action.organizeImport` command.\n- Add `:CocAction` and `:CocFix` commands.\n- Add `codeActions` action.\n- Fix issues with list.\n\n## 2019-05-30\n\n- **Break change** logger folder changed.\n- Add support of vim-repeat for `<Plug>` keymaps.\n- Add `CocRegistNotification()` function.\n- Add argument to rename action.\n- Add `suggest.disableMenuShortcut` configuration.\n- Add glob support for root patterns.\n- Add `<esc>` keymap to list window.\n- Add shortcut in sources list.\n- Add `list.previewSplitRight` configuration.\n- Add `triggerOnly` property to source.\n- Add warning for duplicate extension.\n- Bug fixes.\n\n## 2019-05-07\n\n- **New feature** load extensions from coc-extensions folder.\n- Add `workspace.renameCurrentFile` command.\n- Add `FloatBuffer`, `FloatFactory` and `URI` to exports.\n- Add `resolveItem` support to list.\n- Fix prompt can't work when execute list action.\n- Fix ansiparser for empty color ranges.\n- Fix highlight only work with first 8 items.\n\n## 2019-04-27\n\n- **Break change** vim-node-rpc not required on vim.\n- **Break change** python not required on vim.\n- **Break change** complete items would refreshed after 500ms when not finished.\n- Add `additionalSchemes` for configured language server.\n- Add support for jumpCommand as false.\n- Fix `diagnostic.level` not work.\n\n## 2019-04-09\n\n- **Break change** `--strictMatch` option of list renamed to `--strict`\n- **Break change** `suggest.reloadPumOnInsertChar` support removed.\n- **Break change** no more binary release.\n- **Break change** logic for resolve workspace folder changed.\n- Add `Task` module.\n- Add `getCurrentFunctionSymbol` action.\n- Add `list.source.outline.ctagsFiletypes` setting.\n- Add `suggest.disableMenu` and `suggest.disableMenu` settings.\n- Add `equal` support for complete items.\n- Add support for do action with visual select lines of list.\n- Add expand tilder support for language server command.\n- Add switch matcher support to list.\n- Add select all support to list.\n- Add quickfix action to list.\n- Add `selectionRanges` of LSP.\n- Add load extensions for &rtp support.\n- Add `coc#on_enter()` for formatOnType and add new lines on enter.\n- Improve completion by support trigger completion when pumvisible.\n- Remove document check on `BufWritePre`.\n\n## 2019-03-31\n\n- **Break change** not using vim-node-rpc from npm modules any more.\n- **Break change** rename `<Plug>_` to `<Plug>CocRefresh`.\n- Fix wrong format options send to server.\n- Fix throw error when extension root not created.\n- Fix MarkedString not considered as markdown.\n- Fix echo message on vim exit.\n- Fix error throw on file watch.\n- Fix unexpected update of user configuration.\n\n## 2019-03-28\n\n- Add `workspace.resolveRootFolder`.\n- Add `diagnostic.joinMessageLines` setting.\n- Add `suggest.completionItemKindLabels` setting.\n- Add `memento` support for extension.\n- Add `workspace.getSelectedRange`.\n- Add `Terminal` module.\n- Add command `workbench.action.reloadWindow`.\n- Fix extension not activated by command.\n- Fix broken undo with floating window.\n- Fix document create possible wrong uri & filetype.\n- Improve highlight with floating window.\n\n## 2019-03-24\n\n- **Break change** make number input not trigger completion.\n- **Break change** make none keywords character doesn't filter completion.\n- Add functions for check snippet state.\n- Add setting `diagnostic.checkCurrentLine`.\n- Fix `signature.target` not work.\n- Fix flick of signature window.\n- Fix EPIPE error of node-client.\n- Fix wrong root of FileWatchSysmtem.\n\n## 2019-03-19\n\n- **Break change** signature settings now starts `signature`.\n- **Break change** default request timeout changed to 5s.\n- **Break change** `commands.executeCommand` return promise.\n- Add `coc.preferences.signatureHelpTarget`.\n- Add `diagnostic.maxWindowHeight` & `signature.maxWindowHeight`.\n- Add `diagnostic.enableSign`.\n- Add support for `$COC_NO_PLUGINS`.\n- Add keymaps: `<Plug>(coc-float-hide)` and `<Plug>(coc-float-jump)`.\n- Add `coc.preferences.enableFloatHighlight`.\n- Fix issues with floating window.\n- Fix critical performance issue on diff text.\n- Improve color of `CocHighlightText`.\n- Improve sort of complete items.\n- Improve extension list with version and open action.\n\n## 2019-03-16\n\n- **Break change** change vim config home on windows to '\\$HOME/vimfiles'.\n- Add highlights to float windows.\n- Add CocLocationsAsync().\n- Add support for `b:coc_suggest_disable`.\n- Add support for `b:coc_suggest_blacklist`.\n- Add setting `diagnostic.messageTarget`.\n- Add floating window support for signatures.\n- Fix issues with diagnostic float.\n- Fix info of completion item not shown.\n- Fix CocUpdateSync not work without service start.\n- Fix wrong indent spaces of snippets.\n\n## 2019-03-11\n\n- **Break change** change buffers instead of disk file for `workspace.applyEdits`.\n- **Break change** add config errors to diagnostic list instead of jump locations.\n- **Break change** hack for popup menu flicker is removed, use `suggest.reloadPumOnInsertChar` to enable it.\n- **Break change** use `nvim_select_popupmenu_item` for number select completion.\n- Add floating window for completion items.\n- Add floating window support for diagnostics.\n- Add floating window support for hover documentation.\n- Add `coc#on_enter()` for notify enter pressed.\n- Add setting `coc.preferences.useQuickfixForLocations`.\n- Add support of `g:coc_watch_extensions` for automatic reload extensions.\n- Add command: `editor.action.doCodeAction`.\n- Fix service on restarted on windows after rebuild.\n- Fix config of airline.\n- Fix relative path of watchman.\n- Improve Mru model.\n\n## 2019-03-03\n\n- **Break change** signature change of `workspace.registerKeymap`.\n- **Break change** `<esc>` of CocList can't be remapped any more.\n- **Break change** use `yarnpkg` command instead of `yarn` when possible.\n- **Break change** `noinsert` is removed from `completeopt` when `noselect` is\n  enabled, `<CR>` would break line by default.\n- Add setting `diagnostic.refreshAfterSave`.\n- Add chinese documentation.\n- Add support of multiple line placeholder.\n- Fix edit of nested snippet placeholders.\n- Fix possible infinite create of documents.\n- Fix check for resume completion.\n\n## 2019-02-25\n\n- **Break change** default of `suggest.detailMaxLength` changed to 100.\n- **Break change** option of `workspace.registerKeymap` changed.\n- Add settings: `suggest.detailField`.\n- Add check for autocmd in health check.\n- Add trigger patterns support for complete sources.\n- Add support of `coc-snippets-expand-jump`\n- Add `source` option for completion start.\n- Add `sources.createSource` method.\n\n## 2019-02-22\n\n- **Break change** some configurations have been renamed, checkout #462.\n- **Break change** no longer automatic trigger for CursorHoldI #452.\n- **Break change** add preview option of `completeopt` according to `suggest.enablePreview`.\n- Add statusItem for CocUpdate.\n- Add `-sync` option for `:CocInstall`\n- Add support for floating preview window.\n- Add more module export.\n- Fix check of vim-node-rpc throw error.\n- Fix wrong line for TextEdit of complete item.\n- Fix diagnostics not cleared on service restart.\n\n## 2019-02-17\n\n- **Break change** completion resolve requires CompleteChanged autocmd.\n- **Break change** mapping of space on insert mode of list removed.\n- **Break change** kind of completion item use single letter.\n- Fix snippet not works on GUI vim.\n- Fix cursor vanish on vim by use timer hacks.\n- Fix behavior of list preview window.\n- Fix python check on vim.\n- Fix CocJumpPlaceholder not fired.\n- Fix vscode-open command not work.\n\n## 2019-02-12\n\n- **Break change** function `coc#util#clearmatches` signature changed.\n- Add check for python gtk module.\n- Add check for vim-node-rpc update error.\n- Fix source name of diagnostics.\n- Fix empty buffers created on preview.\n- Fix trigger of `CursorHoldI`.\n\n## 2019-02-11\n\n- **Break change:** internal filetype of settings file changed to jsonc.\n- **Break change:** `coc#util#install` changed to synchronize by default.\n- **Break change:** no document highlight would be added for colored symbol.\n- **Break change:** remove `coc.preferences.openResourceCommand`.\n- Add fallback rename implementation which rename symbols on current buffer.\n- Add command `:CocUpdateSync`.\n- Add `coc.preferences.detailMaxLength` for slice detail on completion menu.\n- Add cancel support for completion.\n- Add `ctags` as fallback of document symbols list.\n- Add default key-mappings for location actions.\n- Add python check on vim.\n- Add `disableSyntaxes` support for completion sources.\n- Add support for change `isProgress` of `StatusBarItem`\n- Add check of coc.nvim version for `CocUpdate`\n- Add `coc.preferences.previewAutoClose`, default true.\n- Add `workspace.add registerAutocmd`.\n- Fix highlight not cleared on vim\n- Fix health check of service state.\n- Fix CursorHoldI not triggered on neovim.\n- Fix sort of list not stable.\n\n## 2019-02-04\n\n- **Break change:** no messages when documentSymbol and workspaceSymbol provider\n  not found.\n- Add support for configure sign in statusline.\n- Add help action for list.\n- Fix parse error on extensions update.\n- Fix wrong uri on windows.\n- Fix cancel list without close ui.\n- Improve startup time by remove jobwait.\n\n## 2019-02-02\n\n- **Break change:** extensions now update automatically, prompt is removed.\n- Add check for extension compatibility.\n- Add transform support for placeholder.\n- Add check for node version.\n- Add error check for list.\n- Add settings: `coc.preferences.diagnostic.virtualTextLines`.\n- Fix preview window not shown.\n- Fix highlight not cleared on vim.\n- Fix highlight commands of list block vim on start.\n- Improve extension load.\n- Improve list experience.\n\n## 2019-01-28\n\n- **Break change:** `coc.preferences.diagnostic.echoMessage` changed to enum.\n- Add mru support for commands and lists list.\n- Add `coc.preferences.diagnostic.refreshOnInsertMode`\n- Add `Mru` module.\n- Improve highlight for lists, support empty `filterLabel`.\n- Fix `findLocations` not work with nest locations.\n- Fix cursor position after apply additionalTextEdits.\n\n## 2019-01-24\n\n- **Break change:** python code for denite support moved to separated repo.\n- **Break change:** Quickfix list no longer used.\n- Add list support.\n- Add configuration: `coc.preferences.diagnostic.virtualText`.\n- Add watch for `&rtp` change.\n- Add support for configure `g:coc_user_config` and `g:coc_global_extensions`\n- Add support for send request to coc on vim start.\n- Add `g:coc_start_at_startup` support.\n- Add configuration: `coc.preferences.invalidInsertCharacters`.\n- Add configuration: `coc.preferences.snippetStatusText`.\n- Add `coc#_insert_key()` for insert keymap.\n- Add `workspace.registerExprKeymap()`.\n- Add detect for `vim-node-rpc` abnormal exist.\n- Add `requireRootPattern` to languageserver configuration.\n- Fix git check, always generate keywords.\n- Fix crash when `righleft` set to 1 on neovim.\n- Fix snippet position could be wrong.\n\n## 2019-01-09\n\n- **Break change:** throw error when languageserver id is invalid.\n- Add watcher for languageserver configuration change.\n- Fix possible invalid package.json.\n- Fix applyEdits not work sometimes.\n- Fix server still started when command search failed.\n- Fix log file not writeable.\n- Improve completion performance.\n\n## 2019-01-03\n\n- **Break change:** using of `g:rooter_patterns` is removed.\n- **Break change:** diagnostics would be updated in insert mode now.\n- Add configuration: `coc.preferences.rootPatterns`\n- Add `TM_SELECTED_TEXT` and `CLIPBOARD` support for snippets.\n- Fix check of latest insert char failed.\n- Fix highlight not cleared sometimes.\n\n## 2019-01-01\n\n- Fix issues with completion.\n\n## 2018-12-31\n\n- **Break change:** created keymaps use rpcrequest instead of rpcnotify.\n- **Break change:** snippets provider is removed, use `coc-snippets` for\n  extension snippets.\n- Add command: `coc.action.insertSnippet`\n- Fix position of snippets.\n- Fix modifier of registered keymaps.\n- Fix completion triggered on complete done.\n- Fix closure function possible conflict.\n- Fix unexpected snippet cancel.\n- Fix document applyEdits, always use current lines.\n- Fix fail of yarn global command.\n- Fix check of changedtick on completion done.\n- Fix line used for textEdit of completion.\n- Fix snippet canceled by `formatOnType`.\n- Fix `CocJumpPlaceholder` not fired\n- Optimize content synchronize.\n\n## 2018-12-27\n\n- **Break change:** no more message on service ready.\n- **Break change:** vim source now registered as extension.\n- **Break change:** complete item sort have reworked.\n- **Break change:** request send to coc would throw when service not ready.\n- Add support for check current state on diagnostic update.\n- Add `env` opinion for registered command languageserver.\n- Add outputChannel for watchman.\n- Add `coc#_select_confirm()` for trigger select and confirm.\n- Add `coc.preferences.numberSelect`.\n- Add priority support for format provider.\n- Add `workspace.watchGlobal` and `workspace.watchOption` methods.\n- Fix cursor disappear on `TextChangedP` with vim.\n- Fix coc process not killed when update on windows.\n- Fix snippet broken on vim.\n- Fix support of startcol of completion result.\n- Fix `labelOffsetSupport` wrong position.\n- Fix flicking on neovim.\n- Fix unicide not considered as iskeyword.\n- Fix watchman client not initialized sometimes.\n- Improve performance for parse iskeyword.\n- Not echo message on vim exit.\n- Not send empty configuration change to languageserver.\n\n## 2018-12-20\n\n- **Break change** configuration for module language server, transport now\n  require specified value.\n- **Break change** new algorithm for score complete items.\n- Add command `workspace.clearWatchman`.\n- Add `quickfixs`, `doCodeAction` and `doQuickfix` actions.\n- Add `g:vim_node_rpc_args` for debug purpose.\n- Add `coc#add_extension()` for specify extensions to install.\n- Fix clients not restarted on CocRestart.\n- Fix `execArgv` and `runtime` not work for node language server.\n- Fix detail of complete item not echoed sometimes.\n- Fix actions missing when registered with same clientId.\n- Fix issues with signature echo.\n- Fix uri is wrong with whitespace.\n- Improve highlight performance with `nvim_call_atomic`.\n\n## 2018-12-17\n\n- **Break change** `vim-node-rpc` now upgrade in background.\n- Add `ignoredRootPaths` to `languageserver` option.\n- Add detect of vim running state.\n- Add `client.vim` for create clients.\n- Fix possible wrong current line of `completeResolve`.\n- Fix snippet not work with `set virtualedit=all`.\n- Fix default timeout to 2000.\n- Fix file mode of log file.\n\n## 2018-12-12\n\n- **Break change** `fixInsertedWord` fix inserted word which ends with word\n  after.\n- **Break change** `onCompleteSelect` is removed.\n- Add `workspace.registerKeymap` for register keymap.\n- Add match score for sort complete items.\n- Fix possible connection lost.\n- Fix priority of diagnostic signs.\n- Fix possible wrong uri.\n- Fix `RevealOutputChannelOn` not default to `never`.\n- Fix possible wrong line used for textEdit of complete item.\n- Fix possible wrong cursor position of snippet after inserted.\n\n## 2018-12-08\n\n- **Break change** default rootPath would be directory of current file, not cwd.\n- **Break change** codeLens feature now disabled by default.\n- **Break change** diagnostic prev/next now loop diagnostics.\n- Add support of neovim highlight namespace.\n- Add support for undo `additionalTextEdits` on neovim\n- Fix configuration resolve could be wrong.\n- Fix word of completion item could be wrong.\n- Fix rootPath could be null.\n- Fix highlight not cleared on restart.\n\n## 2018-12-06\n\n- **Break change** `RevealOutputChannelOn` of language client default to\n  `never`.\n- Fix can't install on windows vim.\n- Fix `displayByAle` not clearing diagnostics.\n- Add check for `vim-node-rpc` update on vim.\n- Add `Resolver` module.\n- Improve apply `WorkspaceEdit`, support `0` as document version and merge\n  edits for same document.\n\n## 2018-12-05\n\n- Add `CocJumpPlaceholder` autocmd.\n- Add `rootPatterns` to `languageserver` config.\n- Add setting: `coc.preferences.hoverTarget`, support use echo.\n- Add setting `coc.preferences.diagnostic.displayByAle` for use ale to display errors.\n- Add setting `coc.preferences.extensionUpdateCheck` for control update check of\n  extensions.\n- Add `coc#config` for set configuration in vim.\n- Fix rootPath not resolved on initialize.\n- Fix possible wrong `tabSize` by use `shiftwidth` option.\n- Fix trigger of `documentColors` request.\n- Fix `vim-node-rpc` service not work on windows vim.\n- Fix `codeLens` not works.\n- Fix highlight of signatureHelp.\n- Fix watchman watching same root multiple times.\n- Fix completion throw undefined error.\n- Fix `open_terminal` not works on vim.\n- Fix possible connection lost by use notification when possible.\n- Fix process not terminated when connection lost.\n- Rework diagnostics with task sequence.\n- Rework configuration with more tests.\n\n## 2018-11-28\n\n- _Break change_ signature help reworked, vim API for echo signature changed.\n- Add `:CocInfo` command.\n- Add trigger for signature help after function expand.\n- Add echo message when provider not found for some actions.\n- Add support for `formatexpr`\n- Add support for locality bonus like VSCode.\n- Add support of `applyAdditionalLEdits` on item selected by `<esc>`\n- Add `coc.preferences.useQuickfixForLocations`\n- Add `coc.preferences.messageLevel`\n- Add support for trigger command which not registered by server.\n- Add `g:coc_denite_quickfix_action`\n- Fix insert unwanted word when trigger `commitCharacter`.\n- Fix rpc request throw on vim.\n- Fix `data` of complete item conflict.\n- Fix code action not work sometime.\n- Fix `coc.preferences.diagnostic.locationlist` not work.\n- Fix `coc.preference.preferCompleteThanJumpPlaceholder`.\n- Fix `workspace.jumpTo` not work sometime.\n- Fix line indent for snippet.\n- Fix trigger of `signatureHelp` and `onTypeFormat`.\n\n## 2018-11-24\n\n- **Break change** sources excluding `around`, `buffer` or `file` are extracted\n  as extensions.\n- **Break change** custom source doesn't exist any more.\n- Add `coc.preferences.preferCompleteThanJumpPlaceholder` to make jump\n  placeholder behavior as confirm completion when possible.\n- Add `CocDiagnosticChange` autocmd for force statusline update.\n- Add `onDidUnloadExtension` event on extension unload.\n- Fix `getDiagnosticsInRange`, consider all interactive ranges.\n- Fix completion throw when `data` on complete item is `string`.\n- Fix `commitCharacters` not works.\n- Fix workspace methods: `renameFile`, `deleteFile` and `resolveRoot`.\n- Fix textEdit of builtin sources not works.\n\n## 2018-11-19\n\n- **Break change** snippet support reworked: support nest snippets, independent\n  session in each buffer and lots of fixes.\n- **Break change** diagnostic list now sort by severity first.\n- Add commands: `:CocUninstall` and `:CocOpenLog`\n- Add cterm color for highlights.\n- Add line highlight support for diagnostic.\n- Add `coc.preferences.fixInsertedWord` to make complete item replace current word.\n- Fix check confirm not works on vim sometimes.\n- Fix check of `vim-node-rpc`.\n- Fix preselect complete item not first sometimes.\n- Improve completion sort result by consider more abort priority and recent\n  selected.\n- Improve colors module, only highlight current buffer and when buffer changed.\n- Improve `doc/coc.txt`\n\n## 2018-11-13\n\n- **Break change** default completion timeout changed to 2s.\n- **Break change** snippet session not canceled on `InsertLeave`, use\n  `<esc>` in normal mode to cancel.\n- Add document color support.\n- Add CocAction 'pickColor' and 'colorPresentation'.\n- Add prompt for install vim-node-rpc module.\n- Add support for `inComplete` completion result.\n- Add status item for snippet session.\n- Add support for fix inserted text of snippet completion item.\n- Fix document highlight not cleared.\n- Fix cancel behavior of snippet.\n- Fix range check of edit on snippet session.\n- Fix check of completion confirm.\n- Fix highlight group 'CocHighlightWrite' not work.\n- Fix command `editor.action.rename` not works.\n- Fix throw error before initialize.\n- Fix `g:coc_node_path` not working.\n- Fix file source throw undefined error.\n- Improve logic of sorting completion items, strict match items comes first.\n\n## 2018-11-07\n\n- **Break change** word source removed from custom sources, enabled for markdown\n  by default.\n- **Break change** ignore sortText when input.length > 3.\n- **Break change** show prompt for install `coc-json` when not found.\n- Fix document content synchronize could be wrong.\n- Fix filetype not converted on completion.\n- Fix complete item possible not resolved.\n- Improve document highlight, no highlight when cursor moved.\n- Improve completion score, use fuzzaldrin-plus replace fuzzaldrin.\n\n## 2018-11-02\n\n- **Break change** no items from snippets source when input is empty.\n- **Break change** `javascript.jsx` would changed to `javascriptreact` as languageId.\n- **Break change** `typescript.tsx` would changed to `typescriptreact` as languageId.\n- Add support for `commitCharacters` and `coc.preferences.acceptSuggestionOnCommitCharacter`.\n- Add setting: `coc.preferences.diagnostic.level`.\n- Add `g:coc_filetype_map` for customize mapping between filetype and languageId.\n- Add `g:coc_node_path` for custom node executable.\n- Add `workspaceFolders` feature to language client.\n- Add `~` to complete item of snippet source.\n- Add `onDidChangeWorkspaceFolder` event\n- Fix `eol` issue by check `eol` option.\n- Fix `workspace.document` could be null.\n- Fix `workspaceFolder` could be null.\n- Fix diagnostic for quickfix buffer.\n- Fix resolve of `coc.preferences.rootPath`\n\n## 2018-10-29\n\n- **Break change** diagnostic reworked, no refresh on insert mode.\n- **Break change** keep `sortText` on filter for better result.\n- **Break change** prefer trigger completion than filter, same as VSCode.\n- **Break change** filetype of document would be first part of `&filetype` split by `.`.\n- **Break change** prefer label as abbr for complete item.\n- Fix creating wrong `textEdit` for snippet.\n- Fix `startcol` of `CompleteResult` not working.\n- Fix `workspaceConfiguration.toJSON` return invalid result.\n- Fix `workspace.readFile` not synchronized with buffer.\n- Fix `workspace.rootPath` not resolved as expected.\n- Fix `CompletionItem` resolved multiple times.\n- Fix check of `latestInsert` on completion.\n- Fix `formatOnType` possible add unnecessary indent.\n- Fix document content synchronized on vim.\n- Fix confirm check of completion for all source.\n- Fix document possible register multiple times.\n- Fix completion always stopped when input is empty.\n- Add warning message when definition not found.\n- Add `redraw` after `g:coc_status` changed.\n- Remove change of `virtualedit` option of snippet.\n- Improved performance of filter completion items.\n\n## 2018-10-25\n\n- Fix `implementation` and `typeDefinition` of language client not working.\n- Fix `diffLines` return wrong range.\n- Fix `setqflist` and `setloclist` not works on vim.\n- Fix snippets and `additionalTextEdits` not works on vim.\n- Fix append lines not works on vim.\n- Fix highlight action not works on vim.\n- Fix null version of `TextDocumentIdentifier` not handled.\n- Add `workspace.registerTextDocumentContentProvider` for handle custom uri.\n- Add `workspace.createStatusBarItem` method.\n\n## 2018-10-21\n\n- **Break change**: `triggerAfterInsertEnter` now respect `minTriggerInputLength`.\n- Add `coc.preferences.minTriggerInputLength`.\n- Add command: `:CocCommand`.\n- Fix `position` of `provideCompletionItems`.\n- Fix content change not trigger after completion.\n- Fix default sorters & matchers of denite sources.\n- Fix `outputChannel` wrong `buftype`.\n- Fix completion not works with `textEdit` add new lines.\n- Fix first item not resolved when `noselect` is disabled\n- Remove using of `diff` module.\n\n## 2018-10-18\n\n- **Break change**: all buffers are created as document.\n- **Break change**: retrieve workspace root on document create.\n- Fix `uri` for all buffer types.\n- Fix bad performance on parse keywords.\n- Fix check of language client state.\n- Fix register of `renameProvider`\n- Fix `CocRequestAsync` not work.\n- Fix `workspace.openResource` error with `wildignore` option.\n- Fix output channel can't shown if hidden.\n- Fix extension activate before document create.\n- Add command `vscode.open` and `editor.action.restart`.\n- Add `workspace.requestInput` method.\n- Add support of `g:rooter_patterns`\n- Add `storagePath` to `ExtensionContext`\n- Add `workspace.env` property.\n- Add support of scoped configuration.\n- Disable buffer highlight on vim.\n\n## 2018-10-14\n\n- **Break change** API: `workspace.resoleModule` only does resolve.\n- **Break change** extension would still be loaded even if current coc version\n  miss match.\n- **Break change** variables are removed from view of `Denite coc-symbols`\n- Fix `workspace.applyEdits`\n- Fix `console.log` throws in extension.\n- Fix invalid `workspace.root` with custom buffer schema.\n- Fix possible crash on neovim 0.3.1 by not attach terminal buffer.\n- Fix jump position not stored when jump to current buffer position.\n- Fix install function not works on vim.\n- Add support for custom uri schema for `workspace.jumpTo` and `workspace.openResource`\n- Add `workspace.findUp` for find up file of current buffer.\n- Add `env` option for custom language server config.\n- Add vim function: `CocRequest` and `CocRequestAsync` for send request to\n  language server in vim.\n- Add `coc.preferences.parseKeywordsLimitLines` and `coc.preferences.hyphenAsKeyword`\n  for buffer parse.\n- Rework completion for performance and accuracy.\n\n## 2018-10-05\n\n- **Break change**, `workspace.onDidChangeConfiguration` emit `ConfigurationChangeEvent` now.\n- Add `position` to function `coc#util#open_terminal`.\n- Improve performance of completion by use vim's filter when possible.\n- Fix service start multiple times.\n- Fix parse of `iskeyword` option, consider `@-@`.\n- Fix completion of snippet: cancel on line change.\n\n## 2018-10-01\n\n- Improved document `didChange` before trigger completion.\n- Add option `coc.preferences.triggerCompletionWait`, default 60.\n- Add watch for `iskeyword` change.\n- Fix snippet jump not works sometime.\n- Fix possible wrong `rootPath` of language server.\n- Fix highlight of highlight action not using terminal colors.\n- Fix detect for insert new line character.\n\n## 2018-09-30\n\n- Add quickfix source of denite and fzf\n- Add option `coc.preferences.rootPath`\n- Add option `revealOutputChannelOn` to language server.\n- Fix jump of placeholder.\n- Fix empty root on language server initialize.\n\n## 2018-09-28\n\n- **Break change**: `coc.preferences.formatOnType` default to `false`.\n- **Break change**: snippet completion disabled in `string` and `comment`.\n- Add support for register local extension.\n- Add title for commands in `Denite coc-command`\n- Fix prompt hidden by echo message.\n- Fix contribute commands not shown in denite interface.\n- Fix parse of `iskeyword`, support character range.\n- Fix `triggerKind` of completion.\n- Fix install extension from url not reloaded.\n\n## 2018-09-27\n\n- **Break change**: `:CocDisable` disabled all events from vim.\n- **Break change**: new snippet implementation.\n  - Support multiple line snippet.\n  - Support VSCode snippet extension.\n  - Support completion of snippets from snippet extension.\n- Add highlight groups for different severity.\n- Add `coc.preferences.formatOnType` option.\n- Add `coc.preferences.snippets.enable` option.\n- Fix snippet not works as `insertText`.\n- Fix echo message with multiple lines.\n- Fix `signatureHelp` with `showcmd` disabled.\n- Fix location list cleared on `:lopen`.\n- Fix diagnostic info not cleared on `:CocDisable`\n- Fix diagnostic info not cleared on buffer unload.\n- Fix buffer highlight not cleared on `highlight` action.\n- Fix format on type not work as expected.\n\n## 2018-09-24\n\n- **Break change**: use `CursorMove` instead of `CursorHold` for diagnostic\n  message.\n- **Break change**: direct move to diagnostic position would show diagnostic\n  message without truncate.\n- **Break change**: snippet would be canceled when mode changed to normal, no\n  mapping of `<esc>` any more.\n- Add format document on `insertLeave` when `onTypeFormat` is supported.\n- Add buffer operations on resource edit.\n- Add `uninstall` action for `Denite coc-extension`.\n- Fix active extension on command not working.\n- Fix delete file from resource edit not works.\n\n## 2018-09-20\n\n- Fix diagnostic check next offset for diagnostics.\n- Add `<Plug>(coc-diagnostic-info)` for show diagnostic message without\n  truncate.\n\n## 2018-09-15\n\n- Fix wrong configuration on update.\n- Fix install command with tag version.\n- Fix using of unsafe `new Buffer`.\n- Add support of trace format & resource operations.\n- Add support of json validation for extension.\n- Add support of format on save by `coc.preferences.formatOnSaveFiletypes`\n\n## 2018-09-10\n\n- Add `Denite coc-extension` for manage extensions.\n- Add actions for manage extension including `toggleExtension` `reloadExtension`\n  `deactivateExtension`\n- Add check for extension update everyday.\n- Fix extensions using same process of coc itself.\n- Fix `configurationSection` should be null if none was specified.\n\n## 2018-09-07\n\n- **Break change**: all extension all separated from core, checkout\n  [Using coc extension](https://github.com/neoclide/coc.nvim/wiki/Using-coc-extensions)\n- Fix `textDocumentSync` option not work when received as object.\n- Fix wrong diagnostic info when using multiple lint servers.\n- Use `CursorHold` for show diagnostic message.\n- Add option `coc.preferences.enableMessage` to disable showing of diagnostic\n  message.\n- Add new events module for receive vim events.\n- Add support for `prepareRename`.\n- Add support for `CodeActionOptions`\n\n## 2018-08-30\n\n- Fix wrong `triggerKind` from VSCode.\n- Add `<Plug>(coc-openlink)` for open link.\n- Add `typescript.jsx` as valid typescript type.\n\n## 2018-08-23\n\n- Fix sometimes client status invalid.\n- Add multiply provider support for all features.\n- Add `documentLink` support\n- Add `documentHighlight` support\n- Add `foldingRange` support\n- Add support of `documentSelector` same as VSCode\n\n## 2018-08-21\n\n- Fix diagnostic and arguments of tsserver.\n- Add `keepfocus` option for `open_terminal`.\n- Improve error catch of autocmds.\n- Add `onTypeFormat` feature for language server\n- Add `onTypeFormat` support for tsserver.\n- Refactor and more tests of workspace.\n- Fix `window/showMessageRequest` request.\n- Use `callAsync` for async request to vim.\n- Add `CocActionAsync` function send async request to server.\n\n## 2018-08-17\n\n- Fix exists terminal buffer not watched.\n- Fix buffer not attached after `edit!`.\n- Fix clean diagnostics of `tsserver.watchBuild` command.\n- Fix refresh of buffer.\n- Fix document not found on `BufEnter`.\n\n  Use `rpcrequest` for `BufCreate`\n\n- Fix no permission of log file.\n\n  Disable create log file for root user.\n\n- Add more command for tsserver:\n\n  - `tsserver.reloadProjects`\n  - `tsserver.openTsServerLog`\n  - `tsserver.goToProjectConfig`\n  - `tsserver.restart`\n\n- Add test for workspace.\n\n## 2018-08-16\n\n- Improved for tsserver:\n\n  - Add `watchBuild` command for build current project with watch in terminal.\n  - Support of untitled buffer\n  - Support `projectRootPath`\n\n- Fix detach error of document.\n- Fix trigger characters not works for some source.\n- Fix document possible not sync before save.\n- Fix denite errors with 0 as result.\n- Fix wrong arguments of tsserver refactor command.\n- Use `drop` for workspace `openResource`.\n- Add clear coc signs on `:CocRestart`.\n- **Break change** all buffer types except `nofile` `help` and `quickfix` are\n  watched for changes.\n\n## 2018-08-15\n\n- Fix filter of completion items on fast input.\n- Fix sometimes fails of include & neosnippet source.\n- Fix sometimes fails to find global modules.\n- Improve complete source initialization.\n\n  - Always respect change of configuration.\n\n- Add ability to start standalone coc service for debugging.\n\n  - Use `NVIM_LISTEN_ADDRESS=/tmp/nvim nvim` to start\n    neovim.\n  - Start coc server by command like `node bin/server.js`\n\n- Add ability to recover from unload buffer.\n\n  Sometimes `bufReadPost` `BufEnter` could be not be fired on buffer create,\n  check buffer on `CursorHold` and `TextChanged` to fix this issue.\n\n- Add tsserver features: `tsserver.formatOnSave` and `tsserver.organizeImportOnSave`\n\n  Both default to false.\n\n- Add tests for completion sources.\n\n## 2018-08-14\n\n- Fix remote source not working.\n- Fix sort of completion items.\n- Fix EPIPE error from net module.\n- Add `tslint.lintProject` command.\n- Add config `coc.preferences.maxCompleteItemCount`.\n- Add `g:coc_auto_copen`, default to `1`.\n\n## 2018-08-12\n\n- **Break change** `:CocRefresh` replaced with `call CocAction('refreshSource')`.\n- Add support filetype change of buffer.\n- Add basic test for completion.\n- Improve loading speed, use child process to initialize vim sources.\n- Improve install.sh, install node when it doesn't exist.\n- Improve interface of workspace.\n- Fix loading of configuration content.\n\n## 2018-08-11\n\n- Fix configuration content not saved on change.\n- Fix thrown error on watchman not found.\n- Fix incompatible options of `child_process`.\n- Fix location list for diagnostics.\n\n  - Reset on `BufWinEnter`.\n  - Available for all windows of single buffer.\n  - Use replace on change for coc location list.\n  - Add debounce.\n\n- Fix signature help behaviour, truncate messages to not overlap.\n- Reworks sources use async import.\n\n## 2018-08-10\n\n- Fix dispose for all modules.\n- Add support for multiple `addWillSaveUntilListener`.\n- Fix `startcol` for json server.\n- Add support filetype `javascriptreact` for tsserver.\n\n## 2018-08-09\n\n- Add `coc#util#install` for installation.\n- Add `install.cmd` for windows.\n\n## 2018-08-08\n\n- Improved location list for diagnostics.\n- Add `internal` option to command.\n\n  Commands registered by server are internal.\n\n- Add support for multiple save wait until requests.\n\n## 2018-08-07\n\n- Add `forceFullSync` to language server option.\n\n## 2018-08-05\n\n- Improve eslint extension to use workspaceFolder.\n- Fix watchman not works with multiple roots.\n- Add feature: dynamic root support for workspace.\n- **Break change** output channel of watchman is removed.\n\n## 2018-08-04\n\n- Fix order of document symbols.\n- Fix completion snippet with `$variable`.\n- Add feature: expand snippet on confirm.\n- Add feature: `<Plug>(coc-complete-custom)` for complete custom sources.\n\n  Default customs sources: `emoji`, `include` and `word`\n\n- **Break change** `emoji` `include` used for all filetypes by default.\n\n## 2018-08-03\n\n- Add command `:CocErrors` for debug.\n- Support `DocumentSymbol` for 'textDocument/documentSymbol'\n\n## 2018-08-02\n\n- Fix error of language client with unsupported schema.\n\n  No document event fired for unsupported schema (eg: fugitive://)\n\n- Fix update empty configuration not works.\n\n## 2018-07-31\n\n- Improve file source triggered with dirname started path.\n\n## 2018-07-30\n\n- Fix source ultisnip not working.\n- Fix custom language client with command not working.\n- Fix wrong arguments passed to `runCommand` function.\n- Improve module install, add `sudo` for `npm install` on Linux.\n- Improve completion on backspace.\n  - Completion is resumed when search is empty.\n  - Completion is triggered when user try to fix search.\n\n## 2018-07-29\n\n- **Break change** all servers are decoupled from coc.nvim\n\n  A prompt for download is shown when server not found.\n\n- **Break change** `vim-node-rpc` decoupled from coc.nvim\n\n  A prompt would be shown to help user install vim-node-rpc in vim.\n\n- Add command `CocConfig`\n\n## 2018-07-28\n\n- Fix uncaught exception error on windows.\n- Use plugin root for assets resolve.\n- Fix emoji source not triggered by `:`.\n- Improve file source to recognize `~` as user home.\n\n## 2018-07-27\n\n- Prompt user for download server module with big extension like `vetur` and `wxml-langserver`\n- **Break change**, section of settings changed: `cssserver.[languageId]` moved to `[languageId]`\n\n  For example: `cssserver.css` section is moved to `css` section.\n\n  This makes coc settings of css languages the same as VSCode.\n\n- **Break change**, `stylelint` extension is disabled by default, add\n\n  ```json\n  \"stylelint.enable\": true,\n  ```\n\n  to your `coc-settings.json` to enable it.\n\n  User will be prompted to download server if `stylelint-langserver` is not\n  installed globally.\n\n- **Break change**, `triggerAfterInsertEnter` is always `true`, add\n\n  ```json\n  \"coc.preferences.triggerAfterInsertEnter\": false,\n  ```\n\n  to your `coc-settings.json` to disable it.\n\n- **Break change**, when `autoTrigger` is `always` completion would be triggered\n  after completion item select.\n\n## 2018-07-24\n\n- better statusline integration with airline and lightline.\n\n## 2018-07-23\n\n- Coc service start much faster.\n- Add vim-node-rpc module.\n- **Break change** global function `CocAutocmd` and `CocResult` are removed.\n- Support Vue with vetur\n\n## 2018-07-21\n\n- Fix issue with `completeopt`.\n- Add source `neosnippet`.\n- Add source `gocode`.\n\n## 2018-07-20\n\n- Add documentation for language server debug.\n- Rework register of functions, avoid undefined function.\n\n## 2018-07-19\n\n- Fix error of `isFile` check.\n- Ignore undefined function on service start.\n\n## 2018-07-17\n\n- Add `coc.preference.jumpCommand` to settings.\n- Make coc service standalone.\n\n## 2018-07-16\n\n- Support arguments for `runCommand` action.\n- Add coc command `workspace.showOutput`.\n- Support output channel for language server.\n- Support `[extension].trace.server` setting for trace server communication.\n\n## 2018-07-15\n\n- Support location list for diagnostic.\n- Add tsserver project errors command.\n\n## 2018-07-14\n\n- Add support for `preselect` of complete item.\n- Add support for socket language server configuration.\n- Fix configured language server doesn't work.\n- Add `workspace.diffDocument` coc command.\n- Fix buffer sometimes not attached.\n- Improve completion of JSON extension.\n\n## 2018-07-13\n\n- **Break change:** `diagnostic` in setting.json changed to `diagnostic`.\n- Fix clearHighlight arguments.\n- Add eslint extension <https://github.com/Microsoft/vscode-eslint>.\n- Fix snippet break with line have \\$variable.\n- Use jsonc-parser replace json5.\n- Add `data/schema.json` for coc-settings.json.\n\n## 2018-07-12\n\n- Fix restart of tsserver not working.\n- Fix edit of current buffer change jumplist by using `:keepjumps`.\n"
  },
  {
    "path": "jest.js",
    "content": "const path = require('path')\nconst os = require('os')\nconst fs = require('fs')\n\nconst tmpdir = process.env.TMPDIR = path.join(os.tmpdir(), 'coc-test')\nprocess.on('uncaughtException', err => {\n  let msg = 'Uncaught exception: ' + err.stack\n  console.error(msg)\n})\n\nprocess.on('exit', () => {\n  fs.rmdirSync(process.env.TMPDIR, {recursive: true, force: true})\n})\n\nmodule.exports = async () => {\n  let dataHome = path.join(tmpdir, process.pid.toString())\n  fs.mkdirSync(dataHome, {recursive: true})\n  process.env.VIMRUNTIME = ''\n  process.env.NODE_ENV = 'test'\n  process.env.COC_NVIM = '1'\n  process.env.COC_DATA_HOME = dataHome\n  process.env.COC_VIMCONFIG = path.join(__dirname, 'src/__tests__')\n}\n"
  },
  {
    "path": "lua/coc/diagnostic.lua",
    "content": "local M = {}\n\nlocal ns = vim.api.nvim_create_namespace('coc-diagnostic')\n\nfunction M.refresh()\n  vim.diagnostic.reset(ns)\n\n  for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do\n    if vim.api.nvim_buf_is_loaded(bufnr) then\n      local ok, items = pcall(vim.api.nvim_buf_get_var, bufnr, 'coc_diagnostic_map')\n      if ok and type(items) == 'table' and vim.tbl_count(items) >= 0 then\n        local diagnostics = {}\n        for _, d in ipairs(items) do\n          diagnostics[#diagnostics + 1] = {\n            bufnr = bufnr,\n            lnum = d.location.range.start.line,\n            end_lnum = d.location.range['end'].line,\n            col = d.location.range.start.character,\n            end_col = d.location.range['end'].character,\n            severity = d.level,\n            message = d.message,\n            source = d.source,\n            code = d.code,\n            namespace = ns,\n          }\n        end\n        vim.diagnostic.set(ns, bufnr, diagnostics)\n      end\n    end\n  end\nend\n\nreturn M\n"
  },
  {
    "path": "lua/coc/highlight.lua",
    "content": "local util = require('coc.util')\nlocal api = vim.api\n\nlocal M = {}\n\nlocal default_priority = 1024\nlocal priorities = {\n  CocListSearch = 2048,\n  CocSearch = 2048\n}\nlocal diagnostic_hlgroups = {\n  CocUnusedHighlight = 0,\n  CocDeprecatedHighlight = 1,\n  CocHintHighlight = 2,\n  CocInfoHighlight = 3,\n  CocWarningHighlight = 4,\n  CocErrorHighlight = 5\n}\nlocal maxCount = vim.g.coc_highlight_maximum_count or 500\nlocal n10 = vim.fn.has('nvim-0.10') == 1 and true or false\n-- 16 ms\nlocal maxTimePerBatchMs = 16\n\nlocal function is_null(value)\n  return value == nil or value == vim.NIL\nend\n\nlocal function is_enabled(value)\n  return value == 1 or value == true\nend\n\n-- 0 based character index to 0 based byte index\nlocal function byte_index(text, character)\n  if character == 0 then\n    return 0\n  end\n  local list = vim.str_utf_pos(text)\n  local bytes = list[character + 1]\n  if bytes == nil then\n    return #text\n  end\n  return bytes - 1\nend\n\nlocal function create_namespace(key)\n  if type(key) == 'number' then\n    if key == -1 then\n      return api.nvim_create_namespace('')\n    end\n    return key\n  end\n  if type(key) ~= 'string' then\n    error('Expect number or string for namespace key, got ' .. type(key))\n  end\n  return api.nvim_create_namespace('coc-' .. key)\nend\n\nlocal function get_priority(hl_group, priority)\n  if priorities[hl_group] ~= nil then\n    return priorities[hl_group]\n  end\n  if type(priority) ~= 'number' then\n    return default_priority\n  end\n  local n = diagnostic_hlgroups[hl_group]\n  if n ~= nil then\n    return priority + n\n  end\n  return priority\nend\n\nlocal function convert_item(item)\n  if #item == 0 then\n    local combine = 0\n    if item.combine or priorities[item.hlGroup] ~= nil then\n      combine = 1\n    end\n    return {item.hlGroup, item.lnum, item.colStart, item.colEnd, combine, item.start_incl, item.end_incl}\n  end\n  return item\nend\n\nlocal function getValuesWithPrefix(dict, prefix)\n  local result = {}\n  local prefixLength = #prefix\n  for key, value in pairs(dict) do\n    if type(key) == 'string' and string.sub(key, 1, prefixLength) == prefix then\n      table.insert(result, value)\n    end\n  end\n  return result\nend\n\nlocal function addHighlights(bufnr, ns, highlights, priority)\n  for _, items in ipairs(highlights) do\n    local converted = convert_item(items)\n    local hlGroup = converted[1]\n    local line = converted[2]\n    local startCol = converted[3]\n    local endCol = converted[4]\n    local hlMode = is_enabled(converted[5]) and 'combine' or 'replace'\n    if endCol == -1 then\n      local text = vim.fn.getbufline(bufnr, line + 1)[1] or ''\n      endCol = #text\n    end\n    priority = get_priority(hlGroup, priority)\n    -- Error: col value outside range\n    pcall(api.nvim_buf_set_extmark, bufnr, ns, line, startCol, {\n          end_col = endCol,\n          hl_group = hlGroup,\n          hl_mode = hlMode,\n          right_gravity = true,\n          end_right_gravity = is_enabled(converted[7]),\n          priority = math.min(priority, 4096)\n    })\n  end\nend\n\nlocal function addHighlightTimer(bufnr, ns, highlights, priority, changedtick)\n  if not api.nvim_buf_is_loaded(bufnr) then\n    return nil\n  end\n  if api.nvim_buf_get_var(bufnr, 'changedtick') ~= changedtick then\n    return nil\n  end\n  local total = #highlights\n  local i = 1\n  local start = util.getCurrentTime()\n  local next = {}\n  while i <= total do\n    local end_idx = math.min(i + maxCount - 1, total)\n    local hls = vim.list_slice(highlights, i, end_idx)\n    addHighlights(bufnr, ns, hls, priority)\n    local duration = util.getCurrentTime() - start\n    if duration > maxTimePerBatchMs and end_idx < total then\n      next = vim.list_slice(highlights, end_idx + 1, total)\n      break\n    end\n    i = end_idx + 1\n  end\n  if #next > 0 then\n    vim.defer_fn(function()\n      addHighlightTimer(bufnr, ns, next, priority, changedtick)\n    end, 10)\n  end\nend\n\n\n-- Get single line extmarks\n-- @param bufnr - buffer number\n-- @param key - namespace id or key string.\n-- @param start_line - start line index, default to 0.\n-- @param end_line - end line index, default to -1.\nfunction M.get_highlights(bufnr, key, start_line, end_line)\n  if not api.nvim_buf_is_loaded(bufnr) then\n    return nil\n  end\n  start_line = type(start_line) == 'number' and start_line or 0\n  end_line = type(end_line) == 'number' and end_line or -1\n  local max = end_line == -1 and api.nvim_buf_line_count(bufnr) or end_line + 1\n  local ns = type(key) == 'number' and key or create_namespace(key)\n  local markers = api.nvim_buf_get_extmarks(bufnr, ns, {start_line, 0}, {end_line, -1}, {details = true})\n  local res = {}\n  for _, mark in ipairs(markers) do\n    local id = mark[1]\n    local line = mark[2]\n    local startCol = mark[3]\n    local details = mark[4] or {}\n    local endCol = details.end_col\n    if line < max then\n      local delta = details.end_row - line\n      if delta == 1 and endCol == 0 then\n        local text = vim.fn.getbufline(bufnr, line + 1)[1] or ''\n        endCol = #text\n      elseif delta > 0 then\n        endCol = -1\n      end\n      if startCol >= endCol then\n        api.nvim_buf_del_extmark(bufnr, ns, id)\n      else\n        table.insert(res, {details.hl_group, line, startCol, endCol, id})\n      end\n    end\n  end\n  return res\nend\n\n-- Add single highlight\n-- @param id - buffer number or 0 for current buffer.\n-- @param key - namespace key or namespace number or -1.\n-- @param hl_group - highlight group.\n-- @param line - 0 based line index.\n-- @param col_start - 0 based col index, inclusive.\n-- @param col_end - 0 based col index, exclusive.\n-- @param opts - Optional table with priority and combine as boolean.\nfunction M.add_highlight(id, key, hl_group, line, col_start, col_end, opts)\n  local bufnr = id == 0 and api.nvim_get_current_buf() or id\n  opts = is_null(opts) and {} or opts\n  if api.nvim_buf_is_loaded(bufnr) then\n    local priority = get_priority(hl_group, opts.priority)\n    if col_end == -1 then\n      local text = vim.fn.getbufline(bufnr, line + 1)[1] or ''\n      col_end = #text\n    end\n    if col_end > 0 and col_end > col_start then\n      local ns = create_namespace(key)\n      pcall(api.nvim_buf_set_extmark, bufnr, ns, line, col_start, {\n        end_col = col_end,\n        hl_group = hl_group,\n        hl_mode = is_enabled(opts.combine) and 'combine' or 'replace',\n        right_gravity = true,\n        end_right_gravity = is_enabled(opts.end_incl),\n        priority = math.min(priority, 4096)\n      })\n    end\n  end\nend\n\n-- Clear all namespaces with coc- namespace prefix.\nfunction M.clear_all()\n  local namespaces = getValuesWithPrefix(api.nvim_get_namespaces(), 'coc-')\n  local bufnrs = api.nvim_list_bufs()\n  for _, bufnr in ipairs(bufnrs) do\n    if api.nvim_buf_is_loaded(bufnr) then\n      for _, ns in ipairs(namespaces) do\n        api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)\n      end\n    end\n  end\nend\n-- Remove extmarks by id table.\n-- @param bufnr - buffer number\n-- @param key - namespace id or key string.\n-- @param ids - table with ids of extmarks.\nfunction M.del_markers(bufnr, key, ids)\n  if api.nvim_buf_is_loaded(bufnr) then\n    local ns = create_namespace(key)\n    for _, id in ipairs(ids) do\n      api.nvim_buf_del_extmark(bufnr, ns, id)\n    end\n  end\nend\n\n-- Add highlights to buffer.\n-- @param bufnr - buffer number\n-- @param key - namespace id or key string.\n-- @param highlights - highlight items, item could be HighlightItem dict or number list.\n-- @param priority - optional priority\nfunction M.set_highlights(bufnr, key, highlights, priority)\n  if api.nvim_buf_is_loaded(bufnr) then\n    local changedtick = api.nvim_buf_get_var(bufnr, 'changedtick')\n    local ns = create_namespace(key)\n    addHighlightTimer(bufnr, ns, highlights, priority, changedtick)\n  end\nend\n\n-- Clear namespace highlights of region.\n-- @param id - buffer number or 0 for current buffer.\n-- @param key - namespace id or key string.\n-- @param start_line - start line index, default to 0.\n-- @param end_line - end line index, default to -1.\nfunction M.clear_highlights(id, key, start_line, end_line)\n  local bufnr = id == 0 and api.nvim_get_current_buf() or id\n  start_line = type(start_line) == 'number' and start_line or 0\n  end_line = type(end_line) == 'number' and end_line or -1\n  if api.nvim_buf_is_loaded(bufnr) then\n    local ns = create_namespace(key)\n    api.nvim_buf_clear_namespace(bufnr, ns, start_line, end_line)\n  end\nend\n\n-- Update highlights of specific region.\n-- @param id - buffer number or 0 for current buffer.\n-- @param key - namespace id or key string.\n-- @param highlights - highlight items, item could be HighlightItem dict or number list.\n-- @param start_line - start line index, default to 0.\n-- @param end_line - end line index, default to -1.\n-- @param priority - optional priority.\n-- @param changedtick - optional buffer changedtick.\nfunction M.update_highlights(id, key, highlights, start_line, end_line, priority, changedtick)\n  local bufnr = id == 0 and api.nvim_get_current_buf() or id\n  start_line = type(start_line) == 'number' and start_line or 0\n  end_line = type(end_line) == 'number' and end_line or -1\n  if api.nvim_buf_is_loaded(bufnr) then\n    local ns = create_namespace(key)\n    local tick = api.nvim_buf_get_var(bufnr, 'changedtick')\n    if type(changedtick) == 'number' and changedtick ~= tick then\n      return\n    end\n    api.nvim_buf_clear_namespace(bufnr, ns, start_line, end_line)\n    addHighlightTimer(bufnr, ns, highlights, priority, tick)\n  end\nend\n\n-- Update namespace highlights of whole buffer.\n-- @param bufnr - buffer number.\n-- @param key - namespace id or key string.\n-- @param highlights - highlight items, item could be HighlightItem dict or number list.\n-- @param priority - optional priority.\n-- @param changedtick - optional buffer changedtick.\nfunction M.buffer_update(bufnr, key, highlights, priority, changedtick)\n  if api.nvim_buf_is_loaded(bufnr) then\n    local ns = create_namespace(key)\n    api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)\n    if #highlights > 0 then\n      local tick = api.nvim_buf_get_var(bufnr, 'changedtick')\n      if type(changedtick) ~= 'number' or tick == changedtick then\n        local winid = vim.fn.bufwinid(bufnr)\n        if winid == -1 then\n          addHighlightTimer(bufnr, ns, highlights, priority, tick)\n        else\n          local info = vim.fn.getwininfo(winid)[1]\n          local topline = info.topline\n          local botline = info.botline\n          if topline <= 5 then\n            addHighlightTimer(bufnr, ns, highlights, priority, tick)\n          else\n            local curr_hls = {}\n            local other_hls = {}\n            for _, hl in ipairs(highlights) do\n              local lnum = hl[2] ~= nil and hl[2] + 1 or hl.lnum + 1\n              if lnum >= topline and lnum <= botline then\n                table.insert(curr_hls, hl)\n              else\n                table.insert(other_hls, hl)\n              end\n            end\n            vim.list_extend(curr_hls, other_hls)\n            addHighlightTimer(bufnr, ns, curr_hls, priority, tick)\n          end\n        end\n      end\n    end\n  end\nend\n\n-- Add highlights to LSP ranges\n-- @param id - buffer number or 0 for current buffer.\n-- @param key - namespace id or key string.\n-- @param hl_group - highlight group.\n-- @param ranges - LSP range list.\n-- @param opts - Optional table with priority and clear, combine as boolean.\nfunction M.highlight_ranges(id, key, hl_group, ranges, opts)\n  local bufnr = id == 0 and api.nvim_get_current_buf() or id\n  if is_null(opts) or type(opts) ~= 'table' then\n    opts = {}\n  end\n  if api.nvim_buf_is_loaded(bufnr) then\n    if opts.clear then\n      local ns = create_namespace(key)\n      api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)\n    end\n    local highlights = {}\n    for _, range in ipairs(ranges) do\n      local sp = range['start']\n      local ep = range['end']\n      local lines = vim.fn.getbufline(bufnr, sp.line + 1, ep.line + 1)\n      for index=sp.line,ep.line,1 do\n        local line = lines[index - sp.line + 1] or ''\n        if #line > 0 then\n          local colStart = index == sp.line and byte_index(line, sp.character) or 0\n          local colEnd = index == ep.line and byte_index(line, ep.character) or #line\n          if colEnd > colStart then\n            local combine = is_enabled(opts.combine) and 1 or 0\n            table.insert(highlights, {hl_group, index, colStart, colEnd, combine, opts.start_incl, opts.end_incl})\n          end\n        end\n      end\n    end\n    if #highlights > 0 then\n      local priority = type(opts.priority) == 'number' and opts.priority or 4096\n      M.set_highlights(bufnr, key, highlights, priority)\n    end\n  end\nend\n\n-- Use matchaddpos to add highlights to window.\n-- @param id - window id, or 0 for current window.\n-- @param buf - buffer number, or 0 for current buffer.\n-- @param ranges - LSP ranges.\n-- @param hlGroup - highlight group.\n-- @param priority - Optional priority, default to 99.\nfunction M.match_ranges(id, buf, ranges, hl_group, priority)\n  local winid = id == 0 and api.nvim_get_current_win() or id\n  local bufnr = buf == 0 and vim.fn.winbufnr(winid) or buf\n  if not api.nvim_win_is_valid(winid) or vim.fn.winbufnr(winid) ~= bufnr then\n    return {}\n  end\n  local ids = {}\n  local pos = {}\n  for _, range in ipairs(ranges) do\n    local sp = range['start']\n    local ep = range['end']\n    local lines = vim.fn.getbufline(bufnr, sp.line + 1, ep.line + 1)\n    for index=sp.line,ep.line,1 do\n      local line = lines[index - sp.line + 1] or ''\n      if #line > 0 then\n        local colStart = index == sp.line and byte_index(line, sp.character) or 0\n        local colEnd = index == ep.line and byte_index(line, ep.character) or #line\n        if colEnd > colStart then\n          table.insert(pos, {index + 1, colStart + 1, colEnd - colStart})\n        end\n      end\n    end\n  end\n  local count = #pos\n  if count > 0 then\n    priority = type(priority) == 'number' and priority or 99\n    local opts = {window = winid}\n    if count < 9 or n10 then\n      ---@diagnostic disable-next-line: param-type-mismatch\n      table.insert(ids, vim.fn.matchaddpos(hl_group, pos, priority, -1, opts))\n    else\n      local group = {}\n      for i=1,count,8 do\n        for j = i,math.min(i+7, count) do\n          table.insert(group, pos[j])\n        end\n        ---@diagnostic disable-next-line: param-type-mismatch\n        table.insert(ids, vim.fn.matchaddpos(hl_group, group, priority, -1, opts))\n        group = {}\n      end\n    end\n  end\n  return ids\nend\n\nreturn M\n"
  },
  {
    "path": "lua/coc/text.lua",
    "content": "local api = vim.api\n\nlocal M = {}\n\nlocal function splitText(text, col)\n  return text:sub(1, col - 1), text:sub(col)\nend\n\nlocal function copy(t)\n  local list = {}\n  for k, v in pairs(t) do\n    list[k] = v\n  end\n  return list\nend\n\nlocal function insertList(target, insert, linePos, colPos)\n  local result = {}\n  for i = 1, #target do\n    if i < linePos or i > linePos then\n      table.insert(result, target[i])\n    else\n      local before, after = splitText(target[i], colPos)\n      for j = 1, #insert do\n        local text = insert[j]\n        if j == 1 then\n          text = before .. text\n        end\n        if j == #insert then\n          text = text .. after\n        end\n        table.insert(result, text)\n      end\n    end\n  end\n  return result\nend\n\n\nlocal function lcsDiff(str1, str2) -- 计算最长公共子序列\n  local function lcs(a, b)\n    local matrix = {}\n    for i = 0, #a do\n      matrix[i] = {}\n      for j = 0, #b do\n        if i == 0 or j == 0 then\n          matrix[i][j] = 0\n        elseif a:sub(i, i) == b:sub(j, j) then\n          matrix[i][j] = matrix[i - 1][j - 1] + 1\n        else\n          matrix[i][j] = math.max(matrix[i - 1][j], matrix[i][j - 1])\n        end\n      end\n    end\n\n    local result = ''\n    local i, j = #a, #b\n    while i > 0 and j > 0 do\n      if a:sub(i, i) == b:sub(j, j) then\n        result = a:sub(i, i) .. result\n        i = i - 1\n        j = j - 1\n      elseif matrix[i - 1][j] > matrix[i][j - 1] then\n        i = i - 1\n      else\n        j = j - 1\n      end\n    end\n\n    return result\n  end\n\n  local common = lcs(str1, str2)\n  local result = {}\n  local i1, i2, ic = 1, 1, 1\n\n  while ic <= #common do\n    -- 处理str1中不在公共序列的部分\n    while i1 <= #str1 and str1:sub(i1, i1) ~= common:sub(ic, ic) do\n      table.insert(result, { type = '-', char = str1:sub(i1, i1) })\n      i1 = i1 + 1\n    end\n\n    -- 处理str2中不在公共序列的部分\n    while i2 <= #str2 and str2:sub(i2, i2) ~= common:sub(ic, ic) do\n      table.insert(result, { type = '+', char = str2:sub(i2, i2) })\n      i2 = i2 + 1\n    end\n\n    -- 添加公共字符\n    if ic <= #common then\n      table.insert(result, { type = '=', char = common:sub(ic, ic) })\n      i1 = i1 + 1\n      i2 = i2 + 1\n      ic = ic + 1\n    end\n  end\n\n  -- 处理剩余字符\n  while i1 <= #str1 do\n    table.insert(result, { type = '-', char = str1:sub(i1, i1) })\n    i1 = i1 + 1\n  end\n\n  while i2 <= #str2 do\n    table.insert(result, { type = '+', char = str2:sub(i2, i2) })\n    i2 = i2 + 1\n  end\n  return result\nend\n\n-- Try find new col in changed text\n-- Not 100% correct, but works most of the time.\n-- Return nil when not found\nlocal function findNewCol(text, col, newText)\n  local before, after = splitText(text, col)\n  if #before == 0 then\n    return 1\n  end\n  if #after == 0 then\n    return #newText + 1\n  end\n  if #before <= #after and string.sub(newText, 1, #before) == before then\n    return col\n  end\n  if string.sub(newText, -#after) == after then\n    return #newText - #after + 1\n  end\n  local diff = lcsDiff(text, newText)\n  local used = 1\n  local index = 1\n  for _, item in ipairs(diff) do\n    if item.type == '-' then\n      used = used + #item.char\n    elseif item.type == '+' then\n      index = index + #item.char\n    elseif item.type == '=' then\n      local total = used + #item.char\n      if total >= col then\n        local plus = col - used\n        used = col\n        index = index + plus\n      else\n        used = total\n        index = index + #item.char\n      end\n    end\n    if used == col then\n      break\n    end\n    if used > col then\n      return nil\n    end\n  end\n  return used == col and index or nil\nend\n\nlocal function findInsert(arr1, arr2, linePos, colPos)\n  local l1 = #arr1\n  local l2 = #arr2\n  if l1 < l2 or linePos < 1 or linePos > #arr1 then\n    return nil\n  end\n  arr2 = copy(arr2)\n  for i = 1, #arr1 - linePos, 1 do\n    local a = arr1[l1 - i + 1]\n    local idx = l2 - i + 1\n    local b = arr2[idx]\n    if b == nil then\n      return nil\n    end\n    if a ~= b then\n      return nil\n    end\n    table.remove(arr2, idx)\n  end\n  local before, after = splitText(arr1[linePos], colPos)\n  local last = arr2[#arr2]\n  if #after > 0 and last:sub(-#after) ~= after then\n    return nil\n  end\n  arr2[#arr2] = last:sub(1, - #after - 1)\n  for index, value in ipairs(arr2) do\n    local text = arr1[index]\n    if index < #arr2 and text ~= value then\n      return nil\n    end\n    if index == #arr2 and text:sub(1, #value) ~= value then\n      return nil\n    end\n  end\n  local pos = {}\n  pos.line = #arr2\n  pos.col = #(arr2[#arr2]) + 1\n  local inserted = {}\n  for i = pos.line, linePos, 1 do\n    if i == pos.line then\n      local text = arr1[i]:sub(pos.col)\n      table.insert(inserted, text)\n    elseif i == linePos then\n      table.insert(inserted, before)\n    else\n      table.insert(inserted, arr1[i])\n    end\n  end\n  return pos, inserted\nend\n\nlocal function findStringDiff(oldStr, newStr, reverseFirst)\n    local len1, len2 = #oldStr, #newStr\n    local maxLen = math.max(len1, len2)\n    -- 先从后往前找差异\n    if reverseFirst then\n        local end1, end2 = len1, len2\n        while end1 >= 1 and end2 >= 1 do\n            local c1 = oldStr:sub(end1, end1)\n            local c2 = newStr:sub(end2, end2)\n            if c1 ~= c2 then\n                break\n            end\n            end1 = end1 - 1\n            end2 = end2 - 1\n        end\n        -- 如果完全相同\n        if end1 < 1 and end2 < 1 then\n            return nil\n        end\n\n        -- 然后从前往后找差异\n        local start = 1\n        while start <= end1 and start <= end2 do\n            local c1 = oldStr:sub(start, start)\n            local c2 = newStr:sub(start, start)\n            if c1 ~= c2 then\n                break\n            end\n            start = start + 1\n        end\n        return {\n            startPos = start,\n            endPosOld = end1,\n            inserted = newStr:sub(start, end2)\n        }\n    else\n        local start = 1\n        while start <= maxLen do\n            local c1 = start <= len1 and oldStr:sub(start, start) or nil\n            local c2 = start <= len2 and newStr:sub(start, start) or nil\n            if c1 ~= c2 then\n                break\n            end\n            start = start + 1\n        end\n        -- 如果完全相同\n        if start > maxLen then\n            return nil\n        end\n        -- 然后从后往前找差异\n        local end1, end2 = len1, len2\n        while end1 >= start and end2 >= start do\n            local c1 = oldStr:sub(end1, end1)\n            local c2 = newStr:sub(end2, end2)\n            if c1 ~= c2 then\n                break\n            end\n            end1 = end1 - 1\n            end2 = end2 - 1\n        end\n        return {\n            startPos = start,\n            endPosOld = end1,\n            inserted = newStr:sub(start, end2)\n        }\n    end\nend\n\nlocal function replaceSubstring(original, startPos, endPos, replacement)\n    local prefix = original:sub(1, startPos - 1)\n    local suffix = original:sub(endPos + 1)\n    return prefix .. replacement .. suffix\nend\n\nlocal function hasConflict(diff1, diff2)\n    -- 如果任一diff为nil（无变化），则无冲突\n    if not diff1 or not diff2 then\n        return false\n    end\n    -- 获取两个diff的修改范围\n    local start1, end1 = diff1.startPos, diff1.endPosOld\n    local start2, end2 = diff2.startPos, diff2.endPosOld\n    -- 处理删除的情况（endPos可能小于startPos）\n    end1 = math.max(end1, start1 - 1)\n    end2 = math.max(end2, start2 - 1)\n    -- 检查范围是否重叠\n    local overlap = not (end1 < start2 or end2 < start1)\n    return overlap\nend\n\nlocal function diffApply(original, current, newText, reverseFirst)\n  local diff1 = findStringDiff(original, current, reverseFirst)\n  local diff2 = findStringDiff(original, newText, not reverseFirst)\n  if hasConflict(diff1, diff2) then\n     diff1 = findStringDiff(original, current, not reverseFirst)\n     diff2 = findStringDiff(original, newText, reverseFirst)\n  end\n  if diff1 == nil or diff2 == nil or hasConflict(diff1, diff2) then\n    return nil\n  end\n  local result\n  if diff1.startPos < diff2.startPos then\n    result = replaceSubstring(original, diff2.startPos, diff2.endPosOld, diff2.inserted)\n    result = replaceSubstring(result, diff1.startPos, diff1.endPosOld, diff1.inserted)\n  else\n    result = replaceSubstring(original, diff1.startPos, diff1.endPosOld, diff1.inserted)\n    result = replaceSubstring(result, diff2.startPos, diff2.endPosOld, diff2.inserted)\n  end\n  return result\nend\n\n-- Change single line by use nvim_buf_set_text\n-- 1 based line number, current line, applied line\nfunction M.changeLineText(bufnr, lnum, current, applied)\n  local diff = findStringDiff(current, applied)\n  if diff ~= nil then\n    local lineIdx = lnum - 1\n    api.nvim_buf_set_text(bufnr, lineIdx, diff.startPos - 1, lineIdx, diff.endPosOld, {diff.inserted})\n  end\nend\n\n-- Check if new line insert.\n-- Check text change instead of insert only.\n-- Check change across multiple lines.\nfunction M.set_lines(bufnr, changedtick, originalLines, replacement, startLine, endLine, changes, cursor, col, linecount)\n  if not api.nvim_buf_is_loaded(bufnr) then\n    return nil\n  end\n  local delta = 0\n  local column = vim.fn.col('.')\n  if type(col) == 'number' then\n    delta = column  - col\n  end\n  local applied = nil\n  local idx = 0\n  local currentBuf = api.nvim_get_current_buf() == bufnr\n  local current = currentBuf and vim.fn.getline('.') or ''\n  if currentBuf and api.nvim_buf_get_var(bufnr, 'changedtick') > changedtick then\n    local lnum = vim.fn.line('.')\n    idx = lnum - startLine\n    if idx >= 1 then\n      local original = originalLines[idx]\n      local count = vim.fn.line('$')\n      if count ~= linecount then\n        -- Check content insert before cursor.\n        if count > linecount then\n          local currentLines = api.nvim_buf_get_lines(bufnr, startLine, endLine + count - linecount, false)\n          -- Cursor not inside\n          if currentLines[idx] == nil then\n            return nil\n          end\n          -- Compare to original lines, find insert position, text\n          local pos, inserted = findInsert(currentLines, originalLines, idx, column)\n          if pos ~= nil then\n            local newText = replacement[pos.line]\n            if newText == nil then\n              return nil\n            end\n            local colPos = findNewCol(originalLines[pos.line], pos.col, newText)\n            if colPos == nil then\n              return nil\n            end\n            replacement = insertList(replacement, inserted, pos.line, colPos)\n            endLine = endLine + count - linecount\n            changes = vim.NIL\n          else\n            return nil\n          end\n        else\n          return nil\n        end\n      else\n        -- current line changed\n        if original ~= nil and original ~= current then\n          local newText = replacement[idx]\n          if newText ~= nil then\n            if newText == original then\n              applied = current\n            else\n              applied = diffApply(original, current, newText, column > #current/2)\n            end\n          end\n        end\n      end\n    end\n  end\n  if applied ~= nil then\n    replacement[idx] = applied\n    if #replacement < 30 then\n      -- use nvim_buf_set_text to keep extmarks\n      for i = 1, math.min(#replacement, #originalLines) do\n        local text = idx == i and current or originalLines[i]\n        M.changeLineText(bufnr, startLine + i, text, replacement[i])\n      end\n      if #replacement > #originalLines then\n        local newLines = vim.list_slice(replacement, #originalLines + 1)\n        api.nvim_buf_set_lines(bufnr, endLine, endLine, false, newLines)\n      elseif #originalLines > #replacement then\n        api.nvim_buf_set_lines(bufnr, startLine + #replacement, endLine, false, {})\n      end\n    else\n      api.nvim_buf_set_lines(bufnr, startLine, endLine, false, replacement)\n    end\n  else\n    if type(changes) == 'table' and #changes > 0 then\n      -- reverse iteration\n      for i = #changes, 1, -1 do\n        local item = changes[i]\n        api.nvim_buf_set_text(bufnr, item[2], item[3], item[4], item[5], item[1])\n      end\n    else\n      api.nvim_buf_set_lines(bufnr, startLine, endLine, false, replacement)\n    end\n  end\n  if currentBuf and type(cursor) == 'table' then\n    vim.fn.cursor({cursor[1], cursor[2] + delta})\n  end\nend\n\nreturn M\n"
  },
  {
    "path": "lua/coc/util.lua",
    "content": "\nlocal M = {}\n\nlocal unpackFn = unpack\nif unpackFn == nil then\n  unpackFn = table.unpack\nend\n\nM.unpack = unpackFn\n\nfunction M.sendErrorMsg(msg)\n  vim.defer_fn(function()\n    vim.api.nvim_call_function('coc#rpc#notify', {'nvim_error_event', {0, 'Lua ' .. _VERSION .. ':'.. msg}})\n  end, 10)\nend\n\nfunction M.getCurrentTime()\n    return os.clock() * 1000\nend\n\nlocal function errorHandler(err)\n    local traceback = debug.traceback(2)\n    return err .. \"\\n\" .. traceback\nend\n\n-- catch the error and send notification to NodeJS\nfunction M.call(module, func, args)\n  local m = require(module)\n  local method = m[func]\n  local result = nil\n  if method ~= nil then\n    local ok, err = xpcall(function ()\n      result = method(unpackFn(args))\n    end, errorHandler)\n    if not ok then\n      local msg = 'Error on ' .. module .. '[' .. func .. ']\" ' .. err\n      M.sendErrorMsg(msg)\n      error(msg)\n    end\n  else\n    local msg = 'Method \"' .. module .. '[' .. func .. ']\" not exists'\n    M.sendErrorMsg(msg)\n    error(msg)\n  end\n  return result\nend\nreturn M\n"
  },
  {
    "path": "lua/coc/vtext.lua",
    "content": "local api = vim.api\nlocal M = {}\nlocal n10 = vim.fn.has('nvim-0.10')\nlocal maxCount = vim.g.coc_highlight_maximum_count or 500\n\nlocal function addVirtualText(bufnr, ns, opts, pre, priority)\n    local align = opts.text_align or 'after'\n    local config = { hl_mode = opts.hl_mode or 'combine', right_gravity = opts.right_gravity }\n    local column = opts.col or 0\n    if align == 'above' or align == 'below' then\n      if #pre == 0 then\n        config.virt_lines = { opts.blocks }\n      else\n        local list =  { {pre, 'Normal'}}\n        vim.list_extend(list, opts.blocks)\n        config.virt_lines = { list }\n      end\n      if align == 'above' then\n        config.virt_lines_above = true\n      end\n    else\n      config.virt_text = opts.blocks\n      if n10 and column ~= 0 then\n        config.virt_text_pos = 'inline'\n      elseif align == 'right' then\n        config.virt_text_pos = 'right_align'\n      elseif type(opts.virt_text_win_col) == 'number' then\n        config.virt_text_win_col = opts.virt_text_win_col\n        config.virt_text_pos = 'overlay'\n      elseif align == 'overlay' then\n        config.virt_text_pos = 'overlay'\n      else\n        config.virt_text_pos = 'eol'\n      end\n      if type(opts.virt_lines) == 'table' then\n        config.virt_lines = opts.virt_lines\n        config.virt_text_pos = 'overlay'\n      end\n    end\n    if type(priority) == 'number' then\n      config.priority = math.min(priority, 4096)\n    end\n    local col = column ~= 0 and column - 1 or 0\n    -- api.nvim_buf_set_extmark(bufnr, ns, opts.line, col, config)\n    -- Error: col value outside range\n    pcall(api.nvim_buf_set_extmark, bufnr, ns, opts.line, col, config)\nend\n\n-- This function is called by buffer.setVirtualText\nfunction M.add(bufnr, ns, line, blocks, opts)\n  local pre = ''\n  if opts.indent == true then\n    local str = vim.fn.getbufline(bufnr, line + 1)[1] or ''\n    pre = string.match(str, \"^%s*\") or ''\n  end\n  local conf = {line = line, blocks = blocks}\n  for key, value in pairs(opts) do\n    conf[key] = value\n  end\n  addVirtualText(bufnr, ns, conf, pre, opts.priority)\nend\n\n-- opts.line - Zero based line number\n-- opts.blocks - List with [text, hl_group]\n-- opts.hl_mode - Default to 'combine'.\n-- opts.col - nvim >= 0.10.0, 1 based.\n-- opts.virt_text_win_col\n-- opts.text_align - Could be 'after' 'right' 'below' 'above', converted on neovim.\n-- indent - add indent when using 'above' and 'below' as text_align\nlocal function addVirtualTexts(bufnr, ns, items, indent, priority)\n  if #items == 0 then\n    return nil\n  end\n  local buflines = {}\n  local start = 0\n  if indent then\n    start = items[1].line\n    local endLine = items[#items].line\n    buflines = api.nvim_buf_get_lines(bufnr, start, endLine + 1, false) or {}\n  end\n  for _, opts in ipairs(items) do\n    local pre = indent and string.match(buflines[opts.line - start + 1], \"^%s*\") or ''\n    addVirtualText(bufnr, ns, opts, pre, priority)\n  end\nend\n\nlocal function addVirtualTextsTimer(bufnr, ns, items, indent, priority, changedtick)\n  if not api.nvim_buf_is_loaded(bufnr) then\n    return nil\n  end\n  if vim.fn.getbufvar(bufnr, 'changedtick', 0) ~= changedtick then\n    return nil\n  end\n  if #items > maxCount then\n    local markers = {}\n    local next = {}\n    vim.list_extend(markers, items, 1, maxCount)\n    vim.list_extend(next, items, maxCount, #items)\n    addVirtualTexts(bufnr, ns, markers, indent, priority)\n    vim.defer_fn(function()\n      addVirtualTextsTimer(bufnr, ns, next, indent, priority, changedtick)\n    end, 10)\n  else\n    addVirtualTexts(bufnr, ns, items, indent, priority)\n  end\nend\n\nfunction M.set(bufnr, ns, items, indent, priority)\n  local changedtick = vim.fn.getbufvar(bufnr, 'changedtick', 0)\n  addVirtualTextsTimer(bufnr, ns, items, indent, priority, changedtick)\nend\nreturn M\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"coc.nvim-master\",\n  \"version\": \"0.0.82\",\n  \"description\": \"LSP based intellisense engine for neovim & vim8.\",\n  \"main\": \"./build/index.js\",\n  \"engines\": {\n    \"node\": \">=16.18.0\"\n  },\n  \"type\": \"commonjs\",\n  \"scripts\": {\n    \"lint\": \"eslint . --quiet --concurrency auto\",\n    \"lint:typecheck\": \"tsc -p tsconfig.json\",\n    \"build\": \"node esbuild.js\",\n    \"test\": \"./node_modules/.bin/jest --forceExit\",\n    \"test-build\": \"./node_modules/.bin/jest --coverage --forceExit\",\n    \"prepare\": \"node esbuild.js\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/neoclide/coc.nvim.git\"\n  },\n  \"keywords\": [\n    \"complete\",\n    \"neovim\"\n  ],\n  \"author\": \"Qiming Zhao <chemzqm@gmail.com>\",\n  \"bugs\": {\n    \"url\": \"https://github.com/neoclide/coc.nvim/issues\"\n  },\n  \"homepage\": \"https://github.com/neoclide/coc.nvim#readme\",\n  \"jest\": {\n    \"globals\": {\n      \"__TEST__\": true\n    },\n    \"projects\": [\n      \"<rootDir>\"\n    ],\n    \"watchman\": false,\n    \"clearMocks\": true,\n    \"globalSetup\": \"./jest.js\",\n    \"testEnvironment\": \"node\",\n    \"coveragePathIgnorePatterns\": [\n      \"<rootDir>/src/__tests__/*\"\n    ],\n    \"moduleFileExtensions\": [\n      \"ts\",\n      \"tsx\",\n      \"json\",\n      \"mjs\",\n      \"js\"\n    ],\n    \"transform\": {\n      \"^.+\\\\.tsx?$\": [\n        \"@swc/jest\"\n      ]\n    },\n    \"testRegex\": \"src/__tests__/.*\\\\.(test|spec)\\\\.ts$\",\n    \"coverageReporters\": [\n      \"text\",\n      \"lcov\"\n    ],\n    \"coverageDirectory\": \"./coverage/\"\n  },\n  \"devDependencies\": {\n    \"@eslint/eslintrc\": \"^3.3.5\",\n    \"@eslint/js\": \"^10.0.1\",\n    \"@swc/jest\": \"^0.2.39\",\n    \"@types/cli-table\": \"^0.3.4\",\n    \"@types/debounce\": \"^1.2.4\",\n    \"@types/fb-watchman\": \"^2.0.6\",\n    \"@types/follow-redirects\": \"^1.14.4\",\n    \"@types/jest\": \"^30.0.0\",\n    \"@types/node\": \"^18.19.25\",\n    \"@types/semver\": \"^7.7.1\",\n    \"@types/unidecode\": \"^1.1.0\",\n    \"@types/uuid\": \"^9.0.8\",\n    \"@types/which\": \"^3.0.4\",\n    \"@typescript-eslint/eslint-plugin\": \"^8.57.0\",\n    \"@typescript-eslint/parser\": \"^8.57.0\",\n    \"esbuild\": \"^0.27.4\",\n    \"eslint\": \"10.0.3\",\n    \"eslint-formatter-unix\": \"^9.0.1\",\n    \"eslint-plugin-jest\": \"^29.15.0\",\n    \"eslint-plugin-jsdoc\": \"^62.8.0\",\n    \"globals\": \"^16.5.0\",\n    \"jest\": \"30.3.0\",\n    \"typescript\": \"^5.5.4\",\n    \"vscode-languageserver\": \"^10.0.0-next.16\"\n  },\n  \"dependencies\": {\n    \"@chemzqm/neovim\": \"^6.3.6\",\n    \"ansi-styles\": \"^5.2.0\",\n    \"bytes\": \"^3.1.2\",\n    \"cli-table\": \"^0.3.11\",\n    \"content-disposition\": \"^0.5.4\",\n    \"debounce\": \"^1.2.1\",\n    \"decompress-response\": \"^6.0.0\",\n    \"fast-diff\": \"^1.3.0\",\n    \"fb-watchman\": \"^2.0.2\",\n    \"follow-redirects\": \"^1.15.11\",\n    \"glob\": \"10.5\",\n    \"http-proxy-agent\": \"^7.0.2\",\n    \"https-proxy-agent\": \"^7.0.6\",\n    \"iconv-lite\": \"^0.7.2\",\n    \"jsonc-parser\": \"^3.3.1\",\n    \"marked\": \"^7.0.5\",\n    \"minimatch\": \"^9.0.4\",\n    \"semver\": \"^7.7.4\",\n    \"strip-ansi\": \"^6.0.1\",\n    \"tar\": \"^7.5.11\",\n    \"tslib\": \"^2.8.1\",\n    \"unidecode\": \"^1.1.0\",\n    \"unzip-stream\": \"^0.3.4\",\n    \"uuid\": \"^9.0.1\",\n    \"vscode-languageserver-protocol\": \"^3.17.6-next.16\",\n    \"vscode-languageserver-textdocument\": \"^1.0.12\",\n    \"vscode-languageserver-types\": \"^3.17.6-next.6\",\n    \"vscode-uri\": \"^3.1.0\",\n    \"which\": \"^4.0.0\"\n  }\n}\n"
  },
  {
    "path": "plugin/coc.lua",
    "content": "if vim.fn.has('nvim-0.10') then\n  vim.api.nvim_create_autocmd({ 'BufEnter' }, {\n    callback = function()\n      require('coc.diagnostic').refresh()\n    end,\n  })\n\n  vim.api.nvim_create_autocmd('User', {\n    pattern = 'CocDiagnosticChange',\n    callback = function()\n      require('coc.diagnostic').refresh()\n    end,\n  })\nend\n"
  },
  {
    "path": "plugin/coc.vim",
    "content": "scriptencoding utf-8\nlet s:is_vim = !has('nvim')\nlet s:is_gvim = s:is_vim && has(\"gui_running\")\nif exists('g:did_coc_loaded') || v:version < 800\n  finish\nendif\n\nfunction! s:checkVersion() abort\n  let l:unsupported = 0\n  if get(g:, 'coc_disable_startup_warning', 0) != 1\n    if s:is_vim\n      let l:unsupported = !has('patch-9.0.0438')\n    else\n      let l:unsupported = !has('nvim-0.8.0')\n    endif\n\n    if l:unsupported == 1\n      echohl Error\n      echom \"coc.nvim requires at least Vim 9.0.0438 or Neovim 0.8.0, but you're using an older version.\"\n      echom \"Please upgrade your (neo)vim.\"\n      echom \"You can add this to your vimrc to avoid this message:\"\n      echom \"    let g:coc_disable_startup_warning = 1\"\n      echom \"Note that some features may error out or behave incorrectly.\"\n      echom \"Please do not report bugs unless you're using at least Vim 9.0.0438 or Neovim 0.8.0.\"\n      echohl None\n      sleep 2\n    endif\n  endif\nendfunction\n\ncall s:checkVersion()\n\nlet g:did_coc_loaded = 1\nlet g:coc_service_initialized = 0\nlet s:root = expand('<sfile>:h:h')\n\nif get(g:, 'coc_start_at_startup', 1) && !s:is_gvim\n  call coc#rpc#start_server()\nendif\n\nfunction! CocTagFunc(pattern, flags, info) abort\n  \" tagfunc can't be set in the sandbox mode, preload the following functions\n  silent! call coc#cursor#move_to()\n  silent! call coc#string#character_index()\n  if a:flags !=# 'c'\n    \" use standard tag search\n    return v:null\n  endif\n  return coc#rpc#request('getTagList', [])\nendfunction\n\n\" Used by popup prompt on vim\nfunction! CocPopupCallback(bufnr, arglist) abort\n  if len(a:arglist) == 2\n    if a:arglist[0] == 'confirm'\n      call coc#rpc#notify('PromptInsert', [a:arglist[1], a:bufnr])\n    elseif a:arglist[0] == 'exit'\n      \" notify exit for vim terminal prompt to ensure cleanup\n      call coc#rpc#notify('PromptExit', [a:bufnr])\n      execute 'silent! bd! '.a:bufnr\n      \"call coc#rpc#notify('PromptUpdate', [a:arglist[1]])\n    elseif a:arglist[0] == 'change'\n      let text = a:arglist[1]\n      let current = getbufvar(a:bufnr, 'current', '')\n      if text !=# current\n        call setbufvar(a:bufnr, 'current', text)\n        let cursor = term_getcursor(a:bufnr)\n        let info = {\n              \\ 'lnum': cursor[0],\n              \\ 'col': cursor[1],\n              \\ 'line': text,\n              \\ 'changedtick': 0\n              \\ }\n        call coc#rpc#notify('CocAutocmd', ['TextChangedI', a:bufnr, info])\n      endif\n    elseif a:arglist[0] == 'send'\n      call coc#rpc#notify('PromptKeyPress', [a:bufnr, a:arglist[1]])\n    endif\n  endif\nendfunction\n\nfunction! CocAction(name, ...) abort\n  if !get(g:, 'coc_service_initialized', 0)\n    throw 'coc.nvim not ready when invoke CocAction \"'.a:name.'\"'\n  endif\n  return coc#rpc#request(a:name, a:000)\nendfunction\n\nfunction! CocHasProvider(name, ...) abort\n  let bufnr = empty(a:000) ? bufnr('%') : a:1\n  return coc#rpc#request('hasProvider', [a:name, bufnr])\nendfunction\n\nfunction! CocActionAsync(name, ...) abort\n  return s:AsyncRequest(a:name, a:000)\nendfunction\n\nfunction! CocRequest(...) abort\n  return coc#rpc#request('sendRequest', a:000)\nendfunction\n\nfunction! CocNotify(...) abort\n  return coc#rpc#request('sendNotification', a:000)\nendfunction\n\nfunction! CocRegisterNotification(id, method, cb) abort\n  call coc#on_notify(a:id, a:method, a:cb)\nendfunction\n\n\" Deprecated, use CocRegisterNotification instead\nfunction! CocRegistNotification(id, method, cb) abort\n  call coc#on_notify(a:id, a:method, a:cb)\nendfunction\n\nfunction! CocLocations(id, method, ...) abort\n  let args = [a:id, a:method] + copy(a:000)\n  return coc#rpc#request('findLocations', args)\nendfunction\n\nfunction! CocLocationsAsync(id, method, ...) abort\n  let args = [a:id, a:method] + copy(a:000)\n  return s:AsyncRequest('findLocations', args)\nendfunction\n\nfunction! CocRequestAsync(...)\n  return s:AsyncRequest('sendRequest', a:000)\nendfunction\n\nfunction! s:AsyncRequest(name, args) abort\n  let Cb = empty(a:args)? v:null : a:args[len(a:args) - 1]\n  if type(Cb) == 2\n    if !coc#rpc#ready()\n      call Cb('service not started', v:null)\n    else\n      call coc#rpc#request_async(a:name, a:args[0:-2], Cb)\n    endif\n    return ''\n  endif\n  call coc#rpc#notify(a:name, a:args)\n  return ''\nendfunction\n\nfunction! s:CommandList(...) abort\n  let list = coc#rpc#request('commandList', a:000)\n  return join(list, \"\\n\")\nendfunction\n\nfunction! s:ExtensionList(...) abort\n  let stats = CocAction('extensionStats')\n  call filter(stats, 'v:val[\"isLocal\"] == v:false')\n  let list = map(stats, 'v:val[\"id\"]')\n  return join(list, \"\\n\")\nendfunction\n\nfunction! s:SearchOptions(...) abort\n  let list = ['-e', '--regexp', '-F', '--fixed-strings', '-L', '--follow',\n        \\ '-g', '--glob', '--hidden', '--no-hidden', '--no-ignore-vcs',\n        \\ '--word-regexp', '-w', '--smart-case', '-S', '--no-config',\n        \\ '--line-regexp', '--no-ignore', '-x']\n  return join(list, \"\\n\")\nendfunction\n\nfunction! s:LoadedExtensions(...) abort\n  let list = CocAction('loadedExtensions')\n  return join(list, \"\\n\")\nendfunction\n\nfunction! s:InstallOptions(...)abort\n  let list = ['-terminal', '-sync']\n  return join(list, \"\\n\")\nendfunction\n\nfunction! s:OpenConfig()\n  let home = coc#util#get_config_home(1)\n  if !isdirectory(home)\n    echohl MoreMsg\n    echom 'Config directory \"'.home.'\" does not exist, create? (y/n)'\n    echohl None\n    let confirm = nr2char(getchar())\n    redraw!\n    if !(confirm ==? \"y\" || confirm ==? \"\\r\")\n      return\n    else\n      call mkdir(home, 'p')\n    end\n  endif\n  execute 'edit '.fnameescape(home.'/coc-settings.json')\n  call coc#rpc#notify('checkJsonExtension', [])\nendfunction\n\nfunction! s:get_color(item, fallback) abort\n  let t = type(a:item)\n  if t == 1\n    return a:item\n  endif\n  if t == 4\n    let item = get(a:item, 'gui', {})\n    let color = get(item, &background, a:fallback)\n    return type(color) == 1 ? color : a:fallback\n  endif\n  return a:fallback\nendfunction\n\nfunction! s:AddAnsiGroups() abort\n  let color_map = {}\n  let colors = ['#282828', '#cc241d', '#98971a', '#d79921', '#458588', '#b16286', '#689d6a', '#a89984', '#928374']\n  let names = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'grey']\n  for i in range(0, len(names) - 1)\n    let name = names[i]\n    if exists('g:terminal_ansi_colors')\n      let color_map[name] = s:get_color(get(g:terminal_ansi_colors, i, colors[i]), colors[i])\n    else\n      let color_map[name] = get(g:, 'terminal_color_'.i, colors[i])\n    endif\n  endfor\n  try\n    for name in keys(color_map)\n      let foreground = toupper(name[0]).name[1:]\n      let foregroundColor = color_map[name]\n      for key in keys(color_map)\n        let background = toupper(key[0]).key[1:]\n        let backgroundColor = color_map[key]\n        exe 'hi default CocList'.foreground.background.' guifg='.foregroundColor.' guibg='.backgroundColor\n      endfor\n      exe 'hi default CocListFg'.foreground. ' guifg='.foregroundColor. ' ctermfg='.foreground\n      exe 'hi default CocListBg'.foreground. ' guibg='.foregroundColor. ' ctermbg='.foreground\n    endfor\n  catch /.*/\n    \" ignore invalid color\n  endtry\nendfunction\n\nfunction! s:CreateHighlight(group, fg, bg) abort\n  let cmd = coc#hlgroup#compose(a:fg, a:bg)\n  if !empty(trim(cmd))\n    exe 'hi default '.a:group.' '.cmd\n  else\n    exe 'hi default link '.a:group.' '.a:fg\n  endif\nendfunction\n\nfunction! s:OpenDiagnostics(...) abort\n  let height = get(a:, 1, 0)\n  call coc#rpc#request('fillDiagnostics', [bufnr('%')])\n  if height\n    execute ':lopen '.height\n   else\n    lopen\n  endif\nendfunction\n\nfunction! s:Disable() abort\n  if get(g:, 'coc_enabled', 0) == 0\n    return\n  endif\n  autocmd! coc_nvim\n  call coc#rpc#notify('detach', [])\n  echohl MoreMsg\n    echom '[coc.nvim] Event disabled'\n  echohl None\n  let g:coc_enabled = 0\nendfunction\n\nfunction! s:Autocmd(...) abort\n  if !get(g:, 'coc_workspace_initialized', 0)\n    return\n  endif\n  call coc#rpc#notify('CocAutocmd', a:000)\nendfunction\n\nfunction! s:HandleBufEnter(bufnr) abort\n  if s:is_vim\n    call coc#api#Buf_flush(a:bufnr)\n  endif\n  call s:Autocmd('BufEnter', a:bufnr)\nendfunction\n\nfunction! s:HandleCharInsert(char, bufnr) abort\n  call s:Autocmd('InsertCharPre', a:char, a:bufnr)\nendfunction\n\nfunction! s:HandleTextChangedI(event, bufnr) abort\n  if s:is_vim\n    \" make sure lines event before changed event.\n    call coc#api#Buf_flush(a:bufnr)\n  endif\n  call s:Autocmd(a:event, a:bufnr, coc#util#change_info())\nendfunction\n\nfunction! s:HandleTextChanged(bufnr) abort\n  if s:is_vim\n    \" make sure lines event before changed event.\n    call coc#api#Buf_flush(a:bufnr)\n  endif\n  call s:Autocmd('TextChanged', a:bufnr, getbufvar(a:bufnr, 'changedtick'))\nendfunction\n\nfunction! s:HandleInsertLeave(bufnr) abort\n  call coc#pum#close()\n  call s:Autocmd('InsertLeave', a:bufnr)\nendfunction\n\nfunction! s:HandleWinScrolled(winid, event) abort\n  if getwinvar(a:winid, 'float', 0)\n    call coc#float#nvim_scrollbar(a:winid)\n  endif\n  if !empty(a:event)\n    let opt = get(a:event, 'all', {})\n    if get(opt, 'topline', 0) == 0 && get(opt, 'height', 0) == 0\n      \" visible region not changed.\n      return\n    endif\n    for key in keys(a:event)\n      let winid = str2nr(key)\n      if winid != 0\n        let info = getwininfo(winid)\n        if !empty(info)\n          call s:Autocmd('WinScrolled', winid, info[0]['bufnr'], [info[0]['topline'], info[0]['botline']])\n        endif\n      endif\n    endfor\n  else\n    \" v:event not exists on old version vim9\n    let info = getwininfo(a:winid)\n    if !empty(info)\n      call s:Autocmd('WinScrolled', a:winid, info[0]['bufnr'], [info[0]['topline'], info[0]['botline']])\n    endif\n  endif\nendfunction\n\nfunction! s:HandleWinClosed(winid) abort\n  call coc#float#on_close(a:winid)\n  call coc#notify#on_close(a:winid)\n  call s:Autocmd('WinClosed', a:winid)\nendfunction\n\nfunction! s:SyncAutocmd(...)\n  if !get(g:, 'coc_workspace_initialized', 0)\n    return\n  endif\n  call coc#rpc#request('CocAutocmd', a:000)\nendfunction\n\nfunction! s:VimLeavePre() abort\n  let g:coc_vim_leaving = 1\n  call s:Autocmd('VimLeavePre')\n  if s:is_vim && exists('$COC_NVIM_REMOTE_ADDRESS')\n    \" Helps to avoid connection error.\n    call coc#rpc#close_connection()\n    return\n  endif\n  if get(g:, 'coc_node_env', '') ==# 'test'\n    return\n  endif\n  if s:is_vim\n    call timer_start(1, { -> coc#client#kill('coc')})\n  endif\nendfunction\n\nfunction! s:VimEnter() abort\n  if coc#rpc#started()\n    if !exists('$COC_NVIM_REMOTE_ADDRESS')\n      call coc#rpc#notify('VimEnter', [join(coc#compat#list_runtime_paths(), \",\")])\n    endif\n  elseif get(g:, 'coc_start_at_startup', 1)\n    call coc#rpc#start_server()\n  endif\n  call s:Highlight()\nendfunction\n\nfunction! s:Enable(initialize)\n  if get(g:, 'coc_enabled', 0) == 1\n    return\n  endif\n\n  let g:coc_enabled = 1\n  sign define CocCurrentLine linehl=CocMenuSel\n  sign define CocListCurrent linehl=CocListLine\n  sign define CocTreeSelected linehl=CocTreeSelected\n  if s:is_vim\n    call coc#api#Tabpage_ids()\n  endif\n\n  augroup coc_nvim\n    autocmd!\n\n    if !v:vim_did_enter\n      autocmd VimEnter            * call s:VimEnter()\n    else\n      call s:Highlight()\n    endif\n    if s:is_vim\n      if exists('##DirChanged')\n        autocmd DirChanged        * call s:Autocmd('DirChanged', getcwd())\n      endif\n      if exists('##TerminalOpen')\n        autocmd TerminalOpen      * call s:Autocmd('TermOpen', +expand('<abuf>'))\n      endif\n      autocmd CursorMoved         list:///* call coc#list#select(bufnr('%'), line('.'))\n      autocmd TabNew              * call coc#api#Tabpage_ids()\n    else\n      autocmd DirChanged        * call s:Autocmd('DirChanged', get(v:event, 'cwd', ''))\n      autocmd TermOpen          * call s:Autocmd('TermOpen', +expand('<abuf>'))\n      autocmd WinEnter          * call coc#float#nvim_win_enter(win_getid())\n    endif\n    if exists('##CompleteChanged')\n      autocmd CompleteChanged   * call timer_start(1, { -> execute('if pumvisible() | call coc#pum#close() | endif')})\n    endif\n    autocmd CursorHold          * call coc#float#check_related()\n    if exists('##WinClosed')\n      autocmd WinClosed         * call s:HandleWinClosed(+expand('<amatch>'))\n    elseif exists('##TabEnter')\n      autocmd TabEnter          * call coc#notify#reflow()\n    endif\n    if exists('##WinScrolled')\n      autocmd WinScrolled         * call s:HandleWinScrolled(+expand('<amatch>'), v:event)\n    endif\n    autocmd ModeChanged         * call s:Autocmd('ModeChanged', v:event)\n    autocmd TabNew              * call s:Autocmd('TabNew', coc#compat#tabnr_id(tabpagenr()))\n    autocmd TabClosed           * call s:Autocmd('TabClosed', coc#compat#call('list_tabpages', []))\n    autocmd WinLeave            * call s:Autocmd('WinLeave', win_getid())\n    autocmd WinEnter            * call s:Autocmd('WinEnter', win_getid())\n    autocmd BufWinLeave         * call s:Autocmd('BufWinLeave', +expand('<abuf>'), bufwinid(+expand('<abuf>')))\n    autocmd BufWinEnter         * call s:Autocmd('BufWinEnter', +expand('<abuf>'), win_getid(), coc#window#visible_range(win_getid()))\n    autocmd FileType            * call s:Autocmd('FileType', expand('<amatch>'), +expand('<abuf>'))\n    autocmd InsertCharPre       * call s:HandleCharInsert(v:char, bufnr('%'))\n    autocmd TextChangedP        * call s:HandleTextChangedI('TextChangedP', +expand('<abuf>'))\n    autocmd TextChangedI        * call s:HandleTextChangedI('TextChangedI', +expand('<abuf>'))\n    autocmd TextChanged         * call s:HandleTextChanged(+expand('<abuf>'))\n    autocmd InsertLeave         * call s:HandleInsertLeave(+expand('<abuf>'))\n    autocmd BufEnter            * call s:HandleBufEnter(+expand('<abuf>'))\n    autocmd InsertEnter         * call s:Autocmd('InsertEnter', +expand('<abuf>'))\n    autocmd BufHidden           * call s:Autocmd('BufHidden', +expand('<abuf>'))\n    autocmd BufWritePost        * call s:Autocmd('BufWritePost', +expand('<abuf>'), getbufvar(+expand('<abuf>'), 'changedtick'))\n    autocmd CursorMoved         * call s:Autocmd('CursorMoved', +expand('<abuf>'), [line('.'), col('.')])\n    autocmd CursorMovedI        * call s:Autocmd('CursorMovedI', +expand('<abuf>'), [line('.'), col('.')])\n    autocmd CursorHold          * call s:Autocmd('CursorHold', +expand('<abuf>'), [line('.'), col('.')], win_getid())\n    autocmd CursorHoldI         * call s:Autocmd('CursorHoldI', +expand('<abuf>'), [line('.'), col('.')], win_getid())\n    autocmd BufNewFile,BufReadPost * call s:Autocmd('BufCreate', +expand('<abuf>'))\n    autocmd BufUnload           * call s:Autocmd('BufUnload', +expand('<abuf>'))\n    autocmd BufWritePre         * call s:SyncAutocmd('BufWritePre', +expand('<abuf>'), bufname(+expand('<abuf>')), getbufvar(+expand('<abuf>'), 'changedtick'))\n    autocmd FocusGained         * if mode() !~# '^c' | call s:Autocmd('FocusGained') | endif\n    autocmd FocusLost           * call s:Autocmd('FocusLost')\n    autocmd VimResized          * call s:Autocmd('VimResized', &columns, &lines)\n    autocmd VimLeavePre         * call s:VimLeavePre()\n    autocmd BufReadCmd,FileReadCmd,SourceCmd list://* call coc#list#setup(expand('<amatch>'))\n    autocmd BufWriteCmd __coc_refactor__* :call coc#rpc#notify('saveRefactor', [+expand('<abuf>')])\n    autocmd ColorScheme * call s:Highlight() | call s:Autocmd('ColorScheme')\n  augroup end\n  if a:initialize == 0\n     call coc#rpc#request('attach', [])\n     echohl MoreMsg\n     echom '[coc.nvim] Event enabled'\n     echohl None\n  endif\nendfunction\n\nfunction! s:StaticHighlight() abort\n  hi default CocSelectedText  ctermfg=Red     guifg=#fb4934 guibg=NONE\n  hi default CocCodeLens      ctermfg=Gray    guifg=#999999 guibg=NONE\n  hi default CocUnderline     term=underline cterm=underline gui=underline guisp=#ebdbb2\n  hi default CocBold          term=bold cterm=bold gui=bold\n  hi default CocItalic        term=italic cterm=italic gui=italic\n  hi default CocStrikeThrough term=strikethrough cterm=strikethrough gui=strikethrough\n  hi default CocMarkdownLink  ctermfg=Blue    guifg=#15aabf guibg=NONE\n  hi default CocDisabled      guifg=#999999   ctermfg=gray\n  hi default CocSearch        ctermfg=Blue    guifg=#15aabf guibg=NONE\n  hi default CocLink          term=underline cterm=underline gui=underline guisp=#15aabf\n  hi default link CocFloatActive         CocSearch\n  hi default link CocFadeOut             Conceal\n  hi default link CocMarkdownCode        markdownCode\n  hi default link CocMarkdownHeader      markdownH1\n  hi default link CocDeprecatedHighlight CocStrikeThrough\n  hi default link CocUnusedHighlight     CocFadeOut\n  hi default link CocListSearch          CocSearch\n  hi default link CocListMode            ModeMsg\n  hi default link CocListPath            Comment\n  hi default link CocHighlightText       CursorColumn\n  hi default link CocHoverRange          Search\n  hi default link CocCursorRange         Search\n  hi default link CocLinkedEditing       CocCursorRange\n  hi default link CocHighlightRead       CocHighlightText\n  hi default link CocHighlightWrite      CocHighlightText\n  \" Notification\n  hi default CocNotificationProgress  ctermfg=Blue    guifg=#15aabf guibg=NONE\n  hi default link CocNotificationButton  CocUnderline\n  hi default link CocNotificationKey     Comment\n  hi default link CocNotificationError   CocErrorFloat\n  hi default link CocNotificationWarning CocWarningFloat\n  hi default link CocNotificationInfo    CocInfoFloat\n  \" Snippet\n  hi default link CocSnippetVisual       Visual\n  \" Tree view highlights\n  hi default link CocTreeTitle       Title\n  hi default link CocTreeDescription Comment\n  hi default link CocTreeOpenClose   CocBold\n  hi default link CocTreeSelected    CursorLine\n  hi default link CocSelectedRange   CocHighlightText\n  \" Symbol highlights\n  hi default link CocSymbolDefault       MoreMsg\n  \"Pum\n  hi default link CocPumSearch           CocSearch\n  hi default link CocPumDetail           Comment\n  hi default link CocPumMenu             CocFloating\n  hi default link CocPumShortcut         Comment\n  hi default link CocPumDeprecated       CocStrikeThrough\n  hi default CocVirtualText             ctermfg=12 guifg=#504945\n  hi default link CocPumVirtualText        CocVirtualText\n  hi default link CocInputBoxVirtualText   CocVirtualText\n  hi default link CocFloatDividingLine     CocVirtualText\n  if &t_Co == 256\n    hi def CocInlineVirtualText guifg=#808080 ctermfg=244\n  else\n    hi def CocInlineVirtualText guifg=#808080 ctermfg=12\n  endif\n  hi def link CocInlineAnnotation MoreMsg\nendfunction\n\ncall s:StaticHighlight()\ncall s:AddAnsiGroups()\n\nfunction! s:Highlight() abort\n  let normalFloat = s:is_vim ? 'Pmenu' : 'NormalFloat'\n  if coc#hlgroup#get_contrast('Normal', normalFloat) > 2.0\n    exe 'hi default CocFloating '.coc#hlgroup#create_bg_command('Normal', &background ==# 'dark' ? -30 : 30)\n    exe 'hi default CocMenuSel '.coc#hlgroup#create_bg_command('CocFloating', &background ==# 'dark' ? -20 : 20)\n    exe 'hi default CocFloatThumb '.coc#hlgroup#create_bg_command('CocFloating', &background ==# 'dark' ? -40 : 40)\n    hi default link CocFloatSbar CocFloating\n  else\n    exe 'hi default link CocFloating '.normalFloat\n    if coc#hlgroup#get_contrast('CocFloating', 'PmenuSel') > 2.0\n      exe 'hi default CocMenuSel '.coc#hlgroup#create_bg_command('CocFloating', &background ==# 'dark' ? -30 : 30)\n    else\n      exe 'hi default CocMenuSel '.coc#hlgroup#get_hl_command(synIDtrans(hlID('PmenuSel')), 'bg', '237', '#13354A')\n    endif\n    hi default link CocFloatThumb        PmenuThumb\n    hi default link CocFloatSbar         PmenuSbar\n  endif\n  exe 'hi default link CocFloatBorder ' .. (hlexists('FloatBorder') ? 'FloatBorder' : 'CocFloating')\n  if coc#hlgroup#get_contrast('Normal', 'CursorLine') < 1.3\n    \" Avoid color too close\n    exe 'hi default CocListLine '.coc#hlgroup#create_bg_command('Normal', &background ==# 'dark' ? -20 : 20)\n  else\n    hi default link CocListLine            CursorLine\n  endif\n\n  if !s:is_vim\n    hi default CocCursorTransparent gui=strikethrough blend=100\n  endif\n\n  let sign_colors = {\n      \\ 'Error': ['Red', '#ff0000'],\n      \\ 'Warn': ['Brown', '#ff922b'],\n      \\ 'Info': ['Yellow', '#fab005'],\n      \\ 'Hint': ['Blue', '#15aabf']\n      \\ }\n  for name in ['Error', 'Warning', 'Info', 'Hint']\n    let suffix = name ==# 'Warning' ? 'Warn' : name\n    if hlexists('DiagnosticUnderline'.suffix)\n      exe 'hi default link Coc'.name.'Highlight DiagnosticUnderline'.suffix\n    else\n      exe 'hi default link Coc'.name.'Highlight CocUnderline'\n    endif\n    if hlexists('DiagnosticSign'.suffix)\n      exe 'hi default link Coc'.name.'Sign DiagnosticSign'.suffix\n    else\n      exe 'hi default Coc'.name.'Sign ctermfg='.sign_colors[suffix][0].' guifg='.sign_colors[suffix][1]\n    endif\n    if hlexists('DiagnosticVirtualText'.suffix)\n      exe 'hi default link Coc'.name.'VirtualText DiagnosticVirtualText'.suffix\n    else\n      call s:CreateHighlight('Coc'.name.'VirtualText', 'Coc'.name.'Sign', 'Normal')\n    endif\n    if hlexists('Diagnostic'.suffix)\n      exe 'hi default link Coc'.name.'Float Diagnostic'.suffix\n    else\n      call s:CreateHighlight('Coc'.name.'Float', 'Coc'.name.'Sign', 'CocFloating')\n    endif\n  endfor\n\n  call s:CreateHighlight('CocInlayHint', 'CocHintSign', 'SignColumn')\n  for name in ['Parameter', 'Type']\n    exe 'hi default link CocInlayHint'.name.' CocInlayHint'\n  endfor\n\n  if get(g:, 'coc_default_semantic_highlight_groups', 1)\n    let hlMap = {\n        \\ 'TypeNamespace': ['@module', 'Include'],\n        \\ 'TypeType': ['@type', 'Type'],\n        \\ 'TypeClass': ['@constructor', 'Special'],\n        \\ 'TypeEnum': ['@type', 'Type'],\n        \\ 'TypeInterface': ['@type', 'Type'],\n        \\ 'TypeStruct': ['@structure', 'Identifier'],\n        \\ 'TypeTypeParameter': ['@variable.parameter', 'Identifier'],\n        \\ 'TypeParameter': ['@variable.parameter', 'Identifier'],\n        \\ 'TypeVariable': ['@variable', 'Identifier'],\n        \\ 'TypeProperty': ['@property', 'Identifier'],\n        \\ 'TypeEnumMember': ['@property', 'Constant'],\n        \\ 'TypeEvent': ['@keyword', 'Keyword'],\n        \\ 'TypeFunction': ['@function', 'Function'],\n        \\ 'TypeMethod': ['@function.method', 'Function'],\n        \\ 'TypeMacro': ['@constant.macro', 'Define'],\n        \\ 'TypeKeyword': ['@keyword', 'Keyword'],\n        \\ 'TypeModifier': ['@keyword.storage', 'StorageClass'],\n        \\ 'TypeComment': ['@comment', 'Comment'],\n        \\ 'TypeString': ['@string', 'String'],\n        \\ 'TypeNumber': ['@number', 'Number'],\n        \\ 'TypeBoolean': ['@boolean', 'Boolean'],\n        \\ 'TypeRegexp': ['@string.regexp', 'String'],\n        \\ 'TypeOperator': ['@operator', 'Operator'],\n        \\ 'TypeDecorator': ['@string.special.symbol', 'Identifier'],\n        \\ 'ModDeprecated': ['@markup.strikethrough', 'CocDeprecatedHighlight']\n        \\ }\n    for [key, value] in items(hlMap)\n      let ts = get(value, 0, '')\n      let fallback = get(value, 1, '')\n      execute 'hi default link CocSem'.key.' '.(coc#hlgroup#valid(ts) ? ts : fallback)\n    endfor\n  endif\n  let symbolMap = {\n      \\ 'Keyword': ['@keyword', 'Keyword'],\n      \\ 'Namespace': ['@module', 'Include'],\n      \\ 'Class': ['@constructor', 'Special'],\n      \\ 'Method': ['@method', 'Function'],\n      \\ 'Property': ['@property', 'Identifier'],\n      \\ 'Text': ['@text', 'CocSymbolDefault'],\n      \\ 'Unit': ['@unit', 'CocSymbolDefault'],\n      \\ 'Value': ['@value', 'CocSymbolDefault'],\n      \\ 'Snippet': ['@snippet', 'CocSymbolDefault'],\n      \\ 'Color': ['@color', 'Float'],\n      \\ 'Reference': ['@text.reference', 'Constant'],\n      \\ 'Folder': ['@folder', 'CocSymbolDefault'],\n      \\ 'File': ['@file', 'Statement'],\n      \\ 'Module': ['@module', 'Statement'],\n      \\ 'Package': ['@package', 'Statement'],\n      \\ 'Field': ['@variable.member', 'Identifier'],\n      \\ 'Constructor': ['@constructor', 'Special'],\n      \\ 'Enum': ['@type', 'CocSymbolDefault'],\n      \\ 'Interface': ['@type', 'CocSymbolDefault'],\n      \\ 'Function': ['@function', 'Function'],\n      \\ 'Variable': ['@variable.builtin', 'Special'],\n      \\ 'Constant': ['@constant', 'Constant'],\n      \\ 'String': ['@string', 'String'],\n      \\ 'Number': ['@number', 'Number'],\n      \\ 'Boolean': ['@boolean', 'Boolean'],\n      \\ 'Array': ['@array', 'CocSymbolDefault'],\n      \\ 'Object': ['@object', 'CocSymbolDefault'],\n      \\ 'Key': ['@key', 'Identifier'],\n      \\ 'Null': ['@null', 'Type'],\n      \\ 'EnumMember': ['@property', 'Identifier'],\n      \\ 'Struct': ['@structure', 'Keyword'],\n      \\ 'Event': ['@constant', 'Constant'],\n      \\ 'Operator': ['@operator', 'Operator'],\n      \\ 'TypeParameter': ['@variable.parameter', 'Identifier'],\n      \\ }\n  for [key, value] in items(symbolMap)\n    let hlGroup = coc#hlgroup#valid(value[0]) ? value[0] : get(value, 1, 'CocSymbolDefault')\n    if hlexists(hlGroup)\n      execute 'hi default CocSymbol'.key.' '.coc#hlgroup#get_hl_command(synIDtrans(hlID(hlGroup)), 'fg', '223', '#ebdbb2')\n    endif\n  endfor\nendfunction\n\nfunction! s:ShowInfo()\n  if coc#rpc#ready()\n    call coc#rpc#notify('showInfo', [])\n  else\n    let lines = []\n    echomsg 'coc.nvim service not started, checking environment...'\n    let node = get(g:, 'coc_node_path', $COC_NODE_PATH == '' ? 'node' : $COC_NODE_PATH)\n    if !executable(node)\n      call add(lines, 'Error: '.node.' is not executable!')\n    else\n      let output = trim(system(node . ' --version'))\n      let ms = matchlist(output, 'v\\(\\d\\+\\).\\(\\d\\+\\).\\(\\d\\+\\)')\n      if empty(ms) || str2nr(ms[1]) < 16 || (str2nr(ms[1]) == 16 && str2nr(ms[2]) < 18)\n        call add(lines, 'Error: Node version '.output.' < 16.18.0, please upgrade node.js')\n      endif\n    endif\n    \" check bundle\n    let file = s:root.'/build/index.js'\n    if !filereadable(file)\n      call add(lines, 'Error: javascript bundle not found, please compile code of coc.nvim by esbuild.')\n    endif\n    if !empty(lines)\n      botright vnew\n      setl filetype=nofile\n      call setline(1, lines)\n    else\n      if get(g:, 'coc_start_at_startup',1)\n        echohl MoreMsg | echon 'Service stopped for some unknown reason, try :CocStart' | echohl None\n      else\n        echohl MoreMsg | echon 'Start on startup is disabled, try :CocStart' | echohl None\n      endif\n    endif\n  endif\nendfunction\n\nfunction! s:CursorRangeFromSelected(type, ...) abort\n  \" add range by operator\n  call coc#rpc#request('cursorsSelect', [bufnr('%'), 'operator', a:type])\nendfunction\n\nfunction! s:FormatFromSelected(type)\n  call CocActionAsync('formatSelected', a:type)\nendfunction\n\nfunction! s:CodeActionFromSelected(type)\n  call CocActionAsync('codeAction', a:type)\nendfunction\n\nfunction! s:CodeActionRefactorFromSelected(type)\n  call CocActionAsync('codeAction', a:type, ['refactor'] ,v:true)\nendfunction\n\ncommand! -nargs=0 CocOutline      :call coc#rpc#notify('showOutline', [])\ncommand! -nargs=? CocDiagnostics  :call s:OpenDiagnostics(<f-args>)\ncommand! -nargs=0 CocInfo         :call s:ShowInfo()\ncommand! -nargs=0 CocOpenLog      :call coc#rpc#notify('openLog',  [])\ncommand! -nargs=0 CocDisable      :call s:Disable()\ncommand! -nargs=0 CocEnable       :call s:Enable(0)\ncommand! -nargs=0 CocConfig       :call s:OpenConfig()\ncommand! -nargs=0 CocLocalConfig  :call coc#rpc#notify('openLocalConfig', [])\ncommand! -nargs=0 CocRestart      :call coc#rpc#restart()\ncommand! -nargs=0 CocStart        :call coc#rpc#start_server()\ncommand! -nargs=0 CocPrintErrors  :call coc#rpc#show_errors()\ncommand! -nargs=1 -complete=custom,s:LoadedExtensions  CocWatch    :call coc#rpc#notify('watchExtension', [<f-args>])\ncommand! -nargs=+ -complete=custom,s:SearchOptions  CocSearch    :call coc#rpc#notify('search', [<f-args>])\ncommand! -nargs=+ -complete=custom,s:ExtensionList  CocUninstall :call CocActionAsync('uninstallExtension', <f-args>)\ncommand! -nargs=* -complete=custom,s:CommandList -range CocCommand :call coc#rpc#notify('runCommand', [<f-args>])\ncommand! -nargs=* -complete=custom,coc#list#options CocList      :call coc#rpc#notify('openList',  [<f-args>])\ncommand! -nargs=? -complete=custom,coc#list#names CocListResume   :call coc#rpc#notify('listResume', [<f-args>])\ncommand! -nargs=? -complete=custom,coc#list#names CocListCancel   :call coc#rpc#notify('listCancel', [])\ncommand! -nargs=? -complete=custom,coc#list#names CocPrev         :call coc#rpc#notify('listPrev', [<f-args>])\ncommand! -nargs=? -complete=custom,coc#list#names CocNext         :call coc#rpc#notify('listNext', [<f-args>])\ncommand! -nargs=? -complete=custom,coc#list#names CocFirst        :call coc#rpc#notify('listFirst', [<f-args>])\ncommand! -nargs=? -complete=custom,coc#list#names CocLast         :call coc#rpc#notify('listLast', [<f-args>])\ncommand! -nargs=0 CocUpdate       :call coc#util#update_extensions(1)\ncommand! -nargs=0 -bar CocUpdateSync   :call coc#util#update_extensions()\ncommand! -nargs=* -bar -complete=custom,s:InstallOptions CocInstall   :call coc#util#install_extension([<f-args>])\n\ncall s:Enable(1)\naugroup coc_dynamic_autocmd\n  autocmd!\naugroup END\naugroup coc_dynamic_content\n  autocmd!\naugroup END\naugroup coc_dynamic_option\n  autocmd!\naugroup END\n\n\" Default key-mappings for completion\nif empty(mapcheck('<C-n>', 'i'))\n  inoremap <silent><expr> <C-n> coc#pum#visible() ? coc#pum#next(1) : coc#inline#visible() ? coc#inline#next() : \"\\<C-n>\"\nendif\nif empty(mapcheck('<C-p>', 'i'))\n  inoremap <silent><expr> <C-p> coc#pum#visible() ? coc#pum#prev(1) : coc#inline#visible() ? coc#inline#prev() : \"\\<C-p>\"\nendif\nif empty(mapcheck('<down>', 'i'))\n  inoremap <silent><expr> <down> coc#pum#visible() ? coc#pum#next(0) : coc#inline#visible() ? coc#inline#next() :\"\\<down>\"\nendif\nif empty(mapcheck('<up>', 'i'))\n  inoremap <silent><expr> <up> coc#pum#visible() ? coc#pum#prev(0) : coc#inline#visible() ? coc#inline#prev() :\"\\<up>\"\nendif\nif empty(mapcheck('<C-e>', 'i'))\n  inoremap <silent><expr> <C-e> coc#pum#visible() ? coc#pum#cancel() : coc#inline#visible() ? coc#inline#cancel() : \"\\<C-e>\"\nendif\nif empty(mapcheck('<C-y>', 'i'))\n  inoremap <silent><expr> <C-y> coc#pum#visible() ? coc#pum#confirm() : coc#inline#visible() ? coc#inline#accept() :\"\\<C-y>\"\nendif\nif empty(mapcheck('<PageDown>', 'i'))\n  inoremap <silent><expr> <PageDown> coc#pum#visible() ? coc#pum#scroll(1) : \"\\<PageDown>\"\nendif\nif empty(mapcheck('<PageUp>', 'i'))\n  inoremap <silent><expr> <PageUp> coc#pum#visible() ? coc#pum#scroll(0) : \"\\<PageUp>\"\nendif\n\nvnoremap <silent> <Plug>(coc-range-select)          :<C-u>call       CocActionAsync('rangeSelect',     visualmode(), v:true)<CR>\nvnoremap <silent> <Plug>(coc-range-select-backward) :<C-u>call       CocActionAsync('rangeSelect',     visualmode(), v:false)<CR>\nnnoremap <Plug>(coc-range-select)                   :<C-u>call       CocActionAsync('rangeSelect',     '', v:true)<CR>\nnnoremap <Plug>(coc-codelens-action)                :<C-u>call       CocActionAsync('codeLensAction')<CR>\nvnoremap <silent> <Plug>(coc-format-selected)       :<C-u>call       CocActionAsync('formatSelected', visualmode())<CR>\nvnoremap <silent> <Plug>(coc-codeaction-selected)   :<C-u>call       CocActionAsync('codeAction', visualmode())<CR>\nvnoremap <Plug>(coc-codeaction-refactor-selected)   :<C-u>call       CocActionAsync('codeAction', visualmode(), ['refactor'], v:true)<CR>\nnnoremap <Plug>(coc-codeaction-selected)            :<C-u>set        operatorfunc=<SID>CodeActionFromSelected<CR>g@\nnnoremap <Plug>(coc-codeaction-refactor-selected)   :<C-u>set        operatorfunc=<SID>CodeActionRefactorFromSelected<CR>g@\nnnoremap <Plug>(coc-codeaction)                     :<C-u>call       CocActionAsync('codeAction', '')<CR>\nnnoremap <Plug>(coc-codeaction-line)                :<C-u>call       CocActionAsync('codeAction', 'currline')<CR>\nnnoremap <Plug>(coc-codeaction-cursor)              :<C-u>call       CocActionAsync('codeAction', 'cursor')<CR>\nnnoremap <Plug>(coc-codeaction-refactor)            :<C-u>call       CocActionAsync('codeAction', 'cursor', ['refactor'], v:true)<CR>\nnnoremap <Plug>(coc-codeaction-source)              :<C-u>call       CocActionAsync('codeAction', '', ['source'], v:true)<CR>\nnnoremap <silent> <Plug>(coc-rename)                :<C-u>call       CocActionAsync('rename')<CR>\nnnoremap <silent> <Plug>(coc-format-selected)       :<C-u>set        operatorfunc=<SID>FormatFromSelected<CR>g@\nnnoremap <silent> <Plug>(coc-format)                :<C-u>call       CocActionAsync('format')<CR>\nnnoremap <silent> <Plug>(coc-diagnostic-info)       :<C-u>call       CocActionAsync('diagnosticInfo')<CR>\nnnoremap <silent> <Plug>(coc-diagnostic-next)       :<C-u>call       CocActionAsync('diagnosticNext')<CR>\nnnoremap <silent> <Plug>(coc-diagnostic-prev)       :<C-u>call       CocActionAsync('diagnosticPrevious')<CR>\nnnoremap <silent> <Plug>(coc-diagnostic-next-error) :<C-u>call       CocActionAsync('diagnosticNext',     'error')<CR>\nnnoremap <silent> <Plug>(coc-diagnostic-prev-error) :<C-u>call       CocActionAsync('diagnosticPrevious', 'error')<CR>\nnnoremap <silent> <Plug>(coc-definition)            :<C-u>call       CocActionAsync('jumpDefinition')<CR>\nnnoremap <silent> <Plug>(coc-declaration)           :<C-u>call       CocActionAsync('jumpDeclaration')<CR>\nnnoremap <silent> <Plug>(coc-implementation)        :<C-u>call       CocActionAsync('jumpImplementation')<CR>\nnnoremap <silent> <Plug>(coc-type-definition)       :<C-u>call       CocActionAsync('jumpTypeDefinition')<CR>\nnnoremap <silent> <Plug>(coc-references)            :<C-u>call       CocActionAsync('jumpReferences')<CR>\nnnoremap <silent> <Plug>(coc-references-used)       :<C-u>call       CocActionAsync('jumpUsed')<CR>\nnnoremap <silent> <Plug>(coc-openlink)              :<C-u>call       CocActionAsync('openLink')<CR>\nnnoremap <silent> <Plug>(coc-fix-current)           :<C-u>call       CocActionAsync('doQuickfix')<CR>\nnnoremap <silent> <Plug>(coc-float-hide)            :<C-u>call       coc#float#close_all()<CR>\nnnoremap <silent> <Plug>(coc-float-jump)            :<c-u>call       coc#float#jump()<cr>\nnnoremap <silent> <Plug>(coc-command-repeat)        :<C-u>call       CocAction('repeatCommand')<CR>\nnnoremap <silent> <Plug>(coc-refactor)              :<C-u>call       CocActionAsync('refactor')<CR>\n\nnnoremap <silent> <Plug>(coc-cursors-operator) :<C-u>set operatorfunc=<SID>CursorRangeFromSelected<CR>g@\nvnoremap <silent> <Plug>(coc-cursors-range)    :<C-u>call CocAction('cursorsSelect', bufnr('%'), 'range', visualmode())<CR>\nnnoremap <silent> <Plug>(coc-cursors-word)     :<C-u>call CocAction('cursorsSelect', bufnr('%'), 'word', 'n')<CR>\nnnoremap <silent> <Plug>(coc-cursors-position) :<C-u>call CocAction('cursorsSelect', bufnr('%'), 'position', 'n')<CR>\n\nvnoremap <silent> <Plug>(coc-funcobj-i)        :<C-U>call CocAction('selectSymbolRange', v:true, visualmode(), ['Method', 'Function'])<CR>\nvnoremap <silent> <Plug>(coc-funcobj-a)        :<C-U>call CocAction('selectSymbolRange', v:false, visualmode(), ['Method', 'Function'])<CR>\nonoremap <silent> <Plug>(coc-funcobj-i)        :<C-U>call CocAction('selectSymbolRange', v:true, '', ['Method', 'Function'])<CR>\nonoremap <silent> <Plug>(coc-funcobj-a)        :<C-U>call CocAction('selectSymbolRange', v:false, '', ['Method', 'Function'])<CR>\n\nvnoremap <silent> <Plug>(coc-classobj-i)       :<C-U>call CocAction('selectSymbolRange', v:true, visualmode(), ['Interface', 'Struct', 'Class'])<CR>\nvnoremap <silent> <Plug>(coc-classobj-a)       :<C-U>call CocAction('selectSymbolRange', v:false, visualmode(), ['Interface', 'Struct', 'Class'])<CR>\nonoremap <silent> <Plug>(coc-classobj-i)       :<C-U>call CocAction('selectSymbolRange', v:true, '', ['Interface', 'Struct', 'Class'])<CR>\nonoremap <silent> <Plug>(coc-classobj-a)       :<C-U>call CocAction('selectSymbolRange', v:false, '', ['Interface', 'Struct', 'Class'])<CR>\n"
  },
  {
    "path": "release.sh",
    "content": "#!/bin/sh\n\nset -e\n[ \"$TRACE\" ] && set -x\n\ngit checkout master\nnpm run lint:typecheck\nif [ $? -ne 0 ]; then\n  echo \"tsc 类型检查未通过\"\n  exit 1\nfi\n\nnpm run lint\nif [ $? -ne 0 ]; then\n  echo \"eslint 检查未通过\"\n  exit 1\nfi\nnpm test\n\nif [ $? -eq 0 ]; then\n  git config --global user.name \"chemzqm\"\n  git config --global user.email \"chemzqm@users.noreply.github.com\"\n  git fetch origin release --depth=1\n  commitmsg=$(git log --oneline -1)\n  mkdir -p .release\n  cp -r .github bin lua build autoload plugin history.md README.md doc .release\n  git checkout release\n  cp -r .release/* .\n  nvim -c 'helptags doc|q'\n\n  changes=$(git status --porcelain)\n  if [ -n \"$changes\" ]; then\n    git add .\n    git commit -m \"$commitmsg\"\n    git push origin release\n  else\n    echo \"No need to commit.\"\n  fi\nfi\n"
  },
  {
    "path": "src/__tests__/autoload/coc/source/email.vim",
    "content": "\" vim source for emails\nfunction! coc#source#email#init() abort\n  return {\n        \\ 'priority': 9,\n        \\ 'shortcut': 'Email',\n        \\ 'triggerCharacters': ['@']\n        \\}\nendfunction\n\nfunction! coc#source#email#should_complete(opt) abort\n  return 1\nendfunction\n\nfunction! coc#source#email#complete(opt, cb) abort\n  let items = ['foo@gmail.com', 'bar@yahoo.com']\n  call a:cb(items)\nendfunction\n"
  },
  {
    "path": "src/__tests__/autoload/coc/source/vim9.vim",
    "content": "vim9script\nexport def Init(): dict<any>\n  return {\n    priority: 9,\n    shortcut: 'Email',\n    triggerCharacters: ['@']\n  }\nenddef\n\nexport def Complete(option: dict<any>, Callback: func(list<any>))\n  const items = ['foo@gmail.com', 'bar@yahoo.com']\n  Callback(items)\nenddef\n"
  },
  {
    "path": "src/__tests__/autoload/legacy.vim",
    "content": "\nfunction legacy#dict_add() dict\n  return self.key + 1\nendfunction\n\nfunction legacy#win_execute() abort\n  call win_execute(win_getid(), ['let w:foo = \"a\".\"b\"'])\nendfunction\n"
  },
  {
    "path": "src/__tests__/autoload/vim9.vim",
    "content": "vim9script\nscriptencoding utf-8\n\nexport def WinExecute(): any\n  win_execute(win_getid(), 'cursor(1, 1)')\n  return v:true\nenddef\n\nexport def Execute(cmd: string): void\n  execute(cmd)\nenddef\n\ndefcompile\n"
  },
  {
    "path": "src/__tests__/client/configuration.test.ts",
    "content": "import path from 'path'\nimport { DidChangeConfigurationNotification, DocumentSelector } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport { SyncConfigurationFeature } from '../../language-client/configuration'\nimport { LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from '../../language-client/index'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nfunction createClient(section: string | string[] | undefined, middleware: Middleware = {}, opts: Partial<LanguageClientOptions> = {}): LanguageClient {\n  const serverModule = path.join(__dirname, './server/configServer.js')\n  const serverOptions: ServerOptions = {\n    run: { module: serverModule, transport: TransportKind.ipc },\n    debug: { module: serverModule, transport: TransportKind.ipc, options: { execArgv: ['--nolazy', '--inspect=6014'] } }\n  }\n\n  const documentSelector: DocumentSelector = [{ scheme: 'file' }]\n  const clientOptions: LanguageClientOptions = Object.assign({\n    documentSelector,\n    synchronize: {\n      configurationSection: section\n    },\n    initializationOptions: {},\n    middleware\n  }, opts)\n\n  const result = new LanguageClient('test', 'Test Language Server', serverOptions, clientOptions)\n  return result\n}\n\nbeforeAll(async () => {\n  await helper.setup()\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\ndescribe('pull configuration feature', () => {\n  let client: LanguageClient\n  beforeAll(async () => {\n    client = createClient(undefined)\n    await client.start()\n  })\n\n  afterAll(async () => {\n    await client.stop()\n  })\n\n  it('should request all configuration', async () => {\n    let config: any\n    client.middleware.workspace = client.middleware.workspace ?? {}\n    client.middleware.workspace.configuration = (params, token, next) => {\n      config = next(params, token)\n      return config\n    }\n    await client.sendNotification('pull0')\n    await helper.waitValue(() => {\n      return config != null\n    }, true)\n    expect(config[0].http).toBeDefined()\n  })\n\n  it('should request configurations with sections', async () => {\n    let config: any\n    client.middleware.workspace = client.middleware.workspace ?? {}\n    client.middleware.workspace.configuration = (params, token, next) => {\n      config = next(params, token)\n      return config\n    }\n    await client.sendNotification('pull1')\n    await helper.waitValue(() => {\n      return config?.length\n    }, 3)\n    expect(config[1]).toBeNull()\n    expect(config[0].proxy).toBeDefined()\n    expect(config[2]).toBeNull()\n  })\n})\n\ndescribe('publish configuration feature', () => {\n  it('should send configuration for languageserver', async () => {\n    let client: LanguageClient\n    client = createClient('languageserver.cpp.settings')\n    let changed\n    client.onNotification('configurationChange', params => {\n      changed = params\n    })\n    await client.start()\n    await helper.waitValue(() => {\n      return changed != null\n    }, true)\n    expect(changed).toEqual({ settings: {} })\n    await client.stop()\n  })\n\n  it('should get configuration from workspace folder', async () => {\n    let folder = path.resolve(__dirname, '../sample')\n    workspace.workspaceFolderControl.addWorkspaceFolder(folder, false)\n    let configFilePath = path.join(folder, '.vim/coc-settings.json')\n    workspace.configurations.addFolderFile(configFilePath, false, folder)\n    let client = createClient('coc.preferences', {}, {\n      workspaceFolder: { name: 'sample', uri: URI.file(folder).toString() }\n    })\n    let changed\n    client.onNotification('configurationChange', params => {\n      changed = params\n    })\n    await client.start()\n    await helper.waitValue(() => {\n      return changed != null\n    }, true)\n    expect(changed.settings.coc.preferences.rootPath).toBe('./src')\n    workspace.workspaceFolderControl.removeWorkspaceFolder(folder)\n    let feature = client.getFeature(DidChangeConfigurationNotification.method)\n    feature.dispose()\n    await client.stop()\n  })\n\n  it('should send configuration for specific sections', async () => {\n    let client: LanguageClient\n    let called = false\n    client = createClient(['coc.preferences', 'npm', 'unknown'], {\n      workspace: {\n        didChangeConfiguration: (sections, next) => {\n          called = true\n          return next(sections)\n        }\n      }\n    })\n    let changed\n    client.onNotification('configurationChange', params => {\n      changed = params\n    })\n    await client.start()\n    await helper.waitValue(() => {\n      return called\n    }, true)\n    await helper.waitValue(() => {\n      return changed != null\n    }, true)\n    expect(changed.settings.coc).toBeDefined()\n    expect(changed.settings.npm).toBeDefined()\n    let { configurations } = workspace\n    configurations.updateMemoryConfig({ 'npm.binPath': 'cnpm' })\n    await helper.waitValue(() => {\n      return changed.settings.npm?.binPath\n    }, 'cnpm')\n    await client.stop()\n  })\n\n  it('should catch reject error', async () => {\n    let client: LanguageClient\n    let called = false\n    client = createClient(['cpp'], {\n      workspace: {\n        didChangeConfiguration: () => {\n          return Promise.reject(new Error('custom error'))\n        }\n      }\n    })\n    let changed\n    client.onNotification('configurationChange', params => {\n      changed = params\n    })\n    await client.start()\n    await helper.wait(50)\n    expect(called).toBe(false)\n    void client.stop()\n    await client.stop()\n  })\n\n  it('should send null settings', async () => {\n    let client: LanguageClient\n    client = createClient(['cpp'], {\n      workspace: {\n        didChangeConfiguration: (_sections, next) => {\n          return next(null)\n        }\n      }\n    })\n    let changed\n    client.onNotification('configurationChange', params => {\n      changed = params\n    })\n    await client.start()\n    await helper.waitValue(() => changed != null, true)\n    expect(changed).toEqual({ settings: null })\n    await client.stop()\n  })\n\n  it('should extractSettingsInformation', async () => {\n    let res = SyncConfigurationFeature.extractSettingsInformation(['http.proxy', 'http.proxyCA'])\n    expect(res.http).toBeDefined()\n    expect(res.http.proxy).toBeDefined()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/client/connection.test.ts",
    "content": "import { Duplex } from 'stream'\nimport { createProtocolConnection, ProgressType, DocumentSymbolParams, DocumentSymbolRequest, InitializeParams, InitializeRequest, InitializeResult, ProtocolConnection, StreamMessageReader, StreamMessageWriter } from 'vscode-languageserver-protocol/node'\nimport { SymbolInformation, SymbolKind } from 'vscode-languageserver-types'\nimport { NullLogger } from '../../language-client/client'\n\nclass TestStream extends Duplex {\n  public _write(chunk: string, _encoding: string, done: () => void): void {\n    this.emit('data', chunk)\n    done()\n  }\n\n  public _read(_size: number): void {\n  }\n}\n\nlet serverConnection: ProtocolConnection\nlet clientConnection: ProtocolConnection\nlet progressType: ProgressType<any> = new ProgressType()\n\nbeforeEach(() => {\n  const up = new TestStream()\n  const down = new TestStream()\n  const logger = new NullLogger()\n  serverConnection = createProtocolConnection(new StreamMessageReader(up), new StreamMessageWriter(down), logger)\n  clientConnection = createProtocolConnection(new StreamMessageReader(down), new StreamMessageWriter(up), logger)\n  serverConnection.listen()\n  clientConnection.listen()\n})\n\nafterEach(() => {\n  serverConnection.dispose()\n  clientConnection.dispose()\n})\n\ndescribe('Connection Tests', () => {\n  it('should ensure proper param passing', async () => {\n    let paramsCorrect = false\n    serverConnection.onRequest(InitializeRequest.type, params => {\n      paramsCorrect = !Array.isArray(params)\n      let result: InitializeResult = {\n        capabilities: {\n        }\n      }\n      return result\n    })\n\n    const init: InitializeParams = {\n      rootUri: 'file:///home/dirkb',\n      processId: 1,\n      capabilities: {},\n      workspaceFolders: null,\n    }\n    await clientConnection.sendRequest(InitializeRequest.type, init)\n    expect(paramsCorrect).toBe(true)\n  })\n\n  it('should provide token', async () => {\n    serverConnection.onRequest(DocumentSymbolRequest.type, params => {\n      expect(params.partialResultToken).toBe('3b1db4c9-e011-489e-a9d1-0653e64707c2')\n      return []\n    })\n\n    const params: DocumentSymbolParams = {\n      textDocument: { uri: 'file:///abc.txt' },\n      partialResultToken: '3b1db4c9-e011-489e-a9d1-0653e64707c2'\n    }\n    await clientConnection.sendRequest(DocumentSymbolRequest.type, params)\n  })\n\n  it('should report result', async () => {\n    let result: SymbolInformation = {\n      name: 'abc',\n      kind: SymbolKind.Class,\n      location: {\n        uri: 'file:///abc.txt',\n        range: { start: { line: 0, character: 1 }, end: { line: 2, character: 3 } }\n      }\n    }\n    serverConnection.onRequest(DocumentSymbolRequest.type, async params => {\n      expect(params.partialResultToken).toBe('3b1db4c9-e011-489e-a9d1-0653e64707c2')\n      await serverConnection.sendProgress(progressType, params.partialResultToken, [result])\n      return []\n    })\n\n    const params: DocumentSymbolParams = {\n      textDocument: { uri: 'file:///abc.txt' },\n      partialResultToken: '3b1db4c9-e011-489e-a9d1-0653e64707c2'\n    }\n    let progressOK = false\n    clientConnection.onProgress(progressType, '3b1db4c9-e011-489e-a9d1-0653e64707c2', values => {\n      progressOK = (values !== undefined && values.length === 1)\n    })\n    await clientConnection.sendRequest(DocumentSymbolRequest.type, params)\n    expect(progressOK).toBeTruthy()\n  })\n\n  it('should provide workDoneToken', async () => {\n    serverConnection.onRequest(DocumentSymbolRequest.type, params => {\n      expect(params.workDoneToken).toBe('3b1db4c9-e011-489e-a9d1-0653e64707c2')\n      return []\n    })\n\n    const params: DocumentSymbolParams = {\n      textDocument: { uri: 'file:///abc.txt' },\n      workDoneToken: '3b1db4c9-e011-489e-a9d1-0653e64707c2'\n    }\n    await clientConnection.sendRequest(DocumentSymbolRequest.type, params)\n  })\n\n  it('should report work done progress', async () => {\n    serverConnection.onRequest(DocumentSymbolRequest.type, async params => {\n      expect(params.workDoneToken).toBe('3b1db4c9-e011-489e-a9d1-0653e64707c2')\n      await serverConnection.sendProgress(progressType, params.workDoneToken, {\n        kind: 'begin',\n        title: 'progress'\n      })\n      await serverConnection.sendProgress(progressType, params.workDoneToken, {\n        kind: 'report',\n        message: 'message'\n      })\n      await serverConnection.sendProgress(progressType, params.workDoneToken, {\n        kind: 'end',\n        message: 'message'\n      })\n      return []\n    })\n\n    const params: DocumentSymbolParams = {\n      textDocument: { uri: 'file:///abc.txt' },\n      workDoneToken: '3b1db4c9-e011-489e-a9d1-0653e64707c2'\n    }\n    let result = ''\n    clientConnection.onProgress(progressType, '3b1db4c9-e011-489e-a9d1-0653e64707c2', value => {\n      result += value.kind\n    })\n    await clientConnection.sendRequest(DocumentSymbolRequest.type, params)\n    expect(result).toBe('beginreportend')\n  })\n})\n"
  },
  {
    "path": "src/__tests__/client/converter.test.ts",
    "content": "import { CompletionTriggerKind, Position, TextDocumentItem, TextDocumentSaveReason } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { URI } from 'vscode-uri'\nimport * as c2p from '../../language-client/utils/codeConverter'\n\ndescribe('converter', () => {\n\n  function createDocument(): TextDocument {\n    return TextDocument.create('file:///1', 'css', 1, '')\n  }\n\n  it('should convertToTextDocumentItem', () => {\n    const cv = c2p.createConverter()\n    let doc = createDocument()\n    expect(cv.asTextDocumentItem(doc).uri).toBe(doc.uri)\n    expect(TextDocumentItem.is(cv.asTextDocumentItem(doc))).toBe(true)\n  })\n\n  it('should asCloseTextDocumentParams', () => {\n    const cv = c2p.createConverter()\n    let doc = createDocument()\n    expect(cv.asCloseTextDocumentParams(doc).textDocument.uri).toBe(doc.uri)\n  })\n\n  it('should asChangeTextDocumentParams', () => {\n    let doc = createDocument()\n    const cv = c2p.createConverter()\n    expect(cv.asFullChangeTextDocumentParams(doc).textDocument.uri).toBe(doc.uri)\n  })\n\n  it('should asWillSaveTextDocumentParams', () => {\n    const cv = c2p.createConverter()\n    let res = cv.asWillSaveTextDocumentParams({ document: createDocument(), bufnr: 1, reason: TextDocumentSaveReason.Manual, waitUntil: () => {} })\n    expect(res.textDocument).toBeDefined()\n    expect(res.reason).toBeDefined()\n  })\n\n  it('should asVersionedTextDocumentIdentifier', () => {\n    const cv = c2p.createConverter()\n    let res = cv.asVersionedTextDocumentIdentifier(createDocument())\n    expect(res.uri).toBeDefined()\n    expect(res.version).toBeDefined()\n  })\n\n  it('should asSaveTextDocumentParams', () => {\n    const cv = c2p.createConverter()\n    let res = cv.asSaveTextDocumentParams(createDocument(), true)\n    expect(res.textDocument.uri).toBeDefined()\n    expect(res.text).toBeDefined()\n    res = cv.asSaveTextDocumentParams(createDocument())\n    expect(res.text).toBeUndefined()\n  })\n\n  it('should asUri', () => {\n    const cv = c2p.createConverter()\n    let uri = URI.file('/tmp/a')\n    expect(cv.asUri(uri)).toBe(uri.toString())\n  })\n\n  it('should asCompletionParams', () => {\n    const cv = c2p.createConverter()\n    let params = cv.asCompletionParams(createDocument(), Position.create(0, 0), { triggerKind: CompletionTriggerKind.Invoked })\n    expect(params.textDocument).toBeDefined()\n    expect(params.position).toBeDefined()\n    expect(params.context).toBeDefined()\n  })\n\n  it('should asTextDocumentPositionParams', () => {\n    const cv = c2p.createConverter()\n    let params = cv.asTextDocumentPositionParams(createDocument(), Position.create(0, 0))\n    expect(params.textDocument).toBeDefined()\n    expect(params.position).toBeDefined()\n  })\n\n  it('should asTextDocumentIdentifier', () => {\n    const cv = c2p.createConverter()\n    let doc = cv.asTextDocumentIdentifier(createDocument())\n    expect(doc.uri).toBeDefined()\n  })\n\n  it('should asReferenceParams', () => {\n    const cv = c2p.createConverter()\n    let params = cv.asReferenceParams(createDocument(), Position.create(0, 0), { includeDeclaration: false })\n    expect(params.textDocument.uri).toBeDefined()\n    expect(params.position).toBeDefined()\n  })\n\n  it('should asDocumentSymbolParams', () => {\n    const cv = c2p.createConverter()\n    let doc = cv.asDocumentSymbolParams(createDocument())\n    expect(doc.textDocument.uri).toBeDefined()\n  })\n\n  it('should asCodeLensParams', () => {\n    const cv = c2p.createConverter()\n    let doc = cv.asCodeLensParams(createDocument())\n    expect(doc.textDocument.uri).toBeDefined()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/client/diagnostics.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport os from 'os'\nimport path from 'path'\nimport { v4 as uuid } from 'uuid'\nimport { CancellationToken, DocumentDiagnosticRequest, Position, TextEdit } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { URI } from 'vscode-uri'\nimport * as lsclient from '../../language-client'\nimport { BackgroundScheduler, DocumentPullStateTracker, PullState } from '../../language-client/diagnostic'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nfunction getId(uri: string): number {\n  let ms = uri.match(/\\d+$/)\n  return ms ? Number(ms[0]) : undefined\n}\n\nfunction createDocument(id: number, version = 1): TextDocument {\n  let uri = `file:///${id}`\n  return TextDocument.create(uri, '', version, '')\n}\n\nfunction createUri(id: number): URI {\n  return URI.file(id.toString())\n}\n\ndescribe('BackgroundScheduler', () => {\n  it('should schedule documents by add', async () => {\n    let uris: string[] = []\n    let client = { error: () => {} }\n    let s = new BackgroundScheduler(client as any, {\n      pull(document) {\n        uris.push(document.uri)\n      },\n      pullAsync(document) {\n        uris.push(document.uri)\n        return Promise.resolve(undefined)\n      }\n    } as any)\n    s.add(createDocument(1))\n    s.add(createDocument(1))\n    s.add(createDocument(2))\n    s.add(createDocument(3))\n    s.trigger()\n    await helper.waitValue(() => {\n      return uris.length\n    }, 3)\n    let ids = uris.map(u => getId(u))\n    expect(ids).toEqual([1, 2, 3])\n  })\n\n  it('should schedule documents by remove', async () => {\n    let uris: string[] = []\n    let client = { error: () => {} }\n    let s = new BackgroundScheduler(client as any, {\n      pull(document) {\n        uris.push(document.uri)\n      },\n      pullAsync(document) {\n        uris.push(document.uri)\n        return Promise.resolve(undefined)\n      }\n    } as any)\n    s.add(createDocument(1))\n    s.add(createDocument(2))\n    s.remove(createDocument(2))\n    s.add(createDocument(3))\n    s.remove(createDocument(3))\n    s.trigger()\n    s.trigger()\n    await helper.waitValue(() => {\n      return uris.length\n    }, 1)\n    let ids = uris.map(u => getId(u))\n    expect(ids).toEqual([1])\n    s.dispose()\n  })\n})\n\ndescribe('DocumentPullStateTracker', () => {\n  it('should track document', async () => {\n    let tracker = new DocumentPullStateTracker()\n    let state = tracker.track(PullState.document, createDocument(1))\n    let other = tracker.track(PullState.document, createDocument(1))\n    expect(state).toBe(other)\n    tracker.track(PullState.workspace, createDocument(3))\n    let id = 'dcf06d3b-79f6-4a5e-bc8d-d3334f7b4cad'\n    tracker.update(PullState.document, createDocument(1, 2), id)\n    tracker.update(PullState.document, createDocument(2, 2), 'f758ae47-c94e-406e-ba41-0f3bb2fe4fc7')\n    let curr = tracker.getResultId(PullState.document, createDocument(1, 2))\n    expect(curr).toBe(id)\n    expect(tracker.getResultId(PullState.workspace, createDocument(1, 2))).toBeUndefined()\n    tracker.unTrack(PullState.document, createDocument(2, 2))\n    expect(tracker.trackingDocuments()).toEqual(['file:///1'])\n    tracker.update(PullState.workspace, createDocument(3, 2), 'fcb905e2-8edb-4239-9150-198c8175ed4a')\n    tracker.update(PullState.workspace, createDocument(1, 2), 'fe96d175-c19f-4705-bff1-101bf83b2953')\n    expect(tracker.tracks(PullState.workspace, createDocument(3, 1))).toBe(true)\n    expect(tracker.tracks(PullState.document, createDocument(4, 1))).toBe(false)\n    let res = tracker.getAllResultIds()\n    expect(res.length).toBe(2)\n  })\n\n  it('should track URI', async () => {\n    let tracker = new DocumentPullStateTracker()\n    let state = tracker.track(PullState.document, createUri(1), undefined)\n    let other = tracker.track(PullState.document, createUri(1), undefined)\n    expect(state).toBe(other)\n    tracker.track(PullState.workspace, createUri(3), undefined)\n    let id = 'dcf06d3b-79f6-4a5e-bc8d-d3334f7b4cad'\n    tracker.update(PullState.document, createUri(1), undefined, id)\n    tracker.update(PullState.document, createUri(2), undefined, 'f758ae47-c94e-406e-ba41-0f3bb2fe4fc7')\n    let curr = tracker.getResultId(PullState.document, createUri(1))\n    expect(curr).toBe(id)\n    tracker.unTrack(PullState.document, createUri(2))\n    expect(tracker.trackingDocuments()).toEqual(['file:///1'])\n    tracker.update(PullState.workspace, createUri(3), undefined, undefined)\n    tracker.update(PullState.workspace, createUri(1), undefined, 'fe96d175-c19f-4705-bff1-101bf83b2953')\n    expect(tracker.tracks(PullState.workspace, createUri(3))).toBe(true)\n    expect(tracker.tracks(PullState.document, createUri(4))).toBe(false)\n    let res = tracker.getAllResultIds()\n    expect(res.length).toBe(1)\n  })\n})\n\ndescribe('DiagnosticFeature', () => {\n  let nvim: Neovim\n  beforeAll(async () => {\n    await helper.setup()\n    nvim = workspace.nvim\n  })\n\n  afterAll(async () => {\n    await helper.shutdown()\n  })\n\n  afterEach(async () => {\n    await helper.reset()\n  })\n\n  async function createServer(interFileDependencies: boolean, workspaceDiagnostics = false, middleware: lsclient.Middleware = {}, fun?: (opt: lsclient.LanguageClientOptions) => void) {\n    let clientOptions: lsclient.LanguageClientOptions = {\n      documentSelector: [{ language: '*' }],\n      middleware,\n      initializationOptions: {\n        interFileDependencies: interFileDependencies == true,\n        workspaceDiagnostics\n      }\n    }\n    if (fun) fun(clientOptions)\n    let serverModule = path.join(__dirname, './server/diagnosticServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      module: serverModule,\n      transport: lsclient.TransportKind.ipc\n    }\n    let client = new lsclient.LanguageClient('html', 'Test Language Server', serverOptions, clientOptions)\n    await client.start()\n    return client\n  }\n\n  function getUri(s: number | string): string {\n    let fsPath = path.join(os.tmpdir(), s.toString())\n    return URI.file(fsPath).toString()\n  }\n\n  it('should work when change visible editor', async () => {\n    let doc = await workspace.loadFile(getUri(1), 'edit')\n    await workspace.loadFile(getUri(3), 'tabe')\n    let client = await createServer(true)\n    await helper.wait(30)\n    await workspace.loadFile(getUri(2), 'edit')\n    await helper.wait(30)\n    await workspace.loadFile(getUri(3), 'tabe')\n    await helper.wait(30)\n    let feature = client.getFeature(DocumentDiagnosticRequest.method)\n    expect(feature).toBeDefined()\n    let provider = feature.getProvider(doc.textDocument)\n    let res = provider.knows(PullState.document, doc.textDocument)\n    expect(res).toBe(false)\n    await client.stop()\n  })\n\n  it('should filter by document selector', async () => {\n    let client = await createServer(true, false, {}, opt => {\n      opt.documentSelector = [{ language: 'vim' }]\n    })\n    let doc = await workspace.loadFile(getUri(1), 'edit')\n    await helper.wait(10)\n    let feature = client.getFeature(DocumentDiagnosticRequest.method)\n    let provider = feature.getProvider(TextDocument.create('file:///1', 'vim', 1, ''))\n    let res = provider.knows(PullState.document, doc.textDocument)\n    expect(res).toBe(false)\n    doc = await workspace.loadFile(getUri(2), 'edit')\n    await helper.waitValue(() => window.activeTextEditor?.uri === doc.uri, true)\n    provider.forget(doc.textDocument)\n    await client.stop()\n  })\n\n  it('should filter by ignore', async () => {\n    let client = await createServer(true, false, {}, opt => {\n      opt.diagnosticPullOptions = {\n        ignored: ['**/*.ts']\n      }\n    })\n    let doc = await workspace.loadFile(getUri('a.ts'), 'edit')\n    await helper.wait(10)\n    let feature = client.getFeature(DocumentDiagnosticRequest.method)\n    let provider = feature.getProvider(doc.textDocument)\n    let res = provider.knows(PullState.document, doc.textDocument)\n    expect(res).toBe(false)\n    await client.stop()\n  })\n\n  it('should not throw on request error', async () => {\n    let client = await createServer(true)\n    await workspace.loadFile(getUri('error'), 'edit')\n    await workspace.loadFile(getUri('cancel'), 'tabe')\n    await workspace.loadFile(getUri('retrigger'), 'tabe')\n    await helper.wait(10)\n    await nvim.command('normal! 2gt')\n    await workspace.loadFile(getUri('unchanged'), 'edit')\n    await helper.wait(20)\n    await client.stop()\n  })\n\n  it('should pull diagnostic on change', async () => {\n    let doc = await workspace.loadFile(getUri('change'), 'edit')\n    let client = await createServer(true, false, {}, opt => {\n      opt.diagnosticPullOptions = {\n        onChange: true,\n        filter: doc => {\n          return doc.uri.endsWith('filtered')\n        }\n      }\n    })\n    let feature = client.getFeature(DocumentDiagnosticRequest.method)\n    let provider = feature.getProvider(doc.textDocument)\n    await helper.waitValue(() => {\n      return provider.knows(PullState.document, doc.textDocument)\n    }, true)\n    await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo')])\n    await helper.waitValue(async () => {\n      let n = await client.sendRequest('getChangeCount') as number\n      return n >= 2\n    }, true)\n    await nvim.call('setline', [1, 'foo'])\n    let d = await workspace.loadFile(getUri('filtered'), 'tabe')\n    await d.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo')])\n    await helper.wait(30)\n    feature.refresh()\n    await nvim.command(`bd! ${doc.bufnr}`)\n    await client.stop()\n  })\n\n  it('should pull diagnostic on save', async () => {\n    let doc = await workspace.loadFile(getUri(uuid() + 'filtered'), 'edit')\n    await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo')])\n    doc = await workspace.loadFile(getUri(uuid() + 'save'), 'tabe')\n    let client = await createServer(true, false, {}, opt => {\n      opt.diagnosticPullOptions = {\n        onSave: true,\n        filter: doc => {\n          return doc.uri.endsWith('filtered')\n        }\n      }\n    })\n    let feature = client.getFeature(DocumentDiagnosticRequest.method)\n    let provider = feature.getProvider(doc.textDocument)\n    await helper.waitValue(() => {\n      return provider.knows(PullState.document, doc.textDocument)\n    }, true)\n    await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo')])\n    await nvim.command('wa')\n    await helper.wait(10)\n    await client.stop()\n  })\n\n  it('should use provideDiagnostics middleware', async () => {\n    let called = false\n    let callHandle = false\n    let middleware = {\n      provideDiagnostics: (doc, id, token, next) => {\n        called = true\n        return next(doc, id, token)\n      },\n      handleDiagnostics: (uri, diagnostics, next) => {\n        callHandle = true\n        return next(uri, diagnostics)\n      }\n    }\n    let client = await createServer(true, false, middleware)\n    let feature = client.getFeature(DocumentDiagnosticRequest.method)\n    expect(feature).toBeDefined()\n    let textDocument = TextDocument.create(getUri('empty'), 'e', 1, '')\n    let provider = feature.getProvider(textDocument)\n    let res = await provider.diagnostics.provideDiagnostics(textDocument, '', CancellationToken.None)\n    expect(called).toBe(true)\n    expect(res).toEqual({ kind: 'full', items: [] })\n    await helper.waitValue(() => {\n      return callHandle\n    }, true)\n    middleware.handleDiagnostics = undefined\n    await client.sendRequest('sendDiagnostics')\n    await client.stop()\n  })\n\n  it('should use provideWorkspaceDiagnostics middleware', async () => {\n    let called = false\n    let client = await createServer(false, true, {\n      provideWorkspaceDiagnostics: (resultIds, token, resultReporter, next) => {\n        called = true\n        return next(resultIds, token, resultReporter)\n      }\n    })\n    expect(called).toBe(true)\n    await helper.waitValue(async () => {\n      let count = await client.sendRequest('getWorkspaceCount') as number\n      return count > 1\n    }, true)\n    await client.stop()\n  })\n\n  it('should receive partial result', async () => {\n    let client = await createServer(false, true, {}, opt => {\n      opt.diagnosticPullOptions = {\n        workspace: false\n      }\n    })\n    let feature = client.getFeature(DocumentDiagnosticRequest.method)\n    let textDocument = TextDocument.create(getUri('empty'), 'e', 1, '')\n    let provider = feature.getProvider(textDocument)\n    let n = 0\n    await provider.diagnostics.provideWorkspaceDiagnostics([{ uri: 'uri', value: '1' }], CancellationToken.None, chunk => {\n      n++\n    })\n    expect(n).toBe(4)\n    await client.stop()\n  })\n\n  it('should fire refresh event', async () => {\n    let client = await createServer(true, false, {})\n    let feature = client.getFeature(DocumentDiagnosticRequest.method)\n    let textDocument = TextDocument.create(getUri('1'), 'e', 1, '')\n    let provider = feature.getProvider(textDocument)\n    let called = false\n    provider.onDidChangeDiagnosticsEmitter.event(() => {\n      called = true\n    })\n    await client.sendNotification('fireRefresh')\n    await helper.waitValue(() => {\n      return called\n    }, true)\n    await client.stop()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/client/dynamic.test.ts",
    "content": "import os from 'os'\nimport path from 'path'\nimport { CancellationToken, CodeActionRequest, CodeLensRequest, CompletionRequest, DidChangeWorkspaceFoldersNotification, DidCreateFilesNotification, DidDeleteFilesNotification, DidRenameFilesNotification, DocumentLinkRequest, DocumentSymbolRequest, ExecuteCommandRequest, InlayHintRequest, InlineValueRequest, Position, Range, RenameRequest, SemanticTokensRegistrationType, SymbolInformation, SymbolKind, TextDocumentContentRequest, WillDeleteFilesRequest, WillRenameFilesRequest, WorkspaceFolder, WorkspaceSymbolRequest } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { URI } from 'vscode-uri'\nimport commands from '../../commands'\nimport * as lsclient from '../../language-client'\nimport { ClientState } from '../../language-client'\nimport { SemanticTokensFeature } from '../../language-client/semanticTokens'\nimport type { TextDocumentContentProviderShape } from '../../language-client/textDocumentContent'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nbeforeAll(async () => {\n  await helper.setup()\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  await helper.reset()\n})\n\ndescribe('DynamicFeature', () => {\n  let textDocument = TextDocument.create('file:///1', 'vim', 1, '\\n')\n  let position = Position.create(1, 1)\n  let token = CancellationToken.None\n\n  async function startServer(opts: any = {}, middleware: lsclient.Middleware = {}): Promise<lsclient.LanguageClient> {\n    let clientOptions: lsclient.LanguageClientOptions = {\n      documentSelector: [{ language: '*' }],\n      initializationOptions: opts,\n      synchronize: {\n        configurationSection: 'languageserver.vim.settings'\n      },\n      middleware\n    }\n    let serverModule = path.join(__dirname, './server/dynamicServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      module: serverModule,\n      transport: lsclient.TransportKind.ipc\n    }\n    let client = new lsclient.LanguageClient('html', 'Test Language Server', serverOptions, clientOptions)\n    await client.start()\n    return client\n  }\n\n  describe('RenameFeature', () => {\n    it('should start server', async () => {\n      let called = false\n      let client = await startServer({ prepareRename: false }, {\n        handleRegisterCapability: async (params, next) => {\n          await Promise.resolve(next(params, CancellationToken.None))\n          return\n        },\n        handleUnregisterCapability: async (params, next) => {\n          called = true\n          await Promise.resolve(next(params, CancellationToken.None))\n          return\n        }\n      })\n      let feature = client.getFeature(RenameRequest.method)\n      let provider = feature.getProvider(textDocument)\n      expect(provider.prepareRename).toBeUndefined()\n      feature.unregister('')\n      client['_state'] = ClientState.StartFailed\n      await helper.waitValue(() => called, true)\n      await client.stop()\n    })\n\n    it('should handle different result', async () => {\n      let client = await startServer({ prepareRename: true }, {\n        provideRenameEdits: (doc, pos, newName, token, next) => {\n          return next(doc, pos, newName, token)\n        },\n        prepareRename: (doc, pos, token, next) => {\n          return next(doc, pos, token)\n        }\n      })\n      let feature = client.getFeature(RenameRequest.method)\n      let provider = feature.getProvider(textDocument)\n      expect(provider.prepareRename).toBeDefined()\n      let res = await provider.prepareRename(textDocument, position, token)\n      expect(res).toBeNull()\n\n      await client.sendRequest('setPrepareResponse', { defaultBehavior: true })\n      res = await provider.prepareRename(textDocument, position, token)\n      expect(res).toBeNull()\n      await client.sendRequest('setPrepareResponse', { range: Range.create(0, 0, 0, 3), placeholder: 'placeholder' })\n      res = await provider.prepareRename(textDocument, position, token)\n      expect((res as any).placeholder).toBe('placeholder')\n      await expect(async () => {\n        await client.sendRequest('setPrepareResponse', { defaultBehavior: false })\n        res = await provider.prepareRename(textDocument, position, token)\n      }).rejects.toThrow(Error)\n      await client.stop()\n    })\n  })\n\n  describe('WorkspaceSymbolFeature', () => {\n    it('should use middleware', async () => {\n      let client = await startServer({}, {\n        provideWorkspaceSymbols: (query, token, next) => {\n          return next(query, token)\n        },\n        resolveWorkspaceSymbol: (item, token, next) => {\n          return next(item, token)\n        }\n      })\n      let feature = client.getFeature(WorkspaceSymbolRequest.method)\n      await helper.waitValue(() => {\n        return feature.getProviders().length\n      }, 2)\n      let provider = feature.getProviders().find(o => typeof o.resolveWorkspaceSymbol === 'function')\n      expect(provider).toBeDefined()\n      let token = CancellationToken.None\n      let res = await provider.provideWorkspaceSymbols('', token)\n      expect(res.length).toBe(0)\n      let sym = SymbolInformation.create('name', SymbolKind.Array, Range.create(0, 1, 0, 1), 'file:///1')\n      let resolved = await provider.resolveWorkspaceSymbol(sym, token)\n      expect(resolved.name).toBe(sym.name)\n      await client.stop()\n    })\n  })\n\n  describe('SemanticTokensFeature', () => {\n    it('should register semanticTokens', async () => {\n      let client = await startServer({})\n      let feature = client.getFeature(SemanticTokensRegistrationType.method)\n      let provider: any\n      await helper.waitValue(() => {\n        provider = feature.getProvider(textDocument)\n        return provider != null\n      }, true)\n      expect(provider.range).toBeUndefined()\n      await client.stop()\n    })\n\n    it('should use middleware', async () => {\n      let client = await startServer({ rangeTokens: true, delta: true }, {})\n      let feature = client.getFeature(SemanticTokensRegistrationType.method)\n      await helper.waitValue(() => {\n        return feature.getProvider(textDocument) != null\n      }, true)\n      let provider = feature.getProvider(textDocument)\n      expect(provider).toBeDefined()\n      expect(provider.range).toBeDefined()\n      let res = await provider.full.provideDocumentSemanticTokensEdits(textDocument, '2', CancellationToken.None)\n      expect(res.resultId).toBe('3')\n      await client.stop()\n    })\n  })\n\n  describe('CodeActionFeature', () => {\n    it('should use registered command', async () => {\n      let client = await startServer({})\n      let feature = client.getFeature(CodeActionRequest.method)\n      await helper.waitValue(() => {\n        return feature.getProvider(textDocument) != null\n      }, true)\n      let provider = feature.getProvider(textDocument)\n      let actions = await provider.provideCodeActions(textDocument, Range.create(0, 1, 0, 1), { diagnostics: [] }, token)\n      expect(actions.length).toBe(1)\n      await client.stop()\n    })\n  })\n\n  describe('PullConfigurationFeature', () => {\n    it('should pull configuration for configured languageserver', async () => {\n      helper.updateConfiguration('languageserver.vim.settings.foo', 'bar')\n      let client = await startServer({})\n      await client.sendNotification('pullConfiguration')\n      await helper.wait(50)\n      let res = await client.sendRequest('getConfiguration')\n      expect(Array.isArray(res)).toBe(true)\n      expect(res[0]).toEqual('bar')\n      helper.updateConfiguration('suggest.noselect', true)\n      await helper.wait(20)\n      await client.stop()\n    })\n  })\n\n  describe('CodeLensFeature', () => {\n    it('should use codeLens middleware', async () => {\n      let fn = jest.fn()\n      let client = await startServer({}, {\n        provideCodeLenses: (doc, token, next) => {\n          fn()\n          return next(doc, token)\n        },\n        resolveCodeLens: (codelens, token, next) => {\n          fn()\n          return next(codelens, token)\n        }\n      })\n      let feature = client.getFeature(CodeLensRequest.method)\n      let provider = feature.getProvider(textDocument).provider\n      expect(provider).toBeDefined()\n      let res = await provider.provideCodeLenses(textDocument, token)\n      expect(res.length).toBe(2)\n      let resolved = await provider.resolveCodeLens(res[0], token)\n      expect(resolved.command).toBeDefined()\n      expect(fn).toHaveBeenCalledTimes(2)\n      await client.stop()\n    })\n\n    it('should no resolve when resolve not exists', async () => {\n      let client = await startServer({ noResolve: true }, {})\n      let feature = client.getFeature(CodeLensRequest.method)\n      let provider = feature.getProvider(textDocument).provider\n      expect(provider).toBeDefined()\n      expect(provider.resolveCodeLens).toBeUndefined()\n      {\n        let feature = client.getFeature(DocumentLinkRequest.method)\n        let provider = feature.getProvider(textDocument)\n        expect(provider).toBeDefined()\n        expect(provider.resolveDocumentLink).toBeUndefined()\n      }\n      {\n        let feature = client.getFeature(InlayHintRequest.method)\n        let provider = feature.getProvider(textDocument).provider\n        expect(provider).toBeDefined()\n        expect(provider.resolveInlayHint).toBeUndefined()\n      }\n      {\n        let feature: SemanticTokensFeature\n        await helper.waitValue(() => {\n          feature = client.getFeature(SemanticTokensRegistrationType.method) as SemanticTokensFeature\n          return feature != null && feature.getProvider(textDocument) != null\n        }, true)\n        let provider = feature.getProvider(textDocument).full\n        expect(provider).toBeDefined()\n        expect(provider.provideDocumentSemanticTokensEdits).toBeUndefined()\n      }\n      await client.stop()\n    })\n  })\n\n  describe('InlineValueFeature', () => {\n    it('should fire refresh', async () => {\n      let client = await startServer({})\n      let feature = client.getFeature(InlineValueRequest.method)\n      expect(feature).toBeDefined()\n      await helper.waitValue(() => {\n        return feature.getProvider(textDocument) != null\n      }, true)\n      let provider = feature.getProvider(textDocument)\n      let called = false\n      provider.onDidChangeInlineValues.event(() => {\n        called = true\n      })\n      await client.sendNotification('fireInlineValueRefresh')\n      await helper.waitValue(() => {\n        return called\n      }, true)\n      await client.stop()\n    })\n  })\n\n  describe('ExecuteCommandFeature', () => {\n    it('should register command with middleware', async () => {\n      let called = false\n      let client = await startServer({}, {\n        executeCommand: (cmd, args, next) => {\n          called = true\n          return next(cmd, args)\n        }\n      })\n      await helper.waitValue(() => {\n        return commands.has('test_command')\n      }, true)\n      let feature = client.getFeature(ExecuteCommandRequest.method)\n      expect(feature).toBeDefined()\n      feature.unregister('other_command')\n      expect(feature.getState().kind).toBe('workspace')\n      let res = await commands.executeCommand('test_command')\n      expect(res).toEqual({ success: true })\n      expect(called).toBe(true)\n      let err\n      let spy = jest.spyOn(client, 'handleFailedRequest').mockImplementation((_type, _token, error) => {\n        err = error\n      })\n      await commands.executeCommand('other_command')\n      spy.mockRestore()\n      expect(err.message).toMatch(/not exists/)\n      await client.sendNotification('unregister')\n      await helper.waitValue(() => {\n        return commands.has('test_command')\n      }, false)\n      await client.stop()\n    })\n\n    it('should register command without middleware', async () => {\n      let client = await startServer({}, {})\n      await helper.waitValue(() => {\n        return commands.has('test_command')\n      }, true)\n      let res = await commands.executeCommand('test_command')\n      expect(res).toEqual({ success: true })\n      await client.stop()\n    })\n  })\n\n  describe('DocumentSymbolFeature', () => {\n    it('should provide documentSymbols without middleware', async () => {\n      let client = await startServer({}, {})\n      let feature = client.getFeature(DocumentSymbolRequest.method)\n      expect(feature).toBeDefined()\n      expect(feature.getState()).toBeDefined()\n      let provider = feature.getProvider(textDocument)\n      let res = await provider.provideDocumentSymbols(textDocument, token)\n      expect(res).toEqual([])\n      await client.stop()\n    })\n\n    it('should provide documentSymbols with middleware', async () => {\n      let called = false\n      let client = await startServer({ label: true }, {\n        provideDocumentSymbols: (doc, token, next) => {\n          called = true\n          return next(doc, token)\n        }\n      })\n      let feature = client.getFeature(DocumentSymbolRequest.method)\n      let provider = feature.getProvider(textDocument)\n      expect(provider.meta).toEqual({ label: 'test' })\n      let res = await provider.provideDocumentSymbols(textDocument, token)\n      expect(res).toEqual([])\n      expect(called).toBe(true)\n      await client.stop()\n    })\n  })\n\n  describe('FileOperationFeature', () => {\n    it('should use middleware for FileOperationFeature', async () => {\n      let n = 0\n      let client = await startServer({}, {\n        workspace: {\n          didCreateFiles: (ev, next) => {\n            n++\n            return next(ev)\n          },\n          didRenameFiles: (ev, next) => {\n            n++\n            return next(ev)\n          },\n          didDeleteFiles: (ev, next) => {\n            n++\n            return Promise.reject(new Error('my error'))\n          },\n          willRenameFiles: (ev, next) => {\n            n++\n            return next(ev)\n          },\n          willDeleteFiles: (ev, next) => {\n            n++\n            return next(ev)\n          }\n        }\n      })\n      let createFeature = client.getFeature(DidCreateFilesNotification.method)\n      createFeature.initialize({\n        workspace: {\n          fileOperations: { didCreate: { filters: [{ pattern: { glob: '' } }] } }\n        }\n      }, ['*'])\n      await createFeature.send({ files: [URI.file('/a/b')] })\n      let renameFeature = client.getFeature(DidRenameFilesNotification.method)\n      await renameFeature.send({ files: [{ oldUri: URI.file('/a/b'), newUri: URI.file('/c/d') }] })\n      let deleteFeature = client.getFeature(DidDeleteFilesNotification.method)\n      await deleteFeature.send({ files: [URI.file('/x/y')] })\n      let willRename = client.getFeature(WillRenameFilesRequest.method)\n      await willRename.send({ files: [{ oldUri: URI.file(__dirname), newUri: URI.file(path.join(__dirname, 'x')) }], waitUntil: () => {} })\n      let willDelete = client.getFeature(WillDeleteFilesRequest.method)\n      await willDelete.send({ files: [URI.file('/x/y')], waitUntil: () => {} })\n      await willDelete.send({ files: [], waitUntil: () => {} })\n      await helper.waitValue(() => {\n        return n\n      }, 5)\n      await client.stop()\n    })\n\n    it('should filter matches', async () => {\n      let n = 0\n      let client = await startServer({}, {\n        workspace: {\n          didCreateFiles: (ev, next) => {\n            n += ev.files.length\n            return next(ev)\n          }\n        }\n      })\n      let createFeature = client.getFeature(DidCreateFilesNotification.method)\n      createFeature.initialize({\n        workspace: {\n          fileOperations: {\n            didCreate: {\n              filters: [\n                { pattern: { glob: '**/', matches: 'folder' } },\n                { pattern: { glob: '**', matches: 'file' } },\n              ]\n            }\n          }\n        }\n      }, ['*'])\n      await createFeature.send({ files: [URI.file(os.tmpdir()), URI.file(__filename)] })\n      await helper.waitValue(() => n, 2)\n      await client.stop()\n    })\n  })\n\n  describe('CompletionItemFeature', () => {\n    it('should register multiple completion sources', async () => {\n      let client = await startServer({}, {})\n      let feature = client.getFeature(CompletionRequest.method)\n      await helper.waitValue(() => {\n        return feature.registrationLength\n      }, 2)\n      await client.stop()\n    })\n  })\n\n  describe('WorkspaceFoldersFeature', () => {\n    it('should register listeners', async () => {\n      let client = await startServer({}, {})\n      let feature = client.getFeature(DidChangeWorkspaceFoldersNotification.method)\n      expect(feature).toBeDefined()\n      let state = feature.getState() as any\n      expect(state.registrations).toBe(true)\n      feature.register({ id: '1', registerOptions: undefined })\n      feature.unregister('b346648e-88e0-44e3-91e3-52fd6addb8c7')\n      feature.unregister('2')\n      await client.stop()\n    })\n\n    it('should handle WorkspaceFoldersRequest', async () => {\n      let client = await startServer({ changeNotifications: true }, {})\n      let folders = workspace.workspaceFolders\n      expect(folders.length).toBe(0)\n      await client.sendNotification('requestFolders')\n      await helper.wait(10)\n      let res = await client.sendRequest('getFolders')\n      expect(res).toBeNull()\n      workspace.workspaceFolderControl.addWorkspaceFolder(process.cwd(), true)\n      await helper.wait(10)\n      await client.stop()\n    })\n\n    it('should use workspaceFolders middleware', async () => {\n      await workspace.loadFile(__filename)\n      let folders = workspace.workspaceFolders\n      expect(folders.length).toBe(1)\n      let called = false\n      let fn = jest.fn()\n      let client = await startServer({ changeNotifications: true }, {\n        workspace: {\n          workspaceFolders: (token, next) => {\n            called = true\n            return next(token)\n          },\n          didChangeWorkspaceFolders: () => {\n            fn()\n            return Promise.reject(new Error('my error'))\n          }\n        }\n      })\n      await client.sendNotification('requestFolders')\n      await helper.waitValue(async () => {\n        let res = await client.sendRequest('getFolders') as WorkspaceFolder[]\n        return Array.isArray(res) && res.length == 1\n      }, true)\n      expect(called).toBe(true)\n      workspace.workspaceFolderControl.addWorkspaceFolder(os.tmpdir(), true)\n      expect(fn).toHaveBeenCalled()\n      await client.stop()\n    })\n\n    it('should send folders event with middleware', async () => {\n      let called = false\n      let client = await startServer({ changeNotifications: true }, {\n        workspace: {\n          didChangeWorkspaceFolders: (ev, next) => {\n            called = true\n            return next(ev)\n          }\n        }\n      })\n      let folders = workspace.workspaceFolders\n      expect(folders.length).toBe(0)\n      await workspace.loadFile(__filename)\n      await helper.waitValue(() => {\n        return called\n      }, true)\n      await client.stop()\n    })\n  })\n\n  describe('TextDocumentContentFeature', () => {\n    it('should register static TextDocumentContent feature', async () => {\n      let client = await startServer({ textDocumentContent: true }, {})\n      let feature = client.getFeature(TextDocumentContentRequest.method)\n      expect(feature.getState()['registrations']).toBe(true)\n      let providers = feature.getProviders() as TextDocumentContentProviderShape[]\n      let provider = providers[0]\n      expect(provider.scheme).toBe('lsptest')\n      let times = 0\n      provider.provider.onDidChange(() => {\n        times++\n      })\n      await client.sendNotification('fireDocumentContentRefresh')\n      await helper.waitValue(() => times, 1)\n      let uri = URI.parse('lsptest:///1')\n      let spy = jest.spyOn(client, 'sendRequest').mockReturnValue(Promise.resolve(undefined))\n      let res = await provider.provider.provideTextDocumentContent(uri, token)\n      expect(res).toBeUndefined()\n      spy.mockRestore()\n      spy = jest.spyOn(client, 'sendRequest').mockReturnValue(Promise.resolve({ text: 'foo' }))\n      res = await provider.provider.provideTextDocumentContent(uri, token)\n      expect(res).toBe('foo')\n      spy.mockRestore()\n      spy = jest.spyOn(client, 'sendRequest').mockReturnValue(Promise.reject(new Error('myerror')))\n      await expect(async () => {\n        await provider.provider.provideTextDocumentContent(uri, token)\n      }).rejects.toThrow(Error)\n      spy.mockRestore()\n      feature.unregister('b346648e-88e0-44e3-91e3-52fd6addb8c7')\n      expect(feature.getState()['registrations']).toBe(false)\n      await client.stop()\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/client/features.test.ts",
    "content": "import * as assert from 'assert'\nimport path from 'path'\nimport { v4 as uuidv4 } from 'uuid'\nimport { ApplyWorkspaceEditParams, CallHierarchyIncomingCall, CallHierarchyItem, CallHierarchyOutgoingCall, CallHierarchyPrepareRequest, CancellationToken, CancellationTokenSource, CodeAction, CodeActionRequest, CodeLensRequest, Color, ColorInformation, ColorPresentation, CompletionItem, CompletionRequest, CompletionTriggerKind, ConfigurationRequest, DeclarationRequest, DefinitionRequest, DidChangeConfigurationNotification, DidChangeTextDocumentNotification, DidChangeWatchedFilesNotification, DidCloseTextDocumentNotification, DidCreateFilesNotification, DidDeleteFilesNotification, DidOpenTextDocumentNotification, DidRenameFilesNotification, DidSaveTextDocumentNotification, Disposable, DocumentColorRequest, DocumentDiagnosticReport, DocumentDiagnosticReportKind, DocumentDiagnosticRequest, DocumentFormattingRequest, DocumentHighlight, DocumentHighlightKind, DocumentHighlightRequest, DocumentLink, DocumentLinkRequest, DocumentOnTypeFormattingRequest, DocumentRangeFormattingRequest, DocumentSelector, DocumentSymbolRequest, ErrorCodes, FoldingRange, FoldingRangeRequest, FullDocumentDiagnosticReport, Hover, HoverRequest, ImplementationRequest, InlayHintKind, InlayHintLabelPart, InlayHintRequest, InlineCompletionItem, InlineCompletionRequest, InlineValueEvaluatableExpression, InlineValueRequest, InlineValueText, InlineValueVariableLookup, LinkedEditingRangeRequest, Location, NotificationType0, ParameterInformation, Position, ProgressToken, ProtocolRequestType, Range, ReferencesRequest, RenameRequest, ResponseError, SelectionRange, SelectionRangeRequest, SemanticTokensRegistrationType, SignatureHelpRequest, SignatureHelpTriggerKind, SignatureInformation, TextDocumentContentRequest, TextDocumentEdit, TextDocumentSyncKind, TextEdit, TypeDefinitionRequest, TypeHierarchyPrepareRequest, WillCreateFilesRequest, WillDeleteFilesRequest, WillRenameFilesRequest, WillSaveTextDocumentNotification, WillSaveTextDocumentWaitUntilRequest, WorkDoneProgressBegin, WorkDoneProgressCreateRequest, WorkDoneProgressEnd, WorkDoneProgressReport, WorkspaceEdit, WorkspaceSymbolRequest } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { URI } from 'vscode-uri'\nimport commands from '../../commands'\nimport { StaticFeature } from '../../language-client/features'\nimport { LanguageClient, LanguageClientOptions, Middleware, RevealOutputChannelOn, ServerOptions, State, TransportKind } from '../../language-client/index'\nimport { InlayHintsFeature } from '../../language-client/inlayHint'\nimport languages from '../../languages'\nimport workspace from '../../workspace'\nimport helper from '../helper'\nimport { FoldingRangeFeature } from '../../language-client/foldingRange'\n\nbeforeAll(async () => {\n  await helper.setup()\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\ndescribe('Client integration', () => {\n  let client!: LanguageClient\n  let middleware: Middleware\n  let uri!: string\n  let document!: TextDocument\n  let tokenSource!: CancellationTokenSource\n  const position: Position = Position.create(1, 1)\n  const range: Range = Range.create(1, 1, 1, 2)\n  let contentProviderDisposable: Disposable\n\n  function rangeEqual(range: Range, sl: number, sc: number, el: number, ec: number): void {\n    assert.strictEqual(range.start.line, sl)\n    assert.strictEqual(range.start.character, sc)\n    assert.strictEqual(range.end.line, el)\n    assert.strictEqual(range.end.character, ec)\n  }\n\n  function positionEqual(pos: Position, l: number, c: number): void {\n    assert.strictEqual(pos.line, l)\n    assert.strictEqual(pos.character, c)\n  }\n\n  function colorEqual(color: Color, red: number, green: number, blue: number, alpha: number): void {\n    assert.strictEqual(color.red, red)\n    assert.strictEqual(color.green, green)\n    assert.strictEqual(color.blue, blue)\n    assert.strictEqual(color.alpha, alpha)\n  }\n\n  function uriEqual(actual: string, expected: string): void {\n    assert.strictEqual(actual, expected)\n  }\n\n  function isArray<T>(value: Array<T> | undefined | null, clazz: any, length = 1): asserts value is Array<T> {\n    assert.ok(Array.isArray(value), `value is array`)\n    assert.strictEqual(value!.length, length, 'value has given length')\n    if (clazz && typeof clazz.is === 'function') {\n      for (let item of value) {\n        assert.ok(clazz.is(item))\n      }\n    }\n  }\n\n  function isDefined<T>(value: T | undefined | null): asserts value is Exclude<T, undefined | null> {\n    if (value === undefined || value === null) {\n      throw new Error(`Value is null or undefined`)\n    }\n  }\n\n  function isFullDocumentDiagnosticReport(value: DocumentDiagnosticReport): asserts value is FullDocumentDiagnosticReport {\n    assert.ok(value.kind === DocumentDiagnosticReportKind.Full)\n  }\n\n  beforeAll(async () => {\n    contentProviderDisposable = workspace.registerTextDocumentContentProvider('lsptests', {\n      provideTextDocumentContent: (_uri: URI) => {\n        return [\n          'REM @ECHO OFF',\n          'cd c:\\\\source',\n          'REM This is the location of the files that you want to sort',\n          'FOR %%f IN (*.doc *.txt) DO XCOPY c:\\\\source\\\\\"%%f\" c:\\\\text /m /y',\n          'REM This moves any files with a .doc or',\n          'REM .txt extension from c:\\\\source to c:\\\\text',\n          'REM %%f is a variable',\n          'FOR %%f IN (*.jpg *.png *.bmp) DO XCOPY C:\\\\source\\\\\"%%f\" c:\\\\images /m /y',\n          'REM This moves any files with a .jpg, .png,',\n          'REM or .bmp extension from c:\\\\source to c:\\\\images;;',\n        ].join('\\n')\n      }\n    })\n\n    uri = URI.parse('lsptests://localhost/test.bat').toString()\n    let doc = await workspace.loadFile(uri.toString())\n    document = doc.textDocument\n    tokenSource = new CancellationTokenSource()\n    const serverModule = path.join(__dirname, './server/testServer.js')\n    const serverOptions: ServerOptions = {\n      run: { module: serverModule, transport: TransportKind.ipc },\n      debug: { module: serverModule, transport: TransportKind.ipc, options: { execArgv: ['--nolazy', '--inspect=6014'] } }\n    }\n    const documentSelector: DocumentSelector = [{ scheme: 'lsptests' }]\n\n    middleware = {}\n    const clientOptions: LanguageClientOptions = {\n      documentSelector, synchronize: {}, initializationOptions: {}, middleware,\n      workspaceFolder: { name: 'test_folder', uri: URI.parse('file-test:///').toString() },\n      outputChannel: helper.createNullChannel(),\n      revealOutputChannelOn: RevealOutputChannelOn.Warn\n    }\n\n    client = new LanguageClient('test svr', 'Test Language Server', serverOptions, clientOptions)\n    let p = client.onReady()\n    await client.start()\n    await p\n  })\n\n  afterAll(async () => {\n    await client.sendNotification('unregister')\n    await helper.wait(30)\n    contentProviderDisposable.dispose()\n    await client.stop()\n  })\n\n  test('InitializeResult', () => {\n    let expected = {\n      capabilities: {\n        textDocumentSync: TextDocumentSyncKind.Full,\n        definitionProvider: true,\n        hoverProvider: true,\n        signatureHelpProvider: {\n          triggerCharacters: [','],\n          retriggerCharacters: [';']\n        },\n        completionProvider: { resolveProvider: true, triggerCharacters: ['\"', ':'] },\n        referencesProvider: true,\n        documentHighlightProvider: true,\n        codeActionProvider: {\n          resolveProvider: true\n        },\n        codeLensProvider: {\n          resolveProvider: true\n        },\n        documentFormattingProvider: true,\n        documentRangeFormattingProvider: {\n          rangesSupport: true\n        },\n        documentOnTypeFormattingProvider: {\n          firstTriggerCharacter: ':'\n        },\n        renameProvider: {\n          prepareProvider: true\n        },\n        documentLinkProvider: {\n          resolveProvider: true\n        },\n        documentSymbolProvider: true,\n        colorProvider: true,\n        declarationProvider: true,\n        foldingRangeProvider: true,\n        implementationProvider: {\n          documentSelector: [{ language: '*' }]\n        },\n        selectionRangeProvider: true,\n        inlineCompletionProvider: {},\n        inlineValueProvider: {},\n        inlayHintProvider: {\n          resolveProvider: true\n        },\n        typeDefinitionProvider: {\n          id: '82671a9a-2a69-4e9f-a8d7-e1034eaa0d2e',\n          documentSelector: [{ language: '*' }]\n        },\n        callHierarchyProvider: true,\n        semanticTokensProvider: {\n          legend: {\n            tokenTypes: [],\n            tokenModifiers: []\n          },\n          range: true,\n          full: {\n            delta: true\n          }\n        },\n        workspace: {\n          fileOperations: {\n            didCreate: { filters: [{ scheme: 'file', pattern: { glob: '**/created-static/**{/,/*.txt}' } }] },\n            didRename: {\n              filters: [\n                { scheme: 'file', pattern: { glob: '**/renamed-static/**/', matches: 'folder' } },\n                { scheme: 'file', pattern: { glob: '**/renamed-static/**/*.txt', matches: 'file' } }\n              ]\n            },\n            didDelete: { filters: [{ scheme: 'file', pattern: { glob: '**/deleted-static/**{/,/*.txt}' } }] },\n            willCreate: { filters: [{ scheme: 'file', pattern: { glob: '**/created-static/**{/,/*.txt}' } }] },\n            willRename: {\n              filters: [\n                { scheme: 'file', pattern: { glob: '**/renamed-static/**/', matches: 'folder' } },\n                { scheme: 'file', pattern: { glob: '**/renamed-static/**/*.txt', matches: 'file' } }\n              ]\n            },\n            willDelete: { filters: [{ scheme: 'file', pattern: { glob: '**/deleted-static/**{/,/*.txt}' } }] },\n          },\n          textDocumentContent: {\n            schemes: ['content-test']\n          }\n        },\n        linkedEditingRangeProvider: true,\n        diagnosticProvider: {\n          identifier: 'da348dc5-c30a-4515-9d98-31ff3be38d14',\n          interFileDependencies: true,\n          workspaceDiagnostics: true\n        },\n        typeHierarchyProvider: true,\n        workspaceSymbolProvider: {\n          resolveProvider: true\n        },\n        notebookDocumentSync: {\n          notebookSelector: [{\n            notebook: { notebookType: 'jupyter-notebook' },\n            cells: [{ language: 'python' }]\n          }]\n        }\n      },\n      customResults: {\n        hello: 'world'\n      }\n    }\n    assert.deepEqual(client.initializeResult, expected)\n  })\n\n  test('feature.getState()', async () => {\n    const testFeature = (method: string, kind: string): void => {\n      let feature = client.getFeature(method as any)\n      assert.notStrictEqual(feature, undefined)\n      let res = feature.getState()\n      assert.strictEqual(res.kind, kind)\n    }\n    const testStaticFeature = (method: string, kind: string): void => {\n      let feature = client.getStaticFeature(method as any)\n      assert.notStrictEqual(feature, undefined)\n      let res = feature.getState()\n      assert.strictEqual(res.kind, kind)\n      assert.ok(StaticFeature.is(feature))\n    }\n    testStaticFeature(ConfigurationRequest.method, 'static')\n    testStaticFeature(WorkDoneProgressCreateRequest.method, 'window')\n    testFeature(DidChangeWatchedFilesNotification.method, 'workspace')\n    testFeature(DidChangeConfigurationNotification.method, 'workspace')\n    testFeature(DidOpenTextDocumentNotification.method, 'document')\n    testFeature(DidChangeTextDocumentNotification.method, 'document')\n    testFeature(WillSaveTextDocumentNotification.method, 'document')\n    testFeature(WillSaveTextDocumentWaitUntilRequest.method, 'document')\n    testFeature(DidSaveTextDocumentNotification.method, 'document')\n    testFeature(DidCloseTextDocumentNotification.method, 'document')\n    testFeature(DidCreateFilesNotification.method, 'workspace')\n    testFeature(DidRenameFilesNotification.method, 'workspace')\n    testFeature(DidDeleteFilesNotification.method, 'workspace')\n    testFeature(WillCreateFilesRequest.method, 'workspace')\n    testFeature(WillRenameFilesRequest.method, 'workspace')\n    testFeature(WillDeleteFilesRequest.method, 'workspace')\n    testFeature(CompletionRequest.method, 'document')\n    testFeature(HoverRequest.method, 'document')\n    testFeature(SignatureHelpRequest.method, 'document')\n    testFeature(DefinitionRequest.method, 'document')\n    testFeature(ReferencesRequest.method, 'document')\n    testFeature(DocumentHighlightRequest.method, 'document')\n    testFeature(CodeActionRequest.method, 'document')\n    testFeature(CodeLensRequest.method, 'document')\n    testFeature(DocumentFormattingRequest.method, 'document')\n    testFeature(DocumentRangeFormattingRequest.method, 'document')\n    testFeature(DocumentOnTypeFormattingRequest.method, 'document')\n    testFeature(RenameRequest.method, 'document')\n    testFeature(DocumentSymbolRequest.method, 'document')\n    testFeature(DocumentLinkRequest.method, 'document')\n    testFeature(DocumentColorRequest.method, 'document')\n    testFeature(DeclarationRequest.method, 'document')\n    testFeature(FoldingRangeRequest.method, 'document')\n    testFeature(ImplementationRequest.method, 'document')\n    testFeature(SelectionRangeRequest.method, 'document')\n    testFeature(TypeDefinitionRequest.method, 'document')\n    testFeature(CallHierarchyPrepareRequest.method, 'document')\n    testFeature(SemanticTokensRegistrationType.method, 'document')\n    testFeature(LinkedEditingRangeRequest.method, 'document')\n    testFeature(TypeHierarchyPrepareRequest.method, 'document')\n    testFeature(InlineCompletionRequest.method, 'document')\n    testFeature(InlineValueRequest.method, 'document')\n    testFeature(InlayHintRequest.method, 'document')\n    testFeature(WorkspaceSymbolRequest.method, 'workspace')\n    testFeature(DocumentDiagnosticRequest.method, 'document')\n  })\n\n  test('warn and show output', async () => {\n    global.__showOutput = true\n    let called = false\n    let spy = jest.spyOn(client.outputChannel, 'show').mockImplementation(() => {\n      called = true\n    })\n    client.warn(undefined, { x: 1 }, true)\n    await helper.waitValue(() => called, true)\n    spy.mockRestore()\n  })\n\n  test('Goto Definition', async () => {\n    const provider = client.getFeature(DefinitionRequest.method).getProvider(document)\n    isDefined(provider)\n    const result = (await provider.provideDefinition(document, position, tokenSource.token)) as Location\n    assert.strictEqual(Location.is(result), true)\n    uriEqual(result.uri, uri)\n    rangeEqual(result.range, 0, 0, 0, 1)\n    let middlewareCalled = false\n    middleware.provideDefinition = (document, position, token, next) => {\n      middlewareCalled = true\n      return next(document, position, token)\n    }\n    await provider.provideDefinition(document, position, tokenSource.token)\n    middleware.provideDefinition = undefined\n    assert.strictEqual(middlewareCalled, true)\n  })\n\n  test('Hover', async () => {\n    const provider = client.getFeature(HoverRequest.method).getProvider(document)\n    isDefined(provider)\n    const result = await provider.provideHover(document, position, tokenSource.token)\n    assert.ok(Hover.is(result))\n    assert.strictEqual((result.contents as any).kind, 'plaintext')\n    assert.strictEqual((result.contents as any).value, 'foo')\n    let middlewareCalled = false\n    middleware.provideHover = (document, position, token, next) => {\n      middlewareCalled = true\n      return next(document, position, token)\n    }\n    await provider.provideHover(document, position, tokenSource.token)\n    middleware.provideHover = undefined\n    assert.strictEqual(middlewareCalled, true)\n  })\n\n  test('Completion', async () => {\n    const provider = client.getFeature(CompletionRequest.method).getProvider(document)\n    isDefined(provider)\n    const result = (await provider.provideCompletionItems(document, position, tokenSource.token, { triggerKind: CompletionTriggerKind.Invoked, triggerCharacter: ':' })) as CompletionItem[]\n\n    isArray(result, CompletionItem)\n    const item = result[0]\n    assert.strictEqual(item.label, 'item')\n    assert.strictEqual(item.insertText, 'text')\n    assert.strictEqual(item.detail, undefined)\n    isDefined(provider.resolveCompletionItem)\n\n    const resolved = await provider.resolveCompletionItem(item, tokenSource.token)\n    isDefined(resolved)\n    assert.strictEqual(resolved.detail, 'detail')\n\n    let middlewareCalled = 0\n    middleware.provideCompletionItem = (document, position, context, token, next) => {\n      middlewareCalled++\n      return next(document, position, context, token)\n    }\n    middleware.resolveCompletionItem = (item, token, next) => {\n      middlewareCalled++\n      return next(item, token)\n    }\n    await provider.provideCompletionItems(document, position, tokenSource.token, { triggerKind: CompletionTriggerKind.Invoked, triggerCharacter: ':' })\n    await provider.resolveCompletionItem(item, tokenSource.token)\n    middleware.provideCompletionItem = undefined\n    middleware.resolveCompletionItem = undefined\n    assert.strictEqual(middlewareCalled, 2)\n  })\n\n  test('SignatureHelpRequest', async () => {\n    await helper.wait(50)\n    let provider = client.getFeature(SignatureHelpRequest.method).getProvider(document)\n    isDefined(provider)\n    const result = await provider.provideSignatureHelp(document, position, tokenSource.token,\n      {\n        isRetrigger: false,\n        triggerKind: SignatureHelpTriggerKind.Invoked,\n        triggerCharacter: ':'\n      }\n    )\n\n    assert.strictEqual(result.activeSignature, 1)\n    assert.strictEqual(result.activeParameter, 1)\n    isArray(result.signatures, SignatureInformation)\n\n    const signature = result.signatures[0]\n    assert.strictEqual(signature.label, 'label')\n    assert.strictEqual(signature.documentation, 'doc')\n    isArray(signature.parameters, ParameterInformation)\n\n    const parameter = signature.parameters[0]\n    assert.strictEqual(parameter.label, 'label')\n    assert.strictEqual(parameter.documentation, 'doc')\n\n    let middlewareCalled = false\n    middleware.provideSignatureHelp = (d, p, c, t, n) => {\n      middlewareCalled = true\n      return n(d, p, c, t)\n    }\n    await provider.provideSignatureHelp(document, position, tokenSource.token,\n      {\n        isRetrigger: false,\n        triggerKind: SignatureHelpTriggerKind.Invoked,\n        triggerCharacter: ':'\n      }\n    )\n    middleware.provideSignatureHelp = undefined\n    assert.ok(middlewareCalled)\n  })\n\n  test('References', async () => {\n    const provider = client.getFeature(ReferencesRequest.method).getProvider(document)\n    isDefined(provider)\n    const result = await provider.provideReferences(document, position, {\n      includeDeclaration: true\n    }, tokenSource.token)\n\n    isArray(result, Location, 2)\n    for (let i = 0; i < result.length; i++) {\n      const location = result[i]\n      rangeEqual(location.range, i, i, i, i)\n      assert.strictEqual(location.uri.toString(), document.uri.toString())\n    }\n\n    let middlewareCalled = false\n    middleware.provideReferences = (d, p, c, t, n) => {\n      middlewareCalled = true\n      return n(d, p, c, t)\n    }\n    await provider.provideReferences(document, position, {\n      includeDeclaration: true\n    }, tokenSource.token)\n    middleware.provideReferences = undefined\n    assert.ok(middlewareCalled)\n  })\n\n  test('Document Highlight', async () => {\n    const provider = client.getFeature(DocumentHighlightRequest.method).getProvider(document)\n    isDefined(provider)\n    const result = await provider.provideDocumentHighlights(document, position, tokenSource.token)\n\n    isArray(result, DocumentHighlight, 1)\n\n    const highlight = result[0]\n    assert.strictEqual(highlight.kind, DocumentHighlightKind.Read)\n    rangeEqual(highlight.range, 2, 2, 2, 2)\n\n    let middlewareCalled = false\n    middleware.provideDocumentHighlights = (d, p, t, n) => {\n      middlewareCalled = true\n      return n(d, p, t)\n    }\n    await provider.provideDocumentHighlights(document, position, tokenSource.token)\n    middleware.provideDocumentHighlights = undefined\n    assert.ok(middlewareCalled)\n  })\n\n  test('Code Actions', async () => {\n    const provider = client.getFeature(CodeActionRequest.method).getProvider(document)\n    isDefined(provider)\n    const result = (await provider.provideCodeActions(document, range, {\n      diagnostics: []\n    }, tokenSource.token)) as CodeAction[]\n\n    assert.strictEqual(result.length, 3)\n    const action = result[0]\n    assert.strictEqual(action.title, 'title')\n    assert.strictEqual(action.command?.title, 'title')\n    assert.strictEqual(action.command?.command, 'test_command')\n    let response = await commands.execute(action.command)\n    expect(response).toEqual({ success: true })\n\n    const resolved = (await provider.resolveCodeAction(result[0], tokenSource.token))\n    assert.strictEqual(resolved?.title, 'resolved')\n\n    let middlewareCalled = false\n    middleware.provideCodeActions = (d, r, c, t, n) => {\n      middlewareCalled = true\n      return n(d, r, c, t)\n    }\n\n    await provider.provideCodeActions(document, range, { diagnostics: [] }, tokenSource.token)\n    middleware.provideCodeActions = undefined\n    assert.ok(middlewareCalled)\n\n    middlewareCalled = false\n    middleware.resolveCodeAction = (c, t, n) => {\n      middlewareCalled = true\n      return n(c, t)\n    }\n\n    await provider.resolveCodeAction!(result[0], tokenSource.token)\n    middleware.resolveCodeAction = undefined\n    assert.ok(middlewareCalled)\n\n    let uri = URI.parse('lsptests://localhost/empty.bat').toString()\n    let textDocument = TextDocument.create(uri, 'bat', 1, '\\n')\n    let res = (await provider.provideCodeActions(textDocument, range, {\n      diagnostics: []\n    }, tokenSource.token)) as CodeAction[]\n    expect(res).toBeUndefined()\n  })\n\n  test('CodeLens', async () => {\n    let feature = client.getFeature(CodeLensRequest.method)\n    let state = feature.getState()\n    expect((state as any).registrations).toBe(true)\n    expect((state as any).matches).toBe(true)\n    let tokenSource = new CancellationTokenSource()\n    let codeLens = await languages.getCodeLens(document, tokenSource.token)\n    expect(codeLens.length).toBe(2)\n    let resolved = await languages.resolveCodeLens(codeLens[0], tokenSource.token)\n    expect(resolved.command).toBeDefined()\n    let fireRefresh = false\n    let provider = feature.getProvider(document)\n    provider.onDidChangeCodeLensEmitter.event(() => {\n      fireRefresh = true\n    })\n    await client.sendNotification('fireCodeLensRefresh')\n    await helper.waitValue(() => fireRefresh, true)\n  })\n\n  test('Progress', async () => {\n    const progressToken = 'TEST-PROGRESS-TOKEN'\n    const middlewareEvents: Array<WorkDoneProgressBegin | WorkDoneProgressReport | WorkDoneProgressEnd> = []\n    let currentProgressResolver: (value: unknown) => void | undefined\n\n    // Set up middleware that calls the current resolve function when it gets its 'end' progress event.\n    middleware.handleWorkDoneProgress = (token: ProgressToken, params, next) => {\n      if (token === progressToken) {\n        middlewareEvents.push(params)\n        if (params.kind === 'end') {\n          setImmediate(currentProgressResolver)\n        }\n      }\n      return next(token, params)\n    }\n\n    // Trigger multiple sample progress events.\n    for (let i = 0; i < 2; i++) {\n      await new Promise<unknown>((resolve, reject) => {\n        currentProgressResolver = resolve\n        void client.sendRequest(\n          new ProtocolRequestType<any, null, never, any, any>('testing/sendSampleProgress'),\n          {},\n          tokenSource.token,\n        ).catch(reject)\n      })\n    }\n\n    middleware.handleWorkDoneProgress = undefined\n\n    // Ensure all events were handled.\n    assert.deepStrictEqual(\n      middlewareEvents.map(p => p.kind),\n      ['begin', 'report', 'end', 'begin', 'report', 'end'],\n    )\n    await client.sendRequest(\n      new ProtocolRequestType<any, null, never, any, any>('testing/beginOnlyProgress'),\n      {},\n      tokenSource.token,\n    )\n  })\n\n  test('Progress percentage is an integer', async () => {\n    const progressToken = 'TEST-PROGRESS-PERCENTAGE'\n    const percentages: Array<number | undefined> = []\n    let currentProgressResolver: (value: unknown) => void | undefined\n\n    // Set up middleware that calls the current resolve function when it gets its 'end' progress event.\n    middleware.handleWorkDoneProgress = (token: ProgressToken, params: WorkDoneProgressBegin | WorkDoneProgressReport | WorkDoneProgressEnd, next) => {\n      if (token === progressToken) {\n        const percentage = params.kind === 'report' || params.kind === 'begin' ? params.percentage : undefined\n        percentages.push(percentage)\n\n        if (params.kind === 'end') {\n          setImmediate(currentProgressResolver)\n        }\n      }\n      return next(token, params)\n    }\n\n    // Trigger a progress event.\n    await new Promise<unknown>(resolve => {\n      currentProgressResolver = resolve\n      void client.sendRequest(\n        new ProtocolRequestType<any, null, never, any, any>('testing/sendPercentageProgress'),\n        {},\n        tokenSource.token,\n      )\n    })\n\n    middleware.handleWorkDoneProgress = undefined\n\n    // Ensure percentages are rounded according to the spec\n    assert.deepStrictEqual(percentages, [0, 50, undefined])\n  })\n\n  test('Document Formatting', async () => {\n    const provider = client.getFeature(DocumentFormattingRequest.method).getProvider(document)\n    isDefined(provider)\n    const result = await provider.provideDocumentFormattingEdits(document, { tabSize: 4, insertSpaces: false }, tokenSource.token)\n\n    isArray(result, TextEdit)\n    const edit = result[0]\n    assert.strictEqual(edit.newText, 'insert')\n    rangeEqual(edit.range, 0, 0, 0, 0)\n\n    let middlewareCalled = true\n    middleware.provideDocumentFormattingEdits = (d, c, t, n) => {\n      middlewareCalled = true\n      return n(d, c, t)\n    }\n    await provider.provideDocumentFormattingEdits(document, { tabSize: 4, insertSpaces: false }, tokenSource.token)\n    middleware.provideDocumentFormattingEdits = undefined\n    assert.ok(middlewareCalled)\n  })\n\n  test('Document Range Formatting', async () => {\n    const provider = client.getFeature(DocumentRangeFormattingRequest.method).getProvider(document)\n    isDefined(provider)\n    const result = await provider.provideDocumentRangeFormattingEdits(document, range, { tabSize: 4, insertSpaces: false }, tokenSource.token)\n\n    isArray(result, TextEdit)\n    const edit = result[0]\n    assert.strictEqual(edit.newText, '')\n    rangeEqual(edit.range, 1, 1, 1, 2)\n\n    let middlewareCalled = true\n    middleware.provideDocumentRangeFormattingEdits = (d, r, c, t, n) => {\n      middlewareCalled = true\n      return n(d, r, c, t)\n    }\n    await provider.provideDocumentRangeFormattingEdits(document, range, { tabSize: 4, insertSpaces: false }, tokenSource.token)\n    middleware.provideDocumentFormattingEdits = undefined\n    assert.ok(middlewareCalled)\n  })\n\n  test('Document on Type Formatting', async () => {\n    const provider = client.getFeature(DocumentOnTypeFormattingRequest.method).getProvider(document)\n    isDefined(provider)\n    const result = await provider.provideOnTypeFormattingEdits(document, position, 'a', { tabSize: 4, insertSpaces: false }, tokenSource.token)\n\n    isArray(result, TextEdit)\n    const edit = result[0]\n    assert.strictEqual(edit.newText, 'replace')\n    rangeEqual(edit.range, 2, 2, 2, 3)\n\n    let middlewareCalled = true\n    middleware.provideOnTypeFormattingEdits = (d, p, s, c, t, n) => {\n      middlewareCalled = true\n      return n(d, p, s, c, t)\n    }\n    await provider.provideOnTypeFormattingEdits(document, position, 'a', { tabSize: 4, insertSpaces: false }, tokenSource.token)\n    middleware.provideDocumentFormattingEdits = undefined\n    assert.ok(middlewareCalled)\n  })\n\n  test('Rename', async () => {\n    const provider = client.getFeature(RenameRequest.method).getProvider(document)\n    isDefined(provider)\n    isDefined(provider.prepareRename)\n    const prepareResult = await provider.prepareRename(document, position, tokenSource.token) as Range\n\n    rangeEqual(prepareResult, 1, 1, 1, 2)\n    const renameResult = await provider.provideRenameEdits(document, position, 'newName', tokenSource.token)\n    assert.ok(WorkspaceEdit.is(renameResult))\n    let middlewareCalled = 0\n    middleware.prepareRename = (d, p, t, n) => {\n      middlewareCalled++\n      return n(d, p, t)\n    }\n    await provider.prepareRename(document, position, tokenSource.token)\n    middleware.prepareRename = undefined\n    middleware.provideRenameEdits = (d, p, w, t, n) => {\n      middlewareCalled++\n      return n(d, p, w, t)\n    }\n    await provider.provideRenameEdits(document, position, 'newName', tokenSource.token)\n    middleware.provideRenameEdits = undefined\n    assert.strictEqual(middlewareCalled, 2)\n  })\n\n  test('Document Link', async () => {\n    const provider = client.getFeature(DocumentLinkRequest.method).getProvider(document)\n    isDefined(provider)\n    const result = await provider.provideDocumentLinks(document, tokenSource.token)\n\n    isArray(result, DocumentLink)\n    const documentLink = result[0]\n    rangeEqual(documentLink.range, 1, 1, 1, 2)\n\n    let middlewareCalled = 0\n    middleware.provideDocumentLinks = (d, t, n) => {\n      middlewareCalled++\n      return n(d, t)\n    }\n    await provider.provideDocumentLinks(document, tokenSource.token)\n    middleware.provideDocumentLinks = undefined\n\n    isDefined(provider.resolveDocumentLink)\n    const resolved = await provider.resolveDocumentLink(documentLink, tokenSource.token)\n    isDefined(resolved.target)\n    assert.strictEqual(resolved.target.toString(), URI.file('/target.txt').toString())\n\n    middleware.resolveDocumentLink = (i, t, n) => {\n      middlewareCalled++\n      return n(i, t)\n    }\n    await provider.resolveDocumentLink(documentLink, tokenSource.token)\n    middleware.resolveDocumentLink = undefined\n    assert.strictEqual(middlewareCalled, 2)\n  })\n\n  test('Document Color', async () => {\n    const provider = client.getFeature(DocumentColorRequest.method).getProvider(document)\n    isDefined(provider)\n    const colors = await provider.provideDocumentColors(document, tokenSource.token)\n\n    isArray(colors, ColorInformation)\n    const color = colors[0]\n\n    rangeEqual(color.range, 1, 1, 1, 2)\n    colorEqual(color.color, 1, 1, 1, 1)\n\n    let middlewareCalled = 0\n    middleware.provideDocumentColors = (d, t, n) => {\n      middlewareCalled++\n      return n(d, t)\n    }\n    await provider.provideDocumentColors(document, tokenSource.token)\n    middleware.provideDocumentColors = undefined\n\n    const presentations = await provider.provideColorPresentations(color.color, { document, range }, tokenSource.token)\n\n    isArray(presentations, ColorPresentation)\n    const presentation = presentations[0]\n    assert.strictEqual(presentation.label, 'label')\n\n    middleware.provideColorPresentations = (c, x, t, n) => {\n      middlewareCalled++\n      return n(c, x, t)\n    }\n    await provider.provideColorPresentations(color.color, { document, range }, tokenSource.token)\n    middleware.provideColorPresentations = undefined\n    assert.strictEqual(middlewareCalled, 2)\n  })\n\n  test('Goto Declaration', async () => {\n    const provider = client.getFeature(DeclarationRequest.method).getProvider(document)\n    isDefined(provider)\n    const result = (await provider.provideDeclaration(document, position, tokenSource.token)) as Location\n\n    uriEqual(result.uri, uri)\n    rangeEqual(result.range, 1, 1, 1, 2)\n\n    let middlewareCalled = false\n    middleware.provideDeclaration = (document, position, token, next) => {\n      middlewareCalled = true\n      return next(document, position, token)\n    }\n    await provider.provideDeclaration(document, position, tokenSource.token)\n    middleware.provideDeclaration = undefined\n    assert.strictEqual(middlewareCalled, true)\n  })\n\n  test('Folding Ranges', async () => {\n    const feature = client.getFeature(FoldingRangeRequest.method) as FoldingRangeFeature\n    const providerData = feature.getProvider(document)\n    isDefined(providerData)\n\n    const provider = providerData.provider\n    isDefined(provider)\n    const result = (await provider.provideFoldingRanges(document, {}, tokenSource.token))\n\n    isArray(result, FoldingRange, 1)\n    const range = result[0]\n    assert.strictEqual(range.startLine, 1)\n    assert.strictEqual(range.endLine, 2)\n    let middlewareCalled = true\n    middleware.provideFoldingRanges = (d, c, t, n) => {\n      middlewareCalled = true\n      return n(d, c, t)\n    }\n    await provider.provideFoldingRanges(document, {}, tokenSource.token)\n    middleware.provideFoldingRanges = undefined\n    assert.ok(middlewareCalled)\n  })\n\n  test('Goto Implementation', async () => {\n    const provider = client.getFeature(ImplementationRequest.method).getProvider(document)\n    isDefined(provider)\n    const result = (await provider.provideImplementation(document, position, tokenSource.token)) as Location\n\n    uriEqual(result.uri, uri)\n    rangeEqual(result.range, 2, 2, 3, 3)\n\n    let middlewareCalled = false\n    middleware.provideImplementation = (document, position, token, next) => {\n      middlewareCalled = true\n      return next(document, position, token)\n    }\n    await provider.provideImplementation(document, position, tokenSource.token)\n    middleware.provideImplementation = undefined\n    assert.strictEqual(middlewareCalled, true)\n  })\n\n  test('Selection Range', async () => {\n    const provider = client.getFeature(SelectionRangeRequest.method).getProvider(document)\n    isDefined(provider)\n    const result = (await provider.provideSelectionRanges(document, [position], tokenSource.token))\n\n    isArray(result, SelectionRange, 1)\n    const range = result[0]\n    rangeEqual(range.range, 1, 2, 3, 4)\n    let middlewareCalled = false\n    middleware.provideSelectionRanges = (d, p, t, n) => {\n      middlewareCalled = true\n      return n(d, p, t)\n    }\n    await provider.provideSelectionRanges(document, [position], tokenSource.token)\n    middleware.provideSelectionRanges = undefined\n    assert.strictEqual(middlewareCalled, true)\n  })\n\n  test('Type Definition', async () => {\n    const provider = client.getFeature(TypeDefinitionRequest.method).getProvider(document)\n    isDefined(provider)\n    const result = (await provider.provideTypeDefinition(document, position, tokenSource.token)) as Location\n\n    uriEqual(result.uri, uri)\n    rangeEqual(result.range, 2, 2, 3, 3)\n\n    let middlewareCalled = false\n    middleware.provideTypeDefinition = (document, position, token, next) => {\n      middlewareCalled = true\n      return next(document, position, token)\n    }\n    await provider.provideTypeDefinition(document, position, tokenSource.token)\n    middleware.provideTypeDefinition = undefined\n    assert.strictEqual(middlewareCalled, true)\n  })\n\n  test('Call Hierarchy', async () => {\n    const provider = client.getFeature(CallHierarchyPrepareRequest.method).getProvider(document)\n    isDefined(provider)\n    const result = (await provider.prepareCallHierarchy(document, position, tokenSource.token)) as CallHierarchyItem[]\n    expect(result.length).toBe(1)\n\n    let middlewareCalled = false\n    middleware.prepareCallHierarchy = (d, p, t, n) => {\n      middlewareCalled = true\n      return n(d, p, t)\n    }\n    await provider.prepareCallHierarchy(document, position, tokenSource.token)\n    middleware.prepareCallHierarchy = undefined\n    assert.strictEqual(middlewareCalled, true)\n\n    const item = result[0]\n    const incoming = (await provider.provideCallHierarchyIncomingCalls(item, tokenSource.token)) as CallHierarchyIncomingCall[]\n    expect(incoming.length).toBe(1)\n    assert.deepEqual(incoming[0].from, item)\n    middlewareCalled = false\n    middleware.provideCallHierarchyIncomingCalls = (i, t, n) => {\n      middlewareCalled = true\n      return n(i, t)\n    }\n    await provider.provideCallHierarchyIncomingCalls(item, tokenSource.token)\n    middleware.provideCallHierarchyIncomingCalls = undefined\n    assert.strictEqual(middlewareCalled, true)\n\n    const outgoing = (await provider.provideCallHierarchyOutgoingCalls(item, tokenSource.token)) as CallHierarchyOutgoingCall[]\n    expect(outgoing.length).toBe(1)\n    assert.deepEqual(outgoing[0].to, item)\n    middlewareCalled = false\n    middleware.provideCallHierarchyOutgoingCalls = (i, t, n) => {\n      middlewareCalled = true\n      return n(i, t)\n    }\n    await provider.provideCallHierarchyOutgoingCalls(item, tokenSource.token)\n    middleware.provideCallHierarchyOutgoingCalls = undefined\n    assert.strictEqual(middlewareCalled, true)\n  })\n\n  const referenceFileUri = URI.parse('/dummy-edit')\n  function ensureReferenceEdit(edits: WorkspaceEdit, type: string, expectedLines: string[]) {\n    // Ensure the edits are as expected.\n    assert.strictEqual(edits.documentChanges?.length, 1)\n    const edit = edits.documentChanges[0] as TextDocumentEdit\n    assert.strictEqual(edit.edits.length, 1)\n    assert.strictEqual(edit.textDocument.uri, referenceFileUri.path)\n    const expectedTextEdit = edit.edits[0] as TextEdit\n    assert.strictEqual(expectedTextEdit.newText.trim(), `${type}:\\n${expectedLines.join('\\n')}`.trim())\n  }\n  async function ensureNotificationReceived(type: string, params: any) {\n    const result = await client.sendRequest(\n      new ProtocolRequestType<any, any, never, any, any>('testing/lastFileOperationRequest'),\n      {},\n      tokenSource.token,\n    )\n    assert.strictEqual(result.type, type)\n    assert.deepEqual(result.params, params)\n    assert.deepEqual(result, {\n      type,\n      params\n    })\n  }\n\n  const createFiles = [\n    '/my/file.txt',\n    '/my/file.js',\n    '/my/folder/',\n    // Static registration for tests is [operation]-static and *.txt\n    '/my/created-static/file.txt',\n    '/my/created-static/file.js',\n    '/my/created-static/folder/',\n    // Dynamic registration for tests is [operation]-dynamic and *.js\n    '/my/created-dynamic/file.txt',\n    '/my/created-dynamic/file.js',\n    '/my/created-dynamic/folder/',\n  ].map(p => URI.file(p))\n\n  const renameFiles = [\n    ['/my/file.txt', '/my-new/file.txt'],\n    ['/my/file.js', '/my-new/file.js'],\n    ['/my/folder/', '/my-new/folder/'],\n    // Static registration for tests is [operation]-static and *.txt\n    ['/my/renamed-static/file.txt', '/my-new/renamed-static/file.txt'],\n    ['/my/renamed-static/file.js', '/my-new/renamed-static/file.js'],\n    ['/my/renamed-static/folder/', '/my-new/renamed-static/folder/'],\n    // Dynamic registration for tests is [operation]-dynamic and *.js\n    ['/my/renamed-dynamic/file.txt', '/my-new/renamed-dynamic/file.txt'],\n    ['/my/renamed-dynamic/file.js', '/my-new/renamed-dynamic/file.js'],\n    ['/my/renamed-dynamic/folder/', '/my-new/renamed-dynamic/folder/'],\n  ].map(([o, n]) => ({ oldUri: URI.file(o), newUri: URI.file(n) }))\n\n  const deleteFiles = [\n    '/my/file.txt',\n    '/my/file.js',\n    '/my/folder/',\n    // Static registration for tests is [operation]-static and *.txt\n    '/my/deleted-static/file.txt',\n    '/my/deleted-static/file.js',\n    '/my/deleted-static/folder/',\n    // Dynamic registration for tests is [operation]-dynamic and *.js\n    '/my/deleted-dynamic/file.txt',\n    '/my/deleted-dynamic/file.js',\n    '/my/deleted-dynamic/folder/',\n  ].map(p => URI.file(p))\n\n  test('File Operations - Will Create Files', async () => {\n    const feature = client.getFeature(WillCreateFilesRequest.method)\n    isDefined(feature)\n\n    const sendCreateRequest = () => new Promise<WorkspaceEdit>(async (resolve, reject) => {\n      void feature.send({ token: CancellationToken.None, files: createFiles, waitUntil: resolve })\n      // If feature.send didn't call waitUntil synchronously then something went wrong.\n      reject(new Error('Feature unexpectedly did not call waitUntil synchronously'))\n    })\n\n    // Send the event and ensure the server responds with an edit referencing the\n    // correct files.\n    let edits = await sendCreateRequest()\n    ensureReferenceEdit(\n      edits,\n      'WILL CREATE',\n      [\n        'file:///my/created-static/file.txt',\n        'file:///my/created-static/folder/',\n        'file:///my/created-dynamic/file.js',\n        'file:///my/created-dynamic/folder/',\n      ],\n    )\n\n    // Add middleware that strips out any folders.\n    middleware.workspace = middleware.workspace || {}\n    middleware.workspace.willCreateFiles = (event, next) => next({\n      ...event,\n      files: event.files.filter(f => !f.path.endsWith('/')),\n    })\n\n    // Ensure we get the same results minus the folders that the middleware removed.\n    edits = await sendCreateRequest()\n    ensureReferenceEdit(\n      edits,\n      'WILL CREATE',\n      [\n        'file:///my/created-static/file.txt',\n        'file:///my/created-dynamic/file.js',\n      ],\n    )\n\n    middleware.workspace.willCreateFiles = undefined\n  })\n\n  test('File Operations - Did Create Files', async () => {\n    const feature = client.getFeature(DidCreateFilesNotification.method)\n    isDefined(feature)\n\n    // Send the event and ensure the server reports the notification was sent.\n    await feature.send({ files: createFiles })\n    await ensureNotificationReceived(\n      'create',\n      {\n        files: [\n          { uri: 'file:///my/created-static/file.txt' },\n          { uri: 'file:///my/created-static/folder/' },\n          { uri: 'file:///my/created-dynamic/file.js' },\n          { uri: 'file:///my/created-dynamic/folder/' },\n        ],\n      },\n    )\n\n    // Add middleware that strips out any folders.\n    middleware.workspace = middleware.workspace || {}\n    middleware.workspace.didCreateFiles = (event, next) => next({\n      files: event.files.filter(f => !f.path.endsWith('/')),\n    })\n\n    // Ensure we get the same results minus the folders that the middleware removed.\n    await feature.send({ files: createFiles })\n    await ensureNotificationReceived(\n      'create',\n      {\n        files: [\n          { uri: 'file:///my/created-static/file.txt' },\n          { uri: 'file:///my/created-dynamic/file.js' },\n        ],\n      },\n    )\n\n    middleware.workspace.didCreateFiles = undefined\n  })\n\n  test('File Operations - Will Rename Files', async () => {\n    const feature = client.getFeature(WillRenameFilesRequest.method)\n    isDefined(feature)\n\n    const sendRenameRequest = () => new Promise<WorkspaceEdit>(async (resolve, reject) => {\n      void feature.send({ files: renameFiles, waitUntil: resolve })\n      // If feature.send didn't call waitUntil synchronously then something went wrong.\n      reject(new Error('Feature unexpectedly did not call waitUntil synchronously'))\n    })\n\n    // Send the event and ensure the server responds with an edit referencing the\n    // correct files.\n    let edits = await sendRenameRequest()\n    ensureReferenceEdit(\n      edits,\n      'WILL RENAME',\n      [\n        'file:///my/renamed-static/file.txt -> file:///my-new/renamed-static/file.txt',\n        'file:///my/renamed-static/folder/ -> file:///my-new/renamed-static/folder/',\n        'file:///my/renamed-dynamic/file.js -> file:///my-new/renamed-dynamic/file.js',\n        'file:///my/renamed-dynamic/folder/ -> file:///my-new/renamed-dynamic/folder/',\n      ],\n    )\n\n    // Add middleware that strips out any folders.\n    middleware.workspace = middleware.workspace || {}\n    middleware.workspace.willRenameFiles = (event, next) => next({\n      ...event,\n      files: event.files.filter(f => !f.oldUri.path.endsWith('/')),\n    })\n\n    // Ensure we get the same results minus the folders that the middleware removed.\n    edits = await sendRenameRequest()\n    ensureReferenceEdit(\n      edits,\n      'WILL RENAME',\n      [\n        'file:///my/renamed-static/file.txt -> file:///my-new/renamed-static/file.txt',\n        'file:///my/renamed-dynamic/file.js -> file:///my-new/renamed-dynamic/file.js',\n      ],\n    )\n\n    middleware.workspace.willRenameFiles = undefined\n  })\n\n  test('File Operations - Did Rename Files', async () => {\n    const feature = client.getFeature(DidRenameFilesNotification.method)\n    isDefined(feature)\n\n    // Send the event and ensure the server reports the notification was sent.\n    await feature.send({ files: renameFiles })\n    await ensureNotificationReceived(\n      'rename',\n      {\n        files: [\n          { oldUri: 'file:///my/renamed-static/file.txt', newUri: 'file:///my-new/renamed-static/file.txt' },\n          { oldUri: 'file:///my/renamed-static/folder/', newUri: 'file:///my-new/renamed-static/folder/' },\n          { oldUri: 'file:///my/renamed-dynamic/file.js', newUri: 'file:///my-new/renamed-dynamic/file.js' },\n          { oldUri: 'file:///my/renamed-dynamic/folder/', newUri: 'file:///my-new/renamed-dynamic/folder/' },\n        ],\n      },\n    )\n\n    // Add middleware that strips out any folders.\n    middleware.workspace = middleware.workspace || {}\n    middleware.workspace.didRenameFiles = (event, next) => next({\n      files: event.files.filter(f => !f.oldUri.path.endsWith('/')),\n    })\n\n    // Ensure we get the same results minus the folders that the middleware removed.\n    await feature.send({ files: renameFiles })\n    await ensureNotificationReceived(\n      'rename',\n      {\n        files: [\n          { oldUri: 'file:///my/renamed-static/file.txt', newUri: 'file:///my-new/renamed-static/file.txt' },\n          { oldUri: 'file:///my/renamed-dynamic/file.js', newUri: 'file:///my-new/renamed-dynamic/file.js' },\n        ],\n      },\n    )\n\n    middleware.workspace.didRenameFiles = undefined\n  })\n\n  test('File Operations - Will Delete Files', async () => {\n    const feature = client.getFeature(WillDeleteFilesRequest.method)\n    isDefined(feature)\n\n    const sendDeleteRequest = () => new Promise<WorkspaceEdit>(async (resolve, reject) => {\n      void feature.send({ files: deleteFiles, waitUntil: resolve })\n      // If feature.send didn't call waitUntil synchronously then something went wrong.\n      reject(new Error('Feature unexpectedly did not call waitUntil synchronously'))\n    })\n\n    // Send the event and ensure the server responds with an edit referencing the\n    // correct files.\n    let edits = await sendDeleteRequest()\n    ensureReferenceEdit(\n      edits,\n      'WILL DELETE',\n      [\n        'file:///my/deleted-static/file.txt',\n        'file:///my/deleted-static/folder/',\n        'file:///my/deleted-dynamic/file.js',\n        'file:///my/deleted-dynamic/folder/',\n      ],\n    )\n\n    // Add middleware that strips out any folders.\n    middleware.workspace = middleware.workspace || {}\n    middleware.workspace.willDeleteFiles = (event, next) => next({\n      ...event,\n      files: event.files.filter(f => !f.path.endsWith('/')),\n    })\n\n    // Ensure we get the same results minus the folders that the middleware removed.\n    edits = await sendDeleteRequest()\n    ensureReferenceEdit(\n      edits,\n      'WILL DELETE',\n      [\n        'file:///my/deleted-static/file.txt',\n        'file:///my/deleted-dynamic/file.js',\n      ],\n    )\n\n    middleware.workspace.willDeleteFiles = undefined\n  })\n\n  test('File Operations - Did Delete Files', async () => {\n    const feature = client.getFeature(DidDeleteFilesNotification.method)\n    isDefined(feature)\n\n    // Send the event and ensure the server reports the notification was sent.\n    await feature.send({ files: deleteFiles })\n    await ensureNotificationReceived(\n      'delete',\n      {\n        files: [\n          { uri: 'file:///my/deleted-static/file.txt' },\n          { uri: 'file:///my/deleted-static/folder/' },\n          { uri: 'file:///my/deleted-dynamic/file.js' },\n          { uri: 'file:///my/deleted-dynamic/folder/' },\n        ],\n      },\n    )\n\n    // Add middleware that strips out any folders.\n    middleware.workspace = middleware.workspace || {}\n    middleware.workspace.didDeleteFiles = (event, next) => next({\n      files: event.files.filter(f => !f.path.endsWith('/')),\n    })\n\n    // Ensure we get the same results minus the folders that the middleware removed.\n    await feature.send({ files: deleteFiles })\n    await ensureNotificationReceived(\n      'delete',\n      {\n        files: [\n          { uri: 'file:///my/deleted-static/file.txt' },\n          { uri: 'file:///my/deleted-dynamic/file.js' },\n        ],\n      },\n    )\n\n    middleware.workspace.didDeleteFiles = undefined\n  })\n\n  test('Semantic Tokens', async () => {\n    const provider = client.getFeature(SemanticTokensRegistrationType.method).getProvider(document)\n    const rangeProvider = provider?.range\n    isDefined(rangeProvider)\n    const rangeResult = await rangeProvider.provideDocumentRangeSemanticTokens(document, range, tokenSource.token)\n    assert.ok(rangeResult !== undefined)\n\n    let middlewareCalled = false\n    middleware.provideDocumentRangeSemanticTokens = (d, r, t, n) => {\n      middlewareCalled = true\n      return n(d, r, t)\n    }\n    await rangeProvider.provideDocumentRangeSemanticTokens(document, range, tokenSource.token)\n    middleware.provideDocumentRangeSemanticTokens = undefined\n    assert.strictEqual(middlewareCalled, true)\n\n    const fullProvider = provider?.full\n    isDefined(fullProvider)\n    const fullResult = await fullProvider.provideDocumentSemanticTokens(document, tokenSource.token)\n    assert.ok(fullResult !== undefined)\n\n    middlewareCalled = false\n    middleware.provideDocumentSemanticTokens = (d, t, n) => {\n      middlewareCalled = true\n      return n(d, t)\n    }\n    await fullProvider.provideDocumentSemanticTokens(document, tokenSource.token)\n    middleware.provideDocumentSemanticTokens = undefined\n    assert.strictEqual(middlewareCalled, true)\n\n    middlewareCalled = false\n    middleware.provideDocumentSemanticTokensEdits = (d, i, t, n) => {\n      middlewareCalled = true\n      return n(d, i, t)\n    }\n    await fullProvider.provideDocumentSemanticTokensEdits!(document, '2', tokenSource.token)\n    middleware.provideDocumentSemanticTokensEdits = undefined\n    assert.strictEqual(middlewareCalled, true)\n    let called = false\n    provider.onDidChangeSemanticTokensEmitter.event(() => {\n      called = true\n    })\n    await client.sendNotification('fireSemanticTokensRefresh')\n    await helper.waitValue(() => {\n      return called\n    }, true)\n  })\n\n  test('Linked Editing Ranges', async () => {\n    const provider = client.getFeature(LinkedEditingRangeRequest.method).getProvider(document)\n    isDefined(provider)\n    const result = await provider.provideLinkedEditingRanges(document, position, tokenSource.token)\n\n    isArray(result.ranges, Range, 1)\n    rangeEqual(result.ranges[0], 1, 1, 1, 1)\n\n    let middlewareCalled = false\n    middleware.provideLinkedEditingRange = (document, position, token, next) => {\n      middlewareCalled = true\n      return next(document, position, token)\n    }\n    await provider.provideLinkedEditingRanges(document, position, tokenSource.token)\n    middleware.provideTypeDefinition = undefined\n    assert.strictEqual(middlewareCalled, true)\n  })\n\n  test('Document diagnostic pull', async () => {\n    const provider = client.getFeature(DocumentDiagnosticRequest.method)?.getProvider(document)\n    isDefined(provider)\n    const result = await provider.diagnostics.provideDiagnostics(document, undefined, tokenSource.token)\n    isDefined(result)\n    isFullDocumentDiagnosticReport(result)\n\n    const diag = result.items[0]\n    rangeEqual(diag.range, 1, 1, 1, 1)\n    assert.strictEqual(diag.message, 'diagnostic')\n\n    let middlewareCalled = false\n    middleware.provideDiagnostics = (document, previousResultId, token, next) => {\n      middlewareCalled = true\n      return next(document, previousResultId, token)\n    }\n    await provider.diagnostics.provideDiagnostics(document, undefined, tokenSource.token)\n    middleware.provideDiagnostics = undefined\n    assert.strictEqual(middlewareCalled, true)\n  })\n\n  test('Workspace diagnostic pull', async () => {\n    const provider = client.getFeature(DocumentDiagnosticRequest.method)?.getProvider(document)\n    isDefined(provider)\n    isDefined(provider.diagnostics.provideWorkspaceDiagnostics)\n    await provider.diagnostics.provideWorkspaceDiagnostics([], tokenSource.token, result => {\n      isDefined(result)\n      isArray(result.items, undefined, 1)\n    })\n\n    let middlewareCalled = false\n    middleware.provideWorkspaceDiagnostics = (resultIds, token, reporter, next) => {\n      middlewareCalled = true\n      return next(resultIds, token, reporter)\n    }\n    await provider.diagnostics.provideWorkspaceDiagnostics([], tokenSource.token, () => {})\n    middleware.provideWorkspaceDiagnostics = undefined\n    assert.strictEqual(middlewareCalled, true)\n  })\n\n  test('Type Hierarchy', async () => {\n    const provider = client.getFeature(TypeHierarchyPrepareRequest.method).getProvider(document)\n    isDefined(provider)\n    const result = await provider.prepareTypeHierarchy(document, position, tokenSource.token)\n\n    isArray(result, undefined, 1)\n    const item = result[0]\n\n    let middlewareCalled = false\n    middleware.prepareTypeHierarchy = (d, p, t, n) => {\n      middlewareCalled = true\n      return n(d, p, t)\n    }\n    await provider.prepareTypeHierarchy(document, position, tokenSource.token)\n    middleware.prepareTypeHierarchy = undefined\n    assert.strictEqual(middlewareCalled, true)\n\n    const incoming = await provider.provideTypeHierarchySupertypes(item, tokenSource.token)\n    isArray(incoming, undefined, 1)\n    middlewareCalled = false\n    middleware.provideTypeHierarchySupertypes = (i, t, n) => {\n      middlewareCalled = true\n      return n(i, t)\n    }\n    await provider.provideTypeHierarchySupertypes(item, tokenSource.token)\n    middleware.provideTypeHierarchySupertypes = undefined\n    assert.strictEqual(middlewareCalled, true)\n\n    const outgoing = await provider.provideTypeHierarchySubtypes(item, tokenSource.token)\n    isArray(outgoing, undefined, 1)\n    middlewareCalled = false\n    middleware.provideTypeHierarchySubtypes = (i, t, n) => {\n      middlewareCalled = true\n      return n(i, t)\n    }\n    await provider.provideTypeHierarchySubtypes(item, tokenSource.token)\n    middleware.provideTypeHierarchySubtypes = undefined\n    assert.strictEqual(middlewareCalled, true)\n  })\n\n  test('Inline Values', async () => {\n    const providerData = client.getFeature(InlineValueRequest.method).getProvider(document)\n    isDefined(providerData)\n    const provider = providerData.provider\n    const results = (await provider.provideInlineValues(document, range, { frameId: 1, stoppedLocation: range }, tokenSource.token))\n\n    isArray(results, undefined, 3)\n\n    for (const r of results) {\n      rangeEqual(r.range, 1, 2, 3, 4)\n    }\n\n    // assert.ok(results[0] instanceof InlineValueText)\n    assert.strictEqual((results[0] as InlineValueText).text, 'text')\n\n    // assert.ok(results[1] instanceof InlineValueVariableLookup)\n    assert.strictEqual((results[1] as InlineValueVariableLookup).variableName, 'variableName')\n\n    // assert.ok(results[2] instanceof InlineValueEvaluatableExpression)\n    assert.strictEqual((results[2] as InlineValueEvaluatableExpression).expression, 'expression')\n\n    let middlewareCalled = false\n    middleware.provideInlineValues = (d, r, c, t, n) => {\n      middlewareCalled = true\n      return n(d, r, c, t)\n    }\n    await provider.provideInlineValues(document, range, { frameId: 1, stoppedLocation: range }, tokenSource.token)\n    middleware.provideInlineValues = undefined\n    assert.strictEqual(middlewareCalled, true)\n  })\n\n  test('Inlay Hints', async () => {\n    let feature = client.getFeature(InlayHintRequest.method) as InlayHintsFeature\n    const providerData = feature.getProvider(document)\n    expect(feature.getProvider(TextDocument.create('term:///1', 'foo', 1, '\\n'))).toBeUndefined()\n    feature.register({ id: uuidv4(), registerOptions: { documentSelector: null } })\n    let res = feature.getRegistration([], { id: '1', workDoneProgress: '' } as any)\n    expect(res).toEqual([undefined, undefined])\n    isDefined(providerData)\n    const provider = providerData.provider\n    const results = (await provider.provideInlayHints(document, range, tokenSource.token))\n\n    isArray(results, undefined, 2)\n\n    const hint = results[0]\n    positionEqual(hint.position, 1, 1)\n    assert.strictEqual(hint.kind, InlayHintKind.Type)\n    const label = hint.label\n    isArray(label as [], InlayHintLabelPart, 1)\n    assert.strictEqual((label as InlayHintLabelPart[])[0].value, 'type')\n\n    let middlewareCalled = false\n    middleware.provideInlayHints = (d, r, t, n) => {\n      middlewareCalled = true\n      return n(d, r, t)\n    }\n    await provider.provideInlayHints(document, range, tokenSource.token)\n    middleware.provideInlayHints = undefined\n    assert.strictEqual(middlewareCalled, true)\n    assert.ok(typeof provider.resolveInlayHint === 'function')\n\n    const resolvedHint = await provider.resolveInlayHint!(hint, tokenSource.token)\n    assert.strictEqual((resolvedHint?.label as InlayHintLabelPart[])[0].tooltip, 'tooltip')\n    let called = false\n    await client.sendNotification('fireInlayHintsRefresh')\n    provider.onDidChangeInlayHints(() => {\n      called = true\n    })\n    await helper.waitValue(() => {\n      return called\n    }, true)\n  })\n\n  test('Inline Completions', async () => {\n    const provider = client.getFeature(InlineCompletionRequest.method).getProvider(document)\n    isDefined(provider)\n    const results = (await provider.provideInlineCompletionItems(document, position, { triggerKind: 1, selectedCompletionInfo: { range, text: 'text' } }, tokenSource.token)) as InlineCompletionItem[]\n\n    isArray(results, InlineCompletionItem, 1)\n\n    rangeEqual(results[0].range!, 1, 2, 3, 4)\n    assert.strictEqual(results[0].filterText!, 'te')\n    assert.strictEqual(results[0].insertText, 'text inline')\n\n    let middlewareCalled = false\n    middleware.provideInlineCompletionItems = (d, r, c, t, n) => {\n      middlewareCalled = true\n      return n(d, r, c, t)\n    }\n    await provider.provideInlineCompletionItems(document, position, { triggerKind: 1, selectedCompletionInfo: undefined }, tokenSource.token)\n    middleware.provideInlineCompletionItems = undefined\n    assert.strictEqual(middlewareCalled, true)\n  })\n\n  test('Workspace symbols', async () => {\n    const providers = client.getFeature(WorkspaceSymbolRequest.method).getProviders()\n    isDefined(providers)\n    assert.strictEqual(providers.length, 2)\n    const provider = providers[0]\n    const results = await provider.provideWorkspaceSymbols('', tokenSource.token)\n    isArray(results, undefined, 1)\n\n    assert.strictEqual(results.length, 1)\n\n    const symbol = await provider.resolveWorkspaceSymbol!(results[0], tokenSource.token)\n    isDefined(symbol)\n    rangeEqual(symbol.location['range'], 1, 2, 3, 4)\n  })\n\n  test('Text Document Content', async () => {\n    let feature = client.getFeature(TextDocumentContentRequest.method)\n    const providers = feature?.getProviders()\n    isDefined(providers)\n    assert.strictEqual(providers.length, 1)\n    const provider = providers[0].provider\n    const result = await provider.provideTextDocumentContent(URI.parse('content-test:///test.txt'), tokenSource.token)\n    assert.strictEqual(result, 'Some test content')\n    feature.unregister('foo')\n    expect(feature.getState()).toBeDefined()\n\n    let middlewareCalled = false\n    middleware.provideTextDocumentContent = (uri, token, next) => {\n      middlewareCalled = true\n      return next(uri, token)\n    }\n    await provider.provideTextDocumentContent(URI.parse('content-test:///test.txt'), tokenSource.token)\n    middleware.provideTextDocumentContent = undefined\n    assert.strictEqual(middlewareCalled, true)\n  })\n\n  test('General middleware', async () => {\n    let middlewareCallCount = 0\n    let throwError = false\n    // Add a general middleware for both requests and notifications\n    middleware.sendRequest = (type, param, token, next) => {\n      middlewareCallCount++\n      return next(type, param, token)\n    }\n    middleware.sendNotification = (type, next, params) => {\n      if (throwError) throw new Error('myerror')\n      middlewareCallCount++\n      return next(type, params)\n    }\n    // Send a request\n    const definitionProvider = client.getFeature(DefinitionRequest.method).getProvider(document)\n    isDefined(definitionProvider)\n    await definitionProvider.provideDefinition(document, position, tokenSource.token)\n    // Send a notification\n    const notificationProvider = client.getFeature(DidSaveTextDocumentNotification.method).getProvider(document)\n    isDefined(notificationProvider)\n    await notificationProvider.send(document)\n    throwError = true\n    await assert.rejects(async () => {\n      await client.sendNotification('not_exists')\n    }, /myerror/)\n    // Verify that both the request and notification went through the middleware\n    middleware.sendRequest = undefined\n    middleware.sendNotification = undefined\n    assert.strictEqual(middlewareCallCount, 2)\n  })\n\n  test('applyEdit middleware', async () => {\n    const middlewareEvents: Array<ApplyWorkspaceEditParams> = []\n    let currentProgressResolver: (value: unknown) => void | undefined\n    let error = false\n\n    middleware.workspace = middleware.workspace || {}\n    middleware.workspace.handleApplyEdit = async (params, next) => {\n      middlewareEvents.push(params)\n      setImmediate(currentProgressResolver)\n      if (error) return new ResponseError(ErrorCodes.InternalError, 'myerror')\n      return next(params, tokenSource.token)\n    }\n\n    // Trigger sample applyEdit event.\n    await new Promise<unknown>(resolve => {\n      currentProgressResolver = resolve\n      void client.sendRequest(\n        new ProtocolRequestType<any, null, never, any, any>('testing/sendApplyEdit'),\n        {},\n        tokenSource.token,\n      )\n    })\n\n    // Ensure event was handled.\n    assert.strictEqual(middlewareEvents.length, 1)\n    assert.strictEqual(middlewareEvents[0].label, 'Apply Edit')\n    error = true\n    let called = false\n    let spy = jest.spyOn(client, 'error').mockImplementation(() => {\n      called = true\n    })\n    await client.sendRequest(\n      new ProtocolRequestType<any, null, never, any, any>('testing/sendApplyEdit'),\n      {},\n      tokenSource.token,\n    )\n    await helper.waitValue(() => called, true)\n    middleware.workspace.handleApplyEdit = undefined\n    spy.mockRestore()\n  })\n})\n\nnamespace CrashNotification {\n  export const type = new NotificationType0('test/crash')\n}\n\nclass CrashClient extends LanguageClient {\n\n  private resolve: (() => void) | undefined\n  public onCrash: Promise<void>\n\n  constructor(id: string, name: string, serverOptions: ServerOptions, clientOptions: LanguageClientOptions) {\n    super(id, name, serverOptions, clientOptions)\n    this.onCrash = new Promise(resolve => {\n      this.resolve = resolve\n    })\n  }\n\n  protected async handleConnectionClosed(): Promise<void> {\n    await super.handleConnectionClosed()\n    this.resolve!()\n  }\n}\n\ndescribe('sever tests', () => {\n  test('Stop fails if server crashes after shutdown request', async () => {\n    let file = path.join(__dirname, './server/crashOnShutdownServer.js')\n    const serverOptions: ServerOptions = {\n      module: file,\n      transport: TransportKind.ipc,\n    }\n    const clientOptions: LanguageClientOptions = {}\n    const client = new LanguageClient('test svr', 'Test Language Server', serverOptions, clientOptions)\n    await client._start()\n\n    await assert.rejects(async () => {\n      await client.stop()\n    }, /Pending response rejected since connection got disposed/)\n    assert.strictEqual(client.needsStart(), true)\n    assert.strictEqual(client.needsStop(), false)\n\n    // Stopping again should be a no-op.\n    await client.stop()\n    assert.strictEqual(client.needsStart(), true)\n    assert.strictEqual(client.needsStop(), false)\n    await helper.wait(20)\n  })\n\n  test('Stop not throw if server shutdown request times out', async () => {\n    const serverOptions: ServerOptions = {\n      module: path.join(__dirname, './server/timeoutOnShutdownServer.js'),\n      transport: TransportKind.ipc,\n    }\n    const clientOptions: LanguageClientOptions = {}\n    const client = new LanguageClient('test svr', 'Test Language Server', serverOptions, clientOptions)\n    await client._start()\n    await client.stop(10)\n  })\n\n  test('Server can not be stopped when connection not exists', async () => {\n    const serverOptions: ServerOptions = {\n      module: path.join(__dirname, './server/testServer.js'),\n      transport: TransportKind.ipc,\n    }\n    const clientOptions: LanguageClientOptions = {}\n    const client = new LanguageClient('test svr', 'Test Language Server', serverOptions, clientOptions)\n    let spy = jest.spyOn(client, 'createConnection' as any).mockReturnValue(Promise.reject(new Error('myerror')))\n    await assert.rejects(async () => {\n      await client.start()\n    }, Error)\n    await assert.rejects(async () => {\n      await client.stop()\n    }, /Client is not running and can't be stopped/)\n    spy.mockRestore()\n  })\n\n  test('Test state change events', async () => {\n    const serverOptions: ServerOptions = {\n      module: path.join(__dirname, './server/nullServer.js'),\n      transport: TransportKind.ipc,\n    }\n    const clientOptions: LanguageClientOptions = {}\n    const client = new LanguageClient('test svr', 'Test Language Server', serverOptions, clientOptions)\n    let state: State | undefined\n    client.onDidChangeState(event => {\n      state = event.newState\n    })\n    await client._start()\n    assert.strictEqual(state, State.Running, 'First start')\n\n    await client.stop()\n    assert.strictEqual(state, State.Stopped, 'First stop')\n\n    await client._start()\n    assert.strictEqual(state, State.Running, 'Second start')\n\n    await client.stop()\n    assert.strictEqual(state, State.Stopped, 'Second stop')\n  })\n\n  test('Test state change events on crash', async () => {\n    const serverOptions: ServerOptions = {\n      module: path.join(__dirname, './server/crashServer.js'),\n      transport: TransportKind.ipc,\n    }\n    const clientOptions: LanguageClientOptions = {}\n    const client = new CrashClient('test svr', 'Test Language Server', serverOptions, clientOptions)\n    let states: State[] = []\n    client.onDidChangeState(event => {\n      states.push(event.newState)\n    })\n    await client._start()\n    assert.strictEqual(states.length, 2, 'First start')\n    assert.strictEqual(states[0], State.Starting)\n    assert.strictEqual(states[1], State.Running)\n\n    states = []\n    await client.sendNotification(CrashNotification.type)\n    await client.onCrash\n\n    await client._start()\n    assert.strictEqual(states.length, 3, 'Restart after crash')\n    assert.strictEqual(states[0], State.Stopped)\n    assert.strictEqual(states[1], State.Starting)\n    assert.strictEqual(states[2], State.Running)\n\n    states = []\n    await client.stop()\n    assert.strictEqual(states.length, 1, 'After stop')\n    assert.strictEqual(states[0], State.Stopped)\n  })\n})\n\ndescribe('Server activation', () => {\n\n  const uri: URI = URI.parse('lsptests://localhost/test.bat')\n  const documentSelector: DocumentSelector = [{ scheme: 'lsptests', language: '*' }]\n  const position: Position = Position.create(1, 1)\n  let contentProviderDisposable!: Disposable\n\n  beforeAll(async () => {\n    contentProviderDisposable = workspace.registerTextDocumentContentProvider('lsptests', {\n      provideTextDocumentContent: (_uri: URI) => {\n        return [\n          'REM @ECHO OFF'\n        ].join('\\n')\n      }\n    })\n\n  })\n\n  afterAll(async () => {\n    contentProviderDisposable.dispose()\n  })\n\n  function createClient(): LanguageClient {\n    const serverModule = path.join(__dirname, './server/customServer.js')\n    const serverOptions: ServerOptions = {\n      run: { module: serverModule, transport: TransportKind.ipc },\n      debug: { module: serverModule, transport: TransportKind.ipc, options: { execArgv: ['--nolazy', '--inspect=6014'] } }\n    }\n\n    const clientOptions: LanguageClientOptions = {\n      documentSelector,\n      synchronize: {},\n      initializationOptions: {},\n      middleware: {},\n    };\n    (clientOptions as ({ $testMode?: boolean })).$testMode = true\n\n    const result = new LanguageClient('test svr', 'Test Language Server', serverOptions, clientOptions)\n    result.registerProposedFeatures()\n    return result\n  }\n\n  test('Start server on request', async () => {\n    const client = createClient()\n    assert.strictEqual(client.state, State.Stopped)\n    const result: number = await client.sendRequest('request', { value: 10 })\n    assert.strictEqual(client.state, State.Running)\n    assert.strictEqual(result, 11)\n    await client.stop()\n  })\n\n  test('Start server fails on request when stopped once', async () => {\n    const client = createClient()\n    assert.strictEqual(client.state, State.Stopped)\n    const result: number = await client.sendRequest('request', { value: 10 })\n    assert.strictEqual(client.state, State.Running)\n    assert.strictEqual(result, 11)\n    await client.stop()\n    await assert.rejects(async () => {\n      await client.sendRequest('request', { value: 10 })\n    }, /Client is not running/)\n  })\n\n  test('Start server on notification', async () => {\n    const client = createClient()\n    assert.strictEqual(client.state, State.Stopped)\n    await client.sendNotification('notification')\n    assert.strictEqual(client.state, State.Running)\n    await client.stop()\n  })\n\n  test('Not fails on notification when stopped once', async () => {\n    const client = createClient()\n    assert.strictEqual(client.state, State.Stopped)\n    await client.sendNotification('notification')\n    assert.strictEqual(client.state, State.Running)\n    await client.stop()\n    await client.sendNotification('notification')\n  })\n\n  test('Add pending request handler', async () => {\n    const client = createClient()\n    assert.strictEqual(client.state, State.Stopped)\n    let requestReceived = false\n    client.onRequest('request', () => {\n      requestReceived = true\n    })\n    await client.sendRequest('triggerRequest')\n    assert.strictEqual(requestReceived, true)\n    await client.stop()\n  })\n\n  test('Add pending notification handler', async () => {\n    const client = createClient()\n    assert.strictEqual(client.state, State.Stopped)\n    let notificationReceived = false\n    client.onNotification('notification', () => {\n      notificationReceived = true\n    })\n    await client.sendRequest('triggerNotification')\n    assert.strictEqual(notificationReceived, true)\n    await client.stop()\n  })\n\n  test('Starting disposed server fails', async () => {\n    const client = createClient()\n    await client._start()\n    await client.dispose()\n    await assert.rejects(async () => {\n      await client._start()\n    }, /Client got disposed and can't be restarted./)\n  })\n\n  async function checkServerStart(client: LanguageClient, disposable: Disposable): Promise<void> {\n    return new Promise((resolve, reject) => {\n      const timeout = setTimeout(() => {\n        reject(new Error(`Server didn't start in 1000 ms.`))\n      }, 1000)\n      client.onDidChangeState(event => {\n        if (event.newState === State.Running) {\n          clearTimeout(timeout)\n          disposable.dispose()\n          resolve()\n        }\n      })\n    })\n  }\n\n  test('Start server on document open', async () => {\n    await workspace.nvim.command('silent! %bwipeout!')\n    const client = createClient()\n    assert.strictEqual(client.state, State.Stopped)\n    const started = checkServerStart(client, workspace.onDidOpenTextDocument(document => {\n      if (workspace.match([{ scheme: 'lsptests', pattern: uri.fsPath }], document) > 0) {\n        void client.start()\n      }\n    }))\n    await workspace.openTextDocument(uri)\n    await started\n    await client.stop()\n  })\n\n  test('Start server on language feature', async () => {\n    const client = createClient()\n    assert.strictEqual(client.state, State.Stopped)\n    const started = checkServerStart(client, languages.registerDeclarationProvider(documentSelector, {\n      provideDeclaration: async () => {\n        await client._start()\n        return undefined\n      }\n    }))\n    await workspace.jumpTo(uri)\n    await helper.doAction('declarations')\n    await started\n    await client.stop()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/client/fileSystemWatcher.test.ts",
    "content": "import path from 'path'\nimport { DidChangeWatchedFilesNotification, DocumentSelector, Emitter, Event, FileChangeType } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport { asRelativePattern } from '../../language-client/fileSystemWatcher'\nimport { LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from '../../language-client/index'\nimport { IFileSystemWatcher } from '../../types'\nimport helper from '../helper'\n\nfunction createClient(fileEvents: IFileSystemWatcher | IFileSystemWatcher[] | undefined, middleware: Middleware = {}): LanguageClient {\n  const serverModule = path.join(__dirname, './server/fileWatchServer.js')\n  const serverOptions: ServerOptions = {\n    run: { module: serverModule, transport: TransportKind.ipc },\n    debug: { module: serverModule, transport: TransportKind.ipc, options: { execArgv: ['--nolazy', '--inspect=6014'] } }\n  }\n\n  const documentSelector: DocumentSelector = [{ scheme: 'file' }]\n  const clientOptions: LanguageClientOptions = {\n    documentSelector,\n    synchronize: { fileEvents },\n    initializationOptions: {},\n    middleware\n  };\n  (clientOptions as ({ $testMode?: boolean })).$testMode = true\n\n  const result = new LanguageClient('test', 'Test Language Server', serverOptions, clientOptions)\n  return result\n}\n\nclass CustomWatcher implements IFileSystemWatcher {\n  public ignoreCreateEvents = false\n  public ignoreChangeEvents = false\n  public ignoreDeleteEvents = false\n  private readonly _onDidCreate = new Emitter<URI>()\n  public readonly onDidCreate: Event<URI> = this._onDidCreate.event\n  private readonly _onDidChange = new Emitter<URI>()\n  public readonly onDidChange: Event<URI> = this._onDidChange.event\n  private readonly _onDidDelete = new Emitter<URI>()\n  public readonly onDidDelete: Event<URI> = this._onDidDelete.event\n  constructor() {\n  }\n\n  public fireCreate(uri: URI): void {\n    this._onDidCreate.fire(uri)\n  }\n\n  public fireChange(uri: URI): void {\n    this._onDidChange.fire(uri)\n  }\n\n  public fireDelete(uri: URI): void {\n    this._onDidDelete.fire(uri)\n  }\n\n  public dispose() {\n  }\n}\n\nbeforeAll(async () => {\n  await helper.setup()\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\ndescribe('FileSystemWatcherFeature', () => {\n  it('should hook file events from client configuration', async () => {\n    let res = asRelativePattern({ baseUri: { name: 'name', uri: '/tmp' }, pattern: '**' })\n    expect(res.baseUri.fsPath).toBe('/tmp')\n    let client: LanguageClient\n    let watcher = new CustomWatcher()\n    let called = false\n    let changes: FileChangeType[] = []\n    client = createClient([watcher], {\n      workspace: {\n        didChangeWatchedFile: async (event, next): Promise<void> => {\n          called = true\n          if (event) {\n            changes.push(event.type)\n          }\n          return next(event)\n        }\n      }\n    })\n    let received: any[]\n    client.onNotification('filesChange', params => {\n      received = params.changes\n    })\n    await client.start()\n    expect(called).toBe(false)\n    client.notifyFileEvent(undefined)\n    await helper.wait(10)\n    let uri = URI.file(__filename)\n    watcher.fireCreate(uri)\n    expect(called).toBe(true)\n    watcher.fireChange(uri)\n    watcher.fireDelete(uri)\n    expect(changes).toEqual([1, 2, 3])\n    await helper.waitValue(() => {\n      return received?.length\n    }, 3)\n    await client.stop()\n    expect(received[2]).toEqual({\n      uri: uri.toString(),\n      type: 3\n    })\n  })\n\n  it('should work with single watcher', async () => {\n    let client: LanguageClient\n    let watcher = new CustomWatcher()\n    client = createClient(watcher, {})\n    let received: any[]\n    client.onNotification('filesChange', params => {\n      received = params.changes\n    })\n    await client.start()\n    let uri = URI.file(__filename)\n    watcher.fireCreate(uri)\n    await helper.waitValue(() => {\n      return received?.length\n    }, 1)\n    let called = false\n    let spy = jest.spyOn(client, 'sendNotification').mockImplementation(() => {\n      called = true\n      return Promise.reject(new Error('myerror'))\n    })\n    watcher.fireChange(uri)\n    await helper.waitValue(() => called, true)\n    spy.mockRestore()\n    await client.stop()\n  })\n\n  it('should support dynamic registration', async () => {\n    let client: LanguageClient\n    client = createClient(undefined)\n    await client.start()\n    await helper.waitValue(async () => {\n      let feature = client.getFeature(DidChangeWatchedFilesNotification.method)\n      if (feature) await (feature as any)._notifyFileEvent()\n      return feature != undefined\n    }, true)\n    await helper.waitValue(async () => {\n      let feature = client.getFeature(DidChangeWatchedFilesNotification.method)\n      let state = feature.getState()\n      return (state as any).registrations\n    }, true)\n    await client.sendNotification('unwatch')\n    await helper.waitValue(() => {\n      let feature = client.getFeature(DidChangeWatchedFilesNotification.method)\n      let state = feature.getState()\n      return (state as any)?.registrations\n    }, false)\n    await client.stop()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/client/integration.test.ts",
    "content": "import * as assert from 'assert'\nimport cp, { ChildProcess } from 'child_process'\nimport fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport { v4 as uuid } from 'uuid'\nimport { CancellationToken, DidCreateFilesNotification, Disposable, ErrorCodes, InlayHintRequest, LSPErrorCodes, MessageType, ResponseError, Trace, WorkDoneProgress } from 'vscode-languageserver-protocol'\nimport { IPCMessageReader, IPCMessageWriter } from 'vscode-languageserver-protocol/node'\nimport { MarkupKind, Range } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport * as lsclient from '../../language-client'\nimport { CloseAction, ErrorAction } from '../../language-client'\nimport { FeatureState, LSPCancellationError, StaticFeature } from '../../language-client/features'\nimport { DefaultErrorHandler, ErrorHandlerResult, InitializationFailedHandler } from '../../language-client/utils/errorHandler'\nimport { disposeAll } from '../../util'\nimport { CancellationError } from '../../util/errors'\nimport * as extension from '../../util/extensionRegistry'\nimport { Registry } from '../../util/registry'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet disposables: Disposable[] = []\nbeforeAll(async () => {\n  await helper.setup()\n})\n\nafterEach(() => {\n  disposeAll(disposables)\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nasync function testLanguageServer(serverOptions: lsclient.ServerOptions, clientOpts?: lsclient.LanguageClientOptions): Promise<lsclient.LanguageClient> {\n  let clientOptions: lsclient.LanguageClientOptions = {\n    documentSelector: ['css'],\n    initializationOptions: {}\n  }\n  if (clientOpts) Object.assign(clientOptions, clientOpts)\n  let client = new lsclient.LanguageClient('css', 'Test Language Server', serverOptions, clientOptions)\n  await client.start()\n  expect(client.initializeResult).toBeDefined()\n  expect(client.started).toBe(true)\n  return client\n}\n\ndescribe('SettingMonitor', () => {\n  it('should setup SettingMonitor', async () => {\n    let clientOptions: lsclient.LanguageClientOptions = {\n      uriConverter: { code2Protocol: uri => uri.toString() },\n      initializationOptions: () => {\n        return {}\n      },\n      markdown: { supportHtml: true },\n      disableDynamicRegister: true\n    }\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      module: serverModule,\n      transport: lsclient.TransportKind.ipc\n    }\n    let client = new lsclient.LanguageClient('html', 'Test Language Server', serverOptions, clientOptions)\n    client.onNotification('customNotification', () => {\n    })\n    client.onProgress(WorkDoneProgress.type, '4fb247f8-0ede-415d-a80a-6629b6a9eaf8', () => {\n    })\n    await client.start()\n    await client.forceDocumentSync()\n    await client.sendNotification('register')\n    await helper.wait(30)\n    expect(client.traceOutputChannel).toBeDefined()\n    let monitor = new lsclient.SettingMonitor(client, 'html.enabled')\n    helper.updateConfiguration('html.enabled', false)\n    disposables.push(monitor.start())\n    await helper.waitValue(() => {\n      return client.state\n    }, lsclient.State.Stopped)\n    helper.updateConfiguration('html.enabled', true, disposables)\n    await helper.waitValue(() => {\n      return client.state != lsclient.State.Stopped\n    }, true)\n    await client.onReady()\n    await client.stop()\n  })\n\n  it('should use SettingMonitor for primary setting', async () => {\n    let clientOptions: lsclient.LanguageClientOptions = {}\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      command: 'node',\n      args: [serverModule],\n      transport: lsclient.TransportKind.stdio,\n      options: { env: false }\n    }\n    let client = new lsclient.LanguageClient('html', 'Test Language Server', serverOptions, clientOptions)\n    let monitor = new lsclient.SettingMonitor(client, 'TestServerEnabled')\n    let spy = jest.spyOn(client, 'start').mockReturnValue(Promise.reject(new Error('myerror')) as any)\n    disposables.push(monitor.start())\n    spy.mockRestore()\n    await client.start()\n    let called = false\n    let s = jest.spyOn(client, 'stop').mockImplementation(() => {\n      called = true\n      return Promise.reject(new Error('myerror'))\n    })\n    helper.updateConfiguration('TestServerEnabled', false)\n    await helper.waitValue(() => called, true)\n    s.mockRestore()\n    await client.stop()\n  })\n})\n\ndescribe('global functions', () => {\n  it('should get working directory', async () => {\n    let cwd = await lsclient.getServerWorkingDir()\n    expect(cwd).toBeDefined()\n    cwd = await lsclient.getServerWorkingDir({ cwd: 'not_exists' })\n    expect(cwd).toBeUndefined()\n  })\n\n  it('should get main root', async () => {\n    expect(lsclient.mainGetRootPath()).toBeUndefined()\n    let uri = URI.file(__filename)\n    await workspace.openResource(uri.toString())\n    expect(lsclient.mainGetRootPath()).toBeDefined()\n    await workspace.nvim.command('bd!')\n  })\n\n  it('should get runtime path', async () => {\n    expect(lsclient.getRuntimePath('node', undefined)).toBe('node')\n    expect(lsclient.getRuntimePath(__filename, undefined)).toBeDefined()\n    let uri = URI.file(__filename)\n    await workspace.openResource(uri.toString())\n    expect(lsclient.getRuntimePath('package.json', undefined)).toBeDefined()\n    let name = path.basename(__filename)\n    expect(lsclient.getRuntimePath(name, __dirname)).toBeDefined()\n  })\n\n  it('should check debug mode', async () => {\n    expect(lsclient.startedInDebugMode(['--debug'])).toBe(true)\n    expect(lsclient.startedInDebugMode(undefined)).toBe(false)\n  })\n})\n\ndescribe('Client events', () => {\n  it('should start server', async () => {\n    let clientOptions: lsclient.LanguageClientOptions = {}\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      module: serverModule,\n      transport: lsclient.TransportKind.ipc\n    }\n    let client = new lsclient.LanguageClient('html', 'Test Language Server', serverOptions, clientOptions)\n    disposables.push(client)\n    await client.start()\n    let called = false\n    let spy = jest.spyOn(client, 'error').mockImplementation(() => {\n      called = true\n    })\n    await client.sendNotification('registerBad')\n    await helper.waitValue(() => called, true)\n    spy.mockRestore()\n    {\n      let spy = jest.spyOn(client['_connection'], 'trace').mockReturnValue(Promise.reject(new Error('myerror')))\n      client.trace = Trace.Compact\n      spy.mockRestore()\n    }\n  })\n\n  it('should restart on error', async () => {\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      command: 'node',\n      args: [serverModule, '--stdio']\n    }\n    let client = await testLanguageServer(serverOptions, {\n      errorHandler: new DefaultErrorHandler('test', 2)\n    })\n    let called = false\n    let spy = jest.spyOn(client, 'start').mockImplementation((async () => {\n      called = true\n      throw new Error('myerror')\n    }) as any)\n    let sp: ChildProcess = client['_serverProcess']\n    sp.kill('SIGKILL')\n    await helper.waitValue(() => called, true)\n    spy.mockRestore()\n  })\n\n  it('should not start on process exit', async () => {\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      command: 'node',\n      args: [serverModule, '--stdio']\n    }\n    let clientOptions: lsclient.LanguageClientOptions = {\n      documentSelector: ['css'],\n      errorHandler: {\n        error: () => ErrorAction.Shutdown,\n        closed: () => {\n          return { action: CloseAction.DoNotRestart, handled: true }\n        }\n      }\n    }\n    let client = new lsclient.LanguageClient('css', 'Test Language Server', serverOptions, clientOptions)\n    await client.start()\n    client['_state'] = lsclient.ClientState.Starting\n    let sp: ChildProcess = client['_serverProcess']\n    sp.kill()\n    await helper.waitValue(() => client['_state'], lsclient.ClientState.StartFailed)\n  })\n\n  it('should register events before server start', async () => {\n    let clientOptions: lsclient.LanguageClientOptions = {}\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      module: serverModule,\n      transport: lsclient.TransportKind.ipc\n    }\n    let client = new lsclient.LanguageClient('html', 'Test Language Server', serverOptions, clientOptions)\n    let name = client.getExtensionName()\n    expect(name).toBe('html')\n    let n = 0\n    let disposable = client.onRequest('customRequest', () => {\n      n++\n      disposable.dispose()\n      return {}\n    })\n    let dispose = client.onNotification('customNotification', () => {\n      n++\n      dispose.dispose()\n    })\n    let dis = client.onProgress(WorkDoneProgress.type, '4fb247f8-0ede-415d-a80a-6629b6a9eaf8', p => {\n      expect(p).toEqual({ kind: 'end', message: 'end message' })\n      n++\n      dis.dispose()\n    })\n    disposables.push(client)\n    await client.start()\n    await client.sendNotification('send')\n    await helper.waitValue(() => {\n      return n\n    }, 3)\n    //   let client = await testEventServer({ initEvent: true })\n  })\n\n  it('should register events after server start', async () => {\n    let clientOptions: lsclient.LanguageClientOptions = {\n      synchronize: {},\n      initializationOptions: { initEvent: true }\n    }\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      module: serverModule,\n      transport: lsclient.TransportKind.stdio\n    }\n    let client = new lsclient.LanguageClient('html', 'Test Language Server', serverOptions, clientOptions)\n    disposables.push(client)\n    await client.start()\n    let n = 0\n    let disposable = client.onRequest('customRequest', () => {\n      n++\n      disposable.dispose()\n      return {}\n    })\n    let dispose = client.onNotification('customNotification', () => {\n      n++\n      dispose.dispose()\n    })\n    let dis = client.onProgress(WorkDoneProgress.type, '4fb247f8-0ede-415d-a80a-6629b6a9eaf8', p => {\n      expect(p).toEqual({ kind: 'end', message: 'end message' })\n      n++\n      dis.dispose()\n    })\n    await client.sendNotification('send')\n    await helper.waitValue(() => {\n      return n\n    }, 3)\n  })\n\n  it('should send progress', async () => {\n    let clientOptions: lsclient.LanguageClientOptions = {\n      synchronize: {},\n      initializationOptions: { initEvent: true }\n    }\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      module: serverModule,\n      transport: lsclient.TransportKind.stdio\n    }\n    let client = new lsclient.LanguageClient('html', 'Test Language Server', serverOptions, clientOptions)\n    let called = false\n    client.onNotification('progressResult', res => {\n      called = true\n      expect(res).toEqual({ kind: 'begin', title: 'begin progress' })\n    })\n    await client.sendProgress(WorkDoneProgress.type, '4b3a71d0-2b3f-46af-be2c-2827f548579f', { kind: 'begin', title: 'begin progress' })\n    await client.start()\n    await helper.waitValue(() => called, true)\n    let spy = jest.spyOn(client['_connection'] as any, 'sendProgress').mockImplementation(() => {\n      throw new Error('error')\n    })\n    await expect(async () => {\n      await client.sendProgress(WorkDoneProgress.type, '', { kind: 'begin', title: '' })\n    }).rejects.toThrow(Error)\n    spy.mockRestore()\n    let p = client.stop()\n    await expect(async () => {\n      await client._start()\n    }).rejects.toThrow(Error)\n    await p\n    await expect(async () => {\n      await client.sendProgress(WorkDoneProgress.type, '', { kind: 'begin', title: '' })\n    }).rejects.toThrow(/not running/)\n  })\n\n  it('should use custom errorHandler', async () => {\n    let throwError = false\n    let called = false\n    let result: ErrorHandlerResult | ErrorAction = { action: ErrorAction.Shutdown, handled: true }\n    let clientOptions: lsclient.LanguageClientOptions = {\n      synchronize: {},\n      errorHandler: {\n        error: () => {\n          return result\n        },\n        closed: () => {\n          called = true\n          if (throwError) throw new Error('myerror')\n          return CloseAction.DoNotRestart\n        }\n      },\n      initializationOptions: { initEvent: true }\n    }\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      module: serverModule,\n      transport: lsclient.TransportKind.stdio\n    }\n    let client = new lsclient.LanguageClient('html', 'Test Language Server', serverOptions, clientOptions)\n    disposables.push(client)\n    throwError = true\n    await assert.rejects(async () => {\n      await client.sendRequest('bad', CancellationToken.Cancelled)\n    }, /cancelled/)\n    await client.sendRequest('doExit')\n    await client.start()\n    await helper.waitValue(() => {\n      return called\n    }, true)\n    await client.handleConnectionError(new Error('error'), { jsonrpc: '' }, 1)\n    result = ErrorAction.Continue\n    await client.handleConnectionError(new Error('error'), { jsonrpc: '' }, 1)\n  })\n\n  it('should handle message events', async () => {\n    let clientOptions: lsclient.LanguageClientOptions = {\n      synchronize: {},\n    }\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      module: serverModule,\n      transport: lsclient.TransportKind.stdio\n    }\n    let client = new lsclient.LanguageClient('html', 'Test Language Server', serverOptions, clientOptions)\n    expect(client.hasPendingResponse).toBeUndefined()\n    disposables.push(client)\n    await client.start()\n    await client.sendNotification('logMessage')\n    await client.sendNotification('showMessage')\n    let types = [MessageType.Error, MessageType.Warning, MessageType.Info, MessageType.Log]\n    let times = 0\n    let result = true\n    const mockMessageFunctions = function(): Disposable {\n      let names = ['showErrorMessage', 'showWarningMessage', 'showInformationMessage']\n      let fns: Function[] = []\n      for (let name of names) {\n        let spy = jest.spyOn(window as any, name).mockImplementation(() => {\n          times++\n          return Promise.resolve(result)\n        })\n        fns.push(() => {\n          spy.mockRestore()\n        })\n      }\n      return Disposable.create(() => {\n        for (let fn of fns) {\n          fn()\n        }\n      })\n    }\n    disposables.push(mockMessageFunctions())\n    for (const t of types) {\n      await client.sendNotification('requestMessage', { type: t })\n    }\n    await helper.waitValue(() => {\n      return times >= 3\n    }, true)\n    let filename = path.join(os.tmpdir(), uuid())\n    let uri = URI.file(filename)\n    fs.writeFileSync(filename, 'foo', 'utf8')\n    let spy = jest.spyOn(workspace, 'openResource').mockImplementation(() => {\n      return Promise.resolve()\n    })\n    let called = false\n    let s = jest.spyOn(window, 'selectRange').mockImplementation(() => {\n      called = true\n      return Promise.reject(new Error('failed'))\n    })\n    await client.sendNotification('showDocument', { external: true, uri: 'lsptest:///1' })\n    await client.sendNotification('showDocument', { uri: 'lsptest:///1', takeFocus: false })\n    await client.sendNotification('showDocument', { uri: uri.toString() })\n    await client.sendNotification('showDocument', { uri: uri.toString(), selection: Range.create(0, 0, 1, 0) })\n    await helper.waitValue(() => called, true)\n    spy.mockRestore()\n    s.mockRestore()\n    fs.unlinkSync(filename)\n    await helper.waitValue(() => {\n      return client.hasPendingResponse\n    }, false)\n  })\n\n  it('should invoke showDocument middleware', async () => {\n    let called = false\n    let clientOptions: lsclient.LanguageClientOptions = {\n      synchronize: {},\n      middleware: {\n        window: {\n          showDocument: async (params, token, next) => {\n            called = true\n            let res = await next(params, token)\n            return res as any\n          }\n        }\n      }\n    }\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      module: serverModule,\n      transport: lsclient.TransportKind.stdio\n    }\n    let client = new lsclient.LanguageClient('html', 'Test Language Server', serverOptions, clientOptions)\n    let uri = URI.file(__filename)\n    await client.start()\n    await client.sendNotification('showDocument', { uri: uri.toString() })\n    await helper.waitValue(() => called, true)\n    await client.restart()\n    await client.stop()\n  })\n})\n\ndescribe('Client integration', () => {\n  it('should initialize from function', async () => {\n    async function testServer(serverOptions: lsclient.ServerOptions) {\n      let clientOptions: lsclient.LanguageClientOptions = {}\n      let client = new lsclient.LanguageClient('HTML', serverOptions, clientOptions)\n      await client.start()\n      await client.dispose()\n      void client.dispose()\n    }\n    await testServer(() => {\n      let module = path.join(__dirname, './server/eventServer.js')\n      let sp = cp.fork(module, ['--node-ipc'], { cwd: process.cwd() })\n      return Promise.resolve({ reader: new IPCMessageReader(sp), writer: new IPCMessageWriter(sp) })\n    })\n    await testServer(() => {\n      let module = path.join(__dirname, './server/eventServer.js')\n      let sp = cp.fork(module, ['--stdio'], {\n        cwd: process.cwd(),\n        execArgv: [],\n        silent: true,\n      })\n      return Promise.resolve({ reader: sp.stdout, writer: sp.stdin })\n    })\n    await testServer(() => {\n      let module = path.join(__dirname, './server/eventServer.js')\n      let sp = cp.fork(module, ['--stdio'], {\n        cwd: process.cwd(),\n        execArgv: [],\n        silent: true,\n      })\n      return Promise.resolve({ process: sp, detached: false })\n    })\n    await testServer(() => {\n      let module = path.join(__dirname, './server/eventServer.js')\n      let sp = cp.fork(module, ['--stdio'], {\n        cwd: process.cwd(),\n        execArgv: [],\n        silent: true,\n      })\n      return Promise.resolve(sp)\n    })\n  })\n\n  it('should initialize use IPC channel', async () => {\n    helper.updateConfiguration('css.trace.server.verbosity', 'verbose', disposables)\n    helper.updateConfiguration('css.trace.server.format', 'json', disposables)\n    let uri = URI.file(__filename)\n    await workspace.loadFile(uri.toString())\n    let serverModule = path.join(__dirname, './server/testInitializeResult.js')\n    let serverOptions: lsclient.ServerOptions = {\n      run: { module: serverModule, transport: lsclient.TransportKind.ipc },\n      debug: { module: serverModule, transport: lsclient.TransportKind.ipc, options: { execArgv: [] } }\n    }\n    let clientOptions: lsclient.LanguageClientOptions = {\n      rootPatterns: ['.vim'],\n      requireRootPattern: true,\n      documentSelector: ['css'],\n      synchronize: {}, initializationOptions: {},\n      middleware: {\n        handleDiagnostics: (uri, diagnostics, next) => {\n          assert.equal(uri, \"uri:/test.ts\")\n          assert.ok(Array.isArray(diagnostics))\n          assert.equal(diagnostics.length, 0)\n          next(uri, diagnostics)\n        }\n      }\n    }\n    let client = new lsclient.LanguageClient('css', 'Test Language Server', serverOptions, clientOptions, true)\n    assert.ok(client.isInDebugMode)\n    await client.start()\n    await helper.waitValue(() => client.diagnostics.has('uri:/test.ts'), true)\n    await client.restart()\n    assert.deepEqual(client.initializeResult.customResults, { hello: 'world' })\n    await client.stop()\n    await assert.rejects(async () => {\n      let options: any = {}\n      let client = new lsclient.LanguageClient('css', 'Test Language Server', options, clientOptions)\n      await client.start()\n    }, /Unsupported/)\n    await assert.rejects(async () => {\n      let options: lsclient.ServerOptions = { command: 'node', transport: lsclient.TransportKind.ipc }\n      let client = new lsclient.LanguageClient('css', 'Test Language Server', options, clientOptions)\n      await client.start()\n    }, /not supported/)\n    await assert.rejects(async () => {\n      let opts: any = { stdio: 'ignore' }\n      let options: lsclient.ServerOptions = { module: serverModule, transport: lsclient.TransportKind.ipc, options: opts }\n      let client = new lsclient.LanguageClient('css', 'Test Language Server', options, clientOptions)\n      await client.start()\n    }, /without stdio/)\n  })\n\n  it('should initialize use stdio', async () => {\n    helper.updateConfiguration('css.trace.server.verbosity', 'verbose', disposables)\n    helper.updateConfiguration('css.trace.server.format', 'text', disposables)\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      module: serverModule,\n      transport: lsclient.TransportKind.stdio\n    }\n    let client = await testLanguageServer(serverOptions, {\n      workspaceFolder: { name: 'test', uri: URI.file(__dirname).toString() },\n      outputChannel: window.createOutputChannel('test'),\n      traceOutputChannel: window.createOutputChannel('test-trace'),\n      markdown: {},\n      disabledFeatures: ['pullDiagnostic'],\n      revealOutputChannelOn: lsclient.RevealOutputChannelOn.Info,\n      outputChannelName: 'custom',\n      connectionOptions: {\n        cancellationStrategy: { sender: {} } as any,\n        maxRestartCount: 10,\n      },\n      stdioEncoding: 'utf8',\n      errorHandler: {\n        error: () => {\n          return lsclient.ErrorAction.Continue\n        },\n        closed: () => {\n          return { action: CloseAction.DoNotRestart, handled: true }\n        }\n      },\n      progressOnInitialization: true,\n      disableMarkdown: true,\n      disableDiagnostics: true\n    })\n    assert.deepStrictEqual(client.supportedMarkupKind, [MarkupKind.PlainText])\n    assert.strictEqual(client.name, 'Test Language Server')\n    assert.strictEqual(client.diagnostics, undefined)\n    expect(client.traceOutputChannel).toBeDefined()\n    client.traceMessage('message')\n    client.traceMessage('message', {})\n    client.trace = Trace.Verbose\n    let d = client.start()\n    let token = CancellationToken.Cancelled\n    let sp: ChildProcess = client['_serverProcess']\n    expect(sp instanceof ChildProcess).toBe(true)\n    sp.stdout.emit('error', new Error('my error'))\n    client.handleFailedRequest(DidCreateFilesNotification.type, token, undefined, '')\n    await expect(async () => {\n      let error = new ResponseError(LSPErrorCodes.RequestCancelled, 'request cancelled')\n      client.handleFailedRequest(DidCreateFilesNotification.type, undefined, error, '')\n    }).rejects.toThrow(CancellationError)\n    await expect(async () => {\n      let error = new ResponseError(LSPErrorCodes.RequestCancelled, 'request cancelled', 'cancelled')\n      client.handleFailedRequest(DidCreateFilesNotification.type, undefined, error, '')\n    }).rejects.toThrow(LSPCancellationError)\n    await expect(async () => {\n      let error = new Error('failed')\n      client.handleFailedRequest(DidCreateFilesNotification.type, undefined, error, '')\n    }).rejects.toThrow(Error)\n    let error = new ResponseError(LSPErrorCodes.ContentModified, 'content changed')\n    client.handleFailedRequest(DidCreateFilesNotification.type, undefined, error, '')\n    error = new ResponseError(ErrorCodes.PendingResponseRejected, '')\n    client.handleFailedRequest(DidCreateFilesNotification.type, undefined, error, '')\n    await expect(async () => {\n      let error = new ResponseError(LSPErrorCodes.ContentModified, 'content changed')\n      client.handleFailedRequest(InlayHintRequest.type, undefined, error, '')\n    }).rejects.toThrow(CancellationError)\n    await client.stop()\n    client.info('message', new Error('my error'), true)\n    client.warn('message', 'error', true)\n    client.warn('message', 0, true)\n    client.logFailedRequest({ method: 'method' }, new Error('error'))\n    let err = new ResponseError(LSPErrorCodes.RequestCancelled, 'response error')\n    client.logFailedRequest('', err)\n    assert.strictEqual(client.diagnostics, undefined)\n    await client.handleConnectionError(new Error('test'), undefined, 0)\n    d.dispose()\n  })\n\n  it('should initialize use pipe', async () => {\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      module: serverModule,\n      transport: lsclient.TransportKind.pipe\n    }\n    let client = await testLanguageServer(serverOptions, {\n      ignoredRootPaths: [workspace.root]\n    })\n    expect(client.serviceState).toBeDefined()\n    await client.stop()\n    await assert.rejects(async () => {\n      let option: lsclient.ServerOptions = {\n        command: 'foobar',\n        transport: lsclient.TransportKind.pipe\n      }\n      await testLanguageServer(option, {})\n    }, /ENOENT/)\n  })\n\n  it('should initialize use socket', async () => {\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      module: serverModule,\n      options: { env: { NODE_SOCKET_TEST: 1 } },\n      transport: {\n        kind: lsclient.TransportKind.socket,\n        port: 8088\n      }\n    }\n    let client = await testLanguageServer(serverOptions)\n    await client.stop()\n    let option: lsclient.ServerOptions = {\n      command: 'node',\n      args: [serverModule],\n      transport: {\n        kind: lsclient.TransportKind.socket,\n        port: 9088\n      }\n    }\n    client = await testLanguageServer(option, {})\n    await client.sendNotification('printMessage')\n    await helper.waitValue(() => {\n      return client.outputChannel.content.match('Stderr') != null\n    }, true)\n    // avoid pending response error\n    await helper.wait(50)\n    await client.stop()\n    await assert.rejects(async () => {\n      let option: lsclient.ServerOptions = {\n        command: 'foobar',\n        transport: {\n          kind: lsclient.TransportKind.socket,\n          port: 9998\n        }\n      }\n      await testLanguageServer(option, {})\n    }, /ENOENT/)\n  })\n\n  it('should initialize as command', async () => {\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      command: 'node',\n      args: [serverModule, '--stdio']\n    }\n    let client = await testLanguageServer(serverOptions)\n    await client.stop()\n  })\n\n  it('should register features', async () => {\n    let features: StaticFeature[] = []\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      command: 'node',\n      args: [serverModule, '--stdio']\n    }\n    let clientOptions: lsclient.LanguageClientOptions = {\n      documentSelector: ['css'],\n      initializationOptions: {}\n    }\n    let client = new lsclient.LanguageClient('css', 'Test Language Server', serverOptions, clientOptions)\n    let called = false\n    class SimpleStaticFeature implements StaticFeature {\n      public method = 'method'\n      public fillClientCapabilities(capabilities): void {\n        // Optionally add capabilities your feature supports\n        capabilities.experimental = capabilities.experimental || {}\n        capabilities.experimental.simpleStaticFeature = true\n      }\n      public preInitialize(): void {\n        called = true\n      }\n      public initialize(): void {\n      }\n      public getState(): FeatureState {\n        return { kind: 'static' }\n      }\n      public dispose(): void {\n      }\n    }\n    features.push(new SimpleStaticFeature())\n    client.registerFeatures(features)\n    await client.start()\n    expect(called).toBe(true)\n    await client.stop()\n  })\n\n  it('should not throw as command', async () => {\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      command: 'not_exists',\n      args: [serverModule, '--stdio']\n    }\n    let clientOptions: lsclient.LanguageClientOptions = {\n      documentSelector: ['css'],\n      initializationOptions: {}\n    }\n    let client = new lsclient.LanguageClient('css', 'Test Language Server', serverOptions, clientOptions)\n    await assert.rejects(async () => {\n      await client.start()\n    }, /failed/)\n    await expect(async () => {\n      await client['$start']()\n    }).rejects.toThrow(/failed/)\n  })\n\n  it('should logMessage', async () => {\n    let called = false\n    let outputChannel = {\n      name: 'empty',\n      content: '',\n      append: () => {\n        called = true\n      },\n      appendLine: () => {\n        called = true\n      },\n      clear: () => {},\n      show: () => {},\n      hide: () => {},\n      dispose: () => {}\n    }\n    helper.updateConfiguration('css.trace.server.verbosity', 'verbose', disposables)\n    let serverOptions: lsclient.ServerOptions = {\n      command: 'node',\n      args: [path.join(__dirname, './server/eventServer.js'), '--stdio']\n    }\n    let client = await testLanguageServer(serverOptions, {\n      outputChannel,\n      initializationOptions: { trace: true }\n    })\n    expect(called).toBe(true)\n    await client.stop()\n  })\n\n  it('should use console for messages', async () => {\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      command: 'node',\n      args: [serverModule, '--stdio']\n    }\n    let client = await testLanguageServer(serverOptions)\n    let fn = jest.fn()\n    let spy = jest.spyOn(console, 'log').mockImplementation(() => {\n      fn()\n    })\n    let s = jest.spyOn(console, 'error').mockImplementation(() => {\n      fn()\n    })\n    client.switchConsole()\n    client.info('message', { info: 'info' })\n    client.warn('message', { info: 'info' })\n    client.error('message', { info: 'info' })\n    client.info('message', { info: 'info' })\n    client.switchConsole()\n    s.mockRestore()\n    spy.mockRestore()\n    await client.stop()\n    expect(fn).toHaveBeenCalled()\n  })\n\n  it('should check version on apply workspaceEdit', async () => {\n    let uri = URI.file(__filename)\n    await workspace.loadFile(uri.toString())\n    let clientOptions: lsclient.LanguageClientOptions = {\n      documentSelector: [{ scheme: 'file' }],\n      initializationOptions: {},\n    }\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      module: serverModule,\n      transport: lsclient.TransportKind.stdio,\n    }\n    let client = new lsclient.LanguageClient('html', 'Test Language Server', serverOptions, clientOptions)\n    let res\n    client.onNotification('result', p => {\n      res = p\n    })\n    let disposable = client.start()\n    await disposable\n    await client.sendNotification('edits')\n    await helper.waitValue(() => {\n      return res\n    }, { applied: false })\n    disposable.dispose()\n    await client.stop()\n  })\n\n  it('should apply simple workspaceEdit', async () => {\n    let clientOptions: lsclient.LanguageClientOptions = {\n      initializationOptions: {},\n    }\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      module: serverModule,\n      transport: lsclient.TransportKind.stdio,\n    }\n    let client = new lsclient.LanguageClient('html', 'Test Language Server', serverOptions, clientOptions)\n    let res\n    client.onNotification('result', p => {\n      res = p\n    })\n    await client.start()\n    await client.sendNotification('simpleEdit')\n    await helper.waitValue(() => {\n      return res != null\n    }, true)\n    expect(res).toEqual({ applied: true })\n    await client.stop()\n  })\n\n  it('should handle error on initialize', async () => {\n    let client: lsclient.LanguageClient\n    let progressOnInitialization = false\n    async function startServer(handler: InitializationFailedHandler | undefined, key = 'throwError'): Promise<lsclient.LanguageClient> {\n      let clientOptions: lsclient.LanguageClientOptions = {\n        initializationFailedHandler: handler,\n        progressOnInitialization,\n        initializationOptions: {\n          [key]: true\n        },\n        connectionOptions: {\n          maxRestartCount: 1\n        }\n      }\n      let serverModule = path.join(__dirname, './server/eventServer.js')\n      let serverOptions: lsclient.ServerOptions = {\n        module: serverModule,\n        transport: lsclient.TransportKind.ipc,\n      }\n      client = new lsclient.LanguageClient('html', 'Test Language Server', serverOptions, clientOptions)\n      await client.start()\n      return client\n    }\n    let messageReturn = {}\n    let spy = jest.spyOn(window, 'showErrorMessage').mockImplementation(() => {\n      return Promise.resolve(messageReturn as any)\n    })\n    let n = 0\n    await expect(async () => {\n      await startServer(() => {\n        n++\n        return n == 1\n      })\n    }).rejects.toThrow(Error)\n    await helper.waitValue(() => {\n      return n\n    }, 2)\n    await expect(async () => {\n      await startServer(undefined)\n    }).rejects.toThrow(Error)\n\n    await expect(async () => {\n      await startServer(undefined, 'normalThrow')\n    }).rejects.toThrow(Error)\n    progressOnInitialization = true\n    await expect(async () => {\n      client = await startServer(undefined, 'utf8')\n    }).rejects.toThrow(/Unsupported position encoding/)\n    await helper.waitValue(() => client.state, lsclient.State.Stopped)\n    await client.stop()\n    spy.mockRestore()\n  })\n\n  it('should attach extension name', async () => {\n    let clientOptions: lsclient.LanguageClientOptions = {}\n    let serverModule = path.join(__dirname, './server/eventServer.js')\n    let serverOptions: lsclient.ServerOptions = {\n      module: serverModule,\n      transport: lsclient.TransportKind.ipc\n    }\n    let client = new lsclient.LanguageClient('html', 'Test Language Server', serverOptions, clientOptions)\n    let registry = Registry.as<extension.IExtensionRegistry>(extension.Extensions.ExtensionContribution)\n    let filepath = path.join(os.tmpdir(), 'single')\n    registry.registerExtension('single', { name: 'single', directory: os.tmpdir(), filepath })\n    client['stack'] = `\\n\\n${filepath}:1:1`\n    let obj = {}\n    client.attachExtensionName(obj)\n    expect(typeof client.getExtensionName()).toBe('string')\n    expect(obj['__extensionName']).toBe('single')\n    registry.unregistExtension('single')\n    await client.dispose()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/client/progressPart.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { Emitter, Event, NotificationHandler, WorkDoneProgressBegin, WorkDoneProgressEnd, WorkDoneProgressReport } from 'vscode-languageserver-protocol'\nimport { ProgressContext, ProgressPart } from '../../language-client/progressPart'\nimport helper from '../helper'\n\ntype ProgressType = WorkDoneProgressBegin | WorkDoneProgressReport | WorkDoneProgressEnd\n\nlet nvim: Neovim\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterEach(async () => {\n  await helper.reset()\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\ndescribe('ProgressPart', () => {\n  function createClient(): ProgressContext & { fire: (ev: ProgressType) => void, token: string | undefined } {\n    let _onDidProgress = new Emitter<ProgressType>()\n    let onDidProgress: Event<ProgressType> = _onDidProgress.event\n    let notificationToken: string | undefined\n    return {\n      id: 'test',\n      get token() {\n        return notificationToken\n      },\n      fire(ev) {\n        _onDidProgress.fire(ev)\n      },\n      onProgress<ProgressType>(_, __, handler: NotificationHandler<ProgressType>) {\n        return onDidProgress(ev => {\n          void handler(ev as any)\n        })\n      },\n      sendNotification(_, params) {\n        notificationToken = (params as any).token\n      }\n    }\n  }\n\n  it('should not start if cancelled', async () => {\n    let client = createClient()\n    let p = new ProgressPart(client, '0c7faec8-e36c-4cde-9815-95635c37d696')\n    p.report({ kind: 'report', message: 'msg' })\n    p.cancel()\n    expect(p.begin({ kind: 'begin', title: 'canceleld' })).toBe(false)\n  })\n\n  it('should report progress', async () => {\n    let client = createClient()\n    let p = new ProgressPart(client, '0c7faec8-e36c-4cde-9815-95635c37d696')\n    p.begin({ kind: 'begin', title: 'p', percentage: 1, cancellable: true })\n    await helper.wait(30)\n    p.report({ kind: 'report', message: 'msg', percentage: 10 })\n    await helper.wait(10)\n    p.report({ kind: 'report', message: 'msg', percentage: 50 })\n    await helper.wait(10)\n    p.done('finished')\n  })\n\n  it('should close notification on cancel', async () => {\n    helper.updateConfiguration('notification.statusLineProgress', false)\n    let client = createClient()\n    let p = new ProgressPart(client, '0c7faec8-e36c-4cde-9815-95635c37d696')\n    let started = p.begin({ kind: 'begin', title: 'canceleld' })\n    expect(started).toBe(true)\n    p.cancel()\n    p.cancel()\n    let winids = await nvim.call('coc#notify#win_list') as number[]\n    await helper.wait(30)\n    expect(winids.length).toBe(1)\n    let win = nvim.createWindow(winids[0])\n    let closing = await win.getVar('closing')\n    expect(closing).toBe(1)\n  })\n\n  it('should send notification on cancel', async () => {\n    helper.updateConfiguration('notification.statusLineProgress', false)\n    let client = createClient()\n    let token = '0c7faec8-e36c-4cde-9815-95635c37d696'\n    let p = new ProgressPart(client, token)\n    let started = p.begin({ kind: 'begin', title: 'canceleld', cancellable: true })\n    expect(started).toBe(true)\n    for (let i = 0; i < 10; i++) {\n      await helper.wait(30)\n      let winids = await nvim.call('coc#notify#win_list') as number[]\n      if (winids.length == 1) break\n    }\n    await helper.wait(30)\n    nvim.call('coc#float#close_all', [], true)\n    await helper.waitValue(() => {\n      return client.token\n    }, token)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/client/server/configServer.js",
    "content": "'use strict'\nconst {createConnection, ConfigurationRequest, DidChangeConfigurationNotification} = require('vscode-languageserver/node')\nconst {URI} = require('vscode-uri')\n\nconst connection = createConnection()\nconsole.log = connection.console.log.bind(connection.console)\nconsole.error = connection.console.error.bind(connection.console)\nconnection.onInitialize((_params) => {\n  return {capabilities: {}}\n})\n\nconnection.onNotification('pull0', () => {\n  void connection.sendRequest(ConfigurationRequest.type, {\n    items: [{\n      scopeUri: URI.file(__filename).toString()\n    }]\n  })\n})\n\nconnection.onNotification('pull1', () => {\n  void connection.sendRequest(ConfigurationRequest.type, {\n    items: [{\n      section: 'http'\n    }, {\n      section: 'editor.cpp.format'\n    }, {\n      section: 'unknown'\n    }]\n  })\n})\n\nconnection.onNotification(DidChangeConfigurationNotification.type, params => {\n  void connection.sendNotification('configurationChange', params)\n})\n\nconnection.listen()\n"
  },
  {
    "path": "src/__tests__/client/server/crashOnShutdownServer.js",
    "content": "'use strict'\nObject.defineProperty(exports, \"__esModule\", {value: true})\nconst node_1 = require(\"vscode-languageserver/node\")\nconst connection = (0, node_1.createConnection)()\nconnection.onInitialize((_params) => {\n  return {capabilities: {}}\n})\nconnection.onShutdown(() => {\n  process.exit(100)\n})\nconnection.listen()\n"
  },
  {
    "path": "src/__tests__/client/server/crashServer.js",
    "content": "\"use strict\"\nObject.defineProperty(exports, \"__esModule\", {value: true})\nconst node_1 = require('vscode-languageserver/node')\nlet CrashNotification;\n(function (CrashNotification) {\n  CrashNotification.type = new node_1.NotificationType0('test/crash')\n})(CrashNotification || (CrashNotification = {}))\nconst connection = (0, node_1.createConnection)()\nconnection.onInitialize((_params) => {\n  return {\n    capabilities: {}\n  }\n})\nconnection.onNotification(CrashNotification.type, () => {\n  process.exit(100)\n})\nconnection.listen()\n"
  },
  {
    "path": "src/__tests__/client/server/customServer.js",
    "content": "\"use strict\"\nObject.defineProperty(exports, \"__esModule\", {value: true})\nconst node_1 = require(\"vscode-languageserver/node\")\nconst connection = (0, node_1.createConnection)()\nconnection.onInitialize((_params) => {\n  return {\n    capabilities: {}\n  }\n})\nconnection.onRequest('request', (param) => {\n  return param.value + 1\n})\nconnection.onNotification('notification', () => {\n})\nconnection.onRequest('triggerRequest', async () => {\n  await connection.sendRequest('request')\n})\nconnection.onRequest('triggerNotification', async () => {\n  await connection.sendNotification('notification')\n})\nconnection.listen()\n"
  },
  {
    "path": "src/__tests__/client/server/diagnosticServer.js",
    "content": "'use strict'\nconst {createConnection, ResponseError, LSPErrorCodes, DiagnosticRefreshRequest, DocumentDiagnosticReportKind, Diagnostic, Range, DiagnosticSeverity, TextDocuments, TextDocumentSyncKind} = require('vscode-languageserver/node')\nconst {TextDocument} = require('vscode-languageserver-textdocument')\nlet documents = new TextDocuments(TextDocument)\n\nconst connection = createConnection()\nconsole.log = connection.console.log.bind(connection.console)\nconsole.error = connection.console.error.bind(connection.console)\nlet options\ndocuments.listen(connection)\nconnection.onInitialize((params) => {\n  options = params.initializationOptions || {}\n  const interFileDependencies = options.interFileDependencies !== false\n  const workspaceDiagnostics = options.workspaceDiagnostics === true\n  const identifier = options.identifier ?? '6d52eff6-96c7-4fd1-910f-f060bcffb23f'\n  return {\n    capabilities: {\n      textDocumentSync: TextDocumentSyncKind.Incremental,\n      diagnosticProvider: {\n        identifier,\n        interFileDependencies,\n        workspaceDiagnostics\n      }\n    }\n  }\n})\n\nlet count = 0\nlet saveCount = 0\nconnection.languages.diagnostics.on((params) => {\n  let uri = params.textDocument.uri\n  if (uri.endsWith('error')) return Promise.reject(new Error('server error'))\n  if (uri.endsWith('cancel')) return new ResponseError(LSPErrorCodes.ServerCancelled, 'cancel', {retriggerRequest: false})\n  if (uri.endsWith('retrigger')) return new ResponseError(LSPErrorCodes.ServerCancelled, 'retrigger', {retriggerRequest: true})\n  if (uri.endsWith('change')) count++\n  if (uri.endsWith('save')) saveCount++\n  if (uri.endsWith('empty')) return null\n  if (uri.endsWith('unchanged')) return {\n    kind: DocumentDiagnosticReportKind.Unchanged,\n    resultId: '1'\n  }\n  return {\n    kind: DocumentDiagnosticReportKind.Full,\n    items: [\n      Diagnostic.create(Range.create(1, 1, 1, 1), 'diagnostic', DiagnosticSeverity.Error)\n    ]\n  }\n})\n\nlet workspaceCount = 0\nconnection.languages.diagnostics.onWorkspace((params, _, __, reporter) => {\n  if (params.previousResultIds.length > 0) {\n    return new Promise((resolve, reject) => {\n      setTimeout(() => {\n        reporter.report({\n          items: [{\n            kind: DocumentDiagnosticReportKind.Full,\n            uri: 'uri1',\n            version: 1,\n            items: [\n              Diagnostic.create(Range.create(1, 0, 1, 1), 'diagnostic', DiagnosticSeverity.Error)\n            ]\n          }]\n        })\n      }, 10)\n      setTimeout(() => {\n        reporter.report(null)\n      }, 15)\n      setTimeout(() => {\n        reporter.report({\n          items: [{\n            kind: DocumentDiagnosticReportKind.Full,\n            uri: 'uri2',\n            version: 1,\n            items: [\n              Diagnostic.create(Range.create(2, 0, 2, 1), 'diagnostic', DiagnosticSeverity.Error)\n            ]\n          }]\n        })\n      }, 20)\n      setTimeout(() => {\n        resolve({items: []})\n      }, 50)\n    })\n  }\n  workspaceCount++\n  if (workspaceCount == 2) {\n    return new ResponseError(LSPErrorCodes.ServerCancelled, 'changed')\n  }\n  return {\n    items: [{\n      kind: DocumentDiagnosticReportKind.Full,\n      uri: 'uri',\n      version: 1,\n      items: [\n        Diagnostic.create(Range.create(1, 1, 1, 1), 'diagnostic', DiagnosticSeverity.Error)\n      ]\n    }]\n  }\n})\n\nconnection.onNotification('fireRefresh', () => {\n  void connection.sendRequest(DiagnosticRefreshRequest.type)\n})\n\nconnection.onRequest('getChangeCount', () => {\n  return count\n})\n\nconnection.onRequest('getSaveCount', () => {\n  return saveCount\n})\n\nconnection.onRequest('getWorkspaceCount', () => {\n  return workspaceCount\n})\n\n\nconnection.onRequest('sendDiagnostics', async (_, __) => {\n  const uri = 'file:///abc.txt'\n  const diagnostics = [{\n    severity: DiagnosticSeverity.Warning,\n    range: {\n      start: {line: 0, character: 0},\n      end: {line: 0, character: 5}\n    },\n    message: \"Example warning: Check your code!\",\n    source: \"ex\"\n  }]\n  connection.sendDiagnostics({uri, diagnostics})\n})\n\nconnection.listen()\n"
  },
  {
    "path": "src/__tests__/client/server/dynamicServer.js",
    "content": "'use strict'\nconst {createConnection, TextDocumentContentRefreshRequest, ProtocolRequestType, Range, TextDocumentSyncKind, Command, RenameRequest, WorkspaceSymbolRequest, SemanticTokensRegistrationType, CodeActionRequest, ConfigurationRequest, DidChangeConfigurationNotification, InlineValueRefreshRequest, ExecuteCommandRequest, CompletionRequest, WorkspaceFoldersRequest, ResponseError, ErrorCodes} = require('vscode-languageserver/node')\n\nconst connection = createConnection()\nconsole.log = connection.console.log.bind(connection.console)\nconsole.error = connection.console.error.bind(connection.console)\n\nlet options\nlet disposables = []\nlet prepareResponse\nlet configuration\nlet folders\nlet foldersEvent\nconst id = 'b346648e-88e0-44e3-91e3-52fd6addb8c7'\nconnection.onInitialize((params) => {\n  options = params.initializationOptions || {}\n  let changeNotifications = options.changeNotifications ?? id\n  return {\n    capabilities: {\n      inlineValueProvider: {},\n      executeCommandProvider: {\n      },\n      documentSymbolProvider: options.label ? {label: 'test'} : true,\n      textDocumentSync: TextDocumentSyncKind.Full,\n      renameProvider: options.prepareRename ? {prepareProvider: true} : true,\n      workspaceSymbolProvider: true,\n      codeLensProvider: {\n        resolveProvider: options.noResolve !== true\n      },\n      documentLinkProvider: {\n        resolveProvider: options.noResolve !== true\n      },\n      inlayHintProvider: {\n        resolveProvider: options.noResolve !== true\n      },\n      workspace: {\n        workspaceFolders: {\n          changeNotifications\n        },\n        fileOperations: {\n          // Static reg is folders + .txt files with operation kind in the path\n          didCreate: {\n            filters: [\n              {scheme: 'lsptest', pattern: {glob: '**/*', matches: 'file', options: {}}},\n              {scheme: 'file', pattern: {glob: '**/*', matches: 'file', options: {ignoreCase: false}}}\n            ]\n          },\n          didRename: {\n            filters: [\n              {scheme: 'file', pattern: {glob: '**/*', matches: 'folder'}},\n              {scheme: 'file', pattern: {glob: '**/*', matches: 'file'}}\n            ]\n          },\n          didDelete: {\n            filters: [{scheme: 'file', pattern: {glob: '**/*'}}]\n          },\n          willCreate: {\n            filters: [{scheme: 'file', pattern: {glob: '**/*'}}]\n          },\n          willRename: {\n            filters: [\n              {scheme: 'file', pattern: {glob: '**/*', matches: 'folder'}},\n              {scheme: 'file', pattern: {glob: '**/*', matches: 'file'}}\n            ]\n          },\n          willDelete: {\n            filters: [{scheme: 'file', pattern: {glob: '**/*'}}]\n          },\n        },\n        textDocumentContent: options.textDocumentContent ? {id, schemes: ['lsptest']} : undefined\n      },\n    }\n  }\n})\n\nconnection.onInitialized(() => {\n  void connection.client.register(RenameRequest.type, {\n    prepareProvider: options.prepareRename\n  }).then(d => {\n    d.dispose()\n  })\n  void connection.client.register(WorkspaceSymbolRequest.type, {\n    resolveProvider: true\n  }).then(d => {\n    disposables.push(d)\n  })\n  let full = false\n  if (options.delta) {\n    full = {delta: true}\n  } else if (options.noResolve) {\n    full = {delta: false}\n  }\n  void connection.client.register(SemanticTokensRegistrationType.method, {\n    full,\n    range: options.rangeTokens,\n    legend: {\n      tokenTypes: [],\n      tokenModifiers: []\n    },\n  })\n  void connection.client.register(CodeActionRequest.method, {\n    resolveProvider: false\n  })\n  void connection.client.register(DidChangeConfigurationNotification.type, {section: undefined})\n  void connection.client.register(ExecuteCommandRequest.type, {\n    commands: ['test_command', 'other_command']\n  }).then(d => {\n    disposables.push(d)\n  })\n  void connection.client.register(CompletionRequest.type, {\n    documentSelector: [{language: 'vim'}]\n  }).then(d => {\n    disposables.push(d)\n  })\n  void connection.client.register(CompletionRequest.type, {\n    triggerCharacters: ['/'],\n  }).then(d => {\n    disposables.push(d)\n  })\n})\n\nlet lastFileOperationRequest\nconnection.workspace.onDidCreateFiles(params => {lastFileOperationRequest = {type: 'create', params}})\nconnection.workspace.onDidRenameFiles(params => {lastFileOperationRequest = {type: 'rename', params}})\nconnection.workspace.onDidDeleteFiles(params => {lastFileOperationRequest = {type: 'delete', params}})\nconnection.workspace.onWillRenameFiles(params => {lastFileOperationRequest = {type: 'willRename', params}})\nconnection.workspace.onWillDeleteFiles(params => {lastFileOperationRequest = {type: 'willDelete', params}})\n\n// connection.onDidChangeWorkspaceFolders(e => {\n//   foldersEvent = params\n// })\n\nconnection.onCompletion(_params => {\n  return [\n    {label: 'item', insertText: 'text'}\n  ]\n})\n\nconnection.onCompletionResolve(item => {\n  item.detail = 'detail'\n  return item\n})\n\nconnection.onRequest(\n  new ProtocolRequestType('testing/lastFileOperationRequest'),\n  () => {\n    return lastFileOperationRequest\n  },\n)\n\nconnection.onNotification('unregister', () => {\n  for (let d of disposables) {\n    d.dispose()\n    disposables = []\n  }\n})\n\nconnection.onDocumentSymbol(() => {\n  return []\n})\n\nconnection.onExecuteCommand(param => {\n  if (param.command === 'test_command') {\n    return {success: true}\n  }\n  throw new ResponseError(ErrorCodes.InvalidRequest, `${param?.command} not exists.`)\n})\n\nconnection.languages.semanticTokens.onDelta(() => {\n  return {\n    resultId: '3',\n    data: []\n  }\n})\n\nconnection.onRequest('setPrepareResponse', param => {\n  prepareResponse = param\n})\n\nconnection.onNotification('pullConfiguration', () => {\n  configuration = connection.sendRequest(ConfigurationRequest.type, {\n    items: [{section: 'foo'}, {}]\n  })\n})\n\nconnection.onRequest('getConfiguration', () => {\n  return configuration\n})\n\nconnection.onRequest('getFolders', () => {\n  return folders\n})\n\nconnection.onRequest('getFoldersEvent', () => {\n  return foldersEvent\n})\n\nconnection.onNotification('fireInlineValueRefresh', () => {\n  void connection.sendRequest(InlineValueRefreshRequest.type)\n})\n\nconnection.onNotification('fireDocumentContentRefresh', () => {\n  void connection.sendRequest(TextDocumentContentRefreshRequest.type, {uri: 'lsptest:///2'})\n  void connection.sendRequest(TextDocumentContentRefreshRequest.type, {uri: 'untitled:///1'})\n})\n\nconnection.onNotification('requestFolders', async () => {\n  folders = await connection.sendRequest(WorkspaceFoldersRequest.type)\n})\n\nconnection.onPrepareRename(() => {\n  return prepareResponse\n})\n\nconnection.onCodeAction(() => {\n  return [\n    Command.create('title', 'editor.action.triggerSuggest')\n  ]\n})\n\nconnection.onWorkspaceSymbol(() => {\n  return []\n})\n\nconnection.onWorkspaceSymbolResolve(item => {\n  return item\n})\n\nconnection.onCodeLens(params => {\n  return [{range: Range.create(0, 0, 0, 3)}, {range: Range.create(1, 0, 1, 3)}]\n})\n\nconnection.onCodeLensResolve(codelens => {\n  return {range: codelens.range, command: {title: 'format', command: 'format'}}\n})\n\nconnection.listen()\n"
  },
  {
    "path": "src/__tests__/client/server/errorServer.js",
    "content": "\"use strict\"\nconst {createConnection, ResponseError} = require(\"vscode-languageserver/node\")\nconst connection = createConnection()\nconnection.onInitialize((_params) => {\n  return {\n    capabilities: {}\n  }\n})\n\nconnection.onSignatureHelp(_params => {\n  return new ResponseError(-32803, 'failed')\n})\n\nconnection.listen()\n"
  },
  {
    "path": "src/__tests__/client/server/eventServer.js",
    "content": "'use strict'\nconst {createConnection, TextEdit, RenameRequest, ProtocolRequestType, TextDocuments, Range, DiagnosticSeverity, Location, Diagnostic, DiagnosticRelatedInformation, PositionEncodingKind, WorkDoneProgress, ResponseError, LogMessageNotification, MessageType, ShowMessageNotification, ShowMessageRequest, ShowDocumentRequest, ApplyWorkspaceEditRequest, TextDocumentSyncKind, Position, RegistrationType} = require('vscode-languageserver/node')\nconst {TextDocument} = require('vscode-languageserver-textdocument')\nlet documents = new TextDocuments(TextDocument)\n\nconst connection = createConnection()\nconsole.log = connection.console.log.bind(connection.console)\nconsole.error = connection.console.error.bind(connection.console)\nlet options\ndocuments.listen(connection)\nconnection.onInitialize((params) => {\n  options = params.initializationOptions || {}\n  if (options.throwError) {\n    setTimeout(() => {\n      process.exit()\n    }, 10)\n    return new ResponseError(1, 'message', {retry: true})\n  }\n  if (options.normalThrow) {\n    setTimeout(() => {\n      process.exit()\n    }, 10)\n    throw new Error('normal throw error')\n  }\n  if (options.utf8) {\n    return {capabilities: {positionEncoding: PositionEncodingKind.UTF8}}\n  }\n  if (options.trace) {\n    setTimeout(() => {\n      connection.tracer.log('This is a trace message')\n      connection.tracer.log('This is a trace message', {'info': 'verbose info'})\n    }, 1)\n  }\n  return {\n    capabilities: {\n      textDocumentSync: TextDocumentSyncKind.Full\n    }\n  }\n})\n\nconnection.onNotification('diagnostics', () => {\n  let diagnostics = []\n  let related = []\n  let uri = 'lsptest:///2'\n  related.push(DiagnosticRelatedInformation.create(Location.create(uri, Range.create(0, 0, 0, 1)), 'dup'))\n  related.push(DiagnosticRelatedInformation.create(Location.create(uri, Range.create(0, 0, 1, 0)), 'dup'))\n  diagnostics.push(Diagnostic.create(Range.create(0, 0, 1, 0), 'msg', DiagnosticSeverity.Error, undefined, undefined, related))\n  void connection.sendDiagnostics({uri: 'lsptest:///1', diagnostics})\n  void connection.sendDiagnostics({uri: 'lsptest:///3', version: 1, diagnostics})\n})\n\nconnection.onNotification('simpleEdit', async () => {\n  let res = await connection.sendRequest(ApplyWorkspaceEditRequest.type, {edit: {documentChanges: []}})\n  void connection.sendNotification('result', res)\n})\n\nconnection.onNotification('register', async () => {\n  void connection.client.register(RenameRequest.type, {\n    prepareProvider: false\n  })\n})\n\nconnection.onNotification('registerBad', async () => {\n  void connection.client.register(new ProtocolRequestType('not_exists'), {})\n})\n\nconnection.onNotification('edits', async () => {\n  let uris = documents.keys()\n  let res = await connection.sendRequest(ApplyWorkspaceEditRequest.type, {\n    edit: {\n      documentChanges: uris.map(uri => {\n        return {\n          textDocument: {uri, version: documents.get(uri).version + 1},\n          edits: [TextEdit.insert(Position.create(0, 0), 'foo')]\n        }\n      })\n    }\n  })\n  void connection.sendNotification('result', res)\n})\n\nconnection.onNotification('send', () => {\n  void connection.sendRequest('customRequest')\n  void connection.sendNotification('customNotification')\n  void connection.sendProgress(WorkDoneProgress.type, '4fb247f8-0ede-415d-a80a-6629b6a9eaf8', {kind: 'end', message: 'end message'})\n})\n\nconnection.onNotification('logMessage', () => {\n  void connection.sendNotification(LogMessageNotification.type, {type: MessageType.Debug, message: 'msg'})\n  void connection.sendNotification(LogMessageNotification.type, {type: MessageType.Error, message: 'msg'})\n  void connection.sendNotification(LogMessageNotification.type, {type: MessageType.Info, message: 'msg'})\n  void connection.sendNotification(LogMessageNotification.type, {type: MessageType.Log, message: 'msg'})\n  void connection.sendNotification(LogMessageNotification.type, {type: MessageType.Warning, message: 'msg'})\n})\n\nconnection.onNotification('showMessage', () => {\n  void connection.sendNotification(ShowMessageNotification.type, {type: MessageType.Error, message: 'msg'})\n  void connection.sendNotification(ShowMessageNotification.type, {type: MessageType.Info, message: 'msg'})\n  void connection.sendNotification(ShowMessageNotification.type, {type: MessageType.Log, message: 'msg'})\n  void connection.sendNotification(ShowMessageNotification.type, {type: MessageType.Warning, message: 'msg'})\n})\n\nconnection.onNotification('requestMessage', async params => {\n  await connection.sendRequest(ShowMessageRequest.type, {type: params.type, message: 'msg', actions: [{title: 'open'}]})\n})\n\nconnection.onNotification('showDocument', async params => {\n  await connection.sendRequest(ShowDocumentRequest.type, params)\n})\n\nconnection.onProgress(WorkDoneProgress.type, '4b3a71d0-2b3f-46af-be2c-2827f548579f', (params) => {\n  void connection.sendNotification('progressResult', params)\n})\n\nconnection.onNotification('printMessage', () => {\n  process.stdin.write('stdin\\n')\n  process.stdout.write('stdout\\n')\n})\n\nconnection.onRequest('doExit', () => {\n  setTimeout(() => {\n    process.exit(1)\n  }, 30)\n})\n\nconnection.listen()\n"
  },
  {
    "path": "src/__tests__/client/server/fileWatchServer.js",
    "content": "'use strict'\nconst {createConnection, DidChangeWatchedFilesNotification} = require('vscode-languageserver/node')\nconst {URI} = require('vscode-uri')\n\nconst connection = createConnection()\nconsole.log = connection.console.log.bind(connection.console)\nconsole.error = connection.console.error.bind(connection.console)\nconnection.onInitialize((_params) => {\n  return {capabilities: {}}\n})\n\nlet disposables = []\nconnection.onInitialized(() => {\n  void connection.client.register(DidChangeWatchedFilesNotification.type, {\n    watchers: [{\n      globPattern: '**/jsconfig.json',\n    }, {\n      globPattern: '**/*.ts',\n      kind: 1\n    }, {\n      globPattern: {\n        baseUri: URI.file(process.cwd()).toString(),\n        pattern: '**/*.vim'\n      },\n      kind: 1\n    }, {\n      globPattern: '**/*.js',\n      kind: 2\n    }, {\n      globPattern: -1\n    }]\n  }).then(d => {\n    disposables.push(d)\n  })\n  void connection.client.register(DidChangeWatchedFilesNotification.type, {\n    watchers: null\n  }).then(d => {\n    disposables.push(d)\n  })\n})\nconnection.onNotification(DidChangeWatchedFilesNotification.type, params => {\n  void connection.sendNotification('filesChange', params)\n})\n\nconnection.onNotification('unwatch', () => {\n  for (let d of disposables) {\n    d.dispose()\n  }\n})\n\nconnection.listen()\n"
  },
  {
    "path": "src/__tests__/client/server/nullServer.js",
    "content": "\"use strict\"\nObject.defineProperty(exports, \"__esModule\", {value: true})\nconst node_1 = require(\"vscode-languageserver/node\")\nconst connection = (0, node_1.createConnection)()\nconnection.onInitialize((_params) => {\n  return {\n    capabilities: {}\n  }\n})\nconnection.onShutdown(() => {\n})\nconnection.listen()\n"
  },
  {
    "path": "src/__tests__/client/server/testDocuments.js",
    "content": "const {ResponseError, LSPErrorCodes} = require('vscode-languageserver/node')\nconst ls = require('vscode-languageserver/node')\nconst {TextDocument} = require('vscode-languageserver-textdocument')\nlet connection = ls.createConnection()\nlet documents = new ls.TextDocuments(TextDocument)\n\nlet lastOpenEvent\nlet lastCloseEvent\nlet lastChangeEvent\nlet lastWillSave\nlet lastDidSave\ndocuments.onDidOpen(e => {\n  lastOpenEvent = {uri: e.document.uri, version: e.document.version}\n})\ndocuments.onDidClose(e => {\n  lastCloseEvent = {uri: e.document.uri}\n})\ndocuments.onDidChangeContent(e => {\n  lastChangeEvent = {uri: e.document.uri, text: e.document.getText()}\n})\ndocuments.onWillSave(e => {\n  lastWillSave = {uri: e.document.uri}\n})\ndocuments.onWillSaveWaitUntil(e => {\n  let uri = e.document.uri\n  if (uri.endsWith('error.vim')) throw new ResponseError(LSPErrorCodes.ContentModified, 'content changed')\n  if (!uri.endsWith('foo.vim')) return []\n  return [ls.TextEdit.insert(ls.Position.create(0, 0), 'abc')]\n})\ndocuments.onDidSave(e => {\n  lastDidSave = {uri: e.document.uri}\n})\ndocuments.listen(connection)\n\nconsole.log = connection.console.log.bind(connection.console)\nconsole.error = connection.console.error.bind(connection.console)\n\nlet opts\nconnection.onInitialize(params => {\n  opts = params.initializationOptions\n  let capabilities = {\n    textDocumentSync: {\n      openClose: true,\n      change: ls.TextDocumentSyncKind.Full,\n      willSave: true,\n      willSaveWaitUntil: true,\n      save: true\n    }\n  }\n  return {capabilities}\n})\n\nconnection.onRequest('getLastOpen', () => {\n  return lastOpenEvent\n})\n\nconnection.onRequest('getLastClose', () => {\n  return lastCloseEvent\n})\n\nconnection.onRequest('getLastChange', () => {\n  return lastChangeEvent\n})\n\nconnection.onRequest('getLastWillSave', () => {\n  return lastWillSave\n})\n\nconnection.onRequest('getLastDidSave', () => {\n  return lastDidSave\n})\n\nlet disposables = []\nconnection.onNotification('registerDocumentSync', () => {\n  let opt = {documentSelector: [{language: 'vim'}]}\n  void connection.client.register(ls.DidOpenTextDocumentNotification.type, opt).then(d => {\n    disposables.push(d)\n  })\n  void connection.client.register(ls.DidCloseTextDocumentNotification.type, opt).then(d => {\n    disposables.push(d)\n  })\n  void connection.client.register(ls.DidChangeTextDocumentNotification.type, Object.assign({\n    syncKind: opts.none === true ? ls.TextDocumentSyncKind.None : ls.TextDocumentSyncKind.Incremental\n  }, opt)).then(d => {\n    disposables.push(d)\n  })\n  void connection.client.register(ls.WillSaveTextDocumentNotification.type, opt).then(d => {\n    disposables.push(d)\n  })\n  void connection.client.register(ls.WillSaveTextDocumentWaitUntilRequest.type, opt).then(d => {\n    disposables.push(d)\n  })\n})\n\nconnection.onNotification('unregisterDocumentSync', () => {\n  for (let dispose of disposables) {\n    dispose.dispose()\n  }\n  disposables = []\n})\n\nconnection.listen()\n"
  },
  {
    "path": "src/__tests__/client/server/testFileWatcher.js",
    "content": "const languageserver = require('vscode-languageserver')\nlet connection = languageserver.createConnection()\nlet documents = new languageserver.TextDocuments()\ndocuments.listen(connection)\n\nconnection.onInitialize(() => {\n  let capabilities = {\n    textDocumentSync: documents.syncKind\n  }\n  return { capabilities }\n})\n\nconnection.onInitialized(() => {\n  connection.sendRequest('client/registerCapability', {\n    registrations: [{\n      id: 'didChangeWatchedFiles',\n      method: 'workspace/didChangeWatchedFiles',\n      registerOptions: {\n        watchers: [{ globPattern: \"**\" }]\n      }\n    }]\n  })\n})\n\nlet received\n\nconnection.onNotification('workspace/didChangeWatchedFiles', params => {\n  received = params\n})\n\nconnection.onRequest('custom/received', async () => {\n  return received\n})\n\nconnection.listen()\n"
  },
  {
    "path": "src/__tests__/client/server/testInitializeResult.js",
    "content": "'use strict'\nObject.defineProperty(exports, \"__esModule\", {value: true})\nconst tslib_1 = require(\"tslib\")\nconst assert = tslib_1.__importStar(require(\"assert\"))\nconst vscode_languageserver_1 = require(\"vscode-languageserver/node\")\nlet connection = vscode_languageserver_1.createConnection()\n\nconnection.onInitialize((params) => {\n  assert.equal(params.capabilities.workspace.applyEdit, true)\n  assert.equal(params.capabilities.workspace.workspaceEdit.documentChanges, true)\n  assert.deepEqual(params.capabilities.workspace.workspaceEdit.resourceOperations, [vscode_languageserver_1.ResourceOperationKind.Create, vscode_languageserver_1.ResourceOperationKind.Rename, vscode_languageserver_1.ResourceOperationKind.Delete])\n  assert.equal(params.capabilities.workspace.workspaceEdit.failureHandling, vscode_languageserver_1.FailureHandlingKind.Undo)\n  assert.equal(params.capabilities.textDocument.completion.completionItem.deprecatedSupport, true)\n  assert.equal(params.capabilities.textDocument.completion.completionItem.preselectSupport, true)\n  assert.equal(params.capabilities.textDocument.signatureHelp.signatureInformation.parameterInformation.labelOffsetSupport, true)\n  assert.equal(params.capabilities.textDocument.rename.prepareSupport, true)\n  let valueSet = params.capabilities.textDocument.completion.completionItemKind.valueSet\n  assert.equal(valueSet[0], 1)\n  assert.equal(valueSet[valueSet.length - 1], vscode_languageserver_1.CompletionItemKind.TypeParameter)\n  let capabilities = {\n    textDocumentSync: 1,\n    completionProvider: {resolveProvider: true, triggerCharacters: ['\"', ':']},\n    hoverProvider: true,\n    renameProvider: {\n      prepareProvider: true\n    }\n  }\n  return {capabilities, customResults: {\"hello\": \"world\"}}\n})\nconnection.onInitialized(() => {\n  void connection.sendDiagnostics({uri: \"uri:/test.ts\", diagnostics: []})\n  void connection.sendDiagnostics({uri: \"uri:/not_exists.ts\", diagnostics: [], version: 1})\n})\n// Listen on the connection\nconnection.listen()\n"
  },
  {
    "path": "src/__tests__/client/server/testServer.js",
    "content": "const assert = require('assert')\nconst {URI} = require('vscode-uri')\nconst {\n  createConnection, CompletionItemKind, ResourceOperationKind, FailureHandlingKind,\n  DiagnosticTag, CompletionItemTag, TextDocumentSyncKind, MarkupKind, SignatureInformation, ParameterInformation,\n  Location, Range, DocumentHighlight, DocumentHighlightKind, CodeAction, Command, TextEdit, Position, DocumentLink,\n  ColorInformation, Color, ColorPresentation, FoldingRange, ProposedFeatures, SelectionRange, SymbolKind, ProtocolRequestType, WorkDoneProgress,\n  SignatureHelpRequest, SemanticTokensRefreshRequest, WorkDoneProgressCreateRequest, CodeLensRefreshRequest, InlayHintRefreshRequest, WorkspaceSymbolRequest, DidChangeConfigurationNotification} = require('vscode-languageserver/node')\n\nconst {\n  DidCreateFilesNotification,\n  DidRenameFilesNotification,\n  DidDeleteFilesNotification,\n  InlineCompletionItem,\n  WillCreateFilesRequest, WillRenameFilesRequest, WillDeleteFilesRequest, InlayHint, InlayHintLabelPart, InlayHintKind, DocumentDiagnosticReportKind, Diagnostic, DiagnosticSeverity, InlineValueText, InlineValueVariableLookup, InlineValueEvaluatableExpression,\n  ApplyWorkspaceEditRequest,\n  DocumentSymbol\n} = require('vscode-languageserver-protocol')\n\nlet connection = createConnection(ProposedFeatures.all)\n\nconsole.log = connection.console.log.bind(connection.console)\nconsole.error = connection.console.error.bind(connection.console)\nlet disposables = []\nconnection.onInitialize(params => {\n  assert.equal((params.capabilities.workspace).applyEdit, true)\n  assert.equal(params.capabilities.workspace.workspaceEdit.documentChanges, true)\n  assert.deepEqual(params.capabilities.workspace.workspaceEdit.resourceOperations, ['create', 'rename', 'delete'])\n  assert.equal(params.capabilities.workspace.workspaceEdit.failureHandling, FailureHandlingKind.Undo)\n  assert.equal(params.capabilities.workspace.workspaceEdit.normalizesLineEndings, true)\n  assert.equal(params.capabilities.workspace.workspaceEdit.changeAnnotationSupport.groupsOnLabel, false)\n  assert.equal(params.capabilities.workspace.symbol.resolveSupport.properties[0], 'location.range')\n  assert.equal(params.capabilities.textDocument.completion.completionItem.deprecatedSupport, true)\n  assert.equal(params.capabilities.textDocument.completion.completionItem.preselectSupport, true)\n  assert.equal(params.capabilities.textDocument.completion.completionItem.tagSupport.valueSet.length, 1)\n  assert.equal(params.capabilities.textDocument.completion.completionItem.tagSupport.valueSet[0], CompletionItemTag.Deprecated)\n  assert.equal(params.capabilities.textDocument.signatureHelp.signatureInformation.parameterInformation.labelOffsetSupport, true)\n  assert.equal(params.capabilities.textDocument.definition.linkSupport, true)\n  assert.equal(params.capabilities.textDocument.declaration.linkSupport, true)\n  assert.equal(params.capabilities.textDocument.implementation.linkSupport, true)\n  assert.equal(params.capabilities.textDocument.typeDefinition.linkSupport, true)\n  assert.equal(params.capabilities.textDocument.rename.prepareSupport, true)\n  assert.equal(params.capabilities.textDocument.publishDiagnostics.relatedInformation, true)\n  assert.equal(params.capabilities.textDocument.publishDiagnostics.tagSupport.valueSet.length, 2)\n  assert.equal(params.capabilities.textDocument.publishDiagnostics.tagSupport.valueSet[0], DiagnosticTag.Unnecessary)\n  assert.equal(params.capabilities.textDocument.publishDiagnostics.tagSupport.valueSet[1], DiagnosticTag.Deprecated)\n  assert.equal(params.capabilities.textDocument.documentLink.tooltipSupport, true)\n  assert.equal(params.capabilities.textDocument.inlineValue.dynamicRegistration, true)\n  assert.equal(params.capabilities.textDocument.inlayHint.dynamicRegistration, true)\n  assert.equal(params.capabilities.textDocument.inlayHint.resolveSupport.properties[0], 'tooltip')\n\n  let valueSet = params.capabilities.textDocument.completion.completionItemKind.valueSet\n  assert.equal(valueSet[0], 1)\n  assert.equal(valueSet[valueSet.length - 1], CompletionItemKind.TypeParameter)\n  assert.deepEqual(params.capabilities.workspace.workspaceEdit.resourceOperations, [ResourceOperationKind.Create, ResourceOperationKind.Rename, ResourceOperationKind.Delete])\n  assert.equal(params.capabilities.workspace.fileOperations.willCreate, true)\n\n  let diagnosticClientCapabilities = params.capabilities.textDocument.diagnostic\n  assert.equal(diagnosticClientCapabilities.dynamicRegistration, true)\n  assert.equal(diagnosticClientCapabilities.relatedDocumentSupport, true)\n\n  const capabilities = {\n    textDocumentSync: TextDocumentSyncKind.Full,\n    definitionProvider: true,\n    hoverProvider: true,\n    signatureHelpProvider: {\n      triggerCharacters: [','],\n      retriggerCharacters: [';']\n    },\n    completionProvider: {resolveProvider: true, triggerCharacters: ['\"', ':']},\n    referencesProvider: true,\n    documentHighlightProvider: true,\n    codeActionProvider: {\n      resolveProvider: true\n    },\n    codeLensProvider: {\n      resolveProvider: true\n    },\n    documentFormattingProvider: true,\n    documentRangeFormattingProvider: {\n      rangesSupport: true\n    },\n    documentOnTypeFormattingProvider: {\n      firstTriggerCharacter: ':'\n    },\n    renameProvider: {\n      prepareProvider: true\n    },\n    documentLinkProvider: {\n      resolveProvider: true\n    },\n    documentSymbolProvider: true,\n    colorProvider: true,\n    declarationProvider: true,\n    foldingRangeProvider: true,\n    implementationProvider: {\n      documentSelector: [{language: '*'}]\n    },\n    selectionRangeProvider: true,\n    inlineValueProvider: {},\n    inlineCompletionProvider: {},\n    inlayHintProvider: {\n      resolveProvider: true\n    },\n    typeDefinitionProvider: {\n      id: '82671a9a-2a69-4e9f-a8d7-e1034eaa0d2e',\n      documentSelector: [{language: '*'}]\n    },\n    callHierarchyProvider: true,\n    semanticTokensProvider: {\n      legend: {\n        tokenTypes: [],\n        tokenModifiers: []\n      },\n      range: true,\n      full: {\n        delta: true\n      }\n    },\n    workspace: {\n      fileOperations: {\n        // Static reg is folders + .txt files with operation kind in the path\n        didCreate: {\n          filters: [{scheme: 'file', pattern: {glob: '**/created-static/**{/,/*.txt}'}}]\n        },\n        didRename: {\n          filters: [\n            {scheme: 'file', pattern: {glob: '**/renamed-static/**/', matches: 'folder'}},\n            {scheme: 'file', pattern: {glob: '**/renamed-static/**/*.txt', matches: 'file'}}\n          ]\n        },\n        didDelete: {\n          filters: [{scheme: 'file', pattern: {glob: '**/deleted-static/**{/,/*.txt}'}}]\n        },\n        willCreate: {\n          filters: [{scheme: 'file', pattern: {glob: '**/created-static/**{/,/*.txt}'}}]\n        },\n        willRename: {\n          filters: [\n            {scheme: 'file', pattern: {glob: '**/renamed-static/**/', matches: 'folder'}},\n            {scheme: 'file', pattern: {glob: '**/renamed-static/**/*.txt', matches: 'file'}}\n          ]\n        },\n        willDelete: {\n          filters: [{scheme: 'file', pattern: {glob: '**/deleted-static/**{/,/*.txt}'}}]\n        },\n      },\n      textDocumentContent: {\n        schemes: ['content-test']\n      }\n    },\n    linkedEditingRangeProvider: true,\n    diagnosticProvider: {\n      identifier: 'da348dc5-c30a-4515-9d98-31ff3be38d14',\n      interFileDependencies: true,\n      workspaceDiagnostics: true\n    },\n    typeHierarchyProvider: true,\n    workspaceSymbolProvider: {\n      resolveProvider: true\n    },\n    notebookDocumentSync: {\n      notebookSelector: [{\n        notebook: {notebookType: 'jupyter-notebook'},\n        cells: [{language: 'python'}]\n      }]\n    }\n  }\n  return {capabilities, customResults: {hello: 'world'}}\n})\n\nconnection.onInitialized(() => {\n  // Dynamic reg is folders + .js files with operation kind in the path\n  void connection.client.register(DidCreateFilesNotification.type, {\n    filters: [{scheme: 'file', pattern: {glob: '**/created-dynamic/**{/,/*.js}'}}]\n  })\n  void connection.client.register(DidRenameFilesNotification.type, {\n    filters: [\n      {scheme: 'file', pattern: {glob: '**/renamed-dynamic/**/', matches: 'folder'}},\n      {scheme: 'file', pattern: {glob: '**/renamed-dynamic/**/*.js', matches: 'file'}}\n    ]\n  })\n  void connection.client.register(DidDeleteFilesNotification.type, {\n    filters: [{scheme: 'file', pattern: {glob: '**/deleted-dynamic/**{/,/*.js}'}}]\n  })\n  void connection.client.register(WillCreateFilesRequest.type, {\n    filters: [{scheme: 'file', pattern: {glob: '**/created-dynamic/**{/,/*.js}'}}]\n  })\n  void connection.client.register(WillRenameFilesRequest.type, {\n    filters: [\n      {scheme: 'file', pattern: {glob: '**/renamed-dynamic/**/', matches: 'folder'}},\n      {scheme: 'file', pattern: {glob: '**/renamed-dynamic/**/*.js', matches: 'file'}}\n    ]\n  })\n  void connection.client.register(WillDeleteFilesRequest.type, {\n    filters: [{scheme: 'file', pattern: {glob: '**/deleted-dynamic/**{/,/*.js}'}}]\n  })\n  void connection.client.register(SignatureHelpRequest.type, {\n    triggerCharacters: [':'],\n    retriggerCharacters: [':']\n  }).then(d => {\n    disposables.push(d)\n  })\n  void connection.client.register(WorkspaceSymbolRequest.type, {\n    workDoneProgress: false,\n    resolveProvider: true\n  }).then(d => {\n    disposables.push(d)\n  })\n  void connection.client.register(DidChangeConfigurationNotification.type, {\n    section: 'http'\n  }).then(d => {\n    disposables.push(d)\n  })\n  void connection.client.register(DidCreateFilesNotification.type, {\n    filters: [{\n      pattern: {\n        glob: '**/renamed-dynamic/**/',\n        matches: 'folder',\n        options: {\n          ignoreCase: true\n        }\n      }\n    }]\n  }).then(d => {\n    disposables.push(d)\n  })\n})\n\nconnection.onNotification('unregister', () => {\n  for (let d of disposables) {\n    d.dispose()\n    disposables = []\n  }\n})\n\nconnection.onCodeLens(params => {\n  return [{range: Range.create(0, 0, 0, 3)}, {range: Range.create(1, 0, 1, 3)}]\n})\n\nconnection.onNotification('fireCodeLensRefresh', () => {\n  void connection.sendRequest(CodeLensRefreshRequest.type)\n})\n\nconnection.onNotification('fireSemanticTokensRefresh', () => {\n  void connection.sendRequest(SemanticTokensRefreshRequest.type)\n})\n\nconnection.onNotification('fireInlayHintsRefresh', () => {\n  void connection.sendRequest(InlayHintRefreshRequest.type)\n})\n\nconnection.onCodeLensResolve(codelens => {\n  return {range: codelens.range, command: {title: 'format', command: 'editor.action.format'}}\n})\n\nconnection.onDeclaration(params => {\n  assert.equal(params.position.line, 1)\n  assert.equal(params.position.character, 1)\n  return {uri: params.textDocument.uri, range: {start: {line: 1, character: 1}, end: {line: 1, character: 2}}}\n})\n\nconnection.onDefinition(params => {\n  assert.equal(params.position.line, 1)\n  assert.equal(params.position.character, 1)\n  return {uri: params.textDocument.uri, range: {start: {line: 0, character: 0}, end: {line: 0, character: 1}}}\n})\n\nconnection.onHover(_params => {\n  return {\n    contents: {\n      kind: MarkupKind.PlainText,\n      value: 'foo'\n    }\n  }\n})\n\nconnection.onCompletion(_params => {\n  return [\n    {label: 'item', insertText: 'text'}\n  ]\n})\n\nconnection.onCompletionResolve(item => {\n  item.detail = 'detail'\n  return item\n})\n\nconnection.onSignatureHelp(_params => {\n  const result = {\n    signatures: [\n      SignatureInformation.create('label', 'doc', ParameterInformation.create('label', 'doc'))\n    ],\n    activeSignature: 1,\n    activeParameter: 1\n  }\n  return result\n})\n\nconnection.onReferences(params => {\n  return [\n    Location.create(params.textDocument.uri, Range.create(0, 0, 0, 0)),\n    Location.create(params.textDocument.uri, Range.create(1, 1, 1, 1))\n  ]\n})\n\nconnection.onDocumentHighlight(_params => {\n  return [\n    DocumentHighlight.create(Range.create(2, 2, 2, 2), DocumentHighlightKind.Read)\n  ]\n})\n\nconnection.onCodeAction(params => {\n  if (params.textDocument.uri.endsWith('empty.bat')) return undefined\n  return [\n    CodeAction.create('title', Command.create('title', 'test_command')),\n    CodeAction.create('other title'),\n    Command.create('title', 'test_command')\n  ]\n})\n\nconnection.onExecuteCommand(params => {\n  if (params.command == 'test_command') {\n    return {success: true}\n  }\n})\n\nconnection.onCodeActionResolve(codeAction => {\n  codeAction.title = 'resolved'\n  return codeAction\n})\n\nconnection.onDocumentFormatting(_params => {\n  return [\n    TextEdit.insert(Position.create(0, 0), 'insert')\n  ]\n})\n\nconnection.onDocumentRangeFormatting(_params => {\n  return [\n    TextEdit.del(Range.create(1, 1, 1, 2))\n  ]\n})\n\nconnection.onDocumentOnTypeFormatting(_params => {\n  return [\n    TextEdit.replace(Range.create(2, 2, 2, 3), 'replace')\n  ]\n})\n\nconnection.onPrepareRename(_params => {\n  return Range.create(1, 1, 1, 2)\n})\n\nconnection.onRenameRequest(_params => {\n  return {documentChanges: []}\n})\n\nconnection.onDocumentLinks(_params => {\n  return [\n    DocumentLink.create(Range.create(1, 1, 1, 2))\n  ]\n})\n\nconnection.onDocumentLinkResolve(link => {\n  link.target = URI.file('/target.txt').toString()\n  return link\n})\n\nconnection.onDocumentSymbol(_params => {\n  return [\n    DocumentSymbol.create('name', undefined, SymbolKind.Method, Range.create(1, 1, 3, 1), Range.create(2, 1, 2, 3))\n  ]\n})\n\nconnection.onDocumentColor(_params => {\n  return [\n    ColorInformation.create(Range.create(1, 1, 1, 2), Color.create(1, 1, 1, 1))\n  ]\n})\n\nconnection.onColorPresentation(_params => {\n  return [\n    ColorPresentation.create('label')\n  ]\n})\n\nconnection.onFoldingRanges(_params => {\n  return [\n    FoldingRange.create(1, 2)\n  ]\n})\n\nconnection.onImplementation(params => {\n  assert.equal(params.position.line, 1)\n  assert.equal(params.position.character, 1)\n  return {uri: params.textDocument.uri, range: {start: {line: 2, character: 2}, end: {line: 3, character: 3}}}\n})\n\nconnection.onSelectionRanges(_params => {\n  return [\n    SelectionRange.create(Range.create(1, 2, 3, 4))\n  ]\n})\n\nlet lastFileOperationRequest\nconnection.workspace.onDidCreateFiles(params => {lastFileOperationRequest = {type: 'create', params}})\nconnection.workspace.onDidRenameFiles(params => {lastFileOperationRequest = {type: 'rename', params}})\nconnection.workspace.onDidDeleteFiles(params => {lastFileOperationRequest = {type: 'delete', params}})\n\nconnection.onRequest(\n  new ProtocolRequestType('testing/lastFileOperationRequest'),\n  () => {\n    return lastFileOperationRequest\n  },\n)\n\nconnection.workspace.onWillCreateFiles(params => {\n  const createdFilenames = params.files.map(f => `${f.uri}`).join('\\n')\n  return {\n    documentChanges: [{\n      textDocument: {uri: '/dummy-edit', version: null},\n      edits: [\n        TextEdit.insert(Position.create(0, 0), `WILL CREATE:\\n${createdFilenames}`),\n      ]\n    }],\n  }\n})\n\nconnection.workspace.onWillRenameFiles(params => {\n  const renamedFilenames = params.files.map(f => `${f.oldUri} -> ${f.newUri}`).join('\\n')\n  return {\n    documentChanges: [{\n      textDocument: {uri: '/dummy-edit', version: null},\n      edits: [\n        TextEdit.insert(Position.create(0, 0), `WILL RENAME:\\n${renamedFilenames}`),\n      ]\n    }],\n  }\n})\n\nconnection.workspace.onWillDeleteFiles(params => {\n  const deletedFilenames = params.files.map(f => `${f.uri}`).join('\\n')\n  return {\n    documentChanges: [{\n      textDocument: {uri: '/dummy-edit', version: null},\n      edits: [\n        TextEdit.insert(Position.create(0, 0), `WILL DELETE:\\n${deletedFilenames}`),\n      ]\n    }],\n  }\n})\n\nconnection.onTypeDefinition(params => {\n  assert.equal(params.position.line, 1)\n  assert.equal(params.position.character, 1)\n  return {uri: params.textDocument.uri, range: {start: {line: 2, character: 2}, end: {line: 3, character: 3}}}\n})\n\nconnection.languages.callHierarchy.onPrepare(params => {\n  return [\n    {\n      kind: SymbolKind.Function,\n      name: 'name',\n      range: Range.create(1, 1, 1, 1),\n      selectionRange: Range.create(2, 2, 2, 2),\n      uri: params.textDocument.uri\n    }\n  ]\n})\n\nconnection.languages.callHierarchy.onIncomingCalls(params => {\n  return [\n    {\n      from: params.item,\n      fromRanges: [Range.create(1, 1, 1, 1)]\n    }\n  ]\n})\n\nconnection.languages.callHierarchy.onOutgoingCalls(params => {\n  return [\n    {\n      to: params.item,\n      fromRanges: [Range.create(1, 1, 1, 1)]\n    }\n  ]\n})\n\nconnection.languages.semanticTokens.onRange(() => {\n  return {\n    resultId: '1',\n    data: []\n  }\n})\n\nconnection.languages.semanticTokens.on(() => {\n  return {\n    resultId: '2',\n    data: []\n  }\n})\n\nconnection.languages.semanticTokens.onDelta(() => {\n  return {\n    resultId: '3',\n    data: []\n  }\n})\n\nconnection.languages.diagnostics.on(() => {\n  return {\n    kind: DocumentDiagnosticReportKind.Full,\n    items: [\n      Diagnostic.create(Range.create(1, 1, 1, 1), 'diagnostic', DiagnosticSeverity.Error)\n    ]\n  }\n})\n\nconnection.languages.diagnostics.onWorkspace(() => {\n  return {\n    items: [{\n      kind: DocumentDiagnosticReportKind.Full,\n      uri: 'uri',\n      version: 1,\n      items: [\n        Diagnostic.create(Range.create(1, 1, 1, 1), 'diagnostic', DiagnosticSeverity.Error)\n      ]\n    }]\n  }\n})\n\nconst typeHierarchySample = {\n  superTypes: [],\n  subTypes: []\n}\nconnection.languages.typeHierarchy.onPrepare(params => {\n  const currentItem = {\n    kind: SymbolKind.Class,\n    name: 'ClazzB',\n    range: Range.create(1, 1, 1, 1),\n    selectionRange: Range.create(2, 2, 2, 2),\n    uri: params.textDocument.uri\n  }\n  typeHierarchySample.superTypes = [{...currentItem, name: 'classA', uri: 'uri-for-A'}]\n  typeHierarchySample.subTypes = [{...currentItem, name: 'classC', uri: 'uri-for-C'}]\n  return [currentItem]\n})\n\nconnection.languages.typeHierarchy.onSupertypes(_params => {\n  return typeHierarchySample.superTypes\n})\n\nconnection.languages.typeHierarchy.onSubtypes(_params => {\n  return typeHierarchySample.subTypes\n})\n\nconnection.languages.inlineValue.on(_params => {\n  return [\n    InlineValueText.create(Range.create(1, 2, 3, 4), 'text'),\n    InlineValueVariableLookup.create(Range.create(1, 2, 3, 4), 'variableName', false),\n    InlineValueEvaluatableExpression.create(Range.create(1, 2, 3, 4), 'expression'),\n  ]\n})\nconnection.languages.inlayHint.on(() => {\n  const one = InlayHint.create(Position.create(1, 1), [InlayHintLabelPart.create('type')], InlayHintKind.Type)\n  one.data = '1'\n  const two = InlayHint.create(Position.create(2, 2), [InlayHintLabelPart.create('parameter')], InlayHintKind.Parameter)\n  two.data = '2'\n  return [one, two]\n})\n\nconnection.languages.inlayHint.resolve(hint => {\n  if (typeof hint.label === 'string') {\n    hint.label = 'tooltip'\n  } else {\n    hint.label[0].tooltip = 'tooltip'\n  }\n  return hint\n})\n\nconnection.languages.onLinkedEditingRange(() => {\n  return {\n    ranges: [Range.create(1, 1, 1, 1)],\n    wordPattern: '\\\\w'\n  }\n})\n\nconnection.languages.inlineCompletion.on(_params => {\n  return [\n    InlineCompletionItem.create('text inline', 'te', Range.create(1, 2, 3, 4))\n  ]\n})\n\nconnection.workspace.textDocumentContent.on(_params => {\n  return {text: 'Some test content'}\n})\n\nconnection.onRequest(\n  new ProtocolRequestType('testing/sendSampleProgress'),\n  async (_, __) => {\n    const progressToken = 'TEST-PROGRESS-TOKEN'\n    await connection.sendRequest(WorkDoneProgressCreateRequest.type, {token: progressToken})\n    void connection.sendProgress(WorkDoneProgress.type, progressToken, {kind: 'begin', title: 'Test Progress'})\n    void connection.sendProgress(WorkDoneProgress.type, progressToken, {kind: 'report', percentage: 50, message: 'Halfway!'})\n    void connection.sendProgress(WorkDoneProgress.type, progressToken, {kind: 'end', message: 'Completed!'})\n  },\n)\n\nconnection.onRequest(\n  new ProtocolRequestType('testing/beginOnlyProgress'),\n  async (_, __) => {\n    const progressToken = 'TEST-PROGRESS-BEGIN'\n    await connection.sendRequest(WorkDoneProgressCreateRequest.type, {token: progressToken})\n  },\n)\n\nconnection.onRequest(new ProtocolRequestType('testing/sendPercentageProgress'), async (_, __) => {\n  // According to the spec, the reported percentage has to be an integer.\n  // Because JS doesn't have integer support, we have rounding code in place.\n  const progressToken2 = 'TEST-PROGRESS-PERCENTAGE'\n  await connection.sendRequest(WorkDoneProgressCreateRequest.type, {token: progressToken2})\n  const progress = connection.window.attachWorkDoneProgress(progressToken2)\n  progress.begin('Test Progress', 0.1)\n  progress.report(49.9, 'Halfway!')\n  progress.done()\n})\n\nconst uri = 'file:///abc.txt'\nconnection.onWorkspaceSymbol(() => {\n  return [\n    {name: 'name', kind: SymbolKind.Array, location: {uri}}\n  ]\n})\n\nconnection.onWorkspaceSymbolResolve(symbol => {\n  symbol.location = Location.create(symbol.location.uri, Range.create(1, 2, 3, 4))\n  return symbol\n})\n\nconnection.onRequest(new ProtocolRequestType('testing/sendApplyEdit'), async (_, __) => {\n  const params = {label: 'Apply Edit', edit: {}}\n  await connection.sendRequest(ApplyWorkspaceEditRequest.type, params)\n})\n\nconnection.onRequest(new ProtocolRequestType('testing/sendDiagnostics'), async (_, __) => {\n  const diagnostics = [{\n    severity: DiagnosticSeverity.Warning,\n    range: {\n      start: {line: 0, character: 0},\n      end: {line: 0, character: 5}\n    },\n    message: \"Example warning: Check your code!\",\n    source: \"ex\"\n  }]\n  connection.sendDiagnostics({uri, diagnostics})\n})\n\n// Listen on the connection\nconnection.listen()\n"
  },
  {
    "path": "src/__tests__/client/server/timeoutOnShutdownServer.js",
    "content": "\"use strict\"\nObject.defineProperty(exports, \"__esModule\", {value: true})\nconst node_1 = require(\"vscode-languageserver/node\")\nconst connection = (0, node_1.createConnection)()\nconnection.onInitialize((_params) => {\n  return {capabilities: {}}\n})\nconnection.onShutdown(async () => {\n  return new Promise((resolve) => {\n    setTimeout(resolve, 200000)\n  })\n})\nconnection.listen()\n"
  },
  {
    "path": "src/__tests__/client/textSynchronization.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport { v4 as uuidv4 } from 'uuid'\nimport { DidChangeTextDocumentNotification, DidCloseTextDocumentNotification, DidOpenTextDocumentNotification, DocumentSelector, Position, Range, TextDocumentSaveReason, TextEdit, WillSaveTextDocumentNotification, WillSaveTextDocumentWaitUntilRequest } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { URI } from 'vscode-uri'\nimport { LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from '../../language-client/index'\nimport Document from '../../model/document'\nimport { TextDocumentContentChange } from '../../types'\nimport { remove } from '../../util/fs'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nfunction createClient(documentSelector: DocumentSelector | undefined | null | LanguageClientOptions, middleware: Middleware = {}, opts: any = {}): LanguageClient {\n  const serverModule = path.join(__dirname, './server/testDocuments.js')\n  const serverOptions: ServerOptions = {\n    run: { module: serverModule, transport: TransportKind.ipc },\n    debug: { module: serverModule, transport: TransportKind.ipc, options: { execArgv: ['--nolazy', '--inspect=6014'] } }\n  }\n  if (documentSelector === undefined) documentSelector = [{ scheme: 'file' }]\n  const clientOptions: LanguageClientOptions = {\n    documentSelector: Array.isArray(documentSelector) ? documentSelector : undefined,\n    synchronize: {},\n    initializationOptions: opts,\n    middleware\n  };\n  (clientOptions as ({ $testMode?: boolean })).$testMode = true\n  if (documentSelector && !Array.isArray(documentSelector)) Object.assign(clientOptions, documentSelector)\n\n  const result = new LanguageClient('test', 'Test Language Server', serverOptions, clientOptions)\n  return result\n}\n\nlet nvim: Neovim\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = workspace.nvim\n})\n\nafterEach(async () => {\n  await helper.reset()\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nasync function loadBuffer(filepath: string): Promise<Document> {\n  let nr = await nvim.call('bufadd', [filepath]) as number\n  await nvim.call('bufload', [nr])\n  await helper.waitValue(async () => {\n    return workspace.getDocument(nr) != null\n  }, true)\n  return workspace.getDocument(nr)\n}\n\ndescribe('TextDocumentSynchronization', () => {\n  describe('DidOpenTextDocumentFeature', () => {\n    it('should register with empty documentSelector', async () => {\n      let client = createClient(undefined)\n      await client.start()\n      let feature = client.getFeature(DidOpenTextDocumentNotification.method)\n      feature.register({ id: uuidv4(), registerOptions: { documentSelector: null } })\n      let res = await client.sendRequest('getLastOpen')\n      expect(res).toBe(null)\n      let docs = feature.openDocuments\n      expect(docs).toBeDefined()\n      await client.stop()\n    })\n\n    it('should send event on document create', async () => {\n      let client = createClient([{ language: 'vim' }])\n      await client.start()\n      let uri = URI.file(path.join(os.tmpdir(), 't.vim'))\n      let doc = await workspace.loadFile(uri.toString())\n      expect(doc.languageId).toBe('vim')\n      let res = await client.sendRequest('getLastOpen') as any\n      expect(res.uri).toBe(doc.uri)\n      expect(res.version).toBe(doc.version)\n      await client.stop()\n    })\n\n    it('should work with middleware', async () => {\n      let called = false\n      let throwError = false\n      let client = createClient({\n        documentSelector: [{ language: 'vim' }],\n        textSynchronization: {}\n      }, {\n        didOpen: (doc, next) => {\n          called = true\n          if (throwError) throw new Error('myerror')\n          return next(doc)\n        }\n      })\n      await client.start()\n      let uri = URI.file(path.join(os.tmpdir(), 't.js'))\n      let doc = await workspace.loadFile(uri.toString())\n      expect(doc.languageId).toBe('javascript')\n      let feature = client.getFeature(DidOpenTextDocumentNotification.method)\n      feature.register({ id: uuidv4(), registerOptions: { documentSelector: [{ language: 'javascript' }] } })\n      let res = await client.sendRequest('getLastOpen') as any\n      expect(res.uri).toBe(doc.uri)\n      expect(called).toBe(true)\n      throwError = true\n      uri = URI.file(path.join(os.tmpdir(), 'a.js'))\n      await workspace.loadFile(uri.toString())\n      await client.stop()\n    })\n\n    it('should delayOpenNotifications', async () => {\n      let uri = URI.file(path.join(os.tmpdir(), 'x.vim'))\n      await workspace.loadFile(uri.toString())\n      let loaded: Set<string> = new Set()\n      let throwError = false\n      let client = createClient({\n        documentSelector: [{ language: 'vim' }],\n        textSynchronization: { delayOpenNotifications: true }\n      }, {\n        didOpen: (data, next) => {\n          loaded.add(URI.parse(data.uri).fsPath)\n          if (throwError) return Promise.reject(new Error('my error'))\n          return next(data)\n        }\n      })\n      await client.start()\n      let feature = client.getFeature(DidOpenTextDocumentNotification.method) as any\n      let filepath = path.join(os.tmpdir(), 't.vim')\n      let doc = await loadBuffer(filepath)\n      expect(loaded.has(filepath)).toBe(false)\n      await nvim.command(`b ${doc.bufnr}`)\n      await helper.waitValue(() => loaded.has(filepath), true)\n      await nvim.command(`bwipeout`)\n      filepath = path.join(os.tmpdir(), 'p.vim')\n      doc = await loadBuffer(filepath)\n      await feature.sendPendingOpenNotifications(doc.uri)\n      expect(loaded.has(filepath)).toBe(false)\n      await feature.callback(doc.textDocument)\n      await feature.callback(TextDocument.create('untitled:///1', 'tex', 1, ''))\n      await feature.sendPendingOpenNotifications()\n      expect(loaded.has(filepath)).toBe(true)\n      throwError = true\n      filepath = path.join(os.tmpdir(), 'foo.vim')\n      doc = await loadBuffer(filepath)\n      feature._pendingOpenNotifications.set(doc.uri, doc.textDocument)\n      await nvim.command(`b ${doc.bufnr}`)\n      await helper.waitValue(() => loaded.has(filepath), true)\n      await client.stop()\n    })\n  })\n\n  describe('DidCloseTextDocumentFeature', () => {\n    it('should send close event', async () => {\n      let uri = URI.file(path.join(os.tmpdir(), 'close.vim'))\n      let doc = await workspace.loadFile(uri.toString())\n      let client = createClient([{ language: 'vim' }])\n      await client.start()\n      await workspace.nvim.command(`bd! ${doc.bufnr}`)\n      await helper.wait(30)\n      let res = await client.sendRequest('getLastClose') as any\n      expect(res.uri).toBe(doc.uri)\n      await client.stop()\n    })\n\n    it('should unregister document selector', async () => {\n      let called = false\n      let client = createClient([{ language: 'javascript' }], {\n        didClose: (e, next) => {\n          called = true\n          return next(e)\n        }\n      })\n      await client.start()\n      let openFeature = client.getFeature(DidOpenTextDocumentNotification.method)\n      let id = uuidv4()\n      let options = { id, registerOptions: { documentSelector: [{ language: 'vim' }] } }\n      openFeature.register(options)\n      let feature = client.getFeature(DidCloseTextDocumentNotification.method)\n      feature.register(options)\n      let uri = URI.file(path.join(os.tmpdir(), 'close.vim'))\n      await workspace.loadFile(uri.toString())\n      await helper.wait(10)\n      feature.unregister('unknown')\n      let spy = jest.spyOn(client, 'sendNotification').mockReturnValue(Promise.reject(new Error('myerror')))\n      feature.unregister(id)\n      spy.mockRestore()\n      let res = await client.sendRequest('getLastClose') as any\n      expect(res).toBeNull()\n      expect(called).toBe(true)\n      await client.stop()\n    })\n  })\n\n  describe('DidChangeTextDocumentFeature', () => {\n    it('should send full change event ', async () => {\n      let called = false\n      let throwError = false\n      let client = createClient([{ language: 'vim' }], {\n        didChange: (e, next) => {\n          called = true\n          if (throwError) return Promise.reject(new Error('myerror'))\n          return next(e)\n        }\n      })\n      await client.start()\n      let uri = URI.file(path.join(os.tmpdir(), 'x.vim'))\n      let doc = await workspace.loadFile(uri.toString())\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'bar')])\n      let res = await client.sendRequest('getLastChange') as any\n      expect(res.text).toBe('bar\\n')\n      expect(called).toBe(true)\n      throwError = true\n      await doc.applyEdits([TextEdit.replace(Range.create(0, 0, 0, 3), '')])\n      await client.stop()\n    })\n\n    it('should send incremental change event', async () => {\n      let client = createClient([{ scheme: 'lsptest' }])\n      expect(client.isSynced('untitled:///1')).toBe(false)\n      await client.start()\n      await client.sendNotification('registerDocumentSync')\n      let feature = client.getFeature(DidChangeTextDocumentNotification.method)\n      feature.register({ registerOptions: {} } as any)\n      let textDocument = TextDocument.create('untitled:///1', 'x', 1, '')\n      expect(feature.getProvider(textDocument)).toBeUndefined()\n      let called = false\n      feature.onNotificationSent(() => {\n        called = true\n      })\n      let doc = await helper.createDocument(`${uuidv4()}.vim`)\n      await helper.waitValue(() => {\n        return client.isSynced(doc.uri)\n      }, true)\n      await nvim.call('setline', [1, 'bar'])\n      await doc.patchChange()\n      await helper.waitValue(() => {\n        return called\n      }, true)\n      let res = await client.sendRequest('getLastChange') as any\n      expect(res.uri).toBe(doc.uri)\n      expect(res.text).toBe('bar\\n')\n      let provider = feature.getProvider(doc.textDocument)\n      expect(provider).toBeDefined()\n      await provider.send({\n        contentChanges: [],\n        textDocument: { uri: doc.uri, version: doc.version },\n        bufnr: doc.bufnr,\n        original: '',\n        document: doc.textDocument,\n        originalLines: []\n      })\n      await client.sendNotification('unregisterDocumentSync')\n      await client.stop()\n    })\n\n    it('should not send change event when syncKind is none', async () => {\n      let client = createClient([{ scheme: 'lsptest' }], {}, { none: true })\n      await client.start()\n      await client.sendNotification('registerDocumentSync')\n      await nvim.command('edit x.vim')\n      let doc = await workspace.document\n\n      let feature = client.getFeature(DidChangeTextDocumentNotification.method)\n      await helper.waitValue(() => {\n        return feature.getProvider(doc.textDocument) != null\n      }, true)\n      let provider = feature.getProvider(doc.textDocument)\n      let changes: TextDocumentContentChange[] = [{\n        range: Range.create(0, 0, 0, 0),\n        text: 'foo'\n      }]\n      await provider.send({\n        contentChanges: changes,\n        document: TextDocument.create(doc.uri, doc.languageId, 2, ''),\n        textDocument: { uri: doc.uri, version: doc.version },\n        bufnr: doc.bufnr\n      } as any)\n      let res = await client.sendRequest('getLastChange') as any\n      expect(res.text).toBe('\\n')\n      await client.stop()\n    })\n  })\n\n  describe('WillSaveFeature', () => {\n    it('should will save event', async () => {\n      let called = false\n      let client = createClient([{ language: 'vim' }], {\n        willSave: (e, next) => {\n          called = true\n          return next(e)\n        }\n      })\n      await client.start()\n      let fsPath = path.join(os.tmpdir(), `${uuidv4()}.vim`)\n      let uri = URI.file(fsPath)\n      await workspace.openResource(uri.toString())\n      let doc = await workspace.document\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'bar')])\n      let feature = client.getFeature(WillSaveTextDocumentNotification.method)\n      let provider = feature.getProvider(doc.textDocument)\n      expect(provider).toBeDefined()\n      await provider.send({ document: doc.textDocument, bufnr: doc.bufnr, reason: TextDocumentSaveReason.Manual, waitUntil: () => {} })\n      let res = await client.sendRequest('getLastWillSave') as any\n      expect(res.uri).toBe(doc.uri)\n      await client.stop()\n      expect(called).toBe(true)\n      if (fs.existsSync(fsPath)) {\n        fs.unlinkSync(fsPath)\n      }\n    })\n  })\n\n  describe('WillSaveWaitUntilFeature', () => {\n    it('should send will save until request', async () => {\n      let client = createClient([{ scheme: 'lsptest' }])\n      await client.start()\n      await client.sendNotification('registerDocumentSync')\n      let fsPath = path.join(os.tmpdir(), `${uuidv4()}-foo.vim`)\n      let uri = URI.file(fsPath)\n      await workspace.openResource(uri.toString())\n      let doc = await workspace.document\n      let feature = client.getFeature(WillSaveTextDocumentNotification.method)\n      feature.register({ registerOptions: {} } as any)\n      await helper.waitValue(() => {\n        return feature.getProvider(doc.textDocument) != null\n      }, true)\n      let waitFeature = client.getFeature(WillSaveTextDocumentWaitUntilRequest.method)\n      waitFeature.register({ registerOptions: {} } as any)\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'x')])\n      nvim.command('w', true)\n      await helper.waitValue(() => {\n        return doc.getDocumentContent()\n      }, 'abcx\\n')\n      await client.sendNotification('unregisterDocumentSync')\n      await client.stop()\n      await remove(fsPath)\n    })\n\n    it('should not throw on response error', async () => {\n      let called = false\n      let client = createClient([], {\n        willSaveWaitUntil: (event, next) => {\n          called = true\n          return next(event)\n        }\n      })\n      await client.start()\n      await client.sendNotification('registerDocumentSync')\n      let fsPath = path.join(os.tmpdir(), `${uuidv4()}-error.vim`)\n      let uri = URI.file(fsPath)\n      await helper.waitValue(() => {\n        let feature = client.getFeature(DidOpenTextDocumentNotification.method)\n        let provider = feature.getProvider(TextDocument.create(uri.toString(), 'vim', 1, ''))\n        return provider != null\n      }, true)\n      await workspace.openResource(uri.toString())\n      let doc = await workspace.document\n      await doc.synchronize()\n      nvim.command('w', true)\n      await helper.waitValue(() => {\n        return called\n      }, true)\n      await client.stop()\n    })\n\n    it('should unregister event handler', async () => {\n      let client = createClient(null)\n      await client.start()\n      await client.sendNotification('registerDocumentSync')\n      await helper.waitValue(() => {\n        let feature = client.getFeature(DidOpenTextDocumentNotification.method)\n        let provider = feature.getProvider(TextDocument.create('file:///f.vim', 'vim', 1, ''))\n        return provider != null\n      }, true)\n      await client.sendNotification('unregisterDocumentSync')\n      await helper.waitValue(() => {\n        let feature = client.getFeature(DidOpenTextDocumentNotification.method)\n        let provider = feature.getProvider(TextDocument.create('file:///f.vim', 'vim', 1, ''))\n        return provider == null\n      }, true)\n      await client.stop()\n    })\n  })\n\n  describe('DidSaveTextDocumentFeature', () => {\n    it('should send did save notification', async () => {\n      let called = false\n      let client = createClient([{ language: 'vim' }], {\n        didSave: (e, next) => {\n          called = true\n          return next(e)\n        }\n      })\n      await client.start()\n      let fsPath = path.join(os.tmpdir(), `${uuidv4()}.vim`)\n      let uri = URI.file(fsPath)\n      await workspace.openResource(uri.toString())\n      let doc = await workspace.document\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'bar')])\n      nvim.command('w', true)\n      await helper.waitValue(() => {\n        return called\n      }, true)\n      let res = await client.sendRequest('getLastWillSave') as any\n      expect(res.uri).toBe(doc.uri)\n      await client.stop()\n      fs.unlinkSync(fsPath)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/client/utils.test.ts",
    "content": "/* eslint-disable */\nimport assert from 'assert'\nimport { spawn } from 'child_process'\nimport { NotificationType, NotificationType1, RequestType, RequestType1 } from 'vscode-languageserver-protocol'\nimport { checkProcessDied, handleChildProcessStartError } from '../../language-client/index'\nimport { data2String, fixNotificationType, fixRequestType, getLocale, getParameterStructures, getTracePrefix, isValidNotificationType, isValidRequestType, parseTraceData } from '../../language-client/utils'\nimport { Delayer } from '../../language-client/utils/async'\nimport { CloseAction, DefaultErrorHandler, ErrorAction, toCloseHandlerResult } from '../../language-client/utils/errorHandler'\nimport { ConsoleLogger, NullLogger } from '../../language-client/utils/logger'\nimport { wait } from '../../util/index'\nimport helper from '../helper'\n\ntest('Logger', () => {\n  const logger = new ConsoleLogger()\n  logger.error('error')\n  logger.warn('warn')\n  logger.info('info')\n  logger.log('log')\n  const nullLogger = new NullLogger()\n  nullLogger.error('error')\n  nullLogger.warn('warn')\n  nullLogger.info('info')\n  nullLogger.log('log')\n})\n\ntest('checkProcessDied', async () => {\n  checkProcessDied(undefined)\n  let child = spawn('sleep', ['3'], { cwd: process.cwd(), detached: true })\n  checkProcessDied(child)\n  await wait(20)\n  assert.rejects(async () => {\n    await handleChildProcessStartError(null, 'msg')\n  })\n})\n\ntest('getLocale', () => {\n  process.env.LANG = ''\n  expect(getLocale()).toBe('en')\n  process.env.LANG = 'en_US.UTF-8'\n  expect(getLocale()).toBe('en_US')\n})\n\ntest('getTraceMessage', () => {\n  expect(getTracePrefix({})).toMatch('Trace')\n  expect(getTracePrefix({ isLSPMessage: true, type: 'request' })).toMatch('LSP')\n})\n\ntest('getParameterStructures', () => {\n  expect(getParameterStructures('auto').toString()).toBe('auto')\n  // test all the cased of getParameterStructures\n  expect(getParameterStructures('byPosition').toString()).toBe('byPosition')\n  expect(getParameterStructures('byName').toString()).toBe('byName')\n  expect(getParameterStructures('unknown').toString()).toBe('auto')\n})\n\ntest('isValidRequestType', () => {\n  expect(isValidRequestType('test')).toBe(true)\n  expect(isValidRequestType({ method: 'test' })).toBe(false)\n  expect(isValidRequestType(new RequestType('test'))).toBe(true)\n})\n\ntest('isValidNotificationType', () => {\n  expect(isValidNotificationType('test')).toBe(true)\n  expect(isValidNotificationType({ method: 'test' })).toBe(false)\n  expect(isValidNotificationType(new NotificationType('test'))).toBe(true)\n})\n\ntest('fixRequestType', () => {\n  expect(fixRequestType('test', [])).toBe('test')\n  for (let i = 0; i <= 10; i++) {\n    let type = { method: 'test', numberOfParams: i }\n    expect(fixRequestType(type, [])).toBeDefined()\n  }\n  let type = { method: 'test', numberOfParams: 1, parameterStructures: 'auto' }\n  let res = fixRequestType(type, []) as RequestType1<unknown, undefined, undefined>\n  expect(res.numberOfParams).toBe(1)\n  expect(res.parameterStructures).toBeDefined()\n})\n\ntest('fixNotificationType', () => {\n  expect(fixNotificationType('test', [])).toBe('test')\n  for (let i = 0; i <= 10; i++) {\n    let type = { method: 'test', numberOfParams: i }\n    expect(fixNotificationType(type, [])).toBeDefined()\n  }\n  let type = { method: 'test', numberOfParams: 1, parameterStructures: 'auto' }\n  let res = fixNotificationType(type, []) as NotificationType1<unknown>\n  expect(res.numberOfParams).toBe(1)\n  expect(res.parameterStructures).toBeDefined()\n})\n\ntest('data2String', () => {\n  let err = new Error('my error')\n  err.stack = undefined\n  let text = data2String(err)\n  expect(text).toMatch('error')\n})\n\ntest('parseTraceData', () => {\n  expect(parseTraceData({})).toBe('{}')\n  expect(parseTraceData('msg')).toMatch('msg')\n  expect(parseTraceData('Params: data')).toMatch('data')\n  expect(parseTraceData('Result: {\"foo\": \"bar\"}')).toMatch('bar')\n})\n\ntest('DefaultErrorHandler', async () => {\n  let spy = jest.spyOn(console, 'error').mockImplementation(() => {\n    // ignore\n  })\n  let handler = new DefaultErrorHandler('test', 2)\n  expect(handler.error(new Error('test'), { jsonrpc: '' }, 1).action).toBe(ErrorAction.Continue)\n  expect(handler.error(new Error('test'), { jsonrpc: '' }, 5).action).toBe(ErrorAction.Shutdown)\n  handler.closed()\n  handler.milliseconds = 1\n  await wait(10)\n  let res = handler.closed()\n  expect(res.action).toBe(CloseAction.Restart)\n  handler.milliseconds = 10 * 1000\n  res = handler.closed()\n  expect(res.action).toBe(CloseAction.DoNotRestart)\n  spy.mockRestore()\n  expect(toCloseHandlerResult(CloseAction.DoNotRestart)).toBeDefined()\n  handler = new DefaultErrorHandler('test', 1, helper.createNullChannel())\n  handler.closed()\n})\n\ntest('Delayer', () => {\n  let count = 0\n  let factory = () => {\n    return Promise.resolve(++count)\n  }\n\n  let delayer = new Delayer(0)\n  let promises: Thenable<any>[] = []\n\n  assert(!delayer.isTriggered())\n  delayer.trigger(factory, -1)\n\n  promises.push(delayer.trigger(factory).then((result) => { assert.equal(result, 1); assert(!delayer.isTriggered()) }))\n  assert(delayer.isTriggered())\n\n  promises.push(delayer.trigger(factory).then((result) => { assert.equal(result, 1); assert(!delayer.isTriggered()) }))\n  assert(delayer.isTriggered())\n\n  promises.push(delayer.trigger(factory).then((result) => { assert.equal(result, 1); assert(!delayer.isTriggered()) }))\n  assert(delayer.isTriggered())\n\n  return Promise.all(promises).then(() => {\n    assert(!delayer.isTriggered())\n  }).finally(() => {\n    delayer.dispose()\n  })\n})\n\ntest('Delayer - forceDelivery', async () => {\n  let count = 0\n  let factory = () => {\n    return Promise.resolve(++count)\n  }\n\n  let delayer = new Delayer(150)\n  delayer.forceDelivery()\n  delayer.trigger(factory).then((result) => { assert.equal(result, 1); assert(!delayer.isTriggered()) })\n  await wait(10)\n  delayer.forceDelivery()\n  expect(count).toBe(1)\n  void delayer.trigger(factory)\n  delayer.trigger(factory, -1)\n  await wait(10)\n  delayer.cancel()\n  expect(count).toBe(1)\n})\n\ntest('Delayer - last task should be the one getting called', function() {\n  let factoryFactory = (n: number) => () => {\n    return Promise.resolve(n)\n  }\n\n  let delayer = new Delayer(0)\n  let promises: Thenable<any>[] = []\n\n  assert(!delayer.isTriggered())\n\n  promises.push(delayer.trigger(factoryFactory(1)).then((n) => { assert.equal(n, 3) }))\n  promises.push(delayer.trigger(factoryFactory(2)).then((n) => { assert.equal(n, 3) }))\n  promises.push(delayer.trigger(factoryFactory(3)).then((n) => { assert.equal(n, 3) }))\n\n  const p = Promise.all(promises).then(() => {\n    assert(!delayer.isTriggered())\n  })\n\n  assert(delayer.isTriggered())\n\n  return p\n})\n"
  },
  {
    "path": "src/__tests__/client/workspaceFolder.test.ts",
    "content": "'use strict'\nimport * as assert from 'assert'\nimport * as proto from 'vscode-languageserver-protocol'\nimport { DidChangeWorkspaceFoldersParams, Disposable } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport { BaseLanguageClient, MessageTransports } from '../../language-client/client'\nimport { WorkspaceFoldersFeature } from '../../language-client/workspaceFolders'\n\nclass TestLanguageClient extends BaseLanguageClient {\n  protected createMessageTransports(): Promise<MessageTransports> {\n    throw new Error('Method not implemented.')\n  }\n  public onRequest(): Disposable {\n    return {\n      dispose: () => {}\n    }\n  }\n}\n\ntype MaybeFolders = proto.WorkspaceFolder[] | undefined\n\nclass TestWorkspaceFoldersFeature extends WorkspaceFoldersFeature {\n  public sendInitialEvent(currentWorkspaceFolders: MaybeFolders): void {\n    super.sendInitialEvent(currentWorkspaceFolders)\n  }\n\n  public initializeWithFolders(currentWorkspaceFolders: MaybeFolders) {\n    super.initializeWithFolders(currentWorkspaceFolders)\n  }\n}\n\nfunction testEvent(initial: MaybeFolders, then: MaybeFolders, added: proto.WorkspaceFolder[], removed: proto.WorkspaceFolder[]) {\n  const client = new TestLanguageClient('foo', 'bar', {})\n\n  let arg: any\n  let spy = jest.spyOn(client, 'sendNotification').mockImplementation((_p1, p2) => {\n    arg = p2\n    return Promise.resolve()\n  })\n\n  const feature = new TestWorkspaceFoldersFeature(client)\n\n  feature.initializeWithFolders(initial)\n  feature.sendInitialEvent(then)\n\n  expect(spy).toHaveBeenCalled()\n  expect(spy).toHaveBeenCalledTimes(1)\n  const notification: DidChangeWorkspaceFoldersParams = arg\n  assert.deepEqual(notification.event.added, added)\n  assert.deepEqual(notification.event.removed, removed)\n}\n\nfunction testNoEvent(initial: MaybeFolders, then: MaybeFolders) {\n  const client = new TestLanguageClient('foo', 'bar', {})\n\n  let spy = jest.spyOn(client, 'sendNotification').mockImplementation(() => {\n    return Promise.resolve()\n  })\n\n  const feature = new TestWorkspaceFoldersFeature(client)\n\n  feature.initializeWithFolders(initial)\n  feature.sendInitialEvent(then)\n  expect(spy).toHaveBeenCalledTimes(0)\n}\n\ndescribe('Workspace Folder Feature Tests', () => {\n  const removedFolder = { uri: URI.parse('file://xox/removed').toString(), name: 'removedName', index: 0 }\n  const addedFolder = { uri: URI.parse('file://foo/added').toString(), name: 'addedName', index: 0 }\n  const addedProto = { uri: 'file://foo/added', name: 'addedName' }\n  const removedProto = { uri: 'file://xox/removed', name: 'removedName' }\n\n  test('remove/add', async () => {\n    assert.ok(!MessageTransports.is({}))\n    testEvent([removedFolder], [addedFolder], [addedProto], [removedProto])\n  })\n\n  test('remove', async () => {\n    testEvent([removedFolder], [], [], [removedProto])\n  })\n\n  test('remove2', async () => {\n    testEvent([removedFolder], undefined, [], [removedProto])\n  })\n\n  test('add', async () => {\n    testEvent([], [addedFolder], [addedProto], [])\n  })\n\n  test('add2', async () => {\n    testEvent(undefined, [addedFolder], [addedProto], [])\n  })\n\n  test('noChange1', async () => {\n    testNoEvent([addedFolder, removedFolder], [addedFolder, removedFolder])\n  })\n\n  test('noChange2', async () => {\n    testNoEvent([], [])\n  })\n\n  test('noChange3', async () => {\n    testNoEvent(undefined, undefined)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/coc-settings.json",
    "content": "{\n  \"suggest.timeout\": 5000,\n  \"tslint.enable\": false\n}\n"
  },
  {
    "path": "src/__tests__/completion/basic.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { v4 as uuidv4 } from 'uuid'\nimport { CancellationToken, Disposable, Position, TextEdit } from 'vscode-languageserver-protocol'\nimport commands from '../../commands'\nimport completion, { Completion } from '../../completion'\nimport sources from '../../completion/sources'\nimport { CompleteOption, CompleteResult, ExtendedCompleteItem, ISource, SourceConfig, SourceType, VimCompleteItem } from '../../completion/types'\nimport { WordDistance } from '../../completion/wordDistance'\nimport events from '../../events'\nimport { disposeAll, waitWithToken } from '../../util'\nimport { byteLength } from '../../util/string'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n  completion.loadConfiguration()\n})\n\nfunction triggerCompletion(source: string): void {\n  nvim.call('coc#start', { source }, true)\n}\nasync function pumvisible(): Promise<boolean> {\n  let res = await nvim.call('coc#pum#visible', []) as number\n  return res == 1\n}\n\nasync function create(items: string[] | VimCompleteItem[], trigger = true, conf?: Partial<SourceConfig>): Promise<string> {\n  let name = uuidv4()\n  disposables.push(sources.createSource({\n    ...(conf ?? {}),\n    name,\n    doComplete: (_opt: CompleteOption): Promise<CompleteResult<ExtendedCompleteItem>> => new Promise(resolve => {\n      if (items.length == 0 || typeof items[0] === 'string') {\n        resolve({\n          items: items.map(s => { return { word: s } })\n        })\n      } else {\n        resolve({ items: items as VimCompleteItem[] })\n      }\n    })\n  }))\n  let mode = await nvim.mode\n  if (mode.mode !== 'i') {\n    await nvim.command('startinsert')\n  }\n  if (trigger) {\n    triggerCompletion(name)\n    await helper.waitPopup()\n  }\n  return name\n}\n\ndescribe('completion', () => {\n  describe('suggest configurations', () => {\n    it('should select item by preselect', async () => {\n      helper.updateConfiguration('suggest.noselect', true)\n      expect(typeof Completion).toBe('function')\n      await create([{ word: 'foo' }, { word: 'foo' }, { word: 'bar', preselect: true }], true)\n      expect(events.completing).toBe(true)\n      await nvim.input('br')\n      await helper.waitValue(() => completion.activeItems.length, 1)\n      await helper.confirmCompletion(0)\n      await helper.waitFor('getline', ['.'], 'bar')\n    })\n\n    it('should disable preselect feature', async () => {\n      helper.updateConfiguration('suggest.enablePreselect', false)\n      await create([{ word: 'foo' }, { word: 'bar' }, { word: 'foot', preselect: true }], true)\n      let info = await nvim.call('coc#pum#info') as any\n      expect(info.index).toBe(0)\n    })\n\n    it('should trigger with none ascii characters', async () => {\n      helper.updateConfiguration('suggest.asciiCharactersOnly', false)\n      await create(['你好'], false)\n      await nvim.input('ni')\n      await helper.waitPopup()\n    }, 10000)\n\n    it('should use insert range instead of replace', async () => {\n      helper.updateConfiguration('suggest.insertMode', 'insert')\n      await helper.createDocument()\n      await nvim.setLine('ffoo')\n      let name = await create(['foo'], false)\n      await nvim.call('cursor', [1, 2])\n      expect(sources.has(name)).toBe(true)\n      await commands.executeCommand('editor.action.triggerSuggest', name)\n      await helper.waitPopup()\n      await helper.confirmCompletion(0)\n      await helper.waitFor('getline', ['.'], 'foofoo')\n    }, 10000)\n\n    it('should use ascii match', async () => {\n      helper.updateConfiguration('suggest.asciiMatch', true)\n      await create(['\\xc1\\xc7\\xc8'], false)\n      await nvim.input('a')\n      await helper.waitPopup()\n      let items = await helper.items()\n      expect(items[0].word).toBe('ÁÇÈ')\n    }, 10000)\n\n    it('should not use ascii match', async () => {\n      helper.updateConfiguration('suggest.asciiMatch', false)\n      await create(['\\xc1\\xc7\\xc8', 'foo'], false)\n      await nvim.input('a')\n      await helper.wait(50)\n      let visible = await pumvisible()\n      expect(visible).toBe(false)\n      await nvim.input('<cr>')\n      await nvim.input('f')\n      await helper.waitPopup()\n    }, 10000)\n\n    it('should not trigger with none ascii characters', async () => {\n      helper.updateConfiguration('suggest.asciiCharactersOnly', true)\n      await create(['你好'], false)\n      await nvim.input('你')\n      await helper.wait(10)\n      let visible = await pumvisible()\n      expect(visible).toBe(false)\n    })\n\n    it('should not trigger with number input', async () => {\n      helper.updateConfiguration('suggest.ignoreRegexps', ['[0-9]+'])\n      await create(['1234', '1984'], false)\n      await nvim.input('1')\n      await helper.waitFor('getline', ['.'], '1')\n      let visible = await pumvisible()\n      expect(visible).toBe(false)\n    })\n\n    it('should disable filter on backspace', async () => {\n      helper.updateConfiguration('suggest.filterOnBackspace', false)\n      await create(['this', 'thoit'], true)\n      await nvim.input('this')\n      await helper.waitValue(() => {\n        return completion.activeItems.length\n      }, 1)\n      await nvim.input('<bs>')\n      await helper.waitValue(() => {\n        return completion.isActivated\n      }, false)\n    })\n\n    it('should select recent used item', async () => {\n      helper.updateConfiguration('suggest.selection', 'recentlyUsed')\n      let name = await create(['foo', 'bar', 'foobar'])\n      await helper.confirmCompletion(1)\n      await nvim.input('<CR>f')\n      triggerCompletion(name)\n      let info = await nvim.call('coc#pum#info') as any\n      expect(info.index).toBe(1)\n    })\n\n    it('should not resolve timeout sources', async () => {\n      helper.updateConfiguration('suggest.timeout', 30)\n      disposables.push(sources.createSource({\n        name: 'timeout',\n        doComplete: (_opt: CompleteOption, token) => new Promise(resolve => {\n          let timer = setTimeout(() => {\n            resolve({ items: [{ word: 'foo' }, { word: 'bar' }] })\n          }, 200)\n          token.onCancellationRequested(() => {\n            clearTimeout(timer)\n          })\n        })\n      }))\n      await nvim.input('if')\n      await helper.waitFor('eval', [\"get(g:,'coc_timeout_sources','')\"], ['timeout'])\n    })\n\n    it('should change default sort method', async () => {\n      const assertWords = async (arr: string[]) => {\n        await helper.waitPopup()\n        let win = await helper.getFloat('pum')\n        let words = await win.getVar('words')\n        expect(words).toEqual(arr)\n      }\n      helper.updateConfiguration('suggest.defaultSortMethod', 'none')\n      await create([{ word: 'far' }, { word: 'foobar' }, { word: 'foo' }], false)\n      await nvim.input('f')\n      await assertWords(['far', 'foobar', 'foo'])\n      await nvim.input('<esc>')\n      helper.updateConfiguration('suggest.defaultSortMethod', 'alphabetical')\n      await helper.wait(10)\n      await nvim.input('of')\n      await assertWords(['far', 'foo', 'foobar'])\n    }, 10000)\n\n    it('should remove duplicated words', async () => {\n      helper.updateConfiguration('suggest.removeDuplicateItems', true)\n      await create([{ word: 'foo', dup: 1 }, { word: 'foo', dup: 1 }], true)\n      let win = await helper.getFloat('pum')\n      let words = await win.getVar('words')\n      expect(words).toEqual(['foo'])\n    })\n\n    it('should remove current word', async () => {\n      helper.updateConfiguration('suggest.removeCurrentWord', true)\n      let buf = await nvim.buffer\n      let doc = workspace.getDocument(buf.id)\n      await buf.setLines(['foo bar', ''], { start: 0, end: -1, strictIndexing: false })\n      await doc.patchChange()\n      await nvim.call('cursor', [2, 1])\n      await nvim.input('if')\n      await helper.waitPopup()\n      await nvim.input('oo')\n      await helper.waitFor('coc#pum#visible', [], 0)\n    }, 10000)\n\n    it('should use border with floatConfig', async () => {\n      let dispose = helper.updateConfiguration('suggest.floatConfig', {\n        border: true,\n        rounded: true,\n        borderhighlight: 'Normal',\n        title: 'title'\n      })\n      await create([{ word: 'foo', kind: 'w', menu: 'x' }, { word: 'foobar', kind: 'w', menu: 'y' }], true)\n      await helper.waitPopup()\n      let win = await helper.getFloat('pum')\n      let id = await nvim.call('coc#float#get_related', [win.id, 'border'])\n      expect(id).toBeGreaterThan(1000)\n      dispose()\n    }, 10000)\n\n    it('should use pumFloatConfig', async () => {\n      helper.updateConfiguration('suggest.floatConfig', {})\n      helper.updateConfiguration('suggest.pumFloatConfig', {\n        border: true,\n        highlight: 'Normal',\n        winblend: 15,\n        shadow: true,\n        rounded: true,\n        title: 'suggest'\n      })\n      await create([{ word: 'foo', kind: 'w', menu: 'x' }, { word: 'foobar', kind: 'w', menu: 'y' }], true)\n      let win = await helper.getFloat('pum')\n      let id = await nvim.call('coc#float#get_related', [win.id, 'border']) as number\n      expect(id).toBeGreaterThan(1000)\n      let hl = await win.getOption('winhl')\n      expect(hl).toMatch('Normal')\n      let border = nvim.createWindow(id)\n      let buf = await border.buffer\n      let lines = await buf.lines\n      expect(lines[0]).toMatch('suggest')\n    })\n\n    it('should do filter when autoTrigger is none', async () => {\n      helper.updateConfiguration('suggest.autoTrigger', 'none')\n      let doc = await workspace.document\n      expect(completion.shouldTrigger(doc, '')).toBe(false)\n      await create(['foo', 'bar'], false)\n      await nvim.input('f')\n      await helper.wait(10)\n      expect(completion.activeItems.length).toBe(0)\n      nvim.call('coc#start', [], true)\n      await helper.waitPopup()\n      expect(completion.activeItems.length).toBe(1)\n      await nvim.input('o')\n      await helper.wait(10)\n      expect(completion.activeItems.length).toBe(1)\n    }, 10000)\n\n    it('should trigger for trigger character when filter failed', async () => {\n      await nvim.command('edit tmp')\n      let doc = await workspace.document\n      doc.chars.addKeyword('-')\n      let option: CompleteOption\n      let source: ISource = {\n        name: 'dash',\n        enable: true,\n        sourceType: SourceType.Service,\n        triggerCharacters: ['-'],\n        doComplete: async (opt: CompleteOption) => {\n          option = opt\n          if (opt.triggerCharacter == '-') return { items: [{ word: '-foo' }] }\n          return { items: [{ word: 'foo' }, { word: 'bar' }, { label: undefined }] }\n        }\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.input('i')\n      triggerCompletion('dash')\n      await helper.waitPopup()\n      expect(option.triggerCharacter).toBeUndefined()\n      await nvim.input('-')\n      await helper.waitValue(() => {\n        let items = completion.activeItems\n        return items && items.length == 1 && items[0].word == '-foo'\n      }, true)\n    }, 10000)\n\n    it('should trigger on trigger character', async () => {\n      helper.updateConfiguration('suggest.autoTrigger', 'none')\n      let fn = jest.fn()\n      let source: ISource = {\n        name: 'trigger',\n        enable: true,\n        sourceType: SourceType.Service,\n        triggerCharacters: ['.'],\n        doComplete: (_opt: CompleteOption) => new Promise(resolve => {\n          fn()\n          resolve({ items: [{ word: 'foo' }, { word: 'bar' }] })\n        })\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.input('if.')\n      await helper.wait(20)\n      expect(fn).toHaveBeenCalledTimes(0)\n      helper.updateConfiguration('suggest.autoTrigger', 'trigger')\n      await nvim.input('f')\n      await helper.wait(20)\n      await nvim.input('.')\n      await helper.waitPopup()\n    }, 10000)\n\n    it('should disable localityBonus', async () => {\n      helper.updateConfiguration('suggest.localityBonus', false)\n      let doc = await workspace.document\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), '\\nfoo\\nfoobar')])\n      await create(['foo', 'foobar'], true)\n      await helper.confirmCompletion(0)\n    })\n\n    it('should not show preview window when enableFloat is disabled', async () => {\n      helper.updateConfiguration('suggest.enableFloat', false)\n      let resolved = false\n      disposables.push(sources.createSource({\n        name: 'info',\n        doComplete: () => Promise.resolve({ items: [{ word: 'foo', info: 'detail' }] }),\n        onCompleteResolve: () => {\n          resolved = true\n        }\n      }))\n      await nvim.command('startinsert')\n      triggerCompletion('info')\n      await helper.waitPopup()\n      let floatWin = await helper.getFloat('pumdetail')\n      expect(floatWin).toBeUndefined()\n      await helper.confirmCompletion(0)\n      await helper.waitValue(() => {\n        return resolved\n      }, true)\n    }, 10000)\n\n    it('should disable graceful filter', async () => {\n      helper.updateConfiguration('suggest.filterGraceful', false)\n      await create(['this'], true)\n      await nvim.input('tih')\n      await helper.waitValue(async () => {\n        let items = await helper.items()\n        return items.length\n      }, 0)\n    })\n\n    it('should change detailField', async () => {\n      helper.updateConfiguration('suggest.detailField', 'abbr')\n      await create([{ word: 'this', detail: 'detail of this' }], true)\n      let floatWin = await helper.getFloat('pum')\n      let buf = await floatWin.buffer\n      expect(buf).toBeDefined()\n    }, 10000)\n\n    it('should change triggerCompletionWait', async () => {\n      let doc = await workspace.document\n      helper.updateConfiguration('suggest.triggerCompletionWait', 200)\n      let name = await create([{ word: 'foo' }, { word: 'bar' }], false)\n      triggerCompletion(name)\n      let spy\n      let p = new Promise<void>(resolve => {\n        spy = jest.spyOn(doc, 'patchChange').mockImplementation(() => {\n          resolve()\n          return Promise.resolve()\n        })\n      })\n      await p\n      await helper.wait(20)\n      completion.cancelAndClose()\n      spy.mockRestore()\n    })\n  })\n\n  describe('suggest variables', () => {\n    beforeEach(() => {\n      disposables.push(sources.createSource({\n        name: 'foo',\n        doComplete: (_opt: CompleteOption) => Promise.resolve({ items: [{ word: 'foo' }] })\n      }))\n    })\n\n    it('should be disabled by b:coc_suggest_disable', async () => {\n      let doc = await workspace.document\n      await doc.buffer.setVar('coc_suggest_disable', 1)\n      await nvim.input('if')\n      await helper.wait(20)\n      let visible = await pumvisible()\n      expect(visible).toBe(false)\n    })\n\n    it('should be disabled by b:coc_disabled_sources', async () => {\n      let doc = await workspace.document\n      await doc.buffer.setVar('coc_disabled_sources', ['foo'])\n      await nvim.input('if')\n      await helper.wait(20)\n      let visible = await pumvisible()\n      expect(visible).toBe(false)\n    })\n\n    it('should be disabled by b:coc_suggest_blacklist', async () => {\n      let doc = await workspace.document\n      await doc.buffer.setVar('coc_suggest_blacklist', ['end'])\n      await nvim.setLine('en')\n      await nvim.input('Ad')\n      await helper.wait(10)\n      let visible = await pumvisible()\n      expect(visible).toBe(false)\n    })\n  })\n\n  describe('shouldComplete()', () => {\n    it('should not complete when shouldComplete return false', async () => {\n      let name = Math.random().toString(16).slice(-6)\n      let called = false\n      let shouldRun = false\n      disposables.push(sources.addSource({\n        name,\n        shouldComplete: () => {\n          return shouldRun\n        },\n        doComplete: (_opt: CompleteOption): Promise<CompleteResult<ExtendedCompleteItem>> => new Promise(resolve => {\n          called = true\n          resolve({ items: [{ word: 'foo' }] })\n        })\n      }))\n      await nvim.input('i')\n      triggerCompletion(name)\n      await helper.wait(20)\n      expect(called).toBe(false)\n      shouldRun = true\n      triggerCompletion(name)\n      await helper.waitPopup()\n    }, 10000)\n\n    it('should not complete with empty sources', async () => {\n      nvim.call('coc#start', { source: 'not_exists' }, true)\n      await helper.wait(10)\n      let visible = await pumvisible()\n      expect(visible).toBe(false)\n    })\n  })\n\n  describe('doComplete()', () => {\n    it('should create pum', async () => {\n      let source: ISource = {\n        enable: true,\n        name: 'menu',\n        shortcut: '',\n        sourceType: SourceType.Service,\n        doComplete: (_opt: CompleteOption) => new Promise(resolve => {\n          resolve({\n            items: [{ word: 'foo', deprecated: true, menu: 'm', kind: 'k' }]\n          })\n        })\n      }\n      disposables.push(sources.addSource(source))\n      disposables.push(sources.addSource({\n        enable: true,\n        name: 'other',\n        shortcut: 's',\n        sourceType: SourceType.Service,\n        doComplete: (_opt: CompleteOption) => new Promise(resolve => {\n          resolve({\n            items: [{ word: 'bar', menu: '' }]\n          })\n        })\n      }))\n      await nvim.input('i')\n      await nvim.call('coc#start', {})\n      await helper.waitPopup()\n      let info = await nvim.call('coc#pum#info') as any\n      expect(info.index).toBe(0)\n    }, 10000)\n\n    it('should show slow source', async () => {\n      let source: ISource = {\n        priority: 0,\n        enable: true,\n        name: 'slow',\n        sourceType: SourceType.Service,\n        triggerCharacters: ['.'],\n        doComplete: (_opt: CompleteOption) => new Promise(resolve => {\n          setTimeout(() => {\n            resolve({ items: [{ word: 'foo', kind: 'w' }, { word: 'bar' }] })\n          }, 50)\n        })\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.input('i.')\n      await helper.waitPopup()\n      expect(completion.isActivated).toBe(true)\n      let items = await helper.items()\n      expect(items.length).toBe(2)\n      await nvim.input('foo')\n      await helper.wait(50)\n      items = await helper.items()\n      expect(items.length).toBe(1)\n    }, 10000)\n\n    it('should catch error', async () => {\n      disposables.push(sources.createSource({\n        name: 'error',\n        doComplete: (_opt: CompleteOption) => new Promise((_resolve, reject) => {\n          reject(new Error('custom error'))\n        })\n      }))\n      await nvim.input('if')\n      await helper.wait(50)\n      let cmdline = await helper.getCmdline()\n      expect(cmdline).toMatch('')\n    })\n\n    it('should show items before slow source finished', async () => {\n      let source: ISource = {\n        name: 'fast',\n        enable: true,\n        doComplete: (_opt: CompleteOption) => new Promise(resolve => {\n          resolve({ items: [{ word: 'foo' }, { word: 'bar' }] })\n        })\n      }\n      disposables.push(sources.addSource(source))\n      let finished = false\n      let slowSource: ISource = {\n        name: 'slow',\n        enable: true,\n        doComplete: (_opt: CompleteOption, token) => new Promise(resolve => {\n          token.onCancellationRequested(() => {\n            clearTimeout(timer)\n            resolve(undefined)\n          })\n          let timer = setTimeout(() => {\n            finished = true\n            resolve({ items: [{ word: 'world' }] })\n          }, 300)\n        })\n      }\n      disposables.push(sources.addSource(slowSource))\n      await nvim.input('if')\n      await events.race(['MenuPopupChanged'], 200)\n      expect(finished).toBe(false)\n    })\n\n    it('should show items when wordDistance is slow', async () => {\n      let _resolve\n      let spy = jest.spyOn(WordDistance, 'create').mockImplementation(() => {\n        return new Promise(resolve => {\n          _resolve = resolve\n        })\n      })\n      await create(['foo', 'foot'], false)\n      await nvim.input('f')\n      await helper.waitPopup()\n      _resolve(undefined)\n      spy.mockRestore()\n    }, 10000)\n  })\n\n  describe('resumeCompletion()', () => {\n    it('should not cancel when trigger for inComplete', async () => {\n      let name = Math.random().toString(16).slice(-6)\n      let _resolve\n      let fireResolve = () => {\n        _resolve({ items: [{ word: 'foo' }, { word: 'foot' }] })\n      }\n      disposables.push(sources.createSource({\n        name,\n        doComplete: (_opt: CompleteOption) => new Promise(resolve => {\n          _resolve = resolve\n        })\n      }))\n      disposables.push(sources.createSource({\n        name: 'inComplete',\n        doComplete: (opt: CompleteOption) => new Promise(resolve => {\n          if (opt.input.length == 1) {\n            resolve({ items: [{ word: 'fa' }], isIncomplete: true })\n          } else {\n            resolve({ items: [{ word: 'footman' }, { word: 'football' }, { word: 'fa' }], isIncomplete: false })\n          }\n        })\n      }))\n      await nvim.input('if')\n      await helper.waitPopup()\n      let items = completion.activeItems\n      expect(items.length).toBe(1)\n      await nvim.input('o')\n      await helper.wait(30)\n      fireResolve()\n      await helper.waitValue(() => {\n        return completion.activeItems.length\n      }, 4)\n    }, 10000)\n\n    it('should refresh pum when complete inComplete sources', async () => {\n      let name = Math.random().toString(16).slice(-6)\n      disposables.push(sources.createSource({\n        name,\n        doComplete: (_opt: CompleteOption) => new Promise(resolve => {\n          resolve({ items: [{ word: 'foo' }, { word: 'foot' }] })\n        })\n      }))\n      let timer\n      let called = false\n      disposables.push(sources.createSource({\n        name: 'inComplete',\n        doComplete: (opt: CompleteOption, token) => new Promise(resolve => {\n          if (opt.input.length == 1) {\n            resolve({ items: [{ word: 'fa' }], isIncomplete: true })\n          } else {\n            token.onCancellationRequested(() => {\n              called = true\n            })\n            timer = setTimeout(() => {\n              resolve({ items: [{ word: 'footman' }, { word: 'football' }, { word: 'fa' }], isIncomplete: false })\n            }, 1000)\n          }\n        })\n      }))\n      await nvim.input('if')\n      await helper.waitPopup()\n      await nvim.input('t')\n      await helper.waitValue((() => {\n        let activeItems = completion.activeItems\n        return activeItems.length == 1 && activeItems[0].word === 'foot'\n      }), true)\n      await nvim.input('t')\n      await helper.waitValue(() => called, true)\n      clearTimeout(timer)\n    }, 10000)\n\n    it('should stop if no filtered items', async () => {\n      await create(['foo', 'bar'], true)\n      expect(completion.isActivated).toBe(true)\n      await nvim.input('fp')\n      await helper.waitValue(() => {\n        return completion.isActivated\n      }, false)\n    })\n\n    it('should stop when selected and no filtered items', async () => {\n      helper.updateConfiguration('suggest.noselect', true)\n      await create(['foo'], true)\n      expect(completion.isActivated).toBe(true)\n      await nvim.call('coc#pum#_navigate', [1, 1])\n      await helper.waitFor('getline', ['.'], 'foo')\n      await nvim.input('(')\n      await helper.waitValue(() => {\n        return completion.isActivated\n      }, false)\n    })\n\n    it('should not resume after text change', async () => {\n      await create(['foo'], false)\n      await nvim.input('f')\n      await helper.waitPopup()\n      await nvim.setLine('fo')\n      await nvim.call('cursor', [2, 3])\n      await helper.waitValue(() => {\n        return completion.isActivated\n      }, false)\n    }, 10000)\n\n    it('should stop with bad insert on CursorMovedI', async () => {\n      await create(['foo', 'fat'], false)\n      await nvim.input('f')\n      await nvim.setLine('f a')\n      await nvim.call('cursor', [2, 4])\n      await helper.wait(30)\n      let visible = await pumvisible()\n      expect(visible).toBe(false)\n    })\n\n    it('should deactivate without filtered items', async () => {\n      await create(['foo', 'foobar'], true)\n      await nvim.input('f')\n      await nvim.input(' a')\n      await helper.waitFor('coc#pum#visible', [], 0)\n      expect(completion.isActivated).toBe(false)\n      completion.cancel()\n    })\n\n    it('should deactivate when insert space', async () => {\n      let source: ISource = {\n        priority: 0,\n        enable: true,\n        name: 'empty',\n        sourceType: SourceType.Service,\n        triggerCharacters: ['.'],\n        doComplete: (_opt: CompleteOption) => new Promise(resolve => {\n          resolve({ items: [{ word: 'foo bar' }] })\n        })\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.input('i.')\n      await helper.waitPopup()\n      expect(completion.isActivated).toBe(true)\n      let items = await helper.items()\n      expect(items[0].word).toBe('foo bar')\n      await nvim.input(' ')\n      await helper.waitValue(async () => {\n        return await pumvisible()\n      }, false)\n    }, 10000)\n\n    it('should use resume input to filter', async () => {\n      let source: ISource = {\n        priority: 0,\n        enable: true,\n        name: 'source',\n        sourceType: SourceType.Service,\n        triggerCharacters: ['.'],\n        doComplete: () => new Promise(resolve => {\n          setTimeout(() => {\n            resolve({ items: [{ word: 'foo' }, { word: 'bar' }] })\n          }, 60)\n        })\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.input('i.')\n      await helper.wait(20)\n      await nvim.input('f')\n      await helper.waitPopup()\n      expect(completion.isActivated).toBe(true)\n      let items = await helper.items()\n      expect(items.length).toBe(1)\n      expect(items[0].word).toBe('foo')\n    }, 10000)\n\n    it('should filter slow source', async () => {\n      disposables.push(sources.addSource({\n        name: 'fast',\n        enable: true,\n        shortcut: 's',\n        sourceType: SourceType.Service,\n        triggerCharacters: ['.'],\n        doComplete: (_opt: CompleteOption) => new Promise(resolve => {\n          resolve({ items: [{ word: 'xyz', menu: '' }] })\n        })\n      }))\n      let source: ISource = {\n        priority: 0,\n        enable: true,\n        name: 'slow',\n        sourceType: SourceType.Service,\n        triggerCharacters: ['.'],\n        doComplete: () => new Promise(resolve => {\n          setTimeout(() => {\n            resolve({ items: [{ word: 'foo' }, { word: 'bar' }] })\n          }, 100)\n        })\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.input('i.')\n      await helper.wait(10)\n      await nvim.input('f')\n      await helper.waitPopup()\n      await nvim.input('o')\n      await helper.waitValue((() => {\n        return completion.activeItems?.length\n      }), 1)\n    }, 10000)\n\n    it('should complete inComplete source', async () => {\n      let source: ISource = {\n        priority: 0,\n        enable: true,\n        name: 'inComplete',\n        sourceType: SourceType.Service,\n        triggerCharacters: ['.'],\n        doComplete: async (opt: CompleteOption) => {\n          if (opt.input.length <= 1) {\n            return { isIncomplete: true, items: [{ word: 'foo' }, { word: opt.input }] }\n          }\n          await helper.wait(10)\n          return { isIncomplete: false, items: [{ word: 'foo' }, { word: opt.input }] }\n        }\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.input('i.')\n      await helper.waitPopup()\n      expect(completion.isActivated).toBe(true)\n      await nvim.input('a')\n      await helper.wait(20)\n      await nvim.input('b')\n    }, 10000)\n\n    it('should not complete inComplete source when isIncomplete is false', async () => {\n      let source: ISource = {\n        priority: 0,\n        enable: true,\n        name: 'inComplete',\n        sourceType: SourceType.Service,\n        triggerCharacters: ['.'],\n        doComplete: async (opt: CompleteOption) => {\n          await helper.wait(30)\n          if (opt.input.length <= 1) {\n            return { isIncomplete: true, items: [{ word: 'foobar' }] }\n          }\n          return { isIncomplete: false, items: [{ word: 'foobar' }] }\n        }\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.input('i.')\n      await helper.waitPopup()\n      expect(completion.isActivated).toBe(true)\n      await nvim.input('fo')\n      await helper.wait(50)\n      await nvim.input('b')\n      await helper.wait(50)\n      expect(completion.isActivated).toBe(true)\n    }, 10000)\n\n    it('should filter when type character after item selected without handle complete done', async () => {\n      let input: string\n      let fn = jest.fn()\n      let source: ISource = {\n        priority: 0,\n        enable: true,\n        name: 'filter',\n        sourceType: SourceType.Service,\n        doComplete: opt => {\n          input = opt.input\n          if (input == 'f') return Promise.resolve({ items: [{ word: 'fo' }] })\n          if (input == 'foo') return Promise.resolve({ items: [{ word: 'foobar' }, { word: 'foot' }] })\n          return Promise.resolve({ items: [] })\n        },\n        onCompleteDone: () => {\n          fn()\n        }\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.input('if')\n      await helper.waitPopup()\n      await nvim.call('coc#pum#_navigate', [1, 1])\n      await helper.wait(20)\n      await nvim.input('o')\n      await helper.waitPopup()\n      expect(fn).toHaveBeenCalledTimes(0)\n    }, 10000)\n  })\n\n  describe('TextChangedI', () => {\n    it('should filter on backspace', async () => {\n      await create(['foo', 'fbi'], true)\n      await nvim.input('fo')\n      await helper.waitValue(() => completion.activeItems.length, 1)\n      await nvim.input('<backspace>')\n      await helper.waitValue(() => completion.activeItems.length, 2)\n    })\n\n    it('should respect commit character', async () => {\n      helper.updateConfiguration('suggest.acceptSuggestionOnCommitCharacter', true)\n      let source: ISource = {\n        enable: true,\n        name: 'commit',\n        sourceType: SourceType.Service,\n        triggerCharacters: ['.'],\n        doComplete: (opt: CompleteOption) => {\n          if (opt.triggerCharacter == '.') {\n            return Promise.resolve({ items: [{ word: 'bar' }] })\n          }\n          return Promise.resolve({ items: [{ word: 'foo' }] })\n        },\n        shouldCommit: (_item, character) => character == '.'\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.input('if')\n      await helper.waitPopup()\n      await nvim.input('o.')\n      await helper.waitFor('getline', ['.'], 'foo.')\n    }, 10000)\n\n    it('should cancel on CursorMoved', async () => {\n      await nvim.setLine('first line')\n      await nvim.input('o')\n      await create(['foo', 'foot'])\n      let [_, line, col] = await nvim.call('getcurpos') as number[]\n      completion.onCursorMovedI(events.bufnr, [line, col], false)\n      expect(completion.isActivated).toBe(true)\n      completion.onCursorMovedI(events.bufnr, [line, col - 1], false)\n      expect(completion.isActivated).toBe(false)\n      await events.fire('PumNavigate', [])\n    })\n\n    it('should stop completion with invalid input', async () => {\n      await nvim.setLine('line ')\n      await nvim.input('Af')\n      await create(['foo', 'foot'])\n      await nvim.setLine('abcd f')\n      await helper.waitValue(() => completion.isActivated, false)\n      await completion.filterResults()\n    })\n\n    it('should check indent change', async () => {\n      await create(['foo', 'bar'])\n      const linenr = completion.option.linenr\n      let changed = completion.hasIndentChange({ lnum: linenr + 1, col: 1, line: '', changedtick: 0, pre: '', })\n      expect(changed).toBe(false)\n    })\n  })\n\n  describe('TextChangedP', () => {\n    it('should cancel on CursorMoved', async () => {\n      let buf = await nvim.buffer\n      await buf.setLines(['', 'bar'], { start: 0, end: -1, strictIndexing: false })\n      let source: ISource = {\n        priority: 99,\n        enable: true,\n        name: 'temp',\n        sourceType: SourceType.Service,\n        doComplete: (_opt: CompleteOption) => Promise.resolve({ items: [{ word: 'foo#abc' }] }),\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.input('if')\n      await helper.waitPopup()\n      void events.fire('CompleteDone', [{}])\n      await helper.wait(10)\n      await events.fire('CursorMovedI', [buf.id, [2, 1, '']])\n      await helper.waitValue(() => {\n        return completion.isActivated\n      }, false)\n    }, 10000)\n  })\n\n  describe('onCompleteResolve', () => {\n    beforeEach(() => {\n      helper.updateConfiguration('coc.source.resolve.triggerCharacters', ['.'])\n    })\n\n    it('should do resolve for complete item', async () => {\n      let resolved = false\n      disposables.push(sources.createSource({\n        name: 'resolve',\n        doComplete: (_opt: CompleteOption) => Promise.resolve({ items: [{ word: 'foo' }] }),\n        onCompleteResolve: item => {\n          resolved = true\n          item.info = 'detail'\n        }\n      }))\n      await nvim.input('i.')\n      await helper.waitPopup()\n      await helper.confirmCompletion(0)\n      await helper.waitFor('getline', ['.'], '.foo')\n      expect(resolved).toBe(true)\n    }, 10000)\n\n    it('should cancel resolve request', async () => {\n      let cancelled = false\n      let called = false\n      disposables.push(sources.createSource({\n        name: 'resolve',\n        doComplete: (_opt: CompleteOption) => Promise.resolve({ items: [{ word: 'foo' }, { word: 'bar' }] }),\n        onCompleteResolve: async (item, _opt, token) => {\n          called = true\n          let res = await waitWithToken(200, token)\n          cancelled = res\n          item.info = 'info'\n        }\n      }))\n      await nvim.input('i.')\n      await helper.waitValue(() => {\n        return called\n      }, true)\n      await nvim.call('coc#pum#_navigate', [1, 0])\n      await helper.waitValue(() => {\n        return cancelled\n      }, true)\n      nvim.call('coc#pum#cancel', [], true)\n      let floatWin = await helper.getFloat('pumdetail')\n      expect(floatWin).toBeUndefined()\n    })\n\n    it('should not throw error', async () => {\n      let called = false\n      disposables.push(sources.createSource({\n        name: 'resolve',\n        doComplete: (_opt: CompleteOption) => Promise.resolve({ items: [{ word: 'foo' }] }),\n        onCompleteResolve: async _item => {\n          called = true\n          throw new Error('custom error')\n        }\n      }))\n      await nvim.input('i.')\n      await helper.waitPopup()\n      expect(called).toBe(true)\n      let cmdline = await helper.getCmdline()\n      expect(cmdline.includes('error')).toBe(false)\n    }, 10000)\n\n    it('should timeout on resolve', async () => {\n      let called = false\n      disposables.push(sources.createSource({\n        name: 'resolve',\n        doComplete: (_opt: CompleteOption) => Promise.resolve({ items: [{ word: 'foo' }] }),\n        onCompleteResolve: async item => {\n          called = true\n          await helper.wait(200)\n          item.info = 'info'\n        }\n      }))\n      await nvim.input('i.')\n      await helper.waitPopup()\n      await helper.waitValue(() => {\n        return called\n      }, true)\n      let floatWin = await helper.getFloat('pumdetail')\n      expect(floatWin).toBeUndefined()\n    }, 10000)\n  })\n\n  describe('trigger completion', () => {\n    it('should trigger completion if triggerAfterInsertEnter is true', async () => {\n      helper.updateConfiguration('suggest.triggerAfterInsertEnter', true)\n      await nvim.command('edit t|setl buftype=nofile')\n      await nvim.input('o')\n      await helper.wait(10)\n      expect(completion.isActivated).toBe(false)\n      await helper.createDocument()\n      await create(['fball', 'football'], false)\n      await nvim.input('f')\n      await nvim.input('<esc>')\n      await nvim.input('A')\n      await helper.waitPopup()\n      expect(completion.isActivated).toBe(true)\n    }, 10000)\n\n    it('should trigger complete when trigger patterns match', async () => {\n      let source: ISource = {\n        priority: 99,\n        enable: true,\n        name: 'temp',\n        triggerPatterns: [/EM/],\n        sourceType: SourceType.Service,\n        doComplete: (opt: CompleteOption) => {\n          if (!opt.input.startsWith('EM')) return null\n          return Promise.resolve({\n            items: [\n              { word: 'foo', filterText: 'EMfoo' },\n              { word: 'bar', filterText: 'EMbar' }\n            ]\n          })\n        },\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.input('i')\n      await nvim.input('EM')\n      await helper.waitPopup()\n      let items = await helper.items()\n      expect(items.length).toBe(2)\n    }, 10000)\n\n    it('should filter and sort on increment search', async () => {\n      await create(['forceDocumentSync', 'format', 'fallback'], false)\n      await nvim.input('f')\n      await helper.waitPopup()\n      await nvim.input('oa')\n      await helper.waitPopup()\n      let items = await helper.items()\n      expect(items.findIndex(o => o.word == 'fallback')).toBe(-1)\n    }, 10000)\n\n    it('should not trigger on insert enter', async () => {\n      await nvim.setLine('f')\n      await create(['foo', 'bar'], false)\n      await nvim.input('<esc>')\n      await nvim.input('A')\n      await helper.wait(1)\n      let visible = await pumvisible()\n      expect(visible).toBe(false)\n    })\n\n    it('should filter on fast input', async () => {\n      await create(['foo', 'bar'], false)\n      await nvim.input('br')\n      await helper.waitPopup()\n      let items = await helper.items()\n      let item = items.find(o => o.word == 'foo')\n      expect(item).toBeFalsy()\n      expect(items[0].word).toBe('bar')\n    }, 10000)\n\n    it('should filter completion when type none trigger character', async () => {\n      let source: ISource = {\n        name: 'test',\n        priority: 10,\n        enable: true,\n        firstMatch: false,\n        sourceType: SourceType.Native,\n        triggerCharacters: [],\n        doComplete: async () => {\n          return Promise.resolve({ items: [{ word: 'if(' }] })\n        }\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.setLine('')\n      await nvim.input('iif')\n      await helper.waitPopup()\n      await nvim.input('(')\n      await helper.wait(50)\n      let res = await pumvisible()\n      expect(res).toBe(true)\n    }, 10000)\n\n    it('should trigger on triggerCharacters', async () => {\n      let source: ISource = {\n        name: 'trigger',\n        enable: true,\n        triggerCharacters: ['.'],\n        doComplete: async () => Promise.resolve({\n          items: [{ word: 'foo' }]\n        })\n      }\n      disposables.push(sources.addSource(source))\n      let source1: ISource = {\n        name: 'trigger1',\n        enable: true,\n        triggerCharacters: ['.'],\n        doComplete: async () => Promise.resolve({\n          items: [{ word: 'bar' }]\n        })\n      }\n      disposables.push(sources.addSource(source1))\n      await nvim.input('i.')\n      await helper.waitPopup()\n      let items = await helper.items()\n      expect(items.length).toBe(2)\n    }, 10000)\n\n    it('should fix start column', async () => {\n      let source: ISource = {\n        name: 'test',\n        priority: 10,\n        enable: true,\n        firstMatch: false,\n        sourceType: SourceType.Native,\n        triggerCharacters: [],\n        doComplete: async () => {\n          return Promise.resolve({ startcol: 0, items: [{ word: 'foo.bar' }] })\n        }\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.setLine('foo.')\n      await nvim.input('Ab')\n      await helper.waitPopup()\n      await helper.confirmCompletion(0)\n      await helper.waitFor('getline', ['.'], 'foo.bar')\n    }, 10000)\n\n    it('should should complete items without input', async () => {\n      await workspace.document\n      let source: ISource = {\n        enable: true,\n        name: 'trigger',\n        priority: 10,\n        sourceType: SourceType.Native,\n        doComplete: async () => Promise.resolve({\n          items: [{ word: 'foo' }, { word: 'bar' }]\n        })\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.command('inoremap <silent><nowait><expr> <c-space> coc#refresh()')\n      await nvim.input('i')\n      await helper.wait(30)\n      await nvim.input('<c-space>')\n      await helper.waitPopup()\n      let items = await helper.items()\n      expect(items.length).toBeGreaterThan(1)\n    }, 10000)\n\n    it('should show float window', async () => {\n      helper.updateConfiguration('suggest.floatConfig', { border: true, title: 'title' })\n      let source: ISource = {\n        name: 'float',\n        priority: 10,\n        enable: true,\n        sourceType: SourceType.Native,\n        doComplete: () => Promise.resolve({\n          items: [{ word: 'foo', info: 'bar' }]\n        })\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.input('if')\n      await helper.waitPopup()\n      let hasFloat = await nvim.call('coc#float#has_float')\n      expect(hasFloat).toBe(1)\n      let res = await helper.visible('foo', 'float')\n      expect(res).toBe(true)\n    }, 10000)\n\n    it('should trigger on triggerPatterns', async () => {\n      let source: ISource = {\n        name: 'pattern',\n        priority: 10,\n        enable: true,\n        sourceType: SourceType.Native,\n        triggerPatterns: [/\\w+\\.$/],\n        doComplete: async () => Promise.resolve({\n          items: [{ word: 'foo' }]\n        })\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.input('ia.')\n      await helper.waitPopup()\n      let res = await helper.visible('foo', 'pattern')\n      expect(res).toBe(true)\n    })\n\n    it('should not trigger triggerOnly source', async () => {\n      let fn = jest.fn()\n      let source: ISource = {\n        name: 'pattern',\n        triggerOnly: true,\n        priority: 10,\n        enable: true,\n        sourceType: SourceType.Native,\n        triggerPatterns: [/^From:\\s*/],\n        doComplete: () => {\n          fn()\n          return { items: [{ word: 'foo' }] }\n        }\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.input('if')\n      await helper.wait(20)\n      expect(fn).toHaveBeenCalledTimes(0)\n    })\n\n    it('should not trigger when cursor moved', async () => {\n      let source: ISource = {\n        name: 'trigger',\n        priority: 10,\n        enable: true,\n        sourceType: SourceType.Native,\n        triggerCharacters: ['.'],\n        doComplete: async () => Promise.resolve({\n          items: [{ word: 'foo' }]\n        })\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.setLine('.a')\n      await nvim.input('A')\n      await nvim.input('<bs>')\n      await nvim.input('<left>')\n      await helper.wait(10)\n      let visible = await pumvisible()\n      expect(visible).toBe(false)\n    })\n\n    it('should trigger when completion is not completed', async () => {\n      let token: CancellationToken\n      let promise = new Promise(resolve => {\n        let source: ISource = {\n          name: 'completion',\n          priority: 10,\n          enable: true,\n          sourceType: SourceType.Native,\n          triggerCharacters: ['.'],\n          doComplete: async (opt, cancellationToken) => {\n            if (opt.triggerCharacter != '.') {\n              token = cancellationToken\n              resolve(undefined)\n              return new Promise<CompleteResult<ExtendedCompleteItem>>((resolve, reject) => {\n                let timer = setTimeout(() => {\n                  resolve({ items: [{ word: 'foo' }] })\n                }, 200)\n                if (cancellationToken.isCancellationRequested) {\n                  clearTimeout(timer)\n                  reject(new Error('Cancelled'))\n                }\n              })\n            }\n            return Promise.resolve({\n              items: [{ word: 'bar' }]\n            })\n          }\n        }\n        disposables.push(sources.addSource(source))\n      })\n      await nvim.input('if')\n      await promise\n      await nvim.input('.')\n      await helper.waitPopup()\n      await helper.visible('bar', 'completion')\n      expect(token).toBeDefined()\n      expect(token.isCancellationRequested).toBe(true)\n    }, 10000)\n  })\n\n  describe('completion results', () => {\n    it('should limit results for low priority source', async () => {\n      helper.updateConfiguration('suggest.lowPrioritySourceLimit', 2)\n      await create(['filename', 'filepath', 'find', 'filter', 'findIndex'], true)\n      let items = await helper.items()\n      expect(items.length).toBe(2)\n    })\n\n    it('should contains duplicated items when dup is 1', async () => {\n      await create([{ word: 'foo', dup: 1 }, { word: 'foo', dup: 1 }], true)\n      let items = await helper.items()\n      expect(items.length).toBe(2)\n    })\n\n    it('should limit result for high priority source', async () => {\n      helper.updateConfiguration('suggest.highPrioritySourceLimit', 2)\n      let source: ISource = {\n        name: 'high',\n        priority: 90,\n        enable: true,\n        sourceType: SourceType.Native,\n        triggerCharacters: ['.'],\n        doComplete: async () => Promise.resolve({\n          items: ['filename', 'filepath', 'filter', 'file'].map(key => ({ word: key }))\n        })\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.input('i.')\n      await helper.waitPopup()\n      let items = await helper.items()\n      expect(items.length).toBeGreaterThan(1)\n    }, 10000)\n\n    it('should truncate label of complete items', async () => {\n      helper.updateConfiguration('suggest.formatItems', ['abbr'])\n      helper.updateConfiguration('suggest.labelMaxLength', 10)\n      let source: ISource = {\n        name: 'high',\n        priority: 90,\n        enable: true,\n        sourceType: SourceType.Native,\n        triggerCharacters: ['.'],\n        doComplete: async () => Promise.resolve({\n          items: ['a', 'b', 'c', 'd'].map(key => ({ word: key.repeat(20) }))\n        })\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.input('i.')\n      await helper.waitPopup()\n      let winid = await nvim.call('coc#float#get_float_by_kind', ['pum']) as number\n      let win = nvim.createWindow(winid)\n      let buf = await win.buffer\n      let lines = await buf.lines\n      expect(lines[0].trim().length).toBe(10)\n    }, 10000)\n\n    it('should render labelDetails', async () => {\n      helper.updateConfiguration('suggest.formatItems', ['abbr'])\n      helper.updateConfiguration('suggest.labelMaxLength', 10)\n      disposables.push(sources.createSource({\n        name: 'test',\n        doComplete: (_opt: CompleteOption) => new Promise(resolve => {\n          resolve({\n            items: [{\n              word: 'x',\n              labelDetails: {\n                detail: 'foo',\n                description: 'bar'\n              }\n            }, {\n              word: 'y'.repeat(8),\n              labelDetails: {\n                detail: 'a'.repeat(20),\n                description: 'b'.repeat(20)\n              }\n            }]\n          })\n        })\n      }))\n      await nvim.input('i')\n      triggerCompletion('test')\n      await helper.waitPopup()\n      let winid = await nvim.call('coc#float#get_float_by_kind', ['pum']) as number\n      let win = nvim.createWindow(winid)\n      let buf = await win.buffer\n      let lines = await buf.lines\n      expect(lines.length).toBe(2)\n      expect(lines[0]).toMatch(/xfoo bar/)\n    }, 10000)\n\n    it('should delete previous items when complete items is null', async () => {\n      let source1: ISource = {\n        name: 'source1',\n        priority: 90,\n        enable: true,\n        sourceType: SourceType.Native,\n        triggerCharacters: ['.'],\n        doComplete: async () => Promise.resolve({\n          items: [{ word: 'foo', dup: 1 }]\n        })\n      }\n      let source2: ISource = {\n        name: 'source2',\n        priority: 90,\n        enable: true,\n        sourceType: SourceType.Native,\n        triggerCharacters: ['.'],\n        doComplete: async (opt: CompleteOption) => {\n          return opt.input == 'foo' ? null : {\n            items: [{ word: 'foo', dup: 1 }], isIncomplete: true\n          }\n        }\n      }\n      disposables.push(sources.addSource(source1))\n      disposables.push(sources.addSource(source2))\n      await nvim.input('i')\n      await nvim.input('.f')\n      await helper.waitPopup()\n      let items = await helper.items()\n      expect(items.length).toEqual(2)\n      await nvim.input('oo')\n      await helper.waitValue(() => {\n        return completion.activeItems?.length\n      }, 1)\n      items = await helper.items()\n      expect(items.length).toEqual(1)\n      expect(items[0].word).toBe('foo')\n    }, 10000)\n\n    it('should cancel completion on navigate', async () => {\n      let source1: ISource = {\n        name: 'source1',\n        priority: 90,\n        enable: true,\n        sourceType: SourceType.Native,\n        doComplete: async () => Promise.resolve({\n          items: [{ word: 'foo' }, { word: 'for' }]\n        })\n      }\n      let cancelled = false\n      let source2: ISource = {\n        name: 'source2',\n        priority: 90,\n        enable: true,\n        sourceType: SourceType.Native,\n        doComplete: async (_opt: CompleteOption, token) => {\n          return new Promise(resolve => {\n            let timer = setTimeout(() => {\n              resolve({ items: [{ word: 'foobar' }] })\n            }, 500)\n            token.onCancellationRequested(() => {\n              cancelled = true\n              clearTimeout(timer)\n            })\n          })\n        }\n      }\n      disposables.push(sources.addSource(source1))\n      disposables.push(sources.addSource(source2))\n\n      await nvim.input('i')\n      await nvim.input('f')\n      await helper.waitPopup()\n      await nvim.input('<down>')\n      await helper.waitValue(() => {\n        return cancelled\n      }, true)\n    }, 10000)\n  })\n\n  describe('indent change', () => {\n    it('should trigger completion after indent change', async () => {\n      await helper.createDocument('t')\n      let source: ISource = {\n        name: 'source1',\n        priority: 90,\n        enable: true,\n        sourceType: SourceType.Native,\n        doComplete: async () => Promise.resolve({\n          items: [\n            { word: 'endif' },\n            { word: 'endfunction' }\n          ]\n        })\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.input('i')\n      await nvim.input('  endi')\n      await helper.waitPopup()\n      await nvim.input('f')\n      await helper.wait(10)\n      await nvim.call('setline', ['.', 'endif'])\n      await helper.waitValue(() => {\n        return completion.option?.col\n      }, 0)\n    }, 10000)\n\n    it('should not trigger completion after indent change with reTriggerAfterIndent disabled', async () => {\n      helper.updateConfiguration('suggest.reTriggerAfterIndent', false)\n      await helper.createDocument('t')\n      let source: ISource = {\n        name: 'source1',\n        priority: 90,\n        enable: true,\n        sourceType: SourceType.Native,\n        doComplete: async () => Promise.resolve({\n          items: [{ word: 'endif' }]\n        })\n      }\n      disposables.push(sources.addSource(source))\n      await nvim.input('i')\n      await nvim.input('  endi')\n      await helper.waitPopup()\n      await nvim.input('f')\n      await helper.wait(10)\n      await nvim.call('setline', ['.', 'endif'])\n      await helper.wait(10)\n      let visible = await pumvisible()\n      expect(visible).toBe(false)\n    }, 10000)\n  })\n\n  describe('Navigate list', () => {\n    it('should navigate completion list', async () => {\n      helper.updateConfiguration('suggest.noselect', true)\n      await create(['foo', 'foot'], true)\n      let items = completion.activeItems\n      nvim.call('coc#pum#_navigate', [1, 1], true)\n      await helper.waitValue(() => {\n        return completion.selectedItem?.word == items[0].word\n      }, true)\n      nvim.call('coc#pum#_navigate', [0, 1], true)\n      await helper.waitValue(() => {\n        return completion.selectedItem\n      }, undefined)\n      completion.cancelAndClose()\n      await events.fire('MenuPopupChanged', [{}])\n      expect(completion.isActivated).toBe(false)\n    })\n\n    it('should not cancel when cursor moved to end of inserted word', async () => {\n      helper.updateConfiguration('suggest.noselect', true)\n      await create(['foo', 'foot'], true)\n      let items = completion.activeItems\n      let { option } = completion\n      nvim.call('coc#pum#_navigate', [1, 1], true)\n      let word = items[0].word\n      await helper.waitValue(() => {\n        return completion.selectedItem?.word == word\n      }, true)\n      completion.onCursorMovedI(option.bufnr, [option.linenr, option.col + byteLength(word) + 1], false)\n      expect(completion.isActivated).toBe(true)\n    })\n  })\n\n  describe('Character insert', () => {\n    beforeAll(() => {\n      let source: ISource = {\n        name: 'insert',\n        firstMatch: false,\n        sourceType: SourceType.Native,\n        triggerCharacters: ['.'],\n        doComplete: async opt => {\n          if (opt.word === 'f') return { items: [{ word: 'foo' }] }\n          if (!opt.triggerCharacter) return { items: [] }\n          let result = {\n            items: [{ word: 'one' }, { word: 'two' }]\n          }\n          return Promise.resolve(result)\n        }\n      }\n      sources.addSource(source)\n    })\n\n    afterAll(() => {\n      sources.removeSource('insert')\n    })\n\n    it('should keep selected text after text change', async () => {\n      let doc = await workspace.document\n      await nvim.setLine('f')\n      await nvim.input('A')\n      await doc.synchronize()\n      triggerCompletion('insert')\n      await helper.waitPopup()\n      let line = await nvim.line\n      expect(line).toBe('f')\n      await nvim.exec(`\n         noa call setline('.', 'foobar')\n         noa call cursor(1, 7)\n         `)\n      await helper.waitValue(async () => {\n        return await pumvisible()\n      }, false)\n    }, 10000)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/completion/float.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport Floating from '../../completion/floating'\nimport { getInsertWord, prefixWord } from '../../completion/pum'\nimport sources from '../../completion/sources'\nimport { CompleteResult, ExtendedCompleteItem, ISource, SourceType } from '../../completion/types'\nimport { FloatConfig } from '../../types'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet source: ISource\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  source = {\n    name: 'float',\n    priority: 10,\n    enable: true,\n    sourceType: SourceType.Native,\n    doComplete: (): Promise<CompleteResult<ExtendedCompleteItem>> => Promise.resolve({\n      items: [{\n        word: 'foo',\n        info: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'\n      }, {\n        word: 'foot',\n        info: 'foot'\n      }, {\n        word: 'football',\n      }]\n    })\n  }\n  sources.addSource(source)\n})\n\nafterAll(async () => {\n  sources.removeSource(source)\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  await helper.reset()\n})\n\ndescribe('completion float', () => {\n  it('should prefix word', () => {\n    expect(prefixWord('foo', 0, '', 0)).toBe('foo')\n    expect(prefixWord('foo', 1, '$foo', 0)).toBe('$foo')\n  })\n\n  it('should get insert word', () => {\n    expect(getInsertWord('word', [], 0)).toBe('word')\n    expect(getInsertWord('word\\nbar', [10], 2)).toBe('word')\n  })\n\n  it('should cancel float window', async () => {\n    await helper.edit()\n    await nvim.setLine('f')\n    await nvim.input('A')\n    nvim.call('coc#start', { source: 'float' }, true)\n    await helper.waitPopup()\n    await helper.confirmCompletion(0)\n    let hasFloat = await nvim.call('coc#float#has_float')\n    expect(hasFloat).toBe(0)\n  })\n\n  it('should adjust float window position', async () => {\n    await helper.edit()\n    await nvim.setLine(' '.repeat(70))\n    await nvim.input('Af')\n    await helper.visible('foo', 'float')\n    let floatWin = await helper.getFloat('pumdetail')\n    let config = await floatWin.getConfig()\n    expect(config.col + config.width).toBeLessThan(180)\n  })\n\n  it('should redraw float window on item change', async () => {\n    await helper.edit()\n    await nvim.setLine(' '.repeat(70))\n    await nvim.input('Af')\n    await helper.visible('foo', 'float')\n    await nvim.call('coc#pum#select', [1, 1, 0])\n    let floatWin = await helper.getFloat('pumdetail')\n    let buf = await floatWin.buffer\n    let lines = await buf.lines\n    expect(lines.length).toBeGreaterThan(0)\n    expect(lines[0]).toMatch('foot')\n  })\n\n  it('should hide float window when item info is empty', async () => {\n    await helper.edit()\n    await nvim.setLine(' '.repeat(70))\n    await nvim.input('Af')\n    await helper.visible('foo', 'float')\n    await nvim.call('coc#pum#select', [2, 1, 0])\n    let floatWin = await helper.getFloat('pumdetail')\n    expect(floatWin).toBeUndefined()\n  })\n\n  it('should hide float window after completion', async () => {\n    await helper.edit()\n    await nvim.setLine(' '.repeat(70))\n    await nvim.input('Af')\n    await helper.visible('foo', 'float')\n    await nvim.input('<C-n>')\n    await helper.wait(30)\n    await nvim.input('<C-y>')\n    await helper.wait(30)\n    let floatWin = await helper.getFloat('pumdetail')\n    expect(floatWin).toBeUndefined()\n  })\n})\n\ndescribe('float config', () => {\n  beforeEach(async () => {\n    await nvim.input('of')\n    await helper.waitPopup()\n  })\n\n  async function createFloat(config: Partial<FloatConfig>, docs = [{ filetype: 'txt', content: 'doc' }]): Promise<Floating> {\n    let floating = new Floating({\n      floatConfig: {\n        border: true,\n        ...config\n      }\n    })\n    floating.show(docs)\n    return floating\n  }\n\n  async function getFloat(): Promise<number> {\n    let win = await helper.getFloat('pumdetail')\n    return win ? win.id : -1\n  }\n\n  async function getRelated(winid: number, kind: string): Promise<number> {\n    if (!winid || winid == -1) return -1\n    let win = nvim.createWindow(winid)\n    let related = await win.getVar('related') as number[]\n    if (!related || !related.length) return -1\n    for (let id of related) {\n      let w = nvim.createWindow(id)\n      let v = await w.getVar('kind')\n      if (v == kind) {\n        return id\n      }\n    }\n    return -1\n  }\n\n  it('should not shown with empty lines', async () => {\n    await createFloat({}, [{ filetype: 'txt', content: '' }])\n    let floatWin = await helper.getFloat('pumdetail')\n    expect(floatWin).toBeUndefined()\n  })\n\n  it('should show window with border', async () => {\n    await createFloat({ border: true, rounded: true, focusable: true })\n    let winid = await getFloat()\n    expect(winid).toBeGreaterThan(0)\n    let id = await getRelated(winid, 'border')\n    expect(id).toBeGreaterThan(0)\n  })\n\n  it('should change window highlights', async () => {\n    await createFloat({ border: true, highlight: 'WarningMsg', borderhighlight: 'MoreMsg' })\n    let winid = await getFloat()\n    expect(winid).toBeGreaterThan(0)\n    let win = nvim.createWindow(winid)\n    let res = await win.getOption('winhl') as string\n    expect(res).toMatch('WarningMsg')\n    let id = await getRelated(winid, 'border')\n    expect(id).toBeGreaterThan(0)\n    win = nvim.createWindow(id)\n    res = await win.getOption('winhl') as string\n    expect(res).toMatch('MoreMsg')\n  })\n\n  it('should add shadow and winblend', async () => {\n    await createFloat({ shadow: true, winblend: 30 })\n    let winid = await getFloat()\n    expect(winid).toBeGreaterThan(0)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/completion/language.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationToken, Disposable } from 'vscode-languageserver-protocol'\nimport { CompletionItem, CompletionItemKind, CompletionList, InsertReplaceEdit, InsertTextFormat, InsertTextMode, Position, Range, TextEdit } from 'vscode-languageserver-types'\nimport commandManager from '../../commands'\nimport completion from '../../completion'\nimport { fixIndent, fixTextEdit, getUltisnipOption } from '../../completion/source-language'\nimport sources from '../../completion/sources'\nimport { CompleteOption, InsertMode, ItemDefaults } from '../../completion/types'\nimport events from '../../events'\nimport languages from '../../languages'\nimport { CompletionItemProvider } from '../../provider'\nimport snippetManager from '../../snippets/manager'\nimport { disposeAll } from '../../util'\nimport window from '../../window'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n})\n\nfunction createCompletionItem(word: string): CompletionItem {\n  return { label: word, filterText: word }\n}\n\ndescribe('LanguageSource util', () => {\n  it('should get ultisnip option', async () => {\n    let item: CompletionItem = { label: 'label' }\n    expect(getUltisnipOption(item)).toBeUndefined()\n    item.data = {}\n    expect(getUltisnipOption(item)).toBeUndefined()\n    item.data.ultisnip = true\n    expect(getUltisnipOption(item)).toBeDefined()\n    item.data.ultisnip = {}\n    expect(getUltisnipOption(item)).toBeDefined()\n  })\n\n  it('should fix range from indent', async () => {\n    let line = '  foo'\n    let currline = 'foo'\n    let range = Range.create(0, 2, 0, 5)\n    expect(fixIndent(line, currline, range)).toBe(-2)\n    expect(range).toEqual(Range.create(0, 0, 0, 3))\n    expect(fixIndent(currline, line, range)).toBe(2)\n    expect(range).toEqual(Range.create(0, 2, 0, 5))\n  })\n\n  it('should fix textEdit', async () => {\n    let edit = TextEdit.insert(Position.create(0, 1), '')\n    expect((fixTextEdit(0, edit) as TextEdit).range.start.character).toBe(0)\n    let insertReplaceEdit = InsertReplaceEdit.create('text', Range.create(0, 1, 0, 1), Range.create(0, 1, 0, 2))\n    fixTextEdit(0, insertReplaceEdit)\n    expect(insertReplaceEdit.insert.start.character).toBe(0)\n    expect(insertReplaceEdit.replace.start.character).toBe(0)\n    fixTextEdit(0, insertReplaceEdit)\n    expect(insertReplaceEdit.insert.start.character).toBe(0)\n    expect(insertReplaceEdit.replace.start.character).toBe(0)\n  })\n\n  it('should select recent item by prefix', async () => {\n    helper.updateConfiguration('suggest.selection', 'recentlyUsedByPrefix', disposables)\n    let provider: CompletionItemProvider = {\n      provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n        label: 'fa'\n      }, {\n        label: 'fb'\n      }, {\n        label: 'foo',\n        kind: CompletionItemKind.Class\n      }]\n    }\n    disposables.push(languages.registerCompletionItemProvider('foo', 'f', null, provider))\n    completion.mru.clear()\n    completion.mru.add('f', {\n      kind: CompletionItemKind.Class,\n      filterText: 'foo',\n      source: sources.getSource('foo'),\n    })\n    await nvim.setLine('f')\n    await nvim.input('A')\n    await nvim.call('coc#start', { source: 'foo' })\n    await helper.waitPopup()\n    let info = await nvim.call('coc#pum#info') as any\n    expect(info).toBeDefined()\n    expect(info.word).toBe('foo')\n  })\n})\n\ndescribe('language source', () => {\n  describe('toggle()', () => {\n    it('should toggle source', () => {\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: 'foo',\n          detail: 'detail of foo'\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('foo', 'f', null, provider))\n      let source = sources.getSource('foo')\n      expect(source).toBeDefined()\n      source.toggle()\n      expect(source.enable).toBe(false)\n      source.toggle()\n      expect(source.enable).toBe(true)\n    })\n  })\n\n  describe('shouldCommit()', () => {\n    it('should check commit characters', async () => {\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: 'foo',\n          detail: 'detail of foo'\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('foo', 'f', null, provider, [], 3, ['.']))\n      let source = sources.getSource('foo')\n      let item = createCompletionItem('foo')\n      let res = source.shouldCommit(item, '.')\n      expect(res).toBe(true)\n    })\n\n    it('should not feedkeys when already inserted before', async () => {\n      helper.updateConfiguration('suggest.acceptSuggestionOnCommitCharacter', true, disposables)\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (_doc, pos): Promise<CompletionItem[]> => [{\n          label: 'foo',\n          textEdit: TextEdit.replace(Range.create(pos.line, pos.character, pos.line, pos.character + 1), `foo($1)$0`),\n          insertTextFormat: InsertTextFormat.Snippet,\n          commitCharacters: ['(']\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('language', 'l', ['*'], provider))\n      await nvim.command('startinsert')\n      nvim.call('coc#start', [{ source: 'language' }], true)\n      await helper.waitPopup()\n      expect(completion.selectedItem).toBeDefined()\n      await nvim.input('(')\n      await helper.waitValue(() => completion.isActivated, false)\n    })\n\n    it('should not feedkeys when have paried characters before', async () => {\n      helper.updateConfiguration('suggest.acceptSuggestionOnCommitCharacter', true, disposables)\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (_doc, pos): Promise<CompletionItem[]> => [{\n          label: 'foo',\n          textEdit: TextEdit.replace(Range.create(pos.line, pos.character, pos.line, pos.character + 1), `foo()$0`),\n          insertTextFormat: InsertTextFormat.Snippet,\n          commitCharacters: ['(']\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('language', 'l', ['*'], provider))\n      await nvim.call('cursor', [1, 1])\n      await nvim.command('startinsert')\n      await nvim.setLine('')\n      nvim.call('coc#start', [{ source: 'language' }], true)\n      await helper.waitPopup()\n      expect(completion.selectedItem).toBeDefined()\n      await nvim.input('()<left>')\n      await helper.waitFor('getline', ['.'], 'foo()')\n    })\n  })\n\n  describe('resolveCompletionItem()', () => {\n    async function getDetailContent(): Promise<string | undefined> {\n      let winid = await nvim.call('coc#float#get_float_by_kind', ['pumdetail'])\n      if (!winid) return\n      let bufnr = await nvim.call('winbufnr', [winid]) as number\n      let lines = await (nvim.createBuffer(bufnr)).lines\n      return lines.join('\\n')\n    }\n\n    it('should return null when canceled or no items returned', async () => {\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => []\n      }\n      disposables.push(languages.registerCompletionItemProvider('foo', 'f', null, provider, [], 3, ['.']))\n      let source = sources.getSource('foo')\n      let opt = await nvim.call('coc#util#get_complete_option') as CompleteOption\n      let res = await source.doComplete(opt, CancellationToken.Cancelled)\n      expect(res).toBeNull()\n      res = await source.doComplete(opt, CancellationToken.None)\n      expect(res).toBeNull()\n    })\n\n    it('should add detail to preview when no resolve exists', async () => {\n      await helper.createDocument('foo.vim')\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: 'foo',\n          detail: 'detail of foo'\n        }, {\n          label: 'bar',\n          detail: 'bar()'\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('foo', 'f', 'vim', provider))\n      let mode = await nvim.mode\n      if (mode.mode !== 'i') {\n        await nvim.input('i')\n      }\n      nvim.call('coc#start', [{ source: 'foo' }], true)\n      await helper.waitPopup()\n      await helper.waitValue(async () => {\n        let content = await getDetailContent()\n        return content && /foo/.test(content)\n      }, true)\n      await nvim.input('<C-n>')\n      await helper.waitValue(async () => {\n        let content = await getDetailContent()\n        return content && /bar/.test(content)\n      }, true)\n    })\n\n    it('should add documentation to preview when no resolve exists', async () => {\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: 'foo',\n          labelDetails: {},\n          documentation: 'detail of foo'\n        }, {\n          label: 'bar',\n          documentation: {\n            kind: 'plaintext',\n            value: 'bar'\n          }\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('foo', 'f', null, provider))\n      await nvim.input('i')\n      await nvim.call('coc#start', { source: 'foo' })\n      await helper.waitPopup()\n      await helper.wait(10)\n      let content = await getDetailContent()\n      expect(content).toMatch('foo')\n      await nvim.input('<C-n>')\n      await helper.wait(30)\n      content = await getDetailContent()\n      expect(content).toMatch('bar')\n    })\n\n    it('should resolve again when request cancelled', async () => {\n      let count = 0\n      let cancelled = false\n      let resolved = false\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{ label: 'bar' }],\n        resolveCompletionItem: (item, token) => {\n          if (count === 0) {\n            count++\n            return new Promise(resolve => {\n              token.onCancellationRequested(() => {\n                cancelled = true\n                resolve(undefined)\n              })\n            })\n          }\n          resolved = true\n          return item\n        },\n      }\n      disposables.push(languages.registerCompletionItemProvider('foo', 'f', null, provider))\n      await nvim.input('i')\n      await nvim.call('coc#start', { source: 'foo' })\n      await helper.waitPopup()\n      await helper.waitValue(() => {\n        return cancelled\n      }, true)\n      nvim.call('coc#pum#close', ['confirm'], true)\n      await helper.waitValue(() => {\n        return resolved\n      }, true)\n    })\n\n    it('should resolve CompletionItem', async () => {\n      let res: CompletionItem | Error | undefined\n      let n = 0\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: 'this',\n          documentation: 'detail of this'\n        }],\n        resolveCompletionItem: item => {\n          if (res instanceof Error) {\n            throw res\n          } else {\n            n++\n            return res\n          }\n        }\n      }\n      disposables.push(languages.registerCompletionItemProvider('foo', 'f', null, provider))\n      let opt = await nvim.call('coc#util#get_complete_option') as CompleteOption\n      let source = sources.getSource('foo')\n      await source.doComplete(opt, CancellationToken.None)\n      let item = createCompletionItem('this')\n      await source.onCompleteResolve(item, opt, CancellationToken.None)\n      res = { label: 'this', textEdit: TextEdit.insert(Position.create(0, 0), 'this') }\n      let p = n\n      await source.onCompleteResolve(item, opt, CancellationToken.None)\n      await source.onCompleteResolve(item, opt, CancellationToken.None)\n      expect(n - p).toBe(1)\n      res = new Error('resolve error')\n      item = createCompletionItem('this')\n      await expect(async () => {\n        await source.onCompleteResolve(item, opt, CancellationToken.None)\n      }).rejects.toThrow(Error)\n    })\n  })\n\n  describe('command', () => {\n    it('should invoke command', async () => {\n      let id = 'test.command'\n      let item: CompletionItem = {\n        label: 'this',\n        command: {\n          command: id,\n          title: id,\n          arguments: []\n        }\n      }\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [item]\n      }\n      disposables.push(languages.registerCompletionItemProvider('foo', 'f', null, provider))\n      let opt = await nvim.call('coc#util#get_complete_option') as any\n      opt.snippetsSupport = false\n      opt.insertMode = InsertMode.Insert\n      let source = sources.getSource('foo')\n      await source.doComplete(opt, CancellationToken.None)\n      await source.onCompleteDone(item, opt)\n      let called = false\n      commandManager.registerCommand(id, () => {\n        called = true\n      })\n      await source.onCompleteDone(item, opt)\n      expect(called).toBe(true)\n    })\n  })\n\n  describe('labelDetails', () => {\n    it('should show labelDetails to documentation window', async () => {\n      helper.updateConfiguration('suggest.labelMaxLength', 10, disposables)\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: 'foo',\n          labelDetails: {\n            detail: 'foo'.repeat(5)\n          }\n        }, {\n          label: 'bar',\n          labelDetails: {\n            description: 'bar'.repeat(5)\n          }\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider))\n      await nvim.input('i')\n      await nvim.call('coc#start', { source: 'edits' })\n      let winid: number\n      await helper.waitValue(async () => {\n        winid = await nvim.call('coc#float#get_float_by_kind', ['pumdetail']) as number\n        return winid > 0\n      }, true)\n      let lines = await helper.getWinLines(winid)\n      expect(lines[0]).toMatch('foo')\n      await nvim.call('coc#pum#_navigate', [1, 1])\n      await helper.waitValue(async () => {\n        lines = await helper.getWinLines(winid)\n        return lines.join(' ').includes('bar')\n      }, true)\n    })\n  })\n\n  describe('additionalTextEdits', () => {\n    it('should fix cursor position with plain text on additionalTextEdits', async () => {\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: 'foo',\n          filterText: 'foo',\n          additionalTextEdits: [TextEdit.insert(Position.create(0, 0), 'a\\nbar')]\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider))\n      await nvim.input('if')\n      await helper.waitPopup()\n      await helper.confirmCompletion(0)\n      await helper.waitFor('getline', ['.'], 'barfoo')\n      let col = await nvim.call('col', ['.'])\n      expect(col).toBe(7)\n    })\n\n    it('should fix cursor position with snippet on additionalTextEdits', async () => {\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: 'if',\n          insertTextFormat: InsertTextFormat.Snippet,\n          textEdit: { range: Range.create(0, 0, 0, 1), newText: 'if($1)' },\n          additionalTextEdits: [TextEdit.insert(Position.create(0, 0), 'bar ')],\n          preselect: true\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider))\n      await nvim.input('ii')\n      await helper.waitPopup()\n      let res = await helper.items()\n      let idx = res.findIndex(o => o.source?.name == 'edits')\n      await helper.confirmCompletion(idx)\n      await helper.waitFor('col', ['.'], 8)\n    })\n\n    it('should fix cursor position with plain text snippet on additionalTextEdits', async () => {\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: 'if',\n          filterText: 'if',\n          insertTextFormat: InsertTextFormat.Snippet,\n          textEdit: { range: Range.create(0, 0, 0, 2), newText: 'do$0' },\n          additionalTextEdits: [TextEdit.insert(Position.create(0, 0), 'bar ')],\n          preselect: true\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider))\n      await nvim.input('iif')\n      await helper.waitPopup()\n      let items = await helper.items()\n      let idx = items.findIndex(o => o.word == 'do' && o.source?.name == 'edits')\n      await helper.confirmCompletion(idx)\n      await helper.waitFor('getline', ['.'], 'bar do')\n      await helper.waitFor('col', ['.'], 7)\n    })\n\n    it('should fix cursor position with nested snippet on additionalTextEdits', async () => {\n      let pos = await window.getCursorPosition()\n      let range = Range.create(pos, pos)\n      let res = await commandManager.executeCommand('editor.action.insertSnippet', TextEdit.replace(range, 'func($1)$0'))\n      expect(res).toBe(true)\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: 'if',\n          filterText: 'if',\n          insertTextFormat: InsertTextFormat.Snippet,\n          insertText: 'do$0',\n          additionalTextEdits: [TextEdit.insert(Position.create(0, 0), 'bar ')],\n          preselect: true\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider))\n      await nvim.input('if')\n      await helper.waitPopup()\n      await helper.confirmCompletion(0)\n      await events.race(['CompleteDone'], 200)\n      let [, lnum, col] = await nvim.call('getcurpos') as [number, number, number]\n      expect(lnum).toBe(1)\n      expect(col).toBe(12)\n    })\n\n    it('should fix cursor position and keep placeholder with snippet on additionalTextEdits', async () => {\n      let text = 'foo0bar1'\n      await nvim.setLine(text)\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: 'var',\n          insertTextFormat: InsertTextFormat.Snippet,\n          textEdit: { range: Range.create(0, text.length + 1, 0, text.length + 1), newText: '${1:foo} = foo0bar1' },\n          additionalTextEdits: [TextEdit.del(Range.create(0, 0, 0, text.length + 1))],\n          preselect: true\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider, ['.']))\n      await nvim.input('A.')\n      await helper.waitPopup()\n      let res = await helper.items()\n      let idx = res.findIndex(o => o.source?.name == 'edits')\n      await helper.confirmCompletion(idx)\n      await helper.waitFor('getline', ['.'], 'foo = foo0bar1')\n      await helper.wait(50)\n      expect(snippetManager.session).toBeDefined()\n      let [, lnum, col] = await nvim.call('getcurpos') as [number, number, number]\n      expect(lnum).toBe(1)\n      expect(col).toBe(3)\n    })\n\n    it('should not cancel current snippet session when additionalTextEdits inside snippet', async () => {\n      await nvim.input('i')\n      snippetManager.cancel()\n      let pos = await window.getCursorPosition()\n      let range = Range.create(pos, pos)\n      await commandManager.executeCommand('editor.action.insertSnippet', TextEdit.replace(range, 'foo($1, $2)$0'), true)\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: 'bar',\n          insertTextFormat: InsertTextFormat.Snippet,\n          textEdit: { range: Range.create(0, 4, 0, 5), newText: 'bar($1)' },\n          additionalTextEdits: [TextEdit.del(Range.create(0, 0, 0, 3))]\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider, ['.']))\n      await nvim.input('b')\n      await helper.waitPopup()\n      let res = await helper.items()\n      let idx = res.findIndex(o => o.source?.name == 'edits')\n      await helper.confirmCompletion(idx)\n      await helper.waitFor('getline', ['.'], '(bar(), )')\n      await helper.waitFor('col', ['.'], 6)\n    })\n  })\n\n  describe('filterText', () => {\n    it('should fix input for snippet item', async () => {\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: 'foo',\n          filterText: 'foo',\n          insertText: '${1:foo}($2)',\n          insertTextFormat: InsertTextFormat.Snippet,\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('snippets-test', 'st', null, provider))\n      await nvim.input('if')\n      await helper.waitPopup()\n      await nvim.call('coc#pum#select', [0, 1, 0])\n      await helper.waitFor('getline', ['.'], 'foo()')\n    })\n\n    it('should fix filterText of complete item', async () => {\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: 'name',\n          sortText: '11',\n          textEdit: {\n            range: Range.create(0, 1, 0, 2),\n            newText: '?.name'\n          }\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('name', 'N', null, provider, ['.']))\n      await nvim.setLine('t')\n      await nvim.input('A.')\n      await helper.waitPopup()\n      await helper.confirmCompletion(0)\n      let line = await nvim.line\n      expect(line).toBe('t?.name')\n    })\n  })\n\n  describe('inComplete result', () => {\n    it('should filter in complete request', async () => {\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (doc, pos, token, context): Promise<CompletionList> => {\n          let option = (context as any).option\n          if (context.triggerCharacter == '.') {\n            return {\n              isIncomplete: true,\n              items: [\n                {\n                  label: 'foo'\n                }, {\n                  label: 'bar'\n                }\n              ]\n            }\n          }\n          if (option.input == 'f') {\n            if (token.isCancellationRequested) return\n            return {\n              isIncomplete: true,\n              items: [\n                {\n                  label: 'foo'\n                }\n              ]\n            }\n          }\n          if (option.input == 'fo') {\n            if (token.isCancellationRequested) return\n            return {\n              isIncomplete: false,\n              items: [\n                {\n                  label: 'foo'\n                }\n              ]\n            }\n          }\n        }\n      }\n      disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider, ['.']))\n      await nvim.input('i.')\n      await helper.waitPopup()\n      await nvim.input('fo')\n      await helper.waitValue(async () => {\n        let items = await helper.items()\n        return items.length\n      }, 1)\n    })\n  })\n\n  describe('itemDefaults', () => {\n    async function start(item: CompletionItem, itemDefaults: ItemDefaults, triggerCharacters: string[] = []): Promise<void> {\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionList> => {\n          return { items: [item], itemDefaults, isIncomplete: false }\n        }\n      }\n      disposables.push(languages.registerCompletionItemProvider('test', 't', null, provider, triggerCharacters))\n      await nvim.input('i')\n      nvim.call('coc#start', [{ source: 'test' }], true)\n      await helper.waitPopup()\n    }\n\n    it('should use range of editRange from itemDefaults', async () => {\n      await nvim.call('setline', ['.', 'bar'])\n      await start({ label: 'foo' }, {\n        editRange: Range.create(0, 0, 0, 3)\n      })\n      await helper.confirmCompletion(0)\n      await helper.waitFor('getline', ['.'], 'foo')\n    })\n\n    it('should use commitCharacters from itemDefaults', async () => {\n      let dispose = helper.updateConfiguration('suggest.acceptSuggestionOnCommitCharacter', true)\n      await start({ label: 'foo' }, { commitCharacters: ['.'] }, ['.'])\n      await nvim.input('.')\n      // should trigger after commit\n      await helper.waitFor('getline', ['.'], 'foo.')\n      expect(events.completing).toBe(true)\n      completion.cancelAndClose()\n      dispose()\n    })\n\n    it('should use replace range of editRange from itemDefaults', async () => {\n      await nvim.call('setline', ['.', 'bar'])\n      await start({ label: 'foo' }, {\n        editRange: {\n          insert: Range.create(0, 0, 0, 0),\n          replace: Range.create(0, 0, 0, 3),\n        }\n      })\n      await helper.confirmCompletion(0)\n      await helper.waitFor('getline', ['.'], 'foo')\n    })\n\n    it('should use insertTextFormat from itemDefaults', async () => {\n      await nvim.call('cursor', [1, 1])\n      await start({ label: 'foo', insertText: 'foo($1)$0' }, {\n        insertTextFormat: InsertTextFormat.Snippet,\n        insertTextMode: InsertTextMode.asIs,\n        data: {}\n      })\n      await helper.confirmCompletion(0)\n      await helper.waitValue(async () => {\n        let line = await nvim.call('getline', ['.']) as string\n        return line.startsWith('foo()')\n      }, true)\n    })\n\n    it('should use textEditText when exists with default range', async () => {\n      await start({ label: 'foo', insertText: 'bar', textEditText: 'foofoo' }, {\n        editRange: Range.create(0, 0, 0, 0)\n      })\n      await helper.confirmCompletion(0)\n      await helper.waitFor('getline', ['.'], 'foofoo')\n    })\n  })\n\n  describe('textEdit', () => {\n    it('should not apply edits when line changed', async () => {\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: 'foo',\n          textEdit: TextEdit.insert(Position.create(0, 0), 'foo($1)'),\n          insertTextFormat: InsertTextFormat.Snippet\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('foo', 'f', null, provider))\n      let source = sources.getSource('foo')\n      expect(source).toBeDefined()\n      let opt = await nvim.call('coc#util#get_complete_option') as any\n      await source.doComplete(opt, CancellationToken.None)\n      let item = createCompletionItem('foo')\n      await nvim.call('append', [0, ['', '']])\n      await nvim.command('normal! G')\n      await source.onCompleteDone(item, opt)\n      let line = await nvim.line\n      expect(line).toBe('')\n    })\n\n    it('should use insert range', async () => {\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: 'foo',\n          insertText: 'foo'\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('foo', 'f', null, provider))\n      let source = sources.getSource('foo')\n      expect(source).toBeDefined()\n      await nvim.setLine('foo')\n      await nvim.input('I')\n      let opt = await nvim.call('coc#util#get_complete_option') as any\n      opt.insertMode = InsertMode.Insert\n      await source.doComplete(opt, CancellationToken.None)\n      let item = createCompletionItem('foo')\n      await source.onCompleteDone(item, opt)\n      let line = await nvim.line\n      expect(line).toBe('foofoo')\n    })\n\n    it('should fix replace range for paired characters', async () => {\n      // LS may failed to replace paired character at the end\n      await nvim.setLine('<>')\n      await nvim.input('i<right>')\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: '<foo>',\n          filterText: '<foo>',\n          // bad range\n          textEdit: { range: Range.create(0, 0, 0, 0), newText: '<foo>' },\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider))\n      nvim.call('coc#start', [{ source: 'edits' }], true)\n      await helper.waitPopup()\n      let idx = completion.activeItems.findIndex(o => o.word == '<foo>')\n      expect(idx).toBeGreaterThan(-1)\n      await helper.confirmCompletion(idx)\n      await helper.waitFor('getline', ['.'], '<foo>')\n    })\n\n    it('should not eat existing paired character on valid range', async () => {\n      await nvim.setLine('fn bar() {}')\n      await nvim.call('cursor', [1, 7])\n      await nvim.input('a')\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (_, position): Promise<CompletionItem[]> => [{\n          label: '(x, y): (i32, i32)',\n          filterText: '(x, y): (i32, i32)',\n          textEdit: { range: Range.create(position.line, position.character, position.line, position.character), newText: '(x, y): (i32, i32)' },\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider))\n      let source = sources.getSource('edits')\n      expect(source).toBeDefined()\n      let opt = await nvim.call('coc#util#get_complete_option') as any\n      await source.doComplete(opt, CancellationToken.None)\n      await source.onCompleteDone({\n        label: '(x, y): (i32, i32)',\n        filterText: '(x, y): (i32, i32)',\n        textEdit: { range: Range.create(0, 7, 0, 7), newText: '(x, y): (i32, i32)' },\n      }, opt)\n      await helper.waitFor('getline', ['.'], 'fn bar((x, y): (i32, i32)) {}')\n    })\n\n    it('should fix bad range', async () => {\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: 'foo',\n          filterText: 'foo',\n          textEdit: { range: Range.create(0, 0, 0, 0), newText: 'foo' },\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider))\n      await nvim.input('i')\n      nvim.call('coc#start', [{ source: 'edits' }], true)\n      await helper.waitPopup()\n      await helper.confirmCompletion(0)\n      await helper.waitFor('getline', ['.'], 'foo')\n    })\n\n    it('should applyEdits for empty word', async () => {\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: '',\n          filterText: '!',\n          textEdit: { range: Range.create(0, 0, 0, 1), newText: 'foo' },\n          data: { word: '' }\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider, ['!']))\n      await nvim.input('i!')\n      await helper.waitPopup()\n      await helper.confirmCompletion(0)\n      await helper.waitFor('getline', ['.'], 'foo')\n    }, 10000)\n\n    it('should provide word when textEdit after startcol', async () => {\n      // some LS would send textEdit after first character,\n      // need fix the word from newText\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (_, position): Promise<CompletionItem[]> => {\n          if (position.line != 0) return null\n          return [{\n            label: 'bar',\n            textEdit: {\n              range: Range.create(0, 1, 0, 1),\n              newText: 'bar'\n            }\n          }, {\n            label: 'bad',\n            textEdit: {\n              replace: Range.create(0, 1, 0, 1),\n              insert: Range.create(0, 1, 0, 1),\n              newText: 'bad'\n            }\n          }]\n        }\n      }\n      disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider))\n      await nvim.input('ib')\n      await helper.waitPopup()\n      let items = completion.activeItems\n      expect(items[0].word).toBe('bar')\n    }, 10000)\n\n    it('should adjust completion position by textEdit start position', async () => {\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (_document, _position, _token, context): Promise<CompletionItem[]> => {\n          if (!context.triggerCharacter) return\n          return [{\n            label: 'foo',\n            textEdit: {\n              range: Range.create(0, 0, 0, 1),\n              newText: '?foo'\n            }\n          }]\n        }\n      }\n      disposables.push(languages.registerCompletionItemProvider('fix', 'f', null, provider, ['?']))\n      await nvim.input('i?')\n      await helper.waitPopup()\n      await helper.confirmCompletion(0)\n      let line = await nvim.line\n      expect(line).toBe('?foo')\n    }, 10000)\n\n    it('should fix range of removed text range', async () => {\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => {\n          return [{\n            label: 'React',\n            textEdit: {\n              range: Range.create(0, 0, 0, 8),\n              newText: 'import React$1 from \"react\"'\n            },\n            insertTextFormat: InsertTextFormat.Snippet\n          }]\n        }\n      }\n      disposables.push(languages.registerCompletionItemProvider('fix', 'f', null, provider, ['?']))\n      await nvim.call('setline', ['.', 'import r;'])\n      await nvim.call('cursor', [1, 8])\n      await nvim.input('a')\n      await nvim.call('coc#start', { source: 'fix' })\n      await helper.waitPopup()\n      await helper.confirmCompletion(0)\n      await helper.waitFor('getline', ['.'], 'import React from \"react\";')\n    }, 10000)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/completion/sources.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport { CancellationToken, CancellationTokenSource, Disposable } from 'vscode-languageserver-protocol'\nimport { Position, Range, TextEdit } from 'vscode-languageserver-types'\nimport { Around } from '../../completion/native/around'\nimport { Buffer } from '../../completion/native/buffer'\nimport { File, filterFiles, getDirectory, getFileItem, getItemsFromRoot, getLastPart, resolveEnvVariables } from '../../completion/native/file'\nimport Source, { firstMatchFuzzy } from '../../completion/source'\nimport VimSource, { checkInclude, getMethodName } from '../../completion/source-vim'\nimport sources, { Sources, getSourceType, logError } from '../../completion/sources'\nimport { CompleteOption, ExtendedCompleteItem, SourceConfig, SourceType } from '../../completion/types'\nimport extensions from '../../extension'\nimport { WordsSource } from '../../snippets/util'\nimport { disposeAll } from '../../util'\nimport workspace from '../../workspace'\nimport helper, { createTmpFile } from '../helper'\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nconst emptyFn = () => Promise.resolve(null)\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n})\n\ndescribe('KeywordsBuffer', () => {\n  it('should parse keywords', async () => {\n    let filepath = await createTmpFile(' ab\\nab')\n    let doc = await helper.createDocument(filepath)\n    let b = sources.getKeywordsBuffer(doc.bufnr)\n    let words = b.getWords()\n    expect(words).toEqual(['ab'])\n    await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo\\nbar')])\n    words = b.getWords()\n    expect(words).toEqual(['foo', 'bar', 'ab'])\n    await doc.applyEdits([TextEdit.replace(Range.create(0, 0, 1, 3), 'def ')])\n    words = b.getWords()\n    expect(words).toEqual(['def', 'ab'])\n  })\n\n  it('should yield match words', async () => {\n    let filepath = await createTmpFile(`_foo\\nbar\\n`)\n    let doc = await helper.createDocument(filepath)\n    let b = sources.getKeywordsBuffer(doc.bufnr)\n    const getResults = (iterable: Iterable<string>) => {\n      let res: string[] = []\n      for (let word of iterable) {\n        res.push(word)\n      }\n      return res\n    }\n    let iterable = b.matchWords(0)\n    expect(getResults(iterable)).toEqual(['_foo', 'bar'])\n    iterable = b.matchWords(2)\n    expect(getResults(iterable)).toEqual(['_foo', 'bar'])\n  })\n})\n\ndescribe('Source', () => {\n  function createSource(opt: SourceConfig): Source {\n    let s = new Source(opt)\n    disposables.push(s)\n    return s\n  }\n\n  function makeid(length) {\n    let result = ''\n    let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'\n    let charactersLength = characters.length\n    for (let i = 0; i < length; i++) {\n      result += characters.charAt(Math.floor(Math.random() *\n        charactersLength))\n    }\n    return result\n  }\n\n  it('should check trigger only source', async () => {\n    expect(typeof Sources).toBe('function')\n    logError('')\n    let name = 'foo'\n    let s = createSource({ name, triggerOnly: true, doComplete: emptyFn })\n    expect(s.triggerOnly).toBe(true)\n    expect(s.triggerPatterns).toBeNull()\n    s = createSource({ name, doComplete: emptyFn })\n    helper.updateConfiguration(`coc.source.${name}.triggerPatterns`, [null, 'foo'])\n    expect(s.triggerOnly).toBe(true)\n  })\n\n  it('should get source type', async () => {\n    for (let t of [SourceType.Native, SourceType.Remote, SourceType.Service]) {\n      expect(getSourceType(t)).toBeDefined()\n    }\n  })\n\n  it('should check complete', async () => {\n    let name = 'foo'\n    let s = createSource({ name, doComplete: emptyFn })\n    helper.updateConfiguration(`coc.source.${name}.disableSyntaxes`, ['comment'])\n    await nvim.input('i')\n    let opt = await nvim.call('coc#util#get_complete_option') as CompleteOption\n    opt.synname = 'Comment'\n    expect(await s.checkComplete(opt)).toBe(false)\n    let result = await s.doComplete(opt, CancellationToken.None)\n    expect(result).toBeNull()\n    opt.synname = 'String'\n    expect(await s.checkComplete(opt)).toBe(true)\n    opt.synname = ''\n    expect(await s.checkComplete(opt)).toBe(true)\n    s = createSource({\n      name, shouldComplete: () => {\n        return Promise.resolve(false)\n      },\n      doComplete: emptyFn\n    })\n    expect(await s.checkComplete(opt)).toBe(false)\n  })\n\n  it('should call optional functions', async () => {\n    await nvim.input('i')\n    let opt = await nvim.call('coc#util#get_complete_option') as CompleteOption\n    let name = 'foo'\n    let n = 0\n    let s = createSource({\n      name,\n      doComplete: emptyFn,\n      refresh: () => {\n        n++\n        return Promise.resolve()\n      },\n      onCompleteDone: () => {\n        n++\n        return Promise.resolve()\n      },\n      onCompleteResolve: () => {\n        n++\n        return Promise.resolve()\n      }\n    })\n    // expect(s.optionalFns).toEqual([])\n    await s.refresh()\n    await s.onCompleteDone({} as any, opt)\n    await s.doComplete(opt, CancellationToken.None)\n    await s.onCompleteResolve({} as any, opt, CancellationToken.None)\n    expect(n).toBe(3)\n  })\n\n  it('should get results', async () => {\n    let name = 'foo'\n    let s = createSource({ name, doComplete: emptyFn })\n    let words = []\n    for (let i = 0; i < 80000; i++) {\n      words.push(makeid(10))\n    }\n    let items: Set<string> = new Set()\n    let tokenSource = new CancellationTokenSource()\n    let p = s.getResults([words], '_$c', '', items, tokenSource.token)\n    tokenSource.cancel()\n    let res = await p\n    expect(res).toBe(true)\n    let n = Date.now()\n    p = s.getResults([words], '_$a', '', items, CancellationToken.None)\n    let spy = jest.spyOn(Date, 'now').mockImplementation(() => {\n      return n + 200\n    })\n    res = await p\n    spy.mockRestore()\n    words = []\n    for (let i = 0; i < 300; i++) {\n      words.push('a' + makeid(10))\n    }\n    items = new Set()\n    res = await s.getResults([words], 'a', '', items, CancellationToken.None)\n    expect(items.size).toBe(50)\n    items = new Set()\n    res = await s.getResults([['你好']], 'ni', '', items, CancellationToken.None)\n    expect(items.size).toBe(1)\n  })\n})\n\ndescribe('vim source', () => {\n  function createSourceFile(name: string, content: string): string {\n    let dir = path.join(os.tmpdir(), `coc/source`)\n    fs.mkdirSync(dir, { recursive: true })\n    let filepath = path.join(dir, `${name}.vim`)\n    fs.writeFileSync(filepath, content, 'utf8')\n    return filepath\n  }\n\n  it('should not throw when pluginPath already used', async () => {\n    await sources.createVimSources(process.cwd())\n    await sources.createVimSources(process.cwd())\n  })\n\n  it('should show error for bad source file', async () => {\n    let filepath = createSourceFile('tmp', '')\n    await sources.createVimSourceExtension(filepath)\n    let line = await helper.getCmdline()\n    expect(line).toMatch('Error')\n  })\n\n  it('should register filetypes extension for vim source', async () => {\n    let content = `\nfunction! coc#source#foo#init()\n  return {'filetypes': ['vim'], 'firstMatch': v:true}\nendfunction\nfunction! coc#source#foo#complete(opt, cb) abort\n  call a:cb([])\nendfunction `\n    let filepath = createSourceFile('foo', content)\n    await sources.createVimSourceExtension(filepath)\n    let ext = extensions.getExtension('coc-vim-source-foo')\n    expect(ext).toBeDefined()\n    await Promise.resolve(ext.deactivate())\n  })\n\n  it('should not run by check complete', async () => {\n    let opt = await nvim.call('coc#util#get_complete_option') as CompleteOption\n    let source = new VimSource({\n      name: 'vim',\n      sourceType: SourceType.Remote,\n      remoteFns: ['on_complete', 'on_enter']\n    })\n    helper.updateConfiguration('coc.source.vim.disableSyntaxes', ['comment'])\n    helper.updateConfiguration('coc.source.vim.filetypes', ['vim'])\n    opt.synname = 'VimComment'\n    opt.filetype = 'vim'\n    let res = await source.checkComplete(opt)\n    expect(res).toBe(false)\n    let result = await source.doComplete(opt, CancellationToken.None)\n    expect(result).toBe(null)\n    opt.synname = ''\n    res = await source.checkComplete(opt)\n    expect(res).toBe(true)\n    result = await source.doComplete(opt, CancellationToken.Cancelled)\n    expect(result).toBe(null)\n    source.onEnter(999)\n    let bufnr = await nvim.call('bufnr', ['%']) as number\n    source.onEnter(bufnr)\n  })\n\n  it('should register extension for vim source', async () => {\n    let content = `\nfunction! coc#source#foo#init()\n  return {'firstMatch': v:true, 'isSnippet': v:true}\nendfunction\n\nfunction! coc#source#foo#on_enter(...)\n  let g:coc_entered = 1\nendfunction\n\nfunction! coc#source#foo#get_startcol(opt)\n  if a:opt['col'] == 1\n    return 0\n  endif\n  return a:opt['col']\nendfunction\n\nfunction! coc#source#foo#complete(opt, cb) abort\n  if a:opt['col'] == 0\n    call a:cb([{'word': '.f'}])\n    return\n  endif\n  call a:cb([])\nendfunction `\n    let filepath = createSourceFile('foo', content)\n    await sources.createVimSourceExtension(filepath)\n    let source = sources.getSource('foo')\n    expect(source).toBeDefined()\n    let bufnr = await nvim.call('bufnr', ['%']) as number\n    source.onEnter(bufnr)\n    let val = await nvim.getVar('coc_entered')\n    expect(val).toBe(1)\n    await nvim.setLine('.')\n    await nvim.input('A')\n    let opt = await nvim.call('coc#util#get_complete_option') as CompleteOption\n    let res = await source.doComplete(opt, CancellationToken.None)\n    expect(res.startcol).toBe(0)\n    expect(res.items).toEqual([{ word: '.f', isSnippet: true }])\n    opt.col = 2\n    res = await source.doComplete(opt, CancellationToken.None)\n    expect(res).toBe(null)\n  })\n\n  it('should not insert snippet when on_complete exists', async () => {\n    let opt = await nvim.call('coc#util#get_complete_option') as CompleteOption\n    let source = new VimSource({\n      name: 'vim',\n      sourceType: SourceType.Remote,\n      remoteFns: ['on_complete']\n    })\n    let item: ExtendedCompleteItem = {\n      word: 'word',\n      abbr: 'word',\n      filterText: 'word',\n      isSnippet: true,\n      insertText: 'word($1)'\n    }\n    let spy = jest.spyOn(nvim, 'call').mockImplementation(() => {\n      return undefined\n    })\n    await source.refresh()\n    await source.onCompleteDone(item, opt)\n    spy.mockRestore()\n    let line = await nvim.line\n    expect(line).toBe('')\n  })\n\n  it('should insert snippet', async () => {\n    let opt = await nvim.call('coc#util#get_complete_option') as CompleteOption\n    let source = new VimSource({\n      name: 'vim',\n      sourceType: SourceType.Remote\n    })\n    let item: ExtendedCompleteItem = {\n      word: 'word',\n      abbr: 'word',\n      filterText: 'word',\n      isSnippet: true,\n      insertText: 'word($1)'\n    }\n    await source.onCompleteDone(item, opt)\n    let line = await nvim.line\n    expect(line).toBe('word()')\n  })\n})\n\ndescribe('native sources', () => {\n  it('should not complete when buffer not exists', async () => {\n    let tokenSource = new CancellationTokenSource()\n    let source = sources.getSource('around')\n    let opt = await nvim.call('coc#util#get_complete_option') as CompleteOption\n    Object.assign(opt, { bufnr: -1, input: 'a' })\n    let res = await source.doComplete(opt, tokenSource.token)\n    expect(res).toBeNull()\n  })\n\n  it('should not complete when check failed', async () => {\n    let tokenSource = new CancellationTokenSource()\n    for (const name of ['around', 'buffer', 'file']) {\n      let source = sources.getSource(name)\n      let opt = await nvim.call('coc#util#get_complete_option') as CompleteOption\n      let spy = jest.spyOn(source, 'checkComplete' as any).mockReturnValue(Promise.resolve(false))\n      let res = await source.doComplete(opt, tokenSource.token)\n      spy.mockRestore()\n      expect(res).toBeNull()\n    }\n  })\n\n  it('should not complete with empty input', async () => {\n    for (const name of ['around', 'buffer']) {\n      let tokenSource = new CancellationTokenSource()\n      let source = sources.getSources({ source: name } as any)[0]\n      let opt = await nvim.call('coc#util#get_complete_option') as CompleteOption\n      let res = await source.doComplete(opt, tokenSource.token)\n      expect(res).toBeNull()\n    }\n  })\n\n  it('should not complete when cancelled', async () => {\n    let opt = await nvim.call('coc#util#get_complete_option') as CompleteOption\n    Object.assign(opt, { input: 'a' })\n    for (const name of ['around', 'buffer']) {\n      let source = sources.getSource(name)\n      let res = await source.doComplete(opt, CancellationToken.Cancelled)\n      expect(res).toBeNull()\n    }\n  })\n\n  it('should resolveEnvVariables', () => {\n    expect(resolveEnvVariables('%HOME%/data%x%', { HOME: '/home' })).toBe('/home/data%x%')\n    expect(resolveEnvVariables('$HOME/${USER}/data', { HOME: '/home', USER: 'foo' })).toBe('/home/foo/data')\n    expect(resolveEnvVariables('$PART/data', {})).toBe('$PART/data')\n  })\n\n  it('should getDirectory', () => {\n    expect(getDirectory('a/b', '/home')).toBe('/home/a')\n    expect(getDirectory(__dirname, '/home')).toBe(path.dirname(__dirname))\n  })\n\n  it('should getItemsFromRoot', async () => {\n    let res = await getItemsFromRoot('a/b', '/not_exists', true, [])\n    expect(res).toEqual([])\n  })\n\n  it('should getLastPart', () => {\n    expect(getLastPart('/a/b!/x/y')).toBe('/x/y')\n    expect(getLastPart('/a/b /x/y')).toBe('/x/y')\n    expect(getLastPart('xy /a/b\\\\ /x/y')).toBe('/a/b\\\\ /x/y')\n    expect(getLastPart('/a/b/x/y!')).toBeNull()\n    expect(getLastPart('x#/')).toBe('/')\n    expect(getLastPart('x /')).toBe('/')\n    expect(getLastPart('/')).toBe('/')\n  })\n\n  it('should getFileItem', async () => {\n    expect(await getFileItem(__dirname, '')).toBeDefined()\n    expect(await getFileItem(__dirname, 'file_not_exists')).toBeNull()\n    expect(await getFileItem(__dirname, path.basename(__filename))).toBeDefined()\n  })\n\n  it('should filterFiles', () => {\n    expect(filterFiles(['.a', '.b', null], false)).toEqual(['.a', '.b'])\n    expect(filterFiles(['a.js', 'b.ts'], true, ['*.js'])).toEqual(['b.ts'])\n  })\n\n  it('should getRoot', async () => {\n    let file = new File(false)\n    let filepath = __filename\n    let cwd = process.cwd()\n    let root = await file.getRoot('./a', '', '', cwd)\n    expect(root).toBe(cwd)\n    root = await file.getRoot('./a', '', filepath, cwd)\n    expect(root).toBe(path.dirname(filepath))\n    root = await file.getRoot('/a/b/', '', filepath, cwd)\n    expect(root).toBe('/a/b/')\n    root = await file.getRoot('/a/b', '', filepath, cwd)\n    expect(root).toBe('/a')\n    root = await file.getRoot('', 'a/b/not_exists', filepath, cwd)\n    expect(root).toBeUndefined()\n    let dir = path.dirname(__dirname)\n    let base = path.basename(__dirname)\n    root = await file.getRoot('', base, __dirname, cwd)\n    expect(root).toBe(dir)\n    root = await file.getRoot('', base, '/a/b', dir)\n    expect(root).toBe(dir)\n    root = await file.getRoot('', '', '', dir)\n    expect(root).toBe(dir)\n    file.isWindows = true\n    root = await file.getRoot('C:\\\\user', '', filepath, cwd)\n    expect(root).toBe('C:\\\\')\n    root = await file.getRoot('C:\\\\user\\\\', '', filepath, cwd)\n    expect(root).toBe('C:\\\\user\\\\')\n    let arr = file.triggerCharacters\n    expect(arr.includes('\\\\')).toBe(true)\n  })\n\n  it('should firstMatchFuzzy', async () => {\n    expect(firstMatchFuzzy(97, true, '_a')).toBe(true)\n    expect(firstMatchFuzzy(97, true, 'a')).toBe(true)\n    expect(firstMatchFuzzy(97, true, 'A')).toBe(true)\n    expect(firstMatchFuzzy(97, true, 'â')).toBe(true)\n    expect(firstMatchFuzzy(226, false, 'â')).toBe(true)\n  })\n\n  it('should works for around source', async () => {\n    let doc = await workspace.document\n    await nvim.setLine('foo ')\n    await doc.synchronize()\n    let { mode } = await nvim.mode\n    expect(mode).toBe('n')\n    await nvim.input('Af')\n    await helper.waitPopup()\n    let res = await helper.visible('foo', 'around')\n    expect(res).toBe(true)\n    await nvim.input('<esc>')\n  })\n\n  it('should works for buffer source', async () => {\n    await helper.createDocument()\n    await nvim.command('set hidden')\n    let doc = await helper.createDocument()\n    await nvim.setLine('other')\n    await nvim.command('bp')\n    await doc.synchronize()\n    let { mode } = await nvim.mode\n    expect(mode).toBe('n')\n    await nvim.input('io')\n    let res = await helper.visible('other', 'buffer')\n    expect(res).toBe(true)\n  })\n\n  it('should trigger for inComplete complete', async () => {\n    await nvim.setLine('foo')\n    await nvim.input('A')\n    let opt = await nvim.call('coc#util#get_complete_option') as CompleteOption\n    opt.triggerForInComplete = true\n    let around = new Around(sources.keywords)\n    let res = await around.doComplete(opt, CancellationToken.None)\n    expect(res).toBeDefined()\n    let buffer = new Buffer(sources.keywords)\n    res = await buffer.doComplete(opt, CancellationToken.None)\n    expect(res).toBeDefined()\n  })\n\n  it('should fix col for file source', async () => {\n    await nvim.command(`edit t|setl iskeyword+=/`)\n    await nvim.setLine('./')\n    await nvim.input('A')\n    nvim.call('coc#start', { source: 'file' }, true)\n    await helper.waitPopup()\n  })\n\n  it('should trim ext for file source', async () => {\n    let cwd = path.resolve(__dirname, '..')\n    let file = path.join(cwd, 't.ts')\n    await helper.edit(file)\n    await nvim.setLine('./')\n    await nvim.input('A')\n    nvim.call('coc#start', { source: 'file' }, true)\n    await helper.waitPopup()\n    let items = helper.completion.activeItems\n    let idx = items.findIndex(o => o.word.endsWith('.ts'))\n    expect(idx).toBe(-1)\n  })\n\n  it('should not complete when cancelled', async () => {\n    await nvim.setLine('/foo')\n    await nvim.input('A')\n    let file = new File(false)\n    let tokenSource = new CancellationTokenSource()\n    let opt = await nvim.call('coc#util#get_complete_option') as CompleteOption\n    let p = file.doComplete(opt, tokenSource.token)\n    tokenSource.cancel()\n    let res = await p\n    expect(res).toBeNull()\n  })\n\n  it('should complete with words source', async () => {\n    let stats = sources.sourceStats()\n    let find = stats.find(o => o.name === '$words')\n    expect(find).toBeUndefined()\n    expect(WordsSource).toBeDefined()\n    let s = sources.getSource('$words') as WordsSource\n    expect(s.name).toBe('$words')\n    expect(s.shortcut).toBe('')\n    expect(s.triggerOnly).toBe(true)\n    s.words = ['foo', 'bar']\n    s.startcol = 1\n    await nvim.setLine('longwords')\n    await nvim.input('A')\n    nvim.call('coc#start', { source: '$words' }, true)\n    await helper.waitPopup()\n    let items = await helper.items()\n    expect(items.map(o => o.word)).toEqual(['foo', 'bar'])\n  })\n\n  it('should get method name', () => {\n    expect(getMethodName('f', ['f', 'o'])).toBe('f')\n    expect(getMethodName('foo', ['Foo', 'Bar'])).toBe('Foo')\n    expect(() => {\n      getMethodName('foo', ['Bar'])\n    }).toThrow()\n    expect(checkInclude('f', ['f', 'o'])).toBe(true)\n    expect(checkInclude('b', ['f', 'o'])).toBe(false)\n    expect(checkInclude('foo', ['Foo', 'Bar'])).toBe(true)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/completion/util.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationToken, CompletionItem, CompletionItemKind, CompletionItemTag, Disposable, InsertTextFormat, Position, Range, TextEdit } from 'vscode-languageserver-protocol'\nimport { sortItems } from '../../completion/complete'\nimport { caseScore, matchScore, matchScoreWithPositions } from '../../completion/match'\nimport sources from '../../completion/sources'\nimport { CompleteOption, InsertMode, ISource, SortMethod } from '../../completion/types'\nimport { checkIgnoreRegexps, Converter, ConvertOption, createKindMap, deltaCount, emptLabelDetails, getDetail, getDocumentations, getInput, getKindHighlight, getKindText, getPriority, getReplaceRange, getResumeInput, getWord, hasAction, highlightOffset, indentChanged, isWordCode, MruLoader, OptionForWord, Selection, shouldIndent, shouldStop, toCompleteDoneItem } from '../../completion/util'\nimport { WordDistance } from '../../completion/wordDistance'\nimport events, { InsertChange } from '../../events'\nimport languages from '../../languages'\nimport { Chars } from '../../model/chars'\nimport { disposeAll } from '../../util'\nimport { getCharCodes } from '../../util/fuzzy'\nimport workspace from '../../workspace'\nimport helper, { createTmpFile } from '../helper'\nlet disposables: Disposable[] = []\n\nlet nvim: Neovim\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(() => {\n  disposeAll(disposables)\n})\n\nfunction getSource(): ISource {\n  return sources.getSource('$words')\n}\n\ndescribe('util functions', () => {\n  it('should toCompleteDoneItem', async () => {\n    expect(toCompleteDoneItem(undefined, undefined)).toEqual({})\n  })\n\n  it('should getPriority', async () => {\n    expect(getPriority(getSource(), 5)).toBe(5)\n  })\n\n  it('should add documentation', () => {\n    let docs = getDocumentations({ label: 'word', detail: 'detail' }, '')\n    expect(docs).toEqual([{ filetype: 'txt', content: 'detail' }])\n    docs = getDocumentations({ label: 'word', documentation: { kind: 'plaintext', value: '' } }, '')\n    expect(docs).toEqual([])\n    docs = getDocumentations({ label: 'word', detail: 'detail' }, '', true)\n    expect(docs).toEqual([])\n    docs = getDocumentations({ label: 'word', detail: 'detail', documentation: { kind: 'markdown', value: 'markdown' } }, 'vim')\n    expect(docs.length).toBe(2)\n    docs = getDocumentations({ word: '' }, '', true)\n    expect(docs).toEqual([])\n    docs = getDocumentations({ word: '', documentation: [{ content: 'content', filetype: 'vim' }] }, '', true)\n    expect(docs).toEqual([{ content: 'content', filetype: 'vim' }])\n    docs = getDocumentations({ word: '', info: 'info' }, '', true)\n    expect(docs).toEqual([{ content: 'info', filetype: 'txt' }])\n  })\n\n  it('should get detail doc', () => {\n    let item: CompletionItem = { label: '', detail: 'detail', labelDetails: {} }\n    expect(getDetail(item, '')).toEqual({ filetype: 'txt', content: 'detail' })\n    item = { label: '', detail: 'detail', labelDetails: { detail: 'detail', description: 'desc' } }\n    expect(getDetail(item, '')).toEqual({ filetype: 'txt', content: 'detail desc' })\n    item = { label: '', detail: 'detail', labelDetails: { description: 'desc' } }\n    expect(getDetail(item, '')).toEqual({ filetype: 'txt', content: ' desc' })\n    item = { label: '', detail: 'detail', labelDetails: { detail: 'detail' } }\n    expect(getDetail(item, '')).toEqual({ filetype: 'txt', content: 'detail' })\n    item = { label: '', detail: 'detail()' }\n    expect(getDetail(item, 'vim')).toEqual({ filetype: 'vim', content: 'detail()' })\n  })\n\n  it('should get deltaCount', () => {\n    let base = { lnum: 1, col: 1, line: '', changedtick: 1, pre: '' }\n    let insert: InsertChange = Object.assign({ insertChar: 's' }, base)\n    expect(deltaCount(insert)).toBe(0)\n    insert = Object.assign({ insertChar: 's', insertChars: ['s'] }, base)\n    expect(deltaCount(insert)).toBe(0)\n    insert = Object.assign({ insertChar: 's', insertChars: ['s', 's'] }, base, { pre: 's' })\n    expect(deltaCount(insert)).toBe(0)\n    insert = Object.assign({ insertChar: '<', insertChars: ['<', '>'] }, base, { pre: '<', line: '<x' })\n    expect(deltaCount(insert)).toBe(0)\n    insert = Object.assign({ insertChar: '<', insertChars: ['<', '>'] }, base, { pre: '<', line: '<>' })\n    expect(deltaCount(insert)).toBe(1)\n  })\n\n  it('should get caseScore', () => {\n    expect(typeof caseScore(10, 10, 2)).toBe('number')\n  })\n\n  it('should check action', async () => {\n    expect(hasAction({ label: 'foo', additionalTextEdits: [] }, {})).toBe(false)\n    expect(hasAction({ label: 'foo', insertTextFormat: InsertTextFormat.Snippet }, {})).toBe(true)\n  })\n\n  it('should check indentChanged', () => {\n    expect(indentChanged(undefined, [1, 1, ''], '')).toBe(false)\n    expect(indentChanged({ word: 'foo' }, [1, 4, 'foo'], '  foo')).toBe(true)\n    expect(indentChanged({ word: 'foo' }, [1, 4, 'bar'], '  foo')).toBe(false)\n  })\n\n  it('should get highlight offset', () => {\n    let n = highlightOffset(3, { abbr: 'abc', filterText: 'def' })\n    expect(n).toBe(-1)\n    expect(highlightOffset(3, { abbr: 'abc', filterText: 'abc' })).toBe(3)\n    expect(highlightOffset(3, { abbr: 'xy abc', filterText: 'abc' })).toBe(6)\n  })\n\n  it('should getKindText', () => {\n    expect(getKindText('t', new Map(), '')).toBe('t')\n    let m = new Map()\n    m.set(CompletionItemKind.Class, 'C')\n    expect(getKindText(CompletionItemKind.Class, m, 'D')).toBe('C')\n    expect(getKindText(CompletionItemKind.Class, new Map(), 'D')).toBe('D')\n  })\n\n  it('should getKindHighlight', async () => {\n    const testHi = (kind: number | string, res: string) => {\n      expect(getKindHighlight(kind)).toBe(res)\n    }\n    testHi(CompletionItemKind.Class, 'CocSymbolClass')\n    testHi(999, 'CocSymbolDefault')\n    testHi('', 'CocSymbolDefault')\n  })\n\n  it('should createKindMap', () => {\n    let map = createKindMap({ constructor: 'C' })\n    expect(map.get(CompletionItemKind.Constructor)).toBe('C')\n    map = createKindMap({ constructor: undefined })\n    expect(map.get(CompletionItemKind.Constructor)).toBe('')\n  })\n\n  it('should checkIgnoreRegexps', () => {\n    expect(checkIgnoreRegexps([], '')).toBe(false)\n    expect(checkIgnoreRegexps(['^^*^^'], 'input')).toBe(false)\n    expect(checkIgnoreRegexps(['^inp', '^ind'], 'input')).toBe(true)\n  })\n\n  it('should getResumeInput', () => {\n    let opt = { line: 'foo', colnr: 4, col: 1, position: { line: 0, character: 3 } }\n    expect(getResumeInput(opt, '')).toBeNull()\n    expect(getResumeInput(opt, 'f')).toBe('')\n    expect(getResumeInput(opt, 'bar')).toBeNull()\n    expect(getResumeInput(opt, 'foot')).toBe('oot')\n  })\n\n  function createOption(bufnr: number, linenr: number, line: string, col: number): Pick<CompleteOption, 'bufnr' | 'linenr' | 'line' | 'col'> {\n    return { bufnr, linenr, line, col }\n  }\n\n  it('should check stop', () => {\n    let opt = createOption(1, 1, 'a', 2)\n    expect(shouldStop(1, { line: '', col: 2, lnum: 1, changedtick: 1, pre: '' }, opt)).toBe(true)\n    expect(shouldStop(1, { line: '', col: 2, lnum: 1, changedtick: 1, pre: ' ' }, opt)).toBe(true)\n    expect(shouldStop(1, { line: '', col: 2, lnum: 1, changedtick: 1, pre: 'fo' }, opt)).toBe(true)\n    expect(shouldStop(2, { line: '', col: 2, lnum: 1, changedtick: 1, pre: 'foob' }, opt)).toBe(true)\n    expect(shouldStop(1, { line: '', col: 2, lnum: 2, changedtick: 1, pre: 'foob' }, opt)).toBe(true)\n    expect(shouldStop(1, { line: '', col: 2, lnum: 1, changedtick: 1, pre: 'barb' }, opt)).toBe(true)\n  })\n\n  it('should check indent', () => {\n    let res = shouldIndent('0{,0},0),0],!^F,o,O,e,=endif,=enddef,=endfu,=endfor', 'endfor')\n    expect(res).toBe(true)\n    res = shouldIndent('', 'endfor')\n    expect(res).toBe(false)\n    res = shouldIndent('0{,0},0),0],!^F,o,O,e,=endif,=enddef,=endfu,=endfor', 'foo bar')\n    expect(res).toBe(false)\n    res = shouldIndent('=~endif,=enddef,=endfu,=endfor', 'Endif')\n    expect(res).toBe(true)\n    res = shouldIndent(' ', '')\n    expect(res).toBe(false)\n    res = shouldIndent('*=endif', 'endif')\n    expect(res).toBe(false)\n    res = shouldIndent('0=foo', '  foo')\n    expect(res).toBe(true)\n  })\n\n  it('should check isWordCode', () => {\n    let chars = new Chars('@,_,#')\n    expect(isWordCode(chars, 97, true)).toBe(true)\n    expect(isWordCode(chars, 97, false)).toBe(true)\n    expect(isWordCode(chars, 10, false)).toBe(false)\n    expect(isWordCode(chars, 0xdc00, false)).toBe(false)\n    expect(isWordCode(chars, 20320, true)).toBe(false)\n  })\n\n  it('should consider none word character as input', () => {\n    let chars = new Chars('@,_,#')\n    let res = getInput(chars, 'a#b#', false)\n    expect(res).toBe('a#b#')\n    res = getInput(chars, '你b#', true)\n    expect(res).toBe('b#')\n    res = getInput(chars, '你b#', false)\n    expect(res).toBe('b#')\n  })\n\n  it('should check emptLabelDetails', () => {\n    expect(emptLabelDetails(null)).toBe(true)\n    expect(emptLabelDetails({})).toBe(true)\n    expect(emptLabelDetails({ detail: '' })).toBe(true)\n    expect(emptLabelDetails({ detail: 'detail' })).toBe(false)\n    expect(emptLabelDetails({ description: 'detail' })).toBe(false)\n  })\n\n  it('should get word from complete item', () => {\n    let item: CompletionItem = { label: 'foo', textEdit: TextEdit.insert(Position.create(0, 0), '$foo\\nbar') }\n    let word = getWord(item, {})\n    expect(word).toBe('$foo')\n    item = { label: 'foo', data: { word: '$foo' } }\n    word = getWord(item, {})\n    expect(word).toBe('$foo')\n    item = { label: 'foo', insertText: 'foo($1)' }\n    word = getWord(item, { insertTextFormat: InsertTextFormat.Snippet })\n    expect(word).toBe('foo()')\n    word = getWord(item, { insertTextFormat: InsertTextFormat.PlainText })\n    expect(word).toBe('foo($1)')\n    item = { label: 'foo' }\n    word = getWord(item, {})\n    expect(word).toBe('foo')\n    item = { label: 'foo', insertText: 'foo' }\n    word = getWord(item, { insertTextFormat: InsertTextFormat.Snippet })\n    expect(word).toBe('foo')\n    item = { label: 'foo', insertText: 'foo($1)', kind: CompletionItemKind.Function }\n    word = getWord(item, { insertTextFormat: InsertTextFormat.Snippet })\n    expect(word).toBe('foo')\n  })\n\n  it('should get replace range', () => {\n    let item: CompletionItem = { label: 'foo' }\n    expect(getReplaceRange(item, undefined)).toBeUndefined()\n    expect(getReplaceRange(item, undefined, 0)).toBeUndefined()\n    expect(getReplaceRange(item, Range.create(0, 0, 0, 3), 0)).toEqual(Range.create(0, 0, 0, 3))\n    expect(getReplaceRange(item, {\n      insert: Range.create(0, 0, 0, 0),\n      replace: Range.create(0, 0, 0, 3),\n    }\n      , 0)).toEqual(Range.create(0, 0, 0, 3))\n    expect(getReplaceRange(item, {\n      insert: Range.create(0, 0, 0, 0),\n      replace: Range.create(0, 0, 0, 3),\n    }\n      , 0, InsertMode.Insert)).toEqual(Range.create(0, 0, 0, 0))\n    item.textEdit = TextEdit.replace(Range.create(0, 0, 0, 3), 'foo')\n    expect(getReplaceRange(item, undefined, 0)).toEqual(Range.create(0, 0, 0, 3))\n    item.textEdit = {\n      newText: 'foo',\n      insert: Range.create(0, 0, 0, 0),\n      replace: Range.create(0, 0, 0, 3),\n    }\n    expect(getReplaceRange(item, undefined, 0)).toEqual(Range.create(0, 0, 0, 3))\n    item.textEdit = {\n      newText: 'foo',\n      insert: Range.create(0, 1, 0, 0),\n      replace: Range.create(0, 1, 0, 3),\n    }\n    expect(getReplaceRange(item, undefined, 0)).toEqual(Range.create(0, 0, 0, 3))\n  })\n\n  describe('Converter', () => {\n    function create(inputStart: number, option: ConvertOption, opt: OptionForWord): Converter {\n      return new Converter(inputStart, option, opt)\n    }\n\n    it('should get previous & after', () => {\n      let opt = {\n        line: '$foo',\n        col: 1,\n        position: Position.create(0, 1)\n      }\n      let option: ConvertOption = {\n        insertMode: InsertMode.Replace,\n        priority: 0,\n        range: Range.create(0, 1, 0, 4),\n        source: getSource(),\n      }\n      let c = create(1, option, opt)\n      expect(c.getPrevious(0)).toBe('$')\n      expect(c.getPrevious(0)).toBe('$')\n      expect(c.getAfter(4)).toBe('foo')\n      expect(c.getAfter(4)).toBe('foo')\n      expect(c.getAfter(2)).toBe('f')\n    })\n\n    it('should convert completion item', () => {\n      let opt = {\n        line: '',\n        position: Position.create(0, 0)\n      }\n      let option: ConvertOption = {\n        insertMode: InsertMode.Replace,\n        range: Range.create(0, 0, 0, 0),\n        priority: 0,\n        source: getSource(),\n      }\n      let item: any = {\n        label: 'f',\n        insertText: 'f',\n        score: 3,\n        data: { optional: true, dup: 0 },\n        tags: [CompletionItemTag.Deprecated]\n      }\n      let c = create(0, option, opt)\n      let res = c.convertToDurationItem(item)\n      expect(res.abbr.endsWith('?')).toBe(true)\n      expect(typeof res.sortText).toBe('string')\n      expect(res.deprecated).toBe(true)\n      expect(res.dup).toBe(false)\n    })\n\n    it('should replace word after cursor', () => {\n      let opt = {\n        line: 'afoo',\n        position: Position.create(0, 1)\n      }\n      let option: ConvertOption = {\n        insertMode: InsertMode.Replace,\n        range: Range.create(0, 1, 0, 1),\n        priority: 0,\n        source: getSource(),\n      }\n      let item: CompletionItem = {\n        label: 'afoo',\n        insertText: 'afoo',\n        textEdit: TextEdit.replace(Range.create(0, 0, 0, 4), 'afoo'),\n      }\n      let c = create(1, option, opt)\n      let res = c.convertToDurationItem(item)\n      expect(res.character).toBe(0)\n      expect(res.word).toBe('a')\n      item.textEdit = TextEdit.replace(Range.create(0, 1, 0, 4), 'foo')\n      item.labelDetails = { description: 'description' }\n      res = c.convertToDurationItem(item)\n      expect(res.character).toBe(1)\n      expect(res.labelDetails).toBeDefined()\n    })\n\n    it('should convert completion item', () => {\n      let opt = {\n        line: '@',\n        position: Position.create(0, 1)\n      }\n      let option: ConvertOption = {\n        range: Range.create(0, 0, 0, 1),\n        insertMode: InsertMode.Replace,\n        priority: 0,\n        asciiMatch: false,\n        source: getSource(),\n      }\n      let item: any = {\n        word: '@foo',\n        abbr: 'foo'\n      }\n      let c = create(1, option, opt)\n      let res = c.convertToDurationItem(item)\n      expect(res.filterText).toBe('@foo')\n      expect(res.delta).toBe(1)\n    })\n  })\n\n  describe('matchScore', () => {\n    function score(word: string, input: string): number {\n      return matchScore(word, getCharCodes(input))\n    }\n\n    it('should match score for last letter', () => {\n      expect(score('#!3', '3')).toBe(1)\n      expect(score('bar', 'f')).toBe(0)\n    })\n\n    it('should return 0 when not matched', () => {\n      expect(score('and', '你')).toBe(0)\n      expect(score('你and', '你的')).toBe(0)\n      expect(score('fooBar', 'Bt')).toBe(0)\n      expect(score('thisbar', 'tihc')).toBe(0)\n    })\n\n    it('should match first letter', () => {\n      expect(score('abc', '')).toBe(0)\n      expect(score('abc', 'a')).toBe(5)\n      expect(score('Abc', 'a')).toBe(2.5)\n      expect(score('__abc', 'a')).toBe(2)\n      expect(score('$Abc', 'a')).toBe(1)\n      expect(score('$Abc', 'A')).toBe(2)\n      expect(score('$Abc', '$A')).toBe(6)\n      expect(score('$Abc', '$a')).toBe(5.5)\n      expect(score('foo_bar', 'b')).toBe(2)\n      expect(score('foo_Bar', 'b')).toBe(1)\n      expect(score('_foo_Bar', 'b')).toBe(0.5)\n      expect(score('_foo_Bar', 'f')).toBe(2)\n      expect(score('bar', 'a')).toBe(1)\n      expect(score('fooBar', 'B')).toBe(2)\n      expect(score('fooBar', 'b')).toBe(1)\n      expect(score('fobtoBar', 'bt')).toBe(2)\n    })\n\n    it('should match follow letters', () => {\n      expect(score('abc', 'ab')).toBe(6)\n      expect(score('adB', 'ab')).toBe(5.75)\n      expect(score('adb', 'ab')).toBe(5.1)\n      expect(score('adCB', 'ab')).toBe(5.05)\n      expect(score('a_b_c', 'ab')).toBe(6)\n      expect(score('FooBar', 'fb')).toBe(3.25)\n      expect(score('FBar', 'fb')).toBe(3)\n      expect(score('FooBar', 'FB')).toBe(6)\n      expect(score('FBar', 'FB')).toBe(6)\n      expect(score('a__b', 'a__b')).toBe(8)\n      expect(score('aBc', 'ab')).toBe(5.5)\n      expect(score('a_B_c', 'ab')).toBe(5.75)\n      expect(score('abc', 'abc')).toBe(7)\n      expect(score('abc', 'aC')).toBe(0)\n      expect(score('abc', 'ac')).toBe(5.1)\n      expect(score('abC', 'ac')).toBe(5.75)\n      expect(score('abC', 'aC')).toBe(6)\n    })\n\n    it('should only allow search once', () => {\n      expect(score('foobar', 'fbr')).toBe(5.2)\n      expect(score('foobaRow', 'fbr')).toBe(5.85)\n      expect(score('foobaRow', 'fbR')).toBe(6.1)\n      expect(score('foobar', 'fa')).toBe(5.1)\n    })\n\n    it('should have higher score for strict match', () => {\n      expect(score('language-client-protocol', 'lct')).toBe(6.1)\n      expect(score('language-client-types', 'lct')).toBe(7)\n    })\n\n    it('should find highest score', () => {\n      expect(score('ArrayRotateTail', 'art')).toBe(3.6)\n    })\n  })\n\n  describe('matchScoreWithPositions', () => {\n    function assertMatch(word: string, input: string, res: [number, ReadonlyArray<number>] | undefined): void {\n      let result = matchScoreWithPositions(word, getCharCodes(input))\n      if (!res) {\n        expect(result).toBeUndefined()\n      } else {\n        expect(result).toEqual(res)\n      }\n    }\n\n    it('should return undefined when not match found', () => {\n      assertMatch('a', 'abc', undefined)\n      assertMatch('a', '', undefined)\n      assertMatch('ab', 'ac', undefined)\n    })\n\n    it('should find matches by position fix', () => {\n      assertMatch('this', 'tih', [5.6, [0, 1, 2]])\n      assertMatch('globalThis', 'tihs', [2.6, [6, 7, 8, 9]])\n    })\n\n    it('should find matched positions', () => {\n      assertMatch('this', 'th', [6, [0, 1]])\n      assertMatch('foo_bar', 'fb', [6, [0, 4]])\n      assertMatch('assertMatch', 'am', [5.75, [0, 6]])\n    })\n  })\n\n  describe('wordDistance', () => {\n    it('should empty when not enabled', async () => {\n      let w = await WordDistance.create(false, {} as any, CancellationToken.None)\n      expect(w.distance(Position.create(0, 0), {} as any)).toBe(0)\n    })\n\n    it('should empty when selectRanges is empty', async () => {\n      let opt = await nvim.call('coc#util#get_complete_option') as CompleteOption\n      let w = await WordDistance.create(true, opt, CancellationToken.None)\n      expect(w).toBe(WordDistance.None)\n    })\n\n    it('should empty when timeout', async () => {\n      disposables.push(languages.registerSelectionRangeProvider([{ language: '*' }], {\n        provideSelectionRanges: _doc => {\n          return [{\n            range: Range.create(0, 0, 0, 1)\n          }]\n        }\n      }))\n      let spy = jest.spyOn(workspace, 'computeWordRanges').mockImplementation(() => {\n        return new Promise(resolve => {\n          setTimeout(() => {\n            resolve(null)\n          }, 50)\n        })\n      })\n      let opt = await nvim.call('coc#util#get_complete_option') as CompleteOption\n      let w = await WordDistance.create(true, opt, CancellationToken.None)\n      spy.mockRestore()\n      expect(w).toBe(WordDistance.None)\n    })\n\n    it('should get distance', async () => {\n      disposables.push(languages.registerSelectionRangeProvider([{ language: '*' }], {\n        provideSelectionRanges: _doc => {\n          return [{\n            range: Range.create(0, 0, 1, 0),\n            parent: {\n              range: Range.create(0, 0, 3, 0)\n            }\n          }]\n        }\n      }))\n      let filepath = await createTmpFile('foo bar\\ndef', disposables)\n      await helper.edit(filepath)\n      let opt = await nvim.call('coc#util#get_complete_option') as CompleteOption\n      let w = await WordDistance.create(true, opt, CancellationToken.None)\n      expect(w.distance(Position.create(1, 0), {} as any)).toBeGreaterThan(0)\n      expect(w.distance(Position.create(0, 0), { word: '', kind: CompletionItemKind.Keyword } as any)).toBeGreaterThan(0)\n      expect(w.distance(Position.create(0, 0), { word: 'not_exists' } as any)).toBeGreaterThan(0)\n      expect(w.distance(Position.create(0, 0), { word: 'bar' } as any)).toBe(0)\n      expect(w.distance(Position.create(0, 0), { word: 'def' } as any)).toBeGreaterThan(0)\n      await nvim.call('cursor', [1, 2])\n      await events.fire('CursorMoved', [opt.bufnr, [1, 2]])\n      expect(w.distance(Position.create(0, 0), { word: 'bar' } as any)).toBe(0)\n    })\n\n    it('should get same range', async () => {\n      disposables.push(languages.registerSelectionRangeProvider([{ language: '*' }], {\n        provideSelectionRanges: _doc => {\n          return [{\n            range: Range.create(0, 0, 1, 0),\n            parent: {\n              range: Range.create(0, 0, 3, 0)\n            }\n          }]\n        }\n      }))\n      let spy = jest.spyOn(workspace, 'computeWordRanges').mockImplementation(() => {\n        return Promise.resolve({ foo: [Range.create(0, 0, 0, 0)] })\n      })\n      let opt = await nvim.call('coc#util#get_complete_option') as any\n      opt.word = ''\n      let w = await WordDistance.create(true, opt, CancellationToken.None)\n      spy.mockRestore()\n      let res = w.distance(Position.create(0, 0), { word: 'foo' } as any)\n      expect(res).toBe(0)\n    })\n  })\n\n  describe('sortItems', () => {\n    it('should sort items', () => {\n      let emptyInput = false\n      let defaultSortMethod: SortMethod = SortMethod.None\n      let a: any = {\n        abbr: 'a', character: 0, filterText: 'a', index: 0, source: '', word: 'a'\n      }\n      let b: any = {\n        abbr: 'b', character: 0, filterText: 'b', index: 0, source: '', word: 'b'\n      }\n      const check = (ap: any, bp: any, res: number) => {\n        let val = sortItems(emptyInput, defaultSortMethod, Object.assign(ap, a), Object.assign(bp, b))\n        expect(val).toBe(res)\n      }\n      check({ score: 1 }, { score: 2 }, 1)\n      check({ priority: 1 }, { priority: 2 }, 1)\n      check({ sortText: 'b' }, { sortText: 'a' }, 1)\n      check({ sortText: 'a' }, { sortText: 'b' }, -1)\n      check({ localBonus: 1 }, { localBonus: 2 }, 1)\n    })\n  })\n\n  describe('MruLoader', () => {\n    it('should add item without prefix', () => {\n      let loader = new MruLoader()\n      loader.add('foo', { kind: '', source: getSource(), filterText: 'foo' })\n      let item = { kind: CompletionItemKind.Class, source: getSource(), filterText: '$foo' }\n      loader.add('foo', item)\n      let score = loader.getScore('', item, Selection.RecentlyUsed)\n      expect(score).toBeGreaterThan(-1)\n      score = loader.getScore('a', item, Selection.RecentlyUsedByPrefix)\n      expect(score).toBe(-1)\n      score = loader.getScore('f', item, Selection.RecentlyUsed)\n      expect(score).toBeGreaterThan(-1)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/configuration/configurationModel.test.ts",
    "content": "import * as assert from 'assert'\nimport { join } from 'path'\nimport { URI } from 'vscode-uri'\nimport { Configuration } from '../../configuration/configuration'\nimport { AllKeysConfigurationChangeEvent, ConfigurationChangeEvent } from '../../configuration/event'\nimport { ConfigurationModel } from '../../configuration/model'\nimport { ConfigurationModelParser } from '../../configuration/parser'\nimport { mergeChanges } from '../../configuration/util'\nimport { Registry } from '../../util/registry'\nimport { IConfigurationRegistry, validateProperty, configurationDefaultsSchemaId, resourceLanguageSettingsSchemaId, allSettings, resourceSettings, Extensions, IConfigurationNode } from '../../configuration/registry'\nimport { ConfigurationScope, ConfigurationTarget } from '../../configuration/types'\nimport { Disposable } from 'vscode-languageserver-protocol'\nimport { disposeAll } from '../../util'\nimport { IJSONContributionRegistry, Extensions as JSONExtensions } from '../../util/jsonRegistry'\n\ndescribe('ConfigurationRegistry', () => {\n  let disposables: Disposable[] = []\n\n  afterAll(() => {\n    disposeAll(disposables)\n  })\n\n  let configuration = Registry.as<IConfigurationRegistry>(Extensions.Configuration)\n  function createNode(id: string): IConfigurationNode {\n    return { id, properties: {} }\n  }\n\n  function length(obj: object): number {\n    return Object.keys(obj).length\n  }\n\n  test('register and unregister configuration', () => {\n    let node = createNode('test')\n    node.properties['test.foo'] = {\n      type: 'string',\n      default: '',\n      markdownDeprecationMessage: 'deprecated'\n    }\n    node.properties['test.bar'] = {\n      type: 'string',\n      scope: ConfigurationScope.APPLICATION,\n      included: false\n    }\n    node.properties['test.resource'] = {\n      type: 'boolean',\n      scope: ConfigurationScope.RESOURCE,\n      markdownDescription: '# Description',\n      default: true\n    }\n    node.properties['test.language'] = {\n      type: 'array',\n      scope: ConfigurationScope.LANGUAGE_OVERRIDABLE,\n      default: []\n    }\n    expect(typeof configurationDefaultsSchemaId).toBe('string')\n    let called = 0\n    configuration.onDidSchemaChange(() => {\n      called++\n    }, null, disposables)\n    configuration.onDidUpdateConfiguration(() => {\n      called++\n    }, null, disposables)\n    configuration.registerConfigurations([node], false)\n    expect(called).toBe(2)\n    let other = createNode('other')\n    other.scope = ConfigurationScope.RESOURCE\n    other.properties['test.foo'] = { type: 'string' }\n    configuration.registerConfiguration(other)\n    configuration.registerConfigurations([other])\n    let keys = Object.keys(allSettings.properties)\n    expect(keys.length).toBe(3)\n    keys = Object.keys(resourceSettings.properties)\n    expect(keys.length).toBe(2)\n    expect(length(configuration.getConfigurationProperties())).toBe(3)\n    expect(length(configuration.getExcludedConfigurationProperties())).toBe(1)\n    let jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution)\n    let schemas = jsonRegistry.getSchemaContributions().schemas\n    expect(schemas[resourceLanguageSettingsSchemaId]).toBeDefined()\n    configuration.deregisterConfigurations([node])\n    keys = Object.keys(allSettings.properties)\n    expect(keys.length).toBe(0)\n    keys = Object.keys(resourceSettings.properties)\n    expect(keys.length).toBe(0)\n    let schema = schemas[resourceLanguageSettingsSchemaId]\n    expect(schema.properties).toEqual({})\n  })\n\n  test('register with extension info', () => {\n    let node = createNode('test')\n    node.extensionInfo = { id: 'coc-test' }\n    node.properties['test.foo'] = {\n      type: 'string',\n      default: '',\n      description: 'foo'\n    }\n    node.properties['test.bar'] = {\n      type: 'string',\n      default: '',\n    }\n    configuration.registerConfiguration(node)\n    expect(allSettings.properties['test.foo'].description).toBeDefined()\n    expect(allSettings.properties['test.bar'].description).toBeDefined()\n    configuration.deregisterConfigurations([node])\n  })\n\n  test('update configurations', () => {\n    let called = 0\n    configuration.onDidSchemaChange(() => {\n      called++\n    }, null, disposables)\n    configuration.updateConfigurations({ add: [], remove: [] })\n    expect(called).toBe(1)\n  })\n\n  test('validateProperty', () => {\n    expect(validateProperty('', {} as any) != null).toBe(true)\n    expect(validateProperty('[docker]') != null).toBe(true)\n    expect(validateProperty('key')).toBeNull()\n  })\n})\n\nfunction toConfigurationModel(content: any): ConfigurationModel {\n  const parser = new ConfigurationModelParser('test')\n  parser.parse(JSON.stringify(content))\n  return parser.configurationModel\n}\ndescribe('ConfigurationModelParser', () => {\n  test('parser no error with empty text', async () => {\n    const parser = new ConfigurationModelParser('test')\n    parser.parse(' ')\n    expect(parser.errors.length).toBe(0)\n  })\n\n  test('parse invalid value', async () => {\n    let parser = new ConfigurationModelParser('test')\n    parser.parse(33 as any)\n    expect(parser.errors.length).toBe(1)\n  })\n\n  test('parse conflict properties', async () => {\n    let parser = new ConfigurationModelParser('test')\n    let called = false\n    let s = jest.spyOn(console, 'error').mockImplementation(() => {\n      called = true\n    })\n    parser.parse(JSON.stringify({ x: 1, 'x.y': {} }, null, 2))\n    s.mockRestore()\n    expect(called).toBe(true)\n  })\n\n  test('parse configuration model with single override identifier', () => {\n    const testObject = new ConfigurationModelParser('')\n    testObject.parse(JSON.stringify({ '[x]': { a: 1 } }))\n    expect(JSON.stringify(testObject.configurationModel.overrides)).toEqual(JSON.stringify([{ identifiers: ['x'], keys: ['a'], contents: { a: 1 } }]))\n  })\n\n  test('parse configuration model with multiple override identifiers', () => {\n    const testObject = new ConfigurationModelParser('')\n\n    testObject.parse(JSON.stringify({ '[x][y]': { a: 1 } }))\n\n    assert.deepStrictEqual(JSON.stringify(testObject.configurationModel.overrides), JSON.stringify([{ identifiers: ['x', 'y'], keys: ['a'], contents: { a: 1 } }]))\n  })\n\n  test('parse configuration model with multiple duplicate override identifiers', () => {\n    const testObject = new ConfigurationModelParser('')\n\n    testObject.parse(JSON.stringify({ '[x][y][x][z]': { a: 1 } }))\n\n    assert.deepStrictEqual(JSON.stringify(testObject.configurationModel.overrides), JSON.stringify([{ identifiers: ['x', 'y', 'z'], keys: ['a'], contents: { a: 1 } }]))\n  })\n\n  test('parse with conflict properties', async () => {\n    const testObject = new ConfigurationModelParser('')\n\n    testObject.parse('{\"x\": 3, \"x\": 4}')\n  })\n})\n\ndescribe('ConfigurationModel', () => {\n  test('setValue for a key that has no sections and not defined', () => {\n    const testObject = new ConfigurationModel({ a: { b: 1 } }, ['a.b'])\n\n    testObject.setValue('f', 1)\n\n    assert.deepStrictEqual(testObject.contents, { a: { b: 1 }, f: 1 })\n    assert.deepStrictEqual(testObject.keys, ['a.b', 'f'])\n    let called = false\n    let s = jest.spyOn(console, 'error').mockImplementation(() => {\n      called = true\n    })\n    testObject.setValue('a.b.c.d', { x: 3 })\n    s.mockRestore()\n  })\n\n  test('setValue for a key that has no sections and defined', () => {\n    const testObject = new ConfigurationModel({ a: { b: 1 }, f: 1 }, ['a.b', 'f'])\n\n    testObject.setValue('f', 3)\n\n    assert.deepStrictEqual(testObject.contents, { a: { b: 1 }, f: 3 })\n    assert.deepStrictEqual(testObject.keys, ['a.b', 'f'])\n  })\n\n  test('setValue for a key that has sections and not defined', () => {\n    const testObject = new ConfigurationModel({ a: { b: 1 }, f: 1 }, ['a.b', 'f'])\n\n    testObject.setValue('b.c', 1)\n\n    const expected: any = {}\n    expected['a'] = { b: 1 }\n    expected['f'] = 1\n    expected['b'] = Object.create(null)\n    expected['b']['c'] = 1\n    expect(testObject.contents).toEqual(expected)\n    // assert.deepStrictEqual(testObject.contents, expected)\n    assert.deepStrictEqual(testObject.keys, ['a.b', 'f', 'b.c'])\n  })\n\n  test('setValue for a key that has sections and defined', () => {\n    const testObject = new ConfigurationModel({ a: { b: 1 }, b: { c: 1 }, f: 1 }, ['a.b', 'b.c', 'f'])\n\n    testObject.setValue('b.c', 3)\n\n    assert.deepStrictEqual(testObject.contents, { a: { b: 1 }, b: { c: 3 }, f: 1 })\n    assert.deepStrictEqual(testObject.keys, ['a.b', 'b.c', 'f'])\n  })\n\n  test('setValue for a key that has sections and sub section not defined', () => {\n    const testObject = new ConfigurationModel({ a: { b: 1 }, f: 1 }, ['a.b', 'f'])\n\n    testObject.setValue('a.c', 1)\n\n    assert.deepStrictEqual(testObject.contents, { a: { b: 1, c: 1 }, f: 1 })\n    assert.deepStrictEqual(testObject.keys, ['a.b', 'f', 'a.c'])\n  })\n\n  test('setValue for a key that has sections and sub section defined', () => {\n    const testObject = new ConfigurationModel({ a: { b: 1, c: 1 }, f: 1 }, ['a.b', 'a.c', 'f'])\n\n    testObject.setValue('a.c', 3)\n\n    assert.deepStrictEqual(testObject.contents, { a: { b: 1, c: 3 }, f: 1 })\n    assert.deepStrictEqual(testObject.keys, ['a.b', 'a.c', 'f'])\n  })\n\n  test('setValue for a key that has sections and last section is added', () => {\n    const testObject = new ConfigurationModel({ a: { b: {} }, f: 1 }, ['a.b', 'f'])\n\n    testObject.setValue('a.b.c', 1)\n\n    assert.deepStrictEqual(testObject.contents, { a: { b: { c: 1 } }, f: 1 })\n    assert.deepStrictEqual(testObject.keys, ['a.b.c', 'f'])\n  })\n\n  test('removeValue: remove a non existing key', () => {\n    const testObject = new ConfigurationModel({ a: { b: 2 } }, ['a.b'])\n\n    testObject.removeValue('a.b.c')\n\n    assert.deepStrictEqual(testObject.contents, { a: { b: 2 } })\n    assert.deepStrictEqual(testObject.keys, ['a.b'])\n  })\n\n  test('removeValue: remove a single segmented key', () => {\n    const testObject = new ConfigurationModel({ a: 1 }, ['a'])\n\n    testObject.removeValue('a')\n\n    assert.deepStrictEqual(testObject.contents, {})\n    assert.deepStrictEqual(testObject.keys, [])\n  })\n\n  test('removeValue: remove a multi segmented key', () => {\n    const testObject = new ConfigurationModel({ a: { b: 1 } }, ['a.b'])\n\n    testObject.removeValue('a.b')\n\n    assert.deepStrictEqual(testObject.contents, {})\n    assert.deepStrictEqual(testObject.keys, [])\n  })\n\n  test('get overriding configuration model for an existing identifier', () => {\n    const testObject = new ConfigurationModel(\n      { a: { b: 1 }, f: 1 }, [],\n      [{ identifiers: ['c'], contents: { a: { d: 1 } }, keys: ['a'] }])\n\n    assert.deepStrictEqual(testObject.override('c').contents, { a: { b: 1, d: 1 }, f: 1 })\n  })\n\n  test('get overriding configuration model for an identifier that does not exist', () => {\n    const testObject = new ConfigurationModel(\n      { a: { b: 1 }, f: 1 }, [],\n      [{ identifiers: ['c'], contents: { a: { d: 1 } }, keys: ['a'] }])\n\n    assert.deepStrictEqual(testObject.override('xyz').contents, { a: { b: 1 }, f: 1 })\n  })\n\n  test('get overriding configuration when one of the keys does not exist in base', () => {\n    const testObject = new ConfigurationModel(\n      { a: { b: 1 }, f: 1 }, [],\n      [{ identifiers: ['c'], contents: { a: { d: 1 }, g: 1 }, keys: ['a', 'g'] }])\n\n    assert.deepStrictEqual(testObject.override('c').contents, { a: { b: 1, d: 1 }, f: 1, g: 1 })\n  })\n\n  test('get overriding configuration when one of the key in base is not of object type', () => {\n    const testObject = new ConfigurationModel(\n      { a: { b: 1 }, f: 1 }, [],\n      [{ identifiers: ['c'], contents: { a: { d: 1 }, f: { g: 1 } }, keys: ['a', 'f'] }])\n\n    assert.deepStrictEqual(testObject.override('c').contents, { a: { b: 1, d: 1 }, f: { g: 1 } })\n  })\n\n  test('get overriding configuration when one of the key in overriding contents is not of object type', () => {\n    const testObject = new ConfigurationModel(\n      { a: { b: 1 }, f: { g: 1 } }, [],\n      [{ identifiers: ['c'], contents: { a: { d: 1 }, f: 1 }, keys: ['a', 'f'] }])\n\n    assert.deepStrictEqual(testObject.override('c').contents, { a: { b: 1, d: 1 }, f: 1 })\n  })\n\n  test('get overriding configuration if the value of overriding identifier is not object', () => {\n    const testObject = new ConfigurationModel(\n      { a: { b: 1 }, f: { g: 1 } }, [],\n      [{ identifiers: ['c'], contents: 'abc', keys: [] }])\n\n    assert.deepStrictEqual(testObject.override('c').contents, { a: { b: 1 }, f: { g: 1 } })\n  })\n\n  test('get overriding configuration if the value of overriding identifier is an empty object', () => {\n    const testObject = new ConfigurationModel(\n      { a: { b: 1 }, f: { g: 1 } }, [],\n      [{ identifiers: ['c'], contents: {}, keys: [] }])\n\n    assert.deepStrictEqual(testObject.override('c').contents, { a: { b: 1 }, f: { g: 1 } })\n  })\n\n  test('simple merge', () => {\n    const base = new ConfigurationModel({ a: 1, b: 2 }, ['a', 'b'])\n    const add = new ConfigurationModel({ a: 3, c: 4 }, ['a', 'c'])\n    const result = base.merge(add)\n\n    assert.deepStrictEqual(result.contents, { a: 3, b: 2, c: 4 })\n    assert.deepStrictEqual(result.keys, ['a', 'b', 'c'])\n  })\n\n  test('recursive merge', () => {\n    const base = new ConfigurationModel({ a: { b: 1 } }, ['a.b'])\n    const add = new ConfigurationModel({ a: { b: 2 } }, ['a.b'])\n    const result = base.merge(add)\n\n    assert.deepStrictEqual(result.contents, { a: { b: 2 } })\n    assert.deepStrictEqual(result.getValue('a'), { b: 2 })\n    assert.deepStrictEqual(result.keys, ['a.b'])\n  })\n\n  test('simple merge overrides', () => {\n    const base = new ConfigurationModel({ a: { b: 1 } }, ['a.b'], [{ identifiers: ['c'], contents: { a: 2 }, keys: ['a'] }])\n    const add = new ConfigurationModel({ a: { b: 2 } }, ['a.b'], [{ identifiers: ['c'], contents: { b: 2 }, keys: ['b'] }])\n    const result = base.merge(add)\n\n    assert.deepStrictEqual(result.contents, { a: { b: 2 } })\n    assert.deepStrictEqual(result.overrides, [{ identifiers: ['c'], contents: { a: 2, b: 2 }, keys: ['a', 'b'] }])\n    assert.deepStrictEqual(result.override('c').contents, { a: 2, b: 2 })\n    assert.deepStrictEqual(result.keys, ['a.b'])\n  })\n\n  test('recursive merge overrides', () => {\n    const base = new ConfigurationModel({ a: { b: 1 }, f: 1 }, ['a.b', 'f'], [{ identifiers: ['c'], contents: { a: { d: 1 } }, keys: ['a'] }])\n    const add = new ConfigurationModel({ a: { b: 2 } }, ['a.b'], [{ identifiers: ['c'], contents: { a: { e: 2 } }, keys: ['a'] }])\n    const result = base.merge(add)\n\n    assert.deepStrictEqual(result.contents, { a: { b: 2 }, f: 1 })\n    assert.deepStrictEqual(result.overrides, [{ identifiers: ['c'], contents: { a: { d: 1, e: 2 } }, keys: ['a'] }])\n    assert.deepStrictEqual(result.override('c').contents, { a: { b: 2, d: 1, e: 2 }, f: 1 })\n    assert.deepStrictEqual(result.keys, ['a.b', 'f'])\n  })\n\n  test('merge overrides when frozen', () => {\n    const model1 = new ConfigurationModel({ a: { b: 1 }, f: 1 }, ['a.b', 'f'], [{ identifiers: ['c'], contents: { a: { d: 1 } }, keys: ['a'] }]).freeze()\n    const model2 = new ConfigurationModel({ a: { b: 2 } }, ['a.b'], [{ identifiers: ['c'], contents: { a: { e: 2 } }, keys: ['a'] }]).freeze()\n    const result = new ConfigurationModel().merge(model1, model2)\n\n    assert.deepStrictEqual(result.contents, { a: { b: 2 }, f: 1 })\n    assert.deepStrictEqual(result.overrides, [{ identifiers: ['c'], contents: { a: { d: 1, e: 2 } }, keys: ['a'] }])\n    assert.deepStrictEqual(result.override('c').contents, { a: { b: 2, d: 1, e: 2 }, f: 1 })\n    assert.deepStrictEqual(result.keys, ['a.b', 'f'])\n  })\n\n  test('Test contents while getting an existing property', () => {\n    let testObject = new ConfigurationModel({ a: 1 })\n    assert.deepStrictEqual(testObject.getValue('a'), 1)\n\n    testObject = new ConfigurationModel({ a: { b: 1 } })\n    assert.deepStrictEqual(testObject.getValue('a'), { b: 1 })\n  })\n\n  test('Test contents are undefined for non existing properties', () => {\n    const testObject = new ConfigurationModel({ awesome: true })\n\n    assert.deepStrictEqual(testObject.getValue('unknownproperty'), undefined)\n  })\n\n  test('Test override gives all content merged with overrides', () => {\n    const testObject = new ConfigurationModel({ a: 1, c: 1 }, [], [{ identifiers: ['b'], contents: { a: 2 }, keys: ['a'] }])\n\n    assert.deepStrictEqual(testObject.override('b').contents, { a: 2, c: 1 })\n  })\n\n  test('Test override when an override has multiple identifiers', () => {\n    const testObject = new ConfigurationModel({ a: 1, c: 1 }, ['a', 'c'], [{ identifiers: ['x', 'y'], contents: { a: 2 }, keys: ['a'] }])\n\n    let actual = testObject.override('x')\n    assert.deepStrictEqual(actual.contents, { a: 2, c: 1 })\n    assert.deepStrictEqual(actual.keys, ['a', 'c'])\n    assert.deepStrictEqual(testObject.getKeysForOverrideIdentifier('x'), ['a'])\n\n    actual = testObject.override('y')\n    assert.deepStrictEqual(actual.contents, { a: 2, c: 1 })\n    assert.deepStrictEqual(actual.keys, ['a', 'c'])\n    assert.deepStrictEqual(testObject.getKeysForOverrideIdentifier('y'), ['a'])\n  })\n\n  test('Test override when an identifier is defined in multiple overrides', () => {\n    const testObject = new ConfigurationModel({ a: 1, c: 1 }, ['a', 'c'], [{ identifiers: ['x'], contents: { a: 3, b: 1 }, keys: ['a', 'b'] }, { identifiers: ['x', 'y'], contents: { a: 2 }, keys: ['a'] }])\n\n    const actual = testObject.override('x')\n    assert.deepStrictEqual(actual.contents, { a: 3, c: 1, b: 1 })\n    assert.deepStrictEqual(actual.keys, ['a', 'c'])\n\n    assert.deepStrictEqual(testObject.getKeysForOverrideIdentifier('x'), ['a', 'b'])\n  })\n\n  test('Test merge when configuration models have multiple identifiers', () => {\n    const testObject = new ConfigurationModel({ a: 1, c: 1 }, ['a', 'c'], [{ identifiers: ['y'], contents: { c: 1 }, keys: ['c'] }, { identifiers: ['x', 'y'], contents: { a: 2 }, keys: ['a'] }])\n    const target = new ConfigurationModel({ a: 2, b: 1 }, ['a', 'b'], [{ identifiers: ['x'], contents: { a: 3, b: 2 }, keys: ['a', 'b'] }, { identifiers: ['x', 'y'], contents: { b: 3 }, keys: ['b'] }])\n\n    const actual = testObject.merge(target)\n\n    assert.deepStrictEqual(actual.contents, { a: 2, c: 1, b: 1 })\n    assert.deepStrictEqual(actual.keys, ['a', 'c', 'b'])\n    assert.deepStrictEqual(actual.overrides, [\n      { identifiers: ['y'], contents: { c: 1 }, keys: ['c'] },\n      { identifiers: ['x', 'y'], contents: { a: 2, b: 3 }, keys: ['a', 'b'] },\n      { identifiers: ['x'], contents: { a: 3, b: 2 }, keys: ['a', 'b'] },\n    ])\n  })\n})\n\ndescribe('CustomConfigurationModel', () => {\n\n  test('simple merge using models', () => {\n    const base = new ConfigurationModelParser('base')\n    base.parse(JSON.stringify({ a: 1, b: 2 }))\n\n    const add = new ConfigurationModelParser('add')\n    add.parse(JSON.stringify({ a: 3, c: 4 }))\n\n    const result = base.configurationModel.merge(add.configurationModel)\n    assert.deepStrictEqual(result.contents, { a: 3, b: 2, c: 4 })\n  })\n\n  test('simple merge with an undefined contents', () => {\n    let base = new ConfigurationModelParser('base')\n    base.parse(JSON.stringify({ a: 1, b: 2 }))\n    let add = new ConfigurationModelParser('add')\n    let result = base.configurationModel.merge(add.configurationModel)\n    assert.deepStrictEqual(result.contents, { a: 1, b: 2 })\n\n    base = new ConfigurationModelParser('base')\n    add = new ConfigurationModelParser('add')\n    add.parse(JSON.stringify({ a: 1, b: 2 }))\n    result = base.configurationModel.merge(add.configurationModel)\n    assert.deepStrictEqual(result.contents, { a: 1, b: 2 })\n\n    base = new ConfigurationModelParser('base')\n    add = new ConfigurationModelParser('add')\n    result = base.configurationModel.merge(add.configurationModel)\n    assert.deepStrictEqual(result.contents, {})\n  })\n\n  test('Recursive merge using config models', () => {\n    const base = new ConfigurationModelParser('base')\n    base.parse(JSON.stringify({ a: { b: 1 } }))\n    const add = new ConfigurationModelParser('add')\n    add.parse(JSON.stringify({ a: { b: 2 } }))\n    const result = base.configurationModel.merge(add.configurationModel)\n    assert.deepStrictEqual(result.contents, { a: { b: 2 } })\n  })\n\n  test('Test contents while getting an existing property', () => {\n    const testObject = new ConfigurationModelParser('test')\n    testObject.parse(JSON.stringify({ a: 1 }))\n    assert.deepStrictEqual(testObject.configurationModel.getValue('a'), 1)\n\n    testObject.parse(JSON.stringify({ a: { b: 1 } }))\n    assert.deepStrictEqual(testObject.configurationModel.getValue('a'), { b: 1 })\n  })\n\n  test('Test contents are undefined for non existing properties', () => {\n    const testObject = new ConfigurationModelParser('test')\n    testObject.parse(JSON.stringify({\n      awesome: true\n    }))\n\n    assert.deepStrictEqual(testObject.configurationModel.getValue('unknownproperty'), undefined)\n  })\n\n  test('Test contents are undefined for undefined config', () => {\n    const testObject = new ConfigurationModelParser('test')\n\n    assert.deepStrictEqual(testObject.configurationModel.getValue('unknownproperty'), undefined)\n  })\n\n  test('Test configWithOverrides gives all content merged with overrides', () => {\n    const testObject = new ConfigurationModelParser('test')\n    testObject.parse(JSON.stringify({ a: 1, c: 1, '[b]': { a: 2 } }))\n\n    assert.deepStrictEqual(testObject.configurationModel.override('b').contents, { a: 2, c: 1, '[b]': { a: 2 } })\n  })\n\n  test('Test configWithOverrides gives empty contents', () => {\n    const testObject = new ConfigurationModelParser('test')\n\n    assert.deepStrictEqual(testObject.configurationModel.override('b').contents, {})\n  })\n\n  test('Test update with empty data', () => {\n    const testObject = new ConfigurationModelParser('test')\n    testObject.parse('')\n\n    assert.deepStrictEqual(testObject.configurationModel.contents, Object.create(null))\n    assert.deepStrictEqual(testObject.configurationModel.keys, [])\n\n    testObject.parse(null!)\n\n    assert.deepStrictEqual(testObject.configurationModel.contents, Object.create(null))\n    assert.deepStrictEqual(testObject.configurationModel.keys, [])\n\n    testObject.parse(undefined!)\n\n    assert.deepStrictEqual(testObject.configurationModel.contents, Object.create(null))\n    assert.deepStrictEqual(testObject.configurationModel.keys, [])\n  })\n\n  test('Test empty property is not ignored', () => {\n    const testObject = new ConfigurationModelParser('test')\n    testObject.parse(JSON.stringify({ '': 1 }))\n\n    // deepStrictEqual seems to ignore empty properties, fall back\n    // to comparing the output of JSON.stringify\n    assert.strictEqual(JSON.stringify(testObject.configurationModel.contents), JSON.stringify({ '': 1 }))\n    assert.deepStrictEqual(testObject.configurationModel.keys, [''])\n  })\n})\n\ndescribe('Configuration', () => {\n  test('Test getConfigurationModel', () => {\n    const parser = new ConfigurationModelParser('test')\n    parser.parse(JSON.stringify({ a: 1 }))\n    const con: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel(), new ConfigurationModel())\n    expect(con.getConfigurationModel(ConfigurationTarget.Default)).toBeDefined()\n    expect(con.getConfigurationModel(ConfigurationTarget.User)).toBeDefined()\n    expect(con.getConfigurationModel(ConfigurationTarget.Workspace)).toBeDefined()\n    expect(con.getConfigurationModel(ConfigurationTarget.WorkspaceFolder, 'folder')).toBeDefined()\n    expect(con.getConfigurationModel(ConfigurationTarget.Memory)).toBeDefined()\n  })\n\n  test('Test resolveFolder', async () => {\n    const con: Configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel())\n    con.addFolderConfiguration('/a/b/c', new ConfigurationModel())\n    con.addFolderConfiguration('/a', new ConfigurationModel())\n    let res = con.resolveFolder('/a/b/c/d/e')\n    expect(res).toBe('/a/b/c')\n  })\n\n  test('Test inspect for overrideIdentifiers', () => {\n    const defaultConfigurationModel = toConfigurationModel({ '[l1]': { a: 1 }, '[l2]': { b: 1 } })\n    const userConfigurationModel = toConfigurationModel({ '[l3]': { a: 2 } })\n    const workspaceConfigurationModel = toConfigurationModel({ '[l1]': { a: 3 }, '[l4]': { a: 3 } })\n    const workspaceFolderConfigurationModel = toConfigurationModel({ '[l3]': { a: 3 } })\n    const testObject: Configuration = new Configuration(defaultConfigurationModel, userConfigurationModel, workspaceConfigurationModel)\n    testObject.updateFolderConfiguration('/foo', workspaceFolderConfigurationModel)\n    const { overrideIdentifiers } = testObject.inspect('a', {})\n    assert.deepStrictEqual(overrideIdentifiers, ['l1', 'l3', 'l4'])\n    let res = testObject.inspect('a', { overrideIdentifier: 'l1' })\n    expect(res.value).toBe(3)\n    expect(res.default.override).toBe(1)\n    expect(res.user).toBeUndefined()\n    res = testObject.inspect('a', { overrideIdentifier: 'l3' })\n    expect(res.user).toEqual({ value: undefined, override: 2 })\n    res = testObject.inspect('a', { overrideIdentifier: 'l3', resource: '/foo/bar' })\n    expect(res.workspaceFolder).toEqual({ value: undefined, override: 3 })\n    testObject.updateValue('b', 3)\n    res = testObject.inspect('b', {})\n    expect(res.memoryValue).toBe(3)\n    res = testObject.inspect('b', { overrideIdentifier: 'l3' })\n    expect(res.memoryValue).toBe(3)\n    const newModel = toConfigurationModel({ a: 4 })\n    testObject.compareAndUpdateFolderConfiguration('/foo', newModel)\n    res = testObject.inspect('a', { resource: '/foo/bar' })\n    expect(res.workspaceFolderValue).toBe(4)\n    testObject.compareAndUpdateFolderConfiguration('/foo', newModel)\n    res = testObject.inspect('a', { resource: '/foo/bar' })\n    expect(res.workspaceFolderValue).toBe(4)\n  })\n\n  test('Test update value', () => {\n    const parser = new ConfigurationModelParser('test')\n    parser.parse(JSON.stringify({ a: 1 }))\n    const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel(), new ConfigurationModel())\n    testObject.updateValue('a', 2)\n    assert.strictEqual(testObject.getValue('a', {}), 2)\n  })\n\n  test('Test update by resource', async () => {\n    const parser = new ConfigurationModelParser('test')\n    parser.parse(JSON.stringify({ a: 1 }))\n    const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel(), new ConfigurationModel())\n    testObject.updateValue('a', 2, { resource: 'file' })\n    testObject.updateValue('a', 3, { resource: 'file' })\n    assert.strictEqual(testObject.getValue('a', { resource: 'file' }), 3)\n    testObject.updateValue('a', undefined, { resource: 'file' })\n    assert.strictEqual(testObject.getValue('a', { resource: 'file' }), 1)\n  })\n\n  test('Test update value after inspect', () => {\n    const parser = new ConfigurationModelParser('test')\n    parser.parse(JSON.stringify({ a: 1 }))\n    const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel(), new ConfigurationModel())\n    testObject.inspect('a', {})\n    testObject.updateValue('a', 2)\n    assert.strictEqual(testObject.getValue('a', {}), 2)\n  })\n\n  test('Test compare and update default configuration', () => {\n    const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel())\n    testObject.updateDefaultConfiguration(toConfigurationModel({\n      'editor.lineNumbers': 'on',\n    }))\n\n    const actual = testObject.compareAndUpdateDefaultConfiguration(toConfigurationModel({\n      'editor.lineNumbers': 'off',\n      '[markdown]': {\n        'editor.wordWrap': 'off'\n      }\n    }), ['editor.lineNumbers', '[markdown]'])\n\n    assert.deepStrictEqual(actual, { keys: ['editor.lineNumbers', '[markdown]'], overrides: [['markdown', ['editor.wordWrap']]] })\n    let res = testObject.compareAndUpdateDefaultConfiguration(toConfigurationModel({\n      '[markdown]': {\n        'editor.lineNumbers': 'off',\n        'editor.wordWrap': 'on',\n        'editor.showbreak': 'off'\n      }\n    }), ['[markdown]'])\n    expect(res.overrides).toEqual([\n      ['markdown', ['editor.lineNumbers', 'editor.showbreak', 'editor.wordWrap']]\n    ])\n\n    res = testObject.compareAndUpdateDefaultConfiguration(toConfigurationModel({}))\n    expect(res.overrides).toEqual([\n      [\n        'markdown',\n        [\n          'editor.lineNumbers',\n          'editor.wordWrap',\n          'editor.showbreak',\n          'editor.lineNumbers',\n          'editor.wordWrap',\n          'editor.showbreak'\n        ]\n      ]\n    ])\n  })\n\n  test('Test compare and update same configurationModel', async () => {\n    const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel())\n    let res = testObject.compareAndUpdateUserConfiguration(testObject.user)\n    expect(res.keys).toEqual([])\n    res = testObject.compareAndUpdateWorkspaceConfiguration(testObject.workspace)\n    expect(res.keys).toEqual([])\n    res = testObject.compareAndUpdateDefaultConfiguration(testObject.defaults)\n    expect(res.keys).toEqual([])\n    testObject.compareAndDeleteFolderConfiguration('/a/b')\n  })\n\n  test('Test compare and update user configuration', () => {\n    const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel())\n    testObject.updateUserConfiguration(toConfigurationModel({\n      'editor.lineNumbers': 'off',\n      'editor.fontSize': 12,\n      '[typescript]': {\n        'editor.wordWrap': 'off'\n      }\n    }))\n\n    const actual = testObject.compareAndUpdateUserConfiguration(toConfigurationModel({\n      'editor.lineNumbers': 'on',\n      'window.zoomLevel': 1,\n      '[typescript]': {\n        'editor.wordWrap': 'on',\n        'editor.insertSpaces': false\n      }\n    }))\n\n    assert.deepStrictEqual(actual, { keys: ['window.zoomLevel', 'editor.lineNumbers', '[typescript]', 'editor.fontSize'], overrides: [['typescript', ['editor.insertSpaces', 'editor.wordWrap']]] })\n  })\n\n  test('Test compare and update workspace configuration', () => {\n    const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel())\n    testObject.updateWorkspaceConfiguration(toConfigurationModel({\n      'editor.lineNumbers': 'off',\n      'editor.fontSize': 12,\n      '[typescript]': {\n        'editor.wordWrap': 'off'\n      }\n    }))\n\n    const actual = testObject.compareAndUpdateWorkspaceConfiguration(toConfigurationModel({\n      'editor.lineNumbers': 'on',\n      'window.zoomLevel': 1,\n      '[typescript]': {\n        'editor.wordWrap': 'on',\n        'editor.insertSpaces': false\n      }\n    }))\n\n    assert.deepStrictEqual(actual, { keys: ['window.zoomLevel', 'editor.lineNumbers', '[typescript]', 'editor.fontSize'], overrides: [['typescript', ['editor.insertSpaces', 'editor.wordWrap']]] })\n\n  })\n\n  test('Test compare and update workspace folder configuration', () => {\n    const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel())\n    testObject.updateFolderConfiguration(URI.file('file1').fsPath, toConfigurationModel({\n      'editor.lineNumbers': 'off',\n      'editor.fontSize': 12,\n      '[typescript]': {\n        'editor.wordWrap': 'off'\n      }\n    }))\n    const actual = testObject.compareAndUpdateFolderConfiguration(URI.file('file1').fsPath, toConfigurationModel({\n      'editor.lineNumbers': 'on',\n      'window.zoomLevel': 1,\n      '[typescript]': {\n        'editor.wordWrap': 'on',\n        'editor.insertSpaces': false\n      }\n    }))\n    assert.deepStrictEqual(actual, { keys: ['window.zoomLevel', 'editor.lineNumbers', '[typescript]', 'editor.fontSize'], overrides: [['typescript', ['editor.insertSpaces', 'editor.wordWrap']]] })\n    testObject.compareAndUpdateFolderConfiguration('/a/b', new ConfigurationModel())\n    expect(testObject.hasFolder('/a/b')).toBe(true)\n  })\n})\n\ndescribe('ConfigurationChangeEvent', () => {\n\n  test('changeEvent affecting keys with new configuration', () => {\n    const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel())\n    const change = configuration.compareAndUpdateUserConfiguration(toConfigurationModel({\n      'window.zoomLevel': 1,\n      'workbench.editor.enablePreview': false,\n      'files.autoSave': 'off',\n    }))\n    const testObject = new ConfigurationChangeEvent(change, undefined, configuration)\n\n    assert.deepStrictEqual(testObject.affectedKeys, ['window.zoomLevel', 'workbench.editor.enablePreview', 'files.autoSave'])\n\n    assert.ok(testObject.affectsConfiguration('window.zoomLevel'))\n    assert.ok(testObject.affectsConfiguration('window'))\n\n    assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview'))\n    assert.ok(testObject.affectsConfiguration('workbench.editor'))\n    assert.ok(testObject.affectsConfiguration('workbench'))\n\n    assert.ok(testObject.affectsConfiguration('files'))\n    assert.ok(testObject.affectsConfiguration('files.autoSave'))\n    assert.ok(!testObject.affectsConfiguration('files.exclude'))\n\n    assert.ok(!testObject.affectsConfiguration('[markdown]'))\n    assert.ok(!testObject.affectsConfiguration('editor'))\n  })\n\n  test('changeEvent affecting keys when configuration changed', () => {\n    const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel())\n    configuration.updateUserConfiguration(toConfigurationModel({\n      'window.zoomLevel': 2,\n      'workbench.editor.enablePreview': true,\n      'files.autoSave': 'off',\n    }))\n    const data = configuration.toData()\n    const change = configuration.compareAndUpdateUserConfiguration(toConfigurationModel({\n      'window.zoomLevel': 1,\n      'workbench.editor.enablePreview': false,\n      'files.autoSave': 'off',\n    }))\n    const testObject = new ConfigurationChangeEvent(change, data, configuration)\n\n    assert.deepStrictEqual(testObject.affectedKeys, ['window.zoomLevel', 'workbench.editor.enablePreview'])\n\n    assert.ok(testObject.affectsConfiguration('window.zoomLevel'))\n    assert.ok(testObject.affectsConfiguration('window'))\n\n    assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview'))\n    assert.ok(testObject.affectsConfiguration('workbench.editor'))\n    assert.ok(testObject.affectsConfiguration('workbench'))\n\n    assert.ok(!testObject.affectsConfiguration('files'))\n    assert.ok(!testObject.affectsConfiguration('[markdown]'))\n    assert.ok(!testObject.affectsConfiguration('editor'))\n  })\n\n  test('changeEvent affecting overrides with new configuration', () => {\n    const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel())\n    const change = configuration.compareAndUpdateUserConfiguration(toConfigurationModel({\n      'files.autoSave': 'off',\n      '[markdown]': {\n        'editor.wordWrap': 'off'\n      },\n      '[typescript][jsonc]': {\n        'editor.lineNumbers': 'off'\n      }\n    }))\n    const testObject = new ConfigurationChangeEvent(change, undefined, configuration)\n\n    assert.deepStrictEqual(testObject.affectedKeys, ['files.autoSave', '[markdown]', '[typescript][jsonc]', 'editor.wordWrap', 'editor.lineNumbers'])\n\n    assert.ok(testObject.affectsConfiguration('files'))\n    assert.ok(testObject.affectsConfiguration('files.autoSave'))\n    assert.ok(!testObject.affectsConfiguration('files.exclude'))\n\n    assert.ok(testObject.affectsConfiguration('[markdown]'))\n    assert.ok(!testObject.affectsConfiguration('[markdown].editor'))\n    assert.ok(!testObject.affectsConfiguration('[markdown].workbench'))\n\n    assert.ok(testObject.affectsConfiguration('editor'))\n    assert.ok(testObject.affectsConfiguration('editor.wordWrap'))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers'))\n    assert.ok(testObject.affectsConfiguration('editor', { languageId: 'markdown' }))\n    assert.ok(testObject.affectsConfiguration('editor', { languageId: 'jsonc' }))\n    assert.ok(testObject.affectsConfiguration('editor', { languageId: 'typescript' }))\n    assert.ok(testObject.affectsConfiguration('editor.wordWrap', { languageId: 'markdown' }))\n    assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { languageId: 'jsonc' }))\n    assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { languageId: 'typescript' }))\n    assert.ok(!testObject.affectsConfiguration('editor.lineNumbers', { languageId: 'markdown' }))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { languageId: 'typescript' }))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { languageId: 'jsonc' }))\n    assert.ok(!testObject.affectsConfiguration('editor', { languageId: 'json' }))\n    assert.ok(!testObject.affectsConfiguration('editor.fontSize', { languageId: 'markdown' }))\n\n    assert.ok(!testObject.affectsConfiguration('editor.fontSize'))\n    assert.ok(!testObject.affectsConfiguration('window'))\n  })\n\n  test('changeEvent affecting overrides when configuration changed', () => {\n    const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel())\n    configuration.updateUserConfiguration(toConfigurationModel({\n      'workbench.editor.enablePreview': true,\n      '[markdown]': {\n        'editor.fontSize': 12,\n        'editor.wordWrap': 'off'\n      },\n      '[css][scss]': {\n        'editor.lineNumbers': 'off',\n        'css.lint.emptyRules': 'error'\n      },\n      'files.autoSave': 'off',\n    }))\n    const data = configuration.toData()\n    const change = configuration.compareAndUpdateUserConfiguration(toConfigurationModel({\n      'files.autoSave': 'off',\n      '[markdown]': {\n        'editor.fontSize': 13,\n        'editor.wordWrap': 'off'\n      },\n      '[css][scss]': {\n        'editor.lineNumbers': 'relative',\n        'css.lint.emptyRules': 'error'\n      },\n      'window.zoomLevel': 1,\n    }))\n    const testObject = new ConfigurationChangeEvent(change, data, configuration)\n\n    assert.deepStrictEqual(testObject.affectedKeys, ['window.zoomLevel', '[markdown]', '[css][scss]', 'workbench.editor.enablePreview', 'editor.fontSize', 'editor.lineNumbers'])\n\n    assert.ok(!testObject.affectsConfiguration('files'))\n\n    assert.ok(testObject.affectsConfiguration('[markdown]'))\n    assert.ok(!testObject.affectsConfiguration('[markdown].editor'))\n    assert.ok(!testObject.affectsConfiguration('[markdown].editor.fontSize'))\n    assert.ok(!testObject.affectsConfiguration('[markdown].editor.wordWrap'))\n    assert.ok(!testObject.affectsConfiguration('[markdown].workbench'))\n    assert.ok(testObject.affectsConfiguration('[css][scss]'))\n\n    assert.ok(testObject.affectsConfiguration('editor'))\n    assert.ok(testObject.affectsConfiguration('editor', { languageId: 'markdown' }))\n    assert.ok(testObject.affectsConfiguration('editor', { languageId: 'css' }))\n    assert.ok(testObject.affectsConfiguration('editor', { languageId: 'scss' }))\n    assert.ok(testObject.affectsConfiguration('editor.fontSize', { languageId: 'markdown' }))\n    assert.ok(!testObject.affectsConfiguration('editor.fontSize', { languageId: 'css' }))\n    assert.ok(!testObject.affectsConfiguration('editor.fontSize', { languageId: 'scss' }))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { languageId: 'scss' }))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { languageId: 'css' }))\n    assert.ok(!testObject.affectsConfiguration('editor.lineNumbers', { languageId: 'markdown' }))\n    assert.ok(!testObject.affectsConfiguration('editor.wordWrap'))\n    assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { languageId: 'markdown' }))\n    assert.ok(!testObject.affectsConfiguration('editor', { languageId: 'json' }))\n    assert.ok(!testObject.affectsConfiguration('editor.fontSize', { languageId: 'json' }))\n\n    assert.ok(testObject.affectsConfiguration('window'))\n    assert.ok(testObject.affectsConfiguration('window.zoomLevel'))\n    assert.ok(testObject.affectsConfiguration('window', { languageId: 'markdown' }))\n    assert.ok(testObject.affectsConfiguration('window.zoomLevel', { languageId: 'markdown' }))\n\n    assert.ok(testObject.affectsConfiguration('workbench'))\n    assert.ok(testObject.affectsConfiguration('workbench.editor'))\n    assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview'))\n    assert.ok(testObject.affectsConfiguration('workbench', { languageId: 'markdown' }))\n    assert.ok(testObject.affectsConfiguration('workbench.editor', { languageId: 'markdown' }))\n  })\n\n  test('changeEvent affecting workspace folders', () => {\n    const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel())\n    configuration.updateWorkspaceConfiguration(toConfigurationModel({ 'window.title': 'custom' }))\n    configuration.updateFolderConfiguration(URI.file('folder1').fsPath, toConfigurationModel({ 'window.zoomLevel': 2, 'window.restoreFullscreen': true }))\n    configuration.updateFolderConfiguration(URI.file('folder2').fsPath, toConfigurationModel({ 'workbench.editor.enablePreview': true, 'window.restoreWindows': true }))\n    const data = configuration.toData()\n    // const workspace = new Workspace('a',\n    // [new WorkspaceFolder({ index: 0, name: 'a', uri: URI.file('folder1') }),\n    // new WorkspaceFolder({ index: 1, name: 'b', uri: URI.file('folder2') }),\n    // new WorkspaceFolder({ index: 2, name: 'c', uri: URI.file('folder3') })])\n\n    const change = mergeChanges(\n      configuration.compareAndUpdateWorkspaceConfiguration(toConfigurationModel({ 'window.title': 'native' })),\n      configuration.compareAndUpdateFolderConfiguration(URI.file('folder1').fsPath, toConfigurationModel({ 'window.zoomLevel': 1, 'window.restoreFullscreen': false })),\n      configuration.compareAndUpdateFolderConfiguration(URI.file('folder2').fsPath, toConfigurationModel({ 'workbench.editor.enablePreview': false, 'window.restoreWindows': false }))\n    )\n    const testObject = new ConfigurationChangeEvent(change, data, configuration)\n\n    assert.deepStrictEqual(testObject.affectedKeys, ['window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows'])\n\n    assert.ok(testObject.affectsConfiguration('window.zoomLevel'))\n    assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file('folder1')))\n    assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file(join('folder1', 'file1'))))\n    assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file('file1')))\n    assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file('file2')))\n    assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file(join('folder2', 'file2'))))\n    assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file(join('folder3', 'file3'))))\n\n    assert.ok(testObject.affectsConfiguration('window.restoreFullscreen'))\n    assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file(join('folder1', 'file1'))))\n    assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file('folder1')))\n    assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file1')))\n    assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file2')))\n    assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file(join('folder2', 'file2'))))\n    assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file(join('folder3', 'file3'))))\n\n    assert.ok(testObject.affectsConfiguration('window.restoreWindows'))\n    assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file('folder2')))\n    assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file(join('folder2', 'file2'))))\n    assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file('file2')))\n    assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file(join('folder1', 'file1'))))\n    assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file(join('folder3', 'file3'))))\n\n    assert.ok(testObject.affectsConfiguration('window.title'))\n    assert.ok(testObject.affectsConfiguration('window.title', URI.file('folder1')))\n    assert.ok(testObject.affectsConfiguration('window.title', URI.file(join('folder1', 'file1'))))\n    assert.ok(testObject.affectsConfiguration('window.title', URI.file('folder2')))\n    assert.ok(testObject.affectsConfiguration('window.title', URI.file(join('folder2', 'file2'))))\n    assert.ok(testObject.affectsConfiguration('window.title', URI.file('folder3')))\n    assert.ok(testObject.affectsConfiguration('window.title', URI.file(join('folder3', 'file3'))))\n    assert.ok(testObject.affectsConfiguration('window.title', URI.file('file1')))\n    assert.ok(testObject.affectsConfiguration('window.title', URI.file('file2')))\n    assert.ok(testObject.affectsConfiguration('window.title', URI.file('file3')))\n\n    assert.ok(testObject.affectsConfiguration('window'))\n    assert.ok(testObject.affectsConfiguration('window', URI.file('folder1')))\n    assert.ok(testObject.affectsConfiguration('window', URI.file(join('folder1', 'file1'))))\n    assert.ok(testObject.affectsConfiguration('window', URI.file('folder2')))\n    assert.ok(testObject.affectsConfiguration('window', URI.file(join('folder2', 'file2'))))\n    assert.ok(testObject.affectsConfiguration('window', URI.file('folder3')))\n    assert.ok(testObject.affectsConfiguration('window', URI.file(join('folder3', 'file3'))))\n    assert.ok(testObject.affectsConfiguration('window', URI.file('file1')))\n    assert.ok(testObject.affectsConfiguration('window', URI.file('file2')))\n    assert.ok(testObject.affectsConfiguration('window', URI.file('file3')))\n\n    assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview'))\n    assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('folder2')))\n    assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file(join('folder2', 'file2'))))\n    assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('folder1')))\n    assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file(join('folder1', 'file1'))))\n    assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('folder3')))\n\n    assert.ok(testObject.affectsConfiguration('workbench.editor'))\n    assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file('folder2')))\n    assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file(join('folder2', 'file2'))))\n    assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file('folder1')))\n    assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file(join('folder1', 'file1'))))\n    assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file('folder3')))\n\n    assert.ok(testObject.affectsConfiguration('workbench'))\n    assert.ok(testObject.affectsConfiguration('workbench', URI.file('folder2')))\n    assert.ok(testObject.affectsConfiguration('workbench', URI.file(join('folder2', 'file2'))))\n    assert.ok(!testObject.affectsConfiguration('workbench', URI.file('folder1')))\n    assert.ok(!testObject.affectsConfiguration('workbench', URI.file('folder3')))\n\n    assert.ok(!testObject.affectsConfiguration('files'))\n    assert.ok(!testObject.affectsConfiguration('files', URI.file('folder1')))\n    assert.ok(!testObject.affectsConfiguration('files', URI.file(join('folder1', 'file1'))))\n    assert.ok(!testObject.affectsConfiguration('files', URI.file('folder2')))\n    assert.ok(!testObject.affectsConfiguration('files', URI.file(join('folder2', 'file2'))))\n    assert.ok(!testObject.affectsConfiguration('files', URI.file('folder3')))\n    assert.ok(!testObject.affectsConfiguration('files', URI.file(join('folder3', 'file3'))))\n  })\n\n  test('changeEvent - all', () => {\n    const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel())\n    configuration.updateFolderConfiguration(URI.file('file1').fsPath, toConfigurationModel({ 'window.zoomLevel': 2, 'window.restoreFullscreen': true }))\n    const data = configuration.toData()\n    const change = mergeChanges(\n      configuration.compareAndUpdateDefaultConfiguration(toConfigurationModel({\n        'editor.lineNumbers': 'off',\n        '[markdown]': {\n          'editor.wordWrap': 'off'\n        }\n      }), ['editor.lineNumbers', '[markdown]']),\n      configuration.compareAndUpdateUserConfiguration(toConfigurationModel({\n        '[json]': {\n          'editor.lineNumbers': 'relative'\n        }\n      })),\n      configuration.compareAndUpdateWorkspaceConfiguration(toConfigurationModel({ 'window.title': 'custom' })),\n      configuration.compareAndDeleteFolderConfiguration(URI.file('file1').fsPath),\n      configuration.compareAndUpdateFolderConfiguration(URI.file('file2').fsPath, toConfigurationModel({ 'workbench.editor.enablePreview': true, 'window.restoreWindows': true })))\n    const testObject = new ConfigurationChangeEvent(change, data, configuration)\n    assert.deepStrictEqual(testObject.affectedKeys, ['editor.lineNumbers', '[markdown]', '[json]', 'window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows', 'editor.wordWrap'])\n\n    assert.ok(testObject.affectsConfiguration('window.title'))\n    assert.ok(testObject.affectsConfiguration('window.title', URI.file('file1')))\n    assert.ok(testObject.affectsConfiguration('window.title', URI.file('file2')))\n\n    assert.ok(testObject.affectsConfiguration('window'))\n    assert.ok(testObject.affectsConfiguration('window', URI.file('file1')))\n    assert.ok(testObject.affectsConfiguration('window', URI.file('file2')))\n\n    assert.ok(testObject.affectsConfiguration('window.zoomLevel'))\n    assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file('file1')))\n    assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file('file2')))\n\n    assert.ok(testObject.affectsConfiguration('window.restoreFullscreen'))\n    assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file1')))\n    assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file2')))\n\n    assert.ok(testObject.affectsConfiguration('window.restoreWindows'))\n    assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file('file2')))\n    assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file('file1')))\n\n    assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview'))\n    assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('file2')))\n    assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('file1')))\n\n    assert.ok(testObject.affectsConfiguration('workbench.editor'))\n    assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file('file2')))\n    assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file('file1')))\n\n    assert.ok(testObject.affectsConfiguration('workbench'))\n    assert.ok(testObject.affectsConfiguration('workbench', URI.file('file2')))\n    assert.ok(!testObject.affectsConfiguration('workbench', URI.file('file1')))\n\n    assert.ok(!testObject.affectsConfiguration('files'))\n    assert.ok(!testObject.affectsConfiguration('files', URI.file('file1')))\n    assert.ok(!testObject.affectsConfiguration('files', URI.file('file2')))\n\n    assert.ok(testObject.affectsConfiguration('editor'))\n    assert.ok(testObject.affectsConfiguration('editor', URI.file('file1')))\n    assert.ok(testObject.affectsConfiguration('editor', URI.file('file2')))\n    assert.ok(testObject.affectsConfiguration('editor', { uri: URI.file('file1').toString(), languageId: 'json' }))\n    assert.ok(testObject.affectsConfiguration('editor', { uri: URI.file('file1').toString(), languageId: 'markdown' }))\n    assert.ok(testObject.affectsConfiguration('editor', { uri: URI.file('file1').toString(), languageId: 'typescript' }))\n    assert.ok(testObject.affectsConfiguration('editor', { uri: URI.file('file2').toString(), languageId: 'json' }))\n    assert.ok(testObject.affectsConfiguration('editor', { uri: URI.file('file2').toString(), languageId: 'markdown' }))\n    assert.ok(testObject.affectsConfiguration('editor', { uri: URI.file('file2').toString(), languageId: 'typescript' }))\n\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers'))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers', URI.file('file1')))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers', URI.file('file2')))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { uri: URI.file('file1').toString(), languageId: 'json' }))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { uri: URI.file('file1').toString(), languageId: 'markdown' }))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { uri: URI.file('file1').toString(), languageId: 'typescript' }))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { uri: URI.file('file2').toString(), languageId: 'json' }))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { uri: URI.file('file2').toString(), languageId: 'markdown' }))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { uri: URI.file('file2').toString(), languageId: 'typescript' }))\n\n    assert.ok(testObject.affectsConfiguration('editor.wordWrap'))\n    assert.ok(!testObject.affectsConfiguration('editor.wordWrap', URI.file('file1')))\n    assert.ok(!testObject.affectsConfiguration('editor.wordWrap', URI.file('file2')))\n    assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { uri: URI.file('file1').toString(), languageId: 'json' }))\n    assert.ok(testObject.affectsConfiguration('editor.wordWrap', { uri: URI.file('file1').toString(), languageId: 'markdown' }))\n    assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { uri: URI.file('file1').toString(), languageId: 'typescript' }))\n    assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { uri: URI.file('file2').toString(), languageId: 'json' }))\n    assert.ok(testObject.affectsConfiguration('editor.wordWrap', { uri: URI.file('file2').toString(), languageId: 'markdown' }))\n    assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { uri: URI.file('file2').toString(), languageId: 'typescript' }))\n\n    assert.ok(!testObject.affectsConfiguration('editor.fontSize'))\n    assert.ok(!testObject.affectsConfiguration('editor.fontSize', URI.file('file1')))\n    assert.ok(!testObject.affectsConfiguration('editor.fontSize', URI.file('file2')))\n  })\n\n  test('changeEvent affecting tasks and launches', () => {\n    const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel())\n    const change = configuration.compareAndUpdateUserConfiguration(toConfigurationModel({\n      launch: {\n        configuration: {}\n      },\n      'launch.version': 1,\n      tasks: {\n        version: 2\n      }\n    }))\n    const testObject = new ConfigurationChangeEvent(change, undefined, configuration)\n\n    assert.deepStrictEqual(testObject.affectedKeys, ['launch', 'launch.version', 'tasks'])\n    assert.ok(testObject.affectsConfiguration('launch'))\n    assert.ok(testObject.affectsConfiguration('launch.version'))\n    assert.ok(testObject.affectsConfiguration('tasks'))\n  })\n})\n\ndescribe('AllKeysConfigurationChangeEvent', () => {\n\n  test('changeEvent', () => {\n    const configuration = new Configuration(new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel())\n    configuration.updateDefaultConfiguration(toConfigurationModel({\n      'editor.lineNumbers': 'off',\n      '[markdown]': {\n        'editor.wordWrap': 'off'\n      }\n    }))\n    configuration.updateUserConfiguration(toConfigurationModel({\n      '[json]': {\n        'editor.lineNumbers': 'relative'\n      }\n    }))\n    configuration.updateWorkspaceConfiguration(toConfigurationModel({ 'window.title': 'custom' }))\n    configuration.updateFolderConfiguration(URI.file('file1').fsPath, toConfigurationModel({ 'window.zoomLevel': 2, 'window.restoreFullscreen': true }))\n    configuration.updateFolderConfiguration(URI.file('file2').fsPath, toConfigurationModel({ 'workbench.editor.enablePreview': true, 'window.restoreWindows': true }))\n    const testObject = new AllKeysConfigurationChangeEvent(configuration, ConfigurationTarget.User)\n\n    assert.deepStrictEqual(testObject.affectedKeys, ['editor.lineNumbers', '[markdown]', '[json]', 'window.title', 'window.zoomLevel', 'window.restoreFullscreen', 'workbench.editor.enablePreview', 'window.restoreWindows'])\n\n    assert.ok(testObject.affectsConfiguration('window.title'))\n    assert.ok(testObject.affectsConfiguration('window.title', URI.file('file1')))\n    assert.ok(testObject.affectsConfiguration('window.title', URI.file('file2')))\n\n    assert.ok(testObject.affectsConfiguration('window'))\n    assert.ok(testObject.affectsConfiguration('window', URI.file('file1')))\n    assert.ok(testObject.affectsConfiguration('window', URI.file('file2')))\n\n    assert.ok(testObject.affectsConfiguration('window.zoomLevel'))\n    assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file('file1')))\n    assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file('file2')))\n\n    assert.ok(testObject.affectsConfiguration('window.restoreFullscreen'))\n    assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file1')))\n    assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file2')))\n\n    assert.ok(testObject.affectsConfiguration('window.restoreWindows'))\n    assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file('file2')))\n    assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file('file1')))\n\n    assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview'))\n    assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('file2')))\n    assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('file1')))\n\n    assert.ok(testObject.affectsConfiguration('workbench.editor'))\n    assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file('file2')))\n    assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file('file1')))\n\n    assert.ok(testObject.affectsConfiguration('workbench'))\n    assert.ok(testObject.affectsConfiguration('workbench', URI.file('file2')))\n    assert.ok(!testObject.affectsConfiguration('workbench', URI.file('file1')))\n\n    assert.ok(!testObject.affectsConfiguration('files'))\n    assert.ok(!testObject.affectsConfiguration('files', URI.file('file1')))\n    assert.ok(!testObject.affectsConfiguration('files', URI.file('file2')))\n\n    assert.ok(testObject.affectsConfiguration('editor'))\n    assert.ok(testObject.affectsConfiguration('editor', URI.file('file1')))\n    assert.ok(testObject.affectsConfiguration('editor', URI.file('file2')))\n    assert.ok(testObject.affectsConfiguration('editor', { uri: URI.file('file1').toString(), languageId: 'json' }))\n    assert.ok(testObject.affectsConfiguration('editor', { uri: URI.file('file1').toString(), languageId: 'markdown' }))\n    assert.ok(testObject.affectsConfiguration('editor', { uri: URI.file('file1').toString(), languageId: 'typescript' }))\n    assert.ok(testObject.affectsConfiguration('editor', { uri: URI.file('file2').toString(), languageId: 'json' }))\n    assert.ok(testObject.affectsConfiguration('editor', { uri: URI.file('file2').toString(), languageId: 'markdown' }))\n    assert.ok(testObject.affectsConfiguration('editor', { uri: URI.file('file2').toString(), languageId: 'typescript' }))\n\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers'))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers', URI.file('file1')))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers', URI.file('file2')))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { uri: URI.file('file1').toString(), languageId: 'json' }))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { uri: URI.file('file1').toString(), languageId: 'markdown' }))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { uri: URI.file('file1').toString(), languageId: 'typescript' }))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { uri: URI.file('file2').toString(), languageId: 'json' }))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { uri: URI.file('file2').toString(), languageId: 'markdown' }))\n    assert.ok(testObject.affectsConfiguration('editor.lineNumbers', { uri: URI.file('file2').toString(), languageId: 'typescript' }))\n\n    assert.ok(!testObject.affectsConfiguration('editor.wordWrap'))\n    assert.ok(!testObject.affectsConfiguration('editor.wordWrap', URI.file('file1')))\n    assert.ok(!testObject.affectsConfiguration('editor.wordWrap', URI.file('file2')))\n    assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { uri: URI.file('file1').toString(), languageId: 'json' }))\n    assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { uri: URI.file('file1').toString(), languageId: 'markdown' }))\n    assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { uri: URI.file('file1').toString(), languageId: 'typescript' }))\n    assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { uri: URI.file('file2').toString(), languageId: 'json' }))\n    assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { uri: URI.file('file2').toString(), languageId: 'markdown' }))\n    assert.ok(!testObject.affectsConfiguration('editor.wordWrap', { uri: URI.file('file2').toString(), languageId: 'typescript' }))\n\n    assert.ok(!testObject.affectsConfiguration('editor.fontSize'))\n    assert.ok(!testObject.affectsConfiguration('editor.fontSize', URI.file('file1')))\n    assert.ok(!testObject.affectsConfiguration('editor.fontSize', URI.file('file2')))\n  })\n})\n"
  },
  {
    "path": "src/__tests__/configuration/configurations.test.ts",
    "content": "import fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport { v1 as uuid } from 'uuid'\nimport { Disposable } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport Configurations, { folderSettingsSchemaId, userSettingsSchemaId } from '../../configuration'\nimport { ConfigurationModel } from '../../configuration/model'\nimport ConfigurationProxy from '../../configuration/shape'\nimport { FolderConfigutions } from '../../configuration/configuration'\nimport { ConfigurationTarget, ConfigurationUpdateTarget } from '../../configuration/types'\nimport { disposeAll, wait } from '../../util'\nimport { remove } from '../../util/fs'\nimport helper from '../helper'\nimport { resourceLanguageSettingsSchemaId } from '../../configuration/registry'\nimport { CONFIG_FILE_NAME } from '../../util/constants'\n\nconst workspaceConfigFile = path.resolve(__dirname, `../sample/.vim/${CONFIG_FILE_NAME}`)\n\nfunction U(fsPath: string): string {\n  return URI.file(fsPath).toString()\n}\n\nfunction createConfigurations(): Configurations {\n  let userConfigFile = path.join(__dirname, './settings.json')\n  return new Configurations(userConfigFile)\n}\n\nconst disposables: Disposable[] = []\n\nafterEach(() => {\n  disposeAll(disposables)\n})\n\nfunction generateTmpDir(): string {\n  return path.join(os.tmpdir(), uuid())\n}\n\ndescribe('FolderConfigutions', () => {\n  it('should getConfigurationByResource', async () => {\n    let c = new FolderConfigutions()\n    expect(c.getConfigurationByResource('')).toBeUndefined()\n    expect(c.getConfigurationByResource('file:///a')).toBeUndefined()\n    let model = new ConfigurationModel()\n    c.set(os.tmpdir(), model)\n    let uri = URI.file(path.join(os.tmpdir(), 'a/foo.js')).toString()\n    let res = c.getConfigurationByResource(uri)\n    expect(res.model).toBe(model)\n  })\n})\n\ndescribe('Configurations', () => {\n  describe('markdownPreference', () => {\n    it('should get markdown preferences', async () => {\n      let configurations = createConfigurations()\n      let preferences = configurations.markdownPreference\n      expect(preferences).toEqual({\n        excludeImages: true,\n        breaks: true\n      })\n    })\n  })\n\n  describe('ConfigurationProxy', () => {\n    it('should create file and parent folder when necessary', async () => {\n      let folder = generateTmpDir()\n      let uri = URI.file(path.join(folder, 'a/b/settings.json'))\n      let proxy = new ConfigurationProxy({}, false)\n      await proxy.modifyConfiguration(uri.fsPath, 'foo', true)\n      let content = fs.readFileSync(uri.fsPath, 'utf8')\n      expect(JSON.parse(content)).toEqual({ foo: true })\n      await proxy.modifyConfiguration(uri.fsPath, 'foo', false)\n      content = fs.readFileSync(uri.fsPath, 'utf8')\n      expect(JSON.parse(content)).toEqual({ foo: false })\n      await remove(folder)\n    })\n\n    it('should get folder from resolver', async () => {\n      let proxy = new ConfigurationProxy({\n        getWorkspaceFolder: (uri: string) => {\n          let fsPath = URI.parse(uri).fsPath\n          if (fsPath.startsWith(os.tmpdir())) {\n            return { uri: URI.file(os.tmpdir()).toString(), name: 'tmp' }\n          }\n          if (fsPath.startsWith(os.homedir())) {\n            return { uri: URI.file(os.homedir()).toString(), name: 'home' }\n          }\n          return undefined\n        },\n        root: __dirname\n      })\n      let uri = proxy.getWorkspaceFolder(URI.file(path.join(os.tmpdir(), 'foo')).toString())\n      expect(uri.fsPath.startsWith(os.tmpdir())).toBe(true)\n      uri = proxy.getWorkspaceFolder(URI.file('abc').toString())\n      expect(uri).toBeUndefined()\n      proxy = new ConfigurationProxy({})\n      uri = proxy.getWorkspaceFolder(URI.file(path.join(os.tmpdir(), 'foo')).toString())\n      expect(uri).toBeUndefined()\n    })\n  })\n\n  describe('watchFile', () => {\n    it('should watch user config file', async () => {\n      let userConfigFile = path.join(os.tmpdir(), `settings-${uuid()}.json`)\n      fs.writeFileSync(userConfigFile, '{\"foo.bar\": true}', { encoding: 'utf8' })\n      let conf = new Configurations(userConfigFile, undefined, false)\n      disposables.push(conf)\n      expect(conf.getDefaultResource()).toBe(undefined)\n      await wait(50)\n      fs.writeFileSync(userConfigFile, '{\"foo.bar\": false}', { encoding: 'utf8' })\n      await helper.waitValue(() => {\n        let c = conf.getConfiguration('foo')\n        return c.get('bar')\n      }, false)\n      fs.rmSync(userConfigFile, { recursive: true })\n    })\n\n    it('should watch folder config file', async () => {\n      let dir = generateTmpDir()\n      let configFile = path.join(dir, '.vim/coc-settings.json')\n      fs.mkdirSync(path.dirname(configFile), { recursive: true })\n      fs.writeFileSync(configFile, '{\"foo.bar\": true}', { encoding: 'utf8' })\n      let conf = new Configurations('', {\n        get root() {\n          return dir\n        },\n        modifyConfiguration: async () => {},\n        getWorkspaceFolder: () => {\n          return URI.file(dir)\n        }\n      }, false)\n      expect(conf.getDefaultResource()).toMatch('file:')\n      disposables.push(conf)\n      let uri = U(dir)\n      let resolved = conf.locateFolderConfigution(uri)\n      expect(resolved).toBeDefined()\n      await wait(20)\n      fs.writeFileSync(configFile, '{\"foo.bar\": false}', { encoding: 'utf8' })\n      await helper.waitValue(() => {\n        let c = conf.getConfiguration('foo')\n        return c.get('bar')\n      }, false)\n    })\n  })\n\n  describe('getJSONSchema()', () => {\n    it('should getJSONSchema', () => {\n      let userConfigFile = path.join(__dirname, '.vim/coc-settings.json')\n      let conf = new Configurations(userConfigFile, undefined)\n      expect(conf.getJSONSchema(userSettingsSchemaId)).toBeDefined()\n      expect(conf.getJSONSchema(folderSettingsSchemaId)).toBeDefined()\n      expect(conf.getJSONSchema(resourceLanguageSettingsSchemaId)).toBeDefined()\n      expect(conf.getJSONSchema('vscode://not_exists')).toBeUndefined()\n    })\n  })\n\n  describe('getDescription()', () => {\n    it('should get description', () => {\n      let userConfigFile = path.join(__dirname, '.vim/coc-settings.json')\n      let conf = new Configurations(userConfigFile, undefined)\n      expect(conf.getDescription('not_exists_key')).toBeUndefined()\n    })\n  })\n\n  describe('addFolderFile()', () => {\n    it('should not add invalid folder from cwd', async () => {\n      let userConfigFile = path.join(__dirname, '.vim/coc-settings.json')\n      let conf = new Configurations(userConfigFile, undefined, true, os.homedir())\n      let res = conf.folderToConfigfile(os.homedir())\n      expect(res).toBeUndefined()\n      res = conf.folderToConfigfile(__dirname)\n      expect(res).toBeUndefined()\n    })\n\n    it('should add folder as workspace configuration', () => {\n      let configurations = createConfigurations()\n      disposables.push(configurations)\n      let fired = false\n      configurations.onDidChange(() => {\n        fired = true\n      })\n      configurations.addFolderFile(workspaceConfigFile)\n      let resource = URI.file(path.resolve(workspaceConfigFile, '../../tmp'))\n      let c = configurations.getConfiguration('coc.preferences', resource)\n      let res = c.inspect('rootPath')\n      expect(res.key).toBe('coc.preferences.rootPath')\n      expect(res.workspaceFolderValue).toBe('./src')\n      expect(c.get('rootPath')).toBe('./src')\n      expect(fired).toBe(false)\n    })\n\n    it('should not add invalid folders', async () => {\n      let configurations = createConfigurations()\n      expect(configurations.addFolderFile('ab')).toBe(false)\n    })\n\n    it('should resolve folder configuration when possible', async () => {\n      let configurations = createConfigurations()\n      expect(configurations.locateFolderConfigution('test:///foo')).toBe(false)\n      let fsPath = path.join(__dirname, `../sample/abc`)\n      expect(configurations.locateFolderConfigution(URI.file(fsPath).toString())).toBe(true)\n      fsPath = path.join(__dirname, `../sample/foo`)\n      expect(configurations.locateFolderConfigution(URI.file(fsPath).toString())).toBe(true)\n    })\n  })\n\n  describe('getConfiguration()', () => {\n    it('should load default configurations', () => {\n      let conf = new Configurations(undefined, {\n        modifyConfiguration: async () => {}\n      })\n      disposables.push(conf)\n      expect(conf.configuration.defaults.contents.coc).toBeDefined()\n      let c = conf.getConfiguration('languageserver')\n      expect(c).toEqual({})\n      expect(c.has('not_exists')).toBe(false)\n    })\n\n    it('should load configuration without folder configuration', async () => {\n      let conf = new Configurations(undefined, {\n        root: path.join(path.dirname(__dirname), 'sample'),\n        modifyConfiguration: async () => {}\n      })\n      disposables.push(conf)\n      conf.addFolderFile(workspaceConfigFile)\n      let c = conf.getConfiguration('coc.preferences')\n      expect(c.rootPath).toBeDefined()\n      c = conf.getConfiguration('coc.preferences', null)\n      expect(c.rootPath).toBeUndefined()\n    })\n\n    it('should inspect configuration', async () => {\n      let conf = new Configurations()\n      let c = conf.getConfiguration('suggest')\n      let res = c.inspect('not_exists')\n      expect(res.defaultValue).toBeUndefined()\n      expect(res.globalValue).toBeUndefined()\n      expect(res.workspaceValue).toBeUndefined()\n      c = conf.getConfiguration()\n      res = c.inspect('not_exists')\n      expect(res.key).toBe('not_exists')\n    })\n\n    it('should update memory config #1', () => {\n      let conf = new Configurations()\n      let fn = jest.fn()\n      conf.onDidChange(e => {\n        expect(e.affectsConfiguration('x')).toBe(true)\n        fn()\n      })\n      conf.updateMemoryConfig({ x: 1 })\n      let config = conf.configuration.memory\n      expect(config.contents).toEqual({ x: 1 })\n      expect(fn).toHaveBeenCalled()\n      expect(conf.configuration.workspace).toBeDefined()\n    })\n\n    it('should update memory config #2', () => {\n      let conf = new Configurations()\n      conf.updateMemoryConfig({ x: 1 })\n      conf.updateMemoryConfig({ x: undefined })\n      let config = conf.configuration.user\n      expect(config.contents).toEqual({})\n    })\n\n    it('should update memory config #3', () => {\n      let conf = new Configurations()\n      conf.updateMemoryConfig({ 'suggest.floatConfig': { border: true } })\n      conf.updateMemoryConfig({ 'x.y': { foo: 1 } })\n      let val = conf.getConfiguration()\n      let res = val.get('suggest') as any\n      expect(res.floatConfig).toEqual({ border: true })\n      res = val.get('x.y') as any\n      expect(res).toEqual({ foo: 1 })\n    })\n\n    it('should handle errors', () => {\n      let tmpFile = path.join(os.tmpdir(), uuid())\n      fs.writeFileSync(tmpFile, '{\"x\":', 'utf8')\n      let conf = new Configurations(tmpFile)\n      disposables.push(conf)\n      let errors = conf.errors\n      expect(errors.size).toBeGreaterThan(0)\n    })\n\n    it('should get nested property', () => {\n      let config = createConfigurations()\n      disposables.push(config)\n      let conf = config.getConfiguration('servers.c')\n      let res = conf.get<string>('trace.server', '')\n      expect(res).toBe('verbose')\n    })\n\n    it('should get user and workspace configuration', () => {\n      let userConfigFile = path.join(__dirname, './settings.json')\n      let configurations = new Configurations(userConfigFile)\n      disposables.push(configurations)\n      let data = configurations.configuration.toData()\n      expect(data.user).toBeDefined()\n      expect(data.workspace).toBeDefined()\n      expect(data.defaults).toBeDefined()\n      let value = configurations.configuration.getValue(undefined, {})\n      expect(value.foo).toBeDefined()\n      expect(value.foo.bar).toBe(1)\n    })\n\n    it('should update configuration', async () => {\n      let configurations = createConfigurations()\n      disposables.push(configurations)\n      configurations.addFolderFile(workspaceConfigFile)\n      let resource = URI.file(path.resolve(workspaceConfigFile, '../..'))\n      let fn = jest.fn()\n      configurations.onDidChange(e => {\n        expect(e.affectsConfiguration('foo')).toBe(true)\n        expect(e.affectsConfiguration('foo.bar')).toBe(true)\n        expect(e.affectsConfiguration('foo.bar', 'file://tmp/foo.js')).toBe(false)\n        fn()\n      })\n      let config = configurations.getConfiguration('foo', resource)\n      let o = config.get<number>('bar')\n      expect(o).toBe(1)\n      await config.update('bar', 6)\n      config = configurations.getConfiguration('foo', resource)\n      expect(config.get<number>('bar')).toBe(6)\n      expect(fn).toHaveBeenCalledTimes(1)\n    })\n\n    it('should remove configuration', async () => {\n      let configurations = createConfigurations()\n      disposables.push(configurations)\n      configurations.addFolderFile(workspaceConfigFile)\n      let resource = URI.file(path.resolve(workspaceConfigFile, '../..'))\n      let fn = jest.fn()\n      configurations.onDidChange(e => {\n        expect(e.affectsConfiguration('foo')).toBe(true)\n        expect(e.affectsConfiguration('foo.bar')).toBe(true)\n        fn()\n      })\n      let config = configurations.getConfiguration('foo', resource)\n      let o = config.get<number>('bar')\n      expect(o).toBe(1)\n      await config.update('bar', null, true)\n      config = configurations.getConfiguration('foo', resource)\n      expect(config.get<any>('bar')).toBeUndefined()\n      expect(fn).toHaveBeenCalledTimes(1)\n    })\n  })\n\n  describe('changeConfiguration', () => {\n    it('should change workspace configuration', async () => {\n      let con = createConfigurations()\n      let m = new ConfigurationModel({ x: { a: 1 } }, ['x.a'])\n      con.changeConfiguration(ConfigurationTarget.Workspace, m, undefined)\n      let res = con.getConfiguration('x')\n      expect(res.a).toBe(1)\n    })\n\n    it('should change default configuration', async () => {\n      let m = new ConfigurationModel({ x: { a: 1 } }, ['x.a'])\n      let con = createConfigurations()\n      con.changeConfiguration(ConfigurationTarget.Default, m, undefined)\n      let res = con.getConfiguration('x')\n      expect(res.a).toBe(1)\n    })\n  })\n\n  describe('update()', () => {\n    it('should update workspace configuration', async () => {\n      let target = ConfigurationUpdateTarget.Workspace\n      let con = createConfigurations()\n      let res = con.getConfiguration()\n      await res.update('x', 3, target)\n      let val = con.getConfiguration().get('x')\n      expect(val).toBe(3)\n    })\n\n    it('should show error when workspace folder not resolved', async () => {\n      let called = false\n      let s = jest.spyOn(console, 'error').mockImplementation(() => {\n        called = true\n      })\n      let con = new Configurations(undefined, {\n        modifyConfiguration: async () => {},\n        getWorkspaceFolder: () => {\n          return undefined\n        }\n      })\n      let conf = con.getConfiguration(undefined, 'file:///1')\n      await conf.update('x', 3, ConfigurationUpdateTarget.WorkspaceFolder)\n      s.mockRestore()\n      expect(called).toBe(true)\n    })\n  })\n\n  describe('getWorkspaceConfigUri()', () => {\n    it('should not get config uri for undefined resource', async () => {\n      let conf = createConfigurations()\n      let res = conf.resolveWorkspaceFolderForResource()\n      expect(res).toBeUndefined()\n    })\n\n    it('should not get config folder same as home', async () => {\n      let conf = new Configurations(undefined, {\n        modifyConfiguration: async () => {},\n        getWorkspaceFolder: () => {\n          return URI.file(os.homedir())\n        }\n      })\n      let uri = U(__filename)\n      let res = conf.resolveWorkspaceFolderForResource(uri)\n      expect(res).toBeUndefined()\n    })\n\n    it('should create config file for workspace folder', async () => {\n      let folder = path.join(os.tmpdir(), `test-workspace-folder-${uuid()}`)\n      let conf = new Configurations(undefined, {\n        modifyConfiguration: async () => {},\n        getWorkspaceFolder: () => {\n          return URI.file(folder)\n        }\n      })\n      let res = conf.resolveWorkspaceFolderForResource('file:///1')\n      expect(res).toBe(folder)\n      let configFile = path.join(folder, '.vim/coc-settings.json')\n      expect(fs.existsSync(configFile)).toBe(true)\n      res = conf.resolveWorkspaceFolderForResource('file:///1')\n      expect(res).toBe(folder)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/configuration/settings.json",
    "content": "{\n  \"foo.bar\": 1,\n  \"bar.foo\": 2,\n  \"schema\": {\n    \"https://example.com\": \"*.yaml\"\n  },\n  \"servers\": {\n    \"c\": {\n      \"trace.server\": \"verbose\"\n    }\n  }\n}"
  },
  {
    "path": "src/__tests__/configuration/util.test.ts",
    "content": "import * as assert from 'assert'\nimport os from 'os'\nimport { ParseError } from 'jsonc-parser'\nimport { addToValueTree, toValuesTree, convertErrors, convertTarget, expand, expandObject, getConfigurationValue, getDefaultValue, mergeChanges, mergeConfigProperties, overrideIdentifiersFromKey, removeFromValueTree, scopeToOverrides, toJSONObject } from '../../configuration/util'\nimport { ConfigurationTarget, ConfigurationUpdateTarget } from '../../configuration/types'\n\ndescribe('Configuration utils', () => {\n  it('convert parse errors', () => {\n    let content = 'foo'\n    let errors: ParseError[] = []\n    errors.push({ error: 2, length: 10, offset: 1 })\n    let arr = convertErrors(content, errors)\n    expect(arr.length).toBe(1)\n  })\n\n  it('get default value', () => {\n    expect(getDefaultValue(undefined)).toBeNull()\n    expect(getDefaultValue('string')).toBe('')\n    expect(getDefaultValue(['string'])).toBe('')\n    expect(getDefaultValue('boolean')).toBe(false)\n    expect(getDefaultValue('integer')).toBe(0)\n    expect(getDefaultValue('number')).toBe(0)\n    expect(getDefaultValue('array')).toEqual([])\n    expect(getDefaultValue('object')).toEqual({})\n  })\n\n  it('should expand', () => {\n    expect(expand('${userHome}')).toBe(os.homedir())\n    expect(expand('${cwd}')).toBe(process.cwd())\n    expect(expand('${env:NODE_ENV}')).toBe('test')\n    expect(expand('${env:NOT_EXISTS}')).toBe('${env:NOT_EXISTS}')\n    expect(expandObject('${env:NODE_ENV}')).toBe('test')\n    expect(expandObject(undefined)).toBe(undefined)\n    let obj = {\n      list: ['${env:NODE_ENV}', '', 1],\n      val: '${env:NODE_ENV}'\n    }\n    let res = expandObject(obj)\n    expect(res).toEqual({ list: ['test', '', 1], val: 'test' })\n  })\n\n  it('should convertTarget', () => {\n    expect(convertTarget(ConfigurationUpdateTarget.Global)).toBe(ConfigurationTarget.User)\n    expect(convertTarget(ConfigurationUpdateTarget.Workspace)).toBe(ConfigurationTarget.Workspace)\n    expect(convertTarget(ConfigurationUpdateTarget.WorkspaceFolder)).toBe(ConfigurationTarget.WorkspaceFolder)\n  })\n\n  it('should scopeToOverrides', () => {\n    expect(scopeToOverrides(null)).toBeUndefined()\n  })\n\n  it('should get overrideIdentifiersFromKey', () => {\n    let res = overrideIdentifiersFromKey('[ ]')\n    expect(res).toEqual([])\n  })\n\n  it('should merge properties', () => {\n    let res = mergeConfigProperties({\n      foo: 'bar',\n      \"x.y.a\": \"x\",\n      \"x.y.b\": \"y\",\n      \"x.t\": \"z\"\n    })\n    expect(res).toEqual({\n      foo: 'bar', x: { y: { a: 'x', b: 'y' }, t: 'z' }\n    })\n  })\n\n  it('should toValuesTree', () => {\n    let res = toValuesTree({\n      'x.y.z': '${env:NODE_ENV}',\n      env: '${env:NODE_ENV}'\n    }, () => {}, true)\n    expect(res).toEqual({\n      x: {\n        y: {\n          z: 'test'\n        }\n      },\n      env: 'test'\n    })\n  })\n\n  it('should addToValueTree conflict #1', () => {\n    let fn = jest.fn()\n    let obj = { x: 66 }\n    addToValueTree(obj, 'x.y', '3', () => {\n      fn()\n    }, true)\n    addToValueTree(obj, 'x.y', '3', () => {})\n    expect(fn).toHaveBeenCalled()\n  })\n\n  it('should addToValueTree conflict #2', () => {\n    let fn = jest.fn()\n    addToValueTree(undefined, 'x', '3', () => {\n      fn()\n    })\n    addToValueTree(undefined, 'x', '3', () => {})\n    expect(fn).toHaveBeenCalled()\n  })\n\n  it('should addToValueTree conflict #3', () => {\n    let obj = { x: true }\n    let fn = jest.fn()\n    addToValueTree(obj, 'x.y', ['foo'], () => {\n      fn()\n    })\n    expect(fn).toHaveBeenCalled()\n  })\n\n  it('removeFromValueTree: remove a non existing key', () => {\n    let target = { a: { b: 2 } }\n    removeFromValueTree(target, 'c')\n    assert.deepStrictEqual(target, { a: { b: 2 } })\n    removeFromValueTree(target, 'c.d.e')\n    assert.deepStrictEqual(target, { a: { b: 2 } })\n  })\n\n  it('removeFromValueTree: remove a multi segmented key from an object that has only sub sections of the key', () => {\n    let target = { a: { b: 2 } }\n\n    removeFromValueTree(target, 'a.b.c')\n\n    assert.deepStrictEqual(target, { a: { b: 2 } })\n  })\n\n  it('removeFromValueTree: remove a single segmented key', () => {\n    let target = { a: 1 }\n\n    removeFromValueTree(target, 'a')\n\n    assert.deepStrictEqual(target, {})\n  })\n\n  it('removeFromValueTree: remove a single segmented key when its value is undefined', () => {\n    let target = { a: undefined }\n\n    removeFromValueTree(target, 'a')\n\n    assert.deepStrictEqual(target, {})\n  })\n\n  it('removeFromValueTree: remove a multi segmented key when its value is undefined', () => {\n    let target = { a: { b: 1 } }\n\n    removeFromValueTree(target, 'a.b')\n\n    assert.deepStrictEqual(target, {})\n  })\n\n  it('removeFromValueTree: remove a multi segmented key when its value is array', () => {\n    let target = { a: { b: [1] } }\n\n    removeFromValueTree(target, 'a.b')\n\n    assert.deepStrictEqual(target, {})\n  })\n\n  it('removeFromValueTree: remove a multi segmented key first segment value is array', () => {\n    let target = { a: [1] }\n\n    removeFromValueTree(target, 'a.0')\n\n    assert.deepStrictEqual(target, { a: [1] })\n  })\n\n  it('removeFromValueTree: remove when key is the first segment', () => {\n    let target = { a: { b: 1 } }\n\n    removeFromValueTree(target, 'a')\n\n    assert.deepStrictEqual(target, {})\n  })\n\n  it('removeFromValueTree: remove a multi segmented key when the first node has more values', () => {\n    let target = { a: { b: { c: 1 }, d: 1 } }\n\n    removeFromValueTree(target, 'a.b.c')\n\n    assert.deepStrictEqual(target, { a: { d: 1 } })\n  })\n\n  it('removeFromValueTree: remove a multi segmented key when in between node has more values', () => {\n    let target = { a: { b: { c: { d: 1 }, d: 1 } } }\n\n    removeFromValueTree(target, 'a.b.c.d')\n\n    assert.deepStrictEqual(target, { a: { b: { d: 1 } } })\n  })\n\n  it('removeFromValueTree: remove a multi segmented key when the last but one node has more values', () => {\n    let target = { a: { b: { c: 1, d: 1 } } }\n\n    removeFromValueTree(target, 'a.b.c')\n\n    assert.deepStrictEqual(target, { a: { b: { d: 1 } } })\n  })\n\n  it('should convert errors', () => {\n    let errors: ParseError[] = []\n    for (let i = 0; i < 17; i++) {\n      errors.push({\n        error: i,\n        offset: 0,\n        length: 10\n      })\n    }\n    // let res = convertErrors('file:///1', 'abc', errors)\n    // expect(res.length).toBe(17)\n  })\n\n  it('should get configuration value', () => {\n    let root = {\n      foo: {\n        bar: 1,\n        from: {\n          to: 2\n        }\n      },\n      bar: [1, 2]\n    }\n    let res = getConfigurationValue(root, 'foo.from.to', 1)\n    expect(res).toBe(2)\n    res = getConfigurationValue(root, 'foo.from', 1)\n    expect(res).toEqual({ to: 2 })\n  })\n\n  it('should get json object', () => {\n    let obj = [{ x: 1 }, { y: 2 }]\n    expect(toJSONObject(obj)).toEqual(obj)\n  })\n})\n\ndescribe('mergeChanges', () => {\n  test('merge only keys', () => {\n    const actual = mergeChanges({ keys: ['a', 'b'], overrides: [] }, { keys: ['c', 'd'], overrides: [] })\n    assert.deepStrictEqual(actual, { keys: ['a', 'b', 'c', 'd'], overrides: [] })\n  })\n\n  test('merge only keys with duplicates', () => {\n    const actual = mergeChanges({ keys: ['a', 'b'], overrides: [] }, { keys: ['c', 'd'], overrides: [] }, { keys: ['a', 'd', 'e'], overrides: [] })\n    assert.deepStrictEqual(actual, { keys: ['a', 'b', 'c', 'd', 'e'], overrides: [] })\n  })\n\n  test('merge only overrides', () => {\n    const actual = mergeChanges({ keys: [], overrides: [['a', ['1', '2']]] }, { keys: [], overrides: [['b', ['3', '4']]] })\n    assert.deepStrictEqual(actual, { keys: [], overrides: [['a', ['1', '2']], ['b', ['3', '4']]] })\n  })\n\n  test('merge only overrides with duplicates', () => {\n    const actual = mergeChanges({ keys: [], overrides: [['a', ['1', '2']], ['b', ['5', '4']]] }, { keys: [], overrides: [['b', ['3', '4']]] }, { keys: [], overrides: [['c', ['1', '4']], ['a', ['2', '3']]] })\n    assert.deepStrictEqual(actual, { keys: [], overrides: [['a', ['1', '2', '3']], ['b', ['5', '4', '3']], ['c', ['1', '4']]] })\n  })\n\n  test('merge', () => {\n    const actual = mergeChanges({ keys: ['b', 'b'], overrides: [['a', ['1', '2']], ['b', ['5', '4']]] }, { keys: ['b'], overrides: [['b', ['3', '4']]] }, { keys: ['c', 'a'], overrides: [['c', ['1', '4']], ['a', ['2', '3']]] })\n    assert.deepStrictEqual(actual, { keys: ['b', 'c', 'a'], overrides: [['a', ['1', '2', '3']], ['b', ['5', '4', '3']], ['c', ['1', '4']]] })\n  })\n\n  test('merge single change', () => {\n    const actual = mergeChanges({ keys: ['b', 'b'], overrides: [['a', ['1', '2']], ['b', ['5', '4']]] })\n    assert.deepStrictEqual(actual, { keys: ['b', 'b'], overrides: [['a', ['1', '2']], ['b', ['5', '4']]] })\n  })\n\n  test('merge no changes', () => {\n    const actual = mergeChanges()\n    assert.deepStrictEqual(actual, { keys: [], overrides: [] })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/core/autocmds.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationToken, Disposable, Emitter } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport { AutocmdItem, createCommand, toAutocmdOption } from '../../core/autocmds'\nimport events from '../../events'\nimport { TextDocumentContentProvider } from '../../provider'\nimport { disposeAll } from '../../util'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n  disposeAll(disposables)\n})\n\ndescribe('watchers', () => {\n  it('should watch options', async () => {\n    await events.fire('OptionSet', ['showmode', 0, 1])\n    let times = 0\n    let fn = () => {\n      times++\n    }\n    let disposable = workspace.watchOption('showmode', fn)\n    disposables.push(workspace.watchOption('showmode', jest.fn()))\n    nvim.command('set showmode', true)\n    expect(workspace.watchers.options.length).toBeGreaterThan(0)\n    await helper.waitValue(() => times, 1)\n    disposable.dispose()\n    nvim.command('set noshowmode', true)\n    await helper.wait(20)\n    expect(times).toBe(1)\n  })\n\n  it('should watch global', async () => {\n    await events.fire('GlobalChange', ['x', 0, 1])\n    let times = 0\n    let fn = () => {\n      times++\n    }\n    let disposable = workspace.watchGlobal('x', fn)\n    workspace.watchGlobal('x', undefined, disposables)\n    workspace.watchGlobal('x', undefined, disposables)\n    await nvim.command('let g:x = 1')\n    await helper.waitValue(() => times, 1)\n    disposable.dispose()\n    await nvim.command('let g:x = 2')\n    await helper.wait(20)\n    expect(times).toBe(1)\n  })\n\n  it('should show error on watch callback error', async () => {\n    let called = false\n    let fn = () => {\n      called = true\n      throw new Error('error')\n    }\n    workspace.watchOption('showmode', fn, disposables)\n    nvim.command('set showmode', true)\n    await helper.waitValue(() => called, true)\n    let line = await helper.getCmdline()\n    expect(line).toMatch('Error on OptionSet')\n    called = false\n    workspace.watchGlobal('y', fn, disposables)\n    await nvim.command('let g:y = 2')\n    await helper.waitValue(() => called, true)\n    line = await helper.getCmdline()\n    expect(line).toMatch('Error on GlobalChange')\n  })\n})\n\ndescribe('contentProvider', () => {\n  it('should not throw for scheme not registered', async () => {\n    await workspace.contentProvider.onBufReadCmd('not_exists', '')\n  })\n\n  it('should register document content provider', async () => {\n    let provider: TextDocumentContentProvider = {\n      provideTextDocumentContent: (_uri, _token): string => 'sample text'\n    }\n    workspace.registerTextDocumentContentProvider('test', provider)\n    await nvim.command('edit test://1')\n    let buf = await nvim.buffer\n    let lines = await buf.lines\n    expect(lines).toEqual(['sample text'])\n  })\n\n  it('should react on change event of document content provider', async () => {\n    let text = 'foo'\n    let emitter = new Emitter<URI>()\n    let event = emitter.event\n    let provider: TextDocumentContentProvider = {\n      onDidChange: event,\n      provideTextDocumentContent: (_uri, _token): string => text\n    }\n    workspace.registerTextDocumentContentProvider('jdk', provider)\n    await nvim.command('edit jdk://1')\n    let doc = await workspace.document\n    text = 'bar'\n    emitter.fire(URI.parse('jdk://1'))\n    await helper.waitFor('getline', ['.'], 'bar')\n    await nvim.command('bwipeout!')\n    await helper.waitValue(() => doc.attached, false)\n    emitter.fire(URI.parse('jdk://1'))\n  })\n})\n\ndescribe('setupDynamicAutocmd()', () => {\n  afterEach(() => {\n    nvim.command(`autocmd! coc_dynamic_autocmd`, true)\n  })\n\n  it('should create command', () => {\n    let res = createCommand(1, 'BufEnter', {\n      callback: () => {},\n      event: ['User Jump'],\n      once: true,\n      nested: true,\n      arglist: ['3', '4'],\n      request: true,\n    })\n    expect(res).toBe(`autocmd coc_dynamic_autocmd BufEnter ++once ++nested  call coc#rpc#request('doAutocmd', [1, 3, 4])`)\n  })\n\n  it('should convert to autocmd option ', () => {\n    let item = new AutocmdItem(1, {\n      stack: '',\n      buffer: 1,\n      pattern: '*.js',\n      once: true,\n      nested: true,\n      arglist: ['2', '3'],\n      event: 'BufEnter', callback: () => {}\n    })\n    let res = toAutocmdOption(item)\n    expect(res).toEqual({\n      group: \"coc_dynamic_autocmd\",\n      buffer: 1,\n      pattern: \"*.js\",\n      once: true,\n      nested: true,\n      command: \"call coc#rpc#notify('doAutocmd', [1, 2, 3])\"\n    })\n  })\n\n  it('should setup autocmd', async () => {\n    await nvim.setLine('foo')\n    let times = 0\n    let disposable = workspace.registerAutocmd({\n      event: ['CursorMoved'],\n      request: true,\n      callback: () => {\n        times++\n      }\n    })\n    nvim.command('doautocmd <nomodeline> CursorMoved', true)\n    await helper.waitValue(() => times, 1)\n    disposable.dispose()\n    await nvim.command('doautocmd <nomodeline> CursorMoved')\n    await helper.wait(10)\n    expect(times).toBe(1)\n  })\n\n  it('should not throw on autocmd callback error', async () => {\n    let called = false\n    let disposable = workspace.registerAutocmd({\n      event: 'CursorHold',\n      request: false,\n      callback: () => {\n        called = true\n        throw new Error('my error')\n      }\n    })\n    nvim.command('doautocmd <nomodeline> CursorHold', true)\n    await helper.waitValue(() => called, true)\n    disposable.dispose()\n  })\n\n  it('should setup user autocmd', async () => {\n    let called = false\n    workspace.registerAutocmd({\n      event: 'User CocJumpPlaceholder',\n      callback: () => {\n        called = true\n      }\n    })\n    await nvim.command('doautocmd <nomodeline> User CocJumpPlaceholder')\n    await helper.waitValue(() => called, true)\n  })\n})\n\ndescribe('doAutocmd()', () => {\n  it('should not throw when command id does not exist', async () => {\n    await workspace.autocmds.doAutocmd(999, [])\n  })\n\n  it('should cancel timeout request autocmd', async () => {\n    let cancelled = false\n    workspace.autocmds.registerAutocmd({\n      event: 'CursorMoved,CursorMovedI',\n      request: true,\n      callback: (token: CancellationToken) => {\n        return new Promise(resolve => {\n          let timer = setTimeout(() => {\n            resolve()\n          }, 5000)\n          token.onCancellationRequested(() => {\n            cancelled = true\n            clearTimeout(timer)\n            resolve()\n          })\n        })\n      },\n      stack: ''\n    })\n    let autocmds = workspace.autocmds.autocmds\n    let keys = autocmds.keys()\n    let max = Math.max(...Array.from(keys))\n    await workspace.autocmds.doAutocmd(max, [], 10)\n    expect(cancelled).toBe(true)\n  })\n\n  it('should dispose', async () => {\n    workspace.autocmds.dispose()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/core/documents.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport { v4 as uuid } from 'uuid'\nimport { Disposable } from 'vscode-languageserver-protocol'\nimport { LocationLink, Position, Range, TextEdit } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport Documents from '../../core/documents'\nimport events from '../../events'\nimport languages from '../../languages'\nimport BufferSync from '../../model/bufferSync'\nimport { disposeAll } from '../../util'\nimport workspace from '../../workspace'\nimport helper, { createTmpFile } from '../helper'\n\nlet documents: Documents\nlet nvim: Neovim\nlet disposables: Disposable[] = []\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  documents = workspace.documentsManager\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\ndescribe('BufferSync', () => {\n  it('should recreate document', async () => {\n    let doc = documents.getDocument(documents.bufnr)\n    let called = false\n    let sync = new BufferSync(doc => {\n      return {\n        bufnr: doc.bufnr,\n        dispose: () => {\n          called = true\n        }\n      }\n    }, documents)\n    sync.create(doc)\n    expect(called).toBe(true)\n  })\n})\n\ndescribe('documents', () => {\n  it('should convert filetype', () => {\n    const shouldConvert = (from: string, to: string): void => {\n      expect(documents.convertFiletype(from)).toBe(to)\n    }\n    shouldConvert('javascript.jsx', 'javascriptreact')\n    shouldConvert('typescript.jsx', 'typescriptreact')\n    shouldConvert('typescript.tsx', 'typescriptreact')\n    shouldConvert('tex', 'latex')\n    Object.assign(documents['_env']['filetypeMap'], { foo: 'bar' })\n    shouldConvert('foo', 'bar')\n  })\n\n  it('should get document', async () => {\n    await helper.createDocument('bar')\n    let doc = await helper.createDocument('foo')\n    let res = documents.getDocument(doc.uri)\n    expect(res.uri).toBe(doc.uri)\n    let uri = 'file:///' + doc.uri.slice(8).toUpperCase()\n    res = documents.getDocument(uri, true)\n    expect(res.uri).toBe(doc.uri)\n    res = documents.getDocument(uri, false)\n    expect(res).toBeNull()\n  })\n\n  it('should resolveRoot', async () => {\n    let res = documents.resolveRoot(['package.json'])\n    expect(res).toBeDefined()\n    expect(() => {\n      documents.resolveRoot(['unexpected file'], true)\n    }).toThrow(Error)\n    await helper.edit(__filename)\n    res = documents.resolveRoot(['package.json'])\n    expect(res).toBeDefined()\n  })\n\n  it('should consider lisp option for iskeyword', async () => {\n    await nvim.command(`e +setl\\\\ lisp t`)\n    let doc = await workspace.document\n    expect(doc.isWord('-')).toBe(true)\n  })\n\n  it('should get languageId', async () => {\n    await helper.createDocument('t.vim')\n    expect(documents.getLanguageId('/a/b')).toBe('')\n    expect(documents.getLanguageId('/a/b.vim')).toBe('vim')\n    expect(documents.getLanguageId('/a/b.c')).toBe('')\n  })\n\n  it('should get lines', async () => {\n    let doc = await helper.createDocument('tmp')\n    await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo\\nbar')])\n    let lines = await documents.getLines(doc.uri)\n    expect(lines).toEqual(['foo', 'bar'])\n    lines = await documents.getLines('lsptest:///1')\n    expect(lines).toEqual([])\n    lines = await documents.getLines('file:///not_exists_file')\n    expect(lines).toEqual([])\n    let uri = URI.file(__filename).toString()\n    lines = await documents.getLines(uri)\n    expect(lines.length).toBeGreaterThan(0)\n  })\n\n  it('should read empty string from none file', async () => {\n    let res = await documents.readFile('test:///1')\n    expect(res).toBe('')\n  })\n\n  it('should get empty line from none file', async () => {\n    let res = await documents.getLine('test:///1', 1)\n    expect(res).toBe('')\n    let uri = URI.file(path.join(__dirname, 'not_exists_file')).toString()\n    res = await documents.getLine(uri, 1)\n    expect(res).toBe('')\n  })\n\n  it('should convert filepath', () => {\n    Object.assign((documents as any)._env, { isCygwin: true, unixPrefix: '/cygdrive/' })\n    let filepath = documents.fixUnixPrefix('C:\\\\Users\\\\Local')\n    expect(filepath).toBe('/cygdrive/c/Users/Local')\n    Object.assign((documents as any)._env, { isCygwin: false })\n  })\n\n  it('should get QuickfixItem from location link', async () => {\n    let doc = await helper.createDocument('quickfix')\n    let loc = LocationLink.create(doc.uri, Range.create(0, 0, 3, 0), Range.create(0, 0, 0, 3))\n    let res = await documents.getQuickfixItem(loc, 'text', 'E', 'module')\n    expect(res.targetRange).toBeDefined()\n    expect(res.type).toBe('E')\n    expect(res.module).toBe('module')\n    expect(res.bufnr).toBe(doc.bufnr)\n  })\n\n  it('should create document', async () => {\n    await helper.createDocument()\n    let bufnrs = await nvim.call('coc#ui#open_files', [[__filename]]) as number[]\n    let bufnr = bufnrs[0]\n    let doc = workspace.getDocument(bufnr)\n    expect(doc).toBeUndefined()\n    doc = await documents.createDocument(bufnr)\n    expect(doc).toBeDefined()\n  })\n\n  it('should check buffer rename on save', async () => {\n    let doc = await workspace.document\n    let bufnr = doc.bufnr\n    let name = `${uuid()}.vim`\n    let tmpfile = path.join(os.tmpdir(), name)\n    await nvim.command(`write ${tmpfile}`)\n    doc = workspace.getDocument(bufnr)\n    expect(doc).toBeDefined()\n    expect(doc.filetype).toBe('vim')\n    expect(doc.bufname).toMatch(name)\n    fs.unlinkSync(tmpfile)\n  })\n\n  it('should get current document', async () => {\n    let p1 = workspace.document\n    let p2 = workspace.document\n    let arr = await Promise.all([p1, p2])\n    expect(arr[0]).toBe(arr[1])\n  })\n\n  it('should get bufnrs', async () => {\n    await workspace.document\n    let bufnrs = Array.from(documents.bufnrs)\n    expect(bufnrs.length).toBe(1)\n  })\n\n  it('should get uri', async () => {\n    let doc = await workspace.document\n    expect(documents.uri).toBe(doc.uri)\n  })\n\n  it('should get current uri', async () => {\n    let doc = await workspace.document\n    documents.detachBuffer(doc.bufnr)\n    let uri = await documents.getCurrentUri()\n    expect(uri).toBeUndefined()\n  })\n\n  it('should attach events on vim', async () => {\n    await documents.attach(nvim, workspace.env)\n    let env = Object.assign(workspace.env, { isVim: true })\n    documents.detach()\n    await documents.attach(nvim, env)\n    documents.detach()\n    await events.fire('CursorMoved', [1, [1, 1]])\n  })\n\n  it('should compute word ranges', async () => {\n    expect(await workspace.computeWordRanges('file:///1', Range.create(0, 0, 1, 0))).toBeNull()\n    let doc = await workspace.document\n    expect(await workspace.computeWordRanges(doc.uri, Range.create(0, 0, 1, 0))).toBeDefined()\n  })\n\n  it('should try code actions', async () => {\n    helper.updateConfiguration('editor.codeActionsOnSave', { 'source.fixAll': false }, disposables)\n    let doc = await workspace.document\n    let res = await documents.tryCodeActionsOnSave(doc)\n    expect(res).toBe(false)\n    helper.updateConfiguration('editor.codeActionsOnSave', {\n      'source.fixAll.eslint': true,\n      'source.organizeImports': 'always'\n    }, disposables)\n    res = await documents.tryCodeActionsOnSave(doc)\n    expect(res).toBe(true)\n  })\n\n  it('should not fire document event when filetype not changed', async () => {\n    let fn = jest.fn()\n    disposables.push(documents.onDidOpenTextDocument(e => {\n      fn()\n    }))\n    let doc = await workspace.document\n    doc.setFiletype('javascript')\n    documents.onFileTypeChange('javascript', doc.bufnr)\n    await helper.wait(10)\n    expect(fn).toHaveBeenCalledTimes(0)\n    doc.detach()\n    documents.onFileTypeChange('javascript', doc.bufnr)\n    await helper.wait(10)\n    expect(fn).toHaveBeenCalledTimes(0)\n  })\n\n  it('should fire document create once on reload', async () => {\n    await helper.createDocument('t.vim')\n    let called = false\n    disposables.push(documents.onDidOpenTextDocument(e => {\n      called = true\n    }))\n    await nvim.command('edit')\n    await helper.waitValue(() => called, true)\n  })\n})\n\ndescribe('formatOnSave', () => {\n  it('should not throw when provider not found', async () => {\n    helper.updateConfiguration('coc.preferences.formatOnSaveFiletypes', ['javascript'], disposables)\n    let filepath = await createTmpFile('')\n    await helper.edit(filepath)\n    await nvim.command('setf javascript')\n    await nvim.setLine('foo')\n    await nvim.command('silent w')\n  })\n\n  it('should invoke format on save', async () => {\n    helper.updateConfiguration('coc.preferences.formatOnSaveFiletypes', ['text'], disposables)\n    disposables.push(languages.registerDocumentFormatProvider(['text'], {\n      provideDocumentFormattingEdits: document => {\n        let lines = document.getText().replace(/\\n$/, '').split(/\\n/)\n        let edits: TextEdit[] = []\n        for (let i = 0; i < lines.length; i++) {\n          let text = lines[i]\n          if (!text.startsWith(' ')) {\n            edits.push(TextEdit.insert(Position.create(i, 0), '  '))\n          }\n        }\n        return edits\n      }\n    }))\n    let filepath = await createTmpFile('a\\nb\\nc\\n')\n    let buf = await helper.edit(filepath)\n    let doc = workspace.getDocument(buf.id)\n    doc.setFiletype('text')\n    await documents.tryFormatOnSave(doc)\n    let lines = await buf.lines\n    expect(lines).toEqual(['  a', '  b', '  c'])\n  })\n\n  it('should cancel when timeout', async () => {\n    helper.updateConfiguration('coc.preferences.formatOnSaveFiletypes', ['*'], disposables)\n    let timer\n    disposables.push(languages.registerDocumentFormatProvider(['*'], {\n      provideDocumentFormattingEdits: () => {\n        return new Promise(resolve => {\n          timer = setTimeout(() => {\n            resolve(undefined)\n          }, 2000)\n        })\n      }\n    }))\n    let filepath = await createTmpFile('a\\nb\\nc\\n')\n    await helper.edit(filepath)\n    let n = Date.now()\n    await nvim.command('w')\n    expect(Date.now() - n).toBeLessThan(1000)\n    clearTimeout(timer)\n  })\n\n  it('should enable format on save', async () => {\n    helper.updateConfiguration('coc.preferences.formatOnSaveFiletypes', null)\n    helper.updateConfiguration('coc.preferences.formatOnSave', true)\n    let doc = await workspace.document\n    let res = documents.shouldFormatOnSave(doc)\n    expect(res).toBe(false)\n    disposables.push(languages.registerDocumentFormatProvider(['*'], {\n      provideDocumentFormattingEdits: () => {\n        return []\n      }\n    }))\n    res = documents.shouldFormatOnSave(doc)\n    expect(res).toBe(true)\n  })\n\n  it('should not format on save when disabled', async () => {\n    helper.updateConfiguration('coc.preferences.formatOnSaveFiletypes', ['text'])\n    disposables.push(languages.registerDocumentFormatProvider(['text'], {\n      provideDocumentFormattingEdits: document => {\n        let lines = document.getText().replace(/\\n$/, '').split(/\\n/)\n        let edits: TextEdit[] = []\n        for (let i = 0; i < lines.length; i++) {\n          edits.push(TextEdit.insert(Position.create(0, 0), '  '))\n        }\n        return edits\n      }\n    }))\n    let filepath = await createTmpFile('a\\nb\\nc\\n')\n    nvim.pauseNotification()\n    nvim.command('e ' + filepath, true)\n    nvim.command('let b:coc_disable_autoformat = 1', true)\n    nvim.command('setf text', true)\n    await nvim.resumeNotification()\n    await nvim.command('w')\n    let buf = await nvim.buffer\n    let lines = await buf.lines\n    expect(lines).toEqual(['a', 'b', 'c'])\n  })\n})\n"
  },
  {
    "path": "src/__tests__/core/editors.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { Disposable } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport Editors, { TextEditor, renamed } from '../../core/editors'\nimport events from '../../events'\nimport { disposeAll } from '../../util'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet editors: Editors\nlet nvim: Neovim\nlet disposables: Disposable[] = []\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  editors = workspace.editors\n})\n\nafterEach(async () => {\n  await helper.reset()\n})\n\nafterAll(async () => {\n  disposeAll(disposables)\n  await helper.shutdown()\n})\n\ndescribe('util', () => {\n  it('should check renamed', async () => {\n    await helper.edit('foo')\n    let editor = editors.activeTextEditor\n    expect(renamed(editor, {\n      bufnr: 0,\n      fullpath: '',\n      tabid: 1,\n      winid: 1000,\n    })).toBe(false)\n    expect(renamed(editor, {\n      bufnr: editor.document.bufnr,\n      fullpath: '',\n      tabid: 1,\n      winid: 1000,\n    })).toBe(true)\n    expect(renamed(editor, {\n      bufnr: editor.document.bufnr,\n      fullpath: URI.parse(editor.document.uri).fsPath,\n      tabid: 1,\n      winid: 1000,\n    })).toBe(false)\n    Object.assign(editor, { uri: 'lsp:///1' })\n    expect(renamed(editor, {\n      bufnr: editor.document.bufnr,\n      fullpath: '',\n      tabid: 1,\n      winid: 1000,\n    })).toBe(false)\n  })\n})\n\ndescribe('editors', () => {\n\n  function assertEditor(editor: TextEditor, tabpagenr: number, winid: number) {\n    expect(editor).toBeDefined()\n    expect(editor.tabpageid).toBe(tabpagenr)\n    expect(editor.winid).toBe(winid)\n  }\n\n  it('should have active editor', async () => {\n    let winid = await nvim.call('win_getid') as number\n    let editor = window.activeTextEditor\n    assertEditor(editor, 1, winid)\n    let editors = window.visibleTextEditors\n    expect(editors.length).toBe(1)\n    workspace.editors.checkTabs([])\n    workspace.editors.checkUnloadedBuffers([])\n  })\n\n  it('should get winids of bufnr', () => {\n    let res = workspace.editors.getBufWinids(1000)\n    expect(res).toEqual([])\n  })\n\n  it('should create editor not created', async () => {\n    await nvim.command(`edit +setl\\\\ buftype=nofile foo`)\n    let doc = await workspace.document\n    await nvim.command('setl buftype=')\n    await events.fire('BufDetach', [doc.bufnr])\n    await events.fire('CursorHold', [doc.bufnr])\n    expect(window.activeTextEditor).toBeDefined()\n    expect(window.visibleTextEditors.length).toBe(1)\n  })\n\n  it('should detect buffer rename', async () => {\n    let doc = await helper.createDocument('foo')\n    await doc.buffer.setName('bar')\n    await events.fire('CursorHold', [doc.bufnr])\n    expect(window.activeTextEditor).toBeDefined()\n    expect(window.activeTextEditor.id).toMatch(/bar$/)\n  })\n\n  it('should detect buffer switch', async () => {\n    let doc = await helper.createDocument('foo')\n    await helper.createDocument('bar')\n    await nvim.command('noa b ' + doc.bufnr)\n    await events.fire('CursorHold', [doc.bufnr])\n    expect(window.activeTextEditor).toBeDefined()\n    expect(window.activeTextEditor.id).toMatch(/foo$/)\n  })\n\n  it('should change active editor on split', async () => {\n    let promise = new Promise<TextEditor>(resolve => {\n      editors.onDidChangeActiveTextEditor(e => {\n        resolve(e)\n      }, null, disposables)\n    })\n    await nvim.command('vnew')\n    let editor = await promise\n    let winid = await nvim.call('win_getid')\n    expect(editor.winid).toBe(winid)\n  })\n\n  it('should change active editor on tabe', async () => {\n    let promise = new Promise<TextEditor>(resolve => {\n      editors.onDidChangeActiveTextEditor(e => {\n        if (e.document.uri.includes('foo')) {\n          resolve(e)\n        }\n      }, null, disposables)\n    })\n    await nvim.command('tabe a | tabe b | tabe foo')\n    let editor = await promise\n    let winid = await nvim.call('win_getid')\n    expect(editor.winid).toBe(winid)\n  })\n\n  it('should change active editor on edit', async () => {\n    await nvim.call('win_getid')\n    let n = 0\n    let promise = new Promise<TextEditor>(resolve => {\n      window.onDidChangeVisibleTextEditors(() => {\n        n++\n      }, null, disposables)\n      editors.onDidChangeActiveTextEditor(e => {\n        n++\n        resolve(e)\n      })\n    })\n    await nvim.command('edit editors')\n    let editor = await promise\n    expect(editor.document.uri).toMatch('editors')\n    await helper.waitValue(() => {\n      return n >= 2\n    }, true)\n  })\n\n  it('should change active editor on window switch', async () => {\n    let winid = await nvim.call('win_getid')\n    await nvim.command('vs foo')\n    await nvim.command('wincmd p')\n    let curr = editors.activeTextEditor\n    expect(curr.winid).toBe(winid)\n    expect(editors.visibleTextEditors.length).toBe(2)\n  })\n\n  it('should cleanup on CursorHold', async () => {\n    let promise = new Promise<TextEditor>(resolve => {\n      editors.onDidChangeActiveTextEditor(e => {\n        if (e.document.uri.includes('foo')) {\n          resolve(e)\n        }\n      }, null, disposables)\n    })\n    await nvim.command('sp foo')\n    await promise\n    await nvim.command('noa close')\n    let bufnr = await nvim.eval(\"bufnr('%')\")\n    await events.fire('CursorHold', [bufnr])\n    expect(editors.visibleTextEditors.length).toBe(1)\n  })\n\n  it('should cleanup on create', async () => {\n    let winid = await nvim.call('win_getid')\n    let promise = new Promise<TextEditor>(resolve => {\n      editors.onDidChangeActiveTextEditor(e => {\n        if (e.document.uri.includes('foo')) {\n          resolve(e)\n        }\n      }, null, disposables)\n    })\n    await nvim.command('tabe foo')\n    await promise\n    await nvim.call('win_execute', [winid, 'noa close'])\n    await nvim.command('edit bar')\n  })\n\n  it('should have current tabpageid after tab changed', async () => {\n    await nvim.command('tabe|doautocmd CursorHold')\n    await helper.waitValue(() => {\n      return editors.visibleTextEditors.length\n    }, 2)\n    let ids: number[] = []\n    editors.visibleTextEditors.forEach(editor => {\n      ids.push(editor.tabpageid)\n    })\n    let editor = editors.visibleTextEditors[editors.visibleTextEditors.length - 1]\n    let previousId = editor.tabpageid\n    await nvim.command('normal! 1gt')\n    await nvim.command('tabe')\n    await helper.waitValue(() => {\n      return editors.visibleTextEditors.length\n    }, 3)\n    expect(editor.tabpageid).toBe(previousId)\n    let tid: number\n    let disposable = editors.onDidTabClose(id => {\n      tid = id\n    })\n    await nvim.command('tabc')\n    await helper.waitValue(() => {\n      return editors.visibleTextEditors.length\n    }, 2)\n    disposable.dispose()\n    expect(editor.tabpageid).toBe(previousId)\n    expect(tid).toBeDefined()\n    editor = editors.visibleTextEditors.find(o => o.tabpageid == tid)\n    expect(editor).toBeUndefined()\n  })\n\n  it('should recreate editor on document reload', async () => {\n    let doc = await helper.createDocument('foo')\n    let bufnr = doc.bufnr\n    await nvim.command('edit!')\n    await helper.waitValue(() => {\n      return workspace.getDocument(bufnr) !== doc\n    }, true)\n    doc = workspace.getDocument(bufnr)\n    expect(editors.activeTextEditor.document.bufnr).toBe(bufnr)\n    expect(editors.activeTextEditor.document === doc).toBe(true)\n    await nvim.command('setf javascript')\n    await helper.waitValue(() => {\n      return doc.filetype\n    }, 'javascript')\n    expect(editors.activeTextEditor.document.filetype).toBe('javascript')\n  })\n})\n\ndescribe('Tabs', () => {\n  it('should attach tabs', async () => {\n    let doc = await workspace.document\n    expect(workspace.tabs.isActive(doc.textDocument)).toBe(true)\n    expect(workspace.tabs.isActive(URI.parse(doc.uri))).toBe(true)\n    expect(workspace.tabs.isVisible(doc.textDocument)).toBe(true)\n    expect(workspace.tabs.isVisible(URI.parse(doc.uri))).toBe(true)\n    workspace.editors['winid'] = 1\n    expect(workspace.tabs.isActive(URI.parse(doc.uri))).toBe(false)\n    let resources = workspace.tabs.getTabResources()\n    expect(resources.size).toBeGreaterThan(0)\n  })\n\n  it('should fire open and close event', async () => {\n    let tabs = workspace.tabs\n    let fn = jest.fn()\n    let disposable = tabs.onOpen(() => {\n      fn()\n    })\n    nvim.command('tabe foo', true)\n    nvim.command('tabe foo', true)\n    await helper.waitValue(() => {\n      return tabs.getTabResources().size\n    }, 2)\n    disposable.dispose()\n    expect(fn).toHaveBeenCalledTimes(1)\n    nvim.command('bd', true)\n    fn = jest.fn()\n    disposable = tabs.onClose(() => {\n      fn()\n    })\n    await helper.waitValue(() => {\n      return tabs.getTabResources().size\n    }, 1)\n    disposable.dispose()\n    expect(fn).toHaveBeenCalledTimes(1)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/core/fileSystemWatcher.test.ts",
    "content": "import bser from 'bser'\nimport net from 'net'\nimport os from 'os'\nimport path from 'path'\nimport { v4 as uuid } from 'uuid'\nimport { Disposable } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport Configurations from '../../configuration/index'\nimport { FileSystemWatcher, FileSystemWatcherManager } from '../../core/fileSystemWatcher'\nimport Watchman, { FileChangeItem } from '../../core/watchman'\nimport WorkspaceFolderController from '../../core/workspaceFolder'\nimport RelativePattern from '../../model/relativePattern'\nimport { GlobPattern } from '../../types'\nimport { disposeAll } from '../../util'\nimport { remove } from '../../util/fs'\nimport helper from '../helper'\n\nlet server: net.Server\nlet client: net.Socket\nconst cwd = path.resolve(__dirname, '../../..')\nconst sockPath = path.join(os.tmpdir(), `watchman-fake-${uuid()}`)\nprocess.env.WATCHMAN_SOCK = sockPath\n\nlet workspaceFolder: WorkspaceFolderController\nlet watcherManager: FileSystemWatcherManager\nlet configurations: Configurations\nlet disposables: Disposable[] = []\n\nfunction wait(ms: number): Promise<any> {\n  return new Promise(resolve => {\n    setTimeout(() => {\n      resolve(undefined)\n    }, ms)\n  })\n}\n\nfunction createFileChange(file: string, isNew = true, exists = true): FileChangeItem {\n  return {\n    size: 1,\n    name: file,\n    exists,\n    new: isNew,\n    type: 'f',\n    mtime_ms: Date.now()\n  }\n}\n\nfunction sendResponse(data: any): void {\n  client.write(bser.dumpToBuffer(data))\n}\n\nfunction sendSubscription(uid: string, root: string, files: FileChangeItem[]): void {\n  client.write(bser.dumpToBuffer({\n    subscription: uid,\n    root,\n    files\n  }))\n}\n\nlet capabilities: any\nlet watchResponse: any\nlet defaultConfig = { watchmanPath: null, enable: true, ignoredFolders: [] }\nbeforeAll(async () => {\n  await helper.setup()\n})\n\nbeforeAll(done => {\n  let userConfigFile = path.join(process.env.COC_VIMCONFIG, 'coc-settings.json')\n  configurations = new Configurations(userConfigFile, undefined)\n  workspaceFolder = new WorkspaceFolderController(configurations)\n  watcherManager = new FileSystemWatcherManager(workspaceFolder, defaultConfig)\n  Object.assign(watcherManager, { disabled: false })\n  watcherManager.attach(helper.createNullChannel())\n  // create a mock sever for watchman\n  server = net.createServer(c => {\n    client = c\n    c.on('data', data => {\n      let obj = bser.loadFromBuffer(data)\n      if (obj[0] == 'watch-project') {\n        sendResponse(watchResponse || { watch: obj[1], warning: 'warning' })\n      } else if (obj[0] == 'unsubscribe') {\n        sendResponse({ path: obj[1] })\n      } else if (obj[0] == 'clock') {\n        sendResponse({ clock: 'clock' })\n      } else if (obj[0] == 'version') {\n        let { optional, required } = obj[1]\n        let res = {}\n        for (let key of optional) {\n          res[key] = true\n        }\n        for (let key of required) {\n          res[key] = true\n        }\n        sendResponse({ capabilities: capabilities || res })\n      } else if (obj[0] == 'subscribe') {\n        sendResponse({ subscribe: obj[2] })\n      } else {\n        sendResponse({})\n      }\n    })\n  })\n  server.on('error', err => {\n    throw err\n  })\n  server.listen(sockPath, () => {\n    done()\n  })\n  server.unref()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  capabilities = undefined\n  watchResponse = undefined\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n  watcherManager.dispose()\n  server.close()\n  await remove(sockPath)\n})\n\ndescribe('watchman', () => {\n  it('should not throw error when not watching', async () => {\n    let client = new Watchman(null)\n    disposables.push(client)\n    let disposable = client.subscribe('**/*', () => {})\n    disposable.dispose()\n    client.dispose()\n  })\n\n  it('should checkCapability', async () => {\n    let client = new Watchman(null)\n    let res = await client.checkCapability()\n    expect(res).toBe(true)\n    capabilities = { relative_root: false }\n    res = await client.checkCapability()\n    expect(res).toBe(false)\n    client.dispose()\n  })\n\n  it('should watchProject', async () => {\n    let client = new Watchman(null)\n    disposables.push(client)\n    let res = await client.watchProject(__dirname)\n    expect(res).toBe(true)\n    client.dispose()\n  })\n\n  it('should unsubscribe', async () => {\n    let client = new Watchman(null)\n    disposables.push(client)\n    await client.watchProject(cwd)\n    let fn = jest.fn()\n    let disposable = client.subscribe(`${cwd}/*`, fn)\n    disposable.dispose()\n    client.dispose()\n  })\n})\n\ndescribe('Watchman#subscribe', () => {\n\n  it('should subscribe file change', async () => {\n    let client = new Watchman(null, helper.createNullChannel())\n    disposables.push(client)\n    await client.watchProject(cwd)\n    let called = false\n    let disposable = client.subscribe(`${cwd}/*`, () => {\n      called = true\n    })\n    let changes: FileChangeItem[] = [createFileChange(`${cwd}/a`)]\n    sendSubscription(client.subscription, cwd, changes)\n    await helper.wait(30)\n    expect(called).toBe(true)\n    disposable.dispose()\n    client.dispose()\n  })\n\n  it('should subscribe with relative_path', async () => {\n    let client = new Watchman(null, helper.createNullChannel())\n    watchResponse = { watch: cwd, relative_path: 'foo' }\n    await client.watchProject(cwd)\n    let fn = jest.fn()\n    let disposable = client.subscribe(`${cwd}/*`, fn)\n    let changes: FileChangeItem[] = [createFileChange(`${cwd}/a`)]\n    sendSubscription(client.subscription, cwd, changes)\n    await wait(30)\n    expect(fn).toHaveBeenCalled()\n    let call = fn.mock.calls[0][0]\n    disposable.dispose()\n    expect(call.root).toBe(path.join(cwd, 'foo'))\n    client.dispose()\n  })\n\n  it('should not subscribe invalid response', async () => {\n    let c = new Watchman(null, helper.createNullChannel())\n    disposables.push(c)\n    watchResponse = { watch: cwd, relative_path: 'foo' }\n    await c.watchProject(cwd)\n    let fn = jest.fn()\n    c.subscribe(`${cwd}/*`, fn)\n    let changes: FileChangeItem[] = [createFileChange(`${cwd}/a`)]\n    sendSubscription('uuid', cwd, changes)\n    await wait(10)\n    sendSubscription(c.subscription, cwd, [])\n    await wait(10)\n    client.write(bser.dumpToBuffer({\n      subscription: c.subscription,\n      root: cwd\n    }))\n    await wait(10)\n    expect(fn).toHaveBeenCalledTimes(0)\n  })\n})\n\ndescribe('Watchman#createClient', () => {\n  it('should not create client when capabilities not match', async () => {\n    capabilities = { relative_root: false }\n    await expect(async () => {\n      await Watchman.createClient(null, cwd)\n    }).rejects.toThrow(Error)\n  })\n\n  it('should not create when watch failed', async () => {\n    watchResponse = {}\n    await expect(async () => {\n      await Watchman.createClient(null, cwd)\n    }).rejects.toThrow(Error)\n  })\n\n  it('should create client', async () => {\n    let client = await Watchman.createClient(null, cwd)\n    disposables.push(client)\n    expect(client).toBeDefined()\n  })\n})\n\ndescribe('fileSystemWatcher', () => {\n\n  async function createWatcher(pattern: GlobPattern, ignoreCreateEvents = false, ignoreChangeEvents = false, ignoreDeleteEvents = false): Promise<FileSystemWatcher> {\n    let watcher = watcherManager.createFileSystemWatcher(\n      pattern,\n      ignoreCreateEvents,\n      ignoreChangeEvents,\n      ignoreDeleteEvents\n    )\n    disposables.push(watcher)\n    return watcher\n  }\n\n  beforeAll(async () => {\n    workspaceFolder.addWorkspaceFolder(cwd, true)\n    await watcherManager.waitClient(cwd)\n  })\n\n  it('should use relative pattern #1', async () => {\n    let folder = workspaceFolder.workspaceFolders[0]\n    expect(folder).toBeDefined()\n    let pattern = new RelativePattern(folder, '**/*')\n    let watcher = await createWatcher(pattern, false, true, true)\n    let fn = jest.fn()\n    watcher.onDidCreate(fn)\n    let changes: FileChangeItem[] = [createFileChange(`a`)]\n    sendSubscription(watcher.subscribe, cwd, changes)\n    await helper.wait(30)\n    expect(fn).toHaveBeenCalled()\n  })\n\n  it('should use relative pattern #2', async () => {\n    let called = false\n    let pattern = new RelativePattern(__dirname, '**/*')\n    let watcher = await createWatcher(pattern, false, true, true)\n    watcher.onDidCreate(() => {\n      called = true\n    })\n    let changes: FileChangeItem[] = [createFileChange(`a`)]\n    sendSubscription(watcher.subscribe, cwd, changes)\n    await helper.wait(30)\n    expect(called).toBe(false)\n  })\n\n  it('should use relative pattern #3', async () => {\n    let called = false\n    let root = path.join(process.cwd(), 'not_exists')\n    let pattern = new RelativePattern(root, '**/*')\n    let watcher = await createWatcher(pattern, false, true, true)\n    watcher.onDidCreate(() => {\n      called = true\n    })\n    await helper.wait(10)\n    let changes: FileChangeItem[] = [createFileChange(`a`)]\n    sendSubscription(watcher.subscribe, cwd, changes)\n    await helper.wait(10)\n    expect(called).toBe(false)\n  })\n\n  it('should watch for file create', async () => {\n    let watcher = await createWatcher('**/*', false, true, true)\n    let called = false\n    watcher.onDidCreate(() => {\n      called = true\n    })\n    let changes: FileChangeItem[] = [createFileChange(`a`)]\n    sendSubscription(watcher.subscribe, cwd, changes)\n    await helper.waitValue(() => {\n      return called\n    }, true)\n  })\n\n  it('should watch for file delete', async () => {\n    let watcher = await createWatcher('**/*', true, true, false)\n    let called = false\n    watcher.onDidDelete(() => {\n      called = true\n    })\n    let changes: FileChangeItem[] = [createFileChange(`a`, false, false)]\n    sendSubscription(watcher.subscribe, cwd, changes)\n    await helper.waitValue(() => {\n      return called\n    }, true)\n  })\n\n  it('should watch for file change', async () => {\n    let watcher = await createWatcher('**/*', false, false, false)\n    let called = false\n    watcher.onDidChange(() => {\n      called = true\n    })\n    let changes: FileChangeItem[] = [createFileChange(`a`, false, true)]\n    sendSubscription(watcher.subscribe, cwd, changes)\n    await helper.waitValue(() => {\n      return called\n    }, true)\n  })\n\n  it('should watch for file rename', async () => {\n    let watcher = await createWatcher('**/*', false, false, false)\n    let called = false\n    watcher.onDidRename(() => {\n      called = true\n    })\n    await helper.wait(50)\n    let changes: FileChangeItem[] = [\n      createFileChange(`a`, false, false),\n      createFileChange(`b`, true, true),\n    ]\n    sendSubscription(watcher.subscribe, cwd, changes)\n    await helper.waitValue(() => {\n      return called\n    }, true)\n  })\n\n  it('should not watch for events', async () => {\n    let watcher = await createWatcher('**/*', true, true, true)\n    let called = false\n    let onChange = () => { called = true }\n    watcher.onDidCreate(onChange)\n    watcher.onDidChange(onChange)\n    watcher.onDidDelete(onChange)\n    let changes: FileChangeItem[] = [\n      createFileChange(`a`, false, false),\n      createFileChange(`b`, true, true),\n      createFileChange(`c`, false, true),\n    ]\n    sendSubscription(watcher.subscribe, cwd, changes)\n    await helper.wait(10)\n    expect(called).toBe(false)\n  })\n\n  it('should watch for folder rename', async () => {\n    let watcher = await createWatcher('**/*')\n    let newFiles: string[] = []\n    let count = 0\n    watcher.onDidRename(e => {\n      count++\n      newFiles.push(e.newUri.fsPath)\n    })\n    let changes: FileChangeItem[] = [\n      createFileChange(`a/1`, false, false),\n      createFileChange(`a/2`, false, false),\n      createFileChange(`b/1`, true, true),\n      createFileChange(`b/2`, true, true),\n    ]\n    sendSubscription(watcher.subscribe, cwd, changes)\n    await helper.waitValue(() => {\n      return count\n    }, 2)\n  })\n\n  it('should watch for new folder', async () => {\n    let watcher = await createWatcher('**/*')\n    expect(watcher).toBeDefined()\n    workspaceFolder.renameWorkspaceFolder(cwd, __dirname)\n    let uri: URI\n    watcher.onDidCreate(e => {\n      uri = e\n    })\n    await watcherManager.waitClient(__dirname)\n    let changes: FileChangeItem[] = [createFileChange(`a`)]\n    sendSubscription(watcher.subscribe, __dirname, changes)\n    await helper.waitValue(() => {\n      return uri?.fsPath\n    }, path.join(__dirname, 'a'))\n  })\n})\n\ndescribe('create FileSystemWatcherManager', () => {\n  it('should attach to existing workspace folder', async () => {\n    let workspaceFolder = new WorkspaceFolderController(configurations)\n    workspaceFolder.addWorkspaceFolder(cwd, false)\n    let watcherManager = new FileSystemWatcherManager(workspaceFolder, { ...defaultConfig, enable: false })\n    watcherManager.disabled = false\n    watcherManager.attach(helper.createNullChannel())\n    await watcherManager.createClient(cwd)\n    await watcherManager.waitClient(cwd)\n    watcherManager.dispose()\n  })\n\n  it('should get watchman path', async () => {\n    let watcherManager = new FileSystemWatcherManager(workspaceFolder, { ...defaultConfig, watchmanPath: 'invalid_command' })\n    process.env.WATCHMAN_SOCK = ''\n    await expect(async () => {\n      await watcherManager.getWatchmanPath()\n    }).rejects.toThrow(Error)\n    process.env.WATCHMAN_SOCK = sockPath\n  })\n})\n"
  },
  {
    "path": "src/__tests__/core/files.test.ts",
    "content": "import { Buffer, Neovim } from '@chemzqm/neovim'\nimport fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport { v4 as uuid } from 'uuid'\nimport { CancellationTokenSource, Disposable } from 'vscode-languageserver-protocol'\nimport { CreateFile, DeleteFile, Position, Range, RenameFile, SnippetTextEdit, StringValue, TextDocumentEdit, TextEdit, VersionedTextDocumentIdentifier, WorkspaceEdit } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport commands from '../../commands'\nimport events from '../../events'\nimport { getOriginalLine, RecoverFunc } from '../../model/editInspect'\nimport RelativePattern from '../../model/relativePattern'\nimport { disposeAll } from '../../util'\nimport { readFile } from '../../util/fs'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport helper, { createTmpFile } from '../helper'\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  await helper.reset()\n  disposeAll(disposables)\n  disposables = []\n})\n\ndescribe('RelativePattern', () => {\n  function testThrow(fn: () => void) {\n    let err\n    try {\n      fn()\n    } catch (e) {\n      err = e\n    }\n    expect(err).toBeDefined()\n  }\n\n  it('should throw for invalid arguments', async () => {\n    testThrow(() => {\n      new RelativePattern('', undefined)\n    })\n    testThrow(() => {\n      new RelativePattern({ uri: undefined } as any, '')\n    })\n  })\n\n  it('should create relativePattern', async () => {\n    for (let base of [__filename, URI.file(__filename), { uri: URI.file(__dirname).toString(), name: 'test' }]) {\n      let p = new RelativePattern(base, '**/*')\n      expect(URI.isUri(p.baseUri)).toBe(true)\n      expect(p.toJSON()).toBeDefined()\n    }\n  })\n})\n\ndescribe('findFiles()', () => {\n  beforeEach(() => {\n    workspace.workspaceFolderControl.setWorkspaceFolders([__dirname])\n  })\n\n  it('should use glob pattern', async () => {\n    let res = await workspace.findFiles('**/*.ts', undefined, 1)\n    expect(res.length).toBeGreaterThan(0)\n  })\n\n  it('should use relativePattern', async () => {\n    let relativePattern = new RelativePattern(URI.file(__dirname), '**/*.ts')\n    let res = await workspace.findFiles(relativePattern)\n    expect(res.length).toBeGreaterThan(0)\n  })\n\n  it('should respect exclude as glob pattern', async () => {\n    let arr = await workspace.findFiles('**/*.ts', 'files*')\n    let res = arr.find(o => path.relative(__dirname, o.fsPath).startsWith('files'))\n    expect(res).toBeUndefined()\n  })\n\n  it('should respect exclude as relativePattern', async () => {\n    let relativePattern = new RelativePattern(URI.file(__dirname), 'files*')\n    let arr = await workspace.findFiles('**/*.ts', relativePattern)\n    let res = arr.find(o => path.relative(__dirname, o.fsPath).startsWith('files'))\n    expect(res).toBeUndefined()\n\n    relativePattern = new RelativePattern(URI.file(path.join(__dirname, 'foo')), '**/*.ts')\n    arr = await workspace.findFiles('**/*.ts', relativePattern, 1)\n    expect(arr.length).toBe(1)\n  })\n\n  it('should respect maxResults', async () => {\n    let arr = await workspace.findFiles('**/*.ts', undefined, 1)\n    expect(arr.length).toBe(1)\n  })\n\n  it('should respect token', async () => {\n    let source = new CancellationTokenSource()\n    source.cancel()\n    let arr = await workspace.findFiles('**/*.ts', undefined, 2, source.token)\n    expect(arr.length).toBe(0)\n  })\n\n  it('should cancel findFiles', async () => {\n    let source = new CancellationTokenSource()\n    let p = workspace.findFiles('**/*.ts', undefined, undefined, source.token)\n    setTimeout(() => {\n      source.cancel()\n    }, 10)\n    let arr = await p\n    expect(arr).toBeDefined()\n  })\n})\n\ndescribe('applyEdits()', () => {\n  it('should not throw when unable to undo & redo', async () => {\n    await commands.executeCommand('workspace.undo')\n    await commands.executeCommand('workspace.redo')\n  })\n\n  it('should throw for unsupported scheme', () => {\n    expect(() => {\n      let edit = TextDocumentEdit.create({ uri: 'lsp:/1', version: 1 }, [TextEdit.insert(Position.create(0, 0), ' ')])\n      workspace.files.validateChanges([edit])\n    }).toThrow(Error)\n    expect(() => {\n      let edit = TextDocumentEdit.create({ uri: 'lsp:/1', version: null }, [TextEdit.insert(Position.create(0, 0), ' ')])\n      workspace.files.validateChanges([edit])\n    }).toThrow(Error)\n    let rename = RenameFile.create('lsp:/1', 'lsp:/2')\n    expect(() => {\n      workspace.files.validateChanges([rename])\n    }).toThrow(Error)\n  })\n\n  it('should show error when document with version not loaded', async () => {\n    let uri = 'lsptest:///file'\n    let versioned = VersionedTextDocumentIdentifier.create(uri, 1)\n    let edit = TextEdit.insert(Position.create(0, 0), 'bar')\n    let change = TextDocumentEdit.create(versioned, [edit])\n    let workspaceEdit: WorkspaceEdit = {\n      documentChanges: [change]\n    }\n    let res = await workspace.applyEdit(workspaceEdit)\n    expect(res).toBe(false)\n    let line = await helper.getCmdline()\n    expect(line).toMatch('Error')\n  })\n\n  it('should apply TextEdit of documentChanges', async () => {\n    let doc = await helper.createDocument()\n    let versioned = VersionedTextDocumentIdentifier.create(doc.uri, doc.version)\n    let edit = TextEdit.insert(Position.create(0, 0), 'bar')\n    let change = TextDocumentEdit.create(versioned, [edit])\n    let workspaceEdit: WorkspaceEdit = {\n      documentChanges: [change]\n    }\n    let res = await workspace.applyEdit(workspaceEdit)\n    expect(res).toBe(true)\n    let line = await nvim.getLine()\n    expect(line).toBe('bar')\n    await nvim.command('bd!')\n    await workspace.files.undoWorkspaceEdit()\n  })\n\n  it('should apply edit with out change buffers', async () => {\n    let doc = await helper.createDocument()\n    await nvim.setLine('bar')\n    await doc.synchronize()\n    let version = doc.version\n    let versioned = VersionedTextDocumentIdentifier.create(doc.uri, doc.version)\n    let edit = TextEdit.replace(Range.create(0, 0, 0, 3), 'bar')\n    let change = TextDocumentEdit.create(versioned, [edit])\n    let workspaceEdit: WorkspaceEdit = {\n      documentChanges: [change]\n    }\n    let res = await workspace.applyEdit(workspaceEdit)\n    expect(res).toBe(true)\n    expect(doc.version).toBe(version)\n  })\n\n  it('should apply snippet edits', async () => {\n    let filepath = await createTmpFile('foo\\nbar\\n')\n    let doc = await helper.createDocument(filepath)\n    let versioned = VersionedTextDocumentIdentifier.create(doc.uri, doc.version)\n    let edit = TextEdit.insert(Position.create(0, 0), 'before\\n')\n    let snippetEdit: SnippetTextEdit = { range: Range.create(2, 0, 2, 0), snippet: StringValue.createSnippet('after($1)') }\n    let change = TextDocumentEdit.create(versioned, [edit, snippetEdit])\n    let workspaceEdit: WorkspaceEdit = {\n      documentChanges: [change]\n    }\n    let res = await workspace.applyEdit(workspaceEdit)\n    expect(res).toBe(true)\n    let newLines = doc.textDocument.lines\n    expect(newLines).toEqual(['before', 'foo', 'bar', 'after()'])\n    await workspace.files.undoWorkspaceEdit()\n    newLines = doc.textDocument.lines\n    expect(newLines).toEqual(['foo', 'bar'])\n  })\n\n  it('should not apply TextEdit if version miss match', async () => {\n    let doc = await helper.createDocument()\n    let versioned = VersionedTextDocumentIdentifier.create(doc.uri, 10)\n    let edit = TextEdit.insert(Position.create(0, 0), 'bar')\n    let change = TextDocumentEdit.create(versioned, [edit])\n    let workspaceEdit: WorkspaceEdit = {\n      documentChanges: [change]\n    }\n    let res = await workspace.applyEdit(workspaceEdit)\n    expect(res).toBe(false)\n  })\n\n  it('should apply edits with changes to buffer', async () => {\n    let doc = await helper.createDocument()\n    let changes = {\n      [doc.uri]: [TextEdit.insert(Position.create(0, 0), 'bar')]\n    }\n    let workspaceEdit: WorkspaceEdit = { changes }\n    let res = await workspace.applyEdit(workspaceEdit)\n    expect(res).toBe(true)\n    let line = await nvim.getLine()\n    expect(line).toBe('bar')\n  })\n\n  it('should apply edits with changes to file not in buffer list', async () => {\n    let filepath = await createTmpFile('bar')\n    let uri = URI.file(filepath).toString()\n    let changes = {\n      [uri]: [TextEdit.insert(Position.create(0, 0), 'foo')]\n    }\n    let res = await workspace.applyEdit({ changes })\n    expect(res).toBe(true)\n    let doc = workspace.getDocument(uri)\n    let content = doc.getDocumentContent()\n    expect(content).toMatch(/^foobar/)\n    await nvim.command('silent! %bwipeout!')\n  })\n\n  it('should apply edits when file does not exist', async () => {\n    let filepath = path.join(__dirname, 'not_exists')\n    disposables.push({\n      dispose: () => {\n        if (fs.existsSync(filepath)) {\n          fs.unlinkSync(filepath)\n        }\n      }\n    })\n    let uri = URI.file(filepath).toString()\n    let changes = {\n      [uri]: [TextEdit.insert(Position.create(0, 0), 'foo')]\n    }\n    let res = await workspace.applyEdit({ changes })\n    expect(res).toBe(true)\n  })\n\n  it('should adjust cursor position after applyEdits', async () => {\n    let doc = await helper.createDocument()\n    let pos = await window.getCursorPosition()\n    expect(pos).toEqual({ line: 0, character: 0 })\n    let edit = TextEdit.insert(Position.create(0, 0), 'foo\\n')\n    let versioned = VersionedTextDocumentIdentifier.create(doc.uri, null)\n    let documentChanges = [TextDocumentEdit.create(versioned, [edit])]\n    let res = await workspace.applyEdit({ documentChanges })\n    expect(res).toBe(true)\n    pos = await window.getCursorPosition()\n    expect(pos).toEqual({ line: 1, character: 0 })\n  })\n\n  it('should throw when waitUntil is not synchronize', async () => {\n    let err\n    workspace.onWillCreateFiles(e => {\n      setTimeout(() => {\n        try {\n          e.waitUntil(Promise.resolve())\n        } catch (e) {\n          err = e\n        }\n      }, 0)\n    }, null, disposables)\n    let file = path.join(os.tmpdir(), uuid())\n    await workspace.createFile(file, { overwrite: true })\n    expect(err).toBeDefined()\n    fs.rmSync(file, { force: true })\n  })\n\n  it('should support null version of documentChanges', async () => {\n    let file = path.join(__dirname, 'foo')\n    await workspace.createFile(file, { ignoreIfExists: true, overwrite: true })\n    let uri = URI.file(file).toString()\n    let versioned = VersionedTextDocumentIdentifier.create(uri, null)\n    let edit = TextEdit.insert(Position.create(0, 0), 'bar')\n    let change = TextDocumentEdit.create(versioned, [edit])\n    let workspaceEdit: WorkspaceEdit = {\n      documentChanges: [change]\n    }\n    let res = await workspace.applyEdit(workspaceEdit)\n    expect(res).toBe(true)\n    await nvim.command('wa')\n    let content = await readFile(file, 'utf8')\n    expect(content).toMatch(/^bar/)\n    await workspace.deleteFile(file, { ignoreIfNotExists: true })\n  })\n\n  it('should support CreateFile edit', async () => {\n    let file = path.join(__dirname, 'foo')\n    let uri = URI.file(file).toString()\n    let workspaceEdit: WorkspaceEdit = {\n      documentChanges: [CreateFile.create(uri, { overwrite: true })]\n    }\n    let res = await workspace.applyEdit(workspaceEdit)\n    expect(res).toBe(true)\n    await workspace.deleteFile(file, { ignoreIfNotExists: true })\n  })\n\n  it('should support DeleteFile edit', async () => {\n    let file = path.join(__dirname, 'foo')\n    await workspace.createFile(file, { ignoreIfExists: true, overwrite: true })\n    let uri = URI.file(file).toString()\n    let workspaceEdit: WorkspaceEdit = {\n      documentChanges: [DeleteFile.create(uri)]\n    }\n    let res = await workspace.applyEdit(workspaceEdit)\n    expect(res).toBe(true)\n  })\n\n  it('should check uri for CreateFile edit', async () => {\n    let workspaceEdit: WorkspaceEdit = {\n      documentChanges: [CreateFile.create('term://.', { overwrite: true })]\n    }\n    let res = await workspace.applyEdit(workspaceEdit)\n    expect(res).toBe(false)\n  })\n\n  it('should support RenameFile edit', async () => {\n    let file = path.join(__dirname, 'foo')\n    await workspace.createFile(file, { ignoreIfExists: true, overwrite: true })\n    let newFile = path.join(__dirname, 'bar')\n    let uri = URI.file(file).toString()\n    let workspaceEdit: WorkspaceEdit = {\n      documentChanges: [RenameFile.create(uri, URI.file(newFile).toString())]\n    }\n    let res = await workspace.applyEdit(workspaceEdit)\n    expect(res).toBe(true)\n    await workspace.deleteFile(newFile, { ignoreIfNotExists: true })\n  })\n\n  it('should support changes with edit and rename', async () => {\n    let fsPath = await createTmpFile('test')\n    let doc = await helper.createDocument(fsPath)\n    let newFile = path.join(os.tmpdir(), `coc-${process.pid}/new-${uuid()}`)\n    let newUri = URI.file(newFile).toString()\n    let edit: WorkspaceEdit = {\n      documentChanges: [\n        {\n          textDocument: {\n            version: null,\n            uri: doc.uri,\n          },\n          edits: [\n            {\n              range: {\n                start: {\n                  line: 0,\n                  character: 0\n                },\n                end: {\n                  line: 0,\n                  character: 4\n                }\n              },\n              newText: 'bar'\n            }\n          ]\n        },\n        {\n          oldUri: doc.uri,\n          newUri,\n          kind: 'rename'\n        }\n      ]\n    }\n    let res = await workspace.applyEdit(edit)\n    expect(res).toBe(true)\n    await nvim.call('cursor', [1, 1])\n    let curr = await workspace.document\n    expect(curr.uri).toBe(newUri)\n    expect(curr.getline(0)).toBe('bar')\n    let line = await nvim.line\n    expect(line).toBe('bar')\n  })\n\n  it('should support edit new file with CreateFile', async () => {\n    let file = path.join(os.tmpdir(), uuid())\n    let uri = URI.file(file).toString()\n    let workspaceEdit: WorkspaceEdit = {\n      documentChanges: [\n        CreateFile.create(uri, { overwrite: true }),\n        TextDocumentEdit.create({ uri, version: 0 }, [\n          TextEdit.insert(Position.create(0, 0), 'foo bar')\n        ])\n      ]\n    }\n    let res = await workspace.applyEdit(workspaceEdit)\n    expect(res).toBe(true)\n    let doc = workspace.getDocument(uri)\n    expect(doc).toBeDefined()\n    let line = doc.getline(0)\n    expect(line).toBe('foo bar')\n    await workspace.deleteFile(file, { ignoreIfNotExists: true })\n  })\n\n  it('should undo and redo workspace edit', async () => {\n    const folder = path.join(os.tmpdir(), uuid())\n    const pathone = path.join(folder, 'a')\n    const pathtwo = path.join(folder, 'b')\n    await workspace.files.createFile(pathone, { overwrite: true })\n    await workspace.files.createFile(pathtwo, { overwrite: true })\n    let uris = [URI.file(pathone).toString(), URI.file(pathtwo).toString()]\n    const assertContent = (one: string, two: string) => {\n      let doc = workspace.getDocument(uris[0])\n      expect(doc.getDocumentContent()).toBe(one)\n      doc = workspace.getDocument(uris[1])\n      expect(doc.getDocumentContent()).toBe(two)\n    }\n    let edits: TextDocumentEdit[] = []\n    edits.push(TextDocumentEdit.create({ uri: uris[0], version: null }, [\n      TextEdit.insert(Position.create(0, 0), 'foo')\n    ]))\n    edits.push(TextDocumentEdit.create({ uri: uris[1], version: null }, [\n      TextEdit.insert(Position.create(0, 0), 'bar')\n    ]))\n    await workspace.applyEdit({ documentChanges: edits })\n    assertContent('foo\\n', 'bar\\n')\n    await workspace.files.undoWorkspaceEdit()\n    assertContent('\\n', '\\n')\n    await workspace.files.redoWorkspaceEdit()\n    assertContent('foo\\n', 'bar\\n')\n  })\n\n  it('should should support annotations', async () => {\n    async function assertEdit(confirm: boolean, description: string | undefined): Promise<void> {\n      let doc = await helper.createDocument(uuid())\n      let edit: WorkspaceEdit = {\n        documentChanges: [\n          {\n            textDocument: { version: doc.version, uri: doc.uri },\n            edits: [\n              {\n                range: Range.create(0, 0, 0, 0),\n                newText: 'bar',\n                annotationId: '85bc78e2-5ef0-4949-b10c-13f476faf430'\n              }\n            ]\n          },\n        ],\n        changeAnnotations: {\n          '85bc78e2-5ef0-4949-b10c-13f476faf430': {\n            needsConfirmation: true,\n            label: 'Text changes',\n            description\n          }\n        }\n      }\n      let p = workspace.files.applyEdit(edit)\n      await helper.waitPrompt()\n      if (confirm) {\n        await nvim.input('<cr>')\n      } else {\n        await nvim.input('<esc>')\n      }\n      await p\n      let content = doc.getDocumentContent()\n      if (confirm) {\n        expect(content).toBe('bar\\n')\n      } else {\n        expect(content).toBe('\\n')\n      }\n    }\n    await assertEdit(true, 'description')\n    await assertEdit(false, undefined)\n  })\n})\n\ndescribe('getOriginalLine', () => {\n  it('should get original line', async () => {\n    let item = { index: 0, filepath: '' }\n    expect(getOriginalLine(item, undefined)).toBeUndefined()\n    expect(getOriginalLine({ index: 0, filepath: '', lnum: 1 }, undefined)).toBe(1)\n    let doc = await helper.createDocument()\n    let change = {\n      textDocument: { version: doc.version, uri: doc.uri },\n      edits: [\n        {\n          range: Range.create(0, 0, 0, 0),\n          newText: 'bar',\n        }, {\n          range: Range.create(2, 0, 2, 0),\n          snippet: StringValue.createSnippet('foo')\n        }\n      ]\n    }\n    expect(getOriginalLine({ index: 0, filepath: '', lnum: 1 }, change)).toBe(1)\n  })\n\n  describe('inspectEdit', () => {\n    async function inspect(edit: WorkspaceEdit): Promise<Buffer> {\n      await workspace.applyEdit(edit)\n      await commands.executeCommand('workspace.inspectEdit')\n      let buf = await nvim.buffer\n      return buf\n    }\n\n    it('should show warning when edit not exists', async () => {\n      (workspace.files as any).editState = undefined\n      await workspace.files.inspectEdit()\n    })\n\n    it('should render with changes', async () => {\n      let fsPath = await createTmpFile('foo\\n1\\n2\\nbar')\n      let doc = await helper.createDocument(fsPath)\n      let newFile = path.join(os.tmpdir(), `coc-${process.pid}/new-${uuid()}`)\n      let newUri = URI.file(newFile).toString()\n      let createFile = path.join(os.tmpdir(), `coc-${process.pid}/create-${uuid()}`)\n      let deleteFile = await createTmpFile('delete')\n      disposables.push(Disposable.create(() => {\n        if (fs.existsSync(newFile)) fs.unlinkSync(newFile)\n        if (fs.existsSync(createFile)) fs.unlinkSync(createFile)\n        if (fs.existsSync(deleteFile)) fs.unlinkSync(deleteFile)\n      }))\n      let edit: WorkspaceEdit = {\n        documentChanges: [\n          {\n            textDocument: { version: null, uri: doc.uri, },\n            edits: [\n              TextEdit.del(Range.create(0, 0, 1, 0)),\n              TextEdit.replace(Range.create(3, 0, 3, 3), 'xyz'),\n            ]\n          },\n          {\n            kind: 'rename',\n            oldUri: doc.uri,\n            newUri\n          }, {\n            kind: 'create',\n            uri: URI.file(createFile).toString()\n          }, {\n            kind: 'delete',\n            uri: URI.file(deleteFile).toString()\n          }\n        ]\n      }\n      let buf = await inspect(edit)\n      let lines = await buf.lines\n      let content = lines.join('\\n')\n      expect(content).toMatch('Change')\n      expect(content).toMatch('Rename')\n      expect(content).toMatch('Create')\n      expect(content).toMatch('Delete')\n      await nvim.command('exe 5')\n      await nvim.input('<CR>')\n      await helper.waitFor('expand', ['%:p'], newFile)\n      let line = await nvim.call('line', ['.'])\n      expect(line).toBe(3)\n    })\n\n    it('should render annotation label', async () => {\n      let filepath = path.join(__dirname, uuid())\n      disposables.push(Disposable.create(() => {\n        if (fs.existsSync(filepath)) {\n          fs.unlinkSync(filepath)\n        }\n      }))\n      let doc = await helper.createDocument(filepath)\n      let edit: WorkspaceEdit = {\n        documentChanges: [\n          {\n            textDocument: { version: doc.version, uri: doc.uri },\n            edits: [\n              {\n                range: Range.create(0, 0, 0, 0),\n                newText: 'bar',\n                annotationId: 'dd866f37-a24c-4503-9c35-c139fb28e25b'\n              }\n            ]\n          }, {\n            textDocument: { version: 1, uri: doc.uri },\n            edits: [\n              {\n                range: Range.create(0, 0, 0, 0),\n                newText: 'bar',\n                annotationId: '9468b9bf-97b6-4b37-b21f-aba8df3ce658'\n              }\n            ]\n          }],\n        changeAnnotations: {\n          'dd866f37-a24c-4503-9c35-c139fb28e25b': {\n            needsConfirmation: false,\n            label: 'Text changes'\n          }\n        }\n      }\n      let buf = await inspect(edit)\n      await events.fire('BufUnload', [buf.id + 1])\n      let winid = await nvim.call('win_getid')\n      let lines = await buf.lines\n      expect(lines[0]).toBe('Text changes')\n      await nvim.command('exe 1')\n      await nvim.command('wa')\n      await nvim.input('<CR>')\n      let bufnr = await nvim.call('bufnr', ['%'])\n      expect(bufnr).toBe(buf.id)\n      await nvim.command('exe 3')\n      await nvim.input('<CR>')\n      let fsPath = URI.parse(doc.uri).fsPath\n      await helper.waitFor('eval', ['expand(\"%:p\")'], fsPath)\n      await nvim.call('win_gotoid', [winid])\n      await nvim.input('<esc>')\n      await helper.wait(10)\n    })\n  })\n\n  describe('createFile()', () => {\n    it('should create and revert parent folder', async () => {\n      const folder = path.join(os.tmpdir(), uuid())\n      const filepath = path.join(folder, 'a/b/bar')\n      disposables.push(Disposable.create(() => {\n        fs.rmSync(folder, { recursive: true, force: true })\n      }))\n      let fns: RecoverFunc[] = []\n      expect(fs.existsSync(folder)).toBe(false)\n      await workspace.files.createFile(filepath, {}, fns)\n      expect(fs.existsSync(filepath)).toBe(true)\n      for (let i = fns.length - 1; i >= 0; i--) {\n        await fns[i]()\n      }\n      expect(fs.existsSync(folder)).toBe(false)\n    })\n\n    it('should throw when file already exists', async () => {\n      let filepath = await createTmpFile('foo', disposables)\n      let fn = async () => {\n        await workspace.createFile(filepath, {})\n      }\n      await expect(fn()).rejects.toThrow(Error)\n    })\n\n    it('should not create file if file exists with ignoreIfExists', async () => {\n      let file = await createTmpFile('foo')\n      await workspace.createFile(file, { ignoreIfExists: true })\n      let content = fs.readFileSync(file, 'utf8')\n      expect(content).toBe('foo')\n    })\n\n    it('should create file if does not exist', async () => {\n      await helper.edit()\n      let filepath = path.join(__dirname, 'foo')\n      await workspace.createFile(filepath, { ignoreIfExists: true })\n      let exists = fs.existsSync(filepath)\n      expect(exists).toBe(true)\n      fs.unlinkSync(filepath)\n    })\n\n    it('should revert file create', async () => {\n      let filepath = path.join(os.tmpdir(), uuid())\n      disposables.push(Disposable.create(() => {\n        if (fs.existsSync(filepath)) fs.unlinkSync(filepath)\n      }))\n      let fns: RecoverFunc[] = []\n      await workspace.files.createFile(filepath, { overwrite: true }, fns)\n      expect(fs.existsSync(filepath)).toBe(true)\n      let bufnr = await nvim.call('bufnr', [filepath]) as number\n      expect(bufnr).toBeGreaterThan(0)\n      let doc = workspace.getDocument(bufnr)\n      expect(doc).toBeDefined()\n      for (let fn of fns) {\n        await fn()\n      }\n      expect(fs.existsSync(filepath)).toBe(false)\n      let loaded = await nvim.call('bufloaded', [filepath])\n      expect(loaded).toBe(0)\n    })\n  })\n\n  describe('renameFile', () => {\n    it('should throw when oldPath not exists', async () => {\n      await workspace.renameFile('/foo', '/foo')\n      await workspace.renameFile('/foo', __filename, { ignoreIfExists: true })\n      let filepath = path.join(__dirname, 'not_exists_file')\n      let newPath = path.join(__dirname, 'bar')\n      let fn = async () => {\n        await workspace.renameFile(filepath, newPath)\n      }\n      await expect(fn()).rejects.toThrow(Error)\n    })\n\n    it('should throw when new path exists and not overwrite', async () => {\n      await expect(async () => {\n        await workspace.renameFile('/foo', __filename, {})\n      }).rejects.toThrow(/exists/)\n    })\n\n    it('should rename file on disk', async () => {\n      let filepath = await createTmpFile('test')\n      let newPath = path.join(path.dirname(filepath), 'new_file')\n      disposables.push(Disposable.create(() => {\n        if (fs.existsSync(newPath)) fs.unlinkSync(newPath)\n        if (fs.existsSync(filepath)) fs.unlinkSync(filepath)\n      }))\n      let fns: RecoverFunc[] = []\n      await workspace.files.renameFile(filepath, newPath, { overwrite: true }, fns)\n      expect(fs.existsSync(newPath)).toBe(true)\n      for (let fn of fns) {\n        await fn()\n      }\n      expect(fs.existsSync(newPath)).toBe(false)\n      expect(fs.existsSync(filepath)).toBe(true)\n    })\n\n    it('should rename if file does not exist', async () => {\n      let filepath = path.join(__dirname, 'foo')\n      let newPath = path.join(__dirname, 'bar')\n      await workspace.createFile(filepath)\n      await workspace.renameFile(filepath, newPath)\n      expect(fs.existsSync(newPath)).toBe(true)\n      expect(fs.existsSync(filepath)).toBe(false)\n      fs.unlinkSync(newPath)\n    })\n\n    it('should rename current buffer with same bufnr', async () => {\n      let file = await createTmpFile('test')\n      let doc = await helper.createDocument(file)\n      await nvim.setLine('bar')\n      await doc.patchChange()\n      let newFile = path.join(os.tmpdir(), `coc-${process.pid}/new-${uuid()}`)\n      disposables.push(Disposable.create(() => {\n        if (fs.existsSync(newFile)) fs.unlinkSync(newFile)\n      }))\n      await workspace.renameFile(file, newFile)\n      let bufnr = await nvim.call('bufnr', ['%'])\n      expect(bufnr).toBe(doc.bufnr)\n      let line = await nvim.line\n      expect(line).toBe('bar')\n      let exists = fs.existsSync(newFile)\n      expect(exists).toBe(true)\n    })\n\n    it('should overwrite if file exists', async () => {\n      let filepath = path.join(os.tmpdir(), uuid())\n      let newPath = path.join(os.tmpdir(), uuid())\n      await workspace.createFile(filepath)\n      await workspace.createFile(newPath)\n      await workspace.renameFile(filepath, newPath, { overwrite: true })\n      expect(fs.existsSync(newPath)).toBe(true)\n      expect(fs.existsSync(filepath)).toBe(false)\n      fs.unlinkSync(newPath)\n    })\n\n    it('should rename buffer in directory and revert', async () => {\n      let folder = path.join(os.tmpdir(), uuid())\n      let newFolder = path.join(os.tmpdir(), uuid())\n      fs.mkdirSync(folder)\n      disposables.push(Disposable.create(() => {\n        fs.rmSync(folder, { recursive: true, force: true })\n        fs.rmSync(newFolder, { recursive: true, force: true })\n      }))\n      let filepath = path.join(folder, 'new_file')\n      await workspace.createFile(filepath)\n      let bufnr = await nvim.call('bufnr', [filepath])\n      expect(bufnr).toBeGreaterThan(0)\n      let fns: RecoverFunc[] = []\n      await workspace.files.renameFile(folder, newFolder, { overwrite: true }, fns)\n      bufnr = await nvim.call('bufnr', [path.join(newFolder, 'new_file')])\n      expect(bufnr).toBeGreaterThan(0)\n      for (let i = fns.length - 1; i >= 0; i--) {\n        await fns[i]()\n      }\n      bufnr = await nvim.call('bufnr', [filepath])\n      expect(bufnr).toBeGreaterThan(0)\n    })\n  })\n\n  describe('loadResource()', () => {\n    it('should load file as hidden buffer', async () => {\n      helper.updateConfiguration('workspace.openResourceCommand', '')\n      let filepath = await createTmpFile('foo')\n      let uri = URI.file(filepath).toString()\n      let doc = await workspace.files.loadResource(uri)\n      let bufnrs = await nvim.call('coc#window#bufnrs') as number[]\n      expect(bufnrs.indexOf(doc.bufnr)).toBe(-1)\n    })\n  })\n\n  describe('deleteFile()', () => {\n    it('should throw when file not exists', async () => {\n      let filepath = path.join(__dirname, 'not_exists')\n      let fn = async () => {\n        await workspace.deleteFile(filepath)\n      }\n      await expect(fn()).rejects.toThrow(Error)\n    })\n\n    it('should ignore when ignoreIfNotExists set', async () => {\n      let filepath = path.join(__dirname, 'not_exists')\n      let fns: RecoverFunc[] = []\n      await workspace.files.deleteFile(filepath, { ignoreIfNotExists: true }, fns)\n      expect(fns.length).toBe(0)\n    })\n\n    it('should unload loaded buffer', async () => {\n      let filepath = await createTmpFile('file to delete')\n      disposables.push(Disposable.create(() => {\n        if (fs.existsSync(filepath)) fs.unlinkSync(filepath)\n      }))\n      await workspace.files.loadResource(URI.file(filepath).toString())\n      let fns: RecoverFunc[] = []\n      await workspace.files.deleteFile(filepath, {}, fns)\n      let loaded = await nvim.call('bufloaded', [filepath])\n      expect(loaded).toBe(0)\n      for (let i = fns.length - 1; i >= 0; i--) {\n        await fns[i]()\n      }\n      expect(fs.existsSync(filepath)).toBe(true)\n      loaded = await nvim.call('bufloaded', [filepath])\n      expect(loaded).toBe(1)\n    })\n\n    it('should delete and recover folder', async () => {\n      let folder = path.join(os.tmpdir(), uuid())\n      disposables.push(Disposable.create(() => {\n        if (fs.existsSync(folder)) fs.rmdirSync(folder)\n      }))\n      fs.mkdirSync(folder)\n      expect(fs.existsSync(folder)).toBe(true)\n      let fns: RecoverFunc[] = []\n      await workspace.files.deleteFile(folder, {}, fns)\n      expect(fs.existsSync(folder)).toBe(false)\n      for (let i = fns.length - 1; i >= 0; i--) {\n        await fns[i]()\n      }\n      expect(fs.existsSync(folder)).toBe(true)\n      await workspace.files.deleteFile(folder, {})\n    })\n\n    it('should delete and recover folder recursive', async () => {\n      let folder = path.join(os.tmpdir(), uuid())\n      disposables.push(Disposable.create(() => {\n        fs.rmSync(folder, { recursive: true, force: true })\n      }))\n      fs.mkdirSync(folder)\n      fs.writeFileSync(path.join(folder, 'new_file'), '', 'utf8')\n      let fns: RecoverFunc[] = []\n      await workspace.files.deleteFile(folder, { recursive: true }, fns)\n      expect(fs.existsSync(folder)).toBe(false)\n      for (let i = fns.length - 1; i >= 0; i--) {\n        await fns[i]()\n      }\n      expect(fs.existsSync(folder)).toBe(true)\n      expect(fs.existsSync(path.join(folder, 'new_file'))).toBe(true)\n      await workspace.files.deleteFile(folder, { recursive: true })\n    })\n\n    it('should delete file if exists', async () => {\n      let filepath = path.join(__dirname, 'foo')\n      await workspace.createFile(filepath)\n      expect(fs.existsSync(filepath)).toBe(true)\n      await workspace.deleteFile(filepath)\n      expect(fs.existsSync(filepath)).toBe(false)\n    })\n  })\n\n  describe('loadFile()', () => {\n    it('should single loadFile', async () => {\n      let doc = await helper.createDocument()\n      let newFile = URI.file(path.join(__dirname, 'abc')).toString()\n      let document = await workspace.loadFile(newFile)\n      let bufnr = await nvim.call('bufnr', '%')\n      expect(document.uri.endsWith('abc')).toBe(true)\n      expect(bufnr).toBe(doc.bufnr)\n    })\n  })\n\n  describe('loadFiles', () => {\n    it('should loadFiles', async () => {\n      let files = ['a', 'b', 'c'].map(key => URI.file(path.join(__dirname, key)).toString())\n      let docs = await workspace.loadFiles(files)\n      let uris = docs.map(o => o.uri)\n      expect(uris).toEqual(files)\n      await workspace.loadFiles([])\n    })\n\n    it('should load uri', async () => {\n      let res = await workspace.loadFiles(['deno:/foo'])\n      expect(res[0].uri).toBe('deno:/foo')\n    })\n  })\n\n  describe('openTextDocument()', () => {\n    it('should open document already exists', async () => {\n      let doc = await helper.createDocument('a')\n      await nvim.command('enew')\n      await workspace.openTextDocument(URI.parse(doc.uri))\n      let curr = await workspace.document\n      expect(curr.uri != doc.uri).toBe(true)\n    })\n\n    it('should throw when file does not exist', async () => {\n      await expect(async () => {\n        await workspace.openTextDocument('/a/b/c')\n      }).rejects.toThrow(Error)\n    })\n\n    it('should open untitled document', async () => {\n      let doc = await workspace.openTextDocument(URI.parse(`untitled:///a/b.js`))\n      expect(doc.uri).toBe('file:///a/b.js')\n    })\n\n    it('should load file that exists', async () => {\n      let doc = await workspace.openTextDocument(URI.file(__filename))\n      expect(URI.parse(doc.uri).fsPath).toBe(__filename)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/core/funcs.test.ts",
    "content": "import fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport { v4 as uuid } from 'uuid'\nimport which from 'which'\nimport Configurations from '../../configuration/index'\nimport * as funcs from '../../core/funcs'\nimport Resolver from '../../model/resolver'\nlet configurations: Configurations\n\nbeforeAll(async () => {\n  let userConfigFile = path.join(process.env.COC_VIMCONFIG, 'coc-settings.json')\n  configurations = new Configurations(userConfigFile, undefined)\n})\n\ndescribe('Resolver()', () => {\n  it('should return empty string when file not exists', async () => {\n    let spy = jest.spyOn(fs, 'existsSync').mockImplementation(() => {\n      return false\n    })\n    let r = new Resolver()\n    let res = await r.yarnFolder\n    expect(res).toBe('')\n    spy.mockRestore()\n  })\n\n  it('should resolve null', async () => {\n    let r = new Resolver()\n    let spy = jest.spyOn(which, 'sync').mockImplementation(() => {\n      throw new Error('not found')\n    })\n    let res = await r.resolveModule('mode')\n    expect(res).toBe(null)\n    spy.mockRestore()\n  })\n\n  it('should resolve npm module', async () => {\n    let r = new Resolver()\n    let folder = path.join(os.tmpdir(), uuid())\n    Object.assign(r, {\n      _npmFolder: folder,\n      _yarnFolder: __dirname,\n    })\n    fs.mkdirSync(path.join(folder, 'name'), { recursive: true })\n    fs.writeFileSync(path.join(folder, 'name', 'package.json'), '', 'utf8')\n    let res = await r.resolveModule('name')\n    expect(res).toBe(path.join(folder, 'name'))\n  })\n})\n\ndescribe('has()', () => {\n  it('should throw for invalid argument', async () => {\n    let env = {\n      isVim: true,\n      version: '8023956'\n    }\n    let err\n    try {\n      expect(funcs.has(env, '0.5.0')).toBe(true)\n    } catch (e) {\n      err = e\n    }\n    expect(err).toBeDefined()\n  })\n\n  it('should detect version on vim8', async () => {\n    let env = {\n      isVim: true,\n      version: '8023956'\n    }\n    expect(funcs.has(env, 'patch-7.4.248')).toBe(true)\n    expect(funcs.has(env, 'patch-8.5.1')).toBe(false)\n    expect(funcs.has(env, 'patch-9.0.0125')).toBe(false)\n  })\n\n  it('should delete version on neovim', async () => {\n    let env = {\n      isVim: false,\n      version: '0.6.1'\n    }\n    expect(funcs.has(env, 'nvim-0.5.0')).toBe(true)\n    expect(funcs.has(env, 'nvim-0.7.0')).toBe(false)\n  })\n})\n\ndescribe('createNameSpace()', () => {\n  it('should create namespace', async () => {\n    let nr = funcs.createNameSpace('ns')\n    expect(nr).toBeDefined()\n    expect(nr).toBe(funcs.createNameSpace('ns'))\n  })\n})\n\ndescribe('getWatchmanPath()', () => {\n  it('should get watchman path', async () => {\n    let res = funcs.getWatchmanPath(configurations)\n    expect(typeof res === 'string' || res == null).toBe(true)\n    configurations.updateMemoryConfig({ 'coc.preferences.watchmanPath': 'not_exists_watchman' })\n    res = funcs.getWatchmanPath(configurations)\n    expect(res).toBeNull()\n    configurations.updateMemoryConfig({ 'coc.preferences.watchmanPath': null })\n  })\n})\n\ndescribe('findUp()', () => {\n  it('should return null when can not find ', async () => {\n    let nvim: any = {\n      call: () => {\n        return __filename\n      }\n    }\n    let res = await funcs.findUp(nvim, os.homedir(), ['file_not_exists'])\n    expect(res).toBeNull()\n  })\n\n  it('should return null when unable find cwd in cwd', async () => {\n    let nvim: any = {\n      call: () => {\n        return ''\n      }\n    }\n    let res = await funcs.findUp(nvim, os.homedir(), ['file_not_exists'])\n    expect(res).toBeNull()\n  })\n})\n\ndescribe('score()', () => {\n  it('should return score', () => {\n    expect(funcs.score(undefined, 'untitled:///1', '')).toBe(0)\n    expect(funcs.score({ scheme: '*' }, 'untitled:///1', '')).toBe(3)\n    expect(funcs.score('vim', 'untitled:///1', 'vim')).toBe(10)\n    expect(funcs.score('*', 'untitled:///1', '')).toBe(5)\n    expect(funcs.score('', 'untitled:///1', 'vim')).toBe(0)\n    expect(funcs.score({ pattern: '/*' }, 'untitled:///1', 'vim', false)).toBe(5)\n    expect(funcs.score({ pattern: { pattern: '/*', baseUri: '/tmp' } }, 'untitled:///1', 'vim', false)).toBe(0)\n    expect(funcs.score({ pattern: { pattern: '/**', baseUri: '/tmp' } }, 'file:///tmp/a/b', 'vim')).toBe(5)\n    expect(funcs.score({ pattern: { pattern: '/**', baseUri: '/tmp' } }, 'file:///foo', 'vim')).toBe(0)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/core/keymaps.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { Disposable } from 'vscode-languageserver-protocol'\nimport Keymaps, { getBufnr, getKeymapModifier } from '../../core/keymaps'\nimport { disposeAll } from '../../util'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet keymaps: Keymaps\nlet disposables: Disposable[] = []\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  keymaps = workspace.keymaps\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n})\n\ndescribe('doKeymap()', () => {\n  it('should not throw when key not mapped', async () => {\n    await keymaps.doKeymap('<C-a>', '')\n  })\n\n  it('should invoke exists keymap', async () => {\n    let called = false\n    keymaps.registerKeymap(['i', 'n'], 'test-keymap', () => {\n      called = true\n      return 'result'\n    })\n    let res = await keymaps.doKeymap('test-keymap', '')\n    expect(res).toBe('result')\n    expect(called).toBe(true)\n  })\n})\n\ndescribe('registerKeymap()', () => {\n  it('should getBufnr', () => {\n    expect(getBufnr(3)).toBe(3)\n    expect(getBufnr(true)).toBe(0)\n  })\n\n  it('should getKeymapModifier', () => {\n    expect(getKeymapModifier('i', true)).toBe('<Cmd>')\n    expect(getKeymapModifier('i')).toBe('<C-o>')\n    expect(getKeymapModifier('s')).toBe('<Esc>')\n    expect(getKeymapModifier('x')).toBe('<C-U>')\n    expect(getKeymapModifier('t')).toBe('<Cmd>')\n  })\n\n  it('should throw for invalid key', () => {\n    let err\n    try {\n      keymaps.registerKeymap(['i'], '', jest.fn())\n    } catch (e) {\n      err = e\n    }\n    expect(err).toBeDefined()\n  })\n\n  it('should throw for duplicated key', async () => {\n    keymaps.registerKeymap(['i'], 'tmp', jest.fn())\n    let err\n    try {\n      keymaps.registerKeymap(['i'], 'tmp', jest.fn())\n    } catch (e) {\n      err = e\n    }\n    expect(err).toBeDefined()\n  })\n\n  it('should register insert key mapping', async () => {\n    let fn = jest.fn()\n    disposables.push(keymaps.registerKeymap(['i'], 'test', fn))\n    let res = await nvim.call('execute', ['verbose imap <Plug>(coc-test)'])\n    expect(res).toMatch('coc#_insert_key')\n  })\n\n  it('should register with different options', async () => {\n    let called = false\n    let fn = () => {\n      called = true\n      return ''\n    }\n    disposables.push(keymaps.registerKeymap(['n', 'v'], 'test', fn, {\n      sync: false,\n      cancel: false,\n      silent: false,\n      repeat: true\n    }))\n    let res = await nvim.exec(`verbose nmap <Plug>(coc-test)`, true)\n    expect(res).toMatch('coc#rpc#notify')\n    await nvim.eval(`feedkeys(\"\\\\<Plug>(coc-test)\")`)\n    await helper.waitValue(() => called, true)\n  })\n})\n\ndescribe('registerExprKeymap()', () => {\n  it('should visual key mapping', async () => {\n    await nvim.setLine('foo')\n    let called = false\n    let fn = () => {\n      called = true\n      return ''\n    }\n    disposables.push(keymaps.registerExprKeymap('x', 'x', fn))\n    await nvim.command('normal! viw')\n    await nvim.input('x<esc>')\n    await helper.waitValue(() => called, true)\n  })\n\n  it('should register expr insert key mapping', async () => {\n    let buf = await nvim.buffer\n    let called = false\n    let fn = () => {\n      called = true\n      return ''\n    }\n    let disposable = keymaps.registerExprKeymap('i', 'x', fn, buf.id)\n    let res = await nvim.exec('imap x', true)\n    expect(res).toMatch('coc#_insert_key')\n    await nvim.input('i')\n    await nvim.input('x')\n    await helper.waitValue(() => called, true)\n    disposable.dispose()\n    res = await nvim.exec('imap x', true)\n    expect(res).toMatch('No mapping found')\n  })\n\n  it('should regist key mapping without cancel pum', async () => {\n    let fn = jest.fn()\n    let disposable = keymaps.registerExprKeymap('i', 'x', fn, false, false)\n    let res = await nvim.exec('imap x', true)\n    expect(res).toMatch('coc#_insert_key')\n    disposable.dispose()\n  })\n})\n\ndescribe('registerLocalKeymap', () => {\n  it('should register local keymap by notification', async () => {\n    let bufnr = await nvim.call('bufnr', ['%']) as number\n    let called = false\n    let disposable = keymaps.registerLocalKeymap(bufnr, 'n', 'n', () => {\n      called = true\n      return ''\n    }, true)\n    let res = await nvim.exec('nmap n', true)\n    await nvim.input('n')\n    await helper.waitValue(() => called, true)\n    disposable.dispose()\n    res = await nvim.exec('nmap n', true)\n    expect(res).toMatch('No mapping found')\n  })\n\n  it('should regist insert mode keymap', async () => {\n    let bufnr = await nvim.call('bufnr', ['%']) as number\n    await nvim.command('startinsert')\n    let called = false\n    let disposable = keymaps.registerLocalKeymap(bufnr, 'i', '<C-i>', () => {\n      called = true\n    }, { cancel: true })\n    disposables.push(disposable)\n    await helper.waitValue(async () => {\n      let out = await nvim.exec('imap <C-i>', true)\n      return out.includes('coc#_insert_key')\n    }, true)\n    await nvim.input('<C-i>')\n    await helper.waitValue(() => called, true)\n    called = false\n    disposable = keymaps.registerLocalKeymap(bufnr, 'i', '<C-o>', () => {\n      called = true\n    }, { cancel: false })\n    disposables.push(disposable)\n    await helper.waitValue(async () => {\n      let out = await nvim.exec('imap <C-o>', true)\n      return out.includes('coc#_insert_key')\n    }, true)\n    await nvim.input('<C-o>')\n    await helper.waitValue(() => called, true)\n  })\n\n  it('should regist cmd keymap', async () => {\n    let bufnr = await nvim.call('bufnr', ['%']) as number\n    let called = false\n    let disposable = keymaps.registerLocalKeymap(bufnr, 'x', '<C-i>', async () => {\n      called = true\n    }, { cmd: true })\n    disposables.push(disposable)\n    await nvim.setLine('foo')\n    await nvim.command('normal! v$')\n    let m = await nvim.mode\n    expect(m.mode).toBe('v')\n    await nvim.input('<C-i>')\n    await helper.waitValue(() => called, true)\n    m = await nvim.mode\n    expect(m.mode).toBe('c')\n  })\n})\n"
  },
  {
    "path": "src/__tests__/core/locations.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport os from 'os'\nimport path from 'path'\nimport { Location, Position, Range } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport commands from '../../commands'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  await nvim.command(`source ${path.join(process.cwd(), 'autoload/coc/ui.vim')}`)\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  await helper.reset()\n})\n\nfunction createLocations(): Location[] {\n  let uri = URI.file(__filename).toString()\n  return [Location.create(uri, Range.create(0, 0, 1, 0)), Location.create(uri, Range.create(2, 0, 3, 0))]\n}\n\ndescribe('showLocations()', () => {\n  it('should show locations by editor.action.showReferences', async () => {\n    let doc = await workspace.document\n    let uri = doc.uri\n    let locations = createLocations()\n    await commands.executeCommand('editor.action.showReferences', uri, Position.create(0, 0), locations)\n    await helper.waitValue(async () => {\n      let wins = await nvim.windows\n      return wins.length > 1\n    }, true)\n  })\n\n  it('should show location list by default', async () => {\n    let locations = createLocations()\n    await workspace.showLocations(locations)\n    await helper.waitFor('bufname', ['%'], 'list:///location')\n  })\n\n  it('should fire autocmd when location list disabled', async () => {\n    Object.assign(workspace.env, {\n      locationlist: false\n    })\n    await nvim.exec(`\nfunction OnLocationsChange()\n  let g:called = 1\nendfunction\nautocmd User CocLocationsChange :call OnLocationsChange()`)\n    let locations = createLocations()\n    await workspace.showLocations(locations)\n    await helper.waitFor('eval', [`get(g:,'called',0)`], 1)\n  })\n\n  it('should show quickfix when quickfix enabled', async () => {\n    helper.updateConfiguration('coc.preferences.useQuickfixForLocations', true)\n    let locations = createLocations()\n    await workspace.showLocations(locations)\n    await helper.waitFor('eval', [`&buftype`], 'quickfix')\n  })\n\n  it('should use customized quickfix open command', async () => {\n    await nvim.setVar('coc_quickfix_open_command', 'copen 1')\n    helper.updateConfiguration('coc.preferences.useQuickfixForLocations', true)\n    let locations = createLocations()\n    await workspace.showLocations(locations)\n    await helper.waitFor('eval', [`&buftype`], 'quickfix')\n    let win = await nvim.window\n    let height = await win.height\n    expect(height).toBe(1)\n  })\n})\n\ndescribe('jumpTo()', () => {\n  it('should jumpTo position', async () => {\n    let uri = URI.file('/tmp/foo')\n    await workspace.jumpTo(uri, { line: 1, character: 1 })\n    await nvim.command('setl buftype=nofile')\n    let buf = await nvim.buffer\n    let name = await buf.name\n    expect(name).toMatch('/foo')\n    await buf.setLines(['foo', 'bar'], { start: 0, end: -1, strictIndexing: false })\n    await workspace.jumpTo(uri, { line: 1, character: 1 })\n    let pos = await nvim.call('getcurpos') as number[]\n    expect(pos.slice(1, 3)).toEqual([2, 2])\n  })\n\n  it('should jumpTo uri without normalize', async () => {\n    let uri = 'zipfile:///tmp/clojure-1.9.0.jar::clojure/core.clj'\n    await workspace.jumpTo(uri)\n    let buf = await nvim.buffer\n    let name = await buf.name\n    expect(name).toBe(uri)\n    let doc = await workspace.document\n    expect(doc.uri.startsWith('zipfile:/tmp')).toBe(true)\n  })\n\n  it('should jump without position', async () => {\n    let uri = URI.file('/tmp/foo').toString()\n    await workspace.jumpTo(uri)\n    let buf = await nvim.buffer\n    let name = await buf.name\n    expect(name).toMatch('/foo')\n  })\n\n  it('should jumpTo custom uri scheme', async () => {\n    let uri = 'jdt://foo'\n    await workspace.jumpTo(uri, { line: 1, character: 1 })\n    let buf = await nvim.buffer\n    let name = await buf.name\n    expect(name).toBe(uri)\n  })\n\n  it('should jump with uri fragment', async () => {\n    let uri = URI.file(__filename).with({ fragment: '3,3' }).toString()\n    await workspace.jumpTo(uri)\n    let cursor = await nvim.call('coc#util#cursor')\n    expect(cursor).toEqual([2, 2])\n    uri = URI.file(__filename).with({ fragment: '1' }).toString()\n    await workspace.jumpTo(uri)\n    cursor = await nvim.call('coc#util#cursor')\n    expect(cursor).toEqual([0, 0])\n  })\n})\n\ndescribe('openResource()', () => {\n  it('should open resource', async () => {\n    let uri = URI.file(path.join(os.tmpdir(), 'bar')).toString()\n    await workspace.openResource(uri)\n    let buf = await nvim.buffer\n    let name = await buf.name\n    expect(name).toMatch('bar')\n  })\n\n  it('should open none file uri', async () => {\n    workspace.registerTextDocumentContentProvider('jd', {\n      provideTextDocumentContent: () => 'jd'\n    })\n    let uri = 'jd://abc'\n    await workspace.openResource(uri)\n    let buf = await nvim.buffer\n    let name = await buf.name\n    expect(name).toBe('jd://abc')\n  })\n\n  it('should open opened buffer', async () => {\n    let buf = await helper.edit()\n    let doc = workspace.getDocument(buf.id)\n    await workspace.openResource(doc.uri)\n    await helper.waitFor('bufnr', ['%'], buf.id)\n  })\n\n  it('should open url', async () => {\n    await helper.mockFunction('coc#ui#open_url', 0)\n    let buf = await helper.edit()\n    let uri = 'http://example.com'\n    await workspace.openResource(uri)\n    await helper.waitFor('bufnr', ['%'], buf.id)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/core/terminals.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport os from 'os'\nimport path from 'path'\nimport { v4 as uuid } from 'uuid'\nimport which from 'which'\nimport Terminals from '../../core/terminals'\nimport { TerminalModel } from '../../model/terminal'\nimport window from '../../window'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet terminals: Terminals\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  terminals = new Terminals()\n})\n\nafterEach(async () => {\n  terminals.reset()\n  await helper.reset()\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\ndescribe('create terminal', () => {\n  it('should use cleaned env', async () => {\n    let terminal = await terminals.createTerminal(nvim, {\n      name: `test-${uuid()}`,\n      shellPath: which.sync('bash'),\n      strictEnv: true\n    })\n    await helper.wait(10)\n    terminal.sendText(`echo $NODE_ENV`, true)\n    await helper.wait(50)\n    let buf = nvim.createBuffer(terminal.bufnr)\n    let lines = await buf.lines\n    expect(lines.includes('test')).toBe(false)\n  })\n\n  it('should use custom shell command', async () => {\n    let terminal = await terminals.createTerminal(nvim, {\n      name: `test-${uuid()}`,\n      shellPath: which.sync('bash')\n    })\n    let bufnr = terminal.bufnr\n    let bufname = await nvim.call('bufname', [bufnr]) as string\n    expect(bufname.includes('bash')).toBe(true)\n  })\n\n  it('should use custom cwd', async () => {\n    let basename = path.basename(os.tmpdir())\n    let terminal = await terminals.createTerminal(nvim, {\n      name: `test-${uuid()}`,\n      cwd: os.tmpdir()\n    })\n    let bufnr = terminal.bufnr\n    let bufname = await nvim.call('bufname', [bufnr]) as string\n    expect(bufname.includes(basename)).toBe(true)\n  })\n\n  it('should have exit code', async () => {\n    let exitStatus\n    terminals.onDidCloseTerminal(terminal => {\n      exitStatus = terminal.exitStatus\n    })\n    let terminal = await terminals.createTerminal(nvim, {\n      name: `test-${uuid()}`,\n      shellPath: which.sync('bash'),\n      strictEnv: true\n    })\n    terminal.sendText('exit', true)\n    await helper.waitFor('bufloaded', [terminal.bufnr], 0)\n    await helper.waitValue(() => {\n      return exitStatus != null\n    }, true)\n    expect(exitStatus.code).toBeDefined()\n  })\n\n  it('should return false on show when buffer unloaded', async () => {\n    let model = new TerminalModel('bash', [], nvim)\n    await model.start()\n    expect(model.bufnr).toBeDefined()\n    await nvim.command(`bd! ${model.bufnr}`)\n    let res = await model.show()\n    expect(res).toBe(false)\n  })\n\n  it('should not throw when show & hide disposed terminal', async () => {\n    let terminal = await terminals.createTerminal(nvim, {\n      name: `test-${uuid()}`,\n      shellPath: which.sync('bash')\n    })\n    terminal.dispose()\n    await terminal.show()\n    await terminal.hide()\n  })\n\n  it('should show terminal on current window', async () => {\n    let terminal = await terminals.createTerminal(nvim, {\n      name: `test-${uuid()}`,\n      shellPath: which.sync('bash')\n    })\n    let winid = await nvim.call('bufwinid', [terminal.bufnr])\n    expect(winid).toBeGreaterThan(0)\n    await nvim.call('win_gotoid', [winid])\n    await terminal.show()\n  })\n\n  it('should show terminal that shown', async () => {\n    let terminal = await terminals.createTerminal(nvim, {\n      name: `test-${uuid()}`,\n      shellPath: which.sync('bash')\n    })\n    let res = await terminal.show(true)\n    expect(res).toBe(true)\n  })\n\n  it('should show hidden terminal', async () => {\n    let terminal = await terminals.createTerminal(nvim, {\n      name: `test-${uuid()}`,\n      shellPath: which.sync('bash')\n    })\n    await terminal.hide()\n    await terminal.show()\n  })\n\n  it('should create terminal', async () => {\n    let terminal = await window.createTerminal({\n      name: `test-${uuid()}`,\n    })\n    expect(terminal).toBeDefined()\n    expect(terminal.processId).toBeDefined()\n    expect(terminal.name).toBeDefined()\n    terminal.dispose()\n    await helper.wait(30)\n    expect(terminal.bufnr).toBeUndefined()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/core/ui.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { Position, Range } from 'vscode-languageserver-types'\nimport * as ui from '../../core/ui'\nimport helper from '../helper'\n\nlet nvim: Neovim\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  await helper.reset()\n})\n\ndescribe('getCursorPosition()', () => {\n  it('should get cursor position', async () => {\n    await nvim.call('cursor', [1, 1])\n    let res = await ui.getCursorPosition(nvim)\n    expect(res).toEqual({\n      line: 0,\n      character: 0\n    })\n  })\n})\n\ndescribe('moveTo()', () => {\n  it('should moveTo position', async () => {\n    await nvim.setLine('foo')\n    await ui.moveTo(nvim, Position.create(0, 1), true)\n    let res = await ui.getCursorPosition(nvim)\n    expect(res).toEqual({ line: 0, character: 1 })\n  })\n})\n\ndescribe('getCursorScreenPosition()', () => {\n  it('should get cursor screen position', async () => {\n    let res = await ui.getCursorScreenPosition(nvim)\n    expect(res).toBeDefined()\n    expect(typeof res.row).toBe('number')\n    expect(typeof res.col).toBe('number')\n  })\n})\n\ndescribe('createFloatFactory()', () => {\n  it('should create FloatFactory', async () => {\n    let f = ui.createFloatFactory(nvim, { border: true, autoHide: false, breaks: false }, { close: true })\n    await f.show([{ content: 'shown', filetype: 'txt' }])\n    let activated = await f.activated()\n    expect(activated).toBe(true)\n    expect(f.window != null).toBe(true)\n    let win = await helper.getFloat()\n    expect(win).toBeDefined()\n    let id = await nvim.call('coc#float#get_related', [win.id, 'border', 0]) as number\n    expect(id).toBeGreaterThan(0)\n    id = await nvim.call('coc#float#get_related', [win.id, 'close', 0]) as number\n    expect(id).toBeGreaterThan(0)\n    await f.show([{ content: 'shown', filetype: 'txt' }], { offsetX: 10 })\n    let curr = await helper.getFloat()\n    expect(curr.id).toBe(win.id)\n  })\n})\n\ndescribe('showMessage()', () => {\n  it('should showMessage on vim', async () => {\n    ui.echoMessages(nvim, 'my message', 'more', 'more')\n    await helper.wait(50)\n    let cmdline = await helper.getCmdline()\n    expect(cmdline).toMatch(/my message/)\n  })\n\n  it('should get messageLevel', () => {\n    let level = ui.toMessageLevel('error')\n    expect(level).toBe(ui.MessageLevel.Error)\n    level = ui.toMessageLevel('warning')\n    expect(level).toBe(ui.MessageLevel.Warning)\n    level = ui.toMessageLevel('more')\n    expect(level).toBe(ui.MessageLevel.More)\n  })\n})\n\ndescribe('getSelection()', () => {\n  it('should return null when no selection exists', async () => {\n    let res = await ui.getSelection(nvim, 'v')\n    expect(res).toBeNull()\n  })\n\n  it('should return range for line selection', async () => {\n    await nvim.setLine('foo')\n    await nvim.input('V')\n    await nvim.input('<esc>')\n    let res = await ui.getSelection(nvim, 'V')\n    expect(res).toEqual({ start: { line: 0, character: 0 }, end: { line: 1, character: 0 } })\n  })\n\n  it('should return range of current line', async () => {\n    await nvim.command('normal! gg')\n    let res = await ui.getSelection(nvim, 'currline')\n    expect(res).toEqual(Range.create(0, 0, 1, 0))\n  })\n})\n\ndescribe('selectRange()', () => {\n  it('should select range #1', async () => {\n    await nvim.call('setline', [1, ['foo', 'b']])\n    await nvim.command('set selection=inclusive')\n    await nvim.command('set virtualedit=onemore')\n    await ui.selectRange(nvim, Range.create(0, 0, 1, 1), true)\n    await nvim.input('<esc>')\n    let res = await ui.getSelection(nvim, 'v')\n    expect(res).toEqual(Range.create(0, 0, 1, 1))\n  })\n\n  it('should select range #2', async () => {\n    await nvim.call('setline', [1, ['foo', 'b']])\n    await ui.selectRange(nvim, Range.create(0, 0, 1, 0), true)\n    await nvim.input('<esc>')\n    let res = await ui.getSelection(nvim, 'v')\n    expect(res).toEqual(Range.create(0, 0, 0, 3))\n  })\n\n  it('should select range #3', async () => {\n    await ui.selectRange(nvim, Range.create(0, 0, 0, 0), true)\n    let m = await nvim.mode\n    expect(m.mode).toBe('v')\n    await nvim.input('<esc>')\n    await ui.selectRange(nvim, Range.create(0, 0, 0, 1), true)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/core/workspaceFolder.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport { Disposable, WorkspaceFolder, WorkspaceFoldersChangeEvent } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport Configurations from '../../configuration/index'\nimport WorkspaceFolderController, { PatternType } from '../../core/workspaceFolder'\nimport { disposeAll } from '../../util'\nimport { CancellationError } from '../../util/errors'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet workspaceFolder: WorkspaceFolderController\nlet configurations: Configurations\nlet disposables: Disposable[] = []\nlet nvim: Neovim\n\nfunction updateConfiguration(key: string, value: any, defaults: any): void {\n  configurations.updateMemoryConfig({ [key]: value })\n  disposables.push({\n    dispose: () => {\n      configurations.updateMemoryConfig({ [key]: defaults })\n    }\n  })\n}\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  let userConfigFile = path.join(process.env.COC_VIMCONFIG, 'coc-settings.json')\n  configurations = new Configurations(userConfigFile, undefined)\n  workspaceFolder = new WorkspaceFolderController(configurations)\n})\n\nafterEach(async () => {\n  await helper.reset()\n  disposeAll(disposables)\n  workspaceFolder.reset()\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\ndescribe('WorkspaceFolderController', () => {\n  describe('asRelativePath()', () => {\n    function assertAsRelativePath(input: string | URI, expected: string, includeWorkspace?: boolean) {\n      const actual = workspaceFolder.getRelativePath(input, includeWorkspace)\n      expect(actual).toBe(expected)\n    }\n\n    it('should get relative path', async () => {\n      workspaceFolder.addWorkspaceFolder(`/Coding/Applications/NewsWoWBot`, false)\n      assertAsRelativePath('/Coding/Applications/NewsWoWBot/bernd/das/brot', 'bernd/das/brot')\n      assertAsRelativePath('/Apps/DartPubCache/hosted/pub.dartlang.org/convert-2.0.1/lib/src/hex.dart',\n        '/Apps/DartPubCache/hosted/pub.dartlang.org/convert-2.0.1/lib/src/hex.dart')\n      assertAsRelativePath('', '')\n      assertAsRelativePath('/foo/bar', '/foo/bar')\n      assertAsRelativePath('in/out', 'in/out')\n      assertAsRelativePath(null, '')\n      assertAsRelativePath(URI.file('/tmp'), '/tmp')\n    })\n\n    it('should asRelativePath, same paths, #11402', async () => {\n      const root = '/home/aeschli/workspaces/samples/docker'\n      const input = '/home/aeschli/workspaces/samples/docker'\n      workspaceFolder.addWorkspaceFolder(root, false)\n      assertAsRelativePath(input, input)\n      const input2 = '/home/aeschli/workspaces/samples/docker/a.file'\n      assertAsRelativePath(input2, 'a.file')\n    })\n\n    it('should asRelativePath, not workspaceFolder', async () => {\n      expect(workspace.asRelativePath('')).toBe('')\n      assertAsRelativePath('/foo/bar', '/foo/bar')\n    })\n\n    it('should asRelativePath, multiple folders', () => {\n      workspaceFolder.addWorkspaceFolder(`/Coding/One`, false)\n      workspaceFolder.addWorkspaceFolder(`/Coding/Two`, false)\n      assertAsRelativePath('/Coding/One/file.txt', 'One/file.txt')\n      assertAsRelativePath('/Coding/Two/files/out.txt', 'Two/files/out.txt')\n      assertAsRelativePath('/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt')\n    })\n\n    it('should slightly inconsistent behaviour of asRelativePath and getWorkspaceFolder, #31553', async () => {\n      workspaceFolder.addWorkspaceFolder(`/Coding/One`, false)\n      workspaceFolder.addWorkspaceFolder(`/Coding/Two`, false)\n\n      assertAsRelativePath('/Coding/One/file.txt', 'One/file.txt')\n      assertAsRelativePath('/Coding/One/file.txt', 'One/file.txt', true)\n      assertAsRelativePath('/Coding/One/file.txt', 'file.txt', false)\n      assertAsRelativePath('/Coding/Two/files/out.txt', 'Two/files/out.txt')\n      assertAsRelativePath('/Coding/Two/files/out.txt', 'Two/files/out.txt', true)\n      assertAsRelativePath('/Coding/Two/files/out.txt', 'files/out.txt', false)\n      assertAsRelativePath('/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt')\n      assertAsRelativePath('/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt', true)\n      assertAsRelativePath('/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt', false)\n    })\n  })\n\n  describe('setWorkspaceFolders()', () => {\n    it('should set valid folders', async () => {\n      workspaceFolder.setWorkspaceFolders([os.tmpdir(), '/a/not_exists'])\n      let folders = workspaceFolder.workspaceFolders\n      expect(folders.length).toBe(2)\n    })\n  })\n\n  describe('getWorkspaceFolder()', () => {\n    it('should get workspaceFolder by uri', async () => {\n      let res = workspaceFolder.getWorkspaceFolder(URI.parse('untitled://1'))\n      expect(res).toBeUndefined()\n      res = workspaceFolder.getWorkspaceFolder(URI.file('/a/b'))\n      expect(res).toBeUndefined()\n      let filepath = path.join(process.cwd(), 'a/b')\n      workspaceFolder.setWorkspaceFolders([process.cwd()])\n      res = workspaceFolder.getWorkspaceFolder(URI.file(filepath))\n      expect(URI.parse(res.uri).fsPath).toBe(process.cwd())\n    })\n  })\n\n  describe('getRootPatterns()', () => {\n    it('should get patterns from b:coc_root_patterns', async () => {\n      await nvim.command('edit t.vim | let b:coc_root_patterns=[\"foo\"]')\n      await nvim.command('setf vim')\n      let doc = await workspace.document\n      let res = workspaceFolder.getRootPatterns(doc, PatternType.Buffer)\n      expect(res).toEqual(['foo'])\n    })\n\n    it('should add patterns from languageserver', () => {\n      updateConfiguration('languageserver.test', {\n        filetypes: ['vim'],\n        rootPatterns: ['bar']\n      }, undefined)\n      workspaceFolder.addRootPattern('vim', ['foo'])\n      let res = workspaceFolder.getServerRootPatterns('vim')\n      expect(res.includes('foo')).toBe(true)\n      expect(res.includes('bar')).toBe(true)\n    })\n\n    it('should get patterns from user configuration', async () => {\n      let doc = await workspace.document\n      let res = workspaceFolder.getRootPatterns(doc, PatternType.Global)\n      expect(res.includes('.git')).toBe(true)\n    })\n  })\n\n  describe('resolveRoot()', () => {\n    const cwd = process.cwd()\n    const expand = (input: string) => {\n      return workspace.expand(input)\n    }\n\n    it('should resolve to cwd for file in cwd', async () => {\n      updateConfiguration('workspace.rootPatterns', [], ['.git', '.hg', '.projections.json'])\n      let file = path.join(os.tmpdir(), 'foo')\n      let doc = await helper.createDocument(file)\n      let res = workspaceFolder.resolveRoot(doc, os.tmpdir(), false, expand)\n      expect(res).toBe(os.tmpdir())\n    })\n\n    it('should ignore cwd by ignore pattern', async () => {\n      updateConfiguration('workspace.rootPatterns', [], ['.git', '.hg', '.projections.json'])\n      updateConfiguration('workspace.ignoredFolders', ['**/*'], ['$HOME'])\n      let file = path.join(os.tmpdir(), 'foo')\n      let doc = await helper.createDocument(file)\n      let res = workspaceFolder.resolveRoot(doc, os.tmpdir(), false, expand)\n      expect(res).toBeNull()\n    })\n\n    it('should not fallback to cwd as workspace folder', async () => {\n      updateConfiguration('workspace.rootPatterns', [], ['.git', '.hg', '.projections.json'])\n      updateConfiguration('workspace.workspaceFolderFallbackCwd', false, true)\n      let file = path.join(os.tmpdir(), 'foo')\n      await nvim.command(`edit ${file}`)\n      let doc = await workspace.document\n      let res = workspaceFolder.resolveRoot(doc, os.tmpdir(), false, expand)\n      expect(res).toBe(null)\n    })\n\n    it('should return null for untitled buffer', async () => {\n      await nvim.command('enew')\n      let doc = await workspace.document\n      let res = workspaceFolder.resolveRoot(doc, cwd, false, expand)\n      expect(res).toBe(null)\n    })\n\n    it('should respect ignored filetypes', async () => {\n      updateConfiguration('workspace.ignoredFiletypes', ['vim'], [])\n      await nvim.command('edit t.vim')\n      await nvim.command('setf vim')\n      let doc = await workspace.document\n      let res = workspaceFolder.resolveRoot(doc, cwd, false, expand)\n      expect(res).toBe(null)\n    })\n\n    it('should respect workspaceFolderCheckCwd', async () => {\n      let called = 0\n      disposables.push(workspaceFolder.onDidChangeWorkspaceFolders(() => {\n        called++\n      }))\n      workspaceFolder.addRootPattern('vim', ['.vim'])\n      await nvim.command('edit a/.vim/t.vim')\n      await nvim.command('setf vim')\n      let doc = await workspace.document\n      let res = workspaceFolder.resolveRoot(doc, cwd, true, expand)\n      expect(res).toBe(process.cwd())\n      await nvim.command('edit a/foo')\n      doc = await workspace.document\n      res = workspaceFolder.resolveRoot(doc, cwd, true, expand)\n      expect(res).toBe(process.cwd())\n      expect(called).toBe(1)\n    })\n\n    it('should respect ignored folders', async () => {\n      updateConfiguration('workspace.ignoredFolders', ['$HOME/foo', '$HOME'], [])\n      let file = path.join(os.homedir(), '.vim/bar')\n      workspaceFolder.addRootPattern('vim', ['.vim'])\n      await nvim.command(`edit ${file}`)\n      await nvim.command('setf vim')\n      let doc = await workspace.document\n      let res = workspaceFolder.resolveRoot(doc, path.join(os.homedir(), 'foo'), true, expand)\n      expect(res).toBe(null)\n    })\n\n    it('should respect specific filetype for bottomUpFileTypes', async () => {\n      updateConfiguration('workspace.rootPatterns', ['.vim'], ['.git', '.hg', '.projections.json'])\n      updateConfiguration('workspace.bottomUpFiletypes', ['vim'], [])\n      let root = path.join(os.tmpdir(), 'a')\n      let dir = path.join(root, '.vim')\n      fs.mkdirSync(dir, { recursive: true })\n      let file = path.join(dir, 'foo.vim')\n      await nvim.command(`edit ${file}`)\n      let doc = await workspace.document\n      expect(doc.filetype).toBe('vim')\n      let res = workspaceFolder.resolveRoot(doc, file, true, expand)\n      expect(res).toBe(root)\n    })\n\n    it('should respect wildcard', async () => {\n      updateConfiguration('workspace.rootPatterns', ['.vim'], ['.git', '.hg', '.projections.json'])\n      updateConfiguration('workspace.bottomUpFiletypes', ['*'], [])\n      let root = path.join(os.tmpdir(), 'a')\n      let dir = path.join(root, '.vim')\n      fs.mkdirSync(dir, { recursive: true })\n      let file = path.join(dir, 'foo')\n      await nvim.command(`edit ${file}`)\n      let doc = await workspace.document\n      let res = workspaceFolder.resolveRoot(doc, file, true, expand)\n      expect(res).toBe(root)\n    })\n  })\n\n  describe('renameWorkspaceFolder()', () => {\n    it('should rename workspaceFolder', async () => {\n      let e: WorkspaceFoldersChangeEvent\n      disposables.push(workspaceFolder.onDidChangeWorkspaceFolders(ev => {\n        e = ev\n      }))\n      let cwd = process.cwd()\n      workspaceFolder.addWorkspaceFolder(cwd, false)\n      workspaceFolder.addWorkspaceFolder(cwd, false)\n      workspaceFolder.renameWorkspaceFolder(cwd, path.join(cwd, '.vim'))\n      expect(e.removed.length).toBe(1)\n      expect(e.added.length).toBe(1)\n    })\n  })\n\n  describe('removeWorkspaceFolder()', () => {\n    it('should remote workspaceFolder', async () => {\n      let e: WorkspaceFoldersChangeEvent\n      disposables.push(workspaceFolder.onDidChangeWorkspaceFolders(ev => {\n        e = ev\n      }))\n      let cwd = process.cwd()\n      workspaceFolder.addWorkspaceFolder(cwd, false)\n      workspaceFolder.removeWorkspaceFolder(cwd)\n      workspaceFolder.removeWorkspaceFolder('/a/b')\n      expect(e.removed.length).toBe(1)\n      expect(e.added.length).toBe(0)\n    })\n\n    it('should not throw for invalid folder', async () => {\n      workspaceFolder.addWorkspaceFolder('tmp', false)\n      workspaceFolder.removeWorkspaceFolder('tmp')\n      workspaceFolder.renameWorkspaceFolder('tmp', 'other')\n    })\n  })\n\n  describe('checkPatterns()', () => {\n    it('should check if pattern exists', async () => {\n      expect(await workspaceFolder.checkPatterns([], ['p'])).toBe(false)\n      let folder: WorkspaceFolder = { name: '', uri: URI.file(process.cwd()).toString() }\n      let res = await workspaceFolder.checkPatterns([folder], ['package.json', '**/not_exists'])\n      expect(res).toBe(true)\n      res = await workspaceFolder.checkPatterns([folder], ['**/not_exists'])\n      expect(res).toBe(false)\n    })\n\n    it('should not throw when checkFolder throw error', async () => {\n      let spy = jest.spyOn(workspaceFolder, 'checkFolder').mockImplementation(() => {\n        return Promise.reject(new Error('error'))\n      })\n      let folder: WorkspaceFolder = { name: '', uri: URI.file(process.cwd()).toString() }\n      await workspaceFolder.checkPatterns([folder], ['package.json', '**/not_exists'])\n      spy.mockRestore()\n    })\n\n    it('should not throw on timeout', async () => {\n      let spy = jest.spyOn(workspaceFolder, 'checkFolder').mockImplementation((_dir, _patterns, token) => {\n        return new Promise((resolve, reject) => {\n          let timer = setTimeout(() => {\n            resolve(undefined)\n          }, 200)\n          token.onCancellationRequested(() => {\n            clearTimeout(timer)\n            reject(new CancellationError())\n          })\n        })\n      })\n      let folder: WorkspaceFolder = { name: '', uri: URI.file(process.cwd()).toString() }\n      let res = await workspaceFolder.checkPatterns([folder], ['**/schema.json'])\n      spy.mockRestore()\n      expect(res).toBe(false)\n    })\n  })\n\n  describe('onDocumentDetach()', () => {\n    it('should check uris', async () => {\n      updateConfiguration('workspace.removeEmptyWorkspaceFolder', true, false)\n      let folder = os.tmpdir()\n      workspaceFolder.addWorkspaceFolder(folder, false)\n      workspaceFolder.onDocumentDetach([URI.parse('untitled:/1'), URI.parse('file:///foo/bar')])\n      expect(workspaceFolder.workspaceFolders.length).toBe(0)\n      workspaceFolder.addWorkspaceFolder(folder, false)\n      workspaceFolder.onDocumentDetach([URI.parse('untitled:/1'), URI.file(path.join(os.tmpdir(), 'foo'))])\n      expect(workspaceFolder.workspaceFolders.length).toBe(1)\n    })\n\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/callHierarchy.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { Disposable, CallHierarchyItem, SymbolKind, Range, SymbolTag, CancellationToken, Position } from 'vscode-languageserver-protocol'\nimport CallHierarchyHandler from '../../handler/callHierarchy'\nimport languages from '../../languages'\nimport workspace from '../../workspace'\nimport { disposeAll } from '../../util'\nimport { URI } from 'vscode-uri'\nimport helper, { createTmpFile } from '../helper'\nimport commands from '../../commands'\n\nlet nvim: Neovim\nlet callHierarchy: CallHierarchyHandler\nlet disposables: Disposable[] = []\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  callHierarchy = helper.plugin.getHandler().callHierarchy\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n})\n\nfunction createCallItem(name: string, kind: SymbolKind, uri: string, range: Range): CallHierarchyItem {\n  return {\n    name,\n    kind,\n    uri,\n    range,\n    selectionRange: range\n  }\n}\n\ndescribe('CallHierarchy', () => {\n  it('should throw when provider does not exist', async () => {\n    await expect(async () => {\n      await callHierarchy.getIncoming()\n    }).rejects.toThrow(Error)\n  })\n\n  it('should return null when provider not exist', async () => {\n    let token = CancellationToken.None\n    let doc = await workspace.document\n    let res: any\n    res = await languages.prepareCallHierarchy(doc.textDocument, Position.create(0, 0), token)\n    expect(res).toBeNull()\n    let item = createCallItem('name', SymbolKind.Class, doc.uri, Range.create(0, 0, 1, 0))\n    res = await languages.provideOutgoingCalls(doc.textDocument, item, token)\n    expect(res).toBeNull()\n    res = await languages.provideIncomingCalls(doc.textDocument, item, token)\n    expect(res).toBeNull()\n  })\n\n  it('should throw when prepare failed', async () => {\n    disposables.push(languages.registerCallHierarchyProvider([{ language: '*' }], {\n      prepareCallHierarchy() {\n        return undefined\n      },\n      provideCallHierarchyIncomingCalls() {\n        return []\n      },\n      provideCallHierarchyOutgoingCalls() {\n        return []\n      }\n    }))\n    let fn = async () => {\n      await callHierarchy.getOutgoing()\n    }\n    await expect(fn()).rejects.toThrow(Error)\n  })\n\n  it('should get incoming & outgoing callHierarchy items', async () => {\n    disposables.push(languages.registerCallHierarchyProvider([{ language: '*' }], {\n      prepareCallHierarchy() {\n        return createCallItem('foo', SymbolKind.Class, 'test:///foo', Range.create(0, 0, 0, 5))\n      },\n      provideCallHierarchyIncomingCalls() {\n        return [{\n          from: createCallItem('bar', SymbolKind.Class, 'test:///bar', Range.create(1, 0, 1, 5)),\n          fromRanges: [Range.create(0, 0, 0, 5)]\n        }]\n      },\n      provideCallHierarchyOutgoingCalls() {\n        return [{\n          to: createCallItem('bar', SymbolKind.Class, 'test:///bar', Range.create(1, 0, 1, 5)),\n          fromRanges: [Range.create(1, 0, 1, 5)]\n        }]\n      }\n    }))\n    let res = await helper.doAction('incomingCalls')\n    expect(res.length).toBe(1)\n    expect(res[0].from.name).toBe('bar')\n    let outgoing = await helper.doAction('outgoingCalls')\n    expect(outgoing.length).toBe(1)\n    res = await callHierarchy.getIncoming(outgoing[0].to)\n    expect(res.length).toBe(1)\n  })\n\n  it('should show warning when provider does not exist', async () => {\n    await helper.doAction('showIncomingCalls')\n    let line = await helper.getCmdline()\n    expect(line).toMatch('not found')\n  })\n\n  it('should show message when no result returned.', async () => {\n    disposables.push(languages.registerCallHierarchyProvider([{ language: '*' }], {\n      prepareCallHierarchy() {\n        return null\n      },\n      provideCallHierarchyIncomingCalls() {\n        return []\n      },\n      provideCallHierarchyOutgoingCalls() {\n        return []\n      }\n    }))\n    await callHierarchy.showCallHierarchyTree('incoming')\n    let line = await helper.getCmdline()\n    expect(line).toMatch('Unable')\n  })\n\n  it('should render description and support default action', async () => {\n    helper.updateConfiguration('callHierarchy.enableTooltip', false)\n    let doc = await workspace.document\n    let bufnr = doc.bufnr\n    await doc.buffer.setLines(['foo'], { start: 0, end: -1, strictIndexing: false })\n    let fsPath = await createTmpFile('foo\\nbar\\ncontent\\n')\n    let uri = URI.file(fsPath).toString()\n    disposables.push(languages.registerCallHierarchyProvider([{ language: '*' }], {\n      prepareCallHierarchy() {\n        return createCallItem('foo', SymbolKind.Class, doc.uri, Range.create(0, 0, 0, 3))\n      },\n      provideCallHierarchyIncomingCalls() {\n        let item = createCallItem('bar', SymbolKind.Class, uri, Range.create(1, 0, 1, 3))\n        item.detail = 'Detail'\n        item.tags = [SymbolTag.Deprecated]\n        return [{\n          from: item,\n          fromRanges: [Range.create(2, 0, 2, 5)]\n        }]\n      },\n      provideCallHierarchyOutgoingCalls() {\n        return []\n      }\n    }))\n    await commands.executeCommand('document.showIncomingCalls')\n    let buf = await nvim.buffer\n    let lines = await buf.lines\n    expect(lines).toEqual([\n      'INCOMING CALLS',\n      '- c foo',\n      '  + c bar Detail'\n    ])\n    await nvim.command('exe 3')\n    await nvim.input('t')\n    await helper.waitFor('getline', ['.'], '  - c bar Detail')\n    await nvim.input('<cr>')\n    await helper.waitFor('expand', ['%:p'], fsPath)\n    let res = await nvim.call('coc#cursor#position')\n    expect(res).toEqual([1, 0])\n    let matches = await nvim.call('getmatches') as any[]\n    expect(matches.length).toBe(2)\n    await nvim.command(`b ${bufnr}`)\n    await helper.wait(50)\n    matches = await nvim.call('getmatches') as any[]\n    expect(matches.length).toBe(0)\n    await nvim.command(`wincmd o`)\n  })\n\n  it('should invoke reveal command', async () => {\n    let doc = await helper.createDocument('foo')\n    await nvim.setLine('foo')\n    let item: any = createCallItem('name', SymbolKind.Class, doc.uri, Range.create(0, 0, 1, 0))\n    let winid = await nvim.call('win_getid') as number\n    let commandId = 'callHierarchy.reveal'\n    await commands.executeCommand(commandId, winid, item)\n    item.ranges = [Range.create(0, 0, 0, 1)]\n    item.sourceUri = 'lsp:/1'\n    await commands.executeCommand(commandId, winid, item)\n    let newDoc = await helper.createDocument('bar')\n    await workspace.jumpTo(doc.uri)\n    item.sourceUri = newDoc.uri\n    await commands.executeCommand(commandId, winid, item)\n  })\n\n  it('should invoke open in new tab action', async () => {\n    let doc = await workspace.document\n    await doc.buffer.setLines(['foo', 'bar'], { start: 0, end: -1, strictIndexing: false })\n    let fsPath = await createTmpFile('foo\\nbar\\ncontent\\n')\n    let uri = URI.file(fsPath).toString()\n    disposables.push(languages.registerCallHierarchyProvider([{ language: '*' }], {\n      prepareCallHierarchy() {\n        return createCallItem('foo', SymbolKind.Class, doc.uri, Range.create(0, 0, 0, 3))\n      },\n      provideCallHierarchyIncomingCalls() {\n        return []\n      },\n      provideCallHierarchyOutgoingCalls() {\n        let item = createCallItem('bar', SymbolKind.Class, uri, Range.create(0, 0, 0, 1))\n        item.detail = 'Detail'\n        return [{\n          to: item,\n          fromRanges: [Range.create(1, 0, 1, 3)]\n        }]\n      }\n    }))\n    let win = await nvim.window\n    await commands.executeCommand('document.showOutgoingCalls')\n    let buf = await nvim.buffer\n    let lines = await buf.lines\n    expect(lines).toEqual([\n      'OUTGOING CALLS',\n      '- c foo',\n      '  + c bar Detail'\n    ])\n    await nvim.command('exe 3')\n    await nvim.input('<tab>')\n    await helper.waitPrompt()\n    await nvim.input('<cr>')\n    await helper.waitFor('tabpagenr', [], 2)\n    doc = await workspace.document\n    expect(doc.uri).toBe(uri)\n    await helper.waitValue(async () => {\n      let res = await nvim.call('getmatches', [win.id]) as any[]\n      return res.length\n    }, 1)\n  })\n\n  it('should invoke show incoming calls action', async () => {\n    let doc = await workspace.document\n    await doc.buffer.setLines(['foo', 'bar'], { start: 0, end: -1, strictIndexing: false })\n    let fsPath = await createTmpFile('foo\\nbar\\ncontent\\n')\n    let uri = URI.file(fsPath).toString()\n    disposables.push(languages.registerCallHierarchyProvider([{ language: '*' }], {\n      prepareCallHierarchy() {\n        return createCallItem('foo', SymbolKind.Class, doc.uri, Range.create(0, 0, 0, 3))\n      },\n      provideCallHierarchyIncomingCalls() {\n        return [{\n          from: createCallItem('test', SymbolKind.Class, 'test:///bar', Range.create(1, 0, 1, 5)),\n          fromRanges: [Range.create(0, 0, 0, 5)]\n        }]\n      },\n      provideCallHierarchyOutgoingCalls() {\n        let item = createCallItem('bar', SymbolKind.Class, uri, Range.create(0, 0, 0, 1))\n        item.detail = 'Detail'\n        return [{\n          to: item,\n          fromRanges: [Range.create(1, 0, 1, 3)]\n        }]\n      }\n    }))\n    await callHierarchy.showCallHierarchyTree('outgoing')\n    let buf = await nvim.buffer\n    let lines = await buf.lines\n    expect(lines).toEqual([\n      'OUTGOING CALLS',\n      '- c foo',\n      '  + c bar Detail'\n    ])\n    await nvim.command('exe 3')\n    await nvim.input('<tab>')\n    await helper.waitPrompt()\n    await nvim.input('3')\n    await helper.wait(200)\n    lines = await buf.lines\n    expect(lines).toEqual([\n      'INCOMING CALLS',\n      '- c bar Detail',\n      '  + c test'\n    ])\n  })\n\n  it('should invoke show outgoing calls action', async () => {\n    let doc = await workspace.document\n    await doc.buffer.setLines(['foo', 'bar'], { start: 0, end: -1, strictIndexing: false })\n    let fsPath = await createTmpFile('foo\\nbar\\ncontent\\n')\n    let uri = URI.file(fsPath).toString()\n    disposables.push(languages.registerCallHierarchyProvider([{ language: '*' }], {\n      prepareCallHierarchy() {\n        return createCallItem('foo', SymbolKind.Class, doc.uri, Range.create(0, 0, 0, 3))\n      },\n      provideCallHierarchyIncomingCalls() {\n        return [{\n          from: createCallItem('test', SymbolKind.Class, 'test:///bar', Range.create(1, 0, 1, 5)),\n          fromRanges: [Range.create(0, 0, 0, 5)]\n        }]\n      },\n      provideCallHierarchyOutgoingCalls() {\n        let item = createCallItem('bar', SymbolKind.Class, uri, Range.create(0, 0, 0, 1))\n        item.detail = 'Detail'\n        return [{\n          to: item,\n          fromRanges: [Range.create(1, 0, 1, 3)]\n        }]\n      }\n    }))\n    await callHierarchy.showCallHierarchyTree('incoming')\n    let buf = await nvim.buffer\n    let lines = await buf.lines\n    expect(lines).toEqual([\n      'INCOMING CALLS',\n      '- c foo',\n      '  + c test'\n    ])\n    await nvim.command('exe 3')\n    await nvim.input('<tab>')\n    await helper.waitPrompt()\n    await nvim.input('4')\n    await helper.wait(200)\n    lines = await buf.lines\n    expect(lines).toEqual([\n      'OUTGOING CALLS',\n      '- c test',\n      '  + c bar Detail'\n    ])\n  })\n\n  it('should invoke dismiss action #1', async () => {\n    let doc = await workspace.document\n    await doc.buffer.setLines(['foo', 'bar'], { start: 0, end: -1, strictIndexing: false })\n    let fsPath = await createTmpFile('foo\\nbar\\ncontent\\n')\n    let uri = URI.file(fsPath).toString()\n    disposables.push(languages.registerCallHierarchyProvider([{ language: '*' }], {\n      prepareCallHierarchy() {\n        return createCallItem('foo', SymbolKind.Class, doc.uri, Range.create(0, 0, 0, 3))\n      },\n      provideCallHierarchyIncomingCalls() {\n        return []\n      },\n      provideCallHierarchyOutgoingCalls() {\n        let item = createCallItem('bar', SymbolKind.Class, uri, Range.create(0, 0, 0, 1))\n        item.detail = 'Detail'\n        return [{\n          to: item,\n          fromRanges: [Range.create(1, 0, 1, 3)]\n        }]\n      }\n    }))\n    await callHierarchy.showCallHierarchyTree('outgoing')\n    let buf = await nvim.buffer\n    let lines = await buf.lines\n    expect(lines).toEqual([\n      'OUTGOING CALLS',\n      '- c foo',\n      '  + c bar Detail'\n    ])\n    await nvim.command('exe 3')\n    await nvim.input('<tab>')\n    await helper.waitPrompt()\n    await nvim.input('2')\n    await helper.wait(200)\n    lines = await buf.lines\n    expect(lines).toEqual([\n      'OUTGOING CALLS',\n      '- c foo'\n    ])\n    await nvim.command('exe 2')\n    await nvim.input('<tab>')\n    await helper.waitPrompt()\n    await nvim.input('2')\n    await helper.wait(30)\n  })\n\n  it('should invoke dismiss action #2', async () => {\n    let doc = await workspace.document\n    await doc.buffer.setLines(['foo', 'bar'], { start: 0, end: -1, strictIndexing: false })\n    let fsPath = await createTmpFile('foo\\nbar\\ncontent\\n')\n    let uri = URI.file(fsPath).toString()\n    disposables.push(languages.registerCallHierarchyProvider([{ language: '*' }], {\n      prepareCallHierarchy() {\n        return createCallItem('foo', SymbolKind.Class, doc.uri, Range.create(0, 0, 0, 3))\n      },\n      provideCallHierarchyIncomingCalls() {\n        return []\n      },\n      provideCallHierarchyOutgoingCalls() {\n        let item = createCallItem('bar', SymbolKind.Class, uri, Range.create(0, 0, 0, 1))\n        item.detail = 'Detail'\n        return [{\n          to: item,\n          fromRanges: [Range.create(1, 0, 1, 3)]\n        }]\n      }\n    }))\n    await helper.doAction('showOutgoingCalls')\n    let buf = await nvim.buffer\n    let lines = await buf.lines\n    expect(lines).toEqual([\n      'OUTGOING CALLS',\n      '- c foo',\n      '  + c bar Detail'\n    ])\n    await nvim.command('exe 3')\n    await nvim.input('t')\n    await helper.waitFor('line', ['$'], 4)\n    await nvim.command('exe 4')\n    await nvim.input('<tab>')\n    await helper.waitPrompt()\n    await nvim.input('2')\n    await helper.waitFor('line', ['$'], 3)\n    lines = await buf.lines\n    expect(lines).toEqual([\n      'OUTGOING CALLS',\n      '- c foo',\n      '  - c bar Detail'\n    ])\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/codeActions.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationToken, CodeAction, CodeActionContext, CodeActionKind, Command, Disposable, Position, Range, TextEdit } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport commands from '../../commands'\nimport ActionsHandler, { shouldAutoApply } from '../../handler/codeActions'\nimport languages, { ProviderName } from '../../languages'\nimport { ProviderResult } from '../../provider'\nimport { checkAction } from '../../provider/codeActionManager'\nimport { disposeAll } from '../../util'\nimport { rangeInRange } from '../../util/position'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nlet codeActions: ActionsHandler\nlet currActions: (CodeAction | Command)[]\nlet resolvedAction: CodeAction\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  codeActions = helper.plugin.getHandler().codeActions\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nbeforeEach(async () => {\n  disposables.push(languages.registerCodeActionProvider([{ language: '*' }], {\n    provideCodeActions: (\n      _document: TextDocument,\n      _range: Range,\n      _context: CodeActionContext,\n      _token: CancellationToken\n    ) => currActions,\n    resolveCodeAction: (\n      _action: CodeAction,\n      _token: CancellationToken\n    ): ProviderResult<CodeAction> => resolvedAction\n  }, undefined))\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n})\n\ndescribe('handler codeActions', () => {\n  describe('autoApply', () => {\n    it('should check auto apply', async () => {\n      expect(shouldAutoApply(undefined)).toBe(false)\n      expect(shouldAutoApply([])).toBe(false)\n      expect(shouldAutoApply([CodeActionKind.Refactor])).toBe(false)\n    })\n  })\n\n  describe('organizeImport', () => {\n    it('should filter command ', () => {\n      let cmd = Command.create('title', 'command')\n      let res = checkAction([CodeActionKind.Refactor], cmd)\n      expect(res).toBe(false)\n      res = checkAction(undefined, cmd)\n      expect(res).toBe(true)\n    })\n\n    it('should return false when organize import action not found', async () => {\n      currActions = []\n      let doc = await helper.createDocument()\n      expect(languages.hasProvider(ProviderName.CodeAction, doc)).toBe(true)\n      let res = await helper.doAction('organizeImport')\n      expect(res).toBe(false)\n      expect(languages.hasProvider('undefined' as any, doc)).toBe(false)\n    })\n\n    it('should perform organize import action', async () => {\n      let doc = await helper.createDocument()\n      await doc.buffer.setLines(['foo', 'bar'], { start: 0, end: -1, strictIndexing: false })\n      let edits: TextEdit[] = []\n      edits.push(TextEdit.replace(Range.create(0, 0, 0, 3), 'bar'))\n      edits.push(TextEdit.replace(Range.create(1, 0, 1, 3), 'foo'))\n      let edit = { changes: { [doc.uri]: edits } }\n      let action = CodeAction.create('organize import', edit, CodeActionKind.SourceOrganizeImports)\n      currActions = [action, CodeAction.create('another action'), Command.create('title', 'command')]\n      await codeActions.organizeImport()\n      let lines = await doc.buffer.lines\n      expect(lines).toEqual(['bar', 'foo'])\n    })\n\n    it('should register editor.action.organizeImport command', async () => {\n      let doc = await helper.createDocument()\n      currActions = []\n      await commands.executeCommand('editor.action.organizeImport')\n      await doc.buffer.setLines(['foo', 'bar'], { start: 0, end: -1, strictIndexing: false })\n      let edits: TextEdit[] = []\n      edits.push(TextEdit.replace(Range.create(0, 0, 0, 3), 'bar'))\n      edits.push(TextEdit.replace(Range.create(1, 0, 1, 3), 'foo'))\n      let edit = { changes: { [doc.uri]: edits } }\n      let action = CodeAction.create('organize import', edit, CodeActionKind.SourceOrganizeImports)\n      currActions = [action, CodeAction.create('another action')]\n      await commands.executeCommand('editor.action.organizeImport')\n      let lines = await doc.buffer.lines\n      expect(lines).toEqual(['bar', 'foo'])\n    })\n  })\n\n  describe('codeActionRange', () => {\n    it('should show warning when no action available', async () => {\n      await helper.createDocument()\n      currActions = []\n      await helper.doAction('codeActionRange', 1, 2, CodeActionKind.QuickFix)\n      let line = await helper.getCmdline()\n      expect(line).toMatch(/No quickfix code action/)\n      await helper.doAction('codeActionRange', 1, 2)\n      line = await helper.getCmdline()\n      expect(line).toMatch(/No code action available/)\n    })\n\n    it('should apply chosen action', async () => {\n      let doc = await helper.createDocument()\n      let edits: TextEdit[] = []\n      edits.push(TextEdit.insert(Position.create(0, 0), 'bar'))\n      let edit = { changes: { [doc.uri]: edits } }\n      let action = CodeAction.create('code fix', edit, CodeActionKind.QuickFix)\n      currActions = [action]\n      let p = codeActions.codeActionRange(1, 2, CodeActionKind.QuickFix)\n      await helper.waitPrompt()\n      await nvim.input('<CR>')\n      await p\n      let buf = nvim.createBuffer(doc.bufnr)\n      let lines = await buf.lines\n      expect(lines[0]).toBe('bar')\n    })\n  })\n\n  describe('getCodeActions', () => {\n    it('should get empty actions', async () => {\n      currActions = []\n      let doc = await helper.createDocument()\n      let res = await codeActions.getCodeActions(doc)\n      expect(res.length).toBe(0)\n    })\n\n    it('should not filter disabled actions', async () => {\n      currActions = []\n      let action = CodeAction.create('foo', CodeActionKind.Source)\n      currActions.push(action)\n      action = CodeAction.create('action', CodeActionKind.Empty)\n      currActions.push(action)\n      action = CodeAction.create('bar', CodeActionKind.QuickFix)\n      action.disabled = { reason: 'disabled' }\n      currActions.push(action)\n      let doc = await helper.createDocument()\n      let res = await codeActions.getCodeActions(doc, Range.create(0, 0, 1, 0))\n      expect(res.length).toBe(2)\n    })\n\n    it('should get all actions', async () => {\n      let doc = await helper.createDocument()\n      await doc.buffer.setLines(['', '', ''], { start: 0, end: -1, strictIndexing: false })\n      let action = CodeAction.create('curr action', CodeActionKind.Empty)\n      currActions = [action]\n      let range: Range\n      disposables.push(languages.registerCodeActionProvider([{ language: '*' }], {\n        provideCodeActions: (\n          _document: TextDocument,\n          r: Range,\n          _context: CodeActionContext, _token: CancellationToken\n        ) => {\n          range = r\n          return [CodeAction.create('a'), CodeAction.create('b'), CodeAction.create('c'), Command.create('title', 'command')]\n        },\n      }, undefined))\n      disposables.push(languages.registerCodeActionProvider([{ language: '*' }], {\n        provideCodeActions: () => {\n          return [CodeAction.create('a')]\n        },\n      }, undefined))\n      let res = await codeActions.getCodeActions(doc)\n      expect(range).toEqual(Range.create(0, 0, 3, 0))\n      expect(res.length).toBe(5)\n    })\n\n    it('should filter actions by range', async () => {\n      let doc = await helper.createDocument()\n      await doc.buffer.setLines(['', '', ''], { start: 0, end: -1, strictIndexing: false })\n      currActions = []\n      let range: Range\n      disposables.push(languages.registerCodeActionProvider([{ language: '*' }], {\n        provideCodeActions: (\n          _document: TextDocument,\n          r: Range,\n          _context: CodeActionContext, _token: CancellationToken\n        ) => {\n          range = r\n          if (rangeInRange(r, Range.create(0, 0, 1, 0))) return [CodeAction.create('a')]\n          return [CodeAction.create('a'), CodeAction.create('b'), CodeAction.create('c')]\n        },\n      }, undefined))\n      let res = await codeActions.getCodeActions(doc, Range.create(0, 0, 0, 0))\n      expect(range).toEqual(Range.create(0, 0, 0, 0))\n      expect(res.length).toBe(1)\n    })\n\n    it('should filter actions by kind prefix', async () => {\n      let doc = await helper.createDocument()\n      let action = CodeAction.create('my action', CodeActionKind.SourceFixAll)\n      currActions = [action]\n      let res = await codeActions.getCodeActions(doc, undefined, [CodeActionKind.Source])\n      expect(res.length).toBe(1)\n      expect(res[0].kind).toBe(CodeActionKind.SourceFixAll)\n      await helper.doAction('fixAll')\n    })\n  })\n\n  describe('getCurrentCodeActions', () => {\n    let range: Range\n    beforeEach(() => {\n      disposables.push(languages.registerCodeActionProvider([{ language: '*' }], {\n        provideCodeActions: (\n          _document: TextDocument,\n          r: Range,\n          _context: CodeActionContext, _token: CancellationToken\n        ) => {\n          range = r\n          return [CodeAction.create('a'), CodeAction.create('b'), CodeAction.create('c')]\n        },\n      }, undefined))\n    })\n\n    it('should get codeActions by line', async () => {\n      currActions = []\n      await helper.createDocument()\n      let res = await helper.doAction('codeActions', 'line')\n      expect(range).toEqual(Range.create(0, 0, 1, 0))\n      expect(res.length).toBe(3)\n    })\n\n    it('should get codeActions by cursor', async () => {\n      currActions = []\n      await helper.createDocument()\n      let res = await codeActions.getCurrentCodeActions('cursor')\n      expect(range).toEqual(Range.create(0, 0, 0, 0))\n      expect(res.length).toBe(3)\n    })\n\n    it('should get codeActions by visual mode', async () => {\n      currActions = []\n      await helper.createDocument()\n      await nvim.setLine('foo')\n      await nvim.command('normal! 0v$')\n      await nvim.input('<esc>')\n      let res = await codeActions.getCurrentCodeActions('v')\n      expect(range).toEqual(Range.create(0, 0, 0, 3))\n      expect(res.length).toBe(3)\n    })\n  })\n\n  describe('doCodeAction', () => {\n    it('should not throw when no action exists', async () => {\n      currActions = []\n      await helper.createDocument()\n      await helper.doAction('codeAction', undefined)\n    })\n\n    it('should apply single code action when only is title', async () => {\n      let doc = await helper.createDocument()\n      let edits: TextEdit[] = []\n      edits.push(TextEdit.insert(Position.create(0, 0), 'bar'))\n      let edit = { changes: { [doc.uri]: edits } }\n      let action = CodeAction.create('code fix', edit, CodeActionKind.QuickFix)\n      currActions = [action]\n      await codeActions.doCodeAction(undefined, 'code fix')\n      let lines = await doc.buffer.lines\n      expect(lines).toEqual(['bar'])\n    })\n\n    it('should apply single code action when only is QuickFix', async () => {\n      let doc = await helper.createDocument()\n      let edits: TextEdit[] = []\n      edits.push(TextEdit.insert(Position.create(0, 0), 'bar'))\n      let edit = { changes: { [doc.uri]: edits } }\n      let action = CodeAction.create('code fix', edit, CodeActionKind.QuickFix)\n      currActions = [action]\n      await codeActions.doCodeAction(undefined, [CodeActionKind.QuickFix])\n      let lines = await doc.buffer.lines\n      expect(lines).toEqual(['bar'])\n    })\n\n    it('should show disabled code action', async () => {\n      let doc = await helper.createDocument()\n      let edits: TextEdit[] = []\n      edits.push(TextEdit.insert(Position.create(0, 0), 'bar'))\n      let edit = { changes: { [doc.uri]: edits } }\n      let refactorAction = CodeAction.create('code refactor', edit, CodeActionKind.Refactor)\n      refactorAction.disabled = { reason: 'invalid position' }\n      let fixAction = CodeAction.create('code fix', edit, CodeActionKind.QuickFix)\n      currActions = [refactorAction, fixAction]\n      let p = codeActions.doCodeAction(undefined, undefined, true)\n      let winid = await helper.waitFloat()\n      let win = nvim.createWindow(winid)\n      let buf = await win.buffer\n      let lines = await buf.lines\n      expect(lines.length).toBe(2)\n      expect(lines[1]).toMatch(/code refactor/)\n      await nvim.input('2')\n      await helper.wait(1)\n      await nvim.input('j')\n      await nvim.input('<cr>')\n      await helper.waitValue(async () => {\n        let cmdline = await helper.getCmdline()\n        return cmdline.includes('invalid position')\n      }, true)\n      await nvim.input('<esc>')\n      await p\n    })\n\n    it('should action dialog to choose action', async () => {\n      let doc = await helper.createDocument()\n      let edits: TextEdit[] = []\n      edits.push(TextEdit.insert(Position.create(0, 0), 'bar'))\n      let edit = { changes: { [doc.uri]: edits } }\n      let action = CodeAction.create('code fix', edit, CodeActionKind.QuickFix)\n      currActions = [action, CodeAction.create('foo')]\n      let promise = codeActions.doCodeAction(null, undefined)\n      await helper.waitFloat()\n      let ids = await nvim.call('coc#float#get_float_win_list') as number[]\n      expect(ids.length).toBeGreaterThan(0)\n      await nvim.input('<CR>')\n      await promise\n      let lines = await doc.buffer.lines\n      expect(lines).toEqual(['bar'])\n    })\n\n    it('should choose code actions by range', async () => {\n      let range: Range\n      disposables.push(languages.registerCodeActionProvider([{ language: '*' }], {\n        provideCodeActions: (\n          _document: TextDocument,\n          r: Range,\n          _context: CodeActionContext, _token: CancellationToken\n        ) => {\n          range = r\n          return [CodeAction.create('my title'), CodeAction.create('b'), CodeAction.create('c')]\n        },\n      }, undefined))\n      await helper.createDocument()\n      await nvim.setLine('abc')\n      await nvim.command('normal! 0v$')\n      await nvim.input('<esc>')\n      await codeActions.doCodeAction('v', 'my title')\n      expect(range).toEqual({ start: { line: 0, character: 0 }, end: { line: 0, character: 3 } })\n    })\n\n    it('should filter by provider kinds', async () => {\n      currActions = []\n      disposables.push(languages.registerCodeActionProvider([{ language: '*' }], {\n        provideCodeActions: () => {\n          return [CodeAction.create('my title'), CodeAction.create('b'), CodeAction.create('c')]\n        },\n      }, undefined, [CodeActionKind.QuickFix]))\n      let doc = await workspace.document\n      let res = await languages.getCodeActions(doc.textDocument, Range.create(0, 0, 1, 1), { only: [CodeActionKind.Refactor], diagnostics: [] }, CancellationToken.None)\n      expect(res).toEqual([])\n    })\n\n    it('should filter by codeAction kind', async () => {\n      currActions = []\n      disposables.push(languages.registerCodeActionProvider([{ language: '*' }], {\n        provideCodeActions: () => {\n          return [\n            CodeAction.create('my title', CodeActionKind.QuickFix),\n            CodeAction.create('b'),\n            Command.create('command', 'command')\n          ]\n        },\n        resolveCodeAction: () => {\n          return null\n        }\n      }, undefined))\n      let doc = await workspace.document\n      let res = await languages.getCodeActions(doc.textDocument, Range.create(0, 0, 1, 1), { only: [CodeActionKind.QuickFix], diagnostics: [] }, CancellationToken.None)\n      expect(res.length).toBe(1)\n      let resolved = await languages.resolveCodeAction(res[0], CancellationToken.None)\n      expect(resolved).toBeDefined()\n      await expect(async () => {\n        await codeActions.doCodeAction(null, 'command', true)\n      }).rejects.toThrow(Error)\n      await codeActions.doCodeAction(null, 'cmd', true)\n      let line = await helper.getCmdline()\n      expect(line).toMatch('No cmd code action')\n    })\n\n    it('should use quickpick', async () => {\n      helper.updateConfiguration('coc.preferences.floatActions', false)\n      currActions = [CodeAction.create('foo', CodeActionKind.QuickFix), CodeAction.create('bar', CodeActionKind.QuickFix)]\n      let spy = jest.spyOn(window.dialogs, 'requestInputList').mockReturnValue(Promise.resolve(0))\n      let action\n      let s = jest.spyOn(codeActions, 'applyCodeAction').mockImplementation((a, _token) => {\n        action = a\n        return Promise.resolve()\n      })\n      await codeActions.doCodeAction(null, undefined)\n      s.mockRestore()\n      spy.mockRestore()\n      expect(action).toBeDefined()\n      expect(action.title).toBe('foo')\n      helper.updateConfiguration('coc.preferences.floatActions', true)\n    })\n  })\n\n  describe('doQuickfix', () => {\n    it('should show message when quickfix action does not exist', async () => {\n      currActions = []\n      await helper.createDocument()\n      await helper.doAction('doQuickfix')\n      let msg = await helper.getCmdline()\n      expect(msg).toMatch('No quickfix')\n    })\n\n    it('should do preferred quickfix action', async () => {\n      let doc = await helper.createDocument()\n      let edits: TextEdit[] = []\n      edits.push(TextEdit.insert(Position.create(0, 0), 'bar'))\n      let edit = { changes: { [doc.uri]: edits } }\n      let action = CodeAction.create('code fix', edit, CodeActionKind.QuickFix)\n      action.isPreferred = true\n      currActions = [CodeAction.create('foo', CodeActionKind.QuickFix), action, CodeAction.create('bar')]\n      await codeActions.doQuickfix()\n      let lines = await doc.buffer.lines\n      expect(lines).toEqual(['bar'])\n    })\n  })\n\n  describe('applyCodeAction', () => {\n    it('should resolve codeAction', async () => {\n      let doc = await helper.createDocument()\n      let edits: TextEdit[] = []\n      edits.push(TextEdit.insert(Position.create(0, 0), 'bar'))\n      let edit = { changes: { [doc.uri]: edits } }\n      let action = CodeAction.create('code fix', CodeActionKind.QuickFix)\n      action.isPreferred = true\n      currActions = [action]\n      resolvedAction = Object.assign({ edit }, action)\n      let arr = await helper.doAction('quickfixes', 'line')\n      await commands.executeCommand('editor.action.doCodeAction', arr[0])\n      let lines = await doc.buffer.lines\n      expect(lines).toEqual(['bar'])\n    })\n\n    it('should not throw when resolved action is null', async () => {\n      await helper.createDocument()\n      let edits: TextEdit[] = []\n      edits.push(TextEdit.insert(Position.create(0, 0), 'bar'))\n      let action = CodeAction.create('code fix', CodeActionKind.QuickFix)\n      action.isPreferred = true\n      currActions = [action]\n      resolvedAction = null\n      let arr = await helper.doAction('quickfixes', 'line')\n      await commands.executeCommand('editor.action.doCodeAction', arr[0])\n    })\n\n    it('should throw for disabled action', async () => {\n      let action: any = CodeAction.create('my action', CodeActionKind.Empty)\n      action.disabled = { reason: 'disabled', providerId: 'x' }\n      await expect(async () => {\n        await helper.doAction('doCodeAction', action)\n      }).rejects.toThrow(Error)\n    })\n\n    it('should invoke registered command after apply edit', async () => {\n      let called\n      disposables.push(commands.registerCommand('test.execute', async (s: string) => {\n        called = s\n        await nvim.command(s)\n      }))\n      let doc = await helper.createDocument()\n      let edits: TextEdit[] = []\n      edits.push(TextEdit.insert(Position.create(0, 0), 'bar'))\n      let edit = { changes: { [doc.uri]: edits } }\n      let action = CodeAction.create('code fix', CodeActionKind.QuickFix)\n      action.isPreferred = true\n      currActions = [action]\n      resolvedAction = Object.assign({\n        edit,\n        command: Command.create('run vim command', 'test.execute', 'normal! $')\n      }, action)\n      let arr = await codeActions.getCurrentCodeActions('line', [CodeActionKind.QuickFix])\n      await codeActions.applyCodeAction(arr[0])\n      let lines = await doc.buffer.lines\n      expect(lines).toEqual(['bar'])\n      expect(called).toBe('normal! $')\n    })\n  })\n\n  it('should execute code action with timeout', async () => {\n    disposeAll(disposables)\n    let doc = await helper.createDocument('t.js')\n    let called = false\n    disposables.push(languages.registerCodeActionProvider([{ language: '*' }], {\n      provideCodeActions: (\n        _document: TextDocument,\n        _range: Range,\n        _context: CodeActionContext,\n        _token: CancellationToken\n      ) => currActions,\n      resolveCodeAction: (\n        _action: CodeAction,\n        token: CancellationToken\n      ): ProviderResult<CodeAction> => {\n        return new Promise(resolve => {\n          token.onCancellationRequested(() => {\n            called = true\n            resolve(undefined)\n            clearTimeout(timer)\n          })\n          let timer = setTimeout(() => {\n            resolve(resolvedAction)\n          }, 200)\n        })\n      }\n    }, undefined))\n    let action = CodeAction.create('fix all', undefined, CodeActionKind.SourceFixAll)\n    currActions = [action]\n    let res = await codeActions.executeCodeActions(doc, undefined, [CodeActionKind.SourceFixAll], 50)\n    expect(res).toEqual([])\n    expect(called).toBe(true)\n  })\n\n  it('should execute organizeImport code action', async () => {\n    let doc = await workspace.document\n    await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo')])\n    let action = CodeAction.create('organize import', undefined, CodeActionKind.SourceOrganizeImports)\n    currActions = [action]\n    let edits: TextEdit[] = []\n    edits.push(TextEdit.replace(Range.create(0, 0, 0, 3), 'bar'))\n    let obj = Object.assign({}, action)\n    obj.edit = { changes: { [doc.uri]: edits } }\n    resolvedAction = obj\n    let res = await codeActions.executeCodeActions(doc, undefined, [CodeActionKind.SourceOrganizeImports], 50)\n    expect(res).toEqual([CodeActionKind.SourceOrganizeImports])\n    let line = doc.getline(0)\n    expect(line).toBe('bar')\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/codelens.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationToken, CodeLens, Command, Disposable, Position, Range, TextEdit } from 'vscode-languageserver-protocol'\nimport commands from '../../commands'\nimport events from '../../events'\nimport CodeLensBuffer, { getCommands, getTextAlign } from '../../handler/codelens/buffer'\nimport CodeLensHandler from '../../handler/codelens/index'\nimport languages from '../../languages'\nimport { disposeAll } from '../../util'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet codeLens: CodeLensHandler\nlet disposables: Disposable[] = []\nlet srcId: number\n\njest.setTimeout(10000)\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  srcId = await nvim.createNamespace('coc-codelens')\n  codeLens = helper.plugin.getHandler().codeLens\n})\n\nbeforeEach(() => {\n  helper.updateConfiguration('codeLens.enable', true)\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  await helper.reset()\n  disposeAll(disposables)\n})\n\nasync function createBufferWithCodeLens(): Promise<CodeLensBuffer> {\n  disposables.push(languages.registerCodeLensProvider([{ language: 'javascript' }], {\n    provideCodeLenses: () => {\n      return [{\n        range: Range.create(0, 0, 0, 1)\n      }]\n    },\n    resolveCodeLens: codeLens => {\n      codeLens.command = Command.create('save', '__save', 1, 2, 3)\n      return codeLens\n    }\n  }))\n  let doc = await helper.createDocument('e.js')\n  await nvim.call('setline', [1, ['a', 'b', 'c']])\n  await doc.synchronize()\n  await codeLens.checkProvider()\n  return codeLens.buffers.getItem(doc.bufnr)\n}\n\ndescribe('codeLenes feature', () => {\n  it('should get text align', async () => {\n    expect(getTextAlign(undefined)).toBe('above')\n    expect(getTextAlign('top')).toBe('above')\n    expect(getTextAlign('eol')).toBe('after')\n    expect(getTextAlign('right_align')).toBe('right')\n  })\n\n  it('should not throw when srcId not exists', async () => {\n    let doc = await workspace.document\n    let item = codeLens.buffers.getItem(doc.bufnr)\n    item.clear()\n    await item.doAction(0)\n  })\n\n  it('should invoke codeLenes action', async () => {\n    let fn = jest.fn()\n    disposables.push(commands.registerCommand('__save', (...args) => {\n      fn(...args)\n    }))\n    await createBufferWithCodeLens()\n    await helper.doAction('codeLensAction')\n    await nvim.call('cursor', [1, 1])\n    expect(fn).toHaveBeenCalledWith(1, 2, 3)\n    await nvim.command('normal! G')\n    await helper.doAction('codeLensAction')\n  })\n\n  it('should toggle codeLens display', async () => {\n    await codeLens.toggle(999)\n    let line = await helper.getCmdline()\n    expect(line).toMatch('not exists')\n    await createBufferWithCodeLens()\n    await commands.executeCommand('document.toggleCodeLens')\n    let doc = await workspace.document\n    let res = await doc.buffer.getExtMarks(srcId, 0, -1, { details: true })\n    expect(res.length).toBe(0)\n    await commands.executeCommand('document.toggleCodeLens')\n    await helper.waitValue(async () => {\n      let res = await doc.buffer.getExtMarks(srcId, 0, -1, { details: true })\n      return res.length > 0\n    }, true)\n  })\n\n  it('should return codeLenes when resolve not exists', async () => {\n    let codeLens = CodeLens.create(Range.create(0, 0, 1, 1))\n    let resolved = await languages.resolveCodeLens(codeLens, CancellationToken.None)\n    expect(resolved).toBeDefined()\n  })\n\n  it('should do codeLenes request and resolve codeLenes', async () => {\n    let buf = await createBufferWithCodeLens()\n    let doc = await workspace.document\n    await helper.waitValue(async () => {\n      let codelens = buf.currentCodeLens\n      return Array.isArray(codelens) && codelens[0].command != null\n    }, true)\n    let markers = await doc.buffer.getExtMarks(srcId, 0, -1)\n    expect(markers.length).toBe(1)\n    let codeLenes = buf.currentCodeLens\n    await languages.resolveCodeLens(codeLenes[0], CancellationToken.None)\n  })\n\n  it('should refresh on empty changes', async () => {\n    await createBufferWithCodeLens()\n    let doc = await workspace.document\n    await nvim.call('setline', [1, ['a', 'b', 'c']])\n    await doc.synchronize()\n    let markers = await doc.buffer.getExtMarks(srcId, 0, -1)\n    expect(markers.length).toBeGreaterThan(0)\n  })\n\n  it('should work with empty codeLens', async () => {\n    disposables.push(languages.registerCodeLensProvider([{ language: 'javascript' }], {\n      provideCodeLenses: () => {\n        return []\n      }\n    }))\n    let doc = await helper.createDocument('t.js')\n    let buf = codeLens.buffers.getItem(doc.bufnr)\n    let codelens = buf.currentCodeLens\n    expect(codelens).toBeUndefined()\n  })\n\n  it('should change codeLenes position', async () => {\n    helper.updateConfiguration('codeLens.position', 'eol')\n    let bufnr = await nvim.call('bufnr', ['%']) as number\n    let item = codeLens.buffers.getItem(bufnr)\n    expect(item.config.position).toBe('eol')\n  })\n\n  it('should refresh codeLens on CursorHold', async () => {\n    disposables.push(languages.registerCodeLensProvider([{ language: 'javascript' }], {\n      provideCodeLenses: document => {\n        let n = document.lineCount\n        let arr: any[] = []\n        for (let i = 0; i <= n - 2; i++) {\n          arr.push({\n            range: Range.create(i, 0, i, 1),\n            command: Command.create('save', '__save', i)\n          })\n        }\n        return arr\n      }\n    }))\n    let doc = await helper.createDocument('example.js')\n    await nvim.call('setline', [1, ['a', 'b', 'c']])\n    await doc.synchronize()\n    await events.fire('CursorHold', [doc.bufnr])\n    await helper.waitValue(async () => {\n      let markers = await doc.buffer.getExtMarks(srcId, 0, -1)\n      return markers.length\n    }, 3)\n    helper.updateConfiguration('codeLens.enable', false)\n    await events.fire('CursorHold', [doc.bufnr])\n  })\n\n  it('should cancel codeLenes request on document change', async () => {\n    let cancelled = false\n    disposables.push(languages.registerCodeLensProvider([{ language: 'javascript' }], {\n      provideCodeLenses: (_, token) => {\n        return new Promise(resolve => {\n          token.onCancellationRequested(() => {\n            cancelled = true\n            clearTimeout(timer)\n            resolve(null)\n          })\n          let timer = setTimeout(() => {\n            resolve([{\n              range: Range.create(0, 0, 0, 1)\n            }, {\n              range: Range.create(1, 0, 1, 1)\n            }])\n          }, 2000)\n          disposables.push({\n            dispose: () => {\n              clearTimeout(timer)\n            }\n          })\n        })\n      },\n      resolveCodeLens: codeLens => {\n        codeLens.command = Command.create('save', '__save')\n        return codeLens\n      }\n    }))\n    let doc = await helper.createDocument('codelens.js')\n    await helper.wait(50)\n    await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'a\\nb\\nc')])\n    expect(cancelled).toBe(true)\n  })\n\n  it('should resolve on CursorMoved', async () => {\n    disposables.push(languages.registerCodeLensProvider([{ language: 'javascript' }], {\n      provideCodeLenses: () => {\n        return [{\n          range: Range.create(190, 0, 190, 1)\n        }, {\n          range: Range.create(191, 0, 191, 1)\n        }]\n      },\n      resolveCodeLens: async codeLens => {\n        codeLens.command = Command.create('save', '__save')\n        return codeLens\n      }\n    }))\n    let doc = await helper.createDocument('example.js')\n    await nvim.call('cursor', [1, 1])\n    let arr = new Array(200)\n    arr.fill('')\n    await nvim.call('setline', [1, arr])\n    await doc.synchronize()\n    await codeLens.checkProvider()\n    await nvim.call('cursor', [190, 1])\n    await events.fire('CursorMoved', [doc.bufnr, [190, 1], false])\n    let bufnr = doc.bufnr\n    await helper.waitValue(() => {\n      let buf = codeLens.buffers.getItem(bufnr)\n      return buf && buf.currentCodeLens && buf.currentCodeLens[0].command != null\n    }, true)\n  }, 10000)\n\n  it('should use picker for multiple codeLenses', async () => {\n    let fn = jest.fn()\n    let resolved = false\n    disposables.push(commands.registerCommand('__save', (...args) => {\n      fn(...args)\n    }))\n    disposables.push(commands.registerCommand('__delete', (...args) => {\n      fn(...args)\n    }))\n    disposables.push(languages.registerCodeLensProvider([{ language: 'javascript' }], {\n      provideCodeLenses: () => {\n        resolved = true\n        return [{\n          range: Range.create(0, 0, 0, 1),\n          command: Command.create('save', '__save', 1, 2, 3)\n        }, {\n          range: Range.create(0, 1, 0, 2),\n          command: Command.create('save', '__delete', 4, 5, 6)\n        }]\n      }\n    }))\n    let doc = await helper.createDocument('example.js')\n    await nvim.call('setline', [1, ['a', 'b', 'c']])\n    await doc.synchronize()\n    await codeLens.checkProvider()\n    await helper.waitValue(() => {\n      return resolved\n    }, true)\n    let p = helper.doAction('codeLensAction')\n    await helper.waitPrompt()\n    await nvim.input('<cr>')\n    await p\n    expect(fn).toHaveBeenCalledWith(1, 2, 3)\n  })\n\n  it('should refresh for failed codeLens request', async () => {\n    let called = 0\n    let fn = jest.fn()\n    disposables.push(commands.registerCommand('__save', (...args) => {\n      fn(...args)\n    }))\n    disposables.push(commands.registerCommand('__foo', (...args) => {\n      fn(...args)\n    }))\n    disposables.push(languages.registerCodeLensProvider([{ language: '*' }], {\n      provideCodeLenses: () => {\n        called++\n        if (called == 1) {\n          return null\n        }\n        return [{\n          range: Range.create(0, 0, 0, 1),\n          command: Command.create('foo', '__foo')\n        }]\n      }\n    }))\n    disposables.push(languages.registerCodeLensProvider([{ language: '*' }], {\n      provideCodeLenses: () => {\n        return [{\n          range: Range.create(0, 0, 0, 1),\n          command: Command.create('save', '__save')\n        }]\n      }\n    }))\n    let doc = await helper.createDocument('example.js')\n    await helper.wait(50)\n    await nvim.call('setline', [1, ['a', 'b', 'c']])\n    await codeLens.checkProvider()\n    let markers = await doc.buffer.getExtMarks(srcId, 0, -1)\n    expect(markers.length).toBeGreaterThan(0)\n    let codeLensBuffer = codeLens.buffers.getItem(doc.buffer.id)\n    await codeLensBuffer.forceFetch()\n    let curr = codeLensBuffer.currentCodeLens\n    expect(curr.length).toBeGreaterThan(1)\n  })\n\n  it('should use custom separator & position', async () => {\n    helper.updateConfiguration('codeLens.separator', '|')\n    helper.updateConfiguration('codeLens.position', 'eol')\n    let doc = await helper.createDocument('example.js')\n    await nvim.call('setline', [1, ['a', 'b', 'c']])\n    await doc.synchronize()\n    disposables.push(languages.registerCodeLensProvider([{ language: '*' }], {\n      provideCodeLenses: () => {\n        return [{\n          range: Range.create(0, 0, 1, 0),\n          command: Command.create('save', '__save')\n        }, {\n          range: Range.create(0, 0, 1, 0),\n          command: Command.create('save', '__save')\n        }]\n      }\n    }))\n    await helper.wait(10)\n    await codeLens.checkProvider()\n    let res = await doc.buffer.getExtMarks(srcId, 0, -1, { details: true })\n    expect(res.length).toBe(1)\n  })\n\n  it('should get commands from codeLenses', async () => {\n    expect(getCommands(1, undefined)).toEqual([])\n    let codeLenses = [CodeLens.create(Range.create(0, 0, 0, 0))]\n    expect(getCommands(0, codeLenses)).toEqual([])\n    codeLenses = [CodeLens.create(Range.create(0, 0, 1, 0)), CodeLens.create(Range.create(2, 0, 3, 0))]\n    codeLenses[0].command = Command.create('save', '__save')\n    expect(getCommands(0, codeLenses).length).toEqual(1)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/commands.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { Disposable } from 'vscode-languageserver-protocol'\nimport commandManager from '../../commands'\nimport CommandsHandler from '../../handler/commands'\nimport { disposeAll } from '../../util'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet commands: CommandsHandler\nlet disposables: Disposable[] = []\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  commands = helper.plugin.handler.commands\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nbeforeEach(async () => {\n  await helper.createDocument()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n})\n\ndescribe('Commands', () => {\n  describe('addVimCommand', () => {\n    it('should register global vim commands', async () => {\n      await commandManager.executeCommand('vim.config')\n      let val = await nvim.getVar('coc_config_init')\n      expect(val).toBe(1)\n      let list = await helper.doAction('commandList')\n      expect(list.includes('vim.config')).toBe(true)\n    })\n\n    it('should add vim command with title', async () => {\n      await helper.plugin.cocAction('addCommand', { id: 'bad', cmd: '', title: '' })\n      commands.addVimCommand({ id: 'list', cmd: 'CocList', title: 'list of coc.nvim' })\n      let res = commandManager.titles.get('vim.list')\n      expect(res).toBe('list of coc.nvim')\n      commandManager.unregister('vim.list')\n      commandManager.unregister('unknown.command')\n      let list = commands.getCommandList()\n      expect(list.includes('bad')).toBe(false)\n    })\n  })\n\n  describe('commandManager', () => {\n    it('should replace builtin command', async () => {\n      let fn = jest.fn()\n      commandManager.registerCommand('editor.action.restart', () => {\n        fn()\n      })\n      await commandManager.executeCommand('editor.action.restart')\n      expect(fn).toHaveBeenCalled()\n    })\n\n    it('should throw when command not found', async () => {\n      await expect(async () => {\n        await commandManager.executeCommand('')\n      }).rejects.toThrow(Error)\n    })\n\n    it('should add to recent', async () => {\n      await commandManager.addRecent('document.checkBuffer', true)\n      let mru = workspace.createMru('commands')\n      let list = await mru.load()\n      expect(list[0]).toBe('document.checkBuffer')\n    })\n  })\n\n  describe('getCommands', () => {\n    it('should get command items', async () => {\n      let res = await helper.doAction('commands')\n      let idx = res.findIndex(o => o.id == 'workspace.showOutput')\n      expect(idx != -1).toBe(true)\n    })\n  })\n\n  describe('repeat', () => {\n    it('should repeat command', async () => {\n      // let buf = await nvim.buffer\n      await nvim.call('setline', [1, ['a', 'b', 'c']])\n      await nvim.call('cursor', [1, 1])\n      commands.addVimCommand({ id: 'remove', cmd: 'normal! dd' })\n      await helper.doAction('runCommand', 'vim.remove')\n      await helper.waitFor('getline', ['.'], 'b')\n      await helper.doAction('repeatCommand')\n      await helper.waitFor('getline', ['.'], 'c')\n    })\n  })\n\n  describe('runCommand', () => {\n    it('should open command list without id', async () => {\n      await commands.runCommand()\n      await helper.waitFor('bufname', ['%'], 'list:///commands')\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/documentColors.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationToken, Color, ColorInformation, ColorPresentation, Disposable, Position, Range } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport commands from '../../commands'\nimport { toHexString } from '../../util/color'\nimport Colors from '../../handler/colors/index'\nimport languages from '../../languages'\nimport { ProviderResult } from '../../provider'\nimport { disposeAll } from '../../util'\nimport path from 'path'\nimport helper from '../helper'\nimport workspace from '../../workspace'\nimport events from '../../events'\n\nlet nvim: Neovim\nlet state = 'normal'\nlet colors: Colors\nlet disposables: Disposable[] = []\nlet colorPresentations: ColorPresentation[] = []\nlet disposable: Disposable\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  await nvim.command(`source ${path.join(process.cwd(), 'autoload/coc/color.vim')}`)\n  colors = helper.plugin.getHandler().colors\n  disposable = languages.registerDocumentColorProvider([{ language: '*' }], {\n    provideColorPresentations: (\n      _color: Color,\n      _context: { document: TextDocument; range: Range },\n      _token: CancellationToken\n    ): ColorPresentation[] => colorPresentations,\n    provideDocumentColors: (\n      document: TextDocument,\n      _token: CancellationToken\n    ): ProviderResult<ColorInformation[]> => {\n      if (state == 'empty') return []\n      if (state == 'error') return Promise.reject(new Error('no color'))\n      let matches = Array.from((document.getText() as any).matchAll(/#\\w{6}/g)) as any\n      return matches.map(o => {\n        let start = document.positionAt(o.index)\n        let end = document.positionAt(o.index + o[0].length)\n        return {\n          range: Range.create(start, end),\n          color: getColor(255, 255, 255)\n        }\n      })\n    }\n  })\n})\n\nbeforeEach(() => {\n  helper.updateConfiguration('colors.filetypes', ['*'])\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  state = 'normal'\n  colorPresentations = []\n  disposeAll(disposables)\n  await helper.reset()\n})\n\nfunction getColor(r: number, g: number, b: number): Color {\n  return { red: r / 255, green: g / 255, blue: b / 255, alpha: 1 }\n}\n\ndescribe('Colors', () => {\n  describe('utils', () => {\n    it('should get hex string', () => {\n      let color = getColor(255, 255, 255)\n      let hex = toHexString(color)\n      expect(hex).toBe('ffffff')\n    })\n  })\n\n  describe('configuration', () => {\n    it('should toggle enable state on configuration change', async () => {\n      let doc = await helper.createDocument()\n      helper.updateConfiguration('colors.filetypes', [])\n      let enabled = colors.isEnabled(doc.bufnr)\n      expect(enabled).toBe(false)\n      helper.updateConfiguration('colors.enable', true)\n      enabled = colors.isEnabled(doc.bufnr)\n      expect(enabled).toBe(true)\n      helper.updateConfiguration('colors.enable', false)\n      enabled = colors.isEnabled(doc.bufnr)\n      expect(enabled).toBe(false)\n    })\n  })\n\n  describe('commands', () => {\n    it('should register editor.action.pickColor command', async () => {\n      await helper.mockFunction('coc#color#pick_color', [0, 0, 0])\n      let doc = await helper.createDocument()\n      await nvim.setLine('#ffffff')\n      doc.forceSync()\n      await colors.doHighlight(doc.bufnr)\n      await commands.executeCommand('editor.action.pickColor')\n      let line = await nvim.getLine()\n      expect(line).toBe('#000000')\n    })\n\n    it('should register editor.action.colorPresentation command', async () => {\n      colorPresentations = [ColorPresentation.create('red'), ColorPresentation.create('#ff0000')]\n      let doc = await helper.createDocument()\n      await nvim.setLine('#ffffff')\n      await doc.synchronize()\n      await colors.doHighlight(doc.bufnr)\n      let p = commands.executeCommand('editor.action.colorPresentation')\n      await helper.waitPrompt()\n      await nvim.input('1')\n      await p\n      let line = await nvim.getLine()\n      expect(line).toBe('red')\n    })\n\n    it('should register document.toggleColors command', async () => {\n      helper.updateConfiguration('colors.filetypes', [])\n      helper.updateConfiguration('colors.enable', true)\n      let doc = await workspace.document\n      await events.fire('BufUnload', [doc.bufnr])\n      await expect(async () => {\n        await commands.executeCommand('document.toggleColors')\n      }).rejects.toThrow(Error)\n      doc = await helper.createDocument()\n      expect(colors.isEnabled(doc.bufnr)).toBe(true)\n      await commands.executeCommand('document.toggleColors')\n      let enabled = colors.isEnabled(doc.bufnr)\n      expect(enabled).toBe(false)\n      await commands.executeCommand('document.toggleColors')\n      enabled = colors.isEnabled(doc.bufnr)\n      expect(enabled).toBe(true)\n    })\n  })\n\n  describe('doHighlight', () => {\n    it('should merge colors of providers', async () => {\n      disposables.push(languages.registerDocumentColorProvider([{ language: '*' }], {\n        provideColorPresentations: (): ColorPresentation[] => colorPresentations,\n        provideDocumentColors: (\n        ): ProviderResult<ColorInformation[]> => {\n          return [{\n            range: Range.create(0, 0, 1, 0),\n            color: getColor(0, 0, 0)\n          }, {\n            range: Range.create(0, 0, 0, 7),\n            color: getColor(1, 1, 1)\n          }]\n        }\n      }))\n      disposables.push(languages.registerDocumentColorProvider([{ language: '*' }], {\n        provideColorPresentations: (): ColorPresentation[] => colorPresentations,\n        provideDocumentColors: (\n        ): ProviderResult<ColorInformation[]> => {\n          return null\n        }\n      }))\n      let doc = await workspace.document\n      await nvim.setLine('#ffffff #ff0000')\n      await doc.synchronize()\n      let colors = await languages.provideDocumentColors(doc.textDocument, CancellationToken.None)\n      expect(colors.length).toBe(3)\n      let color = ColorInformation.create(Range.create(0, 0, 1, 0), getColor(0, 0, 0))\n      let presentation = await languages.provideColorPresentations(color, doc.textDocument, CancellationToken.None)\n      expect(presentation).toEqual([])\n    })\n\n    it('should clearHighlight on empty result', async () => {\n      let doc = await helper.createDocument()\n      await nvim.setLine('#ffffff')\n      state = 'empty'\n      await colors.doHighlight(doc.bufnr)\n      let res = colors.hasColor(doc.bufnr)\n      expect(res).toBe(false)\n    })\n\n    it('should highlight after ColorScheme event', async () => {\n      let doc = await helper.createDocument()\n      await nvim.setLine('#ffffff #ff0000')\n      await doc.synchronize()\n      await colors.doHighlight(doc.bufnr)\n      await events.fire('ColorScheme', [])\n      expect(colors.hasColor(doc.bufnr)).toBe(true)\n    })\n\n    it('should not throw on error result', async () => {\n      let doc = await helper.createDocument()\n      await nvim.setLine('#ffffff')\n      state = 'error'\n      let err\n      try {\n        await colors.doHighlight(doc.bufnr)\n      } catch (e) {\n        err = e\n      }\n      expect(err).toBeUndefined()\n    })\n\n    it('should highlight after document changed', async () => {\n      let doc = await helper.createDocument()\n      await colors.doHighlight(doc.bufnr)\n      expect(colors.hasColor(doc.bufnr)).toBe(false)\n      expect(colors.hasColorAtPosition(doc.bufnr, Position.create(0, 1))).toBe(false)\n      await nvim.setLine('#ffffff #ff0000')\n      await doc.synchronize()\n      await helper.waitValue(() => {\n        return colors.hasColorAtPosition(doc.bufnr, Position.create(0, 1))\n      }, true)\n      expect(colors.hasColor(doc.bufnr)).toBe(true)\n    })\n\n    it('should clearHighlight on clearHighlight', async () => {\n      let doc = await helper.createDocument()\n      await nvim.setLine('#ffffff #ff0000')\n      await doc.synchronize()\n      await colors.doHighlight(doc.bufnr)\n      expect(colors.hasColor(doc.bufnr)).toBe(true)\n      colors.clearHighlight(doc.bufnr)\n      expect(colors.hasColor(doc.bufnr)).toBe(false)\n    })\n\n    it('should highlight colors', async () => {\n      let doc = await helper.createDocument()\n      await nvim.setLine('#ffffff')\n      await colors.doHighlight(doc.bufnr)\n      let exists = await nvim.call('hlexists', 'BGffffff')\n      expect(exists).toBe(1)\n    })\n  })\n\n  describe('hasColor()', () => {\n    it('should return false when bufnr does not exist', async () => {\n      let res = colors.hasColor(99)\n      colors.clearHighlight(99)\n      expect(res).toBe(false)\n    })\n  })\n\n  describe('getColorInformation()', () => {\n    it('should return null when highlighter does not exist', async () => {\n      let res = await colors.getColorInformation(99)\n      expect(res).toBe(null)\n    })\n\n    it('should return null when color not found', async () => {\n      let doc = await helper.createDocument()\n      await nvim.setLine('#ffffff foo ')\n      doc.forceSync()\n      await colors.doHighlight(doc.bufnr)\n      await nvim.call('cursor', [1, 12])\n      let res = await colors.getColorInformation(doc.bufnr)\n      expect(res).toBe(null)\n    })\n  })\n\n  describe('hasColorAtPosition()', () => {\n    it('should return false when bufnr does not exist', async () => {\n      let res = colors.hasColorAtPosition(99, Position.create(0, 0))\n      expect(res).toBe(false)\n    })\n  })\n\n  describe('pickPresentation()', () => {\n    it('should show warning when color does not exist', async () => {\n      await helper.createDocument()\n      await colors.pickPresentation()\n      let msg = await helper.getCmdline()\n      expect(msg).toMatch('Color not found')\n    })\n\n    it('should not throw when presentations do not exist', async () => {\n      colorPresentations = []\n      let doc = await helper.createDocument()\n      await nvim.setLine('#ffffff')\n      doc.forceSync()\n      await colors.doHighlight(99)\n      await colors.doHighlight(doc.bufnr)\n      await helper.doAction('colorPresentation')\n    })\n\n    it('should pick presentations', async () => {\n      colorPresentations = [ColorPresentation.create('red'), ColorPresentation.create('#ff0000')]\n      let doc = await helper.createDocument()\n      await nvim.setLine('#ffffff')\n      doc.forceSync()\n      await colors.doHighlight(doc.bufnr)\n      let p = helper.doAction('colorPresentation')\n      await helper.waitPrompt()\n      await nvim.input('1')\n      await p\n      let line = await nvim.getLine()\n      expect(line).toBe('red')\n    })\n  })\n\n  describe('pickColor()', () => {\n    it('should show warning when color does not exist', async () => {\n      await helper.createDocument()\n      await colors.pickColor()\n      let msg = await helper.getCmdline()\n      expect(msg).toMatch('not found')\n    })\n\n    it('should pickColor', async () => {\n      await helper.mockFunction('coc#color#pick_color', [0, 0, 0])\n      let doc = await helper.createDocument()\n      await nvim.setLine('#ffffff')\n      doc.forceSync()\n      await colors.doHighlight(doc.bufnr)\n      await helper.doAction('pickColor')\n      let line = await nvim.getLine()\n      expect(line).toBe('#000000')\n    })\n\n    it('should not throw when pick color return 0', async () => {\n      await helper.mockFunction('coc#color#pick_color', 0)\n      let doc = await helper.createDocument()\n      await nvim.setLine('#ffffff')\n      doc.forceSync()\n      await colors.doHighlight(doc.bufnr)\n      await helper.doAction('pickColor')\n      let line = await nvim.getLine()\n      expect(line).toBe('#ffffff')\n    })\n\n    it('should return null when provider not exists', async () => {\n      disposable.dispose()\n      let doc = await workspace.document\n      let color = ColorInformation.create(Range.create(0, 0, 0, 6), Color.create(100, 100, 100, 0))\n      let res = await languages.provideColorPresentations(color, doc.textDocument, CancellationToken.None)\n      expect(res).toBeNull()\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/documentLinks.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationToken, Disposable, DocumentLink, Range } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport events from '../../events'\nimport LinksHandler, { sameLinks } from '../../handler/links'\nimport languages from '../../languages'\nimport { disposeAll } from '../../util'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet links: LinksHandler\nlet disposables: Disposable[] = []\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  links = helper.plugin.getHandler().links\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n})\n\ndescribe('Links', () => {\n  it('should check sameLinks', () => {\n    expect(sameLinks([], [])).toBe(true)\n    expect(sameLinks([{ range: Range.create(0, 0, 0, 1) }], [])).toBe(false)\n    expect(sameLinks([{ range: Range.create(0, 0, 0, 1) }], [{ range: Range.create(0, 0, 1, 0) }])).toBe(false)\n  })\n\n  it('should get document links', async () => {\n    disposables.push(languages.registerDocumentLinkProvider([{ language: '*' }], {\n      provideDocumentLinks: (_doc, _token) => {\n        return [\n          DocumentLink.create(Range.create(0, 0, 0, 5), 'test:///foo'),\n          DocumentLink.create(Range.create(1, 0, 1, 5), 'test:///bar')\n        ]\n      }\n    }))\n    let res = await helper.doAction('links')\n    expect(res.length).toBe(2)\n  })\n\n  it('should merge link results', async () => {\n    disposables.push(languages.registerDocumentLinkProvider([{ language: '*' }], {\n      provideDocumentLinks: () => {\n        return [\n          DocumentLink.create(Range.create(0, 0, 0, 5), 'test:///foo'),\n          DocumentLink.create(Range.create(1, 0, 1, 5), 'test:///bar')\n        ]\n      }\n    }))\n    disposables.push(languages.registerDocumentLinkProvider([{ language: '*' }], {\n      provideDocumentLinks: () => {\n        return [\n          DocumentLink.create(Range.create(1, 0, 1, 5), 'test:///bar'),\n          DocumentLink.create(Range.create(2, 0, 2, 5), 'test:///x'),\n        ]\n      }\n    }))\n    disposables.push(languages.registerDocumentLinkProvider([{ language: '*' }], {\n      provideDocumentLinks: () => {\n        return null\n      }\n    }))\n    let res = await links.getLinks()\n    expect(res.length).toBe(3)\n    let link = await languages.resolveDocumentLink(res[0], CancellationToken.None)\n    expect(link).toBeDefined()\n  })\n\n  it('should throw error when link target not resolved', async () => {\n    disposables.push(languages.registerDocumentLinkProvider([{ language: '*' }], {\n      provideDocumentLinks(_doc, _token) {\n        return [\n          DocumentLink.create(Range.create(0, 0, 0, 5))\n        ]\n      },\n      resolveDocumentLink(link) {\n        return link\n      }\n    }))\n    let res = await links.getLinks()\n    let err\n    try {\n      await links.openLink(res[0])\n    } catch (e) {\n      err = e\n    }\n    expect(err).toBeDefined()\n  })\n\n  it('should return link when resolve undefined', async () => {\n    disposables.push(languages.registerDocumentLinkProvider([{ language: '*' }], {\n      provideDocumentLinks(_doc, _token) {\n        return [DocumentLink.create(Range.create(0, 0, 0, 5), 'foo://1')]\n      },\n      resolveDocumentLink() {\n        return undefined\n      }\n    }))\n    let res = await links.getLinks()\n    let link = await languages.resolveDocumentLink(res[0], CancellationToken.None)\n    expect(link).toBeDefined()\n  })\n\n  it('should cancel resolve on InsertEnter', async () => {\n    helper.updateConfiguration('links.tooltip', true)\n    let doc = await workspace.document\n    let called = false\n    let cancelled = false\n    disposables.push(languages.registerDocumentLinkProvider([{ language: '*' }], {\n      provideDocumentLinks(_doc, _token) {\n        return [DocumentLink.create(Range.create(0, 0, 0, 5))]\n      },\n      resolveDocumentLink(link, token) {\n        called = true\n        return new Promise(resolve => {\n          token.onCancellationRequested(() => {\n            cancelled = true\n            clearTimeout(timer)\n            resolve(undefined)\n          })\n          let timer = setTimeout(() => {\n            resolve(link)\n          }, 500)\n        })\n      }\n    }))\n    let p = links.showTooltip()\n    await helper.waitValue(() => {\n      return called\n    }, true)\n    await events.fire('InsertEnter', [doc.bufnr])\n    await p\n    expect(cancelled).toBe(true)\n  })\n\n  it('should open link at current position', async () => {\n    await nvim.setLine('foo')\n    await nvim.command('normal! 0')\n    disposables.push(workspace.registerTextDocumentContentProvider('test', {\n      provideTextDocumentContent: () => {\n        return 'test'\n      }\n    }))\n    disposables.push(languages.registerDocumentLinkProvider([{ language: '*' }], {\n      provideDocumentLinks(_doc, _token) {\n        return [\n          DocumentLink.create(Range.create(0, 0, 0, 5)),\n        ]\n      },\n      resolveDocumentLink(link) {\n        link.target = 'test:///foo'\n        return link\n      }\n    }))\n    await helper.doAction('openLink')\n    let bufname = await nvim.call('bufname', '%')\n    expect(bufname).toBe('test:///foo')\n    await nvim.call('setline', [1, ['a', 'b', 'c']])\n    await nvim.call('cursor', [3, 1])\n    let res = await links.openCurrentLink()\n    expect(res).toBe(false)\n  })\n\n  it('should return false when current links not found', async () => {\n    await nvim.setLine('foo')\n    await nvim.command('normal! 0')\n    disposables.push(languages.registerDocumentLinkProvider([{ language: '*' }], {\n      provideDocumentLinks(_doc, _token) {\n        return []\n      }\n    }))\n    let res = await links.openCurrentLink()\n    expect(res).toBe(false)\n  })\n\n  it('should show tooltip', async () => {\n    await nvim.setLine('foo')\n    await nvim.call('cursor', [1, 1])\n    let resolve = false\n    disposables.push(languages.registerDocumentLinkProvider([{ language: '*' }], {\n      provideDocumentLinks(_doc, _token) {\n        let link = DocumentLink.create(Range.create(0, 0, 0, 5))\n        link.tooltip = 'test'\n        return [link]\n      },\n      resolveDocumentLink(link) {\n        if (!resolve) return\n        link.target = 'http://example.com'\n        return link\n      }\n    }))\n    await links.showTooltip()\n    let win = await helper.getFloat()\n    expect(win).toBeUndefined()\n    helper.updateConfiguration('links.tooltip', true)\n    await links.showTooltip()\n    win = await helper.getFloat()\n    expect(win).toBeUndefined()\n    resolve = true\n    await links.showTooltip()\n    win = await helper.getFloat()\n    let buf = await win.buffer\n    let lines = await buf.lines\n    expect(lines[0]).toMatch('test')\n  })\n\n  it('should enable tooltip on CursorHold', async () => {\n    let doc = await workspace.document\n    helper.updateConfiguration('links.tooltip', true)\n    await nvim.setLine('http://www.baidu.com')\n    await nvim.call('cursor', [1, 1])\n    let link = await links.getCurrentLink()\n    expect(link).toBeDefined()\n    await events.fire('CursorHold', [doc.bufnr])\n    let win = await helper.getFloat()\n    let buf = await win.buffer\n    let lines = await buf.lines\n    expect(lines[0]).toMatch('baidu')\n  })\n})\n\ndescribe('LinkBuffer', () => {\n  it('should getLinks', async () => {\n    let doc = await workspace.document\n    let buf = links.getBuffer(doc.bufnr)\n    await buf.getLinks()\n    expect(buf.links).toEqual([])\n    let timeout = 100\n    disposables.push(languages.registerDocumentLinkProvider([{ language: '*' }], {\n      provideDocumentLinks: (_doc, token) => {\n        return new Promise(resolve => {\n          token.onCancellationRequested(() => {\n            clearTimeout(timer)\n            resolve(undefined)\n          })\n          let timer = setTimeout(() => {\n            resolve([\n              DocumentLink.create(Range.create(0, 0, 0, 5), 'test:///foo'),\n              DocumentLink.create(Range.create(1, 0, 1, 5), 'test:///bar')\n            ])\n          }, timeout)\n        })\n      }\n    }))\n    let p = buf.getLinks()\n    p = buf.getLinks()\n    buf.cancel()\n    await p\n    expect(buf.links).toEqual([])\n  })\n\n  it('should do highlight', async () => {\n    disposables.push(languages.registerDocumentLinkProvider([{ language: '*' }], {\n      provideDocumentLinks: (doc: TextDocument) => {\n        let links: DocumentLink[] = []\n        for (let i = 0; i < doc.lineCount - 1; i++) {\n          links.push(DocumentLink.create(Range.create(i, 0, i, 1), 'test:///bar'))\n        }\n        return links\n      }\n    }))\n    helper.updateConfiguration('links.highlight', true)\n    let doc = await helper.createDocument()\n    await nvim.setLine('foo')\n    await doc.synchronize()\n    let buf = links.getBuffer(doc.bufnr)\n    await helper.waitValue(() => {\n      return buf.links?.length\n    }, 1)\n    await nvim.call('append', [0, ['foo']])\n    doc._forceSync()\n    await helper.waitValue(() => {\n      return buf.links?.length\n    }, 2)\n    await nvim.setLine('foo')\n    doc._forceSync()\n    let hls = await buf.buffer.getHighlights('links')\n    expect(hls.length).toBe(2)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/fold.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationToken, CancellationTokenSource, Disposable, FoldingRange } from 'vscode-languageserver-protocol'\nimport FoldHandler from '../../handler/fold'\nimport languages from '../../languages'\nimport { disposeAll } from '../../util'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet folds: FoldHandler\nlet disposables: Disposable[] = []\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  folds = helper.plugin.getHandler().fold\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nbeforeEach(async () => {\n  await helper.createDocument()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n})\n\ndescribe('Folds', () => {\n  it('should return empty array when provider does not exist', async () => {\n    let doc = await workspace.document\n    let token = (new CancellationTokenSource()).token\n    expect(await languages.provideFoldingRanges(doc.textDocument, {}, token)).toEqual([])\n  })\n\n  it('should return false when no fold ranges found', async () => {\n    disposables.push(languages.registerFoldingRangeProvider([{ language: '*' }], {\n      provideFoldingRanges(_doc) {\n        return []\n      }\n    }))\n    await helper.wait(50)\n    let res = await helper.doAction('fold')\n    expect(res).toBe(false)\n  })\n\n  it('should fold all fold ranges', async () => {\n    disposables.push(languages.registerFoldingRangeProvider([{ language: '*' }], {\n      provideFoldingRanges(_doc) {\n        return [FoldingRange.create(1, 3), FoldingRange.create(4, 6, 0, 0, 'comment')]\n      }\n    }))\n    await helper.wait(50)\n    await nvim.call('setline', [1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']])\n    let res = await folds.fold()\n    expect(res).toBe(true)\n    let closed = await nvim.call('foldclosed', [2])\n    expect(closed).toBe(2)\n    closed = await nvim.call('foldclosed', [5])\n    expect(closed).toBe(5)\n  })\n\n  it('should merge folds from all providers', async () => {\n    let doc = await workspace.document\n    disposables.push(languages.registerFoldingRangeProvider([{ language: '*' }], {\n      provideFoldingRanges() {\n        return [FoldingRange.create(2, 3), FoldingRange.create(4, 6)]\n      }\n    }))\n    disposables.push(languages.registerFoldingRangeProvider([{ language: '*' }], {\n      provideFoldingRanges() {\n        return [FoldingRange.create(1, 2), FoldingRange.create(5, 6), FoldingRange.create(7, 8)]\n      }\n    }))\n    await helper.wait(50)\n    await nvim.call('setline', [1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']])\n    await doc.synchronize()\n    let foldingRanges = await languages.provideFoldingRanges(doc.textDocument, {}, CancellationToken.None)\n    expect(foldingRanges.length).toBe(4)\n  })\n\n  it('should ignore range start at the same line', async () => {\n    let doc = await workspace.document\n    disposables.push(languages.registerFoldingRangeProvider([{ language: '*' }], {\n      provideFoldingRanges() {\n        return [FoldingRange.create(2, 3), FoldingRange.create(4, 6)]\n      }\n    }))\n    disposables.push(languages.registerFoldingRangeProvider([{ language: '*' }], {\n      provideFoldingRanges() {\n        return [FoldingRange.create(4, 5)]\n      }\n    }))\n    await helper.wait(50)\n    await nvim.call('setline', [1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']])\n    await doc.synchronize()\n    let foldingRanges = await languages.provideFoldingRanges(doc.textDocument, {}, CancellationToken.None)\n    expect(foldingRanges.length).toBe(2)\n  })\n\n  it('should fold comment ranges', async () => {\n    disposables.push(languages.registerFoldingRangeProvider([{ language: '*' }], {\n      provideFoldingRanges(_doc) {\n        return [FoldingRange.create(1, 3), FoldingRange.create(4, 6, 0, 0, 'comment')]\n      }\n    }))\n    await helper.wait(50)\n    await nvim.call('setline', [1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']])\n    let res = await folds.fold('comment')\n    expect(res).toBe(true)\n    let closed = await nvim.call('foldclosed', [2])\n    expect(closed).toBe(-1)\n    closed = await nvim.call('foldclosed', [5])\n    expect(closed).toBe(5)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/format.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationToken, CancellationTokenSource, Disposable, Position, Range, TextEdit } from 'vscode-languageserver-protocol'\nimport commands from '../../commands'\nimport Format from '../../handler/format'\nimport languages, { ProviderName } from '../../languages'\nimport { disposeAll } from '../../util'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nlet format: Format\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  format = helper.plugin.getHandler().format\n})\n\nbeforeEach(() => {\n  helper.updateConfiguration('coc.preferences.formatOnType', true)\n})\n\nafterEach(async () => {\n  await helper.reset()\n  disposeAll(disposables)\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\ndescribe('format handler', () => {\n  describe('documentFormat', () => {\n    it('should return null when format provider not exists', async () => {\n      let doc = await workspace.document\n      let res = await languages.provideDocumentFormattingEdits(doc.textDocument, { insertSpaces: false, tabSize: 2 }, CancellationToken.None)\n      expect(res).toBeNull()\n    })\n\n    it('should throw when provider not found', async () => {\n      await expect(async () => {\n        await commands.executeCommand('editor.action.formatDocument', 999)\n      }).rejects.toThrow(Error)\n      await expect(async () => {\n        await commands.executeCommand('editor.action.formatDocument')\n      }).rejects.toThrow(Error)\n      await expect(async () => {\n        let doc = await workspace.document\n        await commands.executeCommand('editor.action.formatDocument', doc.uri)\n      }).rejects.toThrow(Error)\n    })\n\n    it('should return false when get empty edits ', async () => {\n      disposables.push(languages.registerDocumentFormatProvider(['*'], {\n        provideDocumentFormattingEdits: () => {\n          return []\n        }\n      }))\n      let doc = await helper.createDocument()\n      let res = await format.documentFormat(doc)\n      expect(res).toBe(false)\n    })\n\n    it('should use provider that have higher score', async () => {\n      disposables.push(languages.registerDocumentFormatProvider([{ language: 'vim' }], {\n        provideDocumentFormattingEdits: () => {\n          return [TextEdit.insert(Position.create(0, 0), '  ')]\n        }\n      }))\n      disposables.push(languages.registerDocumentFormatProvider(['*'], {\n        provideDocumentFormattingEdits: () => {\n          return []\n        }\n      }))\n      let doc = await helper.createDocument('t.vim')\n      let res = await languages.provideDocumentFormattingEdits(doc.textDocument, { tabSize: 2, insertSpaces: false }, CancellationToken.None)\n      expect(res.length).toBe(1)\n    })\n\n    it('should not fallback to range formatter when document formatter returns null', async () => {\n      let called = false\n      disposables.push(languages.registerDocumentFormatProvider([{ language: 'text' }], {\n        provideDocumentFormattingEdits: () => {\n          return null\n        }\n      }))\n      disposables.push(languages.registerDocumentRangeFormatProvider([{ language: 'text' }], {\n        provideDocumentRangeFormattingEdits: () => {\n          called = true\n          return [TextEdit.insert(Position.create(0, 0), '  ')]\n        }\n      }))\n      let doc = await helper.createDocument('t.txt')\n      let edits = await languages.provideDocumentFormattingEdits(doc.textDocument, { tabSize: 2, insertSpaces: false }, CancellationToken.None)\n      expect(edits).toBeNull()\n      expect(called).toBe(false)\n    })\n\n    it('should fallback to range formatter when document formatter not exists', async () => {\n      let called = false\n      disposables.push(languages.registerDocumentRangeFormatProvider([{ language: 'text' }], {\n        provideDocumentRangeFormattingEdits: () => {\n          called = true\n          return [TextEdit.insert(Position.create(0, 0), '  ')]\n        }\n      }))\n      let doc = await helper.createDocument('t.txt')\n      let edits = await languages.provideDocumentFormattingEdits(doc.textDocument, { tabSize: 2, insertSpaces: false }, CancellationToken.None)\n      expect(called).toBe(true)\n      expect(edits.length).toBe(1)\n    })\n\n    it('should format current buffer', async () => {\n      disposables.push(languages.registerDocumentFormatProvider([{ language: 'vim' }], {\n        provideDocumentFormattingEdits: () => {\n          return [TextEdit.insert(Position.create(0, 0), '  ')]\n        }\n      }))\n      await helper.createDocument('t.vim')\n      await commands.executeCommand('editor.action.format')\n      let line = await nvim.line\n      expect(line).toBe('  ')\n    })\n\n    it('should use specified format provider', async () => {\n      helper.updateConfiguration('coc.preferences.formatterExtension', 'foo', disposables)\n      disposables.push(languages.registerDocumentFormatProvider([{ language: '*' }], {\n        provideDocumentFormattingEdits: () => {\n          return [TextEdit.insert(Position.create(0, 0), '  ')]\n        }\n      }))\n      let doc = await workspace.document\n      let res = await format.documentFormat(doc)\n      expect(res).toBe(true)\n      let provider = {\n        provideDocumentFormattingEdits: doc => {\n          let line = doc.lines[0] as string\n          return [TextEdit.replace(Range.create(0, 0, 0, line.length), 'foo')]\n        }\n      }\n      provider['__extensionName'] = 'foo'\n      disposables.push(languages.registerDocumentFormatProvider([{ language: '*' }], provider))\n      await format.documentFormat(doc)\n      let line = doc.getline(0)\n      expect(line).toBe('foo')\n    })\n  })\n\n  describe('rangeFormat', () => {\n    it('should return null when provider does not exist', async () => {\n      let doc = (await workspace.document).textDocument\n      let range = Range.create(0, 0, 1, 0)\n      let options = await workspace.getFormatOptions()\n      let token = (new CancellationTokenSource()).token\n      expect(await languages.provideDocumentRangeFormattingEdits(doc, range, options, token)).toBe(null)\n      expect(languages.hasProvider(ProviderName.FormatOnType, doc)).toBe(false)\n      expect(languages.hasProvider(ProviderName.OnTypeEdit, doc)).toBe(false)\n      let edits = await languages.provideDocumentFormattingEdits(doc, options, token)\n      expect(edits).toBe(null)\n    })\n\n    it('should return -1 when range not exists', async () => {\n      disposables.push(languages.registerDocumentRangeFormatProvider(['*'], {\n        provideDocumentRangeFormattingEdits: () => {\n          return []\n        }\n      }, 1))\n      let spy = jest.spyOn(window, 'getSelectedRange').mockImplementation(() => {\n        return Promise.resolve(null)\n      })\n      let doc = await workspace.document\n      let res = await format.documentRangeFormat(doc, 'v')\n      spy.mockRestore()\n      expect(res).toBe(-1)\n    })\n\n    it('should invoke range format', async () => {\n      disposables.push(languages.registerDocumentRangeFormatProvider(['text'], {\n        provideDocumentRangeFormattingEdits: (_document, range) => {\n          let lines: number[] = []\n          for (let i = range.start.line; i <= range.end.line; i++) {\n            lines.push(i)\n          }\n          return lines.map(i => {\n            return TextEdit.insert(Position.create(i, 0), '  ')\n          })\n        }\n      }, 1))\n      let doc = await helper.createDocument()\n      doc.setFiletype('text')\n      await nvim.call('setline', [1, ['a', 'b', 'c']])\n      await nvim.command('normal! ggvG')\n      await nvim.input('<esc>')\n      expect(languages.hasFormatProvider(doc.textDocument)).toBe(true)\n      expect(languages.hasProvider(ProviderName.Format, doc.textDocument)).toBe(true)\n      await helper.doAction('formatSelected', 'v')\n      let buf = nvim.createBuffer(doc.bufnr)\n      let lines = await buf.lines\n      expect(lines).toEqual(['  a', '  b', '  c'])\n      let options = await workspace.getFormatOptions(doc.bufnr)\n      let token = (new CancellationTokenSource()).token\n      let edits = await languages.provideDocumentFormattingEdits(doc.textDocument, options, token)\n      expect(edits.length).toBeGreaterThan(0)\n    })\n\n    it('should format range by formatexpr option', async () => {\n      let range: Range\n      disposables.push(languages.registerDocumentRangeFormatProvider(['text'], {\n        provideDocumentRangeFormattingEdits: (_document, r) => {\n          range = r\n          return []\n        }\n      }))\n      let doc = await helper.createDocument()\n      doc.setFiletype('text')\n      await nvim.call('setline', [1, ['a', 'b', 'c']])\n      await nvim.command(`setl formatexpr=CocAction('formatSelected')`)\n      await nvim.command('normal! ggvGgq')\n      expect(range).toEqual({\n        start: { line: 0, character: 0 }, end: { line: 3, character: 0 }\n      })\n    })\n  })\n\n  describe('formatOnType', () => {\n    it('should invoke format', async () => {\n      disposables.push(languages.registerDocumentFormatProvider(['text'], {\n        provideDocumentFormattingEdits: () => {\n          return [TextEdit.insert(Position.create(0, 0), '  ')]\n        }\n      }))\n      let doc = await helper.createDocument()\n      doc.setFiletype('text')\n      await nvim.setLine('foo')\n      await helper.doAction('format')\n      let line = await nvim.line\n      expect(line).toEqual('  foo')\n    })\n\n    it('should respect formatOnTypeFiletypes', async () => {\n      helper.updateConfiguration('coc.preferences.formatOnTypeFiletypes', ['*'])\n      expect(format.shouldFormatOnType('vim')).toBe(true)\n      helper.updateConfiguration('coc.preferences.formatOnTypeFiletypes', ['txt'])\n      let doc = await helper.createDocument('t.vim')\n      let res = await format.tryFormatOnType('\\n', doc)\n      expect(res).toBe(false)\n    })\n\n    it('should not format on type when disabled by variable', async () => {\n      disposables.push(languages.registerDocumentFormatProvider(['*'], {\n        provideDocumentFormattingEdits: () => {\n          return [TextEdit.insert(Position.create(0, 0), '  ')]\n        }\n      }))\n      nvim.pauseNotification()\n      nvim.command('e foo', true)\n      nvim.command('let b:coc_disable_autoformat = 1', true)\n      await nvim.resumeNotification()\n      let doc = await workspace.document\n      let res = await format.tryFormatOnType('\\n', doc)\n      expect(res).toBe(false)\n    })\n\n    it('should does format on type', async () => {\n      let doc = await workspace.document\n      disposables.push(languages.registerOnTypeFormattingEditProvider(['*'], {\n        provideOnTypeFormattingEdits: () => {\n          return [TextEdit.insert(Position.create(0, 0), '  ')]\n        }\n      }, ['|']))\n      let res = await format.tryFormatOnType(';', doc)\n      expect(res).toBe(false)\n      await helper.edit()\n      await nvim.input('i|')\n      await helper.waitFor('getline', ['.'], '  |')\n      let cursor = await window.getCursorPosition()\n      expect(cursor).toEqual({ line: 0, character: 3 })\n    })\n\n    it('should return null when provider not found', async () => {\n      let doc = await workspace.document\n      let res = await languages.provideDocumentOnTypeEdits('|', doc.textDocument, Position.create(0, 0), CancellationToken.None)\n      expect(res).toBeNull()\n    })\n\n    it('should adjust cursor after format on type', async () => {\n      disposables.push(languages.registerOnTypeFormattingEditProvider(['text'], {\n        provideOnTypeFormattingEdits: () => {\n          return [\n            TextEdit.insert(Position.create(0, 0), '  '),\n            TextEdit.insert(Position.create(0, 2), 'end')\n          ]\n        }\n      }, ['|']))\n      disposables.push(languages.registerOnTypeFormattingEditProvider([{ language: '*' }], {\n        provideOnTypeFormattingEdits: () => {\n          return []\n        }\n      }))\n      let doc = await helper.createDocument()\n      doc.setFiletype('text')\n      await nvim.setLine('\"')\n      await nvim.input('i|')\n      await helper.waitFor('getline', ['.'], '  |\"end')\n      let cursor = await window.getCursorPosition()\n      expect(cursor).toEqual({ line: 0, character: 3 })\n    })\n  })\n\n  describe('bracketEnterImprove', () => {\n    afterEach(() => {\n      nvim.command('iunmap <CR>', true)\n    })\n\n    it('should not throw for buffer not attached', async () => {\n      await nvim.command(`edit +setl\\\\ buftype=nofile foo`)\n      let doc = await workspace.document\n      expect(doc.attached).toBe(false)\n      await format.handleEnter(doc.bufnr)\n    })\n\n    it('should format vim file on enter', async () => {\n      let buf = await helper.edit('foo.vim')\n      await buf.setOption('expandtab', true)\n      await nvim.command(`inoremap <silent><expr> <cr> pumvisible() ? coc#_select_confirm() : \"\\\\<C-g>u\\\\<CR>\\\\<c-r>=coc#on_enter()\\\\<CR>\"`)\n      await nvim.setLine('let foo={}')\n      await nvim.command(`normal! gg$`)\n      await nvim.input('i')\n      await nvim.eval(`feedkeys(\"\\\\<CR>\", 'im')`)\n      await helper.waitFor('getline', [2], '  \\\\ ')\n      let lines = await buf.lines\n      expect(lines).toEqual(['let foo={', '  \\\\ ', '  \\\\ }'])\n    })\n\n    it('should use tab on format', async () => {\n      let buf = await helper.edit('foo.vim')\n      await buf.setOption('expandtab', false)\n      await nvim.command(`inoremap <silent><expr> <cr> pumvisible() ? coc#_select_confirm() : \"\\\\<C-g>u\\\\<CR>\\\\<c-r>=coc#on_enter()\\\\<CR>\"`)\n      await nvim.setLine('let foo={}')\n      await nvim.command(`normal! gg$`)\n      await nvim.input('i')\n      await nvim.eval(`feedkeys(\"\\\\<CR>\", 'im')`)\n      await helper.waitFor('getline', ['.'], '\\t\\\\ ')\n    })\n\n    it('should add new line between bracket', async () => {\n      let buf = await helper.edit()\n      await nvim.command(`inoremap <silent><expr> <cr> pumvisible() ? coc#_select_confirm() : \"\\\\<C-g>u\\\\<CR>\\\\<c-r>=coc#on_enter()\\\\<CR>\"`)\n      await nvim.setLine('  {}')\n      await nvim.command(`normal! gg$`)\n      await nvim.input('i')\n      await nvim.eval(`feedkeys(\"\\\\<CR>\", 'im')`)\n      await helper.waitFor('getline', [2], '    ')\n      let lines = await buf.lines\n      expect(lines).toEqual(['  {', '    ', '  }'])\n    })\n  })\n\n  describe('logProvider()', () => {\n    it('should log provider', () => {\n      format.logProvider(1, [])\n      format.logProvider(1, null)\n      let edits = [TextEdit.insert(Position.create(1, 1), 'foo')]\n      format.logProvider(1, edits)\n      let called = false\n      Object.defineProperty(edits, '__extensionName', {\n        get: () => {\n          called = true\n          return 'name'\n        }\n      })\n      format.logProvider(1, edits)\n      expect(called).toBe(true)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/highlights.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { Disposable, DocumentHighlightKind, Position, Range, TextEdit } from 'vscode-languageserver-protocol'\nimport commands from '../../commands'\nimport Highlights from '../../handler/highlights'\nimport languages from '../../languages'\nimport { disposeAll } from '../../util'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nlet highlights: Highlights\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  highlights = helper.plugin.handler.documentHighlighter\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  await helper.reset()\n  disposeAll(disposables)\n  disposables = []\n})\n\nfunction registerProvider(): void {\n  disposables.push(languages.registerDocumentHighlightProvider([{ language: '*' }], {\n    provideDocumentHighlights: async document => {\n      let word = await nvim.eval('expand(\"<cword>\")')\n      // let word = document.get\n      let matches = Array.from((document.getText() as any).matchAll(/\\w+/g)) as any[]\n      let filtered = matches.filter(o => o[0] == word)\n      return filtered.map((o, i) => {\n        let start = document.positionAt(o.index)\n        let end = document.positionAt(o.index + o[0].length)\n        return {\n          range: Range.create(start, end),\n          kind: i == 0 ? DocumentHighlightKind.Text : i % 2 == 0 ? DocumentHighlightKind.Read : DocumentHighlightKind.Write\n        }\n      }).concat([{ range: undefined, kind: 2 }])\n    }\n  }))\n}\n\ndescribe('document highlights', () => {\n  function registerTimerProvider(fn: Function, timeout: number): void {\n    disposables.push(languages.registerDocumentHighlightProvider([{ language: '*' }], {\n      provideDocumentHighlights: (_document, _position, token) => {\n        return new Promise(resolve => {\n          token.onCancellationRequested(() => {\n            clearTimeout(timer)\n            fn()\n            resolve([])\n          })\n          let timer = setTimeout(() => {\n            resolve([{ range: Range.create(0, 0, 0, 3) }])\n          }, timeout)\n        })\n      }\n    }))\n  }\n\n  it('should not throw when no range to jump', async () => {\n    let fn = jest.fn()\n    registerTimerProvider(fn, 10)\n    await commands.executeCommand('document.jumpToNextSymbol')\n    await commands.executeCommand('document.jumpToPrevSymbol')\n  })\n\n  it('should jump to previous range', async () => {\n    disposables.push(languages.registerDocumentHighlightProvider([{ language: '*' }], {\n      provideDocumentHighlights: () => {\n        return [{\n          range: Range.create(0, 0, 0, 1),\n          kind: DocumentHighlightKind.Read\n        }, {\n          range: Range.create(0, 2, 0, 3),\n          kind: DocumentHighlightKind.Read\n        }]\n      }\n    }))\n    await nvim.setLine('foo bar')\n    await nvim.command('normal! $')\n    await commands.executeCommand('document.jumpToPrevSymbol')\n    let cur = await window.getCursorPosition()\n    expect(cur).toEqual(Position.create(0, 2))\n    await commands.executeCommand('document.jumpToPrevSymbol')\n    cur = await window.getCursorPosition()\n    expect(cur).toEqual(Position.create(0, 0))\n    await commands.executeCommand('document.jumpToPrevSymbol')\n    cur = await window.getCursorPosition()\n    expect(cur).toEqual(Position.create(0, 2))\n  })\n\n  it('should jump to next range', async () => {\n    disposables.push(languages.registerDocumentHighlightProvider([{ language: '*' }], {\n      provideDocumentHighlights: () => {\n        return [{\n          range: Range.create(0, 0, 0, 1),\n          kind: DocumentHighlightKind.Read\n        }, {\n          range: Range.create(0, 2, 0, 3),\n          kind: DocumentHighlightKind.Read\n        }]\n      }\n    }))\n    await nvim.setLine('foo bar')\n    await nvim.command('normal! ^')\n    await commands.executeCommand('document.jumpToNextSymbol')\n    let cur = await window.getCursorPosition()\n    expect(cur).toEqual(Position.create(0, 2))\n    await commands.executeCommand('document.jumpToNextSymbol')\n    cur = await window.getCursorPosition()\n    expect(cur).toEqual(Position.create(0, 0))\n    await commands.executeCommand('document.jumpToNextSymbol')\n    cur = await window.getCursorPosition()\n    expect(cur).toEqual(Position.create(0, 2))\n  })\n\n  it('should not throw when provide throws', async () => {\n    disposables.push(languages.registerDocumentHighlightProvider([{ language: '*' }], {\n      provideDocumentHighlights: () => {\n        return null\n      }\n    }))\n    disposables.push(languages.registerDocumentHighlightProvider([{ language: '*' }], {\n      provideDocumentHighlights: () => {\n        throw new Error('fake error')\n      }\n    }))\n    disposables.push(languages.registerDocumentHighlightProvider([{ language: '*' }], {\n      provideDocumentHighlights: () => {\n        return [{\n          range: Range.create(0, 0, 0, 3),\n          kind: DocumentHighlightKind.Read\n        }]\n      }\n    }))\n    let doc = await workspace.document\n    await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo')])\n    let res = await highlights.getHighlights(doc, Position.create(0, 0))\n    expect(res).toBeDefined()\n  })\n\n  it('should return null when highlights provide not exist', async () => {\n    let doc = await workspace.document\n    await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo')])\n    let res = await highlights.getHighlights(doc, Position.create(0, 0))\n    expect(res).toBeNull()\n  })\n\n  it('should cancel request on CursorMoved', async () => {\n    let fn = jest.fn()\n    registerTimerProvider(fn, 3000)\n    await helper.edit()\n    await nvim.setLine('foo')\n    let p = highlights.highlight()\n    await helper.wait(50)\n    await nvim.call('cursor', [1, 2])\n    await p\n    expect(fn).toHaveBeenCalled()\n  })\n\n  it('should cancel on timeout', async () => {\n    helper.updateConfiguration('documentHighlight.timeout', 10)\n    let fn = jest.fn()\n    registerTimerProvider(fn, 3000)\n    await helper.edit()\n    await nvim.setLine('foo')\n    await highlights.highlight()\n    expect(fn).toHaveBeenCalled()\n  })\n\n  it('should add highlights to symbols', async () => {\n    registerProvider()\n    await helper.createDocument()\n    await nvim.setLine('foo bar foo foo bar')\n    await helper.doAction('highlight')\n    let winid = await nvim.call('win_getid') as number\n    expect(highlights.hasHighlights(winid)).toBe(true)\n  })\n\n  it('should return highlight ranges', async () => {\n    registerProvider()\n    await helper.createDocument()\n    await nvim.setLine('foo bar foo')\n    let res = await helper.doAction('symbolRanges')\n    expect(res.length).toBe(2)\n  })\n\n  it('should return null when cursor not in word range', async () => {\n    disposables.push(languages.registerDocumentHighlightProvider([{ language: '*' }], {\n      provideDocumentHighlights: () => {\n        return [{ range: Range.create(0, 0, 0, 3) }]\n      }\n    }))\n    let doc = await helper.createDocument()\n    await nvim.setLine('  oo')\n    await nvim.call('cursor', [1, 2])\n    let res = await highlights.getHighlights(doc, Position.create(0, 0))\n    expect(res).toBeNull()\n  })\n\n  it('should not throw when document is command line', async () => {\n    await nvim.call('feedkeys', ['q:', 'in'])\n    let doc = await workspace.document\n    expect(doc.isCommandLine).toBe(true)\n    await highlights.highlight()\n    await nvim.input('<C-c>')\n  })\n\n  it('should not throw when provider not found', async () => {\n    disposeAll(disposables)\n    await helper.createDocument()\n    await nvim.setLine('  oo')\n    await nvim.call('cursor', [1, 2])\n    await highlights.highlight()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/hover.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationToken, Disposable, Hover, MarkedString, MarkupKind, Position, Range, TextEdit } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport HoverHandler, { addDefinitions, addDocument, isDocumentation, readLines } from '../../handler/hover'\nimport languages from '../../languages'\nimport { Documentation } from '../../types'\nimport { disposeAll } from '../../util'\nimport workspace from '../../workspace'\nimport helper, { createTmpFile } from '../helper'\n\nlet nvim: Neovim\nlet hover: HoverHandler\nlet disposables: Disposable[] = []\nlet hoverResult: Hover\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  hover = helper.plugin.getHandler().hover\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nbeforeEach(async () => {\n  await helper.createDocument()\n  disposables.push(languages.registerHoverProvider([{ language: '*' }], {\n    provideHover: (_doc, _pos, _token) => {\n      return hoverResult\n    }\n  }))\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n})\n\nasync function getDocumentText(): Promise<string> {\n  let lines = await nvim.call('getbufline', ['coc://document', 1, '$']) as string[]\n  return lines.join('\\n')\n}\n\ndescribe('Hover', () => {\n  describe('utils', () => {\n    it('should addDocument', async () => {\n      let docs: Documentation[] = []\n      addDocument(docs, '', '')\n      expect(docs.length).toBe(0)\n    })\n\n    it('should check documentation', async () => {\n      expect(isDocumentation(undefined)).toBe(false)\n      expect(isDocumentation({})).toBe(false)\n      expect(isDocumentation({ filetype: '', content: '' })).toBe(true)\n    })\n\n    it('should readLines', async () => {\n      let res = await readLines('file:///not_exists', 0, 1)\n      expect(res).toEqual([])\n    })\n\n    it('should addDefinitions', async () => {\n      let hovers = []\n      let range = Range.create(0, 0, 0, 0)\n      await addDefinitions(hovers, [undefined, {} as any, { targetUri: 'file:///not_exists', targetRange: range, targetSelectionRange: range }], '')\n      expect(hovers.length).toBe(0)\n      let file = await createTmpFile('  foo\\nbar\\n', disposables)\n      range = Range.create(0, 0, 300, 0)\n      await addDefinitions(hovers, [{ targetUri: URI.file(file).toString(), targetRange: range, targetSelectionRange: range }], '')\n      expect(hovers.length).toBe(1)\n    })\n  })\n\n  describe('onHover', () => {\n    it('should return false when hover not found', async () => {\n      hoverResult = null\n      let res = await hover.onHover('preview')\n      expect(res).toBe(false)\n    })\n\n    it('should show MarkupContent hover', async () => {\n      helper.updateConfiguration('hover.target', 'preview')\n      hoverResult = { contents: { kind: 'plaintext', value: 'my hover' } }\n      await helper.doAction('doHover')\n      let res = await getDocumentText()\n      expect(res).toMatch('my hover')\n    })\n\n    it('should merge hover results', async () => {\n      hoverResult = { contents: { kind: 'plaintext', value: 'my hover' } }\n      disposables.push(languages.registerHoverProvider([{ language: '*' }], {\n        provideHover: (_doc, _pos, _token) => {\n          return null\n        }\n      }))\n      disposables.push(languages.registerHoverProvider([{ language: '*' }], {\n        provideHover: (_doc, _pos, _token) => {\n          return { contents: { kind: 'plaintext', value: 'my hover' } }\n        }\n      }))\n      let doc = await workspace.document\n      let hovers = await languages.getHover(doc.textDocument, Position.create(0, 0), CancellationToken.None)\n      expect(hovers.length).toBe(1)\n    })\n\n    it('should show MarkedString hover', async () => {\n      hoverResult = { contents: 'string hover' }\n      disposables.push(languages.registerHoverProvider([{ language: '*' }], {\n        provideHover: (_doc, _pos, _token) => {\n          return { contents: { language: 'typescript', value: 'language hover' } }\n        }\n      }))\n      await hover.onHover('preview')\n      let res = await getDocumentText()\n      expect(res).toMatch('string hover')\n      expect(res).toMatch('language hover')\n    })\n\n    it('should show MarkedString hover array', async () => {\n      hoverResult = { contents: ['foo', { language: 'typescript', value: 'bar' }] }\n      await hover.onHover('preview')\n      let res = await getDocumentText()\n      expect(res).toMatch('foo')\n      expect(res).toMatch('bar')\n    })\n\n    it('should highlight hover range', async () => {\n      await nvim.setLine('var')\n      await nvim.command('normal! 0')\n      hoverResult = { contents: ['foo'], range: Range.create(0, 0, 0, 3) }\n      await hover.onHover('preview')\n      let res = await nvim.call('getmatches') as any[]\n      expect(res.length).toBe(1)\n      expect(res[0].group).toBe('CocHoverRange')\n      await helper.waitValue(async () => {\n        let res = await nvim.call('getmatches') as any[]\n        return res.length\n      }, 0)\n    })\n  })\n\n  describe('previewHover', () => {\n    it('should echo hover message', async () => {\n      hoverResult = { contents: ['foo'] }\n      let res = await hover.onHover('echo')\n      expect(res).toBe(true)\n      let msg = await helper.getCmdline()\n      expect(msg).toMatch('foo')\n    })\n\n    it('should show hover in float window', async () => {\n      hoverResult = { contents: { kind: 'markdown', value: '```typescript\\nconst foo:number\\n```' } }\n      await hover.onHover('float')\n      let win = await helper.getFloat()\n      expect(win).toBeDefined()\n      let lines = await nvim.eval(`getbufline(winbufnr(${win.id}),1,'$')`)\n      expect(lines).toEqual(['const foo:number'])\n    })\n  })\n\n  describe('getHover', () => {\n    it('should get hover from MarkedString array', async () => {\n      hoverResult = { contents: ['foo', { language: 'typescript', value: 'bar' }] }\n      disposables.push(languages.registerHoverProvider([{ language: '*' }], {\n        provideHover: (_doc, _pos, _token) => {\n          return { contents: { language: 'typescript', value: 'MarkupContent hover' } }\n        }\n      }))\n      disposables.push(languages.registerHoverProvider([{ language: '*' }], {\n        provideHover: (_doc, _pos, _token) => {\n          return { contents: MarkedString.fromPlainText('MarkedString hover') }\n        }\n      }))\n      let res = await helper.doAction('getHover')\n      expect(res.includes('foo')).toBe(true)\n      expect(res.includes('bar')).toBe(true)\n      expect(res.includes('MarkupContent hover')).toBe(true)\n      expect(res.includes('MarkedString hover')).toBe(true)\n    })\n\n    it('should filter empty hover message', async () => {\n      hoverResult = { contents: [''] }\n      disposables.push(languages.registerHoverProvider([{ language: '*' }], {\n        provideHover: (_doc, _pos, _token) => {\n          return { contents: { kind: MarkupKind.PlainText, value: 'value' } }\n        }\n      }))\n      let res = await hover.getHover({ line: 1, col: 2 })\n      expect(res).toEqual(['value'])\n    })\n\n    it('should throw when buffer not attached', async () => {\n      await expect(async () => {\n        await hover.getHover({ bufnr: 999, line: 1, col: 2 })\n      }).rejects.toThrow(/not exists/)\n    })\n  })\n\n  describe('definitionHover', () => {\n    it('should load definition from buffer', async () => {\n      hoverResult = { contents: 'string hover' }\n      let doc = await helper.createDocument()\n      await nvim.call('cursor', [1, 1])\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo\\nbar')])\n      disposables.push(languages.registerDefinitionProvider([{ language: '*' }], {\n        provideDefinition() {\n          return [{\n            targetUri: doc.uri,\n            targetRange: Range.create(0, 0, 1, 3),\n            targetSelectionRange: Range.create(0, 0, 0, 3),\n          }]\n        }\n      }))\n      await helper.doAction('definitionHover', 'preview')\n      let res = await getDocumentText()\n      expect(res).toBe('string hover\\n\\nfoo\\nbar')\n    })\n\n    it('should load definition link from file', async () => {\n      let fsPath = await createTmpFile('foo\\nbar\\n')\n      hoverResult = { contents: 'string hover', range: Range.create(0, 0, 0, 3) }\n      let doc = await helper.createDocument()\n      await nvim.call('cursor', [1, 1])\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo\\nbar')])\n      disposables.push(languages.registerDefinitionProvider([{ language: '*' }], {\n        provideDefinition() {\n          return [{\n            targetUri: URI.file(fsPath).toString(),\n            targetRange: Range.create(0, 0, 1, 3),\n            targetSelectionRange: Range.create(0, 0, 0, 3),\n          }]\n        }\n      }))\n      await hover.definitionHover('preview')\n      let res = await getDocumentText()\n      expect(res).toBe('string hover\\n\\nfoo\\nbar')\n    })\n\n    it('should return false when hover not found', async () => {\n      hoverResult = undefined\n      let res = await hover.definitionHover('float')\n      expect(res).toBe(false)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/index.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { Disposable, SymbolKind } from 'vscode-languageserver-protocol'\nimport commands from '../../commands'\nimport Handler from '../../handler/index'\nimport { toDocumentation } from '../../handler/util'\nimport { ProviderName } from '../../languages'\nimport { disposeAll } from '../../util'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet handler: Handler\nlet disposables: Disposable[] = []\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  handler = (helper.plugin as any).handler\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nbeforeEach(async () => {\n  await helper.createDocument()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n})\n\ndescribe('Handler', () => {\n  describe('util', () => {\n    it('should to documentation', () => {\n      expect(toDocumentation('doc')).toEqual({ content: 'doc', filetype: 'txt' })\n      expect(toDocumentation({ kind: 'markdown', value: 'doc' })).toEqual({ content: 'doc', filetype: 'markdown' })\n    })\n  })\n\n  describe('hasProvider', () => {\n    it('should check provider for document', async () => {\n      let res = await helper.doAction('hasProvider', 'definition')\n      expect(res).toBe(false)\n      await nvim.command(`edit +setl\\\\ buftype=nofile foo`)\n      res = await handler.hasProvider('formatOnType')\n      expect(res).toBe(false)\n    })\n  })\n\n  describe('getIcon', () => {\n    it('should get icon', () => {\n      helper.updateConfiguration('suggest.completionItemKindLabels', {\n        default: 'd'\n      })\n      let res = handler.getIcon(SymbolKind.Array)\n      expect(res).toBeDefined()\n      res = handler.getIcon('a' as any)\n      expect(res.text).toBe('d')\n    })\n  })\n\n  describe('commands', () => {\n    it('should open url', async () => {\n      let fn = jest.fn()\n      let spy = jest.spyOn(nvim, 'call').mockImplementation(() => {\n        fn()\n        return null\n      })\n      await commands.executeCommand('vscode.open', 'http://www.example.com')\n      spy.mockRestore()\n      expect(fn).toHaveBeenCalled()\n    })\n\n    it('should restart', async () => {\n      let fn = jest.fn()\n      let spy = jest.spyOn(nvim, 'command').mockImplementation(() => {\n        fn()\n        return null\n      })\n      await commands.executeCommand('workbench.action.reloadWindow')\n      spy.mockRestore()\n      expect(fn).toHaveBeenCalled()\n    })\n  })\n\n  describe('checkProvider', () => {\n    it('should throw error when provider not found', async () => {\n      let doc = await helper.createDocument()\n      let err\n      try {\n        handler.checkProvider(ProviderName.Definition, doc.textDocument)\n      } catch (e) {\n        err = e\n      }\n      expect(err).toBeDefined()\n    })\n  })\n\n  describe('withRequestToken', () => {\n    it('should cancel previous request when called again', async () => {\n      let cancelled = false\n      let p = handler.withRequestToken('test', token => {\n        return new Promise(s => {\n          token.onCancellationRequested(() => {\n            cancelled = true\n            clearTimeout(timer)\n            s(undefined)\n          })\n          let timer = setTimeout(() => {\n            s(undefined)\n          }, 3000)\n        })\n      }, false)\n      setTimeout(async () => {\n        await handler.withRequestToken('test', () => {\n          return Promise.resolve(undefined)\n        }, false)\n      }, 50)\n      await p\n      expect(cancelled).toBe(true)\n    })\n\n    it('should cancel request on insert start', async () => {\n      let cancelled = false\n      let p = handler.withRequestToken('test', token => {\n        return new Promise(s => {\n          token.onCancellationRequested(() => {\n            cancelled = true\n            clearTimeout(timer)\n            s(undefined)\n          })\n          let timer = setTimeout(() => {\n            s(undefined)\n          }, 3000)\n        })\n      }, false)\n      await nvim.input('i')\n      await p\n      expect(cancelled).toBe(true)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/inlayHint.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationToken, CancellationTokenSource, Disposable, InlayHint, InlayHintKind, Position, Range, TextEdit } from 'vscode-languageserver-protocol'\nimport commands from '../../commands'\nimport InlayHintHandler from '../../handler/inlayHint/index'\nimport languages from '../../languages'\nimport { InlayHintWithProvider, isInlayHint, isValidInlayHint, sameHint } from '../../provider/inlayHintManager'\nimport { disposeAll } from '../../util'\nimport { CancellationError } from '../../util/errors'\nimport workspace from '../../workspace'\nimport helper, { createTmpFile } from '../helper'\n\nlet nvim: Neovim\nlet handler: InlayHintHandler\nlet disposables: Disposable[] = []\nlet ns: number\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  handler = helper.plugin.getHandler().inlayHintHandler\n  ns = await nvim.createNamespace('coc-inlayHint')\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n})\n\nasync function registerProvider(content: string): Promise<Disposable> {\n  let doc = await workspace.document\n  let disposable = languages.registerInlayHintsProvider([{ language: '*' }], {\n    provideInlayHints: (document, range) => {\n      let content = document.getText(range)\n      let lines = content.split(/\\r?\\n/)\n      let hints: InlayHint[] = []\n      for (let i = 0; i < lines.length; i++) {\n        let line = lines[i]\n        if (!line.length) continue\n        let parts = line.split(/\\s+/)\n        let kind: InlayHintKind = i == 0 ? InlayHintKind.Type : InlayHintKind.Parameter\n        hints.push(...parts.map(s => InlayHint.create(Position.create(range.start.line + i, line.length), s, kind)))\n      }\n      return hints\n    }\n  })\n  await helper.wait(10)\n  await doc.buffer.setLines(content.split(/\\n/), { start: 0, end: -1 })\n  await doc.synchronize()\n  return disposable\n}\n\nasync function waitRefresh(bufnr: number) {\n  let buf = handler.getItem(bufnr)\n  return new Promise<void>((resolve, reject) => {\n    let timer = setTimeout(() => {\n      reject(new Error('not refresh after 1s'))\n    }, 1000)\n    buf.onDidRefresh(() => {\n      clearTimeout(timer)\n      resolve()\n    })\n  })\n}\n\ndescribe('InlayHint', () => {\n  describe('utils', () => {\n    it('should check same hint', () => {\n      let hint = InlayHint.create(Position.create(0, 0), 'foo')\n      expect(sameHint(hint, InlayHint.create(Position.create(0, 0), 'bar'))).toBe(false)\n      expect(sameHint(hint, InlayHint.create(Position.create(0, 0), [{ value: 'foo' }]))).toBe(true)\n    })\n\n    it('should check valid hint', () => {\n      let hint = InlayHint.create(Position.create(0, 0), 'foo')\n      expect(isValidInlayHint(hint, Range.create(0, 0, 1, 0))).toBe(true)\n      expect(isValidInlayHint(InlayHint.create(Position.create(0, 0), ''), Range.create(0, 0, 1, 0))).toBe(false)\n      expect(isValidInlayHint(InlayHint.create(Position.create(3, 0), 'foo'), Range.create(0, 0, 1, 0))).toBe(false)\n      expect(isValidInlayHint({ label: 'f' } as any, Range.create(0, 0, 1, 0))).toBe(false)\n    })\n\n    it('should check inlayHint instance', async () => {\n      expect(isInlayHint(null)).toBe(false)\n      let position = Position.create(0, 0)\n      expect(isInlayHint({ position, label: null })).toBe(false)\n      expect(isInlayHint({ position, label: [{ value: '' }] })).toBe(true)\n    })\n  })\n\n  describe('provideInlayHints', () => {\n    // not fail like VSCode\n    it('should not throw when failed', async () => {\n      disposables.push(languages.registerInlayHintsProvider([{ language: '*' }], {\n        provideInlayHints: () => {\n          return Promise.reject(new Error('Test failure'))\n        }\n      }))\n      let doc = await workspace.document\n      let tokenSource = new CancellationTokenSource()\n      await languages.provideInlayHints(doc.textDocument, Range.create(0, 0, 1, 0), tokenSource.token)\n    })\n\n    it('should merge provider results', async () => {\n      disposables.push(languages.registerInlayHintsProvider([{ language: '*' }], {\n        provideInlayHints: () => {\n          return [InlayHint.create(Position.create(0, 0), 'foo')]\n        }\n      }))\n      disposables.push(languages.registerInlayHintsProvider([{ language: '*' }], {\n        provideInlayHints: () => {\n          return [\n            InlayHint.create(Position.create(0, 0), 'foo'),\n            InlayHint.create(Position.create(1, 0), 'bar'),\n            InlayHint.create(Position.create(5, 0), 'bad')]\n        }\n      }))\n      disposables.push(languages.registerInlayHintsProvider([{ language: '*' }], {\n        provideInlayHints: () => {\n          return null\n        }\n      }))\n      await helper.wait(10)\n      let doc = await workspace.document\n      let tokenSource = new CancellationTokenSource()\n      let res = await languages.provideInlayHints(doc.textDocument, Range.create(0, 0, 3, 0), tokenSource.token)\n      expect(res.length).toBe(2)\n    })\n\n    it('should not throw when provider return null', async () => {\n      disposables.push(languages.registerInlayHintsProvider([{ language: '*' }], {\n        provideInlayHints: () => {\n          throw new CancellationError()\n        }\n      }))\n      let doc = await workspace.document\n      let item = handler.getItem(doc.bufnr)\n      item.clearCache()\n      await item.renderRange([0, 1], CancellationToken.Cancelled)\n    })\n\n    it('should resolve inlay hint', async () => {\n      disposables.push(languages.registerInlayHintsProvider([{ language: '*' }], {\n        provideInlayHints: () => {\n          return [InlayHint.create(Position.create(0, 0), 'foo')]\n        },\n        resolveInlayHint: hint => {\n          hint.tooltip = 'tooltip'\n          return hint\n        }\n      }))\n      await helper.wait(10)\n      let doc = await workspace.document\n      let tokenSource = new CancellationTokenSource()\n      let res = await languages.provideInlayHints(doc.textDocument, Range.create(0, 0, 1, 0), tokenSource.token)\n      let resolved = await languages.resolveInlayHint(res[0], tokenSource.token)\n      expect(resolved.tooltip).toBe('tooltip')\n      resolved = await languages.resolveInlayHint(resolved, tokenSource.token)\n      expect(resolved.tooltip).toBe('tooltip')\n    })\n\n    it('should not resolve when cancelled', async () => {\n      disposables.push(languages.registerInlayHintsProvider([{ language: '*' }], {\n        provideInlayHints: () => {\n          return [InlayHint.create(Position.create(0, 0), 'foo')]\n        },\n        resolveInlayHint: (hint, token) => {\n          return new Promise(resolve => {\n            token.onCancellationRequested(() => {\n              clearTimeout(timer)\n              resolve(null)\n            })\n            let timer = setTimeout(() => {\n              resolve(Object.assign({}, hint, { tooltip: 'tooltip' }))\n            }, 200)\n          })\n        }\n      }))\n      await helper.wait(10)\n      let doc = await workspace.document\n      let tokenSource = new CancellationTokenSource()\n      let res = await languages.provideInlayHints(doc.textDocument, Range.create(0, 0, 1, 0), tokenSource.token)\n      let p = languages.resolveInlayHint(res[0], tokenSource.token)\n      tokenSource.cancel()\n      let resolved = await p\n      expect(resolved.tooltip).toBeUndefined()\n    })\n  })\n\n  describe('env & options', () => {\n    it('should not enabled when disabled by configuration', async () => {\n      helper.updateConfiguration('inlayHint.filetypes', [], disposables)\n      let doc = await workspace.document\n      let item = handler.getItem(doc.bufnr)\n      item.clearVirtualText()\n      expect(item.enabled).toBe(false)\n      helper.updateConfiguration('inlayHint.filetypes', ['dos'], disposables)\n      doc = await helper.createDocument()\n      item = handler.getItem(doc.bufnr)\n      expect(item.enabled).toBe(false)\n    })\n  })\n\n  describe('configuration', () => {\n    it('should refresh on insert mode', async () => {\n      helper.updateConfiguration('inlayHint.refreshOnInsertMode', true, disposables)\n      let doc = await helper.createDocument()\n      let disposable = await registerProvider('foo\\nbar')\n      disposables.push(disposable)\n      await nvim.input('i')\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'baz\\n')])\n      await waitRefresh(doc.bufnr)\n      let markers = await doc.buffer.getExtMarks(ns, 0, -1, { details: true })\n      let obj = markers[0][3].virt_text\n      expect(obj).toEqual([['baz', 'CocInlayHintType']])\n      expect(markers[1][3].virt_text).toEqual([['foo', 'CocInlayHintParameter']])\n    })\n\n    it('should disable parameter inlayHint', async () => {\n      helper.updateConfiguration('inlayHint.enableParameter', false, disposables)\n      let doc = await helper.createDocument()\n      let disposable = await registerProvider('foo\\nbar')\n      disposables.push(disposable)\n      await waitRefresh(doc.bufnr)\n      let markers = await doc.buffer.getExtMarks(ns, 0, -1, { details: true })\n      expect(markers.length).toBe(1)\n    })\n\n    it('should enable & disable inlayHint', async () => {\n      let doc = await helper.createDocument()\n      let disposable = await registerProvider('foo\\nbar')\n      disposables.push(disposable)\n      await waitRefresh(doc.bufnr)\n      helper.updateConfiguration('inlayHint.enable', false)\n      let markers = await doc.buffer.getExtMarks(ns, 0, -1, { details: true })\n      expect(markers.length).toBe(0)\n      helper.updateConfiguration('inlayHint.enable', true)\n    })\n\n    it('should change position to eol', async () => {\n      helper.updateConfiguration('inlayHint.position', 'eol', disposables)\n      let doc = await helper.createDocument()\n      let disposable = await registerProvider('foo\\nbar')\n      disposables.push(disposable)\n      await waitRefresh(doc.bufnr)\n      let markers = await doc.buffer.getExtMarks(ns, 0, -1, { details: true })\n      expect(markers.length).toBe(2)\n      for (const m of markers) {\n        let detail = m[3]\n        expect(detail['virt_text_pos']).toBe('eol')\n      }\n    })\n\n    it('should truncate hint label when exceeding maximumLength', async () => {\n      helper.updateConfiguration('inlayHint.maximumLength', 13, disposables)\n      let doc = await helper.createDocument()\n      let disposable = languages.registerInlayHintsProvider([{ language: '*' }], {\n        provideInlayHints: () => {\n          return [\n            InlayHint.create(Position.create(0, 0), 'firstLabel', InlayHintKind.Type),\n            InlayHint.create(Position.create(0, 3), 'secondLabel', InlayHintKind.Type),\n          ]\n        }\n      })\n      disposables.push(disposable)\n      await doc.buffer.setLines(['foo'], { start: 0, end: -1 })\n      await doc.synchronize()\n      await waitRefresh(doc.bufnr)\n      let markers = await doc.buffer.getExtMarks(ns, 0, -1, { details: true })\n      expect(markers.length).toBe(2)\n      let first = markers[0][3].virt_text\n      expect(first).toEqual([['firstLabel', 'CocInlayHintType']])\n      let second = markers[1][3].virt_text\n      expect(second).toEqual([['sec…', 'CocInlayHintType']])\n    })\n\n    it('should not truncate hint label when maximumLength is 0', async () => {\n      helper.updateConfiguration('inlayHint.maximumLength', 0, disposables)\n      let doc = await helper.createDocument()\n      let disposable = languages.registerInlayHintsProvider([{ language: '*' }], {\n        provideInlayHints: () => {\n          return [\n            InlayHint.create(Position.create(0, 0), 'firstLabel', InlayHintKind.Type),\n            InlayHint.create(Position.create(0, 3), 'secondLabel', InlayHintKind.Type),\n          ]\n        }\n      })\n      disposables.push(disposable)\n      await doc.buffer.setLines(['foo'], { start: 0, end: -1 })\n      await doc.synchronize()\n      await waitRefresh(doc.bufnr)\n      let markers = await doc.buffer.getExtMarks(ns, 0, -1, { details: true })\n      expect(markers.length).toBe(2)\n      let first = markers[0][3].virt_text\n      expect(first).toEqual([['firstLabel', 'CocInlayHintType']])\n      let second = markers[1][3].virt_text\n      expect(second).toEqual([['secondLabel', 'CocInlayHintType']])\n    })\n  })\n\n  describe('inlayHint setState', () => {\n    it('should not throw when buffer not exists', async () => {\n      handler.setState('toggle', 9)\n      await commands.executeCommand('document.toggleInlayHint', 9)\n    })\n\n    it('should show message when inlayHint not supported', async () => {\n      let doc = await workspace.document\n      handler.setState('toggle', doc.bufnr)\n      let cmdline = await helper.getCmdline()\n      expect(cmdline).toMatch(/not\\sfound/)\n    })\n\n    it('should show message when not enabled', async () => {\n      helper.updateConfiguration('inlayHint.filetypes', [], disposables)\n      let doc = await helper.createDocument()\n      let disposable = await registerProvider('')\n      disposables.push(disposable)\n      handler.setState('toggle', doc.bufnr)\n      let cmdline = await helper.getCmdline()\n      expect(cmdline).toMatch(/not\\senabled/)\n    })\n\n    it('should toggle inlayHints', async () => {\n      let doc = await helper.createDocument()\n      let disposable = await registerProvider('foo\\nbar')\n      disposables.push(disposable)\n      handler.setState('toggle', doc.bufnr)\n      handler.setState('toggle', doc.bufnr)\n      await helper.waitValue(async () => {\n        let markers = await doc.buffer.getExtMarks(ns, 0, -1, { details: true })\n        return markers.length\n      }, 2)\n    })\n\n    it('should enable & disable inlayHint', async () => {\n      let doc = await helper.createDocument()\n      let disposable = await registerProvider('foo\\nbar')\n      disposables.push(disposable)\n      await commands.executeCommand('document.disableInlayHint')\n      await commands.executeCommand('document.enableInlayHint')\n      let item = handler.getItem(doc.bufnr)\n      expect(item.enabled).toBe(true)\n    })\n  })\n\n  describe('render()', () => {\n    it('should refresh on vim mode', async () => {\n      let doc = await workspace.document\n      await nvim.setLine('foo bar')\n      let item = handler.getItem(doc.bufnr)\n      let r = Range.create(0, 0, 1, 0)\n      item.setVirtualText(r, [])\n      let hint: InlayHintWithProvider = {\n        label: 'string',\n        position: Position.create(0, 0),\n        providerId: ''\n      }\n      let paddingHint: InlayHintWithProvider = {\n        label: 'string',\n        position: Position.create(0, 3),\n        providerId: '',\n        paddingLeft: true,\n        paddingRight: true\n      }\n      item.setVirtualText(r, [hint, paddingHint])\n      await helper.waitValue(async () => {\n        let markers = await doc.buffer.getExtMarks(ns, 0, -1, { details: true })\n        return markers.length\n      }, 2)\n    })\n\n    it('should not refresh when languageId not match', async () => {\n      let doc = await workspace.document\n      disposables.push(languages.registerInlayHintsProvider([{ language: 'javascript' }], {\n        provideInlayHints: () => {\n          let hint = InlayHint.create(Position.create(0, 0), 'foo')\n          return [hint]\n        }\n      }))\n      await nvim.setLine('foo')\n      await doc.synchronize()\n      await helper.wait(30)\n      let markers = await doc.buffer.getExtMarks(ns, 0, -1, { details: true })\n      expect(markers.length).toBe(0)\n    })\n\n    it('should refresh on text change', async () => {\n      let buf = await nvim.buffer\n      let disposable = await registerProvider('foo')\n      disposables.push(disposable)\n      await waitRefresh(buf.id)\n      await buf.setLines(['a', 'b', 'c'], { start: 0, end: -1 })\n      await waitRefresh(buf.id)\n      let markers = await buf.getExtMarks(ns, 0, -1, { details: true })\n      expect(markers.length).toBe(3)\n      let item = handler.getItem(buf.id)\n      await item.render()\n      expect(item.current.length).toBe(3)\n    })\n\n    it('should refresh on insert leave', async () => {\n      let doc = await helper.createDocument()\n      let buf = doc.buffer\n      let disposable = await registerProvider('foo')\n      disposables.push(disposable)\n      await nvim.input('i')\n      await helper.wait(10)\n      await buf.setLines(['a', 'b', 'c'], { start: 0, end: -1 })\n      await helper.wait(30)\n      let markers = await buf.getExtMarks(ns, 0, -1, { details: true })\n      expect(markers.length).toBe(0)\n      await nvim.input('<esc>')\n      await waitRefresh(doc.bufnr)\n      markers = await buf.getExtMarks(ns, 0, -1, { details: true })\n      expect(markers.length).toBe(3)\n    })\n\n    it('should refresh on provider dispose', async () => {\n      let buf = await nvim.buffer\n      let disposable = await registerProvider('foo bar')\n      await waitRefresh(buf.id)\n      disposable.dispose()\n      let markers = await buf.getExtMarks(ns, 0, -1, { details: true })\n      expect(markers.length).toBe(0)\n      let item = handler.getItem(buf.id)\n      expect(item.current.length).toBe(0)\n      await item.render()\n      expect(item.current.length).toBe(0)\n    })\n\n    it('should refresh on scroll', async () => {\n      let arr = new Array(workspace.env.lines * 5)\n      let content = arr.fill('foo').join('\\n')\n      let buf = await nvim.buffer\n      let disposable = await registerProvider(content)\n      disposables.push(disposable)\n      await waitRefresh(buf.id)\n      let item = handler.getItem(buf.id)\n      item.clearVirtualText()\n      item.clearCache()\n      await nvim.command('normal! G')\n      await waitRefresh(buf.id)\n      let markers = await buf.getExtMarks(ns, 0, -1, { details: true })\n      let len = markers.length\n      await nvim.command('normal! gg')\n      await waitRefresh(buf.id)\n      await nvim.command('normal! G')\n      markers = await buf.getExtMarks(ns, 0, -1, { details: true })\n      expect(markers.length).toBeGreaterThan(len)\n    })\n\n    it('should cancel previous render', async () => {\n      let buf = await nvim.buffer\n      let disposable = await registerProvider('foo')\n      disposables.push(disposable)\n      await waitRefresh(buf.id)\n      let item = handler.getItem(buf.id)\n      await item.render()\n      await item.render()\n      expect(item.current.length).toBe(1)\n    })\n\n    it('should resend request on CancellationError', async () => {\n      let called = 0\n      let disposable = languages.registerInlayHintsProvider([{ language: 'vim' }], {\n        provideInlayHints: () => {\n          called++\n          if (called == 1) {\n            throw new CancellationError()\n          }\n          return []\n        }\n      })\n      disposables.push(disposable)\n      await helper.wait(10)\n      let filepath = await createTmpFile('a\\n\\b\\nc\\n', disposables)\n      await helper.createDocument(filepath)\n      await nvim.command('setfiletype vim')\n      await helper.waitValue(() => called, 2)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/inline.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { FormattingOptions, InlineCompletionItem, Position, Range, TextEdit } from 'vscode-languageserver-types'\nimport commands from '../../commands'\nimport sources from '../../completion/sources'\nimport { CompleteOption, CompleteResult, ExtendedCompleteItem } from '../../completion/types'\nimport events from '../../events'\nimport InlineCompletion, { checkInsertedAtBeginning, formatInsertText, getInserted, getInsertText, getPumInserted, InlineSession } from '../../handler/inline'\nimport languages from '../../languages'\nimport { Disposable } from '../../util/protocol'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet inlineCompletion: InlineCompletion\nlet disposables: Disposable[] = []\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  inlineCompletion = helper.plugin.handler.inlineCompletion\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\ndescribe('InlineCompletion', () => {\n  afterEach(async () => {\n    jest.clearAllMocks()\n    inlineCompletion['_inserted'] = undefined\n    await helper.reset()\n    disposables.forEach(d => d.dispose())\n    disposables = []\n    if (inlineCompletion.session) {\n      inlineCompletion.cancel()\n    }\n  })\n\n  function mockInlineInsert(returnValue: boolean): void {\n    // Mock nvim calls\n    let fn = nvim.call\n    nvim.call = jest.fn().mockImplementation((method, ...args) => {\n      if (method === 'coc#inline#_insert') return Promise.resolve(returnValue)\n      if (method === 'coc#inline#clear') return Promise.resolve()\n      return fn.apply(nvim, [method, ...args] as any)\n    })\n  }\n\n  describe('events', () => {\n    it('should trigger on document change', async () => {\n      helper.updateConfiguration('inline.autoTrigger', true, disposables)\n      await nvim.command('startinsert')\n      let doc = await helper.createDocument()\n      let mockProvider = jest.fn()\n      let providerDisposable = languages.registerInlineCompletionItemProvider(\n        [{ language: '*' }],\n        { provideInlineCompletionItems: mockProvider }\n      )\n      disposables.push(providerDisposable)\n      const spy = jest.spyOn(inlineCompletion, 'trigger')\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'test')])\n      expect(spy).toHaveBeenCalledTimes(1)\n    })\n\n    it('should cancel on buffer unload', async () => {\n      let doc = await workspace.document\n      const item: InlineCompletionItem = {\n        insertText: 'completion text',\n        range: Range.create(0, 5, 0, 5)\n      }\n      inlineCompletion['bufnr'] = doc.bufnr\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 5), [item])\n      const spy = jest.spyOn(inlineCompletion, 'cancel')\n      await nvim.command('bwipeout!')\n      workspace.documentsManager.detachBuffer(doc.bufnr)\n      expect(spy).toHaveBeenCalledTimes(1)\n    })\n\n    it('should not cancel when mode changed from i to ic', async () => {\n      let doc = await workspace.document\n      const item: InlineCompletionItem = {\n        insertText: 'completion text',\n        range: Range.create(0, 5, 0, 5)\n      }\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 5), [item])\n      const spy = jest.spyOn(inlineCompletion, 'cancel')\n      await events.fire('ModeChanged', [{ old_mode: 'i', new_mode: 'ic' }])\n      expect(spy).not.toHaveBeenCalled()\n    })\n\n    it('should trigger on pum navigate', async () => {\n      let doc = await workspace.document\n      let providerDisposable = languages.registerInlineCompletionItemProvider(\n        [{ language: '*' }],\n        {\n          provideInlineCompletionItems: () => {\n            return Promise.resolve([{ insertText: 'bar()' }])\n          }\n        }\n      )\n      disposables.push(providerDisposable)\n      disposables.push(sources.createSource({\n        name: 'test',\n        doComplete: (_opt: CompleteOption): Promise<CompleteResult<ExtendedCompleteItem>> => new Promise(resolve => {\n          resolve({ items: [{ word: 'foo' }, { word: 'bar' }] })\n        })\n      }))\n      let mode = await nvim.mode\n      if (mode.mode !== 'i') {\n        await nvim.command('startinsert')\n      }\n      nvim.call('coc#start', { source: 'test' }, true)\n      await helper.waitPopup()\n      await nvim.call('coc#pum#_navigate', [1, 1])\n      await helper.waitFor('coc#inline#visible', [], 1)\n      await inlineCompletion.accept(doc.bufnr)\n      let line = await nvim.line\n      expect(line).toBe('bar()')\n    })\n\n    it('should accept snippet inlineCompletion on pum navigate', async () => {\n      let doc = await workspace.document\n      // Set up a line to work with\n      await nvim.setLine('prefix ')\n      await doc.patchChange()\n      // Register inline completion provider that returns snippet items\n      let providerDisposable = languages.registerInlineCompletionItemProvider(\n        [{ language: '*' }],\n        {\n          provideInlineCompletionItems: () => {\n            return Promise.resolve([{\n              insertText: {\n                value: 'snippet ${1:param1} ${2:param2}',\n                kind: 'snippet'\n              }\n            }])\n          }\n        }\n      )\n      disposables.push(providerDisposable)\n      // Create a completion source\n      disposables.push(sources.createSource({\n        name: 'snippet-test',\n        doComplete: (_opt: CompleteOption): Promise<CompleteResult<ExtendedCompleteItem>> => new Promise(resolve => {\n          resolve({ items: [{ word: 'snip' }, { word: 'snippet' }] })\n        })\n      }))\n      // Start insert mode if not already\n      let mode = await nvim.mode\n      if (mode.mode !== 'i') {\n        await nvim.command('startinsert')\n      }\n      // Move cursor to end of line\n      await nvim.call('cursor', [1, 8]) // After \"prefix \"\n      // Start completion\n      nvim.call('coc#start', { source: 'snippet-test' }, true)\n      await helper.waitPopup()\n      // Navigate in popup to trigger inline completion\n      await nvim.call('coc#pum#_navigate', [1, 1])\n      await helper.waitFor('coc#inline#visible', [], 1)\n      // Spy on executeCommand to check if snippet command is executed\n      const executeCommandSpy = jest.spyOn(commands, 'executeCommand')\n      // Accept the completion\n      let res = await inlineCompletion.accept(doc.bufnr)\n      // Check result\n      expect(res).toBe(true)\n      expect(inlineCompletion.session).toBeUndefined() // Session should be cleared\n      expect(executeCommandSpy).toHaveBeenCalledWith(\n        'editor.action.insertSnippet',\n        expect.objectContaining({\n          range: expect.any(Object),\n          newText: ' ${1:param1} ${2:param2}'\n        })\n      )\n      // Cleanup\n      executeCommandSpy.mockRestore()\n      await inlineCompletion.accept(doc.bufnr)\n      let line = await nvim.line\n      expect(line).toBe('prefix snippet param1 param2')\n    })\n\n    it('should adjust range based on _inserted in insertVtext', async () => {\n      let doc = await workspace.document\n      // Set up document with \"prefix in\" where \"in\" is what would be inserted by pum\n      await nvim.setLine('prefix in')\n      await doc.patchChange()\n      // Create a completion item with range covering \"in\" and insertText that extends it\n      const item: InlineCompletionItem = {\n        insertText: 'inserted text',\n        range: Range.create(0, 7, 0, 7)\n      }\n      // Create session with cursor at end of \"in\"\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 9), [item])\n      // Set _inserted to simulate pum insertion\n      inlineCompletion['_inserted'] = 'in'\n      // Mock inline insert\n      mockInlineInsert(true)\n      // Call insertVtext\n      await inlineCompletion.insertVtext(item)\n      // // Verify that vtext starts after \"in\"\n      expect(inlineCompletion.session.vtext).toBe('serted text')\n      // Check that the range was adjusted in the call to coc#inline#_insert\n      // The col should be 10 (byte index of position after \"in\" + 1)\n      expect(nvim.call).toHaveBeenCalledWith(\n        'coc#inline#_insert',\n        [doc.bufnr, 0, 10, ['serted text'], '']\n      )\n      await inlineCompletion.accept(doc.bufnr)\n      let line = await nvim.line\n      expect(line).toBe('prefix inserted text')\n    })\n  })\n\n  describe('insertVtext()', () => {\n    it('should insert virtual text successfully', async () => {\n      let doc = await workspace.document\n      await nvim.setLine('fooba')\n      await doc.patchChange()\n      const item: InlineCompletionItem = {\n        insertText: 'completion text',\n        range: Range.create(0, 5, 0, 5)\n      }\n      await inlineCompletion.insertVtext(undefined)\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 5), [item])\n      mockInlineInsert(true)\n      await inlineCompletion.insertVtext(item)\n      expect(nvim.call).toHaveBeenCalledWith(\n        'coc#inline#_insert',\n        [doc.bufnr, 0, 6, ['completion text'], '']\n      )\n      expect(inlineCompletion.session.vtext).toBe('completion text')\n    })\n\n    it('should show index when multiple items exist', async () => {\n      let doc = await workspace.document\n      const item1: InlineCompletionItem = { insertText: 'first' }\n      const item2: InlineCompletionItem = { insertText: 'second' }\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 0), [item1, item2])\n      mockInlineInsert(true)\n      await inlineCompletion.insertVtext(item1)\n      expect(nvim.call).toHaveBeenCalledWith(\n        'coc#inline#_insert',\n        [doc.bufnr, 0, 1, ['first'], '(1/2)']\n      )\n    })\n\n    it('should handle item with non-empty range', async () => {\n      let doc = await workspace.document\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'complete')])\n      const item: InlineCompletionItem = {\n        insertText: 'complete method()',\n        range: Range.create(0, 0, 0, 8) // Assume \"complete\" is already typed\n      }\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 8), [item])\n      mockInlineInsert(true)\n      await inlineCompletion.insertVtext(item)\n      expect(inlineCompletion.session.vtext).toBe(' method()')\n    })\n\n    it('should handle cursor in middle of completion range', async () => {\n      let doc = await workspace.document\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'compl()')])\n      const item: InlineCompletionItem = {\n        insertText: 'completeMethod()',\n        range: Range.create(0, 0, 0, 7) // \"compl()\"\n      }\n      // Cursor is at \"compl|()\"\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 5), [item])\n      mockInlineInsert(true)\n      await inlineCompletion.insertVtext(item)\n      expect(inlineCompletion.session.vtext).toBe('eteMethod')\n    })\n\n    it('should handle cursor at the end of completion range but text does not match', async () => {\n      let doc = await workspace.document\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'initialText')])\n      const item: InlineCompletionItem = {\n        insertText: 'initialTextReplacement',\n        range: Range.create(0, 0, 0, 11) // \"initialText\"\n      }\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 11), [item])\n      mockInlineInsert(true)\n      await inlineCompletion.insertVtext(item)\n      expect(inlineCompletion.session.vtext).toBe('Replacement')\n    })\n\n    it('should handle item range where text after cursor does not match end of insertText', async () => {\n      let doc = await workspace.document\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'prefixMismatchSuffix')])\n      const item: InlineCompletionItem = {\n        insertText: 'prefixReplacementSuffix',\n        range: Range.create(0, 0, 0, 20) // \"prefixMismatchSuffix\"\n      }\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 6), [item])\n      mockInlineInsert(true)\n      await inlineCompletion.insertVtext(item)\n      expect(inlineCompletion.session.vtext).toBe('ReplacementSuffix')\n    })\n\n    it('should clean up when insertion fails', async () => {\n      let doc = await workspace.document\n      const item: InlineCompletionItem = { insertText: 'text' }\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 5), [item])\n      mockInlineInsert(false)\n      await inlineCompletion.insertVtext(item)\n      expect(inlineCompletion.session).toBeUndefined()\n      let visible = await inlineCompletion.visible()\n      expect(visible).toBe(false)\n    })\n\n    it('should handle multiline completions', async () => {\n      let doc = await workspace.document\n      const item: InlineCompletionItem = {\n        insertText: 'line1\\nline2\\nline3',\n      }\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 0), [item])\n      mockInlineInsert(true)\n      await inlineCompletion.insertVtext(item)\n      expect(nvim.call).toHaveBeenCalledWith(\n        'coc#inline#_insert',\n        [doc.bufnr, 0, 1, 'line1\\nline2\\nline3'.split('\\n'), \"\"]\n      )\n      expect(inlineCompletion.session.vtext).toBe('line1\\nline2\\nline3')\n    })\n  })\n\n  describe('accept()', () => {\n    it('should not accept when no selected item', async () => {\n      let doc = await workspace.document\n      const item: InlineCompletionItem = {\n        insertText: 'bar',\n      }\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 3), [item], -1, 'bar')\n      let res = await helper.doAction('inlineAccept', doc.bufnr, 'all')\n      expect(res).toBe(false)\n    })\n\n    it('should accept completion and apply TextEdit', async () => {\n      let doc = await workspace.document\n      await nvim.setLine('foo')\n      await doc.patchChange()\n      const item: InlineCompletionItem = {\n        insertText: 'bar',\n      }\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 3), [item], 0, 'bar')\n      const applyEditsSpy = jest.spyOn(doc, 'applyEdits')\n      const moveToSpy = jest.spyOn(window, 'moveTo')\n      await inlineCompletion.accept(doc.bufnr)\n\n      expect(applyEditsSpy).toHaveBeenCalledWith(\n        [TextEdit.replace(Range.create(0, 3, 0, 3), 'bar')],\n        false,\n        false\n      )\n      expect(moveToSpy).toHaveBeenCalledWith(Position.create(0, 6)) // 'foo' + 'bar'\n      expect(inlineCompletion.session).toBeUndefined() // Session should be cleared\n      const content = await doc.buffer.lines\n      expect(content[0]).toBe('foobar')\n    })\n\n    it('should accept completion with a specific range', async () => {\n      let doc = await workspace.document\n      await nvim.setLine('prefixsuffix') // prefix|suffix\n      await doc.patchChange()\n      const item: InlineCompletionItem = {\n        insertText: 'replacement',\n        range: Range.create(0, 6, 0, 6) // Replacing nothing, just inserting at cursor\n      }\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 6), [item], 0, 'replacement')\n      const applyEditsSpy = jest.spyOn(doc, 'applyEdits')\n      const moveToSpy = jest.spyOn(window, 'moveTo')\n      await inlineCompletion.accept(doc.bufnr)\n      // The range in item is used for TextEdit.replace\n      expect(applyEditsSpy).toHaveBeenCalledWith(\n        [TextEdit.replace(Range.create(0, 6, 0, 6), 'replacement')],\n        false,\n        false\n      )\n      expect(moveToSpy).toHaveBeenCalledWith(Position.create(0, 17)) // prefixreplacement|suffix\n      const content = await doc.buffer.lines\n      expect(content[0]).toBe('prefixreplacementsuffix')\n    })\n\n    it('should accept snippet completion item', async () => {\n      let doc = await workspace.document\n      await nvim.setLine('before')\n      await doc.patchChange()\n      const snippetString = 'snippet ${1:one} then ${2:two}'\n      const item: InlineCompletionItem = {\n        insertText: {\n          kind: 'snippet',\n          value: snippetString\n        }\n      }\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 6), [item])\n      inlineCompletion.session.vtext = 'snippet one then two' // What vtext might show\n      let res = await inlineCompletion.accept(doc.bufnr)\n      expect(inlineCompletion.session).toBeUndefined()\n      expect(res).toBe(true)\n    })\n\n    it('should accept word as kind', async () => {\n      let doc = await workspace.document\n      await nvim.setLine('prefix ')\n      await doc.patchChange()\n      const item: InlineCompletionItem = {\n        insertText: 'firstWord secondWord'\n      }\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 7), [item])\n      inlineCompletion.session.vtext = 'firstWord secondWord'\n\n      // Mock isWord\n      const originalIsWord = doc.isWord\n      doc.isWord = jest.fn(char => /[a-zA-Z]/.test(char))\n      await inlineCompletion.accept(doc.bufnr, 'word')\n      expect(inlineCompletion.session).toBeUndefined()\n      const content = await doc.buffer.lines\n      expect(content[0]).toBe('prefix firstWord')\n      doc.isWord = originalIsWord // Restore original\n    })\n\n    it('should accept word as kind with no clear word boundary', async () => {\n      let doc = await workspace.document\n      await nvim.setLine('prefix')\n      await doc.patchChange()\n      const item: InlineCompletionItem = {\n        insertText: 'onlyword' // No spaces or punctuation\n      }\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 6), [item])\n      inlineCompletion.session.vtext = 'onlyword'\n\n      const originalIsWord = doc.isWord\n      doc.isWord = jest.fn(char => /[a-zA-Z]/.test(char))\n\n      const applyEditsSpy = jest.spyOn(doc, 'applyEdits')\n      await inlineCompletion.accept(doc.bufnr, 'word')\n\n      expect(applyEditsSpy).toHaveBeenCalledWith(\n        [TextEdit.replace(Range.create(0, 6, 0, 6), 'onlyword')],\n        false,\n        false\n      )\n      const content = await doc.buffer.lines\n      expect(content[0]).toBe('prefixonlyword')\n      doc.isWord = originalIsWord\n    })\n\n    it('should accept line as kind', async () => {\n      let doc = await workspace.document\n      await nvim.setLine('prefix ')\n      await doc.patchChange()\n      const item: InlineCompletionItem = {\n        insertText: 'firstLine\\nsecondLine'\n      }\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 7), [item])\n      inlineCompletion.session.vtext = 'firstLine\\nsecondLine'\n      await inlineCompletion.accept(doc.bufnr, 'line')\n      expect(inlineCompletion.session).toBeUndefined()\n      const content = await doc.buffer.lines\n      expect(content[0]).toBe('prefix firstLine')\n    })\n\n    it('should accept line as kind with single line insertText', async () => {\n      let doc = await workspace.document\n      await nvim.setLine('prefix ')\n      await doc.patchChange()\n      const item: InlineCompletionItem = {\n        insertText: 'singleLineText'\n      }\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 7), [item])\n      inlineCompletion.session.vtext = 'singleLineText'\n\n      const applyEditsSpy = jest.spyOn(doc, 'applyEdits')\n      await inlineCompletion.accept(doc.bufnr, 'line')\n\n      expect(applyEditsSpy).toHaveBeenCalledWith(\n        [TextEdit.replace(Range.create(0, 7, 0, 7), 'singleLineText')],\n        false,\n        false\n      )\n      const content = await doc.buffer.lines\n      expect(content[0]).toBe('prefix singleLineText')\n    })\n\n    it('should not throw when completion command throws error', async () => {\n      let doc = await workspace.document\n      await nvim.setLine('test')\n      await doc.patchChange()\n      const item: InlineCompletionItem = {\n        insertText: 'text',\n        command: { command: 'test.command', title: 'Test' }\n      }\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 4), [item])\n      inlineCompletion.session.vtext = 'text'\n      let res = await inlineCompletion.accept(doc.bufnr)\n      expect(inlineCompletion.session).toBeUndefined() // Session should still be cleared\n      expect(res).toBe(true)\n    })\n\n    it('should do nothing if bufnr does not match session bufnr', async () => {\n      let doc = await workspace.document\n      const item: InlineCompletionItem = { insertText: 'text' }\n      inlineCompletion.session = new InlineSession(doc.bufnr, Position.create(0, 0), [item])\n      inlineCompletion.session.vtext = 'text' // Simulate vtext is shown\n      let res = await inlineCompletion.accept(doc.bufnr + 1) // Different bufnr\n      expect(res).toBe(false)\n      expect(inlineCompletion.session).toBeDefined() // Session should not be cleared\n    })\n  })\n\n  describe('trigger()', () => {\n    let mockProvider: jest.Mock\n    let providerDisposable: Disposable\n\n    beforeEach(() => {\n      mockProvider = jest.fn()\n      providerDisposable = languages.registerInlineCompletionItemProvider(\n        [{ language: '*' }],\n        { provideInlineCompletionItems: mockProvider }\n      )\n      disposables.push(providerDisposable)\n      // Mock getCurrentState to simulate insert mode\n      jest.spyOn(helper.plugin.handler, 'getCurrentState').mockResolvedValue({\n        doc: workspace.getDocument(workspace.bufnr),\n        position: Position.create(0, 0),\n        mode: 'i',\n        winid: 1,\n      } as any)\n      mockInlineInsert(true) // Assume inline insert will succeed for trigger tests\n    })\n\n    afterEach(() => {\n      if (providerDisposable) providerDisposable.dispose()\n    })\n\n    it('should not trigger if no provider is registered for the document', async () => {\n      providerDisposable.dispose() // Unregister the provider\n      let doc = await workspace.document\n      await helper.doAction('inlineTrigger', doc.bufnr)\n      expect(mockProvider).not.toHaveBeenCalled()\n      expect(inlineCompletion.session).toBeUndefined()\n    })\n\n    it('should return false when not supported', async () => {\n      let doc = await workspace.document\n      let spy = jest.spyOn(workspace, 'has').mockReturnValue(false) // Simulate inline completion not supported\n      let res = await inlineCompletion.trigger(doc.bufnr)\n      expect(res).toBe(false)\n      expect(inlineCompletion.session).toBeUndefined()\n      expect(inlineCompletion.selected).toBeUndefined()\n      spy.mockRestore()\n    })\n\n    it('should not trigger if provider returns no items (autoTrigger: true)', async () => {\n      mockProvider.mockResolvedValue([])\n      const spy = jest.spyOn(window, 'showWarningMessage')\n      await commands.executeCommand('editor.action.triggerInlineCompletion', { autoTrigger: true })\n      expect(mockProvider).toHaveBeenCalled()\n      expect(inlineCompletion.session).toBeUndefined()\n      expect(spy).not.toHaveBeenCalled() // No warning for autoTrigger\n    })\n\n    it('should show warning if provider returns no items (autoTrigger: false)', async () => {\n      mockProvider.mockResolvedValue([])\n      let doc = await workspace.document\n      const spy = jest.spyOn(window, 'showWarningMessage')\n      await inlineCompletion.trigger(doc.bufnr, { autoTrigger: false })\n      expect(mockProvider).toHaveBeenCalled()\n      expect(inlineCompletion.session).toBeUndefined()\n      expect(spy).toHaveBeenCalledWith('No inline completion items from provider.')\n    })\n\n    it('should trigger and create session if provider returns items', async () => {\n      const item: InlineCompletionItem = { insertText: 'suggested' }\n      mockProvider.mockResolvedValue([item])\n      let doc = await workspace.document\n      await inlineCompletion.trigger(doc.bufnr)\n      expect(mockProvider).toHaveBeenCalled()\n      expect(inlineCompletion.session).toBeDefined()\n      expect(inlineCompletion.session.items).toEqual([item])\n      expect(inlineCompletion.session.selected).toEqual(item)\n    })\n\n    it('should filter items based on range', async () => {\n      const item1: InlineCompletionItem = { insertText: 'item1', range: Range.create(0, 0, 0, 1) } // Matches cursor at 0,0\n      const item2: InlineCompletionItem = { insertText: 'item2', range: Range.create(0, 1, 0, 2) } // Does not match cursor at 0,0\n      mockProvider.mockResolvedValue([item1, item2])\n      let doc = await workspace.document\n      await inlineCompletion.trigger(doc.bufnr)\n      expect(inlineCompletion.session).toBeDefined()\n      expect(inlineCompletion.session.items).toEqual([item1])\n    })\n\n    it('should not trigger if document changed and autoTrigger is false without sync', async () => {\n      const item: InlineCompletionItem = { insertText: 'suggested' }\n      mockProvider.mockResolvedValue([item])\n      let doc = await workspace.document\n      await nvim.call('setline', ['.', 'foobar'])\n      expect(doc.hasChanged).toBe(true)\n      const syncSpy = jest.spyOn(doc, 'synchronize')\n      await inlineCompletion.trigger(doc.bufnr, { autoTrigger: false })\n      expect(syncSpy).toHaveBeenCalled()\n      expect(inlineCompletion.session).toBeDefined() // Should still trigger after sync\n    })\n\n    it('should not trigger if token is cancelled before provider call', async () => {\n      mockProvider.mockResolvedValue([{ insertText: 'test' }])\n      let doc = await workspace.document\n      const triggerPromise = inlineCompletion.trigger(doc.bufnr, {}, 10) // With delay\n      await helper.doAction('inlineCancel')\n      await triggerPromise\n      expect(mockProvider).not.toHaveBeenCalled()\n      expect(inlineCompletion.session).toBeUndefined()\n    })\n\n    it('should not trigger if token is cancelled after provider call but before session creation', async () => {\n      const item: InlineCompletionItem = { insertText: 'suggested' }\n      mockProvider.mockImplementation(async () => {\n        inlineCompletion.cancel() // Cancel while provider is \"working\"\n        return [item]\n      })\n      let doc = await workspace.document\n      await inlineCompletion.trigger(doc.bufnr)\n      expect(mockProvider).toHaveBeenCalled()\n      expect(inlineCompletion.session).toBeUndefined()\n    })\n\n    it('should not trigger if current state bufnr does not match', async () => {\n      mockProvider.mockResolvedValue([{ insertText: 'test' }])\n      let prev = await helper.createDocument('foo')\n      let doc = await helper.createDocument('bar')\n      jest.spyOn(helper.plugin.handler, 'getCurrentState').mockResolvedValueOnce({\n        doc: prev,\n        position: Position.create(0, 0),\n        mode: 'i',\n        winid: 1,\n      } as any)\n      await inlineCompletion.trigger(doc.bufnr)\n      expect(mockProvider).not.toHaveBeenCalled() // Provider call is guarded by state check\n      expect(inlineCompletion.session).toBeUndefined()\n    })\n\n    it('should not trigger if current mode is not insert', async () => {\n      mockProvider.mockResolvedValue([{ insertText: 'test' }])\n      let doc = await workspace.document\n      jest.spyOn(helper.plugin.handler, 'getCurrentState').mockResolvedValueOnce({\n        doc: workspace.getDocument(doc.bufnr),\n        position: Position.create(0, 0),\n        mode: 'n', // Not insert mode\n        winid: 1,\n      } as any)\n      await inlineCompletion.trigger(doc.bufnr)\n      expect(mockProvider).not.toHaveBeenCalled()\n      expect(inlineCompletion.session).toBeUndefined()\n    })\n\n    it('should use specified provider if option.provider is given', async () => {\n      const specificProviderMock = jest.fn().mockResolvedValue([{ insertText: 'specific' }])\n      const specificProviderDisposable = languages.registerInlineCompletionItemProvider(\n        [{ language: '*' }],\n        {\n          provideInlineCompletionItems: specificProviderMock,\n          __extensionName: 'mySpecificProvider'\n        } as any,\n      )\n      disposables.push(specificProviderDisposable)\n\n      let doc = await workspace.document\n      await inlineCompletion.trigger(doc.bufnr, { provider: 'mySpecificProvider' })\n      expect(specificProviderMock).toHaveBeenCalled()\n      expect(mockProvider).not.toHaveBeenCalled() // Default provider should not be called\n      expect(inlineCompletion.session).toBeDefined()\n      expect(inlineCompletion.session.selected.insertText).toBe('specific')\n      specificProviderDisposable.dispose()\n    })\n  })\n\n  describe('next and prev', () => {\n    const bufnr = 1\n    const item1: InlineCompletionItem = { insertText: 'item1' }\n    const item2: InlineCompletionItem = { insertText: 'item2' }\n    const item3: InlineCompletionItem = { insertText: 'item3' }\n    let mockInsertVtext: jest.SpyInstance\n\n    const setupSession = (items: InlineCompletionItem[], initialIndex = 0, sessionBufnr = bufnr) => {\n      const session = new InlineSession(sessionBufnr, Position.create(0, 0), items)\n      session.index = initialIndex\n      inlineCompletion.session = session\n      // Simulate that a previous insertVtext call set this\n      if (items.length > 0 && session.selected) {\n        // To make vtextBufnr match, we need to simulate a successful insertVtext\n        inlineCompletion.session.vtext = session.selected.insertText as string\n      }\n      return session\n    }\n\n    beforeEach(() => {\n      // Spy on insertVtext to check if it's called correctly without running its full logic\n      mockInsertVtext = jest.spyOn(inlineCompletion, 'insertVtext').mockResolvedValue(undefined)\n      // Ensure vtextBufnr is reset or managed correctly per test\n      if (inlineCompletion.session) inlineCompletion.session.vtext = undefined\n    })\n\n    afterEach(() => {\n      mockInsertVtext.mockRestore()\n      inlineCompletion.session = undefined\n    })\n\n    describe('next()', () => {\n      it('should do nothing if no session exists', async () => {\n        inlineCompletion.session = undefined\n        await inlineCompletion.next(bufnr)\n        expect(mockInsertVtext).not.toHaveBeenCalled()\n      })\n\n      it('should do nothing if bufnr does not match session vtextBufnr', async () => {\n        setupSession([item1, item2])\n        inlineCompletion.session.vtext = undefined // Ensure vtextBufnr is -1\n        await inlineCompletion.next(bufnr)\n        expect(mockInsertVtext).not.toHaveBeenCalled()\n\n        setupSession([item1, item2], 0, bufnr) // vtextBufnr will be bufnr\n        await inlineCompletion.next(bufnr + 1) // Call with different bufnr\n        expect(mockInsertVtext).not.toHaveBeenCalled()\n      })\n\n      it('should do nothing if session has no items', async () => {\n        const session = setupSession([])\n        await inlineCompletion.next(bufnr)\n        expect(mockInsertVtext).not.toHaveBeenCalled()\n        expect(session.index).toBe(0)\n      })\n\n      it('should do nothing if session has only one item', async () => {\n        const session = setupSession([item1])\n        await inlineCompletion.next(bufnr)\n        expect(mockInsertVtext).not.toHaveBeenCalled()\n        expect(session.index).toBe(0)\n      })\n\n      it('should move to the next item and call insertVtext', async () => {\n        const session = setupSession([item1, item2, item3], 0)\n        await inlineCompletion.next(bufnr)\n        expect(session.index).toBe(1)\n        expect(mockInsertVtext).toHaveBeenCalledWith(item2)\n      })\n\n      it('should loop to the first item when at the last item', async () => {\n        const session = setupSession([item1, item2, item3], 2) // Start at last item\n        await helper.doAction('inlineNext', bufnr)\n        expect(session.index).toBe(0)\n        expect(mockInsertVtext).toHaveBeenCalledWith(item1)\n      })\n    })\n\n    describe('prev()', () => {\n      it('should do nothing if no session exists', async () => {\n        inlineCompletion.session = undefined\n        await inlineCompletion.prev(bufnr)\n        expect(mockInsertVtext).not.toHaveBeenCalled()\n      })\n\n      it('should do nothing if bufnr does not match session vtextBufnr', async () => {\n        setupSession([item1, item2])\n        inlineCompletion.session.vtext = undefined // Ensure vtextBufnr is -1\n        await inlineCompletion.prev(bufnr)\n        expect(mockInsertVtext).not.toHaveBeenCalled()\n\n        setupSession([item1, item2], 0, bufnr) // vtextBufnr will be bufnr\n        await inlineCompletion.prev(bufnr + 1) // Call with different bufnr\n        expect(mockInsertVtext).not.toHaveBeenCalled()\n      })\n\n      it('should do nothing if session has no items', async () => {\n        const session = setupSession([])\n        await inlineCompletion.prev(bufnr)\n        expect(mockInsertVtext).not.toHaveBeenCalled()\n        expect(session.index).toBe(0)\n      })\n\n      it('should do nothing if session has only one item', async () => {\n        const session = setupSession([item1])\n        await inlineCompletion.prev(bufnr)\n        expect(mockInsertVtext).not.toHaveBeenCalled()\n        expect(session.index).toBe(0)\n      })\n\n      it('should move to the previous item and call insertVtext', async () => {\n        const session = setupSession([item1, item2, item3], 1)\n        await helper.doAction('inlinePrev', bufnr)\n        expect(session.index).toBe(0)\n        expect(mockInsertVtext).toHaveBeenCalledWith(item1)\n      })\n\n      it('should loop to the last item when at the first item', async () => {\n        const session = setupSession([item1, item2, item3], 0) // Start at first item\n        await inlineCompletion.prev(bufnr)\n        expect(session.index).toBe(2)\n        expect(mockInsertVtext).toHaveBeenCalledWith(item3)\n      })\n    })\n  })\n\n  describe('commands', () => {\n    describe('document.checkInlineCompletion', () => {\n      let showWarningMessageSpy: jest.SpyInstance\n      let showInformationMessageSpy: jest.SpyInstance\n      let getDocumentSpy: jest.SpyInstance\n      let getProvidersSpy: jest.SpyInstance\n\n      beforeEach(() => {\n        showWarningMessageSpy = jest.spyOn(window, 'showWarningMessage').mockResolvedValue(undefined)\n        showInformationMessageSpy = jest.spyOn(window, 'showInformationMessage').mockResolvedValue(undefined)\n        getDocumentSpy = jest.spyOn(workspace, 'getDocument')\n        getProvidersSpy = jest.spyOn(languages.inlineCompletionItemManager, 'getProviders')\n      })\n\n      afterEach(() => {\n        jest.restoreAllMocks()\n      })\n\n      it('should show warning if inline completion is not supported', async () => {\n        jest.spyOn(workspace, 'has').mockReturnValue(false)\n        await commands.executeCommand('document.checkInlineCompletion')\n        expect(showWarningMessageSpy).toHaveBeenCalledWith(expect.stringContaining('Inline completion is not supported'))\n        expect(showInformationMessageSpy).not.toHaveBeenCalled()\n      })\n\n      it('should show warning if document is not found', async () => {\n        getDocumentSpy.mockReturnValue(null)\n        await commands.executeCommand('document.checkInlineCompletion')\n        expect(showWarningMessageSpy).toHaveBeenCalledWith(expect.stringContaining(`not attached`))\n        expect(showInformationMessageSpy).not.toHaveBeenCalled()\n      })\n\n      it('should show warning if document is not attached', async () => {\n        const mockDoc = { bufnr: 1, attached: false, textDocument: {} } as any\n        getDocumentSpy.mockReturnValue(mockDoc)\n        await commands.executeCommand('document.checkInlineCompletion')\n        expect(showWarningMessageSpy).toHaveBeenCalledWith(expect.stringContaining('not attached'))\n        expect(showInformationMessageSpy).not.toHaveBeenCalled()\n      })\n\n      it('should show warning when disabled by b:coc_inline_disable', async () => {\n        let doc = await workspace.document\n        await doc.buffer.setVar('coc_inline_disable', true)\n        await commands.executeCommand('document.checkInlineCompletion')\n        expect(showWarningMessageSpy).toHaveBeenCalledWith(expect.stringContaining('disabled'))\n        expect(showInformationMessageSpy).not.toHaveBeenCalled()\n        doc.buffer.deleteVar('coc_inline_disable')\n      })\n\n      it('should show warning if no providers are found', async () => {\n        const mockDoc = { bufnr: 1, attached: true, textDocument: {} } as any\n        getDocumentSpy.mockReturnValue(mockDoc)\n        getProvidersSpy.mockReturnValue([])\n        await commands.executeCommand('document.checkInlineCompletion')\n        expect(showWarningMessageSpy).toHaveBeenCalledWith(expect.stringContaining('provider not found'))\n        expect(showInformationMessageSpy).not.toHaveBeenCalled()\n      })\n\n      it('should show information message if providers are found', async () => {\n        const mockDoc = { bufnr: 1, attached: true, textDocument: {} } as any\n        getDocumentSpy.mockReturnValue(mockDoc)\n        const mockProvider1 = { provider: { __extensionName: 'providerOne' } } as any\n        const mockProvider2 = { provider: {} } as any // No __extensionName\n        getProvidersSpy.mockReturnValue([mockProvider1, mockProvider2])\n\n        await commands.executeCommand('document.checkInlineCompletion')\n\n        expect(showInformationMessageSpy).toHaveBeenCalledWith('Inline completion is supported by providerOne, unknown.')\n        expect(showWarningMessageSpy).not.toHaveBeenCalled()\n      })\n\n      it('should show information message with single provider', async () => {\n        const mockDoc = { bufnr: 1, attached: true, textDocument: {} } as any\n        getDocumentSpy.mockReturnValue(mockDoc)\n        const mockProvider = { provider: { __extensionName: 'myProvider' } } as any\n        getProvidersSpy.mockReturnValue([mockProvider])\n\n        await commands.executeCommand('document.checkInlineCompletion')\n\n        expect(showInformationMessageSpy).toHaveBeenCalledWith('Inline completion is supported by myProvider.')\n        expect(showWarningMessageSpy).not.toHaveBeenCalled()\n      })\n    })\n  })\n})\n\n// Tests for standalone functions\ndescribe('Utility functions', () => {\n  describe('formatInsertText', () => {\n    it('should format text with spaces', () => {\n      const text = 'line1\\n  line2'\n      const options: FormattingOptions = { tabSize: 2, insertSpaces: true }\n      const result = formatInsertText(text, options)\n      expect(result).toBe('line1\\n  line2')\n    })\n\n    it('should convert tabs to spaces', () => {\n      const text = 'line1\\n\\tline2'\n      const options: FormattingOptions = { tabSize: 2, insertSpaces: true }\n      const result = formatInsertText(text, options)\n      expect(result).toBe('line1\\n  line2')\n    })\n\n    it('should convert spaces to tabs', () => {\n      const text = 'line1\\n  line2'\n      const options: FormattingOptions = { tabSize: 2, insertSpaces: false }\n      const result = formatInsertText(text, options)\n      expect(result).toBe('line1\\n\\tline2')\n    })\n  })\n\n  describe('getPumInserted', () => {\n    it('should return empty string when current line matches synced line', async () => {\n      const doc = await workspace.document\n      await nvim.setLine('test line')\n      await doc.patchChange() // Synchronize to ensure lines match\n      const cursor = Position.create(0, 5)\n      const result = getPumInserted(doc, cursor)\n      expect(result).toBe('')\n    })\n\n    it('should return inserted text when current line differs from synced line', async () => {\n      const doc = await workspace.document\n      // Set the line in the buffer but don't sync document\n      await nvim.setLine('test inserted line')\n      // Mock the textDocument.lines to simulate a synced state that's different\n      const originalLines = doc.textDocument.lines\n      doc.textDocument.lines = ['test line']\n      const cursor = Position.create(0, 13) // Position after \"test inserted\"\n      const result = getPumInserted(doc, cursor)\n      // Restore original lines\n      doc.textDocument.lines = originalLines\n      expect(result).toBe(' inserted')\n    })\n\n    it('should return undefined when no valid insertion is detected', async () => {\n      const doc = await workspace.document\n      // Current line is completely different, not just an insertion\n      await nvim.setLine('completely different')\n      // Mock the textDocument.lines to simulate a synced state\n      const originalLines = doc.textDocument.lines\n      doc.textDocument.lines = ['original text']\n      const cursor = Position.create(0, 10)\n      const result = getPumInserted(doc, cursor)\n      // Restore original lines\n      doc.textDocument.lines = originalLines\n      expect(result).toBeUndefined()\n    })\n\n    it('should handle cursor at beginning of line', async () => {\n      const doc = await workspace.document\n      await nvim.setLine('prefix original')\n      const originalLines = doc.textDocument.lines\n      doc.textDocument.lines = ['original']\n      const cursor = Position.create(0, 7) // Position after \"prefix \"\n      const result = getPumInserted(doc, cursor)\n      doc.textDocument.lines = originalLines\n      expect(result).toBe('prefix ')\n    })\n\n    it('should handle cursor at end of line', async () => {\n      const doc = await workspace.document\n      await nvim.setLine('original suffix')\n      const originalLines = doc.textDocument.lines\n      doc.textDocument.lines = ['original']\n      const cursor = Position.create(0, 15) // End of \"original suffix\"\n      const result = getPumInserted(doc, cursor)\n      doc.textDocument.lines = originalLines\n      expect(result).toBe(' suffix')\n    })\n  })\n\n  describe('getInsertText', () => {\n    it('should handle plain text', () => {\n      const item: InlineCompletionItem = {\n        insertText: 'plain text'\n      }\n      const options: FormattingOptions = { tabSize: 2, insertSpaces: true }\n      const result = getInsertText(item, options)\n      expect(result).toBe('plain text')\n    })\n\n    it('should handle snippet text', () => {\n      const item: InlineCompletionItem = {\n        insertText: {\n          value: 'snippet ${1:text}',\n          kind: 'snippet'\n        },\n      }\n      const options: FormattingOptions = { tabSize: 2, insertSpaces: true }\n      const result = getInsertText(item, options)\n      expect(result).toBe('snippet text')\n    })\n  })\n\n  describe('getInserted', () => {\n    it('should return undefined when current string is shorter than synced string', () => {\n      const curr = 'foo'\n      const synced = 'foobar'\n      const character = 3\n      const result = getInserted(curr, synced, character)\n      expect(result).toBeUndefined()\n    })\n\n    it('should return undefined when text after cursor does not match end of synced string', () => {\n      const curr = 'fooXYZ'\n      const synced = 'foobar'\n      const character = 3\n      const result = getInserted(curr, synced, character)\n      expect(result).toBeUndefined()\n    })\n\n    it('should return undefined when beginning of current does not match beginning of synced', () => {\n      const curr = 'abcbar'\n      const synced = 'foobar'\n      const character = 3\n      const result = getInserted(curr, synced, character)\n      expect(result).toBeUndefined()\n    })\n\n    it('should identify simple insertion in the middle', () => {\n      const curr = 'fooinsertedbartexthere'\n      const synced = 'foobartexthere'\n      const character = 11 // Position after \"fooinserted\"\n      const result = getInserted(curr, synced, character)\n      expect(result).toEqual({ start: 3, text: 'inserted' })\n    })\n\n    it('should identify insertion at the end', () => {\n      const curr = 'foobarappended'\n      const synced = 'foobar'\n      const character = 14 // Position at the end of curr\n      const result = getInserted(curr, synced, character)\n      expect(result).toEqual({ start: 6, text: 'appended' })\n    })\n\n    it('should identify insertion at the beginning', () => {\n      const curr = 'prefixfoobar'\n      const synced = 'foobar'\n      const character = 6 // Position after \"prefix\"\n      const result = getInserted(curr, synced, character)\n      expect(result).toEqual({ start: 0, text: 'prefix' })\n    })\n\n    it('should handle insertion with special characters', () => {\n      const curr = 'foo\\t\\n🚀bar'\n      const synced = 'foobar'\n      const character = 7 // After special chars (note emoji is a single character)\n      const result = getInserted(curr, synced, character)\n      expect(result).toEqual({ start: 3, text: '\\t\\n🚀' })\n    })\n\n    it('should handle empty insertion', () => {\n      const curr = 'foobar'\n      const synced = 'foobar'\n      const character = 3 // Position in the middle, but no change\n      const result = getInserted(curr, synced, character)\n      expect(result).toEqual({ start: 3, text: '' })\n    })\n  })\n\n  describe('checkInsertedAtBeginning', () => {\n    it('should return true when item has no range and insertText starts with inserted string', () => {\n      const currentLine = 'some text'\n      const triggerCharacter = 4\n      const inserted = 'comp'\n      const item: InlineCompletionItem = {\n        insertText: 'completion'\n      }\n      const result = checkInsertedAtBeginning(currentLine, triggerCharacter, inserted, item)\n      expect(result).toBe(true)\n    })\n\n    it('should return false when item has no range and insertText does not start with inserted string', () => {\n      const currentLine = 'some text'\n      const triggerCharacter = 4\n      const inserted = 'diff'\n      const item: InlineCompletionItem = {\n        insertText: 'completion'\n      }\n      const result = checkInsertedAtBeginning(currentLine, triggerCharacter, inserted, item)\n      expect(result).toBe(false)\n    })\n\n    it('should return true when item has no range and snippet value starts with inserted string', () => {\n      const currentLine = 'some text'\n      const triggerCharacter = 4\n      const inserted = 'comp'\n      const item: InlineCompletionItem = {\n        insertText: {\n          value: 'completion ${1:param}',\n          kind: 'snippet'\n        }\n      }\n      const result = checkInsertedAtBeginning(currentLine, triggerCharacter, inserted, item)\n      expect(result).toBe(true)\n    })\n\n    it('should return false when item has no range and snippet value does not start with inserted string', () => {\n      const currentLine = 'some text'\n      const triggerCharacter = 4\n      const inserted = 'diff'\n      const item: InlineCompletionItem = {\n        insertText: {\n          value: 'completion ${1:param}',\n          kind: 'snippet'\n        }\n      }\n      const result = checkInsertedAtBeginning(currentLine, triggerCharacter, inserted, item)\n      expect(result).toBe(false)\n    })\n\n    it('should return true when item has range and current line portion matches start of insertText', () => {\n      const currentLine = 'prefix completion suffix'\n      const triggerCharacter = 10 // After \"prefix com\"\n      const inserted = 'com'\n      const item: InlineCompletionItem = {\n        insertText: 'completion',\n        range: Range.create(0, 7, 0, 16) // \"completion\"\n      }\n      const result = checkInsertedAtBeginning(currentLine, triggerCharacter, inserted, item)\n      expect(result).toBe(true)\n    })\n\n    it('should return false when item has range and current line portion does not match start of insertText', () => {\n      const currentLine = 'prefix different suffix'\n      const triggerCharacter = 10 // After \"prefix dif\"\n      const inserted = 'dif'\n      const item: InlineCompletionItem = {\n        insertText: 'completion',\n        range: Range.create(0, 7, 0, 16) // \"different\"\n      }\n      const result = checkInsertedAtBeginning(currentLine, triggerCharacter, inserted, item)\n      expect(result).toBe(false)\n    })\n\n    it('should return true when item has range and current line portion matches start of snippet value', () => {\n      const currentLine = 'prefix completion suffix'\n      const triggerCharacter = 10 // After \"prefix com\"\n      const inserted = 'com'\n      const item: InlineCompletionItem = {\n        insertText: {\n          value: 'completion ${1:param}',\n          kind: 'snippet'\n        },\n        range: Range.create(0, 7, 0, 16) // \"completion\"\n      }\n      const result = checkInsertedAtBeginning(currentLine, triggerCharacter, inserted, item)\n      expect(result).toBe(true)\n    })\n\n    it('should handle case with empty inserted string', () => {\n      const currentLine = 'prefix'\n      const triggerCharacter = 6\n      const inserted = ''\n      const item: InlineCompletionItem = {\n        insertText: 'completion'\n      }\n      const result = checkInsertedAtBeginning(currentLine, triggerCharacter, inserted, item)\n      expect(result).toBe(true) // Empty string is always at beginning\n    })\n\n    it('should handle special characters in inserted string', () => {\n      const currentLine = 'prefix\\t\\n🚀completion'\n      const triggerCharacter = 6 // After the emoji\n      const inserted = '\\t\\n🚀'\n      const item: InlineCompletionItem = {\n        insertText: '\\t\\n🚀suffix',\n        range: Range.create(0, 6, 0, 9)\n      }\n      const result = checkInsertedAtBeginning(currentLine, triggerCharacter, inserted, item)\n      expect(result).toBe(true)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/inlineCompletion.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationToken, CancellationTokenSource, Disposable, InlineCompletionContext, InlineCompletionItem, InlineCompletionTriggerKind } from 'vscode-languageserver-protocol'\nimport languages from '../../languages'\nimport { disposeAll } from '../../util'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nbeforeEach(() => {\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n})\n\nlet items: InlineCompletionItem[] = []\n\nfunction registerProvider(): void {\n  disposables.push(languages.registerInlineCompletionItemProvider(['*'], {\n    provideInlineCompletionItems: () => {\n      return Promise.resolve(items)\n    }\n  }))\n}\n\ndescribe('InlineCompletion', () => {\n  it('should provide completion items', async () => {\n    let doc = await workspace.document\n    let pos = await window.getCursorPosition()\n    let context: InlineCompletionContext = { triggerKind: InlineCompletionTriggerKind.Automatic }\n    let res = await languages.provideInlineCompletionItems(doc.textDocument, pos, context, CancellationToken.None)\n    expect(res).toEqual([])\n    registerProvider()\n    disposables.push(languages.registerInlineCompletionItemProvider(['*'], {\n      provideInlineCompletionItems: () => {\n        return Promise.resolve({ items: [InlineCompletionItem.create('foo')] })\n      }\n    }))\n    items = [InlineCompletionItem.create('bar')]\n    res = await languages.provideInlineCompletionItems(doc.textDocument, pos, context, CancellationToken.None)\n    expect(res.length).toBe(2)\n  })\n\n  it('should return empty when token cancelled', async () => {\n    let doc = await workspace.document\n    let pos = await window.getCursorPosition()\n    let context: InlineCompletionContext = { triggerKind: InlineCompletionTriggerKind.Automatic }\n    let cancelled = false\n    disposables.push(languages.registerInlineCompletionItemProvider(['*'], {\n      provideInlineCompletionItems: (_doc, _pos, _context, token) => {\n        return new Promise(resolve => {\n          let timer = setTimeout(() => resolve([]), 500)\n          token.onCancellationRequested(() => {\n            cancelled = true\n            clearTimeout(timer)\n            resolve(undefined)\n          })\n        })\n      }\n    }))\n    let tokenSource = new CancellationTokenSource()\n    let p = languages.provideInlineCompletionItems(doc.textDocument, pos, context, tokenSource.token)\n    tokenSource.cancel()\n    let res = await p\n    expect(cancelled).toBe(true)\n    expect(res).toEqual([])\n  })\n\n  it('should not throw on provider error', async () => {\n    let doc = await workspace.document\n    let pos = await window.getCursorPosition()\n    let context: InlineCompletionContext = { triggerKind: InlineCompletionTriggerKind.Automatic }\n    disposables.push(languages.registerInlineCompletionItemProvider(['*'], {\n      provideInlineCompletionItems: () => {\n        return Promise.reject(new Error('my error'))\n      }\n    }))\n    let tokenSource = new CancellationTokenSource()\n    let res = await languages.provideInlineCompletionItems(doc.textDocument, pos, context, tokenSource.token)\n    expect(res).toEqual([])\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/inlineValue.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationToken, Disposable, InlineValueText, Range } from 'vscode-languageserver-protocol'\nimport languages, { ProviderName } from '../../languages'\nimport { disposeAll } from '../../util'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  // hover = helper.plugin.getHandler().hover\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nbeforeEach(async () => {\n  await helper.createDocument()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n})\n\ndescribe('InlineValue', () => {\n  describe('InlineValueManager', () => {\n    it('should return false when provider not exists', async () => {\n      let doc = await workspace.document\n      let res = languages.hasProvider(ProviderName.InlineValue, doc.textDocument)\n      expect(res).toBe(false)\n    })\n\n    it('should return merged results', async () => {\n      disposables.push(languages.registerInlineValuesProvider([{ language: '*' }], {\n        provideInlineValues: () => {\n          return null\n        }\n      }))\n      disposables.push(languages.registerInlineValuesProvider([{ language: '*' }], {\n        provideInlineValues: () => {\n          return [\n            InlineValueText.create(Range.create(0, 0, 0, 1), 'foo'),\n            InlineValueText.create(Range.create(0, 3, 0, 5), 'bar'),\n          ]\n        }\n      }))\n      disposables.push(languages.registerInlineValuesProvider([{ language: '*' }], {\n        provideInlineValues: () => {\n          return [\n            InlineValueText.create(Range.create(0, 0, 0, 1), 'foo'),\n          ]\n        }\n      }))\n      let doc = await workspace.document\n      let res = await languages.provideInlineValues(doc.textDocument, Range.create(0, 0, 3, 0), { frameId: 3, stoppedLocation: Range.create(0, 0, 0, 3) }, CancellationToken.None)\n      expect(res.length).toBe(2)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/linkedEditing.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { Disposable, Position, Range, TextEdit } from 'vscode-languageserver-protocol'\nimport LinkedEditingHandler from '../../handler/linkedEditing'\nimport languages from '../../languages'\nimport { disposeAll } from '../../util'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet handler: LinkedEditingHandler\nlet disposables: Disposable[] = []\nlet wordPattern: string | undefined\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  handler = helper.plugin.getHandler().linkedEditingHandler\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nbeforeEach(async () => {\n  helper.updateConfiguration('coc.preferences.enableLinkedEditing', true)\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n})\n\nasync function registerProvider(content: string, position: Position): Promise<void> {\n  let doc = await workspace.document\n  disposables.push(languages.registerLinkedEditingRangeProvider([{ language: '*' }], {\n    provideLinkedEditingRanges: (doc, pos) => {\n      let document = workspace.getDocument(doc.uri)\n      let range = document.getWordRangeAtPosition(pos)\n      if (!range) return null\n      let text = doc.getText(range)\n      let ranges: Range[] = document.getSymbolRanges(text)\n      return { ranges, wordPattern }\n    }\n  }))\n  await nvim.setLine(content)\n  await doc.synchronize()\n  await handler.enable(doc, position)\n}\n\nasync function matches(): Promise<number> {\n  let list = await helper.getMatches('CocLinkedEditing')\n  return list.length\n}\n\ndescribe('LinkedEditing', () => {\n  it('should active and cancel on cursor moved', async () => {\n    await registerProvider('foo foo a ', Position.create(0, 0))\n    expect(await matches()).toBe(2)\n    await nvim.command(`normal! $`)\n    await helper.waitValue(() => {\n      return matches()\n    }, 0)\n  })\n\n  it('should active when moved to another word', async () => {\n    await registerProvider('foo foo bar bar bar', Position.create(0, 0))\n    await nvim.call('cursor', [1, 9])\n    await helper.waitValue(() => {\n      return matches()\n    }, 3)\n  })\n\n  it('should active on text change', async () => {\n    let doc = await workspace.document\n    await registerProvider('foo foo a ', Position.create(0, 0))\n    await nvim.call('cursor', [1, 1])\n    await nvim.call('nvim_buf_set_text', [doc.bufnr, 0, 0, 0, 0, ['i']])\n    await doc.synchronize()\n    let line = await nvim.line\n    expect(line).toBe('ifoo ifoo a ')\n    await nvim.call('nvim_buf_set_text', [doc.bufnr, 0, 0, 0, 1, []])\n    await doc.synchronize()\n    line = await nvim.line\n    expect(line).toBe('foo foo a ')\n  })\n\n  it('should cancel when change out of range', async () => {\n    let doc = await workspace.document\n    await registerProvider('foo foo bar', Position.create(0, 0))\n    await helper.waitValue(() => {\n      return matches()\n    }, 2)\n    await nvim.call('nvim_buf_set_text', [doc.bufnr, 0, 9, 0, 10, ['']])\n    await doc.synchronize()\n    await helper.waitValue(() => {\n      return matches()\n    }, 0)\n  })\n\n  it('should not cancel when insert line break before range', async () => {\n    let doc = await workspace.document\n    await registerProvider('foo foo bar', Position.create(0, 0))\n    await helper.waitValue(() => {\n      return matches()\n    }, 2)\n    await doc.applyEdits([TextEdit.insert(Position.create(0, 0), '\\n')])\n    await helper.waitValue(() => matches(), 2)\n  })\n\n  it('should cancel when insert line break in range', async () => {\n    let doc = await workspace.document\n    await registerProvider('foo foo bar', Position.create(0, 0))\n    await helper.waitValue(() => {\n      return matches()\n    }, 2)\n    await doc.applyEdits([TextEdit.insert(Position.create(0, 1), '\\n  ')])\n    await helper.waitValue(() => {\n      return matches()\n    }, 0)\n  })\n\n  it('should cancel on editor change', async () => {\n    await registerProvider('foo foo a ', Position.create(0, 0))\n    await nvim.command(`enew`)\n    await helper.wait(50)\n    await helper.waitValue(() => {\n      return matches()\n    }, 0)\n  })\n\n  it('should cancel when insert none word character', async () => {\n    await registerProvider('foo foo a ', Position.create(0, 0))\n    await nvim.call('cursor', [1, 4])\n    await nvim.input('i')\n    await nvim.input('a')\n    await helper.waitValue(() => {\n      return matches()\n    }, 2)\n    await nvim.input('i')\n    await nvim.input('@')\n    await helper.waitValue(() => {\n      return matches()\n    }, 0)\n  })\n\n  it('should cancel when insert not match wordPattern', async () => {\n    wordPattern = '[A-Z]'\n    await registerProvider('foo foo a ', Position.create(0, 0))\n    await nvim.call('cursor', [1, 4])\n    await nvim.input('i')\n    await nvim.input('A')\n    await helper.waitValue(() => {\n      return matches()\n    }, 2)\n    await nvim.input('i')\n    await nvim.input('3')\n    await helper.waitValue(() => {\n      return matches()\n    }, 0)\n  })\n\n  it('should cancel request on cursor moved', async () => {\n    disposables.push(languages.registerLinkedEditingRangeProvider([{ language: '*' }], {\n      provideLinkedEditingRanges: (doc, pos, token) => {\n        return new Promise(resolve => {\n          token.onCancellationRequested(() => {\n            clearTimeout(timer)\n            resolve(null)\n          })\n          let timer = setTimeout(() => {\n            let document = workspace.getDocument(doc.uri)\n            let range = document.getWordRangeAtPosition(pos)\n            if (!range) return resolve(null)\n            let text = doc.getText(range)\n            let ranges: Range[] = document.getSymbolRanges(text)\n            resolve({ ranges, wordPattern })\n          }, 1000)\n        })\n      }\n    }))\n    let doc = await workspace.document\n    await nvim.setLine('foo foo  ')\n    await doc.synchronize()\n    await nvim.call('cursor', [1, 2])\n    await helper.wait(10)\n    await nvim.call('cursor', [1, 9])\n    await helper.waitValue(() => {\n      return matches()\n    }, 0)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/locations.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationToken, CancellationTokenSource, Disposable, Location, LocationLink, Position, Range } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport LocationHandler from '../../handler/locations'\nimport languages from '../../languages'\nimport services from '../../services'\nimport { disposeAll } from '../../util'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet locations: LocationHandler\nlet disposables: Disposable[] = []\nlet currLocations: Location[] | LocationLink[]\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  Object.assign(workspace.env, {\n    locationlist: false\n  })\n  locations = helper.plugin.getHandler().locations\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nbeforeEach(async () => {\n  await helper.createDocument()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n})\n\nfunction createLocation(name: string, sl: number, sc: number, el: number, ec: number): Location {\n  return Location.create(`test://${name}`, Range.create(sl, sc, el, ec))\n}\n\nfunction createLocationLink(name: string, sl: number, sc: number, el: number, ec: number): LocationLink {\n  let r = Range.create(sl, sc, el, ec)\n  return LocationLink.create(`test://${name}`, r, r)\n}\n\ndescribe('locations', () => {\n  describe('no provider', () => {\n    it('should return null when provider does not exist', async () => {\n      let doc = (await workspace.document).textDocument\n      let pos = Position.create(0, 0)\n      let tokenSource = new CancellationTokenSource()\n      let token = tokenSource.token\n      expect(await languages.getDefinition(doc, pos, token)).toEqual([])\n      expect(await languages.getDefinitionLinks(doc, pos, token)).toEqual([])\n      expect(await languages.getDeclaration(doc, pos, token)).toEqual([])\n      expect(await languages.getTypeDefinition(doc, pos, token)).toEqual([])\n      expect(await languages.getImplementation(doc, pos, token)).toEqual([])\n      expect(await languages.getReferences(doc, { includeDeclaration: false }, pos, token)).toEqual([])\n    })\n  })\n\n  describe('reference', () => {\n    beforeEach(() => {\n      disposables.push(languages.registerReferencesProvider([{ language: '*' }], {\n        provideReferences: () => {\n          return currLocations as any\n        }\n      }))\n    })\n\n    it('should get references', async () => {\n      currLocations = [createLocationLink('foo', 0, 0, 0, 0), createLocationLink('bar', 0, 0, 0, 0)]\n      let res = await helper.doAction('references')\n      expect(res.length).toBe(2)\n    })\n\n    it('should jump to references', async () => {\n      currLocations = [createLocation('foo', 0, 0, 0, 0)]\n      let res = await helper.doAction('jumpReferences', 'edit')\n      expect(res).toBe(true)\n      let name = await nvim.call('bufname', ['%'])\n      expect(name).toBe('test://foo')\n    })\n\n    it('should return false when references not found', async () => {\n      currLocations = []\n      let res = await locations.gotoReferences('edit', true)\n      expect(res).toBe(false)\n      res = await helper.doAction('jumpUsed', 'edit')\n      expect(res).toBe(false)\n    })\n  })\n\n  describe('definition', () => {\n    beforeEach(() => {\n      disposables.push(languages.registerDefinitionProvider([{ language: '*' }], {\n        provideDefinition: () => {\n          return currLocations\n        }\n      }))\n    })\n\n    it('should get definitions', async () => {\n      currLocations = [createLocation('foo', 0, 0, 0, 0), createLocation('bar', 0, 0, 0, 0)]\n      disposables.push(languages.registerDefinitionProvider([{ language: '*' }], {\n        provideDefinition: () => {\n          return [createLocation('foo', 0, 0, 0, 0)]\n        }\n      }))\n      disposables.push(languages.registerDefinitionProvider([{ language: '*' }], {\n        provideDefinition: () => {\n          return createLocation('foo', 0, 0, 0, 0)\n        }\n      }))\n      disposables.push(languages.registerDefinitionProvider([{ language: '*' }], {\n        provideDefinition: () => {\n          return [LocationLink.create(`test://foo`, Range.create(0, 0, 0, 0), Range.create(0, 0, 0, 0)), null]\n        }\n      }))\n      disposables.push(languages.registerDefinitionProvider([{ language: '*' }], {\n        provideDefinition: () => {\n          return [LocationLink.create(`test://foo`, Range.create(0, 0, 0, 0), Range.create(0, 0, 0, 0))]\n        }\n      }))\n      let res = await helper.doAction('definitions')\n      expect(res.length).toBe(2)\n    })\n\n    it('should return empty locations when no definitions exist', async () => {\n      currLocations = null\n      let doc = await workspace.document\n      let res = await languages.getDefinitionLinks(doc.textDocument, Position.create(0, 0), CancellationToken.None)\n      expect(res.length).toBe(0)\n      currLocations = [createLocation('foo', 0, 0, 0, 0)]\n      res = await languages.getDefinitionLinks(doc.textDocument, Position.create(0, 0), CancellationToken.None)\n      expect(res.length).toBe(0)\n    })\n\n    it('should jump to definitions', async () => {\n      currLocations = [createLocation('foo', 0, 0, 0, 0)]\n      let res = await helper.doAction('jumpDefinition', 'edit')\n      expect(res).toBe(true)\n      let name = await nvim.call('bufname', ['%'])\n      expect(name).toBe('test://foo')\n    })\n\n    it('should return false when definitions not found', async () => {\n      currLocations = []\n      let res = await locations.gotoDefinition('edit')\n      expect(res).toBe(false)\n    })\n  })\n\n  describe('declaration', () => {\n    beforeEach(() => {\n      disposables.push(languages.registerDeclarationProvider([{ language: '*' }], {\n        provideDeclaration: () => {\n          return currLocations\n        }\n      }))\n    })\n\n    it('should get declarations', async () => {\n      currLocations = [createLocation('foo', 0, 0, 0, 0), createLocation('bar', 0, 0, 0, 0)]\n      let res = await locations.declarations() as Location[]\n      expect(res.length).toBe(2)\n    })\n\n    it('should jump to declaration', async () => {\n      currLocations = [createLocation('foo', 0, 0, 0, 0)]\n      let res = await locations.gotoDeclaration('edit')\n      expect(res).toBe(true)\n      let name = await nvim.call('bufname', ['%'])\n      expect(name).toBe('test://foo')\n    })\n\n    it('should return false when declaration not found', async () => {\n      currLocations = []\n      let res = await helper.doAction('jumpDeclaration', 'edit')\n      expect(res).toBe(false)\n    })\n  })\n\n  describe('typeDefinition', () => {\n    beforeEach(() => {\n      disposables.push(languages.registerTypeDefinitionProvider([{ language: '*' }], {\n        provideTypeDefinition: () => {\n          return currLocations\n        }\n      }))\n    })\n\n    it('should get type definition', async () => {\n      currLocations = [createLocation('foo', 0, 0, 0, 0), createLocation('bar', 0, 0, 0, 0)]\n      let res = await helper.doAction('typeDefinitions')\n      expect(res.length).toBe(2)\n    })\n\n    it('should jump to type definition', async () => {\n      currLocations = [createLocation('foo', 0, 0, 0, 0)]\n      let res = await locations.gotoTypeDefinition('edit')\n      expect(res).toBe(true)\n      let name = await nvim.call('bufname', ['%'])\n      expect(name).toBe('test://foo')\n    })\n\n    it('should return false when type definition not found', async () => {\n      currLocations = []\n      let res = await helper.doAction('jumpTypeDefinition', 'edit')\n      expect(res).toBe(false)\n    })\n  })\n\n  describe('implementation', () => {\n    beforeEach(() => {\n      disposables.push(languages.registerImplementationProvider([{ language: '*' }], {\n        provideImplementation: () => {\n          return currLocations\n        }\n      }))\n    })\n\n    it('should get implementations', async () => {\n      currLocations = [createLocation('foo', 0, 0, 0, 0), createLocation('bar', 0, 0, 0, 0)]\n      let res = await helper.doAction('implementations')\n      expect(res.length).toBe(2)\n    })\n\n    it('should jump to implementation', async () => {\n      currLocations = [createLocation('foo', 0, 0, 0, 0)]\n      let res = await helper.doAction('jumpImplementation', 'edit')\n      expect(res).toBe(true)\n      let name = await nvim.call('bufname', ['%'])\n      expect(name).toBe('test://foo')\n    })\n\n    it('should return false when implementation not found', async () => {\n      currLocations = []\n      let res = await locations.gotoImplementation('edit')\n      expect(res).toBe(false)\n    })\n  })\n\n  describe('getTagList', () => {\n    it('should return null when cword does not exist', async () => {\n      let res = await helper.doAction('getTagList')\n      expect(res).toBe(null)\n    })\n\n    it('should return null when provider does not exist', async () => {\n      await nvim.setLine('foo')\n      await nvim.command('normal! ^')\n      let res = await locations.getTagList()\n      expect(res).toBe(null)\n    })\n\n    it('should null when buffer not attached', async () => {\n      let doc = await workspace.document\n      if (doc) doc.detach()\n      let res = await locations.getTagList()\n      expect(res).toBe(null)\n    })\n\n    it('should return null when result is empty', async () => {\n      disposables.push(languages.registerDefinitionProvider([{ language: '*' }], {\n        provideDefinition: () => {\n          return []\n        }\n      }))\n      await nvim.setLine('foo')\n      await nvim.command('normal! ^')\n      let res = await locations.getTagList()\n      expect(res).toBe(null)\n    })\n\n    it('should return tag definitions', async () => {\n      disposables.push(languages.registerDefinitionProvider([{ language: '*' }], {\n        provideDefinition: () => {\n          return [createLocation('bar', 2, 0, 2, 5), Location.create(URI.file('/foo').toString(), Range.create(1, 0, 1, 5))]\n        }\n      }))\n      await nvim.setLine('foo')\n      await nvim.command('normal! ^')\n      let res = await locations.getTagList()\n      expect(res).toEqual([\n        {\n          name: 'foo',\n          cmd: 'silent keepjumps call coc#cursor#move_to(2, 0)',\n          filename: 'test://bar'\n        },\n        { name: 'foo', cmd: 'silent keepjumps call coc#cursor#move_to(1, 0)', filename: '/foo' }\n      ])\n    })\n  })\n\n  describe('findLocations', () => {\n    // hook result\n    let fn\n    let result: any\n    beforeAll(() => {\n      fn = services.sendRequest\n      services.sendRequest = () => {\n        return Promise.resolve(result)\n      }\n    })\n\n    afterAll(() => {\n      services.sendRequest = fn\n    })\n\n    it('should handle locations from language client', async () => {\n      result = [createLocation('bar', 2, 0, 2, 5)]\n      await helper.doAction('findLocations', 'foo', 'mylocation', {}, false)\n      let res = await nvim.getVar('coc_jump_locations')\n      expect(res).toEqual([{\n        uri: 'test://bar',\n        lnum: 3,\n        end_lnum: 3,\n        col: 1,\n        end_col: 6,\n        filename: 'test://bar',\n        text: '',\n        range: Range.create(2, 0, 2, 5)\n      }])\n    })\n\n    it('should handle empty result', async () => {\n      result = null\n      let res = await locations.findLocations('foo', 'mylocation', undefined, 'edit')\n      expect(res).toBe(false)\n    })\n\n    it('should handle nested locations', async () => {\n      let location: any = {\n        location: createLocation('file', 0, 0, 0, 0),\n        children: [{\n          location: createLocation('foo', 3, 0, 3, 5),\n          children: []\n        }, {\n          location: createLocation('bar', 4, 0, 4, 5),\n          children: []\n        }]\n      }\n      result = location\n      await locations.findLocations('foo', 'mylocation', {})\n      let res = await nvim.getVar('coc_jump_locations') as any[]\n      expect(res.length).toBe(3)\n    })\n  })\n\n  describe('toLocations()', () => {\n    it('should convert to locations', async () => {\n      let loc = createLocation('file', 0, 0, 0, 0)\n      expect(locations.toLocations(loc).length).toBe(1)\n      expect(locations.toLocations([loc]).length).toBe(1)\n      let link = LocationLink.create(`test://a`, Range.create(0, 0, 1, 0), Range.create(0, 0, 0, 1))\n      expect(locations.toLocations(link).length).toBe(1)\n      expect(locations.toLocations([link]).length).toBe(1)\n      expect(locations.toLocations(null).length).toBe(0)\n      expect(locations.toLocations(undefined).length).toBe(0)\n      let location: any = {\n        location: createLocation('file', 0, 0, 0, 0),\n        children: [{\n          location: link,\n          children: [{\n            location: loc\n          }, null, undefined, {}]\n        }]\n      }\n      expect(locations.toLocations(location).length).toBe(3)\n    })\n  })\n\n  describe('handleLocations', () => {\n    it('should not throw when locations is undefined', async () => {\n      await locations.handleLocations(undefined)\n    })\n\n    it('should not throw when locations is empty array', async () => {\n      await locations.handleLocations([])\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/outline.test.ts",
    "content": "import { Buffer, Neovim } from '@chemzqm/neovim'\nimport { CodeAction, CodeActionKind, Disposable, DocumentSymbol, Range, SymbolKind, SymbolTag, TextEdit } from 'vscode-languageserver-protocol'\nimport events from '../../events'\nimport Symbols from '../../handler/symbols/index'\nimport languages from '../../languages'\nimport { ProviderResult } from '../../provider'\nimport { disposeAll } from '../../util'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport helper from '../helper'\nimport Parser from './parser'\n\nlet nvim: Neovim\nlet symbols: Symbols\nlet disposables: Disposable[] = []\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  symbols = helper.plugin.getHandler().symbols\n})\n\nbeforeEach(() => {\n  disposables.push(languages.registerDocumentSymbolProvider([{ language: 'javascript' }], {\n    provideDocumentSymbols: document => {\n      let content = document.getText()\n      let showDetail = content.includes('detail')\n      let parser = new Parser(content, showDetail)\n      let res: DocumentSymbol[] = parser.parse()\n      if (res.length) {\n        res[0].tags = [SymbolTag.Deprecated]\n      }\n      return Promise.resolve(res)\n    }\n  }))\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n  await nvim.command(`let w:cocViewId = ''`)\n\n})\n\nasync function getOutlineBuffer(): Promise<Buffer | undefined> {\n  let winid = await nvim.call('coc#window#find', ['cocViewId', 'OUTLINE'])\n  if (winid == -1) return undefined\n  let bufnr = await nvim.call('winbufnr', [winid]) as number\n  if (bufnr == -1) return undefined\n  return nvim.createBuffer(bufnr)\n}\n\ndescribe('symbols outline', () => {\n\n  let defaultCode = `class myClass {\n  fun1() { }\n  fun2() {}\n}`\n\n  async function createBuffer(code = defaultCode): Promise<Buffer> {\n    let doc = await helper.createDocument()\n    let buf = doc.buffer\n    doc.setFiletype('javascript')\n    await buf.setOption('modifiable', true)\n    await buf.setLines(code.split('\\n'), { start: 0, end: -1, strictIndexing: false })\n    await doc.synchronize()\n    return buf\n  }\n\n  describe('actions', () => {\n    it('should invoke selected code action', async () => {\n      const codeAction = CodeAction.create('my action', CodeActionKind.Refactor)\n      let uri: string\n      disposables.push(languages.registerCodeActionProvider([{ language: '*' }], {\n        provideCodeActions: () => [codeAction],\n        resolveCodeAction: (action): ProviderResult<CodeAction> => {\n          action.edit = {\n            changes: {\n              [uri]: [TextEdit.del(Range.create(0, 0, 0, 5))]\n            }\n          }\n          return action\n        }\n      }, undefined))\n      await createBuffer()\n      let bufnr = await nvim.call('bufnr', ['%']) as number\n      let doc = workspace.getDocument(bufnr)\n      uri = doc.uri\n      await symbols.showOutline(0)\n      await helper.waitValue(async () => {\n        let id = await nvim.eval('get(w:,\"cocViewId\",v:null)')\n        return id != null\n      }, true)\n      await nvim.call('cursor', [3, 1])\n      let spy = jest.spyOn(window, 'showMenuPicker').mockImplementation(() => {\n        return Promise.resolve(0)\n      })\n      await nvim.input('<tab>')\n      await helper.waitValue(async () => {\n        return await nvim.eval('getline(1)')\n      }, ' myClass {')\n      spy.mockRestore()\n    })\n\n    it('should invoke visual select', async () => {\n      await createBuffer()\n      let bufnr = await nvim.call('bufnr', ['%'])\n      await symbols.showOutline(0)\n      await helper.waitFor('getline', [3], /fun1/)\n      await nvim.command('exe 3')\n      await nvim.input('<tab>')\n      await helper.waitPrompt()\n      await nvim.input('<cr>')\n      await helper.waitFor('mode', [], 'v')\n      let buf = await nvim.buffer\n      expect(buf.id).toBe(bufnr)\n    })\n  })\n\n  describe('configuration', () => {\n    it('should follow cursor', async () => {\n      await createBuffer(`  class myClass {\n  fun1() { }\n  fun2() {}\n}`)\n      let curr = await nvim.call('bufnr', ['%']) as number\n      await symbols.showOutline(0)\n      let bufnr = await nvim.call('bufnr', ['%']) as number\n      await nvim.command('wincmd p')\n      await nvim.command('exe 3')\n      await events.fire('CursorHold', [curr, [3, 1]])\n      await helper.wait(30)\n      await nvim.call('cursor', [1, 1])\n      await events.fire('CursorHold', [curr, [1, 1]])\n      await helper.wait(30)\n      let buf = nvim.createBuffer(bufnr)\n      let lines = await buf.getLines()\n      expect(lines.slice(1)).toEqual([\n        '- c myClass 1', '    m fun1 2', '    m fun2 3'\n      ])\n      let signs = await buf.getSigns({ group: 'CocTree' })\n      expect(signs.length).toBe(1)\n      expect(signs[0]).toEqual({\n        lnum: 2,\n        id: 3001,\n        name: 'CocTreeSelected',\n        priority: 10,\n        group: 'CocTree'\n      })\n      await nvim.command(`bd ${bufnr}`)\n      await events.fire('CursorHold', [curr, [3, 1]])\n    })\n\n    it('should not follow cursor', async () => {\n      helper.updateConfiguration('outline.followCursor', false, disposables)\n      await createBuffer()\n      let curr = await nvim.call('bufnr', ['%']) as number\n      await symbols.showOutline(0)\n      let bufnr = await nvim.call('bufnr', ['%']) as number\n      await nvim.command('wincmd p')\n      await nvim.command('exe 3')\n      await events.fire('CursorHold', [curr])\n      await helper.wait(50)\n      let buf = nvim.createBuffer(bufnr)\n      let signs = await buf.getSigns({ group: 'CocTree' })\n      expect(signs.length).toBe(0)\n    })\n\n    it('should keep current window', async () => {\n      helper.updateConfiguration('outline.keepWindow', true, disposables)\n      await createBuffer()\n      let curr = await nvim.call('bufnr', ['%'])\n      await symbols.showOutline()\n      let bufnr = await nvim.call('bufnr', ['%'])\n      expect(curr).toBe(bufnr)\n    })\n\n    it('should check on buffer switch', async () => {\n      helper.updateConfiguration('outline.checkBufferSwitch', true, disposables)\n      let b = await createBuffer()\n      await symbols.showOutline(1)\n      let buf = await getOutlineBuffer()\n      let bufnr = buf.id\n      await helper.edit('unnamed')\n      await helper.waitValue(async () => {\n        let buf = await getOutlineBuffer()\n        return buf.id > bufnr\n      }, true)\n      buf = await getOutlineBuffer()\n      let lines = await buf.lines\n      expect(lines[0]).toMatch('Document symbol provider not found')\n      await nvim.command(`bd! ${b.id}`)\n      await helper.wait(10)\n      let loaded = await buf.loaded\n      expect(loaded).toBe(true)\n    })\n\n    it('should not check on buffer switch', async () => {\n      helper.updateConfiguration('outline.checkBufferSwitch', false, disposables)\n      await createBuffer()\n      await symbols.showOutline(1)\n      await helper.edit('unnamed')\n      await helper.wait(100)\n      let buf = await getOutlineBuffer()\n      let lines = await buf.lines\n      expect(lines.slice(1)).toEqual([\n        '- c myClass 1', '    m fun1 2', '    m fun2 3'\n      ])\n    })\n\n    it('should not check on buffer reload', async () => {\n      helper.updateConfiguration('outline.checkBufferSwitch', false, disposables)\n      await symbols.showOutline(1)\n      await createBuffer()\n      await helper.wait(50)\n      let buf = await getOutlineBuffer()\n      expect(buf).toBeDefined()\n    })\n\n    it('should sort by category', async () => {\n      let code = `\nclass myClass {\n}\nfun1() {}\n`\n      await createBuffer(code)\n      await symbols.showOutline(1)\n      let buf = await getOutlineBuffer()\n      let lines = await buf.lines\n      expect(lines).toEqual([\n        'OUTLINE Category', '  c myClass 2', '  m fun1 4'\n      ])\n    })\n\n    it('should sort by position', async () => {\n      let code = `class myClass {\n  fun2() { }\n  fun1() {}\n}`\n      helper.updateConfiguration('outline.sortBy', 'position', disposables)\n      await createBuffer(code)\n      await symbols.showOutline(1)\n      let buf = await getOutlineBuffer()\n      let lines = await buf.lines\n      expect(lines).toEqual([\n        'OUTLINE Position', '- c myClass 1', '    m fun2 2', '    m fun1 3'\n      ])\n    })\n\n    it('should sort by name', async () => {\n      let code = `class myClass {\n  fun2() {}\n  fun1() {}\n}`\n      helper.updateConfiguration('outline.sortBy', 'name', disposables)\n      await createBuffer(code)\n      await symbols.showOutline(1)\n      let buf = await getOutlineBuffer()\n      let lines = await buf.lines\n      expect(lines).toEqual([\n        'OUTLINE Name', '- c myClass 1', '    m fun1 3', '    m fun2 2'\n      ])\n    })\n\n    it('should change sort method', async () => {\n      helper.updateConfiguration('outline.detailAsDescription', false, disposables)\n      let code = `class detail {\n  fun2() {}\n  fun1() {}\n}`\n      await createBuffer(code)\n      await symbols.showOutline(0)\n      await helper.wait(30)\n      await nvim.input('<C-s>')\n      await helper.waitPrompt()\n      await nvim.input('<esc>')\n      await helper.wait(30)\n      await nvim.input('<C-s>')\n      await helper.waitPrompt()\n      await nvim.input('3')\n      await helper.waitFor('getline', [1], 'OUTLINE Position')\n    })\n\n    it('should show detail as description', async () => {\n      helper.updateConfiguration('outline.detailAsDescription', true, disposables)\n      let code = `class detail {\n  fun2() {}\n}`\n      await createBuffer(code)\n      await symbols.showOutline(1)\n      let buf = await getOutlineBuffer()\n      let lines = await buf.lines\n      expect(lines.slice(1)).toEqual([\n        '- c detail 1', '    m fun2 () 2'\n      ])\n    })\n\n    it('should not showLineNumber', async () => {\n      helper.updateConfiguration('outline.showLineNumber', false, disposables)\n      let code = `class detail {\n  fun2() {}\n}`\n      await createBuffer(code)\n      await symbols.showOutline(1)\n      let buf = await getOutlineBuffer()\n      let lines = await buf.lines\n      expect(lines.slice(1)).toEqual(['- c detail', '    m fun2 ()'])\n    })\n  })\n\n  describe('events', () => {\n\n    it('should not close TreeView on buffer reload', async () => {\n      await createBuffer()\n      await symbols.showOutline(0)\n      await nvim.command('edit')\n      await helper.wait(30)\n      let winid = await nvim.call('coc#window#find', ['cocViewId', 'OUTLINE'])\n      expect(winid).toBeGreaterThan(0)\n    })\n\n    it('should dispose on buffer unload', async () => {\n      await createBuffer()\n      let curr = await nvim.call('bufnr', ['%'])\n      await symbols.showOutline(0)\n      await nvim.command('tabe')\n      await nvim.command(`bd! ${curr}`)\n      await helper.waitValue(async () => {\n        let buf = await getOutlineBuffer()\n        return buf == null\n      }, true)\n    })\n\n    it('should check current window on BufEnter', async () => {\n      await createBuffer()\n      await symbols.showOutline(1)\n      await nvim.command('enew')\n      await helper.wait(50)\n    })\n\n    it('should recreated when original window exists', async () => {\n      let win = await nvim.window\n      await symbols.showOutline(1)\n      await helper.wait(50)\n      await nvim.setWindow(win)\n      await createBuffer()\n      await helper.waitValue(async () => {\n        let buf = await getOutlineBuffer()\n        return buf != null\n      }, true)\n    })\n\n    it('should keep old outline when new buffer not attached', async () => {\n      await createBuffer()\n      await symbols.showOutline(1)\n      await nvim.command(`vnew +setl\\\\ buftype=nofile`)\n      await helper.wait(50)\n      let buf = await getOutlineBuffer()\n      expect(buf).toBeDefined()\n      let lines = await buf.lines\n      expect(lines.slice(1)).toEqual([\n        '- c myClass 1', '    m fun1 2', '    m fun2 3'\n      ])\n    })\n\n    it('should not reload when switch to original buffer', async () => {\n      await createBuffer()\n      await symbols.showOutline(0)\n      let buf = await getOutlineBuffer()\n      let name = await buf.name\n      await nvim.command('wincmd p')\n      await helper.wait(50)\n      buf = await getOutlineBuffer()\n      let curr = await buf.name\n      expect(curr).toBe(name)\n    })\n  })\n\n  describe('show()', () => {\n    it('should not throw when document not attached', async () => {\n      await nvim.command(`edit +setl\\\\ buftype=nofile t`)\n      await workspace.document\n      await symbols.showOutline(1)\n      let line = await helper.getCmdline()\n      expect(line).toMatch('Unable to show outline')\n    })\n\n    it('should not throw when provider does not exist', async () => {\n      await symbols.showOutline(1)\n      let buf = await getOutlineBuffer()\n      expect(buf).toBeDefined()\n    })\n\n    it('should not throw when symbols is empty', async () => {\n      await createBuffer('')\n      await symbols.showOutline(1)\n      let buf = await getOutlineBuffer()\n      expect(buf).toBeDefined()\n    })\n\n    it('should jump to selected symbol', async () => {\n      await createBuffer()\n      let bufnr = await nvim.call('bufnr', ['%'])\n      await symbols.showOutline(0)\n      await helper.waitFor('getline', [3], '    m fun1 2')\n      await nvim.command('exe 3')\n      await nvim.input('<cr>')\n      await helper.waitValue(async () => {\n        return await nvim.call('bufnr', ['%'])\n      }, bufnr)\n      let cursor = await nvim.call('coc#cursor#position')\n      expect(cursor).toEqual([1, 2])\n    })\n\n    it('should update symbols', async () => {\n      await createBuffer()\n      let doc = await workspace.document\n      let bufnr = await nvim.call('bufnr', ['%']) as number\n      await symbols.showOutline(1)\n      await helper.waitFor('getline', [1], 'class myClass {')\n      let buf = nvim.createBuffer(bufnr)\n      let code = 'class foo{}'\n      await buf.setLines(code.split('\\n'), {\n        start: 0,\n        end: -1,\n        strictIndexing: false\n      })\n      await doc.synchronize()\n      buf = await getOutlineBuffer()\n      await helper.waitFor('eval', [`getbufline(${buf.id},1)[0]`], /No\\sresults/)\n      let lines = await buf.lines\n      expect(lines).toEqual([\n        'No results',\n        '',\n        'OUTLINE Category'\n      ])\n    })\n\n    it('should show label in description', async () => {\n      disposables.push(languages.registerDocumentSymbolProvider([{ language: 'vim' }], {\n        meta: {\n          label: 'vimlsp'\n        },\n        provideDocumentSymbols: _ => {\n          let res: DocumentSymbol[] = [{\n            name: 'let',\n            range: Range.create(0, 0, 0, 3),\n            kind: SymbolKind.Constant,\n            selectionRange: Range.create(0, 0, 0, 3),\n            tags: [SymbolTag.Deprecated]\n          }]\n          return Promise.resolve(res)\n        }\n      }))\n      let doc = await helper.createDocument('t.vim')\n      doc.setFiletype('vim')\n      let buf = await nvim.buffer\n      await buf.setLines(['let'], { start: 0, end: -1, strictIndexing: false })\n      await doc.synchronize()\n      await symbols.showOutline(0)\n      await helper.waitFor('getline', [1], 'OUTLINE vimlsp')\n    })\n  })\n\n  describe('autoPreview', () => {\n    it('should toggle auto preview by press p', async () => {\n      await createBuffer()\n      await symbols.showOutline(0)\n      await helper.waitFor('getline', [3], /fun1/)\n      await nvim.command('exe 2')\n      await nvim.input('p')\n      let winid = await helper.waitFloat()\n      expect(winid).toBeGreaterThan(1000)\n      await nvim.input('p')\n      await helper.waitValue(async () => {\n        let win = nvim.createWindow(winid)\n        let valid = await win.valid\n        return valid === false\n      }, true)\n    })\n\n    it('should close preview when move to line without node', async () => {\n      await createBuffer()\n      await symbols.showOutline(0)\n      await helper.waitFor('getline', [3], /fun1/)\n      await nvim.command('exe 2')\n      await nvim.input('p')\n      let winid = await helper.waitFloat()\n      await nvim.input('l')\n      // debounce for CursorMoved used\n      await helper.wait(50)\n      await nvim.input('k')\n      await helper.waitValue(async () => {\n        let win = nvim.createWindow(winid)\n        let valid = await win.valid\n        return valid === false\n      }, true)\n    })\n\n    it('should show preview when move cursor back', async () => {\n      await createBuffer()\n      await symbols.showOutline(0)\n      await helper.waitFor('getline', [3], /fun1/)\n      await nvim.command('exe 2')\n      await nvim.input('p')\n      let winid = await helper.waitFloat()\n      await nvim.command('wincmd p')\n      await helper.waitValue(async () => {\n        let win = nvim.createWindow(winid)\n        let valid = await win.valid\n        return valid === false\n      }, true)\n      await nvim.command('wincmd p')\n      winid = await helper.waitFloat()\n      expect(winid).toBeGreaterThan(1000)\n    })\n\n    it('should enable auto preview by configuration', async () => {\n      helper.updateConfiguration('outline.autoPreview', true, disposables)\n      await createBuffer()\n      await symbols.showOutline(0)\n      await helper.waitFor('getline', [3], /fun1/)\n      await nvim.command('exe 2')\n      let winid = await helper.waitFloat()\n      expect(winid).toBeGreaterThan(1000)\n    })\n  })\n\n  describe('hide()', () => {\n    it('should hide outline', async () => {\n      await createBuffer('')\n      await helper.doAction('showOutline', 1)\n      await helper.doAction('hideOutline')\n      let buf = await getOutlineBuffer()\n      expect(buf).toBeUndefined()\n    })\n\n    it('should auto hide outline on clicking', async () => {\n      helper.updateConfiguration('outline.autoHide', true, disposables)\n      await createBuffer()\n      await symbols.showOutline()\n      await helper.waitFor('getline', [3], '    m fun1 2')\n      await nvim.command('exe 3')\n      await nvim.input('<cr>')\n      await helper.waitValue(async () => {\n        return await getOutlineBuffer()\n      }, undefined)\n    })\n\n    it('should not throw when outline does not exist', async () => {\n      await symbols.hideOutline()\n      let buf = await getOutlineBuffer()\n      expect(buf).toBeUndefined()\n    })\n  })\n\n  describe('dispose', () => {\n    it('should dispose provider and views', async () => {\n      await createBuffer('')\n      let bufnr = await nvim.call('bufnr', ['%']) as number\n      await symbols.showOutline(1)\n      symbols.dispose()\n      await helper.waitValue(() => {\n        return symbols.hasOutline(bufnr)\n      }, false)\n      let buf = await getOutlineBuffer()\n      expect(buf).toBeUndefined()\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/parser.ts",
    "content": "import { DocumentSymbol, Range, SymbolKind } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\n\n/**\n * A syntax parser that parse `class` and `method` only.\n */\nexport default class Parser {\n  private _curr = 0\n  private _symbols: DocumentSymbol[] = []\n  private currSymbol: DocumentSymbol | undefined\n  private len: number\n  private textDocument: TextDocument\n  constructor(private _content: string, private showDetail = false) {\n    this.len = _content.length\n    this.textDocument = TextDocument.create('test:///a', 'txt', 1, _content)\n  }\n\n  public parse(): DocumentSymbol[] {\n    while (this._curr <= this.len - 1) {\n      this.parseToken()\n    }\n    return this._symbols\n  }\n\n  /**\n   * Parse a symbol, reset currSymbol & _curr\n   */\n  private parseToken(): void {\n    this.skipSpaces()\n    if (this.currSymbol) {\n      let endOffset = this.textDocument.offsetAt(this.currSymbol.range.end)\n      if (this._curr > endOffset) {\n        this.currSymbol = undefined\n      }\n    }\n    let remain = this.getLineRemain()\n    let ms = remain.match(/^(class)\\s(\\w+)\\s\\{\\s*/)\n    if (ms) {\n      // find class\n      let start = this._curr + 6\n      let end = start + ms[2].length\n      let selectionRange = Range.create(this.textDocument.positionAt(start), this.textDocument.positionAt(end))\n      let endPosition = this.findMatchedIndex(this._curr + ms[0].length)\n      let range = Range.create(this.textDocument.positionAt(this._curr), this.textDocument.positionAt(endPosition))\n      let symbolInfo: DocumentSymbol = {\n        range,\n        selectionRange,\n        kind: SymbolKind.Class,\n        name: ms[2],\n        children: []\n      }\n      if (this.currSymbol && this.currSymbol.children) {\n        this.currSymbol.children.push(symbolInfo)\n      } else {\n        this._symbols.push(symbolInfo)\n      }\n      this.currSymbol = symbolInfo\n    } else {\n      let ms = remain.match(/(\\w+)\\((.*)\\)\\s*\\{/)\n      if (ms) {\n        // find method\n        let start = this._curr\n        let end = start + ms[1].length\n        let selectionRange = Range.create(this.textDocument.positionAt(start), this.textDocument.positionAt(end))\n        let endPosition = this.findMatchedIndex(this._curr + ms[0].length)\n        let range = Range.create(this.textDocument.positionAt(this._curr), this.textDocument.positionAt(endPosition))\n        let symbolInfo: DocumentSymbol = {\n          range,\n          selectionRange,\n          kind: SymbolKind.Method,\n          detail: this.showDetail ? `(${ms[2]})` : undefined,\n          name: ms[1]\n        }\n        if (this.currSymbol && this.currSymbol.children) {\n          this.currSymbol.children.push(symbolInfo)\n        } else {\n          this._symbols.push(symbolInfo)\n        }\n      }\n    }\n    this._curr = this._curr + remain.length + 1\n  }\n\n  private findMatchedIndex(start: number): number {\n    let level = 0\n    for (let i = start; i < this.len; i++) {\n      let ch = this._content[i]\n      if (ch == '{') {\n        level = level + 1\n      }\n      if (ch == '}') {\n        if (level == 0) return i\n        level = level - 1\n      }\n    }\n    throw new Error(`Can't find matched }`)\n  }\n\n  private getLineRemain(): string {\n    let chars = ''\n    for (let i = this._curr; i < this.len; i++) {\n      let ch = this._content[i]\n      if (ch == '\\n') break\n      chars = chars + ch\n    }\n    return chars\n  }\n\n  private skipSpaces(): void {\n    for (let i = this._curr; i < this.len; i++) {\n      let ch = this._content[i]\n      if (!ch || /\\S/.test(ch)) {\n        this._curr = i\n        break\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/__tests__/handler/refactor.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport fs from 'fs'\nimport { Position, Range, TextDocumentEdit, TextEdit, WorkspaceEdit } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport commands from '../../commands'\nimport RefactorBuffer, { FileItemDef, fixChangeParams } from '../../handler/refactor/buffer'\nimport Changes from '../../handler/refactor/changes'\nimport Refactor from '../../handler/refactor/index'\nimport languages from '../../languages'\nimport { DidChangeTextDocumentParams } from '../../types'\nimport { Disposable } from '../../util'\nimport workspace from '../../workspace'\nimport helper, { createTmpFile } from '../helper'\n\nlet nvim: Neovim\nlet refactor: Refactor\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  refactor = helper.plugin.getHandler().refactor\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  refactor.reset()\n  await helper.reset()\n})\n\nfunction createEdit(uri: string): WorkspaceEdit {\n  let edit = TextEdit.insert(Position.create(0, 0), 'a')\n  let doc = { uri, version: null }\n  return { documentChanges: [TextDocumentEdit.create(doc, [edit])] }\n}\n\n// assert ranges is expected.\nasync function assertSynchronized(buf: RefactorBuffer) {\n  let buffer = nvim.createBuffer(buf.bufnr)\n  let lines = await buffer.lines\n  let items: { lnum: number, lines: string[] }[] = []\n  for (let i = 0; i < lines.length; i++) {\n    let line = lines[i]\n    if (line.includes('\\u3000') && line.length > 1) {\n      items.push({ lnum: i + 1, lines: [] })\n    }\n  }\n  let curr: { lnum: number, lines: string[] }[] = []\n  buf.fileItems.forEach(item => {\n    item.ranges.forEach(r => {\n      curr.push({ lnum: r.lnum, lines: [] })\n    })\n  })\n  curr.sort((a, b) => a.lnum - b.lnum)\n  expect(items).toEqual(curr)\n}\n\ndescribe('fixChangeParams', () => {\n  function createChangeParams(range: Range, text: string, original: string, originalLines: ReadonlyArray<string>): DidChangeTextDocumentParams {\n    return {\n      textDocument: {\n        uri: 'untitled:/1',\n        version: 1,\n      },\n      originalLines,\n      original,\n      bufnr: 1,\n      contentChanges: [{ range, text }]\n    } as any\n  }\n\n  it('should fix delete change params', async () => {\n    let e = createChangeParams(Range.create(0, 4, 2, 4), '', 'x\\nfoo\\n\\u3000bar', [\n      '\\u3000barx',\n      'foo',\n      '\\u3000bara'\n    ])\n    e = fixChangeParams(e)\n    expect(e.original).toBe('\\u3000barx\\nfoo\\n')\n    expect(e.contentChanges[0].range).toEqual(Range.create(0, 0, 2, 0))\n  })\n\n  it('should fix insert change params', async () => {\n    let e = createChangeParams(Range.create(0, 4, 0, 4), 'x\\nfoo\\n\\u3000bar', '', [\n      '\\u3000bara'\n    ])\n    e = fixChangeParams(e)\n    expect(e.original).toBe('')\n    let change = e.contentChanges[0]\n    expect(change.range).toEqual(Range.create(0, 0, 0, 0))\n    expect(change.text).toBe('\\u3000barx\\nfoo\\n')\n  })\n})\n\ndescribe('refactor', () => {\n  describe('checkInsert()', () => {\n    it('should check inserted ranges', async () => {\n      let c = new Changes()\n      expect(c.checkInsert([1])).toBeUndefined()\n      c.add([{ filepath: __filename, start: 1, lnum: 1, lines: [''] }])\n      expect(c.checkInsert([2])).toBeUndefined()\n    })\n  })\n\n  describe('getFileRange()', () => {\n    it('should throw when range does not exist', async () => {\n      let uri = URI.file(__filename).toString()\n      let locations = [{ uri, range: Range.create(0, 0, 0, 6) }]\n      let buf = await refactor.fromLocations(locations)\n      let fn = () => {\n        buf.getFileRange(1)\n      }\n      expect(fn).toThrow(Error)\n    })\n\n    it('should find file range', async () => {\n      let uri = URI.file(__filename).toString()\n      let locations = [{ uri, range: Range.create(0, 0, 0, 6) }]\n      let buf = await commands.executeCommand<any>('editor.action.showRefactor', locations)\n      let res = buf.getFileRange(4)\n      expect(res).toBeDefined()\n    })\n  })\n\n  describe('getRange()', () => {\n    it('should get delete range', async () => {\n      let filename = await createTmpFile('foo\\n\\nbar\\n')\n      let fileItem: FileItemDef = {\n        filepath: filename,\n        ranges: [{ start: 0, end: 1 }, { start: 2, end: 3 }]\n      }\n      let buf = await refactor.createRefactorBuffer()\n      await buf.addFileItems([fileItem])\n      let res = buf.getFileRange(4)\n      let r = buf.getDeleteRange(res)\n      expect(r).toEqual(Range.create(3, 0, 6, 0))\n      res = buf.getFileRange(7)\n      r = buf.getDeleteRange(res)\n      expect(r).toEqual(Range.create(6, 0, 8, 0))\n    })\n\n    it('should get replace range', async () => {\n      let filename = await createTmpFile('foo\\n\\nbar\\n')\n      let fileItem: FileItemDef = {\n        filepath: filename,\n        ranges: [{ start: 0, end: 1 }, { start: 2, end: 3 }]\n      }\n      let buf = await refactor.createRefactorBuffer()\n      await buf.addFileItems([fileItem])\n      let res = buf.getFileRange(4)\n      let r = buf.getReplaceRange(res)\n      expect(r).toEqual(Range.create(4, 0, 4, 3))\n      res = buf.getFileRange(7)\n      r = buf.getReplaceRange(res)\n      expect(r).toEqual(Range.create(7, 0, 7, 3))\n    })\n  })\n\n  describe('fromWorkspaceEdit()', () => {\n    it('should not create from invalid workspaceEdit', async () => {\n      let res = await refactor.fromWorkspaceEdit(undefined)\n      expect(res).toBeUndefined()\n      res = await refactor.fromWorkspaceEdit({ documentChanges: [] })\n      expect(res).toBeUndefined()\n    })\n\n    it('should create from document changes', async () => {\n      let edit = createEdit(URI.file(__filename).toString())\n      let buf = await refactor.fromWorkspaceEdit(edit)\n      let shown = await buf.valid\n      expect(shown).toBe(true)\n      let items = buf.fileItems\n      expect(items.length).toBe(1)\n      await nvim.command(`bd! ${buf.bufnr}`)\n      await helper.wait(30)\n      let has = refactor.has(buf.bufnr)\n      expect(has).toBe(false)\n    })\n\n    it('should create from workspaceEdit', async () => {\n      let changes = {\n        [URI.file(__filename).toString()]: [{\n          range: Range.create(0, 0, 0, 6),\n          newText: ''\n        }, {\n          range: Range.create(1, 0, 1, 6),\n          newText: ''\n        }, {\n          range: Range.create(50, 0, 50, 1),\n          newText: ' '\n        }, {\n          range: Range.create(60, 0, 60, 1),\n          newText: ' '\n        }]\n      }\n      let edit: WorkspaceEdit = { changes }\n      let buf = await refactor.fromWorkspaceEdit(edit)\n      let shown = await buf.valid\n      expect(shown).toBe(true)\n      let items = buf.fileItems\n      expect(items.length).toBe(1)\n    })\n  })\n\n  describe('fromLocations()', () => {\n    it('should create from locations', async () => {\n      let uri = URI.file(__filename).toString()\n      let locations = [{\n        uri,\n        range: Range.create(0, 0, 0, 6),\n      }, {\n        uri,\n        range: Range.create(1, 0, 1, 6),\n      }]\n      let buf = await refactor.fromLocations(locations)\n      let shown = await buf.valid\n      expect(shown).toBe(true)\n      let items = buf.fileItems\n      expect(items.length).toBe(1)\n    })\n\n    it('should not create from empty locations', async () => {\n      let buf = await refactor.fromLocations([])\n      expect(buf).toBeUndefined()\n    })\n  })\n\n  describe('onChange()', () => {\n    async function setup(): Promise<RefactorBuffer> {\n      let uri = URI.file(__filename).toString()\n      let locations = [{\n        uri,\n        range: Range.create(0, 0, 0, 6),\n      }, {\n        uri,\n        range: Range.create(1, 0, 1, 6),\n      }, {\n        uri,\n        range: Range.create(10, 0, 10, 6),\n      }]\n      return await refactor.fromLocations(locations)\n    }\n\n    it('should refresh on empty text change', async () => {\n      let buf = await setup()\n      let line = await nvim.call('getline', [4])\n      let doc = workspace.getDocument(buf.bufnr)\n      await nvim.call('setline', [4, line])\n      doc._forceSync()\n      let srcId = await nvim.createNamespace('coc-refactor')\n      let markers = await doc.buffer.getExtMarks(srcId, 0, -1)\n      expect(markers.length).toBe(2)\n    })\n\n    it('should detect range delete and undo', async () => {\n      let buf = await setup()\n      let doc = workspace.getDocument(buf.bufnr)\n      let r = buf.getFileRange(4)\n      let end = r.lnum + r.lines.length\n      await nvim.command(`${r.lnum},${end + 1}d`)\n      await doc.synchronize()\n      await assertSynchronized(buf)\n      await nvim.command('undo')\n      await doc.synchronize()\n      await assertSynchronized(buf)\n    })\n\n    it('should detect normal delete', async () => {\n      let buf = await setup()\n      let doc = workspace.getDocument(buf.bufnr)\n      let r = buf.getFileRange(4)\n      await nvim.command(`${r.lnum + 1},${r.lnum + 1}d`)\n      await doc.synchronize()\n      await assertSynchronized(buf)\n    })\n\n    it('should detect insert', async () => {\n      let buf = await setup()\n      let doc = workspace.getDocument(buf.bufnr)\n      let buffer = nvim.createBuffer(buf.bufnr)\n      await buffer.append(['foo'])\n      await doc.synchronize()\n      await assertSynchronized(buf)\n      await buffer.append(['foo', '\\u3000'])\n      await doc.synchronize()\n      await assertSynchronized(buf)\n    })\n  })\n\n  describe('onDocumentChange()', () => {\n    it('should ignore when change after range', async () => {\n      let doc = await helper.createDocument()\n      await doc.buffer.append(['foo', 'bar'])\n      await doc.synchronize()\n      let buf = await refactor.fromLocations([{ uri: doc.uri, range: Range.create(0, 0, 0, 3) }])\n      let lines = await nvim.call('getline', [1, '$'])\n      await doc.buffer.append(['def'])\n      await doc.synchronize()\n      let newLines = await nvim.call('getline', [1, '$'])\n      expect(lines).toEqual(newLines)\n      await assertSynchronized(buf)\n    })\n\n    it('should adjust when change before range', async () => {\n      let doc = await helper.createDocument()\n      await doc.buffer.append(['', '', '', '', 'foo', 'bar'])\n      await doc.synchronize()\n      let buf = await refactor.fromLocations([{ uri: doc.uri, range: Range.create(4, 0, 4, 3) }])\n      await doc.buffer.setLines(['def'], { start: 0, end: 0, strictIndexing: false })\n      await doc.synchronize()\n      let fileRange = buf.getFileRange(4)\n      expect(fileRange.start).toBe(2)\n      expect(fileRange.lines.length).toBe(6)\n      await assertSynchronized(buf)\n    })\n\n    it('should remove ranges when lines empty', async () => {\n      let doc = await helper.createDocument()\n      await doc.buffer.append(['', '', '', '', 'foo', 'bar'])\n      await doc.synchronize()\n      let buf = await refactor.fromLocations([{ uri: doc.uri, range: Range.create(4, 0, 4, 3) }])\n      await doc.buffer.setLines([], { start: 0, end: -1, strictIndexing: false })\n      await doc.synchronize()\n      let lines = await nvim.call('getline', [1, '$']) as string[]\n      expect(lines.length).toBe(3)\n      let items = buf.fileItems\n      expect(items.length).toBe(0)\n      await assertSynchronized(buf)\n    })\n\n    it('should change when liens changed', async () => {\n      let doc = await helper.createDocument()\n      await doc.buffer.append(['', '', '', '', 'foo', 'bar'])\n      await doc.synchronize()\n      let buf = await refactor.fromLocations([{ uri: doc.uri, range: Range.create(4, 0, 4, 3) }])\n      await doc.buffer.setLines(['def', 'def'], { start: 5, end: 6, strictIndexing: false })\n      await doc.synchronize()\n      let lines = await nvim.call('getline', [1, '$']) as string[]\n      expect(lines[lines.length - 2]).toBe('def')\n      await assertSynchronized(buf)\n    })\n  })\n\n  describe('getFileChanges()', () => {\n    it('should get changes #1', async () => {\n      await helper.createDocument()\n      let lines = `\nSave current buffer to make changes\n\\u3000\n\\u3000\n\\u3000/a.ts\n    })\n  } `\n      let buf = await refactor.fromLines(lines.split('\\n'))\n      let changes = await buf.getFileChanges()\n      expect(changes).toEqual([{ lnum: 5, filepath: '/a.ts', lines: ['    })', '  } '] }])\n    })\n\n    it('should get changes #2', async () => {\n      let lines = `\n\\u3000/a.ts\n    })\n  } `\n      let buf = await refactor.fromLines(lines.split('\\n'))\n      let changes = await buf.getFileChanges()\n      expect(changes).toEqual([{ lnum: 2, filepath: '/a.ts', lines: ['    })', '  } '] }])\n    })\n\n    it('should get changes #3', async () => {\n      let lines = `\n\\u3000/a.ts\n    })\n  }\n\\u3000`\n      let buf = await refactor.fromLines(lines.split('\\n'))\n      let changes = await buf.getFileChanges()\n      expect(changes).toEqual([{ lnum: 2, filepath: '/a.ts', lines: ['    })', '  }'] }])\n    })\n\n    it('should get changes #4', async () => {\n      let lines = `\n\\u3000/a.ts\nfoo\n\\u3000/b.ts\nbar\n\\u3000`\n      let buf = await refactor.fromLines(lines.split('\\n'))\n      let changes = await buf.getFileChanges()\n      expect(changes).toEqual([\n        { filepath: '/a.ts', lnum: 2, lines: ['foo'] },\n        { filepath: '/b.ts', lnum: 4, lines: ['bar'] }\n      ])\n    })\n  })\n\n  describe('createRefactorBuffer()', () => {\n    it('should create refactor buffer', async () => {\n      let winid = await nvim.call('win_getid') as number\n      let buf = await refactor.createRefactorBuffer()\n      let curr = await nvim.call('win_getid')\n      expect(curr).toBeGreaterThan(winid)\n      let valid = await buf.valid\n      expect(valid).toBe(true)\n      buf = await refactor.createRefactorBuffer('vim')\n      valid = await buf.valid\n      expect(valid).toBe(true)\n    })\n\n    it('should use conceal for line numbers', async () => {\n      let buf = await refactor.createRefactorBuffer(undefined, true)\n      let fileItem: FileItemDef = {\n        filepath: __filename,\n        ranges: [{ start: 10, end: 11 }, { start: 15, end: 20 }]\n      }\n      await buf.addFileItems([fileItem])\n      let arr = await nvim.call('getmatches') as any[]\n      arr = arr.filter(o => o.group == 'Conceal')\n      expect(arr.length).toBeGreaterThan(0)\n      await buf.addFileItems([{\n        filepath: __filename,\n        ranges: [{ start: 1, end: 3 }]\n      }])\n      await nvim.command('normal! ggdG')\n      let doc = workspace.getDocument(buf.bufnr)\n      await doc.synchronize()\n      let b = nvim.createBuffer(buf.bufnr)\n      let res = await b.getVar('line_infos')\n      expect(res).toEqual({})\n    })\n  })\n\n  describe('splitOpen()', () => {\n    async function setup(): Promise<RefactorBuffer> {\n      let buf = await refactor.createRefactorBuffer()\n      let fileItem: FileItemDef = {\n        filepath: __filename,\n        ranges: [{ start: 10, end: 11 }, { start: 15, end: 20 }]\n      }\n      await buf.addFileItems([fileItem])\n      await nvim.call('cursor', [5, 1])\n      return buf\n    }\n\n    it('should jump to position by <CR>', async () => {\n      let buf = await setup()\n      await buf.splitOpen()\n      let line = await nvim.eval('line(\".\")')\n      let bufname = await nvim.eval('bufname(\"%\")')\n      expect(bufname).toMatch('refactor.test.ts')\n      expect(line).toBe(11)\n    })\n\n    it('should jump split window when original window not valid', async () => {\n      let win = await nvim.window\n      let buf = await setup()\n      await nvim.call('nvim_win_close', [win.id, true])\n      await buf.splitOpen()\n      let line = await nvim.eval('line(\".\")')\n      let bufname = await nvim.eval('bufname(\"%\")')\n      expect(bufname).toMatch('refactor.test.ts')\n      expect(line).toBe(11)\n    })\n  })\n\n  describe('showMenu()', () => {\n    async function setup(): Promise<RefactorBuffer> {\n      let buf = await refactor.createRefactorBuffer()\n      let fileItem: FileItemDef = {\n        filepath: __filename,\n        ranges: [{ start: 10, end: 11 }, { start: 15, end: 20 }]\n      }\n      await buf.addFileItems([fileItem])\n      await nvim.call('cursor', [5, 1])\n      return buf\n    }\n\n    it('should do nothing when cancelled or range not found', async () => {\n      let buf = await setup()\n      let p = buf.showMenu()\n      await helper.waitPrompt()\n      await nvim.input('<esc>')\n      await p\n      let bufnr = await nvim.call('bufnr', ['%'])\n      expect(bufnr).toBe(buf.bufnr)\n      await nvim.call('cursor', [1, 1])\n      p = buf.showMenu()\n      await helper.waitPrompt()\n      await nvim.input('1')\n      await p\n      bufnr = await nvim.call('bufnr', ['%'])\n      expect(bufnr).toBe(buf.bufnr)\n    })\n\n    it('should open file in new tab', async () => {\n      let buf = await setup()\n      await nvim.call('cursor', [4, 1])\n      let p = buf.showMenu()\n      await helper.waitPrompt()\n      await nvim.input('1')\n      await p\n      let nr = await nvim.call('tabpagenr')\n      expect(nr).toBe(2)\n      let lnum = await nvim.call('line', ['.'])\n      expect(lnum).toBe(11)\n    })\n\n    it('should remove current block', async () => {\n      let buf = await setup()\n      await nvim.call('cursor', [4, 1])\n      let p = buf.showMenu()\n      await helper.waitPrompt()\n      await nvim.input('2')\n      await p\n      let items = buf.fileItems\n      expect(items[0].ranges.length).toBe(1)\n      await assertSynchronized(buf)\n    })\n  })\n\n  describe('saveRefactor()', () => {\n    it('should adjust line ranges after change', async () => {\n      let filename = await createTmpFile('foo\\n\\nbar\\n')\n      let fileItem: FileItemDef = {\n        filepath: filename,\n        ranges: [{ start: 0, end: 1 }, { start: 2, end: 3 }]\n      }\n      let buf = await refactor.createRefactorBuffer()\n      const getRanges = () => {\n        let items = buf.fileItems\n        let item = items.find(o => o.filepath == filename)\n        return item.ranges.map(o => {\n          return [o.start, o.start + o.lines.length]\n        })\n      }\n      await buf.addFileItems([fileItem, {\n        filepath: __filename,\n        ranges: [{ start: 1, end: 5 }]\n      }])\n      expect(getRanges()).toEqual([[0, 1], [2, 3]])\n      nvim.pauseNotification()\n      nvim.call('setline', [5, ['xyoo']], true)\n      nvim.command('undojoin', true)\n      nvim.call('append', [5, ['de']], true)\n      nvim.command('undojoin', true)\n      nvim.call('setline', [9, ['b']], true)\n      await nvim.resumeNotification()\n      let doc = workspace.getDocument(buf.bufnr)\n      await doc.synchronize()\n      let res = await helper.doAction('saveRefactor', doc.bufnr)\n      expect(res).toBe(true)\n      expect(getRanges()).toEqual([[0, 2], [3, 4]])\n      let content = fs.readFileSync(filename, 'utf8')\n      expect(content).toBe('xyoo\\nde\\n\\nb\\n')\n    })\n\n    it('should not save when no change made', async () => {\n      let buf = await refactor.createRefactorBuffer()\n      let fileItem: FileItemDef = {\n        filepath: __filename,\n        ranges: [{ start: 10, end: 11 }, { start: 15, end: 20 }]\n      }\n      await buf.addFileItems([fileItem])\n      let res = await buf.save()\n      expect(res).toBe(false)\n    })\n\n    it('should sync buffer change to file', async () => {\n      let doc = await helper.createDocument()\n      await doc.buffer.replace(['foo', 'bar', 'line'], 0)\n      await helper.wait(30)\n      let filename = URI.parse(doc.uri).fsPath\n      let fileItem: FileItemDef = {\n        filepath: filename,\n        ranges: [{ start: 0, end: 2 }]\n      }\n      let buf = await refactor.createRefactorBuffer()\n      await buf.addFileItems([fileItem])\n      await nvim.call('setline', [5, 'changed'])\n      let res = await buf.save()\n      expect(res).toBe(true)\n      expect(fs.existsSync(filename)).toBe(true)\n      let content = fs.readFileSync(filename, 'utf8')\n      let lines = content.split('\\n')\n      expect(lines).toEqual(['changed', 'bar', 'line', ''])\n      fs.unlinkSync(filename)\n    })\n  })\n\n  describe('doRefactor', () => {\n    let disposable: Disposable\n\n    afterEach(() => {\n      if (disposable) disposable.dispose()\n      disposable = null\n    })\n\n    it('should throw when rename provider not found', async () => {\n      await helper.createDocument()\n      let err\n      try {\n        await refactor.doRefactor()\n      } catch (e) {\n        err = e\n      }\n      expect(err).toBeDefined()\n    })\n\n    it('should show message when prepare failed', async () => {\n      await helper.createDocument()\n      disposable = languages.registerRenameProvider(['*'], {\n        prepareRename: () => {\n          return undefined\n        },\n        provideRenameEdits: () => {\n          return null\n        }\n      })\n      await helper.doAction('refactor')\n      let res = await helper.getCmdline()\n      expect(res).toMatch(/Error/)\n    })\n\n    it('should show message when returned edits is null', async () => {\n      await helper.createDocument()\n      disposable = languages.registerRenameProvider(['*'], {\n        provideRenameEdits: () => {\n          return null\n        }\n      })\n      await refactor.doRefactor()\n      let res = await helper.getCmdline()\n      expect(res).toMatch(/returns null/)\n    })\n\n    it('should open refactor window when edits is valid', async () => {\n      let filepath = __filename\n      disposable = languages.registerRenameProvider(['*'], {\n        provideRenameEdits: () => {\n          let changes = {\n            [URI.file(filepath).toString()]: [{\n              range: Range.create(0, 0, 0, 6),\n              newText: ''\n            }, {\n              range: Range.create(1, 0, 1, 6),\n              newText: ''\n            }]\n          }\n          let edit: WorkspaceEdit = { changes }\n          return edit\n        }\n      })\n      await helper.createDocument(filepath)\n      let winid = await nvim.call('win_getid') as number\n      await refactor.doRefactor()\n      let currWin = await nvim.call('win_getid') as number\n      expect(currWin - winid).toBeGreaterThan(0)\n      let bufnr = await nvim.call('bufnr', ['%']) as number\n      let b = refactor.getBuffer(bufnr)\n      expect(b).toBeDefined()\n    })\n  })\n\n  describe('search', () => {\n    it('should open refactor buffer from search result', async () => {\n      let escaped = await nvim.call('fnameescape', [__dirname])\n      await nvim.command(`cd ${escaped}`)\n      await helper.createDocument()\n      await refactor.search(['registerRenameProvider'])\n      let buf = await nvim.buffer\n      let name = await buf.name\n      expect(name).toMatch(/__coc_refactor__/)\n      let lines = await buf.lines\n      expect(lines[0]).toMatch(/Save current buffer/)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/rename.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationToken, Disposable, Position, Range, TextEdit } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport commands from '../../commands'\nimport Rename from '../../handler/rename'\nimport languages from '../../languages'\nimport { disposeAll } from '../../util'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nlet rename: Rename\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  rename = helper.plugin.getHandler().rename\n})\n\nfunction getWordRangeAtPosition(doc: TextDocument, position: Position): Range | null {\n  let lines = doc.getText().split(/\\r?\\n/)\n  let line = lines[position.line]\n  if (line.length == 0 || position.character >= line.length) return null\n  if (!/\\w/.test(line[position.character])) return null\n  let start = position.character\n  let end = position.character + 1\n  if (!/\\w/.test(line[start])) {\n    return Range.create(position, { line: position.line, character: position.character + 1 })\n  }\n  while (start >= 0) {\n    let ch = line[start - 1]\n    if (!ch || !/\\w/.test(ch)) break\n    start = start - 1\n  }\n  while (end <= line.length) {\n    let ch = line[end]\n    if (!ch || !/\\w/.test(ch)) break\n    end = end + 1\n  }\n  return Range.create(position.line, start, position.line, end)\n}\n\nfunction getSymbolRanges(textDocument: TextDocument, word: string): Range[] {\n  let res: Range[] = []\n  let str = ''\n  let content = textDocument.getText()\n  for (let i = 0, l = content.length; i < l; i++) {\n    let ch = content[i]\n    if ('-' == ch && str.length == 0) {\n      continue\n    }\n    let isKeyword = /\\w/.test(ch)\n    if (isKeyword) {\n      str = str + ch\n    }\n    if (str.length > 0 && !isKeyword && str == word) {\n      res.push(Range.create(textDocument.positionAt(i - str.length), textDocument.positionAt(i)))\n    }\n    if (!isKeyword) {\n      str = ''\n    }\n  }\n  return res\n}\n\nbeforeEach(() => {\n  disposables.push(languages.registerRenameProvider([{ language: 'javascript' }], {\n    provideRenameEdits: (doc, position: Position, newName: string) => {\n      let range = getWordRangeAtPosition(doc, position)\n      if (range) {\n        let word = doc.getText(range)\n        if (word) {\n          let ranges = getSymbolRanges(doc, word)\n          return {\n            changes: {\n              [doc.uri]: ranges.map(o => TextEdit.replace(o, newName))\n            }\n          }\n        }\n      }\n      return undefined\n    },\n    prepareRename: (doc, position) => {\n      let range = getWordRangeAtPosition(doc, position)\n      return range ? { range, placeholder: doc.getText(range) } : null\n    }\n  }))\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  await helper.reset()\n  disposeAll(disposables)\n  disposables = []\n})\n\ndescribe('rename handler', () => {\n  describe('getWordEdit', () => {\n    it('should not throw when provider not found', async () => {\n      await helper.edit()\n      let res = await helper.doAction('getWordEdit')\n      expect(res).toBe(null)\n    })\n\n    it('should use document symbols when prepare failed', async () => {\n      let doc = await helper.createDocument('t.js')\n      await nvim.setLine('a')\n      await doc.synchronize()\n      let res = await rename.getWordEdit()\n      expect(res != null).toBe(true)\n    })\n\n    it('should return workspace edit', async () => {\n      let doc = await helper.createDocument('t.js')\n      await nvim.setLine('foo foo')\n      await doc.synchronize()\n      let res = await rename.getWordEdit()\n      expect(res).toBeDefined()\n      expect(res.changes[doc.uri].length).toBe(2)\n    })\n\n    it('should extract words from buffer', async () => {\n      let doc = await helper.createDocument('t')\n      await nvim.setLine('你 你 你')\n      await doc.synchronize()\n      let res = await rename.getWordEdit()\n      expect(res).toBeDefined()\n      expect(res.changes[doc.uri].length).toBe(3)\n    })\n  })\n\n  describe('rename', () => {\n    it('should throw when provider not found', async () => {\n      await helper.edit()\n      await expect(async () => {\n        await helper.doAction('rename', 'foo')\n      }).rejects.toThrow(Error)\n    })\n\n    it('should return false for invalid position', async () => {\n      let doc = await helper.createDocument('t.js')\n      let res = await commands.executeCommand('editor.action.rename', [doc.uri, Position.create(0, 0)])\n      expect(res).toBe(false)\n    })\n\n    it('should use newName from placeholder', async () => {\n      let doc = await helper.createDocument('t.js')\n      await nvim.setLine('foo foo foo')\n      let p = commands.executeCommand('editor.action.rename', doc.uri, Position.create(0, 0))\n      await helper.waitFloat()\n      await nvim.input('<C-u>')\n      await helper.wait(10)\n      await nvim.input('bar')\n      await nvim.input('<cr>')\n      await p\n      let line = await nvim.line\n      expect(line).toBe('bar bar bar')\n    })\n\n    it('should renameCurrentWord by cursors', async () => {\n      await commands.executeCommand('document.renameCurrentWord')\n      let line = await helper.getCmdline()\n      expect(line).toMatch('Invalid position')\n      let doc = await helper.createDocument('t.js')\n      await nvim.setLine('foo foo foo')\n      await commands.executeCommand('document.renameCurrentWord')\n      let ns = await nvim.createNamespace('coc-cursors')\n      let markers = await doc.buffer.getExtMarks(ns, 0, -1)\n      expect(markers.length).toBe(3)\n    })\n\n    it('should return false for empty name', async () => {\n      helper.updateConfiguration('coc.preferences.renameFillCurrent', false)\n      await helper.createDocument('t.js')\n      await nvim.setLine('foo foo foo')\n      let p = rename.rename()\n      await helper.waitFloat()\n      await nvim.input('<C-u>')\n      await helper.wait(10)\n      await nvim.input('<cr>')\n      let res = await p\n      expect(res).toBe(false)\n    })\n\n    it('should not throw when provideRenameEdits throws', async () => {\n      disposables.push(languages.registerRenameProvider([{ language: '*' }], {\n        provideRenameEdits: () => {\n          throw new Error('error')\n        },\n      }))\n      let doc = await workspace.document\n      let res = await languages.provideRenameEdits(doc.textDocument, Position.create(0, 0), 'newName', CancellationToken.None)\n      expect(res).toBeNull()\n    })\n\n    it('should use newName from range', async () => {\n      disposables.push(languages.registerRenameProvider([{ language: '*' }], {\n        provideRenameEdits: (doc, position: Position, newName: string) => {\n          let range = getWordRangeAtPosition(doc, position)\n          if (range) {\n            let word = doc.getText(range)\n            if (word) {\n              let ranges = getSymbolRanges(doc, word)\n              return {\n                changes: {\n                  [doc.uri]: ranges.map(o => TextEdit.replace(o, newName))\n                }\n              }\n            }\n          }\n          return undefined\n        },\n        prepareRename: (doc, position) => {\n          let range = getWordRangeAtPosition(doc, position)\n          return range ? range : null\n        }\n      }))\n      await helper.createDocument()\n      await nvim.setLine('foo foo foo')\n      let p = rename.rename()\n      await helper.waitFloat()\n      await nvim.input('<C-u>')\n      await helper.wait(10)\n      await nvim.input('bar')\n      await nvim.input('<cr>')\n      let res = await p\n      expect(res).toBe(true)\n      await helper.waitFor('getline', ['.'], 'bar bar bar')\n    })\n\n    it('should use newName from cword', async () => {\n      disposables.push(languages.registerRenameProvider([{ language: '*' }], {\n        provideRenameEdits: (doc, position: Position, newName: string) => {\n          let range = getWordRangeAtPosition(doc, position)\n          if (range) {\n            let word = doc.getText(range)\n            if (word) {\n              let ranges = getSymbolRanges(doc, word)\n              return {\n                changes: {\n                  [doc.uri]: ranges.map(o => TextEdit.replace(o, newName))\n                }\n              }\n            }\n          }\n          return undefined\n        }\n      }))\n      await helper.createDocument()\n      await nvim.setLine('foo foo foo')\n      let p = rename.rename()\n      await helper.waitFloat()\n      await nvim.input('<C-u>')\n      await helper.wait(10)\n      await nvim.input('bar')\n      await nvim.input('<cr>')\n      let res = await p\n      expect(res).toBe(true)\n      let line = await nvim.getLine()\n      expect(line).toBe('bar bar bar')\n    })\n\n    it('should return false when result is empty', async () => {\n      disposables.push(languages.registerRenameProvider([{ language: '*' }], {\n        provideRenameEdits: () => {\n          return null\n        }\n      }))\n      await helper.createDocument()\n      await nvim.setLine('foo foo foo')\n      let p = rename.rename()\n      await helper.waitFloat()\n      await nvim.input('<C-u>')\n      await helper.wait(10)\n      await nvim.input('bar')\n      await nvim.input('<cr>')\n      let res = await p\n      expect(res).toBe(false)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/search.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport Refactor from '../../handler/refactor'\nimport Search, { getPathFromArgs } from '../../handler/refactor/search'\nimport helper from '../helper'\nimport path from 'path'\n\nlet nvim: Neovim\nlet refactor: Refactor\n// use fake rg command\nlet cmd = path.resolve(__dirname, '../rg')\nlet cwd = process.cwd()\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  refactor = helper.plugin.getHandler().refactor\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  refactor.reset()\n  await helper.reset()\n})\n\ndescribe('getPathFromArgs', () => {\n  it('should get undefined path', async () => {\n    let res = getPathFromArgs(['a'])\n    expect(res).toBeUndefined()\n    res = getPathFromArgs(['a', 'b', '-c'])\n    expect(res).toBeUndefined()\n    res = getPathFromArgs(['a', '-b', 'c'])\n    expect(res).toBeUndefined()\n  })\n})\n\ndescribe('search', () => {\n\n  it('should open refactor window', async () => {\n    let search = new Search(nvim, cmd)\n    let buf = await refactor.createRefactorBuffer()\n    await search.run([], cwd, buf)\n    await helper.wait(50)\n    let fileItems = buf.fileItems\n    expect(fileItems.length).toBe(2)\n    expect(fileItems[0].ranges.length).toBe(2)\n  })\n\n  it('should abort task', async () => {\n    let search = new Search(nvim, cmd)\n    let buf = await refactor.createRefactorBuffer()\n    let p = search.run(['--sleep', '1000'], cwd, buf)\n    search.abort()\n    await p\n    let fileItems = buf.fileItems\n    expect(fileItems.length).toBe(0)\n  })\n\n  it('should work with CocAction search', async () => {\n    await helper.doAction('search', ['CocAction'])\n    let bufnr = await nvim.call('bufnr', ['%']) as number\n    let buf = refactor.getBuffer(bufnr)\n    expect(buf).toBeDefined()\n  })\n\n  it('should fail on invalid command', async () => {\n    let search = new Search(nvim, 'rrg')\n    let buf = await refactor.createRefactorBuffer()\n    let err\n    try {\n      await search.run([], cwd, buf)\n    } catch (e) {\n      err = e\n    }\n    expect(err).toBeDefined()\n    let msg = await helper.getCmdline()\n    expect(msg).toMatch(/Error on command \"rrg\"/)\n  })\n\n  it('should show empty result when no result found', async () => {\n    await helper.doAction('search', ['should found ' + ' no result'])\n    let bufnr = await nvim.call('bufnr', ['%']) as number\n    let buf = refactor.getBuffer(bufnr)\n    expect(buf).toBeDefined()\n    let buffer = await nvim.buffer\n    let lines = await buffer.lines\n    expect(lines[1]).toMatch(/No match found/)\n  })\n\n  it('should use current search folder for rg', async () => {\n    let search = new Search(nvim, 'rg')\n    await helper.createDocument()\n    let buf = await refactor.createRefactorBuffer()\n    await search.run(['-w', 'createRefactorBuffer', 'src/__tests__'], cwd, buf)\n    let buffer = await nvim.buffer\n    let lines = await buffer.lines\n    expect(lines[1].startsWith('Files: ')).toBe(true)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/selectionRange.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationToken, Disposable, Position, Range, TextEdit } from 'vscode-languageserver-protocol'\nimport SelectionRange from '../../handler/selectionRange'\nimport languages from '../../languages'\nimport workspace from '../../workspace'\nimport window from '../../window'\nimport { disposeAll } from '../../util'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nlet selection: SelectionRange\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  selection = helper.plugin.getHandler().selectionRange\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  await helper.reset()\n  disposeAll(disposables)\n})\n\ndescribe('selectionRange', () => {\n  describe('getSelectionRanges()', () => {\n    it('should throw error when selectionRange provider does not exist', async () => {\n      let doc = await helper.createDocument()\n      await doc.synchronize()\n      await expect(async () => {\n        await helper.doAction('selectionRanges')\n      }).rejects.toThrow(Error)\n    })\n\n    it('should return ranges', async () => {\n      await helper.createDocument()\n      disposables.push(languages.registerSelectionRangeProvider([{ language: '*' }], {\n        provideSelectionRanges: _doc => {\n          return [{\n            range: Range.create(0, 0, 0, 1)\n          }]\n        }\n      }))\n      let res = await selection.getSelectionRanges()\n      expect(res).toBeDefined()\n      expect(Array.isArray(res)).toBe(true)\n    })\n  })\n\n  describe('selectRange()', () => {\n    async function getSelectedRange(): Promise<Range> {\n      let m = await nvim.mode\n      expect(m.mode).toBe('v')\n      await nvim.input('<esc>')\n      let res = await window.getSelectedRange('v')\n      return res\n    }\n\n    it('should not select with empty ranges', async () => {\n      let doc = await helper.createDocument()\n      disposables.push(languages.registerSelectionRangeProvider([{ language: '*' }], {\n        provideSelectionRanges: () => []\n      }))\n      await doc.synchronize()\n      let res = await selection.selectRange('', true)\n      expect(res).toBe(false)\n    })\n\n    it('should select single range', async () => {\n      let doc = await helper.createDocument()\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo\\nbar\\ntest\\n')])\n      disposables.push(languages.registerSelectionRangeProvider([{ language: '*' }], {\n        provideSelectionRanges: () => [{ range: Range.create(0, 0, 0, 3) }]\n      }))\n      await doc.synchronize()\n      let res = await selection.selectRange('', true)\n      expect(res).toBe(true)\n    })\n\n    it('should select ranges forward', async () => {\n      let doc = await helper.createDocument()\n      let called = 0\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo\\nbar\\ntest\\n')])\n      await nvim.call('cursor', [1, 1])\n      await doc.synchronize()\n      disposables.push(languages.registerSelectionRangeProvider([{ language: '*' }], {\n        provideSelectionRanges: _doc => {\n          called += 1\n          let arr = [{\n            range: Range.create(0, 0, 0, 1)\n          }, {\n            range: Range.create(0, 0, 0, 3)\n          }, {\n            range: Range.create(0, 0, 1, 3)\n          }]\n          return arr\n        }\n      }))\n      await doc.synchronize()\n      await helper.doAction('rangeSelect', '', false)\n      await selection.selectRange('', true)\n      expect(called).toBe(1)\n      let res = await getSelectedRange()\n      expect(res).toEqual(Range.create(0, 0, 0, 1))\n      await selection.selectRange('v', true)\n      expect(called).toBe(2)\n      res = await getSelectedRange()\n      expect(res).toEqual(Range.create(0, 0, 0, 3))\n      await selection.selectRange('v', true)\n      expect(called).toBe(3)\n      res = await getSelectedRange()\n      expect(res).toEqual(Range.create(0, 0, 1, 3))\n      await selection.selectRange('v', true)\n      expect(called).toBe(4)\n      let m = await nvim.mode\n      expect(m.mode).toBe('n')\n    })\n\n    it('should select ranges backward', async () => {\n      let doc = await helper.createDocument()\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo\\nbar\\ntest\\n')])\n      await nvim.call('cursor', [1, 1])\n      disposables.push(languages.registerSelectionRangeProvider([{ language: '*' }], {\n        provideSelectionRanges: _doc => {\n          let arr = [{\n            range: Range.create(0, 0, 0, 1)\n          }, {\n            range: Range.create(0, 0, 0, 3)\n          }, {\n            range: Range.create(0, 0, 1, 3)\n          }]\n          return arr\n        }\n      }))\n      await doc.synchronize()\n      await selection.selectRange('', true)\n      let mode = await nvim.call('mode')\n      expect(mode).toBe('v')\n      await nvim.input('<esc>')\n      await window.selectRange(Range.create(0, 0, 1, 3))\n      await nvim.input('<esc>')\n      await selection.selectRange('v', false)\n      let r = await getSelectedRange()\n      expect(r).toEqual(Range.create(0, 0, 0, 3))\n      await nvim.input('<esc>')\n      await selection.selectRange('v', false)\n      r = await getSelectedRange()\n      expect(r).toEqual(Range.create(0, 0, 0, 1))\n      await nvim.input('<esc>')\n      await selection.selectRange('v', false)\n      mode = await nvim.call('mode')\n      expect(mode).toBe('n')\n    })\n  })\n\n  describe('provideSelectionRanges()', () => {\n    it('should return null when no provider available', async () => {\n      let doc = await workspace.document\n      let res = await languages.getSelectionRanges(doc.textDocument, [Position.create(0, 0)], CancellationToken.None)\n      expect(res).toBeNull()\n    })\n\n    it('should return null when no result available', async () => {\n      disposables.push(languages.registerSelectionRangeProvider([{ language: '*' }], {\n        provideSelectionRanges: _doc => {\n          return []\n        }\n      }))\n      let doc = await workspace.document\n      let res = await languages.getSelectionRanges(doc.textDocument, [Position.create(0, 0)], CancellationToken.None)\n      expect(res).toBeNull()\n    })\n\n    it('should append/prepend selection ranges', async () => {\n      let doc = await workspace.document\n      disposables.push(languages.registerSelectionRangeProvider([{ language: '*' }], {\n        provideSelectionRanges: _doc => {\n          return [{ range: Range.create(1, 1, 1, 4) }, { range: Range.create(1, 0, 1, 6) }]\n        }\n      }))\n      disposables.push(languages.registerSelectionRangeProvider([{ language: '*' }], {\n        provideSelectionRanges: _doc => {\n          return [{ range: Range.create(1, 2, 1, 3) }]\n        }\n      }))\n      disposables.push(languages.registerSelectionRangeProvider([{ language: '*' }], {\n        provideSelectionRanges: _doc => {\n          return [{ range: Range.create(1, 2, 1, 3) }]\n        }\n      }))\n      disposables.push(languages.registerSelectionRangeProvider([{ language: '*' }], {\n        provideSelectionRanges: _doc => {\n          return [{ range: Range.create(0, 0, 3, 0) }]\n        }\n      }))\n\n      let res = await languages.getSelectionRanges(doc.textDocument, [Position.create(0, 0)], CancellationToken.None)\n      expect(res.length).toBe(4)\n      expect(res[0].range).toEqual(Range.create(1, 2, 1, 3))\n      expect(res[3].range).toEqual(Range.create(0, 0, 3, 0))\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/semanticTokens.test.ts",
    "content": "import { Buffer, Neovim } from '@chemzqm/neovim'\nimport fs from 'fs'\nimport { tmpdir } from 'os'\nimport path from 'path'\nimport { CancellationToken, CancellationTokenSource, Disposable, Position, Range, SemanticTokensLegend, TextEdit } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport commandManager from '../../commands'\nimport events from '../../events'\nimport SemanticTokensBuffer, { NAMESPACE, toHighlightPart } from '../../handler/semanticTokens/buffer'\nimport SemanticTokens from '../../handler/semanticTokens/index'\nimport languages from '../../languages'\nimport { disposeAll } from '../../util'\nimport { CancellationError } from '../../util/errors'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport helper, { createTmpFile } from '../helper'\n\nconst tempDir = fs.mkdtempSync(path.join(tmpdir(), 'coc'))\n\nlet nvim: Neovim\nlet ns: number\nlet disposables: Disposable[] = []\nlet semanticTokens: SemanticTokens\nlet legend: SemanticTokensLegend = {\n  tokenTypes: [\n    \"comment\",\n    \"keyword\",\n    \"string\",\n    \"number\",\n    \"regexp\",\n    \"operator\",\n    \"namespace\",\n    \"type\",\n    \"struct\",\n    \"class\",\n    \"interface\",\n    \"enum\",\n    \"enumMember\",\n    \"typeParameter\",\n    \"function\",\n    \"method\",\n    \"property\",\n    \"macro\",\n    \"variable\",\n    \"parameter\",\n    \"angle\",\n    \"arithmetic\",\n    \"attribute\",\n    \"bitwise\",\n    \"boolean\",\n    \"brace\",\n    \"bracket\",\n    \"builtinType\",\n    \"character\",\n    \"colon\",\n    \"comma\",\n    \"comparison\",\n    \"constParameter\",\n    \"dot\",\n    \"escapeSequence\",\n    \"formatSpecifier\",\n    \"generic\",\n    \"label\",\n    \"lifetime\",\n    \"logical\",\n    \"operator\",\n    \"parenthesis\",\n    \"punctuation\",\n    \"selfKeyword\",\n    \"semicolon\",\n    \"typeAlias\",\n    \"union\",\n    \"unresolvedReference\"\n  ],\n  tokenModifiers: [\n    \"documentation\",\n    \"declaration\",\n    \"definition\",\n    \"static\",\n    \"abstract\",\n    \"deprecated\",\n    \"readonly\",\n    \"constant\",\n    \"controlFlow\",\n    \"injected\",\n    \"mutable\",\n    \"consuming\",\n    \"async\",\n    \"library\",\n    \"public\",\n    \"unsafe\",\n    \"attribute\",\n    \"trait\",\n    \"callable\",\n    \"intraDocLink\"\n  ]\n}\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  ns = await nvim.createNamespace('coc-semanticTokens')\n  semanticTokens = helper.plugin.getHandler().semanticHighlighter\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  await helper.reset()\n  disposeAll(disposables)\n  semanticTokens.setStaticConfiguration()\n})\n\nconst defaultResult = {\n  resultId: '1',\n  data: [\n    0, 0, 2, 1, 0,\n    0, 3, 4, 14, 2,\n    0, 4, 1, 41, 0,\n    0, 1, 1, 41, 3,\n    0, 2, 1, 25, 0,\n    1, 4, 8, 17, 0,\n    0, 8, 1, 41, 0,\n    0, 1, 3, 2, 0,\n    0, 3, 1, 41, 0,\n    0, 1, 1, 44, 0,\n    1, 0, 1, 25, 0,\n  ]\n}\n\nasync function waitRefresh(tokenBuffer: SemanticTokensBuffer): Promise<void> {\n  return new Promise((resolve, reject) => {\n    let timer = setTimeout(() => {\n      disposable.dispose()\n      reject(new Error(`Timeout after 500ms`))\n    }, 500)\n    let disposable = tokenBuffer.onDidRefresh(() => {\n      disposable.dispose()\n      clearTimeout(timer)\n      resolve()\n    })\n  })\n}\n\nfunction registerRangeProvider(filetype: string, fn: (range: Range) => number[]): Disposable {\n  return languages.registerDocumentRangeSemanticTokensProvider([{ language: filetype }], {\n    provideDocumentRangeSemanticTokens: (_, range) => {\n      return {\n        data: fn(range)\n      }\n    }\n  }, legend)\n}\n\nfunction registerProvider(): void {\n  disposables.push(languages.registerDocumentSemanticTokensProvider([{ language: 'rust' }], {\n    provideDocumentSemanticTokens: () => {\n      return defaultResult\n    },\n    provideDocumentSemanticTokensEdits: (_, previousResultId) => {\n      if (previousResultId !== '1') return undefined\n      return {\n        resultId: '2',\n        edits: [{\n          start: 0,\n          deleteCount: 0,\n          data: [0, 0, 3, 1, 0]\n        }]\n      }\n    }\n  }, legend))\n}\n\nasync function createRustBuffer(enableProvider = true): Promise<Buffer> {\n  helper.updateConfiguration('semanticTokens.filetypes', ['rust'])\n  if (enableProvider) registerProvider()\n  await helper.wait(2)\n  let doc = await workspace.document\n  let code = `fn main() {\n    println!(\"H\");\n}`\n  let buf = await nvim.buffer\n  doc.setFiletype('rust')\n  await buf.setLines(code.split('\\n'), { start: 0, end: -1, strictIndexing: false })\n  await doc.patchChange()\n  return buf\n}\n\ndescribe('semanticTokens', () => {\n  describe('toHighlightPart()', () => {\n    it('should convert to highlight part', () => {\n      expect(toHighlightPart('')).toBe('')\n      expect(toHighlightPart('token')).toBe('Token')\n      expect(toHighlightPart('is key word')).toBe('Is_key_word')\n      expect(toHighlightPart('token')).toBe('Token')\n    })\n  })\n\n  describe('Provider', () => {\n    it('should not throw when buffer item not found', async () => {\n      await events.fire('CursorMoved', [9])\n      await events.fire('BufWinEnter', [9])\n    })\n\n    it('should return null when range provider not exists', async () => {\n      let doc = await workspace.document\n      let res = await languages.provideDocumentRangeSemanticTokens(doc.textDocument, Range.create(0, 0, 1, 0), CancellationToken.None)\n      expect(res).toBeNull()\n    })\n\n    it('should return false when not hasSemanticTokensEdits', async () => {\n      let doc = await workspace.document\n      let res = languages.hasSemanticTokensEdits(doc.textDocument)\n      expect(res).toBe(false)\n    })\n\n    it('should return null when semanticTokens provider not exists', async () => {\n      let token = CancellationToken.None\n      let doc = await workspace.document\n      let res = await languages.provideDocumentSemanticTokens(doc.textDocument, token)\n      expect(res).toBeNull()\n      let r = await languages.provideDocumentSemanticTokensEdits(doc.textDocument, '', token)\n      expect(r).toBeNull()\n    })\n  })\n\n  describe('showHighlightInfo()', () => {\n    it('should show error when not enabled', async () => {\n      await nvim.command('enew')\n      let doc = await workspace.document\n      let winid = await nvim.call('win_getid') as number\n      let item = semanticTokens.getItem(doc.bufnr)\n      await item.onCursorHold(winid, 1)\n      await semanticTokens.inspectSemanticToken()\n      let line = await helper.getCmdline()\n      expect(line).toMatch('not enabled')\n    })\n\n    it('should show error message for buffer not attached', async () => {\n      await nvim.command(`edit +setl\\\\ buftype=nofile foo`)\n      await helper.doAction('inspectSemanticToken')\n      let msg = await helper.getCmdline()\n      expect(msg).toMatch(/not attached/)\n    })\n\n    it('should show message when not enabled', async () => {\n      await helper.edit('t.txt')\n      await helper.doAction('showSemanticHighlightInfo')\n      let buf = await nvim.buffer\n      let lines = await buf.lines\n      expect(lines[2]).toMatch('not enabled for current filetype')\n    })\n\n    it('should show semantic tokens info', async () => {\n      await createRustBuffer()\n      await semanticTokens.highlightCurrent()\n      await commandManager.executeCommand('semanticTokens.checkCurrent')\n      let buf = await nvim.buffer\n      let lines = await buf.lines\n      let content = lines.join('\\n')\n      expect(content).toMatch('Semantic highlight groups used by current buffer')\n    })\n\n    it('should show highlight info for empty legend', async () => {\n      helper.updateConfiguration('semanticTokens.filetypes', ['*'])\n      disposables.push(languages.registerDocumentRangeSemanticTokensProvider([{ language: '*' }], {\n        provideDocumentRangeSemanticTokens: (_, range) => {\n          return {\n            data: []\n          }\n        }\n      }, { tokenModifiers: [], tokenTypes: [] }))\n      await semanticTokens.showHighlightInfo()\n      let buf = await nvim.buffer\n      let lines = await buf.lines\n      let content = lines.join('\\n')\n      expect(content).toMatch('No token')\n    })\n  })\n\n  describe('highlightCurrent()', () => {\n    it('should only highlight limited range on update', async () => {\n      helper.updateConfiguration('semanticTokens.filetypes', ['vim'])\n      let doc = await helper.createDocument('t.vim')\n      let called = false\n      disposables.push(languages.registerDocumentSemanticTokensProvider([{ language: 'vim' }], {\n        provideDocumentSemanticTokens: (doc, token) => {\n          let text = doc.getText()\n          if (!text.trim()) {\n            return Promise.resolve({ resultId: '1', data: [] })\n          }\n          let lines = text.split('\\n')\n          let data = [0, 0, 1, 1, 0]\n          for (let i = 0; i < lines.length; i++) {\n            data.push(1, 0, 1, 1, 0)\n          }\n          return new Promise(resolve => {\n            token.onCancellationRequested(() => {\n              clearTimeout(timer)\n              resolve(undefined)\n            })\n            let timer = setTimeout(() => {\n              called = true\n              resolve({ resultId: '1', data })\n            }, 10)\n          })\n        }\n      }, legend))\n      let item = await semanticTokens.getCurrentItem()\n      item['_dirty'] = true\n      await item.doHighlight(false, 0)\n      let newLine = 'l\\n'\n      await doc.applyEdits([{ range: Range.create(0, 0, 0, 0), newText: `${newLine.repeat(1000)}` }])\n      await item.doHighlight(false, 0)\n      await helper.waitValue(() => called, true)\n      let buf = doc.buffer\n      let markers = await buf.getExtMarks(ns, 0, -1, { details: true })\n      let len = markers.length\n      expect(len).toBeLessThan(400)\n      await nvim.call('cursor', [1, 1])\n      let winid = await nvim.call('win_getid') as number\n      await item.onWinScroll(winid)\n      await helper.waitValue(async () => {\n        let markers = await buf.getExtMarks(ns, 0, -1, { details: true })\n        return markers.length > 100\n      }, true)\n      await nvim.call('cursor', [200, 1])\n      await item.onWinScroll(winid)\n      await helper.waitValue(async () => {\n        let markers = await buf.getExtMarks(ns, 0, -1, { details: true })\n        return markers.length > 200\n      }, true)\n    })\n\n    it('should refresh highlights', async () => {\n      await createRustBuffer()\n      await nvim.command('hi link CocSemDeclarationFunction MoreMsg')\n      await nvim.command('hi link CocSemDocumentation Statement')\n      await window.moveTo({ line: 0, character: 4 })\n      await semanticTokens.highlightCurrent()\n      await commandManager.executeCommand('semanticTokens.inspect')\n      let win = await helper.getFloat()\n      let buf = await win.buffer\n      let lines = await buf.lines\n      let content = lines.join('\\n')\n      expect(content).toMatch('Type: function\\nModifiers: declaration\\nHighlight group: CocSemTypeFunction')\n      await window.moveTo({ line: 1, character: 0 })\n      await commandManager.executeCommand('semanticTokens.inspect')\n      win = await helper.getFloat()\n      expect(win).toBeUndefined()\n    })\n\n    it('should refresh highlights by command', async () => {\n      await helper.edit()\n      let err\n      try {\n        await commandManager.executeCommand('semanticTokens.refreshCurrent')\n      } catch (e) {\n        err = e\n      }\n      expect(err).toBeDefined()\n    })\n\n    it('should reuse exists tokens when version not changed', async () => {\n      let doc = await helper.createDocument('t.vim')\n      await doc.applyEdits([{ range: Range.create(0, 0, 0, 0), newText: 'let' }])\n      let times = 0\n      helper.updateConfiguration('semanticTokens.filetypes', ['vim'])\n      disposables.push(languages.registerDocumentSemanticTokensProvider([{ language: 'vim' }], {\n        provideDocumentSemanticTokens: () => {\n          times++\n          return new Promise(resolve => {\n            resolve({\n              resultId: '1',\n              data: [0, 0, 3, 1, 0]\n            })\n          })\n        }\n      }, legend))\n      let item = await semanticTokens.getCurrentItem()\n      await helper.waitValue(() => {\n        return times\n      }, 1)\n      await item.doHighlight(false, 0)\n      await item.doHighlight(false, 0)\n      expect(times).toBe(1)\n    })\n\n    it('should return null when request cancelled', async () => {\n      let doc = await helper.createDocument('t.vim')\n      let lines: string[] = []\n      for (let i = 0; i < 2000; i++) {\n        lines.push('foo')\n      }\n      await doc.applyEdits([{ range: Range.create(0, 0, 0, 0), newText: lines.join('\\n') }])\n      helper.updateConfiguration('semanticTokens.filetypes', [])\n      let cancel = true\n      let item = await semanticTokens.getCurrentItem()\n      disposables.push(languages.registerDocumentSemanticTokensProvider([{ language: 'vim' }], {\n        provideDocumentSemanticTokens: (doc, token) => {\n          return new Promise(resolve => {\n            if (cancel) {\n              process.nextTick(() => {\n                item.cancel()\n              })\n            }\n            let data = []\n            for (let i = 0; i < 2000; i++) {\n              data.push(...[i == 0 ? 0 : 1, 0, 3, 1, 1])\n            }\n            resolve({\n              resultId: '1',\n              data\n            })\n          })\n        }\n      }, legend))\n      helper.updateConfiguration('semanticTokens.filetypes', ['vim'])\n      await item.doHighlight(false, 0)\n      cancel = false\n      let spy = jest.spyOn(window, 'diffHighlights').mockImplementation(() => {\n        return Promise.resolve(null)\n      })\n      let winid = await nvim.call('win_getid') as number\n      await item.doHighlight(false, 10, winid)\n      await item.doHighlight(false, 0, winid)\n      spy.mockRestore()\n      expect(item.highlights).toBeDefined()\n      await helper.edit('bar')\n    })\n\n    it('should highlight hidden buffer on shown', async () => {\n      helper.updateConfiguration('semanticTokens.filetypes', ['rust'])\n      registerProvider()\n      await nvim.command('edit foo')\n      let code = 'fn main() {\\n  println!(\"H\"); \\n}'\n      let filepath = path.join(tempDir, 'a.rs')\n      fs.writeFileSync(filepath, code, 'utf8')\n      let uri = URI.file(filepath).toString()\n      await workspace.loadFile(uri, '')\n      let doc = workspace.getDocument(uri)\n      await nvim.command('b ' + doc.bufnr)\n      let item = semanticTokens.getItem(doc.bufnr)\n      let called = false\n      item.onDidRefresh(() => {\n        called = true\n      })\n      let buf = doc.buffer\n      expect(doc.filetype).toBe('rust')\n      await nvim.command(`b ${buf.id}`)\n      await helper.waitValue(() => {\n        return called\n      }, true)\n    })\n\n    it('should no highlights when request cancelled', async () => {\n      helper.updateConfiguration('semanticTokens.filetypes', [])\n      let doc = await workspace.document\n      let item = semanticTokens.getItem(doc.bufnr)\n      disposables.push(languages.registerDocumentRangeSemanticTokensProvider([{ language: '*' }], {\n        provideDocumentRangeSemanticTokens: () => {\n          item.cancel()\n          return null\n        }\n      }, legend))\n      let disposable = languages.registerDocumentSemanticTokensProvider([{ language: '*' }], {\n        provideDocumentSemanticTokens: (_, token) => {\n          item.cancel()\n          return null\n        }\n      }, legend)\n      helper.updateConfiguration('semanticTokens.filetypes', ['*'])\n      await item.doHighlight(true, 0)\n      expect(item.highlights).toBeUndefined()\n      disposable.dispose()\n      let winid = await nvim.call('win_getid') as number\n      await item.doHighlight(true)\n      await item.onWinScroll(winid)\n    })\n  })\n\n  describe('highlightRegions()', () => {\n    it('should refresh when buffer visible', async () => {\n      let buf = await createRustBuffer(false)\n      let doc = await workspace.document\n      let item = await semanticTokens.getCurrentItem()\n      let winid = await nvim.call('win_getid') as number\n      await item.highlightRegions(winid, CancellationToken.None)\n      await doc.synchronize()\n      expect(item.enabled).toBe(false)\n      await nvim.command('edit bar')\n      registerProvider()\n      await helper.wait(10)\n      expect(item.enabled).toBe(true)\n      await nvim.command(`b ${buf.id}`)\n      await waitRefresh(item)\n      expect(item.highlights).toBeDefined()\n      await item.highlightRegions(9999, CancellationToken.None)\n    })\n\n    it('should not highlight same region', async () => {\n      let buf = await createRustBuffer()\n      let item = semanticTokens.getItem(buf.id)\n      let winid = await nvim.call('win_getid') as number\n      await item.doHighlight(false, 0)\n      await item.highlightRegions(winid, CancellationToken.None)\n      await item.highlightRegions(winid, CancellationToken.None)\n    })\n\n    it('should highlight region on CursorHold', async () => {\n      let buf = await createRustBuffer()\n      let item = semanticTokens.getItem(buf.id)\n      let winid = await nvim.call('win_getid') as number\n      await item.doHighlight(true, 0, winid)\n      buf.clearNamespace(NAMESPACE)\n      await item.onCursorHold(winid, 1)\n      let highlights = await buf.getHighlights(NAMESPACE)\n      expect(highlights.length).toBeGreaterThan(0)\n    })\n\n    it('should cancel region highlight', async () => {\n      let buf = await createRustBuffer()\n      let item = semanticTokens.getItem(buf.id)\n      await item.doHighlight(false, 0)\n      let tokenSource = new CancellationTokenSource()\n      let spy = jest.spyOn(window, 'diffHighlights').mockImplementation(() => {\n        tokenSource.cancel()\n        return Promise.resolve(null)\n      })\n      let winid = await nvim.call('win_getid') as number\n      await item.highlightRegions(winid, tokenSource.token)\n      spy.mockRestore()\n    })\n  })\n\n  describe('requestRangeHighlights()', () => {\n    it('should return null when canceled', async () => {\n      let doc = await workspace.document\n      let item = semanticTokens.getItem(doc.bufnr)\n      let winid = await nvim.call('win_getid') as number\n      let res = await item.requestRangeHighlights(winid, undefined, CancellationToken.Cancelled)\n      expect(res).toBeNull()\n      let tokenSource = new CancellationTokenSource()\n      disposables.push(languages.registerDocumentRangeSemanticTokensProvider([{ language: '*' }], {\n        provideDocumentRangeSemanticTokens: () => {\n          tokenSource.cancel()\n          return { data: [] }\n        }\n      }, legend))\n      res = await item.requestRangeHighlights(winid, undefined, tokenSource.token)\n      expect(res).toBeNull()\n    })\n\n    it('should return null when convert tokens canceled ', async () => {\n      let doc = await workspace.document\n      let item = semanticTokens.getItem(doc.bufnr)\n      let tokenSource = new CancellationTokenSource()\n      disposables.push(languages.registerDocumentRangeSemanticTokensProvider([{ language: '*' }], {\n        provideDocumentRangeSemanticTokens: () => {\n          return { data: [1, 0, 0, 1, 0] }\n        }\n      }, legend))\n      let spy = jest.spyOn(item, 'getTokenRanges').mockImplementation(() => {\n        return Promise.resolve(null)\n      })\n      let winid = await nvim.call('win_getid') as number\n      let res = await item.requestRangeHighlights(winid, undefined, tokenSource.token)\n      expect(res).toBeNull()\n      spy.mockRestore()\n    })\n  })\n\n  describe('clear highlights', () => {\n    it('should clear highlights of current buffer', async () => {\n      await createRustBuffer()\n      await semanticTokens.highlightCurrent()\n      let buf = await nvim.buffer\n      let markers = await buf.getExtMarks(ns, 0, -1)\n      expect(markers.length).toBeGreaterThan(0)\n      await commandManager.executeCommand('semanticTokens.clearCurrent')\n      markers = await buf.getExtMarks(ns, 0, -1)\n      expect(markers.length).toBe(0)\n    })\n\n    it('should clear all highlights', async () => {\n      await createRustBuffer()\n      await semanticTokens.highlightCurrent()\n      let buf = await nvim.buffer\n      await commandManager.executeCommand('semanticTokens.clearAll')\n      let markers = await buf.getExtMarks(ns, 0, -1)\n      expect(markers.length).toBe(0)\n    })\n  })\n\n  describe('doRangeHighlight()', () => {\n    it('should invoke range provider first time when both kinds exist', async () => {\n      let called = false\n      disposables.push(registerRangeProvider('rust', () => {\n        called = true\n        return []\n      }))\n      let buf = await createRustBuffer()\n      let item = semanticTokens.getItem(buf.id)\n      await waitRefresh(item)\n      expect(called).toBe(true)\n    })\n\n    it('should do range highlight first time', async () => {\n      helper.updateConfiguration('semanticTokens.filetypes', ['vim'])\n      let r: Range\n      disposables.push(registerRangeProvider('vim', range => {\n        r = range\n        return [0, 0, 3, 1, 0]\n      }))\n      let filepath = await createTmpFile('let')\n      fs.renameSync(filepath, filepath + '.vim')\n      let doc = await helper.createDocument(filepath + '.vim')\n      let item = await semanticTokens.getCurrentItem()\n      await doc.synchronize()\n      expect(doc.filetype).toBe('vim')\n      await helper.waitValue(() => {\n        return typeof r !== 'undefined'\n      }, true)\n      let winid = await nvim.call('win_getid') as number\n      await item.onWinScroll(winid)\n    })\n\n    it('should do range highlight after cursor moved', async () => {\n      helper.updateConfiguration('semanticTokens.filetypes', ['vim'])\n      let doc = await helper.createDocument(`95cb98ca-df0a-4cac-9cd3-2459db259b71.vim`)\n      await nvim.call('cursor', [1, 1])\n      let r: Range\n      expect(doc.filetype).toBe('vim')\n      await nvim.call('setline', [2, (new Array(200).fill(''))])\n      await doc.applyEdits([{ range: Range.create(0, 0, 0, 0), newText: 'let' }])\n      disposables.push(registerRangeProvider('vim', range => {\n        r = range\n        return []\n      }))\n      let item = semanticTokens.getItem(doc.bufnr)\n      item.cancel()\n      nvim.call('cursor', [201, 1], true)\n      await helper.waitValue(() => {\n        return r && r.end.line > 200\n      }, true)\n    })\n\n    it('should not throw when range request throws', async () => {\n      helper.updateConfiguration('semanticTokens.filetypes', ['*'])\n      let doc = await workspace.document\n      let called = false\n      disposables.push(languages.registerDocumentRangeSemanticTokensProvider([{ language: '*' }], {\n        provideDocumentRangeSemanticTokens: (_, range) => {\n          called = true\n          throw new Error('custom error')\n        }\n      }, legend))\n      await helper.wait(2)\n      let item = semanticTokens.getItem(doc.bufnr)\n      let winid = await nvim.call('win_getid') as number\n      await item.doRangeHighlight(winid, undefined, CancellationToken.None)\n      expect(called).toBe(true)\n    })\n\n    it('should only cancel range highlight request', async () => {\n      let rangeCancelled = false\n      disposables.push(languages.registerDocumentRangeSemanticTokensProvider([{ language: 'vim' }], {\n        provideDocumentRangeSemanticTokens: (_, range, token) => {\n          return new Promise(resolve => {\n            token.onCancellationRequested(() => {\n              clearTimeout(timeout)\n              rangeCancelled = true\n              resolve(null)\n            })\n            let timeout = setTimeout(() => {\n              resolve({ data: [] })\n            }, 500)\n          })\n        }\n      }, legend))\n      disposables.push(languages.registerDocumentSemanticTokensProvider([{ language: 'vim' }], {\n        provideDocumentSemanticTokens: (_, token) => {\n          return new Promise(resolve => {\n            resolve({\n              resultId: '1',\n              data: [0, 0, 3, 1, 0]\n            })\n          })\n        }\n      }, legend))\n      let doc = await helper.createDocument('t.vim')\n      await doc.applyEdits([{ range: Range.create(0, 0, 0, 0), newText: 'let' }])\n      let item = await semanticTokens.getCurrentItem()\n      helper.updateConfiguration('semanticTokens.filetypes', ['vim'])\n      item.cancel()\n      let p = item.doHighlight(false, 0)\n      await helper.wait(10)\n      item.cancel(true)\n      await p\n      expect(rangeCancelled).toBe(true)\n    })\n\n    it('should do range highlight on CursorHold', async () => {\n      helper.updateConfiguration('semanticTokens.filetypes', ['vim'])\n      disposables.push(registerRangeProvider('vim', range => {\n        return [0, 0, 3, 1, 0]\n      }))\n      await helper.wait(10)\n      let doc = await helper.createDocument('t.vim')\n      await nvim.call('cursor', [1, 1])\n      await doc.applyEdits([{ range: Range.create(0, 0, 0, 0), newText: 'let' }])\n      let item = semanticTokens.getItem(doc.bufnr)\n      item.cancel()\n      let winid = await nvim.call('win_getid') as number\n      doc.buffer.clearNamespace(NAMESPACE)\n      await item.onCursorHold(winid, 1)\n      let highlights = await doc.buffer.getHighlights(NAMESPACE)\n      expect(highlights.length).toBe(1)\n    })\n  })\n\n  describe('triggerSemanticTokens', () => {\n    it('should be disabled by default', async () => {\n      helper.updateConfiguration('semanticTokens.filetypes', [])\n      await workspace.document\n      const curr = await semanticTokens.getCurrentItem()\n      expect(curr.enabled).toBe(false)\n    })\n\n    it('should be enabled', async () => {\n      await createRustBuffer()\n      const curr = await semanticTokens.getCurrentItem()\n      expect(curr.enabled).toBe(true)\n    })\n\n    it('should get legend by API', async () => {\n      await createRustBuffer()\n      const doc = await workspace.document\n      const l = languages.getLegend(doc.textDocument)\n      expect(l).toEqual(legend)\n    })\n\n    it('should doHighlight', async () => {\n      await createRustBuffer()\n      const doc = await workspace.document\n      await nvim.call('CocAction', 'semanticHighlight')\n      const highlights = await doc.buffer.getHighlights(NAMESPACE)\n      expect(highlights.length).toBeGreaterThan(0)\n      expect(highlights[0].hlGroup).toBe('CocSemTypeKeyword')\n    })\n  })\n\n  describe('delta update', () => {\n    it('should perform highlight update', async () => {\n      await createRustBuffer()\n      let buf = await nvim.buffer\n      await semanticTokens.highlightCurrent()\n      await window.moveTo({ line: 0, character: 0 })\n      let doc = await workspace.document\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo')])\n      let curr = await semanticTokens.getCurrentItem()\n      await curr.requestAllHighlights(CancellationToken.None, false)\n      let markers = await buf.getExtMarks(ns, 0, -1, {})\n      expect(markers.length).toBeGreaterThan(0)\n    })\n  })\n\n  describe('checkState', () => {\n    it('should throw for invalid state', async () => {\n      let doc = await workspace.document\n      const toThrow = (cb: () => void) => {\n        expect(cb).toThrow(Error)\n      }\n      let item = semanticTokens.getItem(doc.bufnr)\n      toThrow(() => {\n        item.checkState()\n      })\n      helper.updateConfiguration('semanticTokens.filetypes', ['*'])\n      toThrow(() => {\n        item.checkState()\n      })\n      toThrow(() => {\n        item.checkState()\n      })\n      let enabled = item.enabled\n      expect(enabled).toBe(false)\n      expect(() => {\n        item.checkState()\n      }).toThrow('provider not found')\n      registerProvider()\n    })\n  })\n\n  describe('enabled', () => {\n    it('should check if buffer enabled for semanticTokens', async () => {\n      let doc = await workspace.document\n      let item = semanticTokens.getItem(doc.bufnr)\n      disposables.push(languages.registerDocumentRangeSemanticTokensProvider([{ language: '*' }], {\n        provideDocumentRangeSemanticTokens: (_, range) => {\n          return {\n            data: []\n          }\n        }\n      }, { tokenModifiers: [], tokenTypes: [] }))\n      await helper.wait(2)\n      let winid = await nvim.call('win_getid') as number\n      await item.onShown(winid)\n      expect(item.enabled).toBe(false)\n      helper.updateConfiguration('semanticTokens.filetypes', ['vim'])\n      expect(item.enabled).toBe(false)\n      helper.updateConfiguration('semanticTokens.filetypes', ['*'])\n      expect(item.enabled).toBe(true)\n    })\n\n    it('should toggle enable by configuration', async () => {\n      helper.updateConfiguration('semanticTokens.enable', false)\n      let buf = await createRustBuffer()\n      let item = semanticTokens.getItem(buf.id)\n      helper.updateConfiguration('semanticTokens.enable', true)\n      await waitRefresh(item)\n      let markers = await buf.getExtMarks(ns, 0, -1, {})\n      expect(markers.length).toBeGreaterThan(0)\n      helper.updateConfiguration('semanticTokens.enable', false)\n      markers = await buf.getExtMarks(ns, 0, -1, {})\n      expect(markers.length).toBe(0)\n      helper.updateConfiguration('semanticTokens.enable', true)\n    })\n  })\n\n  describe('Server cancelled', () => {\n    beforeEach(() => {\n      helper.updateConfiguration('semanticTokens.filetypes', ['*'])\n    })\n\n    it('should retrigger range request on server cancel', async () => {\n      let times = 0\n      disposables.push(languages.registerDocumentRangeSemanticTokensProvider([{ language: '*' }], {\n        provideDocumentRangeSemanticTokens: () => {\n          times++\n          if (times == 1) {\n            throw new CancellationError()\n          }\n          return {\n            data: []\n          }\n        }\n      }, { tokenModifiers: [], tokenTypes: [] }))\n      await helper.waitValue(() => {\n        return times > 1\n      }, true)\n    })\n\n    it('should retrigger full request on server cancel', async () => {\n      helper.updateConfiguration('semanticTokens.enable', true)\n      await workspace.document\n      let times = 0\n      disposables.push(languages.registerDocumentSemanticTokensProvider([{ language: '*' }], {\n        provideDocumentSemanticTokens: () => {\n          times++\n          if (times == 1) {\n            throw new CancellationError()\n          }\n          return {\n            data: []\n          }\n        }\n      }, { tokenModifiers: [], tokenTypes: [] }))\n      await helper.waitValue(() => {\n        return times\n      }, 2)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/signature.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { Disposable, ParameterInformation, Range, SignatureInformation } from 'vscode-languageserver-protocol'\nimport commands from '../../commands'\nimport events from '../../events'\nimport Signature from '../../handler/signature'\nimport languages from '../../languages'\nimport { disposeAll } from '../../util'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet signature: Signature\nlet disposables: Disposable[] = []\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  signature = helper.plugin.getHandler().signature\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  await helper.reset()\n  disposeAll(disposables)\n  disposables = []\n})\n\ndescribe('signatureHelp', () => {\n\n  describe('triggerSignatureHelp', () => {\n    it('should show signature by api', async () => {\n      let res = await signature.triggerSignatureHelp()\n      expect(res).toBe(false)\n      disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {\n        provideSignatureHelp: (_doc, _position) => {\n          return {\n            signatures: [SignatureInformation.create('foo()', 'my signature')],\n            activeParameter: null,\n            activeSignature: null\n          }\n        }\n      }))\n      await helper.createDocument()\n      await nvim.input('foo')\n      await commands.executeCommand('editor.action.triggerParameterHints')\n      let win = await helper.getFloat()\n      expect(win).toBeDefined()\n      let lines = await helper.getWinLines(win.id)\n      expect(lines[2]).toMatch('my signature')\n    })\n\n    it('should load configuration', async () => {\n      await nvim.command(`edit +setl\\\\ buftype=nofile tree`)\n      signature.loadConfiguration()\n    })\n\n    it('should use 0 when activeParameter is undefined', async () => {\n      disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {\n        provideSignatureHelp: (_doc, _position) => {\n          return {\n            signatures: [SignatureInformation.create('foo(a)', 'my signature', { label: 'a' })],\n            activeParameter: undefined,\n            activeSignature: null\n          }\n        }\n      }, []))\n      await helper.createDocument()\n      await nvim.input('foo')\n      await helper.doAction('showSignatureHelp')\n      await signature.triggerSignatureHelp()\n      let win = await helper.getFloat()\n      expect(win).toBeDefined()\n      let buf = await win.buffer\n      let hls = await buf.getHighlights(-1 as any)\n      expect(hls.length).toBe(2)\n      expect(hls[0].hlGroup).toBe('CocFloatActive')\n    })\n\n    it('should trigger by space', async () => {\n      let promise = new Promise(resolve => {\n        disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {\n          provideSignatureHelp: (_doc, _position) => {\n            resolve(undefined)\n            return {\n              signatures: [SignatureInformation.create('foo()', 'my signature')],\n              activeParameter: null,\n              activeSignature: null\n            }\n          }\n        }, [' ']))\n      })\n      await helper.createDocument()\n      await nvim.input('i')\n      await helper.wait(30)\n      await nvim.input(' ')\n      await promise\n    })\n\n    it('should show signature help with param label as string', async () => {\n      disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {\n        provideSignatureHelp: (_doc, _position) => {\n          return {\n            signatures: [\n              SignatureInformation.create('foo()', 'my signature'),\n              SignatureInformation.create('foo', 'my signature', ParameterInformation.create('a', 'description')),\n            ],\n            activeParameter: 0,\n            activeSignature: 1\n          }\n        }\n      }, []))\n      await helper.createDocument()\n      await nvim.input('foo')\n      await signature.triggerSignatureHelp()\n      let win = await helper.getFloat()\n      expect(win).toBeDefined()\n      let lines = await helper.getWinLines(win.id)\n      expect(lines.join('\\n')).toMatch(/description/)\n    })\n  })\n\n  describe('events', () => {\n    function registProvider(): void {\n      disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {\n        provideSignatureHelp: (_doc, _position) => {\n          return {\n            signatures: [SignatureInformation.create('foo(x, y)', 'my signature')],\n            activeParameter: 0,\n            activeSignature: 0\n          }\n        }\n      }, ['(', ',']))\n    }\n\n    it('should trigger signature help on TextInsert', async () => {\n      registProvider()\n      await helper.createDocument()\n      await nvim.input('ifoo')\n      await nvim.input('(')\n      await helper.waitValue(async () => {\n        let win = await helper.getFloat()\n        return win != null\n      }, true)\n      let win = await helper.getFloat()\n      let lines = await helper.getWinLines(win.id)\n      expect(lines[2]).toMatch('my signature')\n    })\n\n    it('should trigger signature help on PlaceholderJump', async () => {\n      let called = 0\n      disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {\n        provideSignatureHelp: (_doc, _position) => {\n          called += 1\n          return {\n            signatures: [SignatureInformation.create('foo(x, y)', 'my signature')],\n            activeParameter: 0,\n            activeSignature: 0\n          }\n        }\n      }, ['(', ',']))\n      let doc = await helper.createDocument()\n      Object.assign((workspace as any)._env, { jumpAutocmd: true })\n      await events.fire('PlaceholderJump', [doc.bufnr, { charbefore: ' ', range: Range.create(0, 0, 0, 0) }])\n      Object.assign((workspace as any)._env, { jumpAutocmd: false })\n      await events.fire('PlaceholderJump', [doc.bufnr, { charbefore: '', range: Range.create(0, 0, 0, 0) }])\n      await events.fire('PlaceholderJump', [doc.bufnr + 1, { charbefore: '(', range: Range.create(0, 0, 0, 0) }])\n      expect(called).toBe(0)\n      await nvim.input('ifoo(b)')\n      await events.fire('PlaceholderJump', [doc.bufnr, { charbefore: '(', range: Range.create(0, 5, 0, 6) }])\n      expect(called).toBe(1)\n    })\n\n    it('should cancel trigger on InsertLeave', async () => {\n      disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {\n        provideSignatureHelp: async (_doc, _position, token) => {\n          return new Promise(resolve => {\n            let timer = setTimeout(() => {\n              resolve({\n                signatures: [SignatureInformation.create('foo()', 'my signature')],\n                activeParameter: null,\n                activeSignature: null\n              })\n            }, 1000)\n            token.onCancellationRequested(() => {\n              clearTimeout(timer)\n              resolve(undefined)\n            })\n          })\n        }\n      }, ['(', ',']))\n      await helper.createDocument()\n      await nvim.input('foo')\n      let p = signature.triggerSignatureHelp()\n      await helper.wait(10)\n      await nvim.command('stopinsert')\n      await nvim.call('feedkeys', [String.fromCharCode(27), 'in'])\n      let res = await p\n      expect(res).toBe(false)\n    })\n\n    it('should not close signature on type', async () => {\n      disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {\n        provideSignatureHelp: (_doc, _position) => {\n          return {\n            signatures: [SignatureInformation.create('foo()', 'my signature')],\n            activeParameter: null,\n            activeSignature: null\n          }\n        }\n      }, ['( ,']))\n      let doc = await helper.createDocument()\n      await nvim.input('foo(')\n      await doc.synchronize()\n      await nvim.input('bar')\n      await doc.synchronize()\n      await helper.waitFloat()\n      let win = await helper.getFloat()\n      let lines = await helper.getWinLines(win.id)\n      expect(lines[2]).toMatch('my signature')\n    })\n\n    it('should close signature float when empty signatures returned', async () => {\n      let empty = false\n      disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {\n        provideSignatureHelp: (_doc, _position) => {\n          if (empty) return undefined\n          return {\n            signatures: [SignatureInformation.create('foo()', 'my signature')],\n            activeParameter: null,\n            activeSignature: null\n          }\n        }\n      }, ['(', ',']))\n      await helper.createDocument()\n      await nvim.input('foo(')\n      await helper.wait(100)\n      let win = await helper.getFloat()\n      expect(win).toBeDefined()\n      empty = true\n      await signature.triggerSignatureHelp()\n      await helper.wait(50)\n      let res = await nvim.call('coc#float#valid', [win.id])\n      expect(res).toBe(0)\n    })\n\n    it('should close float on cursor moved', async () => {\n      disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {\n        provideSignatureHelp: (_doc, _position) => {\n          return {\n            signatures: [SignatureInformation.create('foo()', 'my signature')],\n            activeParameter: null,\n            activeSignature: null\n          }\n        }\n      }, ['(', ',']))\n      const show = async () => {\n        await helper.createDocument()\n        await nvim.input('i')\n        await nvim.call('append', [1, 'bar'])\n        await nvim.input('(')\n        await helper.waitValue(async () => {\n          let win = await helper.getFloat()\n          return win != null\n        }, true)\n      }\n      await show()\n      await nvim.call('cursor', [2, 1])\n      await helper.waitValue(async () => {\n        let win = await helper.getFloat()\n        return win == null\n      }, true)\n      await nvim.input('<esc>')\n      await show()\n      await nvim.input(')')\n      await helper.waitValue(async () => {\n        let win = await helper.getFloat()\n        return win == null\n      }, true)\n    })\n  })\n\n  describe('float window', () => {\n    it('should align signature window to top', async () => {\n      disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {\n        provideSignatureHelp: (_doc, _position) => {\n          return {\n            signatures: [SignatureInformation.create('foo()', 'my signature')],\n            activeParameter: null,\n            activeSignature: null\n          }\n        }\n      }, ['(', ',']))\n      await helper.createDocument()\n      let buf = await nvim.buffer\n      await buf.setLines(['', '', '', '', ''], { start: 0, end: -1, strictIndexing: true })\n      await nvim.call('cursor', [5, 1])\n      await nvim.input('foo(')\n      await helper.wait(100)\n      let win = await helper.getFloat()\n      expect(win).toBeDefined()\n      let lines = await helper.getWinLines(win.id)\n      expect(lines[2]).toMatch('my signature')\n      let res = await nvim.call('GetFloatCursorRelative', [win.id]) as any\n      expect(res.row).toBeLessThan(0)\n    })\n\n    it('should show parameter docs', async () => {\n      disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {\n        provideSignatureHelp: (_doc, _position) => {\n          return {\n            signatures: [SignatureInformation.create('foo(a, b)', 'my signature',\n              ParameterInformation.create('a', 'foo'),\n              ParameterInformation.create([7, 8], 'bar'))],\n            activeParameter: 1,\n            activeSignature: null\n          }\n        }\n      }, ['(', ',']))\n      await helper.createDocument()\n      let buf = await nvim.buffer\n      await buf.setLines(['', '', '', '', ''], { start: 0, end: -1, strictIndexing: true })\n      await nvim.call('cursor', [5, 1])\n      await nvim.input('foo(a,')\n      await helper.wait(100)\n      let win = await helper.getFloat()\n      expect(win).toBeDefined()\n      let lines = await helper.getWinLines(win.id)\n      expect(lines.join('\\n')).toMatch('bar')\n    })\n  })\n\n  describe('configurations', () => {\n    let { configurations } = workspace\n    afterEach(() => {\n      configurations.updateMemoryConfig({\n        'signature.target': 'float',\n        'signature.hideOnTextChange': false,\n        'signature.enable': true,\n        'signature.triggerSignatureWait': 500\n      })\n    })\n\n    it('should cancel signature on timeout', async () => {\n      configurations.updateMemoryConfig({ 'signature.triggerSignatureWait': 50 })\n      disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {\n        provideSignatureHelp: (_doc, _position, token) => {\n          return new Promise(resolve => {\n            token.onCancellationRequested(() => {\n              clearTimeout(timer)\n              resolve(undefined)\n            })\n            let timer = setTimeout(() => {\n              resolve({\n                signatures: [SignatureInformation.create('foo()', 'my signature')],\n                activeParameter: null,\n                activeSignature: null\n              })\n            }, 200)\n          })\n        }\n      }, ['(', ',']))\n      await helper.createDocument()\n      await signature.triggerSignatureHelp()\n      let win = await helper.getFloat()\n      expect(win).toBeUndefined()\n      configurations.updateMemoryConfig({ 'signature.triggerSignatureWait': 100 })\n    })\n\n    it('should hide signature window on text change', async () => {\n      configurations.updateMemoryConfig({ 'signature.hideOnTextChange': true })\n      disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {\n        provideSignatureHelp: (_doc, _position) => {\n          let s = SignatureInformation.create('foo()', 'my signature')\n          s.parameters = undefined\n          return {\n            signatures: [s],\n            activeParameter: 0,\n            activeSignature: null\n          }\n        }\n      }, ['(', ',']))\n      await helper.createDocument()\n      await nvim.input('ifoo(')\n      let winid = await helper.waitFloat()\n      await nvim.input('x')\n      await helper.wait(100)\n      let res = await nvim.call('coc#float#valid', [winid])\n      expect(res).toBe(0)\n      configurations.updateMemoryConfig({ 'signature.hideOnTextChange': false })\n    })\n\n    it('should disable signature help trigger', async () => {\n      configurations.updateMemoryConfig({ 'signature.enable': false })\n      disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {\n        provideSignatureHelp: (_doc, _position) => {\n          return {\n            signatures: [SignatureInformation.create('foo()', 'my signature')],\n            activeParameter: null,\n            activeSignature: null\n          }\n        }\n      }, ['(', ',']))\n      await helper.createDocument()\n      await nvim.input('foo')\n      await nvim.input('(')\n      await helper.wait(30)\n      let win = await helper.getFloat()\n      expect(win).toBeUndefined()\n    })\n\n    it('should echo simple signature help', async () => {\n      let idx = 0\n      let activeSignature = null\n      configurations.updateMemoryConfig({ 'signature.target': 'echo' })\n      disposables.push(languages.registerSignatureHelpProvider([{ scheme: 'file' }], {\n        provideSignatureHelp: (_doc, _position) => {\n          return {\n            signatures: [SignatureInformation.create('foo(a, b)', 'my signature',\n              ParameterInformation.create('a', 'foo'),\n              ParameterInformation.create([7, 8], 'bar')),\n            SignatureInformation.create('a'.repeat(workspace.env.columns + 10))\n            ],\n            activeParameter: idx,\n            activeSignature\n          }\n        }\n      }, []))\n      await helper.createDocument()\n      await nvim.input('foo(')\n      await signature.triggerSignatureHelp()\n      let line = await helper.getCmdline()\n      expect(line).toMatch('(a, b)')\n      await nvim.input('a,')\n      idx = 1\n      await signature.triggerSignatureHelp()\n      line = await helper.getCmdline()\n      expect(line).toMatch('foo(a, b)')\n      activeSignature = 1\n      await signature.triggerSignatureHelp()\n      line = await helper.getCmdline()\n      expect(line).toMatch('aaaaaa')\n    })\n\n    it('should echo signature without match', async () => {\n      let signatureHelp = {\n        signatures: [SignatureInformation.create('foo(a, b)', 'my signature',\n          ParameterInformation.create('c', 'foo'),\n          ParameterInformation.create([7, 8], 'bar')),\n        SignatureInformation.create('a'.repeat(workspace.env.columns + 10))\n        ],\n        activeParameter: 0,\n        activeSignature: null\n      }\n      signature.echoSignature(signatureHelp)\n      await helper.wait(20)\n      let line = await helper.getCmdline()\n      expect(line).toMatch('foo')\n      signatureHelp.signatures[0].parameters = undefined\n      signature.echoSignature(signatureHelp)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/symbols.test.ts",
    "content": "import { Buffer, Neovim } from '@chemzqm/neovim'\nimport { CancellationToken, Disposable, Range, SymbolInformation, SymbolKind } from 'vscode-languageserver-protocol'\nimport events from '../../events'\nimport Symbols from '../../handler/symbols/index'\nimport languages from '../../languages'\nimport { asDocumentSymbolTree } from '../../provider/documentSymbolManager'\nimport { disposeAll } from '../../util'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport helper from '../helper'\nimport Parser from './parser'\n\nlet nvim: Neovim\nlet symbols: Symbols\nlet disposables: Disposable[] = []\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  symbols = helper.plugin.getHandler().symbols\n})\n\nbeforeEach(() => {\n  disposables.push(languages.registerDocumentSymbolProvider([{ language: 'javascript' }], {\n    provideDocumentSymbols: document => {\n      let text = document.getText()\n      let parser = new Parser(text, text.includes('detail'))\n      let res = parser.parse()\n      return Promise.resolve(res)\n    }\n  }))\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  disposables = []\n  await helper.reset()\n})\n\ndescribe('Parser', () => {\n  it('should parse content', async () => {\n    let code = `class myClass {\n      fun1() { }\n    }`\n    let parser = new Parser(code)\n    let res = parser.parse()\n    expect(res.length).toBeGreaterThan(0)\n  })\n})\n\ndescribe('symbols handler', () => {\n\n  async function createBuffer(code: string): Promise<Buffer> {\n    let doc = await workspace.document\n    doc.setFiletype('javascript')\n    await doc.buffer.setLines(code.split('\\n'), { start: 0, end: -1, strictIndexing: false })\n    await doc.patchChange()\n    return doc.buffer\n  }\n\n  describe('configuration', () => {\n    it('should get configuration', async () => {\n      let bufnr = await nvim.call('bufnr', ['%']) as number\n      let functionUpdate = symbols.autoUpdate(bufnr)\n      expect(functionUpdate).toBe(false)\n      helper.updateConfiguration('coc.preferences.currentFunctionSymbolAutoUpdate', true)\n      functionUpdate = symbols.autoUpdate(bufnr)\n      expect(functionUpdate).toBe(true)\n    })\n\n    it('should update symbols automatically', async () => {\n      helper.updateConfiguration('coc.preferences.currentFunctionSymbolAutoUpdate', true)\n      let code = `class myClass {\n      fun1() {\n      }\n    }`\n      let buf = await createBuffer(code)\n      await events.fire('CursorMoved', [buf.id, [2, 8]])\n      await helper.waitFor('eval', ['get(b:,\"coc_current_function\",\"\")'], 'fun1')\n      await events.fire('CursorMoved', [buf.id, [1, 8]])\n      await helper.waitFor('eval', ['get(b:,\"coc_current_function\",\"\")'], 'myClass')\n    })\n  })\n\n  describe('documentSymbols', () => {\n    it('should create document symbol tree', () => {\n      let uri = 'lsp:/1'\n      let symbols = [\n        SymbolInformation.create('root', SymbolKind.Function, Range.create(0, 0, 0, 10), uri),\n        SymbolInformation.create('child', SymbolKind.Function, Range.create(0, 3, 0, 7), uri, 'root'),\n        SymbolInformation.create('child', SymbolKind.Function, Range.create(0, 0, 0, 10), uri, 'root'),\n      ]\n      let res = asDocumentSymbolTree(symbols)\n      expect(res.length).toBe(2)\n    })\n\n    it('should get empty metadata when provider not found', async () => {\n      disposeAll(disposables)\n      let doc = await workspace.document\n      let res = languages.getDocumentSymbolMetadata(doc.textDocument)\n      expect(res).toBeNull()\n      let symbols = await languages.getDocumentSymbol(doc.textDocument, CancellationToken.None)\n      expect(symbols).toBeNull()\n    })\n\n    it('should get symbols of current buffer', async () => {\n      let code = `class detail {\n      fun1() { }\n    }`\n      await createBuffer(code)\n      let res = await helper.plugin.cocAction('documentSymbols')\n      expect(res.length).toBe(2)\n      expect(res[1].detail).toBeDefined()\n    })\n\n    it('should get current function symbols', async () => {\n      let code = `class myClass {\n      fun1() {\n      }\n      fun2() {\n      }\n    }\n    `\n      await createBuffer(code)\n      await nvim.call('cursor', [3, 0])\n      let res = await helper.doAction('getCurrentFunctionSymbol')\n      expect(res).toBe('fun1')\n      await nvim.command('normal! G')\n      res = await helper.doAction('getCurrentFunctionSymbol')\n      expect(res).toBe('')\n    })\n\n    it('should reset coc_current_function when symbols do not exist', async () => {\n      let code = `class myClass {\n      fun1() {\n      }\n    }`\n      await createBuffer(code)\n      await nvim.call('cursor', [3, 0])\n      let res = await helper.doAction('getCurrentFunctionSymbol')\n      expect(res).toBe('fun1')\n      await nvim.command('normal! ggdG')\n      res = await symbols.getCurrentFunctionSymbol()\n      expect(res).toBe('')\n    })\n\n    it('should support SymbolInformation', async () => {\n      disposables.push(languages.registerDocumentSymbolProvider(['*'], {\n        provideDocumentSymbols: doc => {\n          let s = SymbolInformation.create('root', SymbolKind.Function, Range.create(0, 0, 0, 10), doc.uri)\n          s.deprecated = true\n          return [\n            s,\n            SymbolInformation.create('child', SymbolKind.Function, Range.create(0, 3, 0, 7), doc.uri, 'root'),\n            SymbolInformation.create('child', SymbolKind.Function, Range.create(0, 0, 0, 10), doc.uri, 'root')\n          ]\n        }\n      }, { label: 'test' }))\n      await helper.createDocument()\n      let res = await symbols.getDocumentSymbols()\n      expect(res.length).toBe(3)\n      expect(res[0].text).toBe('root')\n      await nvim.command('edit +setl\\\\ buftype=nofile b')\n      res = await symbols.getDocumentSymbols()\n      expect(res).toBeUndefined()\n    })\n  })\n\n  describe('selectSymbolRange', () => {\n    it('should show warning when no symbols exist', async () => {\n      disposables.push(languages.registerDocumentSymbolProvider(['*'], {\n        provideDocumentSymbols: () => {\n          return []\n        }\n      }))\n      await helper.createDocument()\n      await nvim.call('cursor', [3, 0])\n      await symbols.selectSymbolRange(false, '', ['Function'])\n      let msg = await helper.getCmdline()\n      expect(msg).toMatch(/No symbols found/)\n    })\n\n    it('should select symbol range at cursor position', async () => {\n      let code = `class myClass {\n      fun1() {\n      }\n    }`\n      await createBuffer(code)\n      await nvim.call('cursor', [3, 0])\n      await helper.doAction('selectSymbolRange', false, '', ['Function', 'Method'])\n      let mode = await nvim.mode\n      expect(mode.mode).toBe('v')\n      await nvim.input('<esc>')\n      let res = await window.getSelectedRange('v')\n      expect(res).toEqual({ start: { line: 1, character: 6 }, end: { line: 2, character: 6 } })\n    })\n\n    it('should select inner range', async () => {\n      let code = `class myClass {\n      fun1() {\n        let foo;\n      }\n}`\n      await createBuffer(code)\n      await nvim.call('cursor', [3, 3])\n      await symbols.selectSymbolRange(true, '', ['Method'])\n      let mode = await nvim.mode\n      expect(mode.mode).toBe('v')\n      await nvim.input('<esc>')\n      let res = await window.getSelectedRange('v')\n      expect(res).toEqual({\n        start: { line: 2, character: 8 }, end: { line: 2, character: 16 }\n      })\n    })\n\n    it('should reset visualmode when selection not found', async () => {\n      let code = `class myClass {}`\n      await createBuffer(code)\n      await nvim.call('cursor', [1, 1])\n      await nvim.command('normal! gg0v$')\n      let mode = await nvim.mode\n      expect(mode.mode).toBe('v')\n      await nvim.input('<esc>')\n      await symbols.selectSymbolRange(true, 'v', ['Method'])\n      mode = await nvim.mode\n      expect(mode.mode).toBe('v')\n    })\n\n    it('should select symbol range from select range', async () => {\n      let code = `class myClass {\n      fun1() {\n      }\n    }`\n      let buf = await createBuffer(code)\n      await nvim.call('cursor', [2, 8])\n      await nvim.command('normal! viw')\n      await nvim.input('<esc>')\n      await helper.doAction('selectSymbolRange', false, 'v', ['Class'])\n      let mode = await nvim.mode\n      expect(mode.mode).toBe('v')\n      let doc = workspace.getDocument(buf.id)\n      await nvim.input('<esc>')\n      let res = await window.getSelectedRange('v')\n      expect(res).toEqual({ start: { line: 0, character: 0 }, end: { line: 3, character: 4 } })\n    })\n  })\n\n  describe('cancel', () => {\n    it('should cancel symbols request on insert', async () => {\n      let cancelled = false\n      disposables.push(languages.registerDocumentSymbolProvider([{ language: 'text' }], {\n        provideDocumentSymbols: (_doc, token) => {\n          return new Promise(s => {\n            token.onCancellationRequested(() => {\n              if (timer) clearTimeout(timer)\n              cancelled = true\n              s(undefined)\n            })\n            let timer = setTimeout(() => {\n              s(undefined)\n            }, 3000)\n          })\n        }\n      }))\n      let doc = await helper.createDocument('t.txt')\n      let p = symbols.getDocumentSymbols(doc.bufnr)\n      setTimeout(async () => {\n        await nvim.input('i')\n      }, 500)\n      await p\n      expect(cancelled).toBe(true)\n    })\n  })\n\n  describe('workspaceSymbols', () => {\n    it('should get workspace symbols', async () => {\n      disposables.push(languages.registerWorkspaceSymbolProvider({\n        provideWorkspaceSymbols: (_query, _token) => {\n          return [SymbolInformation.create('far', SymbolKind.Class, Range.create(0, 0, 0, 0), '')]\n        },\n        resolveWorkspaceSymbol: sym => {\n          let res = Object.assign({}, sym)\n          res.location.uri = 'test:///foo'\n          return res\n        }\n      }))\n      let fn: any = languages.registerWorkspaceSymbolProvider.bind(languages)\n      disposables.push(fn('vim', {\n        provideWorkspaceSymbols: (_query, _token) => {\n          return null\n        }\n      }))\n      let res = await symbols.getWorkspaceSymbols('a')\n      expect(res.length).toBe(1)\n      let resolved = await helper.doAction('resolveWorkspaceSymbol', res[0])\n      expect(resolved?.location?.uri).toBe('test:///foo')\n    })\n\n    it('should return symbol when resolve failed', async () => {\n      disposables.push(languages.registerWorkspaceSymbolProvider({\n        provideWorkspaceSymbols: (_query, _token) => {\n          return [SymbolInformation.create('far', SymbolKind.Class, Range.create(0, 0, 0, 0), '')]\n        }\n      }))\n      let res = await helper.doAction('getWorkspaceSymbols')\n      let resolved = await symbols.resolveWorkspaceSymbol(res[0])\n      expect(resolved).toBeDefined()\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/typeHierarchy.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationToken, TypeHierarchyItem, Disposable, Range, SymbolKind, Position, SymbolTag } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport languages, { ProviderName } from '../../languages'\nimport TypeHierarchyHandler from '../../handler/typeHierarchy'\nimport { addChildren } from '../../tree/LocationsDataProvider'\nimport { disposeAll } from '../../util'\nimport workspace from '../../workspace'\nimport helper, { createTmpFile } from '../helper'\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nlet handler: TypeHierarchyHandler\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  handler = helper.plugin.getHandler().typeHierarchy\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nbeforeEach(async () => {\n  await helper.createDocument()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n})\n\nfunction createItem(name: string, kind?: SymbolKind, uri?: string, range?: Range): TypeHierarchyItem {\n  range = range ?? Range.create(0, 0, 0, 3)\n  return {\n    name,\n    kind: kind ?? SymbolKind.Function,\n    uri: uri ?? 'file:///1',\n    range,\n    selectionRange: range,\n  }\n}\nconst position = Position.create(0, 0)\nconst token = CancellationToken.None\n\ndescribe('TypeHierarchy', () => {\n  describe('TypeHierarchyManager', () => {\n    it('should return false when provider not exists', async () => {\n      let doc = await workspace.document\n      let res = languages.hasProvider(ProviderName.TypeHierarchy, doc.textDocument)\n      expect(res).toBe(false)\n    })\n\n    it('should return merged results', async () => {\n      disposables.push(languages.registerTypeHierarchyProvider([{ language: '*' }], {\n        prepareTypeHierarchy: () => {\n          return null\n        },\n        provideTypeHierarchySubtypes: () => {\n          return []\n        },\n        provideTypeHierarchySupertypes: () => {\n          return []\n        }\n      }))\n      disposables.push(languages.registerTypeHierarchyProvider([{ language: '*' }], {\n        prepareTypeHierarchy: () => {\n          return [createItem('a'), createItem('b')]\n        },\n        provideTypeHierarchySubtypes: () => {\n          return []\n        },\n        provideTypeHierarchySupertypes: () => {\n          return []\n        }\n      }))\n      disposables.push(languages.registerTypeHierarchyProvider([{ language: '*' }], {\n        prepareTypeHierarchy: () => {\n          return [createItem('b'), createItem('c')]\n        },\n        provideTypeHierarchySubtypes: () => {\n          return []\n        },\n        provideTypeHierarchySupertypes: () => {\n          return []\n        }\n      }))\n      let doc = await workspace.document\n      let res = await languages.prepareTypeHierarchy(doc.textDocument, position, token)\n      expect(res.length).toBe(3)\n    })\n\n    it('should return empty array when provider not found', async () => {\n      let item = createItem('foo')\n      let res: any\n      res = await languages.provideTypeHierarchySupertypes(item, token)\n      expect(res).toEqual([])\n      res = await languages.provideTypeHierarchySubtypes(item, token)\n      expect(res).toEqual([])\n    })\n\n    it('should return subtypes and supertypes', async () => {\n      disposables.push(languages.registerTypeHierarchyProvider([{ language: '*' }], {\n        prepareTypeHierarchy: () => {\n          return [createItem('b')]\n        },\n        provideTypeHierarchySubtypes: () => {\n          return [createItem('c')]\n        },\n        provideTypeHierarchySupertypes: () => {\n          return [createItem('d')]\n        }\n      }))\n      let doc = await workspace.document\n      let res = await languages.prepareTypeHierarchy(doc.textDocument, position, token)\n      let arr: any[]\n      arr = await languages.provideTypeHierarchySubtypes(res[0], token)\n      expect(arr.length).toBe(1)\n      expect(arr[0].source).toBeDefined()\n      arr = await languages.provideTypeHierarchySupertypes(res[0], token)\n      expect(arr.length).toBe(1)\n      expect(arr[0].source).toBeDefined()\n    })\n\n    it('should not throw when prepareTypeHierarchy throws', async () => {\n      disposables.push(languages.registerTypeHierarchyProvider([{ language: '*' }], {\n        prepareTypeHierarchy: () => {\n          throw new Error('my error')\n        },\n        provideTypeHierarchySubtypes: () => {\n          return undefined\n        },\n        provideTypeHierarchySupertypes: () => {\n          return undefined\n        }\n      }))\n      let doc = await workspace.document\n      let res = await languages.prepareTypeHierarchy(doc.textDocument, position, token)\n      expect(res).toEqual([])\n    })\n\n    it('should return empty supertypes and supertypes', async () => {\n      disposables.push(languages.registerTypeHierarchyProvider([{ language: '*' }], {\n        prepareTypeHierarchy: () => {\n          return [createItem('b')]\n        },\n        provideTypeHierarchySubtypes: () => {\n          return null\n        },\n        provideTypeHierarchySupertypes: () => {\n          return undefined\n        }\n      }))\n      let doc = await workspace.document\n      let res = await languages.prepareTypeHierarchy(doc.textDocument, position, token)\n      let arr: any[]\n      arr = await languages.provideTypeHierarchySubtypes(res[0], token)\n      expect(arr).toEqual([])\n      arr = await languages.provideTypeHierarchySupertypes(res[0], token)\n      expect(arr).toEqual([])\n    })\n  })\n\n  describe('TypeHierarchyHandler', () => {\n    it('should add children', async () => {\n      let item = createItem('foo')\n      addChildren(item, undefined)\n      expect(item['children']).toBeUndefined()\n      addChildren(item, [], CancellationToken.Cancelled)\n      expect(item['children']).toBeUndefined()\n    })\n\n    it('should throw when provider not exist', async () => {\n      let fn = async () => {\n        await handler.showTypeHierarchyTree('supertypes')\n      }\n      await expect(fn()).rejects.toThrow(Error)\n    })\n\n    it('should show warning when prepare return empty', async () => {\n      disposables.push(languages.registerTypeHierarchyProvider([{ language: '*' }], {\n        prepareTypeHierarchy() {\n          return null\n        },\n        provideTypeHierarchySupertypes() {\n          return []\n        },\n        provideTypeHierarchySubtypes() {\n          return []\n        }\n      }))\n      let plugin = helper.plugin\n      await plugin.cocAction('showSuperTypes')\n      await nvim.command('echo \"\"')\n      await plugin.cocAction('showSubTypes')\n      let line = await helper.getCmdline()\n      expect(line).toMatch('Unable')\n    })\n\n    it('should invoke super types and sub types action', async () => {\n      let doc = await workspace.document\n      disposables.push(languages.registerTypeHierarchyProvider([{ language: '*' }], {\n        prepareTypeHierarchy() {\n          return [createItem('foo', SymbolKind.Class, doc.uri, Range.create(0, 0, 0, 3))]\n        },\n        provideTypeHierarchySupertypes() {\n          return undefined\n        },\n        provideTypeHierarchySubtypes() {\n          return undefined\n        }\n      }))\n      await handler.showTypeHierarchyTree('supertypes')\n      await helper.waitFor('getline', [2], '- c foo')\n      await nvim.command('exe 2')\n      await nvim.input('<tab>')\n      await helper.waitPrompt()\n      await nvim.input('4')\n      await helper.waitFor('getline', [1], 'Sub types')\n      await nvim.input('<tab>')\n      await helper.waitPrompt()\n      await nvim.input('3')\n      await helper.waitFor('getline', [1], 'Super types')\n    })\n\n    it('should render description and support default action', async () => {\n      let doc = await workspace.document\n      let bufnr = doc.bufnr\n      await doc.buffer.setLines(['foo'], { start: 0, end: -1, strictIndexing: false })\n      let fsPath = await createTmpFile('foo\\nbar\\ncontent\\n')\n      let uri = URI.file(fsPath).toString()\n      disposables.push(languages.registerTypeHierarchyProvider([{ language: '*' }], {\n        prepareTypeHierarchy() {\n          return [createItem('foo', SymbolKind.Class, doc.uri, Range.create(0, 0, 0, 3))]\n        },\n        provideTypeHierarchySupertypes() {\n          let item = createItem('bar', SymbolKind.Class, uri, Range.create(1, 0, 1, 3))\n          item.detail = 'Detail'\n          item.tags = [SymbolTag.Deprecated]\n          return [item]\n        },\n        provideTypeHierarchySubtypes() {\n          return []\n        }\n      }))\n      await handler.showTypeHierarchyTree('supertypes')\n      let buf = await nvim.buffer\n      let lines = await buf.lines\n      expect(lines).toEqual([\n        'Super types',\n        '- c foo',\n        '  + c bar Detail'\n      ])\n      await nvim.command('exe 3')\n      await nvim.input('t')\n      await helper.waitFor('getline', ['.'], '  - c bar Detail')\n      await nvim.input('<cr>')\n      await helper.waitFor('expand', ['%:p'], fsPath)\n      let res = await nvim.call('coc#cursor#position')\n      expect(res).toEqual([1, 0])\n      let matches = await nvim.call('getmatches') as any[]\n      expect(matches.length).toBe(1)\n      await nvim.command(`b ${bufnr}`)\n      await helper.wait(50)\n      matches = await nvim.call('getmatches') as any[]\n      expect(matches.length).toBe(0)\n      await nvim.command(`wincmd o`)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/handler/workspace.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport { v4 as uuid } from 'uuid'\nimport { Disposable, Location, Range } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport commands from '../../commands'\nimport events from '../../events'\nimport extensions from '../../extension'\nimport WorkspaceHandler from '../../handler/workspace'\nimport languages from '../../languages'\nimport snippetManager from '../../snippets/manager'\nimport { disposeAll } from '../../util'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet handler: WorkspaceHandler\nlet disposables: Disposable[] = []\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  handler = helper.plugin.getHandler().workspace\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  await helper.reset()\n  disposeAll(disposables)\n})\n\ndescribe('Workspace handler', () => {\n  async function checkFloat(content: string) {\n    let win = await helper.getFloat()\n    expect(win).toBeDefined()\n    let buf = await win.buffer\n    let lines = await buf.lines\n    expect(lines.join('\\n')).toMatch(content)\n  }\n\n  describe('events', () => {\n    it('should reset autocmds of extensions', async () => {\n      workspace.registerAutocmd({\n        event: 'CursorHold',\n        callback: () => {},\n      })\n      workspace.registerAutocmd({\n        event: 'CursorMoved',\n        callback: () => {},\n      })\n      let obj = workspace.autocmds.autocmds.get(1)\n      Object.assign(obj, { _extensiionName: 'test' })\n      let m = extensions.manager as any\n      m._onDidUnloadExtension.fire('test')\n      let map = workspace.autocmds.autocmds\n      let arr = Array.from(map.keys())\n      expect(arr).toEqual([2])\n      let output = await nvim.call('execute', 'autocmd coc_dynamic_autocmd') as string\n      expect(output).toMatch('CursorMoved')\n      expect(output.includes('CursorHold')).toBe(false)\n      nvim.command('autocmd! coc_dynamic_autocmd', true)\n    })\n  })\n\n  describe('commands', () => {\n    it('should check filetype', async () => {\n      await helper.createDocument('t.vim')\n      await commands.executeCommand('document.echoFiletype')\n      let line = await helper.getCmdline()\n      expect(line).toMatch('vim')\n    })\n\n    it('should show workspace folders', async () => {\n      await helper.edit(__filename)\n      await commands.executeCommand('workspace.workspaceFolders')\n      let line = await helper.getCmdline()\n      expect(line).toMatch('coc.nvim')\n    })\n\n    it('should write writeHeapSnapshot', async () => {\n      const v8 = require('v8')\n      let called = false\n      let spy = jest.spyOn(v8, 'writeHeapSnapshot').mockImplementation(() => {\n        called = true\n      })\n      let filepath = await commands.executeCommand('workspace.writeHeapSnapshot')\n      spy.mockRestore()\n      expect(filepath).toBeDefined()\n      expect(called).toBe(true)\n    })\n\n    it('should show output', async () => {\n      window.createOutputChannel('foo')\n      window.createOutputChannel('bar')\n      let p = commands.executeCommand('workspace.showOutput')\n      await helper.waitFloat()\n      await nvim.input('<esc>')\n      await p\n      let bufname = await nvim.call('bufname', ['%'])\n      expect(bufname).toBe('')\n      await commands.executeCommand('workspace.showOutput', 'foo')\n      bufname = await nvim.call('bufname', ['%'])\n      expect(bufname).toMatch('output')\n    })\n\n    it('should open location', async () => {\n      let winid = await nvim.call('win_getid')\n      await commands.executeCommand('workspace.openLocation', winid, Location.create('lsp:/1', Range.create(0, 0, 0, 0)))\n      let bufname = await nvim.call('bufname', ['%'])\n      expect(bufname).toBe('lsp:/1')\n    })\n\n    it('should clear watchman roots', async () => {\n      let success = true\n      let spy = jest.spyOn(window, 'runTerminalCommand').mockImplementation(() => {\n        return Promise.resolve({ success, bufnr: 1 })\n      })\n      let res = await commands.executeCommand('workspace.clearWatchman')\n      expect(res).toBe(true)\n      success = false\n      res = await commands.executeCommand('workspace.clearWatchman')\n      expect(res).toBe(false)\n      spy.mockRestore()\n    })\n  })\n\n  describe('methods', () => {\n    it('should rename buffer', async () => {\n      let doc = await helper.createDocument('a')\n      let fsPath = URI.parse(doc.uri).fsPath.replace(/a$/, 'b')\n      disposables.push(Disposable.create(() => {\n        if (fs.existsSync(fsPath)) fs.unlinkSync(fsPath)\n      }))\n      let p = handler.renameCurrent()\n      await helper.waitValue(() => nvim.call('mode'), 'c')\n      await nvim.input('<backspace>b<cr>')\n      await p\n      let name = await nvim.eval('bufname(\"%\")') as string\n      expect(name.endsWith('b')).toBe(true)\n      p = handler.renameCurrent()\n      await helper.waitValue(() => nvim.call('mode'), 'c')\n      await nvim.input('<C-u><cr>')\n      await p\n    })\n\n    it('should rename file', async () => {\n      let dir = path.join(os.tmpdir(), uuid())\n      fs.mkdirSync(dir, { recursive: true })\n      let fsPath = path.join(dir, 'x')\n      let newPath = path.join(dir, 'b')\n      disposables.push(Disposable.create(() => {\n        fs.rmSync(dir, { recursive: true, force: true })\n      }))\n      fs.writeFileSync(newPath, '', 'utf8')\n      fs.writeFileSync(fsPath, 'foo', 'utf8')\n      await helper.createDocument(fsPath)\n      let spy = jest.spyOn(window, 'showPrompt').mockImplementation(() => {\n        return Promise.resolve(true)\n      })\n      let p = commands.executeCommand('workspace.renameCurrentFile')\n      await helper.waitFor('mode', [], 'c')\n      await nvim.input('<backspace>b<cr>')\n      await p\n      spy.mockRestore()\n      let name = await nvim.eval('bufname(\"%\")') as string\n      expect(name.endsWith('b')).toBe(true)\n      expect(fs.existsSync(newPath)).toBe(true)\n      let content = fs.readFileSync(newPath, 'utf8')\n      expect(content).toMatch(/foo/)\n    })\n\n    it('should not rename when reject overwrite', async () => {\n      let dir = path.join(os.tmpdir(), uuid())\n      fs.mkdirSync(dir, { recursive: true })\n      let fsPath = path.join(dir, 'x')\n      let newPath = path.join(dir, 'b')\n      disposables.push(Disposable.create(() => {\n        fs.rmSync(dir, { recursive: true, force: true })\n      }))\n      fs.writeFileSync(newPath, '', 'utf8')\n      await helper.createDocument(fsPath)\n      let spy = jest.spyOn(window, 'showPrompt').mockImplementation(() => {\n        return Promise.resolve(false)\n      })\n      let p = handler.renameCurrent()\n      await helper.waitFor('mode', [], 'c')\n      await nvim.input('<backspace>b<cr>')\n      await p\n      spy.mockRestore()\n      let bufname = await nvim.call('bufname', ['%'])\n      expect(bufname).toMatch(/x$/)\n    })\n\n    it('should open local config', async () => {\n      let dir = path.join(os.tmpdir(), '.vim')\n      fs.rmSync(dir, { recursive: true, force: true })\n      fs.mkdirSync(path.join(os.tmpdir(), '.git'), { recursive: true })\n      await helper.edit(path.join(os.tmpdir(), 't'))\n      let root = workspace.root\n      expect(root).toBe(os.tmpdir())\n      let p = handler.openLocalConfig()\n      await helper.waitPromptWin()\n      await nvim.input('n')\n      await p\n      p = handler.openLocalConfig()\n      await helper.waitPromptWin()\n      await nvim.input('y')\n      await p\n      let bufname = await nvim.call('bufname', ['%'])\n      expect(bufname).toMatch('coc-settings.json')\n    })\n\n    it('should not throw when workspace folder does not exist', async () => {\n      helper.updateConfiguration('workspace.rootPatterns', [], disposables)\n      helper.updateConfiguration('workspace.ignoredFiletypes', ['vim'], disposables)\n      await nvim.command('enew')\n      await (window as any).openLocalConfig()\n      await nvim.command(`e ${path.join(os.tmpdir(), 'a')}`)\n      await helper.doAction('openLocalConfig')\n      await nvim.command(`e t.md`)\n      await nvim.command('setf markdown')\n      await handler.openLocalConfig()\n      await nvim.command(`e ${path.join(os.tmpdir(), 't.vim')}`)\n      await nvim.command('setf vim')\n      let called = false\n      let spy = jest.spyOn(window, 'showWarningMessage').mockImplementation(() => {\n        called = true\n        return Promise.resolve(undefined)\n      })\n      await commands.executeCommand('workspace.openLocalConfig')\n      expect(called).toBe(true)\n      spy.mockRestore()\n    })\n\n    it('should add workspace folder', async () => {\n      expect(() => {\n        handler.addWorkspaceFolder(undefined)\n      }).toThrow(TypeError)\n      expect(() => {\n        handler.addWorkspaceFolder(__filename)\n      }).toThrow(Error)\n      await helper.plugin.cocAction('addWorkspaceFolder', __dirname)\n      let folders = workspace.workspaceFolderControl.workspaceFolders\n      let uri = URI.file(__dirname).toString()\n      let find = folders.find(o => o.uri === uri)\n      expect(find).toBeDefined()\n    })\n\n    it('should remove workspace folder', async () => {\n      expect(() => {\n        handler.addWorkspaceFolder(__filename)\n      }).toThrow(Error)\n      expect(() => {\n        handler.addWorkspaceFolder(__filename)\n      }).toThrow(Error)\n      await helper.plugin.cocAction('addWorkspaceFolder', __dirname)\n      await helper.plugin.cocAction('removeWorkspaceFolder', __dirname)\n      let folders = workspace.workspaceFolderControl.workspaceFolders\n      let uri = URI.file(__dirname).toString()\n      let find = folders.find(o => o.uri === uri)\n      expect(find).toBeUndefined()\n    })\n\n    it('should check env on vim resized', async () => {\n      await events.fire('VimResized', [80, 80])\n      expect(workspace.env.columns).toBe(80)\n      await events.fire('VimResized', [160, 80])\n      expect(workspace.env.columns).toBe(160)\n    })\n\n    it('should should error message for document not attached', async () => {\n      disposables.push(languages.registerDocumentFormatProvider(['*'], {\n        provideDocumentFormattingEdits: () => {\n          return []\n        }\n      }))\n      await handler.bufferCheck()\n      await checkFloat('Provider state')\n      await nvim.call('coc#float#close_all', [])\n      await nvim.command('edit t|let b:coc_enabled = 0')\n      await commands.executeCommand('document.checkBuffer')\n      await checkFloat('not attached')\n      await nvim.call('coc#float#close_all', [])\n      await nvim.command('edit +setl\\\\ buftype=nofile b')\n      await helper.doAction('bufferCheck')\n      await checkFloat('not attached')\n      await nvim.call('coc#float#close_all', [])\n      helper.updateConfiguration('coc.preferences.maxFileSize', '1KB')\n      await helper.edit(__filename)\n      await handler.bufferCheck()\n      await checkFloat('not attached')\n      await nvim.call('coc#float#close_all', [])\n    })\n\n    it('should check json extension', async () => {\n      let spy = jest.spyOn(extensions, 'has').mockImplementation(() => {\n        return true\n      })\n      await helper.doAction('checkJsonExtension')\n      spy.mockRestore()\n      await helper.doAction('checkJsonExtension')\n      let line = await helper.getCmdline()\n      expect(line).toBeDefined()\n    })\n\n    it('should get rootPatterns', async () => {\n      let bufnr = await nvim.call('bufnr', ['%'])\n      let res = await helper.doAction('rootPatterns', bufnr)\n      expect(res).toBeDefined()\n    })\n\n    it('should get config by key', async () => {\n      let res = await helper.doAction('getConfig', ['suggest'])\n      expect(res.autoTrigger).toBeDefined()\n    })\n\n    it('should open log', async () => {\n      await helper.doAction('openLog')\n      let bufname = await nvim.call('bufname', ['%']) as string\n      expect(bufname).toMatch('coc-nvim')\n    })\n\n    it('should get configuration of current document', async () => {\n      let config = await handler.getConfiguration('suggest')\n      let wait = config.get<number>('triggerCompletionWait')\n      expect(wait).toBe(0)\n    })\n\n    it('should get root patterns', async () => {\n      let doc = await helper.createDocument()\n      let patterns = handler.getRootPatterns(doc.bufnr)\n      expect(patterns).toBeDefined()\n      patterns = handler.getRootPatterns(999)\n      expect(patterns).toBeNull()\n    })\n  })\n\n  describe('doKeymap()', () => {\n    it('should return default value when key mapping does not exist', async () => {\n      let res = await helper.doAction('doKeymap', ['not_exists', ''])\n      expect(res).toBe('')\n    })\n\n    it('should support repeat key mapping', async () => {\n      let called = false\n      await nvim.command('nmap do <Plug>(coc-test)')\n      disposables.push(workspace.registerKeymap(['n'], 'test', () => {\n        called = true\n      }))\n      await helper.waitValue(async () => {\n        let res = await nvim.call('maparg', ['<Plug>(coc-test)', 'n']) as string\n        return res.length > 0\n      }, true)\n      await nvim.call('feedkeys', ['do', 'i'])\n      await helper.waitValue(() => {\n        return called\n      }, true)\n    })\n  })\n\n  describe('snippetCheck()', () => {\n    it('should return false when coc-snippets not found', async () => {\n      let fn = async () => {\n        expect(await handler.snippetCheck(true, false)).toBe(false)\n      }\n      await expect(fn()).rejects.toThrow(Error)\n      let spy = jest.spyOn(extensions.manager, 'call').mockImplementation(() => {\n        return Promise.resolve(true)\n      })\n      expect(await handler.snippetCheck(true, false)).toBe(true)\n      spy.mockRestore()\n    })\n\n    it('should check jump', async () => {\n      expect(await handler.snippetCheck(false, true)).toBe(false)\n      let spy = jest.spyOn(snippetManager, 'jumpable').mockImplementation(() => {\n        return true\n      })\n      expect(await handler.snippetCheck(false, true)).toBe(true)\n      spy.mockRestore()\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/helper.ts",
    "content": "import type { Buffer, Neovim, Window } from '@chemzqm/neovim'\nimport * as cp from 'child_process'\nimport crypto from 'crypto'\nimport { EventEmitter } from 'events'\nimport fs from 'fs'\nimport net, { Server } from 'net'\nimport os from 'os'\nimport path from 'path'\nimport util from 'util'\nimport { v4 as uuid } from 'uuid'\nimport { Disposable } from 'vscode-languageserver-protocol'\nimport attach from '../attach'\nimport type { Completion } from '../completion'\nimport { DurationCompleteItem } from '../completion/types'\nimport events from '../events'\nimport type Document from '../model/document'\nimport type Plugin from '../plugin'\nimport type { ProviderResult } from '../provider'\nimport { OutputChannel } from '../types'\nimport { equals } from '../util/object'\nimport { terminate } from '../util/processes'\nimport type { Workspace } from '../workspace'\nconst vimrc = path.resolve(__dirname, 'vimrc')\n\nexport interface CursorPosition {\n  bufnum: number\n  lnum: number\n  col: number\n}\n\nconst nullChannel: OutputChannel = {\n  content: '',\n  show: () => {},\n  dispose: () => {},\n  name: 'null',\n  append: () => {},\n  appendLine: () => {},\n  clear: () => {},\n  hide: () => {}\n}\n\nprocess.on('uncaughtException', err => {\n  let msg = 'Uncaught exception: ' + err.stack\n  console.error(msg)\n})\n\nexport class Helper extends EventEmitter {\n  public proc: cp.ChildProcess\n  private server: Server\n  public plugin: Plugin\n  public reportError = true\n\n  constructor() {\n    super()\n    this.setMaxListeners(99)\n  }\n\n  public get workspace(): Workspace {\n    if (!this.plugin || !this.plugin.workspace) throw new Error('helper not attached')\n    return this.plugin.workspace\n  }\n\n  public get completion(): Completion {\n    if (!this.plugin || !this.plugin.completion) throw new Error('helper not attached')\n    return this.plugin.completion\n  }\n\n  public get nvim(): Neovim {\n    return this.plugin.nvim\n  }\n\n  public async setup(init = true): Promise<Plugin> {\n    let proc = this.proc = cp.spawn(process.env.NVIM_COMMAND ?? 'nvim', ['-u', vimrc, '-i', 'NONE', '--embed'], {\n      cwd: __dirname\n    })\n    proc.unref()\n    let plugin = this.plugin = attach({ proc })\n    await this.nvim.uiAttach(160, 80, {})\n    this.nvim.call('coc#rpc#set_channel', [1], true)\n    this.nvim.on('vim_error', err => {\n      if (typeof err === 'string' && err.startsWith('Lua')) {\n        console.error('Error from vim: ', err)\n      }\n    })\n    this.nvim.on('notification', async (method, args) => {\n      if (method == 'Log') {\n        // console.log(args)\n      }\n    })\n    if (init) await plugin.init('')\n    return plugin\n  }\n\n  public async setupVim(): Promise<void> {\n    if (process.env.VIM_NODE_RPC != '1') {\n      throw new Error(`VIM_NODE_RPC should be 1`)\n    }\n    let server\n    let promise = new Promise<void>(resolve => {\n      server = this.server = net.createServer(socket => {\n        this.plugin = attach({ reader: socket, writer: socket })\n        this.nvim.on('vim_error', err => {\n          if (this.reportError) console.error('Error from vim: ', err)\n        })\n        resolve()\n      })\n    })\n    let address = await this.listenOnVim(server)\n    let proc = this.proc = cp.spawn(process.env.VIM_COMMAND ?? 'vim', ['--clean', '--not-a-term', '-u', vimrc], {\n      stdio: 'pipe',\n      shell: true,\n      cwd: __dirname,\n      env: {\n        COC_NVIM_REMOTE_ADDRESS: address,\n        ...process.env\n      }\n    })\n    proc.on('error', err => {\n      console.error(err)\n    })\n    proc.on('exit', code => {\n      if (code) console.error('vim exit with code ' + code)\n    })\n    await promise\n    await this.plugin.init('')\n  }\n\n  private async listenOnVim(server: Server): Promise<string> {\n    const isWindows = process.platform === 'win32'\n    return new Promise((resolve, reject) => {\n      if (!isWindows) {\n        // not work on old version vim.\n        const socket = path.join(os.tmpdir(), `coc-test-${uuid()}.sock`)\n        server.listen(socket, () => {\n          resolve(socket)\n        })\n        server.on('error', reject)\n        server.unref()\n      } else {\n        getPort().then(port => {\n          let localhost = '127.0.0.1'\n          server.listen(port, localhost, () => {\n            resolve(`${localhost}:${port}`)\n          })\n          server.on('error', reject)\n        }, reject)\n      }\n      server.unref()\n    })\n  }\n\n  public async reset(): Promise<void> {\n    let mode = await this.nvim.mode\n    if (mode.blocking && mode.mode == 'r') {\n      await this.nvim.input('<cr>')\n    } else if (mode.mode != 'n' || mode.blocking) {\n      await this.nvim.call('feedkeys', [String.fromCharCode(27), 'in'])\n    }\n    this.completion.cancelAndClose()\n    this.workspace.reset()\n    this.nvim.call('coc#float#close_all', [], true)\n    await this.nvim.command('silent! %bwipeout! | setl nopreviewwindow')\n    await this.workspace.document\n  }\n\n  public async shutdown(): Promise<void> {\n    if (this.plugin) this.plugin.dispose()\n    if (this.nvim) await this.nvim.quit()\n    if (this.server) this.server.close()\n    if (this.proc) terminate(this.proc)\n    if (typeof global.gc === 'function') {\n      global.gc()\n    }\n  }\n\n  public wait(ms = 30): Promise<void> {\n    return new Promise(resolve => {\n      setTimeout(() => {\n        resolve()\n      }, ms)\n    })\n  }\n\n  public async waitPrompt(): Promise<void> {\n    for (let i = 0; i < 60; i++) {\n      await this.wait(30)\n      let prompt = await this.nvim.call('coc#prompt#activated')\n      if (prompt) return\n    }\n    throw new Error('Wait prompt timeout after 2s')\n  }\n\n  public async waitPromptWin(): Promise<number> {\n    for (let i = 0; i < 60; i++) {\n      await this.wait(30)\n      let winid = await this.nvim.call('coc#dialog#get_prompt_win') as number\n      if (winid != -1) return winid\n    }\n    throw new Error('Wait prompt window timeout after 2s')\n  }\n\n  public async waitFloat(): Promise<number> {\n    for (let i = 0; i < 50; i++) {\n      await this.wait(20)\n      let winid = await this.nvim.call('GetFloatWin') as number\n      if (winid) return winid\n    }\n    throw new Error('timeout after 2s')\n  }\n\n  public async doAction(method: string, ...args: any[]): Promise<any> {\n    return await this.plugin.cocAction(method, ...args)\n  }\n\n  public async items(): Promise<DurationCompleteItem[]> {\n    return this.completion?.activeItems.slice()\n  }\n\n  public async waitPopup(): Promise<void> {\n    let visible = await this.nvim.call('coc#pum#visible')\n    if (visible) return\n    let res = await events.race(['MenuPopupChanged'], 8000)\n    if (!res) throw new Error('wait pum timeout after 8s')\n  }\n\n  public async confirmCompletion(idx: number): Promise<void> {\n    await this.nvim.call('coc#pum#select', [idx, 1, 1])\n  }\n\n  public async visible(word: string, source?: string): Promise<boolean> {\n    await this.waitPopup()\n    let items = this.completion.activeItems\n    if (!items) return false\n    let item = items.find(o => o.word == word)\n    if (!item) return false\n    if (source && item.source.name != source) return false\n    return true\n  }\n\n  public async edit(file?: string): Promise<Buffer> {\n    if (!file || !path.isAbsolute(file)) {\n      file = path.join(__dirname, file ? file : `${uuid()}`)\n    }\n    let escaped = await this.nvim.call('fnameescape', file) as string\n    await this.nvim.command(`edit ${escaped}`)\n    let doc = await this.workspace.document\n    return doc.buffer\n  }\n\n  public async createDocument(name?: string): Promise<Document> {\n    let buf = await this.edit(name)\n    let doc = this.workspace.getDocument(buf.id)\n    if (!doc) return await this.workspace.document\n    return doc\n  }\n\n  public async listInput(input: string): Promise<void> {\n    await events.fire('InputChar', ['list', input, 0])\n  }\n\n  public async getCmdline(lnum?: number): Promise<string> {\n    let str = ''\n    let n = await this.nvim.eval('&lines') as number\n    for (let i = 1, l = 70; i < l; i++) {\n      let ch = await this.nvim.call('screenchar', [lnum ?? n - 1, i]) as number\n      if (ch == -1) break\n      str += String.fromCharCode(ch)\n    }\n    return str.trim()\n  }\n\n  public updateConfiguration(key: string, value: any, disposables?: Disposable[]): () => void {\n    let curr = this.workspace.getConfiguration(key)\n    let { configurations } = this.workspace\n    configurations.updateMemoryConfig({ [key]: value })\n    let fn = () => {\n      configurations.updateMemoryConfig({ [key]: curr })\n    }\n    if (disposables) disposables.push(Disposable.create(fn))\n    return fn\n  }\n\n  public async getMatches(hlGroup: string): Promise<any[]> {\n    let res = await this.nvim.call('getmatches') as any[]\n    let list = []\n    res.forEach(o => {\n      if (o.group === hlGroup) {\n        for (const [key, value] of Object.entries(o)) {\n          if (key.startsWith('pos')) {\n            list.push(value)\n          }\n        }\n      }\n    })\n    return list\n  }\n\n  public async mockFunction(name: string, result: string | number | any): Promise<void> {\n    let content = `\n    function! ${name}(...)\n      return ${typeof result == 'number' ? result : JSON.stringify(result)}\n    endfunction`\n    await this.nvim.exec(content)\n  }\n\n  public async getFloat(kind?: string): Promise<Window> {\n    if (!kind) {\n      let ids = await this.nvim.call('coc#float#get_float_win_list') as number[]\n      return ids.length ? this.nvim.createWindow(ids[0]) : undefined\n    } else {\n      let id = await this.nvim.call('coc#float#get_float_by_kind', [kind]) as number\n      return id ? this.nvim.createWindow(id) : undefined\n    }\n  }\n\n  public async getWinLines(winid: number): Promise<string[]> {\n    return await this.nvim.eval(`getbufline(winbufnr(${winid}), 1, '$')`) as string[]\n  }\n\n  public async waitFor<T>(method: string, args: any[], value: T): Promise<void> {\n    let find = false\n    let res\n    for (let i = 0; i < 100; i++) {\n      await this.wait(20)\n      res = await this.nvim.call(method, args) as T\n      if (equals(res, value) || (value instanceof RegExp && value.test(res.toString()))) {\n        find = true\n        break\n      }\n    }\n    if (!find) {\n      throw new Error(`waitFor ${value} timeout, current: ${res}`)\n    }\n  }\n\n  public async waitNotification(event: string): Promise<void> {\n    return new Promise((resolve, reject) => {\n      let fn = (method: string) => {\n        if (method == event) {\n          clearTimeout(timer)\n          this.nvim.removeListener('notification', fn)\n          resolve()\n        }\n      }\n      let timer = setTimeout(() => {\n        this.nvim.removeListener('notification', fn)\n        reject(new Error('wait notification timeout after 2s'))\n      }, 2000)\n      this.nvim.on('notification', fn)\n    })\n  }\n\n  public async waitValue<T>(fn: () => ProviderResult<T>, value: T): Promise<void> {\n    let find = false\n    for (let i = 0; i < 200; i++) {\n      await this.wait(20)\n      let res = await Promise.resolve(fn())\n      if (equals(res, value)) {\n        find = true\n        break\n      }\n    }\n    if (!find) {\n      throw new Error(`waitValue ${value} timeout`)\n    }\n  }\n\n  public createNullChannel(): OutputChannel {\n    return nullChannel\n  }\n\n  public generateRandomHash(algorithm = 'sha256') {\n    const randomString = Math.random().toString(36).substring(2) // 生成随机字符串\n    const hash = crypto.createHash(algorithm)\n      .update(randomString)\n      .digest('hex') // 输出十六进制格式\n    return hash\n  }\n}\n\nexport async function createTmpFile(content: string, disposables?: Disposable[]): Promise<string> {\n  let tmpFolder = path.join(os.tmpdir(), `coc-${process.pid}`)\n  if (!fs.existsSync(tmpFolder)) {\n    fs.mkdirSync(tmpFolder)\n  }\n  let fsPath = path.join(tmpFolder, uuid())\n  await util.promisify(fs.writeFile)(fsPath, content, 'utf8')\n  if (disposables) {\n    disposables.push(Disposable.create(() => {\n      if (fs.existsSync(fsPath)) fs.unlinkSync(fsPath)\n    }))\n  }\n  return fsPath\n}\n\nexport function makeLine(length) {\n  let result = ''\n  let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 (){};,\\\\<>+=`^*!@#$%[]:\"/?'\n  let charactersLength = characters.length\n  for (let i = 0; i < length; i++) {\n    result += characters.charAt(Math.floor(Math.random() *\n      charactersLength))\n  }\n  return result\n}\n\nlet currPort = 5000\nexport function getPort(): Promise<number> {\n  let port = currPort\n  let fn = cb => {\n    let server = net.createServer()\n    server.listen(port, () => {\n      server.once('close', () => {\n        currPort = port + 1\n        cb(port)\n      })\n      server.close()\n    })\n    server.on('error', () => {\n      port += 1\n      fn(cb)\n    })\n  }\n  return new Promise(resolve => {\n    fn(resolve)\n  })\n}\n\nexport default new Helper()\n"
  },
  {
    "path": "src/__tests__/list/commandTask.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport path from 'path'\nimport { ListContext, ListTask } from '../../list/types'\nimport manager from '../../list/manager'\nimport helper, { createTmpFile } from '../helper'\nimport BasicList from '../../list/basic'\nimport { Disposable } from 'vscode-languageserver-protocol'\nimport { disposeAll } from '../../util'\n\nclass DataList extends BasicList {\n  public name = 'data'\n  public async loadItems(_context: ListContext): Promise<ListTask> {\n    let fsPath = await createTmpFile(`console.log('foo');console.log('');console.log('bar');`)\n    return this.createCommandTask({\n      cmd: 'node',\n      args: [fsPath],\n      cwd: path.dirname(fsPath),\n      onLine: line => {\n        if (!line) return undefined\n        return {\n          label: line\n        }\n      }\n    })\n  }\n}\n\nclass SleepList extends BasicList {\n  public name = 'sleep'\n  public loadItems(_context: ListContext): Promise<ListTask> {\n    return Promise.resolve(this.createCommandTask({\n      cmd: 'sleep',\n      args: ['10'],\n      onLine: line => {\n        return {\n          label: line\n        }\n      }\n    }))\n  }\n}\n\nclass StderrList extends BasicList {\n  public name = 'stderr'\n  public async loadItems(_context: ListContext): Promise<ListTask> {\n    let fsPath = await createTmpFile(`console.error('stderr');console.log('stdout')`)\n    return Promise.resolve(this.createCommandTask({\n      cmd: 'node',\n      args: [fsPath],\n      cwd: path.dirname(fsPath),\n      onLine: line => {\n        return {\n          label: line\n        }\n      }\n    }))\n  }\n}\n\nclass ErrorTask extends BasicList {\n  public name = 'error'\n  public async loadItems(_context: ListContext): Promise<ListTask> {\n    return Promise.resolve(this.createCommandTask({\n      cmd: 'NOT_EXISTS',\n      args: [],\n      cwd: __dirname,\n      onLine: line => {\n        return {\n          label: line\n        }\n      }\n    }))\n  }\n}\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  manager.reset()\n  await helper.reset()\n})\n\ndescribe('Command task', () => {\n  it('should not show stderr', async () => {\n    disposables.push(manager.registerList(new StderrList()))\n    await manager.start(['stderr'])\n    await manager.session.ui.ready\n    let lines = await nvim.call('getline', [1, '$']) as string[]\n    expect(lines).toEqual(['stdout'])\n  })\n\n  it('should not show error', async () => {\n    disposables.push(manager.registerList(new ErrorTask()))\n    await manager.start(['error'])\n    await helper.wait(300)\n    await nvim.command('redraw')\n    let len = manager.session.ui.length\n    expect(len).toBe(0)\n  })\n\n  it('should create command task', async () => {\n    let list = new DataList()\n    disposables.push(manager.registerList(list))\n    await manager.start(['data'])\n    await manager.session.ui.ready\n    await helper.wait(100)\n    let lines = await nvim.call('getline', [1, '$']) as string[]\n    expect(lines).toEqual(['foo', 'bar'])\n  })\n\n  it('should stop command task', async () => {\n    let list = new SleepList()\n    disposables.push(manager.registerList(list))\n    await manager.start(['sleep'])\n    manager.session.stop()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/list/history.test.ts",
    "content": "import History from '../../list/history'\nimport { DataBase } from '../../list/db'\nimport os from 'os'\nimport fs from 'fs'\nimport path from 'path'\nimport { v4 as uuid } from 'uuid'\n\nfunction createTmpDir(): string {\n  let dir = path.join(os.tmpdir(), uuid())\n  fs.mkdirSync(dir, { recursive: true })\n  return dir\n}\n\nafterEach(() => {\n  let DB_PATH = path.join(process.env.COC_DATA_HOME, 'list_history.dat')\n  if (fs.existsSync(DB_PATH)) {\n    fs.unlinkSync(DB_PATH)\n  }\n})\n\ndescribe('History', () => {\n  it('should migrate history.json', async () => {\n    let dir = createTmpDir()\n    History.migrate(dir)\n    History.migrate(path.join(os.tmpdir(), 'not_exists'))\n    dir = createTmpDir()\n    let file = path.join(dir, 'list-a-history.json')\n    fs.writeFileSync(file, '{\"x\": 1}')\n    History.migrate(dir)\n    dir = createTmpDir()\n    file = path.join(dir, 'list-mrn-history.json')\n    let obj = {\n      'L1VzZXJzL2NoZW16cW0vdmltLWRldi9jb2MubnZpbQ==': ['list']\n    }\n    fs.writeFileSync(file, JSON.stringify(obj, null, 2))\n    History.migrate(dir)\n  })\n\n  it('should filter history', async () => {\n    let db = new DataBase()\n    db.save()\n    db.addItem('name', 'text', '/a/b')\n    let p = { input: '' }\n    let history = new History(p, 'name', db, '/a/b')\n    history.filter()\n    expect(history.filtered).toEqual(['text'])\n    p.input = 't'\n    history.filter()\n    expect(history.filtered).toEqual(['text'])\n    history.previous()\n    history.filter()\n    expect(history.filtered).toEqual(['text'])\n  })\n\n  it('should add item', async () => {\n    let db = new DataBase()\n    let p = { input: '' }\n    let history = new History(p, 'name', db, '/a/b')\n    history.add()\n    p.input = 'input'\n    history.add()\n    p.input = ''\n    history.filter()\n    expect(history.filtered).toEqual(['input'])\n  })\n\n  it('should change to previous', async () => {\n    let db = new DataBase()\n    let p = { input: '' }\n    let history = new History(p, 'name', db, '/a/b')\n    history.previous()\n    db.addItem('name', 'one', '/a/b')\n    db.addItem('name', 'two', '/a/b')\n    db.addItem('name', 'three', '/a/b/c')\n    history.filter()\n    history.previous()\n    history.previous()\n    expect(history.index).toBe(0)\n    expect(history.curr).toBe('one')\n  })\n\n  it('should change to next', async () => {\n    let db = new DataBase()\n    let p = { input: '' }\n    let history = new History(p, 'name', db, '/a/b')\n    history.next()\n    db.addItem('name', 'one', '/a/b')\n    db.addItem('name', 'two', '/a/b')\n    db.addItem('name', 'three', '/a/b/c')\n    history.filter()\n    history.next()\n    history.next()\n    history.next()\n    expect(history.index).toBe(0)\n    expect(history.curr).toBe('one')\n  })\n})\n\ndescribe('DataBase', () => {\n  it('should not throw on load', async () => {\n    let spy = jest.spyOn(DataBase.prototype, 'load').mockImplementation(() => {\n      throw new Error('error')\n    })\n    new DataBase()\n    spy.mockRestore()\n  })\n\n  it('should add items', async () => {\n    let db = new DataBase()\n    db.addItem('name', 'x'.repeat(260), '/a/b/c')\n    let item = db.currItems[0]\n    expect(item[0].length).toBe(255)\n    db.addItem('name', 'xy', '/a/b/c')\n    db.addItem('name', 'xy', '/a/b/c')\n    expect(db.currItems.length).toBe(2)\n  })\n\n  it('should save data', async () => {\n    let db = new DataBase()\n    db.addItem('name', 'text', '/a/b/c')\n    db.addItem('other_name', 'te', '/a/b/x/y')\n    db.save()\n    let d = new DataBase()\n    expect(d.currItems.length).toBe(2)\n  })\n})\n\n"
  },
  {
    "path": "src/__tests__/list/manager.test.ts",
    "content": "import { Neovim, Window } from '@chemzqm/neovim'\nimport EventEmitter from 'events'\nimport path from 'path'\nimport { Range } from 'vscode-languageserver-types'\nimport events from '../../events'\nimport manager, { createConfigurationNode, ListManager } from '../../list/manager'\nimport { IList } from '../../list/types'\nimport { QuickfixItem } from '../../types'\nimport { toArray } from '../../util/array'\nimport { CancellationError } from '../../util/errors'\nimport window from '../../window'\nimport helper from '../helper'\n\nlet nvim: Neovim\nconst locations: ReadonlyArray<QuickfixItem> = [{\n  filename: __filename,\n  col: 2,\n  lnum: 1,\n  text: 'foo'\n}, {\n  filename: __filename,\n  col: 1,\n  lnum: 2,\n  text: 'Bar'\n}, {\n  filename: __filename,\n  col: 1,\n  lnum: 3,\n  text: 'option'\n}]\n\nasync function getFloats(): Promise<Window[]> {\n  let ids = await nvim.call('coc#float#get_float_win_list', []) as number[]\n  if (!ids) return []\n  return ids.map(id => nvim.createWindow(id))\n}\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  await nvim.setVar('coc_jump_locations', locations)\n})\n\nafterEach(async () => {\n  manager.reset()\n  await helper.reset()\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\ndescribe('list', () => {\n  describe('createConfigurationNode', () => {\n    it('should createConfigurationNode', async () => {\n      expect(createConfigurationNode('foo', true)).toBeDefined()\n      expect(createConfigurationNode('bar', false)).toBeDefined()\n      expect(createConfigurationNode('foo', false, 'id')).toBeDefined()\n    })\n  })\n\n  describe('events', () => {\n    it('should cancel and enable prompt', async () => {\n      let winid = await nvim.call('win_getid')\n      await manager.start(['location'])\n      await manager.session.ui.ready\n      await nvim.call('win_gotoid', [winid])\n      await helper.waitValue(async () => {\n        return await nvim.call('coc#prompt#activated')\n      }, 0)\n      await nvim.command('wincmd p')\n      await helper.waitPrompt()\n    })\n  })\n\n  describe('list commands', () => {\n    it('should not quit list with --no-quit', async () => {\n      let list: IList = {\n        name: 'test',\n        actions: [{\n          name: 'open', execute: _item => {\n            // noop\n          }\n        }],\n        defaultAction: 'open',\n        loadItems: () => Promise.resolve([{ label: 'foo' }, { label: 'bar' }]),\n        resolveItem: item => {\n          item.label = item.label.slice(0, 1)\n          return Promise.resolve(item)\n        }\n      }\n      global.__TEST__ = false\n      let disposable = manager.registerList(list)\n      global.__TEST__ = true\n      await manager.start(['--normal', '--no-quit', 'test'])\n      await manager.session.ui.ready\n      let id = await nvim.eval('win_getid()') as number\n      await manager.doAction()\n      disposable.dispose()\n      let wins = await nvim.windows\n      let ids = wins.map(o => o.id)\n      expect(ids).toContain(id)\n    })\n\n    it('should do default action for first item', async () => {\n      expect(ListManager).toBeDefined()\n      await manager.start(['--normal', '--first', 'location'])\n      let filename = path.basename(__filename)\n      await helper.waitValue(async () => {\n        let name = await nvim.eval('bufname(\"%\")') as string\n        return name.includes(filename)\n      }, true)\n      let pos = await nvim.eval('getcurpos()')\n      expect(pos[1]).toBe(1)\n      expect(pos[2]).toBe(2)\n    })\n\n    it('should goto next & previous', async () => {\n      await manager.start(['location'])\n      await manager.session?.ui.ready\n      await helper.waitPrompt()\n      await manager.session?.ui.ready\n      await manager.doAction()\n      await helper.doAction('listCancel')\n      let bufname = await nvim.eval('expand(\"%:p\")')\n      expect(bufname).toMatch('manager.test.ts')\n      await helper.doAction('listNext')\n      let line = await nvim.call('line', '.')\n      expect(line).toBe(2)\n      await helper.doAction('listPrev')\n      line = await nvim.call('line', '.')\n      expect(line).toBe(1)\n    })\n\n    it('should parse arguments', async () => {\n      await manager.start(['--input=test', '--reverse', '--normal', '--no-sort', '--ignore-case', '--top', '--number-select', '--auto-preview', '--strict', 'location'])\n      await manager.session?.ui.ready\n      let opts = manager.session?.listOptions\n      expect(opts).toEqual({\n        reverse: true,\n        numberSelect: true,\n        autoPreview: true,\n        first: false,\n        input: 'test',\n        interactive: false,\n        matcher: 'strict',\n        ignorecase: true,\n        position: 'top',\n        mode: 'normal',\n        noQuit: false,\n        sort: false\n      })\n    })\n  })\n\n  describe('list configuration', () => {\n    it('should change indicator', async () => {\n      helper.updateConfiguration('list.indicator', '>>')\n      manager.prompt.input = 'foo'\n      await manager.start(['location'])\n      await manager.session.ui.ready\n      await helper.waitValue(async () => {\n        let line = await helper.getCmdline()\n        return line.includes('>>')\n      }, true)\n      await events.fire('FocusGained', [])\n    })\n\n    it('should split right for preview window', async () => {\n      helper.updateConfiguration('list.previewSplitRight', true)\n      await manager.doAction('preview')\n      await manager.resume()\n      let win = await nvim.window\n      await manager.start(['location'])\n      await manager.session?.ui.ready\n      await manager.doAction('preview')\n      await helper.waitValue(async () => {\n        let wins = await nvim.windows\n        return wins.length\n      }, 3)\n      manager.prompt.cancel()\n      await nvim.call('win_gotoid', [win.id])\n      await nvim.command('wincmd l')\n      let curr = await nvim.window\n      let isPreview = await curr.getVar('previewwindow')\n      expect(isPreview).toBe(1)\n    })\n\n    it('should use smartcase for strict match', async () => {\n      helper.updateConfiguration('list.smartCase', true)\n      await manager.start(['--input=Man', '--strict', 'location'])\n      await manager.session?.ui.ready\n      let items = await manager.session?.ui.getItems()\n      expect(items.length).toBe(0)\n    })\n\n    it('should use smartcase for fuzzy match', async () => {\n      helper.updateConfiguration('list.smartCase', true)\n      await manager.start(['--input=Man', 'location'])\n      await manager.session?.ui.ready\n      let items = await manager.session?.ui.getItems()\n      expect(items.length).toBe(0)\n    })\n\n    it('should toggle selection mode', async () => {\n      await manager.start(['--normal', 'location'])\n      await manager.session?.ui.ready\n      await helper.waitPrompt()\n      await window.selectRange(Range.create(0, 0, 3, 0))\n      await manager.session?.ui.toggleSelection()\n      let items = await manager.session?.ui.getItems()\n      expect(items.length).toBeGreaterThan(0)\n    })\n\n    it('should change next and previous keymap', async () => {\n      helper.updateConfiguration('list.nextKeymap', '<tab>')\n      helper.updateConfiguration('list.previousKeymap', '<s-tab>')\n      await manager.start(['location'])\n      await manager.session.ui.ready\n      await helper.waitPrompt()\n      await nvim.eval('feedkeys(\"\\\\<tab>\", \"in\")')\n      await helper.waitValue(async () => {\n        let line = await nvim.line\n        return line.includes('Bar')\n      }, true)\n      await nvim.eval('feedkeys(\"\\\\<s-tab>\", \"in\")')\n      await helper.waitValue(async () => {\n        let line = await nvim.line\n        return line.includes('foo')\n      }, true)\n    })\n\n    it('should respect mouse events', async () => {\n      async function setMouseEvent(line: number): Promise<void> {\n        let winid = manager.session?.ui.winid\n        await nvim.command(`let v:mouse_winid = ${winid}`)\n        await nvim.command(`let v:mouse_lnum = ${line}`)\n        await nvim.command(`let v:mouse_col = 1`)\n      }\n      await manager.start(['--normal', 'location'])\n      await manager.session.ui.ready\n      await setMouseEvent(1)\n      await manager.onNormalInput('<LeftMouse>')\n      await setMouseEvent(2)\n      await manager.onNormalInput('<LeftDrag>')\n      await setMouseEvent(3)\n      await manager.onNormalInput('<LeftRelease>')\n      await helper.waitValue(async () => {\n        let items = await manager.session?.ui.getItems()\n        return items.length\n      }, 3)\n    })\n\n    it('should toggle preview', async () => {\n      helper.updateConfiguration('list.floatPreview', true)\n      await manager.start(['--normal', '--auto-preview', 'location'])\n      await manager.session.ui.ready\n      await helper.waitValue(async () => {\n        let wins = await getFloats()\n        return wins.length > 0\n      }, true)\n      await manager.togglePreview()\n      await helper.waitValue(async () => {\n        let wins = await getFloats()\n        return wins.length > 0\n      }, false)\n      await manager.togglePreview()\n      manager.session.ui.setCursor(2)\n      await helper.waitValue(async () => {\n        let wins = await getFloats()\n        return wins.length > 0\n      }, true)\n    })\n\n    it('should show help of current list', async () => {\n      await manager.start(['--normal', '--auto-preview', 'location'])\n      await manager.session.ui.ready\n      await manager.session?.showHelp()\n      let bufname = await nvim.call('bufname', '%')\n      expect(bufname).toBe('[LIST HELP]')\n    })\n\n    it('should resolve list item', async () => {\n      let list: IList = {\n        name: 'test',\n        actions: [{\n          name: 'open', execute: _item => {\n            // noop\n          }\n        }],\n        defaultAction: 'open',\n        loadItems: () => Promise.resolve([{ label: 'foo' }, { label: 'foo bar' }]),\n        resolveItem: item => {\n          item.label = 'foo bar'\n          return Promise.resolve(item)\n        }\n      }\n      let disposable = manager.registerList(list, true)\n      await manager.start(['--normal', 'test'])\n      await manager.session.ui.ready\n      await helper.waitFor('getline', ['.'], 'foo bar')\n      await manager.session.next()\n      await manager.session.resolveItem()\n      disposable.dispose()\n    })\n  })\n\n  describe('descriptions', () => {\n    it('should get descriptions', async () => {\n      let res = await helper.doAction('listDescriptions')\n      expect(res).toBeDefined()\n      expect(res.location).toBeDefined()\n    })\n  })\n\n  describe('switchMatcher()', () => {\n    it('should switch matcher', async () => {\n      await manager.switchMatcher()\n      await manager.start(['--normal', 'location'])\n      manager.session.onInputChange()\n      await manager.session.ui.ready\n      const assertMatcher = (value: string) => {\n        expect(manager.session.listOptions.matcher).toBe(value)\n      }\n      await manager.switchMatcher()\n      assertMatcher('strict')\n      await manager.switchMatcher()\n      assertMatcher('regex')\n      await manager.switchMatcher()\n      assertMatcher('fuzzy')\n      await manager.switchMatcher()\n      assertMatcher('strict')\n      manager.session.listOptions.interactive = true\n      await manager.switchMatcher()\n      assertMatcher('strict')\n      await manager.cancel(true)\n    })\n  })\n\n  describe('loadItems()', () => {\n    it('should ignore cancellation error', async () => {\n      let list: IList = {\n        name: 'cancel',\n        actions: [{ name: 'open', execute: () => {} }],\n        defaultAction: 'open',\n        loadItems: () => Promise.reject(new CancellationError()),\n      }\n      let disposable = manager.registerList(list)\n      await manager.start(['cancel'])\n      disposable.dispose()\n      let line = await helper.getCmdline()\n      expect(line).toBe('')\n    })\n\n    it('should load items for list', async () => {\n      let res = await manager.loadItems('location')\n      expect(res.length).toBeGreaterThan(0)\n      Object.assign(manager, { lastSession: undefined })\n      manager.toggleMode()\n      manager.stop()\n      res = await helper.doAction('listLoadItems', '')\n      expect(res).toBeUndefined()\n      let error = true\n      manager.registerList({\n        name: 'emitter',\n        actions: [],\n        defaultAction: '',\n        loadItems: () => {\n          let emitter: any = new EventEmitter()\n          let interval\n          let timeout\n          emitter.dispose = () => {\n            emitter.removeAllListeners()\n            clearInterval(interval)\n            clearTimeout(timeout)\n          }\n          if (error) {\n            timeout = setTimeout(() => {\n              emitter.emit('error', new Error('error'))\n              emitter.emit('end')\n            }, 2)\n          } else {\n            timeout = setTimeout(() => {\n              emitter.emit('data', { label: 'foo' })\n              emitter.emit('end')\n            }, 2)\n          }\n          interval = setInterval(() => {\n            emitter.emit('data', { label: 'bar' })\n            emitter.emit('error', new Error('error'))\n          }, 10)\n          return emitter\n        }\n      })\n      await expect(async () => {\n        await manager.loadItems('emitter')\n      }).rejects.toThrow(Error)\n      error = false\n      res = await manager.loadItems('emitter')\n      expect(res.length).toBe(1)\n      await helper.wait(50)\n    })\n  })\n\n  describe('onInsertInput()', () => {\n    it('should handle insert input', async () => {\n      await manager.onInsertInput('k')\n      await manager.onInsertInput('<LeftMouse>')\n      await manager.start(['--number-select', 'location'])\n      await manager.session.ui.ready\n      await manager.onInsertInput('1')\n      await manager.onInsertInput(String.fromCharCode(129))\n      let basename = path.basename(__filename)\n      await helper.waitValue(async () => {\n        let bufname = await nvim.call('bufname', ['%']) as string\n        return bufname.includes(basename)\n      }, true)\n    })\n\n    it('should ignore invalid input', async () => {\n      await manager.start(['location'])\n      await manager.session.ui.ready\n      await manager.onInsertInput('<X-y>')\n      await manager.onInsertInput(String.fromCharCode(65533))\n      await manager.onInsertInput(String.fromCharCode(30))\n      expect(manager.isActivated).toBe(true)\n    })\n\n    it('should ignore <plug> insert', async () => {\n      await manager.start(['location'])\n      await manager.session.ui.ready\n      await helper.listInput('<plug>')\n      await helper.listInput('x')\n      expect(manager.isActivated).toBe(true)\n    })\n  })\n\n  describe('parseArgs()', () => {\n    it('should show error for bad option', async () => {\n      manager.parseArgs(['$x', 'location'])\n      await helper.wait(20)\n      let msg = await helper.getCmdline()\n      expect(msg).toMatch('Invalid list option')\n      manager.parseArgs(['-xyz', 'location'])\n      msg = await helper.getCmdline()\n      expect(msg).toMatch('Invalid option')\n    })\n\n    it('should parse valid arguments', async () => {\n      let res = manager.parseArgs([])\n      expect(res.list.name).toBe('lists')\n      res = manager.parseArgs(['lists', '-foo'])\n      expect(res.listArgs).toEqual(['-foo'])\n    })\n\n    it('should show error for interactive with list not support interactive', async () => {\n      manager.parseArgs(['--interactive', 'location'])\n      let msg = await helper.getCmdline()\n      expect(msg).toMatch('not supported')\n    })\n  })\n\n  describe('resume()', () => {\n    it('should resume by name', async () => {\n      await events.fire('FocusGained', [])\n      await manager.start(['location'])\n      await manager.session.ui.ready\n      await manager.session.hide()\n      await manager.resume('location')\n      await helper.doAction('listResume')\n      expect(manager.isActivated).toBe(true)\n      await manager.resume('not_exists')\n      let line = await helper.getCmdline()\n      expect(line).toMatch('Can\\'t find')\n    })\n  })\n\n  describe('triggerCursorMoved()', () => {\n    it('should triggerCursorMoved autocmd', async () => {\n      let called = 0\n      let disposable = events.on('CursorMoved', () => {\n        called++\n      })\n      Object.assign(events, { _cursor: undefined })\n      Object.assign(nvim, { isVim: true })\n      manager.triggerCursorMoved()\n      manager.triggerCursorMoved()\n      Object.assign(nvim, { isVim: false })\n      await helper.waitValue(() => {\n        return called\n      }, 1)\n      disposable.dispose()\n    })\n  })\n\n  describe('first(), last()', () => {\n    it('should get session by name', async () => {\n      let last: string\n      let list: IList = {\n        name: 'test',\n        actions: [{\n          name: 'open',\n          execute: item => {\n            last = toArray(item)[0].label\n          }\n        }],\n        defaultAction: 'open',\n        loadItems: () => Promise.resolve([{ label: 'foo' }, { label: 'bar' }])\n      }\n      manager.registerList(list, true)\n      await manager.start(['test'])\n      await manager.session.ui.ready\n      await helper.doAction('listFirst', 'a')\n      await helper.doAction('listLast', 'a')\n      await manager.first('test')\n      expect(last).toBe('foo')\n      await manager.last('test')\n      expect(last).toBe('bar')\n    })\n  })\n\n  describe('registerList()', () => {\n    it('should recreate list', async () => {\n      let fn = jest.fn()\n      let list: IList = {\n        name: 'test',\n        actions: [{\n          name: 'open', execute: _item => {\n            // noop\n          }\n        }],\n        defaultAction: 'open',\n        loadItems: () => Promise.resolve([{ label: 'foo' }, { label: 'bar' }]),\n        dispose: () => {\n          fn()\n        }\n      }\n      manager.registerList(list, true)\n      helper.updateConfiguration('list.source.test.defaultAction', 'open')\n      let disposable = manager.registerList(list, true)\n      disposable.dispose()\n      expect(fn).toHaveBeenCalled()\n    })\n  })\n\n  describe('start()', () => {\n    it('should show error when loadItems throws', async () => {\n      let list: IList = {\n        name: 'test',\n        actions: [{\n          name: 'open',\n          execute: _item => {\n          }\n        }],\n        defaultAction: 'open',\n        loadItems: () => {\n          throw new Error('test error')\n        }\n      }\n      manager.registerList(list, true)\n      await manager.start(['test'])\n      await helper.wait(20)\n    })\n  })\n\n  describe('list options', () => {\n    it('should respect auto preview option', async () => {\n      await manager.start(['--auto-preview', 'location'])\n      await manager.session.ui.ready\n      await helper.waitFor('winnr', ['$'], 3)\n      let previewWinnr = await nvim.call('coc#list#has_preview')\n      expect(previewWinnr).toBe(2)\n      let bufnr = await nvim.call('winbufnr', previewWinnr) as number\n      let buf = nvim.createBuffer(bufnr)\n      let name = await buf.name\n      expect(name).toMatch('manager.test.ts')\n      await nvim.eval('feedkeys(\"j\", \"in\")')\n      await helper.wait(30)\n      let winnr = await nvim.call('coc#list#has_preview')\n      expect(winnr).toBe(previewWinnr)\n    })\n\n    it('should respect input option', async () => {\n      await manager.start(['--input=foo', 'location'])\n      await manager.session.ui.ready\n      let line = await helper.getCmdline()\n      expect(line).toMatch('foo')\n      expect(manager.isActivated).toBe(true)\n    })\n\n    it('should respect regex filter', async () => {\n      await manager.start(['--input=f.o', '--regex', 'location'])\n      await manager.session.ui.ready\n      let item = await manager.session?.ui.item\n      expect(item.label).toMatch('foo')\n      await manager.session.hide()\n      await manager.start(['--input=f.o', '--ignore-case', '--regex', 'location'])\n      await manager.session.ui.ready\n      item = await manager.session?.ui.item\n      expect(item.label).toMatch('foo')\n    })\n\n    it('should respect normal option', async () => {\n      await manager.start(['--normal', 'location'])\n      await manager.session.ui.ready\n      let line = await helper.getCmdline()\n      expect(line).toBe('')\n    })\n\n    it('should respect nosort option', async () => {\n      await manager.start(['--ignore-case', '--no-sort', 'location'])\n      await manager.session.ui.ready\n      await nvim.input('oo')\n      await helper.waitValue(async () => {\n        let line = await nvim.call('getline', ['.']) as string\n        return line.includes('foo')\n      }, true)\n    })\n\n    it('should respect ignorecase option', async () => {\n      await manager.start(['--ignore-case', '--strict', 'location'])\n      await manager.session.ui.ready\n      expect(manager.isActivated).toBe(true)\n      await nvim.input('bar')\n      await helper.waitValue(() => {\n        return manager.session?.ui.length\n      }, 1)\n      let line = await nvim.line\n      expect(line).toMatch('Bar')\n    })\n\n    it('should respect top & height option', async () => {\n      await manager.start(['--top', '--height=2', 'location'])\n      await manager.session.ui.ready\n      let nr = await nvim.call('winnr')\n      expect(nr).toBe(1)\n      let win = await nvim.window\n      let height = await win.height\n      expect(height).toBe(2)\n    })\n\n    it('should respect number select option', async () => {\n      await manager.start(['--number-select', 'location'])\n      await manager.session.ui.ready\n      await nvim.eval('feedkeys(\"2\", \"in\")')\n      let lnum = locations[1].lnum\n      await helper.waitFor('line', ['.'], lnum)\n    })\n\n    it('should respect tab option', async () => {\n      await manager.start(['--tab', '--auto-preview', 'location'])\n      await manager.session.ui.ready\n      await helper.waitFor('tabpagenr', ['$'], 2)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/list/mappings.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport path from 'path'\nimport { CancellationToken, Disposable } from 'vscode-languageserver-protocol'\nimport BasicList from '../../list/basic'\nimport listConfiguration, { ListConfiguration } from '../../list/configuration'\nimport manager from '../../list/manager'\nimport { IList, ListContext, ListItem } from '../../list/types'\nimport { QuickfixItem } from '../../types'\nimport { disposeAll } from '../../util/index'\nimport window from '../../window'\nimport helper from '../helper'\n\nclass TestList extends BasicList {\n  public name = 'test'\n  public timeout = 3000\n  public text = 'test'\n  public detail = 'detail'\n  public loadItems(_context: ListContext, token: CancellationToken): Promise<ListItem[]> {\n    return new Promise(resolve => {\n      let timer = setTimeout(() => {\n        resolve([{ label: this.text }])\n      }, this.timeout)\n      token.onCancellationRequested(() => {\n        if (timer) {\n          clearTimeout(timer)\n          resolve([])\n        }\n      })\n    })\n  }\n}\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nconst locations: ReadonlyArray<QuickfixItem> = [{\n  filename: __filename,\n  col: 2,\n  lnum: 1,\n  text: 'foo'\n}, {\n  filename: __filename,\n  col: 1,\n  lnum: 2,\n  text: 'Bar'\n}, {\n  filename: __filename,\n  col: 1,\n  lnum: 3,\n  text: 'option'\n}]\n\nasync function waitPreviewWindow(): Promise<void> {\n  for (let i = 0; i < 40; i++) {\n    await helper.wait(50)\n    let has = await nvim.call('coc#list#has_preview') as number\n    if (has > 0) return\n  }\n  throw new Error('timeout after 2s')\n}\n\nconst lineList: IList = {\n  name: 'lines',\n  actions: [{\n    name: 'open',\n    execute: async item => {\n      await window.moveTo({\n        line: (item as ListItem).data.line,\n        character: 0\n      })\n      // noop\n    }\n  }],\n  defaultAction: 'open',\n  async loadItems(_context, _token): Promise<ListItem[]> {\n    let lines = []\n    for (let i = 0; i < 100; i++) {\n      lines.push(i.toString())\n    }\n    return lines.map((line, idx) => ({\n      label: line,\n      data: { line: idx }\n    }))\n  }\n}\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  await nvim.setVar('coc_jump_locations', locations)\n})\n\nafterAll(async () => {\n  disposeAll(disposables)\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  manager.reset()\n  await helper.reset()\n})\n\ndescribe('isValidAction()', () => {\n  it('should check invalid action', () => {\n    let mappings = manager.mappings\n    expect(mappings.isValidAction('foo')).toBe(false)\n    expect(mappings.isValidAction('do:switch')).toBe(true)\n    expect(mappings.isValidAction('eval:@*')).toBe(true)\n    expect(mappings.isValidAction('undefined:undefined')).toBe(false)\n  })\n})\n\ndescribe('User mappings', () => {\n  it('should not throw when session not exists', async () => {\n    let mappings = manager.mappings\n    let res = await mappings.navigate(true)\n    expect(res).toBe(false)\n    res = await mappings.navigate(false)\n    expect(res).toBe(false)\n  })\n\n  it('should show warning for invalid key', async () => {\n    expect(ListConfiguration).toBeDefined()\n    expect(listConfiguration.fixKey('<c-a>')).toBe('<C-a>')\n    listConfiguration.fixKey('<a')\n    let msg = await helper.getCmdline()\n    expect(msg).toMatch('not supported')\n    let revert = helper.updateConfiguration('list.insertMappings', {\n      xy: 'action:tabe',\n    })\n    await helper.wait(30)\n    msg = await helper.getCmdline()\n    revert()\n    await nvim.command('echo \"\"')\n    expect(msg).toMatch('Invalid configuration')\n    revert = helper.updateConfiguration('list.insertMappings', {\n      '<M-x>': 'action:tabe',\n    })\n    await helper.wait(30)\n    msg = await helper.getCmdline()\n    revert()\n    expect(msg).toMatch('Invalid configuration')\n    revert = helper.updateConfiguration('list.insertMappings', {\n      '<C-a>': 'foo:bar',\n    })\n    await helper.wait(30)\n    msg = await helper.getCmdline()\n    revert()\n    expect(msg).toMatch('Invalid configuration')\n  })\n\n  it('should execute action keymap', async () => {\n    let revert = helper.updateConfiguration('list.insertMappings', {\n      '<C-d>': 'action:quickfix',\n    })\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-d>')\n    let buftype = await nvim.eval('&buftype')\n    expect(buftype).toBe('quickfix')\n    revert()\n  })\n\n  it('should execute expr keymap', async () => {\n    await helper.mockFunction('TabOpen', 'quickfix')\n    helper.updateConfiguration('list.insertMappings', {\n      '<C-t>': 'expr:TabOpen',\n    })\n    helper.updateConfiguration('list.normalMappings', {\n      t: 'expr:TabOpen',\n    })\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-t>')\n    let buftype = await nvim.eval('&buftype')\n    expect(buftype).toBe('quickfix')\n    await nvim.command('close')\n    await manager.start(['--normal', 'location'])\n    await manager.session.ui.ready\n    await helper.listInput('t')\n    buftype = await nvim.eval('&buftype')\n    expect(buftype).toBe('quickfix')\n  })\n\n  it('should execute do mappings', async () => {\n    helper.updateConfiguration('list.previousKeymap', '<C-j>')\n    helper.updateConfiguration('list.nextKeymap', '<C-k>')\n    helper.updateConfiguration('list.insertMappings', {\n      '<C-n>': 'do:next',\n      '<C-p>': 'do:previous',\n      '<C-d>': 'do:exit',\n    })\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-n>')\n    let item = await manager.session?.ui.item\n    expect(item.label).toMatch(locations[1].text)\n    await helper.listInput('<C-p>')\n    item = await manager.session?.ui.item\n    expect(item.label).toMatch(locations[0].text)\n    await helper.listInput('<C-k>')\n    item = await manager.session?.ui.item\n    expect(item.label).toMatch(locations[1].text)\n    await helper.listInput('<C-j>')\n    item = await manager.session?.ui.item\n    expect(item.label).toMatch(locations[0].text)\n    await helper.listInput('<C-d>')\n    expect(manager.isActivated).toBe(false)\n  })\n\n  it('should execute prompt mappings', async () => {\n    helper.updateConfiguration('list.insertMappings', {\n      '<C-p>': 'prompt:previous',\n      '<C-n>': 'prompt:next',\n      '<C-a>': 'prompt:start',\n      '<C-e>': 'prompt:end',\n      '<Left>': 'prompt:left',\n      '<Right>': 'prompt:right',\n      '<backspace>': 'prompt:deleteforward',\n      '<C-x>': 'prompt:deletebackward',\n      '<C-k>': 'prompt:removetail',\n      '<C-u>': 'prompt:removeahead',\n    })\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    for (let key of ['<C-p>', '<C-n>', '<C-a>', '<C-e>', '<Left>', '<Right>', '<backspace>', '<C-x>', '<C-k>', '<C-u>']) {\n      await helper.listInput(key)\n    }\n    expect(manager.isActivated).toBe(true)\n  })\n\n  it('should execute feedkeys keymap', async () => {\n    helper.updateConfiguration('list.insertMappings', {\n      '<C-f>': 'feedkeys:\\\\<C-f>',\n      '<C-b>': 'feedkeys!:\\\\<C-b>',\n    })\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-f>')\n    await helper.waitFor('line', ['.'], locations.length)\n    await helper.listInput('<C-b>')\n  })\n\n  it('should execute normal keymap', async () => {\n    helper.updateConfiguration('list.insertMappings', {\n      '<C-g>': 'normal:G',\n    })\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-g>')\n    let line = await nvim.call('line', '.')\n    expect(line).toBe(locations.length)\n  })\n\n  it('should execute command keymap', async () => {\n    helper.updateConfiguration('list.insertMappings', {\n      '<C-w>': 'command:wincmd p',\n    })\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-w>')\n    expect(manager.isActivated).toBe(true)\n    let winnr = await nvim.call('winnr')\n    expect(winnr).toBe(1)\n  })\n\n  it('should execute call keymap', async () => {\n    await helper.mockFunction('Test', 1)\n    helper.updateConfiguration('list.insertMappings', {\n      '<C-t>': 'call:Test',\n    })\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-t>')\n    expect(manager.isActivated).toBe(true)\n  })\n\n  it('should insert clipboard register to prompt', async () => {\n    helper.updateConfiguration('list.insertMappings', {\n      '<C-r>': 'prompt:paste',\n    })\n    await nvim.command('let @* = \"foobar\"')\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-r>')\n    let { input } = manager.prompt\n    expect(input).toMatch('foobar')\n    await nvim.command('let @* = \"\"')\n    await helper.listInput('<C-r>')\n    expect(manager.prompt.input).toMatch('foobar')\n  })\n\n  it('should insert text from default register to prompt', async () => {\n    helper.updateConfiguration('list.insertMappings', {\n      '<C-v>': 'eval:@@',\n    })\n    await nvim.command('let @@ = \"bar\"')\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-v>')\n    let { input } = manager.prompt\n    expect(input).toMatch('bar')\n  })\n})\n\ndescribe('doAction()', () => {\n  it('should throw when action not found', async () => {\n    let mappings = manager.mappings\n    let fn = async () => {\n      await mappings.doAction('foo:bar')\n    }\n    await expect(fn()).rejects.toThrow(/doesn't exist/)\n  })\n\n  it('should not throw when session does not exist', async () => {\n    let mappings = manager.mappings\n    await mappings.doAction('do:selectall')\n    await mappings.doAction('do:help')\n    await mappings.doAction('do:refresh')\n    await mappings.doAction('do:toggle')\n    await mappings.doAction('do:jumpback')\n    await mappings.doAction('prompt:previous')\n    await mappings.doAction('prompt:next')\n    await mappings.doAction('do:refresh')\n  })\n\n  it('should not throw when action name does not exist', async () => {\n    await helper.mockFunction('MyExpr', '')\n    let mappings = manager.mappings\n    await mappings.doAction('expr', 'MyExpr')\n  })\n})\n\ndescribe('getAction()', () => {\n  it('should throw for invalid action', async () => {\n    let mappings = manager.mappings\n    let fn = () => {\n      mappings.getAction('foo')\n    }\n    expect(fn).toThrow(Error)\n    fn = () => {\n      mappings.getAction('do:bar')\n    }\n    expect(fn).toThrow(Error)\n  })\n})\n\ndescribe('Default normal mappings', () => {\n  it('should invoke action', async () => {\n    await manager.start(['--normal', '--no-quit', 'location'])\n    await manager.session.ui.ready\n    let winid = manager.session.ui.winid\n    await helper.listInput('t')\n    let nr = await nvim.call('tabpagenr')\n    expect(nr).toBe(2)\n    await nvim.call('win_gotoid', [winid])\n    await helper.listInput('s')\n    let winnr = await nvim.call('winnr', ['$'])\n    expect(winnr).toBe(3)\n    await nvim.call('win_gotoid', [winid])\n    await helper.listInput('d')\n    let filename = await nvim.call('expand', ['%'])\n    expect(filename).toMatch(path.basename(__filename))\n    await nvim.call('win_gotoid', [winid])\n    await helper.listInput('<cr>')\n    filename = await nvim.call('expand', ['%'])\n    expect(filename).toMatch(path.basename(__filename))\n  })\n\n  it('should select all items by <C-a>', async () => {\n    await manager.start(['--normal', 'location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-a>')\n    let selected = manager.session?.ui.selectedItems\n    expect(selected.length).toBe(locations.length)\n  })\n\n  it('should stop by <C-b>', async () => {\n    await manager.start(['--normal', 'location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-b>')\n    let loading = manager.session?.worker.isLoading\n    expect(loading).toBe(false)\n  })\n\n  it('should jump back by <C-o>', async () => {\n    let doc = await helper.createDocument()\n    await manager.start(['--normal', 'location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-o>')\n    let bufnr = await nvim.call('bufnr', ['%'])\n    expect(bufnr).toBe(doc.bufnr)\n  })\n\n  it('should scroll preview window by <C-e>, <C-y>', async () => {\n    await helper.createDocument()\n    await manager.start(['--auto-preview', '--normal', 'location'])\n    await manager.session.ui.ready\n    await waitPreviewWindow()\n    let winnr = await nvim.call('coc#list#has_preview') as number\n    let winid = await nvim.call('win_getid', [winnr])\n    await helper.listInput('<C-e>')\n    let res = await nvim.call('getwininfo', [winid])\n    expect(res[0].topline).toBeGreaterThan(1)\n    await helper.listInput('<C-y>')\n    res = await nvim.call('getwininfo', [winid])\n    expect(res[0].topline).toBeLessThan(7)\n  })\n\n  it('should insert command by :', async () => {\n    await manager.start(['--normal', 'location'])\n    await manager.session.ui.ready\n    await helper.listInput(':')\n    await nvim.eval('feedkeys(\"let g:x = 1\\\\<cr>\", \"in\")')\n    await helper.waitValue(() => {\n      return nvim.getVar('x')\n    }, 1)\n  })\n\n  it('should select action by <tab>', async () => {\n    await manager.start(['--normal', 'location'])\n    await manager.session.ui.ready\n    let p = helper.listInput('<tab>')\n    await helper.wait(50)\n    await nvim.input('t')\n    await p\n    let nr = await nvim.call('tabpagenr')\n    expect(nr).toBe(2)\n  })\n\n  it('should preview by p', async () => {\n    await manager.start(['--normal', 'location'])\n    await manager.session.ui.ready\n    await helper.listInput('p')\n    let winnr = await nvim.call('coc#list#has_preview')\n    expect(winnr).toBe(2)\n  })\n\n  it('should stop task by <C-c>', async () => {\n    disposables.push(manager.registerList(new TestList()))\n    let p = manager.start(['--normal', 'test'])\n    await helper.wait(50)\n    await nvim.input('<C-c>')\n    await p\n    let len = manager.session?.ui.length\n    expect(len).toBe(0)\n  })\n\n  it('should cancel list by <esc>', async () => {\n    await manager.start(['--normal', 'location'])\n    await manager.session.ui.ready\n    await nvim.eval('feedkeys(\"\\\\<esc>\", \"in\")')\n    await helper.waitValue(() => {\n      return manager.isActivated\n    }, false)\n  })\n\n  it('should reload list by <C-l>', async () => {\n    let list = new TestList()\n    list.timeout = 0\n    disposables.push(manager.registerList(list))\n    await manager.start(['--normal', 'test'])\n    await manager.session.ui.ready\n    list.text = 'new'\n    await helper.listInput('<C-l>')\n    let line = await nvim.line\n    expect(line).toMatch('new')\n  })\n\n  it('should toggle selection <space>', async () => {\n    await manager.start(['--normal', 'location'])\n    await manager.session.ui.ready\n    await helper.listInput(' ')\n    await helper.waitValue(() => {\n      return manager.session?.ui.selectedItems.length\n    }, 1)\n    await helper.listInput('k')\n    await helper.listInput(' ')\n    await helper.waitValue(() => {\n      return manager.session?.ui.selectedItems.length\n    }, 0)\n  })\n\n  it('should change to insert mode by i, o, a', async () => {\n    await manager.start(['--normal', 'location'])\n    await manager.session.ui.ready\n    let keys = ['i', 'I', 'o', 'O', 'a', 'A']\n    for (let key of keys) {\n      await helper.listInput(key)\n      let mode = manager.prompt.mode\n      expect(mode).toBe('insert')\n      await helper.listInput('<C-o>')\n      mode = manager.prompt.mode\n      expect(mode).toBe('normal')\n    }\n  })\n\n  it('should show help by ?', async () => {\n    await manager.start(['--normal', 'location'])\n    await manager.session.ui.ready\n    await helper.listInput('?')\n    let bufname = await nvim.call('bufname', '%')\n    expect(bufname).toBe('[LIST HELP]')\n  })\n})\n\ndescribe('list insert mappings', () => {\n  it('should open by <cr>', async () => {\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('<cr>')\n    let bufname = await nvim.call('expand', ['%:p'])\n    expect(bufname).toMatch('mappings.test.ts')\n  })\n\n  it('should paste input by <C-v>', async () => {\n    await nvim.command('let @* = \"foo\"')\n    await nvim.command('let @@ = \"foo\"')\n    await nvim.call('setreg', ['*', 'foo'])\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-v>')\n    let input = manager.prompt.input\n    expect(input).toBe('foo')\n  })\n\n  it('should insert register content by <C-r>', async () => {\n    await nvim.command('let @* = \"foo\"')\n    await nvim.command('let @@ = \"foo\"')\n    await nvim.call('setreg', ['*', 'foo'])\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-r>')\n    await helper.listInput('*')\n    let input = manager.prompt.input\n    expect(input).toBe('foo')\n    await helper.listInput('<C-r>')\n    await helper.listInput('<')\n    input = manager.prompt.input\n    expect(input).toBe('foo')\n    manager.prompt.reset()\n  })\n\n  it('should cancel by <esc>', async () => {\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('<esc>')\n    expect(manager.isActivated).toBe(false)\n  })\n\n  it('should select action by insert <tab>', async () => {\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    let p = helper.listInput('<tab>')\n    await helper.wait(50)\n    await nvim.input('d')\n    await p\n    let bufname = await nvim.call('bufname', ['%'])\n    expect(bufname).toMatch(path.basename(__filename))\n  })\n\n  it('should select action for visual selected items', async () => {\n    await manager.start(['--normal', 'location'])\n    await manager.session.ui.ready\n    await helper.waitPrompt()\n    await nvim.input('V')\n    await helper.wait(30)\n    await nvim.input('2')\n    await helper.wait(30)\n    await nvim.input('j')\n    await helper.wait(30)\n    await manager.doAction('quickfix')\n    let buftype = await nvim.eval('&buftype')\n    expect(buftype).toBe('quickfix')\n  })\n\n  it('should stop loading by <C-c>', async () => {\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-c>')\n    expect(manager.isActivated).toBe(true)\n  })\n\n  it('should reload by <C-l>', async () => {\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-l>')\n    expect(manager.isActivated).toBe(true)\n  })\n\n  it('should change to normal mode by <C-o>', async () => {\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-o>')\n    expect(manager.isActivated).toBe(true)\n  })\n\n  it('should select line by <down> and <up>', async () => {\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await nvim.eval('feedkeys(\"\\\\<down>\", \"in\")')\n    await nvim.eval('feedkeys(\"\\\\<up>\", \"in\")')\n    expect(manager.isActivated).toBe(true)\n    let line = await nvim.line\n    expect(line).toMatch('foo')\n  })\n\n  it('should move cursor by <left> and <right>', async () => {\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('f')\n    await helper.listInput('<left>')\n    await helper.listInput('<left>')\n    await helper.listInput('a')\n    await helper.listInput('<right>')\n    await helper.listInput('<right>')\n    await helper.listInput('c')\n    let input = manager.prompt.input\n    let mode = manager.prompt.mode\n    manager.prompt.input = input\n    manager.prompt.mode = mode\n    await helper.listInput('<home>')\n    manager.prompt.removeNext()\n    manager.prompt.removeNext()\n    manager.prompt.removeNext()\n    manager.prompt.removeNext()\n    expect(input).toBe('afc')\n  })\n\n  it('should move cursor by leftword and rightword', async () => {\n    let revert = helper.updateConfiguration('list.insertMappings', {\n      '<A-b>': 'prompt:leftword',\n      '<A-f>': 'prompt:rightword',\n    })\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('aaa bbb ccc') // -> aaa bbb ccc|\n    await helper.listInput('<A-b>')       // -> aaa bbb |ccc\n    await helper.listInput('<A-b>')       // -> aaa |bbb ccc\n    await helper.listInput('ddd ')        // -> aaa ddd |bbb ccc\n    await helper.listInput('<A-f>')       // -> aaa ddd bbb |ccc\n    await helper.listInput('eee ')        // -> aaa ddd bbb eee |ccc\n    expect(manager.mappings.hasUserMapping('insert', '<A-b>')).toBe(true)\n    expect(manager.mappings.hasUserMapping('insert', '<A-f>')).toBe(true)\n    let input = manager.prompt.input\n    revert()\n    expect(input).toBe('aaa ddd bbb eee ccc')\n  })\n\n  it('should move cursor by <end> and <home>', async () => {\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('ff')\n    await helper.listInput('<home>')\n    await helper.listInput('<end>')\n    await helper.listInput('<end>')\n    let input = manager.prompt.input\n    manager.prompt.removeWord()\n    manager.prompt.removeWord()\n    manager.prompt.removeTail()\n    manager.prompt.removeTail()\n    expect(input).toBe('ff')\n  })\n\n  it('should move cursor by <PageUp> <PageDown> <C-d>', async () => {\n    disposables.push(manager.registerList(lineList))\n    await manager.start(['lines'])\n    await manager.session.ui.ready\n    await helper.listInput('<PageDown>')\n    await helper.listInput('<PageUp>')\n    await helper.listInput('<C-d>')\n  })\n\n  it('should scroll window by <C-f> and <C-b>', async () => {\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-f>')\n    await helper.listInput('<C-b>')\n  })\n\n  it('should change input by <Backspace>', async () => {\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('f')\n    await helper.listInput('<backspace>')\n    let input = manager.prompt.input\n    expect(input).toBe('')\n  })\n\n  it('should change input by <C-b>', async () => {\n    let revert = helper.updateConfiguration('list.insertMappings', {\n      '<C-b>': 'prompt:removetail',\n    })\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('f')\n    await helper.listInput('o')\n    await helper.listInput('o')\n    await helper.listInput('<C-a>')\n    await helper.listInput('<C-b>')\n    expect(manager.mappings.hasUserMapping('insert', '<C-b>')).toBe(true)\n    let input = manager.prompt.input\n    revert()\n    expect(input).toBe('')\n  })\n\n  it('should change input by <C-h>', async () => {\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('f')\n    await helper.listInput('<C-h>')\n    let input = manager.prompt.input\n    expect(input).toBe('')\n  })\n\n  it('should change input by <C-w>', async () => {\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('f')\n    await helper.listInput('a')\n    await helper.listInput('<C-w>')\n    let input = manager.prompt.input\n    expect(input).toBe('')\n  })\n\n  it('should change input by <C-u>', async () => {\n    await manager.start(['--input=a', 'location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-u>')\n    let input = manager.prompt.input\n    expect(input).toBe('')\n  })\n\n  it('should change input by <C-n> and <C-p>', async () => {\n    async function session(input: string): Promise<void> {\n      await manager.start(['location'])\n      await manager.session.ui.ready\n      for (let ch of input) {\n        await helper.listInput(ch)\n      }\n      await manager.cancel()\n    }\n    await session('foo')\n    await session('bar')\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-n>')\n    let input = manager.prompt.input\n    expect(input.length).toBeGreaterThan(0)\n    await helper.listInput('<C-p>')\n    input = manager.prompt.input\n    expect(input.length).toBeGreaterThan(0)\n  })\n\n  it('should change matcher by <C-s>', async () => {\n    await manager.start(['location'])\n    await manager.session.ui.ready\n    await helper.listInput('<C-s>')\n    let matcher = manager.session?.listOptions.matcher\n    expect(matcher).toBe('strict')\n    await helper.listInput('<C-s>')\n    matcher = manager.session?.listOptions.matcher\n    expect(matcher).toBe('regex')\n    await helper.listInput('f')\n    let len = manager.session?.ui.length\n    expect(len).toBeGreaterThan(0)\n  })\n})\n\ndescribe('evalExpression', () => {\n  it('should exit list', async () => {\n    helper.updateConfiguration('list.normalMappings', {\n      t: 'do:exit',\n    })\n    await manager.start(['--normal', 'location'])\n    await manager.session.ui.ready\n    expect(manager.mappings.hasUserMapping('normal', 't')).toBe(true)\n    await helper.listInput('t')\n    expect(manager.isActivated).toBe(false)\n  })\n\n  it('should cancel prompt', async () => {\n    helper.updateConfiguration('list.normalMappings', {\n      t: 'do:cancel',\n    })\n    await manager.start(['--normal', 'location'])\n    await manager.session.ui.ready\n    await helper.listInput('t')\n    let res = await nvim.call('coc#prompt#activated')\n    expect(res).toBe(0)\n  })\n\n  it('should invoke normal command', async () => {\n    let revert = helper.updateConfiguration('list.normalMappings', {\n      x: 'normal!:G'\n    })\n    await manager.start(['--normal', 'location'])\n    await manager.session.ui.ready\n    await helper.listInput('x')\n    revert()\n    let lnum = await nvim.call('line', ['.'])\n    expect(lnum).toBeGreaterThan(1)\n  })\n\n  it('should toggle, scroll preview', async () => {\n    let revert = helper.updateConfiguration('list.normalMappings', {\n      '<space>': 'do:toggle',\n      a: 'do:toggle',\n      b: 'do:previewtoggle',\n      c: 'do:previewup',\n      d: 'do:previewdown',\n      e: 'prompt:insertregister',\n      f: 'do:stop',\n      g: 'do:togglemode',\n    })\n    await manager.start(['--normal', 'location'])\n    await manager.session.ui.ready\n    await helper.listInput(' ')\n    for (let key of ['a', 'b', 'c', 'd', 'e', 'f', 'g']) {\n      await helper.listInput(key)\n    }\n    revert()\n    expect(manager.isActivated).toBe(true)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/list/session.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { Disposable } from 'vscode-languageserver-protocol'\nimport BasicList from '../../list/basic'\nimport manager from '../../list/manager'\nimport Prompt from '../../list/prompt'\nimport ListSession from '../../list/session'\nimport { IList, ListItem } from '../../list/types'\nimport { disposeAll } from '../../util'\nimport helper from '../helper'\n\nlet labels: string[] = []\nlet lastItem: string\nlet lastItems: ListItem[]\n\nclass SimpleList extends BasicList {\n  public name = 'simple'\n  public detail = 'detail'\n  public options = [{\n    name: 'foo',\n    description: 'foo'\n  }]\n  constructor() {\n    super()\n    this.addAction('open', item => {\n      lastItem = item.label\n    }, { tabPersist: true })\n    this.addMultipleAction('multiple', items => {\n      lastItems = items\n    })\n    this.addAction('parallel', async () => {\n      await helper.wait(100)\n    }, { parallel: true })\n    this.addAction('reload', item => {\n      lastItem = item.label\n    }, { persist: true, reload: true })\n  }\n  public loadItems(): Promise<ListItem[]> {\n    return Promise.resolve(labels.map(s => {\n      return { label: s } as ListItem\n    }))\n  }\n}\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  manager.reset()\n  await helper.reset()\n})\n\ndescribe('list session', () => {\n  describe('doDefaultAction()', () => {\n    it('should throw error when default action does not exist', async () => {\n      labels = ['a', 'b', 'c']\n      let list = new SimpleList()\n      list.defaultAction = 'foo'\n      let len = list.actions.length\n      list.actions.splice(0, len)\n      disposables.push(manager.registerList(list))\n      await manager.start(['--normal', 'simple'])\n      let ui = manager.session.ui\n      await ui.ready\n      let err\n      try {\n        await manager.session.first()\n      } catch (e) {\n        err = e\n      }\n      expect(err).toBeDefined()\n      err = null\n      try {\n        await manager.session.last()\n      } catch (e) {\n        err = e\n      }\n      expect(err).toBeDefined()\n    })\n  })\n\n  describe('doItemAction()', () => {\n    it('should invoke multiple action', async () => {\n      labels = ['a', 'b', 'c']\n      let list = new SimpleList()\n      disposables.push(manager.registerList(list))\n      await manager.start(['--normal', 'simple'])\n      let ui = manager.session.ui\n      await ui.ready\n      await ui.selectAll()\n      await manager.doAction('multiple')\n      expect(lastItems.length).toBe(3)\n      lastItems = undefined\n      await manager.session.doPreview(0)\n      await manager.doAction('not_exists')\n      let line = await helper.getCmdline()\n      expect(line).toMatch('not found')\n    })\n\n    it('should invoke parallel action', async () => {\n      labels = ['a', 'b', 'c']\n      let list = new SimpleList()\n      disposables.push(manager.registerList(list))\n      await manager.start(['--normal', 'simple'])\n      let ui = manager.session.ui\n      await ui.ready\n      await ui.selectAll()\n      let d = Date.now()\n      await manager.doAction('parallel')\n      expect(Date.now() - d).toBeLessThan(300)\n    })\n\n    it('should support tabPersist action', async () => {\n      labels = ['a', 'b', 'c']\n      let list = new SimpleList()\n      disposables.push(manager.registerList(list))\n      await manager.start(['--normal', '--tab', 'simple'])\n      let ui = manager.session.ui\n      await ui.ready\n      await manager.doAction('open')\n      let tabnr = await nvim.call('tabpagenr')\n      expect(tabnr).toBeGreaterThan(1)\n      let win = nvim.createWindow(ui.winid)\n      let valid = await win.valid\n      expect(valid).toBe(true)\n    })\n\n    it('should invoke reload action', async () => {\n      labels = ['a', 'b', 'c']\n      let list = new SimpleList()\n      disposables.push(manager.registerList(list))\n      await manager.start(['--normal', 'simple'])\n      let ui = manager.session.ui\n      await ui.ready\n      labels = ['d', 'e']\n      await manager.doAction('reload')\n      await helper.wait(50)\n      let buf = await nvim.buffer\n      let lines = await buf.lines\n      expect(lines).toEqual(['d', 'e'])\n    })\n  })\n\n  describe('reloadItems()', () => {\n    it('should not reload items when window is hidden', async () => {\n      let fn = jest.fn()\n      let list: IList = {\n        name: 'reload',\n        defaultAction: 'open',\n        actions: [{\n          name: 'open',\n          execute: () => {}\n        }],\n        loadItems: () => {\n          fn()\n          return Promise.resolve([])\n        }\n      }\n      disposables.push(manager.registerList(list))\n      await manager.start(['--normal', 'reload'])\n      let ui = manager.session.ui\n      await ui.ready\n      await manager.cancel(true)\n      let ses = manager.getSession('reload')\n      await ses.reloadItems()\n      expect(fn).toHaveBeenCalledTimes(1)\n    })\n  })\n\n  describe('resume()', () => {\n    it('should do preview on resume', async () => {\n      labels = ['a', 'b', 'c']\n      let lastItem\n      let list = new SimpleList()\n      list.actions.push({\n        name: 'preview',\n        execute: item => {\n          lastItem = item\n        }\n      })\n      disposables.push(manager.registerList(list))\n      await manager.start(['--normal', '--auto-preview', 'simple'])\n      let ui = manager.session.ui\n      await ui.ready\n      await ui.selectLines(1, 2)\n      await helper.wait(50)\n      await nvim.call('coc#window#close', [ui.winid])\n      await helper.wait(100)\n      await manager.session.resume()\n      await helper.wait(100)\n      expect(lastItem).toBeDefined()\n    })\n  })\n\n  describe('jumpBack()', () => {\n    it('should jump back', async () => {\n      let win = await nvim.window\n      labels = ['a', 'b', 'c']\n      let list = new SimpleList()\n      disposables.push(manager.registerList(list))\n      await manager.start(['--normal', 'simple'])\n      let ui = manager.session.ui\n      await ui.ready\n      manager.session.jumpBack()\n      await helper.wait(50)\n      let winid = await nvim.call('win_getid')\n      expect(winid).toBe(win.id)\n    })\n  })\n\n  describe('hide()', () => {\n    it('should not throw when window undefined', async () => {\n      let session = new ListSession(nvim, new Prompt(nvim), new SimpleList(), {\n        reverse: true,\n        numberSelect: true,\n        autoPreview: true,\n        first: false,\n        input: 'test',\n        interactive: false,\n        matcher: 'strict',\n        ignorecase: true,\n        position: 'top',\n        mode: 'normal',\n        noQuit: false,\n        sort: false\n      }, [])\n      await expect(async () => {\n        await session.call('fn_not_exists')\n      }).rejects.toThrow(Error)\n      await session.doPreview(0)\n      await session.first()\n      await session.hide(false, true)\n      let worker: any = session.worker\n      worker._onDidChangeItems.fire({ items: [] })\n      worker._onDidChangeLoading.fire(false)\n    })\n  })\n\n  describe('doNumberSelect()', () => {\n    async function create(len: number): Promise<ListSession> {\n      labels = []\n      for (let i = 0; i < len; i++) {\n        let code = 'a'.charCodeAt(0) + i\n        labels.push(String.fromCharCode(code))\n      }\n      let list = new SimpleList()\n      disposables.push(manager.registerList(list))\n      await manager.start(['--normal', '--number-select', 'simple'])\n      let ui = manager.session.ui\n      await ui.ready\n      return manager.session\n    }\n\n    it('should return false for invalid number', async () => {\n      let session = await create(5)\n      let res = await session.doNumberSelect('a')\n      expect(res).toBe(false)\n      res = await session.doNumberSelect('8')\n      expect(res).toBe(false)\n    })\n\n    it('should consider 0 as 10', async () => {\n      let session = await create(15)\n      let res = await session.doNumberSelect('0')\n      expect(res).toBe(true)\n      expect(lastItem).toBe('j')\n    })\n  })\n})\n\ndescribe('showHelp()', () => {\n  it('should show description and options in help', async () => {\n    labels = ['a', 'b', 'c']\n    let list = new SimpleList()\n    disposables.push(manager.registerList(list))\n    await manager.start(['--normal', 'simple'])\n    let ui = manager.session.ui\n    await ui.ready\n    await manager.session.showHelp()\n    let lines = await nvim.call('getline', [1, '$']) as string[]\n    expect(lines.indexOf('DESCRIPTION')).toBeGreaterThan(0)\n    expect(lines.indexOf('ARGUMENTS')).toBeGreaterThan(0)\n  })\n})\n\ndescribe('chooseAction()', () => {\n  it('should filter actions not have shortcuts', async () => {\n    labels = ['a', 'b', 'c']\n    let fn = jest.fn()\n    let list = new SimpleList()\n    list.actions.push({\n      name: 'a',\n      execute: () => {\n        fn()\n      }\n    })\n    list.actions.push({\n      name: 'b',\n      execute: () => {\n      }\n    })\n    list.actions.push({\n      name: 'ab',\n      execute: () => {\n      }\n    })\n    disposables.push(manager.registerList(list))\n    await manager.start(['--normal', 'simple'])\n    await manager.session.ui.ready\n    let p = manager.session.chooseAction()\n    await helper.wait(50)\n    await nvim.input('a')\n    await p\n    expect(fn).toHaveBeenCalled()\n  })\n\n  it('should choose action by menu picker', async () => {\n    helper.updateConfiguration('list.menuAction', true)\n    labels = ['a', 'b', 'c']\n    let fn = jest.fn()\n    let list = new SimpleList()\n    let len = list.actions.length\n    list.actions.splice(0, len)\n    list.actions.push({\n      name: 'a',\n      execute: () => {\n        fn()\n      }\n    })\n    list.actions.push({\n      name: 'b',\n      execute: () => {\n        fn()\n      }\n    })\n    disposables.push(manager.registerList(list))\n    await manager.start(['--normal', 'simple'])\n    await manager.session.ui.ready\n    let p = manager.session.chooseAction()\n    await helper.waitPrompt()\n    await nvim.input('<cr>')\n    await p\n  })\n})\n"
  },
  {
    "path": "src/__tests__/list/source-funcs.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationToken } from 'vscode-languageserver-protocol'\nimport { DocumentSymbol, Location, Range, SymbolKind } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport which from 'which'\nimport BasicList, { toVimFiletype } from '../../list/basic'\nimport { fixWidth, formatListItems, formatPath, formatUri, UnformattedListItem } from '../../list/formatting'\nimport { getExtensionPrefix, getExtensionPriority, sortExtensionItem } from '../../list/source/extensions'\nimport { mruScore } from '../../list/source/lists'\nimport { contentToItems, getFilterText, loadCtagsSymbols, symbolsToListItems } from '../../list/source/outline'\nimport { sortSymbolItems, toTargetLocation } from '../../list/source/symbols'\nimport { ListItem } from '../../list/types'\nimport { os, path } from '../../util/node'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nclass SimpleList extends BasicList {\n  public name = 'simple'\n  public defaultAction: 'preview'\n  constructor() {\n    super()\n  }\n  public loadItems(): Promise<ListItem[]> {\n    return Promise.resolve([])\n  }\n}\n\ndescribe('List util', () => {\n  it('should get list score', () => {\n    expect(mruScore(['foo'], 'foo')).toBe(1)\n    expect(mruScore([], 'foo')).toBe(-1)\n  })\n})\n\ndescribe('BasicList util', () => {\n  let list: SimpleList\n  beforeAll(() => {\n    list = new SimpleList()\n  })\n\n  it('should get filetype', async () => {\n    expect(toVimFiletype('latex')).toBe('tex')\n    expect(toVimFiletype('foo')).toBe('foo')\n  })\n\n  it('should convert uri', async () => {\n    let uri = URI.file(__filename).toString()\n    let res = await list.convertLocation(uri)\n    expect(res.uri).toBe(uri)\n  })\n\n  it('should convert location with line', async () => {\n    let uri = URI.file(__filename).toString()\n    let res = await list.convertLocation({ uri, line: 'convertLocation()', text: 'convertLocation' })\n    expect(res.uri).toBe(uri)\n    res = await list.convertLocation({ uri, line: 'convertLocation()' })\n    expect(res.uri).toBe(uri)\n  })\n\n  it('should convert location with custom schema', async () => {\n    let uri = 'test:///foo'\n    let res = await list.convertLocation({ uri, line: 'convertLocation()' })\n    expect(res.uri).toBe(uri)\n  })\n})\n\ndescribe('Outline util', () => {\n  it('should getFilterText', () => {\n    expect(getFilterText(DocumentSymbol.create('name', '', SymbolKind.Function, Range.create(0, 0, 0, 1), Range.create(0, 0, 0, 1)), 'kind')).toBe('name')\n    expect(getFilterText(DocumentSymbol.create('name', '', SymbolKind.Function, Range.create(0, 0, 0, 1), Range.create(0, 0, 0, 1)), '')).toBe('nameFunction')\n  })\n\n  it('should load items by ctags', async () => {\n    let doc = await workspace.document\n    let spy = jest.spyOn(which, 'sync').mockImplementation(() => {\n      return ''\n    })\n    let items = await loadCtagsSymbols(doc, nvim, CancellationToken.None)\n    expect(items).toEqual([])\n    spy.mockRestore()\n    doc = await helper.createDocument(__filename)\n    items = await loadCtagsSymbols(doc, nvim, CancellationToken.None)\n    expect(Array.isArray(items)).toBe(true)\n  })\n\n  it('should convert symbols to list items', async () => {\n    let symbols: DocumentSymbol[] = []\n    symbols.push(DocumentSymbol.create('function', '', SymbolKind.Function, Range.create(1, 0, 1, 1), Range.create(1, 0, 1, 1)))\n    symbols.push(DocumentSymbol.create('class', '', SymbolKind.Class, Range.create(0, 0, 0, 1), Range.create(0, 0, 0, 1)))\n    let items = symbolsToListItems(symbols, 'lsp:/1', 'class')\n    expect(items.length).toBe(1)\n    expect(items[0].data.kind).toBe('Class')\n  })\n\n  it('should convert to list items', async () => {\n    let doc = await workspace.document\n    expect(contentToItems('a\\tb\\t2\\td\\n\\n', doc).length).toBe(1)\n  })\n})\n\ndescribe('Extensions util', () => {\n  it('should sortExtensionItem', () => {\n    expect(sortExtensionItem({ data: { priority: 1 } }, { data: { priority: 0 } })).toBe(-1)\n    expect(sortExtensionItem({ data: { id: 'a' } }, { data: { id: 'b' } })).toBe(1)\n    expect(sortExtensionItem({ data: { id: 'b' } }, { data: { id: 'a' } })).toBe(-1)\n  })\n\n  it('should get extension prefix', () => {\n    expect(getExtensionPrefix('')).toBe('+')\n    expect(getExtensionPrefix('disabled')).toBe('-')\n    expect(getExtensionPrefix('activated')).toBe('*')\n    expect(getExtensionPrefix('unknown')).toBe('?')\n  })\n\n  it('should get extension priority', () => {\n    expect(getExtensionPriority('')).toBe(0)\n    expect(getExtensionPriority('unknown')).toBe(2)\n    expect(getExtensionPriority('activated')).toBe(1)\n    expect(getExtensionPriority('disabled')).toBe(-1)\n  })\n})\n\ndescribe('Symbols util', () => {\n  it('should convert to location', () => {\n    let res = toTargetLocation({ uri: 'untitled:1' })\n    expect(Location.is(res)).toBe(true)\n  })\n})\n\ndescribe('formatting', () => {\n  it('should format path', () => {\n    let base = path.basename(__filename)\n    expect(formatPath('short', 'home')).toMatch('home')\n    expect(formatPath('hidden', 'path')).toBe('')\n    expect(formatPath('full', __filename)).toMatch(base)\n    expect(formatPath('short', __filename)).toMatch(base)\n    expect(formatPath('filename', __filename)).toMatch(base)\n  })\n\n  it('should format uri', () => {\n    let cwd = process.cwd()\n    expect(formatUri('http://www.example.com', cwd)).toMatch('http')\n    expect(formatUri(URI.file(__filename).toString(), cwd)).toMatch('source')\n    expect(formatUri(URI.file(os.tmpdir()).toString(), cwd)).toMatch(os.tmpdir())\n  })\n\n  it('should fixWidth', () => {\n    expect(fixWidth('a'.repeat(10), 2)).toBe('a.')\n  })\n\n  it('should sort symbols', () => {\n    const assert = (a, b, n) => {\n      expect(sortSymbolItems(a, b)).toBe(n)\n    }\n    assert({ data: { score: 1 } }, { data: { score: 2 } }, 1)\n    assert({ data: { kind: 1 } }, { data: { kind: 2 } }, -1)\n    assert({ data: { file: 'aa' } }, { data: { file: 'b' } }, 1)\n  })\n\n  it('should format list items', () => {\n    expect(formatListItems(false, [])).toEqual([])\n    let items: UnformattedListItem[] = [{\n      label: ['a', 'b', 'c']\n    }]\n    expect(formatListItems(false, items)).toEqual([{\n      label: 'a\\tb\\tc'\n    }])\n    items = [{\n      label: ['a', 'b', 'c']\n    }, {\n      label: ['foo', 'bar', 'go']\n    }]\n    expect(formatListItems(true, items)).toEqual([{\n      label: 'a  \\tb  \\tc '\n    }, {\n      label: 'foo\\tbar\\tgo'\n    }])\n\n    // items with different column counts (e.g. local vs non-local extensions)\n    items = [{\n      label: ['* foo', '[RTP]', '1.0.0', '/tmp/foo']\n    }, {\n      label: ['+ bar', '2.0.0', '/tmp/bar']\n    }]\n    let result = formatListItems(true, items)\n    expect(result[0].label.split('\\t')).toEqual(['* foo', '[RTP]', '1.0.0   ', '/tmp/foo'])\n    expect(result[1].label.split('\\t')).toEqual(['+ bar', '2.0.0', '/tmp/bar'])\n  })\n})\n"
  },
  {
    "path": "src/__tests__/list/sources.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport fs from 'fs'\nimport os from 'os'\nimport { v4 as uuid } from 'uuid'\nimport { CancellationToken, Diagnostic, DiagnosticSeverity, Disposable, DocumentLink, Emitter, Location, Position, Range, SymbolInformation, SymbolKind, SymbolTag, TextEdit } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport diagnosticManager, { DiagnosticItem } from '../../diagnostic/manager'\nimport events from '../../events'\nimport extensions from '../../extension/index'\nimport { ExtensionInfo, ExtensionManager } from '../../extension/manager'\nimport { ExtensionStat } from '../../extension/stat'\nimport languages from '../../languages'\nimport BasicList, { PreviewOptions } from '../../list/basic'\nimport manager from '../../list/manager'\nimport DiagnosticsList, { convertToLabel } from '../../list/source/diagnostics'\nimport ExtensionList from '../../list/source/extensions'\nimport FolderList from '../../list/source/folders'\nimport OutlineList from '../../list/source/outline'\nimport SymbolsList from '../../list/source/symbols'\nimport { ListArgument, ListContext, ListItem, ListOptions } from '../../list/types'\nimport Document from '../../model/document'\nimport services, { IServiceProvider, ServiceStat } from '../../services'\nimport { QuickfixItem } from '../../types'\nimport { disposeAll } from '../../util'\nimport * as extension from '../../util/extensionRegistry'\nimport { path } from '../../util/node'\nimport { Registry } from '../../util/registry'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport Parser from '../handler/parser'\nimport helper from '../helper'\n\nlet listItems: ListItem[] = []\nclass OptionList extends BasicList {\n  public name = 'option'\n  public options: ListArgument[] = [{\n    name: '-w, -word',\n    description: 'word'\n  }, {\n    name: '-i, -input INPUT',\n    hasValue: true,\n    description: 'input'\n  }, {\n    key: 'name',\n    description: '',\n    name: '-name'\n  }]\n  constructor() {\n    super()\n    this.addLocationActions()\n  }\n  public loadItems(_context: ListContext, _token: CancellationToken): Promise<ListItem[]> {\n    return Promise.resolve(listItems)\n  }\n}\n\nlet previewOptions: PreviewOptions\nclass SimpleList extends BasicList {\n  public name = 'simple'\n  public defaultAction: 'preview'\n  constructor() {\n    super()\n    this.addAction('preview', async (_item, context) => {\n      await this.preview(previewOptions, context)\n    })\n  }\n  public loadItems(): Promise<ListItem[]> {\n    return Promise.resolve(['a', 'b', 'c'].map((s, idx) => {\n      return { label: s, location: Location.create('test:///a', Range.create(idx, 0, idx + 1, 0)) } as ListItem\n    }))\n  }\n}\n\nasync function createContext(option: Partial<ListOptions>): Promise<ListContext> {\n  let buffer = await nvim.buffer\n  let window = await nvim.window\n  return {\n    args: [],\n    buffer,\n    cwd: process.cwd(),\n    input: '',\n    listWindow: nvim.createWindow(1002),\n    options: Object.assign({\n      position: 'bottom',\n      reverse: false,\n      input: '',\n      ignorecase: false,\n      smartcase: false,\n      interactive: false,\n      sort: false,\n      mode: 'normal',\n      matcher: 'strict',\n      autoPreview: false,\n      numberSelect: false,\n      noQuit: false,\n      first: false\n    }, option),\n    window\n  }\n}\n\nlet disposables: Disposable[] = []\nlet nvim: Neovim\nconst locations: QuickfixItem[] = [{\n  filename: __filename,\n  range: Range.create(0, 0, 0, 6),\n  targetRange: Range.create(0, 0, 0, 6),\n  text: 'foo',\n  type: 'Error'\n}, {\n  filename: __filename,\n  range: Range.create(2, 0, 2, 6),\n  text: 'Bar',\n  type: 'Warning'\n}, {\n  filename: __filename,\n  range: Range.create(3, 0, 4, 6),\n  text: 'multiple'\n}, {\n  filename: path.join(os.tmpdir(), '3195369f-5b9f-4c46-99cd-6007c0224595'),\n  range: Range.create(3, 0, 4, 6),\n  text: 'tmpdir'\n}]\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  manager.dispose()\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  listItems = []\n  disposeAll(disposables)\n  manager.reset()\n  await helper.reset()\n})\n\ndescribe('configuration', () => {\n  beforeEach(() => {\n    let list = new OptionList()\n    manager.registerList(list)\n  })\n\n  it('should change default options', async () => {\n    helper.updateConfiguration('list.source.option.defaultOptions', ['--normal'])\n    await manager.start(['option'])\n    await manager.session.ui.ready\n    const mode = manager.prompt.mode\n    expect(mode).toBe('normal')\n  })\n\n  it('should change default action', async () => {\n    helper.updateConfiguration('list.source.option.defaultAction', 'split')\n    await manager.start(['option'])\n    await manager.session.ui.ready\n    const action = manager.session.defaultAction\n    expect(action.name).toBe('split')\n    await manager.session.doAction()\n    let tab = await nvim.tabpage\n    let wins = await tab.windows\n    expect(wins.length).toBeGreaterThan(1)\n  })\n\n  it('should change default arguments', async () => {\n    helper.updateConfiguration('list.source.option.defaultArgs', ['-word'])\n    await manager.start(['option'])\n    await manager.session.ui.ready\n    const context = manager.session.context\n    expect(context.args).toEqual(['-word'])\n  })\n})\n\ndescribe('BasicList', () => {\n  describe('parse arguments', () => {\n    it('should parse args #1', () => {\n      let list = new OptionList()\n      let res = list.parseArguments(['-w'])\n      expect(res).toEqual({ word: true })\n    })\n\n    it('should parse args #2', () => {\n      let list = new OptionList()\n      let res = list.parseArguments(['-word'])\n      expect(res).toEqual({ word: true })\n    })\n\n    it('should parse args #3', () => {\n      let list = new OptionList()\n      let res = list.parseArguments(['-input', 'foo'])\n      expect(res).toEqual({ input: 'foo' })\n    })\n  })\n\n  describe('jumpTo()', () => {\n    let list: OptionList\n    beforeAll(() => {\n      list = new OptionList()\n    })\n\n    it('should jump to uri', async () => {\n      let uri = URI.file(__filename).toString()\n      let ctx = await createContext({ position: 'tab' })\n      await list.jumpTo(uri, null, ctx)\n      let bufname = await nvim.call('bufname', ['%'])\n      expect(bufname).toMatch('sources.test.ts')\n    })\n\n    it('should jump to location', async () => {\n      let uri = URI.file(__filename).toString()\n      let loc = Location.create(uri, Range.create(0, 0, 1, 0))\n      await list.jumpTo(loc, 'edit')\n      let bufname = await nvim.call('bufname', ['%'])\n      expect(bufname).toMatch('sources.test.ts')\n    })\n\n    it('should jump to location with empty range', async () => {\n      let uri = URI.file(__filename).toString()\n      let loc = Location.create(uri, Range.create(0, 0, 0, 0))\n      await list.jumpTo(loc, 'edit')\n      let bufname = await nvim.call('bufname', ['%'])\n      expect(bufname).toMatch('sources.test.ts')\n    })\n  })\n\n  describe('createAction()', () => {\n    it('should overwrite action', async () => {\n      let idx: number\n      let list = new OptionList()\n      listItems.push({\n        label: 'foo',\n        location: Location.create('untitled:///1', Range.create(0, 0, 0, 0))\n      })\n      list.createAction({\n        name: 'foo',\n        execute: () => { idx = 0 }\n      })\n      list.createAction({\n        name: 'foo',\n        execute: () => { idx = 1 }\n      })\n      disposables.push(manager.registerList(list))\n      await manager.start(['--normal', 'option'])\n      await manager.session.ui.ready\n      await manager.doAction('foo')\n      expect(idx).toBe(1)\n    })\n  })\n\n  describe('preview()', () => {\n    beforeEach(() => {\n      let list = new SimpleList()\n      disposables.push(manager.registerList(list))\n    })\n\n    async function doPreview(opts: PreviewOptions): Promise<number> {\n      previewOptions = opts\n      await manager.start(['--normal', 'simple'])\n      await manager.session.ui.ready\n      await manager.doAction('preview')\n      let res = await nvim.call('coc#list#has_preview') as number\n      expect(res).toBeGreaterThan(0)\n      let winid = await nvim.call('win_getid', [res]) as number\n      return winid\n    }\n\n    it('should preview lines', async () => {\n      await doPreview({ filetype: '', lines: ['foo', 'bar'] })\n    })\n\n    it('should preview with bufname', async () => {\n      await doPreview({\n        bufname: 't.js',\n        filetype: 'typescript',\n        lines: ['foo', 'bar']\n      })\n    })\n\n    it('should preview with range highlight', async () => {\n      let winid = await doPreview({\n        bufname: 't.js',\n        filetype: 'typescript',\n        lines: ['foo', 'bar'],\n        range: Range.create(0, 0, 0, 3)\n      })\n      let res = await nvim.call('getmatches', [winid]) as any[]\n      expect(res.length).toBeGreaterThan(0)\n    })\n  })\n\n  describe('previewLocation()', () => {\n    it('should preview sketch buffer', async () => {\n      await nvim.command('new')\n      await nvim.setLine('foo')\n      let doc = await workspace.document\n      expect(doc.uri).toMatch('untitled')\n      let list = new OptionList()\n      listItems.push({\n        label: 'foo',\n        location: Location.create(doc.uri, Range.create(0, 0, 0, 0))\n      })\n      disposables.push(manager.registerList(list))\n      await manager.start(['option'])\n      await manager.session.ui.ready\n      await helper.wait(30)\n      await manager.doAction('preview')\n      await nvim.command('wincmd p')\n      let win = await nvim.window\n      let isPreview = await win.getVar('previewwindow')\n      expect(isPreview).toBe(1)\n      let line = await nvim.line\n      expect(line).toBe('foo')\n    })\n  })\n})\n\ndescribe('list sources', () => {\n  beforeEach(async () => {\n    await nvim.setVar('coc_jump_locations', locations)\n  })\n\n  describe('locations', () => {\n    it('should highlight ranges', async () => {\n      await manager.start(['--normal', '--auto-preview', 'location'])\n      await manager.session.ui.ready\n      await helper.waitFor('winnr', ['$'], 3)\n      manager.prompt.cancel()\n      await nvim.command('wincmd k')\n      let name = await nvim.eval('bufname(\"%\")')\n      expect(name).toMatch('sources.test.ts')\n      let res = await nvim.call('getmatches') as any[]\n      expect(res.length).toBe(1)\n    })\n\n    it('should not use filename when current buffer only', async () => {\n      let filepath = path.join(os.tmpdir(), 'b7d9e548-00ec-4419-98a8-dc03874e405c')\n      let doc = await helper.createDocument(filepath)\n      let locations = [{\n        filename: filepath,\n        bufnr: doc.bufnr,\n        lnum: 1,\n        col: 1,\n        text: 'multiple'\n      }, {\n        filename: filepath,\n        bufnr: doc.bufnr,\n        lnum: 1,\n        col: 1,\n        end_lnum: 2,\n        end_col: 1,\n        text: 'multiple'\n      }]\n      await nvim.setVar('coc_jump_locations', locations)\n      await manager.start(['--normal', '--auto-preview', 'location'])\n      await manager.session.ui.ready\n    })\n\n    it('should change highlight on cursor move', async () => {\n      await manager.start(['--normal', '--auto-preview', 'location'])\n      await manager.session.ui.ready\n      await nvim.command('exe 2')\n      let bufnr = await nvim.eval('bufnr(\"%\")')\n      await events.fire('CursorMoved', [bufnr, [2, 1]])\n      await helper.waitFor('winnr', ['$'], 3)\n      await nvim.command('wincmd k')\n      let res = await nvim.call('getmatches') as any\n      expect(res.length).toBe(1)\n      expect(res[0]['pos1']).toEqual([3, 1, 6])\n    })\n\n    it('should highlight multiple line range', async () => {\n      await manager.start(['--normal', '--auto-preview', 'location'])\n      await manager.session.ui.ready\n      await nvim.command('exe 3')\n      let bufnr = await nvim.eval('bufnr(\"%\")')\n      await events.fire('CursorMoved', [bufnr, [2, 1]])\n      await helper.waitFor('winnr', ['$'], 3)\n      await nvim.command('wincmd k')\n      let res = await nvim.call('getmatches') as any\n      expect(res.length).toBe(1)\n      expect(res[0]['pos1']).toBeDefined()\n      expect(res[0]['pos2']).toBeDefined()\n    })\n\n    it('should do open action', async () => {\n      global.formatFilepath = function() {\n        return ''\n      }\n      await manager.start(['--normal', 'location'])\n      await manager.session.ui.ready\n      await manager.doAction('open')\n      let name = await nvim.eval('bufname(\"%\")')\n      expect(name).toMatch('sources.test.ts')\n      global.formatFilepath = undefined\n    })\n\n    it('should do quickfix action', async () => {\n      await nvim.setVar('coc_quickfix_open_command', 'copen', false)\n      await manager.start(['--normal', 'location'])\n      await manager.session.ui.ready\n      await manager.session.ui.selectAll()\n      await manager.doAction('quickfix')\n      let buftype = await nvim.eval('&buftype')\n      expect(buftype).toBe('quickfix')\n    })\n\n    it('should do refactor action', async () => {\n      await manager.start(['--normal', 'location'])\n      await manager.session.ui.ready\n      await manager.doAction('refactor')\n      let name = await nvim.eval('bufname(\"%\")')\n      expect(name).toMatch('coc_refactor')\n    })\n\n    it('should do tabe action', async () => {\n      await manager.start(['--normal', 'location'])\n      await manager.session.ui.ready\n      await manager.doAction('tabe')\n      let tabs = await nvim.tabpages\n      expect(tabs.length).toBe(2)\n    })\n\n    it('should do drop action', async () => {\n      await manager.start(['--normal', 'location'])\n      await manager.session.ui.ready\n      await manager.doAction('drop')\n      let name = await nvim.eval('bufname(\"%\")')\n      expect(name).toMatch('sources.test.ts')\n    })\n\n    it('should do vsplit action', async () => {\n      await manager.start(['--normal', 'location'])\n      await manager.session.ui.ready\n      await manager.doAction('vsplit')\n      let name = await nvim.eval('bufname(\"%\")')\n      expect(name).toMatch('sources.test.ts')\n    })\n\n    it('should do split action', async () => {\n      await manager.start(['--normal', 'location'])\n      await manager.session.ui.ready\n      await manager.doAction('split')\n      let name = await nvim.eval('bufname(\"%\")')\n      expect(name).toMatch('sources.test.ts')\n    })\n  })\n\n  describe('commands', () => {\n    it('should do run action', async () => {\n      await manager.start(['commands'])\n      await manager.session?.ui.ready\n      await manager.doAction()\n    })\n\n    it('should load commands source', async () => {\n      let registry = Registry.as<extension.IExtensionRegistry>(extension.Extensions.ExtensionContribution)\n      registry.registerExtension('single', {\n        name: 'single',\n        directory: os.tmpdir(),\n        onCommands: ['cmd', 'cmd'],\n        commands: [{ command: 'cmd', title: 'title' }]\n      })\n      await manager.start(['commands'])\n      await manager.session?.ui.ready\n      expect(manager.isActivated).toBe(true)\n      await manager.doAction('append')\n      let line = await helper.getCmdline()\n      expect(line).toMatch(':CocCommand')\n      registry.unregistExtension('single')\n    })\n  })\n\n  describe('diagnostics', () => {\n    function createDiagnostic(msg: string, range?: Range, severity?: DiagnosticSeverity, code?: number): Diagnostic {\n      range = range ? range : Range.create(0, 0, 0, 1)\n      return Diagnostic.create(range, msg, severity || DiagnosticSeverity.Error, code)\n    }\n\n    async function createDocument(name?: string): Promise<Document> {\n      let doc = await helper.createDocument(name)\n      let collection = diagnosticManager.create('test')\n      disposables.push({\n        dispose: () => {\n          collection.clear()\n          collection.dispose()\n        }\n      })\n      let diagnostics: Diagnostic[] = []\n      await doc.buffer.setLines(['foo bar foo bar', 'foo bar', 'foo', 'bar'], {\n        start: 0,\n        end: -1,\n        strictIndexing: false\n      })\n      diagnostics.push(createDiagnostic('error', Range.create(0, 2, 0, 4), DiagnosticSeverity.Error, 1001))\n      diagnostics.push(createDiagnostic('warning', Range.create(0, 5, 0, 6), DiagnosticSeverity.Warning, 1002))\n      diagnostics.push(createDiagnostic('information', Range.create(1, 0, 1, 1), DiagnosticSeverity.Information, 1003))\n      diagnostics.push(createDiagnostic('hint', Range.create(1, 2, 1, 3), DiagnosticSeverity.Hint, 1004))\n      diagnostics.push(createDiagnostic('error', Range.create(2, 0, 2, 2), DiagnosticSeverity.Error, 1005))\n      collection.set(doc.uri, diagnostics)\n      await doc.synchronize()\n      return doc\n    }\n\n    it('should get label', async () => {\n      let item: DiagnosticItem = {\n        code: 1000,\n        col: 0,\n        end_col: 1,\n        end_lnum: 1,\n        file: os.tmpdir(),\n        level: 0,\n        lnum: 1,\n        location: Location.create('file:///1', Range.create(0, 0, 0, 1)),\n        message: 'message',\n        severity: 'error',\n        source: 'source'\n      }\n      expect(convertToLabel(item, process.cwd(), false).indexOf('1000')).toBe(-1)\n      expect(convertToLabel(item, process.cwd(), true, 'hidden').includes('[source 1000]')).toBe(true)\n    })\n\n    it('should load diagnostics source', async () => {\n      await createDocument('a')\n      await createDocument('b')\n      await manager.start(['diagnostics'])\n      await manager.session?.ui.ready\n      expect(manager.isActivated).toBe(true)\n      let buf = await nvim.buffer\n      let lines = await buf.lines\n      expect(lines.length).toEqual(10)\n    })\n\n    it('should filter diagnostics', async () => {\n      await createDocument('list/workspace-folder1/a')\n      await createDocument('list/workspace-folder1/b')\n      await createDocument('list/workspace-folder2/c')\n      await createDocument('list/workspace-folder2/d')\n      const workspaceFolder = path.join(__dirname, 'workspace-folder1')\n      let list = new DiagnosticsList(manager, false)\n      {\n        let res = await list.filterDiagnostics({})\n        expect(res.length).toBe(20)\n        let spy = jest.spyOn(workspace, 'getWorkspaceFolder').mockReturnValue({\n          name: 'workspace-folder1',\n          uri: URI.file(workspaceFolder).toString()\n        })\n        res = await list.filterDiagnostics({ 'workspace-folder': true })\n        expect(res.length).toBe(10)\n        spy.mockRestore()\n        spy = jest.spyOn(workspace, 'getWorkspaceFolder').mockReturnValue(undefined)\n        res = await list.filterDiagnostics({ 'workspace-folder': true })\n        expect(res.length).toBe(20)\n        spy.mockRestore()\n      }\n      {\n        let res = await list.filterDiagnostics({ buffer: true })\n        expect(res.length).toBe(5)\n      }\n      {\n        let res = await list.filterDiagnostics({ level: 'error' })\n        expect(res.length).toBe(8)\n      }\n    })\n\n    it('should refresh on diagnostics refresh', async () => {\n      let doc = await createDocument('bar')\n      await manager.start(['diagnostics'])\n      await manager.session?.ui.ready\n      expect(manager.isActivated).toBe(true)\n      let diagnostics: Diagnostic[] = []\n      let collection = diagnosticManager.create('test')\n      diagnostics.push(createDiagnostic('error', Range.create(0, 0, 0, 2), DiagnosticSeverity.Error, 1000))\n      diagnostics.push(createDiagnostic('error', Range.create(2, 0, 2, 2), DiagnosticSeverity.Error, 1009))\n      collection.set(doc.uri, diagnostics)\n      let buf = await nvim.buffer\n      await helper.waitValue(async () => {\n        let n = await buf.length\n        return n > 1\n      }, true)\n    })\n  })\n\n  describe('extensions', () => {\n    it('should load extensions source', async () => {\n      let folder = path.join(os.tmpdir(), uuid())\n      fs.mkdirSync(path.join(folder, 'foo'), { recursive: true })\n      fs.mkdirSync(path.join(folder, 'bar'), { recursive: true })\n      let infos: ExtensionInfo[] = []\n      infos.push({\n        id: 'foo',\n        version: '1.0.0',\n        description: 'foo',\n        root: path.join(folder, 'foo'),\n        exotic: false,\n        state: 'activated',\n        isLocal: false,\n        isLocked: true,\n        packageJSON: { name: 'foo', engines: {} }\n      })\n      infos.push({\n        id: 'bar',\n        version: '1.0.0',\n        description: 'bar',\n        root: path.join(folder, 'bar'),\n        exotic: false,\n        state: 'activated',\n        isLocal: true,\n        isLocked: false,\n        packageJSON: { name: 'bar', engines: {} }\n      })\n      let spy = jest.spyOn(extensions, 'getExtensionStates').mockImplementation(() => {\n        return Promise.resolve(infos)\n      })\n      const doAction = async (name: string, item: any) => {\n        let action = source.actions.find(o => o.name == name)\n        await action.execute(item)\n      }\n      let states = new ExtensionStat(folder)\n      let manager = new ExtensionManager(states, folder)\n      let source = new ExtensionList(manager)\n      let items = await source.loadItems()\n      expect(items.length).toBe(2)\n      items[0].data.state = 'disabled'\n      await doAction('toggle', items[0])\n      await doAction('toggle', items[1])\n      items[1].data.state = 'loaded'\n      await expect(async () => {\n        await doAction('toggle', items[1])\n      }).rejects.toThrow(Error)\n      await doAction('configuration', items[0])\n      let jsonfile = path.join(folder, 'bar/package.json')\n      fs.writeFileSync(jsonfile, '{}', 'utf8')\n      await doAction('configuration', items[1])\n      fs.writeFileSync(jsonfile, '{\"contributes\": {}}', 'utf8')\n      await doAction('configuration', items[1])\n      await helper.mockFunction('coc#ui#open_url', 0)\n      await doAction('open', items[1])\n      await doAction('disable', items[0])\n      await doAction('disable', items[1])\n      await doAction('enable', items[0])\n      await doAction('enable', items[1])\n      await doAction('lock', items[0])\n      await expect(async () => {\n        await doAction('reload', items[0])\n      }).rejects.toThrow(Error)\n      await doAction('uninstall', items)\n      await doAction('help', items[0])\n      let helpfile = path.join(folder, 'bar/readme.md')\n      fs.writeFileSync(helpfile, '', 'utf8')\n      await doAction('help', items[1])\n      let bufname = await nvim.eval('bufname(\"%\")')\n      expect(bufname).toMatch('readme')\n      source.doHighlight()\n      spy.mockRestore()\n    })\n  })\n\n  describe('folders', () => {\n    it('should load folders source', async () => {\n      await helper.createDocument(__filename)\n      let uid = uuid()\n      let source = new FolderList()\n      const doAction = async (name: string, item: any) => {\n        let action = source.actions.find(o => o.name == name)\n        await action.execute(item)\n      }\n      let res = await source.loadItems()\n      expect(res.length).toBe(1)\n      await doAction('delete', res[0])\n      expect(workspace.folderPaths.length).toBe(0)\n      let p = doAction('edit', res[0])\n      await helper.waitFor('mode', [], 'c')\n      await nvim.input('<cr>')\n      await p\n      p = doAction('edit', res[0])\n      await helper.waitFor('mode', [], 'c')\n      await nvim.input('<C-u>')\n      await nvim.input('<cr>')\n      await p\n      let spy = jest.spyOn(window, 'requestInput').mockReturnValue(Promise.resolve(''))\n      await doAction('newfile', res[0])\n      spy.mockRestore()\n      fs.rmSync(path.join(os.tmpdir(), uid), { recursive: true, force: true })\n      let filepath = path.join(os.tmpdir(), uid, 'bar')\n      spy = jest.spyOn(window, 'requestInput').mockReturnValue(Promise.resolve(filepath))\n      await doAction('newfile', res[0])\n      let exists = fs.existsSync(filepath)\n      expect(exists).toBe(true)\n      spy.mockRestore()\n    })\n  })\n\n  describe('lists', () => {\n    it('should load lists source', async () => {\n      await manager.start(['lists'])\n      await manager.session?.ui.ready\n      expect(manager.isActivated).toBe(true)\n      await manager.doAction()\n      await helper.waitValue(() => {\n        let s = manager.getSession()\n        return s && s.name !== 'lists'\n      }, true)\n    })\n  })\n\n  describe('outline', () => {\n    it('should load items from provider', async () => {\n      let doc = await workspace.document\n      disposables.push(languages.registerDocumentSymbolProvider([{ language: '*' }], {\n        provideDocumentSymbols: document => {\n          let text = document.getText()\n          let parser = new Parser(text, text.includes('detail'))\n          let res = parser.parse()\n          return Promise.resolve(res)\n        }\n      }))\n      let source = new OutlineList()\n      let context = await createContext({})\n      let res = await source.loadItems(context, CancellationToken.None)\n      expect(res).toEqual([])\n      let code = `class myClass {\n      fun1() {\n      }\n    }`\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), code)])\n      res = await source.loadItems(context, CancellationToken.None)\n      expect(res.length).toBe(2)\n      source.doHighlight()\n    })\n\n    it('should load items by ctags', async () => {\n      helper.updateConfiguration('list.source.outline.ctagsFiletypes', ['vim'])\n      await nvim.command('edit +setl\\\\ filetype=vim foo')\n      let doc = await workspace.document\n      expect(doc.filetype).toBe('vim')\n      let source = new OutlineList()\n      let context = await createContext({})\n      context.args = ['-kind', 'function', '-name', 'name']\n      let res = await source.loadItems(context, CancellationToken.None)\n      expect(res).toEqual([])\n      res = await source.loadItems(context, CancellationToken.Cancelled)\n      expect(res).toEqual([])\n    })\n  })\n\n  describe('services', () => {\n    function createService(name: string): IServiceProvider {\n      let _onServiceReady = new Emitter<void>()\n      // public readonly onServiceReady: Event<void> = this.\n      let service: IServiceProvider = {\n        id: name,\n        name,\n        selector: [{ language: 'vim' }],\n        state: ServiceStat.Initial,\n        start(): Promise<void> {\n          service.state = ServiceStat.Running\n          _onServiceReady.fire()\n          return Promise.resolve()\n        },\n        dispose(): void {\n          service.state = ServiceStat.Stopped\n        },\n        stop(): void {\n          service.state = ServiceStat.Stopped\n        },\n        restart(): void {\n          service.state = ServiceStat.Running\n          _onServiceReady.fire()\n        },\n        onServiceReady: _onServiceReady.event\n      }\n      disposables.push(services.register(service))\n      return service\n    }\n\n    it('should load services source', async () => {\n      createService('foo')\n      createService('bar')\n      await manager.start(['services'])\n      await manager.session?.ui.ready\n      expect(manager.isActivated).toBe(true)\n      let lines = await nvim.call('getline', [1, '$']) as string[]\n      expect(lines.length).toBe(2)\n    })\n\n    it('should toggle service state', async () => {\n      let service = createService('foo')\n      await service.start()\n      await manager.start(['services'])\n      await manager.session?.ui.ready\n      expect(manager.isActivated).toBe(true)\n      let ses = manager.session\n      expect(ses.name).toBe('services')\n      await ses.doAction('toggle')\n      expect(service.state).toBe(ServiceStat.Stopped)\n      await ses.doAction('toggle')\n    })\n  })\n\n  describe('sources', () => {\n    it('should load sources source', async () => {\n      await manager.start(['sources'])\n      await manager.session?.ui.ready\n      expect(manager.isActivated).toBe(true)\n      let session = manager.getSession()\n      await session.doAction('open')\n      let bufname = await nvim.call('bufname', '%')\n      expect(bufname).toMatch(/native/)\n    })\n\n    it('should toggle source state', async () => {\n      await manager.start(['sources'])\n      await manager.session?.ui.ready\n      expect(manager.isActivated).toBe(true)\n      let session = manager.getSession()\n      await session.doAction('toggle')\n      await session.doAction('toggle')\n    })\n\n    it('should refresh source', async () => {\n      await manager.start(['sources'])\n      await manager.session?.ui.ready\n      expect(manager.isActivated).toBe(true)\n      let session = manager.getSession()\n      await session.doAction('refresh')\n    })\n  })\n\n  describe('notifications', () => {\n    it('should load notifications history', async () => {\n      await window.showInformationMessage('Info message')\n\n      await manager.start(['notifications'])\n      await manager.session?.ui.ready\n      expect(manager.isActivated).toBe(true)\n      let items = await manager.loadItems('notifications')\n      expect(items.at(-1).label).toContain('INFO'.padEnd(7))\n      expect(items.at(-1).filterText).toBe('Info message')\n\n      const action = manager.session.defaultAction\n      expect(action.name).toBe('clear')\n      await manager.session.doAction()\n      items = await manager.loadItems('notifications')\n      expect(items.length).toBe(0)\n    })\n\n    it('should load notifications from action', async () => {\n      await window.showInformationMessage('Info message')\n\n      const res = await helper.doAction('notificationHistory')\n      expect(res.length).toBe(1)\n      expect(res[0].message).toBe('Info message')\n    })\n  })\n\n  describe('symbols', () => {\n    it('should create list item', () => {\n      let source = new SymbolsList()\n      let symbolItem = SymbolInformation.create('root', SymbolKind.Method, Range.create(0, 0, 0, 10), '')\n      let item = source.createListItem('', symbolItem, 'kind', './foo')\n      expect(item).toBeDefined()\n      symbolItem.tags = [SymbolTag.Deprecated]\n      item = source.createListItem('', symbolItem, 'kind', './foo')\n      let highlights = item.ansiHighlights\n      let find = highlights.find(o => o.hlGroup == 'CocDeprecatedHighlight')\n      expect(find).toBeDefined()\n      source.fuzzyMatch.setPattern('a')\n      item = source.createListItem('a', symbolItem, 'kind', './foo')\n      expect(item).toBeDefined()\n      source.fuzzyMatch.setPattern('r')\n      item = source.createListItem('r', symbolItem, 'kind', './foo')\n      highlights = item.ansiHighlights\n      find = highlights.find(o => o.hlGroup == 'CocListSearch')\n      expect(find).toBeDefined()\n    })\n\n    it('should resolve item', async () => {\n      let source = new SymbolsList()\n      let res = await source.resolveItem({ label: 'label', data: {} })\n      expect(res).toBeNull()\n      let haveResult = false\n      let disposable = languages.registerWorkspaceSymbolProvider({\n        provideWorkspaceSymbols: () => [\n          SymbolInformation.create('root', SymbolKind.Method, Range.create(0, 0, 0, 10), '')\n        ],\n        resolveWorkspaceSymbol: symbolItem => {\n          symbolItem.location = Location.create('lsp:///1', Range.create(0, 0, 1, 0))\n          return haveResult ? symbolItem : null\n        }\n      })\n      disposables.push(disposable)\n      let symbols = await languages.getWorkspaceSymbols('', CancellationToken.None)\n      res = await source.resolveItem({ label: 'label', data: { original: symbols[0] } })\n      expect(res).toBeNull()\n      haveResult = true\n      symbols[0].location = { uri: 'lsp:///1' }\n      res = await source.resolveItem({ label: 'label', data: { original: symbols[0] } })\n      expect(Location.is(res.location)).toBe(true)\n      if (Location.is(res.location)) {\n        expect(res.location.uri).toBe('lsp:///1')\n      }\n    })\n\n    it('should load items', async () => {\n      let source = new SymbolsList()\n      let context = await createContext({ interactive: true })\n      await expect(async () => {\n        await source.loadItems(context, CancellationToken.None)\n      }).rejects.toThrow(Error)\n      disposables.push(languages.registerWorkspaceSymbolProvider({\n        provideWorkspaceSymbols: () => [\n          SymbolInformation.create('root', SymbolKind.Method, Range.create(0, 0, 0, 10), URI.file(__filename).toString())\n        ]\n      }))\n      let res = await source.loadItems(context, CancellationToken.Cancelled)\n      expect(res).toEqual([])\n      context.args = ['-kind', 'function']\n      res = await source.loadItems(context, CancellationToken.None)\n      expect(res).toEqual([])\n      context.args = []\n      helper.updateConfiguration('list.source.symbols.excludes', ['**/*.ts'])\n      res = await source.loadItems(context, CancellationToken.None)\n      expect(res).toEqual([])\n      helper.updateConfiguration('list.source.symbols.excludes', [])\n      res = await source.loadItems(context, CancellationToken.None)\n      expect(res.length).toBe(1)\n    })\n\n    it('should load symbols source', async () => {\n      await helper.createDocument()\n      disposables.push(languages.registerWorkspaceSymbolProvider({\n        provideWorkspaceSymbols: () => []\n      }))\n      await manager.start(['--interactive', 'symbols'])\n      await manager.session?.ui.ready\n      expect(manager.isActivated).toBe(true)\n    })\n  })\n\n  describe('links', () => {\n    it('should load links source', async () => {\n      let disposable = languages.registerDocumentLinkProvider([{ scheme: 'file' }, { scheme: 'untitled' }], {\n        provideDocumentLinks: () => {\n          return [\n            DocumentLink.create(Range.create(0, 0, 0, 5), 'file:///foo'),\n            DocumentLink.create(Range.create(1, 0, 1, 5), 'file:///bar')\n          ]\n        }\n      })\n      await manager.start(['links'])\n      await manager.session?.ui.ready\n      expect(manager.isActivated).toBe(true)\n      await manager.doAction('jump')\n      disposable.dispose()\n    })\n\n    it('should resolve target', async () => {\n      let disposable = languages.registerDocumentLinkProvider([{ scheme: 'file' }, { scheme: 'untitled' }], {\n        provideDocumentLinks: () => {\n          return [\n            DocumentLink.create(Range.create(0, 0, 0, 5)),\n          ]\n        },\n        resolveDocumentLink: link => {\n          link.target = 'file:///foo'\n          return link\n        }\n      })\n      await manager.start(['links'])\n      await manager.session?.ui.ready\n      expect(manager.isActivated).toBe(true)\n      await manager.doAction('open')\n      disposable.dispose()\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/list/ui.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { EventEmitter } from 'events'\nimport { Disposable } from 'vscode-languageserver-protocol'\nimport BasicList from '../../list/basic'\nimport events from '../../events'\nimport manager from '../../list/manager'\nimport { ListItem, IList, ListTask } from '../../list/types'\nimport { disposeAll } from '../../util'\nimport helper from '../helper'\n\nlet labels: string[] = []\nlet lastItem: string\n\nclass SimpleList extends BasicList {\n  public name = 'simple'\n  constructor() {\n    super()\n    this.addAction('open', item => {\n      lastItem = item.label\n    })\n  }\n  public loadItems(): Promise<ListItem[]> {\n    return Promise.resolve(labels.map(s => {\n      return { label: s, ansiHighlights: [{ span: [0, 1], hlGroup: 'MoreMsg' }] } as ListItem\n    }))\n  }\n}\n\nclass SlowTask extends EventEmitter implements ListTask {\n  private interval: NodeJS.Timeout\n  constructor() {\n    super()\n    let i = 0\n    let interval = this.interval = setInterval(() => {\n      i++\n      this.emit('data', {\n        label: i.toString(), highlights: {\n          spans: [[0, 1]],\n          hlGroup: 'Search'\n        }\n      })\n      if (i == 5) {\n        this.emit('end')\n        clearInterval(interval)\n      }\n    }, 50)\n  }\n\n  public dispose(): void {\n    clearInterval(this.interval)\n    this.removeAllListeners()\n  }\n}\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  manager.reset()\n  await helper.reset()\n})\n\ndescribe('list ui', () => {\n  describe('selectLines()', () => {\n    it('should select lines', async () => {\n      labels = ['foo', 'bar']\n      disposables.push(manager.registerList(new SimpleList()))\n      await manager.start(['simple'])\n      let ui = manager.session.ui\n      await ui.ready\n      await ui.selectLines(3, 1)\n      let buf = await nvim.buffer\n      let res = await buf.getSigns({ group: 'coc-list' })\n      expect(res.length).toBe(2)\n    })\n  })\n\n  describe('preselect', () => {\n    it('should select preselect item', async () => {\n      let list: IList = {\n        actions: [{\n          name: 'open',\n          execute: () => {}\n        }],\n        name: 'preselect',\n        defaultAction: 'open',\n        loadItems: () => {\n          return Promise.resolve([{ label: 'foo' }, { label: 'bar', preselect: true }])\n        }\n      }\n      disposables.push(manager.registerList(list))\n      await manager.start(['--tab', 'preselect'])\n      let ui = manager.session.ui\n      await ui.ready\n      ui.restoreWindow()\n      let line = await nvim.line\n      expect(line).toBe('bar')\n    })\n  })\n\n  describe('resume()', () => {\n    it('should resume with selected lines', async () => {\n      labels = ['foo', 'bar']\n      disposables.push(manager.registerList(new SimpleList()))\n      await manager.start(['simple'])\n      let ui = manager.session.ui\n      await ui.ready\n      await ui.selectLines(1, 2)\n      await nvim.call('coc#window#close', [ui.winid])\n      await helper.wait(100)\n      await manager.session.resume()\n      await helper.wait(100)\n      let buf = await nvim.buffer\n      let res = await buf.getSigns({ group: 'coc-list' })\n      expect(res.length).toBe(2)\n    })\n  })\n\n  describe('events', () => {\n    async function mockMouse(winid: number, lnum: number): Promise<void> {\n      await nvim.command(`let v:mouse_winid = ${winid}`)\n      await nvim.command(`let v:mouse_lnum = ${lnum}`)\n      await nvim.command('let v:mouse_col = 1')\n    }\n\n    it('should fire action on double click', async () => {\n      labels = ['foo', 'bar']\n      disposables.push(manager.registerList(new SimpleList()))\n      await manager.start(['simple'])\n      let ui = manager.session.ui\n      await ui.ready\n      await mockMouse(ui.winid, 1)\n      await manager.session.onMouseEvent('<2-LeftMouse>')\n      await helper.waitValue(() => lastItem, 'foo')\n    })\n\n    it('should select clicked line', async () => {\n      labels = ['foo', 'bar']\n      disposables.push(manager.registerList(new SimpleList()))\n      await manager.start(['simple'])\n      let ui = manager.session.ui\n      ui.updateItem(undefined, 0)\n      ui.setLines([], 0, 0)\n      await ui.onMouse('mouseDown')\n      await ui.ready\n      await mockMouse(ui.winid, 2)\n      await ui.onMouse('mouseDrag')\n      await ui.onMouse('mouseUp')\n      await ui.onMouse('mouseDown')\n      await mockMouse(ui.winid, 2)\n      await ui.onMouse('mouseUp')\n      let item = await ui.item\n      await ui.appendItems([])\n      expect(item.label).toBe('bar')\n    })\n\n    it('should jump to original window on click', async () => {\n      labels = ['foo', 'bar']\n      let win = await nvim.window\n      disposables.push(manager.registerList(new SimpleList()))\n      await manager.start(['simple'])\n      let ui = manager.session.ui\n      await ui.ready\n      await mockMouse(win.id, 1)\n      await ui.onMouse('mouseUp')\n      await helper.wait(50)\n      let curr = await nvim.window\n      expect(curr.id).toBe(win.id)\n    })\n\n    it('should highlights items on CursorMoved', async () => {\n      labels = (new Array(400)).fill('a')\n      disposables.push(manager.registerList(new SimpleList()))\n      await manager.start(['--normal', 'simple'])\n      let ui = manager.session.ui\n      await ui.ready\n      await nvim.call('cursor', [350, 1])\n      await events.fire('CursorMoved', [ui.bufnr, [350, 1]])\n      let buf = nvim.createBuffer(ui.bufnr)\n      await helper.waitValue(async () => {\n        let res = await buf.getHighlights('list')\n        return res.length > 300\n      }, true)\n    })\n  })\n})\n\ndescribe('reversed list', () => {\n  it('should render and add highlights', async () => {\n    labels = ['a', 'b', 'c', 'd']\n    disposables.push(manager.registerList(new SimpleList()))\n    await manager.start(['--reverse', 'simple'])\n    let ui = manager.session.ui\n    await ui.ready\n    let buf = nvim.createBuffer(ui.bufnr)\n    let lines = await buf.lines\n    expect(lines).toEqual(['d', 'c', 'b', 'a'])\n    await helper.listInput('a')\n    await helper.wait(50)\n    lines = await buf.lines\n    expect(lines).toEqual(['a'])\n    let res = await buf.getHighlights('list')\n    expect(res.length).toBe(2)\n    let win = nvim.createWindow(ui.winid)\n    let height = await win.height\n    expect(height).toBe(1)\n  })\n\n  it('should moveUp and moveDown', async () => {\n    labels = ['a', 'b', 'c', 'd']\n    disposables.push(manager.registerList(new SimpleList()))\n    await manager.start(['--reverse', 'simple'])\n    let ui = manager.session.ui\n    await ui.ready\n    await ui.moveCursor(-1)\n    await helper.waitFor('line', ['.'], 3)\n    await ui.moveCursor(1)\n    await helper.waitFor('line', ['.'], 4)\n  })\n\n  it('should toggle selection', async () => {\n    labels = ['a', 'b', 'c', 'd']\n    disposables.push(manager.registerList(new SimpleList()))\n    await manager.start(['--reverse', '--normal', 'simple'])\n    let ui = manager.session.ui\n    await ui.ready\n    await ui.toggleSelection()\n    let items = ui.selectedItems\n    expect(items.length).toBeGreaterThan(0)\n    expect(items[0].label).toBe('a')\n    let lnum = await nvim.call('line', ['.'])\n    expect(lnum).toBe(3)\n    await helper.listInput('j')\n    await ui.toggleSelection()\n    items = ui.selectedItems\n    expect(items.length).toBe(0)\n  })\n\n  it('should prepend list items', async () => {\n    let o: any\n    let p = new Promise(resolve => {\n      let list: IList = {\n        actions: [{\n          name: 'open',\n          execute: item => {\n            o = item\n          }\n        }],\n        name: 'slow',\n        defaultAction: 'open',\n        loadItems: () => {\n          let task = new SlowTask()\n          task.on('end', () => {\n            resolve(undefined)\n          })\n          return Promise.resolve(task)\n        }\n      }\n      disposables.push(manager.registerList(list))\n      void manager.start(['--reverse', '--normal', 'slow'])\n    })\n    let ui = manager.session.ui\n    ui.setCursor(99)\n    await p\n    await helper.wait(50)\n    // ui.setCursor(2)\n    let buf = nvim.createBuffer(ui.bufnr)\n    let lines = await buf.lines\n    expect(lines).toEqual(['5', '4', '3', '2', '1'])\n    let lnum = await nvim.call('line', ['.'])\n    expect(lnum).toBe(5)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/list/worker.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport styles from 'ansi-styles'\nimport { EventEmitter } from 'events'\nimport { CancellationToken, Disposable } from 'vscode-languageserver-protocol'\nimport BasicList from '../../list/basic'\nimport manager from '../../list/manager'\nimport { ListContext, ListItem, ListTask } from '../../list/types'\nimport { convertItemLabel, indexOf, parseInput, toInputs } from '../../list/worker'\nimport { disposeAll } from '../../util'\nimport helper from '../helper'\n\nlet items: ListItem[] = []\n\nclass DataList extends BasicList {\n  public name = 'data'\n  public loadItems(): Promise<ListItem[]> {\n    return Promise.resolve(items)\n  }\n}\n\nclass EmptyList extends BasicList {\n  public name = 'empty'\n  public loadItems(): Promise<ListItem[]> {\n    let emitter: any = new EventEmitter()\n    setTimeout(() => {\n      emitter.emit('end')\n    }, 20)\n    return emitter\n  }\n}\n\nclass IntervalTaskList extends BasicList {\n  public name = 'task'\n  public timeout = 3000\n  public loadItems(_context: ListContext, token: CancellationToken): Promise<ListTask> {\n    let emitter: any = new EventEmitter()\n    let i = 0\n    let interval = setInterval(() => {\n      emitter.emit('data', { label: i.toFixed() })\n      i++\n    }, 20)\n    emitter.dispose = () => {\n      clearInterval(interval)\n      emitter.emit('end')\n    }\n    token.onCancellationRequested(() => {\n      emitter.dispose()\n    })\n    return emitter\n  }\n}\n\nclass DelayTask extends BasicList {\n  public name = 'delay'\n  public interactive = true\n  public loadItems(_context: ListContext, token: CancellationToken): Promise<ListTask> {\n    let emitter: any = new EventEmitter()\n    let disposed = false\n    setTimeout(() => {\n      if (disposed) return\n      emitter.emit('data', { label: 'ahead' })\n    }, 10)\n    setTimeout(() => {\n      if (disposed) return\n      emitter.emit('data', { label: 'abort' })\n    }, 20)\n    emitter.dispose = () => {\n      disposed = true\n      emitter.emit('end')\n    }\n    token.onCancellationRequested(() => {\n      emitter.dispose()\n    })\n    return emitter\n  }\n}\n\nclass InteractiveList extends BasicList {\n  public name = 'test'\n  public interactive = true\n  public loadItems(context: ListContext, _token: CancellationToken): Promise<ListItem[]> {\n    return Promise.resolve([{\n      label: styles.magenta.open + (context.input || '') + styles.magenta.close\n    }])\n  }\n}\n\nclass ErrorList extends BasicList {\n  public name = 'error'\n  public interactive = true\n  public loadItems(_context: ListContext, _token: CancellationToken): Promise<ListItem[]> {\n    return Promise.reject(new Error('test error'))\n  }\n}\n\nclass ErrorTaskList extends BasicList {\n  public name = 'task'\n  public loadItems(_context: ListContext, _token: CancellationToken): Promise<ListTask> {\n    let emitter: any = new EventEmitter()\n    let timeout = setTimeout(() => {\n      emitter.emit('error', new Error('task error'))\n    }, 100)\n    emitter.dispose = () => {\n      clearTimeout(timeout)\n    }\n    return emitter\n  }\n}\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  manager.reset()\n  await helper.reset()\n})\n\ndescribe('util', () => {\n  it('should get index', () => {\n    expect(indexOf('Abc', 'a', true, false)).toBe(0)\n    expect(indexOf('Abc', 'A', false, false)).toBe(0)\n    expect(indexOf('abc', 'A', false, true)).toBe(0)\n  })\n\n  it('should parse input with space', () => {\n    let res = parseInput('a b')\n    expect(res).toEqual(['a', 'b'])\n    res = parseInput('a b ')\n    expect(res).toEqual(['a', 'b'])\n    res = parseInput('ab ')\n    expect(res).toEqual(['ab'])\n  })\n\n  it('should parse input with escaped space', () => {\n    let res = parseInput('a\\\\ b')\n    expect(res).toEqual(['a b'])\n  })\n\n  it('should convert item label', () => {\n    expect(convertItemLabel({ label: 'foo\\nbar\\nx' }).label).toBe('foo')\n    const redOpen = '\\x1B[31m'\n    const redClose = '\\x1B[39m'\n    let label = redOpen + 'foo' + redClose\n    expect(convertItemLabel({ label }).label).toBe('foo')\n  })\n\n  it('should convert input', () => {\n    expect(toInputs('foo bar', false)).toEqual(['foo bar'])\n  })\n})\n\ndescribe('list worker', () => {\n\n  it('should work with long running task', async () => {\n    disposables.push(manager.registerList(new IntervalTaskList()))\n    await manager.start(['task'])\n    await manager.session.worker.drawItems()\n    await manager.session.ui.ready\n    await helper.waitValue(() => {\n      return manager.session?.length > 2\n    }, true)\n    await manager.cancel()\n  })\n\n  it('should sort by sortText', async () => {\n    items = [{\n      label: 'abc',\n      sortText: 'b'\n    }, {\n      label: 'ade',\n      sortText: 'a'\n    }]\n    disposables.push(manager.registerList(new DataList()))\n    await manager.start(['data'])\n    await manager.session.ui.ready\n    await helper.listInput('a')\n    await helper.waitFor('getline', ['.'], 'ade')\n    await manager.cancel()\n  })\n\n  it('should ready with undefined result', async () => {\n    items = undefined\n    disposables.push(manager.registerList(new DataList()))\n    await manager.start(['data'])\n    await manager.session.ui.ready\n    await manager.cancel()\n  })\n\n  it('should show empty line for empty task', async () => {\n    disposables.push(manager.registerList(new EmptyList()))\n    await manager.start(['empty'])\n    await manager.session.ui.ready\n    let line = await nvim.call('getline', [1])\n    expect(line).toMatch('No results')\n    await manager.cancel()\n  })\n\n  it('should cancel task by use CancellationToken', async () => {\n    disposables.push(manager.registerList(new IntervalTaskList()))\n    await manager.start(['task'])\n    expect(manager.session?.worker.isLoading).toBe(true)\n    await helper.listInput('1')\n    await helper.wait(50)\n    manager.session?.stop()\n    expect(manager.session?.worker.isLoading).toBe(false)\n  })\n\n  it('should render slow interactive list', async () => {\n    disposables.push(manager.registerList(new DelayTask()))\n    await manager.start(['delay'])\n    await helper.listInput('a')\n    await helper.waitFor('getline', [2], 'abort')\n  })\n\n  it('should work with interactive list', async () => {\n    disposables.push(manager.registerList(new InteractiveList()))\n    await manager.start(['-I', 'test'])\n    await manager.session?.ui.ready\n    expect(manager.isActivated).toBe(true)\n    await helper.listInput('f')\n    await helper.listInput('a')\n    await helper.listInput('x')\n    await helper.waitFor('getline', ['.'], 'fax')\n    await manager.cancel(true)\n  })\n\n  it('should not activate on load error', async () => {\n    disposables.push(manager.registerList(new ErrorList()))\n    await manager.start(['test'])\n    expect(manager.isActivated).toBe(false)\n  })\n\n  it('should deactivate on task error', async () => {\n    disposables.push(manager.registerList(new ErrorTaskList()))\n    await manager.start(['task'])\n    await helper.waitValue(() => {\n      return manager.isActivated\n    }, false)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/markdown/index.test.ts",
    "content": "import { getHighlightItems, toFiletype, parseMarkdown, parseDocuments } from '../../markdown/index'\nimport { Documentation } from '../../types'\n\ndescribe('getHighlightItems', () => {\n  it('should convert filetype', () => {\n    expect(toFiletype(undefined)).toBe('txt')\n    expect(toFiletype('ts')).toBe('typescript')\n    expect(toFiletype('js')).toBe('javascript')\n    expect(toFiletype('bash')).toBe('sh')\n  })\n\n  it('should get highlights in single line', () => {\n    let res = getHighlightItems('this line has highlights', 0, [10, 15])\n    expect(res).toEqual([{\n      colStart: 10,\n      colEnd: 15,\n      lnum: 0,\n      hlGroup: 'CocFloatActive'\n    }])\n  })\n\n  it('should get highlights when active end extended', () => {\n    let res = getHighlightItems('this line', 0, [5, 30])\n    expect(res).toEqual([{\n      colStart: 5,\n      colEnd: 9,\n      lnum: 0,\n      hlGroup: 'CocFloatActive'\n    }])\n  })\n\n  it('should get highlights across line', () => {\n    let res = getHighlightItems('this line\\nhas highlights', 0, [5, 15])\n    expect(res).toEqual([{\n      colStart: 5, colEnd: 9, lnum: 0, hlGroup: 'CocFloatActive'\n    }, {\n      colStart: 0, colEnd: 5, lnum: 1, hlGroup: 'CocFloatActive'\n    }])\n    res = getHighlightItems('a\\nb\\nc\\nd', 0, [2, 5])\n    expect(res).toEqual([\n      { colStart: 0, colEnd: 1, lnum: 1, hlGroup: 'CocFloatActive' },\n      { colStart: 0, colEnd: 1, lnum: 2, hlGroup: 'CocFloatActive' },\n      { colStart: 0, colEnd: 0, lnum: 3, hlGroup: 'CocFloatActive' }\n    ])\n  })\n})\n\ndescribe('parseMarkdown', () => {\n  it('should parse code blocks', () => {\n    let content = `\n\\`\\`\\`js\nvar global = globalThis\n\\`\\`\\`\n\\`\\`\\`ts\nlet str:string\n\\`\\`\\`\n\\`\\`\\`bash\nif\n\\`\\`\\`\n`\n    let res = parseMarkdown(content, {})\n    expect(res.lines).toEqual([\n      'var global = globalThis',\n      '',\n      'let str:string',\n      '',\n      'if'\n    ])\n    expect(res.codes).toEqual([\n      { filetype: 'javascript', startLine: 0, endLine: 1 },\n      { filetype: 'typescript', startLine: 2, endLine: 3 },\n      { filetype: 'sh', startLine: 4, endLine: 5 },\n    ])\n  })\n\n  it('should merge empty lines', () => {\n    let content = `\n![img](http://img.io)\n![img](http://img.io)\n[link](http://example.com)\n[link](javascript:void(0))\n`\n    let res = parseMarkdown(content, { excludeImages: true })\n    expect(res.lines).toEqual([\n      'link',\n      '',\n      'link: http://example.com'\n    ])\n  })\n\n  it('should parse html code block', () => {\n    let content = `\nexample:\n\\`\\`\\`html\n<div>code</div>\n\\`\\`\\`\n    `\n    let res = parseMarkdown(content, {})\n    expect(res.lines).toEqual(['example:', '<div>code</div>'])\n    expect(res.codes).toEqual([{ filetype: 'html', startLine: 1, endLine: 2 }])\n  })\n\n  it('should merge empty lines', async () => {\n    let content = `\nhttps://baidu.com/%25E0%25A4%25A\nfoo\n\n\n\nbar\n `\n    let res = parseMarkdown(content, {})\n    expect(res.lines).toEqual(['foo', '', 'bar'])\n  })\n\n  it('should compose empty lines', () => {\n    let content = 'foo\\n\\n\\nbar\\n\\n\\n'\n    let res = parseMarkdown(content, {})\n    expect(res.lines).toEqual(['foo', '', 'bar'])\n  })\n\n  it('should merge lines', () => {\n    let content = 'first\\nsecond'\n    let res = parseMarkdown(content, {})\n    expect(res.lines).toEqual(['first', 'second'])\n  })\n\n  it('should parse ansi highlights', () => {\n    let content = '__foo__\\n[link](link)'\n    let res = parseMarkdown(content, {})\n    expect(res.lines).toEqual(['foo', 'link'])\n    expect(res.highlights).toEqual([\n      { hlGroup: 'CocBold', lnum: 0, colStart: 0, colEnd: 3 },\n      { hlGroup: 'CocUnderline', lnum: 1, colStart: 0, colEnd: 4 }\n    ])\n  })\n\n  it('should exclude images by option', () => {\n    let content = 'head\\n![img](img)\\ncontent ![img](img) ![img](img)'\n    let res = parseMarkdown(content, { excludeImages: false })\n    expect(res.lines).toEqual(['head', '![img](img)', 'content ![img](img) ![img](img)'])\n    content = 'head\\n![img](img)\\ncontent ![img](img) ![img](img)'\n    res = parseMarkdown(content, { excludeImages: true })\n    expect(res.lines).toEqual(['head', 'content'])\n  })\n\n  it('should render hr', () => {\n    let content = 'foo\\n***\\nbar'\n    let res = parseMarkdown(content, {})\n    expect(res.lines).toEqual(['foo', '───', 'bar'])\n  })\n\n  it('should render deleted text', () => {\n    let content = '~foo~'\n    let res = parseMarkdown(content, {})\n    expect(res.highlights).toEqual([\n      { hlGroup: 'CocStrikeThrough', lnum: 0, colStart: 0, colEnd: 3 }\n    ])\n  })\n\n  it('should render br', () => {\n    let content = 'a  \\nb'\n    let res = parseMarkdown(content, {})\n    expect(res.lines).toEqual(['a', 'b'])\n  })\n\n  it('should render code span', () => {\n    let content = '`foo`'\n    let res = parseMarkdown(content, {})\n    expect(res.highlights).toEqual([\n      { hlGroup: 'CocMarkdownCode', lnum: 0, colStart: 0, colEnd: 3 }\n    ])\n  })\n\n  it('should render html', () => {\n    let content = '<div>foo</div>'\n    let res = parseMarkdown(content, {})\n    expect(res.lines).toEqual(['foo'])\n  })\n\n  it('should render checkbox', () => {\n    let content = '- [x] first\\n- [ ] second'\n    let res = parseMarkdown(content, {})\n    expect(res.lines).toEqual([\n      '  * [X] first', '  * [ ] second'\n    ])\n  })\n\n  it('should render numbered list', () => {\n    let content = '1. one\\n2. two\\n3. three'\n    let res = parseMarkdown(content, {})\n    expect(res.lines).toEqual([\n      '  1. one', '  2. two', '  3. three'\n    ])\n  })\n\n  it('should render nested list', () => {\n    let content = '- foo\\n- bar\\n    - one\\n    - two'\n    let res = parseMarkdown(content, {})\n    expect(res.lines).toEqual([\n      '  * foo', '  * bar', '    * one', '    * two'\n    ])\n  })\n\n  it('should render complicated nested list', () => {\n    let content = `\n- greeting\n  - hello\n    - me\n      you\n      them\n  - hi\n    - me\n      you\n      them\n    - him\n      her\n- bye\n- code\n\n  \\`\\`\\`typescript\n  function foo () {\n          console.log('foo')\n    console.log('bar')\n  }\n  \\`\\`\\`\n`\n    let res = parseMarkdown(content, {})\n    expect(res.lines).toEqual(\n`  * greeting\n    * hello\n      * me\n        you\n        them\n    * hi\n      * me\n        you\n        them\n      * him\n        her\n  * bye\n  * code\n    function foo () {\n            console.log('foo')\n      console.log('bar')\n    }`.split('\\n'))\n  })\n})\n\ndescribe('parseDocuments', () => {\n  it('should parse documents with diagnostic filetypes', () => {\n    let docs = [{\n      filetype: 'Error',\n      content: 'Error text'\n    }, {\n      filetype: 'Warning',\n      content: 'Warning text'\n    }]\n    let res = parseDocuments(docs)\n    expect(res.lines).toEqual([\n      'Error text',\n      '─',\n      'Warning text'\n    ])\n    expect(res.codes).toEqual([\n      { hlGroup: 'CocErrorFloat', startLine: 0, endLine: 1 },\n      { hlGroup: 'CocWarningFloat', startLine: 2, endLine: 3 }\n    ])\n  })\n\n  it('should parse markdown document with filetype document', () => {\n    let docs = [{\n      filetype: 'typescript',\n      content: 'const workspace'\n    }, {\n      filetype: 'markdown',\n      content: '**header**'\n    }]\n    let res = parseDocuments(docs)\n    expect(res.lines).toEqual([\n      'const workspace',\n      '─',\n      'header'\n    ])\n    expect(res.highlights).toEqual([{\n      colEnd: -1,\n      colStart: 0,\n      hlGroup: \"CocFloatDividingLine\",\n      lnum: 1,\n    }, {\n      hlGroup: 'CocBold',\n      lnum: 2,\n      colStart: 0,\n      colEnd: 6\n    }])\n    expect(res.codes).toEqual([\n      { filetype: 'typescript', startLine: 0, endLine: 1 }\n    ])\n  })\n\n  it('should parse document with highlights', () => {\n    let docs: Documentation[] = [{\n      filetype: 'txt',\n      content: 'foo'\n    }, {\n      filetype: 'txt',\n      content: 'foo bar',\n      highlights: [{\n        lnum: 0,\n        colStart: 4,\n        colEnd: 7,\n        hlGroup: 'String'\n      }]\n    }]\n    let res = parseDocuments(docs)\n    let { highlights } = res\n    expect(highlights[1]).toEqual({ lnum: 2, colStart: 4, colEnd: 7, hlGroup: 'String' })\n  })\n\n  it('should parse documents with active highlights', () => {\n    let docs = [{\n      filetype: 'javascript',\n      content: 'func(foo, bar)',\n      active: [5, 8]\n    }, {\n      filetype: 'javascript',\n      content: 'func()',\n      active: [15, 20]\n    }]\n    let res = parseDocuments(docs as any)\n    expect(res.highlights[0]).toEqual({ colStart: 5, colEnd: 8, lnum: 0, hlGroup: 'CocFloatActive' })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/markdown/renderer.test.ts",
    "content": "import { marked } from 'marked'\nimport Renderer, { bulletPointLine, fixHardReturn, generateTableRow, identify, numberedLine, toSpaces, toSpecialSpaces } from '../../markdown/renderer'\nimport * as styles from '../../markdown/styles'\nimport { parseAnsiHighlights, AnsiResult } from '../../util/ansiparse'\n\nmarked.setOptions({\n  renderer: new Renderer(),\n  hooks: Renderer.hooks,\n})\n\nfunction parse(text: string): AnsiResult {\n  let m = marked(text)\n  let res = parseAnsiHighlights(m.split(/\\n/)[0], true)\n  return res\n}\n\ndescribe('styles', () => {\n  it('should add styles', () => {\n    let keys = ['gray', 'magenta', 'bold', 'underline', 'italic', 'strikethrough', 'yellow', 'green', 'blue']\n    for (let key of keys) {\n      let res = styles[key]('text')\n      expect(res).toContain('text')\n    }\n  })\n})\n\ndescribe('Renderer of marked', () => {\n  it('should convert', () => {\n    expect(identify('  ', '')).toBe('')\n    expect(fixHardReturn('a\\rb', true)).toBe('a\\nb')\n    expect(toSpaces('ab')).toBe('  ')\n    expect(toSpecialSpaces('ab')).toBe('\\0\\0\\0\\0\\0\\0')\n    expect(bulletPointLine('  ', '  * foo')).toBe('  * foo')\n    expect(bulletPointLine('  ', 'foo')).toBe('\\0\\0\\0\\0\\0\\0foo')\n    expect(bulletPointLine('  ', '\\0\\0\\0foo')).toBe('\\0\\0\\0foo')\n    expect(generateTableRow('')).toEqual([])\n    expect(numberedLine('  ', 'foo', 1).line).toBe('   foo')\n  })\n\n  it('should create bold highlights', () => {\n    let res = parse('**note**.')\n    expect(res.highlights[0]).toEqual({\n      span: [0, 4],\n      hlGroup: 'CocBold'\n    })\n  })\n\n  it('should create italic highlights', () => {\n    let res = parse('_note_.')\n    expect(res.highlights[0]).toEqual({\n      span: [0, 4],\n      hlGroup: 'CocItalic'\n    })\n  })\n\n  it('should create underline highlights for link', () => {\n    let res = parse('[baidu](https://baidu.com)')\n    expect(res.highlights[0]).toEqual({\n      span: [0, 5],\n      hlGroup: 'CocMarkdownLink'\n    })\n    res = parse('https://baidu.com')\n    expect(res.highlights[0]).toEqual({\n      span: [0, 17],\n      hlGroup: 'CocUnderline'\n    })\n    res = parse('https://baidu.com/%25E0%25A4%25A')\n    expect(res.line).toBe('')\n  })\n\n  it('should parse link', () => {\n    // let res = parse('https://doc.rust-lang.org/nightly/core/iter/traits/iterator/Iterator.t.html#map.v')\n    // console.log(JSON.stringify(res, null, 2))\n    let link = 'https://doc.rust-lang.org/nightly/core/iter/traits/iterator/Iterator.t.html#map.v'\n    let parsed = marked(link)\n    let res = parseAnsiHighlights(parsed.split(/\\n/)[0], true)\n    expect(res.line).toEqual(link)\n    expect(res.highlights.length).toBeGreaterThan(0)\n    expect(res.highlights[0].hlGroup).toBe('CocUnderline')\n  })\n\n  it('should create highlight for code span', () => {\n    let res = parse('`let foo = \"bar\"`')\n    expect(res.highlights[0]).toEqual({\n      span: [0, 15],\n      hlGroup: 'CocMarkdownCode'\n    })\n  })\n\n  it('should create header highlights', () => {\n    let res = parse('# header')\n    expect(res.highlights[0]).toEqual({\n      span: [0, 6],\n      hlGroup: 'CocMarkdownHeader'\n    })\n    res = parse('## header')\n    expect(res.highlights[0]).toEqual({\n      span: [0, 6],\n      hlGroup: 'CocMarkdownHeader'\n    })\n    res = parse('### header')\n    expect(res.highlights[0]).toEqual({\n      span: [0, 6],\n      hlGroup: 'CocMarkdownHeader'\n    })\n  })\n\n  it('should indent blockquote', () => {\n    let res = parse('> header')\n    expect(res.line).toBe('  header')\n  })\n\n  it('should parse image', async () => {\n    let res = parse('![title](http://www.baidu.com)')\n    expect(res.line).toMatch('baidu')\n  })\n\n  it('should preserve code block', () => {\n    let text = '``` js\\nconsole.log(\"foo\")\\n```'\n    let m = marked(text)\n    expect(m.split('\\n')).toEqual([\n      '``` js',\n      'console.log(\"foo\")',\n      '```',\n      ''\n    ])\n  })\n\n  it('should renderer table', () => {\n    let text = `\n| Syntax      | Description |\n| ----------- | ----------- |\n| Header      | Title       |\n| Paragraph   | Text        |\n`\n    let res = marked(text)\n    expect(res).toContain('Syntax')\n  })\n})\n"
  },
  {
    "path": "src/__tests__/memos.json",
    "content": "{}"
  },
  {
    "path": "src/__tests__/modules/attach.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { Disposable } from 'vscode-languageserver-protocol'\nimport events from '../../events'\nimport { disposeAll } from '../../util'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\n\nbeforeAll(async () => {\n  let plugin = await helper.setup(false)\n  nvim = plugin.nvim\n  nvim.emit('notification', 'updateConfig', ['suggest.timeout', 300])\n  nvim.emit('notification', 'action_not_exists', [])\n  let spy = jest.spyOn(console, 'error').mockImplementation(() => {\n    // noop\n  })\n  await plugin.init('')\n  spy.mockRestore()\n})\n\nafterEach(() => {\n  disposeAll(disposables)\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\ndescribe('notifications', () => {\n  it('should notification before plugin ready', () => {\n    nvim.emit('notification', 'VimEnter', [''])\n    let timeout = workspace.getConfiguration('suggest').get('timeout')\n    expect(timeout).toBe(300)\n  })\n\n  it('should do Log', () => {\n    nvim.emit('notification', 'Log', [])\n    nvim.emit('notification', 'redraw', [])\n  })\n\n  it('should do notifications', async () => {\n    nvim.emit('notification', 'listNames', [])\n    let called = false\n    let spy = jest.spyOn(console, 'error').mockImplementation(() => {\n      called = true\n    })\n    nvim.emit('notification', 'name_not_exists', [])\n    nvim.emit('notification', 'MenuInput', [])\n    await helper.waitValue(() => {\n      return called\n    }, true)\n    spy.mockRestore()\n  })\n})\n\ndescribe('request', () => {\n  it('should get results', async () => {\n    let result\n    nvim.emit('request', 'listNames', [], {\n      send: res => {\n        result = res\n      }\n    })\n    await helper.waitValue(() => {\n      return Array.isArray(result)\n    }, true)\n  })\n\n  it('should return error when plugin not ready', async () => {\n    let plugin = helper.plugin\n    Object.assign(plugin, { ready: false })\n    let isErr\n    nvim.emit('request', 'listNames', [], {\n      send: (_res, isError) => {\n        isErr = isError\n      }\n    })\n    await helper.waitValue(() => {\n      return isErr\n    }, true)\n    Object.assign(plugin, { ready: true })\n  })\n\n  it('should not throw when plugin method not found', async () => {\n    let err\n    nvim.emit('request', 'NotExists', [], {\n      send: res => {\n        err = res\n      }\n    })\n    await helper.waitValue(() => {\n      return typeof err === 'string'\n    }, true)\n  })\n\n  it('should echo error instead of throw for autocmds request', async () => {\n    let disposable = events.on('CursorHold', async () => {\n      throw new Error('my error')\n    })\n    let s = jest.spyOn(events, 'fire').mockImplementation(() => {\n      return Promise.reject(new Error('my error'))\n    })\n    nvim.call('coc#rpc#request', ['CocAutocmd', ['CursorHold', 1, [1, 1]]], true)\n    let spy = jest.spyOn(nvim, 'echoError').mockImplementation(() => {\n      called = true\n    })\n    let called = false\n    await helper.waitValue(() => {\n      return called\n    }, true)\n    disposable.dispose()\n    s.mockRestore()\n    spy.mockRestore()\n  })\n})\n\ndescribe('attach', () => {\n  it('should not throw on event handler error', async () => {\n    events.on('CursorHold', () => {\n      throw new Error('error')\n    })\n    let called = false\n    nvim.emit('request', 'CocAutocmd', ['CursorHold'], {\n      send: () => {\n        called = true\n      }\n    })\n    await helper.waitValue(() => {\n      return called\n    }, true)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/chars.test.ts",
    "content": "import { CancellationTokenSource, Range } from 'vscode-languageserver-protocol'\nimport { Chars, IntegerRanges, detectLanguage, getCharCode, parseSegments, sameScope, splitKeywordOption } from '../../model/chars'\nimport { makeLine } from '../helper'\n\ndescribe('funcs', () => {\n  it('should splitKeywordsOptions', () => {\n    expect(splitKeywordOption('')).toEqual([])\n    expect(splitKeywordOption('_,-,128-140,#-43')).toEqual(['_', '-', '128-140', '#-43'])\n    expect(splitKeywordOption('^a-z,#,^')).toEqual(['^a-z', '#', '^'])\n    expect(splitKeywordOption('@,^a-z')).toEqual(['@', '^a-z'])\n    expect(splitKeywordOption('48-57,,,_')).toEqual(['48-57', ',', '_'])\n    expect(splitKeywordOption(' -~,^,,9')).toEqual([' -~', '^,', '9'])\n    expect(splitKeywordOption(' -~,^,')).toEqual([' -~', '^,'])\n  })\n\n  it('should toCharCode', () => {\n    expect(getCharCode('10')).toBe(10)\n    expect(getCharCode('')).toBeUndefined()\n    expect(getCharCode('a')).toBe(97)\n  })\n\n  it('should sameScope', () => {\n    expect(sameScope(1, 3)).toBe(true)\n    expect(sameScope(266, 1024)).toBe(true)\n    expect(sameScope(97, 19970)).toBe(false)\n  })\n\n  it('should use Segmenter', () => {\n    let res = Array.from(parseSegments('你好世界', 'cn'))\n    expect(Array.isArray(res)).toBe(true)\n    let fn = Intl['Segmenter']\n    if (typeof fn === 'function') {\n      Object.defineProperty(Intl, 'Segmenter', {\n        get: () => {\n          return undefined\n        }\n      })\n      res = Array.from(parseSegments('你好世界', 'cn'))\n      Object.defineProperty(Intl, 'Segmenter', {\n        get: () => {\n          return fn\n        }\n      })\n      expect(res).toEqual(['你好世界'])\n      res = Array.from(parseSegments('你好世界', ''))\n      expect(res).toBeDefined()\n    }\n  })\n\n  it('should delete language', () => {\n    expect(detectLanguage('你'.charCodeAt(0))).toBe('cn')\n    expect(detectLanguage('れ'.charCodeAt(0))).toBe('ja')\n    expect(detectLanguage('것'.charCodeAt(0))).toBe('ko')\n    expect(detectLanguage(0xFFFF)).toBe('')\n  })\n})\n\ndescribe('IntegerRanges', () => {\n  it('should add ranges', () => {\n    let r = new IntegerRanges()\n    expect(r.flatten()).toEqual([])\n    r.add(4, 3)\n    r.add(1)\n    r.add(2)\n    expect(r.flatten()).toEqual([1, 1, 2, 2, 3, 4])\n    r.add(2, 7)\n    expect(r.flatten()).toEqual([1, 1, 2, 7])\n    r.add(7, 9)\n    expect(r.flatten()).toEqual([1, 1, 2, 9])\n    r.add(2, 5)\n    expect(r.flatten()).toEqual([1, 1, 2, 9])\n  })\n\n  it('should exclude ranges', () => {\n    let r = new IntegerRanges()\n    r.add(1, 2)\n    r.add(4, 6)\n    r.exclude(3, 3)\n    r.exclude(8)\n    r.exclude(9, 10)\n    expect(r.flatten()).toEqual([1, 2, 4, 6])\n    r.exclude(4, 6)\n    r.exclude(1, 2)\n    expect(r.flatten()).toEqual([])\n    r.add(3, 8)\n    r.exclude(1, 3)\n    r.exclude(8, 9)\n    expect(r.flatten()).toEqual([4, 7])\n    r.exclude(6, 5)\n    expect(r.flatten()).toEqual([4, 4, 7, 7])\n    expect(r.includes(4)).toBe(true)\n    expect(r.includes(7)).toBe(true)\n  })\n\n  it('should check word code', () => {\n    let r = new IntegerRanges([], true)\n    expect(r.includes(258)).toBe(true)\n    expect(r.includes(894)).toBe(false)\n    expect(r.includes(33)).toBe(false)\n  })\n\n  it('should fromKeywordOption', () => {\n    let r = IntegerRanges.fromKeywordOption('@,_')\n    expect(r.includes(97)).toBe(true)\n    expect(r.includes('_'.charCodeAt(0))).toBe(true)\n    r = IntegerRanges.fromKeywordOption('@-@,9,^')\n    expect(r.includes(9)).toBe(true)\n    expect(r.includes('@'.charCodeAt(0))).toBe(true)\n    expect(r.includes('^'.charCodeAt(0))).toBe(true)\n    r = IntegerRanges.fromKeywordOption('@,^a-z')\n    expect(r.includes(97)).toBe(false)\n    r = IntegerRanges.fromKeywordOption('48-57,,,_')\n    expect(r.includes(48)).toBe(true)\n    expect(r.includes(','.charCodeAt(0))).toBe(true)\n    expect(r.includes('_'.charCodeAt(0))).toBe(true)\n    r = IntegerRanges.fromKeywordOption('_,-,128-140,#-43')\n    expect(r.includes(130)).toBe(true)\n    expect(r.includes(43)).toBe(true)\n    expect(r.includes('_'.charCodeAt(0))).toBe(true)\n    expect(r.includes('-'.charCodeAt(0))).toBe(true)\n    expect(r.includes('#'.charCodeAt(0))).toBe(true)\n    r = IntegerRanges.fromKeywordOption(' -~,^,,9')\n    expect(r.includes(' '.charCodeAt(0))).toBe(true)\n    expect(r.includes(','.charCodeAt(0))).toBe(false)\n    expect(r.includes(9)).toBe(true)\n    r = IntegerRanges.fromKeywordOption('65,-x,x-')\n    expect(r.includes(65)).toBe(true)\n    r = IntegerRanges.fromKeywordOption('128-140,-')\n    expect(r.includes('-'.charCodeAt(0))).toBe(true)\n  })\n})\n\ndescribe('chars', () => {\n  describe('isKeywordChar()', () => {\n    it('should match @', () => {\n      let chars = new Chars('@')\n      expect(chars.isKeywordChar('a')).toBe(true)\n      expect(chars.isKeywordChar('z')).toBe(true)\n      expect(chars.isKeywordChar('A')).toBe(true)\n      expect(chars.isKeywordChar('Z')).toBe(true)\n      expect(chars.isKeywordChar('\\u205f')).toBe(false)\n    })\n\n    it('should iterateWords', async () => {\n      let chars = new Chars('@')\n      let res = Array.from(chars.iterateWords(' 你好foo bar'))\n      expect(res).toEqual([[1, 3], [3, 6], [7, 10]])\n    })\n\n    it('should match code range', () => {\n      let chars = new Chars('48-57')\n      expect(chars.isKeywordChar('0')).toBe(true)\n      expect(chars.isKeywordChar('9')).toBe(true)\n    })\n\n    it('should match @-@', () => {\n      let chars = new Chars('@-@')\n      expect(chars.isKeywordChar('@')).toBe(true)\n    })\n\n    it('should match single code', () => {\n      let chars = new Chars('58')\n      expect(chars.isKeywordChar(':')).toBe(true)\n    })\n\n    it('should match single character', () => {\n      let chars = new Chars('_')\n      expect(chars.isKeywordChar('_')).toBe(true)\n    })\n  })\n\n  describe('addKeyword()', () => {\n    it('should add keyword', () => {\n      let chars = new Chars('_')\n      chars.addKeyword(':')\n      expect(chars.isKeywordChar(':')).toBe(true)\n      chars.addKeyword(':')\n      expect(chars.isKeywordChar(':')).toBe(true)\n    })\n  })\n\n  describe('computeWordRanges()', () => {\n    it('should computeWordRanges', async () => {\n      let chars = new Chars('@')\n      let res = await chars.computeWordRanges(['abc def hijkl'], Range.create(0, 4, 0, 7))\n      expect(res).toEqual({\n        def: [\n          {\n            start: {\n              line: 0,\n              character: 4\n            },\n            end: {\n              line: 0,\n              character: 7\n            }\n          }\n        ]\n      })\n      res = await chars.computeWordRanges(['abc def ', 'foo def', ' ', ' abc'], Range.create(0, 3, 4, 0))\n      expect(Object.keys(res)).toEqual(['def', 'foo', 'abc'])\n      const r = (sl, sc, el, ec) => {\n        return Range.create(sl, sc, el, ec)\n      }\n      expect(res['def']).toEqual([r(0, 4, 0, 7), r(1, 4, 1, 7)])\n      expect(res['foo']).toEqual([r(1, 0, 1, 3)])\n      expect(res['abc']).toEqual([r(3, 1, 3, 4)])\n    })\n\n    it('should wait after timeout', async () => {\n      let l = makeLine(200)\n      let arr: string[] = []\n      for (let i = 0; i < 8000; i++) {\n        arr.push(l)\n      }\n      let chars = new Chars('@')\n      let tokenSource = new CancellationTokenSource()\n      let timer = setTimeout(() => {\n        tokenSource.cancel()\n      }, 30)\n      await chars.computeWordRanges(arr, Range.create(0, 0, 8000, 0), tokenSource.token)\n      clearTimeout(timer)\n      expect(tokenSource.token.isCancellationRequested).toBe(true)\n    })\n  })\n\n  describe('matchLine()', () => {\n    it('should matchLine', async () => {\n      let text = 'a'.repeat(2048)\n      let chars = new Chars('@')\n      expect(chars.matchLine(text, 'cn', 3, 128)).toEqual(['a'.repeat(128)])\n      expect(chars.matchLine('a b c')).toEqual([])\n      expect(chars.matchLine('foo bar')).toEqual(['foo', 'bar'])\n      expect(chars.matchLine('?foo bar')).toEqual(['foo', 'bar'])\n      expect(chars.matchLine('?foo $')).toEqual(['foo'])\n      expect(chars.matchLine('?foo foo foo')).toEqual(['foo'])\n      expect(chars.matchLine(' 你好foo')).toEqual(['你好', 'foo'])\n      expect(chars.matchLine('bar你好', 'cn')).toEqual(['bar', '你好'])\n      expect(chars.matchLine('foo😍bar foo，bar')).toEqual(['foo', 'bar'])\n      expect(chars.matchLine('你好世界', '')).toBeDefined()\n    })\n  })\n\n  describe('iskeyword()', () => {\n    it('should check isKeyword', () => {\n      let chars = new Chars('@')\n      expect(chars.isKeyword('foo')).toBe(true)\n      expect(chars.isKeyword('f@')).toBe(false)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/cursors.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { Position, Range, TextEdit } from 'vscode-languageserver-types'\nimport commands from '../../commands'\nimport Cursors from '../../cursors'\nimport CursorsSession, { surroundChanges } from '../../cursors/session'\nimport TextRange from '../../cursors/textRange'\nimport { getChange, getDelta, getVisualRanges, isSurroundChange, isTextChange, splitRange, SurroundChange, TextChange } from '../../cursors/util'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet cursors: Cursors\nlet ns: number\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  ns = await nvim.createNamespace('coc-cursors')\n  cursors = window.cursors\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  nvim.pauseNotification()\n  cursors.reset()\n  await nvim.resumeNotification()\n  await helper.reset()\n})\n\nasync function rangeCount(): Promise<number> {\n  let buf = await nvim.buffer\n  let markers = await buf.getExtMarks(ns, 0, -1)\n  return markers.length\n}\n\ndescribe('cursors utils', () => {\n  describe('getDelta()', () => {\n    it('should get delta count', async () => {\n      expect(getDelta({ prepend: [1, 'foo'], append: [1, 'bar'], remove: false })).toBe(4)\n      expect(getDelta({ offset: 0, remove: 2, insert: 'foo' })).toBe(1)\n    })\n  })\n\n  describe('surroundChanges()', () => {\n    it('should check surround changes', async () => {\n      expect(surroundChanges([], 0)).toBe(false)\n      expect(surroundChanges([{ offset: 1, add: 'f' }, { offset: 3, add: 'f' }], 0)).toBe(false)\n    })\n\n    it('should get surround change', async () => {\n      const getText = (newText: string): string => {\n        let r = new TextRange(0, 0, 'foo')\n        let res = getChange(r, Range.create(0, 0, 0, 3), newText) as SurroundChange\n        expect(isSurroundChange(res)).toBe(true)\n        r.applySurroundChange(res)\n        return r.text\n      }\n      expect(getText('\"foo\"')).toBe('\"foo\"')\n      expect(getText('o')).toBe('o')\n      expect(getText('')).toBe('')\n    })\n  })\n\n  describe('getChange()', () => {\n    it('should get end change', async () => {\n      const getText = (character: number, newText: string) => {\n        let start = Position.create(0, character)\n        let r = new TextRange(0, 0, 'foo')\n        let res = getChange(r, Range.create(start, r.range.end), newText) as TextChange\n        expect(isTextChange(res)).toBe(true)\n        r.applyTextChange(res)\n        return r.text\n      }\n      expect(getText(3, 'bar')).toBe('foobar')\n      expect(getText(1, '')).toBe('f')\n      expect(getText(2, 'ba')).toBe('foba')\n    })\n\n    it('should get normal change', async () => {\n      const getText = (start: number, end: number, newText: string) => {\n        let r = new TextRange(0, 0, 'foo')\n        let res = getChange(r, Range.create(0, start, 0, end), newText) as TextChange\n        expect(isTextChange(res)).toBe(true)\n        r.applyTextChange(res)\n        return r.text\n      }\n      expect(getText(0, 0, 'a')).toBe('afoo')\n      expect(getText(0, 1, '')).toBe('oo')\n      expect(getText(0, 2, 'ba')).toBe('bao')\n    })\n\n    it('should split ranges', async () => {\n      let doc = await workspace.document\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo\\nbar\\n\\nend')])\n      let ranges = splitRange(doc, Range.create(0, 3, 3, 0))\n      expect(ranges).toEqual([Range.create(1, 0, 1, 3)])\n    })\n\n    it('should get visual ranges', async () => {\n      let doc = await workspace.document\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo\\nbar\\nend')])\n      let ranges = getVisualRanges(doc, Range.create(0, 3, 3, 0))\n      expect(ranges.length).toBe(4)\n    })\n  })\n})\n\ndescribe('cursors', () => {\n  describe('cancel()', () => {\n    it('should cancel cursors session', async () => {\n      cursors.cancel(999)\n      let doc = await workspace.document\n      cursors.cancel(doc.bufnr)\n      await nvim.call('setline', [1, ['a', 'b']])\n      await nvim.call('cursor', [1, 1])\n      await doc.synchronize()\n      await cursors.select(doc.bufnr, 'position', 'n')\n      let activated = await cursors.isActivated()\n      expect(activated).toBe(true)\n      cursors.cancel(doc.bufnr)\n      activated = await cursors.isActivated()\n      expect(activated).toBe(false)\n    })\n\n    it('should cancel when no have ranges', async () => {\n      let doc = await workspace.document\n      let session = cursors.createSession(doc)\n      session.checkRanges()\n      let activated = await cursors.isActivated()\n      expect(activated).toBe(false)\n      session.cancel()\n      session.dispose()\n    })\n  })\n\n  describe('select()', () => {\n    it('should throw with unsupported kind', async () => {\n      let doc = await workspace.document\n      let fn = async () => {\n        await cursors.select(doc.bufnr, 'undefined', 'n')\n      }\n      await expect(fn()).rejects.toThrow(/not supported/)\n    })\n\n    it('should select by position', async () => {\n      let doc = await workspace.document\n      await nvim.call('setline', [1, ['a', 'b']])\n      await nvim.call('cursor', [1, 1])\n      await doc.synchronize()\n      await cursors.select(doc.bufnr, 'position', 'n')\n      let n = await rangeCount()\n      expect(n).toBe(1)\n      await nvim.setOption('virtualedit', 'onemore')\n      await nvim.call('cursor', [2, 2])\n      await cursors.select(doc.bufnr, 'position', 'n')\n      n = await rangeCount()\n      expect(n).toBe(2)\n      await cursors.select(doc.bufnr, 'position', 'n')\n      n = await rangeCount()\n      expect(n).toBe(1)\n    })\n\n    it('should select by word', async () => {\n      let doc = await workspace.document\n      await nvim.call('setline', [1, ['foo', 'bar']])\n      await nvim.call('cursor', [1, 1])\n      await doc.synchronize()\n      await cursors.select(doc.bufnr, 'word', 'n')\n      let n = await rangeCount()\n      expect(n).toBe(1)\n      await nvim.call('cursor', [2, 2])\n      await cursors.select(doc.bufnr, 'word', 'n')\n      n = await rangeCount()\n      expect(n).toBe(2)\n      await cursors.select(doc.bufnr, 'word', 'n')\n      n = await rangeCount()\n      expect(n).toBe(1)\n    })\n\n    it('should toggle select', async () => {\n      let doc = await workspace.document\n      await nvim.call('setline', [1, ['foo', 'bar']])\n      await nvim.call('cursor', [1, 1])\n      await doc.synchronize()\n      await cursors.select(doc.bufnr, 'word', 'n')\n      let n = await rangeCount()\n      expect(n).toBe(1)\n      await cursors.select(doc.bufnr, 'word', 'n')\n      n = await rangeCount()\n      expect(n).toBe(0)\n      let activated = await doc.buffer.getVar('coc_cursors_activated')\n      expect(activated).toBe(0)\n    })\n\n    it('should select last character', async () => {\n      let doc = await workspace.document\n      await nvim.setOption('virtualedit', 'onemore')\n      await nvim.call('setline', [1, ['}', '{']])\n      await nvim.call('cursor', [1, 2])\n      await doc.synchronize()\n      await cursors.select(doc.bufnr, 'word', 'n')\n      let n = await rangeCount()\n      expect(n).toBe(1)\n      await nvim.call('cursor', [2, 1])\n      await doc.synchronize()\n      await cursors.select(doc.bufnr, 'word', 'n')\n      n = await rangeCount()\n      expect(n).toBe(2)\n    })\n\n    it('should select by visual range', async () => {\n      let doc = await workspace.document\n      await cursors.select(doc.bufnr, 'range', 'v')\n      let activated = await cursors.isActivated()\n      expect(activated).toBe(false)\n      await nvim.call('setline', [1, ['\"foo\"', '\"bar\"']])\n      await nvim.call('cursor', [1, 1])\n      await nvim.command('normal! vE')\n      await doc.synchronize()\n      await cursors.select(doc.bufnr, 'range', 'v')\n      let n = await rangeCount()\n      expect(n).toBe(1)\n      await nvim.call('cursor', [2, 1])\n      await nvim.command('normal! vE')\n      await cursors.select(doc.bufnr, 'range', 'v')\n      n = await rangeCount()\n      expect(n).toBe(2)\n      await cursors.select(doc.bufnr, 'range', 'v')\n      n = await rangeCount()\n      expect(n).toBe(1)\n    })\n\n    it('should select visual blocks', async () => {\n      let doc = await workspace.document\n      await nvim.call('setline', [1, ['let x = \"foo\"', 'let y = \"bar\"']])\n      await doc.synchronize()\n      await nvim.call('cursor', [1, 1])\n      await nvim.input('<C-v>')\n      await nvim.input('je')\n      await helper.wait(30)\n      await cursors.select(doc.bufnr, 'range', '\\x16')\n      let n = await rangeCount()\n      expect(n).toBe(2)\n    })\n\n    it('should select by operator char type', async () => {\n      await nvim.command('nmap x  <Plug>(coc-cursors-operator)')\n      let bufnr = await nvim.call('bufnr', ['%']) as number\n      await nvim.call('setline', [1, ['\"short\"', '\"long\"']])\n      await nvim.call('cursor', [1, 2])\n      await nvim.input('xi\"')\n      await helper.waitValue(() => {\n        let s = cursors.getSession(bufnr)\n        return s ? s.currentRanges.length : 0\n      }, 1)\n    })\n\n    it('should select by operator line type', async () => {\n      await nvim.command('nmap x  <Plug>(coc-cursors-operator)')\n      let bufnr = await nvim.call('bufnr', ['%']) as number\n      await nvim.call('setline', [1, ['\"short\"', '\"long\"']])\n      await nvim.call('cursor', [1, 2])\n      await nvim.input('xap')\n      await helper.waitValue(() => {\n        let s = cursors.getSession(bufnr)\n        return s ? s.currentRanges.length : 0\n      }, 2)\n    })\n  })\n\n  describe('addRanges()', () => {\n    it('should add ranges', async () => {\n      let doc = await workspace.document\n      await nvim.call('setline', [1, ['foo foo foo', 'bar bar']])\n      await doc.synchronize()\n      let ranges = [\n        Range.create(0, 0, 0, 3),\n        Range.create(0, 4, 0, 7),\n        Range.create(0, 8, 0, 11),\n        Range.create(1, 0, 1, 3),\n        Range.create(1, 4, 1, 7)\n      ]\n      await commands.executeCommand('editor.action.addRanges', ranges)\n      let n = await rangeCount()\n      expect(n).toBe(5)\n    })\n  })\n\n  describe('validChange()', () => {\n    it('should check valid change', async () => {\n      let doc = await workspace.document\n      await nvim.call('setline', [1, ['foo', 'foo', '']])\n      await doc.synchronize()\n      let ranges = [\n        Range.create(0, 0, 0, 3),\n        Range.create(1, 0, 1, 3),\n      ]\n      await helper.doAction('addRanges', ranges)\n      let session = cursors.getSession(doc.bufnr)\n      expect(session.validChange(Range.create(0, 0, 1, 0), '')).toBe(false)\n      expect(session.validChange(Range.create(0, 0, 2, 0), '\\n\\n')).toBe(false)\n      expect(session.validChange(Range.create(1, 0, 1, 3), 'bar')).toBe(false)\n    })\n  })\n\n  describe('onChange()', () => {\n    let session: CursorsSession\n\n    function edit(sl: number, sc: number, el: number, ec: number, text: string): TextEdit {\n      let r = Range.create(sl, sc, el, ec)\n      return TextEdit.replace(r, text)\n    }\n\n    async function assertEdits(edits: TextEdit[], characters: number[], line?: string) {\n      let doc = await workspace.document\n      await nvim.call('setline', [1, ['foo foo foo', '']])\n      await doc.synchronize()\n      let ranges = [\n        Range.create(0, 0, 0, 3),\n        Range.create(0, 4, 0, 7),\n        Range.create(0, 8, 0, 11),\n      ]\n      await cursors.addRanges(ranges)\n      session = cursors.getSession(doc.bufnr)\n      let p = new Promise(resolve => {\n        let disposable = session.onDidUpdate(() => {\n          disposable.dispose()\n          resolve(undefined)\n        })\n        void doc.applyEdits(edits)\n      })\n      await p\n      if (line != null) {\n        expect(doc.getline(0)).toBe(line)\n      }\n      let arr: number[] = []\n      session.currentRanges.forEach(r => {\n        arr.push(r.start.character, r.end.character)\n      })\n      expect(arr).toEqual(characters)\n      session.cancel()\n    }\n\n    it('should adjust on text insert', async () => {\n      await assertEdits([edit(0, 0, 0, 0, 'bar\\n')], [0, 3, 4, 7, 8, 11])\n      await assertEdits([edit(0, 0, 0, 0, 'b')], [0, 4, 5, 9, 10, 14], 'bfoo bfoo bfoo')\n      await assertEdits([edit(0, 1, 0, 1, 'b')], [0, 4, 5, 9, 10, 14], 'fboo fboo fboo')\n      await assertEdits([edit(0, 3, 0, 3, 'b')], [0, 4, 5, 9, 10, 14], 'foob foob foob')\n      await assertEdits([edit(0, 3, 0, 4, '\\n')], [0, 3, 0, 3, 4, 7], 'foo')\n      await assertEdits([edit(1, 0, 1, 0, 'bar')], [0, 3, 4, 7, 8, 11])\n      await nvim.call('setline', [1, ['foo foo foo', '']])\n      await nvim.call('cursor', [1, 4])\n      await assertEdits([edit(0, 8, 0, 8, 'b')], [0, 4, 5, 9, 10, 14], 'bfoo bfoo bfoo')\n      let col = await nvim.call('col', ['.'])\n      expect(col).toBe(5)\n    })\n\n    it('should adjust on text delete', async () => {\n      await assertEdits([edit(0, 2, 0, 3, '')], [0, 2, 3, 5, 6, 8], 'fo fo fo')\n      await assertEdits([edit(0, 3, 0, 4, '')], [0, 3, 3, 6, 7, 10], 'foofoo foo')\n      await assertEdits([edit(0, 4, 0, 7, '')], [0, 0, 1, 1, 2, 2], '  ')\n      await nvim.setLine('foo foo')\n      await nvim.call('cursor', [1, 4])\n      await assertEdits([edit(0, 3, 0, 7, '')], [0, 3, 4, 7], 'foo foo')\n      await assertEdits([edit(0, 1, 0, 11, '')], [], 'f')\n    })\n\n    it('should adjust on text change', async () => {\n      await assertEdits([edit(0, 0, 0, 0, '\"'), edit(0, 3, 0, 3, '\"')], [0, 5, 6, 11, 12, 17], '\"foo\" \"foo\" \"foo\"')\n      await assertEdits([edit(0, 0, 0, 1, 'b')], [0, 3, 4, 7, 8, 11], 'boo boo boo')\n      await assertEdits([edit(0, 0, 0, 3, 'ba')], [0, 2, 3, 5, 6, 8], 'ba ba ba')\n      await nvim.call('setline', [1, ['', '']])\n      await nvim.call('cursor', [2, 1])\n      await assertEdits([edit(0, 4, 0, 5, 'ba')], [0, 4, 5, 9, 10, 14], 'baoo baoo baoo')\n      let col = await nvim.call('col', ['.'])\n      expect(col).toBe(1)\n    })\n\n    it('should adjust on range remove', async () => {\n      let doc = await workspace.document\n      await nvim.call('setline', [1, ['foo', 'foobar']])\n      await doc.synchronize()\n      let ranges = [Range.create(0, 0, 0, 3), Range.create(1, 0, 1, 6)]\n      await cursors.addRanges(ranges)\n      session = cursors.getSession(doc.bufnr)\n      await doc.applyEdits([TextEdit.del(Range.create(0, 0, 0, 3))])\n      await doc.synchronize()\n      let lines = await doc.buffer.lines\n      expect(lines).toEqual(['', ''])\n      session.cancel()\n    })\n\n    it('should adjust on undo & redo', async () => {\n      let doc = await workspace.document\n      let edits = [edit(0, 0, 0, 0, '\"'), edit(0, 3, 0, 3, '\"')]\n      await nvim.call('setline', [1, ['foo foo foo', '']])\n      await doc.synchronize()\n      let ranges = [\n        Range.create(0, 0, 0, 3),\n        Range.create(0, 4, 0, 7),\n        Range.create(0, 8, 0, 11),\n      ]\n      await cursors.addRanges(ranges)\n      session = cursors.getSession(doc.bufnr)\n      let p = new Promise(resolve => {\n        let disposable = session.onDidUpdate(() => {\n          disposable.dispose()\n          resolve(undefined)\n        })\n        void doc.applyEdits(edits)\n      })\n      await p\n      await nvim.command('undo')\n      await helper.waitValue(() => {\n        return nvim.getLine()\n      }, 'foo foo foo')\n      expect(session.currentRanges).toEqual(ranges)\n    })\n\n    it('should highlight on empty content change', async () => {\n      let doc = await workspace.document\n      await nvim.call('setline', [1, ['foo', '']])\n      await doc.synchronize()\n      let ranges = [Range.create(0, 0, 0, 3)]\n      await cursors.addRanges(ranges)\n      session = cursors.getSession(doc.bufnr)\n      await nvim.call('setline', [1, ['foo', '']])\n      await doc.synchronize()\n      let c = await rangeCount()\n      expect(c).toBe(1)\n    })\n\n    it('should cancel when insert line break', async () => {\n      let doc = await workspace.document\n      await nvim.call('setline', [1, ['foo', '']])\n      await doc.synchronize()\n      let ranges = [Range.create(0, 0, 0, 3)]\n      await cursors.addRanges(ranges)\n      session = cursors.getSession(doc.bufnr)\n      await nvim.call('cursor', [1, 2])\n      await nvim.input('i<cr>')\n      await doc.synchronize()\n      let activated = await cursors.isActivated()\n      expect(activated).toBe(false)\n    })\n  })\n\n  describe('applyComposedEdit()', () => {\n    async function setup(): Promise<CursorsSession> {\n      let doc = await workspace.document\n      await nvim.call('setline', [1, ['bar foo foo', 'foo']])\n      await doc.synchronize()\n      let session = cursors.createSession(doc)\n      session.addRanges([\n        Range.create(0, 4, 0, 7),\n        Range.create(0, 8, 0, 11),\n        Range.create(1, 0, 1, 3),\n      ])\n      return session\n    }\n\n    it('should check change before first range', async () => {\n      let s = await setup()\n      let doc = await workspace.document\n      let res = s.applyComposedEdit(doc.textDocument.lines.slice(), ['abc foob foob', 'foob'])\n      expect(res).toBe(false)\n    })\n\n    it('should check change of first range', async () => {\n      let s = await setup()\n      let doc = await workspace.document\n      let res = s.applyComposedEdit(doc.textDocument.lines.slice(), ['bar foo foob', 'foob'])\n      expect(res).toBe(false)\n    })\n\n    it('should check delete exceed range', async () => {\n      let s = await setup()\n      let doc = await workspace.document\n      let res = s.applyComposedEdit(doc.textDocument.lines.slice(), ['bar fofoo', 'foo'])\n      expect(res).toBe(false)\n    })\n\n    it('should check content prepend', async () => {\n      let s = await setup()\n      let doc = await workspace.document\n      let res = s.applyComposedEdit(doc.textDocument.lines.slice(), ['bar bfoo bfoo', 'bfoo'])\n      expect(res).toBe(true)\n      expect(s.currentRanges).toEqual([\n        Range.create(0, 4, 0, 8),\n        Range.create(0, 9, 0, 13),\n        Range.create(1, 0, 1, 4),\n      ])\n      s = await setup()\n      doc = await workspace.document\n      res = s.applyComposedEdit(doc.textDocument.lines.slice(), ['bar bfoo bfoo', 'xfoo'])\n      expect(res).toBe(false)\n    })\n\n    it('should check content insert', async () => {\n      let s = await setup()\n      let doc = await workspace.document\n      let res = s.applyComposedEdit(doc.textDocument.lines.slice(), ['bar fboo fboo', 'fboo'])\n      expect(res).toBe(true)\n      expect(s.currentRanges).toEqual([\n        Range.create(0, 4, 0, 8),\n        Range.create(0, 9, 0, 13),\n        Range.create(1, 0, 1, 4),\n      ])\n    })\n\n    it('should check content append', async () => {\n      let s = await setup()\n      let doc = await workspace.document\n      let res = s.applyComposedEdit(doc.textDocument.lines.slice(), ['bar foob foob', 'foob'])\n      expect(res).toBe(true)\n      expect(s.currentRanges).toEqual([\n        Range.create(0, 4, 0, 8),\n        Range.create(0, 9, 0, 13),\n        Range.create(1, 0, 1, 4),\n      ])\n    })\n\n    it('should check content delete #1', async () => {\n      let s = await setup()\n      let doc = await workspace.document\n      let res = s.applyComposedEdit(doc.textDocument.lines.slice(), ['bar oo oo', 'oo'])\n      expect(res).toBe(true)\n      expect(s.currentRanges).toEqual([\n        Range.create(0, 4, 0, 6),\n        Range.create(0, 7, 0, 9),\n        Range.create(1, 0, 1, 2),\n      ])\n    })\n\n    it('should check content delete #2', async () => {\n      let s = await setup()\n      let doc = await workspace.document\n      let res = s.applyComposedEdit(doc.textDocument.lines.slice(), ['bar  ', ''])\n      expect(res).toBe(true)\n      expect(s.currentRanges).toEqual([\n        Range.create(0, 4, 0, 4),\n        Range.create(0, 5, 0, 5),\n        Range.create(1, 0, 1, 0),\n      ])\n    })\n\n    it('should check content delete #3', async () => {\n      let s = await setup()\n      let doc = await workspace.document\n      let res = s.applyComposedEdit(doc.textDocument.lines.slice(), ['bar fo fo', 'fo'])\n      expect(res).toBe(true)\n      expect(s.currentRanges).toEqual([\n        Range.create(0, 4, 0, 6),\n        Range.create(0, 7, 0, 9),\n        Range.create(1, 0, 1, 2),\n      ])\n    })\n\n    it('should check content change #1', async () => {\n      let s = await setup()\n      let doc = await workspace.document\n      let res = s.applyComposedEdit(doc.textDocument.lines.slice(), ['bar fa fa', 'fa'])\n      expect(res).toBe(true)\n      expect(s.currentRanges).toEqual([\n        Range.create(0, 4, 0, 6),\n        Range.create(0, 7, 0, 9),\n        Range.create(1, 0, 1, 2),\n      ])\n    })\n\n    it('should check content change #1', async () => {\n      let s = await setup()\n      let doc = await workspace.document\n      let res = s.applyComposedEdit(doc.textDocument.lines.slice(), ['bar fa fa', 'fa'])\n      expect(res).toBe(true)\n      expect(s.currentRanges).toEqual([\n        Range.create(0, 4, 0, 6),\n        Range.create(0, 7, 0, 9),\n        Range.create(1, 0, 1, 2),\n      ])\n    })\n\n    it('should check content change #2', async () => {\n      let s = await setup()\n      let doc = await workspace.document\n      let res = s.applyComposedEdit(doc.textDocument.lines.slice(), ['bar ab ab', 'ab'])\n      expect(res).toBe(true)\n      expect(s.currentRanges).toEqual([\n        Range.create(0, 4, 0, 6),\n        Range.create(0, 7, 0, 9),\n        Range.create(1, 0, 1, 2),\n      ])\n    })\n\n    it('should check content change #3', async () => {\n      let s = await setup()\n      let doc = await workspace.document\n      let res = s.applyComposedEdit(doc.textDocument.lines.slice(), ['bar xfa xfa', 'xfa'])\n      expect(res).toBe(true)\n      expect(s.currentRanges).toEqual([\n        Range.create(0, 4, 0, 7),\n        Range.create(0, 8, 0, 11),\n        Range.create(1, 0, 1, 3),\n      ])\n    })\n\n    it('should check content change #4', async () => {\n      let s = await setup()\n      let doc = await workspace.document\n      let res = s.applyComposedEdit(doc.textDocument.lines.slice(), ['bar xfao xfao', 'xfao'])\n      expect(res).toBe(true)\n      expect(s.currentRanges).toEqual([\n        Range.create(0, 4, 0, 8),\n        Range.create(0, 9, 0, 13),\n        Range.create(1, 0, 1, 4),\n      ])\n    })\n\n    it('should check surround add', async () => {\n      let s = await setup()\n      let doc = await workspace.document\n      let res = s.applyComposedEdit(doc.textDocument.lines.slice(), ['bar \"foo\" \"foo\"', '\"foo\"'])\n      expect(res).toBe(true)\n      expect(s.currentRanges).toEqual([\n        Range.create(0, 4, 0, 9),\n        Range.create(0, 10, 0, 15),\n        Range.create(1, 0, 1, 5),\n      ])\n    })\n\n    it('should check surround remove', async () => {\n      let doc = await workspace.document\n      await nvim.call('setline', [1, ['bar \"foo\" \"foo\"', '\"foo\"']])\n      await doc.synchronize()\n      let s = cursors.createSession(doc)\n      s.addRanges([\n        Range.create(0, 4, 0, 9),\n        Range.create(0, 10, 0, 15),\n        Range.create(1, 0, 1, 5),\n      ])\n      let res = s.applyComposedEdit(doc.textDocument.lines.slice(), ['bar foo foo', 'foo'])\n      expect(res).toBe(true)\n      expect(s.currentRanges).toEqual([\n        Range.create(0, 4, 0, 7),\n        Range.create(0, 8, 0, 11),\n        Range.create(1, 0, 1, 3),\n      ])\n    })\n\n    it('should check surround change', async () => {\n      let doc = await workspace.document\n      await nvim.call('setline', [1, ['bar \"foo\" \"foo\"', '\"foo\"']])\n      await doc.synchronize()\n      let s = cursors.createSession(doc)\n      s.addRanges([\n        Range.create(0, 4, 0, 9),\n        Range.create(0, 10, 0, 15),\n        Range.create(1, 0, 1, 5),\n      ])\n      let res = s.applyComposedEdit(doc.textDocument.lines.slice(), [`bar 'foo' 'foo'`, `'foo'`])\n      expect(res).toBe(true)\n      expect(s.currentRanges).toEqual([\n        Range.create(0, 4, 0, 9),\n        Range.create(0, 10, 0, 15),\n        Range.create(1, 0, 1, 5),\n      ])\n    })\n  })\n\n  describe('key mappings', () => {\n    async function setup(): Promise<void> {\n      let doc = await workspace.document\n      await nvim.call('setline', [1, ['a', 'b', 'c']])\n      await doc.synchronize()\n      let session = cursors.createSession(doc)\n      session.addRanges([\n        Range.create(0, 0, 0, 1),\n        Range.create(1, 0, 1, 1),\n        Range.create(2, 0, 2, 1),\n      ])\n    }\n\n    async function hasKeymap(key): Promise<boolean> {\n      let buf = await nvim.buffer\n      let keymaps = await buf.getKeymap('n') as any\n      return keymaps.find(o => o.lhs == key) != null\n    }\n\n    it('should setup cancel keymap', async () => {\n      await setup()\n      let count = await rangeCount()\n      expect(count).toBe(3)\n      await nvim.input('<esc>')\n      await helper.wait(50)\n      count = await rangeCount()\n      expect(count).toBe(0)\n      let has = await hasKeymap('<Esc>')\n      expect(has).toBe(false)\n    })\n\n    it('should next key wrapscan', async () => {\n      await setup()\n      await nvim.call('cursor', [1, 1])\n      const next = async (line: number, character: number) => {\n        await nvim.input('<C-n>')\n        await helper.waitValue(async () => {\n          return await nvim.call('coc#cursor#position')\n        }, [line, character])\n      }\n      await next(1, 0)\n      await next(2, 0)\n      await next(0, 0)\n    })\n\n    it('should previous key wrapscan', async () => {\n      await setup()\n      await nvim.call('cursor', [3, 1])\n      const prev = async (line: number, character: number) => {\n        await nvim.input('<C-p>')\n        await helper.waitValue(async () => {\n          return await nvim.call('coc#cursor#position')\n        }, [line, character])\n      }\n      await prev(1, 0)\n      await prev(0, 0)\n      await prev(2, 0)\n    })\n\n    it('should next key no wrapscan', async () => {\n      helper.updateConfiguration('cursors.wrapscan', false)\n      await setup()\n      await nvim.call('cursor', [3, 1])\n      const next = async (line: number, character: number) => {\n        await nvim.input('<C-n>')\n        await helper.wait(50)\n        let cursor = await nvim.call('coc#cursor#position')\n        expect(cursor).toEqual([line, character])\n      }\n      await next(2, 0)\n    })\n\n    it('should previous key no wrapscan', async () => {\n      helper.updateConfiguration('cursors.wrapscan', false)\n      await setup()\n      await nvim.call('cursor', [1, 1])\n      const prev = async (line: number, character: number) => {\n        await nvim.input('<C-p>')\n        await helper.wait(30)\n        let cursor = await nvim.call('coc#cursor#position')\n        expect(cursor).toEqual([line, character])\n      }\n      await prev(0, 0)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/db.test.ts",
    "content": "import fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport DB from '../../model/db'\nimport Mru from '../../model/mru'\nconst root = fs.mkdtempSync(path.join(os.tmpdir(), 'coc-mru-'))\n\nlet db: DB\nbeforeAll(async () => {\n  db = new DB(path.join(root, 'db.json'))\n})\n\nafterAll(async () => {\n  db.destroy()\n})\n\nafterEach(async () => {\n  db.clear()\n})\n\ndescribe('DB', () => {\n\n  test('db.exists()', () => {\n    let exists = db.exists('a.b')\n    expect(exists).toBe(false)\n    db.push('a.b', { foo: 1 })\n    exists = db.exists('a.b.foo')\n    expect(exists).toBe(true)\n  })\n\n  test('db.load()', () => {\n    fs.rmSync(root, { force: true, recursive: true })\n    db.clear()\n    expect(db.fetch(undefined)).toEqual({})\n  })\n\n  test('db.fetch()', () => {\n    let res = db.fetch('x')\n    expect(res).toBeUndefined()\n    db.push('x', 1)\n    res = db.fetch('x')\n    expect(res).toBe(1)\n    db.push('x', { foo: 1 })\n    res = db.fetch('x')\n    expect(res).toEqual({ foo: 1 })\n  })\n\n  test('db.delete()', () => {\n    db.push('foo.bar', 1)\n    db.delete('not_exists')\n    db.delete('foo.bar')\n    let exists = db.exists('foo.bar')\n    expect(exists).toBe(false)\n  })\n\n  test('db.push()', () => {\n    db.push('foo.x', 1)\n    db.push('foo.y', '2')\n    db.push('foo.z', true)\n    db.push('foo.n', null)\n    db.push('foo.o', { x: 1 })\n    let res = db.fetch('foo')\n    expect(res).toEqual({\n      x: 1,\n      y: '2',\n      z: true,\n      n: null,\n      o: { x: 1 }\n    })\n  })\n})\n\ndescribe('Mru', () => {\n  it('should load items', async () => {\n    let mru = new Mru('test', root)\n    await mru.clean()\n    let res = await mru.load()\n    expect(res.length).toBe(0)\n    res = mru.loadSync()\n    expect(res.length).toBe(0)\n  })\n\n  it('should consider last line break', async () => {\n    let file = path.join(root, 'test')\n    fs.writeFileSync(file, '1\\n2\\n3\\n4\\n5\\n', 'utf8')\n    let mru = new Mru('test', root)\n    let res = await mru.load()\n    expect(res.length).toBe(5)\n    await mru.clean()\n  })\n\n  it('should load sync', async () => {\n    let file = path.join(root, 'test')\n    fs.writeFileSync(file, '\\n', 'utf8')\n    let mru = new Mru('test', root)\n    let res = mru.loadSync()\n    expect(res.length).toBe(0)\n    fs.writeFileSync(file, '1\\n2\\n3\\n4\\n5\\n', 'utf8')\n    res = mru.loadSync()\n    expect(res.length).toBe(5)\n  })\n\n  it('should limit lines', async () => {\n    let file = path.join(root, 'test')\n    fs.writeFileSync(file, '1\\n2\\n3\\n4\\n5\\n', 'utf8')\n    let mru = new Mru('test', root, 3)\n    let lines = await mru.load()\n    expect(lines).toEqual(['1', '2', '3'])\n    await mru.clean()\n  })\n\n  it('should add items', async () => {\n    let mru = new Mru('test', root)\n    await mru.add('a')\n    await mru.add('b')\n    let res = await mru.load()\n    expect(res.length).toBe(2)\n    await mru.clean()\n  })\n\n  it('should consider BOM', async () => {\n    let mru = new Mru('test', root)\n    let file = path.join(root, 'test')\n    let buf = Buffer.from([239, 187, 191])\n    fs.writeFileSync(file, buf)\n    await mru.add('item')\n    let res = await mru.load()\n    expect(res.length).toBe(1)\n  })\n\n  it('should add when file it does not exist', async () => {\n    let mru = new Mru('test', root)\n    await mru.clean()\n    await mru.add('a')\n    let res = await mru.load()\n    expect(res).toEqual(['a'])\n  })\n\n  it('should remove item', async () => {\n    let mru = new Mru('test', root)\n    await mru.add('a')\n    await mru.remove('a')\n    let res = await mru.load()\n    expect(res.length).toBe(0)\n    await mru.clean()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/diagnosticBuffer.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { Diagnostic, DiagnosticSeverity, DiagnosticTag, Location, Position, Range, TextEdit } from 'vscode-languageserver-types'\nimport { DiagnosticBuffer } from '../../diagnostic/buffer'\nimport workspace from '../../workspace'\nimport helper from '../helper'\nimport { URI } from 'vscode-uri'\n\nlet nvim: Neovim\nasync function createDiagnosticBuffer(): Promise<DiagnosticBuffer> {\n  let doc = await workspace.document\n  return new DiagnosticBuffer(nvim, doc)\n}\n\nfunction createDiagnostic(msg: string, range?: Range, severity?: DiagnosticSeverity, tags?: DiagnosticTag[]): Diagnostic & { collection: string } {\n  range = range ? range : Range.create(0, 0, 0, 1)\n  return Object.assign(Diagnostic.create(range, msg, severity || DiagnosticSeverity.Error, 999, 'test'), { collection: 'test', tags })\n}\n\nasync function getExtmarkers(bufnr: number, ns: number): Promise<[number, number, number, number, string][]> {\n  let res = await nvim.call('nvim_buf_get_extmarks', [bufnr, ns, 0, -1, { details: true }]) as any\n  return res.map(o => {\n    return [o[1], o[2], o[3].end_row, o[3].end_col, o[3].hl_group]\n  })\n}\n\nlet ns: number\nlet virtualTextSrcId: number\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  ns = await nvim.createNamespace('coc-diagnostic')\n  virtualTextSrcId = await nvim.createNamespace('coc-diagnostic-virtualText')\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  await helper.reset()\n})\n\ndescribe('diagnostic buffer', () => {\n  describe('showFloat()', () => {\n    it('should not show float when disabled', async () => {\n      helper.updateConfiguration('diagnostic.messageTarget', 'echo')\n      let buf = await createDiagnosticBuffer()\n      let diagnostics = [createDiagnostic('foo')]\n      let res = await buf.showFloat(diagnostics, 'echo')\n      expect(res).toBe(false)\n    })\n\n    it('should not show float in insert mode', async () => {\n      let doc = await workspace.document\n      let buf = new DiagnosticBuffer(nvim, doc)\n      await nvim.input('i')\n      let mode = await nvim.mode\n      expect(mode.mode).toBe('i')\n      let diagnostics = [createDiagnostic('foo')]\n      let res = await buf.showFloat(diagnostics)\n      expect(res).toBe(false)\n    })\n\n    it('should show related information in floating window', async () => {\n      let buf = await createDiagnosticBuffer()\n      let range = Range.create(0, 0, 0, 10)\n      let location = Location.create(URI.file(__filename).toString(), range)\n      let diagnostic = Diagnostic.create(range, 'msg', 1, 1000, 'test', [{ location, message: 'this is a related information' }])\n      await buf.showFloat([diagnostic])\n      await nvim.call('cursor', [1, 1])\n\n      let winid = await helper.waitFloat()\n      let win = nvim.createWindow(winid)\n      let floatBuf = await win.buffer\n      let lines = await floatBuf.lines\n      expect(lines.length).toBe(7)\n      expect(lines[2]).toBe('Related information:')\n      expect(lines[4].includes('this is a related information')).toBe(true)\n    })\n\n    it('should show formated diagnostics', async () => {\n      helper.updateConfiguration('diagnostic.format', '[%source] %message')\n      let buf = await createDiagnosticBuffer()\n      let diagnostic = createDiagnostic('foo')\n      await buf.showFloat([diagnostic])\n      await nvim.call('cursor', [1, 1])\n\n      let winid = await helper.waitFloat()\n      let win = nvim.createWindow(winid)\n      let floatBuf = await win.buffer\n      let lines = await floatBuf.lines\n      expect(lines[0]).toEqual('[test] foo')\n    })\n  })\n\n  describe('refresh()', () => {\n    it('should not add signs when disabled', async () => {\n      helper.updateConfiguration('diagnostic.enableSign', false)\n      let diagnostics = [createDiagnostic('foo'), createDiagnostic('bar')]\n      let buf = await createDiagnosticBuffer()\n      buf.addSigns('a', diagnostics)\n      await helper.wait(30)\n      let res = await nvim.call('sign_getplaced', [buf.bufnr, { group: 'CocDiagnostica' }])\n      let signs = res[0].signs\n      expect(signs).toEqual([])\n    })\n\n    it('should filter sign by signLevel', async () => {\n      helper.updateConfiguration('diagnostic.signLevel', 'error')\n      let range = Range.create(0, 0, 0, 3)\n      let diagnostics = [createDiagnostic('foo', range, DiagnosticSeverity.Warning), createDiagnostic('bar', range, DiagnosticSeverity.Warning)]\n      let buf = await createDiagnosticBuffer()\n      buf.addSigns('a', diagnostics)\n      await helper.wait(30)\n      let res = await nvim.call('sign_getplaced', [buf.bufnr, { group: 'CocDiagnostica' }])\n      let signs = res[0].signs\n      expect(signs).toBeDefined()\n      expect(signs.length).toBe(0)\n    })\n\n    it('should set diagnostic info', async () => {\n      let r = Range.create(0, 1, 0, 2)\n      let diagnostics = [\n        createDiagnostic('foo', r, DiagnosticSeverity.Error),\n        createDiagnostic('bar', r, DiagnosticSeverity.Warning),\n        createDiagnostic('foo', r, DiagnosticSeverity.Hint),\n        createDiagnostic('bar', r, DiagnosticSeverity.Information)\n      ]\n      let buf = await createDiagnosticBuffer()\n      await buf.update('', diagnostics)\n      let buffer = await nvim.buffer\n      let res = await buffer.getVar('coc_diagnostic_info')\n      expect(res).toEqual({\n        lnums: [1, 1, 1, 1],\n        information: 1,\n        hint: 1,\n        warning: 1,\n        error: 1\n      })\n    })\n\n    it('should add highlight', async () => {\n      let buf = await createDiagnosticBuffer()\n      let doc = workspace.getDocument(buf.bufnr)\n      await nvim.setLine('abc')\n      await doc.patchChange()\n      nvim.pauseNotification()\n      buf.updateHighlights('', [\n        createDiagnostic('foo', Range.create(0, 0, 0, 1), DiagnosticSeverity.Error),\n        createDiagnostic('bar', Range.create(0, 0, 0, 1), DiagnosticSeverity.Warning)\n      ])\n      await nvim.resumeNotification()\n      let markers = await getExtmarkers(buf.bufnr, ns)\n      expect(markers).toEqual([\n        [0, 0, 0, 1, 'CocWarningHighlight'],\n        [0, 0, 0, 1, 'CocErrorHighlight']\n      ])\n      nvim.pauseNotification()\n      buf.updateHighlights('', [])\n      await nvim.resumeNotification()\n      let res = await nvim.call('nvim_buf_get_extmarks', [buf.bufnr, ns, 0, -1, { details: true }]) as any[]\n      expect(res.length).toBe(0)\n    })\n\n    it('should add deprecated highlight', async () => {\n      let diagnostic = createDiagnostic('foo', Range.create(0, 0, 0, 1), DiagnosticSeverity.Information, [DiagnosticTag.Deprecated])\n      let buf = await createDiagnosticBuffer()\n      let doc = workspace.getDocument(buf.bufnr)\n      await nvim.setLine('foo')\n      await doc.patchChange()\n      nvim.pauseNotification()\n      buf.updateHighlights('', [diagnostic])\n      await nvim.resumeNotification()\n      let res = await nvim.call('nvim_buf_get_extmarks', [buf.bufnr, ns, 0, -1, {}]) as [number, number, number][]\n      expect(res.length).toBe(2)\n    })\n\n    it('should not refresh for empty diagnostics', async () => {\n      let buf: any = await createDiagnosticBuffer()\n      let fn = jest.fn()\n      buf.refresh = () => {\n        fn()\n      }\n      buf.update('c', [])\n      expect(fn).toHaveBeenCalledTimes(0)\n    })\n\n    it('should refresh when content changes is empty', async () => {\n      let diagnostic = createDiagnostic('foo', Range.create(0, 0, 0, 1), DiagnosticSeverity.Error)\n      let buf = await createDiagnosticBuffer()\n      let doc = workspace.getDocument(buf.bufnr)\n      await nvim.setLine('foo')\n      doc._forceSync()\n      nvim.pauseNotification()\n      buf.updateHighlights('', [diagnostic])\n      await nvim.resumeNotification()\n      await nvim.setLine('foo')\n      await doc.patchChange()\n      doc._forceSync()\n      let res = await nvim.call('nvim_buf_get_extmarks', [buf.bufnr, ns, 0, -1, { details: true }]) as any\n      expect(res.length).toBe(1)\n    })\n  })\n\n  describe('setDiagnosticInfo()', () => {\n    it('should include lines', async () => {\n      helper.updateConfiguration('diagnostic.virtualTextCurrentLineOnly', false)\n      let buf = await createDiagnosticBuffer()\n      let r = Range.create(1, 1, 1, 3)\n      let diagnostics = [\n        createDiagnostic('foo', r, DiagnosticSeverity.Information),\n        createDiagnostic('foo', r, DiagnosticSeverity.Information),\n        createDiagnostic('foo', r, DiagnosticSeverity.Hint),\n        createDiagnostic('foo', r, DiagnosticSeverity.Hint),\n        createDiagnostic('foo', r, DiagnosticSeverity.Warning),\n        createDiagnostic('foo', r, DiagnosticSeverity.Warning),\n      ]\n      await buf.update('', diagnostics)\n      let buffer = await nvim.buffer\n      let res = await buffer.getVar(\"coc_diagnostic_info\") as any\n      expect(res.lnums).toEqual([0, 2, 2, 2])\n    })\n  })\n\n  describe('echoMessage', () => {\n    it('should not echoMessage when disabled', async () => {\n      helper.updateConfiguration('diagnostic.enableMessage', 'never')\n      let buf = await createDiagnosticBuffer()\n      let res = await buf.echoMessage(false, Position.create(0, 0))\n      res = await buf.echoMessage(true, Position.create(0, 0))\n      expect(res).toBe(false)\n    })\n  })\n\n  describe('showVirtualText()', () => {\n    beforeEach(() => {\n      helper.updateConfiguration('diagnostic.virtualText', true)\n    })\n\n    it('should not show virtualText when disabled', async () => {\n      helper.updateConfiguration('diagnostic.virtualTextCurrentLineOnly', false)\n      let buf = await createDiagnosticBuffer()\n      await buf.setState(false)\n      let diagnostic = createDiagnostic('foo')\n      let diagnostics = [diagnostic]\n      await buf.update('', diagnostics)\n      let res = await buf.showVirtualTextCurrentLine(1)\n      expect(res).toBe(false)\n      helper.updateConfiguration('diagnostic.virtualTextCurrentLineOnly', true)\n      buf.loadConfiguration()\n      await buf.setState(false)\n      res = await buf.showVirtualTextCurrentLine(1)\n      expect(res).toBe(false)\n    })\n\n    it('should change format of virtualText message', async () => {\n      helper.updateConfiguration('diagnostic.virtualTextFormat', '%source %message')\n      let buf = await createDiagnosticBuffer()\n      let diagnostic = createDiagnostic('foo')\n      await buf.update('', [diagnostic])\n      let res = await nvim.call('nvim_buf_get_extmarks', [buf.bufnr, virtualTextSrcId, 0, -1, { details: true }]) as any\n      let texts = res[0][3].virt_text\n      expect(texts[0][0]).toBe(' test foo')\n    })\n\n    it('should show virtual text on current line', async () => {\n      let diagnostic = createDiagnostic('foo')\n      let buf = await createDiagnosticBuffer()\n      let diagnostics = [diagnostic]\n      await buf.update('', diagnostics)\n      let res = await nvim.call('nvim_buf_get_extmarks', [buf.bufnr, virtualTextSrcId, 0, -1, { details: true }]) as any\n      expect(res.length).toBe(1)\n      let texts = res[0][3].virt_text\n      expect(texts[0]).toEqual([' foo', 'CocErrorVirtualText'])\n    })\n\n    it('should show virtual text at window column', async () => {\n      helper.updateConfiguration('diagnostic.virtualTextWinCol', 90)\n      let diagnostic = createDiagnostic('foo')\n      let buf = await createDiagnosticBuffer()\n      let diagnostics = [diagnostic]\n      await buf.update('', diagnostics)\n      let res = await nvim.call('nvim_buf_get_extmarks', [buf.bufnr, virtualTextSrcId, 0, -1, { details: true }]) as any\n      expect(res.length).toBe(1)\n      let texts = res[0][3].virt_text\n      expect(texts[0]).toEqual([' foo', 'CocErrorVirtualText'])\n    })\n\n    it('should virtual text on all lines', async () => {\n      helper.updateConfiguration('diagnostic.virtualTextCurrentLineOnly', false)\n      let buf = await createDiagnosticBuffer()\n      let diagnostics = [\n        createDiagnostic('foo', Range.create(0, 0, 0, 1)),\n        createDiagnostic('bar', Range.create(1, 0, 1, 1)),\n      ]\n      await buf.update('', diagnostics)\n      let res = await nvim.call('nvim_buf_get_extmarks', [buf.bufnr, virtualTextSrcId, 0, -1, { details: true }]) as any\n      expect(res.length).toBe(2)\n    })\n\n    it('should filter by virtualTextLevel', async () => {\n      helper.updateConfiguration('diagnostic.virtualTextLevel', 'error')\n      helper.updateConfiguration('diagnostic.virtualTextAlign', 'after')\n      let buf = await createDiagnosticBuffer()\n      let diagnostics = [\n        createDiagnostic('foo', Range.create(0, 0, 0, 1), DiagnosticSeverity.Error),\n        createDiagnostic('foo', Range.create(0, 0, 0, 1), DiagnosticSeverity.Warning),\n        createDiagnostic('bar', Range.create(1, 0, 1, 1), DiagnosticSeverity.Warning),\n      ]\n      await buf.update('', diagnostics)\n      let res = await nvim.call('nvim_buf_get_extmarks', [buf.bufnr, virtualTextSrcId, 0, -1, { details: true }]) as any\n      expect(res.length).toBe(1)\n    })\n\n    it('should limit virtual text count of one line', async () => {\n      helper.updateConfiguration('diagnostic.virtualTextCurrentLineOnly', false)\n      helper.updateConfiguration('diagnostic.virtualTextLimitInOneLine', 1)\n      let buf = await createDiagnosticBuffer()\n      let diagnostics = [\n        createDiagnostic('foo', Range.create(0, 0, 0, 1)),\n        createDiagnostic('bar', Range.create(0, 0, 0, 1)),\n      ]\n      await buf.update('', diagnostics)\n      let res = await nvim.call('nvim_buf_get_extmarks', [buf.bufnr, virtualTextSrcId, 0, -1, { details: true }]) as any\n      expect(res[0][3].virt_text.length).toBe(1)\n    })\n  })\n\n  describe('updateLocationList()', () => {\n    beforeEach(() => {\n      helper.updateConfiguration('diagnostic.locationlistUpdate', true)\n    })\n\n    it('should update location list', async () => {\n      let buf = await createDiagnosticBuffer()\n      await nvim.call('setloclist', [0, [], 'r', { title: 'Diagnostics of coc', items: [] }])\n      await buf.update('a', [createDiagnostic('foo')])\n      let res = await nvim.eval(`getloclist(bufwinid(${buf.bufnr}))`) as any[]\n      expect(res.length).toBe(1)\n      expect(res[0].text).toBe('[test 999] foo [E]')\n    })\n  })\n\n  describe('clear()', () => {\n    beforeEach(() => {\n      helper.updateConfiguration('diagnostic.virtualText', true)\n    })\n\n    it('should clear all diagnostics', async () => {\n      let diagnostic = createDiagnostic('foo')\n      let buf = await createDiagnosticBuffer()\n      let diagnostics = [diagnostic]\n      await buf.update('', diagnostics)\n      buf.clear()\n      let buffer = await nvim.buffer\n      let res = await buffer.getVar(\"coc_diagnostic_info\")\n      expect(res == null).toBe(true)\n    })\n  })\n\n  describe('reset()', () => {\n    it('should clear exists diagnostics', async () => {\n      let buf = await createDiagnosticBuffer()\n      let diagnostic = createDiagnostic('foo')\n      let diagnostics = [diagnostic]\n      await buf.update('test', diagnostics)\n      await helper.wait(30)\n      await buf.reset({})\n      let res = await buf.doc.buffer.getVar(\"coc_diagnostic_info\") as any\n      expect(res?.error).toBe(0)\n    })\n\n    it('should not refresh when not enabled', async () => {\n      let buf = await createDiagnosticBuffer()\n      let diagnostic = createDiagnostic('foo')\n      let diagnostics = [diagnostic]\n      await buf.update('test', diagnostics)\n      await buf.setState(false)\n      await buf.setState(false)\n      await buf.reset({ diagnostics: [createDiagnostic('bar')] })\n      let res = await buf.doc.buffer.getVar(\"coc_diagnostic_info\") as any\n      expect(res).toBeNull()\n      await buf.setState(true)\n      res = await buf.doc.buffer.getVar(\"coc_diagnostic_info\") as any\n      expect(res?.error).toBe(1)\n    })\n  })\n\n  describe('isEnabled()', () => {\n    it('should return false when buffer disposed', async () => {\n      let buf = await createDiagnosticBuffer()\n      await nvim.command(`bd! ${buf.bufnr}`)\n      buf.dispose()\n      let res = await buf.isEnabled()\n      expect(res).toBe(false)\n      let arr = buf.getHighlightItems([])\n      expect(arr.length).toBe(0)\n    })\n  })\n\n  describe('getHighlightItems()', () => {\n    it('should get highlights', async () => {\n      let buf = await createDiagnosticBuffer()\n      let doc = workspace.getDocument(workspace.bufnr)\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo\\nbar')])\n      let diagnostics = [\n        createDiagnostic('one', Range.create(0, 0, 0, 1), DiagnosticSeverity.Warning),\n        createDiagnostic('one', Range.create(0, 1, 0, 2), DiagnosticSeverity.Warning),\n        createDiagnostic('two', Range.create(0, 0, 2, 3), DiagnosticSeverity.Error),\n        createDiagnostic('three', Range.create(1, 0, 1, 2), DiagnosticSeverity.Hint),\n      ]\n      diagnostics[0].tags = [DiagnosticTag.Unnecessary]\n      diagnostics[1].tags = [DiagnosticTag.Deprecated]\n      let res = buf.getHighlightItems(diagnostics)\n      expect(res.length).toBe(7)\n      expect(res.map(o => o.hlGroup)).toEqual([\n        'CocUnusedHighlight',\n        'CocWarningHighlight',\n        'CocErrorHighlight',\n        'CocDeprecatedHighlight',\n        'CocWarningHighlight',\n        'CocHintHighlight',\n        'CocErrorHighlight'\n      ])\n    })\n  })\n\n  describe('getDiagnostics()', () => {\n    it('should get sorted diagnostics', async () => {\n      let buf = await createDiagnosticBuffer()\n      let diagnostics = [\n        createDiagnostic('three', Range.create(0, 1, 0, 2), DiagnosticSeverity.Error),\n        createDiagnostic('one', Range.create(0, 0, 0, 2), DiagnosticSeverity.Warning),\n        createDiagnostic('two', Range.create(0, 0, 0, 2), DiagnosticSeverity.Error),\n      ]\n      diagnostics[0].tags = [DiagnosticTag.Unnecessary]\n      await buf.reset({\n        x: diagnostics,\n        y: [createDiagnostic('four', Range.create(0, 0, 0, 2), DiagnosticSeverity.Error)]\n      })\n      let res = buf.getDiagnosticsAt(Position.create(0, 1), false)\n      let arr = res.map(o => o.message)\n      expect(arr).toEqual(['four', 'two', 'three', 'one'])\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/diagnosticCollection.test.ts",
    "content": "import DiagnosticCollection from '../../diagnostic/collection'\nimport { Diagnostic, DiagnosticSeverity, DiagnosticTag, Range } from 'vscode-languageserver-types'\n\nfunction createDiagnostic(msg: string, range?: Range): Diagnostic {\n  range = range ? range : Range.create(0, 0, 0, 1)\n  return Diagnostic.create(range, msg)\n}\n\ndescribe('diagnostic collection', () => {\n\n  it('should create collection', () => {\n    let collection = new DiagnosticCollection('test')\n    expect(collection.name).toBe('test')\n    collection.dispose()\n  })\n\n  it('should set diagnostic with uri', () => {\n    let collection = new DiagnosticCollection('test')\n    let diagnostic = createDiagnostic('error')\n    let uri = 'file:///1'\n    collection.set(uri, [diagnostic])\n    expect(collection.get(uri).length).toBe(1)\n    collection.set(uri, [])\n    expect(collection.get(uri).length).toBe(0)\n  })\n\n  it('should set severity for hint tags', async () => {\n    let collection = new DiagnosticCollection('test')\n    let diagnostics = [{\n      range: null,\n      message: undefined,\n      tags: [DiagnosticTag.Deprecated]\n    }, {\n      range: Range.create(0, 0, 0, 1),\n      message: undefined,\n      tags: [DiagnosticTag.Unnecessary]\n    }]\n    let uri = 'file:///1'\n    collection.set(uri, diagnostics)\n    let arr = collection.get(uri)\n    expect(arr.length).toBe(2)\n    expect(arr[0].severity).toBe(DiagnosticSeverity.Hint)\n    expect(arr[1].severity).toBe(DiagnosticSeverity.Hint)\n  })\n\n  it('should clear diagnostics with null as diagnostics', () => {\n    let collection = new DiagnosticCollection('test')\n    let diagnostic = createDiagnostic('error')\n    let uri = 'file:///1'\n    collection.set(uri, [diagnostic])\n    expect(collection.get(uri).length).toBe(1)\n    collection.set(uri, null)\n    expect(collection.get(uri).length).toBe(0)\n  })\n\n  it('should clear diagnostics with undefined as diagnostics in entries', () => {\n    let collection = new DiagnosticCollection('test')\n    let diagnostic = createDiagnostic('error')\n    let entries: [string, Diagnostic[] | null][] = [\n      ['file:1', [diagnostic]],\n      ['file:1', undefined]\n    ]\n    let uri = 'file:///1'\n    collection.set(entries)\n    expect(collection.get(uri).length).toBe(0)\n  })\n\n  it('should set diagnostics with entries', () => {\n    let collection = new DiagnosticCollection('test')\n    let diagnostic = createDiagnostic('error')\n    let uri = 'file:///1'\n    let other = 'file:///2'\n    let entries: [string, Diagnostic[]][] = [\n      [uri, [diagnostic]],\n      [other, [diagnostic]],\n      [uri, [createDiagnostic('other')]]\n    ]\n    collection.set(entries)\n    expect(collection.get(uri).length).toBe(2)\n    expect(collection.get(other).length).toBe(1)\n  })\n\n  it('should delete diagnostics for uri', () => {\n    let collection = new DiagnosticCollection('test')\n    let diagnostic = createDiagnostic('error')\n    let uri = 'file:///1'\n    collection.set(uri, [diagnostic])\n    collection.delete(uri)\n    expect(collection.get(uri).length).toBe(0)\n  })\n\n  it('should clear all diagnostics', () => {\n    let collection = new DiagnosticCollection('test')\n    let diagnostic = createDiagnostic('error')\n    let uri = 'file:///1'\n    let fn = jest.fn()\n    collection.set(uri, [diagnostic])\n    collection.onDidDiagnosticsChange(fn)\n    collection.clear()\n    expect(collection.get(uri).length).toBe(0)\n    expect(fn).toHaveBeenCalledTimes(1)\n  })\n\n  it('should call for every uri with diagnostics', () => {\n    let collection = new DiagnosticCollection('test')\n    let diagnostic = createDiagnostic('error')\n    let uri = 'file:///1'\n    let other = 'file:///2'\n    let entries: [string, Diagnostic[]][] = [\n      [uri, [diagnostic]],\n      [other, [diagnostic]],\n      [uri, [createDiagnostic('other')]]\n    ]\n    collection.set(entries)\n    let arr: string[] = []\n    collection.forEach(uri => {\n      arr.push(uri)\n    })\n    expect(arr).toEqual([uri, other])\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/diagnosticManager.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport os from 'os'\nimport path from 'path'\nimport { Diagnostic, DiagnosticSeverity, DiagnosticTag, Location, Position, Range } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport manager from '../../diagnostic/manager'\nimport { getHighlightGroup, getNameFromSeverity, getSeverityName, getSeverityType, severityLevel, sortDiagnostics } from '../../diagnostic/util'\nimport Document from '../../model/document'\nimport window from '../../window'\nimport commands from '../../commands'\nimport workspace from '../../workspace'\nimport fs from 'fs'\nimport helper, { createTmpFile } from '../helper'\n\nlet nvim: Neovim\nfunction createDiagnostic(msg: string, range?: Range, severity?: DiagnosticSeverity): Diagnostic {\n  range = range ? range : Range.create(0, 0, 0, 1)\n  return Diagnostic.create(range, msg, severity || DiagnosticSeverity.Error)\n}\n\nlet virtualTextSrcId: number\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  virtualTextSrcId = await nvim.createNamespace('coc-diagnostic-virtualText')\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  manager.reset()\n  await helper.reset()\n})\n\nasync function createDocument(name?: string): Promise<Document> {\n  let doc = await helper.createDocument(name)\n  let collection = manager.create('test')\n  let diagnostics: Diagnostic[] = []\n  await doc.buffer.setLines(['foo bar foo bar', 'foo bar', 'foo', 'bar'], {\n    start: 0,\n    end: -1,\n    strictIndexing: false\n  })\n  await doc.synchronize()\n  diagnostics.push(createDiagnostic('error', Range.create(0, 2, 0, 4), DiagnosticSeverity.Error))\n  diagnostics.push(createDiagnostic('warning', Range.create(0, 5, 0, 6), DiagnosticSeverity.Warning))\n  diagnostics.push(createDiagnostic('information', Range.create(1, 0, 1, 1), DiagnosticSeverity.Information))\n  diagnostics.push(createDiagnostic('hint', Range.create(1, 2, 1, 3), DiagnosticSeverity.Hint))\n  diagnostics.push(createDiagnostic('error', Range.create(2, 0, 2, 2), DiagnosticSeverity.Error))\n  collection.set(doc.uri, diagnostics)\n  await helper.waitValue(() => {\n    let buf = manager.getItem(doc.bufnr)\n    if (!buf.config.autoRefresh) return true\n    return buf.getDiagnosticsAt(Position.create(0, 0), true).length > 0\n  }, true)\n  return doc\n}\n\ndescribe('diagnostic manager', () => {\n  describe('defineSigns', () => {\n    it('should defineSigns', () => {\n      manager.defineSigns({\n        enableHighlightLineNumber: false\n      })\n    })\n  })\n\n  describe('setLocationlist()', () => {\n    it('should set location list', async () => {\n      let doc = await createDocument()\n      await helper.doAction('fillDiagnostics', doc.bufnr)\n      let res = await nvim.call('getloclist', [doc.bufnr]) as any[]\n      expect(res.length).toBeGreaterThan(2)\n      helper.updateConfiguration('diagnostic.locationlistLevel', 'error')\n      await manager.setLocationlist(doc.bufnr)\n      res = await nvim.call('getloclist', [doc.bufnr]) as any[]\n      expect(res.length).toBe(2)\n    })\n\n    it('should throw when buffer not attached', async () => {\n      await nvim.command(`vnew +setl\\\\ buftype=nofile`)\n      let doc = await workspace.document\n      let fn = async () => {\n        await manager.setLocationlist(doc.bufnr)\n      }\n      await expect(fn()).rejects.toThrow(/not/)\n    })\n  })\n\n  describe('events', () => {\n    it('should delay refresh when buffer visible', async () => {\n      let doc = await helper.createDocument()\n      await nvim.command('edit tmp')\n      let collection = manager.create('foo')\n      let diagnostics: Diagnostic[] = []\n      await doc.buffer.setLines(['foo bar foo bar', 'foo bar', 'foo', 'bar'], {\n        start: 0,\n        end: -1,\n        strictIndexing: false\n      })\n      await doc.synchronize()\n      diagnostics.push(createDiagnostic('error', Range.create(0, 2, 0, 4), DiagnosticSeverity.Error))\n      collection.set(doc.uri, diagnostics)\n      let buf = doc.buffer\n      let val = await buf.getVar('coc_diagnostic_info') as any\n      expect(val == null).toBe(true)\n      let ns = await nvim.createNamespace('coc-diagnosticfoo')\n      let markers = await buf.getExtMarks(ns, 0, -1)\n      expect(markers.length).toBe(0)\n      await nvim.command(`b ${buf.id}`)\n      await helper.waitFor('eval', ['empty(get(b:,\"coc_diagnostic_info\",{}))'], 0)\n      collection.dispose()\n    })\n\n    it('should delay refresh on InsertLeave', async () => {\n      let doc = await workspace.document\n      await nvim.input('i')\n      let collection = manager.create('foo')\n      let diagnostics: Diagnostic[] = []\n      await doc.buffer.setLines(['foo bar foo bar', 'foo bar', 'foo', 'bar'], {\n        start: 0,\n        end: -1,\n        strictIndexing: false\n      })\n      await doc.synchronize()\n      diagnostics.push(createDiagnostic('error', Range.create(0, 2, 0, 4), DiagnosticSeverity.Error))\n      collection.set(doc.uri, diagnostics)\n      let buf = doc.buffer\n      await helper.waitValue(async () => {\n        let val = await buf.getVar('coc_diagnostic_info') as any\n        return val == null\n      }, true)\n      let ns = await nvim.createNamespace('coc-diagnosticfoo')\n      let markers = await buf.getExtMarks(ns, 0, -1)\n      expect(markers.length).toBe(0)\n      await nvim.input('<esc>')\n      await helper.waitValue(async () => {\n        let markers = await buf.getExtMarks(ns, 0, -1)\n        return markers.length\n      }, 1)\n    })\n\n    it('should show diagnostic virtual text on CursorMoved', async () => {\n      helper.updateConfiguration('diagnostic.virtualText', true)\n      helper.updateConfiguration('diagnostic.virtualTextCurrentLineOnly', true)\n      let doc = await createDocument()\n      await helper.wait(30)\n      let markers = await doc.buffer.getExtMarks(virtualTextSrcId, 0, -1, { details: true })\n      await manager.toggleDiagnosticBuffer(doc.bufnr)\n      await nvim.call('cursor', [1, 3])\n      await helper.wait(30)\n      markers = await doc.buffer.getExtMarks(virtualTextSrcId, 0, -1, { details: true })\n      expect(markers.length).toBe(0)\n    })\n  })\n\n  describe('refresh()', () => {\n    it('should refresh on buffer create', async () => {\n      let uri = URI.file(path.join(path.dirname(__dirname), 'doc')).toString()\n      let fn = jest.fn()\n      let disposable = manager.onDidRefresh(() => {\n        fn()\n      })\n      let collection = manager.create('tmp')\n      let diagnostic = createDiagnostic('My Error')\n      collection.set(uri, [diagnostic])\n      let doc = await helper.createDocument('doc')\n      await helper.wait(30)\n      let val = await doc.buffer.getVar('coc_diagnostic_info') as any\n      expect(fn).toHaveBeenCalled()\n      expect(val).toBeDefined()\n      expect(val.error).toBe(1)\n      collection.dispose()\n      disposable.dispose()\n    })\n  })\n\n  describe('toggleDiagnostic()', () => {\n    it('should toggle diagnostics for all buffer', async () => {\n      await createDocument()\n      let doc = await createDocument()\n      await helper.doAction('diagnosticToggle')\n      let item = manager.getItem(doc.bufnr)\n      expect(item.config.enable).toBe(false)\n      await manager.toggleDiagnostic(1)\n      expect(item.config.enable).toBe(true)\n    })\n  })\n\n  describe('getDiagnosticList()', () => {\n    it('should get all diagnostics', async () => {\n      await createDocument()\n      let collection = manager.create('test')\n      let fsPath = await createTmpFile('foo')\n      let doc = await helper.createDocument(fsPath)\n      let diagnostics: Diagnostic[] = []\n      diagnostics.push(createDiagnostic('error', Range.create(0, 0, 0, 1), DiagnosticSeverity.Error))\n      diagnostics.push(createDiagnostic('error', Range.create(0, 1, 0, 2), DiagnosticSeverity.Error))\n      diagnostics.push(createDiagnostic('error', Range.create(0, 2, 0, 3), DiagnosticSeverity.Warning))\n      collection.set(doc.uri, diagnostics)\n      collection.set('file:///1', [])\n      let list = await helper.doAction('diagnosticList')\n      expect(list).toBeDefined()\n      expect(list.length).toBeGreaterThanOrEqual(5)\n      expect(list[0].severity).toBe('Error')\n      expect(list[1].severity).toBe('Error')\n      expect(list[2].severity).toBe('Error')\n    })\n\n    it('should filter diagnostics by configuration', async () => {\n      helper.updateConfiguration('diagnostic.level', 'warning')\n      helper.updateConfiguration('diagnostic.showUnused', false)\n      helper.updateConfiguration('diagnostic.showDeprecated', false)\n      let doc = await createDocument()\n      let buf = manager.getItem(doc.bufnr)\n      let diagnostics = manager.getDiagnostics(buf)['test']\n      diagnostics[0].tags = [DiagnosticTag.Unnecessary]\n      diagnostics[2].tags = [DiagnosticTag.Deprecated]\n      let list = await manager.getDiagnosticList()\n      expect(list.length).toBe(3)\n      let res = manager.getDiagnostics(buf)['test']\n      expect(res.length).toBe(1)\n      let ranges = manager.getSortedRanges(doc.uri, buf.config.level)\n      expect(ranges.length).toBe(3)\n    })\n\n    it('should load file from disk ', async () => {\n      let fsPath = __filename\n      let collection = manager.create('test')\n      let diagnostics: Diagnostic[] = []\n      diagnostics.push(createDiagnostic('error', Range.create(0, 0, 0, 1), DiagnosticSeverity.Error))\n      let uri = URI.file(fsPath).toString()\n      collection.set(uri, diagnostics)\n      let arr: Diagnostic[] = []\n      arr.push(createDiagnostic('error', Range.create(1, 0, 1, 1), undefined))\n      collection.set('test:1', arr)\n      let list = await manager.getDiagnosticList()\n      expect(list.length).toBe(2)\n    })\n  })\n\n  describe('preview()', () => {\n    it('should not throw with empty diagnostics', async () => {\n      await helper.doAction('diagnosticPreview')\n      let tabpage = await nvim.tabpage\n      let wins = await tabpage.windows\n      expect(wins.length).toBe(1)\n    })\n\n    it('should open preview window', async () => {\n      await createDocument()\n      await nvim.call('cursor', [1, 3])\n      await manager.preview()\n      let res = await nvim.call('coc#window#find', ['&previewwindow', 1])\n      expect(res).toBeDefined()\n    })\n  })\n\n  describe('setConfigurationErrors()', () => {\n    it('should set configuration errors on refresh', async () => {\n      let file = path.join(os.tmpdir(), '69075963-48d6-4427-92db-287a09d5e976')\n      fs.writeFileSync(file, ']', 'utf8')\n      workspace.configurations.parseConfigurationModel(file)\n      let errors = workspace.configurations.errors\n      expect(errors.size).toBeGreaterThan(0)\n      let list = await manager.getDiagnosticList()\n      expect(list.length).toBe(1)\n      expect(list[0].file).toBe(file)\n      manager.checkConfigurationErrors()\n      fs.unlinkSync(file)\n    })\n  })\n\n  describe('create()', () => {\n    it('should create diagnostic collection', async () => {\n      let doc = await workspace.document\n      let collection = manager.create('test')\n      collection.set(doc.uri, [createDiagnostic('foo')])\n      await helper.waitValue(async () => {\n        let info = await doc.buffer.getVar('coc_diagnostic_info')\n        return info != null\n      }, true)\n    })\n  })\n\n  describe('getSortedRanges()', () => {\n    it('should get sorted ranges of document', async () => {\n      let doc = await workspace.document\n      await nvim.call('setline', [1, ['a', 'b', 'c']])\n      let collection = manager.create('test')\n      let diagnostics: Diagnostic[] = []\n      diagnostics.push(createDiagnostic('x', Range.create(0, 0, 0, 1)))\n      diagnostics.push(createDiagnostic('y', Range.create(0, 1, 0, 2)))\n      diagnostics.push(createDiagnostic('z', Range.create(1, 0, 1, 2)))\n      collection.set(doc.uri, diagnostics)\n      let item = manager.getItem(doc.bufnr)\n      let level = item.config.level\n      let ranges = manager.getSortedRanges(doc.uri, level)\n      expect(ranges[0]).toEqual(Range.create(0, 0, 0, 1))\n      expect(ranges[1]).toEqual(Range.create(0, 1, 0, 2))\n      expect(ranges[2]).toEqual(Range.create(1, 0, 1, 2))\n      ranges = manager.getSortedRanges(doc.uri, level, 'error')\n      expect(ranges.length).toBe(3)\n      expect(manager.getSortedRanges(doc.uri, level, 'warning').length).toBe(0)\n    })\n  })\n\n  describe('getDiagnosticsInRange', () => {\n    it('should get diagnostics in range', async () => {\n      let doc = await createDocument()\n      let res = manager.getDiagnosticsInRange(doc.textDocument, Range.create(0, 0, 1, 0))\n      expect(res.length).toBe(3)\n      doc = await helper.createDocument()\n      res = manager.getDiagnosticsInRange(doc.textDocument, Range.create(0, 0, 1, 0))\n      expect(res.length).toBe(0)\n    })\n  })\n\n  describe('getCurrentDiagnostics', () => {\n    it('should get undefined when buffer not attached', async () => {\n      await nvim.command(`edit +setl\\\\ buftype=nofile tmp`)\n      let res = await manager.getCurrentDiagnostics()\n      await helper.doAction('diagnosticInfo')\n      expect(res).toBeUndefined()\n    })\n\n    it('should get diagnostics under cursor', async () => {\n      await createDocument()\n      let diagnostics = await manager.getCurrentDiagnostics()\n      expect(diagnostics.length).toBe(0)\n      await nvim.call('cursor', [1, 4])\n      diagnostics = await manager.getCurrentDiagnostics()\n      expect(diagnostics.length).toBe(1)\n      helper.updateConfiguration('diagnostic.checkCurrentLine', true)\n      await nvim.call('cursor', [1, 2])\n      diagnostics = await manager.getCurrentDiagnostics()\n      expect(diagnostics.length).toBe(2)\n    })\n\n    it('should get empty diagnostic at end of line', async () => {\n      let doc = await workspace.document\n      await nvim.setLine('foo')\n      doc.forceSync()\n      await nvim.command('normal! $')\n      let diagnostic = Diagnostic.create(Range.create(0, 3, 1, 0), 'error', DiagnosticSeverity.Error)\n      let collection = manager.create('empty')\n      collection.set(doc.uri, [diagnostic])\n      await manager.refreshBuffer(doc.bufnr)\n      let diagnostics = await manager.getCurrentDiagnostics()\n      expect(diagnostics.length).toBeGreaterThanOrEqual(1)\n      expect(diagnostics[0].message).toBe('error')\n      collection.dispose()\n      await manager.refreshBuffer(99)\n    })\n\n    it('should get diagnostic next to end of line', async () => {\n      let doc = await workspace.document\n      await nvim.setLine('foo')\n      doc.forceSync()\n      await nvim.command('normal! $')\n      let diagnostic = Diagnostic.create(Range.create(0, 3, 0, 4), 'error', DiagnosticSeverity.Error)\n      let collection = manager.create('empty')\n      collection.set(doc.uri, [diagnostic])\n      await manager.refreshBuffer(doc.bufnr)\n      let diagnostics = await manager.getCurrentDiagnostics()\n      expect(diagnostics.length).toBeGreaterThanOrEqual(1)\n      expect(diagnostics[0].message).toBe('error')\n      collection.dispose()\n    })\n\n    it('should get diagnostic with empty range at end of line', async () => {\n      let doc = await workspace.document\n      await nvim.setLine('foo')\n      doc.forceSync()\n      await nvim.command('normal! $')\n      let diagnostic = Diagnostic.create(Range.create(0, 3, 1, 0), 'error', DiagnosticSeverity.Error)\n      let collection = manager.create('empty')\n      collection.set(doc.uri, [diagnostic])\n      await manager.refreshBuffer(doc.bufnr)\n      let diagnostics = await manager.getCurrentDiagnostics()\n      expect(diagnostics.length).toBeGreaterThanOrEqual(1)\n      expect(diagnostics[0].message).toBe('error')\n      collection.dispose()\n    })\n\n    it('should get diagnostic pass end of the buffer lines', async () => {\n      let doc = await workspace.document\n      await nvim.setLine('foo')\n      doc.forceSync()\n      await nvim.command('normal! ^')\n      let diagnostic = Diagnostic.create(Range.create(1, 0, 1, 0), 'error', DiagnosticSeverity.Error)\n      let collection = manager.create('empty')\n      collection.set(doc.uri, [diagnostic])\n      await manager.refreshBuffer(doc.bufnr)\n      let diagnostics = await manager.getCurrentDiagnostics()\n      expect(diagnostics.length).toBeGreaterThanOrEqual(1)\n      expect(diagnostics[0].message).toBe('error')\n      collection.dispose()\n    })\n\n  })\n\n  describe('jumpRelated', () => {\n    it('should does nothing when no diagnostic exists', async () => {\n      let doc = await workspace.document\n      await nvim.call('cursor', [1, 1])\n      await commands.executeCommand('workspace.diagnosticRelated')\n      let bufnr = await nvim.eval('bufnr(\"%\")')\n      expect(bufnr).toBe(doc.bufnr)\n    })\n\n    it('should does nothing when no related information exists', async () => {\n      let doc = await createDocument()\n      await nvim.call('cursor', [1, 4])\n      await manager.jumpRelated()\n      let bufnr = await nvim.eval('bufnr(\"%\")')\n      expect(bufnr).toBe(doc.bufnr)\n    })\n\n    it('should jump to related position', async () => {\n      let doc = await workspace.document\n      let range = Range.create(0, 0, 0, 10)\n      let location = Location.create(URI.file(__filename).toString(), range)\n      let diagnostic = Diagnostic.create(range, 'msg', DiagnosticSeverity.Error, 1000, 'test',\n        [{ location, message: 'test' }])\n      let collection = manager.create('positions')\n      collection.set(doc.uri, [diagnostic])\n      await manager.refreshBuffer(doc.uri)\n      await nvim.call('cursor', [1, 1])\n      await manager.jumpRelated()\n      let bufname = await nvim.call('bufname', '%')\n      expect(bufname).toMatch('diagnosticManager')\n    })\n\n    it('should open location list', async () => {\n      let doc = await workspace.document\n      let range = Range.create(0, 0, 0, 10)\n      let diagnostic = Diagnostic.create(range, 'msg', DiagnosticSeverity.Error, 1000, 'test',\n        [{\n          location: Location.create(URI.file(__filename).toString(), Range.create(1, 0, 1, 10)),\n          message: 'foo'\n        }, {\n          location: Location.create(URI.file(__filename).toString(), Range.create(2, 0, 2, 10)),\n          message: 'bar'\n        }])\n      let collection = manager.create('positions')\n      collection.set(doc.uri, [diagnostic])\n      await manager.refreshBuffer(doc.uri)\n      await nvim.call('cursor', [1, 1])\n      await manager.jumpRelated()\n      await helper.waitFor('bufname', ['%'], 'list:///location')\n      await nvim.input('<esc>')\n    })\n  })\n\n  describe('jumpPrevious & jumpNext', () => {\n    it('should jump to previous', async () => {\n      let doc = await createDocument()\n      await nvim.command('normal! G$')\n      let ranges = manager.getSortedRanges(doc.uri, undefined)\n      ranges.reverse()\n      for (let i = 0; i < ranges.length; i++) {\n        await manager.jumpPrevious()\n        let pos = await window.getCursorPosition()\n        expect(pos).toEqual(ranges[i].start)\n      }\n      await helper.doAction('diagnosticPrevious')\n    })\n\n    it('should jump to next', async () => {\n      let doc = await createDocument()\n      await nvim.call('cursor', [0, 0])\n      let ranges = manager.getSortedRanges(doc.uri, undefined)\n      for (let i = 0; i < ranges.length; i++) {\n        await manager.jumpNext()\n        let pos = await window.getCursorPosition()\n        expect(pos).toEqual(ranges[i].start)\n      }\n      await helper.doAction('diagnosticNext')\n    })\n\n    it('should consider invalid position', async () => {\n      let doc = await helper.createDocument('foo.js')\n      let collection = manager.create('foo')\n      let diagnostics: Diagnostic[] = []\n      await doc.buffer.setLines(['foo bar', '', 'foo', 'bar'], {\n        start: 0,\n        end: -1,\n        strictIndexing: false\n      })\n      await nvim.call('cursor', [2, 0])\n      await doc.synchronize()\n      diagnostics.push(createDiagnostic('error', Range.create(0, 1, 0, 2), DiagnosticSeverity.Error))\n      diagnostics.push(createDiagnostic('warning', Range.create(1, 1, 1, 1), DiagnosticSeverity.Warning))\n      diagnostics.push(createDiagnostic('warning', Range.create(2, 1, 2, 1), DiagnosticSeverity.Warning))\n      collection.set(doc.uri, diagnostics)\n      await manager.jumpNext()\n      let pos = await window.getCursorPosition()\n      expect(pos).toEqual(Position.create(2, 1))\n    })\n\n    it('should not throw when buffer not attached', async () => {\n      let doc = await workspace.document\n      await manager.jumpNext()\n      await nvim.command('edit foo | setl buftype=nofile')\n      doc = await workspace.document\n      expect(doc.attached).toBe(false)\n      await manager.jumpNext()\n    })\n\n    it('should respect wrapscan', async () => {\n      await createDocument()\n      await nvim.command('setl nowrapscan')\n      await nvim.command('normal! G$')\n      await manager.jumpNext()\n      let pos = await window.getCursorPosition()\n      expect(pos).toEqual({ line: 3, character: 2 })\n      await nvim.command('normal! gg0')\n      await manager.jumpPrevious()\n      pos = await window.getCursorPosition()\n      expect(pos).toEqual({ line: 0, character: 0 })\n    })\n  })\n\n  describe('diagnostic configuration', () => {\n    it('should use filetype map from config', async () => {\n      helper.updateConfiguration('diagnostic.filetypeMap', { default: 'bufferType' })\n      helper.updateConfiguration('diagnostic.messageDelay', 10)\n      let doc = await createDocument('foo.js')\n      await nvim.setLine('foo')\n      await doc.synchronize()\n      let collection = manager.getCollectionByName('test')\n      let diagnostic = createDiagnostic('99', Range.create(0, 0, 0, 3), DiagnosticSeverity.Error)\n      diagnostic.codeDescription = {\n        href: 'http://www.example.com'\n      }\n      let diagnostics = [diagnostic]\n      collection.set(doc.uri, diagnostics)\n      await nvim.call('cursor', [1, 2])\n      await manager.echoCurrentMessage()\n      let win = await helper.getFloat()\n      let bufnr = await nvim.call('winbufnr', [win.id]) as number\n      let buf = nvim.createBuffer(bufnr)\n      let lines = await buf.lines\n      expect(lines.join('\\n')).toMatch('www.example.com')\n    })\n\n    it('should show floating window on cursor hold', async () => {\n      helper.updateConfiguration('diagnostic.messageTarget', 'float')\n      helper.updateConfiguration('diagnostic.messageDelay', 10)\n      await createDocument()\n      await nvim.call('cursor', [1, 3])\n      let winid = await helper.waitFloat()\n      let bufnr = await nvim.call('nvim_win_get_buf', winid) as number\n      let buf = nvim.createBuffer(bufnr)\n      let lines = await buf.lines\n      expect(lines.join('\\n')).toMatch('error')\n    })\n\n    it('should filter diagnostics by messageLevel', async () => {\n      helper.updateConfiguration('diagnostic.messageLevel', 'error')\n      helper.updateConfiguration('diagnostic.messageTarget', 'echo')\n      await createDocument()\n      await nvim.call('cursor', [1, 6])\n      await manager.echoCurrentMessage()\n      let line = await helper.getCmdline()\n      expect(line.indexOf('warning')).toBe(-1)\n    })\n\n    it('should echo messages on CursorHold', async () => {\n      helper.updateConfiguration('diagnostic.messageTarget', 'echo')\n      await createDocument()\n      await nvim.call('cursor', [1, 3])\n      await helper.waitValue(async () => {\n        let line = await helper.getCmdline()\n        return line.length > 0\n      }, true)\n    })\n\n    it('should not echo messages on CursorHold', async () => {\n      await nvim.command('echo \"\"')\n      helper.updateConfiguration('diagnostic.enableMessage', 'never')\n      await createDocument()\n      await nvim.call('cursor', [1, 3])\n      await helper.wait(30)\n      let line = await helper.getCmdline()\n      expect(line).toBe('')\n    })\n\n    it('should show diagnostics of current line', async () => {\n      helper.updateConfiguration('diagnostic.checkCurrentLine', true)\n      await createDocument()\n      await nvim.call('cursor', [1, 3])\n      let winid = await helper.waitFloat()\n      let win = nvim.createWindow(winid)\n      let buf = await win.buffer\n      let lines = await buf.lines\n      expect(lines.length).toBe(3)\n    })\n\n    it('should filter diagnostics by level', async () => {\n      helper.updateConfiguration('diagnostic.level', 'warning')\n      let doc = await createDocument()\n      let item = manager.getItem(doc.bufnr)\n      let diagnosticsMap = manager.getDiagnostics(item)\n      for (let diagnostics of Object.values(diagnosticsMap)) {\n        for (let diagnostic of diagnostics) {\n          expect(diagnostic.severity != DiagnosticSeverity.Hint).toBe(true)\n          expect(diagnostic.severity != DiagnosticSeverity.Information).toBe(true)\n        }\n      }\n    })\n\n    it('should send ale diagnostic items', async () => {\n      helper.updateConfiguration('diagnostic.displayByAle', true)\n      let content = `\n    function! MockAleResults(bufnr, collection, items)\n      let g:collection = a:collection\n      let g:items = a:items\n    endfunction\n    `\n      let file = await createTmpFile(content)\n      await nvim.command(`source ${file}`)\n      await createDocument()\n      await helper.waitValue(async () => {\n        let items = await nvim.getVar('items') as any\n        return Array.isArray(items)\n      }, true)\n      await nvim.command('bd!')\n      await helper.waitFor('eval', ['get(g:,\"items\",[])'], [])\n    })\n\n    it('should send to vim.diagnostic', async () => {\n      helper.updateConfiguration('diagnostic.displayByVimDiagnostic', true)\n\n      let doc = await createDocument()\n      let buf = nvim.createBuffer(doc.bufnr)\n      let items = await buf.getVar('coc_diagnostic_map') as any\n      expect(items.length).toBe(5)\n\n      let res = await nvim.lua('return vim.diagnostic.get()') as any[]\n      expect(res.length).toBe(5)\n      expect(res[0].severity).toBe(1)\n      expect(res[0].message).toBe('error')\n      expect(res[1].source).toBe('test')\n    })\n  })\n\n  describe('diagnostic util', () => {\n    it('should get severity level', () => {\n      expect(severityLevel('hint')).toBe(DiagnosticSeverity.Hint)\n      expect(severityLevel('error')).toBe(DiagnosticSeverity.Error)\n      expect(severityLevel('warning')).toBe(DiagnosticSeverity.Warning)\n      expect(severityLevel('information')).toBe(DiagnosticSeverity.Information)\n      expect(severityLevel('')).toBe(DiagnosticSeverity.Hint)\n    })\n\n    it('should get Coc severity name', () => {\n      expect(getNameFromSeverity(null as any)).toBe('CocError')\n      expect(getNameFromSeverity(DiagnosticSeverity.Error)).toBe('CocError')\n      expect(getNameFromSeverity(DiagnosticSeverity.Warning)).toBe('CocWarning')\n      expect(getNameFromSeverity(DiagnosticSeverity.Information)).toBe('CocInfo')\n      expect(getNameFromSeverity(DiagnosticSeverity.Hint)).toBe('CocHint')\n    })\n\n    it('should get severity name', () => {\n      expect(getSeverityName(DiagnosticSeverity.Error)).toBe('Error')\n      expect(getSeverityName(DiagnosticSeverity.Warning)).toBe('Warning')\n      expect(getSeverityName(DiagnosticSeverity.Information)).toBe('Information')\n      expect(getSeverityName(DiagnosticSeverity.Hint)).toBe('Hint')\n    })\n\n    it('should get severity type', () => {\n      expect(getSeverityType(DiagnosticSeverity.Error)).toBe('E')\n      expect(getSeverityType(DiagnosticSeverity.Warning)).toBe('W')\n      expect(getSeverityType(DiagnosticSeverity.Information)).toBe('I')\n      expect(getSeverityType(DiagnosticSeverity.Hint)).toBe('I')\n    })\n\n    it('should sort diagnostics', () => {\n      let diagnostics: Diagnostic[] = [\n        { range: Range.create(1, 0, 1, 10), message: 'a', severity: DiagnosticSeverity.Warning },\n        { range: Range.create(0, 0, 0, 10), message: 'b', severity: DiagnosticSeverity.Error },\n        { range: Range.create(0, 0, 0, 10), message: 'c', severity: DiagnosticSeverity.Error, source: 'c' },\n        { range: Range.create(0, 0, 0, 10), message: 'd', severity: DiagnosticSeverity.Error, source: 'd' },\n      ]\n      diagnostics.sort(sortDiagnostics)\n      expect(diagnostics.map(d => d.message)).toEqual(['c', 'd', 'b', 'a'])\n    })\n\n    it('should get highlight group', () => {\n      let diagnostic: Diagnostic = {\n        range: Range.create(0, 0, 0, 10),\n        message: 'error message',\n        severity: DiagnosticSeverity.Error,\n        tags: [DiagnosticTag.Deprecated, DiagnosticTag.Unnecessary]\n      }\n      let groups = getHighlightGroup(diagnostic)\n      expect(groups).toContain('CocDeprecatedHighlight')\n      expect(groups).toContain('CocUnusedHighlight')\n      expect(groups).toContain('CocErrorHighlight')\n    })\n  })\n\n  describe('toggleDiagnosticBuffer', () => {\n    it('should not throw when bufnr is invliad or disabled', async () => {\n      let doc = await workspace.document\n      await helper.doAction('diagnosticToggleBuffer', 99)\n      helper.updateConfiguration('diagnostic.enable', false)\n      await manager.toggleDiagnosticBuffer(doc.bufnr)\n    })\n\n    it('should toggle current buffer', async () => {\n      let doc = await workspace.document\n      await manager.toggleDiagnosticBuffer()\n      let buf = nvim.createBuffer(doc.bufnr)\n      let res = await buf.getVar('coc_diagnostic_disable') as any\n      expect(res).toBe(1)\n    })\n\n    it('should toggle diagnostics for buffer', async () => {\n      let doc = await createDocument()\n      await manager.toggleDiagnosticBuffer(doc.bufnr)\n      let buf = nvim.createBuffer(doc.bufnr)\n      let res = await buf.getVar('coc_diagnostic_info') as any\n      expect(res == null).toBe(true)\n      await manager.toggleDiagnosticBuffer(doc.bufnr, 1)\n      res = await buf.getVar('coc_diagnostic_info') as any\n      expect(res.error).toBe(2)\n    })\n  })\n\n  describe('refresh', () => {\n    beforeEach(() => {\n      helper.updateConfiguration('diagnostic.autoRefresh', false)\n    })\n\n    it('should refresh by bufnr', async () => {\n      let doc = await createDocument()\n      let buf = nvim.createBuffer(doc.bufnr)\n      let res = await buf.getVar('coc_diagnostic_info') as any\n      // should not refresh\n      expect(res == null).toBe(true)\n      await manager.refresh(doc.bufnr)\n      await helper.waitValue(async () => {\n        let res = await buf.getVar('coc_diagnostic_info') as any\n        return res?.error\n      }, 2)\n      await manager.refresh(99)\n    })\n\n    it('should refresh all buffers', async () => {\n      let uris = ['one', 'two'].map(s => URI.file(path.join(os.tmpdir(), s)).toString())\n      await workspace.loadFile(uris[0], 'tabe')\n      await workspace.loadFile(uris[1], 'tabe')\n      let collection = manager.create('tmp')\n      collection.set([[uris[0], [createDiagnostic('Error one')]], [uris[1], [createDiagnostic('Error two')]]])\n      await helper.doAction('diagnosticRefresh')\n      let bufnrs = [workspace.getDocument(uris[0]).bufnr, workspace.getDocument(uris[1]).bufnr]\n      for (let bufnr of bufnrs) {\n        let buf = nvim.createBuffer(bufnr)\n        let res = await buf.getVar('coc_diagnostic_info') as any\n        expect(res?.error).toBe(1)\n      }\n      collection.dispose()\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/dialog.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport events from '../../events'\nimport { Dialog, DialogButton } from '../../model/dialog'\nimport Notification from '../../model/notification'\nimport ProgressNotification from '../../model/progress'\nimport helper from '../helper'\n\nlet nvim: Neovim\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  await helper.reset()\n})\n\ndescribe('Dialog module', () => {\n  it('should show dialog', async () => {\n    let dialog = new Dialog(nvim, { content: '你好' })\n    expect(await dialog.winid).toBeNull()\n    await dialog.show({})\n    let winid = await dialog.winid\n    let win = nvim.createWindow(winid)\n    let width = await win.width\n    expect(width).toBe(4)\n    await nvim.call('coc#float#close', [winid])\n  })\n\n  it('should invoke callback with index -1', async () => {\n    let callback = jest.fn()\n    let dialog = new Dialog(nvim, { content: '你好', callback, highlights: [] })\n    await dialog.show({})\n    let winid = await dialog.winid\n    await nvim.call('coc#float#close', [winid])\n    await helper.wait(50)\n    expect(callback).toHaveBeenCalledWith(-1)\n  })\n\n  it('should invoke callback on click', async () => {\n    let callback = jest.fn()\n    let buttons: DialogButton[] = [{\n      index: 0,\n      text: 'yes'\n    }, {\n      index: 1,\n      text: 'no'\n    }]\n    let dialog = new Dialog(nvim, { content: '你好', buttons, callback })\n    await dialog.show({})\n    let winid = await dialog.winid\n    let btnwin = await nvim.call('coc#float#get_related', [winid, 'buttons'])\n    await nvim.call('win_gotoid', [btnwin])\n    await nvim.call('cursor', [2, 1])\n    await nvim.call('coc#float#nvim_float_click', [])\n    await helper.wait(20)\n    expect(callback).toHaveBeenCalledWith(0)\n  })\n})\n\ndescribe('Notification', () => {\n  it('should invoke callback', async () => {\n    let n = new Notification(nvim, { content: 'foo\\nbar' })\n    await n.show({})\n    await events.fire('FloatBtnClick', [n.bufnr, 1])\n    n.dispose()\n    let called = false\n    n = new Notification(nvim, {\n      content: 'foo\\nbar',\n      buttons: [{ index: 1, text: 'text' }, { index: 2, text: 'disabled', disabled: true }],\n      callback: () => {\n        called = true\n      }\n    })\n    await n.show({ border: true })\n    await events.fire('FloatBtnClick', [n.bufnr, 0])\n    expect(called).toBe(true)\n  })\n})\n\ndescribe('ProgressNotification', () => {\n  it('should cancel on window close', async () => {\n    let n = new ProgressNotification(nvim, {\n      cancellable: true,\n      task: (_progress, token) => {\n        return new Promise(resolve => {\n          token.onCancellationRequested(() => {\n            resolve(undefined)\n          })\n        })\n      }\n    })\n    await n.show({})\n    let p = new Promise(resolve => {\n      n.onDidFinish(e => {\n        resolve(e)\n      })\n    })\n    await nvim.call('coc#float#close_all', [])\n    let res = await p\n    expect(res).toBeUndefined()\n  })\n\n  it('should not fire event when disposed', async () => {\n    let fn = async (success: boolean) => {\n      let n = new ProgressNotification(nvim, {\n        cancellable: true,\n        task: () => {\n          return new Promise((resolve, reject) => {\n            if (success) {\n              setTimeout(resolve, 20)\n            } else {\n              setTimeout(() => {\n                reject(new Error('timeout'))\n              }, 20)\n            }\n          })\n        }\n      })\n      let times = 0\n      n.onDidFinish(() => {\n        times++\n      })\n      await n.show({})\n      n.dispose()\n      await helper.wait(20)\n      expect(times).toBe(0)\n    }\n    await fn(true)\n    await fn(false)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/document.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport fs from 'fs'\nimport path from 'path'\nimport { Position, Range, TextEdit } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { URI } from 'vscode-uri'\nimport Document, { getNotAttachReason, getUri } from '../../model/document'\nimport { computeLinesOffsets, firstDiffLine, LinesTextDocument } from '../../model/textdocument'\nimport { Disposable, disposeAll } from '../../util'\nimport { applyEdits, filterSortEdits } from '../../util/textedit'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\n\nfunction createTextDocument(lines: string[], eol = true): LinesTextDocument {\n  return new LinesTextDocument('file://a', 'txt', 1, lines, 1, eol)\n}\n\nasync function setLines(doc: Document, lines: string[]): Promise<void> {\n  let edit = TextEdit.insert(Position.create(0, 0), lines.join('\\n'))\n  await doc.applyEdits([edit])\n}\n\ndescribe('LinesTextDocument', () => {\n  it('should get first diff line ', async () => {\n    {\n      let res = firstDiffLine(['a', 'b'], ['a', 'b'])\n      expect(res).toBeUndefined()\n    }\n    {\n      let res = firstDiffLine(['a', 'c'], ['a', 'b'])\n      expect(res).toEqual([2, 'c', 'b'])\n    }\n    {\n      let res = firstDiffLine(['a'], ['a', 'b'])\n      expect(res).toEqual([2, '', 'b'])\n    }\n    {\n      let res = firstDiffLine(['a', 'b'], ['a'])\n      expect(res).toEqual([2, 'b', ''])\n    }\n  })\n\n  it('should apply edits', () => {\n    let textDocument = new LinesTextDocument('', '', 1, [\n      'use std::io::Result;'\n    ], 1, true)\n    // 1234567890\n    let edits = [\n      { range: { start: { line: 0, character: 7 }, end: { line: 0, character: 11 } }, newText: \"\" },\n      { range: { start: { line: 0, character: 13 }, end: { line: 0, character: 19 } }, newText: \"io\" },\n      { range: { start: { line: 0, character: 19 }, end: { line: 0, character: 19 } }, newText: \"::\" },\n      {\n        range: { start: { line: 0, character: 19 }, end: { line: 0, character: 19 } }, newText: \"{Result, Error}\"\n      }\n    ]\n    edits = filterSortEdits(textDocument, edits)\n    let res = applyEdits(textDocument, edits)\n    expect(res).toEqual(['use std::io::{Result, Error};'])\n    textDocument = new LinesTextDocument('', '', 1, [''], 1, true)\n    res = applyEdits(textDocument, [TextEdit.replace(Range.create(0, 0, 1, 0), '')])\n    expect(res).toEqual([''])\n  })\n\n  it('should throw for overlapping edits', () => {\n    let textDocument = new LinesTextDocument('', '', 1, [\n      'use std::io::Result;'\n    ], 1, true)\n    let edits = [\n      { range: { start: { line: 0, character: 1 }, end: { line: 0, character: 3 } }, newText: \"foo\" },\n      { range: { start: { line: 0, character: 2 }, end: { line: 0, character: 5 } }, newText: \"new\" }\n    ]\n    expect(() => {\n      applyEdits(textDocument, edits)\n    }).toThrow()\n  })\n\n  it('should return undefined when not changed', () => {\n    let textDocument = new LinesTextDocument('', '', 1, [\n      'foo bar'\n    ], 1, true)\n    let edits = [\n      { range: { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } }, newText: \"f\" },\n      { range: { start: { line: 0, character: 2 }, end: { line: 0, character: 3 } }, newText: \"o\" }\n    ]\n    let res = applyEdits(textDocument, edits)\n    expect(res).toBeUndefined()\n  })\n\n  it('should get length', () => {\n    let doc = createTextDocument(['foo'])\n    expect(doc.length).toBe(4)\n    expect(doc.getText().length).toBe(4)\n    expect(doc.length).toBe(4)\n    doc = createTextDocument(['foo'], false)\n    expect(doc.length).toBe(3)\n  })\n\n  it('should getText by range', () => {\n    let doc = createTextDocument(['foo', 'bar'])\n    expect(doc.getText(Range.create(0, 0, 0, 1))).toBe('f')\n    expect(doc.getText(Range.create(0, 0, 1, 0))).toBe('foo\\n')\n  })\n\n  it('should get positionAt', () => {\n    let doc = createTextDocument([], false)\n    expect(doc.positionAt(0)).toEqual(Position.create(0, 0))\n  })\n\n  it('should get offsetAt', () => {\n    let doc = createTextDocument([''], false)\n    expect(doc.offsetAt(Position.create(1, 0))).toBe(0)\n    expect(doc.offsetAt({ line: -1, character: -1 })).toBe(0)\n  })\n\n  it('should work when eol enabled', () => {\n    let doc = createTextDocument(['foo', 'bar'])\n    expect(doc.lineCount).toBe(3)\n    let content = doc.getText()\n    expect(content).toBe('foo\\nbar\\n')\n    content = doc.getText(Range.create(0, 0, 0, 3))\n    expect(content).toBe('foo')\n    let textLine = doc.lineAt(0)\n    expect(textLine.text).toBe('foo')\n    textLine = doc.lineAt(Position.create(0, 3))\n    expect(textLine.text).toBe('foo')\n    let pos = doc.positionAt(4)\n    expect(pos).toEqual({ line: 1, character: 0 })\n    content = doc.getText(Range.create(0, 0, 0, 3))\n    expect(content).toBe('foo')\n    let offset = doc.offsetAt(Position.create(0, 4))\n    expect(offset).toBe(4)\n    offset = doc.offsetAt(Position.create(2, 1))\n    expect(offset).toBe(8)\n    expect(doc.end).toEqual(Position.create(2, 0))\n  })\n\n  it('should throw for invalid line', () => {\n    let doc = createTextDocument(['foo', 'bar'])\n    let fn = () => {\n      doc.lineAt(-1)\n    }\n    expect(fn).toThrow(Error)\n    fn = () => {\n      doc.lineAt(3)\n    }\n    expect(fn).toThrow(Error)\n  })\n\n  it('should work when eol disabled', () => {\n    let doc = new LinesTextDocument('file://a', 'txt', 1, ['foo'], 1, false)\n    expect(doc.getText()).toBe('foo')\n    expect(doc.lineCount).toBe(1)\n    expect(doc.end).toEqual(Position.create(0, 3))\n  })\n\n  it('should computeLinesOffsets', () => {\n    expect(computeLinesOffsets(['foo'], true)).toEqual([0, 4])\n    expect(computeLinesOffsets(['foo'], false)).toEqual([0])\n  })\n\n  it('should get uri for unknown buftype', () => {\n    let res = getUri('foo', 3, '')\n    expect(res).toBe('unknown:3')\n    res = getUri('foo', 3, 'terminal')\n    expect(res).toEqual('terminal:3')\n    res = getUri(__filename, 3, 'terminal')\n    expect(URI.parse(res).fsPath).toBe(__filename)\n  })\n\n  it('should work with line not last one', () => {\n    let doc = createTextDocument(['foo', 'bar'])\n    let textLine = doc.lineAt(0)\n    expect(textLine.lineNumber).toBe(0)\n    expect(textLine.text).toBe('foo')\n    expect(textLine.range).toEqual(Range.create(0, 0, 0, 3))\n    expect(textLine.rangeIncludingLineBreak).toEqual(Range.create(0, 0, 1, 0))\n    expect(textLine.isEmptyOrWhitespace).toBe(false)\n  })\n\n  it('should work with last line', () => {\n    let doc = createTextDocument(['foo', 'bar'])\n    let textLine = doc.lineAt(2)\n    expect(textLine.rangeIncludingLineBreak).toEqual(Range.create(2, 0, 2, 0))\n  })\n\n  it('should not attach when size exceeded', async () => {\n    let reason = getNotAttachReason('', 1, 99)\n    expect(reason).toMatch('exceed')\n  })\n\n  it('should get intersect range', async () => {\n    let doc = createTextDocument(['foo', 'bar'])\n    let res = doc.intersectWith(Range.create(0, 0, 2, 1))\n    expect(res).toEqual(Range.create(0, 0, 2, 0))\n  })\n})\n\ndescribe('Document', () => {\n  beforeAll(async () => {\n    await helper.setup()\n    nvim = helper.nvim\n  })\n\n  afterAll(async () => {\n    await helper.shutdown()\n  })\n\n  afterEach(async () => {\n    await helper.reset()\n  })\n\n  describe('properties', () => {\n    it('should get languageId', async () => {\n      await nvim.command(`edit +setl\\\\ filetype=txt.vim foo`)\n      let doc = await workspace.document\n      expect(doc.languageId).toBe('txt')\n    })\n\n    it('should parse iskeyword of character range', async () => {\n      await nvim.setOption('iskeyword', 'a-z,A-Z,48-57,_')\n      let opt = await nvim.getOption('iskeyword')\n      expect(opt).toBe('a-z,A-Z,48-57,_')\n    })\n\n    it('should get start word', async () => {\n      let doc = await workspace.document\n      expect(doc.getStartWord('abc def')).toBe('abc')\n      expect(doc.getStartWord('x')).toBe('x')\n      expect(doc.getStartWord(' ')).toBe('')\n      expect(doc.getStartWord('')).toBe('')\n    })\n\n    it('should get word range', async () => {\n      let doc = await workspace.document\n      await nvim.setLine('foo bar#')\n      await doc.synchronize()\n      let range = doc.getWordRangeAtPosition({ line: 0, character: 0 })\n      expect(range).toEqual(Range.create(0, 0, 0, 3))\n      range = doc.getWordRangeAtPosition({ line: 0, character: 3 })\n      expect(range).toBeNull()\n      range = doc.getWordRangeAtPosition({ line: 0, character: 4 })\n      expect(range).toEqual(Range.create(0, 4, 0, 7))\n      range = doc.getWordRangeAtPosition({ line: 0, character: 7 })\n      expect(range).toBeNull()\n      range = doc.getWordRangeAtPosition({ line: 0, character: 7 }, '#')\n      expect(range).toEqual(Range.create(0, 4, 0, 8))\n    })\n\n    it('should fix start col', async () => {\n      let doc = await workspace.document\n      expect(doc.fixStartcol(Position.create(0, 3), ['#'])).toBe(0)\n      await nvim.setLine('foo #def')\n      expect(doc.fixStartcol(Position.create(0, 6), ['#'])).toBe(4)\n    })\n\n    it('should get lines', async () => {\n      let doc = await workspace.document\n      let lines = doc.getLines()\n      expect(lines).toEqual([''])\n    })\n\n    it('should add additional keywords', async () => {\n      await nvim.command(`edit foo | let b:coc_additional_keywords=['#']`)\n      let doc = await workspace.document\n      expect(doc.isWord('#')).toBe(true)\n    })\n\n    it('should check has changed', async () => {\n      let doc = await workspace.document\n      expect(doc.hasChanged).toBe(false)\n      await nvim.setLine('foo bar')\n      await helper.waitValue(() => {\n        return doc.hasChanged\n      }, false)\n    })\n\n    it('should get symbol ranges', async () => {\n      let doc = await workspace.document\n      await nvim.setLine('-foo bar foo')\n      let ranges = doc.getSymbolRanges('foo')\n      expect(ranges.length).toBe(2)\n    })\n\n    it('should get current line', async () => {\n      let doc = await workspace.document\n      await setLines(doc, ['first line', 'second line'])\n      let line = doc.getline(1, true)\n      expect(line).toBe('second line')\n      line = doc.getline(0, false)\n      expect(line).toBe('first line')\n    })\n\n    it('should get variable form buffer', async () => {\n      await nvim.command('autocmd BufNewFile,BufRead * let b:coc_variable = 1')\n      let doc = await helper.createDocument()\n      let val = doc.getVar<number>('variable')\n      expect(val).toBe(1)\n    })\n\n    it('should attach change events', async () => {\n      let doc = await workspace.document\n      await nvim.setLine('abc')\n      await doc.patchChange()\n      let content = doc.getDocumentContent()\n      expect(content.indexOf('abc')).toBe(0)\n    })\n\n    it('should not attach change events when b:coc_enabled is false', async () => {\n      nvim.command('edit t|let b:coc_enabled = 0', true)\n      let doc = await workspace.document\n      let val = doc.getVar<number>('enabled', 0)\n      expect(val).toBe(0)\n      await nvim.setLine('abc')\n      await doc.patchChange()\n      let content = doc.getDocumentContent()\n      expect(content.indexOf('abc')).toBe(-1)\n      expect(doc.notAttachReason).toMatch('coc_enabled')\n    })\n\n    it('should attach nofile document by b:coc_force_attach', async () => {\n      nvim.command(`e +setl\\\\ buftype=nofile foo| let b:coc_force_attach = 1`, true)\n      let doc = await workspace.document\n      expect(doc.buftype).toBe('nofile')\n      expect(doc.attached).toBe(true)\n    })\n\n    it('should not attach nofile buffer', async () => {\n      nvim.command('edit t|setl buftype=nofile', true)\n      let doc = await workspace.document\n      expect(doc.notAttachReason).toMatch('nofile')\n    })\n\n    it('should get lineCount, previewwindow, winid', async () => {\n      let doc = await workspace.document\n      let { lineCount, winid } = doc\n      expect(lineCount).toBe(1)\n      expect(winid != -1).toBe(true)\n    })\n  })\n\n  describe('attach()', () => {\n    it('should not attach when buffer not loaded', async () => {\n      await nvim.command('tabe foo | doautocmd CursorHold')\n      let doc = await workspace.document\n      let spy = jest.spyOn(doc.buffer, 'attach').mockImplementation(() => {\n        return Promise.reject(new Error('detached'))\n      })\n      doc.attach()\n      spy.mockRestore()\n      await nvim.command(`bd ${doc.bufnr}`)\n      doc.attach()\n      await helper.wait(10)\n      expect(doc.attached).toBe(false)\n      await doc.synchronize()\n    })\n\n    it('should consider eol option', async () => {\n      await nvim.command('edit foo|setl noeol')\n      await nvim.setLine('foo')\n      let doc = await workspace.document\n      expect(typeof doc.hasChanged).toBe('boolean')\n      await doc.patchChange()\n      await helper.waitValue(() => doc.content, 'foo')\n    })\n  })\n\n  describe('applyEdits()', () => {\n    it('should not throw with old API', async () => {\n      let doc = await workspace.document\n      await doc.applyEdits(nvim as any, [] as any)\n      expect(doc.previewwindow).toBe(false)\n    })\n\n    it('should not apply when not change happens', async () => {\n      let doc = await workspace.document\n      let res = await doc.applyEdits([TextEdit.insert(Position.create(0, 0), '')])\n      expect(res).toBeUndefined()\n    })\n\n    it('should simple applyEdits', async () => {\n      let doc = await workspace.document\n      let edits: TextEdit[] = []\n      edits.push({\n        range: Range.create(0, 0, 0, 0),\n        newText: 'a\\n'\n      })\n      edits.push({\n        range: Range.create(0, 0, 0, 0),\n        newText: 'b\\n'\n      })\n      let edit = await doc.applyEdits(edits)\n      let content = doc.getDocumentContent()\n      expect(content).toBe('a\\nb\\n\\n')\n      await doc.applyEdits([edit])\n      expect(doc.getDocumentContent()).toEqual('\\n')\n    })\n\n    it('should return revert edit', async () => {\n      let doc = await workspace.document\n      let edit = await doc.applyEdits([TextEdit.replace(Range.create(0, 0, 0, 0), 'foo')])\n      expect(doc.getDocumentContent()).toBe('foo\\n')\n      edit = await doc.applyEdits([edit])\n      expect(doc.getDocumentContent()).toBe('\\n')\n      edit = await doc.applyEdits([edit])\n      expect(doc.getDocumentContent()).toBe('foo\\n')\n    })\n\n    it('should apply merged edits', async () => {\n      let doc = await workspace.document\n      await nvim.setLine('foo')\n      await doc.patchChange()\n      let edits: TextEdit[] = []\n      edits.push({\n        range: Range.create(0, 0, 0, 3),\n        newText: ''\n      })\n      edits.push({\n        range: Range.create(0, 0, 0, 0),\n        newText: 'bar'\n      })\n      let edit = await doc.applyEdits(edits)\n      let line = await nvim.line\n      expect(line).toBe('bar')\n      await doc.applyEdits([edit])\n      expect(doc.getDocumentContent()).toBe('foo\\n')\n    })\n\n    it('should apply textedit exceed end', async () => {\n      let doc = await workspace.document\n      let edits: TextEdit[] = []\n      edits.push({\n        range: Range.create(0, 0, 999999, 99999),\n        newText: 'foo\\n'\n      })\n      await doc.applyEdits(edits)\n      let content = doc.getDocumentContent()\n      expect(content).toBe('foo\\n')\n    })\n\n    it('should move cursor', async () => {\n      await nvim.input('ia')\n      await helper.wait(30)\n      let doc = await workspace.document\n      let edits: TextEdit[] = []\n      edits.push({\n        range: Range.create(0, 0, 0, 1),\n        newText: 'foo'\n      })\n      await doc.applyEdits(edits, false, true)\n      let cursor = await nvim.call('getcurpos') as number[]\n      expect(cursor[1]).toBe(1)\n      expect(cursor[2]).toBe(4)\n    })\n\n    it('should applyEdits with range not sorted', async () => {\n      let doc = await workspace.document\n      await doc.buffer.setLines([\n        'aa',\n        'bb',\n        'cc',\n        'dd'\n      ], { start: 0, end: -1, strictIndexing: false })\n      await doc.patchChange()\n      let edits = [\n        { range: { start: { line: 3, character: 0 }, end: { line: 3, character: 1 } }, newText: \"\" },\n        { range: { start: { line: 0, character: 2 }, end: { line: 1, character: 0 } }, newText: \"\" },\n      ]\n      await doc.applyEdits(edits)\n      let lines = await doc.buffer.lines\n      expect(lines).toEqual(['aabb', 'cc', 'd'])\n    })\n\n    it('should applyEdits with insert as same position', async () => {\n      let doc = await workspace.document\n      await doc.buffer.setLines([\n        'foo'\n      ], { start: 0, end: -1, strictIndexing: false })\n      await doc.patchChange()\n      let edits = [\n        { range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }, newText: 'aa' },\n        { range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }, newText: 'bb' },\n      ]\n      await doc.applyEdits(edits)\n      let lines = await nvim.call('getline', [1, '$'])\n      expect(lines).toEqual(['aabbfoo'])\n    })\n\n    it('should applyEdits with bad range', async () => {\n      let doc = await workspace.document\n      await doc.buffer.setLines([], { start: 0, end: -1, strictIndexing: false })\n      await doc.patchChange()\n      let edits = [{ range: { start: { line: -1, character: -1 }, end: { line: -1, character: -1 } }, newText: 'foo' },]\n      await doc.applyEdits(edits)\n      let lines = await nvim.call('getline', [1, '$'])\n      expect(lines).toEqual(['foo'])\n    })\n\n    it('should applyEdits with lines', async () => {\n      let doc = await workspace.document\n      await doc.buffer.setLines([\n        'aa',\n        'bb',\n        'cc',\n        'dd'\n      ], { start: 0, end: -1, strictIndexing: false })\n      await doc.patchChange()\n      let edits = [\n        { range: { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } }, newText: \"\" },\n        { range: { start: { line: 0, character: 2 }, end: { line: 1, character: 0 } }, newText: \"\" },\n      ]\n      await doc.applyEdits(edits)\n      let lines = await nvim.call('getline', [1, '$'])\n      expect(lines).toEqual(['abb', 'cc', 'dd'])\n    })\n\n    it('should applyEdits with changed lines', async () => {\n      let doc = await workspace.document\n      let buf = doc.buffer\n      const assertChange = async (sl, sc, el, ec, text, lines) => {\n        let r = Range.create(sl, sc, el, ec)\n        let edits = [TextEdit.replace(r, text)]\n        await doc.applyEdits(edits)\n        let curr = await buf.lines\n        expect(curr).toEqual(lines)\n      }\n      await nvim.setLine('a')\n      await doc.patchChange()\n      await assertChange(0, 1, 0, 1, '\\nb', ['a', 'b'])\n      await assertChange(1, 0, 2, 0, 'c\\n', ['a', 'c'])\n      await assertChange(1, 0, 2, 0, '', ['a'])\n      await assertChange(1, 0, 1, 0, 'b\\nc\\n', ['a', 'b', 'c'])\n      await assertChange(2, 0, 3, 0, 'e\\n', ['a', 'b', 'e'])\n    })\n\n    it('should apply single textedit', async () => {\n      let doc = await workspace.document\n      let buf = doc.buffer\n      const assertChange = async (sl, sc, el, ec, text, lines) => {\n        let r = Range.create(sl, sc, el, ec)\n        let edits = [TextEdit.replace(r, text)]\n        await doc.applyEdits(edits)\n        let curr = await buf.lines\n        expect(curr).toEqual(lines)\n      }\n      await nvim.setLine('foo')\n      await doc.patchChange()\n      await assertChange(1, 0, 1, 0, 'bar', ['foo', 'bar'])\n      await assertChange(2, 0, 2, 0, 'do\\n', ['foo', 'bar', 'do'])\n      await assertChange(2, 1, 3, 0, '', ['foo', 'bar', 'd'])\n      await assertChange(2, 0, 3, 0, 'if', ['foo', 'bar', 'if'])\n      await assertChange(2, 0, 2, 2, 'x', ['foo', 'bar', 'x'])\n    })\n\n    it('should apply multiple edits', async () => {\n      let arr = new Array(200)\n      arr.fill('foo bar a b c d e')\n      let ranges: Range[] = []\n      let edits: TextEdit[] = []\n      for (let i = 0; i < arr.length; i++) {\n        ranges.push(Range.create(i, 0, i, 3))\n        ranges.push(Range.create(i, 4, i, 7))\n        ranges.push(Range.create(i, 8, i, 9))\n        ranges.push(Range.create(i, 10, i, 11))\n        ranges.push(Range.create(i, 12, i, 13))\n        ranges.push(Range.create(i, 14, i, 15))\n        ranges.push(Range.create(i, 16, i, 17))\n        edits.push(TextEdit.insert(Position.create(i, 0), `${i + 1} `))\n      }\n      let doc = await helper.createDocument()\n      let buf = doc.buffer\n      await buf.setLines(arr)\n      buf.highlightRanges('test', 'MoreMsg', ranges)\n      await doc.patchChange()\n      await doc.applyEdits(edits)\n    })\n\n    it('should consider latest change', async () => {\n      let doc = await helper.createDocument()\n      let buf = doc.buffer\n      {\n        let edits: TextEdit[] = [TextEdit.insert(Position.create(0, 0), 'bar')]\n        nvim.call('setline', [1, 'foo'], true)\n        await doc.applyEdits(edits)\n        let line = await nvim.line\n        expect(line).toBe('barfoo')\n      }\n      {\n        await buf.setLines(['  foo'])\n        await doc.patchChange()\n        nvim.call('setline', [1, '  fooa'], true)\n        nvim.call('cursor', [1, 7], true)\n        let edits: TextEdit[] = [TextEdit.del(Range.create(0, 0, 0, 1))]\n        await doc.applyEdits(edits)\n        let line = await nvim.line\n        expect(line).toBe(' fooa')\n      }\n      {\n        await buf.setLines(['foo'])\n        await nvim.call('cursor', [1, 3])\n        await doc.synchronize()\n        nvim.call('setline', [1, 'fo'], true)\n        let edits: TextEdit[] = [TextEdit.insert(Position.create(0, 0), ' ')]\n        await doc.applyEdits(edits)\n        let line = await nvim.line\n        expect(line).toBe(' fo')\n      }\n    })\n  })\n\n  describe('changeLines()', () => {\n    it('should change lines', async () => {\n      let doc = await workspace.document\n      await doc.changeLines([[0, '']])\n      await doc.buffer.replace(['a', 'b', 'c'], 0)\n      await doc.changeLines([[0, 'd'], [2, 'f']])\n      let lines = await doc.buffer.lines\n      expect(lines).toEqual(['d', 'b', 'f'])\n    })\n  })\n\n  describe('getOffset()', () => {\n    it('should get offset', async () => {\n      let doc = await workspace.document\n      let offset = doc.getOffset(1, 0)\n      expect(offset).toBe(0)\n    })\n  })\n\n  describe('synchronize', () => {\n    it('should synchronize on lines change', async () => {\n      let document = await workspace.document\n      let doc = TextDocument.create('untitled:1', 'txt', 1, document.getDocumentContent())\n      let disposables = []\n      document.onDocumentChange(e => {\n        TextDocument.update(doc, e.contentChanges.slice(), 2)\n      }, null, disposables)\n      // document.on\n      await nvim.setLine('abc')\n      document.forceSync()\n      expect(doc.getText()).toBe('abc\\n')\n      disposeAll(disposables)\n    })\n\n    it('should synchronize changes after applyEdits', async () => {\n      let document = await workspace.document\n      let doc = TextDocument.create('untitled:1', 'txt', 1, document.getDocumentContent())\n      let disposables = []\n      document.onDocumentChange(e => {\n        TextDocument.update(doc, e.contentChanges.slice(), e.textDocument.version)\n      }, null, disposables)\n      await nvim.setLine('abc')\n      await document.patchChange()\n      await document.applyEdits([TextEdit.insert({ line: 0, character: 0 }, 'd')])\n      expect(doc.getText()).toBe('dabc\\n')\n      disposeAll(disposables)\n    })\n\n    it('should consider empty lines', async () => {\n      let document = await workspace.document\n      await nvim.call('setline', [1, ['foo', 'bar']])\n      await document.patchChange()\n      await nvim.command('normal! ggdG')\n      await nvim.call('append', [1, ['foo', 'bar']])\n      await document.patchChange()\n      let lines = document.textDocument.lines\n      expect(lines).toEqual(['', 'foo', 'bar'])\n    })\n  })\n\n  describe('recreate', () => {\n    async function assertDocument(fn: (doc: Document) => Promise<void>): Promise<void> {\n      let disposables: Disposable[] = []\n      let fsPath = path.join(__dirname, 'document.txt')\n      fs.writeFileSync(fsPath, '{\\nfoo\\n}\\n', 'utf8')\n      await helper.edit(fsPath)\n      let document = await workspace.document\n      document.forceSync()\n      let doc = TextDocument.create(document.uri, 'txt', document.version, document.getDocumentContent())\n      let uri = doc.uri\n      workspace.onDidOpenTextDocument(e => {\n        if (e.uri == uri) {\n          doc = TextDocument.create(e.uri, 'txt', e.version, e.getText())\n        }\n      }, null, disposables)\n      workspace.onDidCloseTextDocument(e => {\n        if (e.uri == doc.uri) doc = null\n      }, null, disposables)\n      workspace.onDidChangeTextDocument(e => {\n        TextDocument.update(doc, e.contentChanges.slice(), e.textDocument.version)\n      }, null, disposables)\n      await fn(document)\n      document = await workspace.document\n      document.forceSync()\n      let text = document.getDocumentContent()\n      expect(doc).toBeDefined()\n      expect(doc.getText()).toBe(text)\n      disposeAll(disposables)\n      fs.unlinkSync(fsPath)\n    }\n\n    it('should synchronize after make changes', async () => {\n      await assertDocument(async () => {\n        await nvim.call('setline', [1, 'a'])\n        await nvim.call('setline', [2, 'b'])\n      })\n    })\n\n    it('should synchronize after edit', async () => {\n      await assertDocument(async doc => {\n        let fsPath = URI.parse(doc.uri).fsPath\n        fs.writeFileSync(fsPath, '{\\n}\\n', 'utf8')\n        await nvim.command('edit')\n        await nvim.call('deletebufline', [doc.bufnr, 1])\n        doc = await workspace.document\n        let content = doc.getDocumentContent()\n        expect(content).toBe('}\\n')\n      })\n    })\n\n    it('should synchronize after force edit', async () => {\n      await assertDocument(async doc => {\n        let fsPath = URI.parse(doc.uri).fsPath\n        fs.writeFileSync(fsPath, '{\\n}\\n', 'utf8')\n        await nvim.command('edit')\n        await nvim.call('deletebufline', [doc.bufnr, 1])\n        doc = await workspace.document\n        let content = doc.getDocumentContent()\n        expect(content).toBe('}\\n')\n      })\n    })\n  })\n\n  describe('applyEdits', () => {\n    it('should synchronize on enter', async () => {\n      let doc = await workspace.document\n      await doc.buffer.setLines(['foox', 'bar'])\n      await nvim.call('cursor', [1, 2])\n      await nvim.input('a')\n      await doc.synchronize()\n      void nvim.input('<cr>x')\n      await doc.applyEdits([{\n        range: Range.create(0, 0, 1, 3),\n        newText: '\"foox\"\\n\"bar\"'\n      }])\n      await helper.waitFor('getline', ['.'], 'xox\"')\n      let lines = await doc.buffer.lines\n      expect(lines).toEqual(['\"fo', 'xox\"', '\"bar\"'])\n    })\n\n    it('should synchronize content add on apply', async () => {\n      let doc = await workspace.document\n      await doc.buffer.setLines(['aaa', 'bbb', 'ccc'])\n      await nvim.call('cursor', [2, 1])\n      void nvim.input('Ab')\n      await doc.applyEdits([{\n        range: Range.create(0, 0, 0, 0),\n        newText: '1'\n      }, {\n        range: Range.create(1, 0, 1, 0),\n        newText: '2'\n      }, {\n        range: Range.create(2, 0, 2, 0),\n        newText: '3'\n      }, {\n        range: Range.create(2, 3, 2, 3),\n        newText: '\\nfoo'\n      }])\n      await helper.waitFor('getline', ['.'], '2bbbb')\n      let lines = doc.getLines()\n      expect(lines).toEqual(['1aaa', '2bbbb', '3ccc', 'foo'])\n    })\n\n    it('should synchronize content change on multiple lines change', async () => {\n      let arr = (new Array(40)).fill('')\n      let doc = await workspace.document\n      await doc.buffer.setLines(arr)\n      await nvim.call('cursor', [1, 1])\n      let edits: TextEdit[] = []\n      let contents = []\n      for (let i = 0; i < arr.length; i++) {\n        edits.push(TextEdit.insert(Position.create(i, 0), `${i}`))\n        contents.push(`${i}`)\n      }\n      void nvim.input('Ax')\n      await doc.applyEdits(edits)\n      await helper.waitFor('getline', ['.'], '0x')\n      contents[0] = '0x'\n      let lines = doc.getLines()\n      expect(lines).toEqual(contents)\n    })\n\n    it('should synchronize content delete', async () => {\n      let doc = await workspace.document\n      await doc.buffer.setLines(['foo f', 'bar'])\n      await doc.synchronize()\n      await nvim.command('normal! ^2l')\n      void nvim.input('a<backspace>')\n      await doc.applyEdits([{\n        range: Range.create(0, 0, 1, 3),\n        newText: 'foo foo'\n      }])\n      await helper.waitFor('getline', ['.'], 'fo foo')\n      let lines = await doc.buffer.lines\n      expect(lines).toEqual(['fo foo'])\n    })\n  })\n\n  describe('highlights', () => {\n    it('should add highlights to document', async () => {\n      let buf = await nvim.buffer\n      await buf.setLines(['你好', 'world'], { start: 0, end: -1, strictIndexing: false })\n      let ranges = [\n        Range.create(0, 0, 0, 2),\n        Range.create(1, 0, 1, 3)\n      ]\n      let ns = await nvim.createNamespace('coc-highlight')\n      nvim.pauseNotification()\n      buf.highlightRanges('highlight', 'Search', ranges)\n      await nvim.resumeNotification()\n      let markers = await buf.getExtMarks(ns, 0, -1)\n      expect(markers.length).toBe(2)\n      nvim.pauseNotification()\n      buf.clearNamespace('highlight')\n      await nvim.resumeNotification()\n      markers = await buf.getExtMarks(ns, 0, -1)\n      expect(markers.length).toBe(0)\n    })\n\n    it('should add and clear highlights of current window', async () => {\n      let buf = await nvim.buffer\n      await buf.setLines(['你好', 'world'], { start: 0, end: -1, strictIndexing: false })\n      let win = await nvim.window\n      let ranges = [\n        Range.create(0, 0, 0, 2),\n        Range.create(1, 0, 1, 3)\n      ]\n      let res = await win.highlightRanges('Search', ranges)\n      expect(res.length).toBe(1)\n      let matches = await nvim.call('getmatches', [win.id]) as any\n      nvim.pauseNotification()\n      win.clearMatchGroup('Search')\n      await nvim.resumeNotification()\n      matches = await nvim.call('getmatches', [win.id])\n      expect(matches.length).toBe(0)\n    })\n\n    it('should clear matches by ids', async () => {\n      let buf = await nvim.buffer\n      await buf.setLines(['你好', 'world'], { start: 0, end: -1, strictIndexing: false })\n      let win = await nvim.window\n      let ranges = [\n        Range.create(0, 0, 0, 2),\n        Range.create(1, 0, 1, 3)\n      ]\n      let ids = await win.highlightRanges('Search', ranges)\n      nvim.pauseNotification()\n      win.clearMatches(ids)\n      await nvim.resumeNotification()\n      let matches = await nvim.call('getmatches', [win.id]) as any\n      expect(matches.length).toBe(0)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/events.test.ts",
    "content": "import { CancellationTokenSource, Disposable } from 'vscode-languageserver-protocol'\nimport events from '../../events'\nimport { disposeAll, wait } from '../../util'\nimport { CancellationError } from '../../util/errors'\n\nconst disposables: Disposable[] = []\nafterEach(async () => {\n  disposeAll(disposables)\n})\n\ndescribe('register handler', () => {\n  it('should fire InsertEnter and InsertLeave when necessary', async () => {\n    let fn = jest.fn()\n    events.on('InsertEnter', fn, null, disposables)\n    events.on('InsertLeave', fn, null, disposables)\n    expect(events.pumvisible).toBe(false)\n    expect(events.insertMode).toBe(false)\n    await events.fire('CursorMovedI', [1, [1, 1]])\n    expect(events.insertMode).toBe(false)\n    await events.fire('CursorMoved', [1, [1, 1]])\n    expect(events.insertMode).toBe(false)\n    expect(fn).toHaveBeenCalledTimes(2)\n  })\n\n  it('should fire only once', async () => {\n    let fn = jest.fn()\n    events.once('ready', () => {\n      fn()\n    })\n    await events.fire('ready', [])\n    await events.fire('ready', [])\n    await events.fire('ready', [])\n    expect(fn).toHaveBeenCalledTimes(1)\n  })\n\n  it('should fire visible event once', async () => {\n    let fn = jest.fn()\n    let event\n    events.once('WindowVisible', ev => {\n      event = ev\n      fn()\n    })\n    await events.fire('BufWinEnter', [1, 1000, [1, 2]])\n    await events.fire('WinScrolled', [1000, 2, [2, 3]])\n    await wait(20)\n    await events.fire('WinClosed', [1000])\n    expect(fn).toHaveBeenCalledTimes(1)\n    expect(event).toEqual({ bufnr: 2, winid: 1000, region: [2, 3] })\n  })\n\n  it('should cancel visible event', async () => {\n    let fn = jest.fn()\n    events.once('WindowVisible', () => {\n      fn()\n    })\n    await events.fire('BufWinEnter', [1, 1000])\n    await events.fire('WinClosed', [1000])\n    await wait(10)\n    expect(fn).toHaveBeenCalledTimes(0)\n  })\n\n  it('should track slow handler', async () => {\n    events.on('BufWritePre', async () => {\n      await wait(50)\n    }, null, disposables)\n    events.timeout = 20\n    events.requesting = true\n    await events.fire('BufWritePre', [1, '', 1])\n    events.requesting = false\n    events.timeout = 1000\n  })\n\n  it('should on throw on handler error', async () => {\n    events.on('BufWritePre', async () => {\n      throw new Error('test error')\n    }, null, disposables)\n    events.on('BufWritePre', () => {\n      throw new CancellationError()\n    }, null, disposables)\n    await events.fire('BufWritePre', [1, '', 1])\n  })\n\n  it('should register single handler', async () => {\n    let fn = jest.fn()\n    let obj = {}\n    let disposable = events.on('BufEnter', fn, obj)\n    disposables.push(disposable)\n    await events.fire('BufEnter', ['a', 'b'])\n    expect(fn).toHaveBeenCalledWith('a', 'b')\n  })\n\n  it('should register multiple events', async () => {\n    let fn = jest.fn()\n    let disposable = events.on(['TaskExit', 'TaskStderr'], fn)\n    disposables.push(disposable)\n    await events.fire('TaskExit', [])\n    await events.fire('TaskStderr', [])\n    expect(fn).toHaveBeenCalledTimes(2)\n  })\n\n  it('should resolve after timeout', async () => {\n    let fn = (): Promise<void> => new Promise(resolve => {\n      setTimeout(() => {\n        resolve()\n      }, 20)\n    })\n    let disposable = events.on('FocusGained', fn, {})\n    disposables.push(disposable)\n    let ts = Date.now()\n    await events.fire('FocusGained', [])\n    expect(Date.now() - ts >= 10).toBe(true)\n  })\n\n  it('should emit TextInsert after TextChangedI', async () => {\n    let arr: string[] = []\n    events.on('TextInsert', () => {\n      arr.push('insert')\n    }, null, disposables)\n    events.on('TextChangedI', () => {\n      arr.push('change')\n    }, null, disposables)\n    await events.fire('InsertCharPre', ['i', 1])\n    await events.fire('TextChangedI', [1, {\n      lnum: 1,\n      col: 2,\n      pre: 'i',\n      changedtick: 1,\n      line: 'i'\n    }])\n    expect(events.lastChangeTs).toBeDefined()\n    await events.race(['TextInsert'])\n    expect(arr).toEqual(['change', 'insert'])\n    await events.fire('ModeChanged', [{ old_mode: 'n', new_mode: 'i' }])\n    expect(events.mode).toBeDefined()\n  })\n\n  it('should race events', async () => {\n    let p = events.race(['InsertCharPre', 'TextChangedI', 'MenuPopupChanged'])\n    await events.fire('InsertCharPre', ['i', 1])\n    await events.fire('TextChangedI', [1, {\n      lnum: 1,\n      col: 2,\n      pre: 'i',\n      changedtick: 1\n    }])\n    let res = await p\n    expect(res.name).toBe('InsertCharPre')\n    res = await events.race(['TextChanged'], 50)\n    expect(res).toBeUndefined()\n  })\n\n  it('should race same events', async () => {\n    let arr: any[] = []\n    void events.race(['TextChangedI'], 200).then(res => {\n      arr.push(res)\n    })\n    void events.race(['TextChangedI'], 200).then(res => {\n      arr.push(res)\n    })\n    await events.fire('TextChangedI', [2, {}])\n    expect(arr.length).toBe(2)\n    expect(arr.map(o => o.name)).toEqual(['TextChangedI', 'TextChangedI'])\n  })\n\n  it('should cancel race by CancellationToken', async () => {\n    let tokenSource = new CancellationTokenSource()\n    setTimeout(() => {\n      tokenSource.cancel()\n    }, 20)\n    let res = await events.race(['TextChanged'], tokenSource.token)\n    expect(res).toBeUndefined()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/extensionInstaller.test.ts",
    "content": "import fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport { v4 as uuid } from 'uuid'\nimport { getDependencies, getExtensionDependencies, Info, Installer, isNpmCommand, isYarn, registryUrl } from '../../extension/installer'\nimport { remove } from '../../util/fs'\n\nconst rcfile = path.join(os.tmpdir(), '.npmrc')\nlet tmpfolder: string\nafterEach(() => {\n  if (tmpfolder) {\n    fs.rmSync(tmpfolder, { force: true, recursive: true })\n  }\n})\n\ndescribe('utils', () => {\n  it('should getDependencies & getExtensionDependencies', async () => {\n    expect(getDependencies({})).toEqual([])\n    expect(getDependencies({ dependencies: { 'coc.nvim': '0.0.1' } })).toEqual([])\n    expect(getExtensionDependencies({})).toEqual([])\n    expect(getExtensionDependencies({ extensionDependencies: ['extension-1', 'extension-1'] })).toEqual(['extension-1'])\n  })\n\n  it('should check command is npm or yarn', async () => {\n    expect(isNpmCommand('npm')).toBe(true)\n    expect(isYarn('yarnpkg')).toBe(true)\n  })\n\n  it('should get registry url', async () => {\n    const getUrl = () => {\n      return registryUrl(os.tmpdir())\n    }\n    fs.rmSync(rcfile, { force: true, recursive: true })\n    expect(getUrl().toString()).toBe('https://registry.npmjs.org/')\n    fs.writeFileSync(rcfile, '', 'utf8')\n    expect(getUrl().toString()).toBe('https://registry.npmjs.org/')\n    fs.writeFileSync(rcfile, 'coc.nvim:registry=https://example.org', 'utf8')\n    expect(getUrl().toString()).toBe('https://example.org/')\n    fs.writeFileSync(rcfile, '#coc.nvim:registry=https://example.org', 'utf8')\n    expect(getUrl().toString()).toBe('https://registry.npmjs.org/')\n    fs.writeFileSync(rcfile, 'coc.nvim:registry=example.org', 'utf8')\n    expect(getUrl().toString()).toBe('https://registry.npmjs.org/')\n    fs.rmSync(rcfile, { force: true, recursive: true })\n  })\n\n  it('should parse name & version', async () => {\n    const getInfo = (def: string): { name?: string, version?: string } => {\n      let installer = new Installer(__dirname, 'npm', def)\n      return installer.info\n    }\n    expect(getInfo('https://github.com')).toEqual({ name: undefined, version: undefined })\n    expect(getInfo('@yaegassy/coc-intelephense')).toEqual({ name: '@yaegassy/coc-intelephense', version: undefined })\n    expect(getInfo('@yaegassy/coc-intelephense@1.0.0')).toEqual({ name: '@yaegassy/coc-intelephense', version: '1.0.0' })\n    expect(getInfo('foo@1.0.0')).toEqual({ name: 'foo', version: '1.0.0' })\n  })\n})\n\ndescribe('Installer', () => {\n  describe('fetch() & download()', () => {\n    it('should throw with invalid url', async () => {\n      let installer = new Installer(__dirname, 'npm', 'foo')\n      let fn = async () => {\n        await installer.fetch('url')\n      }\n      await expect(fn()).rejects.toThrow()\n      fn = async () => {\n        await installer.download('url', { dest: '' })\n      }\n      await expect(fn()).rejects.toThrow()\n    })\n  })\n\n  describe('getInfo()', () => {\n    it('should get install arguments', async () => {\n      let installer = new Installer(__dirname, 'npm', 'https://github.com/')\n      expect(installer.getInstallArguments('pnpm', 'https://github.com/')).toEqual({ env: 'development', args: ['install'] })\n      expect(installer.getInstallArguments('npm', '')).toEqual({ env: 'production', args: ['install', '--ignore-scripts', '--no-package-lock', '--omit=dev', '--legacy-peer-deps', '--no-global'] })\n      expect(installer.getInstallArguments('yarn', '')).toEqual({ env: 'production', args: ['install', '--ignore-scripts', '--no-lockfile', '--production', '--ignore-engines'] })\n      expect(installer.getInstallArguments('pnpm', '')).toEqual({ env: 'production', args: ['install', '--ignore-scripts', '--no-lockfile', '--production', '--config.strict-peer-dependencies=false'] })\n    })\n\n    it('should getInfo from url', async () => {\n      let installer = new Installer(__dirname, 'npm', 'https://github.com/')\n      let spy = jest.spyOn(installer, 'getInfoFromUri').mockImplementation(() => {\n        return Promise.resolve({ name: 'vue-vscode-snippets', version: '1.0.0' })\n      })\n      let res = await installer.getInfo()\n      expect(res).toBeDefined()\n      spy.mockRestore()\n    })\n\n    it('should use latest version', async () => {\n      let installer = new Installer(__dirname, 'npm', 'coc-omni')\n      let spy = jest.spyOn(installer, 'fetch').mockImplementation(url => {\n        expect(url.toString()).toMatch('coc-omni')\n        return Promise.resolve(JSON.stringify({\n          name: 'coc-omni',\n          'dist-tags': { latest: '1.0.0' },\n          versions: {\n            '1.0.0': {\n              version: '1.0.0',\n              dist: { tarball: 'tarball' },\n              engines: { coc: '>=0.0.80' }\n            }\n          }\n        }))\n      })\n      let info = await installer.getInfo()\n      expect(info).toBeDefined()\n      spy.mockRestore()\n    })\n\n    it('should throw when version not found', async () => {\n      let installer = new Installer(__dirname, 'npm', 'coc-omni@1.0.2')\n      let spy = jest.spyOn(installer, 'fetch').mockImplementation(() => {\n        return Promise.resolve(JSON.stringify({\n          name: 'coc-omni',\n          'dist-tags': { latest: '1.0.0' },\n          versions: {\n            '1.0.0': {\n              version: '1.0.0',\n              dist: { tarball: 'tarball' },\n              engines: { coc: '>=0.0.80' }\n            }\n          }\n        }))\n      })\n      let fn = async () => {\n        await installer.getInfo()\n      }\n      await expect(fn()).rejects.toThrow(/doesn't exists/)\n      spy.mockRestore()\n    })\n\n    it('should throw when not coc.nvim extension', async () => {\n      let installer = new Installer(__dirname, 'npm', 'coc-omni')\n      let spy = jest.spyOn(installer, 'fetch').mockImplementation(() => {\n        return Promise.resolve(JSON.stringify({\n          name: 'coc-omni',\n          'dist-tags': { latest: '1.0.0' },\n          versions: {\n            '1.0.0': {\n              version: '1.0.0',\n              dist: { tarball: 'tarball' }\n            }\n          }\n        }))\n      })\n      let fn = async () => {\n        await installer.getInfo()\n      }\n      await expect(fn()).rejects.toThrow(/not a valid/)\n      spy.mockRestore()\n    })\n  })\n\n  describe('getInfoFromUri()', () => {\n    it('should throw for url that not supported', async () => {\n      let installer = new Installer(__dirname, 'npm', 'https://example.com')\n      let fn = async () => {\n        await installer.getInfoFromUri()\n      }\n      await expect(fn()).rejects.toThrow(/not supported/)\n    })\n\n    it('should get info from url #1', async () => {\n      let installer = new Installer(__dirname, 'npm', 'https://github.com/sdras/vue-vscode-snippets')\n      let spy = jest.spyOn(installer, 'fetch').mockImplementation(() => {\n        return Promise.resolve(JSON.stringify({ name: 'vue-vscode-snippets', version: '1.0.0' }))\n      })\n      let info = await installer.getInfoFromUri()\n      expect(info['dist.tarball']).toMatch(/master.tar.gz/)\n      spy.mockRestore()\n    })\n\n    it('should get info from url #2', async () => {\n      let installer = new Installer(__dirname, 'npm', 'https://github.com/sdras/vue-vscode-snippets@main')\n      let spy = jest.spyOn(installer, 'fetch').mockImplementation(() => {\n        return Promise.resolve({ name: 'vue-vscode-snippets', version: '1.0.0', engines: { coc: '>=0.0.1' } })\n      })\n      let info = await installer.getInfoFromUri()\n      expect(info['dist.tarball']).toMatch(/main.tar.gz/)\n      expect(info['engines.coc']).toEqual('>=0.0.1')\n      spy.mockRestore()\n    }, 10000)\n  })\n\n  describe('update()', () => {\n    it('should skip install & update for symbolic folder', async () => {\n      tmpfolder = path.join(os.tmpdir(), 'foo')\n      fs.rmSync(tmpfolder, { recursive: true, force: true })\n      fs.symlinkSync(__dirname, tmpfolder, 'dir')\n      let installer = new Installer(os.tmpdir(), 'npm', 'foo')\n      let res = await installer.doInstall({ name: 'foo' })\n      expect(res).toBe(false)\n      let val = await installer.update()\n      expect(val).toBeUndefined()\n    })\n\n    it('should update from url', async () => {\n      let url = 'https://github.com/sdras/vue-vscode-snippets@main'\n      let installer = new Installer(__dirname, 'npm', url)\n      let spy = jest.spyOn(installer, 'getInfo').mockImplementation(() => {\n        return Promise.resolve({ version: '1.0.0', name: 'vue-vscode-snippets' })\n      })\n      let s = jest.spyOn(installer, 'doInstall').mockImplementation(() => {\n        return Promise.resolve(true)\n      })\n      let res = await installer.update(url)\n      expect(res).toBeDefined()\n      spy.mockRestore()\n      s.mockRestore()\n    })\n\n    it('should skip update when current version is latest', async () => {\n      tmpfolder = path.join(os.tmpdir(), 'coc-pairs')\n      let installer = new Installer(os.tmpdir(), 'npm', 'coc-pairs')\n      let version = '1.0.0'\n      let spy = jest.spyOn(installer, 'getInfo').mockImplementation(() => {\n        return Promise.resolve({ version })\n      })\n      let info = await installer.getInfo()\n      fs.mkdirSync(tmpfolder)\n      fs.writeFileSync(path.join(tmpfolder, 'package.json'), `{\"version\": \"${info.version}\"}`, 'utf8')\n      let res = await installer.update()\n      expect(res).toBeUndefined()\n      spy.mockRestore()\n    })\n\n    it('should skip update when version not satisfies', async () => {\n      tmpfolder = path.join(os.tmpdir(), 'coc-pairs')\n      let installer = new Installer(os.tmpdir(), 'npm', 'coc-pairs')\n      let version = '2.0.0'\n      let spy = jest.spyOn(installer, 'getInfo').mockImplementation(() => {\n        return Promise.resolve({ version, 'engines.coc': '>=99.0.0' })\n      })\n      fs.mkdirSync(tmpfolder)\n      fs.writeFileSync(path.join(tmpfolder, 'package.json'), `{\"version\": \"1.0.0\"}`, 'utf8')\n      let fn = async () => {\n        await installer.update()\n      }\n      await expect(fn()).rejects.toThrow(Error)\n      spy.mockRestore()\n    })\n\n    it('should return undefined when update not performed', async () => {\n      tmpfolder = path.join(os.tmpdir(), 'coc-pairs')\n      let installer = new Installer(os.tmpdir(), 'npm', 'coc-pairs')\n      let version = '2.0.0'\n      let spy = jest.spyOn(installer, 'getInfo').mockImplementation(() => {\n        return Promise.resolve({ version })\n      })\n      let s = jest.spyOn(installer, 'doInstall').mockImplementation(() => {\n        return Promise.resolve(false)\n      })\n      fs.mkdirSync(tmpfolder)\n      fs.writeFileSync(path.join(tmpfolder, 'package.json'), `{\"version\": \"1.0.0\"}`, 'utf8')\n      let res = await installer.update()\n      expect(res).toBeUndefined()\n      spy.mockRestore()\n      s.mockRestore()\n    })\n\n    it('should update extension', async () => {\n      tmpfolder = path.join(os.tmpdir(), 'coc-pairs')\n      let installer = new Installer(os.tmpdir(), 'npm', 'coc-pairs')\n      let version = '2.0.0'\n      let spy = jest.spyOn(installer, 'getInfo').mockImplementation(() => {\n        return Promise.resolve({ version, name: 'coc-pairs' })\n      })\n      let s = jest.spyOn(installer, 'doInstall').mockImplementation(() => {\n        return Promise.resolve(true)\n      })\n      fs.mkdirSync(tmpfolder, { recursive: true })\n      fs.writeFileSync(path.join(tmpfolder, 'package.json'), `{\"version\": \"1.0.0\"}`, 'utf8')\n      let res = await installer.update()\n      expect(res).toBeDefined()\n      spy.mockRestore()\n      s.mockRestore()\n      await remove(tmpfolder)\n    })\n  })\n\n  describe('install()', () => {\n    it('should throw when version not match required', async () => {\n      let installer = new Installer(__dirname, 'npm', 'coc-omni')\n      let spy = jest.spyOn(installer, 'getInfo').mockImplementation(() => {\n        return Promise.resolve({\n          name: 'coc-omni',\n          version: '1.0.0',\n          'dist.tarball': '',\n          'engines.coc': '>=99.0.0'\n        })\n      })\n      let fn = async () => {\n        await installer.install()\n      }\n      await expect(fn()).rejects.toThrow(Error)\n      spy.mockRestore()\n    })\n\n    it('should return install info', async () => {\n      let installer = new Installer(__dirname, 'npm', 'coc-omni')\n      let spy = jest.spyOn(installer, 'getInfo').mockImplementation(() => {\n        return Promise.resolve({\n          name: 'coc-omni',\n          version: '1.0.0',\n          'dist.tarball': '',\n          'engines.coc': '>=0.0.1'\n        })\n      })\n      let s = jest.spyOn(installer, 'doInstall').mockImplementation(() => {\n        return Promise.resolve(true)\n      })\n      let res = await installer.install()\n      expect(res.updated).toBe(true)\n      s.mockRestore()\n      spy.mockRestore()\n    })\n\n    it('should throw and remove folder when download failed', async () => {\n      tmpfolder = path.join(os.tmpdir(), uuid())\n      let installer = new Installer(tmpfolder, 'npm', 'coc-omni')\n      let folder: string\n      let option: any\n      let spy = jest.spyOn(installer, 'download').mockImplementation((_url, opt) => {\n        folder = opt.dest\n        option = opt\n        fs.mkdirSync(folder, { recursive: true })\n        throw new Error('my error')\n      })\n      let info: Info = { name: 'coc-omni', version: '1.0.0', 'dist.tarball': 'https://registry.npmjs.org/-/coc-omni-1.0.0.tgz' }\n      let fn = async () => {\n        await installer.doInstall(info)\n      }\n      await expect(fn()).rejects.toThrow(Error)\n      expect(option.etagAlgorithm).toBe('md5')\n      let exists = fs.existsSync(folder)\n      expect(exists).toBe(false)\n      spy.mockRestore()\n    })\n\n    it('should revert folder when download failed', async () => {\n      tmpfolder = path.join(os.tmpdir(), uuid())\n      let installer = new Installer(tmpfolder, 'npm', 'coc-omni')\n      let f = path.join(tmpfolder, 'coc-omni')\n      fs.mkdirSync(f, { recursive: true })\n      fs.writeFileSync(path.join(f, 'package.json'), '{}', 'utf8')\n      let spy = jest.spyOn(installer, 'download').mockImplementation(() => {\n        throw new Error('my error')\n      })\n      let info: Info = { name: 'coc-omni', version: '1.0.0', 'dist.tarball': 'tarball' }\n      let fn = async () => {\n        await installer.doInstall(info)\n      }\n      await expect(fn()).rejects.toThrow(Error)\n      spy.mockRestore()\n      let exist = fs.existsSync(path.join(f, 'package.json'))\n      expect(exist).toBe(true)\n    })\n\n    it('should install new extension', async () => {\n      tmpfolder = path.join(os.tmpdir(), uuid())\n      let installer = new Installer(tmpfolder, 'npm', 'coc-omni')\n      let f = path.join(tmpfolder, 'coc-omni')\n      let spy = jest.spyOn(installer, 'download').mockImplementation((_url, option) => {\n        if (option.onProgress) {\n          option.onProgress('10')\n        }\n        fs.mkdirSync(option.dest, { recursive: true })\n        let file = path.join(option.dest, 'package.json')\n        fs.writeFileSync(file, '{version: \"1.0.0\"}', 'utf8')\n        return Promise.resolve()\n      })\n      let info: Info = { name: 'coc-omni', version: '1.0.0', 'dist.tarball': 'tarball' }\n      let res = await installer.doInstall(info)\n      spy.mockRestore()\n      expect(res).toBe(true)\n      let exist = fs.existsSync(path.join(f, 'package.json'))\n      expect(exist).toBe(true)\n    })\n\n    it('should install new version', async () => {\n      tmpfolder = path.join(os.tmpdir(), uuid())\n      let installer = new Installer(tmpfolder, 'npm', 'coc-omni')\n      let f = path.join(tmpfolder, 'coc-omni')\n      fs.mkdirSync(f, { recursive: true })\n      fs.writeFileSync(path.join(f, 'package.json'), '{}', 'utf8')\n      let spy = jest.spyOn(installer, 'download').mockImplementation((_url, option) => {\n        if (option.onProgress) {\n          option.onProgress('10')\n        }\n        fs.mkdirSync(option.dest, { recursive: true })\n        let file = path.join(option.dest, 'package.json')\n        fs.writeFileSync(file, '{version: \"1.0.0\"}', 'utf8')\n        return Promise.resolve()\n      })\n      let info: Info = { name: 'coc-omni', version: '1.0.0', 'dist.tarball': 'tarball' }\n      let res = await installer.doInstall(info)\n      spy.mockRestore()\n      expect(res).toBe(true)\n      let exist = fs.existsSync(path.join(f, 'package.json'))\n      expect(exist).toBe(true)\n    })\n\n    it('should install dependencies', async () => {\n      let npm = path.resolve(__dirname, '../npm')\n      tmpfolder = path.join(os.tmpdir(), uuid())\n      fs.mkdirSync(tmpfolder)\n      let installer = new Installer(tmpfolder, npm, 'coc-omni')\n      let called = false\n      installer.on('message', () => {\n        called = true\n      })\n      await installer.installDependencies(tmpfolder, ['a', 'b'])\n      expect(called).toBe(true)\n    })\n\n    it('should reject on install error', async () => {\n      let npm = path.resolve(__dirname, '../npm')\n      tmpfolder = path.join(os.tmpdir(), uuid())\n      fs.mkdirSync(tmpfolder)\n      let installer = new Installer(tmpfolder, npm, 'coc-omni')\n      let spy = jest.spyOn(installer, 'getInstallArguments').mockImplementation(() => {\n        return { env: 'production', args: ['--error'] }\n      })\n      let fn = async () => {\n        await installer.installDependencies(tmpfolder, ['a', 'b'])\n      }\n      await expect(fn()).rejects.toThrow(Error)\n      spy.mockRestore()\n    })\n\n    it('should install extension dependencies', async () => {\n      let getInfoSpy = jest.spyOn(Installer.prototype, 'getInfo').mockImplementation(async function() {\n        // @ts-expect-error this\n        const name = this.info.name\n        return { name, version: '1.0.0', 'dist.tarball': `https://example.com/${name}.tgz` }\n      })\n      let downloadSpy = jest.spyOn(Installer.prototype, 'download').mockImplementation(async function(url, options) {\n        fs.mkdirSync(options.dest, { recursive: true })\n        let name = path.basename(url, '.tgz')\n        let pkg = {\n          name,\n          version: '1.0.0',\n          engines: { coc: '>=0.0.1' },\n          extensionDependencies: name === 'coc-extension-with-dependencies' ? ['coc-dependency-1', 'coc-dependency-2'] : []\n        }\n        fs.writeFileSync(path.join(options.dest, 'package.json'), JSON.stringify(pkg))\n      })\n\n      tmpfolder = path.join(os.tmpdir(), 'coc-test')\n      let installer = new Installer(tmpfolder, 'npm', 'coc-extension-with-dependencies@1.0.0')\n      await installer.install()\n\n      expect(fs.existsSync(path.join(tmpfolder, 'coc-extension-with-dependencies'))).toBe(true)\n      expect(fs.existsSync(path.join(tmpfolder, 'coc-dependency-1'))).toBe(true)\n      expect(fs.existsSync(path.join(tmpfolder, 'coc-dependency-2'))).toBe(true)\n\n      getInfoSpy.mockRestore()\n      downloadSpy.mockRestore()\n    }, 10000)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/extensionManager.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport { v4 as uuid } from 'uuid'\nimport { Disposable } from 'vscode-languageserver-protocol'\nimport events from '../../events'\nimport { API, checkCommand, checkFileSystem, checkLanguageId, Extension, ExtensionManager, ExtensionType, getActivationEvents, getEvents, getOnCommandList, toWorkspaceContainsPatterns } from '../../extension/manager'\nimport { ExtensionJson, ExtensionStat } from '../../extension/stat'\nimport { disposeAll } from '../../util'\nimport { Extensions as ExtensionsInfo, getExtensionDefinitions, IExtensionRegistry } from '../../util/extensionRegistry'\nimport { writeJson } from '../../util/fs'\nimport { deepIterate } from '../../util/object'\nimport { Registry } from '../../util/registry'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet disposables: Disposable[] = []\nlet nvim: Neovim\nlet tmpfolder: string\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterEach(() => {\n  disposeAll(disposables)\n  if (fs.existsSync(tmpfolder)) {\n    fs.rmSync(tmpfolder, { force: true, recursive: true })\n  }\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nfunction createFolder(): string {\n  let folder = path.join(os.tmpdir(), uuid())\n  fs.mkdirSync(folder, { recursive: true })\n  return folder\n}\n\ndescribe('utils', () => {\n  it('should get events', () => {\n    expect(getEvents(undefined)).toEqual([])\n    expect(getEvents(['a', 'b'])).toEqual(['a', 'b'])\n    expect(getEvents(['x:y', 'x:z'])).toEqual(['x'])\n  })\n\n  it('should get onCommand list', async () => {\n    let res = getOnCommandList(['onCommand:a', 'onCommand', 'onCommand:b'])\n    expect(res).toEqual(['a', 'b'])\n    expect(getOnCommandList(undefined)).toEqual([])\n  })\n\n  it('should getActivationEvents', async () => {\n    expect(getActivationEvents({} as any)).toEqual([])\n    expect(getActivationEvents({ activationEvents: 1 } as any)).toEqual([])\n    expect(getActivationEvents({ activationEvents: ['a', ''] } as any)).toEqual(['a'])\n    expect(getActivationEvents({ activationEvents: ['a', 1] } as any)).toEqual(['a'])\n  })\n\n  it('should checkLanguageId', () => {\n    expect(checkLanguageId({ languageId: 'vim', filetype: 'vim' }, [])).toBe(false)\n    expect(checkLanguageId({ languageId: 'vim', filetype: 'vim' }, ['onLanguage:java', 'onLanguage:vim'])).toBe(true)\n  })\n\n  it('should checkCommand', async () => {\n    expect(checkCommand('cmd', [])).toBe(false)\n    expect(checkCommand('cmd', ['onCommand:abc'])).toBe(false)\n    expect(checkCommand('cmd', ['onCommand:def', 'onCommand:cmd'])).toBe(true)\n  })\n\n  it('should checkFilesystem', async () => {\n    expect(checkFileSystem('file:///1', [])).toBe(false)\n    expect(checkFileSystem('file:///1', ['onFileSystem:x', 'onFileSystem:file'])).toBe(true)\n  })\n\n  it('should toWorkspaceContainsPatterns', async () => {\n    let res = toWorkspaceContainsPatterns(['workspaceContains:', 'workspaceContains:a.js', 'workspaceContains:b.js'])\n    expect(res).toEqual(['a.js', 'b.js'])\n    res = toWorkspaceContainsPatterns(['workspaceContains:', 'workspaceContains:**/b.js'])\n    expect(res).toEqual(['**/b.js'])\n  })\n})\n\ndescribe('ExtensionManager', () => {\n  function create(folder = createFolder(), activate = false): ExtensionManager {\n    let stats = new ExtensionStat(folder)\n    let manager = new ExtensionManager(stats, tmpfolder)\n    disposables.push(manager)\n    if (activate) void manager.activateExtensions()\n    return manager\n  }\n\n  function createExtension(folder: string, packageJSON: ExtensionJson, code?: string): void {\n    fs.mkdirSync(folder, { recursive: true })\n    code = code ?? `exports.activate = () => {return {folder: \"${folder}\"}}`\n    let jsonfile = path.join(folder, 'package.json')\n    fs.writeFileSync(jsonfile, JSON.stringify(packageJSON), 'utf8')\n    let file = packageJSON.main ?? 'index.js'\n    fs.writeFileSync(path.join(folder, file), code, 'utf8')\n  }\n\n  function createGlobalExtension(name: string, contributes?: any): string {\n    tmpfolder = createFolder()\n    let extFolder = path.join(tmpfolder, 'node_modules', name)\n    createExtension(extFolder, { name, main: 'entry.js', engines: { coc: '>=0.0.1' }, contributes })\n    return extFolder\n  }\n\n  describe('activateExtensions()', () => {\n    it('should registExtensions', async () => {\n      let res = await helper.doAction('registerExtensions')\n      expect(res).toBe(true)\n    })\n\n    it('should throw on error', async () => {\n      tmpfolder = createFolder()\n      createExtension(tmpfolder, {\n        name: 'name',\n        engines: { coc: '>= 0.0.80' },\n        activationEvents: ['onLanguage:vim'],\n        contributes: {}\n      })\n      let manager = create(tmpfolder)\n      await manager.loadExtension(tmpfolder)\n      await manager.activateExtensions()\n      let fn = () => {\n        manager.tryActivateExtensions('onLanguage', () => {\n          throw new Error('test error')\n        })\n      }\n      expect(fn).toThrow(Error)\n    })\n\n    it('should not throw when autoActivated throws', async () => {\n      tmpfolder = createFolder()\n      createExtension(tmpfolder, {\n        name: 'name',\n        engines: { coc: '>= 0.0.80' },\n        activationEvents: ['*']\n      })\n      let manager = create(tmpfolder)\n      await manager.loadExtension(tmpfolder)\n      let extension = manager.getExtension('name').extension\n      let spy = jest.spyOn(manager, 'checkAutoActivate' as any).mockImplementation(() => {\n        throw new Error('test error')\n      })\n      await manager.autoActivate('name', extension)\n      spy.mockRestore()\n    })\n\n    it('should automatically activated', async () => {\n      let folder = createFolder()\n      fs.writeFileSync(path.join(folder, 'base.js'), 'foo', 'utf8')\n      workspace.workspaceFolderControl.addWorkspaceFolder(folder, false)\n      tmpfolder = createFolder()\n      let code = `exports.activate = (ctx) => {return {abs: ctx.asAbsolutePath('./foo')}}`\n      createExtension(tmpfolder, {\n        name: 'auto',\n        engines: { coc: '>= 0.0.80' },\n        activationEvents: ['workspaceContains:base.js'],\n        contributes: {\n          rootPatterns: [\n            {\n              filetype: \"javascript\",\n              patterns: [\n                \"package.json\",\n                \"jsconfig.json\"\n              ]\n            }\n          ]\n        }\n      }, code)\n      let manager = create(tmpfolder)\n      let spy = jest.spyOn(workspace, 'checkPatterns').mockImplementation(() => {\n        return Promise.resolve(true)\n      })\n      disposables.push(Disposable.create(() => {\n        spy.mockRestore()\n      }))\n      await manager.activateExtensions()\n      await manager.loadExtension(tmpfolder)\n      let item = manager.getExtension('auto')\n      await helper.waitValue(() => {\n        return item.extension.isActive\n      }, true)\n      expect(manager.all.length).toBe(1)\n      expect(manager.getExtensionState('auto')).toBe('activated')\n      expect(item.extension.exports['abs']).toBeDefined()\n      fs.rmSync(folder, { recursive: true, force: true })\n    })\n  })\n\n  describe('activationEvents', () => {\n    async function createExtension(manager: ExtensionManager, ...events: string[]): Promise<Extension<API>> {\n      let id = uuid()\n      let isActive = false\n      let packageJSON = {\n        name: id,\n        activationEvents: events\n      }\n      let ext = {\n        id,\n        packageJSON,\n        exports: void 0,\n        extensionPath: '',\n        activate: async () => {\n          isActive = true\n        }\n      } as any\n      Object.defineProperty(ext, 'isActive', {\n        get: () => isActive\n      })\n      await manager.registerInternalExtension(ext, () => {\n        isActive = false\n      })\n      return ext\n    }\n\n    it('should load local extension on runtimepath change', async () => {\n      tmpfolder = createFolder()\n      let manager = create(tmpfolder, true)\n      writeJson(path.join(tmpfolder, 'package.json'), {\n        name: 'local',\n        engines: { coc: '>=0.0.1' },\n        contributes: {\n          configuration: {\n            properties: {\n              'local.enable': {\n                type: 'boolean',\n                default: true,\n                description: \"Enable local\"\n              }\n            }\n          }\n        }\n      })\n      fs.writeFileSync(path.join(tmpfolder, 'index.js'), '')\n      let called = false\n      workspace.onDidChangeConfiguration(e => {\n        if (e.affectsConfiguration('local.enable')) {\n          called = true\n        }\n      })\n      await nvim.command(`set runtimepath^=${tmpfolder}`)\n      await helper.waitValue(() => {\n        return manager.has('local')\n      }, true)\n      expect(called).toBe(true)\n      let ext = manager.getExtension('local')\n      expect(ext.extension.isActive).toBe(true)\n      let c = workspace.getConfiguration('local')\n      expect(c.get('enable')).toBe(true)\n      fs.rmSync(tmpfolder, { force: true, recursive: true })\n    })\n\n    it('should activate on language', async () => {\n      tmpfolder = createFolder()\n      let manager = create(tmpfolder, true)\n      let ext = await createExtension(manager, 'workspaceContains:foobar', 'onLanguage:javascript')\n      expect(ext.isActive).toBe(false)\n      await nvim.command('edit /tmp/a.js')\n      await nvim.command('setf javascript')\n      await helper.wait(50)\n      expect(ext.isActive).toBe(true)\n      ext = await createExtension(manager, 'onLanguage:javascript')\n      expect(ext.isActive).toBe(true)\n    })\n\n    it('should activate on command', async () => {\n      tmpfolder = createFolder()\n      let manager = create(tmpfolder, true)\n      let ext = await createExtension(manager, 'onCommand:test.echo')\n      await events.fire('Command', ['test.bac'])\n      await events.fire('Command', ['test.echo'])\n      await helper.wait(30)\n      expect(ext.isActive).toBe(true)\n    })\n\n    it('should activate on workspace contains', async () => {\n      tmpfolder = createFolder()\n      let manager = create(tmpfolder, true)\n      let ext = await createExtension(manager, 'workspaceContains:package.json')\n      await createExtension(manager, 'workspaceContains:file_not_exists')\n      let root = path.resolve(__dirname, '../../..')\n      await nvim.command(`edit ${path.join(root, 'file.js')}`)\n      await helper.waitValue(() => {\n        return ext.isActive\n      }, true)\n    })\n\n    it('should activate on file system', async () => {\n      tmpfolder = createFolder()\n      let manager = create(tmpfolder, true)\n      let ext = await createExtension(manager, 'onFileSystem:zip')\n      await nvim.command('edit zip:///a')\n      await helper.wait(30)\n      expect(ext.isActive).toBe(true)\n      ext = await createExtension(manager, 'onFileSystem:zip')\n      expect(ext.isActive).toBe(true)\n    })\n  })\n\n  describe('has()', () => {\n    it('should check current extensions', async () => {\n      let manager = create()\n      expect(manager.has('id')).toBe(false)\n      expect(manager.getExtension('id')).toBeUndefined()\n      expect(manager.loadedExtensions).toEqual([])\n      expect(manager.all).toEqual([])\n    })\n  })\n\n  describe('activate()', () => {\n    it('should throw when extension not registered', async () => {\n      tmpfolder = createFolder()\n      let manager = create(tmpfolder)\n      let fn = async () => {\n        await manager.activate('name')\n      }\n      await expect(fn()).rejects.toThrow(Error)\n      fn = async () => {\n        await manager.call('name', 'fn', [])\n      }\n      await expect(fn()).rejects.toThrow(Error)\n    })\n\n    it('should activate extension with dependencies', async () => {\n      tmpfolder = createFolder()\n      let manager = create(tmpfolder)\n\n      let depFolder = path.join(tmpfolder, 'coc-ext-dep')\n      createExtension(depFolder, {\n        name: 'coc-ext-dep',\n        engines: { coc: '>=0.0.1' }\n      }, `exports.activate = () => { return { name: 'coc-ext-dep' } }`)\n\n      let mainFolder = path.join(tmpfolder, 'coc-ext-main')\n      createExtension(mainFolder, {\n        name: 'coc-ext-main',\n        engines: { coc: '>=0.0.1' },\n        extensionDependencies: ['coc-ext-dep']\n      }, `exports.activate = () => { return { name: 'coc-ext-main' } }`)\n\n      await manager.loadExtension(depFolder)\n      await manager.loadExtension(mainFolder)\n\n      await manager.activate('coc-ext-main')\n\n      expect(manager.getExtension('coc-ext-dep').extension.isActive).toBe(true)\n      expect(manager.getExtension('coc-ext-main').extension.isActive).toBe(true)\n    })\n\n    it('should fail when dependency activation fails', async () => {\n      tmpfolder = createFolder()\n      let manager = create(tmpfolder)\n\n      let depFolder = path.join(tmpfolder, 'coc-ext-dep')\n      createExtension(depFolder, {\n        name: 'coc-ext-dep',\n        engines: { coc: '>=0.0.1' }\n      }, `exports.activate = () => { throw new Error('Dependency failed') }`)\n\n      let mainFolder = path.join(tmpfolder, 'coc-ext-main')\n      createExtension(mainFolder, {\n        name: 'coc-ext-main',\n        engines: { coc: '>=0.0.1' },\n        extensionDependencies: ['coc-ext-dep']\n      }, `exports.activate = () => { return { name: 'coc-ext-main' } }`)\n\n      await manager.loadExtension(depFolder)\n      await manager.loadExtension(mainFolder)\n\n      let result = await manager.activate('coc-ext-main')\n\n      expect(result).toBe(false)\n      expect(manager.getExtension('coc-ext-main').extension.isActive).toBe(false)\n    })\n\n    it('should fail on circular dependencies', async () => {\n      tmpfolder = createFolder()\n      let manager = create(tmpfolder)\n\n      let ext1Folder = path.join(tmpfolder, 'coc-ext1')\n      createExtension(ext1Folder, {\n        name: 'coc-ext1',\n        engines: { coc: '>=0.0.1' },\n        extensionDependencies: ['coc-ext2']\n      }, `exports.activate = () => { return { name: 'coc-ext1' } }`)\n\n      let ext2Folder = path.join(tmpfolder, 'coc-ext2')\n      createExtension(ext2Folder, {\n        name: 'coc-ext2',\n        engines: { coc: '>=0.0.1' },\n        extensionDependencies: ['coc-ext1']\n      }, `exports.activate = () => { return { name: 'coc-ext2' } }`)\n\n      await manager.loadExtension(ext1Folder)\n      await manager.loadExtension(ext2Folder)\n\n      let result = await manager.activate('coc-ext1')\n      expect(result).toBe(false)\n    })\n  })\n\n  describe('call()', () => {\n    it('should activate extension that not activated', async () => {\n      tmpfolder = createFolder()\n      let code = `exports.activate = () => {return {getId: () => {return 'foo'}}}`\n      createExtension(tmpfolder, { name: 'name', engines: { coc: '>=0.0.1' } }, code)\n      let manager = create(tmpfolder)\n      await manager.loadExtension(tmpfolder)\n      let item = manager.getExtension('name')\n      expect(item.extension.isActive).toBe(false)\n      let res = await manager.call('name', 'getId', [])\n      expect(res).toBe('foo')\n      let fn = async () => {\n        await manager.call('name', 'fn', [])\n      }\n      await expect(fn()).rejects.toThrow(Error)\n    })\n  })\n\n  describe('loadExtensionFile()', () => {\n    it('should load single file extension', async () => {\n      tmpfolder = createFolder()\n      let filepath = path.join(tmpfolder, 'abc.js')\n      fs.writeFileSync(filepath, `exports.activate = (ctx) => {return {storagePath: ctx.storagePath}}`, 'utf8')\n      let manager = create(tmpfolder, true)\n      await manager.loadExtensionFile(filepath)\n      let item = manager.getExtension('single-abc')\n      expect(item.extension.isActive).toBe(true)\n      let file = path.join(tmpfolder, 'single-abc-data')\n      expect(item.extension.exports['storagePath']).toBe(file)\n    })\n\n    it('should not load extension when filepath not exists', async () => {\n      tmpfolder = createFolder()\n      let manager = create(tmpfolder, true)\n      let filepath = path.join(tmpfolder, 'abc.js')\n      await manager.loadExtensionFile(filepath)\n      let item = manager.getExtension('single-abc')\n      expect(item).toBeUndefined()\n    })\n  })\n\n  describe('uninstallExtensions()', () => {\n    it('should show message for extensions not found', async () => {\n      let manager = create(tmpfolder)\n      await manager.uninstallExtensions(['foo'])\n      let line = await helper.getCmdline()\n      expect(line).toMatch('not found')\n    })\n  })\n\n  describe('cleanExtensions()', () => {\n    it('should return extension ids that not disabled', async () => {\n      tmpfolder = createFolder()\n      let foo = path.join(tmpfolder, 'foo')\n      createExtension(foo, { name: 'foo', engines: { coc: '>=0.0.1' } })\n      let bar = path.join(tmpfolder, 'bar')\n      createExtension(bar, { name: 'bar', engines: { coc: '>=0.0.1' } })\n      let obj = { dependencies: { foo: '1.0.0', bar: '1.0.0' } }\n      writeJson(path.join(tmpfolder, 'package.json'), obj)\n      let manager = create(tmpfolder)\n      await manager.loadExtension(foo)\n      await manager.loadExtension(bar)\n      manager.states.setDisable('foo', true)\n      let res = await manager.cleanExtensions()\n      expect(res).toEqual(['bar'])\n    })\n  })\n\n  describe('loadedExtension()', () => {\n    it('should throw on bad extension', async () => {\n      tmpfolder = createFolder()\n      createExtension(tmpfolder, { name: 'name', engines: {} })\n      let manager = create(tmpfolder)\n      let fn = async () => {\n        await manager.loadExtension(tmpfolder)\n      }\n      await expect(fn()).rejects.toThrow(Error)\n      fn = async () => {\n        await manager.loadExtension([tmpfolder])\n      }\n      await expect(fn()).rejects.toThrow(Error)\n    })\n\n    it('should return false when disabled', async () => {\n      tmpfolder = createFolder()\n      createExtension(tmpfolder, { name: 'name', engines: { coc: '>=0.0.1' } })\n      let manager = create(tmpfolder)\n      manager.states.setDisable('name', true)\n      let res = await manager.loadExtension(tmpfolder)\n      expect(res).toBe(false)\n    })\n\n    it('should load local extension', async () => {\n      tmpfolder = createFolder()\n      createExtension(tmpfolder, { name: 'name', engines: { vscode: '1.0' } })\n      let manager = create(tmpfolder)\n      await manager.loadExtension(tmpfolder)\n      await manager.loadExtension([tmpfolder])\n      let item = manager.getExtension('name')\n      expect(item.isLocal).toBe(true)\n      expect(item.extension.isActive).toBe(false)\n      await item.extension.activate()\n      expect(item.extension.isActive).toBe(true)\n    })\n\n    it('should load and activate global extension', async () => {\n      let contributes = {\n        configuration: {\n          properties: {\n            'name.enable': {\n              type: 'boolean',\n              description: \"Enable name\"\n            }\n          }\n        }\n      }\n      let extFolder = createGlobalExtension('name', contributes)\n      let manager = create(tmpfolder)\n      manager.states.addExtension('name', '>=0.0.1')\n      let res = await manager.loadExtension(extFolder)\n      await manager.activateExtensions()\n      expect(res).toBe(true)\n      let item = manager.getExtension('name')\n      expect(item.isLocal).toBe(false)\n      expect(item.extension.extensionPath.endsWith('name')).toBe(true)\n      let result = await item.extension.activate()\n      expect(result).toBeDefined()\n      expect(result).toEqual(item.extension.exports)\n      await manager.deactivate('name')\n      let stat = manager.getExtensionState('name')\n      expect(stat).toBe('loaded')\n      let c = workspace.getConfiguration('name')\n      expect(c.get('enable')).toBe(false)\n      manager.unregistContribution('name')\n      c = workspace.getConfiguration('name')\n      expect(c.get('enable', undefined)).toBe(undefined)\n    })\n  })\n\n  describe('unloadExtension()', () => {\n    it('should unload extension', async () => {\n      let extFolder = createGlobalExtension('name')\n      let manager = create(tmpfolder)\n      manager.states.addExtension('name', '>=0.0.1')\n      await manager.loadExtension(extFolder)\n      let res = manager.getExtension('name')\n      expect(res).toBeDefined()\n      let fn = jest.fn()\n      manager.onDidUnloadExtension(() => {\n        fn()\n      })\n      await manager.unloadExtension('name')\n      res = manager.getExtension('name')\n      expect(res).toBeUndefined()\n      await manager.unloadExtension('name')\n      expect(fn).toHaveBeenCalledTimes(1)\n    })\n  })\n\n  describe('reloadExtension()', () => {\n    it('should throw when extension not registered', async () => {\n      tmpfolder = createFolder()\n      let manager = create(tmpfolder)\n      let fn = async () => {\n        await manager.reloadExtension('id')\n      }\n      await expect(fn()).rejects.toThrow(Error)\n    })\n\n    it('should reload single file extension', async () => {\n      tmpfolder = createFolder()\n      let filepath = path.join(tmpfolder, 'test.js')\n      fs.writeFileSync(filepath, `exports.activate = () => {return {file: \"${filepath}\"}};exports.deactivate = () => {}`, 'utf8')\n      let manager = create(tmpfolder)\n      await manager.activateExtensions()\n      await manager.loadExtensionFile(filepath)\n      let item = manager.getExtension('single-test')\n      expect(item.extension.isActive).toBe(true)\n      await manager.activate('single-test')\n      await manager.reloadExtension('single-test')\n      item = manager.getExtension('single-test')\n      expect(item.extension.isActive).toBe(true)\n      await item.deactivate()\n      expect(item.extension.isActive).toBe(false)\n      process.env.COC_NO_PLUGINS = '1'\n      await manager.activateExtensions()\n    })\n\n    it('should reload extension from directory', async () => {\n      tmpfolder = createFolder()\n      let extFolder = path.join(tmpfolder, 'node_modules', 'name')\n      createExtension(extFolder, { name: 'name', main: 'entry.js', engines: { coc: '>=0.0.1' } })\n      let manager = create(tmpfolder)\n      let res = await manager.loadExtension(extFolder)\n      expect(res).toBe(true)\n      await manager.reloadExtension('name')\n      let item = manager.getExtension('name')\n      expect(item.extension.isActive).toBe(false)\n    })\n  })\n\n  describe('registerExtension()', () => {\n    it('should not register disabled extension', async () => {\n      tmpfolder = createFolder()\n      let manager = create(tmpfolder)\n      manager.states.setDisable('name', true)\n      await manager.registerExtension(tmpfolder, {\n        name: 'name',\n        engines: { coc: '>=0.0.1' },\n      }, ExtensionType.Internal)\n      let item = manager.getExtension('name')\n      expect(item).toBeUndefined()\n    })\n\n    it('should throw error on activate', async () => {\n      tmpfolder = createFolder()\n      let code = `exports.activate = () => {throw new Error('my error')}`\n      createExtension(tmpfolder, { name: 'name', engines: { coc: '>=0.0.1' } }, code)\n      let manager = create(tmpfolder)\n      await manager.loadExtension(tmpfolder)\n      let item = manager.getExtension('name')\n      let fn = async () => {\n        await item.extension.activate()\n      }\n      await expect(fn()).rejects.toThrow()\n      fn = async () => {\n        // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n        item.extension.exports\n      }\n      await expect(fn()).rejects.toThrow()\n    })\n\n    it('should catch error on deactivate', async () => {\n      tmpfolder = createFolder()\n      let code = `exports.activate = () => { return {}};exports.deactivate = () => {throw new Error('my error')}`\n      createExtension(tmpfolder, { name: 'name', engines: { coc: '>=0.0.1' } }, code)\n      let manager = create(tmpfolder)\n      await manager.loadExtension(tmpfolder)\n      let item = manager.getExtension('name')\n      await item.deactivate()\n      await item.extension.activate()\n      await item.deactivate()\n    })\n\n    it('should not throw on register error', async () => {\n      let manager = create()\n      let spy = jest.spyOn(manager, 'registerExtension').mockImplementation(() => {\n        throw new Error('my error')\n      })\n      manager.registerExtensions([{\n        root: __filename,\n        isLocal: false,\n        packageJSON: {} as any\n      }])\n      spy.mockRestore()\n    })\n  })\n\n  describe('toggleExtension()', () => {\n    it('should not toggle disabled extension', async () => {\n      tmpfolder = createFolder()\n      let manager = create(tmpfolder)\n      manager.states.setDisable('foo', true)\n      await manager.toggleExtension('foo')\n    })\n\n    it('should toggle single file extension', async () => {\n      tmpfolder = createFolder()\n      let filepath = path.join(tmpfolder, 'test.js')\n      fs.writeFileSync(filepath, `exports.activate = () => {return {file: \"${filepath}\"}};exports.deactivate = () => {}`, 'utf8')\n      let manager = create(tmpfolder, true)\n      await manager.loadExtensionFile(filepath)\n      await manager.toggleExtension('single-test')\n      let item = manager.getExtension('single-test')\n      expect(item).toBeUndefined()\n      await manager.toggleExtension('single-test')\n    })\n\n    it('should toggle global extension', async () => {\n      tmpfolder = createFolder()\n      let folder = createGlobalExtension('global')\n      let manager = create(tmpfolder, true)\n      manager.states.addExtension('global', '>=0.0.1')\n      await manager.loadExtension(folder)\n      let item = manager.getExtension('global')\n      expect(item.extension.isActive).toBe(true)\n      await manager.toggleExtension('global')\n      item = manager.getExtension('global')\n      expect(item).toBeUndefined()\n      await manager.toggleExtension('global')\n      item = manager.getExtension('global')\n      expect(item.extension.isActive).toBe(true)\n    })\n\n    it('should toggle local extension', async () => {\n      tmpfolder = createFolder()\n      let folder = path.join(tmpfolder, 'local')\n      createExtension(folder, { name: 'local', main: 'entry.js', engines: { coc: '>=0.0.1' } })\n      let manager = create(tmpfolder, true)\n      await manager.loadExtension(folder)\n      let item = manager.getExtension('local')\n      expect(item.extension.isActive).toBe(true)\n      expect(item.isLocal).toBe(true)\n      await manager.toggleExtension('local')\n      item = manager.getExtension('local')\n      expect(item).toBeUndefined()\n      await manager.toggleExtension('local')\n      let state = manager.getExtensionState('local')\n      expect(state).toBe('activated')\n    })\n  })\n\n  describe('watchExtension()', () => {\n    it('should throw when watchman not found', async () => {\n      tmpfolder = createFolder()\n      let extFolder = path.join(tmpfolder, 'node_modules', 'name')\n      createExtension(extFolder, { name: 'name', main: 'entry.js', engines: { coc: '>=0.0.1' } })\n      let manager = create(tmpfolder)\n      let res = await manager.loadExtension(extFolder)\n      expect(res).toBe(true)\n      let spy = jest.spyOn(workspace.fileSystemWatchers, 'getWatchmanPath').mockImplementation(() => {\n        return Promise.reject(new Error('not found'))\n      })\n      let fn = async () => {\n        await manager.watchExtension('name')\n      }\n      await expect(fn()).rejects.toThrow(Error)\n      spy.mockRestore()\n      await expect(async () => {\n        await helper.doAction('watchExtension', 'not_exists_extension')\n      }).rejects.toThrow(/not found/)\n    })\n\n    it('should reload extension on file change', async () => {\n      tmpfolder = createFolder()\n      workspace.fileSystemWatchers.disabled = false\n      let extFolder = path.join(tmpfolder, 'node_modules', 'name')\n      createExtension(extFolder, { name: 'name', main: 'entry.js', engines: { coc: '>=0.0.1' } })\n      let manager = create(tmpfolder)\n      let res = await manager.loadExtension(extFolder)\n      expect(res).toBe(true)\n      let called = false\n      let fn = jest.fn()\n      let r = jest.spyOn(workspace, 'getWatchmanPath').mockImplementation(() => {\n        return 'watchman'\n      })\n      let s = jest.spyOn(manager, 'reloadExtension').mockImplementation(() => {\n        fn()\n        return Promise.resolve()\n      })\n      let spy = jest.spyOn(workspace.fileSystemWatchers, 'createClient').mockImplementation(() => {\n        return {\n          dispose: () => {},\n          subscribe: (_key: string, cb: Function) => {\n            setTimeout(() => {\n              called = true\n              cb()\n            }, 20)\n          }\n        } as any\n      })\n      await manager.watchExtension('name')\n      await helper.waitValue(() => {\n        return called\n      }, true)\n      expect(fn).toHaveBeenCalled()\n      r.mockRestore()\n      spy.mockRestore()\n      s.mockRestore()\n    })\n\n    it('should watch single file extension', async () => {\n      let dir = createFolder()\n      let id = uuid()\n      let filepath = path.join(dir, `${id}.js`)\n      fs.writeFileSync(filepath, `exports.activate = () => {return {file: \"${filepath}\"}};exports.deactivate = () => {}`, 'utf8')\n      let manager = create(dir)\n      await manager.loadExtensionFile(filepath)\n      await manager.watchExtension(`single-${id}`)\n      let fn = async () => {\n        await manager.watchExtension('single-unknown')\n      }\n      await expect(fn()).rejects.toThrow(Error)\n      let called = false\n      let spy = jest.spyOn(manager, 'loadExtensionFile').mockImplementation(() => {\n        called = true\n        return Promise.resolve('')\n      })\n      await helper.waitValue(() => {\n        return called\n      }, true)\n      spy.mockRestore()\n      fs.unlinkSync(filepath)\n    })\n  })\n\n  describe('loadFileExtensions', () => {\n    it('should load extension files', async () => {\n      tmpfolder = createFolder()\n      let filepath = path.join(tmpfolder, 'abc.js')\n      fs.writeFileSync(filepath, `exports.activate = (ctx) => {return {storagePath: ctx.storagePath}}`, 'utf8')\n      let manager = create(tmpfolder, true)\n      Object.assign(manager, { singleExtensionsRoot: tmpfolder })\n      await manager.loadFileExtensions()\n      let item = manager.getExtension('single-abc')\n      expect(item.extension.isActive).toBe(true)\n    })\n  })\n\n  describe('registContribution', () => {\n    it('should register definitions', async () => {\n      let json = `{\n\"configuration\": {\n    \"definitions\": {\n      \"flexible\": {\n        \"type\": \"object\",\n        \"$ref\": 3,\n        \"properties\": {\n          \"grow\": {\n            \"$ref\": \"#/definitions/flexible.position\"\n          },\n          \"omit\": {\n            \"$ref\": \"#/definitions/flexible.position\"\n          }\n        }\n      }\n    },\n    \"properties\": {\n      \"explorer.presets\": {\n        \"toggle\": {\n          \"$ref\": \"#/properties/explorer.toggle\"\n        },\n        \"mykey\": {\n          \"$ref\": \"#/definitions/mapping.keyMappings\"\n        }\n      }\n    }\n  }\n}`\n      let obj = JSON.parse(json)\n      tmpfolder = createFolder()\n      let manager = create(tmpfolder, false)\n      let packageJSON = { contributes: obj }\n      manager.registContribution('@explorer', packageJSON, __dirname)\n      const extensionRegistry = Registry.as<IExtensionRegistry>(ExtensionsInfo.ExtensionContribution)\n      let info = extensionRegistry.getExtension('@explorer')\n      let definitions = info.definitions\n      expect(definitions['explorer.flexible']).toBeDefined()\n      let refs: string[] = []\n      deepIterate(definitions, (node, key) => {\n        if (key == '$ref' && typeof node[key] === 'string') {\n          refs.push(node[key])\n        }\n      })\n      expect(refs).toEqual([\n        '#/definitions/explorer.flexible.position',\n        '#/definitions/explorer.flexible.position'\n      ])\n      refs = []\n      let properties = manager.configurationNodes[0].properties\n      deepIterate(properties, (node, key) => {\n        if (key == '$ref' && typeof node[key] === 'string') {\n          refs.push(node[key])\n        }\n      })\n      expect(refs).toEqual([\n        '#/properties/explorer.toggle',\n        '#/definitions/explorer.mapping.keyMappings'\n      ])\n      let defs = getExtensionDefinitions()\n      expect(defs['explorer.flexible']).toBeDefined()\n    })\n  })\n\n  describe('loadFileOrFolder()', () => {\n\n    it('should throw for invalid extension', async () => {\n      tmpfolder = createFolder()\n      let manager = create(tmpfolder, false)\n      await expect(async () => {\n        await manager.load('file_not_exists', false)\n      }).rejects.toThrow(Error)\n      let id = uuid()\n      let filpath = path.join(os.tmpdir(), id)\n      fs.writeFileSync(filpath, '', 'utf8')\n      await manager.toggleExtension(`single-${id}`)\n      await expect(async () => {\n        await manager.load(filpath, false)\n      }).rejects.toThrow(/disabled/)\n      fs.rmSync(filpath, { force: true })\n    })\n\n    it('should load extension without active', async () => {\n      tmpfolder = createFolder()\n      let manager = create(tmpfolder, false)\n      createExtension(tmpfolder, {\n        name: 'name',\n        engines: { coc: '>= 0.0.80' },\n        activationEvents: ['*'],\n        contributes: {}\n      })\n      let res = await manager.load(tmpfolder, false)\n      expect(res.isActive).toBe(false)\n      expect(res.name).toBe('name')\n      expect(res.exports).toEqual({})\n      await manager.activateExtensions()\n      await res.unload()\n      fs.rmSync(tmpfolder, { recursive: true })\n    })\n\n    it('should load and active extension', async () => {\n      tmpfolder = createFolder()\n      let manager = create(tmpfolder, false)\n      createExtension(tmpfolder, {\n        name: 'active',\n        engines: { coc: '>= 0.0.80' },\n        activationEvents: ['*'],\n        contributes: {}\n      }, `exports.activate = () => 'api';exports.foo = 'bar';`)\n      let res = await manager.load(tmpfolder, true)\n      expect(res.isActive).toBe(true)\n      expect(res.name).toBe('active')\n      expect(res.api).toBe('api')\n      expect(res.exports).toEqual({ foo: 'bar' })\n      await res.unload()\n      fs.rmSync(tmpfolder, { recursive: true })\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/extensionModules.test.ts",
    "content": "process.env.COC_NO_PLUGINS = '1'\nimport { Neovim } from '@chemzqm/neovim'\nimport fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport { v4 as uuid } from 'uuid'\nimport { Disposable } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport events from '../../events'\nimport { checkExtensionRoot, ExtensionStat, getExtensionName, getJsFiles, loadExtensionJson, loadGlobalJsonAsync, toInterval, validExtensionFolder } from '../../extension/stat'\nimport { InstallBuffer, InstallChannel } from '../../extension/ui'\nimport { disposeAll } from '../../util'\nimport { loadJson, writeJson } from '../../util/fs'\nimport window from '../../window'\nimport helper from '../helper'\n\nlet disposables: Disposable[] = []\nlet nvim: Neovim\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterEach(() => {\n  disposeAll(disposables)\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nfunction createFolder(): string {\n  let folder = path.join(os.tmpdir(), uuid())\n  fs.mkdirSync(folder, { recursive: true })\n  disposables.push(Disposable.create(() => {\n    fs.rmSync(folder, { recursive: true, force: true })\n  }))\n  return folder\n}\n\ndescribe('utils', () => {\n  describe('getJsFiles', () => {\n    it('should get js files', async () => {\n      let res = await getJsFiles(__dirname)\n      expect(Array.isArray(res)).toBe(true)\n    })\n  })\n\n  describe('loadGlobalJsonAsync()', () => {\n    it('should throw when engines not valid', async () => {\n      let folder = createFolder()\n      let file = path.join(folder, 'package.json')\n      fs.writeFileSync(file, '{}', 'utf8')\n      await expect(async () => {\n        await loadGlobalJsonAsync(folder, '0.0.80')\n      }).rejects.toThrow(/Invalid engines/)\n      fs.writeFileSync(file, '{\"engines\": {}}', 'utf8')\n      await expect(async () => {\n        await loadGlobalJsonAsync(folder, '0.0.80')\n      }).rejects.toThrow(/Invalid engines/)\n    })\n\n    it('should throw when version not match', async () => {\n      let folder = createFolder()\n      let file = path.join(folder, 'package.json')\n      fs.writeFileSync(file, '{\"engines\": {\"coc\": \">=0.0.80\"}}', 'utf8')\n      await expect(async () => {\n        await loadGlobalJsonAsync(folder, '0.0.79')\n      }).rejects.toThrow(/not match/)\n    })\n\n    it('should throw when main file not found', async () => {\n      let folder = createFolder()\n      let file = path.join(folder, 'package.json')\n      fs.writeFileSync(file, '{\"engines\": {\"coc\": \">=0.0.80\"}}', 'utf8')\n      await expect(async () => {\n        await loadGlobalJsonAsync(folder, '0.0.80')\n      }).rejects.toThrow(/not found/)\n    })\n\n    it('should load json', async () => {\n      let folder = createFolder()\n      let file = path.join(folder, 'package.json')\n      fs.writeFileSync(file, '{\"name\": \"foo\",\"engines\": {\"coc\": \">=0.0.80\"}}', 'utf8')\n      fs.writeFileSync(path.join(folder, 'index.js'), '', 'utf8')\n      let res = await loadGlobalJsonAsync(folder, '0.0.80')\n      expect(res.name).toBe('foo')\n    })\n  })\n\n  describe('validExtensionFolder()', () => {\n    it('should check validExtensionFolder', async () => {\n      expect(validExtensionFolder(__dirname, '')).toBe(false)\n      let folder = path.join(os.tmpdir(), uuid())\n      fs.mkdirSync(folder)\n      disposables.push(Disposable.create(() => {\n        fs.rmSync(folder, { recursive: true, force: true })\n      }))\n      writeJson(path.join(folder, 'index.js'), '')\n      let filepath = path.join(folder, 'package.json')\n      writeJson(filepath, { name: 'name', engines: { coc: '>=0.0.81' } })\n      expect(validExtensionFolder(folder, '0.0.82')).toBe(true)\n    })\n  })\n\n  describe('checkExtensionRoot', () => {\n\n    it('should not throw on error', async () => {\n      let spy = jest.spyOn(fs, 'existsSync').mockImplementation(() => {\n        throw new Error('my error')\n      })\n      let called = false\n      let s = jest.spyOn(console, 'error').mockImplementation(() => {\n        called = true\n      })\n      let root = path.join(os.tmpdir(), 'foo-bar')\n      let res = checkExtensionRoot(root)\n      s.mockRestore()\n      spy.mockRestore()\n      expect(res).toBe(false)\n    })\n\n    it('should create root when it does not exist', async () => {\n      let root = path.join(os.tmpdir(), 'foo-bar')\n      let res = checkExtensionRoot(root)\n      expect(res).toBe(true)\n      expect(fs.existsSync(path.join(root, 'package.json'))).toBe(true)\n      let method = typeof fs['rmSync'] === 'function' ? 'rmSync' : 'rmdirSync'\n      fs[method](root, { recursive: true })\n    })\n\n    it('should remove unexpted file', async () => {\n      let root = path.join(os.tmpdir(), uuid())\n      fs.writeFileSync(root, '')\n      let res = checkExtensionRoot(root)\n      expect(res).toBe(true)\n      expect(fs.existsSync(path.join(root, 'package.json'))).toBe(true)\n      let method = typeof fs['rmSync'] === 'function' ? 'rmSync' : 'rmdirSync'\n      fs[method](root, { recursive: true })\n    })\n  })\n\n  describe('loadExtensionJson()', () => {\n    function testErrors(data: any, version: string, count, createJs = false): any {\n      let folder = path.join(os.tmpdir(), uuid())\n      fs.mkdirSync(folder)\n      disposables.push(Disposable.create(() => {\n        fs.rmSync(folder, { recursive: true, force: true })\n      }))\n      if (createJs) writeJson(path.join(folder, 'index.js'), '')\n      let filepath = path.join(folder, 'package.json')\n      if (data) writeJson(filepath, data)\n      let errors: string[] = []\n      let json = loadExtensionJson(folder, version, errors)\n      expect(errors.length).toBe(count)\n      return json\n    }\n\n    it('should add errors', async () => {\n      testErrors(undefined, '', 1)\n      testErrors({}, '', 2)\n      testErrors({ name: 'name', main: 'main' }, '', 1)\n      testErrors({ name: 'name', engines: {} }, '', 2)\n      testErrors({ name: 'name', engines: { coc: '>=0.0.81' } }, '0.0.79', 1, true)\n      testErrors({ name: 'name', engines: { coc: '>=0.0.81', main: 'index.js' } }, '0.0.82', 0, true)\n    })\n\n    it('should not check entry for vscode extension', async () => {\n      testErrors({ name: 'name', engines: { vscode: '0.10.x' } }, '', 0)\n    })\n  })\n\n  describe('getExtensionName', () => {\n    it('should get extension name', async () => {\n      expect(getExtensionName('foo')).toBe('foo')\n      expect(getExtensionName('http://1')).toBe('http://1')\n      expect(getExtensionName('@a/b')).toBe('@a/b')\n      expect(getExtensionName('semver@1.2.3')).toBe('semver')\n    })\n  })\n})\n\ndescribe('ExtensionStat', () => {\n  function createDB(folder: string, data: any): string {\n    let s = JSON.stringify(data, null, 2)\n    let filepath = path.join(folder, 'db.json')\n    fs.writeFileSync(filepath, s, 'utf8')\n    return filepath\n  }\n\n  function create(): [ExtensionStat, string] {\n    let folder = path.join(os.tmpdir(), uuid())\n    fs.mkdirSync(folder)\n    disposables.push(Disposable.create(() => {\n      fs.rmSync(folder, { force: true, recursive: true })\n    }))\n    return [new ExtensionStat(folder), path.join(folder, 'package.json')]\n  }\n\n  it('should not throw on create', async () => {\n    let spy = jest.spyOn(ExtensionStat.prototype, 'migrate' as any).mockImplementation(() => {\n      throw new Error('my error')\n    })\n    let folder = path.join(os.tmpdir(), uuid())\n    fs.mkdirSync(folder)\n    let stat = new ExtensionStat(folder)\n    spy.mockRestore()\n    expect(stat).toBeDefined()\n  })\n\n  it('should add local extension', async () => {\n    let folder = path.join(os.tmpdir(), uuid())\n    let stat = new ExtensionStat(folder)\n    stat.addLocalExtension('name', folder)\n    expect(stat.getFolder('name')).toBe(folder)\n    expect(stat.getFolder('unknown')).toBeUndefined()\n  })\n\n  it('should addNoPromptFolder', async () => {\n    let [state, filepath] = create()\n    let uri = URI.file(path.dirname(filepath)).toString()\n    expect(state.shouldPrompt(uri)).toBe(true)\n    state.addNoPromptFolder(uri)\n    state.addNoPromptFolder(uri)\n    expect(state.shouldPrompt(uri)).toBe(false)\n  })\n\n  it('should iterate activated extensions', () => {\n    let folder = createFolder()\n    writeJson(path.join(folder, 'package.json'), {\n      disabled: ['x', 'y'],\n      dependencies: { x: '', y: '', z: '', a: '' }\n    })\n    let names: string[] = []\n    let stat = new ExtensionStat(folder)\n    for (let name of stat.activated()) {\n      names.push(name)\n    }\n    expect(names).toEqual(['z', 'a'])\n  })\n\n  it('should migrate #1', async () => {\n    let folder = createFolder()\n    let stat = new ExtensionStat(folder)\n    expect(stat.getExtensionsStat()).toEqual({})\n    let data = {\n      extension: {\n        x: { disabled: true },\n        y: { locked: true },\n        z: {}\n      }\n    }\n    let filepath = createDB(folder, data)\n    writeJson(path.join(folder, 'package.json'), {\n      dependencies: { x: '', y: '', z: '', a: '' }\n    })\n    stat = new ExtensionStat(folder)\n    let res = stat.getExtensionsStat()\n    expect(res).toEqual({ x: 1, y: 2, z: 0, a: 0 })\n    let obj = loadJson(path.join(folder, 'package.json')) as any\n    expect(obj.disabled).toEqual(['x'])\n    expect(obj.locked).toEqual(['y'])\n    expect(fs.existsSync(filepath)).toBe(false)\n  })\n\n  it('should migrate #2', async () => {\n    let folder = createFolder()\n    let stat = new ExtensionStat(folder)\n    expect(stat.getExtensionsStat()).toEqual({})\n    let data = {}\n    createDB(folder, data)\n    writeJson(path.join(folder, 'package.json'), {})\n    stat = new ExtensionStat(folder)\n    let res = stat.getExtensionsStat()\n    expect(res).toEqual({})\n    let obj = loadJson(path.join(folder, 'package.json')) as any\n    expect(obj.disabled).toEqual([])\n    expect(obj.locked).toEqual([])\n  })\n\n  it('should load disabled & locked from package.json', async () => {\n    let folder = createFolder()\n    let obj = {\n      disabled: ['foo'],\n      locked: ['bar'],\n      dependencies: {\n        foo: '',\n        bar: '',\n        z: ''\n      }\n    }\n    writeJson(path.join(folder, 'package.json'), obj)\n    let stat = new ExtensionStat(folder)\n    expect(stat.disabledExtensions).toEqual(['foo'])\n    expect(stat.lockedExtensions).toEqual(['bar'])\n    expect(stat.getExtensionsStat()['z']).toBe(0)\n  })\n\n  it('should add & remove extension', async () => {\n    let [stat, jsonFile] = create()\n    stat.addExtension('foo', '')\n    expect(stat.getExtensionsStat()).toEqual({ foo: 0 })\n    let res = loadJson(jsonFile) as any\n    expect(res).toEqual({ dependencies: { foo: '' } })\n    stat.removeExtension('foo',)\n    expect(stat.isDisabled('foo')).toBe(false)\n    expect(stat.getExtensionsStat()).toEqual({})\n    res = loadJson(jsonFile) as any\n    expect(res).toEqual({ dependencies: {} })\n  })\n\n  it('should remove extension not exists', async () => {\n    let [stat] = create()\n    stat.removeExtension('foo')\n  })\n\n  it('should remove from disabled and locked extensions', async () => {\n    let [stat, jsonFile] = create()\n    stat.addExtension('foo', '')\n    stat.setDisable('foo', true)\n    stat.setLocked('foo', true)\n    let res = loadJson(jsonFile) as any\n    expect(res.disabled).toEqual(['foo'])\n    expect(res.locked).toEqual(['foo'])\n    stat.removeExtension('foo')\n    res = loadJson(jsonFile) as any\n    expect(res.disabled).toEqual([])\n    expect(res.locked).toEqual([])\n  })\n\n  it('should setDisable', async () => {\n    let [stat] = create()\n    stat.addExtension('foo', '')\n    stat.setDisable('foo', true)\n    expect(stat.hasExtension('foo')).toBe(true)\n    expect(stat.isDisabled('foo')).toBe(true)\n    stat.setDisable('foo', false)\n    expect(stat.isDisabled('foo')).toBe(false)\n    expect(stat.disabledExtensions).toEqual([])\n  })\n\n  it('should setLocked', async () => {\n    let [stat] = create()\n    stat.addExtension('foo', '')\n    stat.setLocked('foo', true)\n    expect(stat.lockedExtensions).toEqual(['foo'])\n    stat.setLocked('foo', false)\n    expect(stat.lockedExtensions).toEqual([])\n  })\n\n  it('should check update', async () => {\n    let [stat] = create()\n    expect(stat.shouldUpdate('never')).toBe(false)\n    expect(stat.shouldUpdate('daily')).toBe(true)\n    stat.setLastUpdate()\n    expect(stat.shouldUpdate('weekly')).toBe(false)\n  })\n\n  it('should toInterval', async () => {\n    expect(typeof toInterval('daily')).toBe('number')\n    expect(typeof toInterval('weekly')).toBe('number')\n  })\n\n  it('should get dependencies', async () => {\n    let [stat] = create()\n    expect(stat.dependencies).toEqual({})\n    expect(stat.globalIds).toEqual([])\n    stat.addExtension('foo', '')\n    expect(stat.dependencies).toEqual({ foo: '' })\n    expect(stat.globalIds).toEqual(['foo'])\n  })\n\n  it('should filterGlobalExtensions', async () => {\n    let [stat, jsonFile] = create()\n    expect(stat.filterGlobalExtensions(['foo', 'bar', undefined, 3] as any)).toEqual(['foo', 'bar'])\n    stat.addExtension('foo', '')\n    expect(stat.filterGlobalExtensions(['foo', 'bar'])).toEqual(['bar'])\n    stat.setDisable('bar', true)\n    expect(stat.filterGlobalExtensions(['foo', 'bar'])).toEqual([])\n    let folder = path.resolve(jsonFile, '../node_modules')\n    fs.mkdirSync(folder)\n    fs.mkdirSync(path.join(folder, 'uri'))\n    writeJson(path.join(folder, 'uri', 'package.json'), {})\n    stat.addExtension('uri', 'http://git')\n    stat.addExtension('simple', '')\n    fs.mkdirSync(path.join(folder, 'simple'))\n    writeJson(path.join(folder, 'simple', 'package.json'), {})\n    let res = stat.filterGlobalExtensions(['http://git'])\n    expect(res).toEqual([])\n  })\n})\n\ndescribe('InstallBuffer', () => {\n  afterEach(() => {\n    events.requesting = false\n  })\n\n  it('should sync by not split', async () => {\n    global.__TEST__ = false\n    let buf = new InstallBuffer({ isUpdate: false, updateUIInTab: false })\n    disposables.push(buf)\n    events.requesting = true\n    await buf.start(['a', 'b', 'c'])\n    let wins = await nvim.windows\n    expect(wins.length).toBe(1)\n    global.__TEST__ = true\n  })\n\n  it('should draw buffer with stats', async () => {\n    let buf = new InstallBuffer({ isUpdate: true, updateUIInTab: true })\n    disposables.push(buf)\n    buf.draw()\n    await buf.start(['a', 'b', 'c', 'd'])\n    buf.startProgress('a')\n    buf.startProgress('b')\n    buf.startProgress('c')\n    buf.addMessage('a', 'Updated to 1.0.0')\n    buf.addMessage('b', 'message')\n    buf.finishProgress('a', true)\n    buf.finishProgress('b', false)\n    buf.draw()\n    buf.finishProgress('c', true)\n    buf.finishProgress('d', true)\n    let buffer = await nvim.buffer\n    let lines = await buffer.lines\n    expect(lines.length).toBe(6)\n    buf.draw()\n  })\n\n  it('should stop when all items finished', async () => {\n    let buf = new InstallBuffer({ isUpdate: false })\n    disposables.push(buf)\n    await buf.start(['a', 'b'])\n    buf.startProgress('a')\n    buf.startProgress('b')\n    expect(buf.remains).toBe(2)\n    buf.finishProgress('a', true)\n    buf.finishProgress('b', true)\n    buf.draw()\n    expect(buf.getMessages(0)).toEqual([])\n    expect(buf.stopped).toBe(true)\n  })\n\n  it('should show messages and dispose', async () => {\n    events.requesting = true\n    let buf = new InstallBuffer({ isUpdate: true })\n    disposables.push(buf)\n    await buf.start(['a', 'b'])\n    buf.startProgress('a')\n    buf.addMessage('a', 'start')\n    buf.addMessage('a', 'finish')\n    buf.finishProgress('a', true)\n    buf.draw()\n    let bufnr = await nvim.call('bufnr', ['%'])\n    await nvim.call('cursor', [3, 4])\n    let id = await helper.waitFloat()\n    let win = nvim.createWindow(id)\n    let buffer = await win.buffer\n    let lines = await buffer.lines\n    expect(lines.join(' ')).toBe('start finish')\n    await nvim.command(`bd! ${bufnr}`)\n    expect(buf.stopped).toBe(true)\n  })\n})\n\ndescribe('InstallChannel', () => {\n  it('should create install InstallChannel', async () => {\n    let outputChannel = window.createOutputChannel('test')\n    let channel = new InstallChannel({ isUpdate: true }, outputChannel)\n    channel.start(['a', 'b'])\n    channel.startProgress('a')\n    channel.addMessage('a', 'msg', true)\n    channel.addMessage('a', 'msg', false)\n    channel.finishProgress('a', true)\n    channel.finishProgress('b', false)\n  })\n\n  it('should create update InstallChannel', async () => {\n    let outputChannel = window.createOutputChannel('test')\n    let channel = new InstallChannel({ isUpdate: false }, outputChannel)\n    channel.start(['a', 'b'])\n    channel.startProgress('a')\n    channel.finishProgress('a', true)\n    channel.finishProgress('b', false)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/extensions.test.ts",
    "content": "import fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport { v4 as uuid } from 'uuid'\nimport { URI } from 'vscode-uri'\nimport which from 'which'\nimport commands from '../../commands'\nimport { ConfigurationUpdateTarget } from '../../configuration/types'\nimport extensions, { Extensions, toUrl } from '../../extension'\nimport { Disposable, disposeAll } from '../../util'\nimport { writeFile, writeJson } from '../../util/fs'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet tmpfolder: string\nlet disposables: Disposable[] = []\nbeforeAll(async () => {\n  await helper.setup()\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(() => {\n  if (tmpfolder) {\n    fs.rmSync(tmpfolder, { force: true, recursive: true })\n    tmpfolder = undefined\n  }\n  disposeAll(disposables)\n})\n\ndescribe('extensions', () => {\n  it('should convert url', async () => {\n    expect(toUrl('https://github.com/a/b.git#master')).toBe('https://github.com/a/b')\n    expect(toUrl('https://github.com/a/b.git#main')).toBe('https://github.com/a/b')\n    expect(toUrl('url')).toBe('')\n  })\n\n  it('should have events', async () => {\n    expect(Extensions).toBeDefined()\n    expect(extensions.onDidLoadExtension).toBeDefined()\n    expect(extensions.onDidActiveExtension).toBeDefined()\n    expect(extensions.onDidUnloadExtension).toBeDefined()\n    expect(extensions.schemes).toBeDefined()\n    expect(extensions.createInstaller('npm', 'id')).toBeDefined()\n  })\n\n  it('should not throw with addSchemeProperty', async () => {\n    extensions.addSchemeProperty('', null)\n  })\n\n  it('should get update settings', async () => {\n    let settings = extensions.getUpdateSettings()\n    expect(settings.updateCheck).toBe('never')\n    expect(settings.updateUIInTab).toBe(false)\n    expect(settings.silentAutoupdate).toBe(true)\n    let config = workspace.getConfiguration('extensions')\n    await config.update('updateCheck', 'weekly', ConfigurationUpdateTarget.Global)\n    await config.update('updateUIInTab', true, ConfigurationUpdateTarget.Global)\n    await config.update('silentAutoupdate', false, ConfigurationUpdateTarget.Global)\n    settings = extensions.getUpdateSettings()\n    expect(settings.updateCheck).toBe('weekly')\n    expect(settings.updateUIInTab).toBe(true)\n    expect(settings.silentAutoupdate).toBe(false)\n    await config.update('updateCheck', undefined, ConfigurationUpdateTarget.Global)\n    await config.update('updateUIInTab', undefined, ConfigurationUpdateTarget.Global)\n    await config.update('silentAutoupdate', undefined, ConfigurationUpdateTarget.Global)\n  })\n\n  it('should toggle auto update', async () => {\n    await commands.executeCommand('extensions.toggleAutoUpdate')\n    let config = workspace.getConfiguration('extensions')\n    expect(config.get('updateCheck')).toBe('daily')\n    await commands.executeCommand('extensions.toggleAutoUpdate')\n    config = workspace.getConfiguration('extensions')\n    expect(config.get('updateCheck')).toBe('never')\n    await config.update('extensions.updateCheck', undefined, ConfigurationUpdateTarget.Global)\n  })\n\n  it('should get extensions stat', async () => {\n    process.env.COC_NO_PLUGINS = '1'\n    await extensions.globalExtensions()\n    let stats = await extensions.getExtensionStates()\n    expect(stats.length).toBe(0)\n    process.env.COC_NO_PLUGINS = '0'\n  })\n\n  it('should add global extensions', async () => {\n    extensions.states.addExtension('foo', '0.0.1')\n    extensions.states.addExtension('bar', '0.0.1')\n    extensions.modulesFolder = path.join(os.tmpdir(), uuid())\n    let folder = path.join(extensions.modulesFolder, 'foo')\n    writeJson(path.join(folder, 'package.json'), { name: 'foo', engines: { coc: '>=0.0.1' } })\n    fs.writeFileSync(path.join(folder, 'index.js'), '')\n    let res = await extensions.globalExtensions()\n    expect(res.length).toBe(1)\n    fs.rmSync(extensions.modulesFolder, { recursive: true })\n    extensions.states.removeExtension('foo')\n  })\n\n  it('should has extension', async () => {\n    let res = extensions.has('test')\n    expect(res).toBe(false)\n    expect(extensions.isActivated('unknown')).toBe(false)\n    let loaded = await helper.doAction('loadedExtensions')\n    expect(loaded).toEqual([])\n    let stats = await helper.doAction('extensionStats')\n    expect(stats).toBeDefined()\n  })\n\n  it('should load global extensions', async () => {\n    extensions.states.addExtension('foo', '0.0.1')\n    let stats = extensions.globalExtensionStats()\n    expect(stats).toEqual([])\n    extensions.states.removeExtension('foo')\n    process.env.COC_NO_PLUGINS = '1'\n    stats = extensions.globalExtensionStats()\n    expect(stats).toEqual([])\n    process.env.COC_NO_PLUGINS = '0'\n  })\n\n  it('should load extension stats from runtimepath', () => {\n    let f1 = path.join(os.tmpdir(), uuid())\n    fs.mkdirSync(f1)\n    writeJson(path.join(f1, 'package.json'), { name: 'name', engines: { coc: '>=0.0.1' } })\n    fs.writeFileSync(path.join(f1, 'index.js'), '')\n    let f2 = path.join(os.tmpdir(), uuid())\n    fs.mkdirSync(f2)\n    writeJson(path.join(f2, 'package.json'), { name: 'folder', engines: { coc: '>=0.0.1' } })\n    fs.writeFileSync(path.join(f2, 'index.js'), '')\n    extensions.states.addExtension('folder', '0.0.1')\n    let res = extensions.runtimeExtensionStats([f1, f2])\n    expect(res.length).toBe(1)\n    expect(res[0].id).toBe('name')\n    extensions.states.removeExtension('folder')\n    fs.rmSync(f1, { recursive: true, force: true })\n    fs.rmSync(f2, { recursive: true, force: true })\n  })\n\n  it('should force update extensions', async () => {\n    let spy = jest.spyOn(extensions, 'installExtensions').mockImplementation(() => {\n      return Promise.resolve()\n    })\n    await commands.executeCommand('extensions.forceUpdateAll')\n    spy.mockRestore()\n  })\n\n  it('should auto update', async () => {\n    let spy = jest.spyOn(extensions.states, 'shouldUpdate').mockImplementation(() => {\n      return true\n    })\n    let s = jest.spyOn(extensions, 'updateExtensions').mockImplementation(() => {\n      return Promise.reject(new Error('error on update'))\n    })\n    await extensions.activateExtensions()\n    spy.mockRestore()\n    s.mockRestore()\n  })\n\n  it('should use absolute path for npm', async () => {\n    let res = extensions.npm\n    expect(path.isAbsolute(res)).toBe(true)\n  })\n\n  it('should not throw when npm not found', async () => {\n    let spy = jest.spyOn(which, 'sync').mockImplementation(() => {\n      throw new Error('not executable')\n    })\n    let res = extensions.npm\n    expect(res).toBeNull()\n    await extensions.updateExtensions()\n    spy.mockRestore()\n  })\n\n  it('should get all extensions', () => {\n    let list = extensions.all\n    expect(Array.isArray(list)).toBe(true)\n  })\n\n  it('should call extension API', async () => {\n    let fn = async () => {\n      await extensions.call('test', 'echo', ['5'])\n    }\n    await expect(fn()).rejects.toThrow(Error)\n  })\n\n  it('should catch error when installExtensions', async () => {\n    let spy = jest.spyOn(extensions, 'createInstaller').mockImplementation(() => {\n      return {\n        on: (_key, cb) => {\n          cb('msg', false)\n        },\n        install: () => {\n          return Promise.resolve({ name: 'name', url: 'http://e', version: '1.0.0' })\n        }\n      } as any\n    })\n    let s = jest.spyOn(extensions.states, 'setLocked').mockImplementation(() => {\n      throw new Error('my error')\n    })\n    await extensions.installExtensions(['abc@1.0.0'])\n    spy.mockRestore()\n    s.mockRestore()\n  })\n\n  it('should catch error on updateExtensions', async () => {\n    let spy = jest.spyOn(extensions, 'globalExtensionStats').mockImplementation(() => {\n      return [{ id: 'test' }] as any\n    })\n    let s = jest.spyOn(extensions, 'createInstaller').mockImplementation(() => {\n      return {\n        on: () => {},\n        update: () => {\n          return Promise.resolve(path.join(os.tmpdir(), uuid()))\n        }\n      } as any\n    })\n    await helper.doAction('updateExtensions', true)\n    spy.mockRestore()\n    s.mockRestore()\n  })\n\n  it('should update enabled extensions', async () => {\n    let spy = jest.spyOn(extensions, 'globalExtensionStats').mockImplementation(() => {\n      return [{ id: 'test' }, { id: 'global', isLocked: true }, { id: 'disabled', state: 'disabled' }] as any\n    })\n    let s = jest.spyOn(extensions, 'createInstaller').mockImplementation(() => {\n      return {\n        on: (_key, cb) => {\n          cb('msg', false)\n        },\n        update: async () => {\n          await helper.wait(1)\n          return ''\n        }\n      } as any\n    })\n    await extensions.updateExtensions(true, true)\n    spy.mockRestore()\n    s.mockRestore()\n  })\n\n  it('should update extensions by url', async () => {\n    let spy = jest.spyOn(extensions, 'globalExtensionStats').mockImplementation(() => {\n      return [{ id: 'test', exotic: true, uri: 'http://example.com' }] as any\n    })\n    let called = false\n    let s = jest.spyOn(extensions, 'createInstaller').mockImplementation(() => {\n      return {\n        on: (_key, cb) => {\n          cb('msg', false)\n        },\n        update: async url => {\n          await helper.wait(1)\n          called = true\n          expect(url).toBe('http://example.com')\n          return ''\n        }\n      } as any\n    })\n    await extensions.updateExtensions()\n    expect(called).toBe(true)\n    spy.mockRestore()\n    s.mockRestore()\n  })\n\n  it('should clean unnecessary folders & links', async () => {\n    // create folder and link in modulesFolder\n    let folder = path.join(extensions.modulesFolder, 'test')\n    let link = path.join(extensions.modulesFolder, 'test-link')\n    fs.mkdirSync(folder, { recursive: true })\n    fs.symlinkSync(folder, link)\n    let stats = extensions.states\n    stats.addExtension('foo', '1.0.0')\n    let extensionFolder = path.join(extensions.modulesFolder, 'foo')\n    fs.mkdirSync(extensionFolder, { recursive: true })\n    extensions.cleanModulesFolder()\n    expect(fs.existsSync(folder)).toBe(false)\n    expect(fs.existsSync(link)).toBe(false)\n    stats.removeExtension('foo')\n    expect(fs.existsSync(extensionFolder)).toBe(true)\n    fs.rmSync(extensionFolder, { recursive: true })\n  })\n\n  it('should install global extension', async () => {\n    expect(extensions.getExtensionById('coc-omni')).toBeUndefined()\n    let folder = path.join(extensions.modulesFolder, 'coc-omni')\n    let spy = jest.spyOn(extensions, 'createInstaller').mockImplementation(() => {\n      return {\n        on: () => {},\n        install: async () => {\n          fs.mkdirSync(folder, { recursive: true })\n          let file = path.join(folder, 'package.json')\n          await writeFile(file, JSON.stringify({ name: 'coc-omni', engines: { coc: '>=0.0.1' }, version: '0.0.1' }, null, 2))\n          await writeFile(path.join(folder, 'index.js'), 'exports.activate = () => {}')\n          return { name: 'coc-omni', version: '1.0.0', folder }\n        }\n      } as any\n    })\n    await helper.doAction('installExtensions', 'coc-omni')\n    let item = extensions.getExtension('coc-omni')\n    expect(item).toBeDefined()\n    expect(extensions.getExtensionById('coc-omni')).toBeDefined()\n    expect(item.extension.isActive).toBe(true)\n    expect(extensions.isActivated('coc-omni')).toBe(true)\n    let globals = extensions.globalExtensionStats()\n    expect(globals.length).toBe(1)\n    expect((await extensions.getExtensionStates()).length).toBeGreaterThan(0)\n    spy.mockRestore()\n    await helper.doAction('reloadExtension', 'coc-omni')\n    await helper.doAction('deactivateExtension', 'coc-omni')\n    await helper.doAction('activeExtension', 'coc-omni')\n    await helper.doAction('toggleExtension', 'coc-omni')\n    await helper.doAction('uninstallExtension', 'coc-omni')\n    item = extensions.getExtension('coc-omni')\n    expect(item).toBeUndefined()\n  })\n\n  it('should checkRecommendation', async () => {\n    await extensions.checkRecommendation({ name: 'tmp', uri: URI.file(__dirname).toString() })\n    tmpfolder = path.join(os.tmpdir(), uuid())\n    let folder = path.join(tmpfolder, '.vim')\n    fs.mkdirSync(folder, { recursive: true })\n\n    // fs.mkdirSync(path.join(tmpfolder, '.git'), { recursive: true })\n    let jsonFile = path.join(folder, 'coc-settings.json')\n    fs.writeFileSync(jsonFile, `{\"extensions.recommendations\": [\"coc-abc\", \"coc-def\"]}`)\n    let returnValue\n    let calledTimes = 0\n    let spy = jest.spyOn(window, 'showInformationMessage').mockImplementation(() => {\n      calledTimes++\n      return Promise.resolve(returnValue)\n    })\n    disposables.push({\n      dispose: () => {\n        spy.mockRestore()\n      }\n    })\n    await helper.edit(jsonFile)\n    workspace.workspaceFolderControl.addWorkspaceFolder(tmpfolder, true)\n    await helper.waitValue(() => calledTimes, 1)\n    let called = false\n    let s = jest.spyOn(extensions, 'installExtensions').mockImplementation(() => {\n      called = true\n      return Promise.resolve(undefined)\n    })\n    disposables.push({\n      dispose: () => {\n        s.mockRestore()\n      }\n    })\n    returnValue = { index: 1 }\n    let uri = URI.file(tmpfolder).toString()\n    await extensions.checkRecommendation({ name: 'tmp', uri })\n    expect(called).toBe(true)\n    returnValue = { index: 2 }\n    await extensions.checkRecommendation({ name: 'tmp', uri })\n    expect(extensions.states.shouldPrompt(uri)).toBe(false)\n    let curr = calledTimes\n    await extensions.checkRecommendation({ name: 'tmp', uri })\n    expect(calledTimes).toBe(curr)\n    extensions.states.reset()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/fetch.test.ts",
    "content": "import fs from 'fs'\nimport http, { Server } from 'http'\nimport os from 'os'\nimport path from 'path'\nimport semver from 'semver'\nimport { URL } from 'url'\nimport { promisify } from 'util'\nimport { v4 as uuid } from 'uuid'\nimport { CancellationTokenSource } from 'vscode-languageserver-protocol'\nimport download, { getEtag, getExtname } from '../../model/download'\nimport fetch, { getAgent, getDataType, getRequestModule, getSystemProxyURI, getText, request, resolveRequestOptions, toPort, toURL } from '../../model/fetch'\nimport helper, { getPort } from '../helper'\n\nprocess.env.NO_PROXY = '*'\nlet port: number\nbeforeAll(async () => {\n  await helper.setup()\n  port = await createServer()\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n  for (let server of servers) {\n    server.close()\n  }\n  servers = []\n})\n\nafterEach(() => {\n  helper.workspace.configurations.reset()\n})\n\nlet servers: Server[] = []\nasync function createServer(): Promise<number> {\n  let port = await getPort()\n  return await new Promise(resolve => {\n    const server = http.createServer((req, res) => {\n      if (req.url === '/bad_json') {\n        res.writeHead(200, { 'Content-Type': 'application/json;charset=utf8' })\n        res.end('{\"x\"')\n      }\n      if (req.url === '/slow') {\n        setTimeout(() => {\n          res.writeHead(200)\n          res.end('abc')\n        }, 50)\n      }\n      if (req.url === '/json') {\n        res.writeHead(200, { 'Content-Type': 'application/json;charset=utf8' })\n        res.end(JSON.stringify({ result: 'succeed' }))\n      }\n      if (req.url === '/text') {\n        res.writeHead(200, { 'Content-Type': 'text/plain' })\n        res.end('text')\n      }\n      if (req.url === '/404') {\n        res.writeHead(404, { 'Content-Type': 'text/plain' })\n        res.end('not found')\n      }\n      if (req.url === '/reject') {\n        setTimeout(() => {\n          res.socket.destroy(new Error('Rejected'))\n        }, 20)\n      }\n      if (req.url === '/close') {\n        res.writeHead(200, { 'Content-Type': 'text/plain' })\n        res.write(\"foo\")\n        setTimeout(() => {\n          res.destroy(new Error('closed'))\n        }, 20)\n      }\n      if (req.url === '/binary') {\n        let file = path.join(os.tmpdir(), 'binary_file')\n        if (!fs.existsSync(file)) {\n          res.writeHead(404)\n          res.end()\n          return\n        }\n        let stat = fs.statSync(file)\n        res.setHeader('Content-Length', stat.size)\n        res.setHeader('Etag', '\"4c6426ac7ef186464ecbb0d81cbfcb1e\"')\n        res.writeHead(200)\n        let stream = fs.createReadStream(file, { highWaterMark: 10 * 1024 })\n        stream.pipe(res)\n      }\n      if (req.url.startsWith('/zip')) {\n        let zipfile = path.resolve(__dirname, '../test.zip')\n        if (req.url.indexOf('nolength=1') == -1) {\n          let stat = fs.statSync(zipfile)\n          res.setHeader('Content-Length', stat.size)\n          res.setHeader('Content-Disposition', 'attachment')\n        }\n        res.setHeader('Content-Type', 'application/zip')\n        res.writeHead(200)\n        let stream = fs.createReadStream(zipfile, { highWaterMark: 1 * 1024 })\n        stream.pipe(res)\n      }\n      if (req.url === '/tgz') {\n        res.setHeader('Content-Disposition', 'attachment; filename=\"file.tgz\"')\n        res.setHeader('Content-Type', 'application/octet-stream')\n        let tarfile = path.resolve(__dirname, '../test.tar.gz')\n        let stat = fs.statSync(tarfile)\n        res.setHeader('Content-Length', stat.size)\n        res.writeHead(200)\n        let stream = fs.createReadStream(tarfile)\n        stream.pipe(res)\n      }\n    })\n    servers.push(server)\n    server.unref()\n    server.listen(port, () => {\n      resolve(port)\n    })\n  })\n}\n\ndescribe('utils', () => {\n  it('should getText', () => {\n    expect(getText({ x: 1 })).toBe('{\"x\":1}')\n  })\n\n  it('should getExtname', () => {\n    let res = getExtname('attachment; x=\"y\"')\n    expect(res).toBeUndefined()\n  })\n\n  it('should getPort', async () => {\n    expect(toPort(80, 'http')).toBe(80)\n    expect(toPort('80', 'http')).toBe(80)\n    expect(toPort('x', 'http')).toBe(80)\n    expect(toPort('', 'https')).toBe(443)\n  })\n\n  it('should getEtag', () => {\n    expect(getEtag({})).toBeUndefined()\n    expect(getEtag({ etag: '\"abc\"' })).toBe('abc')\n    expect(getEtag({ etag: 'W/\"abc\"' })).toBe('abc')\n    expect(getEtag({ etag: 'Wabc\"' })).toBeUndefined()\n  })\n\n  it('should get data type', () => {\n    expect(getDataType(null)).toBe('null')\n    expect(getDataType(undefined)).toBe('undefined')\n    expect(getDataType('s')).toBe('string')\n    let b = Buffer.from('abc', 'utf8')\n    expect(getDataType(b)).toBe('buffer')\n    expect(getDataType({})).toBe('object')\n    expect(getDataType(new Date())).toBe('unknown')\n  })\n\n  it('should getRequestModule', () => {\n    let url = toURL('https://www.baidu.com')\n    expect(getRequestModule(url)).toBeDefined()\n  })\n\n  it('should convert to URL', () => {\n    expect(() => { toURL('') }).toThrow()\n    expect(() => { toURL('file:///1') }).toThrow()\n    expect(() => { toURL(undefined) }).toThrow()\n    expect(toURL('https://www.baidu.com').toString()).toBe('https://www.baidu.com/')\n    let u = new URL('http://www.baidu.com')\n    expect(toURL(u)).toBe(u)\n  })\n\n  it('should report valid proxy', () => {\n    let agent = getAgent(new URL('http://google.com'), { proxy: 'domain.com:1234' })\n    expect(agent).toBe(null)\n\n    agent = getAgent(new URL('http://google.com'), { proxy: 'ftp://domain.com:1234' })\n    expect(agent).toBe(null)\n\n    agent = getAgent(new URL('http://google.com'), { proxy: '' })\n    expect(agent).toBe(null)\n\n    agent = getAgent(new URL('http://google.com'), { proxy: 'domain.com' })\n    expect(agent).toBe(null)\n\n    agent = getAgent(new URL('https://google.com'), { proxy: 'https://domain.com' })\n    let proxy = (agent as any).proxy\n    expect(proxy.host).toBe('domain.com')\n    expect(proxy.protocol).toBe('https:')\n    expect((agent as any).connectOpts.port).toBe(443)\n\n    agent = getAgent(new URL('http://google.com'), { proxy: 'http://domain.com', proxyStrictSSL: true })\n    proxy = (agent as any).proxy\n    expect(proxy.host).toBe('domain.com')\n    expect(proxy.protocol).toBe('http:')\n    expect((agent as any).connectOpts.port).toBe(80)\n\n    agent = getAgent(new URL('http://google.com'), { proxy: 'https://domain.com:1234' })\n    proxy = (agent as any).proxy\n    expect(proxy.host).toBe('domain.com:1234')\n    expect(proxy.hostname).toBe('domain.com')\n    expect(proxy.port).toBe('1234')\n    expect((agent as any).connectOpts.port).toBe(1234)\n\n    agent = getAgent(new URL('http://google.com'), { proxy: 'http://user:pass@domain.com:1234' })\n    proxy = (agent as any).proxy\n    expect(proxy.host).toBe('domain.com:1234')\n    expect(proxy.hostname).toBe('domain.com')\n    expect(proxy.port).toBe('1234')\n    expect((agent as any).connectOpts.port).toBe(1234)\n    expect(proxy.username).toBe('user')\n    expect(proxy.password).toBe('pass')\n  })\n\n  it('should getAgent from proxy', () => {\n    let agent = getAgent(new URL('http://google.com'), { proxy: 'http://user:@domain.com' })\n    let proxy = (agent as any).proxy\n    expect(proxy.host).toBe('domain.com')\n    expect(proxy.username).toBe('user')\n    expect((agent as any).connectOpts.port).toBe(80)\n  })\n\n  it('should getSystemProxyURI', () => {\n    let url = new URL('http://www.example.com')\n    let http_proxy = 'http://127.0.0.1:7070'\n    expect(getSystemProxyURI(url, { NO_PROXY: '*', HTTP_PROXY: http_proxy })).toBeNull()\n    expect(getSystemProxyURI(url, { no_proxy: '*', HTTP_PROXY: http_proxy })).toBeNull()\n    expect(getSystemProxyURI(new URL('http://www.example.com:80'), {\n      NO_PROXY: 'xyz:33,example.com:80',\n      HTTP_PROXY: http_proxy\n    })).toBeNull()\n    expect(getSystemProxyURI(url, {\n      NO_PROXY: 'baidu.com,example.com',\n      HTTP_PROXY: http_proxy\n    })).toBeNull()\n    expect(getSystemProxyURI(url, { HTTP_PROXY: http_proxy })).toBe(http_proxy)\n    expect(getSystemProxyURI(url, { http_proxy })).toBe(http_proxy)\n    expect(getSystemProxyURI(url, {})).toBe(null)\n    url = new URL('https://www.example.com')\n    let https_proxy = 'https://127.0.0.1:7070'\n    expect(getSystemProxyURI(url, { HTTPS_PROXY: https_proxy })).toBe(https_proxy)\n    expect(getSystemProxyURI(url, { https_proxy })).toBe(https_proxy)\n    expect(getSystemProxyURI(url, { HTTP_PROXY: http_proxy })).toBe(http_proxy)\n    expect(getSystemProxyURI(url, { http_proxy })).toBe(http_proxy)\n    expect(getSystemProxyURI(url, {})).toBe(null)\n  })\n\n  it('should resolve request options #1', async () => {\n    let file = path.join(os.tmpdir(), `${uuid()}/ca`)\n    fs.mkdirSync(path.dirname(file))\n    fs.writeFileSync(file, 'ca', 'utf8')\n    helper.updateConfiguration('http.proxyAuthorization', 'authorization')\n    helper.updateConfiguration('http.proxyCA', file)\n    let url = new URL('http://www.example.com:7070')\n    let res = resolveRequestOptions(url, {\n      query: { x: 1 },\n      method: 'POST',\n      headers: {\n        'Custom-X': '1'\n      },\n      user: 'user',\n      password: 'password',\n      timeout: 1000,\n      data: { foo: '1' },\n      buffer: true,\n    })\n    expect(res.path).toBe('/?x=1')\n    expect(Buffer.isBuffer(res.ca)).toBe(true)\n  })\n\n  it('should resolve request options #2', async () => {\n    let url = new URL('https://abc:123@www.example.com')\n    let res = resolveRequestOptions(url, {\n      user: 'user',\n      data: 'data'\n    })\n    expect(res.port).toBe(443)\n    expect(res.path).toBe('/')\n    expect(res.auth).toBe('abc:123')\n  })\n})\n\ndescribe('fetch', () => {\n\n  it('should fetch json', async () => {\n    let res = await fetch(`http://127.0.0.1:${port}/json`, {\n      method: 'POST',\n      data: 'data'\n    })\n    expect(res).toEqual({ result: 'succeed' })\n    res = await fetch(`http://127.0.0.1:${port}/json`, { buffer: true })\n    expect(Buffer.isBuffer(res)).toBe(true)\n    let fn = async () => {\n      await fetch(`http://127.0.0.1:${port}/bad_json`)\n    }\n    await expect(fn()).rejects.toThrow(Error)\n  })\n\n  it('should catch error on reject or abnormal response', async () => {\n    let fn = async () => {\n      await fetch(`http://127.0.0.1:${port}/reject`)\n    }\n    await expect(fn()).rejects.toThrow()\n  })\n\n  it('should catch abnormal close', async () => {\n    let version = semver.parse(process.version)\n    if (version.major >= 16) {\n      let fn = async () => {\n        await fetch(`http://127.0.0.1:${port}/close`)\n      }\n      await expect(fn()).rejects.toThrow()\n      fn = async () => {\n        await download(`http://127.0.0.1:${port}/close`, { dest: os.tmpdir() })\n      }\n      await expect(fn()).rejects.toThrow()\n    }\n  })\n\n  it('should throw on 404 response', async () => {\n    let fn = async () => {\n      await fetch(`http://127.0.0.1:${port}/404`)\n    }\n    await expect(fn()).rejects.toThrow(Error)\n  })\n\n  it('should catch proxy error', async () => {\n    delete process.env.NO_PROXY\n    process.env.HTTP_PROXY = `http://127.0.0.1`\n    let fn = async () => {\n      await fetch(`http://127.0.0.1:${port}/json`)\n    }\n    await expect(fn()).rejects.toThrow()\n    delete process.env.HTTP_PROXY\n  })\n\n  it('should throw for ECONNRESET error', async () => {\n    await expect(async () => {\n      let obj: any = {}\n      let url = new URL(`http://127.0.0.1:${port}/text`)\n      let opts = resolveRequestOptions(url, {})\n      let p = request(url, undefined, opts, undefined, obj)\n      let err: any = new Error('ECONNRESET')\n      err.code = 'ECONNRESET'\n      obj.req.destroy(err)\n      await p\n    }).rejects.toThrow(/ECONNRESET/)\n  })\n\n  it('should fetch text', async () => {\n    let res = await fetch(`http://127.0.0.1:${port}/text`)\n    expect(res).toBe('text')\n    let fn = async () => {\n      let port = await getPort()\n      res = await fetch(`http://127.0.0.1:${port}/not_exists`, { timeout: 2000 })\n    }\n    await expect(fn()).rejects.toThrow()\n  })\n\n  it('should throw on timeout', async () => {\n    let fn = async () => {\n      await fetch(`http://127.0.0.1:${port}/slow`, { timeout: 50 })\n    }\n    await expect(fn()).rejects.toThrow(Error)\n    let url = new URL(`http://127.0.0.1:${port}/slow`)\n    let opts = {\n      method: 'GET',\n      hostname: '127.0.0.1',\n      port,\n      path: '/slow',\n      rejectUnauthorized: true,\n      maxRedirects: 3,\n      headers: {\n        'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64)',\n        'Accept-Encoding': 'gzip, deflate'\n      },\n      timeout: 50,\n      agent: new http.Agent({ keepAlive: true })\n    }\n    fn = async () => {\n      await request(url, undefined, opts)\n    }\n    await expect(fn()).rejects.toThrow(Error)\n    fn = async () => {\n      await download(url, Object.assign(opts, { dest: os.tmpdir() }))\n    }\n    await expect(fn()).rejects.toThrow(Error)\n\n    opts.agent.destroy()\n  })\n\n  it('should cancel by CancellationToken', async () => {\n    let fn = async () => {\n      let tokenSource = new CancellationTokenSource()\n      let p = fetch(`http://127.0.0.1:${port}/slow`, { timeout: 50 }, tokenSource.token)\n      await helper.wait(1)\n      tokenSource.cancel()\n      await p\n    }\n    await expect(fn()).rejects.toThrow(Error)\n  })\n})\n\ndescribe('download', () => {\n  let binary_file: string\n  let tempdir = path.join(os.tmpdir(), uuid())\n\n  beforeAll(async () => {\n    binary_file = path.join(os.tmpdir(), 'binary_file')\n    if (!fs.existsSync(binary_file)) {\n      let data = Buffer.alloc(100 * 1024, 0)\n      await promisify(fs.writeFile)(binary_file, data)\n    }\n    // create binary files\n  })\n\n  it('should throw for bad option', async () => {\n    let url = 'https://127.0.0.1'\n    let fn = async () => {\n      await download(url, { dest: 'a/b' })\n    }\n    await expect(fn()).rejects.toThrow(Error)\n    fn = async () => {\n      await download(url, { dest: __filename })\n    }\n    await expect(fn()).rejects.toThrow(/not directory/)\n  })\n\n  it('should throw on ECONNRESET', async () => {\n    let obj: any = {}\n    let p = download(`http://127.0.0.1:${port}/binary`, { dest: tempdir }, undefined, obj)\n    let err: any = new Error('ECONNRESET')\n    err.code = 'ECONNRESET'\n    await expect(async () => {\n      obj.req.destroy(err)\n      await p\n    }).rejects.toThrow(Error)\n  })\n\n  it('should throw when unable to extract', async () => {\n    let url = `http://127.0.0.1:${port}/text`\n    let fn = async () => {\n      await download(url, { dest: tempdir, extract: true })\n    }\n    await expect(fn()).rejects.toThrow(/extract method/)\n  })\n\n  it('should throw for bad response', async () => {\n    let fn = async () => {\n      await download(`http://127.0.0.1:${port}/404`, { dest: tempdir })\n    }\n    await expect(fn()).rejects.toThrow(Error)\n    fn = async () => {\n      await download(`http://127.0.0.1:${port}/reject`, { dest: tempdir })\n    }\n    await expect(fn()).rejects.toThrow()\n    fn = async () => {\n      let port = await getPort()\n      await download(`http://127.0.0.1:${port}/not_exists`, { dest: tempdir, timeout: 2000 })\n    }\n    await expect(fn()).rejects.toThrow()\n  })\n\n  it('should throw on timeout', async () => {\n    let fn = async () => {\n      await download(`http://127.0.0.1:${port}/slow`, { dest: tempdir, timeout: 50 })\n    }\n    await expect(fn()).rejects.toThrow()\n  })\n\n  it('should download binary file', async () => {\n    let url = `http://127.0.0.1:${port}/binary`\n    let called = false\n    let res = await download(url, {\n      etagAlgorithm: 'md5',\n      dest: tempdir, onProgress: p => {\n        expect(typeof p).toBe('string')\n        called = true\n      }\n    })\n    expect(called).toBe(true)\n    let exists = fs.existsSync(res)\n    expect(exists).toBe(true)\n  })\n\n  it('should throw when etag check failed', async () => {\n    let url = `http://127.0.0.1:${port}/binary`\n    let called = false\n    let fn = async () => {\n      await download(url, {\n        etagAlgorithm: 'sha256',\n        dest: tempdir, onProgress: p => {\n          expect(typeof p).toBe('string')\n          called = true\n        }\n      })\n    }\n    await expect(fn()).rejects.toThrow(/Etag check failed/)\n  })\n\n  it('should download zip file', async () => {\n    let url = `http://127.0.0.1:${port}/zip`\n    let res = await download(url, {\n      dest: tempdir,\n      extract: true\n    })\n    let file = path.join(tempdir, 'log.txt')\n    let exists = fs.existsSync(file)\n    expect(exists).toBe(true)\n    res = await download(url + '?nolength=1', {\n      dest: tempdir,\n      extract: true\n    })\n    exists = fs.existsSync(file)\n    expect(exists).toBe(true)\n  })\n\n  it('should download tgz', async () => {\n    let url = `http://127.0.0.1:${port}/tgz`\n    let opts = {\n      dest: tempdir,\n      extract: true,\n      timeout: 3000,\n      strip: 0\n    }\n    let res = await download(url, opts)\n    let file = path.join(res, 'test.js')\n    let exists = fs.existsSync(file)\n    expect(exists).toBe(true)\n    opts.strip = undefined\n    res = await download(url, opts)\n    expect(res).toBeDefined()\n  })\n\n  it('should cancel download by CancellationToken', async () => {\n    let fn = async () => {\n      let tokenSource = new CancellationTokenSource()\n      let p = download(`http://127.0.0.1:${port}/slow`, { dest: tempdir }, tokenSource.token)\n      await helper.wait(10)\n      tokenSource.cancel()\n      await p\n    }\n    await expect(fn()).rejects.toThrow(Error)\n  })\n\n  it('should throw on agent error', async () => {\n    delete process.env.NO_PROXY\n    process.env.HTTP_PROXY = `http://127.0.0.1`\n    let fn = async () => {\n      await download(`http://127.0.0.1:${port}/json`, { dest: tempdir })\n    }\n    await expect(fn()).rejects.toThrow(/using proxy/)\n    delete process.env.HTTP_PROXY\n    process.env.NO_PROXY = '*'\n    fn = async () => {\n      let agent = new http.Agent({ keepAlive: true })\n      let p = download(`http://127.0.0.1:${port}/slow`, { dest: tempdir, timeout: 50, agent })\n      await p\n      agent.destroy()\n    }\n    await expect(fn()).rejects.toThrow(/timeout/)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/filter.test.ts",
    "content": "import { isWhitespaceAtPos, fuzzyScore, isSeparatorAtPos, isPatternInWord, createMatches, FuzzyScorer, fuzzyScoreGraceful, fuzzyScoreGracefulAggressive, anyScore, nextTypoPermutation } from '../../util/filter'\nimport * as assert from 'assert'\n\ndescribe('filter functions', () => {\n  function assertMatches(pattern: string, word: string, decoratedWord: string | undefined, filter: FuzzyScorer, opts: { patternPos?: number; wordPos?: number; firstMatchCanBeWeak?: boolean } = {}) {\n    const r = filter(pattern, pattern.toLowerCase(), opts.patternPos || 0, word, word.toLowerCase(), opts.wordPos || 0, { firstMatchCanBeWeak: opts.firstMatchCanBeWeak ?? false, boostFullMatch: true })\n    assert.ok(!decoratedWord === !r)\n    if (r) {\n      const matches = createMatches(r)\n      let actualWord = ''\n      let pos = 0\n      for (const match of matches) {\n        actualWord += word.substring(pos, match.start)\n        actualWord += '^' + word.substring(match.start, match.end).split('').join('^')\n        pos = match.end\n      }\n      actualWord += word.substring(pos)\n      assert.strictEqual(actualWord, decoratedWord)\n    }\n  }\n\n  function assertTopScore(filter: typeof fuzzyScore, pattern: string, expected: number, ...words: string[]) {\n    let topScore = -(100 * 10)\n    let topIdx = 0\n    for (let i = 0; i < words.length; i++) {\n      const word = words[i]\n      const m = filter(pattern, pattern.toLowerCase(), 0, word, word.toLowerCase(), 0)\n      if (m) {\n        const [score] = m\n        if (score > topScore) {\n          topScore = score\n          topIdx = i\n        }\n      }\n    }\n    assert.strictEqual(topIdx, expected, `${pattern} -> actual=${words[topIdx]} <> expected=${words[expected]}`)\n  }\n\n  test('isWhitespaceAtPos()', () => {\n    expect(isWhitespaceAtPos('abc', -1)).toBe(false)\n    expect(isWhitespaceAtPos('abc', 0)).toBe(false)\n    expect(isWhitespaceAtPos(' bc', 0)).toBe(true)\n  })\n\n  test('isSeparatorAtPos()', () => {\n    expect(isSeparatorAtPos('abc', -1)).toBe(false)\n    expect(isSeparatorAtPos('abc', 6)).toBe(false)\n    expect(isSeparatorAtPos('abc', 0)).toBe(false)\n    expect(isSeparatorAtPos(' abc', 0)).toBe(true)\n    expect(isSeparatorAtPos('😕abc', 0)).toBe(true)\n  })\n\n  test('isPatternInWord()', () => {\n    const check = (pattern: string, word: string, patternPos = 0, wordPos = 0, result: boolean) => {\n      let res = isPatternInWord(pattern.toLowerCase(), patternPos, pattern.length, word.toLowerCase(), wordPos, word.length)\n      expect(res).toBe(result)\n    }\n    check('abc', 'defabc', 0, 0, true)\n    check('abc', 'defabc', 0, 4, false)\n    check('abc', 'defab/c', 0, 0, true)\n  })\n\n  test('fuzzyScore, #23215', function() {\n    assertMatches('tit', 'win.tit', 'win.^t^i^t', fuzzyScore)\n    assertMatches('title', 'win.title', 'win.^t^i^t^l^e', fuzzyScore)\n    assertMatches('WordCla', 'WordCharacterClassifier', '^W^o^r^dCharacter^C^l^assifier', fuzzyScore)\n    assertMatches('WordCCla', 'WordCharacterClassifier', '^W^o^r^d^Character^C^l^assifier', fuzzyScore)\n  })\n\n  test('fuzzyScore, #23332', function() {\n    assertMatches('dete', '\"editor.quickSuggestionsDelay\"', undefined, fuzzyScore)\n  })\n\n  test('fuzzyScore, #23190', function() {\n    assertMatches('c:\\\\do', '& \\'C:\\\\Documents and Settings\\'', '& \\'^C^:^\\\\^D^ocuments and Settings\\'', fuzzyScore)\n    assertMatches('c:\\\\do', '& \\'c:\\\\Documents and Settings\\'', '& \\'^c^:^\\\\^D^ocuments and Settings\\'', fuzzyScore)\n  })\n\n  test('fuzzyScore, #23581', function() {\n    assertMatches('close', 'css.lint.importStatement', '^css.^lint.imp^ort^Stat^ement', fuzzyScore)\n    assertMatches('close', 'css.colorDecorators.enable', '^css.co^l^orDecorator^s.^enable', fuzzyScore)\n    assertMatches('close', 'workbench.quickOpen.closeOnFocusOut', 'workbench.quickOpen.^c^l^o^s^eOnFocusOut', fuzzyScore)\n    assertTopScore(fuzzyScore, 'close', 2, 'css.lint.importStatement', 'css.colorDecorators.enable', 'workbench.quickOpen.closeOnFocusOut')\n  })\n\n  test('fuzzyScore, #23458', function() {\n    assertMatches('highlight', 'editorHoverHighlight', 'editorHover^H^i^g^h^l^i^g^h^t', fuzzyScore)\n    assertMatches('hhighlight', 'editorHoverHighlight', 'editor^Hover^H^i^g^h^l^i^g^h^t', fuzzyScore)\n    assertMatches('dhhighlight', 'editorHoverHighlight', undefined, fuzzyScore)\n  })\n  test('fuzzyScore, #23746', function() {\n    assertMatches('-moz', '-moz-foo', '^-^m^o^z-foo', fuzzyScore)\n    assertMatches('moz', '-moz-foo', '-^m^o^z-foo', fuzzyScore)\n    assertMatches('moz', '-moz-animation', '-^m^o^z-animation', fuzzyScore)\n    assertMatches('moza', '-moz-animation', '-^m^o^z-^animation', fuzzyScore)\n  })\n\n  test('fuzzyScore', () => {\n    assertMatches('ab', 'abA', '^a^bA', fuzzyScore)\n    assertMatches('ccm', 'cacmelCase', '^ca^c^melCase', fuzzyScore)\n    assertMatches('bti', 'the_black_knight', undefined, fuzzyScore)\n    assertMatches('ccm', 'camelCase', undefined, fuzzyScore)\n    assertMatches('cmcm', 'camelCase', undefined, fuzzyScore)\n    assertMatches('BK', 'the_black_knight', 'the_^black_^knight', fuzzyScore)\n    assertMatches('KeyboardLayout=', 'KeyboardLayout', undefined, fuzzyScore)\n    assertMatches('LLL', 'SVisualLoggerLogsList', 'SVisual^Logger^Logs^List', fuzzyScore)\n    assertMatches('LLLL', 'SVilLoLosLi', undefined, fuzzyScore)\n    assertMatches('LLLL', 'SVisualLoggerLogsList', undefined, fuzzyScore)\n    assertMatches('TEdit', 'TextEdit', '^Text^E^d^i^t', fuzzyScore)\n    assertMatches('TEdit', 'TextEditor', '^Text^E^d^i^tor', fuzzyScore)\n    assertMatches('TEdit', 'Textedit', '^Text^e^d^i^t', fuzzyScore)\n    assertMatches('TEdit', 'text_edit', '^text_^e^d^i^t', fuzzyScore)\n    assertMatches('TEditDit', 'TextEditorDecorationType', '^Text^E^d^i^tor^Decorat^ion^Type', fuzzyScore)\n    assertMatches('TEdit', 'TextEditorDecorationType', '^Text^E^d^i^torDecorationType', fuzzyScore)\n    assertMatches('Tedit', 'TextEdit', '^Text^E^d^i^t', fuzzyScore)\n    assertMatches('ba', '?AB?', undefined, fuzzyScore)\n    assertMatches('bkn', 'the_black_knight', 'the_^black_^k^night', fuzzyScore)\n    assertMatches('bt', 'the_black_knight', 'the_^black_knigh^t', fuzzyScore)\n    assertMatches('ccm', 'camelCasecm', '^camel^Casec^m', fuzzyScore)\n    assertMatches('fdm', 'findModel', '^fin^d^Model', fuzzyScore)\n    assertMatches('fob', 'foobar', '^f^oo^bar', fuzzyScore)\n    assertMatches('fobz', 'foobar', undefined, fuzzyScore)\n    assertMatches('foobar', 'foobar', '^f^o^o^b^a^r', fuzzyScore)\n    assertMatches('form', 'editor.formatOnSave', 'editor.^f^o^r^matOnSave', fuzzyScore)\n    assertMatches('g p', 'Git: Pull', '^Git:^ ^Pull', fuzzyScore)\n    assertMatches('g p', 'Git: Pull', '^Git:^ ^Pull', fuzzyScore)\n    assertMatches('gip', 'Git: Pull', '^G^it: ^Pull', fuzzyScore)\n    assertMatches('gip', 'Git: Pull', '^G^it: ^Pull', fuzzyScore)\n    assertMatches('gp', 'Git: Pull', '^Git: ^Pull', fuzzyScore)\n    assertMatches('gp', 'Git_Git_Pull', '^Git_Git_^Pull', fuzzyScore)\n    assertMatches('is', 'ImportStatement', '^Import^Statement', fuzzyScore)\n    assertMatches('is', 'isValid', '^i^sValid', fuzzyScore)\n    assertMatches('lowrd', 'lowWord', '^l^o^wWo^r^d', fuzzyScore)\n    assertMatches('myvable', 'myvariable', '^m^y^v^aria^b^l^e', fuzzyScore)\n    assertMatches('no', '', undefined, fuzzyScore)\n    assertMatches('no', 'match', undefined, fuzzyScore)\n    assertMatches('ob', 'foobar', undefined, fuzzyScore)\n    assertMatches('sl', 'SVisualLoggerLogsList', '^SVisual^LoggerLogsList', fuzzyScore)\n    assertMatches('sllll', 'SVisualLoggerLogsList', '^SVisua^l^Logger^Logs^List', fuzzyScore)\n    assertMatches('Three', 'HTMLHRElement', undefined, fuzzyScore)\n    assertMatches('Three', 'Three', '^T^h^r^e^e', fuzzyScore)\n    assertMatches('fo', 'barfoo', undefined, fuzzyScore)\n    assertMatches('fo', 'bar_foo', 'bar_^f^oo', fuzzyScore)\n    assertMatches('fo', 'bar_Foo', 'bar_^F^oo', fuzzyScore)\n    assertMatches('fo', 'bar foo', 'bar ^f^oo', fuzzyScore)\n    assertMatches('fo', 'bar.foo', 'bar.^f^oo', fuzzyScore)\n    assertMatches('fo', 'bar/foo', 'bar/^f^oo', fuzzyScore)\n    assertMatches('fo', 'bar\\\\foo', 'bar\\\\^f^oo', fuzzyScore)\n  })\n\n  test('fuzzyScore (first match can be weak)', function() {\n\n    assertMatches('Three', 'HTMLHRElement', 'H^TML^H^R^El^ement', fuzzyScore, { firstMatchCanBeWeak: true })\n    assertMatches('tor', 'constructor', 'construc^t^o^r', fuzzyScore, { firstMatchCanBeWeak: true })\n    assertMatches('ur', 'constructor', 'constr^ucto^r', fuzzyScore, { firstMatchCanBeWeak: true })\n    assertTopScore(fuzzyScore, 'tor', 2, 'constructor', 'Thor', 'cTor')\n  })\n\n  test('fuzzyScore, many matches', function() {\n\n    assertMatches(\n      'aaaaaa',\n      'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',\n      '^a^a^a^a^a^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',\n      fuzzyScore\n    )\n    let res = fuzzyScore('a'.repeat(1024), 'a'.repeat(1024), 0, 'word', 'word', 0)\n    expect(res).toBeUndefined()\n  })\n\n  test('Freeze when fjfj -> jfjf, https://github.com/microsoft/vscode/issues/91807', function() {\n    assertMatches(\n      'jfjfj',\n      'fjfjfjfjfjfjfjfjfjfjfj',\n      undefined, fuzzyScore\n    )\n    assertMatches(\n      'jfjfjfjfjfjfjfjfjfj',\n      'fjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfj',\n      undefined, fuzzyScore\n    )\n    assertMatches(\n      'jfjfjfjfjfjfjfjfjfjjfjfjfjfjfjfjfjfjfjjfjfjfjfjfjfjfjfjfjjfjfjfjfjfjfjfjfjfjjfjfjfjfjfjfjfjfjfjjfjfjfjfjfjfjfjfjfj',\n      'fjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfj',\n      undefined, fuzzyScore\n    )\n    assertMatches(\n      'jfjfjfjfjfjfjfjfjfj',\n      'fJfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfj',\n      'f^J^f^j^f^j^f^j^f^j^f^j^f^j^f^j^f^j^f^jfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfj', // strong match\n      fuzzyScore\n    )\n    assertMatches(\n      'jfjfjfjfjfjfjfjfjfj',\n      'fjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfj',\n      'f^j^f^j^f^j^f^j^f^j^f^j^f^j^f^j^f^j^f^jfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfjfj', // any match\n      fuzzyScore, { firstMatchCanBeWeak: true }\n    )\n  })\n\n  test('fuzzyScore, issue #26423', function() {\n\n    assertMatches('baba', 'abababab', undefined, fuzzyScore)\n\n    assertMatches(\n      'fsfsfs',\n      'dsafdsafdsafdsafdsafdsafdsafasdfdsa',\n      undefined,\n      fuzzyScore\n    )\n    assertMatches(\n      'fsfsfsfsfsfsfsf',\n      'dsafdsafdsafdsafdsafdsafdsafasdfdsafdsafdsafdsafdsfdsafdsfdfdfasdnfdsajfndsjnafjndsajlknfdsa',\n      undefined,\n      fuzzyScore\n    )\n  })\n\n  test('Fuzzy IntelliSense matching vs Haxe metadata completion, #26995', function() {\n    assertMatches('f', ':Foo', ':^Foo', fuzzyScore)\n    assertMatches('f', ':foo', ':^foo', fuzzyScore)\n  })\n\n  test('Separator only match should not be weak #79558', function() {\n    assertMatches('.', 'foo.bar', 'foo^.bar', fuzzyScore)\n  })\n\n  test('Cannot set property \\'1\\' of undefined, #26511', function() {\n    const word = new Array<void>(123).join('a')\n    const pattern = new Array<void>(120).join('a')\n    fuzzyScore(pattern, pattern.toLowerCase(), 0, word, word.toLowerCase(), 0)\n    assert.ok(true) // must not explode\n  })\n\n  test('Vscode 1.12 no longer obeys \\'sortText\\' in completion items (from language server), #26096', function() {\n    assertMatches('  ', '  group', undefined, fuzzyScore, { patternPos: 2 })\n    assertMatches('  g', '  group', '  ^group', fuzzyScore, { patternPos: 2 })\n    assertMatches('g', '  group', '  ^group', fuzzyScore)\n    assertMatches('g g', '  groupGroup', undefined, fuzzyScore)\n    assertMatches('g g', '  group Group', '  ^group^ ^Group', fuzzyScore)\n    assertMatches(' g g', '  group Group', '  ^group^ ^Group', fuzzyScore, { patternPos: 1 })\n    assertMatches('zz', 'zzGroup', '^z^zGroup', fuzzyScore)\n    assertMatches('zzg', 'zzGroup', '^z^z^Group', fuzzyScore)\n    assertMatches('g', 'zzGroup', 'zz^Group', fuzzyScore)\n  })\n\n  test('patternPos isn\\'t working correctly #79815', function() {\n    assertMatches(':p'.substr(1), 'prop', '^prop', fuzzyScore, { patternPos: 0 })\n    assertMatches(':p', 'prop', '^prop', fuzzyScore, { patternPos: 1 })\n    assertMatches(':p', 'prop', undefined, fuzzyScore, { patternPos: 2 })\n    assertMatches(':p', 'proP', 'pro^P', fuzzyScore, { patternPos: 1, wordPos: 1 })\n    assertMatches(':p', 'aprop', 'a^prop', fuzzyScore, { patternPos: 1, firstMatchCanBeWeak: true })\n    assertMatches(':p', 'aprop', undefined, fuzzyScore, { patternPos: 1, firstMatchCanBeWeak: false })\n  })\n\n  test('topScore - fuzzyScore', function() {\n\n    assertTopScore(fuzzyScore, 'cons', 2, 'ArrayBufferConstructor', 'Console', 'console')\n    assertTopScore(fuzzyScore, 'Foo', 1, 'foo', 'Foo', 'foo')\n\n    // #24904\n    assertTopScore(fuzzyScore, 'onMess', 1, 'onmessage', 'onMessage', 'onThisMegaEscape')\n\n    assertTopScore(fuzzyScore, 'CC', 1, 'camelCase', 'CamelCase')\n    assertTopScore(fuzzyScore, 'cC', 0, 'camelCase', 'CamelCase')\n    // assertTopScore(fuzzyScore, 'cC', 1, 'ccfoo', 'camelCase');\n    // assertTopScore(fuzzyScore, 'cC', 1, 'ccfoo', 'camelCase', 'foo-cC-bar');\n\n    // issue #17836\n    // assertTopScore(fuzzyScore, 'TEdit', 1, 'TextEditorDecorationType', 'TextEdit', 'TextEditor');\n    assertTopScore(fuzzyScore, 'p', 4, 'parse', 'posix', 'pafdsa', 'path', 'p')\n    assertTopScore(fuzzyScore, 'pa', 0, 'parse', 'pafdsa', 'path')\n\n    // issue #14583\n    assertTopScore(fuzzyScore, 'log', 3, 'HTMLOptGroupElement', 'ScrollLogicalPosition', 'SVGFEMorphologyElement', 'log', 'logger')\n    assertTopScore(fuzzyScore, 'e', 2, 'AbstractWorker', 'ActiveXObject', 'else')\n\n    // issue #14446\n    assertTopScore(fuzzyScore, 'workbench.sideb', 1, 'workbench.editor.defaultSideBySideLayout', 'workbench.sideBar.location')\n\n    // issue #11423\n    assertTopScore(fuzzyScore, 'editor.r', 2, 'diffEditor.renderSideBySide', 'editor.overviewRulerlanes', 'editor.renderControlCharacter', 'editor.renderWhitespace')\n    // assertTopScore(fuzzyScore, 'editor.R', 1, 'diffEditor.renderSideBySide', 'editor.overviewRulerlanes', 'editor.renderControlCharacter', 'editor.renderWhitespace');\n    // assertTopScore(fuzzyScore, 'Editor.r', 0, 'diffEditor.renderSideBySide', 'editor.overviewRulerlanes', 'editor.renderControlCharacter', 'editor.renderWhitespace');\n\n    assertTopScore(fuzzyScore, '-mo', 1, '-ms-ime-mode', '-moz-columns')\n    // dupe, issue #14861\n    assertTopScore(fuzzyScore, 'convertModelPosition', 0, 'convertModelPositionToViewPosition', 'convertViewToModelPosition')\n    // dupe, issue #14942\n    assertTopScore(fuzzyScore, 'is', 0, 'isValidViewletId', 'import statement')\n\n    assertTopScore(fuzzyScore, 'title', 1, 'files.trimTrailingWhitespace', 'window.title')\n\n    assertTopScore(fuzzyScore, 'const', 1, 'constructor', 'const', 'cuOnstrul')\n  })\n\n  test('Unexpected suggestion scoring, #28791', function() {\n    assertTopScore(fuzzyScore, '_lines', 1, '_lineStarts', '_lines')\n    assertTopScore(fuzzyScore, '_lines', 1, '_lineS', '_lines')\n    assertTopScore(fuzzyScore, '_lineS', 0, '_lineS', '_lines')\n  })\n\n  test('HTML closing tag proposal filtered out #38880', function() {\n    assertMatches('\\t\\t<', '\\t\\t</body>', '^\\t^\\t^</body>', fuzzyScore, { patternPos: 0 })\n    assertMatches('\\t\\t<', '\\t\\t</body>', '\\t\\t^</body>', fuzzyScore, { patternPos: 2 })\n    assertMatches('\\t<', '\\t</body>', '\\t^</body>', fuzzyScore, { patternPos: 1 })\n  })\n\n  test('fuzzyScoreGraceful', () => {\n\n    assertMatches('rlut', 'result', undefined, fuzzyScore)\n    assertMatches('rlut', 'result', '^res^u^l^t', fuzzyScoreGraceful)\n\n    assertMatches('cno', 'console', '^co^ns^ole', fuzzyScore)\n    assertMatches('cno', 'console', '^co^ns^ole', fuzzyScoreGraceful)\n    assertMatches('cno', 'console', '^c^o^nsole', fuzzyScoreGracefulAggressive)\n    assertMatches('cno', 'co_new', '^c^o_^new', fuzzyScoreGraceful)\n    assertMatches('cno', 'co_new', '^c^o_^new', fuzzyScoreGracefulAggressive)\n  })\n\n  test('List highlight filter: Not all characters from match are highlighted #66923', () => {\n    assertMatches('foo', 'barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar_foo', 'barbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar_^f^o^o', fuzzyScore)\n  })\n\n  test('Autocompletion is matched against truncated filterText to 54 characters #74133', () => {\n    assertMatches(\n      'foo',\n      'ffffffffffffffffffffffffffffbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar_foo',\n      'ffffffffffffffffffffffffffffbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar_^f^o^o',\n      fuzzyScore\n    )\n    assertMatches(\n      'Aoo',\n      'Affffffffffffffffffffffffffffbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar_foo',\n      '^Affffffffffffffffffffffffffffbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar_f^o^o',\n      fuzzyScore\n    )\n    assertMatches(\n      'foo',\n      'Gffffffffffffffffffffffffffffbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbarbar_foo',\n      undefined,\n      fuzzyScore\n    )\n  })\n\n  test('\"Go to Symbol\" with the exact method name doesn\\'t work as expected #84787', function() {\n    const match = fuzzyScore(':get', ':get', 1, 'get', 'get', 0, { firstMatchCanBeWeak: true, boostFullMatch: true })\n    assert.ok(Boolean(match))\n  })\n\n  test('Wrong highlight after emoji #113404', function() {\n    assertMatches('di', '✨div classname=\"\"></div>', '✨^d^iv classname=\"\"></div>', fuzzyScore)\n    assertMatches('di', 'adiv classname=\"\"></div>', 'adiv classname=\"\"></^d^iv>', fuzzyScore)\n  })\n\n  test('Suggestion is not highlighted #85826', function() {\n    assertMatches('SemanticTokens', 'SemanticTokensEdits', '^S^e^m^a^n^t^i^c^T^o^k^e^n^sEdits', fuzzyScore)\n    assertMatches('SemanticTokens', 'SemanticTokensEdits', '^S^e^m^a^n^t^i^c^T^o^k^e^n^sEdits', fuzzyScoreGracefulAggressive)\n  })\n\n  test('IntelliSense completion not correctly highlighting text in front of cursor #115250', function() {\n    assertMatches('lo', 'log', '^l^og', fuzzyScore)\n    assertMatches('.lo', 'log', '^l^og', anyScore)\n    assertMatches('.', 'log', 'log', anyScore)\n  })\n\n  test('configurable full match boost', function() {\n    const prefix = 'create'\n    const a = 'createModelServices'\n    const b = 'create'\n\n    const aBoost = fuzzyScore(prefix, prefix, 0, a, a.toLowerCase(), 0, { boostFullMatch: true, firstMatchCanBeWeak: true })\n    const bBoost = fuzzyScore(prefix, prefix, 0, b, b.toLowerCase(), 0, { boostFullMatch: true, firstMatchCanBeWeak: true })\n    assert.ok(aBoost)\n    assert.ok(bBoost)\n    assert.ok(aBoost[0] < bBoost[0])\n\n    const aScore = fuzzyScore(prefix, prefix, 0, a, a.toLowerCase(), 0, { boostFullMatch: false, firstMatchCanBeWeak: true })\n    const bScore = fuzzyScore(prefix, prefix, 0, b, b.toLowerCase(), 0, { boostFullMatch: false, firstMatchCanBeWeak: true })\n    assert.ok(aScore)\n    assert.ok(bScore)\n    assert.ok(aScore[0] === bScore[0])\n  })\n\n  test('Unexpected suggest highlighting ignores whole word match in favor of matching first letter#147423', function() {\n\n    assertMatches('i', 'machine/{id}', 'machine/{^id}', fuzzyScore)\n    assertMatches('ok', 'obobobf{ok}/user', '^obobobf{o^k}/user', fuzzyScore)\n  })\n\n  test('nextTypoPermutation', () => {\n    expect(nextTypoPermutation('abc', 2)).toBeUndefined()\n  })\n\n  test('createMatches()', () => {\n    expect(createMatches(undefined)).toEqual([])\n  })\n})\n\n"
  },
  {
    "path": "src/__tests__/modules/floatFactory.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport events from '../../events'\nimport FloatFactoryImpl from '../../model/floatFactory'\nimport snippetManager from '../../snippets/manager'\nimport { Documentation } from '../../types'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet floatFactory: FloatFactoryImpl\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  floatFactory = new FloatFactoryImpl(nvim)\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n  floatFactory.dispose()\n})\n\nafterEach(async () => {\n  floatFactory.close()\n  await helper.reset()\n})\n\ndescribe('FloatFactory', () => {\n  describe('show()', () => {\n    it('should close after create window', async () => {\n      let docs: Documentation[] = [{\n        filetype: 'markdown',\n        content: 'f'\n      }]\n      let p = floatFactory.show(docs, { shadow: true, focusable: true, rounded: true, border: [1, 1, 1, 1] })\n      floatFactory.close()\n      await helper.wait(10)\n      let win = floatFactory.window\n      expect(win).toBeNull()\n    })\n\n    it('should show window', async () => {\n      expect(floatFactory.window).toBe(null)\n      expect(floatFactory.buffer).toBe(null)\n      expect(floatFactory.bufnr).toBe(0)\n      let docs: Documentation[] = [{\n        filetype: 'markdown',\n        content: 'f'.repeat(81)\n      }]\n      await floatFactory.show(docs, { rounded: true })\n      expect(floatFactory.window).toBeDefined()\n      expect(floatFactory.buffer).toBeDefined()\n      let hasFloat = await nvim.call('coc#float#has_float')\n      expect(hasFloat).toBe(1)\n      await floatFactory.show([{ filetype: 'txt', content: '' }])\n      expect(floatFactory.window).toBe(null)\n    })\n\n    it('should close when MenuPopupChanged', async () => {\n      let docs: Documentation[] = [{\n        filetype: 'markdown',\n        content: 'f'.repeat(81)\n      }]\n      await floatFactory.show(docs, { focusable: true })\n      await events.fire('BufEnter', [floatFactory.bufnr])\n      let ev = {\n        row: 21,\n        startcol: 0,\n        index: 0,\n        word: '',\n        height: 1,\n        width: 1,\n        col: 10,\n        size: 1,\n        scrollbar: true,\n        inserted: true,\n        move: false,\n      }\n      await events.fire('MenuPopupChanged', [ev, 22])\n      await events.fire('MenuPopupChanged', [ev, 20])\n      expect(floatFactory.window).toBeNull()\n      floatFactory.close()\n    })\n\n    it('should create fixed float window', async () => {\n      let docs: Documentation[] = [{\n        filetype: 'markdown',\n        content: 'foo'\n      }]\n      await floatFactory.show(docs, { position: 'fixed', focusable: true, bottom: 1, right: 1 })\n      let res = await nvim.call('screenpos', [floatFactory.window.id, 1, 1]) as any\n      expect(res).toBeDefined()\n      expect(res.col > 150).toBe(true)\n      expect(res.row > 70).toBe(true)\n      floatFactory.close()\n    })\n\n    it('should create window', async () => {\n      let docs: Documentation[] = [{\n        filetype: 'markdown',\n        content: 'f'.repeat(81)\n      }]\n      await floatFactory.create(docs)\n      expect(floatFactory.window).toBeDefined()\n    })\n\n    it('should catch error on create', async () => {\n      let fn = floatFactory.unbind\n      floatFactory.unbind = () => {\n        throw new Error('bad')\n      }\n      let docs: Documentation[] = [{\n        filetype: 'markdown',\n        content: 'f'.repeat(81)\n      }]\n      await floatFactory.show(docs)\n      floatFactory.unbind = fn\n      let msg = await helper.getCmdline()\n      expect(msg).toMatch('bad')\n    })\n\n    it('should show only one window', async () => {\n      await helper.edit()\n      await nvim.setLine('foo')\n      let docs: Documentation[] = [{\n        filetype: 'markdown',\n        content: 'foo'\n      }]\n      await Promise.all([\n        floatFactory.show(docs),\n        floatFactory.show(docs)\n      ])\n      let count = 0\n      let wins = await nvim.windows\n      for (let win of wins) {\n        let isFloat = await win.getVar('float')\n        if (isFloat) count++\n      }\n      expect(count).toBe(1)\n    })\n\n    it('should close window when close called after create', async () => {\n      let docs: Documentation[] = [{\n        filetype: 'markdown',\n        content: 'f'\n      }]\n      let p = floatFactory.show(docs)\n      await helper.wait(10)\n      floatFactory.close()\n      await p\n      let activated = await floatFactory.activated()\n      expect(activated).toBe(false)\n    })\n\n    it('should not create on visual mode', async () => {\n      await helper.createDocument()\n      await nvim.call('cursor', [1, 1])\n      await nvim.setLine('foo')\n      await nvim.command('normal! v$')\n      let docs: Documentation[] = [{\n        filetype: 'markdown',\n        content: 'f'\n      }]\n      await floatFactory.show(docs)\n      expect(floatFactory.window).toBe(null)\n    })\n\n    it('should allow select mode', async () => {\n      await helper.createDocument()\n      await snippetManager.insertSnippet('${1:foo}')\n      let docs: Documentation[] = [{\n        filetype: 'markdown',\n        content: 'foo'\n      }]\n      await floatFactory.show(docs)\n      let { mode } = await nvim.mode\n      expect(mode).toBe('s')\n      await nvim.input('<esc>')\n    })\n  })\n\n  describe('checkRetrigger', () => {\n    it('should check retrigger', async () => {\n      expect(floatFactory.checkRetrigger(99)).toBe(false)\n      let bufnr = await nvim.call('bufnr', ['%']) as number\n      let docs: Documentation[] = [{\n        filetype: 'markdown',\n        content: 'f'\n      }]\n      await floatFactory.show(docs)\n      expect(floatFactory.checkRetrigger(99)).toBe(false)\n      expect(floatFactory.checkRetrigger(bufnr)).toBe(true)\n    })\n  })\n\n  describe('options', () => {\n    it('should config maxHeight and maxWidth', async () => {\n      let docs: Documentation[] = [{\n        filetype: 'markdown',\n        content: 'f'.repeat(80) + '\\nbar',\n      }]\n      await floatFactory.show(docs, {\n        maxWidth: 20,\n        maxHeight: 1\n      })\n      let win = floatFactory.window\n      expect(win).toBeDefined()\n      let width = await win.width\n      let height = await win.height\n      expect(width).toBe(19)\n      expect(height).toBe(1)\n    })\n\n    it('should set border, title, highlight, borderhighlight, cursorline', async () => {\n      let docs: Documentation[] = [{\n        filetype: 'markdown',\n        content: 'foo\\nbar'\n      }]\n      await floatFactory.show(docs, {\n        border: [1, 1, 1, 1],\n        title: 'title',\n        highlight: 'Pmenu',\n        borderhighlight: 'MoreMsg',\n        cursorline: true\n      })\n      let activated = await floatFactory.activated()\n      expect(activated).toBe(true)\n    })\n\n    it('should respect prefer top', async () => {\n      let docs: Documentation[] = [{\n        filetype: 'markdown',\n        content: 'foo\\nbar'\n      }]\n      await nvim.call('append', [1, ['', '', '']])\n      await nvim.command('exe 4')\n      await floatFactory.show(docs, { preferTop: true })\n      let win = await helper.getFloat()\n      expect(win).toBeDefined()\n      let pos = await nvim.call('nvim_win_get_position', [win.id])\n      expect(pos).toEqual([1, 0])\n    })\n  })\n\n  describe('events', () => {\n    it('should hide on BufEnter', async () => {\n      await helper.edit()\n      let docs: Documentation[] = [{\n        filetype: 'markdown',\n        content: 'foo'\n      }]\n      await floatFactory.show(docs)\n      await nvim.command(`edit foo`)\n      await helper.waitFor('coc#float#has_float', [], 0)\n    })\n\n    it('should not hide when not moved', async () => {\n      let bufnr = await nvim.call('bufnr', ['%']) as number\n      let docs: Documentation[] = [{\n        filetype: 'markdown',\n        content: 'foo'\n      }]\n      await floatFactory.show(docs, { focusable: false })\n      floatFactory._onCursorMoved(false, bufnr, [1, 1])\n    })\n\n    it('should hide on CursorMoved', async () => {\n      let doc = await helper.createDocument()\n      await nvim.input('i')\n      await nvim.setLine('foo')\n      let docs: Documentation[] = [{\n        filetype: 'markdown',\n        content: 'foo'\n      }]\n      await floatFactory.show(docs)\n      await helper.waitFloat()\n      floatFactory._onCursorMoved(true, doc.bufnr, [3, 3])\n      await helper.waitFor('coc#float#has_float', [], 0)\n    })\n\n    it('should not hide when cursor position not changed', async () => {\n      await helper.edit()\n      await nvim.setLine('foo')\n      let cursor = await nvim.eval(\"[line('.'), col('.')]\")\n      let docs: Documentation[] = [{\n        filetype: 'markdown',\n        content: 'foo'\n      }]\n      await floatFactory.show(docs)\n      floatFactory._onCursorMoved(false, floatFactory.bufnr, [1, 1])\n      await nvim.call('cursor', cursor)\n      await helper.wait(10)\n      await nvim.call('cursor', cursor)\n      await helper.wait(10)\n      await helper.waitFor('coc#float#has_float', [], 1)\n    })\n\n    it('should preserve float when autohide disable and not overlap with pum', async () => {\n      let doc = await helper.createDocument()\n      await doc.buffer.setLines(['foo', '', '', '', 'f'], { start: 0, end: -1, strictIndexing: false })\n      await doc.synchronize()\n      await nvim.call('cursor', [5, 1])\n      await nvim.input('A')\n      await helper.wait(50)\n      nvim.call('coc#start', [], true)\n      await helper.waitPopup()\n      let docs: Documentation[] = [{\n        filetype: 'markdown',\n        content: 'foo'\n      }]\n      await floatFactory.show(docs, {\n        preferTop: true,\n        autoHide: false\n      })\n      let activated = await floatFactory.activated()\n      expect(activated).toBe(true)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/fs.test.ts",
    "content": "import { findUp, isDirectory, findMatch, watchFile, writeJson, loadJson, normalizeFilePath, checkFolder, getFileType, isGitIgnored, readFileLine, readFileLines, fileStartsWith, writeFile, remove, renameAsync, isParentFolder, parentDirs, inDirectory, getFileLineCount, sameFile, lineToLocation, resolveRoot, statAsync, FileType } from '../../util/fs'\nimport { v4 as uuid } from 'uuid'\nimport path from 'path'\nimport fs from 'fs'\nimport os from 'os'\nimport { CancellationToken, CancellationTokenSource, Range } from 'vscode-languageserver-protocol'\n\nexport function wait(ms: number): Promise<void> {\n  return new Promise(resolve => {\n    setTimeout(() => {\n      resolve(undefined)\n    }, ms)\n  })\n}\n\ndescribe('fs', () => {\n  describe('normalizeFilePath()', () => {\n    it('should fs normalizeFilePath', () => {\n      let res = normalizeFilePath('//')\n      expect(res).toBe('/')\n      res = normalizeFilePath('/a/b/')\n      expect(res).toBe('/a/b')\n    })\n  })\n\n  it('should check directory', () => {\n    expect(isDirectory(null)).toBe(false)\n    expect(isDirectory('')).toBe(false)\n    expect(isDirectory(__filename)).toBe(false)\n    expect(isDirectory(process.cwd())).toBe(true)\n  })\n\n  it('should watch file', async () => {\n    let filepath = path.join(os.tmpdir(), uuid())\n    fs.writeFileSync(filepath, 'file', 'utf8')\n    let called = false\n    let disposable = watchFile(filepath, () => {\n      called = true\n    }, true)\n    fs.writeFileSync(filepath, 'new file', 'utf8')\n    await wait(2)\n    disposable.dispose()\n    disposable = watchFile('file_not_exists', () => {}, true)\n    disposable.dispose()\n  })\n\n  describe('stat()', () => {\n    it('fs statAsync', async () => {\n      let res = await statAsync(__filename)\n      expect(res).toBeDefined()\n      expect(res.isFile()).toBe(true)\n    })\n\n    it('fs statAsync #1', async () => {\n      let res = await statAsync(path.join(__dirname, 'file_not_exist'))\n      expect(res).toBeNull()\n    })\n  })\n\n  describe('loadJson()', () => {\n    it('should loadJson()', () => {\n      let file = path.join(__dirname, 'not_exists.json')\n      expect(loadJson(file)).toEqual({})\n    })\n\n    it('should loadJson with bad format', async () => {\n      let file = path.join(os.tmpdir(), uuid())\n      fs.writeFileSync(file, 'foo', 'utf8')\n      expect(loadJson(file)).toEqual({})\n    })\n  })\n\n  describe('writeJson()', () => {\n    it('should writeJson file', async () => {\n      let file = path.join(os.tmpdir(), uuid())\n      writeJson(file, { x: 1 })\n      expect(loadJson(file)).toEqual({ x: 1 })\n    })\n\n    it('should create file with folder', async () => {\n      let file = path.join(os.tmpdir(), uuid(), 'foo', 'bar')\n      writeJson(file, { foo: '1' })\n      expect(loadJson(file)).toEqual({ foo: '1' })\n    })\n  })\n\n  describe('lineToLocation', () => {\n    it('should not throw when file not exists', async () => {\n      let res = await lineToLocation(path.join(os.tmpdir(), 'not_exists'), 'ab')\n      expect(res).toBeDefined()\n    })\n\n    it('should use empty range when not found', async () => {\n      let res = await lineToLocation(__filename, 'a'.repeat(100))\n      expect(res).toBeDefined()\n      expect(res.range).toEqual(Range.create(0, 0, 0, 0))\n    })\n\n    it('should get location', async () => {\n      let file = path.join(os.tmpdir(), uuid())\n      fs.writeFileSync(file, '\\nfoo\\n', 'utf8')\n      let res = await lineToLocation(file, 'foo', 'foo')\n      expect(res.range).toEqual(Range.create(1, 0, 1, 3))\n    })\n  })\n\n  describe('remove()', () => {\n    it('should remove files', async () => {\n      await remove(path.join(os.tmpdir(), uuid()))\n      let p = path.join(os.tmpdir(), uuid())\n      fs.writeFileSync(p, 'data', 'utf8')\n      await remove(p)\n      let exists = fs.existsSync(p)\n      expect(exists).toBe(false)\n      await remove(undefined)\n    })\n\n    it('should not throw error', async () => {\n      let spy = jest.spyOn(fs, 'rm').mockImplementation(() => {\n        throw new Error('my error')\n      })\n      let p = path.join(os.tmpdir(), uuid())\n      await remove(p)\n      spy.mockRestore()\n    })\n\n    it('should remove folder', async () => {\n      let f = path.join(os.tmpdir(), uuid())\n      let p = path.join(f, 'a/b/c')\n      fs.mkdirSync(p, { recursive: true })\n      await remove(f)\n      let exists = fs.existsSync(f)\n      expect(exists).toBe(false)\n    })\n  })\n\n  describe('getFileType()', () => {\n    it('should get filetype', async () => {\n      let res = await getFileType(__dirname)\n      expect(res).toBe(FileType.Directory)\n      res = await getFileType(__filename)\n      expect(res).toBe(FileType.File)\n      let newPath = path.join(os.tmpdir(), uuid())\n      fs.symlinkSync(__filename, newPath)\n      res = await getFileType(newPath)\n      expect(res).toBe(FileType.SymbolicLink)\n      fs.unlinkSync(newPath)\n      let spy = jest.spyOn(fs, 'lstat').mockImplementation((...args) => {\n        let cb = args[args.length - 1] as Function\n        return cb(undefined, {\n          isFile: () => { return false },\n          isDirectory: () => { return false },\n          isSymbolicLink: () => { return false }\n        })\n      })\n      res = await getFileType('__file')\n      expect(res).toBe(FileType.Unknown)\n      spy.mockRestore()\n    })\n  })\n\n  describe('checkFolder()', () => {\n    it('should check file in folder', async () => {\n      let cwd = process.cwd()\n      let res = await checkFolder(cwd, ['package.json'])\n      expect(res).toBe(true)\n      res = await checkFolder(cwd, ['**/schema.json', 'package.json'])\n      expect(res).toBe(true)\n      res = await checkFolder(cwd, [])\n      expect(res).toBe(false)\n      res = await checkFolder(cwd, ['not_exists_fs'], CancellationToken.None)\n      expect(res).toBe(false)\n      res = await checkFolder(os.homedir(), ['not_exists_fs'])\n      expect(res).toBe(false)\n      res = await checkFolder('/a/b/c', ['not_exists_fs'])\n      expect(res).toBe(false)\n      let tokenSource = new CancellationTokenSource()\n      let p = checkFolder(cwd, ['**/a.java'], tokenSource.token)\n      let fn = async () => {\n        tokenSource.cancel()\n        res = await p\n      }\n      await expect(fn()).rejects.toThrow(Error)\n      expect(res).toBe(false)\n    })\n  })\n\n  describe('renameAsync()', () => {\n    it('should rename file', async () => {\n      let id = uuid()\n      let filepath = path.join(os.tmpdir(), id)\n      await writeFile(filepath, id)\n      let dest = path.join(os.tmpdir(), 'bar')\n      await renameAsync(filepath, dest)\n      let exists = fs.existsSync(dest)\n      expect(exists).toBe(true)\n      fs.unlinkSync(dest)\n    })\n\n    it('should throw when file does not exist', async () => {\n      let err\n      try {\n        await renameAsync('/foo/bar', '/a')\n      } catch (e) {\n        err = e\n      }\n      expect(err).toBeDefined()\n    })\n  })\n\n  describe('getFileLineCount', () => {\n    it('should throw when file does not exist', async () => {\n      let err\n      try {\n        await getFileLineCount('/foo/bar')\n      } catch (e) {\n        err = e\n      }\n      expect(err).toBeDefined()\n    })\n  })\n\n  describe('sameFile', () => {\n    it('should be casesensitive', () => {\n      expect(sameFile('/a', '/A', false)).toBe(false)\n      expect(sameFile('/a', '/A', true)).toBe(true)\n    })\n  })\n\n  describe('readFileLine', () => {\n    it('should read line', async () => {\n      let res = await readFileLine(__filename, 1)\n      expect(res).toBeDefined()\n      res = await readFileLine(__filename, 9999)\n      expect(res).toBeDefined()\n      expect(res).toBe('')\n    })\n\n    it('should throw when file does not exist', async () => {\n      const fn = async () => {\n        await readFileLine(__filename + 'fooobar', 1)\n      }\n      await expect(fn()).rejects.toThrow(Error)\n    })\n  })\n\n  describe('readFileLines', () => {\n    it('should throw when file does not exist', async () => {\n      const fn = async () => {\n        await readFileLines(__filename + 'fooobar', 0, 3)\n      }\n      await expect(fn()).rejects.toThrow(Error)\n    })\n\n    it('should read lines', async () => {\n      let res = await readFileLines(__filename, 0, 1)\n      expect(res.length).toBe(2)\n    })\n  })\n\n  describe('fileStartsWith()', () => {\n    it('should check casesensitive case', () => {\n      expect(fileStartsWith('/a/b', '/A', false)).toBe(false)\n      expect(fileStartsWith('/a/b', '/A', true)).toBe(true)\n    })\n  })\n\n  describe('isGitIgnored()', () => {\n    it('should be not ignored', async () => {\n      let res = await isGitIgnored(__filename)\n      expect(res).toBeFalsy()\n      let filepath = path.join(process.cwd(), 'build/index.js')\n      res = await isGitIgnored(filepath)\n      expect(res).toBe(true)\n    })\n\n    it('should be ignored', async () => {\n      let res = await isGitIgnored('')\n      let uid = uuid()\n      expect(res).toBe(false)\n      res = await isGitIgnored(path.join(os.tmpdir(), uid))\n      expect(res).toBe(false)\n      res = await isGitIgnored(path.resolve(__dirname, '../lib/index.js.map'))\n      expect(res).toBe(false)\n      res = await isGitIgnored(__filename)\n      expect(res).toBe(false)\n      let filepath = path.join(os.tmpdir(), uid)\n      fs.writeFileSync(filepath, '', { encoding: 'utf8' })\n      res = await isGitIgnored(filepath)\n      expect(res).toBe(false)\n      if (fs.existsSync(filepath)) fs.unlinkSync(filepath)\n    })\n  })\n\n  describe('inDirectory', () => {\n    it('should support wildcard', async () => {\n      let res = inDirectory(__dirname, ['**/file_not_exist.json'])\n      expect(res).toBe(false)\n    })\n  })\n\n  describe('parentDirs', () => {\n    it('get parentDirs', () => {\n      let dirs = parentDirs('/a/b/c')\n      expect(dirs).toEqual(['/', '/a', '/a/b'])\n      expect(parentDirs('/')).toEqual(['/'])\n    })\n  })\n\n  describe('isParentFolder', () => {\n    it('check parent folder', () => {\n      expect(isParentFolder('/a/b', '/a/b/')).toBe(false)\n      expect(isParentFolder('/a', '/a/b')).toBe(true)\n      expect(isParentFolder('/a/b', '/a/b')).toBe(false)\n      expect(isParentFolder('/a/b', '/a/b', true)).toBe(true)\n      expect(isParentFolder('//', '/', true)).toBe(true)\n      expect(isParentFolder('/a/b/', '/a/b/c', true)).toBe(true)\n    })\n  })\n\n  describe('resolveRoot', () => {\n    it('resolve root consider root path', () => {\n      let res = resolveRoot(__dirname, ['.git'])\n      expect(res).toMatch('coc.nvim')\n    })\n\n    it('should ignore glob pattern', () => {\n      let res = resolveRoot(__dirname, [path.basename(__filename)], undefined, false, false, [\"**/__tests__/**\"])\n      expect(res).toBeFalsy()\n    })\n\n    it('should ignore glob pattern bottom up', () => {\n      let res = resolveRoot(__dirname, [path.basename(__filename)], undefined, true, false, [\"**/__tests__/**\"])\n      expect(res).toBeFalsy()\n    })\n\n    it('should resolve from parent folders', () => {\n      let root = path.resolve(__dirname, '../extensions/snippet-sample')\n      let res = resolveRoot(root, ['package.json'])\n      expect(res.endsWith('coc.nvim')).toBe(true)\n    })\n\n    it('should resolve from parent folders with bottom-up method', () => {\n      let dir = path.join(os.tmpdir(), 'extensions/snippet-sample')\n      fs.mkdirSync(dir, { recursive: true })\n      fs.writeFileSync(path.resolve(dir, '../package.json'), '{}')\n      let res = resolveRoot(dir, ['package.json'], null, true)\n      expect(res.endsWith('extensions')).toBe(true)\n      fs.rmSync(path.dirname(dir), { recursive: true, force: true })\n    })\n\n    it('should resolve to cwd', () => {\n      let root = path.resolve(__dirname, '../../..')\n      let res = resolveRoot(root, ['package.json'], root, false, true)\n      expect(res).toBe(root)\n    })\n\n    it('should resolve to root', () => {\n      let root = path.resolve(__dirname, '../extensions/test/')\n      let res = resolveRoot(root, ['package.json'], root, false, false)\n      expect(res).toBe(path.resolve(__dirname, '../../../'))\n    })\n\n    it('should not resolve to home', () => {\n      let res = resolveRoot(__dirname, ['.config'], undefined, false, false, [os.homedir()])\n      expect(res != os.homedir()).toBeTruthy()\n    })\n  })\n\n  describe('findUp', () => {\n    it('should findMatch by pattern', async () => {\n      let res = findMatch(process.cwd(), ['*.json'])\n      expect(res).toMatch('.json')\n      res = findMatch(process.cwd(), ['*.json_not_exists'])\n      expect(res).toBeUndefined()\n    })\n\n    it('findUp by filename', () => {\n      let filepath = findUp('package.json', __dirname)\n      expect(filepath).toMatch('coc.nvim')\n      filepath = findUp('not_exists', __dirname)\n      expect(filepath).toBeNull()\n    })\n\n    it('findUp by filenames', async () => {\n      let filepath = findUp(['src'], __dirname)\n      expect(filepath).toMatch('coc.nvim')\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/fuzzyMatch.test.ts",
    "content": "import { matchScoreWithPositions } from '../../completion/match'\nimport { FuzzyMatch, matchSpansReverse, FuzzyWasi, initFuzzyWasm } from '../../model/fuzzyMatch'\nimport { getCharCodes } from '../../util/fuzzy'\n\ndescribe('FuzzyMatch', () => {\n  let api: FuzzyWasi\n  beforeAll(async () => {\n    api = await initFuzzyWasm()\n  })\n\n  it('should match spans', () => {\n    let f = new FuzzyMatch(api)\n    const verify = (input: string, positions: number[], results: [number, number][], max?: number) => {\n      let arr = f.matchSpans(input, positions, max)\n      let res: [number, number][] = []\n      for (let item of arr) {\n        res.push(item)\n      }\n      expect(res).toEqual(results)\n    }\n    verify('foobar', [0, 1, 3], [[0, 2], [3, 4]])\n    verify('foobar', [0], [[0, 1]])\n    verify('你', [0], [[0, 3]])\n    verify(' 你', [1], [[1, 4]])\n    verify('foobar', [0, 2, 3, 4, 1], [[0, 1], [2, 5]])\n    verify('foobar', [10], [])\n    verify('foobar', [0, 2, 4], [[0, 1], [2, 3], [4, 5]])\n    verify('foobar', [1, 4], [[1, 2]], 3)\n    verify('foobar', [5], [], 3)\n  })\n\n  it('should should matchSpansReverse', () => {\n    const verify = (input: string, positions: number[], results: [number, number][], endIndex?: number, max?: number) => {\n      let arr = matchSpansReverse(input, positions, endIndex, max)\n      let res: [number, number][] = []\n      for (let item of arr) {\n        res.push(item)\n      }\n      expect(res).toEqual(results)\n    }\n    verify('foobar', [3, 1, 0], [[0, 2], [3, 4]])\n    verify('foobar', [-1, 2, 3, 1, 0], [[0, 2], [3, 4]], 2)\n    verify('foobar', [0], [[0, 1]])\n    verify('你', [0], [[0, 3]])\n    verify(' 你', [1], [[1, 4]])\n    verify('foobar', [5, 4, 3, 2, 1], [[1, 6]])\n    verify('foobar', [5], [], 0, 2)\n    verify('foobar', [5, 1], [[1, 2]], 0, 2)\n    verify('f', [0, 1], [], 3)\n    verify('foo', [0, 1, 0, 0, 0], [[0, 1]])\n  })\n\n  it('should createScoreFunction', async () => {\n    let f = new FuzzyMatch(api)\n    let fn = f.createScoreFunction('a', 0)\n    expect(fn).toBeDefined()\n    fn = f.createScoreFunction('a', 0, undefined, 'normal')\n    expect(fn).toBeDefined()\n    fn = f.createScoreFunction('a', 0, undefined, 'aggressive')\n    expect(fn).toBeDefined()\n    fn = f.createScoreFunction('a', 0, undefined, 'any')\n    expect(fn).toBeDefined()\n    let res = fn('asdf')\n    expect(res).toBeDefined()\n    expect(res[2]).toBe(0)\n    let spans: [number, number][] = []\n    for (let span of f.matchScoreSpans('asdf', res)) {\n      spans.push(span)\n    }\n    expect(spans).toEqual([[0, 1]])\n    res = fn('asdf')\n    expect(res).toBeDefined()\n  })\n\n  it('should throw when not set pattern', () => {\n    let p = new FuzzyMatch(api)\n    let fn = () => {\n      p.match('text')\n    }\n    expect(fn).toThrow(Error)\n    p.free()\n  })\n\n  it('should slice pattern when necessary', () => {\n    let pat = 'a'.repeat(258)\n    let p = new FuzzyMatch(api)\n    p.setPattern(pat)\n    let res = p.match('a'.repeat(260))\n    expect(res).toBeDefined()\n    expect(res.positions.length).toBe(256)\n  })\n\n  it('should match empty pattern', () => {\n    let p = new FuzzyMatch(api)\n    p.setPattern('')\n    let res = p.match('foo')\n    expect(res.score).toBe(100)\n    expect(res.positions.length).toBe(0)\n  })\n\n  it('should increase content size when necessary', () => {\n    let p = new FuzzyMatch(api)\n    p.setPattern('p')\n    let res = p.match('b'.repeat(2100))\n    expect(res).toBeUndefined()\n    expect(p.getSizes()[0]).toBe(2101)\n    p.free()\n  })\n\n  it('should slice content when necessary', () => {\n    let p = new FuzzyMatch(api)\n    p.setPattern('a')\n    let res = p.match('b'.repeat(40960))\n    expect(res).toBeUndefined()\n    expect(p.getSizes()[0]).toBe(4097)\n    p.free()\n    p.free()\n  })\n\n  it('should fuzzy match ascii', () => {\n    let p = new FuzzyMatch(api)\n    p.setPattern('fb')\n    let res = p.match('fooBar')\n    expect(res).toBeDefined()\n    expect(Array.from(res.positions)).toEqual([0, 3])\n    res = p.match('foaab')\n    expect(res).toBeDefined()\n    expect(Array.from(res.positions)).toEqual([0, 4])\n  })\n\n  it('should fuzzy match multi byte', () => {\n    let p = new FuzzyMatch(api)\n    p.setPattern('f你好')\n    let res = p.match('foo你好Bar')\n    expect(Array.from(res.positions)).toEqual([0, 3, 4])\n  })\n\n  it('should match highlights', () => {\n    let p = new FuzzyMatch(api)\n    p.setPattern('fb')\n    let res = p.matchHighlights('fooBar', 'Text')\n    expect(res).toBeDefined()\n    expect(res.highlights).toEqual([\n      { span: [0, 1], hlGroup: 'Text' },\n      { span: [3, 4], hlGroup: 'Text' }\n    ])\n    p.setPattern('你')\n    res = p.matchHighlights('吃了吗你', 'Text')\n    expect(res).toBeDefined()\n    expect(res.highlights).toEqual([\n      { span: [9, 12], hlGroup: 'Text' }\n    ])\n    res = p.matchHighlights('abc', 'Text')\n    expect(res).toBeUndefined()\n  })\n\n  it('should support matchSeq', () => {\n    let p = new FuzzyMatch(api)\n    p.setPattern('foob')\n    let res = p.match('fooBar')\n    expect(Array.from(res.positions)).toEqual([0, 1, 2, 3])\n    p.setPattern('f b', true)\n    res = p.match('foo bar')\n    expect(Array.from(res.positions)).toEqual([0, 3, 4])\n  })\n\n  it('should better performance', () => {\n    function makeid(length) {\n      let result = ''\n      let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'\n      let charactersLength = characters.length\n      for (let i = 0; i < length; i++) {\n        result += characters.charAt(Math.floor(Math.random() *\n          charactersLength))\n      }\n      return result\n    }\n    let arr: string[] = []\n    for (let i = 0; i < 8000; i++) {\n      arr.push(makeid(50))\n    }\n    let pat = makeid(3)\n    let p = new FuzzyMatch(api)\n    p.setPattern(pat, true)\n    let ts = Date.now()\n    for (const text of arr) {\n      p.match(text)\n    }\n    // console.log(Date.now() - ts)\n    let codes = getCharCodes(pat)\n    ts = Date.now()\n    for (const text of arr) {\n      matchScoreWithPositions(text, codes)\n    }\n    // console.log(Date.now() - ts)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/highlighter.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport Highlighter from '../../model/highlighter'\nimport helper from '../helper'\n\nlet nvim: Neovim\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\ndescribe('Highlighter', () => {\n\n  let highlighter: Highlighter\n  beforeEach(() => {\n    highlighter = new Highlighter()\n  })\n\n  it('should add line', () => {\n    highlighter.addLine('foo', 'Comment')\n    expect(highlighter.getline(0)).toBe('foo')\n    expect(highlighter.getline(2)).toBe('')\n    expect(highlighter.highlights).toEqual([{ lnum: 0, colStart: 0, colEnd: 3, hlGroup: 'Comment' }])\n    expect(highlighter.content).toBe('foo')\n  })\n\n  it('should add lines', () => {\n    highlighter.addLines(['foo', 'bar'])\n    expect(highlighter.content).toBe('foo\\nbar')\n  })\n\n  it('should parse ansi highlights', () => {\n    const redOpen = '\\x1B[31m'\n    const redClose = '\\x1B[39m'\n    highlighter.addLine(redOpen + 'foo' + redClose + 'bar' + redOpen + redClose)\n    expect(highlighter.content).toBe('foobar')\n  })\n\n  it('should add texts', () => {\n    highlighter.addTexts([{ text: 'foo' }, { text: 'bar', hlGroup: 'Comment' }])\n    highlighter.addText('')\n    highlighter.addText(undefined)\n    expect(highlighter.highlights).toEqual([{ lnum: 0, colStart: 3, colEnd: 6, hlGroup: 'Comment' }])\n    expect(highlighter.content).toBe('foobar')\n  })\n\n  it('should render to buffer', async () => {\n    let buf = await nvim.createNewBuffer(true, true)\n    highlighter.addLine('foo', 'Comment')\n    highlighter.addLine('bar')\n    nvim.pauseNotification()\n    highlighter.render(buf)\n    await nvim.resumeNotification()\n    let lines = await buf.lines\n    expect(lines).toEqual(['foo', 'bar'])\n  })\n})\n\n"
  },
  {
    "path": "src/__tests__/modules/line.test.ts",
    "content": "import LineBuilder from '../../model/line'\n\ndescribe('LineBuilder', () => {\n  it('should append', async () => {\n    let line = new LineBuilder(true)\n    line.append('')\n    line.append('text')\n    line.append('comment', 'Comment')\n    line.append('nested', undefined, [{ hlGroup: 'Search', offset: 1, length: 2 }])\n    expect(line.label).toBe('text comment nested')\n    expect(line.highlights).toEqual([\n      { hlGroup: 'Comment', span: [5, 12] },\n      { hlGroup: 'Search', span: [14, 16] }\n    ])\n    let other = new LineBuilder()\n    other.append('text', 'More')\n    line.appendBuilder(other)\n    expect(line.label).toBe('text comment nested text')\n    expect(line.highlights).toEqual([\n      { hlGroup: 'Comment', span: [5, 12] },\n      { hlGroup: 'Search', span: [14, 16] },\n      { hlGroup: 'More', span: [20, 24] }\n    ])\n  })\n\n  it('should append without space', async () => {\n    let line = new LineBuilder(false)\n    line.append('text')\n    let other = new LineBuilder()\n    other.append('text', 'More')\n    line.appendBuilder(other)\n    expect(line.label).toBe('texttext')\n    expect(line.highlights).toEqual([\n      { hlGroup: 'More', span: [4, 8] }\n    ])\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/logger.test.ts",
    "content": "import { FileLogger, toTwoDigits, toThreeDigits, textToLogLevel, format, DEFAULT_LOG_LEVEL, LogLevel, stringifyLogLevel } from '../../logger/log'\nimport { createLogger, logger, getTimestamp, resolveLogFilepath, emptyFile } from '../../logger/index'\nimport path from 'path'\nimport fs from 'fs'\nimport os from 'os'\nimport { v4 as uuid } from 'uuid'\n\nlet filepath: string\nafterEach(() => {\n  if (fs.existsSync(filepath)) fs.unlinkSync(filepath)\n})\n\ndescribe('FileLogger', () => {\n  it('should have DEFAULT_LOG_LEVEL', () => {\n    expect(DEFAULT_LOG_LEVEL).toBeDefined()\n    expect(logger).toBeDefined()\n  })\n\n  it('should get LogLevel', () => {\n    expect(stringifyLogLevel('' as any)).toBe('')\n  })\n\n  it('should getTimestamp', () => {\n    let res = getTimestamp(new Date())\n    expect(res).toBeDefined()\n  })\n\n  it('should convert digits', () => {\n    expect(toTwoDigits(1)).toBe('01')\n    expect(toTwoDigits(11)).toBe('11')\n    expect(toThreeDigits(1)).toBe('001')\n    expect(toThreeDigits(10)).toBe('010')\n    expect(toThreeDigits(100)).toBe('100')\n  })\n\n  it('should get level from text', () => {\n    expect(textToLogLevel('trace')).toBe(LogLevel.Trace)\n    expect(textToLogLevel('debug')).toBe(LogLevel.Debug)\n    expect(textToLogLevel('info')).toBe(LogLevel.Info)\n    expect(textToLogLevel('error')).toBe(LogLevel.Error)\n    expect(textToLogLevel('warning')).toBe(LogLevel.Warning)\n    expect(textToLogLevel('warn')).toBe(LogLevel.Warning)\n    expect(textToLogLevel('off')).toBe(LogLevel.Off)\n    expect(textToLogLevel('')).toBe(LogLevel.Info)\n  })\n\n  it('should format', () => {\n    let obj = {\n      x: 1,\n      y: '2',\n      z: {}\n    } as any\n    obj.z.parent = obj\n    let res = format([obj], 2, true, false)\n    expect(res).toBeDefined()\n    res = format([obj])\n    expect(res).toBeDefined()\n  })\n\n  it('should create logger', async () => {\n    filepath = path.join(os.tmpdir(), uuid())\n    let fileLogger = new FileLogger(filepath, LogLevel.Trace, {\n      color: false,\n      depth: 2,\n      showHidden: false,\n      userFormatters: true\n    })\n    let logger = fileLogger.createLogger('scope')\n    logger.log('msg')\n    logger.trace('trace', 'data', {}, 1, true)\n    logger.debug('debug')\n    logger.info('info')\n    logger.warn('warn')\n    logger.error('error')\n    logger.fatal('fatal')\n    logger.mark('mark')\n    await logger.flush()\n    let content = fs.readFileSync(filepath, 'utf8')\n    let lines = content.split(/\\n/)\n    expect(lines.length).toBe(8)\n    expect(logger.category).toBeDefined()\n    expect(logger.getLevel()).toBeDefined()\n  })\n\n  it('should switch to console', () => {\n    filepath = path.join(os.tmpdir(), uuid())\n    let fileLogger = new FileLogger(filepath, LogLevel.Trace, {})\n    let logger = fileLogger.createLogger('scope')\n    fileLogger.switchConsole()\n    let fn = jest.fn()\n    let spy = jest.spyOn(console, 'error').mockImplementation(() => {\n      fn()\n    })\n    logger.error('error')\n    spy.mockRestore()\n    expect(fn).toHaveBeenCalled()\n    fn = jest.fn()\n    spy = jest.spyOn(console, 'log').mockImplementation(() => {\n      fn()\n    })\n    logger.info('info')\n    spy.mockRestore()\n    expect(fn).toHaveBeenCalled()\n  })\n\n  it('should enable color', async () => {\n    filepath = path.join(os.tmpdir(), uuid())\n    let fileLogger = new FileLogger(filepath, LogLevel.Trace, {\n      color: true\n    })\n    let logger = fileLogger.createLogger('scope')\n    logger.info('msg', 1, true, { foo: 'bar' })\n    await logger.flush()\n    let content = fs.readFileSync(filepath, 'utf8')\n    expect(content.indexOf('\\x33')).toBeGreaterThan(-1)\n  })\n\n  it('should change level', () => {\n    filepath = path.join(os.tmpdir(), uuid())\n    let fileLogger = new FileLogger(filepath, LogLevel.Off, {})\n    fileLogger.setLevel(LogLevel.Debug)\n    fileLogger.setLevel(LogLevel.Debug)\n  })\n\n  it('should work with off level', async () => {\n    filepath = path.join(os.tmpdir(), uuid())\n    let fileLogger = new FileLogger(filepath, LogLevel.Off, {\n      color: false,\n      depth: 2,\n      showHidden: false,\n      userFormatters: true\n    })\n    let logger = fileLogger.createLogger('scope')\n    logger.log('msg')\n    logger.trace('trace')\n    logger.debug('debug')\n    logger.info('info')\n    logger.warn('warn')\n    logger.error('error')\n    logger.fatal('fatal')\n    logger.mark('mark')\n    await logger.flush()\n    expect(fs.existsSync(filepath)).toBe(false)\n  })\n\n  it('should work without formatter', async () => {\n    filepath = path.join(os.tmpdir(), uuid())\n    let fileLogger = new FileLogger(filepath, LogLevel.Trace, {\n      userFormatters: false\n    })\n    let logger = fileLogger.createLogger('scope')\n    logger.log('msg\\n')\n    await logger.flush()\n    let content = fs.readFileSync(filepath, 'utf8')\n    let lines = content.split(/\\n/)\n    expect(lines.length).toBe(2)\n  })\n\n  it('should use backup file', async () => {\n    filepath = path.join(os.tmpdir(), uuid())\n    let fileLogger = new FileLogger(filepath, LogLevel.Trace, {\n      userFormatters: true\n    })\n    let logger = fileLogger.createLogger('scope')\n    let spy = jest.spyOn(fileLogger, 'shouldBackup').mockImplementation(() => {\n      return true\n    })\n    for (let i = 0; i < 6; i++) {\n      logger.log(1)\n    }\n    await logger.flush()\n    spy.mockRestore()\n    let newFile = filepath + `_1`\n    expect(fs.existsSync(newFile)).toBe(true)\n  })\n\n  it('should not throw on error', async () => {\n    filepath = path.join(os.tmpdir(), uuid())\n    let fileLogger = new FileLogger(filepath, LogLevel.Trace, {\n      userFormatters: false\n    })\n    let logger = fileLogger.createLogger('scope')\n    let fn = jest.fn()\n    let s = jest.spyOn(console, 'error').mockImplementation(() => {\n      fn()\n    })\n    let spy = jest.spyOn(fileLogger, 'shouldBackup').mockImplementation(() => {\n      throw new Error('my error')\n    })\n    logger.log('msg\\n')\n    await logger.flush()\n    expect(fn).toHaveBeenCalled()\n    s.mockRestore()\n    spy.mockRestore()\n  })\n\n  it('should create default logger', () => {\n    expect(createLogger()).toBeDefined()\n  })\n\n  it('should resolveLogFilepath from env', () => {\n    let filepath = '/tmp/log'\n    process.env.NVIM_COC_LOG_FILE = filepath\n    expect(resolveLogFilepath()).toBe(filepath)\n    process.env.NVIM_COC_LOG_FILE = ''\n    process.env.XDG_RUNTIME_DIR = os.tmpdir()\n    expect(resolveLogFilepath()).toBeDefined()\n    process.env.XDG_RUNTIME_DIR = '/dir_not_exists'\n    expect(resolveLogFilepath()).toBeDefined()\n    process.env.XDG_RUNTIME_DIR = ''\n    expect(resolveLogFilepath()).toBeDefined()\n  })\n\n  it('should empty file', async () => {\n    emptyFile('/file_not_exists')\n    filepath = path.join(os.tmpdir(), uuid())\n    fs.writeFileSync(filepath, 'data', 'utf8')\n    emptyFile(filepath)\n    let content = fs.readFileSync(filepath, 'utf8')\n    expect(content.trim().length).toBe(0)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/map.test.ts",
    "content": "import * as assert from 'assert'\nimport { LinkedMap, LRUCache, Touch } from '../../util/map'\n\ndescribe('Map', () => {\n\n  test('LinkedMap - Simple', () => {\n    const map = new LinkedMap<string, string>()\n    map.trimOld(99)\n    assert.strictEqual(map.first, undefined)\n    assert.strictEqual(map.last, undefined)\n    assert.strictEqual(map.shift(), undefined)\n    map.set('ak', 'av')\n    map.set('bk', 'bv')\n    assert.deepStrictEqual([...map.keys()], ['ak', 'bk'])\n    assert.deepStrictEqual([...map.values()], ['av', 'bv'])\n    assert.strictEqual(map.first, 'av')\n    assert.strictEqual(map.last, 'bv')\n    map.set('ak', 'av', Touch.AsNew)\n    map.set('x', 'av', Touch.AsNew)\n    map.set('y', 'av', Touch.AsOld)\n    map.set('z', 'av', null)\n    map.remove('x')\n    map.get('y', null)\n    map.shift()\n  })\n\n  test('LinkedMap - Touch Old one', () => {\n    const map = new LinkedMap<string, string>()\n    assert.deepStrictEqual(map.isEmpty(), true)\n    map.set('ak', 'av', Touch.AsOld)\n    map.set('ak', 'av')\n    map.set('ak', 'av', Touch.AsOld)\n    assert.deepStrictEqual([...map.keys()], ['ak'])\n    assert.deepStrictEqual([...map.values()], ['av'])\n    assert.deepStrictEqual(map.isEmpty(), false)\n  })\n\n  test('LinkedMap - Touch New one', () => {\n    const map = new LinkedMap<string, string>()\n    map.set('ak', 'av')\n    map.set('ak', 'av', Touch.AsNew)\n    assert.deepStrictEqual([...map.keys()], ['ak'])\n    assert.deepStrictEqual([...map.values()], ['av'])\n  })\n\n  test('LinkedMap - Touch Old two', () => {\n    const map = new LinkedMap<string, string>()\n    map.set('ak', 'av')\n    map.set('bk', 'bv')\n    map.set('bk', 'bv', Touch.AsOld)\n    assert.deepStrictEqual([...map.keys()], ['bk', 'ak'])\n    assert.deepStrictEqual([...map.values()], ['bv', 'av'])\n  })\n\n  test('LinkedMap - Touch New two', () => {\n    const map = new LinkedMap<string, string>()\n    map.set('ak', 'av')\n    map.set('bk', 'bv')\n    map.set('ak', 'av', Touch.AsNew)\n    assert.deepStrictEqual([...map.keys()], ['bk', 'ak'])\n    assert.deepStrictEqual([...map.values()], ['bv', 'av'])\n  })\n\n  test('LinkedMap - Touch Old from middle', () => {\n    const map = new LinkedMap<string, string>()\n    map.set('ak', 'av')\n    map.set('bk', 'bv')\n    map.set('ck', 'cv')\n    map.set('bk', 'bv', Touch.AsOld)\n    assert.deepStrictEqual([...map.keys()], ['bk', 'ak', 'ck'])\n    assert.deepStrictEqual([...map.values()], ['bv', 'av', 'cv'])\n  })\n\n  test('LinkedMap - Touch New from middle', () => {\n    const map = new LinkedMap<string, string>()\n    map.set('ak', 'av')\n    map.set('bk', 'bv')\n    map.set('ck', 'cv')\n    map.set('bk', 'bv', Touch.AsNew)\n    assert.deepStrictEqual([...map.keys()], ['ak', 'ck', 'bk'])\n    assert.deepStrictEqual([...map.values()], ['av', 'cv', 'bv'])\n  })\n\n  test('LinkedMap - basics', function() {\n    const map = new LinkedMap<string, any>()\n\n    assert.strictEqual(map.size, 0)\n\n    map.set('1', 1)\n    map.set('2', '2')\n    map.set('3', true)\n\n    const obj = Object.create(null)\n    map.set('4', obj)\n\n    const date = Date.now()\n    map.set('5', date)\n\n    assert.strictEqual(map.size, 5)\n    assert.strictEqual(map.get('1'), 1)\n    assert.strictEqual(map.get('2'), '2')\n    assert.strictEqual(map.get('3'), true)\n    assert.strictEqual(map.get('4'), obj)\n    assert.strictEqual(map.get('5'), date)\n    assert.ok(!map.get('6'))\n\n    map.delete('6')\n    assert.strictEqual(map.size, 5)\n    assert.strictEqual(map.delete('1'), true)\n    assert.strictEqual(map.delete('2'), true)\n    assert.strictEqual(map.delete('3'), true)\n    assert.strictEqual(map.delete('4'), true)\n    assert.strictEqual(map.delete('5'), true)\n\n    assert.strictEqual(map.size, 0)\n    assert.ok(!map.get('5'))\n    assert.ok(!map.get('4'))\n    assert.ok(!map.get('3'))\n    assert.ok(!map.get('2'))\n    assert.ok(!map.get('1'))\n\n    map.set('1', 1)\n    map.set('2', '2')\n    map.set('3', true)\n\n    assert.ok(map.has('1'))\n    assert.strictEqual(map.get('1'), 1)\n    assert.strictEqual(map.get('2'), '2')\n    assert.strictEqual(map.get('3'), true)\n\n    map.clear()\n\n    assert.strictEqual(map.size, 0)\n    assert.ok(!map.get('1'))\n    assert.ok(!map.get('2'))\n    assert.ok(!map.get('3'))\n    assert.ok(!map.has('1'))\n  })\n\n  test('LinkedMap - Iterators', () => {\n    const map = new LinkedMap<number, any>()\n    map.set(1, 1)\n    map.set(2, 2)\n    map.set(3, 3)\n\n    for (const elem of map.keys()) {\n      assert.ok(elem)\n    }\n\n    for (const elem of map.values()) {\n      assert.ok(elem)\n    }\n\n    for (const elem of map.entries()) {\n      assert.ok(elem)\n    }\n\n    {\n      const keys = map.keys()\n      const values = map.values()\n      const entries = map.entries()\n      map.get(1)\n      keys.next()\n      values.next()\n      entries.next()\n    }\n\n    {\n      const keys = map.keys()\n      const values = map.values()\n      const entries = map.entries()\n      map.get(1, Touch.AsNew)\n\n      let exceptions = 0\n      try {\n        keys.next()\n      } catch (err) {\n        exceptions++\n      }\n      try {\n        values.next()\n      } catch (err) {\n        exceptions++\n      }\n      try {\n        entries.next()\n      } catch (err) {\n        exceptions++\n      }\n\n      assert.strictEqual(exceptions, 3)\n    }\n  })\n\n  test('LinkedMap - LRU Cache simple', () => {\n    const cache = new LRUCache<number, number>(5)\n    assert.strictEqual(cache.limit, 5)\n      ;[1, 2, 3, 4, 5].forEach(value => cache.set(value, value))\n    assert.strictEqual(cache.ratio, 1)\n    assert.strictEqual(cache.size, 5)\n    cache.set(6, 6)\n    assert.strictEqual(cache.size, 5)\n    assert.deepStrictEqual([...cache.keys()], [2, 3, 4, 5, 6])\n    cache.set(7, 7)\n    assert.strictEqual(cache.size, 5)\n    assert.deepStrictEqual([...cache.keys()], [3, 4, 5, 6, 7])\n    const values: number[] = [];\n    [3, 4, 5, 6, 7].forEach(key => values.push(cache.get(key)!))\n    assert.deepStrictEqual(values, [3, 4, 5, 6, 7])\n    cache.ratio = 0.2\n    cache.ratio = 0.8\n    cache.limit = 0\n    assert.strictEqual(cache.size, 0)\n  })\n\n  test('LinkedMap - LRU Cache get', () => {\n    const cache = new LRUCache<number, number>(5);\n\n    [1, 2, 3, 4, 5].forEach(value => cache.set(value, value))\n    assert.strictEqual(cache.size, 5)\n    assert.deepStrictEqual([...cache.keys()], [1, 2, 3, 4, 5])\n    cache.get(3)\n    assert.deepStrictEqual([...cache.keys()], [1, 2, 4, 5, 3])\n    cache.peek(4)\n    assert.deepStrictEqual([...cache.keys()], [1, 2, 4, 5, 3])\n    const values: number[] = [];\n    [1, 2, 3, 4, 5].forEach(key => values.push(cache.get(key)!))\n    assert.deepStrictEqual(values, [1, 2, 3, 4, 5])\n  })\n\n  test('LinkedMap - LRU Cache limit', () => {\n    const cache = new LRUCache<number, number>(10)\n\n    for (let i = 1; i <= 10; i++) {\n      cache.set(i, i)\n    }\n    assert.strictEqual(cache.size, 10)\n    cache.limit = 5\n    assert.strictEqual(cache.size, 5)\n    assert.deepStrictEqual([...cache.keys()], [6, 7, 8, 9, 10])\n    cache.limit = 20\n    assert.strictEqual(cache.size, 5)\n    for (let i = 11; i <= 20; i++) {\n      cache.set(i, i)\n    }\n    assert.deepStrictEqual(cache.size, 15)\n    const values: number[] = []\n    for (let i = 6; i <= 20; i++) {\n      values.push(cache.get(i)!)\n      assert.strictEqual(cache.get(i), i)\n    }\n    assert.deepStrictEqual([...cache.values()], values)\n  })\n\n  test('LinkedMap - LRU Cache limit with ratio', () => {\n    const cache = new LRUCache<number, number>(10, 0.5)\n\n    for (let i = 1; i <= 10; i++) {\n      cache.set(i, i)\n    }\n    assert.strictEqual(cache.size, 10)\n    cache.set(11, 11)\n    assert.strictEqual(cache.size, 5)\n    assert.deepStrictEqual([...cache.keys()], [7, 8, 9, 10, 11])\n    const values: number[] = [];\n    [...cache.keys()].forEach(key => values.push(cache.get(key)!))\n    assert.deepStrictEqual(values, [7, 8, 9, 10, 11])\n    assert.deepStrictEqual([...cache.values()], values)\n  })\n\n  test('LinkedMap - toJSON / fromJSON', () => {\n    let map = new LinkedMap<string, string>()\n    map.set('ak', 'av')\n    map.set('bk', 'bv')\n    map.set('ck', 'cv')\n\n    const json = map.toJSON()\n    map = new LinkedMap<string, string>()\n    map.fromJSON(json)\n\n    let i = 0\n    map.forEach((value, key) => {\n      if (i === 0) {\n        assert.strictEqual(key, 'ak')\n        assert.strictEqual(value, 'av')\n      } else if (i === 1) {\n        assert.strictEqual(key, 'bk')\n        assert.strictEqual(value, 'bv')\n      } else if (i === 2) {\n        assert.strictEqual(key, 'ck')\n        assert.strictEqual(value, 'cv')\n      }\n      i++\n    })\n    i = 0\n    assert.throws(() => {\n      map.forEach(function(this: object) {\n        assert.deepStrictEqual(this, {})\n        if (i == 2) {\n          map.set('1', '')\n        }\n        i++\n      }, {})\n    })\n    i = 0\n    for (let _item of map) {\n      i++\n    }\n    assert.strictEqual(i, 4)\n  })\n\n  test('LinkedMap - delete Head and Tail', function() {\n    const map = new LinkedMap<string, number>()\n\n    assert.strictEqual(map.size, 0)\n\n    map.set('1', 1)\n    assert.strictEqual(map.size, 1)\n    map.delete('1')\n    assert.strictEqual(map.get('1'), undefined)\n    assert.strictEqual(map.size, 0)\n    assert.strictEqual([...map.keys()].length, 0)\n  })\n\n  test('LinkedMap - delete Head', function() {\n    const map = new LinkedMap<string, number>()\n\n    assert.strictEqual(map.size, 0)\n\n    map.set('1', 1)\n    map.set('2', 2)\n    assert.strictEqual(map.size, 2)\n    map.delete('1')\n    assert.strictEqual(map.get('2'), 2)\n    assert.strictEqual(map.size, 1)\n    assert.strictEqual([...map.keys()].length, 1)\n    assert.strictEqual([...map.keys()][0], '2')\n  })\n\n  test('LinkedMap - delete Tail', function() {\n    const map = new LinkedMap<string, number>()\n\n    assert.strictEqual(map.size, 0)\n\n    map.set('1', 1)\n    map.set('2', 2)\n    assert.strictEqual(map.size, 2)\n    map.delete('2')\n    assert.strictEqual(map.get('1'), 1)\n    assert.strictEqual(map.size, 1)\n    assert.strictEqual([...map.keys()].length, 1)\n    assert.strictEqual([...map.keys()][0], '1')\n  })\n\n  test('LinkedMap, - before and after', function(): void {\n    const map = new LinkedMap<string, number>()\n    map.set('1', 1)\n    map.set('2', 2)\n    assert.strictEqual(map.before('2'), 1)\n    assert.strictEqual(map.before('1'), undefined)\n    assert.strictEqual(map.after('1'), 2)\n    assert.strictEqual(map.after('2'), undefined)\n    assert.strictEqual(map.after('3'), undefined)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/memos.test.ts",
    "content": "import Memos from '../../model/memos'\nimport os from 'os'\nimport path from 'path'\nimport fs from 'fs'\nimport { loadJson, writeJson } from '../../util/fs'\n\nlet filepath = path.join(os.tmpdir(), 'test')\nlet memos: Memos\nbeforeEach(() => {\n  memos = new Memos(filepath)\n})\n\nafterEach(() => {\n  if (fs.existsSync(filepath)) {\n    fs.unlinkSync(filepath)\n  }\n})\n\ndescribe('Memos', () => {\n  it('should update and get', async () => {\n    let memo = memos.createMemento('x')\n    await memo.update('foo.bar', 'memo')\n    let res = memo.get<string>('foo.bar')\n    expect(res).toBe('memo')\n    await memo.update('foo.bar', undefined)\n    res = memo.get<string>('foo.bar')\n    expect(res).toBeUndefined()\n  })\n\n  it('should get value for key if it does not exist', async () => {\n    let memo = memos.createMemento('y')\n    let res = memo.get<any>('xyz')\n    expect(res).toBeUndefined()\n  })\n\n  it('should use defaultValue when it does not exist', async () => {\n    let memo = memos.createMemento('y')\n    let res = memo.get<any>('f.o.o', 'default')\n    expect(res).toBe('default')\n  })\n\n  it('should update multiple values', async () => {\n    let memo = memos.createMemento('x')\n    await memo.update('foo', 'x')\n    await memo.update('bar', 'y')\n    expect(memo.get<string>('foo')).toBe('x')\n    expect(memo.get<string>('bar')).toBe('y')\n  })\n\n  it('should merge content', async () => {\n    memos.merge(path.join(os.tmpdir(), 'file_not_exists_memos'))\n    let oldPath = path.join(os.tmpdir(), 'old_memos.json')\n    writeJson(oldPath, { old: { release: true } })\n    memos.merge(oldPath)\n    let obj = loadJson(filepath) as any\n    expect(obj.old.release).toBe(true)\n    expect(fs.existsSync(oldPath)).toBe(false)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/menu.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationTokenSource } from 'vscode-languageserver-protocol'\nimport Menu, { isMenuItem, toIndexText } from '../../model/menu'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet menu: Menu\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  if (menu) menu.dispose()\n  await helper.reset()\n})\n\ndescribe('Menu', () => {\n  it('should check isMenuItem', () => {\n    expect(isMenuItem(null)).toBe(false)\n  })\n\n  it('should get index text', () => {\n    expect(toIndexText(99)).toBe('  ')\n  })\n\n  it('should dispose on window close', async () => {\n    await nvim.command('vnew')\n    let currWin = await nvim.window\n    menu = new Menu(nvim, { shortcuts: true, items: [{ text: 'foo' }, { text: 'bar', disabled: true }] })\n    let p = new Promise(resolve => {\n      menu.onDidClose(v => {\n        resolve(v)\n      })\n    })\n    await menu.show()\n    let win = await helper.getFloat()\n    nvim.call('coc#window#close', [currWin.id], true)\n    nvim.call('coc#float#close', [win.id], true)\n    let res = await p\n    expect(res).toBe(-1)\n  })\n\n  it('should cancel by <esc>', async () => {\n    menu = new Menu(nvim, { items: [{ text: 'foo' }, { text: 'bar', disabled: true }] })\n    let p = new Promise(resolve => {\n      menu.onDidClose(v => {\n        resolve(v)\n      })\n    })\n    await menu.show()\n    await helper.waitPrompt()\n    await nvim.input('<esc>')\n    let res = await p\n    expect(res).toBe(-1)\n  })\n\n  it('should cancel before float window shown', async () => {\n    let tokenSource: CancellationTokenSource = new CancellationTokenSource()\n    menu = new Menu(nvim, { items: [{ text: 'foo' }] }, tokenSource.token)\n    let p = new Promise(resolve => {\n      menu.onDidClose(v => {\n        resolve(v)\n      })\n    })\n    let promise = menu.show()\n    tokenSource.cancel()\n    await promise\n    let res = await p\n    expect(res).toBe(-1)\n  })\n\n  it('should support menu shortcut', async () => {\n    menu = new Menu(nvim, { items: [{ text: 'foo' }, { text: 'bar' }, { text: 'baba' }], shortcuts: true, title: 'Actions' })\n    let p = new Promise(resolve => {\n      menu.onDidClose(v => {\n        resolve(v)\n      })\n    })\n    await menu.show()\n    await helper.waitPrompt()\n    await nvim.input('b')\n    let res = await p\n    expect(res).toBe(1)\n  })\n\n  it('should support content', async () => {\n    menu = new Menu(nvim, { items: [{ text: 'foo' }, { text: 'bar' }], content: 'content' })\n    await menu.show({ confirmKey: '<C-j>' })\n    let p = new Promise(resolve => {\n      menu.onDidClose(v => {\n        resolve(v)\n      })\n    })\n    let lines = await menu.buffer.lines\n    expect(lines[0]).toBe('content')\n    await nvim.input('<C-j>')\n    let res = await p\n    expect(res).toBe(0)\n    menu.dispose()\n  })\n\n  it('should select by CR', async () => {\n    menu = new Menu(nvim, { items: ['foo', 'bar'] })\n    let p = new Promise(resolve => {\n      menu.onDidClose(v => {\n        resolve(v)\n      })\n    })\n    await menu.show()\n    await helper.waitPrompt()\n    await nvim.input('j<cr>')\n    let res = await p\n    expect(res).toBe(1)\n  })\n\n  it('should show menu in center', async () => {\n    menu = new Menu(nvim, { items: ['foo', 'bar'], position: 'center' })\n    await menu.show()\n    expect(menu.buffer).toBeDefined()\n  })\n\n  it('should ignore invalid index', async () => {\n    menu = new Menu(nvim, { items: ['foo', 'bar'] })\n    await menu.show()\n    await helper.waitPrompt()\n    await nvim.input('0')\n    await helper.wait(30)\n    let exists = await nvim.call('coc#float#has_float', [])\n    expect(exists).toBe(1)\n  })\n\n  it('should select by index number', async () => {\n    menu = new Menu(nvim, { items: ['foo', 'bar'] })\n    let p = new Promise(resolve => {\n      menu.onDidClose(v => {\n        resolve(v)\n      })\n    })\n    await menu.show()\n    await helper.waitPrompt()\n    await nvim.input('1')\n    let res = await p\n    expect(res).toBe(0)\n  })\n\n  it('should choose item after timer', async () => {\n    menu = new Menu(nvim, { items: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'] })\n    await menu.show()\n    let p = new Promise(resolve => {\n      menu.onDidClose(n => {\n        resolve(n)\n      })\n    })\n    await helper.waitPrompt()\n    await nvim.input('1')\n    let res = await p\n    expect(res).toBe(0)\n  })\n\n  it('should navigate by j, k, g & G', async () => {\n    menu = new Menu(nvim, { items: ['one', 'two', 'three'] })\n    expect(menu.buffer).toBeUndefined()\n    await menu.onInputChar('session', 'j')\n    await menu.show({ floatHighlight: 'CocFloating', floatBorderHighlight: 'CocFloatBorder' })\n    let id = await nvim.call('GetFloatWin') as number\n    expect(id).toBeGreaterThan(0)\n    let win = nvim.createWindow(id)\n    await nvim.input('x')\n    await nvim.input('j')\n    await nvim.input('j')\n    await nvim.input('j')\n    await helper.wait(50)\n    let cursor = await win.cursor\n    expect(cursor[0]).toBe(1)\n    await nvim.input('k')\n    await nvim.input('k')\n    await nvim.input('k')\n    await helper.wait(50)\n    cursor = await win.cursor\n    expect(cursor[0]).toBe(1)\n    await nvim.input('G')\n    await helper.wait(50)\n    cursor = await win.cursor\n    expect(cursor[0]).toBe(3)\n    await nvim.input('g')\n    await helper.wait(50)\n    cursor = await win.cursor\n    expect(cursor[0]).toBe(1)\n    await nvim.input('<C-f>')\n    await nvim.input('<C-b>')\n    await nvim.input('9')\n    await helper.wait(20)\n  })\n\n  it('should select by numbers', async () => {\n    let selected: number\n    menu = new Menu(nvim, { items: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'] })\n    await menu.show()\n    let promise = new Promise(resolve => {\n      menu.onDidClose(n => {\n        selected = n\n        resolve(undefined)\n      })\n    })\n    await helper.waitPrompt()\n    await nvim.input('1')\n    await helper.wait(10)\n    await nvim.input('0')\n    await promise\n    expect(selected).toBe(9)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/outputChannel.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport OutputChannel from '../../model/outputChannel'\nimport { wait } from '../../util'\nimport helper from '../helper'\n\nlet nvim: Neovim\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterEach(async () => {\n  await helper.reset()\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\ndescribe('OutputChannel', () => {\n  test('without nvim', () => {\n    let o = new OutputChannel('f')\n    o.appendLine('foo')\n    o.append('bar')\n    o.show()\n    o.hide()\n    o.clear()\n  })\n\n  test('channel name with special characters', async () => {\n    let ch = new OutputChannel(\"a@b 'c\", nvim)\n    ch.show(false, 'edit')\n    let bufname = await nvim.call('bufname', '%')\n    expect(bufname).toBe('output:///a@b%20\\'c')\n    let bufnr = await nvim.call('bufnr', ['%'])\n    ch.hide()\n    await helper.wait(10)\n    let loaded = await nvim.call('bufloaded', [bufnr])\n    expect(loaded).toBe(0)\n    ch.dispose()\n  })\n\n  test('outputChannel.show(true)', async () => {\n    await nvim.setLine('foo')\n    let c = new OutputChannel('0', nvim)\n    let bufnr = (await nvim.buffer).id\n    c.show(true)\n    await helper.waitFor('bufnr', ['%'], bufnr)\n    c.hide()\n    c.clear(1)\n    c.dispose()\n    c.append('')\n    c.appendLine('')\n  })\n\n  test('outputChannel.keep()', async () => {\n    await nvim.setLine('foo')\n    let c = new OutputChannel('clear', nvim)\n    c.appendLine('foo')\n    c.appendLine('bar')\n    c.show()\n    await helper.wait(10)\n    c.clear(2)\n    let lines = await nvim.call('getbufline', ['output:///clear', 1, '$']) as string[]\n    expect(lines.includes('bar')).toBe(true)\n  })\n\n  test('outputChannel.show(false)', async () => {\n    let c = new OutputChannel('1', nvim)\n    let bufnr = (await nvim.buffer).id\n    c.show()\n    await wait(100)\n    let nr = (await nvim.buffer).id\n    expect(bufnr).toBeLessThan(nr)\n  })\n\n  test('outputChannel.appendLine()', async () => {\n    let c = new OutputChannel('2', nvim)\n    c.show()\n    await wait(100)\n    let buf = await nvim.buffer\n    c.appendLine('foo')\n    await helper.waitFor('eval', [`join(getbufline(${buf.id},1,'$'),'\\n')`], /foo/)\n  })\n\n  test('outputChannel.append()', async () => {\n    let c = new OutputChannel('3', nvim)\n    c.show(false)\n    await wait(60)\n    c.append('foo')\n    c.append('bar')\n    await wait(50)\n    let buf = await nvim.buffer\n    await helper.waitFor('eval', [`join(getbufline(${buf.id},1,'$'),'\\n')`], /foo/)\n  })\n\n  test('outputChannel.clear()', async () => {\n    let c = new OutputChannel('4', nvim)\n    c.show(false)\n    await wait(30)\n    let buf = await nvim.buffer\n    c.appendLine('foo')\n    c.appendLine('bar')\n    await wait(30)\n    c.clear()\n    await wait(30)\n    let lines = await buf.lines\n    let content = lines.join('')\n    expect(content).toBe('')\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/picker.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationTokenSource } from 'vscode-languageserver-protocol'\nimport events from '../../events'\nimport Picker, { toPickerItems } from '../../model/picker'\nimport { QuickPickItem } from '../../types'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet picker: Picker\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  if (picker) picker.dispose()\n  picker = undefined\n  await helper.reset()\n})\n\nasync function inputChar(ch: string): Promise<void> {\n  await picker.onInputChar('picker', ch)\n}\n\nconst items: QuickPickItem[] = [{ label: 'foo' }, { label: 'bar' }]\ndescribe('util', () => {\n  it('should convert picker items', () => {\n    expect(toPickerItems([{ label: 'foo' }])).toEqual([{ label: 'foo' }])\n    expect(toPickerItems(['foo'])).toEqual([{ label: 'foo' }])\n  })\n})\n\ndescribe('Picker create', () => {\n  it('should show dialog with buttons', async () => {\n    picker = new Picker(nvim, { title: 'title', items: items.concat([{ label: 'three', picked: true }]) })\n    let winid = await picker.show({ pickerButtons: true })\n    expect(winid).toBeDefined()\n    let id = await nvim.call('coc#float#get_related', [winid, 'buttons'])\n    expect(id).toBeGreaterThan(0)\n    let res = await nvim.call('sign_getplaced', [picker.buffer.id, { group: 'PopUpCocDialog' }])\n    expect(res[0].signs).toBeDefined()\n    expect(res[0].signs[0].name).toBe('CocCurrentLine')\n  })\n\n  it('should cancel dialog when cancellation token requested', async () => {\n    let tokenSource = new CancellationTokenSource()\n    picker = new Picker(nvim, { title: 'title', items }, tokenSource.token)\n    let winid = await picker.show({ pickerButtons: true, pickerButtonShortcut: true })\n    expect(winid).toBeDefined()\n    tokenSource.cancel()\n    let win = nvim.createWindow(winid)\n    await helper.waitValue(async () => {\n      return await win.valid\n    }, false)\n  })\n\n  it('should cancel dialog without window', async () => {\n    let tokenSource = new CancellationTokenSource()\n    picker = new Picker(nvim, { title: 'title', items }, tokenSource.token)\n    expect(picker.buffer).toBeUndefined()\n    expect(picker.currIndex).toBe(0)\n    await picker.onInputChar('picker', 'i')\n    picker.changeLine(-1)\n    tokenSource.cancel()\n  })\n})\n\ndescribe('Picker key mappings', () => {\n  it('should toggle selection mouse click bracket', async () => {\n    picker = new Picker(nvim, { title: 'title', items })\n    let winid = await picker.show()\n    await nvim.setVar('mouse_position', [winid, 1, 1])\n    await nvim.input('<LeftRelease>')\n    await helper.wait(50)\n    let buf = picker.buffer\n    let lines = await buf.getLines({ start: 0, end: 1, strictIndexing: false })\n    expect(lines[0]).toMatch(/^\\[x\\]/)\n    await inputChar('<LeftRelease>')\n    await events.fire('FloatBtnClick', [picker.bufnr, 0])\n  })\n\n  it('should change current line on mouse click label', async () => {\n    picker = new Picker(nvim, { title: 'title', items })\n    let winid = await picker.show()\n    await nvim.setVar('mouse_position', [winid, 2, 4])\n    await nvim.input('<LeftRelease>')\n    await helper.wait(50)\n    let buf = picker.buffer\n    let res = await nvim.call('sign_getplaced', [buf.id, { group: 'PopUpCocDialog' }])\n    expect(res[0].signs).toBeDefined()\n    expect(res[0].signs[0].name).toBe('CocCurrentLine')\n    await events.fire('FloatBtnClick', [picker.bufnr, 1])\n  })\n\n  it('should cancel by <esc>', async () => {\n    await helper.createDocument()\n    picker = new Picker(nvim, { title: 'title', items })\n    let winid = await picker.show({ pickerButtons: true })\n    expect(winid).toBeDefined()\n    let fn = jest.fn()\n    picker.onDidClose(fn)\n    await picker.onInputChar('picker', '<esc>')\n    expect(fn).toHaveBeenCalledTimes(1)\n  })\n\n  it('should confirm by <CR>', async () => {\n    await helper.createDocument()\n    let item: QuickPickItem = { label: 'item', description: 'description' }\n    picker = new Picker(nvim, { title: 'title', items: [item].concat(items) })\n    let winid = await picker.show({ pickerButtons: true })\n    expect(winid).toBeDefined()\n    let fn = jest.fn()\n    picker.onDidClose(fn)\n    await picker.onInputChar('picker', ' ')\n    await picker.onInputChar('picker', ' ')\n    await picker.onInputChar('picker', 'k')\n    await picker.onInputChar('picker', ' ')\n    await events.fire('FloatBtnClick', [picker.bufnr + 1, 0])\n    await events.fire('FloatBtnClick', [picker.bufnr, 0])\n    expect(fn).toHaveBeenCalledTimes(1)\n  })\n\n  it('should move cursor by j, k, g & G', async () => {\n    await helper.createDocument()\n    picker = new Picker(nvim, { title: 'title', items })\n    function getSigns(): Promise<any> {\n      return nvim.call('sign_getplaced', [picker.buffer.id, { group: 'PopUpCocDialog' }])\n    }\n    let winid = await picker.show({ pickerButtons: true })\n    await helper.waitFloat()\n    expect(winid).toBeDefined()\n    await nvim.input('j')\n    await helper.wait(100)\n    let res = await getSigns()\n    expect(res[0].signs[0].lnum).toBe(2)\n    await nvim.input('k')\n    await helper.wait(100)\n    res = await getSigns()\n    expect(res[0].signs[0].lnum).toBe(1)\n    await nvim.input('G')\n    await helper.wait(100)\n    res = await getSigns()\n    expect(res[0].signs[0].lnum).toBe(2)\n    await nvim.input('g')\n    await helper.wait(100)\n    res = await getSigns()\n    expect(res[0].signs[0].lnum).toBe(1)\n  })\n\n  it('should toggle selection by <space>', async () => {\n    await helper.createDocument()\n    picker = new Picker(nvim, { title: 'title', items })\n    let winid = await picker.show({\n      maxWidth: 60,\n      floatHighlight: 'CocFloating',\n      floatBorderHighlight: 'Normal',\n      rounded: true,\n      confirmKey: 'r',\n      pickerButtons: true\n    })\n    await helper.waitFloat()\n    expect(winid).toBeDefined()\n    let fn = jest.fn()\n    picker.onDidClose(fn)\n    await inputChar(' ')\n    let lines = await nvim.call('getbufline', [picker.buffer.id, 1])\n    expect(lines[0]).toMatch('[x]')\n    await inputChar('r')\n  })\n\n  it('should scroll forward & backward', async () => {\n    await helper.createDocument()\n    let items = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'].map(s => {\n      return { label: s }\n    })\n    picker = new Picker(nvim, { title: 'title', items })\n    let event\n    picker.onDidClose(ev => {\n      event = ev\n    })\n    let winid = await picker.show({ maxHeight: 3 })\n    expect(winid).toBeDefined()\n    await picker.onInputChar('picker', '<C-f>')\n    let info = await nvim.call('getwininfo', [winid])\n    expect(info[0]).toBeDefined()\n    await picker.onInputChar('picker', '<C-b>')\n    info = await nvim.call('getwininfo', [winid])\n    expect(info[0]).toBeDefined()\n    await inputChar('<cr>')\n    expect(event).toBeUndefined()\n  })\n\n  it('should fire selected items on cr', async () => {\n    picker = new Picker(nvim, { title: 'title', items: items.concat([{ label: 'three', picked: true }]) })\n    let event\n    picker.onDidClose(e => {\n      event = e\n    })\n    let winid = await picker.show({ pickerButtons: true })\n    expect(winid).toBeDefined()\n    await inputChar('<cr>')\n    expect(event).toEqual([2])\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/plugin.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport path from 'path'\nimport * as vsTypes from 'vscode-languageserver-types'\nimport * as exportObj from '../../index'\nimport Plugin from '../../plugin'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet plugin: Plugin\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  plugin = helper.plugin\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  await helper.reset()\n})\n\ndescribe('Plugin', () => {\n  it('should check hasAction', () => {\n    expect(plugin.hasAction('NOT_EXISTS')).toBe(false)\n    expect(plugin.hasAction('rename')).toBe(true)\n  })\n\n  it('should throw when action exists', () => {\n    expect(() => {\n      plugin.addAction('rename', () => {})\n    }).toThrow(Error)\n  })\n})\n\ndescribe('exports', () => {\n  it('should exports all types from vscode-languageserver-types', () => {\n    // TODO: LanguageKind added in 3.18, we didn't use this yet\n    // TODO: CodeActionTag added in 3.18, but prpoposed\n    const excludes = [\n      'EOL',\n      'URI',\n      'TextDocument',\n      'LanguageKind',\n      'CodeActionTag',\n    ]\n    let list: string[] = []\n    for (let key of Object.keys(vsTypes)) {\n      if (typeof exportObj[key] === 'undefined' && !excludes.includes(key)) {\n        list.push(key)\n      }\n    }\n    expect(list.length).toBe(0)\n    for (let key of ['InlineCompletionItem', 'InlineCompletionContext']) {\n      expect(exportObj[key]).toBeDefined()\n    }\n  })\n})\n\ndescribe('help tags', () => {\n  it('should generate help tags', async () => {\n    let root = workspace.pluginRoot\n    let dir = await nvim.call('fnameescape', path.join(root, 'doc'))\n    let res = await nvim.call('execute', `helptags ${dir}`) as string\n    expect(res.length).toBe(0)\n  })\n\n  it('should return jumpable', async () => {\n    let jumpable = await helper.plugin.cocAction('snippetCheck', false, true)\n    expect(jumpable).toBe(false)\n  })\n\n  it('should show CocInfo', async () => {\n    await helper.doAction('showInfo')\n    let line = await nvim.line\n    expect(line).toMatch('version')\n  })\n\n  it('should ensure current document created', async () => {\n    await nvim.command('tabe tmp.js')\n    let res = await helper.plugin.cocAction('ensureDocument')\n    expect(res).toBe(true)\n    let bufnr = await nvim.call('bufnr', ['%']) as number\n    let doc = workspace.getDocument(bufnr)\n    expect(doc).toBeDefined()\n  })\n\n  it('should get related information', async () => {\n    let res = await helper.plugin.cocAction('diagnosticRelatedInformation')\n    expect(res).toEqual([])\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/quickpick.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CancellationTokenSource, Disposable } from 'vscode-languageserver-protocol'\nimport events from '../../events'\nimport QuickPick from '../../model/quickpick'\nimport { QuickPickItem } from '../../types'\nimport { disposeAll } from '../../util'\nimport window from '../../window'\nimport helper from '../helper'\nexport type Item = QuickPickItem | string\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nlet ns: number\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  ns = await nvim.createNamespace('coc-input-box')\n})\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  disposables = []\n})\n\nasync function getTitleLine(): Promise<string> {\n  let winids = await nvim.call('coc#float#get_float_win_list') as number[]\n  let winid = Math.min(...winids)\n  let id = await nvim.call('coc#float#get_related', [winid, 'border']) as number\n  let win = nvim.createWindow(id)\n  let buf = await win.buffer\n  let lines = await buf.lines\n  return lines[0]\n}\n\ndescribe('InputBox', () => {\n  it('should request input', async () => {\n    let winid = await nvim.call('win_getid')\n    let p = window.requestInput('Name')\n    await helper.waitFloat()\n    await nvim.input('bar<enter>')\n    let res = await p\n    let curr = await nvim.call('win_getid')\n    expect(curr).toBe(winid)\n    expect(res).toBe('bar')\n  })\n\n  it('should use input method of vim', async () => {\n    helper.updateConfiguration('coc.preferences.promptInput', false)\n    let defaultValue = 'default'\n    let p = window.requestInput('Name', defaultValue)\n    await helper.wait(50)\n    await nvim.input('<enter>')\n    let res = await p\n    expect(res).toBe(defaultValue)\n  })\n\n  it('should return empty string when input empty', async () => {\n    let p = window.requestInput('Name')\n    await helper.wait(30)\n    await nvim.input('<enter>')\n    let res = await p\n    expect(res).toBe('')\n  })\n\n  it('should emit change event', async () => {\n    let input = await window.createInputBox('', '', {})\n    disposables.push(input)\n    let curr: string\n    input.onDidChange(text => {\n      curr = text\n    })\n    await nvim.input('abc')\n    await helper.waitValue((() => {\n      return curr\n    }), 'abc')\n    input.title = 'foo'\n    expect(input.title).toBe('foo')\n    input.loading = true\n    expect(input.loading).toBe(true)\n    input.borderhighlight = 'WarningMsg'\n    expect(input.borderhighlight).toBe('WarningMsg')\n  })\n\n  it('should not check bufnr for events', async () => {\n    let input = await window.createInputBox('', undefined, {})\n    disposables.push(input)\n    let bufnr = input.bufnr\n    let called = false\n    input.onDidChange(() => {\n      called = true\n    })\n    await events.fire('BufWinLeave', [bufnr + 1])\n    await events.fire('PromptInsert', ['', bufnr + 1])\n    await events.fire('TextChangedI', [bufnr + 1, {\n      lnum: 1,\n      col: 1,\n      line: '',\n      changedtick: 0,\n      pre: ''\n    }])\n    expect(called).toBe(false)\n    expect(input.bufnr).toBeDefined()\n    expect(input.dimension).toBeDefined()\n  })\n\n  it('should change input value', async () => {\n    let input = await window.createInputBox('', undefined, {})\n    disposables.push(input)\n    let called = false\n    input.onDidChange(() => {\n      called = true\n    })\n    input.value = 'foo'\n    await helper.waitValue(async () => {\n      let lines = await nvim.call('getbufline', [input.bufnr, 1]) as string[]\n      return lines[0]\n    }, 'foo')\n    expect(called).toBe(true)\n    expect(input.value).toBe('foo')\n  })\n\n  it('should show and hide placeHolder', async () => {\n    let input = await window.createInputBox('title', undefined, { placeHolder: 'placeHolder' })\n    disposables.push(input)\n    let buf = nvim.createBuffer(input.bufnr)\n    let markers = await buf.getExtMarks(ns, 0, -1, { details: true })\n    expect(markers.length).toBe(1)\n    let blocks = markers[0][3].virt_text\n    expect(blocks).toEqual([['placeHolder', 'CocInputBoxVirtualText']])\n    await nvim.input('a')\n    await helper.waitValue(async () => {\n      let markers = await buf.getExtMarks(ns, 0, -1, { details: true })\n      return markers.length\n    }, 0)\n  })\n})\n\ndescribe('QuickPick', () => {\n  it('should not thrown when window not shown', async () => {\n    let q = new QuickPick(nvim)\n    q.items = undefined\n    expect(q.winid).toBeUndefined()\n    expect(q.activeItems).toEqual([])\n    q.title = 'title'\n    expect(q.title).toBe('title')\n    q.loading = true\n    expect(q.loading).toBe(true)\n    q.value = 'value'\n    expect(q.value).toBe('value')\n    expect(q.buffer).toBeUndefined()\n    expect(q.currIndex).toBe(0)\n    q.setCursor(0)\n    q.filterItems('a')\n    q.showFilteredItems()\n    q.toggePicked(0)\n    q.dispose()\n  })\n\n  it('should show picker items on filter', async () => {\n    let q = new QuickPick(nvim, {})\n    q.items = [{\n      label: 'foo',\n      picked: true\n    }, {\n      label: 'bar',\n      picked: true\n    }, {\n      label: 'asdf',\n      picked: false\n    }]\n    q.canSelectMany = true\n    await q.show()\n    await nvim.input('f')\n    await helper.waitValue(() => {\n      return q.activeItems.length\n    }, 2)\n    expect(q.value).toBe('f')\n    expect(q.selectedItems.length).toBe(2)\n    expect(q.inputBox).toBeDefined()\n    await nvim.input('<C-space>')\n    await helper.waitValue(() => {\n      return q.selectedItems.length\n    }, 1)\n    q.showFilteredItems()\n    await events.fire('BufWinLeave', [q.buffer.id])\n    q.dispose()\n  })\n})\n\ndescribe('showQuickPick', () => {\n  async function testQuickPick(items: Item[], canPickMany: boolean, cancel: boolean, res: any) {\n    let p = window.showQuickPick(items, { canPickMany })\n    await helper.waitFloat()\n    await nvim.input('b')\n    await nvim.input('<C-space>')\n    await helper.wait(50)\n    if (cancel) {\n      await nvim.input('<esc>')\n    } else {\n      await nvim.input('<cr>')\n    }\n    let result = await p\n    if (res == null) {\n      expect(result).toBe(res)\n    } else {\n      expect(res).toEqual(res)\n    }\n  }\n\n  it('should resolve for empty list', async () => {\n    let res = await window.showQuickPick([], { title: 'title' })\n    expect(res).toBeUndefined()\n  })\n\n  it('should resolve undefined when token cancelled', async () => {\n    let tokenSource = new CancellationTokenSource()\n    let token = tokenSource.token\n    tokenSource.cancel()\n    let res = await window.showQuickPick(['foo', 'bar'], undefined, token)\n    expect(res).toBeUndefined()\n    await helper.wait(20)\n    tokenSource = new CancellationTokenSource()\n    token = tokenSource.token\n    let p = window.showQuickPick(['foo', 'bar'], undefined, token)\n    tokenSource.cancel()\n    res = await p\n    expect(res).toBeUndefined()\n  })\n\n  it('should show quickfix with items or texts', async () => {\n    await testQuickPick(['foo', 'bar'], false, false, 'bar')\n    await testQuickPick(['foo', 'bar'], true, false, ['bar'])\n    await testQuickPick(['foo', 'bar'], false, true, undefined)\n    let items: QuickPickItem[] = [{ label: 'foo', description: 'desc' }, { label: 'bar', picked: true }]\n    await testQuickPick(items, false, false, { label: 'bar', picked: true })\n    await testQuickPick(items, true, false, [{ label: 'bar', picked: true }])\n  })\n\n  it('should use title option', async () => {\n    let p = window.showQuickPick(['foo', 'bar'], { title: 'title' })\n    await helper.waitFloat()\n    let line = await getTitleLine()\n    expect(line).toMatch('title')\n    await nvim.input('<esc>')\n    await p\n  })\n\n  it('should match on description', async () => {\n    let items: QuickPickItem[] = [{ label: 'foo', description: 'desc' }, { label: 'bar', picked: true }]\n    let p = window.showQuickPick(items, { matchOnDescription: true })\n    await helper.waitFloat()\n    await nvim.input('d')\n    await helper.wait(10)\n    await nvim.input('<cr>')\n    let res = await p\n    expect(res).toBeDefined()\n  })\n})\n\ndescribe('QuickPick configuration', () => {\n  afterEach(() => {\n    helper.workspace.configurations.reset()\n  })\n\n  it('should respect width of quickpick', async () => {\n    helper.updateConfiguration('dialog.maxWidth', null)\n    let quickpick = await window.createQuickPick()\n    disposables.push(quickpick)\n    quickpick.items = [{ label: 'foo' }, { label: 'bar' }]\n    quickpick.width = 50\n    quickpick.value = ''\n    await quickpick.show()\n    let win = nvim.createWindow(quickpick.winid)\n    let width = await win.width\n    expect(width).toBe(50)\n  })\n\n  it('should scroll by <C-f> and <C-b>', async () => {\n    helper.updateConfiguration('dialog.maxHeight', 2)\n    let quickpick = await window.createQuickPick()\n    quickpick.value = ''\n    quickpick.items = [{ label: 'one' }, { label: 'two' }, { label: 'three' }]\n    disposables.push(quickpick)\n    await quickpick.show()\n    let winid = quickpick.winid\n    await nvim.input('<C-f>')\n    await helper.wait(1)\n    await nvim.input('<C-f>')\n    await helper.waitValue(async () => {\n      let info = await nvim.call('getwininfo', [winid])\n      return info[0].topline\n    }, 2)\n    await nvim.input('<C-b>')\n    await nvim.input('<C-x>')\n    await helper.wait(1)\n    await nvim.input('<C-b>')\n    await helper.waitValue(async () => {\n      let info = await nvim.call('getwininfo', [winid])\n      return info[0].topline\n    }, 1)\n  })\n\n  it('should respect configurations', async () => {\n    helper.updateConfiguration('dialog.maxWidth', 30)\n    helper.updateConfiguration('dialog.rounded', false)\n    helper.updateConfiguration('dialog.floatHighlight', 'Normal')\n    helper.updateConfiguration('dialog.floatBorderHighlight', 'Normal')\n    helper.updateConfiguration('dialog.maxHeight', 2)\n    let quickpick = await window.createQuickPick()\n    quickpick.items = [{ label: 'one' }, { label: 'two' }, { label: 'three' }]\n    await quickpick.show()\n    let winids = await nvim.call('coc#float#get_float_win_list') as number[]\n    let winid = Math.max(...winids)\n    let win = nvim.createWindow(winid)\n    let h = await win.height\n    expect(h).toBe(2)\n    await nvim.input('<esc>')\n  })\n\n})\n\ndescribe('createQuickPick', () => {\n  it('should throw when unable to open input window', async () => {\n    let fn = nvim.call\n    nvim.call = (...args: any) => {\n      if (args[0] === 'coc#dialog#create_prompt_win') return undefined\n      return fn.apply(nvim, args)\n    }\n    disposables.push(Disposable.create(() => {\n      nvim.call = fn\n    }))\n    let fun = async () => {\n      let quickpick = await window.createQuickPick({\n        items: [{ label: 'foo' }, { label: 'bar' }],\n      })\n      await quickpick.show()\n    }\n    await expect(fun()).rejects.toThrow(/Unable to open/)\n  })\n\n  it('should throw when unable to open list window', async () => {\n    let fn = nvim.call\n    let spy = jest.spyOn(nvim, 'call').mockImplementation((...args: any) => {\n      if (args[0] === 'coc#dialog#create_list') return undefined\n      return fn.apply(nvim, args)\n    })\n    let fun = async () => {\n      let quickpick = await window.createQuickPick({\n        items: [{ label: 'foo' }, { label: 'bar' }],\n      })\n      disposables.push(quickpick)\n      await quickpick.show()\n    }\n    await expect(fun()).rejects.toThrow(/Unable to open/)\n    spy.mockRestore()\n    await nvim.call('feedkeys', [String.fromCharCode(27), 'in'])\n  })\n\n  it('should respect initial value', async () => {\n    let q = await window.createQuickPick()\n    q.items = [{ label: 'foo' }, { label: 'bar' }]\n    q.value = 'value'\n    await q.show()\n    let winids = await nvim.call('coc#float#get_float_win_list') as number[]\n    let winid = Math.min(...winids)\n    let buf = await (nvim.createWindow(winid)).buffer\n    let lines = await buf.lines\n    expect(lines[0]).toBe('value')\n    await nvim.input('<esc>')\n  })\n\n  it('should change current line by <C-j> and <C-k>', async () => {\n    let quickpick = await window.createQuickPick()\n    quickpick.items = [{ label: 'one'.repeat(30) }, { label: 'two' }, { label: 'three' }]\n    await quickpick.show()\n    disposables.push(quickpick)\n    let win = nvim.createWindow(quickpick.winid)\n    let height = await win.height\n    expect(height).toBe(4)\n    await nvim.input('<C-j>')\n    await helper.wait(1)\n    await nvim.input('<C-j>')\n    await helper.waitValue(() => {\n      return quickpick.currIndex\n    }, 2)\n    await nvim.input('<C-k>')\n    await helper.wait(1)\n    await nvim.input('<C-k>')\n    await helper.waitValue(() => {\n      return quickpick.currIndex\n    }, 0)\n  })\n\n  it('should toggle selected item by <C-space>', async () => {\n    let quickpick = await window.createQuickPick()\n    quickpick.items = [{ label: 'one' }, { label: 'two' }, { label: 'three' }]\n    await quickpick.show()\n    disposables.push(quickpick)\n    await nvim.input('<C-sapce>')\n    await helper.wait(10)\n    await nvim.input('<C-k>')\n    await helper.wait(10)\n    await nvim.input('<C-sapce>')\n    await helper.waitValue(() => {\n      return quickpick.selectedItems.length\n    }, 0)\n  })\n\n  it('should not handle events from other buffer', async () => {\n    let quickpick = await window.createQuickPick({\n      items: [{ label: 'one' }, { label: 'two' }, { label: 'three' }],\n    })\n    await quickpick.show()\n    disposables.push(quickpick)\n    await events.fire('BufWinLeave', [quickpick.buffer.id + 1])\n    await events.fire('PromptKeyPress', [quickpick.buffer.id + 1, 'C-f'])\n    expect(quickpick.currIndex).toBe(0)\n  })\n\n  it('should change title', async () => {\n    let quickpick = await window.createQuickPick()\n    quickpick.items = [{ label: 'one' }, { label: 'two' }]\n    quickpick.title = 'from'\n    disposables.push(quickpick)\n    quickpick.title = 'to'\n    expect(quickpick.title).toBe('to')\n    await quickpick.show()\n    let line = await getTitleLine()\n    expect(line).toMatch(/to/)\n  })\n\n  it('should change loading', async () => {\n    let quickpick = await window.createQuickPick()\n    quickpick.items = [{ label: 'one' }, { label: 'two' }]\n    disposables.push(quickpick)\n    await quickpick.show()\n    quickpick.loading = true\n    expect(quickpick.loading).toBe(true)\n    quickpick.loading = false\n    expect(quickpick.loading).toBe(false)\n  })\n\n  it('should change items', async () => {\n    let quickpick = await window.createQuickPick()\n    quickpick.items = [{ label: 'one' }, { label: 'two' }]\n    await quickpick.show()\n    disposables.push(quickpick)\n    quickpick.onDidChangeValue(val => {\n      if (val == '>') {\n        quickpick.items = [{ label: 'three' }]\n      }\n    })\n    await nvim.input('>')\n    await helper.waitValue(async () => {\n      let lines = await quickpick.buffer.lines\n      return lines\n    }, ['three'])\n  })\n\n  it('should change activeItems', async () => {\n    let quickpick = await window.createQuickPick<QuickPickItem>()\n    quickpick.items = [{ label: 'one' }]\n    disposables.push(quickpick)\n    await quickpick.show()\n    quickpick.onDidChangeValue(val => {\n      if (val == 'f') {\n        quickpick.activeItems = [{ label: 'foo', description: 'description' }, { label: 'foot' }, { label: 'bar' }]\n      }\n    })\n    await nvim.input('f')\n    await helper.waitValue(async () => {\n      let lines = await quickpick.buffer.lines\n      return lines\n    }, ['foo description', 'foot', 'bar'])\n  })\n\n  it('should check InputListSelect', async () => {\n    const createQuickPick = async () => {\n      let quickpick = await window.createQuickPick<QuickPickItem>()\n      quickpick.items = [{ label: 'one' }]\n      disposables.push(quickpick)\n      await quickpick.show()\n      return quickpick\n    }\n    for (let val of [-1, 1]) {\n      let quickpick = await createQuickPick()\n      let called = false\n      quickpick.onDidFinish(() => {\n        called = true\n      })\n      await events.fire('InputListSelect', [val])\n      await helper.waitValue(() => {\n        return called\n      }, true)\n    }\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/regions.test.ts",
    "content": "import Regions from '../../model/regions'\n\ndescribe('Regions', () => {\n  it('should add #1', async () => {\n    let r = new Regions()\n    r.add(1, 2)\n    r.add(2, 1)\n    expect(r.current).toEqual([1, 2])\n  })\n\n  it('should add #2', async () => {\n    let r = new Regions()\n    r.add(3, 4)\n    r.add(1, 5)\n    expect(r.current).toEqual([1, 5])\n  })\n\n  it('should add #3', async () => {\n    let r = new Regions()\n    r.add(2, 3)\n    r.add(1, 2)\n    expect(r.current).toEqual([1, 3])\n  })\n\n  it('should add #4', async () => {\n    let r = new Regions()\n    r.add(2, 5)\n    r.add(3, 4)\n    expect(r.current).toEqual([2, 5])\n  })\n\n  it('should add #5', async () => {\n    let r = new Regions()\n    r.add(3, 4)\n    r.add(1, 5)\n    expect(r.current).toEqual([1, 5])\n  })\n\n  it('should add #6', async () => {\n    let r = new Regions()\n    r.add(1, 2)\n    r.add(3, 5)\n    expect(r.current).toEqual([1, 5])\n    r.add(1, 8)\n    expect(r.current).toEqual([1, 8])\n  })\n\n  it('should add #7', async () => {\n    let r = new Regions()\n    r.add(1, 2)\n    r.add(1, 5)\n    expect(r.current).toEqual([1, 5])\n    r.add(9, 10)\n    r.add(5, 6)\n    expect(r.current).toEqual([1, 6, 9, 10])\n  })\n\n  it('should check range', async () => {\n    let r = new Regions()\n    r.add(1, 2)\n    r.add(1, 5)\n    expect(r.has(3, 5)).toBe(true)\n    expect(r.has(3, 6)).toBe(false)\n    r.add(6, 8)\n    expect(r.has(1, 8)).toBe(true)\n  })\n\n  it('should get range', async () => {\n    let r = new Regions()\n    r.add(1, 2)\n    r.add(1, 5)\n    expect(r.isEmpty).toBe(false)\n    expect(r.getRange(8)).toBeUndefined()\n    expect(r.getRange(9)).toBeUndefined()\n    expect(r.getRange(1)).toEqual([1, 5])\n    expect(r.getRange(5)).toEqual([1, 5])\n  })\n\n  it('should get uncovered range', async () => {\n    let r = new Regions()\n    expect(r.toUncoveredSpan([1, 2], 3, 10)).toEqual([0, 5])\n    r.add(0, 5)\n    expect(r.toUncoveredSpan([1, 2], 3, 10)).toBeUndefined()\n    r.add(8, 10)\n    expect(r.toUncoveredSpan([4, 6], 3, 20)).toEqual([5, 8])\n  })\n\n  it('should merge spans', async () => {\n    expect(Regions.mergeSpans([[0, 1], [1, 2]])).toEqual([[0, 2]])\n    expect(Regions.mergeSpans([[0, 1], [2, 3]])).toEqual([[0, 1], [2, 3]])\n    expect(Regions.mergeSpans([[2, 3], [0, 1]])).toEqual([[2, 3], [0, 1]])\n    expect(Regions.mergeSpans([[1, 4], [0, 5]])).toEqual([[0, 5]])\n    expect(Regions.mergeSpans([[1, 4], [2, 3]])).toEqual([[1, 4]])\n    expect(Regions.mergeSpans([[1, 2], [2, 3], [3, 4]])).toEqual([[1, 4]])\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/sandbox/log.js",
    "content": "const {wait, nvim} = require('coc.nvim')\nconsole.log('log')\nconsole.debug('debug')\nconsole.info('info')\nconsole.error('error')\nconsole.warn('warn')\nmodule.exports = () => {\n  return {wait, nvim}\n}\n"
  },
  {
    "path": "src/__tests__/modules/semanticTokensBuilder.test.ts",
    "content": "import { SemanticTokensBuilder } from '../../model/semanticTokensBuilder'\nimport { Range, SemanticTokensLegend } from 'vscode-languageserver-protocol'\n\nfunction toArr(uint32Arr: ReadonlyArray<number>): number[] {\n  const r = []\n  for (let i = 0, len = uint32Arr.length; i < len; i++) {\n    r[i] = uint32Arr[i]\n  }\n  return r\n}\n\nfunction deepStrictEqual(one: any, two: any): void {\n  expect(one).toEqual(two)\n}\n\ndescribe('SemanticTokensBuilder', () => {\n  it('should build SemanticTokensBuilder simple', () => {\n    const builder = new SemanticTokensBuilder()\n    builder.push(1, 0, 5, 1, 1)\n    builder.push(1, 10, 4, 2, 2)\n    builder.push(2, 2, 3, 2, 2)\n    deepStrictEqual(toArr(builder.build().data), [\n      1, 0, 5, 1, 1,\n      0, 10, 4, 2, 2,\n      1, 2, 3, 2, 2\n    ])\n  })\n\n  it('should throw for bad arguments', async () => {\n    const builder = new SemanticTokensBuilder()\n    expect(() => {\n      builder.push(undefined, undefined, undefined, undefined)\n    }).toThrow(Error)\n    expect(() => {\n      builder.push(Range.create(0, 0, 0, 3), '')\n    }).toThrow(Error)\n    Object.assign(builder, { _hasLegend: true })\n    expect(() => {\n      builder.push(Range.create(0, 0, 1, 3), '')\n    }).toThrow(Error)\n    expect(() => {\n      builder.push(Range.create(0, 0, 0, 3), '')\n    }).toThrow(Error)\n  })\n\n  it('should build SemanticTokensBuilder no modifier', () => {\n    const builder = new SemanticTokensBuilder()\n    builder.push(1, 0, 5, 1)\n    builder.push(1, 10, 4, 2)\n    builder.push(2, 2, 3, 2)\n    deepStrictEqual(toArr(builder.build().data), [\n      1, 0, 5, 1, 0,\n      0, 10, 4, 2, 0,\n      1, 2, 3, 2, 0\n    ])\n  })\n\n  it('should build SemanticTokensBuilder out of order 1', () => {\n    const builder = new SemanticTokensBuilder()\n    builder.push(2, 0, 5, 1, 1)\n    builder.push(2, 10, 1, 2, 2)\n    builder.push(2, 15, 2, 3, 3)\n    builder.push(1, 0, 4, 4, 4)\n    deepStrictEqual(toArr(builder.build().data), [\n      1, 0, 4, 4, 4,\n      1, 0, 5, 1, 1,\n      0, 10, 1, 2, 2,\n      0, 5, 2, 3, 3\n    ])\n  })\n\n  it('SemanticTokensBuilder out of order 2', () => {\n    const builder = new SemanticTokensBuilder()\n    builder.push(2, 10, 5, 1, 1)\n    builder.push(2, 2, 4, 2, 2)\n    deepStrictEqual(toArr(builder.build().data), [\n      2, 2, 4, 2, 2,\n      0, 8, 5, 1, 1\n    ])\n  })\n\n  test('SemanticTokensBuilder with legend', () => {\n    const legend: SemanticTokensLegend = {\n      tokenTypes: ['aType', 'bType', 'cType', 'dType'],\n      tokenModifiers: ['mod0', 'mod1', 'mod2', 'mod3', 'mod4', 'mod5']\n    }\n    const builder = new SemanticTokensBuilder(legend)\n    builder.push(Range.create(1, 0, 1, 5), 'bType')\n    builder.push(Range.create(2, 0, 2, 4), 'cType', ['mod0', 'mod5'])\n    builder.push(Range.create(3, 0, 3, 3), 'dType', ['mod2', 'mod4'])\n    deepStrictEqual(toArr(builder.build().data), [\n      1, 0, 5, 1, 0,\n      1, 0, 4, 2, 1 | (1 << 5),\n      1, 0, 3, 3, (1 << 2) | (1 << 4)\n    ])\n    expect(() => {\n      builder.push(Range.create(3, 0, 3, 3), 'dType', ['mod2', 'mod4', 'mod10'])\n    }).toThrow(Error)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/server.js",
    "content": "\"use strict\"\nObject.defineProperty(exports, \"__esModule\", {value: true})\nconst node_1 = require(\"vscode-languageserver/node\")\nconst connection = (0, node_1.createConnection)()\nlet notified = false\nconnection.onInitialize((_params) => {\n  return {\n    capabilities: {}\n  }\n})\n\nconnection.onRequest('request', (param) => {\n  return param.value + 1\n})\n\nconnection.onNotification('notification', () => {\n  notified = true\n})\n\nconnection.onRequest('notified', () => {\n  return {notified}\n})\n\nconnection.onRequest('triggerRequest', async () => {\n  await connection.sendRequest('request')\n})\n\nconnection.onNotification('triggerNotification', async () => {\n  await connection.sendNotification('notification', {x: 1})\n})\n\nconnection.listen()\n"
  },
  {
    "path": "src/__tests__/modules/services.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport fs from 'fs'\nimport net from 'net'\nimport os from 'os'\nimport path from 'path'\nimport { v4 as uuid } from 'uuid'\nimport { Disposable } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { URI } from 'vscode-uri'\nimport { LanguageClient, RevealOutputChannelOn, ServerOptions, State, TransportKind } from '../../language-client'\nimport services, { convertState, documentSelectorToLanguageIds, getDocumentSelector, getForkOptions, getLanguageServerOptions, getRevealOutputChannelOn, getSpawnOptions, getStateName, getTransportKind, isValidServerConfig, LanguageServerConfig, ServiceStat, stateString } from '../../services'\nimport { disposeAll } from '../../util'\nimport { Workspace } from '../../workspace'\nimport events from '../../events'\nimport helper from '../helper'\nimport window from '../../window'\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nlet workspace: Workspace\nconst serverModule = path.join(__dirname, 'server.js')\nbeforeAll(async () => {\n  await helper.setup()\n  workspace = helper.workspace\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n})\n\nfunction toConfig(c: Partial<LanguageServerConfig>): LanguageServerConfig {\n  if (!c.filetypes) {\n    c.filetypes = ['vim']\n  }\n  return c as LanguageServerConfig\n}\n\ndescribe('services', () => {\n  describe('functions', () => {\n    it('should convertState', async () => {\n      expect(convertState(null as any)).toBeUndefined()\n    })\n\n    it('should check valid server config', async () => {\n      expect(isValidServerConfig('name', {} as any)).toBe(false)\n      expect(isValidServerConfig('name', { module: [] } as any)).toBe(false)\n      expect(isValidServerConfig('name', { command: [] } as any)).toBe(false)\n      expect(isValidServerConfig('name', { transport: '' } as any)).toBe(false)\n      expect(isValidServerConfig('name', { transportPort: 'ab' } as any)).toBe(false)\n      expect(isValidServerConfig('name', { filetypes: '' } as any)).toBe(false)\n      expect(isValidServerConfig('name', { additionalSchemes: '' } as any)).toBe(false)\n      expect(isValidServerConfig('name', { additionalSchemes: [1] } as any)).toBe(false)\n      expect(isValidServerConfig('name', { module: 'module', filetypes: ['vim'] } as any)).toBe(true)\n    })\n\n    it('should get state name', async () => {\n      expect(getStateName(ServiceStat.Initial)).toBe('init')\n      expect(getStateName(ServiceStat.Running)).toBe('running')\n      expect(getStateName(ServiceStat.Starting)).toBe('starting')\n      expect(getStateName(ServiceStat.StartFailed)).toBe('startFailed')\n      expect(getStateName(ServiceStat.Stopping)).toBe('stopping')\n      expect(getStateName(ServiceStat.Stopped)).toBe('stopped')\n      expect(getStateName(null as any)).toBe('unknown')\n    })\n\n    it('should use languageserver config from workspace folder', async () => {\n      let folder = path.join(os.tmpdir(), uuid())\n      fs.mkdirSync(path.join(folder, '.vim'), { recursive: true })\n      let configFile = path.join(folder, '.vim/coc-settings.json')\n      fs.writeFileSync(configFile, '{\"languageserver\": {\"foo\": {\"command\":\"bar\", \"filetypes\": [\"vim\"]}, \"bar\": {}}}')\n      let uri = URI.file(path.join(folder, 't')).toString()\n      let added = workspace.configurations.locateFolderConfigution(uri)\n      expect(added).toBe(true)\n      let w = workspace.workspaceFolderControl\n      w.addWorkspaceFolder(folder, true)\n      let s = services.getService('foo')\n      let spy = jest.spyOn(window as any, 'showErrorMessage').mockImplementation(() => {\n        return Promise.resolve()\n      })\n      expect(s).toBeDefined()\n      await s.restart()\n      spy.mockRestore()\n      w.removeWorkspaceFolder(folder)\n    })\n\n    it('should get stateString', async () => {\n      expect(stateString(State.Stopped)).toBe('stopped')\n      expect(stateString(State.Running)).toBe('running')\n      expect(stateString(State.Starting)).toBe('starting')\n      expect(stateString(null as any)).toBe('unknown')\n    })\n\n    it('should getSpawnOptions', async () => {\n      expect(getSpawnOptions(toConfig({ cwd: process.cwd() }))).toBeDefined()\n      expect(getSpawnOptions(toConfig({ cwd: process.cwd(), detached: true, shell: true, env: {} }))).toBeDefined()\n    })\n\n    it('should getForkOptions', async () => {\n      expect(getForkOptions(toConfig({ cwd: process.cwd() }))).toBeDefined()\n      expect(getForkOptions(toConfig({ cwd: process.cwd(), execArgv: [], env: {} }))).toBeDefined()\n    })\n\n    it('should getTransportKind', async () => {\n      expect(getTransportKind(toConfig({}))).toBe(TransportKind.ipc)\n      expect(getTransportKind(toConfig({ transport: 'ipc' }))).toBe(TransportKind.ipc)\n      expect(getTransportKind(toConfig({ transport: 'stdio' }))).toBe(TransportKind.stdio)\n      expect(getTransportKind(toConfig({ transport: 'pipe' }))).toBe(TransportKind.pipe)\n      expect(getTransportKind(toConfig({ transport: 'socket', transportPort: 3300 }))).toEqual({ kind: TransportKind.socket, port: 3300 })\n    })\n\n    it('should getDocumentSelector', async () => {\n      expect(getDocumentSelector(undefined, [])).toEqual([{ scheme: 'file' }, { scheme: 'untitled' }])\n      expect(getDocumentSelector(['vim'], []).length).toBe(2)\n    })\n\n    it('should getRevealOutputChannelOn', async () => {\n      expect(getRevealOutputChannelOn('error')).toBe(RevealOutputChannelOn.Error)\n      expect(getRevealOutputChannelOn('info')).toBe(RevealOutputChannelOn.Info)\n      expect(getRevealOutputChannelOn('warn')).toBe(RevealOutputChannelOn.Warn)\n      expect(getRevealOutputChannelOn('never')).toBe(RevealOutputChannelOn.Never)\n      expect(getRevealOutputChannelOn('')).toBe(RevealOutputChannelOn.Never)\n    })\n\n    it('should getLanguageServerOptions', async () => {\n      expect(getLanguageServerOptions('x', 'y', {} as any)).toBe(null)\n      expect(getLanguageServerOptions('x', 'y', { filetypes: ['vim'] })).toBe(null)\n      expect(getLanguageServerOptions('x', 'y', toConfig({ module: 'not_exists' }))).toBe(null)\n      expect(getLanguageServerOptions('x', 'y', toConfig({ module: __filename, maxRestartCount: 1 }))).toBeDefined()\n      expect(getLanguageServerOptions('x', 'y', toConfig({ module: __filename, runtime: process.execPath }))).toBeDefined()\n      expect(getLanguageServerOptions('x', 'y', toConfig({ command: 'cmd', args: [], disableWorkspaceFolders: true, disableSnippetCompletion: true } as any))).toBeDefined()\n      expect(getLanguageServerOptions('x', 'y', toConfig({ command: 'cmd', ignoredRootPaths: ['/foo'], initializationOptions: {} }))).toBeDefined()\n    })\n\n    it('should use socket port for language server #1', async () => {\n      let opts = getLanguageServerOptions('x', 'y', toConfig({ port: 3300, host: '127.0.0.1' }))\n      let fn = opts[1] as Function\n      await expect(fn()).rejects.toThrow(Error)\n    })\n\n    it('should use socket port for language server #2', async () => {\n      let connected = false\n      let s\n      let server = net.createServer(socket => {\n        connected = true\n        s = socket\n      })\n      server.listen(12580, '127.0.0.1')\n      let opts = getLanguageServerOptions('x', 'y', toConfig({ port: 12580 }))\n      let fn = opts[1] as Function\n      let res = await fn()\n      await helper.wait(30)\n      expect(res).toBeDefined()\n      expect(connected).toBe(true)\n      s.destroy()\n      server.close()\n    })\n\n    it('should documentSelectorToLanguageIds', async () => {\n      expect(documentSelectorToLanguageIds(['vim'])).toEqual(['vim'])\n    })\n  })\n\n  describe('getServiceStats()', () => {\n    it('should get services', async () => {\n      let res = await helper.doAction('services')\n      expect(res).toBeDefined()\n    })\n  })\n\n  describe('toggle()', () => {\n    it('should throw when service not found ', async () => {\n      let fn = async () => {\n        await helper.doAction('toggleService', 'id')\n      }\n      await expect(fn()).rejects.toThrow(Error)\n    })\n\n    it('should toggle language client state', async () => {\n      const serverOptions: ServerOptions = {\n        module: serverModule,\n        transport: TransportKind.ipc,\n      }\n      const client = new LanguageClient('test', 'Test Language Server', serverOptions, {\n        documentSelector: [{ language: 'vim', scheme: 'file' }]\n      })\n      let d = services.registerLanguageClient(client)\n      disposables.push(d)\n      let p = services.toggle('test')\n      void services.toggle('test')\n      await p\n      let s = services.getService('test')\n      expect(s.state).toBe(ServiceStat.Running)\n      d.dispose()\n    })\n  })\n\n  describe('start()', () => {\n    it('should delay start when not plugin not ready', async () => {\n      Object.assign(events, { _ready: false })\n      let called = false\n      services.tryStartService({\n        id: 'test',\n        start: () => {\n          called = true\n        }\n      } as any)\n      let started = false\n      services.tryStartService({\n        id: 'test',\n        state: ServiceStat.Initial,\n        selector: [{ language: '*' }],\n        start: () => {\n          started = true\n        }\n      } as any)\n\n      await events.fire('ready', [])\n      expect(called).toBe(false)\n      expect(started).toBe(true)\n    })\n\n    it('should start language client on by document', async () => {\n      const serverOptions: ServerOptions = {\n        module: serverModule,\n        transport: TransportKind.ipc,\n      }\n      const client = new LanguageClient('test', 'Test Language Server', serverOptions, {\n        documentSelector: [{ language: 'vim', scheme: 'file' }]\n      })\n      disposables.push(services.registerLanguageClient(client))\n      let document = TextDocument.create('file:///1', 'vim', 1, '')\n      await services.start(document)\n      await services.start(TextDocument.create('file:///2', 'java', 1, ''))\n      let s = services.getService('test')\n      expect(s.state).toBe(ServiceStat.Running)\n      let code = `call coc#on_notify('test', 'notification', { -> execute('let g:called = 1')})`\n      await nvim.exec(code)\n      await helper.doAction('registerNotification', 'test', 'notification')\n      await client.sendNotification('triggerNotification')\n      await helper.waitValue(() => {\n        return nvim.getVar('called')\n      }, 1)\n    })\n  })\n\n  describe('stop()', () => {\n    it('should not throw when service not found ', async () => {\n      await services.stop('id')\n    })\n  })\n\n  describe('shouldStart()', () => {\n    it('should start when document matches', async () => {\n      await helper.edit('t.vim')\n      const serverOptions: ServerOptions = {\n        module: serverModule,\n        transport: TransportKind.ipc,\n      }\n      const client = new LanguageClient('test', 'Test Language Server', serverOptions, {\n        documentSelector: [{ language: 'vim', scheme: 'file' }]\n      })\n      disposables.push(services.registerLanguageClient(client))\n      services.register({ id: 'test' } as any)\n      await helper.waitValue(() => {\n        return client.state\n      }, State.Running)\n      await nvim.command('bd!')\n    })\n\n    it('should not start when client already started', async () => {\n      await helper.edit('t.vim')\n      const serverOptions: ServerOptions = {\n        module: serverModule,\n        transport: TransportKind.ipc,\n      }\n      const client = new LanguageClient('test', 'Test Language Server', serverOptions, {\n        documentSelector: [{ language: 'vim', scheme: 'file' }]\n      })\n      await client.start()\n      disposables.push(services.registerLanguageClient(client))\n      await nvim.command('bd!')\n    })\n  })\n\n  describe('registerLanguageClient', () => {\n\n    it('should not create client when not enabled', async () => {\n      workspace.configurations.updateMemoryConfig({\n        languageserver: {\n          test: {\n            filetypes: ['vim'],\n            enabled: false\n          }\n        }\n      })\n      disposables.push(services.registerLanguageClient('test', { filetypes: ['vim'], enable: true }))\n      let client = services.getService('test')\n      expect(client).toBeDefined()\n      await client.start()\n      expect(client.state).toBe(ServiceStat.Initial)\n    })\n\n    it('should not start for bad config', async () => {\n      workspace.configurations.updateMemoryConfig({\n        languageserver: {\n          test: {\n            filetypes: ['vim']\n          }\n        }\n      })\n      disposables.push(services.registerLanguageClient('test', { filetypes: ['vim'], enable: true }))\n      let client = services.getService('test')\n      expect(client).toBeDefined()\n      await client.start()\n      expect(client.state).toBe(ServiceStat.Initial)\n    })\n\n    it('should start and stop language client', async () => {\n      let config = { filetypes: ['vim'], module: serverModule, enabled: false }\n      workspace.configurations.updateMemoryConfig({\n        languageserver: { test: config }\n      })\n      disposables.push(services.registerLanguageClient('test', config))\n      disposables.push(services.registerLanguageClient('test', config))\n      let client = services.getService('test')\n      let p = client.start()\n      void client.start()\n      await p\n      await client.start()\n      await client.restart()\n      let pro = client.stop()\n      void client.stop()\n      await pro\n      expect(client.state).toBe(ServiceStat.Stopped)\n    })\n\n    it('should start language client by restart', async () => {\n      let config = { filetypes: ['vim'], module: serverModule, enabled: false }\n      workspace.configurations.updateMemoryConfig({\n        languageserver: { test: config }\n      })\n      disposables.push(services.registerLanguageClient('test', config))\n      let client = services.getService('test')\n      await client.restart()\n      expect(client.state).toBe(ServiceStat.Running)\n    })\n\n    it('should not throw on start error', async () => {\n      const serverOptions: ServerOptions = {\n        module: serverModule,\n        transport: TransportKind.ipc,\n      }\n      const client = new LanguageClient('test', 'Test Language Server', serverOptions, {})\n      let spy = jest.spyOn(client, 'start').mockImplementation(() => {\n        throw new Error('custom error')\n      })\n      disposables.push(services.registerLanguageClient(client))\n      let service = services.getService('test')\n      await service.start()\n      spy.mockRestore()\n      let line = await helper.getCmdline()\n      expect(line).toMatch('failed to start')\n    })\n\n    it('should sendRequest & sendNotification', async () => {\n      const serverOptions: ServerOptions = {\n        module: serverModule,\n        transport: TransportKind.ipc,\n      }\n      const client = new LanguageClient('test', 'Test Language Server', serverOptions, {})\n      disposables.push(services.registerLanguageClient(client))\n      let service = services.getService('test')\n      await service.start()\n      let res = await helper.plugin.cocAction('sendRequest', 'test', 'request', { value: 2 })\n      expect(res).toBe(3)\n      await helper.plugin.cocAction('sendNotification', 'test', 'notification', {})\n      let result = await service.client.sendRequest('notified')\n      expect(result).toEqual({ notified: true })\n    })\n\n    it('should throw when service not found', async () => {\n      let fn = async () => {\n        await services.sendNotification('id', 'method')\n      }\n      await expect(fn()).rejects.toThrow(Error)\n    })\n\n    it('should register notification when client created', async () => {\n      const serverOptions: ServerOptions = {\n        module: serverModule,\n        transport: TransportKind.ipc,\n      }\n      const client = new LanguageClient('test', 'Test Language Server', serverOptions, {})\n      services.registerLanguageClient(client)\n      let service = services.getService('test')\n      await helper.plugin.cocAction('registerNotification', 'test', 'notification')\n      await service.start()\n      await service.client.sendNotification('triggerNotification')\n      await helper.wait(10)\n      await services.stop('test')\n    })\n\n    it('should register notification when client not created', async () => {\n      await helper.plugin.cocAction('registerNotification', 'def', 'notification')\n      workspace.configurations.updateMemoryConfig({\n        languageserver: {\n          def: {\n            filetypes: ['vim'],\n            module: serverModule,\n          }\n        }\n      })\n      services.registerLanguageClient('def', { filetypes: ['.vim'], module: serverModule }, URI.file(__dirname))\n      let res\n      let spy = jest.spyOn(services, 'sendNotificationVim' as any).mockImplementation((id, method, result) => {\n        res = { id, method, result }\n      })\n      let service = services.getService('def')\n      await service.start()\n      await service.client.sendNotification('triggerNotification')\n      await helper.waitValue(() => {\n        return res != undefined\n      }, true)\n      await services.stop('def')\n      spy.mockRestore()\n      expect(res).toEqual({ id: 'def', method: 'notification', result: { x: 1 } })\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/sources.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport path from 'path'\nimport { Disposable } from 'vscode-languageserver-protocol'\nimport sources from '../../completion/sources'\nimport { ISource, SourceType } from '../../completion/types'\nimport events from '../../events'\nimport { disposeAll } from '../../util'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n})\n\ndescribe('sources', () => {\n  it('should check commit', () => {\n    expect(sources.shouldCommit(undefined, undefined, '')).toBe(false)\n    let source = sources.getSource('$words')\n    expect(sources.shouldCommit(source, { word: '' }, '.')).toBe(false)\n    expect(sources.shouldCommit(source, { word: '' }, '')).toBe(false)\n  })\n\n  it('should get normal sources', () => {\n    sources.createSource({\n      name: 'name',\n      documentSelector: [{ language: 'vim' }],\n      doComplete: () => null\n    })\n    let arr = sources.getNormalSources('', 'test:///1')\n    let res = arr.find(o => o.name === 'name')\n    expect(res).toBeUndefined()\n    sources.createSource({\n      name: 'name',\n      documentSelector: [{ language: '*' }],\n      doComplete: () => null\n    })\n    arr = sources.getNormalSources('x', 'test:///1')\n    res = arr.find(o => o.name === 'name')\n    expect(res).toBeDefined()\n  })\n\n  it('should get trigger sources', () => {\n    let res = sources.getTriggerSources('', 'vim', 'test:///1')\n    expect(res).toEqual([])\n    let arr = ['around', 'buffer', 'file']\n    res = sources.getTriggerSources('', 'vim', 'test:///1', arr)\n    let find = res.find(o => arr.includes(o.name))\n    expect(find).toBeUndefined()\n    sources.createSource({\n      name: 'name',\n      documentSelector: [{ language: 'vim' }],\n      doComplete: () => null\n    })\n    helper.updateConfiguration('coc.source.name.triggerCharacters', ['.'])\n    res = sources.getTriggerSources('.', 'vim', 'test:///1', arr)\n    find = res.find(o => o.name === 'name')\n    expect(find).toBeDefined()\n    res = sources.getTriggerSources('.', 'txt', 'test:///1', arr)\n    find = res.find(o => o.name === 'name')\n    expect(find).toBeUndefined()\n  })\n\n  it('should do document enter', async () => {\n    let fn = jest.fn()\n    let source: ISource = {\n      name: 'enter',\n      enable: true,\n      priority: 0,\n      sourceType: SourceType.Service,\n      triggerCharacters: [],\n      doComplete: () => Promise.resolve({ items: [] }),\n      onEnter: fn\n    }\n    disposables.push(sources.addSource(source))\n    let buffer = await nvim.buffer\n    await events.fire('BufEnter', [buffer.id])\n    expect(fn).toHaveBeenCalled()\n  })\n\n  it('should get sources by split filetypes', async () => {\n    disposables.push(sources.addSource({\n      name: 'foo',\n      filetypes: ['foo'],\n      enable: true,\n      doComplete: () => Promise.resolve({ items: [] }),\n    }))\n    disposables.push(sources.addSource({\n      name: 'bar',\n      filetypes: ['bar'],\n      enable: true,\n      doComplete: () => Promise.resolve({ items: [] }),\n    }))\n    let arr = sources.getNormalSources('foo.bar', 'file:///a')\n    let names = arr.map(s => s.name)\n    expect(names.includes('foo')).toBe(true)\n    expect(names.includes('bar')).toBe(true)\n  })\n\n  it('should return source states', async () => {\n    disposables.push(sources.addSource({\n      name: 'foo',\n      documentSelector: ['vim'],\n      enable: true,\n      doComplete: () => Promise.resolve({ items: [] }),\n    }))\n    let stats = await helper.doAction('sourceStat')\n    expect(stats.length > 1).toBe(true)\n  })\n\n  it('should toggle source state', async () => {\n    await helper.doAction('toggleSource', 'around')\n    let s = sources.getSource('around')\n    expect(s.enable).toBe(false)\n    sources.toggleSource('around')\n  })\n})\n\ndescribe('sources#has', () => {\n\n  it('should has source', () => {\n    expect(sources.has('around')).toBe(true)\n  })\n\n  it('should not has source', () => {\n    expect(sources.has('NotExists')).toBe(false)\n  })\n})\n\ndescribe('sources#refresh', () => {\n  it('should refresh if possible', async () => {\n    let fn = jest.fn()\n    let source: ISource = {\n      name: 'refresh',\n      enable: true,\n      priority: 0,\n      sourceType: SourceType.Service,\n      triggerCharacters: [],\n      doComplete: () => Promise.resolve({ items: [] }),\n      refresh: fn\n    }\n    disposables.push(sources.addSource(source))\n    await helper.doAction('refreshSource', 'refresh')\n    expect(fn).toHaveBeenCalled()\n  })\n\n  it('should work if refresh not defined', async () => {\n    let source: ISource = {\n      name: 'refresh',\n      enable: true,\n      priority: 0,\n      sourceType: SourceType.Service,\n      triggerCharacters: [],\n      doComplete: () => Promise.resolve({ items: [] })\n    }\n    disposables.push(sources.addSource(source))\n    await sources.refresh('refresh')\n  })\n})\n\ndescribe('sources#createSource', () => {\n  it('should throw on create source', async () => {\n    expect(() => {\n      sources.createSource({\n        doComplete: () => Promise.resolve({\n          items: [{\n            word: 'custom'\n          }]\n        })\n      } as any)\n    }).toThrow()\n  })\n\n  it('should create vim source', async () => {\n    let folder = path.resolve(__dirname, '..')\n    await nvim.command(`set runtimepath+=${folder}`)\n    disposables.push({\n      dispose: () => {\n        sources.removeSource('email')\n      }\n    })\n    await helper.waitValue(() => {\n      return sources.has('email')\n    }, true)\n    await helper.createDocument()\n    await nvim.input('i@')\n    await helper.visible('foo@gmail.com')\n  })\n})\n\ndescribe('sources#getTriggerSources()', () => {\n  it('should filter by filetypes', async () => {\n    let source: ISource = {\n      name: 'test',\n      enable: true,\n      priority: 0,\n      filetypes: ['javascript'],\n      sourceType: SourceType.Service,\n      triggerCharacters: ['#'],\n      doComplete: () => Promise.resolve({ items: [] })\n    }\n    disposables.push(sources.addSource(source))\n    let res = sources.getTriggerSources('#', 'javascript', 'file:///tmp.js')\n    expect(res.find(o => o.name == 'test')).toBeDefined()\n  })\n\n  it('should filter by documentSelector', async () => {\n    let source: ISource = {\n      name: 'test',\n      enable: true,\n      priority: 0,\n      documentSelector: [{ language: 'javascript' }],\n      sourceType: SourceType.Service,\n      triggerCharacters: ['#'],\n      doComplete: () => Promise.resolve({ items: [] })\n    }\n    disposables.push(sources.addSource(source))\n    let res = sources.getTriggerSources('#', 'javascript', 'file:///tmp.js')\n    expect(res.find(o => o.name == 'test')).toBeDefined()\n  })\n\n  it('should filter disabled sources', async () => {\n    await nvim.setLine('foo bar ')\n    let buf = await nvim.buffer\n    await buf.setVar('coc_disabled_sources', ['around', 'buffer', 'file'])\n    await nvim.input('Af')\n    await helper.wait(30)\n    await nvim.input('/')\n    await helper.wait(100)\n    let visible = await nvim.call('pumvisible')\n    expect(visible).toBe(0)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/strWidth.test.ts",
    "content": "import { initStrWidthWasm, StrWidth, StrWidthWasi } from '../../model/strwidth'\n\nlet api: StrWidthWasi\nbeforeAll(async () => {\n  api = await initStrWidthWasm()\n})\n\ndescribe('strWidth', () => {\n  it('should get display width', async () => {\n    let sw = new StrWidth(api)\n    sw.setAmbw(true)\n    expect(sw.getWidth('')).toBe(0)\n    expect(sw.getWidth('foo')).toBe(3)\n    expect(sw.getWidth('嘻嘻')).toBe(4)\n  })\n\n  it('should slice when content too long', async () => {\n    let sw = new StrWidth(api)\n    expect(sw.getWidth('p'.repeat(8192))).toBe(4095)\n  })\n\n  it('should use cache', async () => {\n    let sw = new StrWidth(api)\n    expect(sw.getWidth(' ', true)).toBe(1)\n    expect(sw.getWidth(' ', true)).toBe(1)\n    expect(sw.getWidth(' ', true)).toBe(1)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/task.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { Disposable } from 'vscode-languageserver-protocol'\nimport { disposeAll } from '../../util'\nimport workspace from '../../workspace'\nimport helper, { createTmpFile } from '../helper'\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterEach(() => {\n  disposeAll(disposables)\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\ndescribe('task test', () => {\n  it('should start task', async () => {\n    let task = workspace.createTask('sleep')\n    disposables.push(task)\n    let started = await task.start({ cmd: 'sleep', args: ['50'] })\n    expect(started).toBe(true)\n  })\n\n  it('should stop task', async () => {\n    let task = workspace.createTask('sleep')\n    disposables.push(task)\n    await task.start({ cmd: 'sleep', args: ['50'] })\n    await task.stop()\n    let running = await task.running\n    expect(running).toBe(false)\n  })\n\n  it('should emit exit event', async () => {\n    let fn = jest.fn()\n    let task = workspace.createTask('sleep')\n    disposables.push(task)\n    task.onExit(fn)\n    await task.start({ cmd: 'sleep', args: ['50'] })\n    await helper.wait(10)\n    await task.stop()\n    expect(fn).toHaveBeenCalled()\n  })\n\n  it('should emit stdout event', async () => {\n    let file = await createTmpFile('echo foo')\n    let task = workspace.createTask('echo')\n    disposables.push(task)\n    let p = new Promise<string[]>(resolve => {\n      let lines: string[] = []\n      task.onStdout(stdout => {\n        lines.push(...stdout)\n      })\n      task.onExit(() => {\n        resolve(lines)\n      })\n    })\n    await task.start({ cmd: '/bin/sh', args: [file] })\n    let lines = await p\n    expect(lines).toEqual(['foo'])\n  })\n\n  it('should change environment variables', async () => {\n    let file = await createTmpFile('echo $NODE_ENV\\necho $COC_NVIM_TEST')\n    let task = workspace.createTask('ENV')\n    disposables.push(task)\n    let lines: string[] = []\n    task.onStdout(arr => {\n      lines.push(...arr)\n    })\n    let p = new Promise<void>(resolve => {\n      task.onExit(() => {\n        resolve()\n      })\n    })\n    await task.start({\n      cmd: '/bin/sh',\n      args: [file],\n      env: {\n        NODE_ENV: 'production',\n        COC_NVIM_TEST: 'yes'\n      }\n    })\n    await p\n    expect(lines).toEqual(['production', 'yes'])\n    let res = await nvim.call('getenv', 'COC_NVIM_TEST')\n    expect(res).toBeNull()\n  })\n\n  it('should receive stdout lines as expected', async () => {\n    let file = await createTmpFile('echo 3\\necho \"\"\\necho 4')\n    let task = workspace.createTask('ENV')\n    let p = new Promise(resolve => {\n      let lines: string[] = []\n      task.onStdout(arr => {\n        lines.push(...arr)\n      })\n      task.onExit(() => {\n        resolve(lines)\n      })\n    })\n    await task.start({ cmd: '/bin/sh', args: [file] })\n    let lines = await p\n    expect(lines).toEqual(['3', '', '4'])\n    task.dispose()\n  })\n\n  it('should emit stderr event', async () => {\n    let file = await createTmpFile('console.error(\"start\\\\n\\\\nend\");')\n    let task = workspace.createTask('error')\n    disposables.push(task)\n    let p = new Promise<string[]>(resolve => {\n      let lines: string[] = []\n      task.onStderr(arr => {\n        lines.push(...arr)\n      })\n      task.onExit(() => {\n        resolve(lines)\n      })\n    })\n    await task.start({ cmd: 'node', args: [file] })\n    let lines = await p\n    expect(lines).toEqual(['start', '', 'end'])\n  })\n\n  it('should not receive event from other task', async () => {\n    let task1 = workspace.createTask('one')\n    disposables.push(task1)\n    let count = 0\n    let cb = () => {\n      count++\n    }\n    task1.onExit(cb)\n    task1.onStderr(cb)\n    task1.onStdout(cb)\n    let file = await createTmpFile('console.log(\"start\");console.error(\"end\");')\n    let task = workspace.createTask('error')\n    await task.start({ cmd: 'node', args: [file] })\n    let promise = new Promise<void>(resolve => {\n      task.onExit(() => {\n        resolve(undefined)\n      })\n    })\n    await promise\n    expect(count).toBe(0)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/terminal.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport helper from '../helper'\nimport { TerminalModel } from '../../model/terminal'\n\nlet nvim: Neovim\nlet terminal: TerminalModel\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  terminal = new TerminalModel('sh', [], nvim)\n  await terminal.start(__dirname, { COC_TERMINAL: `option '-term'` })\n})\n\nafterAll(async () => {\n  terminal.dispose()\n  await helper.shutdown()\n})\n\ndescribe('terminal properties', () => {\n  it('should get name', () => {\n    let name = terminal.name\n    expect(name).toBe('sh')\n  })\n\n  it('should have correct cwd and env', async () => {\n    let bufnr = terminal.bufnr\n    terminal.sendText('echo $PWD')\n    await helper.wait(300)\n    let lines = await nvim.call('getbufline', [bufnr, 1, '$']) as string[]\n    expect(lines[0].trim().length).toBeGreaterThan(0)\n    terminal.sendText('echo $COC_TERMINAL')\n    await helper.wait(300)\n    lines = await nvim.call('getbufline', [bufnr, 1, '$']) as string[]\n    expect(lines.includes(`option '-term'`)).toBe(true)\n    terminal.onExit(-1)\n  })\n\n  it('should get pid', async () => {\n    let pid = await terminal.processId\n    expect(typeof pid).toBe('number')\n  })\n\n  it('should hide terminal window', async () => {\n    await terminal.hide()\n    let winnr = await nvim.call('bufwinnr', terminal.bufnr)\n    expect(winnr).toBe(-1)\n  })\n\n  it('should show terminal window', async () => {\n    await terminal.show()\n    let winnr = await nvim.call('bufwinnr', terminal.bufnr)\n    expect(winnr != -1).toBe(true)\n  })\n\n  it('should  not throw when not shown', async () => {\n    let terminal = new TerminalModel('sh', [], nvim)\n    terminal.sendText('text')\n    await terminal.start(__dirname, {})\n    await terminal.show()\n    await terminal.show()\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/util.test.ts",
    "content": "import style from 'ansi-styles'\nimport * as assert from 'assert'\nimport cp, { spawn } from 'child_process'\nimport fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport { v4 as uuid } from 'uuid'\nimport vm from 'vm'\nimport { AnnotatedTextEdit, CancellationToken, CancellationTokenSource, ChangeAnnotation, Color, Position, Range, SymbolKind, TextDocumentEdit, TextEdit, WorkspaceEdit } from 'vscode-languageserver-protocol'\nimport { ConfigurationScope } from '../../configuration/types'\nimport { LinesTextDocument } from '../../model/textdocument'\nimport { DocumentChange } from '../../types'\nimport { concurrent, delay, disposeAll, wait, waitWithToken } from '../../util'\nimport { ansiparse, parseAnsiHighlights } from '../../util/ansiparse'\nimport * as arrays from '../../util/array'\nimport { filter, forEach, map, YieldOptions } from '../../util/async'\nimport * as color from '../../util/color'\nimport { pluginRoot } from '../../util/constants'\nimport { getSymbolKind } from '../../util/convert'\nimport * as diff from '../../util/diff'\nimport * as errors from '../../util/errors'\nimport * as extension from '../../util/extensionRegistry'\nimport * as factory from '../../util/factory'\nimport * as fuzzy from '../../util/fuzzy'\nimport * as Is from '../../util/is'\nimport { Extensions, IJSONContributionRegistry } from '../../util/jsonRegistry'\nimport * as lodash from '../../util/lodash'\nimport { Mutex } from '../../util/mutex'\nimport * as numbers from '../../util/numbers'\nimport * as objects from '../../util/object'\nimport * as platform from '../../util/platform'\nimport * as positions from '../../util/position'\nimport { executable, isRunning, runCommand, terminate } from '../../util/processes'\nimport { convertProperties, Registry } from '../../util/registry'\nimport { Sequence } from '../../util/sequence'\nimport * as strings from '../../util/string'\nimport * as textedits from '../../util/textedit'\nimport { createTiming } from '../../util/timing'\nimport helper from '../helper'\n\nfunction createTextDocument(lines: string[]): LinesTextDocument {\n  return new LinesTextDocument('file://a', 'txt', 1, lines, 1, true)\n}\n\nfunction toEdit(sl, sc, el, ec, text): TextEdit {\n  return TextEdit.replace(Range.create(sl, sc, el, ec), text)\n}\n\nlet logfile = path.join(os.tmpdir(), 'log_test.js')\nbeforeAll(() => {\n  let code = `const {wait, nvim} = require('coc.nvim')\nconsole.log('log')\nconsole.debug('debug')\nconsole.info('info')\nconsole.error('error')\nconsole.warn('warn')\nmodule.exports = () => {\n  return {wait, nvim}\n}`\n  fs.writeFileSync(logfile, code, 'utf8')\n})\n\nafterAll(() => {\n  fs.unlinkSync(logfile)\n})\n\ndescribe('factory', () => {\n  afterAll(() => {\n    global.__TEST__ = true\n  })\n\n  const emptyLogger: factory.ILogger = {\n    log: () => {},\n    info: () => {},\n    error: () => {},\n    debug: () => {},\n    warn: () => {},\n    trace: () => {},\n    fatal: () => {},\n    mark: () => {}\n  }\n\n  it('should create logger', () => {\n    let fn = jest.fn()\n    const sandbox = factory.createSandbox(logfile, {\n      log: () => {\n        fn()\n      },\n      info: () => {\n        fn()\n      },\n      error: () => {\n        fn()\n      },\n      debug: () => {\n        fn()\n      },\n      warn: () => {\n        fn()\n      },\n      trace: () => {\n      },\n      fatal: () => {\n      },\n      mark: () => {\n      }\n    })\n    vm.runInContext(`\nconsole.log('log')\nconsole.debug('debug')\nconsole.info('info')\nconsole.error('error')\nconsole.warn('warn')`, sandbox)\n    expect(fn).toHaveBeenCalled()\n  })\n\n  it('should create console', () => {\n    let res = factory.createConsole({ x: 1 }, {} as any)\n    expect(res).toEqual({ x: 1 })\n    let called = false\n    let val = 1\n    res = factory.createConsole({\n      warn: () => {\n      },\n      custom: () => {\n        val = 2\n      }\n    }, {\n      warn: () => {\n        called = true\n      }\n    } as any)\n      ; (res as any).custom()\n      ; (res as Console).warn()\n    expect(val).toBe(1)\n    expect(called).toBe(true)\n  })\n\n  it('should copy properties', () => {\n    let obj = factory.copyGlobalProperties({} as any, global)\n    expect(typeof obj['fetch']).toBe('function')\n  })\n\n  it('should not throw process.chdir', () => {\n    const sandbox = factory.createSandbox(logfile, emptyLogger)\n    let res = vm.runInContext(`process.chdir()`, sandbox)\n    expect(res).toBeUndefined()\n  })\n\n  it('should throw with umask', () => {\n    const sandbox = factory.createSandbox(logfile, emptyLogger)\n    let res = vm.runInContext(`process.umask()`, sandbox)\n    expect(typeof res).toBe('number')\n    let err\n    try {\n      res = vm.runInContext(`process.umask(18)`, sandbox)\n    } catch (e) {\n      err = e\n    }\n    expect(err).toBeDefined()\n  })\n\n  it('should throw with process.exit', () => {\n    const sandbox = factory.createSandbox(logfile, emptyLogger)\n    let err\n    try {\n      vm.runInContext(`process.exit()`, sandbox)\n    } catch (e) {\n      err = e\n    }\n    expect(err).toBeDefined()\n  })\n\n  it('should get module prototype', () => {\n    const Module = require('module')\n    expect(factory.getProtoWithCompile(Module as any)).toBeDefined()\n    function fn() {}\n    expect(() => {\n      factory.getProtoWithCompile(fn)\n    }).toThrow(Error)\n    fn.prototype._compile = () => {}\n    expect(factory.getProtoWithCompile(fn)).toBeDefined()\n  })\n\n  it('should hook require', () => {\n    const sandbox = factory.createSandbox(logfile, factory.consoleLogger, 'hook', false)\n    let fn = factory.compileInSandbox(sandbox, { wait() {} })\n    let obj: any = {}\n    fn.apply(obj, [`const {wait} = require('coc.nvim')\\nmodule.exports = wait`, logfile])\n    expect(typeof obj.exports).toBe('function')\n  })\n\n  it('should createSandbox', () => {\n    const Module = require('module')\n    const sandbox = factory.createSandbox(logfile, emptyLogger, 'hook', false)\n    delete Module._cache[require.resolve(logfile)]\n    let exports = sandbox.require(logfile)\n    expect(typeof exports).toBe('function')\n    let obj = exports()\n    expect(typeof obj.wait).toBe('function')\n  })\n\n  it('should clear the cache', () => {\n    const Module = require('module')\n    let filename = path.join(os.tmpdir(), 'cache_test.js')\n    fs.writeFileSync(filename, 'module.exports = {x: 1}', 'utf8')\n    let sandbox = factory.createSandbox(filename, emptyLogger, 'hook')\n    let exports = sandbox.require(filename)\n    delete Module._cache[require.resolve(filename)]\n    fs.writeFileSync(filename, 'module.exports = {y: 1}', 'utf8')\n    sandbox = factory.createSandbox(filename, emptyLogger, 'hook')\n    exports = sandbox.require(filename)\n    expect(exports).toEqual({ y: 1 })\n    fs.rmSync(filename, { force: true })\n  })\n\n  it('should create extension', () => {\n    global.__TEST__ = false\n    let filename = path.join(os.tmpdir(), 'hash.js')\n    fs.writeFileSync(filename, `#! /usr/bin/env node\n    module.exports = function(){\n      return {fs: require(\"fs\"), resolved: require.resolve('fs')}\n    }`, 'utf8')\n    let exp = factory.createExtension('hash', filename, false) as any\n    let res = exp.activate()\n    expect(res.fs).toBeDefined()\n    expect(res.resolved).toBe('fs')\n    fs.rmSync(filename, { force: true })\n  })\n})\n\ndescribe('platform', () => {\n  it('should get platform', () => {\n    expect(platform.getPlatform({ platform: 'win32' } as any)).toBe(platform.Platform.Windows)\n    expect(platform.getPlatform({ platform: 'darwin' } as any)).toBe(platform.Platform.Mac)\n    expect(platform.getPlatform({ platform: 'linux' } as any)).toBe(platform.Platform.Linux)\n    expect(platform.getPlatform({ platform: 'unknown' } as any)).toBe(platform.Platform.Unknown)\n  })\n\n  it('should check platform', () => {\n    expect(platform.isWeb).toBeDefined()\n    expect(platform.isLinux).toBeDefined()\n    expect(platform.isNative).toBeDefined()\n    expect(platform.isWindows).toBeDefined()\n    expect(platform.isMacintosh).toBeDefined()\n  })\n})\n\ndescribe('textedit', () => {\n\n  function createEdit(uri: string): WorkspaceEdit {\n    let edit = TextEdit.insert(Position.create(0, 0), 'a')\n    let doc = { uri, version: null }\n    return { documentChanges: [TextDocumentEdit.create(doc, [edit])] }\n  }\n\n  function addPosition(position: Position, line: number, character: number): Position {\n    return Position.create(position.line + line, position.character + character)\n  }\n\n  test('getChangedPosition', () => {\n    const assertPosition = (start, edit, arr) => {\n      let res = textedits.getChangedPosition(start, edit)\n      expect(res).toEqual(Position.create(arr[0], arr[1]))\n    }\n    let pos = Position.create(0, 0)\n    assertPosition(pos, TextEdit.insert(pos, 'abc'), [0, 3])\n    assertPosition(pos, TextEdit.insert(pos, 'a\\nb\\nc'), [2, 1])\n    let edit = TextEdit.replace(Range.create(pos, Position.create(0, 3)), 'abc')\n    assertPosition(pos, edit, [0, 0])\n    pos = Position.create(0, 1)\n    let r = Range.create(addPosition(pos, 0, -1), pos)\n    assertPosition(pos, TextEdit.replace(r, 'a\\nb\\n'), [2, -1])\n    pos = Position.create(1, 3)\n    edit = TextEdit.replace(Range.create(Position.create(0, 1), Position.create(1, 0)), 'abc')\n    assertPosition(pos, edit, [-1, 4])\n  })\n\n  test('getChangedLineCount', () => {\n    let pos = Position.create(5, 0)\n    let edits: TextEdit[] = [\n      TextEdit.replace(Range.create(0, 1, 1, 0), ''),\n      TextEdit.replace(Range.create(2, 1, 3, 0), ''),\n      TextEdit.replace(Range.create(10, 1, 12, 0), 'foo'),\n    ]\n    expect(textedits.getChangedLineCount(pos, edits)).toBe(-2)\n  })\n\n  test('getPosition()', () => {\n    let pos = Position.create(1, 3)\n    const assertChange = (rl, rc, el, ec, text, val): void => {\n      let edit = TextEdit.replace(Range.create(rl, rc, el, ec), text)\n      let res = textedits.getPosition(pos, edit)\n      expect(res).toEqual(val)\n    }\n    assertChange(0, 1, 1, 0, 'abc', Position.create(0, 7))\n    assertChange(0, 1, 1, 1, 'abc', Position.create(0, 6))\n    assertChange(0, 1, 1, 0, 'abc\\n', Position.create(1, 3))\n    assertChange(1, 1, 1, 2, '', Position.create(1, 2))\n    assertChange(1, 1, 3, 0, '', Position.create(1, 3))\n  })\n\n  test('getStartLine()', () => {\n    const assertLine = (rl, rc, el, ec, text, val: number): void => {\n      let edit = TextEdit.replace(Range.create(rl, rc, el, ec), text)\n      let res = textedits.getStartLine(edit)\n      expect(res).toBe(val)\n    }\n    assertLine(0, 0, 0, 0, 'abc\\n', -1)\n    assertLine(1, 0, 1, 0, 'd\\n', 0)\n    assertLine(0, 0, 0, 0, 'abc', 0)\n  })\n\n  test('getPositionFromEdits()', () => {\n    const assertEdits = (pos, edits, exp: [number, number]) => {\n      let res = textedits.getPositionFromEdits(pos, edits)\n      expect(res).toEqual(Position.create(exp[0], exp[1]))\n    }\n    let pos = Position.create(5, 1)\n    let edits: TextEdit[] = [\n      TextEdit.replace(Range.create(0, 3, 1, 0), ''),\n      TextEdit.replace(Range.create(2, 4, 3, 0), ''),\n      TextEdit.replace(Range.create(3, 4, 4, 0), ''),\n      TextEdit.replace(Range.create(4, 1, 5, 0), ''),\n      TextEdit.replace(Range.create(6, 1, 6, 1), 'foo'),\n    ]\n    assertEdits(pos, edits, [1, 10])\n  })\n\n  it('should check empty workspaceEdit', () => {\n    let workspaceEdit: WorkspaceEdit = createEdit('untitled:/1')\n    expect(textedits.emptyWorkspaceEdit(workspaceEdit)).toBe(false)\n    expect(textedits.emptyWorkspaceEdit({ documentChanges: [] })).toBe(true)\n  })\n\n  it('should get ranges', async () => {\n    let ranges = textedits.getRangesFromEdit('test:/1', {})\n    expect(ranges).toBeUndefined()\n    let edit: WorkspaceEdit = { changes: { 'test:/2': [TextEdit.insert(Position.create(0, 0), ' ')] } }\n    ranges = textedits.getRangesFromEdit('test:/1', edit)\n    expect(ranges).toBeUndefined()\n    ranges = textedits.getRangesFromEdit('test:/2', edit)\n    expect(ranges).toBeDefined()\n    edit = { documentChanges: [TextDocumentEdit.create({ uri: 'test:/1', version: null }, [TextEdit.insert(Position.create(0, 0), ' ')])] }\n    ranges = textedits.getRangesFromEdit('test:/1', edit)\n    expect(ranges).toBeDefined()\n  })\n\n  it('should get all annotation ids for confirm', () => {\n    let doc = { uri: 'test:///1', version: null }\n    let changes: DocumentChange[] = []\n    let ids = [uuid(), uuid(), uuid()]\n    changes.push({\n      textDocument: doc,\n      edits: [\n        AnnotatedTextEdit.insert(Position.create(0, 0), 'foo', ids[0]),\n        AnnotatedTextEdit.insert(Position.create(1, 0), 'bar', ids[1]),\n      ]\n    })\n    changes.push({\n      kind: 'delete',\n      uri: 'test:///2',\n      annotationId: ids[2]\n    })\n    changes.push({\n      kind: 'delete',\n      uri: 'test:///3',\n    })\n    let annotations: { [id: string]: ChangeAnnotation } = {}\n    annotations[ids[0]] = { label: '0', needsConfirmation: true }\n    annotations[ids[1]] = { label: '1', needsConfirmation: true }\n    annotations[ids[2]] = { label: '2', needsConfirmation: true }\n    let res = textedits.getConfirmAnnotations(changes, annotations)\n    expect(res.length).toBe(3)\n  })\n\n  it('should create filtered changes', () => {\n    let doc = { uri: 'test:///1', version: null }\n    let changes: DocumentChange[] = []\n    let ids = [uuid(), uuid(), uuid()]\n    changes.push({\n      textDocument: doc,\n      edits: [\n        AnnotatedTextEdit.insert(Position.create(0, 0), 'foo', ids[0]),\n        AnnotatedTextEdit.insert(Position.create(1, 0), 'bar', ids[1]),\n      ]\n    })\n    changes.push({\n      kind: 'delete',\n      uri: 'test:///2',\n      annotationId: ids[2]\n    })\n    changes.push({\n      kind: 'delete',\n      uri: 'test:///3',\n    })\n    let res = textedits.createFilteredChanges(changes, [ids[0], ids[2]])\n    expect(res.length).toBe(2)\n    expect(res).toEqual([{\n      textDocument: {\n        uri: \"test:///1\",\n        version: null\n      },\n      edits: [{\n        range: {\n          start: { line: 1, character: 0 },\n          end: { line: 1, character: 0 }\n        },\n        newText: \"bar\",\n        annotationId: ids[1]\n      }]\n    },\n    {\n      kind: \"delete\",\n      uri: \"test:///3\"\n    }])\n    res = textedits.createFilteredChanges(changes, ids)\n    expect(res.length).toBe(1)\n  })\n\n  it('should check edit is denied', () => {\n    let ids = [uuid(), uuid()]\n    let edits = [\n      AnnotatedTextEdit.insert(Position.create(0, 0), 'foo', ids[0]),\n      AnnotatedTextEdit.insert(Position.create(1, 0), 'bar', ids[1]),\n    ]\n    expect(textedits.isDeniedEdit(edits[0], [ids[0]])).toBe(true)\n    expect(textedits.isDeniedEdit(edits[1], [ids[0]])).toBe(false)\n  })\n\n  it('should check empty TextEdit', () => {\n    expect(textedits.emptyTextEdit(TextEdit.insert(Position.create(0, 0), ''))).toBe(true)\n    expect(textedits.emptyTextEdit(TextEdit.insert(Position.create(0, 0), 'a'))).toBe(false)\n  })\n\n  it('should get well formed edit', () => {\n    let r = Range.create(1, 0, 0, 0)\n    let edit: TextEdit = { range: r, newText: 'foo' }\n    let res = textedits.getWellformedEdit(edit)\n    expect(res.range).toEqual(Range.create(0, 0, 1, 0))\n    r = Range.create(0, 0, 1, 0)\n    edit = { range: r, newText: 'foo' }\n    res = textedits.getWellformedEdit(edit)\n    expect(res.range).toBe(r)\n  })\n\n  it('should check line count change', () => {\n    let r = Range.create(0, 0, 0, 5)\n    let edit: TextEdit = { range: r, newText: 'foo' }\n    expect(textedits.lineCountChange(edit)).toBe(0)\n    edit = { range: Range.create(0, 0, 1, 0), newText: 'foo' }\n    expect(textedits.lineCountChange(edit)).toBe(-1)\n  })\n\n  it('should filter and sort textedits', () => {\n    let doc = createTextDocument(['foo'])\n    expect(textedits.filterSortEdits(doc, [TextEdit.insert(Position.create(0, 0), 'a\\r\\nb')])).toEqual([\n      TextEdit.insert(Position.create(0, 0), 'a\\nb')\n    ])\n    expect(textedits.filterSortEdits(doc, [TextEdit.replace(Range.create(0, 0, 0, 3), 'foo')])).toEqual([])\n    expect(textedits.filterSortEdits(doc, [\n      TextEdit.insert(Position.create(0, 1), 'b'),\n      TextEdit.insert(Position.create(0, 0), 'a'),\n    ])).toEqual([\n      TextEdit.insert(Position.create(0, 0), 'a'),\n      TextEdit.insert(Position.create(0, 1), 'b'),\n    ])\n  })\n\n  it('should fix edit range', () => {\n    let doc = createTextDocument(['foo'])\n    let range = Range.create(0, 0, 0, 5)\n    let res = textedits.filterSortEdits(doc, [TextEdit.replace(range, 'bar')])\n    expect(res[0].range).toEqual(Range.create(0, 0, 0, 3))\n  })\n\n  it('should get range text', async () => {\n    {\n      let text = textedits.getRangeText([''], Range.create(0, 0, 0, 0))\n      expect(text).toBe('')\n    }\n    {\n      let lines = ['foo', 'aabb', 'bar']\n      let text = textedits.getRangeText(lines, Range.create(0, 1, 2, 1))\n      expect(text).toBe('oo\\naabb\\nb')\n    }\n  })\n\n  it('should reduceTextEdit', () => {\n    let e: TextEdit\n    e = TextEdit.replace(Range.create(0, 0, 0, 3), 'foo')\n    expect(textedits.reduceTextEdit(e, '')).toEqual(e)\n    e = TextEdit.replace(Range.create(0, 0, 0, 3), 'foo\\nbar')\n    expect(textedits.reduceTextEdit(e, 'bar')).toEqual(\n      TextEdit.replace(Range.create(0, 0, 0, 0), 'foo\\n')\n    )\n    e = TextEdit.replace(Range.create(0, 0, 0, 3), 'foo\\nbar')\n    expect(textedits.reduceTextEdit(e, 'foo')).toEqual(\n      TextEdit.replace(Range.create(0, 3, 0, 3), '\\nbar')\n    )\n    e = TextEdit.replace(Range.create(0, 0, 0, 3), 'def')\n    expect(textedits.reduceTextEdit(e, 'daf')).toEqual(\n      TextEdit.replace(Range.create(0, 1, 0, 2), 'e')\n    )\n    e = TextEdit.replace(Range.create(2, 0, 3, 0), 'ascii ascii bar\\n')\n    expect(textedits.reduceTextEdit(e, 'xyz ascii bar\\n')).toEqual(\n      TextEdit.replace(Range.create(2, 0, 2, 3), 'ascii')\n    )\n  })\n\n  it('should get revert edit', async () => {\n    {\n      let res = textedits.getRevertEdit(['aa'], ['aa'], 0)\n      expect(res).toBeUndefined()\n    } {\n      let res = textedits.getRevertEdit(['foo', 'bar'], ['foo 1', 'bar 2'], 0)\n      expect(res).toEqual(TextEdit.replace(Range.create(0, 0, 2, 0), 'foo\\nbar\\n'))\n    } {\n      let res = textedits.getRevertEdit(['foo', 'bar'], ['foo', 'bar', 'after'], 2)\n      expect(res).toEqual(TextEdit.replace(Range.create(2, 0, 3, 0), ''))\n    }\n  })\n\n  it('should merge textedits #1', () => {\n    let edits = [toEdit(0, 0, 0, 0, 'foo'), toEdit(0, 1, 0, 1, 'bar')]\n    let lines = ['ab']\n    let res = textedits.mergeTextEdits(edits, lines, ['fooabarb'])\n    expect(res).toEqual(toEdit(0, 0, 0, 1, 'fooabar'))\n  })\n\n  it('should merge textedits #2', () => {\n    let edits = [toEdit(0, 0, 1, 0, 'foo\\n')]\n    let lines = ['bar']\n    let res = textedits.mergeTextEdits(edits, lines, ['foo'])\n    expect(res).toEqual(toEdit(0, 0, 1, 0, 'foo\\n'))\n  })\n\n  it('should merge textedits #3', () => {\n    let edits = [toEdit(0, 0, 0, 1, 'd'), toEdit(1, 0, 1, 1, 'e'), toEdit(2, 0, 3, 0, 'f\\n')]\n    let lines = ['a', 'b', 'c']\n    let res = textedits.mergeTextEdits(edits, lines, ['d', 'e', 'f'])\n    expect(res).toEqual(toEdit(0, 0, 3, 0, 'd\\ne\\nf\\n'))\n  })\n\n  it('should convert to text changes', () => {\n    expect(textedits.validEdit(TextEdit.insert(Position.create(0, 0), 'abc'))).toBe(false)\n    expect(textedits.validEdit(TextEdit.insert(Position.create(0, 1), 'abc\\n'))).toBe(false)\n    expect(textedits.toTextChanges(['foo'], [])).toEqual([])\n    expect(textedits.toTextChanges(['foo'], [TextEdit.insert(Position.create(3, 1), '')])).toEqual([])\n    expect(textedits.toTextChanges(['foo'], [TextEdit.insert(Position.create(1, 1), '')])).toEqual([])\n    expect(textedits.toTextChanges(['foo'], [TextEdit.insert(Position.create(1, 0), 'bar\\n')])).toEqual([[['', 'bar'], 0, 3, 0, 3]])\n    expect(textedits.toTextChanges(['foo'], [TextEdit.replace(Range.create(0, 0, 1, 0), 'bar\\n')])).toEqual([[['bar'], 0, 0, 0, 3]])\n  })\n})\n\ndescribe('Registry', () => {\n  it('should add to registry', () => {\n    Registry.add('key', {})\n    expect(Registry.knows('key')).toBe(true)\n    expect(Registry.as('key')).toEqual({})\n    expect(Registry.as('not_exists')).toBeNull()\n  })\n\n  it('should get jsonRegistry', () => {\n    let r = Registry.as<IJSONContributionRegistry>(Extensions.JSONContribution)\n    expect(r).toBeDefined()\n    r.registerSchema('uri', {} as any)\n    let res = r.getSchemaContributions()\n    expect(res.schemas.uri).toBeDefined()\n  })\n\n  it('should convertProperties', () => {\n    expect(convertProperties(undefined)).toEqual({})\n    expect(convertProperties({ key: { type: 'number' } }, ConfigurationScope.RESOURCE)).toEqual({\n      key: { scope: ConfigurationScope.RESOURCE, type: 'number' }\n    })\n    let properties = {\n      foo: {\n      },\n      bar: {\n        type: 'string',\n        scope: 'language-overridable'\n      },\n      resource: {\n        type: 'string',\n        scope: 'resource'\n      },\n      window: {\n        type: 'string',\n        default: ''\n      },\n      format: {\n        type: 'string',\n        scope: 'window'\n      },\n      'coc.source.name': {\n        type: 'string',\n        scope: 'resource'\n      },\n      'list.source.name': {\n        type: 'string',\n        scope: 'resource'\n      },\n    }\n    let res = convertProperties(properties)\n    expect(res.foo).toBeDefined()\n    expect(res.format.scope).toBe(ConfigurationScope.WINDOW)\n    expect(res.bar.scope).toBe(ConfigurationScope.LANGUAGE_OVERRIDABLE)\n    expect(res.resource.scope).toBe(ConfigurationScope.RESOURCE)\n    expect(res.window.scope).toBe(ConfigurationScope.WINDOW)\n    expect(res['coc.source.name'].scope).toBe(ConfigurationScope.APPLICATION)\n    expect(res['list.source.name'].scope).toBe(ConfigurationScope.APPLICATION)\n  })\n\n  it('should parse extension name', () => {\n    let parseSource = extension.parseExtensionName\n    expect(parseSource(``)).toBeUndefined()\n    expect(parseSource(`a)`, 0)).toBe('coc.nvim')\n    expect(parseSource(`a`, 0)).toBe('coc.nvim')\n    let registry = Registry.as<extension.IExtensionRegistry>(extension.Extensions.ExtensionContribution)\n    let filepath = path.join(os.tmpdir(), 'single')\n    registry.registerExtension('single', { name: 'single', directory: os.tmpdir(), filepath })\n    expect(parseSource(`\\n\\n${filepath}:1:1`)).toBe('single')\n    // expect(parseSource(`\\n\\n${filepath.slice(0, -3)}:1:1`)).toBeUndefined()\n    expect(parseSource(`\\n\\n/a/b:1:1`)).toBe('coc.nvim')\n    let dir = fs.realpathSync(os.tmpdir())\n    expect(parseSource(`\\n\\n${path.join(dir, 'foo')}:1:1`)).toBe('single')\n    let lines = [\n      `at FormatRangeManager.addProvider (${pluginRoot}/src/provider/manager.ts:28:55`,\n      `at FormatRangeManager.register (${pluginRoot}/formatRangeManager.ts:17:17)`,\n      `at PrettierEditService.registerDocumentFormatEditorProviders (${filepath}:253:17)`\n    ]\n    let res = parseSource(`\\n\\n${lines.join('\\n')}`, 2)\n    expect(res).toBe('single')\n    registry.unregistExtension('single')\n  })\n\n  it('should check rootPattern and commands', () => {\n    expect(extension.validRootPattern({} as any)).toBe(false)\n    expect(extension.validCommandContribution({} as any)).toBe(false)\n  })\n\n  it('should get properties', () => {\n    let properties = extension.getProperties({})\n    expect(properties).toEqual({})\n    properties = extension.getProperties({ properties: { x: 1 } })\n    expect(properties).toEqual({ x: 1 })\n    properties = extension.getProperties([{ properties: { x: 1 } }, { properties: { y: 2 } }])\n    expect(properties).toEqual({ x: 1, y: 2 })\n  })\n\n  it('should get onCommands and commands', () => {\n    let registry = Registry.as<extension.IExtensionRegistry>(extension.Extensions.ExtensionContribution)\n    registry.registerExtension('single', {\n      name: 'single',\n      directory: os.tmpdir(),\n      onCommands: ['a', 'b', 'cmd', undefined],\n      commands: [{ command: 'cmd', title: 'title' }]\n    })\n    expect(registry.commands.length).toBeGreaterThan(0)\n    expect(registry.onCommands.length).toBeGreaterThan(0)\n    expect(registry.getCommandTitle('cmd')).toBe('title')\n    expect(registry.getCommandTitle('not_exists')).toBeUndefined()\n    registry.unregistExtension('single')\n  })\n\n  it('should get rootPatterns by fieltype', () => {\n    let registry = Registry.as<extension.IExtensionRegistry>(extension.Extensions.ExtensionContribution)\n    registry.registerExtension('single', {\n      name: 'single',\n      directory: os.tmpdir(),\n      rootPatterns: [{ filetype: 'vim', patterns: ['.foo', '.bar', undefined] }]\n    })\n    expect(registry.getRootPatternsByFiletype('vim')).toEqual(['.foo', '.bar'])\n    expect(registry.getRootPatternsByFiletype('ts')).toEqual([])\n    registry.unregistExtension('single')\n  })\n})\n\ndescribe('errors', () => {\n  it('should return errors', () => {\n    expect(errors.directoryNotExists('dir').message).toMatch('dir')\n    expect(errors.illegalArgument('name') instanceof Error).toBe(true)\n    expect(errors.illegalArgument() instanceof Error).toBe(true)\n    expect(errors.shouldNotAsync('method') instanceof Error).toBe(true)\n    errors.onUnexpectedError(new errors.CancellationError())\n    expect(() => {\n      errors.onUnexpectedError(new Error('my error'))\n    }).toThrow()\n    expect(() => {\n      errors.onUnexpectedError('error')\n    }).toThrow()\n    errors.assert(true)\n    expect(() => {\n      errors.assert(false)\n    }).toThrow()\n  })\n\n  it('should check CancellationError', () => {\n    let err = new Error('Canceled')\n    err.name = 'Canceled'\n    expect(errors.isCancellationError(err)).toBe(true)\n    expect(errors.shouldIgnore(err)).toBe(true)\n  })\n\n  it('should check shouldIgnore', async () => {\n    expect(errors.shouldIgnore(new errors.CancellationError())).toBe(true)\n    let err = new Error('transport disconnected')\n    expect(errors.shouldIgnore(err)).toBe(true)\n  })\n})\n\ndescribe('numbers', () => {\n  it('should work with numbers', () => {\n    expect(numbers.toNumber(undefined, 5)).toBe(5)\n    expect(numbers.toNumber(undefined)).toBe(0)\n    expect(numbers.toNumber(1, 5)).toBe(1)\n    expect(numbers.clamp(1, 1, 3)).toBe(1)\n    expect(numbers.clamp(5, 1, 3)).toBe(3)\n    expect(numbers.rot(6, 5)).toBe(1)\n  })\n})\n\ndescribe('strings', () => {\n  it('should get byte indexes', () => {\n    let bytes = strings.bytes\n    let fn = bytes('abcde')\n    expect(fn(0)).toBe(0)\n    expect(fn(1)).toBe(1)\n    expect(fn(8)).toBe(5)\n    fn = bytes('你ab好')\n    expect(fn(0)).toBe(0)\n    expect(fn(1)).toBe(3)\n    expect(fn(2)).toBe(4)\n    fn = bytes('abcdefghi', 3)\n    expect(fn(5)).toBe(3)\n    fn = bytes('😘😘')\n    expect(fn(2)).toBe(4)\n    expect(fn(4)).toBe(8)\n    fn = bytes(String.fromCharCode(0xdc02) + 'ab')\n    expect(fn(2)).toBe(4)\n  })\n\n  it('should get byte index from utf16 index', () => {\n    let testIndex = (text: string, index: number) => {\n      let res = Buffer.byteLength(text.slice(0, index))\n      expect(strings.byteIndex(text, index)).toBe(res)\n    }\n    testIndex('abc', 2)\n    testIndex('汉字abc', 2)\n    testIndex('汉字abc', 4)\n    testIndex('😘foo', 3)\n    testIndex('', 3)\n    testIndex(String.fromCharCode(0xdc02) + 'ab', 2)\n  })\n\n  it('should get byte length', () => {\n    expect(strings.byteLength('a')).toBe(1)\n    expect(strings.byteLength('你')).toBe(3)\n    expect(strings.byteLength('a😘b')).toBe(6)\n    expect(strings.byteLength('a😘b', 1)).toBe(5)\n    expect(strings.byteLength('a😘b', 3)).toBe(1)\n  })\n\n  it('should get character index from byte index', () => {\n    expect(strings.characterIndex('ab', 0)).toBe(0)\n    expect(strings.characterIndex('abc', 1)).toBe(1)\n    expect(strings.characterIndex('ab', 99)).toBe(2)\n    expect(strings.characterIndex('abc', 1)).toBe(1)\n    expect(strings.characterIndex('ôbc', 2)).toBe(1)\n    expect(strings.characterIndex('ô你c', 2)).toBe(1)\n    expect(strings.characterIndex('你c', 3)).toBe(1)\n    expect(strings.characterIndex('😘def', 4)).toBe(2)\n    expect(strings.characterIndex('\\ude18def', 3)).toBe(1)\n    expect(strings.utf8_code2len(65537)).toBe(4)\n  })\n\n  it('should slice content by bytes', () => {\n    expect(strings.byteSlice('你', 0, 1)).toBe('你')\n    expect(strings.byteSlice('你', 0, 3)).toBe('你')\n    expect(strings.byteSlice('abc你', 3, 6)).toBe('你')\n    expect(strings.byteSlice('foo', 1)).toBe('oo')\n  })\n\n  it('should get case', () => {\n    expect(strings.getCase('a'.charCodeAt(0))).toBe(1)\n    expect(strings.getCase('A'.charCodeAt(0))).toBe(2)\n    expect(strings.getCase('#'.charCodeAt(0))).toBe(0)\n  })\n\n  it('should get next word code', () => {\n    function assertNext(text: string, index: number, res: [number, string] | undefined): void {\n      let arr = res === undefined ? undefined : [res[0], res[1].charCodeAt(0)]\n      let result = strings.getNextWord(fuzzy.getCharCodes(text), index)\n      expect(result).toEqual(arr)\n    }\n    assertNext('abc', 0, [0, 'a'])\n    assertNext('abc', 1, undefined)\n    assertNext('abC', 1, [2, 'C'])\n  })\n\n  it('should get character indexes', () => {\n    expect(strings.getCharIndexes('abaca', 'a')).toEqual([0, 2, 4])\n    expect(strings.getCharIndexes('abd', 'f')).toEqual([])\n  })\n\n  it('should convert to lines', () => {\n    expect(strings.contentToLines('foo', false)).toEqual(['foo'])\n    expect(strings.contentToLines('foo\\n', true)).toEqual(['foo'])\n  })\n\n  it('should get smartcaseIndex', () => {\n    expect(strings.smartcaseIndex('a', 'A')).toBe(0)\n    expect(strings.smartcaseIndex('a', 'a')).toBe(0)\n    expect(strings.smartcaseIndex('ab', 'a')).toBe(-1)\n    expect(strings.smartcaseIndex('', 'a')).toBe(0)\n    expect(strings.smartcaseIndex('ab', 'xaB')).toBe(1)\n    expect(strings.smartcaseIndex('aA', 'aaA')).toBe(1)\n    expect(strings.smartcaseIndex('aB', 'aaA')).toBe(-1)\n    expect(strings.smartcaseIndex('AA', 'aaA')).toBe(-1)\n    expect(strings.smartcaseIndex('aA', 'axdefA')).toBe(-1)\n    expect(strings.smartcaseIndex('abC', 'aaBDefabC')).toBe(6)\n  })\n\n  it('should convert to integer', () => {\n    expect(strings.toErrorText('a')).toBe('a')\n    expect(strings.toInteger('a')).toBeUndefined()\n    expect(strings.toInteger('1')).toBe(1)\n  })\n\n  it('should check highlight character', () => {\n    expect(strings.isHighlightGroupCharCode('1'.charCodeAt(0))).toBe(true)\n    expect(strings.isHighlightGroupCharCode('9'.charCodeAt(0))).toBe(true)\n    expect(strings.isHighlightGroupCharCode('a'.charCodeAt(0))).toBe(true)\n    expect(strings.isHighlightGroupCharCode('z'.charCodeAt(0))).toBe(true)\n    expect(strings.isHighlightGroupCharCode('A'.charCodeAt(0))).toBe(true)\n    expect(strings.isHighlightGroupCharCode('Z'.charCodeAt(0))).toBe(true)\n    expect(strings.isHighlightGroupCharCode('.'.charCodeAt(0))).toBe(true)\n    expect(strings.isHighlightGroupCharCode('_'.charCodeAt(0))).toBe(true)\n    expect(strings.isHighlightGroupCharCode('@'.charCodeAt(0))).toBe(true)\n    expect(strings.isHighlightGroupCharCode(' '.charCodeAt(0))).toBe(false)\n  })\n\n  it('should convert to text', () => {\n    expect(strings.toText(undefined)).toBe('')\n    expect(strings.toText(null)).toBe('')\n    expect(strings.toText(3)).toBe('3')\n  })\n\n  it('should check isEmojiImprecise', () => {\n    expect(strings.isEmojiImprecise(999)).toBe(false)\n    expect(strings.isEmojiImprecise(0x1F1E7)).toBe(true)\n    expect(strings.isEmojiImprecise(8987)).toBe(true)\n    expect(strings.isEmojiImprecise(128764)).toBe(true)\n    expect(strings.isEmojiImprecise(129008)).toBe(true)\n    expect(strings.isEmojiImprecise(129782)).toBe(true)\n    expect(strings.isEmojiImprecise(129535)).toBe(true)\n  })\n\n  it('should get parts', () => {\n    let res = strings.rangeParts('foo bar', Range.create(0, 0, 0, 4))\n    expect(res).toEqual(['', 'bar'])\n    res = strings.rangeParts('foo\\nbar', Range.create(0, 1, 1, 1))\n    expect(res).toEqual(['f', 'ar'])\n    res = strings.rangeParts('x\\nfoo\\nbar\\ny', Range.create(0, 1, 2, 3))\n    expect(res).toEqual(['x', '\\ny'])\n    res = strings.rangeParts('foo\\nbar\\nx', Range.create(1, 0, 1, 1))\n    expect(res).toEqual(['foo\\n', 'ar\\nx'])\n    res = strings.rangeParts('x\\nfoo\\nbar\\ny', Range.create(0, 1, 1, 0))\n    expect(res).toEqual(['x', 'foo\\nbar\\ny'])\n  })\n\n  it('should equalsIgnoreCase', () => {\n    expect(strings.equalsIgnoreCase('', '')).toBe(true)\n    expect(!strings.equalsIgnoreCase('', '1')).toBe(true)\n    expect(!strings.equalsIgnoreCase('1', '')).toBe(true)\n    expect(strings.equalsIgnoreCase('a', 'a')).toBe(true)\n    expect(strings.equalsIgnoreCase('abc', 'Abc')).toBe(true)\n    expect(strings.equalsIgnoreCase('abc', 'ABC')).toBe(true)\n    expect(strings.equalsIgnoreCase('Höhenmeter', 'HÖhenmeter')).toBe(true)\n    expect(strings.equalsIgnoreCase('ÖL', 'Öl')).toBe(true)\n  })\n\n  it('should doEqualsIgnoreCase', () => {\n    expect(strings.doEqualsIgnoreCase('a', undefined)).toBe(false)\n    expect(strings.doEqualsIgnoreCase('a', 'b')).toBe(false)\n    expect(strings.doEqualsIgnoreCase('你', '的')).toBe(false)\n  })\n\n  it('should find index', () => {\n    expect(strings.indexOf('a,b,c', ',', 2)).toBe(3)\n    expect(strings.indexOf('a,b,c', ',', 1)).toBe(1)\n    expect(strings.indexOf('a,b,c', 't')).toBe(-1)\n  })\n\n  it('should upperFirst', () => {\n    expect(strings.upperFirst('')).toBe('')\n    expect(strings.upperFirst('abC')).toBe('AbC')\n    expect(strings.upperFirst(undefined)).toBe('')\n  })\n\n  it('should getUnicodeClass', () => {\n    expect(strings.getUnicodeClass(null)).toBe('other')\n    expect(strings.getUnicodeClass('')).toBe('other')\n    expect(strings.getUnicodeClass('\\0')).toBe('other')\n    expect(strings.getUnicodeClass('\\x1b')).toBe('punctuation')\n    expect(strings.getUnicodeClass('，')).toBe('punctuation')\n    expect(strings.getUnicodeClass('你')).toBe('cjkideograph')\n    expect(strings.getUnicodeClass('😘')).toBe('other')\n    expect(strings.getUnicodeClass('a')).toBe('word')\n  })\n})\n\ndescribe('getSymbolKind()', () => {\n  it('should get symbol kind', () => {\n    for (let i = 1; i <= 27; i++) {\n      expect(getSymbolKind(i as SymbolKind)).toBeDefined()\n    }\n  })\n})\n\ndescribe('Is', () => {\n  it('should url', () => {\n    expect(Is.isUrl('')).toBe(false)\n    expect(Is.isUrl(undefined)).toBe(false)\n    expect(Is.isUrl('file:1')).toBe(true)\n  })\n\n  it('should check insert replace edit', () => {\n    expect(Is.isEditRange(null)).toBe(false)\n    let r = Range.create(0, 0, 0, 1)\n    expect(Is.isEditRange(r)).toBe(true)\n    expect(Is.isEditRange({ insert: r, replace: r })).toBe(true)\n  })\n\n  it('should check command', () => {\n    expect(Is.isCommand(undefined)).toBe(false)\n    expect(Is.isCommand({})).toBe(false)\n    expect(Is.isCommand({ title: '', command: '' })).toBe(false)\n    expect(Is.isCommand({ title: 'title', command: 'cmd' })).toBe(true)\n  })\n\n  it('should check array', () => {\n    expect(Is.array(false)).toBe(false)\n  })\n\n  it('should check empty object', () => {\n    expect(Is.emptyObject(false)).toBe(false)\n    expect(Is.emptyObject({})).toBe(true)\n    expect(Is.emptyObject({ x: 1 })).toBe(false)\n  })\n\n  it('should check typed array', () => {\n    let arr = new Array(10)\n    arr.fill(1)\n    expect(Is.typedArray<Uint32Array>(arr, v => {\n      return v >= 0\n    })).toBe(true)\n  })\n})\n\ndescribe('lodash', () => {\n  it('should set defaults', () => {\n    let res = lodash.defaults({ a: 1 }, { b: 2 }, { a: 3 }, null)\n    expect(res).toEqual({ a: 1, b: 2 })\n    res = lodash.defaults({}, { constructor: 'fn' })\n    expect(res.constructor).toBe('fn')\n  })\n})\n\ndescribe('color', () => {\n  it('should check dark color', () => {\n    expect(color.isDark(Color.create(0.03, 0.01, 0.01, 0))).toBe(true)\n  })\n})\n\ndescribe('parseAnsiHighlights', () => {\n  function testColorHighlight(highlight: string, hlGroup: string, markdown = true) {\n    let text = `${style[highlight].open}text${style[highlight].close}`\n    let res = parseAnsiHighlights(text, markdown)\n    expect(res.highlights.length).toBeGreaterThan(0)\n    let o = res.highlights.find(o => o.hlGroup == hlGroup)\n    expect(o).toBeDefined()\n  }\n\n  it('should parse foreground color', () => {\n    testColorHighlight('yellow', 'CocMarkdownCode')\n    testColorHighlight('blue', 'CocMarkdownLink')\n    testColorHighlight('magenta', 'CocMarkdownHeader')\n    testColorHighlight('green', 'CocListFgGreen')\n    testColorHighlight('green', 'CocListFgGreen', false)\n  })\n\n  it('should parse background color', () => {\n    let text = `${style.bgRed.open}text${style.bgRed.close}`\n    let res = parseAnsiHighlights(text, false)\n    expect(res.highlights.length).toBeGreaterThan(0)\n    expect(res.highlights[0].hlGroup).toBe('CocListBgRed')\n    text = '\\u001b[33m\\u001b[mnormal'\n    res = parseAnsiHighlights(text, false)\n    expect(res.highlights.length).toBe(0)\n  })\n\n  it('should parse foreground and background', () => {\n    let text = `${style.bgRed.open}${style.blue.open}text${style.blue.close}${style.bgRed.close}`\n    let res = parseAnsiHighlights(text, true)\n    expect(res.highlights.length).toBeGreaterThan(0)\n    expect(res.highlights[0].hlGroup).toBe('CocListBlueRed')\n  })\n\n  it('should erase char', () => {\n    let text = `foo\\u0008bar`\n    let res = parseAnsiHighlights(text, true)\n    expect(res.line).toBe('fobar')\n    text = `${style.bgRed.open}foo${style.bgRed.close}\\u0008bar`\n    res = parseAnsiHighlights(text, true)\n    expect(res.line).toBe('fobar')\n    text = `${style.bgRed.open}f${style.bgRed.close}\\u0008bar`\n    res = parseAnsiHighlights(text, true)\n    expect(res.line).toBe('bar')\n  })\n\n  it('should not throw for bad control character', () => {\n    let text = '\\x1bafoo'\n    let res = parseAnsiHighlights(text)\n    expect(res.line).toBeDefined()\n    text = '\\x1b[33;44mabc\\x1b[33,44m'\n    res = parseAnsiHighlights(text)\n    expect(res.line).toBe('abc')\n  })\n})\n\ndescribe('Arrays', () => {\n\n  it('distinct()', () => {\n    function compare(a: string): string {\n      return a\n    }\n\n    assert.deepStrictEqual(arrays.distinct(['32', '4', '5'], compare), ['32', '4', '5'])\n    assert.deepStrictEqual(arrays.distinct(['32', '4', '5', '4'], compare), ['32', '4', '5'])\n    assert.deepStrictEqual(arrays.distinct(['32', 'constructor', '5', '1'], compare), ['32', 'constructor', '5', '1'])\n    assert.deepStrictEqual(arrays.distinct(['32', 'constructor', 'proto', 'proto', 'constructor'], compare), ['32', 'constructor', 'proto'])\n    assert.deepStrictEqual(arrays.distinct(['32', '4', '5', '32', '4', '5', '32', '4', '5', '5'], compare), ['32', '4', '5'])\n  })\n\n  it('tail()', () => {\n    assert.strictEqual(arrays.tail([1, 2, 3]), 3)\n  })\n\n  it('intersect()', () => {\n    assert.ok(!arrays.intersect([1, 2, 3], [4, 5]))\n  })\n\n  it('isFalsyOrEmpty()', () => {\n    assert.ok(arrays.isFalsyOrEmpty([]))\n    assert.ok(arrays.isFalsyOrEmpty(false))\n    assert.ok(!arrays.isFalsyOrEmpty([1]))\n  })\n\n  it('should check intable', () => {\n    assert.ok(arrays.intable(1, [[0, 1], [2, 3], [4, 5]]))\n    assert.ok(arrays.intable(2, [[0, 1], [4, 6], [8, 9]]) === false)\n    assert.ok(arrays.intable(5, [[0, 1], [2, 3], [4, 5]]))\n    assert.ok(arrays.intable(6, [[0, 1], [2, 3], [4, 5]]) === false)\n  })\n\n  it('binarySearch()', () => {\n    let comparator = (a, b) => a - b\n    assert.ok(typeof arrays.binarySearch2 === 'function')\n    assert.ok(arrays.binarySearch([1, 2, 3], 2, comparator) == 1)\n    assert.ok(arrays.binarySearch([1, 2, 3, 4], 3, comparator) == 2)\n    assert.ok(arrays.binarySearch([1, 2, 3, 4], 1, comparator) == 0)\n    assert.ok(arrays.binarySearch([1, 2, 3, 4], 0.5, comparator) == -1)\n    assert.ok(arrays.binarySearch([1, 2, 3, 5], 6, comparator) == -5)\n  })\n\n  it('toArray()', () => {\n    assert.deepStrictEqual(arrays.toArray(1), [1])\n    assert.deepStrictEqual(arrays.toArray(null), [])\n    assert.deepStrictEqual(arrays.toArray(undefined), [])\n    assert.deepStrictEqual(arrays.toArray([1, 2]), [1, 2])\n  })\n\n  it('findIndex()', () => {\n    expect(arrays.findIndex([1, 2, 3, 4], 3, 1)).toBe(2)\n    expect(arrays.findIndex([1, 2, 3, 4], 3)).toBe(2)\n  })\n\n  it('group()', () => {\n    let res = arrays.group([1, 2, 3, 4, 5], 3)\n    assert.deepStrictEqual(res, [[1, 2, 3], [4, 5]])\n  })\n\n  it('groupBy()', () => {\n    let res = arrays.groupBy([0, 0, 3, 4], v => v != 0)\n    assert.deepStrictEqual(res, [[3, 4], [0, 0]])\n  })\n\n  it('lastIndex()', () => {\n    let res = arrays.lastIndex([1, 2, 3], x => x < 3)\n    assert.strictEqual(res, 1)\n  })\n\n  it('flatMap()', () => {\n    let objs: { [key: string]: number[] }[] = [{ x: [1, 2] }, { y: [3, 4] }, { z: [5, 6] }]\n    function values(item: { [key: string]: number[] }): number[] {\n      return Object.keys(item).reduce((p, c) => p.concat(item[c]), [])\n    }\n    let res = arrays.flatMap(objs, values)\n    assert.deepStrictEqual(res, [1, 2, 3, 4, 5, 6])\n  })\n\n  it('addSortedArray()', () => {\n    expect(arrays.addSortedArray('a', ['d', 'e'])).toEqual(['a', 'd', 'e'])\n    expect(arrays.addSortedArray('f', ['d', 'e'])).toEqual(['d', 'e', 'f'])\n    expect(arrays.addSortedArray('d', ['d', 'e'])).toEqual(['d', 'e'])\n    expect(arrays.addSortedArray('e', ['d', 'f'])).toEqual(['d', 'e', 'f'])\n  })\n})\n\ndescribe('Position', () => {\n  function addPosition(position: Position, line: number, character: number): Position {\n    return Position.create(position.line + line, position.character + character)\n  }\n\n  test('samePosition', () => {\n    let pos = Position.create(0, 0)\n    expect(positions.samePosition(pos, Position.create(0, 0))).toBe(true)\n  })\n\n  test('adjacentPosition', () => {\n    let pos = Position.create(0, 0)\n    expect(positions.adjacentPosition(pos, Range.create(0, 0, 0, 1))).toBe(true)\n    expect(positions.adjacentPosition(pos, Range.create(1, 0, 1, 1))).toBe(false)\n    pos = Position.create(1, 1)\n    expect(positions.adjacentPosition(pos, Range.create(1, 0, 1, 1))).toBe(true)\n  })\n\n  test('equalsRange', () => {\n    let r = Range.create(0, 0, 0, 1)\n    expect(positions.equalsRange(r, r)).toBe(true)\n    expect(positions.equalsRange(r, Range.create(0, 1, 0, 1))).toBe(false)\n  })\n\n  test('compareRangesUsingStarts', () => {\n    let pos = Position.create(3, 3)\n    let range = Range.create(pos, pos)\n    const r = (a, b, c, d) => {\n      return Range.create(a, b, c, d)\n    }\n    expect(positions.compareRangesUsingStarts(range, range)).toBe(0)\n    expect(positions.compareRangesUsingStarts(r(1, 1, 1, 1), range)).toBeLessThan(0)\n    expect(positions.compareRangesUsingStarts(r(3, 3, 3, 4), range)).toBeGreaterThan(0)\n    expect(positions.compareRangesUsingStarts(r(4, 0, 4, 1), range)).toBeGreaterThan(0)\n    expect(positions.compareRangesUsingStarts(r(3, 3, 4, 1), range)).toBeGreaterThan(0)\n  })\n\n  test('adjustRangePosition', () => {\n    let pos = Position.create(3, 3)\n    expect(positions.adjustRangePosition(Range.create(0, 0, 1, 0), pos)).toEqual(Range.create(3, 3, 4, 0))\n  })\n\n  test('rangeInRange', () => {\n    let pos = Position.create(0, 0)\n    let r = Range.create(pos, pos)\n    expect(positions.rangeInRange(r, r)).toBe(true)\n    expect(positions.rangeInRange(r, Range.create(addPosition(pos, 1, 0), pos))).toBe(false)\n    expect(positions.rangeInRange(Range.create(0, 1, 0, 1), Range.create(0, 0, 0, 1))).toBe(true)\n  })\n\n  test('rangeOverlap', () => {\n    let r = Range.create(0, 0, 0, 0)\n    expect(positions.rangeOverlap(r, Range.create(0, 0, 0, 0))).toBe(false)\n    expect(positions.rangeOverlap(Range.create(0, 0, 0, 10), Range.create(0, 1, 0, 2))).toBe(true)\n    expect(positions.rangeOverlap(Range.create(0, 0, 0, 1), Range.create(0, 1, 0, 2))).toBe(false)\n    expect(positions.rangeOverlap(Range.create(0, 1, 0, 2), Range.create(0, 0, 0, 1))).toBe(false)\n    expect(positions.rangeOverlap(Range.create(0, 0, 0, 1), Range.create(0, 2, 0, 3))).toBe(false)\n  })\n\n  test('rangeAdjacent', () => {\n    let r = Range.create(1, 1, 1, 2)\n    expect(positions.rangeAdjacent(r, Range.create(0, 0, 0, 0))).toBe(false)\n    expect(positions.rangeAdjacent(r, Range.create(1, 1, 1, 3))).toBe(false)\n    expect(positions.rangeAdjacent(r, Range.create(0, 0, 1, 1))).toBe(true)\n    expect(positions.rangeAdjacent(r, Range.create(1, 2, 1, 4))).toBe(true)\n  })\n\n  test('positionInRange', () => {\n    let pos = Position.create(0, 0)\n    let r = Range.create(pos, pos)\n    expect(positions.positionInRange(pos, r)).toBe(0)\n    pos = Position.create(0, 1)\n    r = Range.create(0, 0, 0, 3)\n    expect(positions.positionInRange(pos, r)).toBe(0)\n  })\n\n  test('comparePosition', () => {\n    let pos = Position.create(0, 0)\n    expect(positions.comparePosition(pos, pos)).toBe(0)\n  })\n\n  test('should get start end position by content', () => {\n    expect(positions.getEnd(Position.create(0, 0), 'foo')).toEqual({ line: 0, character: 3 })\n    expect(positions.getEnd(Position.create(0, 1), 'foo\\nbar')).toEqual({ line: 1, character: 3 })\n  })\n\n  test('isSingleLine', () => {\n    let pos = Position.create(0, 0)\n    let r = Range.create(pos, pos)\n    expect(positions.isSingleLine(r)).toBe(true)\n  })\n\n  test('toValidRange', () => {\n    expect(positions.toValidRange(Range.create(1, 0, 0, 1))).toEqual(Range.create(0, 1, 1, 0))\n    expect(positions.toValidRange({\n      start: { line: -1, character: -1 },\n      end: { line: -1, character: -1 },\n    })).toEqual(Range.create(0, 0, 0, 0))\n  })\n\n})\n\ndescribe('utility', () => {\n\n  it('should not throw for invalid ms', async () => {\n    await wait(-1)\n  })\n\n  it('should disposeAll', () => {\n    disposeAll([undefined, undefined])\n  })\n\n  it('should wait with token', async () => {\n    let res = await waitWithToken(1, CancellationToken.None)\n    expect(res).toBe(false)\n    let tokenSource = new CancellationTokenSource()\n    let token = tokenSource.token\n    let p = waitWithToken(200, token)\n    await wait(1)\n    tokenSource.cancel()\n    res = await p\n    expect(res).toBe(true)\n    res = await waitWithToken(10, CancellationToken.Cancelled)\n    expect(res).toBe(true)\n    res = await waitWithToken(0, CancellationToken.None)\n    expect(res).toBe(true)\n  })\n\n  it('should check executable', () => {\n    let res = executable('command_not_exists')\n    expect(res).toBe(false)\n  })\n\n  it('should check isRunning', () => {\n    expect(isRunning(process.pid)).toBe(true)\n    let spy = jest.spyOn(process, 'kill').mockImplementation(() => {\n      let e = new Error() as any\n      e.code = 'EPERM'\n      throw e\n    })\n    expect(isRunning(process.pid)).toBe(true)\n    spy.mockRestore()\n  })\n\n  it('should run command on windows', async () => {\n    await runCommand('echo 1')\n    await runCommand('echo 1', { cwd: __dirname }, 1, true)\n  })\n\n  it('should run command with timeout', async () => {\n    await expect(async () => {\n      await runCommand('sleep 2', { cwd: __dirname }, 0.01)\n    }).rejects.toThrow(errors.CancellationError)\n  })\n\n  it('should run command with Cancellation token', async () => {\n    let tokenSource = new CancellationTokenSource()\n    let token = tokenSource.token\n    setTimeout(() => {\n      tokenSource.cancel()\n    }, 20)\n    await expect(async () => {\n      await runCommand('sleep 2', { cwd: __dirname, encoding: 'unknown' }, token)\n    }).rejects.toThrow(errors.CancellationError)\n  })\n\n  it('should run command with encoding support', async () => {\n    let res = await runCommand('echo \"\\\\xc4\\\\xe3\\\\x0a\"', { cwd: __dirname, encoding: 'cp936' }, 1, true)\n    expect(res.length).toBeGreaterThan(0)\n  })\n\n  it('should throw on command error', async () => {\n    await expect(async () => {\n      await runCommand('command_not_exists', { cwd: __dirname })\n    }).rejects.toThrow(Error)\n  })\n\n  it('should resolve concurrent with empty task', async () => {\n    let fn = jest.fn()\n    await concurrent([], fn, 3)\n    expect(fn).toHaveBeenCalledTimes(0)\n  })\n\n  it('should run concurrent', async () => {\n    let res: number[] = []\n    let fn = (n: number): Promise<void> => {\n      return new Promise(resolve => {\n        setTimeout(() => {\n          res.push(n)\n          resolve()\n        }, n * 10)\n      })\n    }\n    let arr = [5, 4, 3, 6, 8]\n    let ts = Date.now()\n    await concurrent(arr, fn, 3)\n    let dt = Date.now() - ts\n    expect(dt).toBeGreaterThanOrEqual(100)\n    expect(res).toEqual([3, 4, 5, 6, 8])\n  })\n\n  it('should delay function #1', () => {\n    let times = 0\n    let fn = () => {\n      times++\n    }\n    let delied = delay(fn, 50)\n    delied()\n    delied(100)\n    expect(times).toBe(0)\n    delied.clear()\n  })\n\n  it('should delay function #2', async () => {\n    let times = 0\n    let fn = () => {\n      times++\n    }\n    let delied = delay(fn, 50)\n    delied(100)\n    delied(10)\n    await helper.wait(50)\n    expect(times).toBe(1)\n  })\n})\n\ndescribe('fuzzy match test', () => {\n  it('should be fuzzy match', () => {\n    let needle = 'aBc'\n    let codes = fuzzy.getCharCodes(needle)\n    expect(fuzzy.fuzzyMatch(codes, 'abc')).toBeFalsy()\n    expect(fuzzy.fuzzyMatch(codes, 'ab')).toBeFalsy()\n    expect(fuzzy.fuzzyMatch(codes, 'addbdd')).toBeFalsy()\n    expect(fuzzy.fuzzyMatch(codes, 'abbbBc')).toBeTruthy()\n    expect(fuzzy.fuzzyMatch(codes, 'daBc')).toBeTruthy()\n    expect(fuzzy.fuzzyMatch(codes, 'ABCz')).toBeTruthy()\n    expect(fuzzy.fuzzyMatch(codes, 'axy')).toBeFalsy()\n  })\n\n  it('should be fuzzy for character', () => {\n    expect(fuzzy.fuzzyChar('a', 'a')).toBeTruthy()\n    expect(fuzzy.fuzzyChar('a', 'A')).toBeTruthy()\n    expect(fuzzy.fuzzyChar('z', 'z')).toBeTruthy()\n    expect(fuzzy.fuzzyChar('z', 'Z')).toBeTruthy()\n    expect(fuzzy.fuzzyChar('A', 'a')).toBeFalsy()\n    expect(fuzzy.fuzzyChar('A', 'A')).toBeTruthy()\n    expect(fuzzy.fuzzyChar('Z', 'z')).toBeFalsy()\n    expect(fuzzy.fuzzyChar('Z', 'Z')).toBeTruthy()\n    expect(fuzzy.fuzzyChar('Z', 'z', true)).toBeTruthy()\n    expect(fuzzy.fuzzyChar('i', 'İ')).toBeTruthy()\n    expect(fuzzy.fuzzyChar('a', 'İ')).toBeFalsy()\n    expect(fuzzy.fuzzyChar('i', 'İ', true)).toBeTruthy()\n    expect(fuzzy.fuzzyChar('İ', 'i')).toBeFalsy()\n    expect(fuzzy.fuzzyChar('İ', 'i', true)).toBeTruthy()\n    expect(fuzzy.fuzzyChar('Ᾰ', 'ᾰ', true)).toBeTruthy()\n    expect(fuzzy.fuzzyChar('ᾰ', 'Ᾰ')).toBeTruthy()\n  })\n})\n\ndescribe('object test', () => {\n  it('mixin should recursive', () => {\n    let res = objects.mixin({ a: { b: 1 } }, { a: { c: 2 }, d: 3 })\n    expect(res.a.b).toBe(1)\n    expect(res.a.c).toBe(2)\n    expect(res.d).toBe(3)\n    res = objects.mixin({}, true)\n    expect(res).toEqual({})\n    res = objects.mixin({ x: 1 }, { x: 2 }, false)\n    expect(res).toEqual({ x: 1 })\n    res = objects.mixin(Date, {})\n    expect(res).toEqual({})\n    res = objects.mixin({ x: 3, y: new Date() }, { y: 4 }, true)\n    expect(res).toEqual({ x: 3, y: 4 })\n  })\n\n  it('should deep clone', () => {\n    let re = new RegExp('a', 'g')\n    expect(objects.deepClone(re)).toBe(re)\n  })\n\n  it('should change to readonly', () => {\n    let obj = { x: 1 }\n    let res = objects.toReadonly(obj)\n    let fn = () => {\n      res.x = 3\n    }\n    expect(fn).toThrow()\n  })\n\n  it('should not deep freeze', () => {\n    objects.deepFreeze(false)\n    objects.deepFreeze(true)\n  })\n\n  it('should check equals', () => {\n    expect(objects.equals(false, 1)).toBe(false)\n    expect(objects.equals([1], {})).toBe(false)\n    expect(objects.equals([1, 2], [1, 3])).toBe(false)\n  })\n\n  it('should check empty object', () => {\n    expect(objects.isEmpty({})).toBe(true)\n    expect(objects.isEmpty([])).toBe(true)\n    expect(objects.isEmpty(null)).toBe(true)\n    expect(objects.isEmpty({ x: 1 })).toBe(false)\n  })\n\n  it('should omit null and undefined properties', () => {\n    expect(objects.omitNullUndefined({ a: 1, b: null, c: undefined, d: \"text\" })).toEqual({ a: 1, d: 'text' })\n  })\n\n  it('should deepIterate', () => {\n    let obj = {\n      x: 1,\n      $ref: '#1',\n      items: [{\n        obj: [{\n          y: 2,\n          $ref: '#2'\n        }, 4]\n      }, {\n        $ref: '#3'\n      }]\n    }\n    let vals: string[] = []\n    objects.deepIterate(obj, (obj, key) => {\n      if (key === '$ref') {\n        vals.push(obj[key])\n      }\n    })\n    expect(vals).toEqual(['#1', '#2', '#3'])\n  })\n})\n\ndescribe('ansiparse', () => {\n  it('ansiparse #1', () => {\n    let str = '\\u001b[33mText\\u001b[mnormal'\n    let res = ansiparse(str)\n    expect(res).toEqual([{\n      foreground: 'yellow', text: 'Text'\n    }, {\n      text: 'normal'\n    }])\n  })\n\n  it('ansiparse #2', () => {\n    let str = '\\u001b[33m\\u001b[mText'\n    let res = ansiparse(str)\n    expect(res).toEqual([\n      { foreground: 'yellow', text: '' },\n      { text: 'Text' }])\n  })\n\n  it('ansiparse #3', () => {\n    let str = 'this.\\u001b[0m\\u001b[31m\\u001b[1mhistory\\u001b[0m.add()'\n    let res = ansiparse(str)\n    expect(res[1]).toEqual({\n      foreground: 'red',\n      bold: true, text: 'history'\n    })\n  })\n})\n\ndescribe('Mutex', () => {\n  it('mutex run in serial', async () => {\n    let lastTs: number\n    let fn = () => new Promise<void>(resolve => {\n      if (lastTs) {\n        let dt = Date.now() - lastTs\n        expect(dt).toBeGreaterThanOrEqual(2)\n      }\n      lastTs = Date.now()\n      setTimeout(() => {\n        resolve()\n      }, 3)\n    })\n    let mutex = new Mutex()\n    await Promise.all([\n      mutex.use(fn),\n      mutex.use(fn),\n      mutex.use(fn)\n    ])\n  })\n\n  it('mutex run after job finish', async () => {\n    let count = 0\n    let fn = () => new Promise<void>(resolve => {\n      count = count + 1\n      setTimeout(() => {\n        resolve()\n      }, 10)\n    })\n    let mutex = new Mutex()\n    await mutex.use(fn)\n    await helper.wait(1)\n    await mutex.use(fn)\n    expect(count).toBe(2)\n  })\n\n  it('should release on reject', async () => {\n    let mutex = new Mutex()\n    let err\n    try {\n      await mutex.use(() => {\n        return Promise.reject(new Error('err'))\n      })\n    } catch (e) {\n      err = e\n    }\n    expect(err).toBeDefined()\n    expect(mutex.busy).toBe(false)\n  })\n})\n\ndescribe('Sequence', () => {\n  it('should run sequence', async () => {\n    let s = new Sequence()\n    let res: number[] = []\n    s.run(async () => {\n      await helper.wait(3)\n      res.push(0)\n    })\n    s.run(async () => {\n      await helper.wait(2)\n      res.push(1)\n    })\n    s.run(async () => {\n      await helper.wait(1)\n      res.push(2)\n    })\n    await s.waitFinish()\n    expect(res).toEqual([0, 1, 2])\n  })\n\n  it('should cancel sequence', async () => {\n    let s = new Sequence()\n    let res: number[] = []\n    s.run(async () => {\n      await helper.wait(10)\n      res.push(0)\n    })\n    s.run(async () => {\n      await helper.wait(20)\n      res.push(1)\n    })\n    s.cancel()\n    await s.waitFinish()\n    expect(res).toEqual([])\n  })\n})\n\ndescribe('terminate', () => {\n  it('should terminate process', async () => {\n    let cwd = process.cwd()\n    let child = spawn('sleep', ['3'], { cwd, detached: true })\n    let res = terminate(child, cwd)\n    expect(res).toBe(true)\n    await helper.waitValue(() => {\n      return child.connected\n    }, false)\n    terminate(child, cwd)\n    terminate({ killed: true } as any, cwd)\n  })\n\n  it('should terminate on other platform', () => {\n    let child = spawn('ls', [], { detached: true })\n    let res = terminate(child, process.cwd(), platform.Platform.Windows)\n    expect(res).toBe(false)\n    res = terminate(child, undefined, platform.Platform.Windows)\n    expect(res).toBe(false)\n    res = terminate(child, process.cwd(), platform.Platform.Unknown)\n    expect(res).toBe(true)\n    let spy: any = jest.spyOn(cp, 'execFileSync').mockImplementation(() => {\n      return undefined\n    })\n    child = spawn('ls', [], { detached: true })\n    res = terminate(child, process.cwd(), platform.Platform.Windows)\n    expect(res).toBe(true)\n    spy.mockRestore()\n    spy = jest.spyOn(cp, 'spawnSync').mockImplementation(() => {\n      throw new Error('bad')\n    })\n    child = spawn('ls', [], { detached: true })\n    res = terminate(child, process.cwd(), platform.Platform.Linux)\n    expect(res).toBe(false)\n    spy.mockRestore()\n    spy = jest.spyOn(cp, 'spawnSync').mockImplementation(() => {\n      return { error: new Error('bad') } as any\n    })\n    child = spawn('ls', [], { detached: true })\n    res = terminate(child, process.cwd(), platform.Platform.Linux)\n    expect(res).toBe(false)\n    spy.mockRestore()\n  })\n})\n\ndescribe('diff', () => {\n  describe('diff lines', () => {\n    function diffLines(oldStr: string, newStr: string): diff.ChangedLines {\n      let oldLines = oldStr.split('\\n')\n      return diff.diffLines(oldLines, newStr.split('\\n'), oldLines.length - 2)\n    }\n\n    it('should consider new line insert on insert mode', async () => {\n      let res = diff.getTextEdit(['abc', ''], ['abc', '', ''], Position.create(1, 0), true)\n      expect(res).toEqual(toEdit(0, 3, 0, 3, '\\n'))\n    })\n\n    it('should get textedit without cursor', () => {\n      let res = diff.getTextEdit(['a', 'b'], ['a', 'b'])\n      expect(res).toBeUndefined()\n      res = diff.getTextEdit(['a', 'b'], ['a', 'b'], Position.create(0, 0))\n      expect(res).toBeUndefined()\n      res = diff.getTextEdit(['a', 'b'], ['a', 'b', 'c'])\n      expect(res).toEqual(toEdit(2, 0, 2, 0, 'c\\n'))\n      res = diff.getTextEdit(['a', 'b', 'c'], ['a'])\n      expect(res).toEqual(toEdit(1, 0, 3, 0, ''))\n      res = diff.getTextEdit(['a', 'b'], ['a', 'd'])\n      expect(res).toEqual(toEdit(1, 0, 1, 1, 'd'))\n      res = diff.getTextEdit(['a', 'b'], ['a', 'd', 'e'])\n      expect(res).toEqual(toEdit(1, 0, 1, 1, 'd\\ne'))\n      res = diff.getTextEdit(['a', 'b', 'e'], ['a', 'd', 'e'])\n      expect(res).toEqual(toEdit(1, 0, 1, 1, 'd'))\n      res = diff.getTextEdit(['a', 'b', 'e'], ['e'])\n      expect(res).toEqual(toEdit(0, 0, 2, 0, ''))\n      res = diff.getTextEdit(['a', 'b', 'e'], ['d', 'c', 'a', 'b', 'e'])\n      expect(res).toEqual(toEdit(0, 0, 0, 0, 'd\\nc\\n'))\n      res = diff.getTextEdit(['a', 'b'], ['a', 'b', ''])\n      expect(res).toEqual(toEdit(2, 0, 2, 0, '\\n'))\n      res = diff.getTextEdit(['a', 'b'], ['a', 'b', '', ''])\n      expect(res).toEqual(toEdit(2, 0, 2, 0, '\\n\\n'))\n    })\n\n    it('should reduceTextEdit', () => {\n      let res = diff.reduceReplaceEdit(TextEdit.replace(Range.create(0, 0, 3, 1), 'abd'), 'a\\nb\\nc\\nd', Position.create(0, 1))\n      expect(res).toEqual(TextEdit.replace(Range.create(0, 1, 3, 0), 'b'))\n      res = diff.reduceReplaceEdit(TextEdit.replace(Range.create(3, 1, 3, 9), ' '.repeat(5)), ' '.repeat(8), Position.create(3, 3))\n      expect(res).toEqual(TextEdit.replace(Range.create(3, 3, 3, 6), ''))\n      res = diff.reduceReplaceEdit(TextEdit.replace(Range.create(3, 1, 3, 4), ' '.repeat(5)), ' '.repeat(3), Position.create(3, 3))\n      expect(res).toEqual(TextEdit.replace(Range.create(3, 1, 3, 1), '  '))\n      res = diff.reduceReplaceEdit(TextEdit.replace(Range.create(3, 1, 3, 4), 'x'.repeat(5)), ' '.repeat(3), Position.create(3, 3))\n      expect(res).toEqual(TextEdit.replace(Range.create(3, 1, 3, 4), 'x'.repeat(5)))\n      res = diff.reduceReplaceEdit(TextEdit.replace(Range.create(1, 0, 2, 0), 'd\\n'), 'b\\n')\n      expect(res).toEqual(TextEdit.replace(Range.create(1, 0, 1, 1), 'd'))\n    })\n\n    it('should get textedit for single line change', () => {\n      let res = diff.getTextEdit(['foo', 'c'], ['', 'c'], Position.create(0, 0), false)\n      expect(res).toEqual(toEdit(0, 0, 0, 3, ''))\n      res = diff.getTextEdit([''], ['foo'], Position.create(0, 0), false)\n      expect(res).toEqual(toEdit(0, 0, 0, 0, 'foo'))\n      res = diff.getTextEdit(['foo bar'], ['foo r'], Position.create(0, 4), false)\n      expect(res).toEqual(toEdit(0, 4, 0, 6, ''))\n      res = diff.getTextEdit(['f'], ['foo f'], Position.create(0, 0), false)\n      expect(res).toEqual(toEdit(0, 0, 0, 0, 'foo '))\n      res = diff.getTextEdit([' foo '], [' bar '], Position.create(0, 0), false)\n      expect(res).toEqual(toEdit(0, 1, 0, 4, 'bar'))\n      res = diff.getTextEdit(['foo'], ['bar'], Position.create(0, 0), true)\n      expect(res).toEqual(toEdit(0, 0, 0, 3, 'bar'))\n      res = diff.getTextEdit(['aa'], ['aaaa'], Position.create(0, 1), true)\n      expect(res).toEqual(toEdit(0, 0, 0, 0, 'aa'))\n    })\n\n    it('should diff changed lines', () => {\n      let res = diffLines('a\\n', 'b\\n')\n      expect(res).toEqual({ start: 0, end: 1, replacement: ['b'] })\n      res = diff.diffLines(['a', 'b'], ['c', 'd', 'a', 'b'], -1)\n      expect(res).toEqual({ start: 0, end: 0, replacement: ['c', 'd'] })\n    })\n\n    it('should diff added lines', () => {\n      let res = diffLines('a\\n', 'a\\nb\\n')\n      expect(res).toEqual({\n        start: 1,\n        end: 1,\n        replacement: ['b']\n      })\n    })\n\n    it('should diff remove lines', () => {\n      let res = diffLines('a\\n\\n', 'a\\n')\n      expect(res).toEqual({\n        start: 1,\n        end: 2,\n        replacement: []\n      })\n    })\n\n    it('should diff remove multiple lines', () => {\n      let res = diffLines('a\\n\\n\\n', 'a\\n')\n      expect(res).toEqual({\n        start: 1,\n        end: 3,\n        replacement: []\n      })\n    })\n\n    it('should diff removed line', () => {\n      let res = diffLines('a\\n\\n\\nb', 'a\\n\\nb')\n      expect(res).toEqual({\n        start: 2,\n        end: 3,\n        replacement: []\n      })\n    })\n\n    it('should reduce changed lines', () => {\n      let res = diff.diffLines(['a', 'b', 'c'], ['a', 'b', 'c', 'd'], 0)\n      expect(res).toEqual({\n        start: 3,\n        end: 3,\n        replacement: ['d']\n      })\n    })\n  })\n\n  describe('get common prefix & suffix', () => {\n    it('should getCommonPrefixLen', () => {\n      expect(diff.getCommonPrefixLen('aa', 'abc', 0)).toBe(0)\n      expect(diff.getCommonPrefixLen(' '.repeat(5), ' '.repeat(10), 4)).toBe(4)\n      expect(diff.getCommonPrefixLen('xy', 'dy', 2)).toBe(0)\n    })\n\n    it('should getCommonSuffixLen', () => {\n      expect(diff.getCommonSuffixLen('aa', 'aa', 0)).toBe(0)\n      expect(diff.getCommonSuffixLen('aa', 'ab', 2)).toBe(0)\n      expect(diff.getCommonSuffixLen(' '.repeat(3), ' '.repeat(5), 2)).toBe(2)\n    })\n  })\n\n  describe('patch line', () => {\n    it('should patch line', () => {\n      let res = diff.patchLine('foo', 'bar foo bar')\n      expect(res.length).toBe(7)\n      expect(res).toBe('    foo')\n      res = diff.patchLine('foo', 'foo')\n      expect(res).toBe('foo')\n      res = diff.patchLine('foo', 'oo')\n      expect(res).toBe('oo')\n    })\n  })\n\n  function blockMilliseconds(ms: number): void {\n    let ts = Date.now()\n    let i = 0\n    while (true) {\n      if (Date.now() - ts > ms) {\n        break\n      }\n      i++\n    }\n  }\n\n  describe('async', () => {\n    it('should do filter', async () => {\n      await filter([], () => true, () => {})\n      await filter([{ label: 'a' }, { label: 'b' }, { label: 'c' }], v => {\n        return { code: v.label.charCodeAt(0) }\n      }, (items, done) => {\n        expect(items.length).toBe(3)\n        expect(done).toBe(true)\n      })\n      let n = 0\n      let res: string[] = []\n      let finished: boolean\n      await filter<string>(['a', 'b', 'c'], () => {\n        blockMilliseconds(30)\n        return true\n      }, (items, done) => {\n        n++\n        res.push(...items)\n        finished = done\n      })\n      expect(n).toBe(3)\n      expect(res).toEqual(['a', 'b', 'c'])\n      expect(finished).toEqual(true)\n    })\n\n    it('should cancel filter when possible', async () => {\n      let tokenSource = new CancellationTokenSource()\n      let token = tokenSource.token\n      process.nextTick(() => {\n        tokenSource.cancel()\n      })\n      await filter([1, 2, 3, 4, 5, 6, 7, 8], i => {\n        if (i > 1) {\n          let ts = Date.now()\n          while (true) {\n            if (Date.now() - ts > 40) break\n          }\n        }\n        return true\n      }, (_, done) => {\n        expect(done).toBeFalsy()\n      }, token)\n    })\n\n    it('should perform async forEach', async () => {\n      await forEach([], () => {})\n      let res = []\n      await forEach([1, 2], x => res.push(x))\n      expect(res).toEqual([1, 2])\n      let result = []\n      const items = Array(1024 * 20).fill(1) // 足够大的数组以确保会yield\n      await forEach(items, () => result.push(helper.generateRandomHash()), undefined, { yieldAfter: 5 })\n      expect(result.length).toBe(items.length)\n      result = []\n      await forEach(items, () => result.push(helper.generateRandomHash()), undefined)\n      expect(result.length).toBe(items.length)\n      // it should cancel with callback called.\n      let tokenSource = new CancellationTokenSource()\n      let token = tokenSource.token\n      let called = false\n      let cb = () => {\n        tokenSource.cancel()\n        called = true\n      }\n      result = []\n      await forEach(items, () => result.push(helper.generateRandomHash()), token, { yieldAfter: 1, yieldCallback: cb })\n      expect(called).toBe(true)\n      expect(result.length).toBeLessThan(items.length)\n    })\n\n    it('should map with empty array should return empty array', async () => {\n      const result = await map([], x => x * 2)\n      expect(result).toEqual([])\n    })\n\n    it('should map correct transform items', async () => {\n      const items = Array(1024 * 20).fill(1) // 足够大的数组以确保会yield\n      const result = await map(items, () => helper.generateRandomHash())\n      expect(result.length).toBe(items.length)\n    })\n\n    it('should map yieldCallback when yielding', async () => {\n      const items = Array(1024 * 20).fill(1) // 足够大的数组以确保会yield\n      let tokenSource = new CancellationTokenSource()\n      let token = tokenSource.token\n      let called = false\n      let cb = () => {\n        tokenSource.cancel()\n        called = true\n      }\n      const options: YieldOptions = { yieldCallback: cb, yieldAfter: 5 }\n      await map(items, x => helper.generateRandomHash(), token, options)\n      expect(called).toBe(true)\n    })\n\n    it('should cancel on map', async () => {\n      let total = 1024 * 1024\n      const items = Array(total).fill(1) // 足够大的数组以确保会yield\n      let tokenSource = new CancellationTokenSource()\n      let token = tokenSource.token\n      process.nextTick(() => {\n        tokenSource.cancel()\n      }, 0)\n      let res = await map(items, x => helper.generateRandomHash(), token)\n      expect(res[res.length - 1]).toBeUndefined()\n    })\n  })\n\n  describe('timing', () => {\n    it('should trace', async () => {\n      let t = createTiming('name', 1)\n      t.start()\n      t.start('label')\n      await helper.wait(10)\n      t.stop()\n      t.start()\n      t.stop()\n    })\n\n    it('should no timeout', () => {\n      let t = createTiming('name')\n      t.start()\n      t.stop()\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/window.test.ts",
    "content": "import { Buffer, Neovim } from '@chemzqm/neovim'\nimport { HighlightItem } from '@chemzqm/neovim/lib/api/Buffer'\nimport { CancellationToken, Disposable, Emitter } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport { convertHighlightItem } from '../../core/highlights'\nimport events from '../../events'\nimport Notification, { toButtons, toTitles } from '../../model/notification'\nimport { formatMessage } from '../../model/progress'\nimport { TreeItem, TreeItemCollapsibleState } from '../../tree'\nimport { disposeAll } from '../../util'\nimport window, { Window } from '../../window'\nimport workspace from '../../workspace'\nimport helper, { createTmpFile } from '../helper'\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\n\ninterface FileNode {\n  filepath: string\n  isFolder?: boolean\n}\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  await helper.reset()\n  disposeAll(disposables)\n  disposables = []\n})\n\ndescribe('window', () => {\n  describe('functions', () => {\n    it('should formatMessage', () => {\n      expect(Window).toBeDefined()\n      expect(formatMessage('a', 'b', 1)).toBe('a b 1%')\n      expect(formatMessage(undefined, undefined, 1)).toBe('1%')\n      expect(formatMessage('a', undefined, 0)).toBe('a')\n    })\n\n    it('should convert highlight item', () => {\n      let res = convertHighlightItem({\n        colStart: 0,\n        colEnd: 1,\n        hlGroup: 'Search',\n        lnum: 0,\n        combine: true\n      })\n      expect(res).toEqual(['Search', 0, 0, 1, 1, 0, 0])\n    })\n\n    it('should get offset', async () => {\n      let buf = await nvim.buffer\n      await nvim.call('setline', [buf.id, ['bar', 'foo']])\n      await nvim.call('cursor', [2, 2])\n      let n = await window.getOffset()\n      expect(n).toBe(5)\n    })\n\n    it('should get cursor screen position', async () => {\n      let pos = await window.getCursorScreenPosition()\n      expect(pos).toEqual({ row: 0, col: 0 })\n    })\n\n    it('should export terminals', async () => {\n      expect(Array.isArray(window.terminals)).toBe(true)\n      expect(window.onDidOpenTerminal).toBeDefined()\n      expect(window.onDidCloseTerminal).toBeDefined()\n    })\n\n    it('should selected range', async () => {\n      await nvim.setLine('foobar')\n      await nvim.command('normal! viw')\n      await nvim.eval(`feedkeys(\"\\\\<Esc>\", 'in')`)\n      let range = await window.getSelectedRange('v')\n      expect(range).toEqual({ start: { line: 0, character: 0 }, end: { line: 0, character: 6 } })\n    })\n\n    it('should run terminal command', async () => {\n      let res = await window.runTerminalCommand('ls', __dirname)\n      expect(res.success).toBe(true)\n      res = await window.runTerminalCommand('echo 1', process.cwd(), true)\n      expect(res.success).toBe(true)\n    })\n\n    it('should open temimal buffer', async () => {\n      let bufnr = await window.openTerminal('ls', { autoclose: false, keepfocus: false })\n      let curr = await nvim.eval('bufnr(\"%\")')\n      expect(curr).toBe(bufnr)\n      let buftype = await nvim.eval('&buftype')\n      expect(buftype).toBe('terminal')\n    })\n\n    it('should create float factory', async () => {\n      helper.updateConfiguration('coc.preferences.excludeImageLinksInMarkdownDocument', false)\n      helper.updateConfiguration('floatFactory.floatConfig', {\n        winblend: 10,\n        rounded: true,\n        border: true,\n        close: true\n      })\n      let f = window.createFloatFactory({ modes: ['n', 'i'] })\n      await f.show([{ content: 'content', filetype: 'txt' }])\n      let win = await helper.getFloat()\n      expect(win).toBeDefined()\n      let id = await nvim.call('coc#float#get_related', [win.id, 'border', 0]) as number\n      expect(id).toBeGreaterThan(0)\n    })\n\n    it('should createStatusBarItem', async () => {\n      let item = window.createStatusBarItem(1, { progress: true })\n      item.text = 'test'\n      item.show()\n      expect(item.text).toBe('test')\n      expect(item.isProgress).toBe(true)\n      let other = window.createStatusBarItem()\n      other.text = 'bar'\n      other.show()\n      await helper.waitValue(async () => {\n        let res = await nvim.getVar('coc_status') as string\n        return res.includes('bar')\n      }, true)\n      item.hide()\n      item.dispose()\n      other.dispose()\n    })\n\n    it('should create outputChannel', () => {\n      let channel = window.createOutputChannel('channel')\n      expect(channel.name).toBe('channel')\n    })\n\n    it('should create TreeView instance', async () => {\n      let emitter = new Emitter<FileNode | undefined>()\n      let removed = false\n      let treeView = window.createTreeView('files', {\n        treeDataProvider: {\n          onDidChangeTreeData: emitter.event,\n          getChildren: root => {\n            if (root) return undefined\n            if (removed) return [{ filepath: '/foo/a', isFolder: true }]\n            return [{ filepath: '/foo/a', isFolder: true }, { filepath: '/foo/b.js' }]\n          },\n          getTreeItem: (node: FileNode) => {\n            let { filepath, isFolder } = node\n            return new TreeItem(URI.file(filepath), isFolder ? TreeItemCollapsibleState.Collapsed : TreeItemCollapsibleState.None)\n          },\n        }\n      })\n      disposables.push(emitter)\n      disposables.push(treeView)\n      await treeView.show()\n      let filetype = await nvim.eval('&filetype')\n      expect(filetype).toBe('coctree')\n    })\n\n    it('should show outputChannel', async () => {\n      window.createOutputChannel('channel')\n      window.showOutputChannel('channel')\n      let buf = await nvim.buffer\n      let name = await buf.name\n      expect(name).toMatch('channel')\n    })\n\n    it('should not show channel not exists', async () => {\n      let buf = await nvim.buffer\n      let bufnr = buf.id\n      window.showOutputChannel('NONE', 'edit')\n      await helper.wait(10)\n      buf = await nvim.buffer\n      expect(buf.id).toBe(bufnr)\n    })\n\n    it('should get cursor position', async () => {\n      await nvim.setLine('       ')\n      await nvim.call('cursor', [1, 3])\n      let pos = await window.getCursorPosition()\n      expect(pos).toEqual({\n        line: 0,\n        character: 2\n      })\n    })\n\n    it('should moveTo position in insert mode', async () => {\n      await nvim.setLine('foo')\n      await nvim.input('i')\n      await window.moveTo({ line: 0, character: 3 })\n      let col = await nvim.call('col', '.')\n      expect(col).toBe(4)\n      let virtualedit = await nvim.getOption('virtualedit')\n      expect(virtualedit).toBe('')\n    })\n\n    it('should choose quickpick', async () => {\n      let p = window.showQuickpick(['a', 'b'])\n      await helper.waitPrompt()\n      await nvim.input('1')\n      await nvim.input('<CR>')\n      let res = await p\n      expect(res).toBe(0)\n    })\n\n    it('should cancel quickpick', async () => {\n      let p = window.showQuickpick(['a', 'b'])\n      await helper.waitPrompt()\n      await nvim.input('<esc>')\n      let res = await p\n      expect(res).toBe(-1)\n    })\n\n    it('should show prompt', async () => {\n      let p = window.showPrompt('prompt')\n      await helper.wait(50)\n      await nvim.input('y')\n      let res = await p\n      expect(res).toBe(true)\n    })\n\n    it('should show dialog', async () => {\n      let dialog = await window.showDialog({ content: 'foo' })\n      let winid = await dialog.winid\n      expect(winid).toBeDefined()\n      expect(winid).toBeGreaterThan(1000)\n    })\n\n    it('should show menu', async () => {\n      let p = window.showMenuPicker(['a', 'b', 'c'], 'choose item')\n      await helper.waitValue(async () => {\n        return await nvim.call('coc#float#has_float', [])\n      }, 1)\n      await nvim.input('2')\n      let res = await p\n      expect(res).toBe(1)\n      res = await window.showMenuPicker(['foo'], { title: 'title', position: 'center' }, CancellationToken.Cancelled)\n      expect(res).toBe(-1)\n    })\n\n    it('should return select items for picker', async () => {\n      let curr = await nvim.call('win_getid')\n      let p = window.showPickerDialog(['foo', 'bar'], 'select')\n      await helper.waitFloat()\n      await helper.waitPrompt()\n      await nvim.input(' ')\n      await nvim.input('<cr>')\n      let res = await p\n      let winid = await nvim.call('win_getid')\n      expect(winid).toBe(curr)\n      expect(res).toEqual(['foo'])\n    })\n\n    it('should return undefined for picker', async () => {\n      let p = window.showPickerDialog(['foo', 'bar'], 'select')\n      await helper.waitFloat()\n      await helper.waitPrompt()\n      await nvim.input('<esc>')\n      let res = await p\n      expect(res).toBeUndefined()\n    })\n\n    it('should return undefined when cancelled', async () => {\n      let token = CancellationToken.Cancelled\n      let res = await window.showPickerDialog(['foo', 'bar'], 'select', token)\n      expect(res).toBeUndefined()\n    })\n\n    it('should get visible ranges of bufnr', async () => {\n      let buf = await helper.edit('not_exists')\n      let range = await window.getVisibleRanges(buf.id)\n      expect(range.length).toBe(1)\n      let winid = await nvim.call('win_getid') as number\n      range = await window.getVisibleRanges(buf.id, winid)\n      expect(range.length).toBe(1)\n      range = await window.getVisibleRanges(buf.id, 9999)\n      expect(range.length).toBe(0)\n      await nvim.command('enew')\n      range = await window.getVisibleRanges(buf.id)\n      expect(range.length).toBe(0)\n    })\n\n    it('should requestInputList', async () => {\n      Object.assign(workspace.env, { lines: 3 })\n      {\n        let p = window.requestInputList('prompt', ['foo', 'bar', 'abc', 'def'])\n        await helper.waitValue(async () => {\n          let m = await nvim.mode\n          return m.mode\n        }, 'c')\n        await nvim.input('1<cr>')\n        let res = await p\n        expect(res).toBe(0)\n      }\n      {\n        let p = window.requestInputList('prompt', ['foo', 'bar', 'abc', 'def'])\n        await helper.waitValue(async () => {\n          let m = await nvim.mode\n          return m.mode\n        }, 'c')\n        await nvim.input('8<cr>')\n        let res = await p\n        expect(res).toBe(-1)\n      }\n    })\n  })\n\n  describe('window showMessage', () => {\n    async function ensureNotification(idx: number): Promise<void> {\n      let winid = await helper.waitFloat()\n      await nvim.call('coc#notify#choose', [winid, idx])\n    }\n\n    it('should echo lines', async () => {\n      await window.echoLines(['a', 'b'])\n      let ch = await nvim.call('screenchar', [79, 1]) as number\n      let s = String.fromCharCode(ch)\n      expect(s).toBe('a')\n    })\n\n    it('should echo multiple lines with truncate', async () => {\n      await window.echoLines(['a', 'b'.repeat(99), 'd', 'e'], true)\n      let ch = await nvim.call('screenchar', [79, 1]) as number\n      let s = String.fromCharCode(ch)\n      expect(s).toBe('a')\n      await window.echoLines(['a', 'b'.repeat(200)], true)\n    })\n\n    it('should show messages', async () => {\n      window.showMessage('more')\n      window.showMessage('error', 'error')\n      window.showMessage('warning', 'warning')\n      window.showMessage('moremsg', 'more')\n    })\n\n    it('should show message item', async () => {\n      helper.updateConfiguration('coc.preferences.enableMessageDialog', true)\n      let p = window.showInformationMessage('information message', { title: 'first' }, { title: 'second' })\n      await ensureNotification(0)\n      let res = await p\n      expect(res).toEqual({ title: 'first' })\n      res = await window.showInformationMessage('information message')\n      expect(res).toBeUndefined()\n    })\n\n    it('should show warning message', async () => {\n      helper.updateConfiguration('coc.preferences.enableMessageDialog', true)\n      let p = window.showWarningMessage('warning message', 'first', 'second')\n      await ensureNotification(1)\n      let res = await p\n      expect(res).toBe('second')\n    })\n\n    it('should show error message', async () => {\n      helper.updateConfiguration('coc.preferences.enableMessageDialog', true)\n      let p = window.showErrorMessage('error message', 'first', 'second')\n      await ensureNotification(0)\n      let res = await p\n      expect(res).toBe('first')\n    })\n\n    it('should show confirm for message', async () => {\n      helper.updateConfiguration('coc.preferences.enableMessageDialog', false)\n      let spy = jest.spyOn(nvim, 'call').mockImplementationOnce((method, _args) => {\n        expect(method).toBe('confirm')\n        return Promise.resolve('2') as any\n      })\n      let p = window.showInformationMessage('error message', 'first', 'second')\n      let res = await p\n      spy.mockRestore()\n      expect(res).toBe('second')\n    })\n\n    it('should use messageDialogKind for confirm mode', async () => {\n      helper.updateConfiguration('coc.preferences.messageDialogKind', 'confirm')\n      let spy = jest.spyOn(nvim, 'call').mockImplementationOnce((method, args) => {\n        expect(method).toBe('confirm')\n        expect(args[0]).toBe('test message')\n        expect(args[1]).toBe('1first\\n2second')\n        return Promise.resolve('1') as any\n      })\n      let p = window.showInformationMessage('test message', 'first', 'second')\n      let res = await p\n      spy.mockRestore()\n      expect(res).toBe('first')\n    })\n\n    it('should use messageDialogKind for menu mode', async () => {\n      helper.updateConfiguration('coc.preferences.messageDialogKind', 'menu')\n      let spy = jest.spyOn(window.dialogs, 'showMenuPicker').mockImplementation(() => {\n        return Promise.resolve(1) as any\n      })\n      let res = await window.notifications._showMessage('Warning', 'test message', ['first', 'second'])\n      expect(spy).toHaveBeenCalledWith(['first', 'second'], {\n        position: 'center',\n        content: 'test message',\n        title: 'Choose an action',\n        borderhighlight: 'CocWarningFloat'\n      })\n      expect(res).toBe('second')\n      spy.mockRestore()\n    })\n\n    it('should use messageDialogKind for notification mode', async () => {\n      helper.updateConfiguration('coc.preferences.messageDialogKind', 'notification')\n      let p = window.showInformationMessage('notification message', 'first', 'second')\n      await ensureNotification(0)\n      let res = await p\n      expect(res).toBe('first')\n    })\n\n    it('should echo error messages regardless of messageDialogKind', async () => {\n      helper.updateConfiguration('coc.preferences.messageDialogKind', 'menu')\n      let spy = jest.spyOn(window.notifications, 'echoMessages')\n      await window.showErrorMessage('error message')\n      expect(spy).toHaveBeenCalledWith('error message', 'error')\n      spy.mockRestore()\n    })\n\n    it('should echo messages without items regardless of messageDialogKind', async () => {\n      helper.updateConfiguration('coc.preferences.messageDialogKind', 'confirm')\n      let spy = jest.spyOn(window.notifications, 'echoMessages')\n      await window.showInformationMessage('info message')\n      expect(spy).toHaveBeenCalledWith('info message', 'more')\n      spy.mockRestore()\n    })\n\n    it('should echo messages without items when configured messageReportKind', async () => {\n      helper.updateConfiguration('coc.preferences.messageReportKind', 'echo')\n      let spy = jest.spyOn(window.notifications, 'echoMessages')\n      await window.showInformationMessage('info message')\n      expect(spy).toHaveBeenCalledWith('info message', 'more')\n      spy.mockRestore()\n    })\n\n    it('should use notification messages without items when configured messageReportKind', async () => {\n      helper.updateConfiguration('coc.preferences.messageReportKind', 'notification')\n      let spy = jest.spyOn(window.notifications, 'createNotification')\n      await window.showInformationMessage('info message')\n      expect(spy).toHaveBeenCalledWith('info', 'info message', [])\n      spy.mockRestore()\n    })\n\n    it('should handle unexpected messageReportKind', async () => {\n      helper.updateConfiguration('coc.preferences.messageReportKind', 'invalid')\n      let p = window.showInformationMessage('invalid info message')\n      await expect(p).rejects.toThrow('Unexpected messageReportKind: invalid')\n    })\n\n    it('should handle unexpected messageDialogKind', async () => {\n      helper.updateConfiguration('coc.preferences.messageDialogKind', 'invalid')\n      let p = window.showInformationMessage('test message', 'first', 'second')\n      await expect(p).rejects.toThrow('Unexpected messageDialogKind: invalid')\n    })\n\n    it('should respect enableMessageDialog for backward compatibility', async () => {\n      helper.updateConfiguration('coc.preferences.enableMessageDialog', true)\n      helper.updateConfiguration('coc.preferences.messageDialogKind', 'confirm')\n      let p = window.showInformationMessage('notification message', 'first', 'second')\n      await ensureNotification(0)\n      let res = await p\n      expect(res).toBe('first')\n    })\n  })\n\n  describe('window notifications', () => {\n    it('should toButtons', () => {\n      expect(toButtons(['foo', 'bar']).length).toBe(2)\n    })\n\n    it('should toTitles', () => {\n      expect(toTitles(['foo', 'bar']).length).toBe(2)\n      expect(toTitles([{ title: 'foo' }]).length).toBe(1)\n    })\n\n    it('should show notification with options', async () => {\n      await window.showNotification({\n        content: 'my notification',\n        title: 'title',\n      })\n      let ids = await nvim.call('coc#float#get_float_win_list') as number[]\n      expect(ids.length).toBe(1)\n      let win = nvim.createWindow(ids[0])\n      let kind = await win.getVar('kind')\n      expect(kind).toBe('notification')\n      let winid = await nvim.call('coc#float#get_related', [win.id, 'border'])\n      let bufnr = await nvim.call('winbufnr', [winid]) as number\n      let buf = nvim.createBuffer(bufnr)\n      let lines = await buf.lines\n      expect(lines[0].includes('title')).toBe(true)\n    })\n\n    it('should ignore events of other buffers', async () => {\n      let bufnr = workspace.bufnr\n      let notification = new Notification(nvim, {})\n      await events.fire('BufWinLeave', [bufnr + 1])\n      await events.fire('FloatBtnClick', [bufnr + 1, 1])\n      notification.dispose()\n    })\n\n    it('should show notification without border', async () => {\n      helper.updateConfiguration('notification.border', false)\n      await window.showNotification({\n        content: 'my notification',\n        title: 'title',\n      })\n      let win = await helper.getFloat()\n      let height = await nvim.call('coc#float#get_height', [win.id])\n      expect(height).toBe(2)\n    })\n\n    it('should show status line progress by default', async () => {\n      let called = 0\n      let text: string\n      setTimeout(async () => {\n        text = await nvim.getVar('coc_status') as string\n      }, 10)\n      let res = await window.withProgress({ title: 'Processing' }, progress => {\n        let n = 0\n        return new Promise(resolve => {\n          let interval = setInterval(() => {\n            progress.report({ message: 'progress', increment: 1 })\n            n = n + 10\n            called = called + 1\n            if (n == 30) {\n              clearInterval(interval)\n              resolve('done')\n            }\n          }, 10)\n        })\n      })\n      expect(text).toMatch('Processing')\n      expect(called).toBeGreaterThan(1)\n      expect(res).toBe('done')\n    })\n\n    it('should show progress notification', async () => {\n      helper.updateConfiguration('notification.statusLineProgress', false)\n      let called = 0\n      let res = await window.withProgress({ title: 'Downloading', cancellable: true }, (progress, token) => {\n        let n = 0\n        return new Promise(resolve => {\n          let interval = setInterval(() => {\n            progress.report({ message: 'progress', increment: 1 })\n            n = n + 10\n            called = called + 1\n            if (n == 100) {\n              clearInterval(interval)\n              resolve('done')\n            }\n          }, 10)\n          token.onCancellationRequested(() => {\n            clearInterval(interval)\n            resolve(undefined)\n          })\n        })\n      })\n      expect(called).toBeGreaterThan(8)\n      expect(res).toBe('done')\n    })\n\n    it('should cancel progress notification on window close', async () => {\n      helper.updateConfiguration('notification.statusLineProgress', false)\n      let called = 0\n      let p = window.withProgress({ title: 'Downloading', cancellable: true }, (progress, token) => {\n        let n = 0\n        return new Promise(resolve => {\n          let interval = setInterval(() => {\n            progress.report({ message: 'progress', increment: 1 })\n            n = n + 10\n            called = called + 1\n            if (n == 100) {\n              clearInterval(interval)\n              resolve('done')\n            }\n          }, 10)\n          token.onCancellationRequested(() => {\n            clearInterval(interval)\n            resolve(undefined)\n          })\n        })\n      })\n      await helper.wait(30)\n      await nvim.call('coc#float#close_all', [])\n      let res = await p\n      expect(called).toBeLessThan(10)\n      expect(res).toBe(undefined)\n    })\n\n    it('should cancel progress when resolved', async () => {\n      helper.updateConfiguration('notification.statusLineProgress', false)\n      let called = 0\n      let p = window.withProgress({ title: 'Process' }, () => {\n        called = called + 1\n        return Promise.resolve()\n      })\n      await p\n      let win = await helper.getFloat()\n      if (win) {\n        let res = await nvim.call('coc#window#get_var', [win.id, 'closing'])\n        expect(res).toBe(1)\n      }\n      expect(called).toBe(1)\n    })\n\n    it('should be disabled by configuration', async () => {\n      helper.updateConfiguration('notification.statusLineProgress', false)\n      helper.updateConfiguration('notification.disabledProgressSources', ['test'])\n      let p = window.withProgress({ title: 'Downloading', source: 'test' }, (progress, token) => {\n        let n = 0\n        return new Promise(resolve => {\n          let interval = setInterval(() => {\n            progress.report({ message: 'progress', increment: 1 })\n            n = n + 1\n            if (n == 10) {\n              clearInterval(interval)\n              resolve('done')\n            }\n          }, 10)\n        })\n      })\n      await helper.wait(30)\n      let win = await helper.getFloat()\n      expect(win).toBeUndefined()\n      let res = await p\n      expect(res).toBe('done')\n    })\n\n    it('should show error message when rejected', async () => {\n      helper.updateConfiguration('notification.statusLineProgress', false)\n      let p = window.withProgress({ title: 'Process' }, () => {\n        return Promise.reject(new Error('Unable to fetch'))\n      })\n      let res = await p\n      expect(res).toBe(undefined)\n      let cmdline = await helper.getCmdline()\n      expect(cmdline).toMatch(/Unable to fetch/)\n    })\n  })\n\n  describe('diffHighlights', () => {\n    let ns = 'window-test'\n    let priority = 99\n    let ns_id: number\n    beforeAll(async () => {\n      ns_id = await nvim.call('coc#highlight#create_namespace', [ns]) as number\n    })\n\n    async function createFile(content = 'foo\\nbar'): Promise<Buffer> {\n      let file = await createTmpFile(content)\n      return await helper.edit(file)\n    }\n\n    async function setHighlights(hls: HighlightItem[]): Promise<void> {\n      let bufnr = await nvim.call('bufnr', ['%']) as number\n      let arr = hls.map(o => [o.hlGroup, o.lnum, o.colStart, o.colEnd, o.combine === false ? 0 : 1, o.end_incl ? 1 : 0, o.start_incl ? 1 : 0])\n      await nvim.call('coc#highlight#set', [bufnr, ns, arr, priority])\n    }\n\n    it('should return null when canceled', async () => {\n      let buf = await createFile()\n      let items: HighlightItem[] = []\n      let token = CancellationToken.Cancelled\n      let res = await window.diffHighlights(buf.id, ns, items, undefined, token)\n      expect(res).toBe(null)\n    })\n\n    it('should add new highlights', async () => {\n      let buf = await createFile()\n      let items: HighlightItem[] = [{\n        hlGroup: 'Search',\n        lnum: 0,\n        colStart: 0,\n        colEnd: 3\n      }]\n      let res = await window.diffHighlights(buf.id, ns, items)\n      expect(res).toBeDefined()\n      expect(res.add.length).toBe(1)\n      await window.applyDiffHighlights(buf.id, ns, priority, res)\n      let markers = await buf.getExtMarks(ns_id, 0, -1, { details: true })\n      expect(markers.length).toBe(1)\n      expect(markers[0][3].end_col).toBe(3)\n    })\n\n    it('should update with new highlights', async () => {\n      let buf = await createFile('foo\\nbar\\nbaz')\n      let items: HighlightItem[] = [{\n        hlGroup: 'Search',\n        lnum: 0,\n        colStart: 0,\n        colEnd: 3\n      }, {\n        hlGroup: 'Search',\n        lnum: 2,\n        colStart: 0,\n        colEnd: 3\n      }]\n      await setHighlights(items)\n      let newItems: HighlightItem[] = [{\n        hlGroup: 'Search',\n        lnum: 0,\n        colStart: 0,\n        colEnd: 1\n      }, {\n        hlGroup: 'Search',\n        lnum: 1,\n        colStart: 0,\n        colEnd: 3\n      }]\n      let res = await window.diffHighlights(buf.id, ns, newItems)\n      await window.applyDiffHighlights(buf.id, ns, priority, res)\n      let markers = await buf.getExtMarks(ns_id, 0, -1, { details: true })\n      expect(markers.length).toBe(2)\n    })\n\n    it('should ignore lines without highlights', async () => {\n      let buf = await createFile()\n      let items: HighlightItem[] = [{\n        hlGroup: 'Search',\n        lnum: 1,\n        colStart: 0,\n        colEnd: 3\n      }]\n      await setHighlights(items)\n      let res = await window.diffHighlights(buf.id, ns, [])\n      await window.applyDiffHighlights(buf.id, ns, priority, res)\n      let markers = await buf.getExtMarks(ns_id, 0, -1, { details: true })\n      expect(markers.length).toBe(0)\n    })\n\n    it('should return empty diff', async () => {\n      let buf = await createFile()\n      let items: HighlightItem[] = [{\n        hlGroup: 'Search',\n        lnum: 0,\n        colStart: 0,\n        colEnd: 3\n      }]\n      await setHighlights(items)\n      let res = await window.diffHighlights(buf.id, ns, items)\n      expect(res).toBeDefined()\n      expect(res.remove).toEqual([])\n      expect(res.add).toEqual([])\n      expect(res.removeMarkers).toEqual([])\n    })\n\n    it('should remove and add highlights', async () => {\n      let buf = await createFile()\n      let items: HighlightItem[] = [{\n        hlGroup: 'Search',\n        lnum: 0,\n        colStart: 0,\n        colEnd: 3\n      }]\n      await setHighlights(items)\n      items = [{\n        hlGroup: 'Search',\n        lnum: 1,\n        colStart: 0,\n        colEnd: 3\n      }]\n      let res = await window.diffHighlights(buf.id, ns, items)\n      expect(res).toBeDefined()\n      expect(res.add.length).toBe(1)\n      expect(res.removeMarkers.length).toBe(1)\n      await window.applyDiffHighlights(buf.id, ns, priority, res)\n      let markers = await buf.getExtMarks(ns_id, 0, -1, { details: true })\n      expect(markers.length).toBe(1)\n      expect(markers[0][1]).toBe(1)\n      expect(markers[0][3].end_col).toBe(3)\n    })\n\n    it('should update highlights of single line', async () => {\n      let buf = await createFile()\n      let items: HighlightItem[] = [{\n        hlGroup: 'Search',\n        lnum: 0,\n        colStart: 0,\n        colEnd: 1\n      }, {\n        hlGroup: 'Search',\n        lnum: 1,\n        colStart: 2,\n        colEnd: 3\n      }]\n      await setHighlights(items)\n      items = [{\n        hlGroup: 'Search',\n        lnum: 0,\n        colStart: 2,\n        colEnd: 3\n      }]\n      let res = await window.diffHighlights(buf.id, ns, items)\n      expect(res).toBeDefined()\n      expect(res.add.length).toBe(1)\n      expect(res.removeMarkers.length).toBe(2)\n      await window.applyDiffHighlights(buf.id, ns, priority, res)\n      let markers = await buf.getExtMarks(ns_id, 0, -1, { details: true })\n      expect(markers.length).toBe(1)\n      expect(markers[0][1]).toBe(0)\n      expect(markers[0][3].end_col).toBe(3)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/modules/workspace.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport { v4 as uuid } from 'uuid'\nimport { Disposable } from 'vscode-languageserver-protocol'\nimport { Location, Position, Range, TextEdit } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport { userSettingsSchemaId } from '../../configuration'\nimport events from '../../events'\nimport { disposeAll } from '../../util'\nimport workspace, { Workspace } from '../../workspace'\nimport helper, { createTmpFile } from '../helper'\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nlet tmpFolder = path.join(os.tmpdir(), `coc-${process.pid}`)\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  if (!fs.existsSync(tmpFolder)) fs.mkdirSync(tmpFolder)\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  await helper.reset()\n  disposeAll(disposables)\n  disposables = []\n})\n\ndescribe('workspace properties', () => {\n  it('should have initialized', async () => {\n    let { nvim, uri, insertMode, workspaceFolder, cwd, documents, textDocuments } = workspace\n    expect(insertMode).toBe(false)\n    expect(nvim).toBeTruthy()\n    expect(documents.length).toBe(1)\n    expect(textDocuments.length).toBe(1)\n    expect(cwd).toBe(process.cwd())\n    let floatSupported = workspace.floatSupported\n    expect(floatSupported).toBe(true)\n    let { pluginRoot } = workspace\n    expect(typeof pluginRoot).toBe('string')\n    let { isVim, isNvim } = workspace\n    expect(isVim).toBe(false)\n    expect(isNvim).toBe(true)\n    expect(uri).toBeDefined()\n    expect(workspaceFolder).toBeUndefined()\n    let watchmanPath = workspace.getWatchmanPath()\n    expect(watchmanPath == null || typeof watchmanPath === 'string').toBe(true)\n    let folder = workspace.getWorkspaceFolder(URI.parse('lsp:/1'))\n    expect(folder).toBeUndefined()\n    let rootPath = await helper.doAction('currentWorkspacePath')\n    expect(rootPath).toBe(process.cwd())\n  })\n\n  it('should get filetyps', async () => {\n    await helper.edit('f.js')\n    let filetypes = workspace.filetypes\n    expect(filetypes.has('javascript')).toBe(true)\n    let languageIds = workspace.languageIds\n    expect(languageIds.has('javascript')).toBe(true)\n  })\n\n  it('should get display width', () => {\n    expect(workspace.getDisplayWidth('a')).toBe(1)\n  })\n\n  it('should get channelNames', async () => {\n    let names = workspace.channelNames\n    expect(Array.isArray(names)).toBe(true)\n  })\n\n  it('should work with deprecated method', async () => {\n    await nvim.setLine('foo')\n    await workspace['moveTo'](Position.create(0, 1))\n    let col = await nvim.call('col', ['.'])\n    expect(col).toBe(2)\n  })\n})\n\ndescribe('workspace methods', () => {\n  it('should call vim method', async () => {\n    let res = await workspace.callAsync('bufnr', ['%'])\n    expect(typeof res).toBe('number')\n    let obj: any = workspace.env\n    obj.isVim = true\n    disposables.push({\n      dispose: () => {\n        obj.isVim = false\n      }\n    })\n    res = await workspace.callAsync('bufnr', ['%'])\n    expect(typeof res).toBe('number')\n  })\n\n  it('should get the document', async () => {\n    let doc = await workspace.document\n    let buf = await nvim.buffer\n    expect(doc.buffer.equals(buf)).toBeTruthy()\n    doc = workspace.getDocument(doc.uri)\n    expect(doc.buffer.equals(buf)).toBeTruthy()\n  })\n\n  it('should get uri', async () => {\n    let doc = await workspace.document\n    expect(workspace.getUri(doc.bufnr, undefined)).toBeDefined()\n    expect(workspace.getUri(999, null)).toBeNull()\n    expect(workspace.getUri(999)).toBe('')\n  })\n\n  it('should fixWin32unixPrefix', async () => {\n    expect(workspace.fixWin32unixFilepath('/foo')).toBe('/foo')\n  })\n\n  it('should get attached document', async () => {\n    let fn = () => {\n      workspace.getAttachedDocument('file://not_exists')\n    }\n    expect(fn).toThrow(Error)\n    await nvim.command(`edit +setl\\\\ buftype=nofile [tree]`)\n    let doc = await workspace.document\n    expect(doc.attached).toBe(false)\n    fn = () => {\n      workspace.getAttachedDocument(doc.bufnr)\n    }\n    expect(fn).toThrow(Error)\n  })\n\n  it('should get format options of without bufnr', async () => {\n    let opts = await workspace.getFormatOptions()\n    expect(opts.insertSpaces).toBe(true)\n    expect(opts.tabSize).toBe(2)\n  })\n\n  it('should get format options of current buffer', async () => {\n    let buf = await nvim.buffer\n    await buf.setVar('coc_trim_trailing_whitespace', 1)\n    await buf.setVar('coc_trim_final_newlines', 1)\n    await buf.setOption('shiftwidth', 8)\n    await buf.setOption('expandtab', false)\n    let doc = workspace.getDocument(buf.id)\n    let opts = await workspace.getFormatOptions(doc.uri)\n    expect(opts).toEqual({\n      tabSize: 8,\n      insertSpaces: false,\n      insertFinalNewline: true,\n      trimTrailingWhitespace: true,\n      trimFinalNewlines: true\n    })\n  })\n\n  it('should check document', async () => {\n    let doc = await workspace.document\n    expect(workspace.hasDocument(doc.uri)).toBe(true)\n    expect(workspace.hasDocument(doc.uri, doc.version)).toBe(true)\n    expect(workspace.hasDocument(doc.uri, doc.version - 1)).toBe(false)\n  })\n\n  it('should get format options when uri does not exist', async () => {\n    let uri = URI.file('/tmp/foo').toString()\n    let opts = await workspace.getFormatOptions(uri)\n    expect(opts.insertSpaces).toBe(true)\n    expect(opts.tabSize).toBe(2)\n  })\n\n  it('should create file watcher', async () => {\n    let watcher = workspace.createFileSystemWatcher('**/*.ts')\n    expect(watcher).toBeDefined()\n  })\n\n  it('should get quickfix item from Location', async () => {\n    let filepath = await createTmpFile('quickfix')\n    let uri = URI.file(filepath).toString()\n    let p = Position.create(0, 0)\n    let loc = Location.create(uri, Range.create(p, p))\n    let item = await workspace.getQuickfixItem(loc)\n    expect(item.filename).toBe(filepath)\n    expect(item.text).toBe('quickfix')\n  })\n\n  it('should get quickfix list from Locations', async () => {\n    let filepathA = await createTmpFile('fileA:1\\nfileA:2\\nfileA:3')\n    let uriA = URI.file(filepathA).toString()\n    let filepathB = await createTmpFile('fileB:1\\nfileB:2\\nfileB:3')\n    let uriB = URI.file(filepathB).toString()\n    let p1 = Position.create(0, 0)\n    let p2 = Position.create(1, 0)\n    let locations: Location[] = []\n    locations.push(Location.create(uriA, Range.create(p1, p1)))\n    locations.push(Location.create(uriA, Range.create(p2, p2)))\n    locations.push(Location.create(uriB, Range.create(p1, p1)))\n    locations.push(Location.create(uriB, Range.create(p2, p2)))\n    let items = await workspace.getQuickfixList(locations)\n    expect(items[0].filename).toBe(filepathA)\n    expect(items[0].text).toBe('fileA:1')\n    expect(items[1].filename).toBe(filepathA)\n    expect(items[1].text).toBe('fileA:2')\n    expect(items[2].filename).toBe(filepathB)\n    expect(items[2].text).toBe('fileB:1')\n    expect(items[3].filename).toBe(filepathB)\n    expect(items[3].text).toBe('fileB:2')\n  })\n\n  it('should get line of document', async () => {\n    let doc = await workspace.document\n    await nvim.setLine('abc')\n    let line = await workspace.getLine(doc.uri, 0)\n    expect(line).toBe('abc')\n  })\n\n  it('should get line of file', async () => {\n    let filepath = await createTmpFile('quickfix')\n    let uri = URI.file(filepath).toString()\n    let line = await workspace.getLine(uri, 0)\n    expect(line).toBe('quickfix')\n  })\n\n  it('should read content from buffer', async () => {\n    let doc = await workspace.document\n    await doc.applyEdits([{ range: Range.create(0, 0, 0, 0), newText: 'foo' }])\n    let line = await workspace.readFile(doc.uri)\n    expect(line).toBe('foo\\n')\n  })\n\n  it('should read content from file', async () => {\n    let filepath = await createTmpFile('content')\n    let content = await workspace.readFile(URI.file(filepath).toString())\n    expect(content).toBe(content)\n  })\n\n  it('should expand filepath', async () => {\n    let home = os.homedir()\n    let res = workspace.expand('~/$NODE_ENV/')\n    expect(res.startsWith(home)).toBeTruthy()\n    expect(res).toContain(process.env.NODE_ENV)\n\n    res = workspace.expand('$HOME/$NODE_ENV/')\n    expect(res.startsWith(home)).toBeTruthy()\n    expect(res).toContain(process.env.NODE_ENV)\n  })\n\n  it('should expand variables', async () => {\n    expect(workspace.expand('${workspace}/foo')).toBe(`${workspace.root}/foo`)\n    expect(workspace.expand('${env:NODE_ENV}')).toBe(process.env.NODE_ENV)\n    expect(workspace.expand('${cwd}')).toBe(workspace.cwd)\n    let folder = path.basename(workspace.root)\n    expect(workspace.expand('${workspaceFolderBasename}')).toBe(folder)\n    await helper.edit('bar.ts')\n    expect(workspace.expand('${file}')).toContain('bar')\n    expect(workspace.expand('${fileDirname}')).toBe(path.dirname(__dirname))\n    expect(workspace.expand('${fileExtname}')).toBe('.ts')\n    expect(workspace.expand('${fileBasename}')).toBe('bar.ts')\n    expect(workspace.expand('${fileBasenameNoExtension}')).toBe('bar')\n  })\n\n  it('should run command', async () => {\n    let res = await workspace.runCommand('ls', __dirname, 1000)\n    expect(res).toMatch('workspace')\n    res = await workspace.runCommand('ls')\n    expect(res).toBeDefined()\n  })\n\n  it('should export deprecated properties', async () => {\n    expect(workspace.completeOpt).toBeDefined()\n    expect(workspace.createNameSpace('name')).toBeDefined()\n    expect(Workspace).toBeDefined()\n    expect(workspace['onDidOpenTerminal']).toBeDefined()\n    expect(workspace['onDidCloseTerminal']).toBeDefined()\n    let spy = jest.spyOn(workspace.nvim, 'call').mockImplementation(() => {\n      return null\n    })\n    workspace.checkVersion(0)\n    spy.mockRestore()\n  })\n\n  it('should resolve module path if exists', async () => {\n    let res = await workspace.resolveModule('bytes')\n    res = await workspace.resolveModule('bytes')\n    expect(res).toBeTruthy()\n  })\n\n  it('should not resolve module if it does not exist', async () => {\n    let res = await workspace.resolveModule('foo')\n    res = await workspace.resolveModule('foo')\n    expect(res).toBeFalsy()\n  })\n\n  it('should return match score for document', async () => {\n    let doc = await helper.createDocument('tmp.xml')\n    expect(workspace.match(['xml'], doc.textDocument)).toBe(10)\n    expect(workspace.match(['wxml'], doc.textDocument)).toBe(0)\n    expect(workspace.match([{ language: 'xml' }], doc.textDocument)).toBe(10)\n    expect(workspace.match([{ language: 'wxml' }], doc.textDocument)).toBe(0)\n    expect(workspace.match([{ pattern: '**/*.xml' }], doc.textDocument)).toBe(5)\n    expect(workspace.match([{ pattern: '**/*.html' }], doc.textDocument)).toBe(0)\n    expect(workspace.match([{ scheme: 'file' }], doc.textDocument)).toBe(5)\n    expect(workspace.match([{ scheme: 'term' }], doc.textDocument)).toBe(0)\n    expect(workspace.match([{ language: 'xml' }, { scheme: 'file' }], doc.textDocument)).toBe(10)\n    expect(workspace.match([{ language: 'xml', scheme: 'file', pattern: '**/*.xml' }], doc.textDocument)).toBe(10)\n  })\n\n  it('should handle will save event', async () => {\n    async function doRename() {\n      let fsPath = await createTmpFile('foo', disposables)\n      let newPath = path.join(path.dirname(fsPath), 'new_file')\n      disposables.push(Disposable.create(() => {\n        if (fs.existsSync(newPath)) fs.unlinkSync(newPath)\n      }))\n      await workspace.renameFile(fsPath, newPath, { overwrite: true })\n      if (fs.existsSync(newPath)) fs.unlinkSync(newPath)\n    }\n    let called = false\n    let disposable = workspace.onWillRenameFiles(e => {\n      let p = new Promise<void>(resolve => {\n        setTimeout(() => {\n          called = true\n          resolve()\n        }, 10)\n      })\n      e.waitUntil(p)\n    })\n    await doRename()\n    disposable.dispose()\n    expect(called).toBe(true)\n    called = false\n    disposable = workspace.onWillRenameFiles(e => {\n      called = true\n      e.waitUntil(Promise.resolve({ changes: {} }))\n    })\n    await doRename()\n    expect(called).toBe(true)\n    disposable.dispose()\n  })\n\n  it('should getWatchConfig', async () => {\n    helper.updateConfiguration('fileSystemWatch.enable', null, disposables)\n    helper.updateConfiguration('fileSystemWatch.watchmanPath', '~/bin/watchman', disposables)\n    helper.updateConfiguration('fileSystemWatch.ignoredFolders', ['~'], disposables)\n    let config = workspace.getWatchConfig()\n    expect(config.enable).toBe(false)\n    expect(typeof config.watchmanPath).toBe('string')\n    expect(config.ignoredFolders).toEqual([os.homedir()])\n  })\n})\n\ndescribe('workspace utility', () => {\n  it('should create database', async () => {\n    let filpath = path.join(process.env.COC_DATA_HOME, 'test.json')\n    if (fs.existsSync(filpath)) {\n      fs.unlinkSync(filpath)\n    }\n    let db = workspace.createDatabase('test')\n    let res = db.exists('xyz')\n    expect(res).toBe(false)\n    db.destroy()\n  })\n\n  it('should get current state', async () => {\n    let buf = await helper.edit()\n    await buf.setLines(['foo', 'bar'], { start: 0, end: -1, strictIndexing: false })\n    await nvim.call('cursor', [2, 2])\n    let doc = workspace.getDocument(buf.id)\n    let state = await workspace.getCurrentState()\n    expect(doc.uri).toBe(state.document.uri)\n    expect(state.position).toEqual({ line: 1, character: 1 })\n  })\n\n  it('should findUp to tsconfig.json from current file', async () => {\n    await helper.edit(path.join(__dirname, 'edit'))\n    let filepath = await workspace.findUp('tsconfig.json')\n    expect(filepath).toMatch('tsconfig.json')\n  })\n\n  it('should findUp from current file ', async () => {\n    await helper.edit('foo')\n    let filepath = await workspace.findUp('tsconfig.json')\n    expect(filepath).toMatch('tsconfig.json')\n  })\n\n  it('should not findUp from file in other directory', async () => {\n    await nvim.command(`edit ${path.join(os.tmpdir(), uuid())}`)\n    let filepath = await workspace.findUp('tsconfig.json')\n    expect(filepath).toBeNull()\n  })\n\n  it('should register autocmd', async () => {\n    let event: any\n    let eventCount = 0\n    let disposables = []\n    workspace.registerAutocmd({\n      event: 'TextYankPost',\n      request: true,\n      arglist: ['v:event'],\n      callback: ev => {\n        eventCount += 1\n        event = ev\n      }\n    }, disposables)\n    await nvim.setLine('foo')\n    await nvim.command('normal! yy')\n    await helper.wait(30)\n    expect(event.regtype).toBe('V')\n    expect(event.operator).toBe('y')\n    expect(event.regcontents).toEqual(['foo'])\n    expect(eventCount).toBe(1)\n    disposables.forEach(d => d.dispose())\n  })\n\n  it('should register keymap', async () => {\n    let n = 0\n    let fn = () => {\n      n++\n    }\n    await nvim.command('nmap go <Plug>(coc-echo)')\n    let disposable = workspace.registerKeymap(['n', 'v'], 'echo', fn, { sync: true })\n    let { mode } = await nvim.mode\n    expect(mode).toBe('n')\n    await nvim.call('feedkeys', ['go', 'i'])\n    await helper.waitValue(() => n, 1)\n    disposable.dispose()\n    await nvim.call('feedkeys', ['go', 'i'])\n    await helper.wait(20)\n    expect(n).toBe(1)\n  })\n\n  it('should register expr keymap', async () => {\n    let called = false\n    let fn = () => {\n      called = true\n      return '\"\"'\n    }\n    await nvim.input('i')\n    let { mode } = await nvim.mode\n    expect(mode).toBe('i')\n    let disposable = workspace.registerExprKeymap('i', '\"', fn)\n    await helper.wait(30)\n    await nvim.call('feedkeys', ['\"', 't'])\n    await helper.wait(30)\n    expect(called).toBe(true)\n    let line = await nvim.line\n    expect(line).toBe('\"\"')\n    disposable.dispose()\n  })\n\n  it('should register buffer expr keymap', async () => {\n    let fn = () => '\"\"'\n    await nvim.input('i')\n    let disposable = workspace.registerExprKeymap('i', '\"', fn, true, false)\n    await helper.wait(30)\n    await nvim.call('feedkeys', ['\"', 't'])\n    await helper.wait(30)\n    let line = await nvim.line\n    expect(line).toBe('\"\"')\n    disposable.dispose()\n  })\n\n  it('should check nvim version', async () => {\n    expect(workspace.has('patch-7.4.248')).toBe(false)\n    expect(workspace.has('nvim-0.5.0')).toBe(true)\n    expect(workspace.has('nvim-9.0.0')).toBe(false)\n  })\n\n  it('should registerLocalKeymap by old API', async () => {\n    let called = false\n    let fn = workspace.registerLocalKeymap.bind(workspace) as any\n    let disposable = fn('n', 'n', () => { called = true })\n    await nvim.call('feedkeys', ['n', 't'])\n    await helper.waitValue(() => called, true)\n    disposable.dispose()\n    let res = await nvim.exec('nmap n', true)\n    expect(res).toMatch('No mapping found')\n  })\n})\n\ndescribe('workspace events', () => {\n\n  it('should listen to fileType change', async () => {\n    let buf = await helper.edit()\n    await nvim.command('setf xml')\n    await helper.wait(50)\n    let doc = workspace.getDocument(buf.id)\n    expect(doc.filetype).toBe('xml')\n  })\n\n  it('should fire onDidOpenTextDocument', async () => {\n    let fn = jest.fn()\n    workspace.onDidOpenTextDocument(fn, null, disposables)\n    await helper.edit()\n    await helper.wait(30)\n    expect(fn).toHaveBeenCalledTimes(1)\n  })\n\n  it('should fire onDidChangeTextDocument', async () => {\n    let fn = jest.fn()\n    await helper.edit()\n    workspace.onDidChangeTextDocument(fn, null, disposables)\n    await nvim.setLine('foo')\n    let doc = await workspace.document\n    doc.forceSync()\n    await helper.wait(20)\n    expect(fn).toHaveBeenCalledTimes(1)\n  })\n\n  it('should fire onDidChangeConfiguration', async () => {\n    let fn = jest.fn()\n    let disposable = workspace.onDidChangeConfiguration(e => {\n      disposable.dispose()\n      expect(e.affectsConfiguration('tsserver')).toBe(true)\n      expect(e.affectsConfiguration('tslint')).toBe(false)\n      fn()\n    })\n    let config = workspace.getConfiguration('tsserver')\n    await config.update('enable', false)\n    expect(fn).toHaveBeenCalledTimes(1)\n    await config.update('enable', undefined)\n  })\n\n  it('should resolve json schema', async () => {\n    expect(workspace.resolveJSONSchema(userSettingsSchemaId)).toBeDefined()\n  })\n\n  it('should get empty configuration for none exists section', () => {\n    let config = workspace.getConfiguration('notexists')\n    let keys = Object.keys(config)\n    expect(keys.length).toBe(0)\n  })\n\n  it('should fire onWillSaveUntil', async () => {\n    let doc = await workspace.document\n    let filepath = URI.parse(doc.uri).fsPath\n    let fn = jest.fn()\n    let disposable = workspace.onWillSaveTextDocument(event => {\n      let promise = new Promise<TextEdit[]>(resolve => {\n        fn()\n        let edit: TextEdit = {\n          newText: 'foo',\n          range: Range.create(0, 0, 0, 0)\n        }\n        resolve([edit])\n      })\n      event.waitUntil(promise)\n    })\n    await nvim.setLine('bar')\n    await helper.wait(30)\n    await events.fire('BufWritePre', [doc.bufnr, doc.bufname])\n    await helper.wait(30)\n    let content = doc.getDocumentContent()\n    expect(content.startsWith('foobar')).toBe(true)\n    disposable.dispose()\n    expect(fn).toHaveBeenCalledTimes(1)\n    if (fs.existsSync(filepath)) {\n      fs.unlinkSync(filepath)\n    }\n  })\n\n  it('should not work for async waitUntil', async () => {\n    let doc = await helper.createDocument()\n    let filepath = URI.parse(doc.uri).fsPath\n    let disposable = workspace.onWillSaveTextDocument(event => {\n      setTimeout(() => {\n        let edit: TextEdit = {\n          newText: 'foo',\n          range: Range.create(0, 0, 0, 0)\n        }\n        event.waitUntil(Promise.resolve([edit]))\n      }, 30)\n    })\n    await nvim.setLine('bar')\n    await helper.wait(30)\n    await nvim.command('wa')\n    let content = doc.getDocumentContent()\n    expect(content).toMatch('bar')\n    disposable.dispose()\n    if (fs.existsSync(filepath)) {\n      fs.unlinkSync(filepath)\n    }\n  })\n\n  it('should only use first returned textEdits', async () => {\n    let doc = await helper.createDocument()\n    let filepath = URI.parse(doc.uri).fsPath\n    disposables.push(Disposable.create(() => {\n      if (fs.existsSync(filepath)) {\n        fs.unlinkSync(filepath)\n      }\n    }))\n    workspace.onWillSaveTextDocument(event => {\n      event.waitUntil(Promise.resolve(undefined))\n    }, null, disposables)\n    workspace.onWillSaveTextDocument(event => {\n      let promise = new Promise<TextEdit[]>(resolve => {\n        setTimeout(() => {\n          let edit: TextEdit = {\n            newText: 'foo',\n            range: Range.create(0, 0, 0, 0)\n          }\n          resolve([edit])\n        }, 10)\n      })\n      event.waitUntil(promise)\n    }, null, disposables)\n    workspace.onWillSaveTextDocument(event => {\n      let promise = new Promise<TextEdit[]>(resolve => {\n        setTimeout(() => {\n          let edit: TextEdit = {\n            newText: 'bar',\n            range: Range.create(0, 0, 0, 0)\n          }\n          resolve([edit])\n        }, 30)\n      })\n      event.waitUntil(promise)\n    }, null, disposables)\n    await nvim.setLine('bar')\n    await helper.wait(30)\n    await nvim.command('wa')\n    let content = doc.getDocumentContent()\n    expect(content).toMatch('foo')\n  })\n\n  it('should attach & detach', async () => {\n    let buf = await helper.edit()\n    await nvim.command('CocDisable')\n    let doc = workspace.getDocument(buf.id)\n    expect(doc).toBeUndefined()\n    await nvim.command('CocEnable')\n    doc = workspace.getDocument(buf.id)\n    expect(doc.bufnr).toBe(buf.id)\n  })\n})\n\ndescribe('workspace registerBufferSync', () => {\n  it('should register', async () => {\n    await helper.createDocument()\n    let created = 0\n    let deleted = 0\n    let changed = 0\n    let disposable = workspace.registerBufferSync(() => {\n      created = created + 1\n      return {\n        dispose: () => {\n          deleted += 1\n        },\n        onChange: () => {\n          changed += 1\n        }\n      }\n    })\n    disposables.push(disposable)\n    let doc = await helper.createDocument()\n    expect(created).toBe(2)\n    await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo')])\n    expect(changed).toBe(1)\n    await nvim.command('bd!')\n    expect(deleted).toBe(1)\n  })\n\n  it('should invoke onTextChange', async () => {\n    let called = 0\n    disposables.push(workspace.registerBufferSync(() => {\n      return {\n        dispose: () => {\n        },\n        onTextChange: () => {\n          called = called + 1\n        }\n      }\n    }))\n    let doc = await helper.createDocument()\n    await nvim.setLine('foo')\n    await doc.synchronize()\n    expect(called).toBe(1)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/npm",
    "content": "#!/usr/bin/env node\n\nconsole.log('arguments' + JSON.stringify(process.argv))\nconsole.error('error msg')\nconsole.log('start')\nconsole.log('finish')\n\nif (process.argv.includes('--error')) {\n  process.exit(3)\n}\n"
  },
  {
    "path": "src/__tests__/rg",
    "content": "#!/usr/bin/env node\n// black:30 red:31 green:32\n\nlet content = `\\x1b[30mmodules/cursors.test.ts\\x1b[m\n\\x1b[32m218\\x1b[m-    let doc = await setup()\n\\x1b[32m219\\x1b[m-    await nvim.call('cursor', [1, 4])\n\\x1b[32m220\\x1b[m:    await nvim.input('\\x1b[31mabc\\x1b[m')\n\\x1b[32m221\\x1b[m-    await helper.wait(30)\n\\x1b[32m222\\x1b[m-    doc.forceSync()\n\\x1b[32m223\\x1b[m-    await helper.wait(100)\n\\x1b[32m224\\x1b[m-    let lines = await nvim.call('getline', [1, '$'])\n\\x1b[32m225\\x1b[m:    expect(lines).toEqual(['\\x1b[31mabc\\x1b[m fooabc fooabc', 'barabc barabc'])\n\\x1b[32m226\\x1b[m-  })\n\\x1b[32m227\\x1b[m-\n--\n\\x1b[32m32\\x1b[m-    expect(rangeCount()).toBe(5)\n\\x1b[32m33\\x1b[m-    let lines = await nvim.call('getline', [1, '$'])\n\\x1b[32m34\\x1b[m:    expect(lines).toEqual(['\\x1b[31mabc\\x1b[m fooabc fooabc', 'barabc barabc'])\n\\x1b[32m35\\x1b[m-  })\n\\x1b[32m36\\x1b[m-\n\n\\x1b[30mmodules/position.test.ts\\x1b[m\n\\x1b[32m42\\x1b[m-  test('getChangedPosition #1', () => {\n\\x1b[32m43\\x1b[m-    let pos = Position.create(0, 0)\n\\x1b[32m44\\x1b[m:    let edit = TextEdit.insert(pos, '\\x1b[31mabc\\x1b[m')\n\\x1b[32m45\\x1b[m-    let res = getChangedPosition(pos, edit)\n\\x1b[32m46\\x1b[m-    expect(res).toEqual({ line: 0, character: 3 })\n`\n\nlet idx = process.argv.findIndex(s => s == '--sleep')\nif (idx !== -1) {\n  let ms = process.argv[idx + 1]\n  setTimeout(() => {\n    process.stdout.write(content)\n  }, ms)\n} else {\n  process.stdout.write(content)\n}\n"
  },
  {
    "path": "src/__tests__/sample/.vim/coc-settings.json",
    "content": "{\n  \"coc.preferences.rootPath\": \"./src\"\n}\n"
  },
  {
    "path": "src/__tests__/snippets/manager.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport path from 'path'\nimport { CompletionItem, Disposable, InsertTextFormat, InsertTextMode, Position, Range, TextEdit } from 'vscode-languageserver-protocol'\nimport commandManager from '../../commands'\nimport events from '../../events'\nimport languages from '../../languages'\nimport Document from '../../model/document'\nimport { CompletionItemProvider } from '../../provider'\nimport snippetManager, { SnippetManager } from '../../snippets/manager'\nimport { SnippetEdit } from '../../snippets/session'\nimport { SnippetString } from '../../snippets/string'\nimport { disposeAll } from '../../util'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet doc: Document\nlet disposables: Disposable[] = []\n\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  let pyfile = path.join(__dirname, '../ultisnips.py')\n  await nvim.command(`execute 'pyxfile '.fnameescape('${pyfile}')`)\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n})\n\nbeforeEach(async () => {\n  doc = await helper.createDocument()\n})\n\ndescribe('snippet provider', () => {\n  describe('Events', () => {\n    it('should change status item on editor change', async () => {\n      let doc = await helper.createDocument('foo')\n      await nvim.input('i')\n      await snippetManager.insertSnippet('${1:foo} $1 ')\n      let val = await nvim.getVar('coc_status')\n      expect(val).toBeDefined()\n      expect(snippetManager.isActivated(doc.bufnr)).toBe(true)\n      await nvim.command('edit bar')\n      await helper.waitValue(async () => {\n        let val = await nvim.getVar('coc_status') as string\n        return val.includes('SNIP')\n      }, false)\n      await nvim.command('buffer ' + doc.bufnr)\n      await helper.waitValue(async () => {\n        let val = await nvim.getVar('coc_status') as string\n        return val.includes('SNIP')\n      }, true)\n    })\n\n    it('should check position on InsertEnter', async () => {\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'bar')])\n      let isActive = await snippetManager.insertSnippet('${1:foo} $1 ', false, Range.create(0, 0, 0, 0))\n      expect(isActive).toBe(true)\n      let line = await nvim.line\n      await nvim.call('cursor', [1, line.length + 1])\n      await events.fire('InsertEnter', [doc.bufnr])\n      expect(snippetManager.session.isActive).toBe(false)\n    })\n\n    it('should synchronize on CompleteDone', async () => {\n      let doc = await workspace.document\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foot\\n')])\n      await nvim.call('cursor', [2, 1])\n      await nvim.command('startinsert')\n      let res = await snippetManager.insertSnippet('${1/(.*)/${1:/capitalize}/}$1', true, Range.create(1, 0, 1, 0))\n      expect(res).toBe(true)\n      await snippetManager.selectCurrentPlaceholder()\n      await nvim.input('f')\n      await helper.waitPopup()\n      let line = await nvim.line\n      expect(line).toBe('f')\n      await nvim.input('t')\n      let s = snippetManager.session\n      await doc.patchChange()\n      events.completing = false\n      await s.onCompleteDone()\n      line = await nvim.line\n      expect(line).toBe('Ftft')\n      await nvim.input('<backspace>')\n      await helper.waitValue(() => {\n        return nvim.line\n      }, 'Ff')\n    })\n\n    it('should show & hide status item', async () => {\n      let doc = await workspace.document\n      let buf = doc.buffer\n      let curr = await helper.createDocument()\n      await buf.setLines([], { start: 0, end: -1 })\n      let isActive = await snippetManager.insertBufferSnippet(buf.id, ' ${1:foo} $1 $0', Range.create(0, 0, 0, 0))\n      expect(isActive).toBe(true)\n      let status = await nvim.getVar('coc_status')\n      expect(!!status).toBe(false)\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 1), 'x')])\n      await helper.waitValue(() => doc.getline(0), ' xfoo xfoo ')\n      let active = await buf.getVar('coc_snippet_active')\n      expect(active).toBe(1)\n      active = await curr.buffer.getVar('coc_snippet_active')\n      expect(active != 1).toBe(true)\n    })\n  })\n\n  describe('insertSnippet()', () => {\n    it('should throw when current buffer not attached', async () => {\n      await nvim.command(`vnew +setl\\\\ buftype=nofile`)\n      await expect(async () => {\n        await snippetManager.insertSnippet('foo')\n      }).rejects.toThrow(Error)\n    })\n\n    it('should replace range for ultisnip with python code', async () => {\n      await nvim.setLine('foo')\n      await snippetManager.insertSnippet('`!p snip.rv = vim.current.line`', false, Range.create(0, 0, 0, 3), InsertTextMode.asIs, {})\n      let line = await nvim.line\n      expect(line).toBe('')\n      await helper.doAction('selectCurrentPlaceholder')\n    })\n\n    it('should not active when insert plain snippet', async () => {\n      await snippetManager.insertSnippet('foo')\n      let line = await nvim.line\n      expect(line).toBe('foo')\n      expect(snippetManager.session.isActive).toBe(false)\n      expect(snippetManager.getSession(doc.bufnr)).toBeUndefined()\n    })\n\n    it('should insert snippet by action', async () => {\n      await nvim.input('i')\n      let res = await helper.plugin.cocAction('snippetInsert', Range.create(0, 0, 0, 0), '${1:foo}')\n      expect(res).toBe(true)\n    })\n\n    it('should start new session if session exists', async () => {\n      await nvim.setLine('bar')\n      await snippetManager.insertSnippet('${1:foo} ')\n      await nvim.input('<esc>')\n      await nvim.command('stopinsert')\n      await nvim.input('A')\n      let s = new SnippetString()\n      s.appendPlaceholder('bar')\n      let active = await snippetManager.insertSnippet(s)\n      expect(active).toBe(true)\n      let line = await nvim.getLine()\n      expect(line).toBe('foo barbar')\n    })\n\n    it('should start nest session', async () => {\n      await snippetManager.insertSnippet('${1:foo} ${2:bar}', true, Range.create(0, 0, 0, 0), InsertTextMode.asIs, {})\n      await nvim.input('<backspace>i')\n      let s = snippetManager.session\n      await s.forceSynchronize()\n      let active = await snippetManager.insertSnippet('${1:x} $1', true, undefined, undefined, {\n        actions: {\n          preExpand: 'vim.vars[\"last\"] = snip.last_placeholder.current_text'\n        }\n      })\n      expect(active).toBe(true)\n      let last = await nvim.getVar('last')\n      expect(last).toBe('i')\n    })\n\n    it('should insert nested snippet on CompleteDone with correct position', async () => {\n      await snippetManager.insertSnippet('`!p snip.rv = \" \" * (10 - len(t[1]))`${1:inner}', true, Range.create(0, 0, 0, 0), InsertTextMode.asIs, {})\n      let bufnr = await nvim.call('bufnr', ['%']) as number\n      let session = snippetManager.getSession(bufnr)\n      expect(session.isActive).toBe(true)\n      let line = await nvim.line\n      expect(line).toBe('     inner')\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: 'bar',\n          insertTextFormat: InsertTextFormat.Snippet,\n          textEdit: { range: Range.create(0, 5, 0, 6), newText: '${1:foobar}' },\n          preselect: true\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('edits', 'edit', null, provider))\n      await nvim.input('b')\n      await helper.waitPopup()\n      let res = await helper.items()\n      let idx = res.findIndex(o => o.source?.name == 'edits')\n      nvim.call('coc#pum#select', [idx, 1, 1], true)\n      await events.race(['PlaceholderJump'], 200)\n      await session.synchronize()\n      line = await nvim.line\n      expect(line).toBe('    foobar')\n    })\n  })\n\n  describe('insertBufferSnippet()', () => {\n    it('should throw when buffer not attached', async () => {\n      await nvim.command(`vnew +setl\\\\ buftype=nofile`)\n      let bufnr = await nvim.call('bufnr', ['%']) as number\n      expect(snippetManager.jumpable()).toBe(false)\n      let res = await snippetManager.resolveSnippet('${1:foo}')\n      expect(res).toBeUndefined()\n      await expect(async () => {\n        await snippetManager.insertBufferSnippet(bufnr, 'foo', Range.create(0, 0, 0, 0))\n      }).rejects.toThrow(Error)\n    })\n  })\n\n  describe('insertBufferSnippets()', () => {\n    it('should insert snippets', async () => {\n      let doc = await helper.createDocument()\n      await helper.createDocument()\n      let edits: SnippetEdit[] = []\n      edits.push({ range: Range.create(0, 0, 0, 0), snippet: 'foo($1)' })\n      edits.push({ range: Range.create(0, 0, 0, 0), snippet: 'bar($1)' })\n      let result = await snippetManager.insertBufferSnippets(doc.bufnr, edits)\n      expect(result).toBe(true)\n      let lines = await doc.buffer.lines\n      expect(lines).toEqual(['foo()bar()'])\n      await nvim.command(`b ${doc.bufnr}`)\n      // selected on BufEnter\n      await helper.waitFor('col', ['.'], 5)\n    })\n\n    it('should select placeholder', async () => {\n      let doc = await workspace.document\n      let edits: SnippetEdit[] = []\n      edits.push({ range: Range.create(0, 0, 0, 0), snippet: 'foo($1)' })\n      edits.push({ range: Range.create(0, 0, 0, 0), snippet: 'bar($1)' })\n      let result = await snippetManager.insertBufferSnippets(doc.bufnr, edits, true)\n      expect(result).toBe(true)\n      let cursor = await window.getCursorPosition()\n      expect(cursor).toEqual(Position.create(0, 4))\n    })\n  })\n\n  describe('nextPlaceholder()', () => {\n    it('should go to next placeholder', async () => {\n      await snippetManager.insertSnippet('${1:a} ${2:b}')\n      await helper.doAction('snippetNext')\n      let col = await nvim.call('col', '.')\n      expect(col).toBe(3)\n    })\n\n    it('should remove keymap on nextPlaceholder when session not exists', async () => {\n      await nvim.command(`edit +setl\\\\ buftype=nofile foo`)\n      await events.fire('Enter', [])\n      let buf = await nvim.buffer\n      await nvim.call('coc#snippet#enable')\n      await snippetManager.nextPlaceholder()\n      let val = await buf.getVar('coc_snippet_active')\n      expect(val).toBe(0)\n    })\n\n    it('should respect preferCompleteThanJumpPlaceholder', async () => {\n      helper.updateConfiguration('suggest.preferCompleteThanJumpPlaceholder', true, disposables)\n      let provider: CompletionItemProvider = {\n        provideCompletionItems: async (): Promise<CompletionItem[]> => [{\n          label: 'foot',\n          insertTextFormat: InsertTextFormat.Snippet,\n          insertText: '${1:foot}',\n          textEdit: { range: Range.create(0, 0, 0, 0), newText: '${1:foot}' },\n          preselect: true\n        }]\n      }\n      disposables.push(languages.registerCompletionItemProvider('edits', 'E', ['*'], provider))\n      await snippetManager.insertSnippet('${1} ${2:bar} foot')\n      let mode = await nvim.mode\n      expect(mode.mode).toBe('i')\n      nvim.call('coc#start', { source: 'edits' }, true)\n      await helper.waitPopup()\n      await nvim.input('<C-j>')\n      await helper.waitFor('getline', ['.'], 'foot bar foot')\n      let placeholder = snippetManager.session.placeholder\n      expect(placeholder.index).toBe(1)\n    })\n  })\n\n  describe('previousPlaceholder()', () => {\n    it('should goto previous placeholder', async () => {\n      await snippetManager.insertSnippet('${1:a} ${2:b}')\n      await snippetManager.nextPlaceholder()\n      await helper.doAction('snippetPrev')\n      let col = await nvim.call('col', '.')\n      expect(col).toBe(1)\n    })\n\n    it('should remove keymap on previousPlaceholder when session not exists', async () => {\n      await nvim.command(`edit +setl\\\\ buftype=nofile foo`)\n      let buf = await nvim.buffer\n      await nvim.call('coc#snippet#enable')\n      await snippetManager.previousPlaceholder()\n      let val = await buf.getVar('coc_snippet_active')\n      expect(val).toBe(0)\n    })\n  })\n\n  describe('cancel()', () => {\n    it('should cancel snippet session', async () => {\n      let buffer = doc.buffer\n      let active = await snippetManager.insertSnippet('${1:foo}')\n      expect(active).toBe(true)\n      await helper.doAction('snippetCancel')\n      expect(snippetManager.session.isActive).toBe(false)\n      let val = await buffer.getVar('coc_snippet_active')\n      expect(val).toBe(0)\n    })\n  })\n\n  describe('jumpable()', () => {\n    it('should check jumpable', async () => {\n      await nvim.input('i')\n      await snippetManager.insertSnippet('${1:foo} ${2:bar}')\n      let jumpable = snippetManager.jumpable()\n      expect(jumpable).toBe(true)\n      await snippetManager.nextPlaceholder()\n      jumpable = snippetManager.jumpable()\n      expect(jumpable).toBe(true)\n      await snippetManager.nextPlaceholder()\n      jumpable = snippetManager.jumpable()\n      expect(jumpable).toBe(false)\n    })\n  })\n\n  describe('synchronize text', () => {\n    it('should update placeholder on placeholder update', async () => {\n      let doc = await workspace.document\n      await nvim.command('startinsert')\n      await snippetManager.insertSnippet('$1\\n${1/,/|/g}', true, undefined, InsertTextMode.adjustIndentation, {})\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'a,b')])\n      let s = snippetManager.getSession(doc.bufnr)\n      await s.forceSynchronize()\n      let lines = await nvim.call('getline', [1, '$'])\n      expect(lines).toEqual(['a,b', 'a|b'])\n    })\n\n    it('should synchronize when position changed and pum visible', async () => {\n      let doc = await workspace.document\n      await nvim.setLine('foo')\n      await nvim.input('o')\n      let res = await snippetManager.insertSnippet(\"`!p snip.rv = ' '*(4- len(t[1]))`${1}\", true, undefined, InsertTextMode.asIs, {})\n      expect(res).toBe(true)\n      let line = await nvim.line\n      expect(line).toBe('    ')\n      await nvim.input('f')\n      await helper.waitFor('coc#pum#visible', [], 1)\n      await nvim.input('<C-e>')\n      let s = snippetManager.getSession(doc.bufnr)\n      expect(s).toBeDefined()\n    })\n\n    it('should adjust cursor position on update', async () => {\n      await nvim.call('cursor', [1, 1])\n      await nvim.input('i')\n      await snippetManager.insertSnippet('${1/..*/ -> /}$1')\n      let line = await nvim.line\n      expect(line).toBe('')\n      await nvim.input('x')\n      let s = snippetManager.getSession(doc.bufnr)\n      expect(s).toBeDefined()\n      await s.forceSynchronize()\n      line = await nvim.line\n      expect(line).toBe(' -> x')\n      let col = await nvim.call('col', '.')\n      expect(col).toBe(6)\n    })\n\n    it('should not synchronize text on change final placeholder', async () => {\n      let doc = await workspace.document\n      await nvim.input('i')\n      let res = await snippetManager.insertSnippet('$0e$1mpty$0')\n      expect(res).toBe(true)\n      await nvim.call('nvim_buf_set_text', [doc.bufnr, 0, 0, 0, 0, ['abc']])\n      await doc.synchronize()\n      let s = snippetManager.getSession(doc.bufnr)\n      await s.forceSynchronize()\n      let line = await nvim.line\n      expect(line).toBe('abcempty')\n    })\n  })\n\n  describe('resolveSnippet()', () => {\n    it('should resolve snippet text', async () => {\n      let snippet = await snippetManager.resolveSnippet('${1:foo}')\n      expect(snippet.toString()).toBe('foo')\n      snippet = await snippetManager.resolveSnippet('${1:foo} ${2:`!p snip.rv = \"foo\"`}', {})\n      expect(snippet.toString()).toBe('foo foo')\n    })\n\n    it('should resolve python when have python snippet', async () => {\n      await nvim.command('startinsert')\n      let res = await snippetManager.insertSnippet('${1:foo} `!p snip.rv = t[1]`', true, Range.create(0, 0, 0, 0), InsertTextMode.asIs, {}) as any\n      expect(res).toBe(true)\n      let snippet = await snippetManager.resolveSnippet('${1:x} `!p snip.rv= t[1]`', {})\n      expect(snippet.toString()).toBe('x x')\n    })\n\n    it('should throw when resolve throw error', async () => {\n      let s = snippetManager.session\n      let spy = jest.spyOn(s, 'resolveSnippet').mockImplementation(() => {\n        throw new Error('custom error')\n      })\n      await expect(() => {\n        return snippetManager.resolveSnippet('${1:x}')\n      }).rejects.toThrow(Error)\n      spy.mockRestore()\n    })\n  })\n\n  describe('normalizeInsertText()', () => {\n    it('should normalizeInsertText', async () => {\n      let doc = await workspace.document\n      let res = await snippetManager.normalizeInsertText(doc.bufnr, 'foo\\nbar', '  ', InsertTextMode.asIs)\n      expect(res).toBe('foo\\nbar')\n    })\n\n    it('should respect noExpand', async () => {\n      await nvim.command('startinsert')\n      let res = await snippetManager.insertSnippet('\\t\\t${1:foo}', true, Range.create(0, 0, 0, 0), InsertTextMode.adjustIndentation, {\n        noExpand: true\n      })\n      expect(res).toBe(true)\n      let line = await nvim.line\n      expect(line).toBe('\\t\\tfoo')\n    })\n  })\n\n  describe('insertSnippet command', () => {\n    it('should insert ultisnips snippet', async () => {\n      expect(SnippetManager).toBeDefined()\n      await nvim.setLine('foo')\n      let edit = TextEdit.replace(Range.create(0, 0, 0, 3), '${1:`echo \"bar\"`}')\n      await commandManager.executeCommand('editor.action.insertSnippet', edit, {})\n      let line = await nvim.line\n      expect(line).toBe('bar')\n      edit = TextEdit.replace(Range.create(0, 0, 0, 3), '${1:`echo \"foo\"`}')\n      await commandManager.executeCommand('editor.action.insertSnippet', edit, { regex: '' })\n      line = await nvim.line\n      expect(line).toBe('foo')\n    })\n  })\n\n  describe('Snippet context and actions', () => {\n    describe('context', () => {\n      it('should insert context snippet', async () => {\n        await nvim.setLine('prefix')\n        await nvim.input('A')\n        let isActive = await snippetManager.insertSnippet('pre${1:foo} $0', true, undefined, undefined, {\n          range: Range.create(0, 0, 0, 6),\n          context: `True;vim.vars['before'] = snip.before`\n        })\n        expect(isActive).toBe(true)\n        let before = await nvim.getVar('before')\n        expect(before).toBe('prefix')\n      })\n    })\n\n    describe('pre_expand', () => {\n      it('should insert with pre_expand and user set cursor', async () => {\n        await nvim.command('normal! gg')\n        await nvim.setLine('foo')\n        await nvim.input('A')\n        await snippetManager.insertSnippet('$1 ${2:bar}', true, Range.create(0, 0, 0, 3), undefined, {\n          actions: {\n            preExpand: \"snip.buffer[snip.line] = ' '*4; snip.cursor.set(snip.line, 4)\"\n          }\n        })\n        let line = await nvim.line\n        expect(line).toBe('     bar')\n        let pos = await window.getCursorPosition()\n        expect(pos).toEqual({ line: 0, character: 4 })\n        snippetManager.cancel()\n      })\n\n      it('should move to end of file with pre_expand', async () => {\n        let buf = await nvim.buffer\n        await buf.setLines(['x', 'foo'], { start: 0, end: 0 })\n        await nvim.command('normal! gg')\n        await nvim.input('A')\n        await snippetManager.insertSnippet('def $1():', true, Range.create(0, 0, 0, 1), undefined, {\n          actions: { preExpand: \"del snip.buffer[snip.line]; snip.buffer.append(''); snip.cursor.set(len(snip.buffer)-1, 0)\" }\n        })\n        let lines = await buf.lines\n        expect(lines).toEqual(['foo', '', 'def ():'])\n        let pos = await window.getCursorPosition()\n        expect(pos).toEqual({ line: 2, character: 4 })\n      })\n\n      it('should insert line before with pre_expand', async () => {\n        let buf = await nvim.buffer\n        await nvim.setLine('foo')\n        await nvim.command('normal! gg')\n        await nvim.input('A')\n        await snippetManager.insertSnippet('pre$1():', true, Range.create(0, 0, 0, 3), undefined, {\n          actions: {\n            preExpand: \"snip.buffer[snip.line:snip.line] = [''];\"\n          }\n        })\n        let lines = await buf.lines\n        expect(lines).toEqual(['', 'pre():'])\n        let pos = await window.getCursorPosition()\n        expect(pos).toEqual({ line: 1, character: 3 })\n      })\n\n      it('should insert snippetwith pre_expand as nested python snippet', async () => {\n        await snippetManager.insertSnippet('`!p snip.rv = \" \" * (10 - len(t[1]))`${1:inner}', true, Range.create(0, 0, 0, 0), InsertTextMode.asIs, {})\n        await nvim.setVar('coc_selected_text', 'bar')\n        await snippetManager.insertSnippet('${1:foo}', true, Range.create(0, 5, 0, 10), undefined, {\n          actions: {\n            preExpand: 'vim.vars[\"v\"] = snip.visual_content'\n          }\n        })\n        let line = await nvim.line\n        expect(line).toBe('       foo')\n        let res = await nvim.getVar('v')\n        expect(res).toBe('bar')\n        let val = await nvim.getVar('coc_selected_text')\n        expect(val).toBeNull()\n      })\n    })\n\n    describe('post_expand', () => {\n      it('should change snippet_start and snippet_end on lines change', async () => {\n        let buf = await nvim.buffer\n        await nvim.input('i')\n        let codes = [\n          \"snip.buffer[0:0] = ['', '']\",\n          \"vim.vars['first'] = [snip.snippet_start[0],snip.snippet_start[1],snip.snippet_end[0],snip.snippet_end[1]]\",\n          \"snip.buffer[0:1] = []\",\n          \"vim.vars['second'] = [snip.snippet_start[0],snip.snippet_start[1],snip.snippet_end[0],snip.snippet_end[1]]\",\n        ]\n        let activated = await snippetManager.insertSnippet('pre$1():', true, Range.create(0, 0, 0, 0), undefined, {\n          actions: { postExpand: codes.join(';') }\n        })\n        expect(activated).toBe(true)\n        let first = await nvim.getVar('first')\n        expect(first).toEqual([2, 0, 2, 6])\n        let second = await nvim.getVar('second')\n        expect(second).toEqual([1, 0, 1, 6])\n        let lines = await buf.lines\n        expect(lines).toEqual(['', 'pre():'])\n      })\n\n      it('should allow change after snippet', async () => {\n        await nvim.input('i')\n        let buf = await nvim.buffer\n        // add two new lines\n        let codes = [\n          \"snip.buffer[snip.snippet_end[0]+1:snip.snippet_end[0]+1] = ['', '']\",\n        ]\n        await snippetManager.insertSnippet('def $1()', true, Range.create(0, 0, 0, 0), undefined, {\n          actions: { postExpand: codes.join(';') }\n        })\n        let session = snippetManager.getSession(buf.id)\n        expect(session.isActive).toBe(true)\n        let lines = await buf.lines\n        expect(lines).toEqual(['def ()', '', ''])\n      })\n    })\n\n    describe('post_jump', () => {\n      it('should insert before snippet', async () => {\n        let buf = await nvim.buffer\n        await nvim.input('i')\n        let line = await nvim.call('line', ['.']) as number\n        let codes = [\n          'if snip.tabstop == 2: snip.buffer[0:0] = [\"aa\", \"bb\"];vim.vars[\"positions\"] = [snip.snippet_start[0], snip.snippet_end[0]];vim.vars[\"direction\"] = snip.jump_direction;',\n        ]\n        let activated = await snippetManager.insertSnippet('${1:foo} ${2:bar} $0', true, Range.create(line - 1, 0, line - 1, 0), undefined, {\n          actions: { postJump: codes.join(';') }\n        })\n        expect(activated).toBe(true)\n        await snippetManager.nextPlaceholder()\n        await events.race(['PlaceholderJump'], 500)\n        let lines = await buf.lines\n        expect(lines).toEqual(['aa', 'bb', 'foo bar '])\n        let positions = await nvim.getVar('positions')\n        expect(positions).toEqual([2, 2])\n        await snippetManager.previousPlaceholder()\n      })\n\n      it('should pass variables to snip', async () => {\n        await nvim.input('o')\n        let codes = [\n          \"vim.vars['positions'] = [snip.snippet_start[0],snip.snippet_start[1],snip.snippet_end[0],snip.snippet_end[1]]\",\n          \"vim.vars['tabstop'] = snip.tabstop\",\n          \"vim.vars['jump_direction'] = snip.jump_direction\",\n          \"vim.vars['tabstops'] = str(snip.tabstops)\",\n        ]\n        let activated = await snippetManager.insertSnippet('${1:foo} ${2:测试} $0', true, Range.create(1, 0, 1, 0), undefined, {\n          actions: { postJump: codes.join(';') }\n        })\n        expect(activated).toBe(true)\n        await events.race(['PlaceholderJump'], 200)\n        let positions = await nvim.getVar('positions')\n        expect(positions).toEqual([1, 0, 1, 7])\n        let tabstop = await nvim.getVar('tabstop')\n        expect(tabstop).toBe(1)\n        let dir = await nvim.getVar('jump_direction')\n        expect(dir).toBe(1)\n        let tabstops = await nvim.getVar('tabstops')\n        expect(tabstops).toMatch('测试')\n        await snippetManager.nextPlaceholder()\n        await snippetManager.previousPlaceholder()\n      })\n    })\n  })\n\n  describe('dispose()', () => {\n    it('should dispose', async () => {\n      let active = await snippetManager.insertSnippet('${1:foo}')\n      expect(active).toBe(true)\n      snippetManager.dispose()\n      expect(snippetManager.session).toBeUndefined()\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/snippets/parser.test.ts",
    "content": "/* eslint-disable */\nimport * as assert from 'assert'\nimport { EvalKind } from '../../snippets/eval'\nimport { Choice, CodeBlock, ConditionMarker, ConditionString, FormatString, getPlaceholderId, Marker, mergeTexts, Placeholder, Scanner, SnippetParser, Text, TextmateSnippet, TokenType, Transform, transformEscapes, Variable } from '../../snippets/parser'\n\ndescribe('SnippetParser', () => {\n\n  test('transformEscapes', () => {\n    assert.equal(transformEscapes('b\\\\uabc\\\\LDef'), 'bAbcdef')\n    assert.equal(transformEscapes('b\\\\lAbc\\\\LDef'), 'babcdef')\n    assert.equal(transformEscapes('b\\\\Uabc\\\\Edef'), 'bABCdef')\n    assert.equal(transformEscapes('b\\\\LABC\\\\Edef'), 'babcdef')\n    assert.equal(transformEscapes(' \\\\n \\\\t'), ' \\n \\t')\n  })\n\n  test('Empty Marker', () => {\n    assert.ok(Marker != null)\n    assert.strictEqual((new Text('')).snippet, undefined)\n  })\n\n  test('Scanner', () => {\n\n    const scanner = new Scanner()\n    assert.equal(scanner.next().type, TokenType.EOF)\n\n    scanner.text('abc')\n    assert.equal(scanner.next().type, TokenType.VariableName)\n    assert.equal(scanner.next().type, TokenType.EOF)\n\n    scanner.text('{{abc}}')\n    assert.equal(scanner.next().type, TokenType.CurlyOpen)\n    assert.equal(scanner.next().type, TokenType.CurlyOpen)\n    assert.equal(scanner.next().type, TokenType.VariableName)\n    assert.equal(scanner.next().type, TokenType.CurlyClose)\n    assert.equal(scanner.next().type, TokenType.CurlyClose)\n    assert.equal(scanner.next().type, TokenType.EOF)\n\n    scanner.text('abc() ')\n    assert.equal(scanner.next().type, TokenType.VariableName)\n    assert.equal(scanner.next().type, TokenType.OpenParen)\n    assert.equal(scanner.next().type, TokenType.CloseParen)\n    assert.equal(scanner.next().type, TokenType.Format)\n    assert.equal(scanner.next().type, TokenType.EOF)\n\n    scanner.text('abc 123')\n    assert.equal(scanner.next().type, TokenType.VariableName)\n    assert.equal(scanner.next().type, TokenType.Format)\n    assert.equal(scanner.next().type, TokenType.Int)\n    assert.equal(scanner.next().type, TokenType.EOF)\n\n    scanner.text('$foo')\n    assert.equal(scanner.next().type, TokenType.Dollar)\n    assert.equal(scanner.next().type, TokenType.VariableName)\n    assert.equal(scanner.next().type, TokenType.EOF)\n\n    scanner.text('$foo_bar')\n    assert.equal(scanner.next().type, TokenType.Dollar)\n    assert.equal(scanner.next().type, TokenType.VariableName)\n    assert.equal(scanner.next().type, TokenType.EOF)\n\n    scanner.text('$foo-bar')\n    assert.equal(scanner.next().type, TokenType.Dollar)\n    assert.equal(scanner.next().type, TokenType.VariableName)\n    assert.equal(scanner.next().type, TokenType.Dash)\n    assert.equal(scanner.next().type, TokenType.VariableName)\n    assert.equal(scanner.next().type, TokenType.EOF)\n\n    scanner.text('${foo}')\n    assert.equal(scanner.next().type, TokenType.Dollar)\n    assert.equal(scanner.next().type, TokenType.CurlyOpen)\n    assert.equal(scanner.next().type, TokenType.VariableName)\n    assert.equal(scanner.next().type, TokenType.CurlyClose)\n    assert.equal(scanner.next().type, TokenType.EOF)\n\n    scanner.text('${1223:foo}')\n    assert.equal(scanner.next().type, TokenType.Dollar)\n    assert.equal(scanner.next().type, TokenType.CurlyOpen)\n    assert.equal(scanner.next().type, TokenType.Int)\n    assert.equal(scanner.next().type, TokenType.Colon)\n    assert.equal(scanner.next().type, TokenType.VariableName)\n    assert.equal(scanner.next().type, TokenType.CurlyClose)\n    assert.equal(scanner.next().type, TokenType.EOF)\n\n    scanner.text('\\\\${}')\n    assert.equal(scanner.next().type, TokenType.Backslash)\n    assert.equal(scanner.next().type, TokenType.Dollar)\n    assert.equal(scanner.next().type, TokenType.CurlyOpen)\n    assert.equal(scanner.next().type, TokenType.CurlyClose)\n\n    scanner.text('${foo/regex/format/option}')\n    assert.equal(scanner.next().type, TokenType.Dollar)\n    assert.equal(scanner.next().type, TokenType.CurlyOpen)\n    assert.equal(scanner.next().type, TokenType.VariableName)\n    assert.equal(scanner.next().type, TokenType.Forwardslash)\n    assert.equal(scanner.next().type, TokenType.VariableName)\n    assert.equal(scanner.next().type, TokenType.Forwardslash)\n    assert.equal(scanner.next().type, TokenType.VariableName)\n    assert.equal(scanner.next().type, TokenType.Forwardslash)\n    assert.equal(scanner.next().type, TokenType.VariableName)\n    assert.equal(scanner.next().type, TokenType.CurlyClose)\n    assert.equal(scanner.next().type, TokenType.EOF)\n  })\n\n  function assertText(value: string, expected: string, ultisnip = false) {\n    const p = new SnippetParser(ultisnip)\n    const actual = p.text(value)\n    assert.equal(actual, expected)\n  }\n\n  function assertMarker(input: TextmateSnippet | Marker[] | string, ...ctors: Function[]) {\n    let marker: Marker[]\n    if (input instanceof TextmateSnippet) {\n      marker = input.children\n    } else if (typeof input === 'string') {\n      const p = new SnippetParser()\n      marker = p.parse(input).children\n    } else {\n      marker = input\n    }\n    while (marker.length > 0) {\n      let m = marker.pop()\n      let ctor = ctors.pop()\n      assert.ok(m instanceof ctor)\n    }\n    assert.equal(marker.length, ctors.length)\n    assert.equal(marker.length, 0)\n  }\n\n  function assertTextAndMarker(value: string, escaped: string, ...ctors: Function[]) {\n    assertText(value, escaped)\n    assertMarker(value, ...ctors)\n  }\n\n  function assertEscaped(value: string, expected: string) {\n    const actual = SnippetParser.escape(value)\n    assert.equal(actual, expected)\n  }\n\n  test('Parser, escaped', function() {\n    assertEscaped('foo$0', 'foo\\\\$0')\n    assertEscaped('foo\\\\$0', 'foo\\\\\\\\\\\\$0')\n    assertEscaped('f$1oo$0', 'f\\\\$1oo\\\\$0')\n    assertEscaped('${1:foo}$0', '\\\\${1:foo\\\\}\\\\$0')\n    assertEscaped('$', '\\\\$')\n  })\n\n  test('Parser, escaped ultisnips', () => {\n    const actual = new SnippetParser(true).text('t\\\\`a\\\\`\\n\\\\$ \\\\{\\\\}')\n    expect(actual).toBe('t`a`\\n$ {}')\n  })\n\n  test('Parser, transform with empty placeholder', () => {\n    const actual = new SnippetParser(true).text('${1} ${1/^(.*)/$1aa/}')\n    expect(actual).toBe(' aa')\n  })\n\n  test('Parser, isPlainText()', function() {\n    const s = (input: string, res: boolean) => {\n      assert.equal(SnippetParser.isPlainText(input), res)\n    }\n    s('abc', true)\n    s('abc$0', true)\n    s('ab$0chh', false)\n    s('ab$1chh', false)\n  })\n\n  test('Parser, paried curly brace in placeholder', () => {\n    const getText = (text): string => {\n      const parser = new SnippetParser(false)\n      let snip = parser.parse(text)\n      let res: Text\n      snip.walk(marker => {\n        if (marker instanceof Text) {\n          res = marker\n        }\n        return true\n      })\n      return res ? res.value : undefined\n    }\n\n    let text = getText('${1:{foo}}')\n    expect(text).toBe('{foo}')\n    text = getText('${1:ab{foo}}')\n    expect(text).toBe('ab{foo}')\n    text = getText('${1:ab{foo}cd}')\n    expect(text).toBe('ab{foo}cd')\n  })\n\n  test('Parser, first placeholder / variable', function() {\n    const first = (input: string): Marker => {\n      const p = new SnippetParser(false)\n      let s = p.parse(input, true)\n      return s.first\n    }\n    const assertPlaceholder = (m: any, index: number) => {\n      assert.equal(m instanceof Placeholder, true)\n      assert.equal(m.index, index)\n    }\n    assertPlaceholder(first('foo'), 0)\n    assertPlaceholder(first('${1:foo}'), 1)\n    assertPlaceholder(first('${2:foo}'), 2)\n\n    const p = new SnippetParser(false)\n    let s = p.parse('${1/from/to/}', true)\n    let placeholder = s.placeholders[0]\n    assert.strictEqual(placeholder.toTextmateString(), '${1/from/to/}')\n  })\n\n  test('Parser, text', () => {\n    assertText('$', '$')\n    assertText('\\\\\\\\$', '\\\\$')\n    assertText('{', '{')\n    assertText('\\\\}', '}')\n    assertText('\\\\abc', '\\\\abc')\n    assertText('foo${f:\\\\}}bar', 'foo}bar')\n    assertText('\\\\{', '\\\\{')\n    assertText('I need \\\\\\\\\\\\$', 'I need \\\\$')\n    assertText('\\\\', '\\\\')\n    assertText('\\\\{{', '\\\\{{')\n    assertText('{{', '{{')\n    assertText('{{dd', '{{dd')\n    assertText('}}', '}}')\n    assertText('ff}}', 'ff}}')\n\n    assertText('${foo/.*/complex${1:/upcase/i}', '${foo/.*/complex/upcase/i')\n    assertText('${foo/.*/${1/upcase}', '${foo/.*/${1/upcase}')\n    assertText('${VISUAL/.*/complex${1:/upcase}/i}', '${VISUAL/.*/complex/upcase/i}', true)\n    assertText('${foo/.*/complex${p:/upcase}/i}', '${foo/.*/complex/upcase/i}')\n\n    assertText('farboo', 'farboo')\n    assertText('far{{}}boo', 'far{{}}boo')\n    assertText('far{{123}}boo', 'far{{123}}boo')\n    assertText('far\\\\{{123}}boo', 'far\\\\{{123}}boo')\n    assertText('far{{id:bern}}boo', 'far{{id:bern}}boo')\n    assertText('far{{id:bern {{basel}}}}boo', 'far{{id:bern {{basel}}}}boo')\n    assertText('far{{id:bern {{id:basel}}}}boo', 'far{{id:bern {{id:basel}}}}boo')\n    assertText('far{{id:bern {{id2:basel}}}}boo', 'far{{id:bern {{id2:basel}}}}boo')\n  })\n\n  test('Parser ConditionMarker', () => {\n    {\n      let m = new ConditionMarker(1,\n        [new Text('a '), new FormatString(1)],\n        [new Text('b '), new FormatString(2)],\n      )\n      let val = m.resolve('', ['', 'foo', 'bar'])\n      expect(val).toBe('b bar')\n      val = m.resolve('x', ['', 'foo', 'bar'])\n      expect(val).toBe('a foo')\n      m.addIfMarker(new Text('if'))\n      m.addElseMarker(new Text('else'))\n      let s = m.toTextmateString()\n      expect(s).toBe('(?1:a ${1}if:b ${2}else)')\n      expect(m.clone()).toBeDefined()\n    }\n    {\n      let m = new ConditionMarker(1,\n        [new Text('foo')],\n        []\n      )\n      let text = m.toTextmateString()\n      expect(text).toBe('(?1:foo)')\n    }\n  })\n\n  test('Parser, TM text', () => {\n    assertTextAndMarker('foo${1:bar}}', 'foobar}', Text, Placeholder, Text)\n    assertTextAndMarker('foo${1:bar}${2:foo}}', 'foobarfoo}', Text, Placeholder, Placeholder, Text)\n    assertTextAndMarker('foo${1:bar\\\\}${2:foo}}', 'foobar}foo', Text, Placeholder)\n\n    let [, placeholder] = new SnippetParser().parse('foo${1:bar\\\\}${2:foo}}').children\n    let { children } = (<Placeholder>placeholder)\n\n    assert.equal((<Placeholder>placeholder).index, '1')\n    assert.ok(children[0] instanceof Text)\n    assert.equal(children[0].toString(), 'bar}')\n    assert.ok(children[1] instanceof Placeholder)\n    assert.equal(children[1].toString(), 'foo')\n  })\n\n  test('Parser, placeholder', () => {\n    assertTextAndMarker('farboo', 'farboo', Text)\n    assertTextAndMarker('far{{}}boo', 'far{{}}boo', Text)\n    assertTextAndMarker('far{{123}}boo', 'far{{123}}boo', Text)\n    assertTextAndMarker('far\\\\{{123}}boo', 'far\\\\{{123}}boo', Text)\n  })\n\n  test('Parser, literal code', () => {\n    assertTextAndMarker('far`123`boo', 'far`123`boo', Text)\n    assertTextAndMarker('far\\\\`123\\\\`boo', 'far\\\\`123\\\\`boo', Text)\n  })\n\n  test('Parser, variables/tabstop', () => {\n    assertTextAndMarker('$far-boo', '-boo', Variable, Text)\n    assertTextAndMarker('\\\\$far-boo', '$far-boo', Text)\n    assertTextAndMarker('far$farboo', 'far', Text, Variable)\n    assertTextAndMarker('far${farboo}', 'far', Text, Variable)\n    assertTextAndMarker('$123', '', Placeholder)\n    assertTextAndMarker('$farboo', '', Variable)\n    assertTextAndMarker('$far12boo', '', Variable)\n    assertTextAndMarker('000_${far}_000', '000__000', Text, Variable, Text)\n    assertTextAndMarker('FFF_${TM_SELECTED_TEXT}_FFF$0', 'FFF__FFF', Text, Variable, Text, Placeholder)\n  })\n\n  test('Parser, variables/placeholder with defaults', () => {\n    assertTextAndMarker('${name:value}', 'value', Variable)\n    assertTextAndMarker('${1:value}', 'value', Placeholder)\n    assertTextAndMarker('${1:bar${2:foo}bar}', 'barfoobar', Placeholder)\n\n    assertTextAndMarker('${name:value', '${name:value', Text)\n    assertTextAndMarker('${1:bar${2:foobar}', '${1:barfoobar', Text, Placeholder)\n  })\n\n  test('Parser, variable transforms', function() {\n    assertTextAndMarker('${foo///}', '', Variable)\n    assertTextAndMarker('${foo/regex/format/gmi}', '', Variable)\n    assertTextAndMarker('${foo/([A-Z][a-z])/format/}', '', Variable)\n\n    // invalid regex\n    assertTextAndMarker('${foo/([A-Z][a-z])/format/GMI}', '${foo/([A-Z][a-z])/format/GMI}', Text)\n    assertTextAndMarker('${foo/([A-Z][a-z])/format/funky}', '${foo/([A-Z][a-z])/format/funky}', Text)\n    assertTextAndMarker('${foo/([A-Z][a-z]/format/}', '${foo/([A-Z][a-z]/format/}', Text)\n\n    // tricky regex\n    assertTextAndMarker('${foo/m\\\\/atch/$1/i}', '', Variable)\n    assertMarker('${foo/regex\\/format/options}', Text)\n\n    // incomplete\n    assertTextAndMarker('${foo///', '${foo///', Text)\n    assertTextAndMarker('${foo/regex/format/options', '${foo/regex/format/options', Text)\n\n    // format string\n    assertMarker('${foo/.*/${0:fooo}/i}', Variable)\n    assertMarker('${foo/.*/${1}/i}', Variable)\n    assertMarker('${foo/.*/$1/i}', Variable)\n    assertMarker('${foo/.*/This-$1-encloses/i}', Variable)\n    assertMarker('${foo/.*/complex${1:else}/i}', Variable)\n    assertMarker('${foo/.*/complex${1:-else}/i}', Variable)\n    assertMarker('${foo/.*/complex${1:+if}/i}', Variable)\n    assertMarker('${foo/.*/complex${1:?if:else}/i}', Variable)\n    assertMarker('${foo/.*/complex${1:/upcase}/i}', Variable)\n\n  })\n\n  test('Parse, parse code block', () => {\n    assertText('aa \\\\`echo\\\\`', 'aa `echo`', true)\n    assertText('aa `xyz`', 'aa ', true)\n    assertText('aa `!v xyz`', 'aa ', true)\n    assertText('aa `!p xyz`', 'aa ', true)\n    assertText('aa `!p foo\\nbar`', 'aa ', true)\n    assertText('aa `!p py', 'aa `!p py', true)\n    assertText('aa `!p \\n`', 'aa ', true)\n    assertText('aa `!p\\n  1\\n  2`', 'aa ', true)\n    const c = text => {\n      return (new SnippetParser(true)).parse(text)\n    }\n    assertMarker(c('`foo`'), CodeBlock)\n    assertMarker(c('`!v bar`'), CodeBlock)\n    assertMarker(c('`!p python`'), CodeBlock)\n    const assertPlaceholder = (text: string, kind: EvalKind, code: string) => {\n      let p = c(text).children[0]\n      assert.ok(p instanceof Placeholder)\n      let m = p.children[0] as CodeBlock\n      assert.ok(m instanceof CodeBlock)\n      assert.equal(m.kind, kind)\n      assert.equal(m.code, code)\n    }\n    assertPlaceholder('${1:` foo `}', 'shell', 'foo')\n    assertPlaceholder('${1:`!v bar`}', 'vim', 'bar')\n    assertPlaceholder('${1:`!p python`}', 'python', 'python')\n    assertPlaceholder('${1:`!p x\\\\`y`}', 'python', 'x\\\\`y')\n    assertPlaceholder('${1:`!p x\\ny`}', 'python', 'x\\ny')\n    assertPlaceholder('${1:`!p \\nx\\ny`}', 'python', 'x\\ny')\n  })\n\n  test('Parser, CodeBlock toTextmateString', () => {\n    const c = text => {\n      return (new SnippetParser(true)).parse(text)\n    }\n    expect(c('`foo`').toTextmateString()).toBe('`foo`')\n    expect(c('`!p snip.rv`').toTextmateString()).toBe('`!p snip.rv`')\n    expect(c('`!v \"var\"`').toTextmateString()).toBe('`!v \"var\"`')\n  })\n\n  test('Parser, placeholder with CodeBlock primary', () => {\n    const c = text => {\n      return (new SnippetParser(true)).parse(text)\n    }\n    let s = c('${1/^_(.*)/$1/} $1 aa ${1:`!p snip.rv = \"_foo\"`}')\n    let arr = s.placeholders\n    arr = arr.filter(o => o.index == 1)\n    assert.equal(arr.length, 3)\n    let filtered = arr.filter(o => o.primary === true)\n    assert.equal(filtered.length, 1)\n    assert.equal(filtered[0], arr[2])\n    let children = arr.map(o => o.children[0])\n    assert.ok(children[0] instanceof Text)\n    assert.ok(children[1] instanceof Text)\n    assert.ok(children[2] instanceof CodeBlock)\n  })\n\n  test('Parser, placeholder with CodeBlock not primary', () => {\n    const c = text => {\n      return (new SnippetParser(true)).parse(text)\n    }\n    let s = c('${1/^_(.*)/$1/} ${1:_foo} ${2:bar} $1 $3 ${1:`!p snip.rv = \"three\"`}')\n    let arr = s.placeholders\n    arr = arr.filter(o => o.index == 1)\n    assert.equal(arr.length, 4)\n    assert.ok(arr[0].transform)\n    assert.equal(arr[1].primary, true)\n    assert.equal(arr[2].toString(), '_foo')\n    assert.equal(arr[3].toString(), '_foo')\n    assert.deepEqual(s.values, { '0': '', '1': '_foo', '2': 'bar', '3': '' })\n    arr[1].index = 1.1\n    assert.deepEqual(s.values, { '0': '', '1': '_foo', '2': 'bar', '3': '' })\n    s = c('${1:`!p snip.rv = t[2]`} ${2:`!p snip.rv = t[1]`}')\n    assert.deepEqual(s.orderedPyIndexBlocks, [])\n  })\n\n  test('Parser, python CodeBlock with related', () => {\n    const c = text => {\n      return (new SnippetParser(true)).parse(text)\n    }\n    let s = c('${1:_foo} ${2:bar} $1 $3 ${3:`!p snip.rv = str(t[1]) + str(t[2])`}')\n    let b = s.pyBlocks[0]\n    expect(b).toBeDefined()\n    expect(b.related).toEqual([1, 2])\n  })\n\n  test('Parser, python CodeBlock by sequence', () => {\n    const c = text => {\n      return (new SnippetParser(true)).parse(text)\n    }\n    let s = c('${2:\\{${3:`!p foo`}\\}} ${1:`!p bar`}')\n    let arr = s.pyBlocks\n    expect(arr[0].code).toBe('foo')\n    expect(arr[1].code).toBe('bar')\n  })\n\n  test('Parser, hasPython()', () => {\n    const c = text => {\n      return (new SnippetParser(true)).parse(text)\n    }\n    assert.equal(c('${1:`!p foo`}').hasPythonBlock, true)\n    assert.equal(c('`!p foo`').hasPythonBlock, true)\n    assert.equal(c('$1').hasPythonBlock, false)\n  })\n\n  test('Parser, insertBefore', () => {\n    const c = text => {\n      return (new SnippetParser(true)).parse(text)\n    }\n    let m = new Placeholder(2)\n    m.insertBefore('\\n')\n    let p = new Placeholder(1)\n    m.parent = p\n    m.insertBefore('\\n')\n    {\n      let s = c('start ${1:foo}')\n      p = s.children[1] as Placeholder\n      p.insertBefore('\\n')\n      let t = s.children[0] as Text\n      assert.equal(t.value, 'start \\n')\n    }\n    {\n      let s = c('${1:foo} end')\n      p = s.children[0] as Placeholder\n      p.insertBefore('\\n')\n      let t = s.children[0] as Text\n      assert.equal(t.value, '\\n')\n    }\n  })\n\n  test('Parser, hasCodeBlock()', () => {\n    const c = text => {\n      return (new SnippetParser(true)).parse(text)\n    }\n    assert.equal(c('${1:`!p foo`}').hasCodeBlock, true)\n    assert.equal(c('`!p foo`').hasCodeBlock, true)\n    assert.equal(c('$1').hasCodeBlock, false)\n    let s = (new SnippetParser(false)).parse('`!p foo`', true)\n    assert.strictEqual(s.hasCodeBlock, false)\n    let len = s.fullLen(s.children[1])\n    assert.strictEqual(len, 0)\n  })\n\n  test('Parser, resolved variable', async () => {\n    const c = text => {\n      return (new SnippetParser(true)).parse(text)\n    }\n    let s = c('${1:${VISUAL}} $1')\n    assert.ok(s.children[0] instanceof Placeholder)\n    assert.ok(s.children[0].children[0] instanceof Variable)\n    let v = s.children[0].children[0] as Variable\n    assert.equal(v.name, 'VISUAL')\n    {\n      let s = c('`!p`')\n      let m = s.children[0] as CodeBlock\n      await m.resolve(undefined as any)\n    }\n  })\n\n  test('Parser, convert and resolve variables', async () => {\n    const c = text => {\n      return (new SnippetParser(false)).parse(text)\n    }\n    {\n      let s = c('${1:${foo}x${foo:bar}} $1')\n      await s.resolveVariables({\n        resolve: async (variable) => {\n          if (variable.name == 'foo') return 'f'\n          return undefined\n        }\n      })\n      assert.equal(s.placeholders[0].children.length, 1)\n      assert.equal(s.toString(), 'fxf fxf')\n    }\n    {\n      let s = c('${myname/(.*)$/${1:/capitalize}/}')\n      let variable = s.children[0] as Variable\n      variable.appendChild(new Text(''))\n      expect(s.toTextmateString()).toBe('${myname:/(.*)$/${1:/capitalize}/}')\n      s = s.clone()\n      await s.resolveVariables({\n        resolve: async (_variable) => {\n          return undefined\n        }\n      })\n      expect(s.toString()).toBe('Myname')\n    }\n  })\n\n  test('Parser, resolved ultisnip variable', async () => {\n    const c = text => {\n      return (new SnippetParser(true)).parse(text)\n    }\n    let s = c('${VISUAL/\\\\w+\\\\s*/\\\\u$0\\\\\\\\x/} ${visual}')\n    await s.resolveVariables({\n      resolve: async (variable) => {\n        if (variable.name === 'VISUAL') return 'visual'\n        return ''\n      }\n    })\n    expect(s.clone().toString()).toBe('Visual\\\\x ${visual}')\n  })\n\n  test('Parser variable with code', () => {\n    // not allowed on ultisnips.\n    const c = text => {\n      return (new SnippetParser(true)).parse(text)\n    }\n    let s = c('${foo:`!p snip.rv = \"bar\"`}')\n    assert.ok(s.children[0] instanceof Text)\n    assert.ok(s.children[1] instanceof CodeBlock)\n  })\n\n  test('Parser, transform condition if text', () => {\n    const p = new SnippetParser(true)\n    let snip = p.parse('begin|${1:t}${1/(t)$|(a)$|(.*)/(?1:abular)(?2:rray)/}')\n    expect(snip.toString()).toBe('begin|tabular')\n    let m = snip.placeholders.find(o => o.index == 1 && o.primary)\n    m.setOnlyChild(new Text('a'))\n    snip.onPlaceholderUpdate(m)\n    expect(snip.toString()).toBe('begin|array')\n  })\n\n  test('Parser, transform condition not match', () => {\n    const p = new SnippetParser(true)\n    let snip = p.parse('${1:xyz} ${1/^(f)(b?)/(?2:_:two)/}')\n    expect(snip.toString()).toBe('xyz xyz')\n  })\n\n  test('Parser, transform backslash in condition', () => {\n    const p = new SnippetParser(true)\n    let snip = p.parse('${1:foo} ${1/^(f)/(?1:x\\\\)\\\\:a:two)/}')\n    expect(snip.toString()).toBe('foo x):aoo')\n  })\n\n  test('Parser, transform backslash in format string', () => {\n    const p = new SnippetParser(true)\n    let snip = p.parse('${1:\\\\n} ${1/^(\\\\\\\\n)/$1aa/}')\n    expect(snip.toString()).toBe('\\\\n \\\\naa')\n  })\n\n  test('Parser, ultisnips transform replacement', () => {\n    const p = new SnippetParser(true)\n    let snip = p.parse('${1:foo} ${1/^\\\\w/$0_/}')\n    expect(snip.toString()).toBe('foo f_oo')\n    snip = p.parse('${1:foo} ${1/^\\\\w//}')\n    expect(snip.toString()).toBe('foo oo')\n    snip = p.parse('${1:Foo} ${1/^(\\\\w+)$/\\\\u$1 (?1:-\\\\l$1)/g}')\n    expect(snip.toString()).toBe('Foo Foo -foo')\n  })\n\n\n  test('Parser, convert ultisnips regex', () => {\n    const p = new SnippetParser(true)\n    let snip = p.parse('${1:foo} ${1/^\\\\A/_/}')\n    expect(snip.toString()).toBe('foo _foo')\n  })\n\n  test('Parser, transform condition else text', () => {\n    const p = new SnippetParser(true)\n    let snip = p.parse('${1:foo} ${1/^(f)(b?)/(?2:_:two)/}')\n    expect(snip.toString()).toBe('foo twooo')\n    let m = snip.placeholders.find(o => o.index == 1 && o.primary)\n    m.setOnlyChild(new Text('fb'))\n    snip.onPlaceholderUpdate(m)\n    expect(snip.toString()).toBe('fb _')\n  })\n\n  test('Parser, transform escape sequence', () => {\n    const p = new SnippetParser(true)\n    const snip = p.parse('${1:a text}\\n${1/\\\\w+\\\\s*/\\\\u$0/}')\n    expect(snip.toString()).toBe('a text\\nA text')\n  })\n\n  test('Parser, transform backslash', () => {\n    const p = new SnippetParser(true)\n    const snip = p.parse('${1:a}\\n${1/\\\\w+/\\\\(\\\\)\\\\:\\\\x\\\\\\\\y/}')\n    expect(snip.toString()).toBe('a\\n():\\\\x\\\\y')\n  })\n\n  test('Parser, transform with ascii option', () => {\n    let p = new SnippetParser()\n    let snip = p.parse('${1:pêche}\\n${1/.*/$0/a}')\n    expect(snip.toString()).toBe('pêche\\npeche')\n    p = new SnippetParser()\n    snip = p.parse('${1/.*/$0/a}\\n${1:pêche}')\n    expect(snip.toString()).toBe('peche\\npêche')\n  })\n\n  test('Parser, placeholder with transform', () => {\n    const p = new SnippetParser()\n    const snippet = p.parse('${1:type}${1/(.+)/ /}')\n    let s = snippet.toString()\n    assert.equal(s.length, 5)\n  })\n\n  test('Parser, placeholder transforms', function() {\n    assertTextAndMarker('${1///}', '', Placeholder)\n    assertTextAndMarker('${1/regex/format/gmi}', '', Placeholder)\n    assertTextAndMarker('${1/([A-Z][a-z])/format/}', '', Placeholder)\n    assertTextAndMarker('${1///}', '', Placeholder)\n\n    // tricky regex\n    assertTextAndMarker('${1/m\\\\/atch/$1/i}', '', Placeholder)\n    assertMarker('${1/regex\\/format/options}', Text)\n\n    // incomplete\n    assertTextAndMarker('${1///', '${1///', Text)\n    assertTextAndMarker('${1/regex/format/options', '${1/regex/format/options', Text)\n  })\n\n  test('No way to escape forward slash in snippet regex #36715', function() {\n    assertMarker('${TM_DIRECTORY/src\\\\//$1/}', Variable)\n  })\n\n  test('No way to escape forward slash in snippet format section #37562', function() {\n    assertMarker('${TM_SELECTED_TEXT/a/\\\\/$1/g}', Variable)\n    assertMarker('${TM_SELECTED_TEXT/a/in\\\\/$1ner/g}', Variable)\n    assertMarker('${TM_SELECTED_TEXT/a/end\\\\//g}', Variable)\n  })\n\n  test('Parser, placeholder with choice', () => {\n\n    assertTextAndMarker('${1|one,two,three|}', 'one', Placeholder)\n    assertTextAndMarker('${1|one|}', 'one', Placeholder)\n    assertTextAndMarker('${1|one1,two2|}', 'one1', Placeholder)\n    assertTextAndMarker('${1|one1\\\\,two2|}', 'one1,two2', Placeholder)\n    assertTextAndMarker('${1|one1\\\\|two2|}', 'one1|two2', Placeholder)\n    assertTextAndMarker('${1|one1\\\\atwo2|}', 'one1\\\\atwo2', Placeholder)\n    assertTextAndMarker('${1|one,two,three,|}', '${1|one,two,three,|}', Text)\n    assertTextAndMarker('${1|one,', '${1|one,', Text)\n\n    const p = new SnippetParser()\n    const snippet = p.parse('${1|one,two,three|}')\n    assertMarker(snippet, Placeholder)\n    const expected = [Placeholder, Text, Text, Text]\n    snippet.walk(marker => {\n      assert.equal(marker, expected.shift())\n      return true\n    })\n  })\n\n  test('Snippet choices: unable to escape comma and pipe, #31521', function() {\n    assertTextAndMarker('console.log(${1|not\\\\, not, five, 5, 1   23|});', 'console.log(not, not);', Text, Placeholder, Text)\n  })\n\n  test('Marker, basic toTextmateString', function() {\n\n    function assertTextsnippetString(input: string, expected: string): void {\n      const snippet = new SnippetParser().parse(input)\n      const actual = snippet.toTextmateString()\n      assert.equal(actual, expected)\n    }\n\n    assertTextsnippetString('$1', '$1')\n    assertTextsnippetString('\\\\$1', '\\\\$1')\n    assertTextsnippetString('console.log(${1|not\\\\, not, five, 5, 1   23|});', 'console.log(${1|not\\\\, not, five, 5, 1   23|});')\n    assertTextsnippetString('console.log(${1|not\\\\, not, \\\\| five, 5, 1   23|});', 'console.log(${1|not\\\\, not, \\\\| five, 5, 1   23|});')\n    assertTextsnippetString('this is text', 'this is text')\n    assertTextsnippetString('this ${1:is ${2:nested with $var}}', 'this ${1:is ${2:nested with ${var}}}')\n    assertTextsnippetString('this ${1:is ${2:nested with $var}}}', 'this ${1:is ${2:nested with ${var}}}\\\\}')\n    {\n      const snippet = new SnippetParser(true).parse('${1:Foo} ${1/^(\\\\w+)$/\\\\x\\\\u$1/g}')\n      const actual = snippet.children[2].toTextmateString()\n      expect(actual).toBe('${1:\\\\\\\\xFoo/^(\\\\w+)$/\\\\\\\\x\\\\u${1}/g}')\n    }\n  })\n\n  test('Marker, toTextmateString() <-> identity', function() {\n\n    function assertIdent(input: string): void {\n      // full loop: (1) parse input, (2) generate textmate string, (3) parse, (4) ensure both trees are equal\n      const snippet = new SnippetParser().parse(input)\n      const input2 = snippet.toTextmateString()\n      const snippet2 = new SnippetParser().parse(input2)\n\n      function checkCheckChildren(marker1: Marker, marker2: Marker) {\n        assert.ok(marker1 instanceof Object.getPrototypeOf(marker2).constructor)\n        assert.ok(marker2 instanceof Object.getPrototypeOf(marker1).constructor)\n\n        assert.equal(marker1.children.length, marker2.children.length)\n        assert.equal(marker1.toString(), marker2.toString())\n\n        for (let i = 0; i < marker1.children.length; i++) {\n          checkCheckChildren(marker1.children[i], marker2.children[i])\n        }\n      }\n\n      checkCheckChildren(snippet, snippet2)\n    }\n\n    assertIdent('$1')\n    assertIdent('\\\\$1')\n    assertIdent('console.log(${1|not\\\\, not, five, 5, 1   23|});')\n    assertIdent('console.log(${1|not\\\\, not, \\\\| five, 5, 1   23|});')\n    assertIdent('this is text')\n    assertIdent('this ${1:is ${2:nested with $var}}')\n    assertIdent('this ${1:is ${2:nested with $var}}}')\n    assertIdent('this ${1:is ${2:nested with $var}} and repeating $1')\n  })\n\n  test('Parser, choice marker', () => {\n    const { placeholders } = new SnippetParser().parse('${1|one,two,three|}')\n\n    assert.equal(placeholders.length, 1)\n    assert.ok(placeholders[0].choice instanceof Choice)\n    assert.ok(placeholders[0].choice.clone() instanceof Choice)\n    assert.ok(placeholders[0].children[0] instanceof Choice)\n    assert.equal((<Choice>placeholders[0].children[0]).options.length, 3)\n\n    assertText('${1|one,two,three|}', 'one')\n    assertText('\\\\${1|one,two,three|}', '${1|one,two,three|}')\n    assertText('${1\\\\|one,two,three|}', '${1\\\\|one,two,three|}')\n    assertText('${1||}', '${1||}')\n  })\n\n  test('Backslash character escape in choice tabstop doesn\\'t work #58494', function() {\n\n    const { placeholders } = new SnippetParser().parse('${1|\\\\,,},$,\\\\|,\\\\\\\\|}')\n    assert.equal(placeholders.length, 1)\n    assert.ok(placeholders[0].choice instanceof Choice)\n  })\n\n  test('Parser, only textmate', () => {\n    const p = new SnippetParser()\n    assertMarker(p.parse('far{{}}boo'), Text)\n    assertMarker(p.parse('far{{123}}boo'), Text)\n    assertMarker(p.parse('far\\\\{{123}}boo'), Text)\n\n    assertMarker(p.parse('far$0boo'), Text, Placeholder, Text)\n    assertMarker(p.parse('far${123}boo'), Text, Placeholder, Text)\n    assertMarker(p.parse('far\\\\${123}boo'), Text)\n  })\n\n  test('Parser, real world', () => {\n    let marker = new SnippetParser().parse('console.warn(${1: $TM_SELECTED_TEXT })').children\n\n    assert.equal(marker[0].toString(), 'console.warn(')\n    assert.ok(marker[1] instanceof Placeholder)\n    assert.equal(marker[2].toString(), ')')\n\n    const placeholder = <Placeholder>marker[1]\n    assert.equal(placeholder, false)\n    assert.equal(placeholder.index, '1')\n    assert.equal(placeholder.children.length, 3)\n    assert.ok(placeholder.children[0] instanceof Text)\n    assert.ok(placeholder.children[1] instanceof Variable)\n    assert.ok(placeholder.children[1].clone() instanceof Variable)\n    assert.ok(placeholder.children[2] instanceof Text)\n    assert.equal(placeholder.children[0].toString(), ' ')\n    assert.equal(placeholder.children[1].toString(), '')\n    assert.equal(placeholder.children[2].toString(), ' ')\n\n    const nestedVariable = <Variable>placeholder.children[1]\n    assert.equal(nestedVariable.name, 'TM_SELECTED_TEXT')\n    assert.equal(nestedVariable.children.length, 0)\n\n    marker = new SnippetParser().parse('$TM_SELECTED_TEXT').children\n    assert.equal(marker.length, 1)\n    assert.ok(marker[0] instanceof Variable)\n  })\n\n  test('Parser, transform example', () => {\n    let { children } = new SnippetParser().parse('${1:name} : ${2:type}${3/\\\\s:=(.*)/${1:+ :=}${1}/};\\n$0')\n\n    //${1:name}\n    assert.ok(children[0] instanceof Placeholder)\n    assert.equal(children[0].children.length, 1)\n    assert.equal(children[0].children[0].toString(), 'name')\n    assert.equal((<Placeholder>children[0]).transform, undefined)\n\n    // :\n    assert.ok(children[1] instanceof Text)\n    assert.equal(children[1].toString(), ' : ')\n\n    //${2:type}\n    assert.ok(children[2] instanceof Placeholder)\n    assert.equal(children[2].children.length, 1)\n    assert.equal(children[2].children[0].toString(), 'type')\n\n    //${3/\\\\s:=(.*)/${1:+ :=}${1}/}\n    assert.ok(children[3] instanceof Placeholder)\n    assert.equal(children[3].children.length, 1)\n    assert.notEqual((<Placeholder>children[3]).transform, undefined)\n    let transform = (<Placeholder>children[3]).transform\n    assert.equal(transform.regexp, '/\\\\s:=(.*)/')\n    assert.equal(transform.children.length, 2)\n    assert.ok(transform.children[0] instanceof FormatString)\n    assert.equal((<FormatString>transform.children[0]).index, 1)\n    assert.equal((<FormatString>transform.children[0]).ifValue, ' :=')\n    assert.ok(transform.children[1] instanceof FormatString)\n    assert.equal((<FormatString>transform.children[1]).index, 1)\n    assert.ok(children[4] instanceof Text)\n    assert.equal(children[4].toString(), ';\\n')\n\n  })\n\n  test('Parser, ConditionString', () => {\n    assert.ok(ConditionString != undefined)\n    let s = new ConditionString(0, 'if', 'else')\n    assert.strictEqual(s.toTextmateString(), '(?0:if:else)')\n    s = new ConditionString(0, 'if', '')\n    assert.strictEqual(s.clone().toTextmateString(), '(?0:if)')\n    // invalid examples\n    assertText('$1 ${1/.*/(?p:foo:bar)/}', ' (?p:foo:bar)', true)\n    assertText('$1 ${1/.*/(?1foobar)/}', ' (?1foobar)', true)\n    assertText('$1 ${1/.*/(?1:foo:bar/}', ' (?1:foo:bar', true)\n    assertText('$1 ${1/.*/', ' ${1/.*/', true)\n    assertText('${foo', '${foo')\n    assertText('$foo', '$foo', true)\n    assertText('${foo}', '${foo}', true)\n    assertText('$1 ${1/.*/(?1:', ' ${1/.*/(?1:', true)\n  })\n\n  test('Parser, FormatString', () => {\n    let { children } = new SnippetParser().parse('${foo/^x/complex${1:?if:else}/i}')\n    let transform = children[0]['transform'] as Transform\n    let res = transform.resolve('y')\n    assert.strictEqual(res, 'complexelse')\n    let formatString = transform.children[1] as FormatString\n    assert.strictEqual(formatString.toTextmateString(), '${1:?if:else}')\n    let assertResolve = (shorthandName: string, value: string, result: string) => {\n      let formatString = new FormatString(0, shorthandName)\n      assert.strictEqual(formatString.resolve(value), result)\n      assert.ok(formatString.toTextmateString().includes(shorthandName))\n    }\n    assertResolve('upcase', '', '')\n    assertResolve('downcase', '', '')\n    assertResolve('capitalize', '', '')\n    assertResolve('pascalcase', '', '')\n    assertResolve('pascalcase', '', '')\n    assertResolve('pascalcase', '1', '1')\n    let f = new FormatString(0, undefined, 'if', undefined)\n    assert.strictEqual(f.toTextmateString(), '${0:+if}')\n    f = new FormatString(0, undefined, undefined, 'else')\n    assert.strictEqual(f.toTextmateString(), '${0:-else}')\n  })\n\n  test('Parser, default placeholder values', () => {\n\n    assertMarker('errorContext: `${1:err}`, error: $1', Text, Placeholder, Text, Placeholder)\n\n    const [, p1, , p2] = new SnippetParser().parse('errorContext: `${1:err}`, error:$1').children\n\n    assert.equal((<Placeholder>p1).index, '1')\n    assert.equal((<Placeholder>p1).children.length, '1')\n    assert.equal((<Text>(<Placeholder>p1).children[0]), 'err')\n\n    assert.equal((<Placeholder>p2).index, '1')\n    assert.equal((<Placeholder>p2).children.length, '1')\n    assert.equal((<Text>(<Placeholder>p2).children[0]), 'err')\n  })\n\n  test('Parser, default placeholder values and one transform', () => {\n\n    assertMarker('errorContext: `${1:err}`, error: ${1/err/ok/}', Text, Placeholder, Text, Placeholder)\n\n    const [, p3, , p4] = new SnippetParser().parse('errorContext: `${1:err}`, error:${1/err/ok/}').children\n\n    assert.equal((<Placeholder>p3).index, '1')\n    assert.equal((<Placeholder>p3).children.length, '1')\n    assert.equal((<Text>(<Placeholder>p3).children[0]), 'err')\n    assert.equal((<Placeholder>p3).transform, undefined)\n\n    assert.equal((<Placeholder>p4).index, '1')\n    assert.equal((<Placeholder>p4).children.length, '1')\n    assert.equal((<Text>(<Placeholder>p4).children[0]), 'ok')\n    assert.notEqual((<Placeholder>p4).transform, undefined)\n  })\n\n  test('Repeated snippet placeholder should always inherit, #31040', function() {\n    assertText('${1:foo}-abc-$1', 'foo-abc-foo')\n    assertText('${1:foo}-abc-${1}', 'foo-abc-foo')\n    assertText('${1:foo}-abc-${1:bar}', 'foo-abc-foo')\n    assertText('${1}-abc-${1:foo}', 'foo-abc-foo')\n  })\n\n  test('backspace esapce in TM only, #16212', () => {\n    const actual = new SnippetParser().text('Foo \\\\\\\\${abc}bar')\n    assert.equal(actual, 'Foo \\\\bar')\n  })\n\n  test('colon as variable/placeholder value, #16717', () => {\n    let actual = new SnippetParser().text('${TM_SELECTED_TEXT:foo:bar}')\n    assert.equal(actual, 'foo:bar')\n\n    actual = new SnippetParser().text('${1:foo:bar}')\n    assert.equal(actual, 'foo:bar')\n  })\n\n  test('incomplete placeholder', () => {\n    assertTextAndMarker('${1:}', '', Placeholder)\n  })\n\n  test('marker#len', () => {\n\n    function assertLen(template: string, ...lengths: number[]): void {\n      const snippet = new SnippetParser().parse(template, true)\n      snippet.walk(m => {\n        const expected = lengths.shift()\n        assert.equal(m.len(), expected)\n        return true\n      })\n      assert.equal(lengths.length, 0)\n    }\n\n    assertLen('text$0', 4, 0)\n    assertLen('$1text$0', 0, 4, 0)\n    assertLen('te$1xt$0', 2, 0, 2, 0)\n    assertLen('errorContext: `${1:err}`, error: $0', 15, 0, 3, 10, 0)\n    assertLen('errorContext: `${1:err}`, error: $1$0', 15, 0, 3, 10, 0, 3, 0)\n    assertLen('$TM_SELECTED_TEXT$0', 0, 0)\n    assertLen('${TM_SELECTED_TEXT:def}$0', 0, 3, 0)\n  })\n\n  test('marker#replaceWith', () => {\n    let m = new Placeholder(1)\n    expect(m.replaceWith(new Text(''))).toBe(false)\n    let p = new Placeholder(2)\n    p.appendChild(m)\n    p.replaceChildren([])\n    expect(m.replaceWith(new Text(''))).toBe(false)\n  })\n\n  test('parser, parent node', function() {\n    let snippet = new SnippetParser().parse('This ${1:is ${2:nested}}$0', true)\n\n    assert.equal(snippet.placeholders.length, 3)\n    let [first, second] = snippet.placeholders\n    assert.equal(first.index, '1')\n    assert.equal(second.index, '2')\n    assert.ok(second.parent === first)\n    assert.ok(first.parent === snippet)\n\n    snippet = new SnippetParser().parse('${VAR:default${1:value}}$0', true)\n    assert.equal(snippet.placeholders.length, 2)\n      ;[first] = snippet.placeholders\n    assert.equal(first.index, '1')\n\n    assert.ok(snippet.children[0] instanceof Variable)\n    assert.ok(first.parent === snippet.children[0])\n  })\n\n  test('Maximum call stack size exceeded, #28983', () => {\n    new SnippetParser().parse('${1:${foo:${1}}}')\n  })\n\n  test('Snippet can freeze the editor, #30407', () => {\n    const seen = new Set<Marker>()\n    seen.clear()\n    new SnippetParser().parse('class ${1:${TM_FILENAME/(?:\\\\A|_)([A-Za-z0-9]+)(?:\\\\.rb)?/(?2::\\\\u$1)/g}} < ${2:Application}Controller\\n  $3\\nend').walk(marker => {\n      assert.ok(!seen.has(marker))\n      seen.add(marker)\n      return true\n    })\n\n    seen.clear()\n    new SnippetParser().parse('${1:${FOO:abc$1def}}').walk(marker => {\n      assert.ok(!seen.has(marker))\n      seen.add(marker)\n      return true\n    })\n  })\n\n  test('Snippets: make parser ignore `${0|choice|}`, #31599', function() {\n    assertTextAndMarker('${0|foo,bar|}', '${0|foo,bar|}', Text)\n    assertTextAndMarker('${1|foo,bar|}', 'foo', Placeholder)\n  })\n\n\n  test('Transform -> FormatString#resolve', function() {\n\n    // shorthand functions\n    assert.equal(new FormatString(1, 'upcase').resolve('foo'), 'FOO')\n    assert.equal(new FormatString(1, 'downcase').resolve('FOO'), 'foo')\n    assert.equal(new FormatString(1, 'capitalize').resolve('bar'), 'Bar')\n    assert.equal(new FormatString(1, 'capitalize').resolve('bar no repeat'), 'Bar no repeat')\n    assert.equal(new FormatString(1, 'pascalcase').resolve('bar-foo'), 'BarFoo')\n    assert.equal(new FormatString(1, 'notKnown').resolve('input'), 'input')\n\n    // if\n    assert.equal(new FormatString(1, undefined, 'foo', undefined).resolve(undefined), '')\n    assert.equal(new FormatString(1, undefined, 'foo', undefined).resolve(''), '')\n    assert.equal(new FormatString(1, undefined, 'foo', undefined).resolve('bar'), 'foo')\n\n    // else\n    assert.equal(new FormatString(1, undefined, undefined, 'foo').resolve(undefined), 'foo')\n    assert.equal(new FormatString(1, undefined, undefined, 'foo').resolve(''), 'foo')\n    assert.equal(new FormatString(1, undefined, undefined, 'foo').resolve('bar'), 'bar')\n\n    // if-else\n    assert.equal(new FormatString(1, undefined, 'bar', 'foo').resolve(undefined), 'foo')\n    assert.equal(new FormatString(1, undefined, 'bar', 'foo').resolve(''), 'foo')\n    assert.equal(new FormatString(1, undefined, 'bar', 'foo').resolve('baz'), 'bar')\n  })\n\n  test('Snippet variable transformation doesn\\'t work if regex is complicated and snippet body contains \\'$$\\' #55627', function() {\n    const snippet = new SnippetParser().parse('const fileName = \"${TM_FILENAME/(.*)\\\\..+$/$1/}\"')\n    assert.equal(snippet.toTextmateString(), 'const fileName = \"${TM_FILENAME/(.*)\\\\..+$/${1}/}\"')\n  })\n\n  test('[BUG] HTML attribute suggestions: Snippet session does not have end-position set, #33147', function() {\n\n    const { placeholders } = new SnippetParser().parse('src=\"$1\"', true)\n    const [first, second] = placeholders\n\n    assert.equal(placeholders.length, 2)\n    assert.equal(first.index, 1)\n    assert.equal(second.index, 0)\n\n  })\n\n  test('Snippet optional transforms are not applied correctly when reusing the same variable, #37702', function() {\n\n    const transform = new Transform()\n    assert.strictEqual(transform.toString(), '')\n    transform.appendChild(new FormatString(1, 'upcase'))\n    transform.appendChild(new FormatString(2, 'upcase'))\n    transform.regexp = /^(.)|-(.)/g\n\n    assert.equal(transform.resolve('my-file-name'), 'MyFileName')\n\n    const clone = transform.clone()\n    assert.equal(clone.resolve('my-file-name'), 'MyFileName')\n    transform.regexp = /^(.)|-(.)/i\n    assert.strictEqual(transform.clone().regexp.ignoreCase, true)\n  })\n\n  test('problem with snippets regex #40570', function() {\n\n    const snippet = new SnippetParser().parse('${TM_DIRECTORY/.*src[\\\\/](.*)/$1/}')\n    assertMarker(snippet, Variable)\n  })\n\n  test('Variable transformation doesn\\'t work if undefined variables are used in the same snippet #51769', function() {\n    let transform = new Transform()\n    transform.appendChild(new Text('bar'))\n    transform.regexp = new RegExp('foo', 'gi')\n    assert.equal(transform.toTextmateString(), '/foo/bar/ig')\n  })\n\n  test('Snippet parser freeze #53144', function() {\n    let snippet = new SnippetParser().parse('${1/(void$)|(.+)/${1:?-\\treturn nil;}/}')\n    assertMarker(snippet, Placeholder)\n  })\n\n  test('snippets variable not resolved in JSON proposal #52931', function() {\n    assertTextAndMarker('FOO${1:/bin/bash}', 'FOO/bin/bash', Text, Placeholder)\n  })\n\n  test('Mirroring sequence of nested placeholders not selected properly on backjumping #58736', function() {\n    let snippet = new SnippetParser().parse('${3:nest1 ${1:nest2 ${2:nest3}}} $3')\n    assert.equal(snippet.children.length, 3)\n    assert.ok(snippet.children[0] instanceof Placeholder)\n    assert.ok(snippet.children[1] instanceof Text)\n    assert.ok(snippet.children[2] instanceof Placeholder)\n\n    function assertParent(marker: Marker) {\n      marker.children.forEach(assertParent)\n      if (!(marker instanceof Placeholder)) {\n        return\n      }\n      let found = false\n      let m: Marker = marker\n      while (m && !found) {\n        if (m.parent === snippet) {\n          found = true\n        }\n        m = m.parent\n      }\n      assert.ok(found)\n    }\n    let [, , clone] = snippet.children\n    assertParent(clone)\n  })\n})\n\ndescribe('TextmateSnippet', () => {\n  test('TextmateSnippet#enclosingPlaceholders', () => {\n    let snippet = new SnippetParser().parse('This ${1:is ${2:nested}}$0', true)\n    let [first, second] = snippet.placeholders\n\n    assert.deepEqual(snippet.enclosingPlaceholders(first), [])\n    assert.deepEqual(snippet.enclosingPlaceholders(second), [first])\n  })\n\n  test('TextmateSnippet#getTextBefore', () => {\n    let snippet = new SnippetParser().parse('This ${1:is ${2:nested}}$0', true)\n    expect(snippet.getTextBefore(snippet, undefined)).toBe('')\n    let [first, second] = snippet.placeholders\n    expect(snippet.getTextBefore(second, first)).toBe('is ')\n    snippet = new SnippetParser().parse('This ${1:foo ${2:is ${3:nested}}} $0', true)\n    let arr = snippet.placeholders\n    expect(snippet.getTextBefore(arr[2], arr[0])).toBe('foo is ')\n  })\n\n  test('TextmateSnippet#offset', () => {\n    let snippet = new SnippetParser().parse('te$1xt', true)\n    assert.equal(snippet.offset(snippet.children[0]), 0)\n    assert.equal(snippet.offset(snippet.children[1]), 2)\n    assert.equal(snippet.offset(snippet.children[2]), 2)\n\n    snippet = new SnippetParser().parse('${TM_SELECTED_TEXT:def}', true)\n    assert.equal(snippet.offset(snippet.children[0]), 0)\n    assert.equal(snippet.offset((<Variable>snippet.children[0]).children[0]), 0)\n\n    // foreign marker\n    assert.equal(snippet.offset(new Text('foo')), -1)\n  })\n\n  test('TextmateSnippet#placeholder', () => {\n    let snippet = new SnippetParser().parse('te$1xt$0', true)\n    let placeholders = snippet.placeholders\n    assert.equal(placeholders.length, 2)\n\n    snippet = new SnippetParser().parse('te$1xt$1$0', true)\n    placeholders = snippet.placeholders\n    assert.equal(placeholders.length, 3)\n\n\n    snippet = new SnippetParser().parse('te$1xt$2$0', true)\n    placeholders = snippet.placeholders\n    assert.equal(placeholders.length, 3)\n\n    snippet = new SnippetParser().parse('${1:bar${2:foo}bar}$0', true)\n    placeholders = snippet.placeholders\n    assert.equal(placeholders.length, 3)\n  })\n\n  test('TextmateSnippet#replace 1/2', function() {\n    let snippet = new SnippetParser().parse('aaa${1:bbb${2:ccc}}$0', true)\n\n    assert.equal(snippet.placeholders.length, 3)\n    const [, second] = snippet.placeholders\n    assert.equal(second.index, '2')\n\n    const enclosing = snippet.enclosingPlaceholders(second)\n    assert.equal(enclosing.length, 1)\n    assert.equal(enclosing[0].index, '1')\n    let marker = snippet.placeholders.find(o => o.index == 2)\n    let nested = new SnippetParser().parse('ddd$1eee', false)\n    let err\n    try {\n      snippet.replace(marker, nested.children)\n    } catch (e) {\n      err = e\n    }\n    expect(err).toBeDefined()\n  })\n\n  test('TextmateSnippet#replace 2/2', () => {\n    let snippet = new SnippetParser().parse('aaa${1:bbb${2:ccc}}$0', true)\n\n    assert.equal(snippet.placeholders.length, 3)\n    const [, second] = snippet.placeholders\n    assert.equal(second.index, '2')\n\n    let nested = new SnippetParser().parse('dddeee$0', true)\n    snippet.replace(second, nested.children)\n\n    assert.equal(snippet.toString(), 'aaabbbdddeee')\n    assert.equal(snippet.placeholders.length, 4)\n  })\n\n  test('TextmateSnippet replace variable with placeholder', async () => {\n    let snippet = new SnippetParser().parse('|${1:${foo}} ${foo} $1 ${bar}|', true)\n    await snippet.resolveVariables({\n      resolve: _variable => {\n        return undefined\n      }\n    })\n    let placeholders = snippet.placeholders\n    let indexes = placeholders.map(o => o.index)\n    expect(indexes).toEqual([1, 2, 2, 1, 3, 0])\n    let p = placeholders.find(o => o.index == 2 && o.primary)\n    p.setOnlyChild(new Text('x'))\n    snippet.onPlaceholderUpdate(p)\n    expect(snippet.toString()).toBe('|x x x bar|')\n  })\n\n  test('mergeTexts()', () => {\n    let m = new TextmateSnippet(false)\n    m.replaceChildren([\n      new Text('c'),\n      new Placeholder(1),\n      new Text('a'),\n      new Text('b'),\n      new Placeholder(2),\n      new Text('c'),\n      new Text(''),\n      new Text('d'),\n      new Text('e')\n    ])\n    mergeTexts(m, 0)\n    expect(m.hasPythonBlock).toBe(false)\n    expect(m.hasCodeBlock).toBe(false)\n    expect(m.children.length).toBe(5)\n    expect(m.children[2].toString()).toBe('ab')\n    expect(m.children[4].toString()).toBe('cde')\n  })\n\n  test('getPlaceholderId', () => {\n    const p = new Placeholder(1)\n    let id = getPlaceholderId(p)\n    expect(typeof id).toBe('number')\n    expect(p.id).toBe(id)\n    expect(getPlaceholderId(p)).toBe(id)\n  })\n})\n"
  },
  {
    "path": "src/__tests__/snippets/session.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport path from 'path'\nimport { Position, Range, TextEdit } from 'vscode-languageserver-protocol'\nimport { SnippetConfig, SnippetEdit, SnippetSession } from '../../snippets/session'\nimport { UltiSnippetContext } from '../../snippets/util'\nimport { Disposable, disposeAll } from '../../util'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  let pyfile = path.join(__dirname, '../ultisnips.py')\n  await nvim.command(`execute 'pyxfile '.fnameescape('${pyfile}')`)\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nafterEach(async () => {\n  disposeAll(disposables)\n  await helper.reset()\n})\n\nasync function createSession(enableHighlight = false, preferComplete = false, nextOnDelete = false): Promise<SnippetSession> {\n  let doc = await workspace.document\n  let config: SnippetConfig = { highlight: enableHighlight, preferComplete, nextOnDelete }\n  let session = new SnippetSession(nvim, doc, config)\n  disposables.push(session)\n  disposables.push(workspace.onDidChangeTextDocument(e => {\n    if (e.bufnr == session.bufnr) session.onChange(e)\n  }))\n  return session\n}\n\ndescribe('SnippetSession', () => {\n  const defaultRange = Range.create(0, 0, 0, 0)\n  const defaultContext = {\n    id: `1-1`,\n    line: '',\n    range: defaultRange\n  }\n\n  async function start(inserted: string, range = defaultRange, select = true, context?: UltiSnippetContext): Promise<boolean> {\n    await nvim.input('i')\n    let doc = await workspace.document\n    let session = new SnippetSession(nvim, doc, { highlight: false, nextOnDelete: false, preferComplete: false })\n    return await session.start(inserted, range, select, context)\n  }\n\n  async function getCursorRange(): Promise<Range> {\n    let pos = await window.getCursorPosition()\n    return Range.create(pos, pos)\n  }\n\n  describe('start()', () => {\n    it('should not activate when insert empty snippet', async () => {\n      let res = await start('', defaultRange)\n      expect(res).toBe(false)\n    })\n\n    it('should insert escaped text', async () => {\n      let res = await start('\\\\`a\\\\` \\\\$ \\\\{\\\\}', Range.create(0, 0, 0, 0), false, defaultContext)\n      expect(res).toBe(true)\n      let line = await nvim.line\n      expect(line).toBe('`a` $ {}')\n    })\n\n    it('should not start with plain snippet when jump to final placeholder', async () => {\n      let res = await start('bar$0', defaultRange)\n      expect(res).toBe(false)\n      let pos = await window.getCursorPosition()\n      expect(pos).toEqual({ line: 0, character: 3 })\n    })\n\n    it('should start with range replaced', async () => {\n      await nvim.setLine('foo')\n      let res = await start('bar$0', Range.create(0, 0, 0, 3), true)\n      expect(res).toBe(false)\n      let line = await nvim.line\n      expect(line).toBe('bar')\n    })\n\n    it('should fix indent of next line when necessary', async () => {\n      let buf = await nvim.buffer\n      await nvim.setLine('  ab')\n      await nvim.input('i')\n      let session = await createSession()\n      await session.selectCurrentPlaceholder()\n      let res = await session.start('${1:x}\\n', Range.create(0, 3, 0, 3))\n      expect(res).toBe(true)\n      let lines = await buf.lines\n      expect(lines).toEqual(['  ax', '  b'])\n    })\n\n    it('should insert indent for snippet endsWith line break', async () => {\n      let buf = await nvim.buffer\n      await nvim.setLine('  bar')\n      await nvim.command('startinsert')\n      await nvim.call('cursor', [1, 3])\n      let session = await createSession()\n      let res = await session.start('${1:foo}\\n', Range.create(0, 2, 0, 2))\n      expect(res).toBe(true)\n      let lines = await buf.lines\n      expect(lines).toEqual(['  foo', '  bar'])\n    })\n\n    it('should start without select placeholder', async () => {\n      let session = await createSession()\n      let res = await session.start(' ${1:aa} ', defaultRange, false)\n      expect(res).toBe(true)\n      let { mode } = await nvim.mode\n      expect(mode).toBe('n')\n      await session.selectCurrentPlaceholder()\n      await helper.waitFor('mode', [], 's')\n    })\n\n    it('should use default variable value', async () => {\n      let session = await createSession()\n      let res = await session.start('${foo:bar}', defaultRange, false)\n      expect(res).toBe(true)\n      let line = await nvim.getLine()\n      expect(line).toBe('bar')\n    })\n\n    it('should select none transform placeholder', async () => {\n      await start('${1/..*/ -> /}xy$1', defaultRange)\n      let col = await nvim.call('col', '.')\n      expect(col).toBe(3)\n    })\n\n    it('should indent multiple lines variable text', async () => {\n      let buf = await nvim.buffer\n      let text = 'abc\\n  def'\n      await nvim.setVar('coc_selected_text', text)\n      await start('fun\\n  ${0:${TM_SELECTED_TEXT:return}}\\nend')\n      let lines = await buf.lines\n      expect(lines.length).toBe(4)\n      expect(lines).toEqual([\n        'fun', '  abc', '    def', 'end'\n      ])\n      let val = await nvim.getVar('coc_selected_text')\n      expect(val).toBe(null)\n    })\n\n    it('should resolve VISUAL', async () => {\n      let text = 'abc'\n      await nvim.setVar('coc_selected_text', text)\n      await start('$VISUAL')\n      let line = await nvim.line\n      expect(line).toBe('abc')\n    })\n\n    it('should resolve default value of VISUAL', async () => {\n      await nvim.setVar('coc_selected_text', '')\n      await start('${VISUAL:foo}')\n      let line = await nvim.line\n      expect(line).toBe('foo')\n    })\n  })\n\n  describe('insertSnippetEdits', () => {\n    it('should insert snippets', async () => {\n      await helper.createDocument()\n      let session = await createSession()\n      await helper.createDocument()\n      let doc = session.document\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo\\n\\nbar')])\n      let res = await session.insertSnippetEdits([])\n      expect(res).toBe(false)\n      let edits: SnippetEdit[] = []\n      edits.push({ range: Range.create(0, 0, 0, 3), snippet: 'foo($1)' })\n      edits.push({ range: Range.create(2, 0, 2, 3), snippet: 'bar($1)' })\n      res = await session.insertSnippetEdits(edits)\n      expect(res).toBe(true)\n      let lines = await doc.buffer.lines\n      expect(lines).toEqual(['foo()', '', 'bar()'])\n      let range = session.placeholder!.range\n      expect(range).toEqual(Range.create(0, 4, 0, 4))\n      let ses = await createSession()\n      res = await ses.insertSnippetEdits([{ range: Range.create(0, 0, 0, 0), snippet: 'foo' }])\n      expect(res).toBe(true)\n      doc = ses.document\n      let line = doc.getline(0)\n      expect(line).toBe('foo')\n      expect(ses.selected).toBe(false)\n    })\n  })\n\n  describe('nested snippet', () => {\n    it('should start with nest snippet', async () => {\n      let session = await createSession()\n      let res = await session.start('${1:a} ${2:b}', defaultRange, false)\n      let line = await nvim.getLine()\n      expect(line).toBe('a b')\n      expect(res).toBe(true)\n      let { placeholder } = session\n      expect(placeholder.index).toBe(1)\n      res = await session.start('${1:foo} | ${2:bar}', defaultRange)\n      expect(res).toBe(true)\n      placeholder = session.placeholder\n      expect(placeholder.value).toBe('foo')\n      expect(placeholder.index).toBe(1)\n      line = await nvim.getLine()\n      expect(line).toBe('foo | bara b')\n      expect(session.snippet.text).toBe('foo | bara b')\n      await session.nextPlaceholder()\n      placeholder = session.placeholder\n      expect(placeholder.index).toBe(2)\n      expect(session.placeholder.value).toBe('bar')\n      let col = await nvim.call('col', ['.'])\n      expect(col).toBe(9)\n      await session.nextPlaceholder()\n      expect(session.isActive).toBe(true)\n      // should finalize snippet\n      expect(session.placeholder.index).toBe(1)\n      await session.nextPlaceholder()\n      expect(session.placeholder.index).toBe(2)\n      expect(session.placeholder.value).toBe('b')\n    })\n\n    it('should start nest snippet without select', async () => {\n      await nvim.command('startinsert')\n      let session = await createSession()\n      let res = await session.start('${1:a} $1', defaultRange)\n      res = await session.start('${1:foo}', Range.create(0, 0, 0, 1), false)\n      expect(res).toBe(true)\n      let line = await nvim.line\n      expect(line).toBe('foo foo')\n      await session.selectCurrentPlaceholder()\n      await session.nextPlaceholder()\n      expect(session.placeholder).toBeDefined()\n    })\n\n    it('should not nested when range not contains', async () => {\n      await nvim.command('startinsert')\n      let session = await createSession()\n      let res = await session.start('${1:a} ${2:b}', defaultRange)\n      res = await session.start('${1:foo} ${2:bar}', Range.create(0, 0, 0, 3), false)\n      expect(res).toBe(true)\n      let line = await nvim.line\n      expect(line).toBe('foo bar')\n    })\n  })\n\n  describe('getRanges()', () => {\n    it('should getRanges of placeholder', async () => {\n      async function checkRanges(snippet: string, results: any) {\n        let session = await createSession()\n        await session.start(snippet, defaultRange)\n        let curr = session.placeholder\n        let res = session.snippet.getRanges(curr.marker)\n        expect(res).toEqual(results)\n        session.deactivate()\n        await nvim.setLine('')\n      }\n      await checkRanges('$1 $1', [])\n      await checkRanges('${foo}', [Range.create(0, 0, 0, 3)])\n      await checkRanges('${2:${1:foo}}', [Range.create(0, 0, 0, 3)])\n      await checkRanges('${2:${1:foo}} ${2/^_(.*)/$1/}', [Range.create(0, 0, 0, 3)])\n    })\n  })\n\n  describe('synchronize()', () => {\n    it('should cancel when before and body changed', async () => {\n      let session = await createSession()\n      await nvim.setLine('x')\n      await nvim.input('a')\n      await session.start('${1:foo }bar', defaultRange)\n      await nvim.setLine('yfoo  bar')\n      await session.forceSynchronize()\n      expect(session.isActive).toBe(false)\n    })\n\n    it('should synchronize content change', async () => {\n      let session = await createSession(true)\n      await session.checkPosition()\n      expect(session.version).toBe(-1)\n      await session.start('${1:foo}${2:`!p snip.rv = \"\"`} `!p snip.rv = t[1] + t[2]`', defaultRange, true, {\n        id: '1-1',\n        line: '',\n        range: defaultRange\n      })\n      await nvim.input('bar')\n      await session.forceSynchronize()\n      await helper.waitFor('getline', ['.'], 'bar bar')\n    })\n\n    it('should cancel with unexpected change', async () => {\n      let session = await createSession(true)\n      await nvim.setLine('c')\n      await nvim.input('A')\n      await session.start('${1:foo}', Range.create(0, 1, 0, 1))\n      await nvim.setLine('bxoo')\n      await session.forceSynchronize()\n      expect(session.isActive).toBe(false)\n    })\n\n    it('should cancel when document have changed', async () => {\n      let session = await createSession()\n      let doc = await workspace.document\n      await nvim.input('i')\n      await session.start('${2:foo} ${1}', defaultRange)\n      await nvim.setLine('bfoo ')\n      await doc.patchChange()\n      await nvim.setLine('xfoo ')\n      await nvim.call('cursor', [1, 1])\n      await session.forceSynchronize()\n      expect(session.snippet.text).toBe('xfoo ')\n      expect(session.isActive).toBe(true)\n    })\n\n    it('should reset snippet when cancelled', async () => {\n      let session = await createSession()\n      await nvim.input('i')\n      await session.start('${1} `!p snip.rv = t[1]`', defaultRange, false, defaultContext)\n      await nvim.setLine('b ')\n      let cancelled = false\n      let spy = jest.spyOn(session.snippet['_tmSnippet'], 'updatePythonCodes').mockImplementation(() => {\n        return new Promise(resolve => {\n          session.cancel()\n          setImmediate(() => {\n            resolve()\n            cancelled = true\n          })\n        })\n      })\n      await helper.waitValue(() => cancelled, true)\n      expect(session.snippet.text).toBe(' ')\n      spy.mockRestore()\n      await session.onCompleteDone()\n    })\n\n    it('should not cancel when change after snippet', async () => {\n      let session = await createSession()\n      await nvim.setLine(' x')\n      await nvim.input('i')\n      await session.start('${1:foo }bar', defaultRange)\n      await nvim.setLine('foo bar y')\n      await session.forceSynchronize()\n      expect(session.isActive).toBe(true)\n    })\n\n    it('should cancel when change before and in snippet', async () => {\n      let session = await createSession()\n      await nvim.setLine(' x')\n      await nvim.input('i')\n      await session.start('${1:foo }bar', defaultRange)\n      await nvim.setLine('afoobar')\n      await session.forceSynchronize()\n      expect(session.isActive).toBe(false)\n    })\n\n    it('should not cancel when change text', async () => {\n      let session = await createSession()\n      await nvim.input('i')\n      await session.start('${1:foo} bar', defaultRange)\n      await nvim.setLine('foodbar')\n      await session.forceSynchronize()\n      expect(session.isActive).toBe(true)\n      expect(session.snippet.text).toBe('foodbar')\n    })\n\n    it('should able to jump when current placeholder destroyed', async () => {\n      let session = await createSession()\n      await nvim.input('i')\n      await session.start('${1:foo} bar', defaultRange)\n      await nvim.setLine('fobar')\n      await session.forceSynchronize()\n      expect(session.isActive).toBe(true)\n      await session.nextPlaceholder()\n      expect(session.isActive).toBe(false)\n    })\n\n    it('should adjust with removed text', async () => {\n      let session = await createSession()\n      await nvim.input('i')\n      await session.start('${1:foo} bar$0', defaultRange)\n      await nvim.input('<esc>')\n      await nvim.call('cursor', [1, 5])\n      await nvim.input('i')\n      await nvim.input('<backspace>')\n      await helper.wait(1)\n      await session.forceSynchronize()\n      expect(session.isActive).toBe(true)\n      await session.nextPlaceholder()\n      let col = await nvim.call('col', ['.'])\n      expect(col).toBe(7)\n    })\n\n    it('should automatically select next placeholder', async () => {\n      let session = await createSession(false, false, true)\n      await nvim.input('i')\n      await session.start('${1:foo} bar$0', defaultRange)\n      await nvim.input('<backspace>')\n      await session.forceSynchronize()\n      let placeholder = session.placeholder\n      expect(placeholder.index).toBe(0)\n    })\n\n    it('should changed none current placeholder', async () => {\n      let session = await createSession()\n      await nvim.input('i')\n      await session.start('$1 $2', defaultRange)\n      await nvim.input('<esc>A')\n      await nvim.input(' ')\n      await session.forceSynchronize()\n      expect(session.isActive).toBe(true)\n      let placeholder = session.snippet.getPlaceholderByIndex(2)\n      expect(placeholder.value).toBe(' ')\n      let p = session.placeholder\n      expect(p.index).toBe(1)\n    })\n\n    it('should update cursor column after synchronize', async () => {\n      let session = await createSession()\n      await nvim.input('i')\n      await session.start('${1} ${1:foo}', defaultRange)\n      await nvim.input('b')\n      await session.forceSynchronize()\n      let pos = await window.getCursorPosition()\n      expect(pos).toEqual(Position.create(0, 3))\n      await nvim.input('a')\n      await session.forceSynchronize()\n      pos = await window.getCursorPosition()\n      let line = await nvim.line\n      expect(line).toEqual('ba ba')\n      expect(pos).toEqual(Position.create(0, 5))\n      await nvim.input('<backspace>')\n      await session.forceSynchronize()\n      pos = await window.getCursorPosition()\n      expect(pos).toEqual(Position.create(0, 3))\n      line = await nvim.line\n      expect(line).toBe('b b')\n    })\n\n    it('should update cursor line after synchronize', async () => {\n      let buf = await nvim.buffer\n      let session = await createSession()\n      await nvim.input('i')\n      await session.start('${1} ${1:foo}x', defaultRange)\n      await nvim.input('b')\n      await session.forceSynchronize()\n      let pos = await window.getCursorPosition()\n      expect(pos).toEqual(Position.create(0, 3))\n      await nvim.input('<cr>')\n      await session.forceSynchronize()\n      expect(session.isActive).toBe(true)\n      let lines = await buf.lines\n      expect(lines).toEqual(['b', ' b', 'x'])\n      pos = await window.getCursorPosition()\n      expect(pos).toEqual(Position.create(2, 0))\n    })\n\n    it('should synchronize changes at the same time', async () => {\n      await nvim.input('i')\n      let doc = await workspace.document\n      let session = await createSession()\n      let res = await session.start('|$1 $1|', defaultRange)\n      expect(res).toBe(true)\n      let line = await nvim.line\n      expect(line).toBe('| |')\n      let p = new Promise(resolve => {\n        doc.onDocumentChange(_e => {\n          resolve(undefined)\n        })\n      })\n      await nvim.input('xy')\n      await p\n      await doc.applyEdits([TextEdit.replace(Range.create(0, 1, 0, 3), '')])\n      await session.forceSynchronize()\n      line = await nvim.line\n      expect(line).toBe('| |')\n    })\n\n    it('should deactivate when synchronize text is wrong', async () => {\n      let doc = await workspace.document\n      let session = await createSession()\n      let res = await session.start('${1:foo}', defaultRange)\n      expect(res).toBe(true)\n      let spy = jest.spyOn(session.snippet, 'replaceWithText').mockImplementation(() => {\n        return Promise.resolve({ snippetText: 'xy', marker: undefined })\n      })\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'p')])\n      await session.forceSynchronize()\n      spy.mockRestore()\n      expect(session.isActive).toBe(false)\n    })\n\n    it('should reset position when change before snippet', async () => {\n      let session = await createSession()\n      await nvim.setLine('x')\n      await nvim.input('a')\n      let r = await getCursorRange()\n      await session.start('${1:foo} bar', r)\n      await nvim.call('coc#cursor#move_to', [0, 0])\n      await nvim.command('startinsert')\n      await nvim.setLine('yfoo bar')\n      await session.forceSynchronize()\n      expect(session.isActive).toBe(true)\n      let start = session.snippet.start\n      expect(start).toEqual(Position.create(0, 1))\n      session.deactivate()\n    })\n\n    it('should cancel change synchronize', async () => {\n      let doc = await workspace.document\n      let session = await createSession()\n      let res = await session.start('${1:foo}', defaultRange)\n      expect(res).toBe(true)\n      session.cancel(true)\n      await doc.applyEdits([TextEdit.insert(Position.create(0, 1), 'x')])\n      process.nextTick(() => {\n        session.cancel()\n      })\n      await session._synchronize()\n      expect(session.snippet.tmSnippet.toString()).toBe('foo')\n    })\n  })\n\n  describe('deactivate()', () => {\n    it('should deactivate on cursor outside', async () => {\n      let buf = await nvim.buffer\n      let session = await createSession()\n      let res = await session.start('a${1:a}b', defaultRange)\n      expect(res).toBe(true)\n      await buf.append(['foo', 'bar'])\n      await nvim.call('cursor', [2, 2])\n      await session.checkPosition()\n      expect(session.isActive).toBe(false)\n    })\n\n    it('should not throw when jump on deactivate session', async () => {\n      let session = await createSession()\n      session.deactivate()\n      await session.start('${1:foo} $0', defaultRange)\n      await session.selectPlaceholder(undefined)\n      await session.forceSynchronize()\n      await session.previousPlaceholder()\n      await session.nextPlaceholder()\n    })\n\n    it('should cancel keymap on jump final placeholder', async () => {\n      let session = await createSession()\n      await nvim.input('i')\n      await session.start('$0x${1:a}b$0', defaultRange)\n      let line = await nvim.line\n      expect(line).toBe('xab')\n      let map = await nvim.call('maparg', ['<C-j>', 'i']) as string\n      expect(map).toMatch('coc#snippet#jump')\n      await session.nextPlaceholder()\n      map = await nvim.call('maparg', ['<C-j>', 'i']) as string\n      expect(map).toBe('')\n    })\n  })\n\n  describe('nextPlaceholder()', () => {\n    it('should not throw when session not activated', async () => {\n      let session = await createSession()\n      await session.start('${foo} ${bar}', defaultRange, false)\n      session.deactivate()\n      await session.nextPlaceholder()\n      await session.previousPlaceholder()\n    })\n\n    it('should jump to variable placeholder', async () => {\n      let session = await createSession()\n      await session.start('${foo} ${bar}', defaultRange, false)\n      await session.selectCurrentPlaceholder()\n      await session.nextPlaceholder()\n      let pos = await window.getCursorPosition()\n      expect(pos).toEqual({ line: 0, character: 6 })\n    })\n\n    it('should jump to variable placeholder after number placeholder', async () => {\n      let session = await createSession()\n      await session.start('${foo} ${1:bar}', defaultRange, false)\n      await session.selectCurrentPlaceholder()\n      await session.nextPlaceholder()\n      let pos = await window.getCursorPosition()\n      expect(pos).toEqual({ line: 0, character: 2 })\n    })\n\n    it('should jump to first placeholder', async () => {\n      let session = await createSession()\n      await session.start('${foo} ${foo} ${2:bar}', defaultRange, false)\n      await session.selectCurrentPlaceholder()\n      let pos = await window.getCursorPosition()\n      expect(pos).toEqual({ line: 0, character: 10 })\n      await session.nextPlaceholder()\n      pos = await window.getCursorPosition()\n      expect(pos).toEqual({ line: 0, character: 2 })\n      await session.nextPlaceholder()\n      pos = await window.getCursorPosition()\n      expect(pos).toEqual({ line: 0, character: 11 })\n    })\n\n    it('should goto next placeholder', async () => {\n      let session = await createSession()\n      let res = await session.start('${1:a} ${2:b} c', defaultRange)\n      expect(res).toBe(true)\n      await session.nextPlaceholder()\n      let { placeholder } = session\n      expect(placeholder.index).toBe(2)\n    })\n\n    it('should jump to none transform placeholder', async () => {\n      let session = await createSession()\n      let res = await session.start('${1} ${2/^_(.*)/$2/}bar$2', defaultRange)\n      expect(res).toBe(true)\n      let line = await nvim.line\n      expect(line).toBe(' bar')\n      await session.nextPlaceholder()\n      let col = await nvim.call('col', '.')\n      expect(col).toBe(5)\n    })\n\n    it('should remove white space on jump', async () => {\n      let session = await createSession()\n      let opts = {\n        removeWhiteSpace: true,\n        ...defaultContext\n      }\n      let res = await session.start('foo  $1\\n${2:bar} $0', defaultRange, true, opts)\n      expect(res).toBe(true)\n      let line = await nvim.line\n      expect(line).toBe('foo  ')\n      await session.nextPlaceholder()\n      expect(session.isActive).toBe(true)\n      let lines = await session.document.buffer.lines\n      expect(lines[0]).toBe('foo')\n      let p = session.placeholder\n      await session.removeWhiteSpaceBefore(p)\n    })\n  })\n\n  describe('previousPlaceholder()', () => {\n\n    it('should goto previous placeholder', async () => {\n      let session = await createSession()\n      let res = await session.start('${1:foo} ${2:bar}', defaultRange)\n      expect(res).toBe(true)\n      await session.nextPlaceholder()\n      expect(session.placeholder.index).toBe(2)\n      await session.previousPlaceholder()\n      expect(session.placeholder.index).toBe(1)\n    })\n  })\n\n  describe('highlights()', () => {\n    it('should add highlights', async () => {\n      let ns = await nvim.call('coc#highlight#create_namespace', ['snippets']) as number\n      let session = await createSession(true)\n      await session.start('${2:bar ${1:foo}} $2', defaultRange)\n      await session.nextPlaceholder()\n      let buf = nvim.createBuffer(workspace.bufnr)\n      let markers = await buf.getExtMarks(ns, 0, -1, { details: true })\n      expect(markers.length).toBe(2)\n      expect(markers[0][3].hl_group).toBe('CocSnippetVisual')\n      expect(markers[1][3].hl_group).toBe('CocSnippetVisual')\n      session.deactivate()\n    })\n  })\n\n  describe('checkPosition()', () => {\n\n    it('should cancel snippet if position out of range', async () => {\n      let session = await createSession()\n      await nvim.setLine('bar')\n      await session.start('${1:foo}', defaultRange)\n      await nvim.call('cursor', [1, 5])\n      await session.checkPosition()\n      expect(session.isActive).toBe(false)\n    })\n\n    it('should not cancel snippet if position in range', async () => {\n      let session = await createSession()\n      await session.start('${1:foo}', defaultRange)\n      await nvim.call('cursor', [1, 3])\n      await session.checkPosition()\n      expect(session.isActive).toBe(true)\n    })\n  })\n\n  describe('resolveSnippet()', () => {\n    it('should resolveSnippet', async () => {\n      let session = await createSession()\n      let res = await session.resolveSnippet(nvim, '${1:`!p snip.rv = \"foo\"`}', { line: 'foo', range: Range.create(0, 0, 0, 3) })\n      expect(res).toBe('foo')\n    })\n  })\n\n  describe('selectPlaceholder()', () => {\n    it('should select range placeholder', async () => {\n      let session = await createSession()\n      await session.start('${1:abc}', defaultRange)\n      let mode = await nvim.mode\n      expect(mode.mode).toBe('s')\n      await nvim.input('<backspace>')\n      let line = await nvim.line\n      expect(line).toBe('')\n    })\n\n    it('should select empty placeholder', async () => {\n      let session = await createSession()\n      await session.start('a ${1} ${2}', defaultRange)\n      let mode = await nvim.mode\n      expect(mode.mode).toBe('i')\n      let col = await nvim.call('col', '.')\n      expect(col).toBe(3)\n    })\n\n    it('should select choice placeholder', async () => {\n      await nvim.input('i')\n      let session = await createSession()\n      await session.start('${1|one,two,three|}', defaultRange)\n      let line = await nvim.line\n      expect(line).toBe('one')\n      await helper.waitPopup()\n      let items = await helper.items()\n      expect(items.length).toBe(3)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/snippets/snippet.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport * as assert from 'assert'\nimport path from 'path'\nimport { CancellationToken, CancellationTokenSource } from 'vscode-languageserver-protocol'\nimport { Position, Range } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport events from '../../events'\nimport { addPythonTryCatch, executePythonCode, generateContextId, getInitialPythonCode, getVariablesCode, hasPython } from '../../snippets/eval'\nimport { CodeBlock, Placeholder, SnippetParser, Text, TextmateSnippet } from '../../snippets/parser'\nimport { CocSnippet, getNextPlaceholder, getUltiSnipActionCodes } from '../../snippets/snippet'\nimport { SnippetString } from '../../snippets/string'\nimport { convertRegex, getTextAfter, getTextBefore, normalizeSnippetString, shouldFormat, toSnippetString, UltiSnippetContext } from '../../snippets/util'\nimport { padZero, parseComments, parseCommentstring, SnippetVariableResolver } from '../../snippets/variableResolve'\nimport { UltiSnippetOption } from '../../types'\nimport { getEnd } from '../../util/position'\nimport workspace from '../../workspace'\nimport helper from '../helper'\n\nlet nvim: Neovim\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n  let pyfile = path.join(__dirname, '../ultisnips.py')\n  await nvim.command(`execute 'pyxfile '.fnameescape('${pyfile}')`)\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nasync function createSnippet(snippet: string | TextmateSnippet, opts?: UltiSnippetOption, range = Range.create(0, 0, 0, 0), line = '') {\n  let resolver = new SnippetVariableResolver(nvim, workspace.workspaceFolderControl)\n  let snip = new CocSnippet(snippet, Position.create(0, 0), nvim, resolver)\n  let context: UltiSnippetContext\n  if (opts) {\n    context = { range, line, ...opts, id: generateContextId(workspace.bufnr) }\n    await executePythonCode(nvim, getInitialPythonCode(context))\n  }\n  await snip.init(context)\n  return snip\n}\n\ndescribe('SnippetString', () => {\n  it('should check SnippetString', () => {\n    expect(SnippetString.isSnippetString(null)).toBe(false)\n    let snippetString = new SnippetString()\n    expect(SnippetString.isSnippetString(snippetString)).toBe(true)\n    expect(SnippetString.isSnippetString({})).toBe(false)\n  })\n\n  it('should build snippet string', () => {\n    let snippetString: SnippetString\n\n    snippetString = new SnippetString()\n    assert.strictEqual(snippetString.appendText('I need $ and $').value, 'I need \\\\$ and \\\\$')\n\n    snippetString = new SnippetString()\n    assert.strictEqual(snippetString.appendText('I need \\\\$').value, 'I need \\\\\\\\\\\\$')\n\n    snippetString = new SnippetString()\n    snippetString.appendPlaceholder('fo$o}')\n    assert.strictEqual(snippetString.value, '${1:fo\\\\$o\\\\}}')\n\n    snippetString = new SnippetString()\n    snippetString.appendText('foo').appendTabstop(0).appendText('bar')\n    assert.strictEqual(snippetString.value, 'foo$0bar')\n\n    snippetString = new SnippetString()\n    snippetString.appendText('foo').appendTabstop().appendText('bar')\n    assert.strictEqual(snippetString.value, 'foo$1bar')\n\n    snippetString = new SnippetString()\n    snippetString.appendText('foo').appendTabstop(42).appendText('bar')\n    assert.strictEqual(snippetString.value, 'foo$42bar')\n\n    snippetString = new SnippetString()\n    snippetString.appendText('foo').appendPlaceholder('farboo').appendText('bar')\n    assert.strictEqual(snippetString.value, 'foo${1:farboo}bar')\n\n    snippetString = new SnippetString()\n    snippetString.appendText('foo').appendPlaceholder('far$boo').appendText('bar')\n    assert.strictEqual(snippetString.value, 'foo${1:far\\\\$boo}bar')\n\n    snippetString = new SnippetString()\n    snippetString.appendText('foo').appendPlaceholder(b => b.appendText('abc').appendPlaceholder('nested')).appendText('bar')\n    assert.strictEqual(snippetString.value, 'foo${1:abc${2:nested}}bar')\n\n    snippetString = new SnippetString()\n    snippetString.appendVariable('foo', 'foo')\n    assert.strictEqual(snippetString.value, '${foo:foo}')\n\n    snippetString = new SnippetString()\n    snippetString.appendText('foo').appendVariable('TM_SELECTED_TEXT').appendText('bar')\n    assert.strictEqual(snippetString.value, 'foo${TM_SELECTED_TEXT}bar')\n\n    snippetString = new SnippetString()\n    snippetString.appendVariable('BAR', b => b.appendPlaceholder('ops'))\n    assert.strictEqual(snippetString.value, '${BAR:${1:ops}}')\n\n    snippetString = new SnippetString()\n    snippetString.appendVariable('BAR', b => {})\n    assert.strictEqual(snippetString.value, '${BAR}')\n\n    snippetString = new SnippetString()\n    snippetString.appendChoice(['b', 'a', 'r'])\n    assert.strictEqual(snippetString.value, '${1|b,a,r|}')\n\n    snippetString = new SnippetString()\n    snippetString.appendChoice(['b,1', 'a,2', 'r,3'])\n    assert.strictEqual(snippetString.value, '${1|b\\\\,1,a\\\\,2,r\\\\,3|}')\n\n    snippetString = new SnippetString()\n    snippetString.appendChoice(['b', 'a', 'r'], 0)\n    assert.strictEqual(snippetString.value, '${0|b,a,r|}')\n\n    snippetString = new SnippetString()\n    snippetString.appendText('foo').appendChoice(['far', 'boo']).appendText('bar')\n    assert.strictEqual(snippetString.value, 'foo${1|far,boo|}bar')\n\n    snippetString = new SnippetString()\n    snippetString.appendText('foo').appendChoice(['far', '$boo']).appendText('bar')\n    assert.strictEqual(snippetString.value, 'foo${1|far,$boo|}bar')\n\n    snippetString = new SnippetString()\n    snippetString.appendText('foo').appendPlaceholder('farboo').appendChoice(['far', 'boo']).appendText('bar')\n    assert.strictEqual(snippetString.value, 'foo${1:farboo}${2|far,boo|}bar')\n  })\n\n  it('should escape/apply snippet choices correctly', () => {\n    {\n      const s = new SnippetString()\n      s.appendChoice([\"aaa$aaa\"])\n      s.appendText(\"bbb$bbb\")\n      assert.strictEqual(s.value, '${1|aaa$aaa|}bbb\\\\$bbb')\n    }\n    {\n      const s = new SnippetString()\n      s.appendChoice([\"aaa,aaa\"])\n      s.appendText(\"bbb$bbb\")\n      assert.strictEqual(s.value, '${1|aaa\\\\,aaa|}bbb\\\\$bbb')\n    }\n    {\n      const s = new SnippetString()\n      s.appendChoice([\"aaa|aaa\"])\n      s.appendText(\"bbb$bbb\")\n      assert.strictEqual(s.value, '${1|aaa\\\\|aaa|}bbb\\\\$bbb')\n    }\n    {\n      const s = new SnippetString()\n      s.appendChoice([\"aaa\\\\aaa\"])\n      s.appendText(\"bbb$bbb\")\n      assert.strictEqual(s.value, '${1|aaa\\\\\\\\aaa|}bbb\\\\$bbb')\n    }\n  })\n})\n\ndescribe('toSnippetString()', () => {\n  it('should convert snippet to string', async () => {\n    expect(() => {\n      toSnippetString(1 as any)\n    }).toThrow(TypeError)\n    expect(toSnippetString(new SnippetString())).toBe('')\n  })\n})\n\ndescribe('CocSnippet', () => {\n  async function assertResult(snip: string, resolved: string, opts?: UltiSnippetOption) {\n    let c = await createSnippet(snip, opts)\n    expect(c.text).toBe(resolved)\n  }\n\n  async function assertPyxValue(code: string, res: any) {\n    let val = await nvim.call(`pyxeval`, code) as string\n    if (typeof res === 'number' || typeof res === 'string' || typeof res === 'boolean') {\n      expect(val).toBe(res)\n    } else if (res instanceof RegExp) {\n      expect(val).toMatch(res)\n    } else {\n      expect(val).toEqual(res)\n    }\n  }\n\n  describe('resolveVariables()', () => {\n    it('should padZero', () => {\n      expect(padZero(1)).toBe('01')\n      expect(padZero(10)).toBe('10')\n    })\n\n    it('should getVariablesCode', () => {\n      expect(getVariablesCode({})).toBe('t = ()')\n      expect(getVariablesCode({ 1: 'foo', 3: 'bar' })).toBe('t = (\"\",\"foo\",\"\",\"bar\",)')\n    })\n\n    it('should resolve uppercase variables', async () => {\n      let doc = await helper.createDocument()\n      let fsPath = URI.parse(doc.uri).fsPath\n      await assertResult('$TM_FILENAME', path.basename(fsPath))\n      await assertResult('$TM_FILENAME_BASE', path.basename(fsPath, path.extname(fsPath)))\n      await assertResult('$TM_DIRECTORY', path.dirname(fsPath))\n      await assertResult('$TM_FILEPATH', fsPath)\n      await nvim.call('setreg', ['\"\"', 'foo'])\n      await assertResult('$YANK', 'foo')\n      await assertResult('$TM_LINE_INDEX', '0')\n      await assertResult('$TM_LINE_NUMBER', '1')\n      await nvim.setLine('foo')\n      await assertResult('$TM_CURRENT_LINE', 'foo')\n      await nvim.call('setreg', ['*', 'foo'])\n      await assertResult('$CLIPBOARD', 'foo')\n      let d = new Date()\n      await assertResult('$CURRENT_YEAR', d.getFullYear().toString())\n      await assertResult('$NOT_EXISTS', 'NOT_EXISTS')\n      await assertResult('$TM_CURRENT_WORD', 'foo')\n    })\n\n    it('should resolve new VSCode variables', async () => {\n      let doc = await helper.createDocument()\n      await doc.buffer.setOption('comments', 's1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-')\n      await doc.buffer.setOption('commentstring', '')\n      let fsPath = URI.parse(doc.uri).fsPath\n      let c = await createSnippet('$RANDOM')\n      expect(c.text.length).toBe(6)\n      c = await createSnippet('$RANDOM_HEX')\n      expect(c.text.length).toBe(6)\n      c = await createSnippet('$UUID')\n      expect(c.text).toMatch('-')\n      c = await createSnippet('$RELATIVE_FILEPATH')\n      expect(c.text).toMatch(path.basename(fsPath))\n      c = await createSnippet('$WORKSPACE_NAME')\n      expect(c.text.length).toBeGreaterThan(0)\n      c = await createSnippet('$WORKSPACE_FOLDER')\n      expect(c.text.length).toBeGreaterThan(0)\n      await assertResult('$LINE_COMMENT', '//')\n      await assertResult('$BLOCK_COMMENT_START', '/*')\n      await assertResult('$BLOCK_COMMENT_END', '*/')\n      await doc.buffer.setOption('comments', '')\n      await doc.buffer.setOption('commentstring', '// %s')\n      await assertResult('$LINE_COMMENT', '//')\n      await assertResult('$BLOCK_COMMENT_START', '')\n      await assertResult('$BLOCK_COMMENT_END', '')\n    })\n\n    it('should resolve variables in placeholders', async () => {\n      await nvim.setLine('foo')\n      await assertResult('$1 ${1:$TM_CURRENT_LINE}', 'foo foo')\n      await assertResult('$1 ${1:$TM_CURRENT_LINE bar}', 'foo bar foo bar')\n      await assertResult('$2 ${2:|${1:$TM_CURRENT_LINE}|}', '|foo| |foo|')\n      await assertResult('$1 $2 ${2:${1:|$TM_CURRENT_LINE|}}', '|foo| |foo| |foo|')\n    })\n\n    it('should resolve variables  with default value', async () => {\n      await assertResult('$1 ${1:${VISUAL:foo}}', 'foo foo')\n    })\n\n    it('should resolve for lower case variables', async () => {\n      await assertResult('${foo:abcdef} ${bar}', 'abcdef bar')\n      await assertResult('${1:${foo:abcdef}} ${1/^\\\\w\\\\w(.*)/$1/}', 'abcdef cdef')\n    })\n  })\n\n  describe('getUltiSnipOption', () => {\n    it('should get snippets option', async () => {\n      let c = await createSnippet('${1:foo}', { noExpand: true })\n      let m = c.tmSnippet.children[0]\n      expect(c.getUltiSnipOption(m, 'noExpand')).toBe(true)\n      expect(c.getUltiSnipOption(c.tmSnippet, 'noExpand')).toBe(true)\n      expect(c.getUltiSnipOption(new Text(''), 'trimTrailingWhitespace')).toBeUndefined()\n    })\n  })\n\n  describe('findParent()', () => {\n    it('should throw when not found', async () => {\n      let snip = new TextmateSnippet()\n      snip.appendChild(new Text('f'))\n      let c = await createSnippet(snip)\n      expect(() => {\n        c.findParent(Range.create(1, 0, 1, 0))\n      }).toThrow(Error)\n    })\n\n    it('should not use adjacent choice placeholder', async () => {\n      let c = await createSnippet('a\\n${1|one,two,three|}\\nb')\n      let res = c.findParent(Range.create(1, 0, 1, 0))\n      expect(res.marker instanceof TextmateSnippet).toBe(true)\n    })\n  })\n\n  describe('replaceWithText()', () => {\n    it('should not return undefined when no change', async () => {\n      let c = await createSnippet('${1:foo}')\n      let token = (new CancellationTokenSource()).token\n      let res = await c.replaceWithText(Range.create(0, 0, 0, 0), '', token)\n      expect(res).toBeDefined()\n      expect(res.snippetText).toBe('foo')\n    })\n\n    it('should replace with Text for choice placeholder', async () => {\n      let c = await createSnippet(' ${1|one,two,three|} ')\n      let res = c.replaceWithMarker(Range.create(0, 2, 0, 4), new Text('bar'))\n      expect(res.children.length).toBe(1)\n      expect(res.children[0].toString()).toBe('obar')\n    })\n\n    it('should not insert line break at the start of placeholder', async () => {\n      let c = await createSnippet(' ${1:bar} ')\n      let p = c.getPlaceholderByIndex(1).marker\n      let res = c.replaceWithMarker(Range.create(0, 1, 0, 1), new Text('\\n'), p)\n      let text = c.tmSnippet.children[0] as Text\n      expect(text.value).toBe(' \\n')\n      expect(res.toString()).toBe('bar')\n    })\n\n    it('should return undefined when cursor not changed', async () => {\n      let doc = await workspace.document\n      let c = await createSnippet('${1:foo}')\n      let token = (new CancellationTokenSource()).token\n      let res = await c.replaceWithText(Range.create(0, 0, 0, 3), '', token, undefined, doc.cursor)\n      expect(res.delta).toBeUndefined()\n    })\n\n    it('should synchronize without related change', async () => {\n      const assertChange = async (range: Range, newText: string, resultText: string) => {\n        let token = (new CancellationTokenSource()).token\n        let c = await createSnippet('begin ${1:foo} end')\n        await c.replaceWithText(range, newText, token)\n        expect(c.text).toBe(resultText)\n        let start = Position.create(0, 0)\n        let end = getEnd(start, resultText)\n        expect(c.range).toEqual(Range.create(start, end))\n        return c\n      }\n      // insert text\n      await assertChange(Range.create(0, 0, 0, 0), 'aa ', 'aa begin foo end')\n      // insert placeholder\n      let snippet = await assertChange(Range.create(0, 6, 0, 6), 'xx', 'begin xxfoo end')\n      let p = snippet.getPlaceholderByIndex(1)\n      expect(p.value).toBe('xxfoo')\n      // delete text of placeholder\n      snippet = await assertChange(Range.create(0, 6, 0, 9), '', 'begin  end')\n      p = snippet.getPlaceholderByIndex(1)\n      expect(p.value).toBe('')\n      // delete text\n      await assertChange(Range.create(0, 0, 0, 6), '', 'foo end')\n      //  delete Text and Placeholder\n      snippet = await assertChange(Range.create(0, 0, 0, 8), '', 'o end')\n      p = snippet.getPlaceholderByIndex(1)\n      expect(p).toBeUndefined()\n      let marker = snippet.getPlaceholderById(0.5, 0)\n      expect(marker).toBeDefined()\n      marker = snippet.getPlaceholderById(10, 9)\n      expect(marker).toBeUndefined()\n    })\n\n    it('should prefer current placeholder', async () => {\n      let m: Placeholder\n      let c = await createSnippet('b ${1:${2:bar} foo} x')\n      let marker = c.getPlaceholderByIndex(1).marker\n      // use outer\n      m = c.replaceWithMarker(Range.create(0, 2, 0, 3), new Text('insert'), marker) as Placeholder\n      expect(m).toBe(marker)\n      expect(m.children.length).toBe(1)\n      expect(m.children[0].toString()).toBe('insertar foo')\n      // use inner\n      c = await createSnippet('b ${1:${2:bar} foo} x')\n      m = c.replaceWithMarker(Range.create(0, 2, 0, 3), new Text('insert')) as Placeholder\n      expect(m instanceof Placeholder).toBe(true)\n      expect(m.index).toBe(2)\n      expect(m.children.length).toBe(1)\n      expect(m.children[0].toString()).toBe('insertar')\n    })\n\n    it('should insert with marker', async () => {\n      let c; let m\n      c = await createSnippet('${1:foo} ${2:bar}')\n      m = c.replaceWithMarker(Range.create(0, 0, 0, 0), new Text('before'))\n      expect(m.toString()).toBe('beforefoo')\n      expect(m.children.length).toBe(1)\n      c = await createSnippet('${1:foo} ${2:bar}')\n      m = c.replaceWithMarker(Range.create(0, 1, 0, 1), new Text('before'))\n      expect(m.toString()).toBe('fbeforeoo')\n      expect(m.children.length).toBe(1)\n      c = await createSnippet('${1:foo} ${2:bar}')\n      m = c.replaceWithMarker(Range.create(0, 3, 0, 3), new Text('before'))\n      expect(m.toString()).toBe('foobefore')\n      expect(m.children.length).toBe(1)\n    })\n\n    it('should insert inside text', async () => {\n      let c = await createSnippet('foo ${1:bar}')\n      let marker = (new SnippetParser()).parse('${1:a}', true)\n      let res = c.replaceWithMarker(Range.create(0, 1, 0, 2), marker)\n      expect(res).toBe(c.tmSnippet)\n      expect(c.tmSnippet.toString()).toBe('fao bar')\n    })\n\n    it('should change final placeholder', async () => {\n      let c = await createSnippet('${1:foo} ${0:bar}')\n      let changed = c.replaceWithMarker(Range.create(0, 4, 0, 4), new Text(' '))\n      expect(changed.toString()).toBe('foo  bar')\n      c.synchronize()\n      changed = c.replaceWithMarker(Range.create(0, 5, 0, 6), new Text(''))\n      expect(changed['index']).toBe(0)\n      expect(changed.toString()).toBe('ar')\n    })\n\n    it('should replace with Text when placeholder is not primary', async () => {\n      let c = await createSnippet('$1 ${1:foo}')\n      let result = await c.replaceWithText(Range.create(0, 0, 0, 1), 'b', CancellationToken.None)\n      expect(result.marker instanceof Text).toBe(true)\n      expect(result.snippetText).toBe('boo foo')\n    })\n  })\n\n  describe('replaceWithSnippet()', () => {\n    it('should insert nested placeholder', async () => {\n      let c = await createSnippet('${1:foo}\\n$1', {})\n      c.deactivateSnippet(undefined)\n      // expect(c.getUltiSnipActionCodes(undefined, 'postJump')).toBeUndefined()\n      let res = await c.replaceWithSnippet(Range.create(0, 0, 0, 3), '${1:bar}')\n      expect(res.toString()).toBe('bar')\n      expect(res.parent.snippet.toString()).toBe('bar\\nbar')\n      expect(c.text).toBe('bar\\nbar')\n    })\n\n    it('should insert python snippet to normal snippet', async () => {\n      let c = await createSnippet('${1:foo}\\n$1', {})\n      let p = c.getPlaceholderByIndex(1)\n      expect(c.hasPython).toBe(false)\n      let res = await c.replaceWithSnippet(p.range, '${1:x} `!p snip.rv = t[1]`', p.marker, { line: '', range: p.range, id: `1-1` })\n      expect(res.toString()).toBe('x x')\n      expect(c.text).toBe('x x\\nx x')\n      let r = c.getPlaceholderByMarker(res.first)\n      let source = new CancellationTokenSource()\n      let result = await c.replaceWithText(r.range, 'bar', source.token)\n      expect(result.snippetText).toBe('bar x\\nx x')\n      expect(c.text).toBe('bar bar\\nbar bar')\n      expect(c.hasPython).toBe(true)\n    })\n\n    it('should not change match for original placeholders', async () => {\n      let c = await createSnippet('`!p snip.rv = match.group(1)` $1', {\n        regex: '^(\\\\w+)'\n      }, Range.create(0, 0, 0, 3), 'foo')\n      let p = c.getPlaceholderByIndex(1)\n      expect(c.hasPython).toBe(true)\n      expect(c.text).toBe('foo ')\n      let context = {\n        id: `1-1`,\n        regex: '^(\\\\w+)',\n        line: 'bar',\n        range: Range.create(0, 0, 0, 3)\n      }\n      await executePythonCode(nvim, getInitialPythonCode(context))\n      await c.replaceWithSnippet(p.range, '`!p snip.rv = match.group(1)`', p.marker, context)\n      expect(c.text).toBe('foo bar')\n    })\n\n    it('should update with independent python global', async () => {\n      let c = await createSnippet('${1:foo} `!p snip.rv = t[1]`', {})\n      let range = Range.create(0, 0, 0, 3)\n      let line = await nvim.line\n      await c.replaceWithSnippet(range, '${1:bar} `!p snip.rv = t[1]`', undefined, { range, line, id: `1-1` })\n      expect(c.text).toBe('bar bar bar bar')\n      let token = (new CancellationTokenSource()).token\n      let res = await c.replaceWithText(Range.create(0, 0, 0, 3), 'xy', token)\n      expect(c.text).toBe('xy xy xy xy')\n      expect(res.delta).toBeUndefined()\n    })\n\n    it('should not throw when parent not exist', async () => {\n      let c = await createSnippet('${1:foo}', {})\n      await c.onMarkerUpdate(new Placeholder(1), CancellationToken.None)\n    })\n\n    it('should not synchronize with none primary placeholder change', async () => {\n      let c = await createSnippet('${1:foo}\\n$1', {})\n      let res = await c.replaceWithSnippet(Range.create(1, 0, 1, 3), '${1:bar}')\n      expect(res.toString()).toBe('bar')\n      expect(c.tmSnippet.toString()).toBe('foo\\nbar')\n    })\n  })\n\n  describe('getMarkerPosition', () => {\n    it('should get position of marker', async () => {\n      let c = await createSnippet('${1:foo}')\n      expect(c.getMarkerPosition(new Placeholder(1))).toBeUndefined()\n      let cloned = c.tmSnippet.clone()\n      expect(c.getMarkerPosition(cloned)).toBeUndefined()\n      expect(c.getMarkerPosition(c.tmSnippet)).toBeDefined()\n    })\n  })\n\n  describe('code block initialize', () => {\n    it('should init shell code block', async () => {\n      await assertResult('`echo \"hello\"` world', 'hello world', {})\n    })\n\n    it('should init vim block', async () => {\n      await assertResult('`!v eval(\"1 + 1\")` = 2', '2 = 2', {})\n      await nvim.setLine('  ')\n      await assertResult('${1:`!v indent(\".\")`} \"$1\"', '2 \"2\"', {})\n    })\n\n    it('should init code block in placeholders', async () => {\n      await assertResult('f ${1:`echo \"b\"`}', 'f b', {})\n      await assertResult('f ${1:`!v \"b\"`}', 'f b', {})\n      await assertResult('f ${1:`!p snip.rv = \"b\"`}', 'f b', {})\n    })\n\n    it('should setup python globals', async () => {\n      await helper.edit('t.js')\n      await createSnippet('`!p snip.rv = fn`', {})\n      await assertPyxValue('fn', 't.js')\n      await assertPyxValue('path', /t\\.js$/)\n      await assertPyxValue('t', [''])\n      await createSnippet('`!p snip.rv = fn`', {\n        regex: '[ab]',\n        context: 'False'\n      }, Range.create(0, 2, 0, 3), 'a b')\n      await assertPyxValue('match.group(0)', 'b')\n    })\n\n    it('should setup python match', async () => {\n      let c = await createSnippet('\\\\\\\\frac{`!p snip.rv = match.group(1)`}{$1}$0', {\n        regex: '((\\\\d+)|(\\\\d*)(\\\\\\\\)?([A-Za-z]+)((\\\\^|_)(\\\\{\\\\d+\\\\}|\\\\d))*)/',\n        context: 'True'\n      }, Range.create(0, 0, 0, 3), '20/')\n      await assertPyxValue('match.group(1)', '20')\n      expect(c.text).toBe('\\\\frac{20}{}')\n    })\n\n    it('should work with methods of snip', async () => {\n      await nvim.command('setl shiftwidth=4 ft=txt tabstop=4 expandtab')\n      await createSnippet('`!p snip.rv = \"a\"`', {}, Range.create(0, 4, 0, 8), '    abcd')\n      await executePythonCode(nvim, [])\n      await executePythonCode(nvim, [\n        'snip.shift(1)',\n        // ultisnip indent only when there's '\\n' in snip.rv\n        'snip += \"\"',\n        'newLine = snip.mkline(\"foo\")'\n      ])\n      await assertPyxValue('newLine', '        foo')\n      await executePythonCode(nvim, [\n        'snip.unshift(1)',\n        'newLine = snip.mkline(\"b\")'\n      ])\n      await assertPyxValue('newLine', '    b')\n      await executePythonCode(nvim, [\n        'snip.shift(1)',\n        'snip.reset_indent()',\n        'newLine = snip.mkline(\"f\")'\n      ])\n      await assertPyxValue('newLine', '    f')\n      await executePythonCode(nvim, [\n        'fff = snip.opt(\"&fff\", \"foo\")',\n        'ft = snip.opt(\"&ft\", \"ft\")',\n      ])\n      await assertPyxValue('fff', 'foo')\n      await assertPyxValue('ft', 'txt')\n    })\n\n    it('should init python code block', async () => {\n      await assertResult('`!p snip.rv = \"a\"` = a', 'a = a', {})\n      await assertResult('`!p snip.rv = t[1]` = ${1:a}', 'a = a', {})\n      await assertResult('`!p snip.rv = t[1]` = ${1:`!v eval(\"\\'a\\'\")`}', 'a = a', {})\n      await assertResult('`!p snip.rv = t[1] + t[2]` = ${1:a} ${2:b}', 'ab = a b', {})\n    })\n\n    it('should init python placeholder', async () => {\n      await assertResult('foo ${1/^\\\\|(.*)\\\\|$/$1/} ${1:|`!p snip.rv = \"a\"`|}', 'foo a |a|', {})\n      await assertResult('foo $1 ${1:`!p snip.rv = \"a\"`}', 'foo a a', {})\n      await assertResult('${1/^_(.*)/$1/} $1 aa ${1:`!p snip.rv = \"_foo\"`}', 'foo _foo aa _foo', {})\n    })\n\n    it('should init nested python placeholder', async () => {\n      await assertResult('${1:foo`!p snip.rv = t[2]`} ${2:bar} $1', 'foobar bar foobar', {})\n      await assertResult('${3:f${2:oo${1:b`!p snip.rv = \"ar\"`}}} `!p snip.rv = t[3]`', 'foobar foobar', {})\n    })\n\n    it('should recursive init python placeholder', async () => {\n      await assertResult('${1:`!p snip.rv = t[2]`} ${2:`!p snip.rv = t[3]`} ${3:`!p snip.rv = t[4][0]`} ${4:bar}', 'b b b bar', {})\n      await assertResult('${1:foo} ${2:`!p snip.rv = t[1][0]`} ${3:`!p snip.rv = \"\"`} ${4:`!p snip.rv = t[2]`}', 'foo f  f', {})\n    })\n\n    it('should update python block from placeholder', async () => {\n      await assertResult('`!p snip.rv = t[1][0] if len(t[1]) > 0 else \"\"` ${1:`!p snip.rv = t[2]`} ${2:foo}', 'f foo foo', {})\n    })\n  })\n\n  describe('updatePlaceholder()', () => {\n    async function assertUpdate(text: string, value: string, result: string, index = 1, ultisnip: UltiSnippetOption | null = {}): Promise<CocSnippet> {\n      let c = await createSnippet(text, ultisnip)\n      let p = c.getPlaceholderByIndex(index)\n      expect(p != null).toBe(true)\n      p.marker.setOnlyChild(new Text(value))\n      await c.tmSnippet.update(nvim, p.marker, CancellationToken.None)\n      expect(c.tmSnippet.toString()).toBe(result)\n      return c\n    }\n\n    it('should update variable placeholders', async () => {\n      await assertUpdate('${foo} ${foo}', 'bar', 'bar bar', 1, null)\n      await assertUpdate('${1:${foo:x}} $1', 'bar', 'bar bar', 1, null)\n    })\n\n    it('should not update when cancelled', async () => {\n      let c = await createSnippet('${1:foo} `!p snip.rv = t[1]`', {})\n      let p = c.getPlaceholderByIndex(1)\n      expect(p != null).toBe(true)\n      p.marker.setOnlyChild(new Text('bar'))\n      await c.tmSnippet.update(nvim, p.marker, CancellationToken.Cancelled)\n      expect(c.tmSnippet.toString()).toBe('bar foo')\n    })\n\n    it('should work with snip.c', async () => {\n      let code = [\n        '#ifndef ${1:`!p',\n        'if not snip.c:',\n        '  import random, string',\n        \"  name = re.sub(r'[^A-Za-z0-9]+','_', snip.fn).upper()\",\n        \"  rand = ''.join(random.sample(string.ascii_letters+string.digits, 8))\",\n        \"  snip.rv = ('%s_%s' % (name,rand)).upper()\",\n        \"else:\",\n        \"  snip.rv = snip.c + t[2]`}\",\n        '#define $1',\n        '$2'\n      ].join('\\n')\n      let c = await createSnippet(code, {})\n      let first = c.text.split('\\n')[0]\n      let p = c.getPlaceholderByIndex(2)\n      expect(p).toBeDefined()\n      p.marker.setOnlyChild(new Text('foo'))\n      await c.tmSnippet.update(nvim, p.marker, CancellationToken.None)\n      let t = c.tmSnippet.toString()\n      expect(t.startsWith(first)).toBe(true)\n      expect(t.split('\\n').map(s => s.endsWith('foo'))).toEqual([true, true, true])\n    })\n\n    it('should update placeholder with code blocks', async () => {\n      await assertUpdate('${1:`echo \"foo\"`} $1', 'bar', 'bar bar')\n      await assertUpdate('${2:${1:`echo \"foo\"`}} $2', 'bar', 'bar bar')\n      await assertUpdate('${1:`!v \"foo\"`} $1', 'bar', 'bar bar')\n      await assertUpdate('${1:`!p snip.rv = \"foo\"`} $1', 'bar', 'bar bar')\n    })\n\n    it('should update related python blocks', async () => {\n      // multiple\n      await assertUpdate('`!p snip.rv = t[1]` ${1:`!p snip.rv = \"foo\"`} `!p snip.rv = t[1]`', 'bar', 'bar bar bar')\n      // parent\n      await assertUpdate('`!p snip.rv = t[2]` ${2:foo ${1:`!p snip.rv = \"foo\"`}}', 'bar', 'foo bar foo bar')\n      // related placeholders\n      await assertUpdate('${2:foo `!p snip.rv = t[1]`} ${1:`!p snip.rv = \"foo\"`}', 'bar', 'foo bar bar')\n    })\n\n    it('should update python code blocks with normal placeholder values', async () => {\n      await assertUpdate('`!p snip.rv = t[1]` $1 `!p snip.rv = t[1]`', 'bar', 'bar bar bar')\n      await assertUpdate('`!p snip.rv = t[2]` ${2:foo $1}', 'bar', 'foo bar foo bar')\n      await assertUpdate('${2:foo `!p snip.rv = t[1]`} $1', 'bar', 'foo bar bar')\n    })\n\n    it('should reset values for removed placeholders', async () => {\n      // Keep remained placeholder this is same behavior of VSCode.\n      let s = await assertUpdate('${2:bar${1:foo}} $2 $1', 'bar', 'bar bar foo', 2)\n      let p = s.getPlaceholderByIndex(2).marker\n      let marker = getNextPlaceholder(p, false)\n      let prev = s.getPlaceholderByMarker(marker)\n      expect(prev).toBeDefined()\n      expect(prev.value).toBe('foo')\n      // python placeholder, reset to empty value\n      await assertUpdate('${2:bar${1:foo}} $2 `!p snip.rv = t[1]`', 'bar', 'bar bar ', 2)\n      // not reset since $1 still exists\n      await assertUpdate('${2:bar${1:foo}} $2 $1 `!p snip.rv = t[1]`', 'bar', 'bar bar foo foo', 2)\n    })\n  })\n\n  describe('getNextPlaceholder()', () => {\n    it('should get next placeholder', async () => {\n      let c = await createSnippet('${1:a} ${2:b}')\n      let p = c.getPlaceholderByIndex(1)\n      let nested = await c.replaceWithSnippet(p.range, '${1:foo} ${2:bar}')\n      nested.placeholders.forEach(p => {\n        p.primary = false\n      })\n      let snip = c.snippets[1]\n      expect(c.snippets[1]).toBe(nested)\n      let marker = snip.first\n      let next = getNextPlaceholder(marker, true)\n      expect(next.index).toBe(2)\n      expect(next.toString()).toBe('bar')\n      {\n        let m = nested.placeholders.find(o => o.index === 0)\n        let next = getNextPlaceholder(m, false)\n        expect(next.toString()).toBe('foo bar')\n      }\n    })\n\n    it('should not throw when next not exists', async () => {\n      expect(getNextPlaceholder(new Placeholder(1), true)).toBeUndefined()\n      expect(getNextPlaceholder(undefined, true)).toBeUndefined()\n    })\n    it('should not throw when next not exists', async () => {\n      expect(getNextPlaceholder(new Placeholder(1), true)).toBeUndefined()\n      expect(getNextPlaceholder(undefined, true)).toBeUndefined()\n    })\n\n    it('should prefer primary placeholder', async () => {\n      let c = await createSnippet('$1 $2 ${1:foo}')\n      let p = c.getPlaceholderByIndex(2)\n      let next = getNextPlaceholder(p.marker, false)\n      expect(next.index).toBe(1)\n      expect(next.primary).toBe(true)\n    })\n  })\n\n  describe('getUltiSnipActionCodes()', () => {\n    it('should not get codes when action not exists', () => {\n      expect(getUltiSnipActionCodes(undefined, 'postJump')).toBeUndefined()\n      expect(getUltiSnipActionCodes(new Text(''), 'postJump')).toBeUndefined()\n      let snip = (new SnippetParser()).parse('${1:a}', true)\n      expect(getUltiSnipActionCodes(snip, 'postJump')).toBeUndefined()\n    })\n\n    it('should get codes when exists action', async () => {\n      let snip = (new SnippetParser()).parse('${1:a}', true)\n      snip.related.context = {\n        id: `1-1`,\n        line: '',\n        range: Range.create(0, 0, 0, 0),\n        actions: { postJump: 'jump' }\n      }\n      let res = getUltiSnipActionCodes(snip, 'postJump')\n      expect(res.length).toBe(2)\n    })\n  })\n\n  describe('getRanges getSnippetPlaceholders getTabStops', () => {\n    it('should get ranges of placeholder', async () => {\n      let c = await createSnippet('${2:${1:x} $1}\\n$2', {})\n      let p = c.getPlaceholderByIndex(1)\n      let arr = c.getRanges(p.marker)\n      expect(arr.length).toBe(2)\n      expect(arr[0]).toEqual(Range.create(0, 0, 0, 1))\n      expect(arr[1]).toEqual(Range.create(0, 2, 0, 3))\n      expect(c.text).toBe('x x\\nx x')\n    })\n\n    it('should get range of marker snippet', async () => {\n      let c = await createSnippet('${1:foo}', {})\n      let p = new Placeholder(1)\n      expect(c.getSnippetRange(p)).toBeUndefined()\n      let snip = (new SnippetParser()).parse('${1:a}', true)\n      expect(c.getSnippetRange(snip.children[0])).toBeUndefined()\n      let range = c.getSnippetRange(c.tmSnippet.children[0])\n      expect(range).toEqual(Range.create(0, 0, 0, 3))\n    })\n\n    it('should get snippet tabstops', async () => {\n      let c = await createSnippet('${1:foo}', {})\n      let p = new Placeholder(1)\n      expect(c.getSnippetTabstops(p)).toEqual([])\n      let tabstops = c.getSnippetTabstops(c.tmSnippet.children[0])\n      expect(tabstops.length).toBe(2)\n    })\n  })\n\n  describe('utils', () => {\n    function assertThrow(fn: () => void) {\n      let err\n      try {\n        fn()\n      } catch (e) {\n        err = e\n      }\n      expect(err).toBeDefined()\n    }\n\n    it('should getTextBefore', () => {\n      function assertText(r: number[], text: string, pos: [number, number], res: string): void {\n        let t = getTextBefore(Range.create(r[0], r[1], r[2], r[3]), text, Position.create(pos[0], pos[1]))\n        expect(t).toBe(res)\n      }\n      assertText([1, 1, 2, 1], 'abc\\nd', [1, 1], '')\n      assertText([1, 1, 2, 1], 'abc\\nd', [2, 1], 'abc\\nd')\n      assertText([1, 1, 3, 1], 'abc\\n\\nd ', [3, 1], 'abc\\n\\nd')\n    })\n\n    it('should getTextAfter', () => {\n      function assertText(r: number[], text: string, pos: [number, number], res: string): void {\n        let t = getTextAfter(Range.create(r[0], r[1], r[2], r[3]), text, Position.create(pos[0], pos[1]))\n        expect(t).toBe(res)\n      }\n      assertText([1, 1, 2, 1], 'abc\\nd', [1, 1], 'abc\\nd')\n      assertText([1, 1, 2, 1], 'abc\\nd', [2, 1], '')\n      assertText([1, 1, 3, 1], 'abc\\n\\nd', [2, 0], '\\nd')\n      assertText([0, 0, 0, 3], 'abc', [0, 3], '')\n    })\n\n    it('should check shouldFormat', () => {\n      expect(shouldFormat(' f')).toBe(true)\n      expect(shouldFormat('a\\nb')).toBe(true)\n      expect(shouldFormat('foo')).toBe(false)\n    })\n\n    it('should normalizeSnippetString', () => {\n      expect(normalizeSnippetString('a\\n\\n\\tb', '  ', {\n        insertSpaces: true,\n        trimTrailingWhitespace: true,\n        tabSize: 2\n      })).toBe('a\\n\\n    b')\n      expect(normalizeSnippetString('a\\n\\n  b', '\\t', {\n        insertSpaces: false,\n        trimTrailingWhitespace: true,\n        tabSize: 2\n      })).toBe('a\\n\\n\\t\\tb')\n      let res = normalizeSnippetString('a\\n\\n\\tb', '\\t', {\n        insertSpaces: false,\n        trimTrailingWhitespace: false,\n        noExpand: true,\n        tabSize: 2\n      })\n      expect(res).toBe('a\\n\\t\\n\\t\\tb')\n    })\n\n    it('should throw for invalid regex', async () => {\n      assertThrow(() => {\n        convertRegex('\\\\z')\n      })\n      assertThrow(() => {\n        convertRegex('(?s)')\n      })\n      assertThrow(() => {\n        convertRegex('(?x)')\n      })\n      assertThrow(() => {\n        convertRegex('a\\nb')\n      })\n      assertThrow(() => {\n        convertRegex('(<)?(\\\\w+@\\\\w+(?:\\\\.\\\\w+)+)(?(1)>|$)')\n      })\n      assertThrow(() => {\n        convertRegex('(<)?(\\\\w+@\\\\w+(?:\\\\.\\\\w+)+)(?(1)>|)')\n      })\n    })\n\n    it('should convert regex', async () => {\n      // \\\\A\n      expect(convertRegex('\\\\A')).toBe('^')\n      expect(convertRegex('f(?#abc)b')).toBe('fb')\n      expect(convertRegex('f(?P<abc>def)b')).toBe('f(?<abc>def)b')\n      expect(convertRegex('f(?P=abc)b')).toBe('f\\\\k<abc>b')\n    })\n\n    it('should catch error with executePythonCode', async () => {\n      let fn = async () => {\n        await executePythonCode(nvim, ['INVALID_CODE'])\n      }\n      await expect(fn()).rejects.toThrow(Error)\n    })\n\n    it('should set error with addPythonTryCatch', async () => {\n      let code = addPythonTryCatch('INVALID_CODE', true)\n      await nvim.command(`pyx ${code}`)\n      let msg = await nvim.getVar('errmsg')\n      expect(msg).toBeDefined()\n      expect(msg).toMatch('INVALID_CODE')\n    })\n\n    it('should cancel code block eval when necessary', async (): Promise<void> => {\n      {\n        let block = new CodeBlock('echo \"foo\"', 'shell')\n        await block.resolve(nvim, CancellationToken.Cancelled)\n        expect(block.len()).toBe(0)\n      }\n      {\n        let block = new CodeBlock('bufnr(\"%\")', 'vim')\n        await block.resolve(nvim, CancellationToken.None)\n        let bufnr = await nvim.eval('bufnr(\"%\")')\n        expect(block.value).toBe(`${bufnr}`)\n      }\n      {\n        let block = new CodeBlock('v:null', 'vim')\n        await block.resolve(nvim)\n        expect(block.value).toBe('')\n      }\n      {\n        await executePythonCode(nvim, [`snip = SnippetUtil(\"\", (0, 0), (0, 0), None)`])\n        let block = new CodeBlock('snip.rv = \"foo\"', 'python')\n        let tokenSource = new CancellationTokenSource()\n        let token = tokenSource.token\n        process.nextTick(() => {\n          tokenSource.cancel()\n        })\n        await block.resolve(nvim, token)\n      }\n    })\n\n    it('should parse comments', async () => {\n      expect(parseCommentstring('a%sb')).toBeUndefined()\n      expect(parseCommentstring('// %s')).toBe('//')\n      expect(parseComments('')).toEqual({\n        start: undefined,\n        end: undefined,\n        single: undefined\n      })\n      expect(parseComments('s:/*')).toEqual({\n        start: '/*',\n        end: undefined,\n        single: undefined\n      })\n      expect(parseComments('e:*/')).toEqual({\n        end: '*/',\n        start: undefined,\n        single: undefined\n      })\n      expect(parseComments(':#,:b')).toEqual({\n        end: undefined,\n        start: undefined,\n        single: '#'\n      })\n    })\n\n    it('should set request variable', async () => {\n      events.requesting = true\n      await executePythonCode(nvim, ['stat = __requesting'])\n      let res = await nvim.call('pyxeval', ['stat'])\n      expect(res).toBe(true)\n      events.requesting = false\n      await executePythonCode(nvim, ['stat = __requesting'])\n      res = await nvim.call('pyxeval', ['stat'])\n      expect(res).toBe(false)\n    })\n\n    it('should check hasPython', () => {\n      expect(hasPython(undefined)).toBe(false)\n      expect(hasPython({ context: 'context' })).toBe(true)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/tree/basicProvider.test.ts",
    "content": "import { CancellationTokenSource, Disposable } from 'vscode-languageserver-protocol'\nimport commandsManager from '../../commands'\nimport { TreeItemCollapsibleState } from '../../tree'\nimport { HistoryInput } from '../../tree/filter'\nimport BasicDataProvider, { TreeNode } from '../../tree/BasicDataProvider'\nimport { disposeAll } from '../../util'\n\nlet disposables: Disposable[] = []\n\ntype NodeDef = [string, NodeDef[]?]\n\ninterface CustomNode extends TreeNode {\n  kind?: string\n  x?: number\n  y?: number\n}\n\nafterEach(async () => {\n  disposeAll(disposables)\n  disposables = []\n})\n\nfunction createNode(label: string, children?: TreeNode[], key?: string, tooltip?: string): CustomNode {\n  let res: TreeNode = { label }\n  if (children) res.children = children\n  if (tooltip) res.tooltip = tooltip\n  if (key) res.key = key\n  return res\n}\n\nlet defaultDef: NodeDef[] = [\n  ['a', [['c'], ['d']]],\n  ['b', [['e'], ['f']]],\n  ['g']\n]\n\nfunction createLabels(data: ReadonlyArray<TreeNode>): string[] {\n  let res: string[] = []\n  const addLabels = (n: TreeNode, level: number) => {\n    res.push(' '.repeat(level) + n.label)\n    if (n.children) {\n      for (let node of n.children) {\n        addLabels(node, level + 1)\n      }\n    }\n  }\n  for (let item of data || []) {\n    addLabels(item, 0)\n  }\n  return res\n}\n\nfunction findNode(label: string, nodes: ReadonlyArray<TreeNode>): TreeNode | undefined {\n  for (let n of nodes) {\n    if (n.label == label) {\n      return n\n    }\n    let children = n.children\n    if (Array.isArray(children)) {\n      let find = findNode(label, children)\n      if (find) return find\n    }\n  }\n}\n\nexport function createNodes(defs: NodeDef[]): TreeNode[] {\n  return defs.map(o => {\n    let children\n    if (Array.isArray(o[1])) {\n      children = createNodes(o[1])\n    }\n    return createNode(o[0], children)\n  })\n}\n\ndescribe('HistoryInput()', () => {\n  it('should manage history inputs', async () => {\n    let h = new HistoryInput()\n    h.add('a')\n    h.add('b')\n    expect(h.next('')).toBe('b')\n    expect(h.next('a')).toBe('b')\n    expect(h.next('b')).toBe('a')\n    expect(h.toJSON()).toBe(`[b,a]`)\n    expect(h.previous('')).toBe('a')\n    expect(h.previous('a')).toBe('b')\n    expect(h.previous('b')).toBe('a')\n  })\n})\n\ndescribe('BasicDataProvider', () => {\n  describe('getChildren()', () => {\n    it('should get children from root', async () => {\n      let nodes = createNodes(defaultDef)\n      let provider = new BasicDataProvider({\n        provideData: () => {\n          return nodes\n        }\n      })\n      disposables.push(provider)\n      let res = await provider.getChildren()\n      expect(res.length).toBe(3)\n      expect(res.map(o => o.label)).toEqual(['a', 'b', 'g'])\n    })\n\n    it('should throw when result is not array', async () => {\n      let provider = new BasicDataProvider({\n        provideData: () => {\n          return undefined\n        }\n      })\n      disposables.push(provider)\n      await expect(async () => {\n        await provider.getChildren()\n      }).rejects.toThrow(Error)\n      expect(provider.getLevel(undefined)).toBe(0)\n    })\n\n    it('should get children from child node', async () => {\n      let provider = new BasicDataProvider({\n        provideData: () => {\n          return createNodes(defaultDef)\n        }\n      })\n      disposables.push(provider)\n      let res = await provider.getChildren()\n      let nodes = await provider.getChildren(res[0])\n      expect(nodes.length).toBe(2)\n      expect(nodes.map(o => o.label)).toEqual(['c', 'd'])\n    })\n\n    it('should throw when provideData throws', async () => {\n      let provider = new BasicDataProvider({\n        provideData: () => {\n          throw new Error('my error')\n        }\n      })\n      disposables.push(provider)\n      let err\n      try {\n        await provider.getChildren()\n      } catch (e) {\n        err = e\n      }\n      expect(err).toBeDefined()\n    })\n  })\n\n  describe('getTreeItem()', () => {\n    it('should get tree item from node', async () => {\n      let provider = new BasicDataProvider({\n        provideData: () => {\n          return createNodes(defaultDef)\n        }\n      })\n      disposables.push(provider)\n      let res = await provider.getChildren()\n      let item = provider.getTreeItem(res[0])\n      expect(item).toBeDefined()\n      expect(item.collapsibleState).toBe(TreeItemCollapsibleState.Collapsed)\n      item = provider.getTreeItem(res[2])\n      expect(item.collapsibleState).toBe(TreeItemCollapsibleState.None)\n    })\n\n    it('should respect expandLevel option', async () => {\n      let def: NodeDef[] = [\n        ['a', [['c', [['e'], ['f']]], ['d']]],\n        ['b']\n      ]\n      let provider = new BasicDataProvider({\n        expandLevel: 1,\n        provideData: () => {\n          return createNodes(def)\n        }\n      })\n      disposables.push(provider)\n      let res = await provider.getChildren()\n      let item = provider.getTreeItem(res[0])\n      expect(item).toBeDefined()\n      expect(item.collapsibleState).toBe(TreeItemCollapsibleState.Expanded)\n      item = provider.getTreeItem(res[0].children[0])\n      expect(item.collapsibleState).toBe(TreeItemCollapsibleState.Collapsed)\n      let n = 0\n      provider.iterate(res[0], undefined, 0, () => {\n        n++\n        return true\n      })\n      expect(n).toBe(5)\n    })\n\n    it('should include highlights', async () => {\n      let provider = new BasicDataProvider({\n        provideData: () => {\n          return [createNode('a', [], undefined, 'tip')]\n        }\n      })\n      disposables.push(provider)\n      let res = await provider.getChildren()\n      let item = provider.getTreeItem(res[0])\n      expect(item).toBeDefined()\n      expect(item.tooltip).toBe('tip')\n    })\n\n    it('should use icon from node', async () => {\n      let node = createNode('a', [], undefined, 'tip')\n      node.icon = {\n        text: 'i',\n        hlGroup: 'Function'\n      }\n      let provider = new BasicDataProvider({\n        provideData: () => {\n          return [node]\n        }\n      })\n      disposables.push(provider)\n      let res = await provider.getChildren()\n      let item = provider.getTreeItem(res[0])\n      expect(item).toBeDefined()\n      expect(item.icon).toBeDefined()\n      expect(item.icon).toEqual({\n        text: 'i',\n        hlGroup: 'Function'\n      })\n    })\n\n    it('should resolve icon', async () => {\n      let provider = new BasicDataProvider<CustomNode>({\n        provideData: () => {\n          let node = createNode('a', [], undefined, 'tip')\n          node.kind = 'function'\n          return [node]\n        },\n        resolveIcon: item => {\n          if (item.kind === 'function') {\n            return {\n              text: 'f',\n              hlGroup: 'Function'\n            }\n          }\n        }\n      })\n      disposables.push(provider)\n      let res = await provider.getChildren()\n      let item = provider.getTreeItem(res[0])\n      expect(item).toBeDefined()\n      expect(item.icon).toEqual({\n        text: 'f',\n        hlGroup: 'Function'\n      })\n    })\n  })\n\n  describe('getParent()', () => {\n    it('should get undefined when data does not exist', async () => {\n      let node = createNode('a')\n      let provider = new BasicDataProvider({\n        provideData: () => {\n          return [node]\n        }\n      })\n      disposables.push(provider)\n      let res = provider.getParent(node)\n      expect(res).toBeUndefined()\n    })\n\n    it('should get parent node', async () => {\n      let node = createNode('g')\n      let provider = new BasicDataProvider({\n        provideData: () => {\n          return [\n            createNode('a', [createNode('c', [node]), createNode('d')]),\n            createNode('b', [createNode('e'), createNode('f')]),\n            createNode('g')\n          ]\n        }\n      })\n      disposables.push(provider)\n      await provider.getChildren()\n      let res = provider.getParent(node)\n      expect(res).toBeDefined()\n      expect(res.label).toBe('c')\n      // console.log(provider.labels.join('\\n'))\n    })\n  })\n\n  describe('resolveTreeItem()', () => {\n    it('should resolve tooltip and command', async () => {\n      let node = createNode('a')\n      let provider = new BasicDataProvider({\n        provideData: () => {\n          return [node]\n        },\n        resolveItem: item => {\n          item.tooltip = 'tip'\n          item.command = {\n            command: 'test command',\n            title: 'test'\n          }\n          return item\n        }\n      })\n      disposables.push(provider)\n      await provider.getChildren()\n      let source = new CancellationTokenSource()\n      let item = provider.getTreeItem(node)\n      let resolved = await provider.resolveTreeItem(item, node, source.token)\n      expect(resolved.tooltip).toBe('tip')\n      expect(resolved.command.command).toBe('test command')\n    })\n\n    it('should register command invoke click', async () => {\n      let node = createNode('a')\n      let called: TreeNode\n      let provider = new BasicDataProvider({\n        provideData: () => {\n          return [node]\n        },\n        handleClick: item => {\n          called = item\n        }\n      })\n      disposables.push(provider)\n      await provider.getChildren()\n      let source = new CancellationTokenSource()\n      let item = provider.getTreeItem(node)\n      let resolved = await provider.resolveTreeItem(item, node, source.token)\n      expect(resolved.command).toBeDefined()\n      expect(resolved.command.command).toMatch('invoke')\n      await commandsManager.execute(resolved.command)\n      expect(called).toBeDefined()\n      expect(called).toBe(node)\n    })\n  })\n\n  describe('update()', () => {\n    it('should add children with event', async () => {\n      let defs: NodeDef[] = [\n        ['a', [['b']]],\n        ['b', [['f']]]\n      ]\n      let nodes = createNodes(defs)\n      let b = nodes[0].children[0]\n      let provider = new BasicDataProvider({\n        provideData: () => {\n          return nodes\n        }\n      })\n      disposables.push(provider)\n      await provider.getChildren()\n      let called = false\n      provider.onDidChangeTreeData(node => {\n        expect(node).toBe(b)\n        called = true\n      })\n      let newDefs: NodeDef[] = [\n        ['a', [['b', [['c'], ['d']]]]],\n        ['b', [['f']]]\n      ]\n      let curr = provider.update(createNodes(newDefs))\n      let labels = createLabels(curr)\n      expect(labels).toEqual([\n        'a', ' b', '  c', '  d', 'b', ' f'\n      ])\n      expect(called).toBe(true)\n      expect(b.children).toBeDefined()\n      expect(b.children.length).toBe(2)\n    })\n\n    it('should remove children with event', async () => {\n      let defs: NodeDef[] = [\n        ['a', [['b', [['c'], ['d']]]]],\n        ['e', [['f']]]\n      ]\n      let nodes = createNodes(defs)\n      let b = nodes[0].children[0]\n      let provider = new BasicDataProvider({\n        provideData: () => {\n          return nodes\n        }\n      })\n      disposables.push(provider)\n      await provider.getChildren()\n      let called = false\n      provider.onDidChangeTreeData(node => {\n        expect(node).toBe(b)\n        called = true\n      })\n      let newDefs: NodeDef[] = [\n        ['a', [['b']]],\n        ['e', [['f']]]\n      ]\n      let curr = provider.update(createNodes(newDefs))\n      let labels = createLabels(curr)\n      expect(labels).toEqual([\n        'a', ' b', 'e', ' f'\n      ])\n      expect(called).toBe(true)\n      expect(b.children).toBeUndefined()\n    })\n\n    it('should not fire event for children when parent have changed', async () => {\n      let defs: NodeDef[] = [\n        ['a', [['b', [['c'], ['d']]]]]\n      ]\n      let nodes = createNodes(defs)\n      let provider = new BasicDataProvider({\n        provideData: () => {\n          return nodes\n        }\n      })\n      disposables.push(provider)\n      await provider.getChildren()\n      let called = 0\n      provider.onDidChangeTreeData(node => {\n        expect(node).toBeUndefined()\n        called += 1\n      })\n      let newDefs: NodeDef[] = [\n        ['a', [['b', [['c'], ['d'], ['g']]]]],\n        ['e', [['f']]]\n      ]\n      let curr = provider.update(createNodes(newDefs))\n      expect(called).toBe(1)\n      let labels = createLabels(curr)\n      expect(labels).toEqual([\n        'a', ' b', '  c', '  d', '  g', 'e', ' f'\n      ])\n    })\n\n    it('should fire events for independent node change', async () => {\n      let defs: NodeDef[] = [\n        ['a', [['b', [['c']]]]],\n        ['e', [['f']]]\n      ]\n      let nodes = createNodes(defs)\n      let provider = new BasicDataProvider({\n        provideData: () => {\n          return nodes\n        }\n      })\n      disposables.push(provider)\n      await provider.getChildren()\n      let called = []\n      provider.onDidChangeTreeData(node => {\n        called.push(node)\n      })\n      let newDefs: NodeDef[] = [\n        ['a', [['b', [['c'], ['d']]]]],\n        ['e', [['f', [['g']]]]]\n      ]\n      let curr = provider.update(createNodes(newDefs))\n      expect(called.length).toBe(2)\n      expect(called[0].label).toBe('b')\n      expect(called[1].label).toBe('f')\n      let labels = createLabels(curr)\n      expect(labels).toEqual([\n        'a', ' b', '  c', '  d', 'e', ' f', '  g'\n      ])\n    })\n\n    it('should apply new properties', async () => {\n      let defs: NodeDef[] = [\n        ['a', [['b']]],\n        ['e', [['f']]]\n      ]\n      let nodes = createNodes(defs)\n      let provider = new BasicDataProvider<CustomNode>({\n        provideData: () => {\n          return nodes\n        }\n      })\n      disposables.push(provider)\n      await provider.getChildren()\n      let newNodes = createNodes([\n        ['a', [['b', [['c']]]]],\n        ['e', [['f', [['g']]]]]\n      ])\n      let b = newNodes[0].children[0]\n      Object.assign(b, { x: 1, y: 2 })\n      let curr = provider.update(newNodes)\n      let node = curr[0].children[0]\n      expect(node).toBeDefined()\n      expect(node.x).toBe(1)\n      expect(node.y).toBe(2)\n    })\n\n    it('should keep references and have new data sequence', async () => {\n      let defs: NodeDef[] = [\n        ['a', [['b'], ['c']]],\n        ['e', [['f']]],\n        ['g']\n      ]\n      let nodes = createNodes(defs)\n      let keeps = [\n        findNode('a', nodes),\n        findNode('b', nodes),\n        findNode('c', nodes),\n        findNode('e', nodes),\n        findNode('f', nodes),\n      ]\n      let provider = new BasicDataProvider({\n        provideData: () => {\n          return nodes\n        }\n      })\n      disposables.push(provider)\n      await provider.getChildren()\n      let newNodes = createNodes([\n        ['a', [['c', [['d'], ['h']]], ['b']]],\n        ['e', [['f', [['j']]], ['i']]]\n      ])\n      let curr = provider.update(newNodes)\n      expect(curr).toBe(nodes)\n      expect(keeps[0]).toBe(findNode('a', curr))\n      expect(keeps[1]).toBe(findNode('b', curr))\n      expect(keeps[2]).toBe(findNode('c', curr))\n      expect(keeps[3]).toBe(findNode('e', curr))\n      expect(keeps[4]).toBe(findNode('f', curr))\n      let labels = createLabels(curr)\n      expect(labels).toEqual([\n        'a', ' c', '  d', '  h', ' b', 'e', ' f', '  j', ' i'\n      ])\n    })\n\n    it('should use key for nodes', async () => {\n      let nodes = [\n        createNode('a', [], 'x'),\n        createNode('a', [], 'y'),\n        createNode('a', [], 'z'),\n      ]\n      let provider = new BasicDataProvider({\n        provideData: () => {\n          return nodes\n        }\n      })\n      disposables.push(provider)\n      await provider.getChildren()\n      let newNodes = [\n        createNode('a', [], 'x'),\n        createNode('a', [], 'z'),\n      ]\n      let curr = provider.update(newNodes)\n      expect(curr.length).toBe(2)\n      expect(curr[0].key).toBe('x')\n      expect(curr[1].key).toBe('z')\n    })\n\n    it('should reset data', async () => {\n      let nodes = [\n        createNode('a', [], 'x'),\n      ]\n      let provider = new BasicDataProvider({\n        provideData: () => {\n          return nodes\n        }\n      })\n      disposables.push(provider)\n      await provider.getChildren()\n      let newNodes = [\n        createNode('a', [], 'x'),\n      ]\n      let curr = provider.update(newNodes, true)\n      expect(curr === nodes).toBe(false)\n    })\n  })\n\n  describe('dispose', () => {\n    it('should invoke onDispose from opts', async () => {\n      let called = false\n      let provider = new BasicDataProvider({\n        provideData: () => {\n          return []\n        },\n        onDispose: () => {\n          called = true\n        }\n      })\n      provider.dispose()\n      expect(called).toBe(true)\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/tree/treeView.test.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { Disposable } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport events from '../../events'\nimport { ProviderResult } from '../../provider'\nimport { TreeDataProvider, TreeViewOptions } from '../../tree'\nimport BasicDataProvider, { ProviderOptions, TreeNode } from '../../tree/BasicDataProvider'\nimport { getItemLabel, TreeItem, TreeItemCollapsibleState } from '../../tree/TreeItem'\nimport TreeView from '../../tree/TreeView'\nimport { disposeAll } from '../../util'\nimport workspace from '../../workspace'\nimport helper from '../helper'\nimport { createNodes } from './basicProvider.test'\n\ntype NodeDef = [string, NodeDef[]?]\n\nlet nvim: Neovim\nlet disposables: Disposable[] = []\nlet treeView: TreeView<TreeNode>\nlet provider: BasicDataProvider<TreeNode>\nlet nodes: TreeNode[]\nbeforeAll(async () => {\n  await helper.setup()\n  nvim = helper.nvim\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nbeforeEach(async () => {\n  await helper.createDocument()\n})\n\nafterEach(async () => {\n  if (provider) provider.dispose()\n  if (treeView) treeView.dispose()\n  disposeAll(disposables)\n  await helper.reset()\n})\n\nfunction createNode(label: string, children?: TreeNode[], key?: string, tooltip?: string): TreeNode {\n  let res: TreeNode = { label }\n  if (children) res.children = children\n  if (tooltip) res.tooltip = tooltip\n  if (key) res.key = key\n  return res\n}\n\nfunction createTreeView(defs: NodeDef[], opts: Partial<TreeViewOptions<TreeNode>> = {}, providerOpts: Partial<ProviderOptions<TreeNode>> = {}) {\n  nodes = createNodes(defs)\n  provider = new BasicDataProvider(Object.assign(providerOpts, {\n    provideData: () => {\n      return nodes\n    }\n  }))\n  treeView = new TreeView('test', Object.assign(opts, {\n    bufhidden: 'hide',\n    treeDataProvider: provider\n  }))\n}\n\nfunction updateData(defs: NodeDef[], reset = false) {\n  nodes = createNodes(defs)\n  provider.update(nodes, reset)\n}\n\nfunction makeUpdateUIThrowError() {\n  let spy = jest.spyOn(treeView as any, 'updateUI').mockImplementation(() => {\n    throw new Error('Test error')\n  })\n  disposables.push(Disposable.create(() => {\n    spy.mockRestore()\n  }))\n}\n\nlet defaultDef: NodeDef[] = [\n  ['a', [['c'], ['d']]],\n  ['b', [['e'], ['f']]],\n  ['g']\n]\n\nasync function checkLines(arr: string[]): Promise<void> {\n  await helper.waitValue(async () => {\n    return await nvim.call('getline', [1, '$'])\n  }, arr)\n}\n\ndescribe('TreeView', () => {\n  describe('TreeItem()', () => {\n    it('should create TreeItem from resourceUri', async () => {\n      let item = new TreeItem(URI.file('/foo/bar.ts'))\n      expect(item.resourceUri).toBeDefined()\n      expect(item.label).toBe('bar.ts')\n      expect(item.label).toBeDefined()\n    })\n\n    it('should get item label', async () => {\n      let item = new TreeItem({ label: 'foo' }, TreeItemCollapsibleState.None)\n      expect(getItemLabel(item)).toBe('foo')\n    })\n  })\n\n  describe('show()', () => {\n    it('should show with title', async () => {\n      createTreeView(defaultDef)\n      expect(treeView).toBeDefined()\n      expect(treeView.visible).toBe(false)\n      expect(await treeView.checkLines()).toBe(false)\n      await treeView.show()\n      let visible = treeView.visible\n      expect(visible).toBe(true)\n      await checkLines(['test', '+ a', '+ b', '  g'])\n      treeView.registerLocalKeymap('n', undefined, () => {})\n      let called = false\n      treeView.registerLocalKeymap('n', 'p', () => {\n        called = true\n      }, false)\n      await helper.wait(30)\n      await nvim.input('p')\n      await helper.waitValue(() => called, true)\n    })\n\n    it('should not show when visible', async () => {\n      createTreeView(defaultDef)\n      await treeView.show()\n      let windowId = treeView.windowId\n      await treeView.show()\n      expect(treeView.windowId).toBe(windowId)\n    })\n\n    it('should reuse window', async () => {\n      createTreeView(defaultDef)\n      await treeView.show()\n      let windowId = treeView.windowId\n      provider.dispose()\n      createTreeView(defaultDef)\n      await treeView.show()\n      expect(treeView.windowId).toBe(windowId)\n    })\n\n    it('should render item icon', async () => {\n      createTreeView(defaultDef)\n      nodes[0].icon = { text: 'i', hlGroup: 'Title' }\n      nodes[1].icon = { text: 'i', hlGroup: 'Title' }\n      nodes[2].icon = { text: 'i', hlGroup: 'Title' }\n      await treeView.show()\n      await checkLines(['test', '+ i a', '+ i b', '  i g'])\n    })\n  })\n\n  describe('configuration', () => {\n    it('should change open close icon', async () => {\n      createTreeView(defaultDef)\n      await treeView.show()\n      let { configurations } = workspace\n      configurations.updateMemoryConfig({\n        'tree.openedIcon': '',\n        'tree.closedIcon': '',\n      })\n      await checkLines(['test', ' a', ' b', '  g'])\n    })\n  })\n\n  describe('events', () => {\n    function waitVisibilityEvent(visible: boolean): Promise<void> {\n      return new Promise((resolve, reject) => {\n        let timer = setTimeout(() => {\n          disposable.dispose()\n          reject(new Error('event not fired after 2s'))\n        }, 2000)\n        let disposable = treeView.onDidChangeVisibility(e => {\n          clearTimeout(timer)\n          expect(e.visible).toBe(visible)\n          disposable.dispose()\n          resolve(undefined)\n        })\n      })\n    }\n\n    it('should emit visibility change event', async () => {\n      createTreeView(defaultDef)\n      let p = waitVisibilityEvent(true)\n      await treeView.show()\n      await p\n      nvim.command('close', true)\n      await waitVisibilityEvent(false)\n      p = waitVisibilityEvent(true)\n      await treeView.show()\n      await p\n      nvim.command('enew', true)\n      await waitVisibilityEvent(false)\n      p = waitVisibilityEvent(true)\n      await treeView.show()\n      await p\n    })\n\n    it('should dispose on tab close', async () => {\n      await nvim.command('tabe')\n      await nvim.command('tabe')\n      createTreeView(defaultDef)\n      await treeView.show()\n      await nvim.command('close')\n      await nvim.command('normal! 2gt')\n      await nvim.command('close')\n      await nvim.command('normal! 1gt')\n      await nvim.command('tabonly')\n      await helper.waitValue(() => {\n        return treeView.valid\n      }, false)\n    })\n\n    it('should registerLocalKeymap before shown', async () => {\n      createTreeView(defaultDef)\n      let called = false\n      treeView.registerLocalKeymap('n', 'p', () => {\n        called = true\n      }, true)\n      await treeView.show()\n      await events.race(['TextChanged'], 50)\n      await nvim.input('p')\n      await helper.waitValue(() => {\n        return called\n      }, true)\n    })\n  })\n\n  describe('public properties', () => {\n    it('should change title', async () => {\n      createTreeView(defaultDef)\n      treeView.title = 'foo'\n      await treeView.show()\n      await checkLines(['foo', '+ a', '+ b', '  g'])\n      treeView.title = 'bar'\n      await events.race(['TextChanged'], 50)\n      await checkLines(['bar', '+ a', '+ b', '  g'])\n      treeView.title = undefined\n      await events.race(['TextChanged'], 50)\n    })\n\n    it('should change description', async () => {\n      createTreeView(defaultDef)\n      treeView.description = 'desc'\n      await treeView.show()\n      await checkLines(['test desc', '+ a', '+ b', '  g'])\n      treeView.description = 'foo bar'\n      await events.race(['TextChanged'], 50)\n      await checkLines(['test foo bar', '+ a', '+ b', '  g'])\n      treeView.description = ''\n      await events.race(['TextChanged'], 50)\n      await checkLines(['test', '+ a', '+ b', '  g'])\n    })\n\n    it('should change message', async () => {\n      createTreeView(defaultDef)\n      treeView.message = 'hello'\n      await treeView.show()\n      await checkLines(['hello', '', 'test', '+ a', '+ b', '  g'])\n      treeView.message = 'foo'\n      await events.race(['TextChanged'], 50)\n      await checkLines(['foo', '', 'test', '+ a', '+ b', '  g'])\n      treeView.message = undefined\n      await events.race(['TextChanged'], 50)\n      await checkLines(['test', '+ a', '+ b', '  g'])\n    })\n  })\n\n  describe('options', () => {\n    it('should disable winfixwidth', async () => {\n      createTreeView(defaultDef, { winfixwidth: false })\n      await treeView.show()\n      let res = await nvim.eval('&winfixwidth')\n      expect(res).toBe(0)\n    })\n\n    it('should disable leaf indent', async () => {\n      createTreeView(defaultDef, { disableLeafIndent: true })\n      await treeView.show()\n      await checkLines(['test', '+ a', '+ b', 'g'])\n    })\n\n    it('should should adjust window width', async () => {\n      let def: NodeDef[] = [\n        ['a', [['c'], ['d']]],\n        ['very long line']\n      ]\n      createTreeView(def, { autoWidth: true })\n      await treeView.show('belowright 10vs')\n      let width = await nvim.call('winwidth', [0])\n      expect(width).toBeGreaterThan(10)\n      expect(treeView.targetWinId).toBeDefined()\n    })\n\n    it('should support many selection', async () => {\n      createTreeView(defaultDef, { canSelectMany: true })\n      await treeView.show()\n      let selection: TreeNode[]\n      treeView.onDidChangeSelection(e => {\n        selection = e.selection\n      })\n      await nvim.command('exe 1')\n      await nvim.input('<space>')\n      await helper.wait(10)\n      await nvim.command('exe 2')\n      await nvim.input('<space>')\n      await helper.waitValue(() => {\n        return selection?.length\n      }, 1)\n      await nvim.command('exe 3')\n      await nvim.input('<space>')\n      await helper.waitValue(() => {\n        return selection?.length\n      }, 2)\n      await nvim.input('<space>')\n      await helper.waitValue(() => {\n        return selection.length\n      }, 1)\n      let buf = await nvim.buffer\n      let res = await nvim.call('sign_getplaced', [buf.id, { group: 'CocTree' }])\n      let signs = res[0].signs\n      expect(treeView.selection.length).toBe(1)\n      expect(signs.length).toBe(1)\n      expect(signs[0]).toEqual({\n        lnum: 2,\n        id: 3001,\n        name: 'CocTreeSelected',\n        priority: 10,\n        group: 'CocTree'\n      })\n    })\n  })\n\n  describe('key-mappings', () => {\n    async function getSingns() {\n      let buf = await nvim.buffer\n      let res = await nvim.call('sign_getplaced', [buf.id, { group: 'CocTree' }])\n      return res[0].signs.length\n    }\n\n    it('should jump back by <C-o>', async () => {\n      let winid = await nvim.call('win_getid')\n      createTreeView(defaultDef)\n      await treeView.show()\n      await helper.wait(30)\n      await nvim.input('<C-o>')\n      await helper.waitValue(() => {\n        return nvim.call('win_getid', [])\n      }, winid)\n    })\n\n    it('should toggle selection by <space>', async () => {\n      createTreeView(defaultDef)\n      await treeView.show()\n      let selection: TreeNode[]\n      treeView.onDidChangeSelection(e => {\n        selection = e.selection\n      })\n      await nvim.command('exe 1')\n      await nvim.input('<space>')\n      await helper.wait(10)\n      await nvim.command('exe 2')\n      await nvim.input('<space>')\n      await helper.waitValue(() => selection.length, 1)\n      await nvim.command('exe 3')\n      await nvim.input('<space>')\n      await helper.waitValue(async () => {\n        return await getSingns()\n      }, 1)\n      await nvim.input('<space>')\n      await helper.waitValue(async () => {\n        return await getSingns()\n      }, 0)\n    })\n\n    it('should reset signs after expand & collapse', async () => {\n      createTreeView(defaultDef)\n      await treeView.show()\n      await nvim.command('exe 2')\n      await nvim.input('t')\n      await checkLines([\n        'test',\n        '- a',\n        '    c',\n        '    d',\n        '+ b',\n        '  g',\n      ])\n      await nvim.command('exe 3')\n      await nvim.input('<space>')\n      await helper.waitValue(() => {\n        return getSingns()\n      }, 1)\n      await nvim.command('exe 2')\n      await nvim.input('t')\n      await helper.waitValue(() => {\n        return getSingns()\n      }, 0)\n      await nvim.input('t')\n      await helper.waitValue(() => {\n        return getSingns()\n      }, 1)\n    })\n\n    it('should close tree view by close key', async () => {\n      helper.updateConfiguration('tree.key.close', 'c')\n      createTreeView(defaultDef)\n      await treeView.show()\n      await helper.wait(30)\n      expect(treeView.visible).toBe(true)\n      await nvim.input('c')\n      await helper.waitValue(() => treeView.visible, false)\n    })\n\n    it('should invoke command by <cr>', async () => {\n      let node: TreeNode\n      createTreeView(defaultDef, {}, {\n        handleClick: n => {\n          node = n\n        }\n      })\n      await treeView.show()\n      await treeView.invokeCommand(undefined)\n      await nvim.input('<cr>')\n      await helper.waitValue(() => node, undefined)\n      await nvim.command('exe 2')\n      await nvim.input('<cr>')\n      await helper.waitValue(() => node && node.label, 'a')\n    })\n\n    it('should not throw when resolve command cancelled', async () => {\n      let node: TreeNode\n      let cancelled = false\n      createTreeView(defaultDef, {}, {\n        handleClick: n => {\n          node = n\n        },\n        resolveItem: (item, _node, token) => {\n          return new Promise(resolve => {\n            let timer = setTimeout(() => {\n              item.command = {\n                title: 'not exists',\n                command: 'test'\n              }\n              resolve(item)\n            }, 5000)\n            token.onCancellationRequested(() => {\n              cancelled = true\n              clearTimeout(timer)\n              resolve(item)\n            })\n          })\n        }\n      })\n      await treeView.show()\n      await nvim.command('exe 2')\n      let spy = jest.spyOn(console, 'error').mockImplementation(() => {\n        // noop\n      })\n      await nvim.input('<cr>')\n      await helper.wait(10)\n      await nvim.command('exe 1')\n      await helper.waitValue(() => cancelled, true)\n      spy.mockRestore()\n      expect(node).toBeUndefined()\n    })\n\n    it('should toggle expand by t', async () => {\n      createTreeView(defaultDef)\n      let c = nodes[0].children[0]\n      c.children = [createNode('h')]\n      await treeView.show()\n      await nvim.command('exe 1')\n      await nvim.input('t')\n      await helper.wait(10)\n      await nvim.command('exe 3')\n      await nvim.input('t')\n      await helper.wait(10)\n      await nvim.command('exe 2')\n      await nvim.input('t')\n      await checkLines([\n        'test', '- a', '  + c', '    d', '- b', '    e', '    f', '  g'\n      ])\n      await nvim.command('exe 2')\n      await nvim.input('t')\n      await checkLines([\n        'test', '+ a', '- b', '    e', '    f', '  g'\n      ])\n    })\n\n    it('should should collapse parent node by t', async () => {\n      createTreeView(defaultDef)\n      await treeView.show()\n      await nvim.command('exe 2')\n      await nvim.input('t')\n      await checkLines([\n        'test',\n        '- a',\n        '    c',\n        '    d',\n        '+ b',\n        '  g',\n      ])\n      await nvim.command('exe 3')\n      await nvim.input('t')\n      await checkLines([\n        'test',\n        '+ a',\n        '+ b',\n        '  g',\n      ])\n    })\n\n    it('should collapse all nodes by M', async () => {\n      createTreeView(defaultDef)\n      let c = nodes[0].children[0]\n      c.children = [createNode('h')]\n      await treeView.show()\n      await helper.wait(50)\n      await nvim.command('exe 2')\n      await nvim.input('t')\n      await helper.wait(50)\n      await nvim.command('exe 3')\n      await nvim.input('t')\n      await helper.wait(50)\n      await nvim.command('exe 6')\n      await nvim.input('t')\n      await checkLines([\n        'test',\n        '- a',\n        '  - c',\n        '      h',\n        '    d',\n        '- b',\n        '    e',\n        '    f',\n        '  g',\n      ])\n      await nvim.input('M')\n      await checkLines([\n        'test',\n        '+ a',\n        '+ b',\n        '  g',\n      ])\n      let res = await treeView.checkLines()\n      expect(res).toBe(true)\n    })\n\n    it('should toggle expand on open/close icon click ', async () => {\n      createTreeView(defaultDef)\n      await treeView.show()\n      await nvim.call('cursor', [1, 1])\n      await nvim.input('<LeftRelease>')\n      await helper.wait(20)\n      await nvim.call('cursor', [2, 1])\n      await nvim.input('<LeftRelease>')\n      await checkLines([\n        'test',\n        '- a',\n        '    c',\n        '    d',\n        '+ b',\n        '  g',\n      ])\n      await nvim.input('<LeftRelease>')\n      await checkLines([\n        'test',\n        '+ a',\n        '+ b',\n        '  g',\n      ])\n      let res = await treeView.checkLines()\n      expect(res).toBe(true)\n    })\n\n    it('should invoke command on node click', async () => {\n      let node: TreeNode\n      createTreeView(defaultDef, {}, {\n        handleClick: n => {\n          node = n\n        }\n      })\n      await treeView.show()\n      await nvim.call('cursor', [2, 3])\n      await nvim.input('<LeftRelease>')\n      await helper.waitValue(() => node != null, true)\n      expect(node.label).toBe('a')\n    })\n  })\n\n  describe('invokeActions', () => {\n    it('should show warning when resolveActions does not exist', async () => {\n      createTreeView(defaultDef)\n      await treeView.show()\n      await treeView.invokeActions(undefined)\n      await nvim.call('cursor', [2, 3])\n      await nvim.input('<tab>')\n      await helper.waitValue(async () => {\n        let cmdline = await helper.getCmdline()\n        return cmdline.includes('No actions')\n      }, true)\n    })\n\n    it('should show warning when resolveActions is empty', async () => {\n      createTreeView(defaultDef, {}, {\n        resolveActions: () => {\n          return []\n        }\n      })\n      await treeView.show()\n      await nvim.call('cursor', [2, 3])\n      await nvim.input('<tab>')\n      await helper.waitValue(async () => {\n        let cmdline = await helper.getCmdline()\n        return cmdline.includes('No actions')\n      }, true)\n    })\n\n    it('should invoke selected action', async () => {\n      let args: any[]\n      let called = false\n      createTreeView(defaultDef, {}, {\n        resolveActions: (item, element) => {\n          args = [item, element]\n          return [{\n            title: 'one',\n            handler: () => {\n              called = true\n            }\n          }]\n        }\n      })\n      await treeView.show()\n      await nvim.call('cursor', [2, 3])\n      await nvim.input('<tab>')\n      await helper.waitPrompt()\n      await nvim.input('<esc>')\n      await helper.wait(20)\n      await nvim.input('<tab>')\n      await helper.waitPrompt()\n      await nvim.input('<cr>')\n      await helper.waitValue(() => {\n        return called\n      }, true)\n      expect(called).toBe(true)\n      expect(args[0].label).toBe('a')\n      expect(args[1].label).toBe('a')\n    })\n  })\n\n  describe('events', () => {\n    it('should emit visibility change on buffer unload', async () => {\n      createTreeView(defaultDef)\n      let visible\n      treeView.onDidChangeVisibility(e => {\n        visible = e.visible\n      })\n      await treeView.show()\n      let buf = await nvim.buffer\n      nvim.command(`bd! ${buf.id}`, true)\n      await helper.waitValue(() => visible, false)\n    })\n\n    it('should show tooltip on CursorHold', async () => {\n      createTreeView(defaultDef, {}, {\n        resolveItem: (item, node) => {\n          if (node.label == 'a') {\n            item.tooltip = 'first'\n          }\n          if (node.label == 'b') {\n            item.tooltip = { kind: 'markdown', value: '#title' }\n          }\n          return item\n        }\n      })\n      await treeView.show()\n      await nvim.command('exe 2')\n      let bufnr = await nvim.eval(`bufnr('%')`) as number\n      await events.fire('CursorHold', [bufnr, [2, 1]])\n      let win = await helper.getFloat()\n      expect(win).toBeDefined()\n      let buf = await win.buffer\n      let lines = await buf.lines\n      expect(lines).toEqual(['first'])\n      await nvim.command('exe 3')\n      await events.fire('CursorHold', [bufnr, [3, 1]])\n      lines = await buf.lines\n      expect(lines).toEqual(['#title'])\n      await events.fire('CursorHold', [bufnr, [1, 1]])\n    })\n  })\n\n  describe('data change', () => {\n    it('should ignore hidden node change', async () => {\n      createTreeView(defaultDef)\n      await treeView.show()\n      let tick = await nvim.eval('b:changedtick')\n      updateData([\n        ['a', [['c', [['h']]], ['d']]],\n        ['b', [['e'], ['f']]],\n        ['g']\n      ])\n      await helper.wait(20)\n      let curr = await nvim.eval('b:changedtick')\n      expect(curr).toBe(tick)\n    })\n\n    it('should render all nodes on root change', async () => {\n      createTreeView(defaultDef)\n      await treeView.show()\n      updateData([\n        ['g'],\n        ['h'],\n        ['b', [['e'], ['f']]],\n        ['a', [['c'], ['d']]]\n      ])\n      await checkLines([\n        'test',\n        '  g',\n        '  h',\n        '+ b',\n        '+ a',\n      ])\n      let res = await treeView.checkLines()\n      expect(res).toBe(true)\n    })\n\n    it('should keep node open state', async () => {\n      createTreeView(defaultDef)\n      let c = nodes[0].children[0]\n      c.children = [createNode('h')]\n      await treeView.show()\n      await nvim.command('exe 2')\n      await nvim.input('t')\n      await helper.wait(50)\n      await nvim.command('exe 3')\n      await nvim.input('t')\n      await helper.wait(50)\n      await nvim.command('exe 6')\n      await nvim.input('t')\n      await helper.wait(50)\n      updateData([\n        ['h'],\n        ['g', [['i']]],\n        ['b', [['f']]],\n        ['a', [['c'], ['j']]]\n      ])\n      await checkLines([\n        'test',\n        '  h',\n        '+ g',\n        '- b',\n        '    f',\n        '- a',\n        '    c',\n        '    j',\n      ])\n      let res = await treeView.checkLines()\n      expect(res).toBe(true)\n    })\n\n    it('should render changed nodes', async () => {\n      createTreeView(defaultDef)\n      await treeView.show()\n      await nvim.command('exe 2')\n      await nvim.input('t')\n      await events.race(['TextChanged'])\n      updateData([\n        ['a', [['h', [['i']]], ['d']]],\n        ['b', [['e'], ['f']]],\n        ['g'],\n      ])\n      await checkLines([\n        'test',\n        '- a',\n        '  + h',\n        '    d',\n        '+ b',\n        '  g',\n      ])\n      let res = await treeView.checkLines()\n      expect(res).toBe(true)\n    })\n\n    it('should error message on error', async () => {\n      createTreeView(defaultDef)\n      await treeView.show()\n      await nvim.command('exe 2')\n      await nvim.input('t')\n      await events.race(['TextChanged'])\n      let msg = 'Unable to fetch children'\n      provider.getChildren = () => {\n        throw new Error(msg)\n      }\n      updateData([['a']])\n      await events.race(['TextChanged'])\n      let line = await nvim.call('getline', [1])\n      expect(line).toMatch(msg)\n      await helper.wait(50)\n      let res = await treeView.checkLines()\n      expect(res).toBe(true)\n    })\n\n    it('should reset message when data exists', async () => {\n      createTreeView([])\n      let curr = []\n      provider.getChildren = () => {\n        return Promise.resolve(curr)\n      }\n      await treeView.show()\n      await checkLines([\n        'No results',\n        '',\n        'test',\n      ])\n      curr = [createNode('h')]\n      await treeView.render()\n      await checkLines([\n        'test',\n        '  h',\n      ])\n    })\n\n    it('should show error message on refresh error', async () => {\n      createTreeView(defaultDef)\n      await treeView.show()\n      makeUpdateUIThrowError()\n      updateData([\n        ['a', [['h'], ['d']]],\n        ['b', [['e'], ['f']]],\n        ['g'],\n      ])\n      await helper.waitValue(async () => {\n        let line = await helper.getCmdline()\n        return line.includes('Error on tree refresh')\n      }, true)\n    })\n\n    it('should render deprecated node with deprecated highlight', async () => {\n      createTreeView(defaultDef)\n      await treeView.show()\n      let defs: NodeDef[] = [\n        ['a'],\n        ['b']\n      ]\n      let nodes = createNodes(defs)\n      nodes[0].deprecated = true\n      provider.update(nodes)\n      await checkLines([\n        'test',\n        '  a',\n        '  b',\n      ])\n      let ns = await nvim.call('coc#highlight#create_namespace', ['tree'])\n      let bufnr = await nvim.call('bufnr', ['%'])\n      let markers = await nvim.call('nvim_buf_get_extmarks', [bufnr, ns, [1, 0], [1, -1], { details: true }]) as any[]\n      expect(markers.length > 0).toBe(true)\n      expect(markers[0][3]['hl_group']).toBe('CocDeprecatedHighlight')\n    })\n\n    it('should not throw when getTreeItem return undefined', async () => {\n      let provider: TreeDataProvider<any> = {\n        getTreeItem: (): TreeItem => {\n          return undefined\n        },\n        getChildren: (): ProviderResult<readonly any[]> => {\n          return [{ label: 'a' }]\n        }\n      }\n      let treeView = new TreeView('test', {\n        bufhidden: 'hide',\n        treeDataProvider: provider\n      })\n      await treeView.show()\n      await checkLines([\n        'test',\n      ])\n      treeView.dispose()\n    })\n  })\n\n  describe('focusItem()', () => {\n    it('should not throw when node not rendered', async () => {\n      createTreeView(defaultDef)\n      treeView.selectItem(undefined)\n      treeView.focusItem(nodes[0])\n      treeView.unselectItem(999)\n      await treeView.show()\n      let c = nodes[0].children[0]\n      await treeView.onHover(3)\n      treeView.focusItem(c)\n      treeView.focusItem(undefined)\n    })\n\n    it('should focus rendered node', async () => {\n      createTreeView(defaultDef)\n      await treeView.show()\n      treeView.focusItem(nodes[1])\n      let line = await nvim.call('getline', ['.'])\n      expect(line).toBe('+ b')\n    })\n  })\n\n  describe('reveal()', () => {\n    it('should throw error when getParent does not exist', async () => {\n      createTreeView(defaultDef)\n      provider.getParent = undefined\n      await treeView.show()\n      let err\n      try {\n        await treeView.reveal(nodes[0].children[0])\n      } catch (e) {\n        err = e\n      }\n      expect(err).toBeDefined()\n    })\n\n    it('should select item', async () => {\n      createTreeView(defaultDef)\n      let c = nodes[0].children[0]\n      let h = createNode('h')\n      c.children = [h]\n      await treeView.show()\n      await treeView.reveal(h, { expand: true })\n      await checkLines([\n        'test',\n        '- a',\n        '  - c',\n        '      h',\n        '    d',\n        '+ b',\n        '  g',\n      ])\n      let selection = treeView.selection\n      expect(selection.length).toBe(1)\n      expect(selection[0].label).toBe('h')\n      let line = await nvim.call('getline', ['.'])\n      expect(line).toMatch('h')\n    })\n\n    it('should not select item', async () => {\n      createTreeView(defaultDef)\n      await treeView.show()\n      await treeView.reveal(nodes[1], { select: false })\n      let lnum = await nvim.call('line', ['.'])\n      expect(lnum).toBe(1)\n    })\n\n    it('should focus item', async () => {\n      createTreeView(defaultDef)\n      await treeView.show()\n      await treeView.reveal(nodes[1], { focus: true })\n      let line = await nvim.call('getline', ['.'])\n      expect(line).toMatch('b')\n    })\n\n    it('should expand item which single level', async () => {\n      createTreeView(defaultDef)\n      let c = nodes[0].children[0]\n      c.children = [createNode('h')]\n      await treeView.show()\n      await treeView.reveal(nodes[0], { expand: true })\n      await checkLines([\n        'test',\n        '- a',\n        '  + c',\n        '    d',\n        '+ b',\n        '  g',\n      ])\n    })\n\n    it('should expand item which 2 level', async () => {\n      createTreeView(defaultDef)\n      let c = nodes[0].children[0]\n      c.children = [createNode('h')]\n      await treeView.show()\n      await treeView.reveal(nodes[0], { expand: 2 })\n      await checkLines([\n        'test',\n        '- a',\n        '  - c',\n        '      h',\n        '    d',\n        '+ b',\n        '  g',\n      ])\n    })\n  })\n\n  describe('filter', () => {\n    afterEach(() => {\n      nvim.call('coc#prompt#stop_prompt', ['filter'], true)\n    })\n\n    async function createFilterTreeView(opts: Partial<ProviderOptions<TreeNode>> = {}): Promise<void> {\n      createTreeView(defaultDef, { enableFilter: true }, opts)\n      await treeView.show()\n      await helper.wait(20)\n      let tick = await nvim.eval('b:changedtick') as number\n      await nvim.input('f')\n      await helper.waitValue(async () => {\n        let c = await nvim.eval('b:changedtick') as number\n        return c - tick > 1\n      }, true)\n    }\n\n    it('should start filter by input', async () => {\n      await createFilterTreeView()\n      await treeView.reveal(undefined)\n      await checkLines([\n        'test', ' ', '  a', '  c', '  d', '  b', '  e', '  f', '  g'\n      ])\n      await nvim.input('a')\n      await helper.waitFor('getline', [2], 'a ')\n    })\n\n    it('should not throw error on filter', async () => {\n      await createFilterTreeView()\n      let spy = jest.spyOn(treeView as any, 'getRenderedLine').mockImplementation(() => {\n        throw new Error('Error on updateUI')\n      })\n      await nvim.input('a')\n      await helper.wait(50)\n      spy.mockRestore()\n    })\n\n    it('should add & remove Cursor highlight on window change', async () => {\n      let winid = await nvim.call('win_getid')\n      let ns = await nvim.call('coc#highlight#create_namespace', ['tree'])\n      await createFilterTreeView()\n      let bufnr = await nvim.call('bufnr', ['%'])\n      let markers = await nvim.call('nvim_buf_get_extmarks', [bufnr, ns, [1, 0], [1, -1], {}]) as [number, number, number][]\n      expect(markers[0]).toBeDefined()\n      await nvim.call('win_gotoid', [winid])\n      markers = await nvim.call('nvim_buf_get_extmarks', [bufnr, ns, [1, 0], [1, -1], {}]) as [number, number, number][]\n      expect(markers.length).toBe(0)\n      await nvim.command('wincmd p')\n      markers = await nvim.call('nvim_buf_get_extmarks', [bufnr, ns, [1, 0], [1, -1], {}]) as [number, number, number][]\n      expect(markers.length).toBe(1)\n    })\n\n    it('should filter new nodes on data change', async () => {\n      await createFilterTreeView()\n      await nvim.input('a')\n      await helper.wait(50)\n      updateData([\n        ['ab'],\n        ['e'],\n        ['fA']\n      ])\n      await helper.waitValue(async () => {\n        return await nvim.call('getline', [1, '$'])\n      }, ['test', 'a ', '  ab', '  fA',])\n    })\n\n    it('should change selected item by <up> and <down>', async () => {\n      await createFilterTreeView()\n      await nvim.input('a')\n      await helper.wait(50)\n      updateData([\n        ['ab'],\n        ['fA']\n      ])\n      await helper.wait(30)\n      await nvim.input('<down>')\n      await helper.waitValue(() => {\n        let curr = treeView.selection[0]\n        return curr.label\n      }, 'fA')\n      await nvim.input('<down>')\n      await helper.waitValue(() => {\n        let curr = treeView.selection[0]\n        return curr.label\n      }, 'ab')\n      await nvim.input('<up>')\n      await helper.waitValue(() => {\n        let curr = treeView.selection[0]\n        return curr.label\n      }, 'fA')\n      await nvim.input('<up>')\n      await helper.waitValue(() => {\n        let curr = treeView.selection[0]\n        return curr.label\n      }, 'ab')\n    })\n\n    it('should not throw with empty nodes', async () => {\n      await createFilterTreeView()\n      await nvim.input('ab')\n      await helper.wait(50)\n      await nvim.input('<up>')\n      await helper.wait(50)\n      await nvim.input('<down>')\n      await helper.wait(50)\n      await nvim.input('<cr>')\n      await checkLines(['test', 'ab '])\n      let curr = treeView.selection[0]\n      expect(curr).toBeUndefined()\n    })\n\n    it('should invoke command by <cr>', async () => {\n      let node\n      await createFilterTreeView({\n        handleClick: n => {\n          node = n\n        }\n      })\n      await nvim.input('<cr>')\n      await helper.waitValue(() => node != null, true)\n      let curr = treeView.selection[0]\n      expect(curr).toBeDefined()\n    })\n\n    it('should keep state when press <cr> with empty selection', async () => {\n      await createFilterTreeView()\n      await nvim.input('ab')\n      await helper.wait(50)\n      await nvim.input('<cr>')\n      await checkLines(['test', 'ab '])\n    })\n\n    it('should delete last filter character by <bs>', async () => {\n      await createFilterTreeView()\n      await nvim.input('a')\n      await helper.wait(20)\n      await nvim.input('<bs>')\n      await checkLines([\n        'test', ' ', '  a', '  c', '  d', '  b', '  e', '  f', '  g'\n      ])\n    })\n\n    it('should clean filter character by <C-u>', async () => {\n      await createFilterTreeView()\n      await nvim.input('ab')\n      await helper.wait(20)\n      await nvim.input('<C-u>')\n      await checkLines([\n        'test', ' ', '  a', '  c', '  d', '  b', '  e', '  f', '  g'\n      ])\n    })\n\n    it('should cancel filter by <esc> and <C-o>', async () => {\n      await createFilterTreeView()\n      await helper.waitPrompt()\n      await nvim.input('<esc>')\n      await checkLines([\n        'test',\n        '+ a',\n        '+ b',\n        '  g',\n      ])\n      await nvim.input('f')\n      await helper.wait(20)\n      await nvim.input('<C-o>')\n      await checkLines([\n        'test',\n        '+ a',\n        '+ b',\n        '  g',\n      ])\n    })\n\n    it('should navigate input history by <C-n> and <C-p>', async () => {\n      await createFilterTreeView()\n      await nvim.input('a')\n      await helper.wait(10)\n      await nvim.input('<esc>')\n      await helper.wait(10)\n      await nvim.input('f')\n      await helper.wait(10)\n      await nvim.input('b')\n      await helper.wait(10)\n      await nvim.input('<C-o>')\n      await helper.wait(10)\n      await nvim.input('f')\n      await helper.wait(10)\n      await nvim.input('<C-n>')\n      await checkLines(['test', 'b ', '  b',])\n      await nvim.input('<C-p>')\n      await checkLines(['test', 'a ', '  a',])\n    })\n\n    it('should not throw on filter error', async () => {\n      await createFilterTreeView()\n      let spy = jest.spyOn(treeView as any, 'redraw').mockImplementation(() => {\n        throw new Error('test error')\n      })\n      await nvim.input('a')\n      await helper.wait(20)\n      spy.mockRestore()\n    })\n  })\n})\n"
  },
  {
    "path": "src/__tests__/ultisnips.py",
    "content": "__requesting = True\ndef coc_UltiSnips_create():\n    import re, vim, os, sys\n    from collections import defaultdict, namedtuple\n\n    _Placeholder = namedtuple(\"_Placeholder\", [\"current_text\", \"start\", \"end\"])\n    _VisualContent = namedtuple(\"_VisualContent\", [\"mode\", \"text\"])\n    _Position = namedtuple(\"_Position\", [\"line\", \"col\"])\n    # is_vim = vim.eval('has(\"nvim\")') == '0'\n\n    def byte2col(line, nbyte):\n        \"\"\"Convert a column into a byteidx suitable for a mark or cursor\n        position inside of vim.\"\"\"\n        line = vim.current.buffer[line - 1]\n        raw_bytes = line.encode(vim.eval(\"&encoding\"), \"replace\")[:nbyte]\n        return len(raw_bytes.decode(vim.eval(\"&encoding\"), \"replace\"))\n\n    def col2byte(line, col):\n        \"\"\"Convert a valid column index into a byte index inside of vims\n        buffer.\"\"\"\n        # We pad the line so that selecting the +1 st column still works.\n        pre_chars = (vim.current.buffer[line - 1] + \"  \")[:col]\n        return len(pre_chars.encode(vim.eval(\"&encoding\"), \"replace\"))\n\n    def get_visual_content():\n        mode = vim.eval(\"visualmode()\")\n        if mode == '':\n          return ''\n        sl, sbyte = map(\n            int, (vim.eval(\"\"\"line(\"'<\")\"\"\"), vim.eval(\"\"\"col(\"'<\")\"\"\"))\n        )\n        el, ebyte = map(\n            int, (vim.eval(\"\"\"line(\"'>\")\"\"\"), vim.eval(\"\"\"col(\"'>\")\"\"\"))\n        )\n        sc = byte2col(sl, sbyte - 1)\n        ec = byte2col(el, ebyte - 1)\n        # When 'selection' is 'exclusive', the > mark is one column behind the\n        # actual content being copied, but never before the < mark.\n        if vim.eval(\"&selection\") == \"exclusive\":\n            if not (sl == el and sbyte == ebyte):\n                ec -= 1\n\n        _vim_line_with_eol = lambda ln: vim.current.buffer[ln] + \"\\n\"\n\n        if sl == el:\n            text = _vim_line_with_eol(sl - 1)[sc : ec + 1]\n        else:\n            text = _vim_line_with_eol(sl - 1)[sc:]\n            for cl in range(sl, el - 1):\n                text += _vim_line_with_eol(cl)\n            text += _vim_line_with_eol(el - 1)[: ec + 1]\n        return text\n\n    def _expand_anon(value, trigger=\"\"):\n        pos = vim.eval('coc#cursor#position()')\n        line = int(pos[0])\n        character = int(pos[1])\n        args = r'[{\"start\":{\"line\":%d,\"character\":%d},\"end\":{\"line\":%d,\"character\":%d}}, \"%s\", v:null, {}]' % (line, character - len(trigger), line, character, re.sub(r'\"', r'\\\\\"', value.replace('\\\\', '\\\\\\\\')))\n        # Python of vim doesn't have event loop, use vim's timer\n        if __requesting:\n            vim.eval(r'coc#util#timer(\"coc#rpc#notify\",[\"snippetInsert\", %s])' % (args))\n        else:\n            code = r'coc#rpc#request(\"snippetInsert\", %s)' % (args)\n            vim.eval(code)\n            vim.command('redraw')\n\n    def expand_anon(value, trigger=\"\", cursor = None):\n        if len(value) == 0:\n            return\n        _expand_anon(value, trigger)\n        if cursor is not None:\n            cursor.preserve()\n\n    class Position:\n        \"\"\"Represents a Position in a text file: (0 based line index, 0 based column\n        index) and provides methods for moving them around.\"\"\"\n\n        def __init__(self, line, col):\n            self.line = line\n            self.col = col\n\n        def move(self, pivot, delta):\n            \"\"\"'pivot' is the position of the first changed character, 'delta' is\n            how text after it moved.\"\"\"\n            if self < pivot:\n                return\n            if delta.line == 0:\n                if self.line == pivot.line:\n                    self.col += delta.col\n            elif delta.line > 0:\n                if self.line == pivot.line:\n                    self.col += delta.col - pivot.col\n                self.line += delta.line\n            else:\n                self.line += delta.line\n                if self.line == pivot.line:\n                    self.col += -delta.col + pivot.col\n\n        def __add__(self, pos):\n            assert isinstance(pos, Position)\n            return Position(self.line + pos.line, self.col + pos.col)\n\n        def __sub__(self, pos):\n            assert isinstance(pos, Position)\n            return Position(self.line - pos.line, self.col - pos.col)\n\n        def __eq__(self, other):\n            return (self.line, self.col) == (other.line, other.col)\n\n        def __ne__(self, other):\n            return (self.line, self.col) != (other.line, other.col)\n\n        def __lt__(self, other):\n            return (self.line, self.col) < (other.line, other.col)\n\n        def __le__(self, other):\n            return (self.line, self.col) <= (other.line, other.col)\n\n        def __repr__(self):\n            return \"(%i,%i)\" % (self.line, self.col)\n\n        def __getitem__(self, index):\n            if index > 1:\n                raise IndexError(\"position can be indexed only 0 (line) and 1 (column)\")\n            if index == 0:\n                return self.line\n            else:\n                return self.col\n\n    def diff(a, b, sline=0):\n        \"\"\"\n        Return a list of deletions and insertions that will turn 'a' into 'b'. This\n        is done by traversing an implicit edit graph and searching for the shortest\n        route. The basic idea is as follows:\n\n            - Matching a character is free as long as there was no\n              deletion/insertion before. Then, matching will be seen as delete +\n              insert [1].\n            - Deleting one character has the same cost everywhere. Each additional\n              character costs only have of the first deletion.\n            - Insertion is cheaper the earlier it happens. The first character is\n              more expensive that any later [2].\n\n        [1] This is that world -> aolsa will be \"D\" world + \"I\" aolsa instead of\n            \"D\" w , \"D\" rld, \"I\" a, \"I\" lsa\n        [2] This is that \"hello\\n\\n\" -> \"hello\\n\\n\\n\" will insert a newline after\n            hello and not after \\n\n        \"\"\"\n        d = defaultdict(list)  # pylint:disable=invalid-name\n        seen = defaultdict(lambda: sys.maxsize)\n\n        d[0] = [(0, 0, sline, 0, ())]\n        cost = 0\n        deletion_cost = len(a) + len(b)\n        insertion_cost = len(a) + len(b)\n        while True:\n            while len(d[cost]):\n                x, y, line, col, what = d[cost].pop()\n\n                if a[x:] == b[y:]:\n                    return what\n\n                if x < len(a) and y < len(b) and a[x] == b[y]:\n                    ncol = col + 1\n                    nline = line\n                    if a[x] == \"\\n\":\n                        ncol = 0\n                        nline += 1\n                    lcost = cost + 1\n                    if (\n                        what\n                        and what[-1][0] == \"D\"\n                        and what[-1][1] == line\n                        and what[-1][2] == col\n                        and a[x] != \"\\n\"\n                    ):\n                        # Matching directly after a deletion should be as costly as\n                        # DELETE + INSERT + a bit\n                        lcost = (deletion_cost + insertion_cost) * 1.5\n                    if seen[x + 1, y + 1] > lcost:\n                        d[lcost].append((x + 1, y + 1, nline, ncol, what))\n                        seen[x + 1, y + 1] = lcost\n                if y < len(b):  # INSERT\n                    ncol = col + 1\n                    nline = line\n                    if b[y] == \"\\n\":\n                        ncol = 0\n                        nline += 1\n                    if (\n                        what\n                        and what[-1][0] == \"I\"\n                        and what[-1][1] == nline\n                        and what[-1][2] + len(what[-1][-1]) == col\n                        and b[y] != \"\\n\"\n                        and seen[x, y + 1] > cost + (insertion_cost + ncol) // 2\n                    ):\n                        seen[x, y + 1] = cost + (insertion_cost + ncol) // 2\n                        d[cost + (insertion_cost + ncol) // 2].append(\n                            (\n                                x,\n                                y + 1,\n                                line,\n                                ncol,\n                                what[:-1]\n                                + ((\"I\", what[-1][1], what[-1][2], what[-1][-1] + b[y]),),\n                            )\n                        )\n                    elif seen[x, y + 1] > cost + insertion_cost + ncol:\n                        seen[x, y + 1] = cost + insertion_cost + ncol\n                        d[cost + ncol + insertion_cost].append(\n                            (x, y + 1, nline, ncol, what + ((\"I\", line, col, b[y]),))\n                        )\n                if x < len(a):  # DELETE\n                    if (\n                        what\n                        and what[-1][0] == \"D\"\n                        and what[-1][1] == line\n                        and what[-1][2] == col\n                        and a[x] != \"\\n\"\n                        and what[-1][-1] != \"\\n\"\n                        and seen[x + 1, y] > cost + deletion_cost // 2\n                    ):\n                        seen[x + 1, y] = cost + deletion_cost // 2\n                        d[cost + deletion_cost // 2].append(\n                            (\n                                x + 1,\n                                y,\n                                line,\n                                col,\n                                what[:-1] + ((\"D\", line, col, what[-1][-1] + a[x]),),\n                            )\n                        )\n                    elif seen[x + 1, y] > cost + deletion_cost:\n                        seen[x + 1, y] = cost + deletion_cost\n                        d[cost + deletion_cost].append(\n                            (x + 1, y, line, col, what + ((\"D\", line, col, a[x]),))\n                        )\n            cost += 1\n\n    class VimBuffer:\n\n        \"\"\"Wrapper around the current Vim buffer.\"\"\"\n\n        def __getitem__(self, idx):\n            return vim.current.buffer[idx]\n\n        def __setitem__(self, idx, text):\n            vim.current.buffer[idx] = text\n\n        def __len__(self):\n            return len(vim.current.buffer)\n\n        @property\n        def number(self):  # pylint:disable=no-self-use\n            \"\"\"The bufnr() of the current buffer.\"\"\"\n            return vim.current.buffer.number\n\n        @property\n        def filetypes(self):\n            return [ft for ft in vim.eval(\"&filetype\").split(\".\") if ft]\n\n    class VimBufferProxy(VimBuffer):\n        \"\"\"\n        Proxy object used for tracking changes that made from snippet actions.\n\n        Unfortunately, vim by itself lacks of the API for changing text in\n        trackable maner.\n\n        Vim marks offers limited functionality for tracking line additions and\n        deletions, but nothing offered for tracking changes within single line.\n\n        Instance of this class is passed to all snippet actions and behaves as\n        internal vim.current.window.buffer.\n\n        All changes that are made by user passed to diff algorithm, and resulting\n        diff applied to internal snippet structures to ensure they are in sync with\n        actual buffer contents.\n        \"\"\"\n        def __init__(self, handlers):\n            \"\"\"\n            Instantiate new object.\n            \"\"\"\n            self._buffer = vim.current.buffer\n            self._change_tick = int(vim.eval(\"b:changedtick\"))\n            self._forward_edits = True\n            self._handlers = handlers\n\n        def is_buffer_changed_outside(self):\n            \"\"\"\n            Returns true, if buffer was changed without using proxy object, like\n            with vim.command() or through internal vim.current.window.buffer.\n            \"\"\"\n            return self._change_tick < int(vim.eval(\"b:changedtick\"))\n\n        def validate_buffer(self):\n            \"\"\"\n            Raises exception if buffer is changed beyond proxy object.\n            \"\"\"\n            if self.is_buffer_changed_outside():\n                raise os.error(\n                    \"buffer was modified using vim.command or \"\n                    + \"vim.current.buffer; that changes are untrackable and leads to \"\n                    + \"errors in snippet expansion; use special variable `snip.buffer` \"\n                    \"for buffer modifications.\\n\\n\"\n                    + \"See :help UltiSnips-buffer-proxy for more info.\"\n                )\n\n        def __setitem__(self, key, value):\n            \"\"\"\n            Behaves as vim.current.window.buffer.__setitem__ except it tracks\n            changes and applies them to the current snippet stack.\n            \"\"\"\n            if isinstance(key, slice):\n                value = [line for line in value]\n                changes = list(self._get_diff(key.start, key.stop, value))\n                self._buffer[key.start : key.stop] = [line.strip(\"\\n\") for line in value]\n            else:\n                value = value\n                changes = list(self._get_line_diff(key, self._buffer[key], value))\n                self._buffer[key] = value\n\n            self._change_tick += 1\n\n            if self._forward_edits:\n                for change in changes:\n                    self._apply_change(change)\n\n        def __setslice__(self, i, j, text):\n            \"\"\"\n            Same as __setitem__.\n            \"\"\"\n            self.__setitem__(slice(i, j), text)\n\n        def __getitem__(self, key):\n            \"\"\"\n            Just passing call to the vim.current.window.buffer.__getitem__.\n            \"\"\"\n            return self._buffer[key]\n\n        def __getslice__(self, i, j):\n            \"\"\"\n            Same as __getitem__.\n            \"\"\"\n            return self.__getitem__(slice(i, j))\n\n        def __len__(self):\n            \"\"\"\n            Same as len(vim.current.window.buffer).\n            \"\"\"\n            return len(self._buffer)\n\n        def append(self, line, line_number=-1):\n            \"\"\"\n            Same as vim.current.window.buffer.append(), but with tracking changes.\n            \"\"\"\n            if line_number < 0:\n                line_number = len(self)\n            if not isinstance(line, list):\n                line = [line]\n            self[line_number:line_number] = [l for l in line]\n\n        def insert(self, index, line):\n            self[index:index] = [line]\n\n        def __delitem__(self, key):\n            if isinstance(key, slice):\n                self.__setitem__(key, [])\n            else:\n                self.__setitem__(slice(key, key + 1), [])\n\n        def _get_diff(self, start, end, new_value):\n            \"\"\"\n            Very fast diffing algorithm when changes are across many lines.\n            \"\"\"\n            for line_number in range(start, end):\n                if line_number < 0:\n                    line_number = len(self._buffer) + line_number\n                yield (\"D\", line_number, 0, self._buffer[line_number], True)\n\n            if start < 0:\n                start = len(self._buffer) + start\n            for line_number in range(0, len(new_value)):\n                yield (\"I\", start + line_number, 0, new_value[line_number], True)\n\n        def _get_line_diff(self, line_number, before, after):\n            \"\"\"\n            Use precise diffing for tracking changes in single line.\n            \"\"\"\n            if before == \"\":\n                for change in self._get_diff(line_number, line_number + 1, [after]):\n                    yield change\n            else:\n                for change in diff(before, after):\n                    yield (change[0], line_number, change[2], change[3])\n\n        def _apply_change(self, change):\n            \"\"\"\n            Apply changeset to current snippets stack, correctly moving around\n            snippet itself or its child.\n            \"\"\"\n            # ('I', 4, 0, 'xy')\n            # change_type, line_number, column_number, change_text = change[0:4]\n            if len(self._handlers) > 0:\n              for handler in self._handlers:\n                handler._apply_change(change)\n            else:\n              print(str(change))\n              pass\n\n        def _disable_edits(self):\n            \"\"\"\n            Temporary disable applying changes to snippets stack. Should be done\n            while expanding anonymous snippet in the middle of jump to prevent\n            double tracking.\n            \"\"\"\n            self._forward_edits = False\n\n        def _enable_edits(self):\n            \"\"\"\n            Enables changes forwarding back.\n            \"\"\"\n            self._forward_edits = True\n\n    class PositionWrapper(object):\n\n        def __init__(self, position):\n            self._position = position\n            self._valid = True\n\n        def _apply_change(self, change):\n            # ('I', 4, 0, 'xy', True)\n            pos = self._position\n            start = Position(change[1], change[2])\n            col = change[2]\n            insert = change[0] == 'I'\n            newline = len(change) > 4\n            if newline:\n                if not insert and change[1] == pos.line:\n                    self._valid = False\n                    return\n            else:\n                if change[1] == pos.line:\n                  if not insert and change[2] + len(change[3]) > pos.col:\n                      self._valid = False\n                      return\n                  col = len(change[3]) if insert else 0 - len(change[3])\n            lc = 0\n            if newline:\n                lc = 1 if insert else -1\n            pos.move(start, _Position(lc, col))\n\n        @property\n        def valid(self):\n            return self._valid\n\n        @property\n        def position(self):\n            return self._position\n\n    class SnippetUtilCursor(object):\n        def __init__(self, cursor):\n            self._cursor = [cursor[0] - 1, cursor[1]]\n            self._set = False\n\n        def preserve(self):\n            self._set = True\n            cursor = vim.current.window.cursor\n            self._cursor = [cursor[0] - 1, cursor[1]]\n\n        def is_set(self):\n            return self._set\n\n        def set(self, line, column):\n            self.__setitem__(0, line)\n            self.__setitem__(1, column)\n            # vim.current.window.cursor = self.to_vim_cursor()\n\n        def to_vim_cursor(self):\n            return (self._cursor[0] + 1, self._cursor[1])\n\n        def __getitem__(self, index):\n            return self._cursor[index]\n\n        def __setitem__(self, index, value):\n            self._set = True\n            self._cursor[index] = value\n\n        def __len__(self):\n            return 2\n\n        def __str__(self):\n            return str((self._cursor[0], self._cursor[1]))\n\n    class IndentUtil(object):\n\n        \"\"\"Utility class for dealing properly with indentation.\"\"\"\n\n        def __init__(self):\n            self.reset()\n\n        def reset(self):\n            \"\"\"Gets the spacing properties from Vim.\"\"\"\n            self.shiftwidth = int(\n                vim.eval(\"exists('*shiftwidth') ? shiftwidth() : &shiftwidth\")\n            )\n            self._expandtab = vim.eval(\"&expandtab\") == \"1\"\n            self._tabstop = int(vim.eval(\"&tabstop\"))\n\n        def ntabs_to_proper_indent(self, ntabs):\n            \"\"\"Convert 'ntabs' number of tabs to the proper indent prefix.\"\"\"\n            line_ind = ntabs * self.shiftwidth * \" \"\n            line_ind = self.indent_to_spaces(line_ind)\n            line_ind = self.spaces_to_indent(line_ind)\n            return line_ind\n\n        def indent_to_spaces(self, indent):\n            \"\"\"Converts indentation to spaces respecting Vim settings.\"\"\"\n            indent = indent.expandtabs(self._tabstop)\n            right = (len(indent) - len(indent.rstrip(\" \"))) * \" \"\n            indent = indent.replace(\" \", \"\")\n            indent = indent.replace(\"\\t\", \" \" * self._tabstop)\n            return indent + right\n\n        def spaces_to_indent(self, indent):\n            \"\"\"Converts spaces to proper indentation respecting Vim settings.\"\"\"\n            if not self._expandtab:\n                indent = indent.replace(\" \" * self._tabstop, \"\\t\")\n            return indent\n\n    class BaseContext(object):\n        def __init__(self):\n            super().__init__()\n            self._cursor = SnippetUtilCursor(vim.current.window.cursor)\n            line = self._cursor[0]\n            pos = Position(line, byte2col(line + 1, self._cursor[1]))\n            wrapper = PositionWrapper(pos)\n            self._handlers = [wrapper]\n            self._buffer = VimBufferProxy(self._handlers)\n\n        @property\n        def window(self):\n            return vim.current.window\n\n        @property\n        def buffer(self):\n            return self._buffer\n\n        @property\n        def cursor(self):\n            return self._cursor\n\n        @property\n        def line(self):\n            return vim.current.window.cursor[0] - 1\n\n        @property\n        def column(self):\n            return vim.current.window.cursor[1]\n\n        @property\n        def visual_mode(self):\n            return vim.eval(\"visualmode()\")\n\n        @property\n        def visual_text(self):\n            if \"coc_selected_text\" in vim.vars:\n                return vim.vars[\"coc_selected_text\"]\n            return ''\n\n        @property\n        def last_placeholder(self):\n            if \"coc_last_placeholder\" in vim.vars:\n                p = vim.vars[\"coc_last_placeholder\"]\n                start = _Position(p[\"start\"][\"line\"], p[\"start\"][\"col\"])\n                end = _Position(p[\"end\"][\"line\"], p[\"end\"][\"col\"])\n                return _Placeholder(p[\"current_text\"], start, end)\n            return None\n\n        def expand_anon(self, value, trigger=\"\", description=\"\", options=\"\", context=None, actions=None):\n            expand_anon(value, trigger, self._cursor)\n            return True\n\n    class SnippetUtil(object):\n\n        def __init__(self, _initial_indent, start, end, context):\n            self._ind = IndentUtil()\n            self._visual = _VisualContent(\n                vim.eval(\"visualmode()\"), vim.eval('get(g:,\"coc_selected_text\",\"\")')\n            )\n            self._initial_indent = _initial_indent\n            self._reset(\"\")\n            self._start = Position(start[0], start[1])\n            self._end = Position(end[0], end[1])\n            self._context = context\n\n        def _reset(self, cur):\n            \"\"\"Gets the snippet ready for another update.\n\n            :cur: the new value for c.\n\n            \"\"\"\n            self._ind.reset()\n            self._cur = cur\n            self._rv = \"\"\n            self._changed = False\n            self.reset_indent()\n\n        def shift(self, amount=1):\n            \"\"\"Shifts the indentation level. Note that this uses the shiftwidth\n            because thats what code formatters use.\n\n            :amount: the amount by which to shift.\n\n            \"\"\"\n            self.indent += \" \" * self._ind.shiftwidth * amount\n\n        def unshift(self, amount=1):\n            \"\"\"Unshift the indentation level. Note that this uses the shiftwidth\n            because thats what code formatters use.\n\n            :amount: the amount by which to unshift.\n\n            \"\"\"\n            by = -self._ind.shiftwidth * amount\n            try:\n                self.indent = self.indent[:by]\n            except IndexError:\n                self.indent = \"\"\n\n        def mkline(self, line=\"\", indent=None):\n            \"\"\"Creates a properly set up line.\n\n            :line: the text to add\n            :indent: the indentation to have at the beginning\n                    if None, it uses the default amount\n\n            \"\"\"\n            if indent is None:\n                indent = self.indent\n                # this deals with the fact that the first line is\n                # already properly indented\n                if \"\\n\" not in self._rv:\n                    try:\n                        indent = indent[len(self._initial_indent) :]\n                    except IndexError:\n                        indent = \"\"\n                indent = self._ind.spaces_to_indent(indent)\n\n            return indent + line\n\n        def reset_indent(self):\n            \"\"\"Clears the indentation.\"\"\"\n            self.indent = self._initial_indent\n\n        def expand_anon(self, value, trigger=\"\", description=\"\", options=\"\", context=None, actions=None):\n            expand_anon(value, trigger)\n            return True\n\n\n        # Utility methods\n        @property\n        def fn(self):  # pylint:disable=no-self-use,invalid-name\n            \"\"\"The filename.\"\"\"\n            return vim.eval('expand(\"%:t\")') or \"\"\n\n        @property\n        def basename(self):  # pylint:disable=no-self-use\n            \"\"\"The filename without extension.\"\"\"\n            return vim.eval('expand(\"%:t:r\")') or \"\"\n\n        @property\n        def ft(self):  # pylint:disable=invalid-name\n            \"\"\"The filetype.\"\"\"\n            return self.opt(\"&filetype\", \"\")\n\n        @property\n        def rv(self):  # pylint:disable=invalid-name\n            \"\"\"The return value.\n\n            The text to insert at the location of the placeholder.\n\n            \"\"\"\n            return self._rv\n\n        @rv.setter\n        def rv(self, value):  # pylint:disable=invalid-name\n            \"\"\"See getter.\"\"\"\n            self._changed = True\n            self._rv = value\n\n        @property\n        def _rv_changed(self):\n            \"\"\"True if rv has changed.\"\"\"\n            return self._changed\n\n        @property\n        def c(self):  # pylint:disable=invalid-name\n            \"\"\"The current text of the placeholder.\"\"\"\n            return self._cur\n\n        @property\n        def v(self):  # pylint:disable=invalid-name\n            \"\"\"Content of visual expansions.\"\"\"\n            return self._visual\n\n        @property\n        def p(self):\n            if \"coc_last_placeholder\" in vim.vars:\n                p = vim.vars[\"coc_last_placeholder\"]\n                start = _Position(p[\"start\"][\"line\"], p[\"start\"][\"col\"])\n                end = _Position(p[\"end\"][\"line\"], p[\"end\"][\"col\"])\n                return _Placeholder(p[\"current_text\"], start, end)\n            return None\n\n        @property\n        def context(self):\n            return self._context\n\n        def opt(self, option, default=None):  # pylint:disable=no-self-use\n            \"\"\"Gets a Vim variable.\"\"\"\n            if vim.eval(\"exists('%s')\" % option) == \"1\":\n                try:\n                    return vim.eval(option)\n                except vim.error:\n                    pass\n            return default\n\n        def __add__(self, value):\n            \"\"\"Appends the given line to rv using mkline.\"\"\"\n            self.rv += \"\\n\"  # pylint:disable=invalid-name\n            self.rv += self.mkline(value)\n            return self\n\n        def __lshift__(self, other):\n            \"\"\"Same as unshift.\"\"\"\n            self.unshift(other)\n\n        def __rshift__(self, other):\n            \"\"\"Same as shift.\"\"\"\n            self.shift(other)\n\n        @property\n        def snippet_start(self):\n            \"\"\"\n            Returns start of the snippet in format (line, column).\n            \"\"\"\n            return self._start\n\n        @property\n        def snippet_end(self):\n            \"\"\"\n            Returns end of the snippet in format (line, column).\n            \"\"\"\n            return self._end\n\n        @property\n        def buffer(self):\n            return vim.current.buffer\n\n    class ContextSnippet(BaseContext):\n        def __init__(self):\n            super().__init__()\n            self._before = vim.eval('strpart(getline(\".\"), 0, col(\".\") - 1)')\n            self._after = vim.eval('strpart(getline(\".\"), col(\".\") - 1)')\n\n        @property\n        def before(self):\n            return self._before\n\n        @property\n        def after(self):\n            return self._after\n\n    class PreExpandContext(BaseContext):\n        @property\n        def visual_content(self):  # pylint:disable=no-self-use\n            if \"coc_selected_text\" in vim.vars:\n                return vim.vars[\"coc_selected_text\"]\n            return ''\n\n        def getResult(self):\n            wrapper = self._handlers[0]\n            valid = wrapper.valid and not self._cursor.is_set()\n            if (self._cursor.is_set()):\n                vimcursor = self._cursor.to_vim_cursor()\n            else:\n                if wrapper.valid:\n                    position = wrapper.position\n                    line = position.line + 1\n                    vimcursor = (line, col2byte(line, position.col))\n                else:\n                    vimcursor = vim.current.window.cursor\n            # vim.current.window.cursor = vimcursor\n            # 0 based, line - character\n            cursor = [vimcursor[0] - 1, byte2col(vimcursor[0], vimcursor[1])]\n            return [valid, cursor]\n\n    class PostExpandContext(BaseContext):\n        def __init__(self, positions):\n            super().__init__()\n            self._start = PositionWrapper(Position(positions[0], positions[1]))\n            self._end = PositionWrapper(Position(positions[2], positions[3]))\n            self._handlers.extend([self._start, self._end])\n\n        @property\n        def snippet_start(self):\n            return self._start.position\n\n        @property\n        def snippet_end(self):\n            return self._end.position\n\n    class PostJumpContext(PostExpandContext):\n        def __init__(self, positions, tabstop, forward):\n            super().__init__(positions)\n            self.tabstop = tabstop\n            self.jump_direction = 1 if forward else -1\n\n        @property\n        def tabstops(self):\n            vimtabstops = vim.vars[\"coc_ultisnips_tabstops\"]\n            if vimtabstops is None:\n                return {}\n            tabstops = {}\n            for stop in vimtabstops:\n                index = stop['index']\n                indexes = stop['range']\n                start = _Position(indexes[0], indexes[1])\n                end =  _Position(indexes[2], indexes[3])\n                tabstops[index] = _Placeholder(stop['text'], start, end)\n            return tabstops\n\n    namespace = {\n        'SnippetUtil': SnippetUtil,\n        'ContextSnippet': ContextSnippet,\n        'PreExpandContext': PreExpandContext,\n        'PostExpandContext': PostExpandContext,\n        'PostJumpContext': PostJumpContext,\n    }\n    return namespace\n\ncoc_ultisnips_dict = coc_UltiSnips_create()\nSnippetUtil = coc_ultisnips_dict['SnippetUtil']\nContextSnippet = coc_ultisnips_dict['ContextSnippet']\n\n# vim:set et sw=4 ts=4:\n"
  },
  {
    "path": "src/__tests__/vim.test.ts",
    "content": "process.env.VIM_NODE_RPC = '1'\nimport type { Buffer, Neovim, Tabpage, Window } from '@chemzqm/neovim'\nimport fs from 'fs'\nimport os from 'os'\nimport path from 'path'\nimport util from 'util'\nimport { v4 as uuid } from 'uuid'\nimport { Position, Range, TextEdit, type Disposable } from 'vscode-languageserver-protocol'\nimport type { CompleteResult, ExtendedCompleteItem } from '../completion/types'\nimport events from '../events'\nimport type { VirtualTextItem } from '../handler/inlayHint/buffer'\nimport { sameFile } from '../util/fs'\nimport { type Helper } from './helper'\n// make sure VIM_NODE_RPC take effect first\nconst helper = require('./helper').default as Helper\n\nfunction disposeAll(disposables: Disposable[]): void {\n  while (disposables.length) {\n    const item = disposables.pop()\n    item?.dispose()\n  }\n}\n\nconst disposables: Disposable[] = []\nlet nvim: Neovim\nlet featuredPropList = false\nbeforeAll(async () => {\n  await helper.setupVim()\n  nvim = helper.workspace.nvim\n  // for text_padding_left of property\n  if (helper.workspace.has('patch-9.0.1782')) {\n    featuredPropList = true\n  }\n})\n\nafterEach(() => {\n  disposeAll(disposables)\n})\n\nafterAll(async () => {\n  await helper.shutdown()\n})\n\nasync function createTmpFile(content: string, disposables?: Disposable[]): Promise<string> {\n  let tmpFolder = path.join(os.tmpdir(), `coc-${process.pid}`)\n  if (!fs.existsSync(tmpFolder)) {\n    fs.mkdirSync(tmpFolder)\n  }\n  let fsPath = path.join(tmpFolder, uuid())\n  await util.promisify(fs.writeFile)(fsPath, content, 'utf8')\n  if (disposables) {\n    disposables.push({\n      dispose: () => {\n        if (fs.existsSync(fsPath)) fs.unlinkSync(fsPath)\n      }\n    })\n  }\n  return fsPath\n}\n\ndescribe('workspace', () => {\n  it('should not has nvim feature', () => {\n    expect(helper.workspace.has('nvim-0.4.0')).toBe(false)\n    expect(helper.workspace.has('patch-9.0.0000')).toBe(true)\n  })\n})\n\ndescribe('vim api', () => {\n  it('should start server', async () => {\n    await nvim.setLine('foobar')\n    let buf = await nvim.buffer\n    let lines = await buf.lines\n    expect(lines).toEqual(['foobar'])\n    await nvim.command('bd!')\n  })\n\n  it('should show info', async () => {\n    global.REVISION = '2e82259f'\n    let handler = helper.plugin.getHandler().workspace\n    await handler.showInfo()\n    await nvim.command('bd!')\n  })\n\n  it('should navigate complete items', async () => {\n    helper.updateConfiguration('suggest.noselect', true)\n    const sources = require('../completion/sources').default\n    let name = Math.random().toString(16).slice(-6)\n    let disposable = sources.createSource({\n      name,\n      doComplete: (_opt): Promise<CompleteResult<ExtendedCompleteItem>> => new Promise(resolve => {\n        resolve({\n          items: [{ word: 'foo\\nbar' }, { word: 'word' }]\n        })\n      })\n    })\n    await nvim.input('i')\n    nvim.call('coc#start', { source: name }, true)\n    await helper.waitPopup()\n    await nvim.call('coc#pum#_navigate', [1, 1])\n    await helper.waitFor('getline', ['.'], 'foo')\n    expect(helper.completion.isActivated).toBe(true)\n    await nvim.call('coc#pum#close', ['cancel'])\n    await nvim.input('<esc>')\n    await helper.waitFor('mode', [], 'n')\n    disposable.dispose()\n    await nvim.command('silent! %bwipeout!')\n  })\n\n  it('should echo message by callTimer', async () => {\n    const ui = require('../core/ui')\n    ui.echoMessages(nvim, 'message', 'more', 'more')\n    await helper.waitValue(async () => {\n      let line = await helper.getCmdline()\n      return line.includes('message')\n    }, true)\n  })\n\n  it('should call async', async () => {\n    const funcs = require('../core/funcs')\n    await nvim.command('normal! gg')\n    let res = await funcs.callAsync(nvim, 'line', ['.'])\n    expect(res).toBe(1)\n  })\n})\n\ndescribe('call_function', () => {\n  beforeAll(async () => {\n    let folder = path.resolve(__dirname)\n    await nvim.command(`set runtimepath+=${folder}`)\n  })\n\n  it('should throw when call vim9 void function', async () => {\n    await expect(async () => {\n      await nvim.call('vim9#Execute', ['g:x = $\"foo\"'])\n    }).rejects.toThrow(Error)\n    // should not report error\n    nvim.call('vim9#Execute', ['g:x = $\"abc\"'], true)\n    let x = await nvim.getVar('x')\n    expect(x).toBe('abc')\n  })\n\n  it('should call dict function', async () => {\n    let res = await nvim.callDictFunction({ key: 1 }, 'legacy#dict_add')\n    expect(res).toBe(2)\n  })\n\n  it('should use notify for execute', async () => {\n    nvim.call('execute', 'let g:x = \"a\".\"b\"', true)\n    let res = await nvim.getVar('x')\n    expect(res).toBe('ab')\n  })\n\n  it('should not throw for win_execute', async () => {\n    // old style syntax\n    await nvim.call('execute', ['let g:y = \"a\".\"b\"'])\n    let y = await nvim.getVar('y')\n    expect(y).toBe('ab')\n    // new style syntax in vim9 function\n    let res = await nvim.call('vim9#WinExecute', [])\n    expect(res).toBe(true)\n    // old style syntax win_execute in legacy function\n    await nvim.call('legacy#win_execute', [])\n    let win = await nvim.window\n    let val = await win.getVar('foo')\n    expect(val).toBe('ab')\n  })\n\n  it('should eval with legacy syntax', async () => {\n    let res = await nvim.call('eval', ['\"a\".\"b\"'])\n    expect(res).toBe('ab')\n  })\n\n  it('should not conflict with global function', async () => {\n    await nvim.exec([\n      'function! Win_execute(...) abort',\n      ' throw \"my error\"',\n      'endfunction'\n    ].join('\\n'))\n    let winid = await nvim.call('win_getid') as number\n    await nvim.call('win_execute', [winid, 'let w:f = \"b\"'])\n    let win = nvim.createWindow(winid)\n    let val = await win.getVar('f')\n    expect(val).toBe('b')\n  })\n})\n\ndescribe('client API', () => {\n  it('should set current dir', async () => {\n    await nvim.setDirectory(__dirname)\n    let res = await nvim.call('getcwd') as string\n    expect(sameFile(res, __dirname)).toBe(true)\n  })\n\n  it('should input characters', async () => {\n    await nvim.input('iabc')\n    await helper.waitFor('getline', ['.'], 'abc')\n    await nvim.input('<esc>')\n    await helper.waitFor('mode', [], 'n')\n    await nvim.command('bwipeout!')\n  })\n\n  it('should set var', async () => {\n    await nvim.setVar('foo', 'bar', false)\n    let res = await nvim.getVar('foo')\n    expect(res).toBe('bar')\n  })\n\n  it('should del var', async () => {\n    await expect(async () => {\n      nvim.pauseNotification()\n      nvim.deleteVar('not_exists')\n      await nvim.resumeNotification()\n    }).rejects.toThrow(Error)\n    await nvim.setVar('foo', 'bar', false)\n    nvim.deleteVar('foo')\n    let res = await nvim.getVar('foo')\n    expect(res).toBeNull()\n  })\n\n  it('should set option', async () => {\n    await nvim.setOption('emoji', false)\n    let res = await nvim.getOption('emoji')\n    expect(res).toBe(false)\n  })\n\n  it('should set current buffer', async () => {\n    let bufnr = await nvim.call('bufadd', ['foo']) as number\n    await nvim.call('bufload', [bufnr])\n    await nvim.setBuffer(nvim.createBuffer(bufnr))\n    let b = await nvim.buffer\n    expect(b.id).toBe(bufnr)\n    await nvim.command('silent! %bwipeout!')\n  })\n\n  it('should execute vim script', async () => {\n    let output = await nvim.exec(`echo 'foo'\\necho 'bar'`, true)\n    expect(output).toBe('foo\\nbar')\n    output = await nvim.exec(`let g:x = '5'\\nunlet g:x`)\n    expect(output).toBe('')\n  })\n\n  it('should create new buffer', async () => {\n    let buf = await nvim.createNewBuffer()\n    let valid = await buf.valid\n    expect(valid).toBe(true)\n    let listed = await buf.getOption('buflisted')\n    expect(listed).toBe(false)\n    buf = await nvim.createNewBuffer(true, true)\n    valid = await buf.valid\n    expect(valid).toBe(true)\n    listed = await buf.getOption('buflisted')\n    expect(listed).toBe(true)\n    let buftype = await buf.getOption('buftype')\n    expect(buftype).toBe('nofile')\n  })\n\n  it('should set current window', async () => {\n    let winid = await nvim.call('win_getid') as number\n    await nvim.command('sp | sp | sp')\n    let win = nvim.createWindow(winid)\n    await nvim.setWindow(win)\n    let curr = await nvim.call('win_getid') as number\n    expect(curr).toBe(winid)\n    await nvim.command('only!')\n  })\n\n  it('should set current tabpage', async () => {\n    let tab = await nvim.tabpage\n    await nvim.command('tabe')\n    await nvim.setTabpage(tab)\n    let nr = await nvim.call('tabpagenr')\n    expect(nr).toBe(tab.id)\n    let tabpages = await nvim.tabpages\n    expect(tabpages.length).toBe(2)\n    await nvim.command('tabonly!')\n  })\n\n  it('should list windows', async () => {\n    let wins = await nvim.windows\n    expect(Array.isArray(wins)).toBe(true)\n  })\n\n  it('should call atomic', async () => {\n    await expect(async () => {\n      nvim.pauseNotification()\n      nvim.call('abc', [], true)\n      await nvim.resumeNotification()\n    }).rejects.toThrow(Error)\n    let res = await nvim.getVvar('errmsg')\n    expect(res).toBe('')\n  })\n\n  it('should execute command', async () => {\n    await nvim.command('sp')\n    let wins = await nvim.windows\n    expect(wins.length).toBe(2)\n    await nvim.command('only')\n    wins = await nvim.windows\n    expect(wins.length).toBe(1)\n  })\n\n  it('should allow legacy script on command', async () => {\n    await nvim.command('let g:x = v:argv[0].\" bar\"')\n    let res = await nvim.getVar('x')\n    expect(res).toMatch('bar')\n  })\n\n  it('should not throw for silent error command', async () => {\n    await expect(async () => {\n      await nvim.command('abcdefg')\n    }).rejects.toThrow(/E492/)\n    await nvim.command('silent! abcdefg')\n  })\n\n  it('should use legacy eval', async () => {\n    let res = await nvim.eval('\"a\".\"b\"')\n    expect(res).toBe('ab')\n  })\n\n  it('should get api info', async () => {\n    let info = await nvim.apiInfo\n    expect(typeof info[0]).toBe('number')\n  })\n\n  it('should get buffer list', async () => {\n    let bufs = await nvim.buffers\n    expect(typeof bufs[0].id).toBe('number')\n  })\n\n  it('should feedkeys', async () => {\n    await nvim.setLine('foo')\n    await nvim.feedKeys('$', 'int', false)\n    let col = await nvim.call('col', ['.'])\n    expect(col).toBe(3)\n    await nvim.command('bd!')\n  })\n\n  it('should list runtimepath', async () => {\n    let res = await nvim.runtimePaths\n    expect(Array.isArray(res)).toBe(true)\n  })\n\n  it('should get command output', async () => {\n    let res = await nvim.commandOutput('echo \"foo\".\"bar\"')\n    expect(res).toMatch(/foobar/)\n    await expect(async () => {\n      await nvim.commandOutput('echonot_exists')\n    }).rejects.toThrow(/E492/)\n  })\n\n  it('should get line & set line', async () => {\n    await nvim.setLine('foo')\n    let curr = await nvim.getLine()\n    expect(curr).toBe('foo')\n    await nvim.deleteCurrentLine()\n    curr = await nvim.getLine()\n    expect(curr).toBe('')\n  })\n\n  it('should get var', async () => {\n    await nvim.setVar('foo', 'bar')\n    let res = await nvim.getVar('foo')\n    expect(res).toBe('bar')\n    nvim.deleteVar('foo')\n    res = await nvim.getVar('foo')\n    expect(res).toBeNull()\n  })\n\n  it('should get vvar', async () => {\n    let res = await nvim.getVvar('progpath')\n    expect(res).toMatch('vim')\n  })\n\n  it('should get current buffer, window, tabpage', async () => {\n    expect(await nvim.buffer).toBeDefined()\n    expect(await nvim.window).toBeDefined()\n    expect(await nvim.tabpage).toBeDefined()\n  })\n\n  it('should get strwidth', async () => {\n    let w = await nvim.strWidth('foo')\n    expect(w).toBe(3)\n  })\n\n  it('should out_write', async () => {\n    nvim.outWrite('foo')\n    nvim.outWriteLine('bar')\n    let env = helper.workspace.env\n    let line = await helper.getCmdline(env.lines - 1)\n    expect(line).toBe('foobar')\n  })\n\n  it('should err_write', async () => {\n    nvim.errWrite('foo')\n    nvim.errWriteLine('bar')\n    let env = helper.workspace.env\n    let line = await helper.getCmdline(env.lines - 1)\n    expect(line).toBe('foobar')\n  })\n\n  it('should create namespace', async () => {\n    let ns = await nvim.createNamespace('foo')\n    expect(typeof ns).toBe('number')\n    let namespace = await nvim.createNamespace('foo')\n    expect(ns).toBe(namespace)\n  })\n\n  it('should add and delete keymap', async () => {\n    nvim.setKeymap('n', ' ', ':normal! G', { nowait: true, script: true })\n    let res = await nvim.exec('nmap <space>', true)\n    expect(res).toMatch('normal!')\n    nvim.deleteKeymap('n', ' ')\n    res = await nvim.exec('nmap <sapce>', true)\n    expect(res).toMatch('No mapping found')\n  })\n})\n\ndescribe('Buffer API', () => {\n  let buffer: Buffer\n  beforeEach(async () => {\n    buffer = await nvim.buffer\n  })\n\n  afterEach(async () => {\n    await nvim.command('bd!')\n  })\n\n  it('should checkLines on CursorHold', async () => {\n    let doc = await helper.createDocument()\n    let buffer = doc.buffer\n    await buffer.setLines(['1', '2'], {})\n    await events.fire('CursorHold', [buffer.id, [1, 1]])\n    let called = false\n    events.on('LinesChanged', bufnr => {\n      if (bufnr == buffer.id) {\n        called = true\n      }\n    }, null, disposables)\n    Object.assign(doc, { lines: [''], _changedtick: doc.changedtick + 1 })\n    await events.fire('CursorHold', [buffer.id, [1, 1]])\n    expect(called).toBe(true)\n    expect(doc.getLines()).toEqual(['1', '2'])\n  })\n\n  it('should set buffer option', async () => {\n    await buffer.setOption('buflisted', false)\n    let curr = await buffer.getOption('buflisted')\n    expect(curr).toBe(false)\n    await buffer.setOption('buflisted', true)\n    curr = await buffer.getOption('buflisted')\n    expect(curr).toBe(true)\n  })\n\n  it('should get changedtick', async () => {\n    let changedtick = await buffer.changedtick\n    let curr = await nvim.eval('b:changedtick')\n    expect(changedtick).toBe(curr)\n  })\n\n  it('should add and delete buffer keymap', async () => {\n    buffer.setKeymap('n', 'e', ':normal! G', { noremap: true, nowait: true, silent: true })\n    let res = await nvim.exec('nmap e', true)\n    expect(res).toMatch('normal!')\n    buffer.deleteKeymap('n', 'e')\n    res = await nvim.exec('nmap e', true)\n    expect(res).toMatch('No mapping found')\n  })\n\n  it('should check buffer valid', async () => {\n    let valid = await buffer.valid\n    expect(valid).toBe(true)\n    let buf = nvim.createBuffer(99)\n    valid = await buf.valid\n    expect(valid).toBe(false)\n  })\n\n  it('should get mark', async () => {\n    await buffer.append(['', '', ''])\n    let c = await buffer.length\n    expect(c).toBe(4)\n    await nvim.command(`normal! Gm\"`)\n    let m = await buffer.mark('\"')\n    expect(m).toEqual([4, 0])\n    await nvim.command('bd!')\n  })\n\n  it('should add highlight', async () => {\n    let ns = await nvim.createNamespace('test') as number\n    await nvim.setLine('foo')\n    let buf = await nvim.buffer\n    await buf.addHighlight({\n      hlGroup: 'MoreMsg',\n      line: 0,\n      colStart: 0,\n      colEnd: 3,\n      srcId: ns\n    })\n    let curr = await buf.getHighlights('test')\n    expect(curr).toEqual([{ hlGroup: 'MoreMsg', lnum: 0, colStart: 0, colEnd: 3, id: 1001 }])\n    buf.clearNamespace(ns)\n    curr = await buf.getHighlights('test')\n    expect(curr).toEqual([])\n  })\n\n  it('should get line count', async () => {\n    await buffer.append(['', '', '', ''])\n    await nvim.command('tabe')\n    let n = await buffer.length\n    expect(n).toBe(5)\n    await nvim.command('silent! %bwipeout!')\n    await expect(async () => {\n      let buf = nvim.createBuffer(-1)\n      await buf.length\n    }).rejects.toThrow(/Invalid buffer/)\n  })\n\n  it('should get lines', async () => {\n    await buffer.setLines(['1', '2', '3', '4'], { start: 0, end: -1, strictIndexing: false })\n    let lines = await buffer.lines\n    expect(lines).toEqual(['1', '2', '3', '4'])\n    lines = await buffer.getLines({ start: 0, end: 1, strictIndexing: false })\n    expect(lines).toEqual(['1'])\n    lines = await buffer.getLines({ start: -2, end: -1, strictIndexing: false })\n    expect(lines).toEqual(['4'])\n    await nvim.command('bd!')\n  })\n\n  it('should set lines', async () => {\n    // insert\n    await buffer.setLines(['1', '2', '3'], { start: 0, end: 0, strictIndexing: true })\n    let lines = await buffer.lines\n    expect(lines).toEqual(['1', '2', '3', ''])\n    // replace\n    await buffer.setLines(['4'], { start: 2, end: -1, strictIndexing: true })\n    lines = await buffer.lines\n    expect(lines).toEqual(['1', '2', '4'])\n    // delete\n    await buffer.setLines([], { start: 1, end: 2, strictIndexing: true })\n    lines = await buffer.lines\n    expect(lines).toEqual(['1', '4'])\n    await buffer.setLines(['2', '3'], { start: 1, end: 2, strictIndexing: true })\n    lines = await buffer.lines\n    expect(lines).toEqual(['1', '2', '3'])\n    await nvim.command('bd!')\n  })\n\n  it('should set name', async () => {\n    await buffer.setName('foo')\n    let name = await buffer.name\n    expect(name).toBe('foo')\n    await nvim.command('bd!')\n  })\n\n  it('should change buffer variable', async () => {\n    await buffer.setVar('foo', 'bar', false)\n    let curr = await buffer.getVar('foo')\n    expect(curr).toBe('bar')\n    buffer.deleteVar('foo')\n    curr = await buffer.getVar('foo')\n    expect(curr).toBeNull()\n\n    // another non-current buffer\n    const buf2 = await nvim.createNewBuffer()\n    await buf2.setVar('foo', 'qux', false)\n    let curr2 = await buf2.getVar('foo')\n    expect(curr2).toBe('qux')\n    buf2.deleteVar('foo')\n    curr = await buf2.getVar('foo')\n    expect(curr).toBeNull()\n  })\n\n  it('should add virtual text', async () => {\n    let buf = await nvim.buffer\n    await nvim.call('setline', ['.', '  foo'])\n    let ns = await nvim.createNamespace('virtual-text')\n    buf.setVirtualText(ns, 0, [['bar', 'MoreMsg']], { text_align: 'above', indent: true })\n    let types = await nvim.call('coc#api#GetNamespaceTypes', [ns])\n    let props = await nvim.call('prop_list', [1, { types }]) as any[]\n    expect(props.length).toBe(1)\n    let prop = props[0]\n    if (featuredPropList) {\n      expect(prop.text_align).toBe('above')\n      expect(prop.text_padding_left).toBe(2)\n      expect(prop.text).toBe('bar')\n    }\n  })\n\n  it('should set multiple virtual texts', async () => {\n    let buf = await nvim.buffer\n    let arr = (new Array(10)).fill('foo')\n    await buf.setLines(arr)\n    let ns = await nvim.createNamespace('vtext-set')\n    let len = await buf.length\n    let items: VirtualTextItem[] = []\n    for (let i = 0; i < len; i++) {\n      items.push({\n        blocks: [[`${i}`, 'MoreMsg']],\n        line: i,\n        col: 1,\n        right_gravity: true,\n        virt_text_win_col: 0,\n        hl_mode: 'blend'\n      })\n    }\n    await nvim.call('coc#vtext#set', [buf.id, ns, items, false, 900])\n    let types = await nvim.call('coc#api#GetNamespaceTypes', [ns])\n    let props = await nvim.call('prop_list', [1, { types, end_lnum: len }]) as any[]\n    expect(props.length).toBe(10)\n    let prop = props[0]\n    expect(prop.lnum).toBe(1)\n    expect(prop.col).toBe(1)\n    if (featuredPropList) {\n      expect(prop.text).toBe('0')\n    }\n  })\n\n  it('should update highlights', async () => {\n    let buf = await nvim.buffer\n    await buf.setLines(['foo', 'bar'])\n    let hls = []\n    hls.push({ lnum: 0, colStart: 0, colEnd: 3, hlGroup: 'MoreMsg' })\n    hls.push({ lnum: 1, colStart: 1, colEnd: 3, hlGroup: 'MoreMsg' })\n    buf.updateHighlights('test', hls, { priority: 80 })\n    let arr = await buf.getHighlights('test')\n    expect(arr.length).toBe(2)\n    let obj = {}\n    for (const key of ['hlGroup', 'lnum', 'colStart', 'colEnd']) {\n      obj[key] = arr[0][key]\n    }\n    expect(obj).toEqual(hls[0])\n    await nvim.call('coc#highlight#clear_all', [])\n    buf.updateHighlights('test', [hls[0]], { priority: 80, start: 0, end: 1 })\n    arr = await buf.getHighlights('test')\n    expect(arr.length).toBe(1)\n    let hl = { lnum: 1, colStart: 0, colEnd: -1, hlGroup: 'MoreMsg' }\n    buf.updateHighlights('test', [hl], { priority: 80 })\n    arr = await buf.getHighlights('test')\n    expect(arr.length).toBe(1)\n  })\n\n  it('should highlight ranges', async () => {\n    let buf = await nvim.buffer\n    await buf.setLines(['foo', 'bar'])\n    const range = Range.create(0, 0, 2, 0)\n    buf.highlightRanges('test', 'MoreMsg', [range])\n    let arr = await buf.getHighlights('test')\n    expect(arr.length).toBe(2)\n  })\n})\n\ndescribe('Window API', () => {\n  let win: Window\n  beforeEach(async () => {\n    win = await nvim.window\n  })\n\n  it('should get buffer of window', async () => {\n    let buf = await win.buffer\n    let curr = await nvim.buffer\n    expect(buf.id).toBe(curr.id)\n  })\n\n  it('should set buffer', async () => {\n    let bufnr = await nvim.call('bufadd', ['foo']) as number\n    await nvim.call('bufload', [bufnr])\n    await win.setBuffer(nvim.createBuffer(bufnr))\n    let buf = await win.buffer\n    expect(buf.id).toBe(bufnr)\n    await nvim.command('silent! %bwipeout!')\n  })\n\n  it('should get position', async () => {\n    await nvim.command('sp')\n    let res = await win.position\n    expect(res[0]).toBeGreaterThan(0)\n    expect(res[1]).toBe(0)\n    await nvim.command('only!')\n  })\n\n  it('should get and set height', async () => {\n    let h = await win.height\n    await win.setHeight(3)\n    let curr = await win.height\n    expect(curr).toBe(3)\n    await win.setHeight(h)\n  })\n\n  it('should get and set width', async () => {\n    await nvim.command('vs')\n    await win.setWidth(5)\n    let curr = await win.width\n    expect(curr).toBe(5)\n    await nvim.command('only!')\n  })\n\n  it('should get and set cursor', async () => {\n    let buf = await nvim.buffer\n    await buf.setLines(['1', '2', '3', '4'], { start: 0, end: -1, strictIndexing: false })\n    await win.setCursor([3, 1])\n    let cursor = await win.cursor\n    expect(cursor).toEqual([3, 0])\n    await nvim.command('bd!')\n  })\n\n  it('should get and set option', async () => {\n    let relative = await win.getOption('relativenumber')\n    expect(relative).toBe(false)\n    await win.setOption('relativenumber', true)\n    relative = await win.getOption('relativenumber')\n    expect(relative).toBe(true)\n    await win.setOption('relativenumber', false)\n    await expect(async () => {\n      await win.getOption('not_exists')\n    }).rejects.toThrow('Invalid')\n    await expect(async () => {\n      await win.setOption('not_exists', '')\n    }).rejects.toThrow('Invalid')\n  })\n\n  it('should get and set var', async () => {\n    await win.setVar('foo', 'bar')\n    let curr = await win.getVar('foo')\n    expect(curr).toBe('bar')\n    let res = await win.getVar('not_exists')\n    expect(res).toBeNull()\n    win.deleteVar('foo')\n    curr = await win.getVar('foo')\n    expect(curr).toBe(null)\n  })\n\n  it('should check window is valid', async () => {\n    let valid = await win.valid\n    expect(valid).toBe(true)\n    let tab = await win.tabpage\n    let nr = await tab.number\n    expect(nr).toBe(1)\n    let n = await win.number\n    expect(n).toBe(1)\n    await nvim.command('vs')\n    await nvim.call('win_gotoid', [win.id])\n    await win.close(true)\n    valid = await win.valid\n    expect(valid).toBe(false)\n    await nvim.command('only!')\n  })\n\n  it('should add and clear matches', async () => {\n    let buf = await nvim.buffer\n    let arr = new Array(10)\n    arr.fill('foo')\n    await buf.setLines(arr)\n    let ranges: Range[] = []\n    for (let i = 0; i < 10; i++) {\n      ranges.push(Range.create(i, 0, i, 3))\n    }\n    let win = await nvim.window\n    let ids = await win.highlightRanges('MoreMsg', ranges)\n    expect(ids.length).toBeGreaterThan(0)\n    let matches = await helper.getMatches('MoreMsg')\n    expect(matches.length).toBe(10)\n    win.clearMatches(ids)\n    matches = await helper.getMatches('MoreMsg')\n    expect(matches.length).toBe(0)\n  })\n})\n\ndescribe('Popup', () => {\n  it('should works for popup window', async () => {\n    let winid = await nvim.call('popup_create', [['foo', 'bar'], {}]) as number\n    expect(winid).toBeGreaterThan(1000)\n    let win = nvim.createWindow(winid)\n    let buf = await win.buffer\n    expect(buf.id).toBeGreaterThan(0)\n    let pos = await win.position\n    expect(typeof pos[0]).toBe('number')\n    expect(typeof pos[1]).toBe('number')\n    await win.setHeight(10)\n    let height = await win.height\n    expect(height).toBe(10)\n    await win.setWidth(20)\n    let width = await win.width\n    expect(width).toBe(20)\n    await win.setCursor([1, 2])\n    let cur = await win.cursor\n    expect(cur).toEqual([1, 2])\n    await win.setOption('relativenumber', true)\n    // different on neovim which returns true and false\n    let option = await win.getOption('relativenumber')\n    expect(option).toBe(true)\n    await win.setVar('foo', 'bar', false)\n    let val = await win.getVar('foo')\n    expect(val).toBe('bar')\n    win.deleteVar('foo')\n    val = await win.getVar('foo')\n    expect(val).toBeNull()\n    let valid = await win.valid\n    expect(valid).toBe(true)\n    // not work on vim\n    let num = await win.number\n    expect(num).toBe(0)\n    let tabpage = await win.tabpage\n    expect(tabpage.id).toBeGreaterThan(0)\n    await win.close(true)\n    await nvim.call('popup_clear', [])\n  })\n\n  it('should create inputBox', async () => {\n    let input = await helper.plugin.window.createInputBox('title', '')\n    input.title = 'new title'\n    let curr: string\n    input.onDidChange(text => {\n      curr = text\n    })\n    await nvim.input('abc')\n    await helper.waitValue((() => {\n      return curr\n    }), 'abc')\n    input.dispose()\n  })\n})\n\ndescribe('Tabpage API', () => {\n  let tab: Tabpage\n  beforeEach(async () => {\n    tab = await nvim.tabpage\n  })\n\n  it('should get window list', async () => {\n    await nvim.command('vs')\n    let wins = await tab.windows\n    expect(wins.length).toBe(2)\n    await nvim.command('only!')\n  })\n\n  it('should get and set var', async () => {\n    await tab.setVar('foo', 'bar')\n    let curr = await tab.getVar('foo')\n    expect(curr).toBe('bar')\n    tab.deleteVar('foo')\n    curr = await tab.getVar('foo')\n    expect(curr).toBe(null)\n  })\n\n  it('should get current window', async () => {\n    let valid = await tab.valid\n    expect(valid).toBe(true)\n    let win = await tab.window\n    let curr = await nvim.call('win_getid')\n    expect(win.id).toBe(curr)\n  })\n})\n\ndescribe('notify', () => {\n  it('should call function by notify', async () => {\n    let curr = await nvim.call('line', ['.'])\n    nvim.call('setline', [curr, 'foo'], true)\n    await helper.waitValue(async () => {\n      return await nvim.call('getline', [curr])\n    }, 'foo')\n    await nvim.command('normal! dd')\n  })\n})\n\ndescribe('document', () => {\n  async function shouldEqual(doc, synced = false): Promise<void> {\n    let lines = synced ? doc.textDocument.lines : doc.getLines()\n    let cur = await doc.buffer.lines\n    expect(lines).toEqual(cur)\n  }\n\n  it('should synchronize current buffer when call vim function', async () => {\n    let doc = await helper.createDocument()\n    await nvim.call('appendbufline', [doc.bufnr, 0, ['3', '4', '5']])\n    await nvim.call('setbufline', [doc.bufnr, 1, 'txt'])\n    await shouldEqual(doc)\n  })\n\n  it('should synchronize changes', async () => {\n    let lines = []\n    for (let i = 1; i < 8; i++) {\n      lines.push(`line ${i}`)\n    }\n    let filepath = await createTmpFile(lines.join('\\n'), disposables)\n    let doc = await helper.createDocument(filepath)\n    let bufnr = doc.buffer.id\n    // remove first line\n    nvim.pauseNotification()\n    nvim.call('deletebufline', [bufnr, 1, 3], true)\n    nvim.call('appendbufline', [bufnr, 0, ['3', '4', '5']], true)\n    await nvim.resumeNotification(true)\n    await shouldEqual(doc)\n    await doc.patchChange()\n  })\n\n  it('should patch change of current line', async () => {\n    let doc = await helper.createDocument()\n    nvim.call('setline', ['.', 'foo'], true)\n    await doc.patchChange()\n    await shouldEqual(doc, true)\n    nvim.call('setline', ['.', 'foo'], true)\n    await doc.patchChange()\n    await shouldEqual(doc, true)\n  })\n\n  it('should patch change', async () => {\n    let doc = await helper.workspace.document\n    // synchronize after user input\n    await nvim.input('o')\n    await doc.patchChange()\n    let buf = doc.buffer\n    // synchronize after api\n    buf.setLines(['aa', 'bb'], {\n      start: 0,\n      end: 1,\n      strictIndexing: false\n    }, true)\n    await doc.patchChange()\n    await shouldEqual(doc)\n    await nvim.deleteCurrentLine()\n    await shouldEqual(doc)\n    await nvim.setLine('foo')\n    await shouldEqual(doc)\n    await nvim.command('stopinsert')\n  })\n\n  it('should synchronize after changeLines', async () => {\n    let doc = await helper.createDocument()\n    await doc.buffer.setLines(['a', 'b', 'c', 'd'])\n    await doc.synchronize()\n    await doc.changeLines([\n      [0, 'd'],\n      [1, 'c'],\n      [2, 'b'],\n      [3, 'a'],\n    ])\n    await shouldEqual(doc)\n  })\n\n  it('should add and remove lines', async () => {\n    let doc = await helper.workspace.document\n    await doc.applyEdits([TextEdit.insert(Position.create(0, 0), 'foo\\nbar\\n')])\n    await shouldEqual(doc)\n    await doc.applyEdits([TextEdit.replace(Range.create(0, 0, 3, 0), '')])\n    await shouldEqual(doc)\n    await nvim.command('bd!')\n  })\n\n  it('should synchronize hidden buffer after replace lines', async () => {\n    let doc = await helper.createDocument()\n    await doc.buffer.setLines(['a', 'b', 'c', 'd'])\n    await nvim.command('enew')\n    await shouldEqual(doc)\n    await doc.applyEdits([TextEdit.replace(Range.create(0, 0, 4, 0), 'c\\nb\\na\\n')])\n    await doc.patchChange()\n    await shouldEqual(doc)\n    await nvim.command('bd!')\n  })\n\n  async function assertBuffer(lines: string[], hls: [string, number, number, number][]): Promise<void> {\n    let buf = await nvim.buffer\n    let curr = await buf.lines\n    expect(curr).toEqual(lines)\n    let highlights = await buf.getHighlights('test')\n    let arr = highlights.map(o => [o.hlGroup, o.lnum, o.colStart, o.colEnd])\n    expect(arr).toEqual(hls)\n  }\n\n  it('should apply single line edit', async () => {\n    let doc = await helper.createDocument()\n    await doc.buffer.setLines(['foo foo'])\n    await doc.patchChange()\n    let ranges = [Range.create(0, 0, 0, 3), Range.create(0, 4, 0, 7)]\n    doc.buffer.highlightRanges('test', 'MoreMsg', ranges)\n    let edit = TextEdit.replace(Range.create(0, 3, 0, 4), 'xy')\n    await doc.applyEdits([edit])\n    await assertBuffer(['fooxyfoo'], [\n      ['MoreMsg', 0, 0, 3],\n      ['MoreMsg', 0, 5, 8],\n    ])\n    edit = TextEdit.replace(Range.create(0, 1, 0, 7), '')\n    await doc.applyEdits([edit])\n    await assertBuffer(['fo'], [])\n    await doc.buffer.append(['bar'])\n    await doc.patchChange()\n    ranges = [Range.create(0, 0, 0, 1), Range.create(1, 2, 1, 3)]\n    doc.buffer.highlightRanges('test', 'MoreMsg', ranges)\n    edit = TextEdit.replace(Range.create(0, 1, 1, 2), 'x')\n    await doc.applyEdits([edit])\n    await doc.patchChange()\n    await assertBuffer(['fxr'], [\n      ['MoreMsg', 0, 0, 1],\n      ['MoreMsg', 0, 2, 3],\n    ])\n  })\n\n  it('should apply multi lines edit', async () => {\n    let doc = await helper.createDocument()\n    await doc.buffer.setLines(['foo foo'])\n    await doc.patchChange()\n    let ranges = [Range.create(0, 0, 0, 3), Range.create(0, 4, 0, 7)]\n    doc.buffer.highlightRanges('test', 'MoreMsg', ranges)\n    let edit = TextEdit.replace(Range.create(0, 3, 0, 4), 'a\\nb\\nc')\n    await doc.applyEdits([edit])\n    await assertBuffer(['fooa', 'b', 'cfoo'], [\n      ['MoreMsg', 0, 0, 3],\n      ['MoreMsg', 2, 1, 4],\n    ])\n    edit = TextEdit.replace(Range.create(0, 3, 2, 1), '\\n')\n    await doc.applyEdits([edit])\n    await assertBuffer(['foo', 'foo'], [\n      ['MoreMsg', 0, 0, 3],\n      ['MoreMsg', 1, 0, 3],\n    ])\n  })\n\n  it('should apply for lines replace edit', async () => {\n    let doc = await helper.createDocument()\n    await doc.buffer.setLines(['foo', 'bar'])\n    await doc.patchChange()\n    let edit = TextEdit.replace(Range.create(0, 0, 1, 0), 'a\\nb\\n')\n    await doc.applyEdits([edit, TextEdit.insert(Position.create(1, 0), 'x')])\n    let lines = await doc.buffer.lines\n    expect(lines).toEqual(['a', 'b', 'xbar'])\n    edit = TextEdit.replace(Range.create(0, 0, 2, 0), '')\n    await doc.applyEdits([edit, TextEdit.replace(Range.create(2, 0, 2, 1), '')])\n    lines = await doc.buffer.lines\n    expect(lines).toEqual(['bar'])\n  })\n\n  it('should apply multiple edits', async () => {\n    let doc = await helper.createDocument()\n    let arr = new Array(10)\n    arr.fill('foo bar a b c d e')\n    let ranges: Range[] = []\n    let edits: TextEdit[] = []\n    for (let i = 0; i < arr.length; i++) {\n      ranges.push(Range.create(i, 0, i, 3))\n      ranges.push(Range.create(i, 4, i, 7))\n      ranges.push(Range.create(i, 8, i, 9))\n      ranges.push(Range.create(i, 10, i, 11))\n      ranges.push(Range.create(i, 12, i, 13))\n      ranges.push(Range.create(i, 14, i, 15))\n      ranges.push(Range.create(i, 16, i, 17))\n      edits.push(TextEdit.insert(Position.create(i, 0), `${i + 1} `))\n    }\n    let buf = doc.buffer\n    await buf.setLines(arr)\n    buf.highlightRanges('test', 'Title', ranges)\n    await doc.synchronize()\n    await doc.applyEdits(edits)\n    await events.race(['TextChanged'], 200)\n    let hls = await buf.getHighlights('test')\n    expect(hls.length).toBe(70)\n  })\n\n  it('should consider latest change', async () => {\n    let doc = await helper.createDocument()\n    let buf = doc.buffer\n    {\n      let edits: TextEdit[] = [TextEdit.insert(Position.create(0, 0), 'bar')]\n      nvim.call('setline', [1, 'foo'], true)\n      await doc.applyEdits(edits)\n      let line = await nvim.line\n      expect(line).toBe('foobar')\n    }\n    {\n      await buf.setLines(['  foo'])\n      await doc.patchChange()\n      nvim.call('setline', [1, '  fooa'], true)\n      nvim.call('cursor', [1, 7], true)\n      let edits: TextEdit[] = [TextEdit.del(Range.create(0, 0, 0, 1))]\n      await doc.applyEdits(edits)\n      let line = await nvim.line\n      expect(line).toBe(' fooa')\n    }\n    {\n      await buf.setLines(['foo'])\n      await nvim.call('cursor', [1, 3])\n      await doc.synchronize()\n      nvim.call('setline', [1, 'fo'], true)\n      let edits: TextEdit[] = [TextEdit.insert(Position.create(0, 0), ' ')]\n      await doc.applyEdits(edits)\n      let line = await nvim.line\n      expect(line).toBe(' fo')\n    }\n  })\n})\n"
  },
  {
    "path": "src/__tests__/vimrc",
    "content": "set nocompatible\n\nset hidden\nset noswapfile\nset nobackup\nset tabstop=2\nset cmdheight=2\nset shiftwidth=2\nset updatetime=300\nset expandtab\nset noshowmode\nset shortmess=aFtW\nset noruler\n\nlet s:dir = expand('<sfile>:h')\nlet s:root = expand('<sfile>:h:h:h')\nlet g:coc_node_env = 'test'\n\nexecute 'set runtimepath+='.s:root\n\ncall coc#add_command('config', 'let g:coc_config_init = 1')\n\n\" Float window id on current tab.\nfunction! GetFloatWin() abort\n  if has('nvim')\n    for i in range(1, winnr('$'))\n      let id = win_getid(i)\n      let config = nvim_win_get_config(id)\n      if (!empty(config) && config['focusable'] == v:true && !empty(config['relative']))\n        if !getwinvar(id, 'button', 0)\n          return id\n        endif\n      endif\n    endfor\n  else\n    let ids = popup_list()\n    return get(filter(ids, 'get(popup_getpos(v:val),\"visible\",0)'), 0, 0)\n  endif\n  return 0\nendfunction\n\n\" float/popup relative to current cursor position\nfunction! GetFloatCursorRelative(winid) abort\n  if !coc#float#valid(a:winid)\n    return v:null\n  endif\n  let winid = win_getid()\n  if winid == a:winid\n    return v:null\n  endif\n  let [cursorLine, cursorCol] = coc#cursor#screen_pos()\n  if has('nvim')\n    let [row, col] = nvim_win_get_position(a:winid)\n    return {'row' : row - cursorLine, 'col' : col - cursorCol}\n  endif\n  let pos = popup_getpos(a:winid)\n  return {'row' : pos['line'] - cursorLine - 1, 'col' : pos['col'] - cursorCol - 1}\nendfunction\n\n\" fake clipboard\nlet g:clipboard = {\n  \\  'name': 'fakeClipboard',\n  \\  'copy': {\n  \\    '+': {lines, regtype -> extend(g:, {'fakeClipboard': [lines, regtype]}) },\n  \\    '*': {lines, regtype -> extend(g:, {'fakeClipboard': [lines, regtype]}) },\n  \\   },\n  \\  'paste': {\n  \\    '+': {-> get(g:, 'fakeClipboard', [])},\n  \\    '*': {-> get(g:, 'fakeClipboard', [])},\n  \\  },\n  \\}\n"
  },
  {
    "path": "src/attach.ts",
    "content": "'use strict'\nimport { attach, Attach, Neovim } from '@chemzqm/neovim'\nimport events from './events'\nimport { createLogger } from './logger'\nimport Plugin from './plugin'\nimport { VERSION } from './util/constants'\nimport { semver } from './util/node'\nimport { toErrorText } from './util/string'\nimport { createTiming } from './util/timing'\nconst logger = createLogger('attach')\n\n/**\n * Request actions that not need plugin ready\n */\nconst ACTIONS_NO_WAIT = ['installExtensions', 'updateExtensions']\nconst semVer = semver.parse(VERSION)\nlet pendingNotifications: [string, any[]][] = []\nconst NO_ERROR_REQUEST = ['doAutocmd', 'CocAutocmd']\n\nexport default (opts: Attach, requestApi = false): Plugin => {\n  const nvim: Neovim = attach(opts, createLogger('node-client'), requestApi)\n  nvim.setVar('coc_process_pid', process.pid, true)\n  nvim.setClientInfo('coc', { major: semVer.major, minor: semVer.minor, patch: semVer.patch }, 'remote', {}, {})\n  const plugin = new Plugin(nvim)\n  let disposable = events.on('ready', () => {\n    disposable.dispose()\n    for (let [method, args] of pendingNotifications) {\n      plugin.cocAction(method, ...args).catch(e => {\n        console.error(`Error on notification \"${method}\": ${e}`)\n        logger.error(`Error on notification ${method}`, e)\n      })\n    }\n    pendingNotifications = []\n  })\n\n  nvim.on('notification', async (method, args) => {\n    switch (method) {\n      case 'VimEnter': {\n        await plugin.init(args[0])\n        break\n      }\n      case 'Log': {\n        logger.debug('Vim log', ...args)\n        break\n      }\n      case 'TaskExit':\n      case 'TaskStderr':\n      case 'TaskStdout':\n      case 'GlobalChange':\n      case 'PromptInsert':\n      case 'PromptExit':\n      case 'InputChar':\n      case 'MenuInput':\n      case 'OptionSet':\n      case 'PromptKeyPress':\n      case 'FloatBtnClick':\n      case 'InputListSelect':\n      case 'PumNavigate':\n        logger.trace('Event: ', method, ...args)\n        await events.fire(method, args)\n        break\n      case 'CocAutocmd':\n        logger.trace('Notification autocmd:', ...args)\n        await events.fire(args[0], args.slice(1))\n        break\n      case 'redraw':\n        break\n      default: {\n        try {\n          logger.info('receive notification:', method, args)\n          if (!plugin.isReady) {\n            pendingNotifications.push([method, args])\n            return\n          }\n          await plugin.cocAction(method, ...args)\n        } catch (e) {\n          console.error(`Error on notification \"${method}\": ${toErrorText(e)}`)\n          logger.error(`Error on notification ${method}`, e)\n        }\n      }\n    }\n  })\n\n  let timing = createTiming('Request', 3000)\n  nvim.on('request', async (method: string, args, resp) => {\n    timing.start(method)\n    try {\n      events.requesting = true\n      if (method == 'CocAutocmd') {\n        logger.trace('Request autocmd:', ...args)\n        await events.fire(args[0], args.slice(1))\n        resp.send(undefined)\n      } else {\n        if (!plugin.isReady && !ACTIONS_NO_WAIT.includes(method)) {\n          logger.warn(`Plugin not ready on request \"${method}\"`, args)\n          resp.send('Plugin not ready', true)\n        } else {\n          logger.info('Request action:', method, args)\n          let res = await plugin.cocAction(method, ...args)\n          resp.send(res)\n        }\n      }\n      events.requesting = false\n    } catch (e) {\n      events.requesting = false\n      // Avoid autocmd request failure\n      if (NO_ERROR_REQUEST.includes(method)) {\n        nvim.echoError(new Error(`Request \"${method}\" failed`))\n        resp.send('')\n      } else {\n        resp.send(toErrorText(e), true)\n      }\n      logger.error(`Request error:`, method, args, e)\n    }\n    timing.stop()\n  })\n  return plugin\n}\n"
  },
  {
    "path": "src/commands.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { Command as VCommand } from 'vscode-languageserver-types'\nimport events from './events'\nimport { createLogger } from './logger'\nimport Mru from './model/mru'\nimport { toArray } from './util/array'\nimport { Extensions as ExtensionsInfo, IExtensionRegistry } from './util/extensionRegistry'\nimport { Disposable } from './util/protocol'\nimport { Registry } from './util/registry'\nimport { toText } from './util/string'\nconst logger = createLogger('commands')\n\n// command center\nexport interface Command {\n  readonly id: string | string[]\n  execute(...args: any[]): void | Promise<any>\n}\n\nclass CommandItem implements Disposable, Command {\n  constructor(\n    public id: string,\n    private impl: (...args: any[]) => void,\n    private thisArg: any,\n    public internal: boolean\n  ) {\n  }\n\n  public execute(...args: any[]): void | Promise<any> {\n    let { impl, thisArg } = this\n    return impl.apply(thisArg, toArray(args))\n  }\n\n  public dispose(): void {\n    this.thisArg = null\n    this.impl = null\n  }\n}\n\nconst extensionRegistry = Registry.as<IExtensionRegistry>(ExtensionsInfo.ExtensionContribution)\n\nclass CommandManager implements Disposable {\n  private readonly commands = new Map<string, CommandItem>()\n  public titles = new Map<string, string>()\n  private mru = new Mru('commands')\n  public nvim: Neovim\n\n  public get commandList(): { id: string, title: string }[] {\n    let res: { id: string, title: string }[] = []\n    for (let item of this.commands.values()) {\n      if (!item.internal) {\n        let { id } = item\n        let title = this.titles.get(id) ?? extensionRegistry.getCommandTitle(id)\n        res.push({ id, title: toText(title) })\n      }\n    }\n    return res\n  }\n\n  public dispose(): void {\n    for (const registration of this.commands.values()) {\n      registration.dispose()\n    }\n    this.commands.clear()\n  }\n\n  public execute(command: VCommand): Promise<any> {\n    return this.executeCommand(command.command, ...(command.arguments ?? []))\n  }\n\n  public register<T extends Command>(command: T, internal: boolean, description?: string): T {\n    for (const id of Array.isArray(command.id) ? command.id : [command.id]) {\n      this.registerCommand(id, command.execute, command, internal)\n      if (description) this.titles.set(id, description)\n    }\n    return command\n  }\n\n  public has(id: string): boolean {\n    return this.commands.has(id)\n  }\n\n  public unregister(id: string): void {\n    let item = this.commands.get(id)\n    if (!item) return\n    item.dispose()\n    this.commands.delete(id)\n  }\n\n  /**\n   * Registers a command that can be invoked via a keyboard shortcut,\n   * a menu item, an action, or directly.\n   *\n   * Registering a command with an existing command identifier twice\n   * will cause an error.\n   * @param command A unique identifier for the command.\n   * @param impl A command handler function.\n   * @param thisArg The `this` context used when invoking the handler function.\n   * @return Disposable which unregisters this command on disposal.\n   */\n  public registerCommand<T>(id: string, impl: (...args: any[]) => T | Promise<T>, thisArg?: any, internal = false): Disposable {\n    if (id.startsWith(\"_\")) internal = true\n    if (this.commands.has(id)) logger.warn(`Command ${id} already registered`)\n    this.commands.set(id, new CommandItem(id, impl, thisArg, internal))\n    return Disposable.create(() => {\n      this.commands.delete(id)\n    })\n  }\n\n  /**\n   * Executes the command denoted by the given command identifier.\n   *\n   * * *Note 1:* When executing an editor command not all types are allowed to\n   * be passed as arguments. Allowed are the primitive types `string`, `boolean`,\n   * `number`, `undefined`, and `null`, as well as [`Position`](#Position), [`Range`](#Range), [`URI`](#URI) and [`Location`](#Location).\n   * * *Note 2:* There are no restrictions when executing commands that have been contributed\n   * by extensions.\n   * @param command Identifier of the command to execute.\n   * @param rest Parameters passed to the command function.\n   * @return A promise that resolves to the returned value of the given command. `undefined` when\n   * the command handler function doesn't return anything.\n   */\n  public executeCommand<T>(command: string, ...rest: any[]): Promise<T> {\n    let cmd = this.commands.get(command)\n    if (!cmd) throw new Error(`Command: ${command} not found`)\n    return Promise.resolve(cmd.execute.apply(cmd, rest))\n  }\n\n  /**\n   * Used for user invoked command.\n   */\n  public async fireCommand(id: string, ...args: any[]): Promise<unknown> {\n    // needed to load onCommand extensions\n    await events.fire('Command', [id])\n    let start = Date.now()\n    let res = await this.executeCommand(id, ...args)\n    if (args.length == 0) {\n      await this.addRecent(id, events.lastChangeTs > start)\n    }\n    return res\n  }\n\n  public async addRecent(cmd: string, repeat: boolean): Promise<void> {\n    await this.mru.add(cmd)\n    if (repeat) this.nvim.command(`silent! call repeat#set(\"\\\\<Plug>(coc-command-repeat)\", -1)`, true)\n  }\n\n  public async repeatCommand(): Promise<void> {\n    let mruList = await this.mru.load()\n    let first = mruList[0]\n    if (first) {\n      await this.executeCommand(first)\n      await this.nvim.command(`silent! call repeat#set(\"\\\\<Plug>(coc-command-repeat)\", -1)`)\n    }\n  }\n}\n\nexport default new CommandManager()\n"
  },
  {
    "path": "src/completion/complete.ts",
    "content": "'use strict'\nimport type { Neovim } from '@chemzqm/neovim'\nimport { Position, Range } from 'vscode-languageserver-types'\nimport { createLogger } from '../logger'\nimport type Document from '../model/document'\nimport { waitWithToken } from '../util'\nimport { isFalsyOrEmpty } from '../util/array'\nimport { anyScore, FuzzyScore, fuzzyScore, fuzzyScoreGracefulAggressive, FuzzyScorer } from '../util/filter'\nimport * as Is from '../util/is'\nimport { clamp } from '../util/numbers'\nimport { CancellationToken, CancellationTokenSource, Disposable, Emitter, Event } from '../util/protocol'\nimport { characterIndex } from '../util/string'\nimport workspace from '../workspace'\nimport { CompleteConfig, CompleteItem, CompleteOption, DurationCompleteItem, InsertMode, ISource, SortMethod } from './types'\nimport { Converter, ConvertOption, getPriority, useAscii } from './util'\nimport { WordDistance } from './wordDistance'\nconst logger = createLogger('completion-complete')\nconst MAX_DISTANCE = 2 << 20\nconst MIN_TIMEOUT = 50\nconst MAX_TIMEOUT = 15000\nconst MAX_TRIGGER_WAIT = 200\n\nconst WORD_SOURCES = new Set(['buffer', 'around', 'word'])\n\nexport interface CompleteResultToFilter {\n  items: DurationCompleteItem[]\n  isIncomplete?: boolean\n}\n\nexport default class Complete {\n  // identify this complete\n  private results: Map<string, CompleteResultToFilter> = new Map()\n  private _input = ''\n  private _completing = false\n  private timer: NodeJS.Timeout\n  private names: string[] = []\n  private asciiMatch: boolean\n  private timeout: number\n  private cid = 0\n  private minCharacter = Number.MAX_SAFE_INTEGER\n  private inputStart: number\n  private completingSources: Set<string> = new Set()\n  private readonly _onDidRefresh = new Emitter<void>()\n  private wordDistance: WordDistance | undefined\n  private tokenSources: Set<CancellationTokenSource> = new Set()\n  private tokensInfo: WeakMap<CancellationTokenSource, boolean> = new WeakMap()\n  private itemsMap: WeakMap<DurationCompleteItem, CompleteItem> = new WeakMap()\n  public readonly onDidRefresh: Event<void> = this._onDidRefresh.event\n  constructor(public option: CompleteOption,\n    public readonly document: Document,\n    private config: CompleteConfig,\n    private sources: ISource<CompleteItem>[]) {\n    this.inputStart = characterIndex(option.line, option.col)\n    this.timeout = clamp(this.config.timeout, MIN_TIMEOUT, MAX_TIMEOUT)\n    sources.sort((a, b) => (b.priority ?? 99) - (a.priority ?? 99))\n    this.names = sources.map(o => o.name)\n    this.asciiMatch = config.asciiMatch && useAscii(option.input)\n  }\n\n  private get nvim(): Neovim {\n    return workspace.nvim\n  }\n\n  // trigger texts starts at character\n  public getTrigger(character: number): string {\n    let { linenr, col } = this.option\n    let line = this.document.getline(linenr - 1)\n    let pre = line.slice(0, characterIndex(line, col)) + this.input\n    return pre.slice(character)\n  }\n\n  private fireRefresh(waitTime: number): void {\n    clearTimeout(this.timer)\n    if (!waitTime) {\n      // Needed to wait this._completing = false\n      process.nextTick(() => {\n        this._onDidRefresh.fire()\n      })\n    } else {\n      this.timer = setTimeout(() => {\n        this._onDidRefresh.fire()\n      }, waitTime)\n    }\n  }\n\n  private get totalLength(): number {\n    let len = 0\n    for (let result of this.results.values()) {\n      len += result.items.length\n    }\n    return len\n  }\n\n  public resolveItem(item: DurationCompleteItem | undefined): { source: ISource, item: CompleteItem } | undefined {\n    if (!item) return undefined\n    return { source: item.source, item: this.itemsMap.get(item) }\n  }\n\n  public get isCompleting(): boolean {\n    return this._completing\n  }\n\n  public get input(): string {\n    return this._input\n  }\n\n  public get isEmpty(): boolean {\n    return this.results.size === 0\n  }\n\n  private get hasInComplete(): boolean {\n    for (let result of this.results.values()) {\n      if (result.isIncomplete) return true\n    }\n    return false\n  }\n\n  public getIncompleteSources(): ISource[] {\n    return this.sources.filter(s => {\n      let res = this.results.get(s.name)\n      return res && res.isIncomplete === true\n    })\n  }\n\n  public async doComplete(): Promise<boolean> {\n    let tokenSource = this.createTokenSource(false)\n    let token = tokenSource.token\n    let res = await Promise.all([\n      this.nvim.call('coc#util#synname', []),\n      this.nvim.call('coc#_suggest_variables', []),\n      this.document.patchChange()\n    ]) as [string, { disable: boolean, disabled_sources: string[], blacklist: string[] }, undefined]\n    if (token.isCancellationRequested) return\n    this.option.synname = res[0]\n    let variables = res[1]\n    if (variables.disable) {\n      logger.warn('suggest cancelled by b:coc_suggest_disable')\n      return true\n    }\n    if (!isFalsyOrEmpty(variables.disabled_sources)) {\n      this.sources = this.sources.filter(s => !variables.disabled_sources.includes(s.name))\n      if (this.sources.length === 0) {\n        logger.warn('suggest cancelled by b:coc_disabled_sources')\n        return true\n      }\n    }\n    if (!isFalsyOrEmpty(variables.blacklist) && variables.blacklist.includes(this.option.input)) {\n      logger.warn('suggest cancelled by b:coc_suggest_blacklist')\n      return true\n    }\n    void WordDistance.create(this.config.localityBonus, this.option, token).then(instance => {\n      this.wordDistance = instance\n    })\n    await waitWithToken(clamp(this.config.triggerCompletionWait, 0, MAX_TRIGGER_WAIT), tokenSource.token)\n    await this.completeSources(this.sources, tokenSource, this.cid)\n  }\n\n  private async completeSources(sources: ReadonlyArray<ISource>, tokenSource: CancellationTokenSource, cid: number): Promise<void> {\n    const token = tokenSource.token\n    if (token.isCancellationRequested) return\n    this._completing = true\n    const remains: Set<string> = new Set(sources.map(s => s.name))\n    let timer: NodeJS.Timeout\n    let disposable: Disposable\n    let tp = new Promise<void>(resolve => {\n      disposable = token.onCancellationRequested(() => {\n        clearTimeout(timer)\n        resolve()\n      })\n      timer = setTimeout(() => {\n        let names = Array.from(remains)\n        disposable.dispose()\n        tokenSource.cancel()\n        logger.warn(`Completion timeout after ${this.timeout}ms`, names)\n        this.nvim.setVar(`coc_timeout_sources`, names, true)\n        resolve()\n      }, this.timeout)\n    })\n    // default insert or replace range\n    const range = this.getDefaultRange()\n    let promises = sources.map(s => this.completeSource(s, range, token).then(added => {\n      remains.delete(s.name)\n      if (token.isCancellationRequested) return\n      if (this.completingSources.size === 0) {\n        this.fireRefresh(0)\n      } else if (added) {\n        this.fireRefresh(16)\n      }\n    }))\n    await Promise.race([tp, Promise.allSettled(promises)])\n    this.tokenSources.delete(tokenSource)\n    disposable.dispose()\n    clearTimeout(timer)\n    if (cid === this.cid) this._completing = false\n  }\n\n  private async completeSource(source: ISource, range: Range, token: CancellationToken): Promise<boolean> {\n    // new option for each source\n    let opt = Object.assign({}, this.option)\n    let { asciiMatch } = this\n    const insertMode = this.config.insertMode\n    const sourceName = source.name\n    let added = false\n    this.completingSources.add(sourceName)\n    try {\n      if (Is.func(source.shouldComplete)) {\n        let shouldRun = await Promise.resolve(source.shouldComplete(opt))\n        if (!shouldRun || token.isCancellationRequested) return\n      }\n      const start = Date.now()\n      const map = this.itemsMap\n      await new Promise<void>((resolve, reject) => {\n        Promise.resolve(source.doComplete(opt, token)).then(result => {\n          if (token.isCancellationRequested) {\n            resolve(undefined)\n            return\n          }\n          let len = result ? result.items.length : 0\n          logger.debug(`Source \"${sourceName}\" finished with ${len} items ms cost:`, Date.now() - start)\n          if (len > 0) {\n            if (Is.number(result.startcol)) {\n              let line = opt.linenr - 1\n              range = Range.create(line, characterIndex(opt.line, result.startcol), line, range.end.character)\n            }\n            const priority = getPriority(source, this.config.languageSourcePriority)\n            const option: ConvertOption = { source, insertMode, priority, asciiMatch, itemDefaults: result.itemDefaults, range }\n            const converter = new Converter(this.inputStart, option, opt)\n            const items = result.items.reduce((items, item) => {\n              let completeItem = converter.convertToDurationItem(item)\n              if (!completeItem) {\n                logger.error(`Unexpected completion item from ${sourceName}:`, item)\n                return items\n              }\n              map.set(completeItem, item)\n              items.push(completeItem)\n              return items\n            }, [])\n            this.minCharacter = Math.min(this.minCharacter, converter.minCharacter)\n            this.results.set(sourceName, { items, isIncomplete: result.isIncomplete === true })\n            added = true\n          } else {\n            this.results.delete(sourceName)\n          }\n          resolve()\n        }, (err: Error) => {\n          reject(err)\n        })\n      })\n    } catch (err) {\n      // this.nvim.echoError(err)\n      logger.error('Complete error:', source.name, err)\n    }\n    this.completingSources.delete(sourceName)\n    return added\n  }\n\n  public async completeInComplete(resumeInput: string): Promise<undefined> {\n    let { document } = this\n    this.cancelInComplete()\n    let tokenSource = this.createTokenSource(true)\n    await document.patchChange()\n    let { input, colnr, linenr, followWord, position } = this.option\n    Object.assign(this.option, {\n      word: resumeInput + followWord,\n      input: resumeInput,\n      line: document.getline(linenr - 1),\n      position: { line: position.line, character: position.character + resumeInput.length - input.length },\n      colnr: colnr + (resumeInput.length - input.length),\n      triggerCharacter: undefined,\n      triggerForInComplete: true\n    })\n    this.cid++\n    const sources = this.getIncompleteSources()\n    await this.completeSources(sources, tokenSource, this.cid)\n  }\n\n  public filterItems(input: string): DurationCompleteItem[] | undefined {\n    let { results, names, option, inputStart } = this\n    this._input = input\n    let len = input.length\n    let { maxItemCount, defaultSortMethod, removeDuplicateItems, removeCurrentWord } = this.config\n    let arr: DurationCompleteItem[] = []\n    let words: Set<string> = new Set()\n    const emptyInput = len == 0\n    const lowInput = input.toLowerCase()\n    const scoreFn: FuzzyScorer = (!this.config.filterGraceful || this.totalLength > 2000) ? fuzzyScore : fuzzyScoreGracefulAggressive\n    const scoreOption = { boostFullMatch: true, firstMatchCanBeWeak: false }\n    const anchor = Position.create(option.linenr - 1, inputStart)\n    for (let name of names) {\n      let result = results.get(name)\n      if (!result) continue\n      let isWord = WORD_SOURCES.has(name)\n      let items = result.items\n      for (let idx = 0; idx < items.length; idx++) {\n        let item = items[idx]\n        let { word, filterText, dup } = item\n        if (dup !== true && words.has(word)) continue\n        if (removeCurrentWord && isWord && word === input) continue\n        if (removeDuplicateItems && item.isSnippet !== true && words.has(word)) continue\n        let fuzzyResult: FuzzyScore | undefined\n        if (!emptyInput) {\n          scoreOption.firstMatchCanBeWeak = item.delta === 0 && item.character !== inputStart\n          if (item.delta > 0) {\n            // better input to make it have higher score and better highlight\n            let prev = filterText.slice(0, item.delta)\n            fuzzyResult = scoreFn(prev + input, prev.toLowerCase() + lowInput, 0, filterText, filterText.toLowerCase(), 0, scoreOption)\n          } else {\n            fuzzyResult = scoreFn(input, lowInput, 0, filterText, filterText.toLowerCase(), 0, scoreOption)\n          }\n          if (fuzzyResult == null) continue\n          item.score = fuzzyResult[0]\n          item.positions = fuzzyResult\n          if (this.wordDistance) item.localBonus = MAX_DISTANCE - this.wordDistance.distance(anchor, item)\n        } else if (item.character < inputStart) {\n          let trigger = option.line.slice(item.character, inputStart)\n          scoreOption.firstMatchCanBeWeak = true\n          fuzzyResult = anyScore(trigger, trigger.toLowerCase(), 0, filterText, filterText.toLowerCase(), 0, scoreOption)\n          item.score = fuzzyResult[0]\n          item.positions = fuzzyResult\n        } else {\n          item.score = 0\n          item.positions = undefined\n        }\n        words.add(word)\n        arr.push(item)\n      }\n    }\n    arr.sort(sortItems.bind(null, emptyInput, defaultSortMethod))\n    return this.limitCompleteItems(arr.slice(0, maxItemCount))\n  }\n\n  public async filterResults(input: string): Promise<DurationCompleteItem[] | undefined> {\n    if (input.length > this.option.input.length && this.hasInComplete) {\n      this.fireRefresh(30)\n      void this.completeInComplete(input)\n      return undefined\n    }\n    clearTimeout(this.timer)\n    return this.filterItems(input)\n  }\n\n  private limitCompleteItems(items: DurationCompleteItem[]): DurationCompleteItem[] {\n    let { highPrioritySourceLimit, lowPrioritySourceLimit } = this.config\n    if (!highPrioritySourceLimit && !lowPrioritySourceLimit) return items\n    let counts: Map<ISource, number> = new Map()\n    return items.filter(item => {\n      let { priority, source } = item\n      let isLow = priority < 90\n      let curr = counts.get(source) || 0\n      if ((lowPrioritySourceLimit && isLow && curr == lowPrioritySourceLimit)\n        || (highPrioritySourceLimit && !isLow && curr == highPrioritySourceLimit)) {\n        return false\n      }\n      counts.set(source, curr + 1)\n      return true\n    })\n  }\n\n  private getDefaultRange(): Range {\n    let { insertMode } = this.config\n    let { linenr, followWord, position } = this.option\n    let line = linenr - 1\n    let end = position.character + (insertMode == InsertMode.Replace ? followWord.length : 0)\n    return Range.create(line, this.inputStart, line, end)\n  }\n\n  private createTokenSource(isIncomplete: boolean): CancellationTokenSource {\n    let tokenSource = new CancellationTokenSource()\n    this.tokenSources.add(tokenSource)\n    tokenSource.token.onCancellationRequested(() => {\n      this.tokenSources.delete(tokenSource)\n    })\n    this.tokensInfo.set(tokenSource, isIncomplete)\n    return tokenSource\n  }\n\n  private cancelInComplete(): void {\n    let { tokenSources, tokensInfo } = this\n    for (let tokenSource of Array.from(tokenSources)) {\n      if (tokensInfo.get(tokenSource) === true) {\n        tokenSource.cancel()\n      }\n    }\n  }\n\n  public cancel(): void {\n    let { tokenSources, timer } = this\n    clearTimeout(timer)\n    for (let tokenSource of Array.from(tokenSources)) {\n      tokenSource.cancel()\n    }\n    tokenSources.clear()\n    this._completing = false\n  }\n\n  public dispose(): void {\n    this.cancel()\n    this.results.clear()\n    this._onDidRefresh.dispose()\n  }\n}\n\nexport function sortItems(emptyInput: boolean, defaultSortMethod: SortMethod, a: DurationCompleteItem, b: DurationCompleteItem): number {\n  let sa = a.sortText\n  let sb = b.sortText\n  if (a.score !== b.score) return b.score - a.score\n  if (a.priority !== b.priority) return b.priority - a.priority\n  if (a.source === b.source && sa !== sb) return sa < sb ? -1 : 1\n  if (a.localBonus !== b.localBonus) return b.localBonus - a.localBonus\n  // not sort with empty input, the item not replace trigger have higher priority\n  if (emptyInput) return b.character - a.character\n  switch (defaultSortMethod) {\n    case SortMethod.None:\n      return 0\n    case SortMethod.Alphabetical:\n      return a.filterText.localeCompare(b.filterText)\n    case SortMethod.Length:\n    default: // Fallback on length\n      return a.filterText.length - b.filterText.length\n  }\n}\n"
  },
  {
    "path": "src/completion/floating.ts",
    "content": "'use strict'\nimport { createLogger } from '../logger'\nimport { parseDocuments } from '../markdown'\nimport { Documentation, FloatConfig } from '../types'\nimport { getConditionValue } from '../util'\nimport { CancellationError, isCancellationError } from '../util/errors'\nimport * as Is from '../util/is'\nimport { CancellationToken, CancellationTokenSource } from '../util/protocol'\nimport workspace from '../workspace'\nimport { CompleteItem, CompleteOption, ISource } from './types'\nimport { getDocumentations } from './util'\nconst logger = createLogger('completion-floating')\nconst RESOLVE_TIMEOUT = getConditionValue(500, 50)\n\nexport default class Floating {\n  private resolveTokenSource: CancellationTokenSource | undefined\n  constructor(private config: { floatConfig: FloatConfig }) {\n  }\n\n  public async resolveItem(source: ISource, item: CompleteItem, opt: CompleteOption, showDocs: boolean, detailRendered = false): Promise<void> {\n    this.cancel()\n    if (Is.func(source.onCompleteResolve)) {\n      try {\n        await this.requestWithToken(token => {\n          return Promise.resolve(source.onCompleteResolve(item, opt, token))\n        })\n      } catch (e) {\n        if (isCancellationError(e)) return\n        logger.error(`Error on resolve complete item from ${source.name}:`, item, e)\n        // not return, may need show/hide docs.\n      }\n    }\n    if (showDocs) {\n      this.show(getDocumentations(item, opt.filetype, detailRendered))\n    }\n  }\n\n  public show(docs: Documentation[]): void {\n    let config = this.config.floatConfig\n    docs = docs.filter(o => o.content.trim().length > 0)\n    if (docs.length === 0) {\n      this.close()\n    } else {\n      const markdownPreference = workspace.configurations.markdownPreference\n      let { lines, codes, highlights } = parseDocuments(docs, markdownPreference)\n      let opts: any = {\n        codes,\n        highlights,\n        highlight: config.highlight ?? 'CocFloating',\n        maxWidth: config.maxWidth || 80,\n        rounded: config.rounded ? 1 : 0,\n        focusable: config.focusable === true ? 1 : 0\n      }\n      if (Is.string(config.title)) opts.title = config.title\n      if (config.shadow) opts.shadow = 1\n      if (config.border) opts.border = [1, 1, 1, 1]\n      if (config.borderhighlight) opts.borderhighlight = config.borderhighlight\n      if (typeof config.winblend === 'number') opts.winblend = config.winblend\n      let { nvim } = workspace\n      nvim.call('coc#dialog#create_pum_float', [lines, opts], true)\n      nvim.redrawVim()\n    }\n  }\n\n  public close(): void {\n    workspace.nvim.call('coc#pum#close_detail', [], true)\n    workspace.nvim.redrawVim()\n  }\n\n  public cancel(): void {\n    if (this.resolveTokenSource) {\n      this.resolveTokenSource.cancel()\n      this.resolveTokenSource = undefined\n    }\n  }\n\n  private requestWithToken(fn: (token: CancellationToken) => Promise<void>): Promise<void> {\n    let tokenSource = this.resolveTokenSource = new CancellationTokenSource()\n    return new Promise<void>((resolve, reject) => {\n      let called = false\n      let onFinish = (err?: Error) => {\n        if (called) return\n        called = true\n        disposable.dispose()\n        clearTimeout(timer)\n        if (this.resolveTokenSource === tokenSource) {\n          this.resolveTokenSource = undefined\n        }\n        if (err) {\n          reject(err)\n        } else {\n          resolve()\n        }\n      }\n      let timer = setTimeout(() => {\n        tokenSource.cancel()\n      }, RESOLVE_TIMEOUT)\n      let disposable = tokenSource.token.onCancellationRequested(() => {\n        onFinish(new CancellationError())\n      })\n      fn(tokenSource.token).then(() => {\n        onFinish()\n      }, e => {\n        onFinish(e)\n      })\n    })\n  }\n}\n"
  },
  {
    "path": "src/completion/index.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { Position, Range, SelectedCompletionInfo } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport commands from '../commands'\nimport type { IConfigurationChangeEvent } from '../configuration/types'\nimport events, { InsertChange, PopupChangeEvent } from '../events'\nimport { createLogger } from '../logger'\nimport type Document from '../model/document'\nimport { defaultValue, disposeAll, getConditionValue, pariedCharacters } from '../util'\nimport { isFalsyOrEmpty, toArray } from '../util/array'\nimport { onUnexpectedError } from '../util/errors'\nimport * as Is from '../util/is'\nimport { debounce } from '../util/node'\nimport { toNumber } from '../util/numbers'\nimport { toObject } from '../util/object'\nimport type { Disposable } from '../util/protocol'\nimport { byteIndex, byteLength, byteSlice, toText } from '../util/string'\nimport window from '../window'\nimport workspace from '../workspace'\nimport Complete from './complete'\nimport Floating from './floating'\nimport PopupMenu, { PopupMenuConfig } from './pum'\nimport sources from './sources'\nimport { CompleteConfig, CompleteDoneOption, CompleteFinishKind, CompleteItem, CompleteOption, DurationCompleteItem, InsertMode, ISource, SortMethod } from './types'\nimport { checkIgnoreRegexps, createKindMap, deltaCount, getInput, getResumeInput, MruLoader, shouldStop, toCompleteDoneItem } from './util'\nconst logger = createLogger('completion')\nconst TRIGGER_TIMEOUT = getConditionValue(200, 20)\nconst CURSORMOVE_DEBOUNCE = getConditionValue(20, 20)\n\nexport class Completion implements Disposable {\n  public config: CompleteConfig\n  private staticConfig: PopupMenuConfig\n  private pum: PopupMenu\n  private _mru: MruLoader\n  private pretext: string | undefined\n  private triggerTimer: NodeJS.Timeout\n  private popupEvent: PopupChangeEvent\n  private floating: Floating\n  private disposables: Disposable[] = []\n  private complete: Complete | null = null\n  private _debounced: ((bufnr: number, cursor: [number, number], hasInsert: boolean) => void) & { clear(): void }\n  // Ordered items shown in the pum\n  public activeItems: ReadonlyArray<DurationCompleteItem> = []\n\n  private get nvim(): Neovim {\n    return workspace.nvim\n  }\n\n  public init(): void {\n    this.loadConfiguration()\n    workspace.onDidChangeConfiguration(this.loadConfiguration, this, this.disposables)\n    window.onDidChangeActiveTextEditor(e => {\n      this.loadLocalConfig(e.document)\n    }, null, this.disposables)\n    this._mru = new MruLoader()\n    this.pum = new PopupMenu(this.staticConfig, this._mru)\n    this.floating = new Floating(this.staticConfig)\n    this._debounced = debounce(this.onCursorMovedI.bind(this), CURSORMOVE_DEBOUNCE)\n    events.on('BufEnter', () => {\n      this._debounced.clear()\n    }, null, this.disposables)\n    events.on(['CursorMoved', 'InsertLeave'], () => {\n      this.cancelAndClose()\n    }, null, this.disposables)\n    events.on('PumNavigate', () => {\n      this.complete?.cancel()\n    }, null, this.disposables)\n    events.on('CursorMovedI', this._debounced, this, this.disposables)\n    events.on('CursorMovedI', () => {\n      clearTimeout(this.triggerTimer)\n    }, null, this.disposables)\n    events.on('InsertEnter', this.onInsertEnter, this, this.disposables)\n    events.on('TextChangedI', this.onTextChangedI, this, this.disposables)\n    events.on('MenuPopupChanged', async ev => {\n      if (this.complete == null) return\n      this.popupEvent = ev\n      if (ev.inserted) this.complete.cancel()\n      let selectedItem = this.activeItems[ev.index]\n      let resolved = this.complete.resolveItem(selectedItem)\n      if (!resolved || (!ev.move && this.complete.isCompleting)) return\n      let detailRendered = selectedItem.detailRendered\n      let showDocs = this.config.enableFloat\n      await this.floating.resolveItem(resolved.source, resolved.item, this.option, showDocs, detailRendered)\n    }, null, this.disposables)\n    this.nvim.call('coc#ui#check_pum_keymappings', [this.config.autoTrigger], true)\n    commands.registerCommand('editor.action.triggerSuggest', async (source?: string) => {\n      await this.startCompletion({ source })\n    }, this, true)\n  }\n\n  public get mru(): MruLoader {\n    return this._mru\n  }\n\n  public onCursorMovedI(bufnr: number, cursor: [number, number], hasInsert: boolean): void {\n    if (hasInsert || !this.option || bufnr !== this.option.bufnr) return\n    let { linenr, col } = this.option\n    if (this.selectedItem\n      && linenr === cursor[0]\n      && col + byteLength(this.selectedItem.word) + 1 == cursor[1]) {\n      return\n    }\n    // Possible cursor move out and move back, not cancel\n    if (linenr === cursor[0] && cursor[1] === byteLength(toText(this.pretext)) + 1) {\n      return\n    }\n    this.cancelAndClose()\n  }\n\n  public get option(): CompleteOption {\n    if (!this.complete) return null\n    return this.complete.option\n  }\n\n  public get isActivated(): boolean {\n    return this.complete != null\n  }\n\n  public get selectedItem(): DurationCompleteItem | undefined {\n    if (!this.popupEvent) return undefined\n    return this.activeItems[this.popupEvent.index]\n  }\n\n  public get selectedCompletionInfo(): SelectedCompletionInfo | undefined {\n    let item = this.selectedItem\n    let { pretext } = this\n    if (!pretext || !item) return undefined\n    let line = this.option.linenr - 1\n    let end = pretext.length\n    return { range: Range.create(line, item.character, line, end), text: item.word }\n  }\n  /**\n   * Configuration for current document\n   */\n  private loadLocalConfig(doc?: Document): void {\n    let suggest = workspace.getConfiguration('suggest', doc)\n    this.config = {\n      autoTrigger: suggest.get<string>('autoTrigger', 'always'),\n      insertMode: suggest.get<InsertMode>('insertMode', InsertMode.Replace),\n      filterGraceful: suggest.get<boolean>('filterGraceful', true),\n      enableFloat: suggest.get<boolean>('enableFloat', true),\n      languageSourcePriority: suggest.get<number>('languageSourcePriority', 99),\n      snippetsSupport: suggest.get<boolean>('snippetsSupport', true),\n      defaultSortMethod: suggest.get<SortMethod>('defaultSortMethod', SortMethod.Length),\n      removeDuplicateItems: suggest.get<boolean>('removeDuplicateItems', false),\n      removeCurrentWord: suggest.get<boolean>('removeCurrentWord', false),\n      acceptSuggestionOnCommitCharacter: suggest.get<boolean>('acceptSuggestionOnCommitCharacter', false),\n      triggerCompletionWait: suggest.get<number>('triggerCompletionWait', 0),\n      triggerAfterInsertEnter: suggest.get<boolean>('triggerAfterInsertEnter', false),\n      maxItemCount: suggest.get<number>('maxCompleteItemCount', 256),\n      timeout: suggest.get<number>('timeout', 500),\n      minTriggerInputLength: suggest.get<number>('minTriggerInputLength', 1),\n      localityBonus: suggest.get<boolean>('localityBonus', true),\n      highPrioritySourceLimit: suggest.get<number>('highPrioritySourceLimit', null),\n      lowPrioritySourceLimit: suggest.get<number>('lowPrioritySourceLimit', null),\n      ignoreRegexps: suggest.get<string[]>('ignoreRegexps', []),\n      asciiMatch: suggest.get<boolean>('asciiMatch', true),\n      asciiCharactersOnly: suggest.get<boolean>('asciiCharactersOnly', false),\n    }\n  }\n\n  public loadConfiguration(e?: IConfigurationChangeEvent): CompleteConfig {\n    if (e && !e.affectsConfiguration('suggest')) return\n    if (e) this.pum.reset()\n    let suggest = workspace.initialConfiguration.get('suggest') as any\n    let labels = defaultValue(suggest.completionItemKindLabels, {})\n    this.staticConfig = Object.assign(this.staticConfig ?? {}, {\n      kindMap: createKindMap(labels),\n      defaultKindText: toText(labels['default']),\n      detailField: suggest.detailField,\n      detailMaxLength: toNumber(suggest.detailMaxLength, 100),\n      invalidInsertCharacters: toArray(suggest.invalidInsertCharacters),\n      formatItems: suggest.formatItems,\n      filterOnBackspace: suggest.filterOnBackspace,\n      floatConfig: toObject(suggest.floatConfig),\n      pumFloatConfig: suggest.pumFloatConfig,\n      labelMaxLength: suggest.labelMaxLength,\n      reTriggerAfterIndent: !!suggest.reTriggerAfterIndent,\n      reversePumAboveCursor: !!suggest.reversePumAboveCursor,\n      snippetIndicator: toText(suggest.snippetIndicator),\n      noselect: !!suggest.noselect,\n      enablePreselect: !!suggest.enablePreselect,\n      virtualText: !!suggest.virtualText,\n      selection: suggest.selection\n    })\n    let doc = workspace.getDocument(workspace.bufnr)\n    this.loadLocalConfig(doc)\n  }\n\n  public async startCompletion(opt?: { source?: string, col?: number }): Promise<void> {\n    clearTimeout(this.triggerTimer)\n    let sourceList: ISource[]\n    if (Is.string(opt.source)) {\n      sourceList = toArray(sources.getSource(opt.source))\n    }\n    let doc = workspace.getAttachedDocument(events.bufnr)\n    let info = await this.nvim.call('coc#util#change_info') as InsertChange\n    info.pre = byteSlice(info.line, 0, info.col - 1)\n    const option = this.getCompleteOption(doc, info, true, opt.col)\n    await this._startCompletion(option, sourceList)\n  }\n\n  private async _startCompletion(option: CompleteOption, sourceList?: ISource[]): Promise<void> {\n    this._debounced.clear()\n    let doc = workspace.getAttachedDocument(option.bufnr)\n    option.filetype = doc.filetype\n    logger.debug('trigger completion with', option)\n    this.cancelAndClose()\n    sourceList = sourceList ?? sources.getSources(option)\n    if (isFalsyOrEmpty(sourceList)) return\n    this.pretext = byteSlice(option.line, 0, option.colnr - 1)\n    let complete = this.complete = new Complete(\n      option,\n      doc,\n      this.config,\n      sourceList)\n    events.completing = true\n    void events.fire('CompleteStart', [option])\n    complete.onDidRefresh(async () => {\n      clearTimeout(this.triggerTimer)\n      if (complete.isEmpty) {\n        this.cancelAndClose(false)\n        return\n      }\n      await this.filterResults()\n    })\n    let shouldStop = await complete.doComplete()\n    if (shouldStop) this.cancelAndClose(false)\n  }\n\n  public hasIndentChange(info: InsertChange): boolean {\n    let { option, pretext } = this\n    if (!option || option.linenr != info.lnum) return false\n    let previous = pretext.match(/^\\s*/)[0]\n    let current = info.pre.match(/^\\s*/)[0]\n    if (previous == current || pretext.slice(previous.length) !== info.pre.slice(current.length)) return false\n    return true\n  }\n\n  private async onTextChangedI(bufnr: number, info: InsertChange): Promise<void> {\n    const doc = workspace.getDocument(bufnr)\n    if (!doc || !doc.attached) return\n    this._debounced.clear()\n    const { option, staticConfig } = this\n    const filterOnBackspace = this.staticConfig.filterOnBackspace\n    if (option != null) {\n      // detect item word insert\n      if (!info.insertChar) {\n        let pre = byteSlice(option.line, 0, option.col)\n        if (this.selectedItem) {\n          let { word, startcol } = this.popupEvent\n          if (byteSlice(option.line, 0, startcol) + word == info.pre) {\n            this.pretext = info.pre\n            return\n          }\n        } else if (pre + this.pum.search == info.pre) {\n          this.pretext = info.pre\n          return\n        }\n      }\n      // retrigger after indent\n      if (staticConfig.reTriggerAfterIndent && this.hasIndentChange(info)) {\n        this.cancelAndClose()\n        await this.triggerCompletion(doc, info)\n        return\n      }\n      if (shouldStop(bufnr, info, option) || (filterOnBackspace === false && info.pre.length < this.pretext.length)) {\n        this.cancelAndClose()\n        return\n      }\n    }\n    if (info.pre === this.pretext) return\n    clearTimeout(this.triggerTimer)\n    let pretext = this.pretext = info.pre\n    if (!info.insertChar) {\n      if (this.complete) await this.filterResults()\n      return\n    }\n    // check commit\n    if (this.config.acceptSuggestionOnCommitCharacter && this.selectedItem) {\n      let last = pretext.slice(-1)\n      let resolvedItem = this.selectedItem\n      let result = this.complete.resolveItem(resolvedItem)\n      if (result && sources.shouldCommit(result.source, result.item, last)) {\n        logger.debug(`commit by commit character: ${last}`)\n        let startcol = byteIndex(this.option.line, resolvedItem.character) + 1\n        let delta = deltaCount(info)\n        await this.nvim.call('coc#pum#replace', [startcol, resolvedItem.word, delta])\n        await this.stop(CompleteFinishKind.Confirm, true)\n        let res = await this.nvim.evalVim(`[getline('.'),col('.'),mode()]`) as [string, number, string]\n        let currentPre = byteSlice(res[0], 0, res[1] - 1)\n        // Don't know how to decide feedkeys is Needed\n        if (res[2] != 'i' || currentPre[currentPre.length - 1] == last) return\n        if (pariedCharacters.has(last) && currentPre.slice(-2) == `${last}${pariedCharacters.get(last)}`) return\n        // Need nvim_buf_set_text on vim9 to insert to end of line\n        // if (info.line === pretext && last === ';') {\n        this.nvim.call('feedkeys', [last, 'n'], true)\n        return\n      }\n    }\n    // trigger character\n    if (!doc.chars.isKeywordChar(info.insertChar)) {\n      let triggerSources = this.getTriggerSources(doc, pretext)\n      if (triggerSources.length > 0) {\n        await this.triggerCompletion(doc, info, triggerSources)\n        return\n      }\n    }\n    // trigger by normal character\n    if (!this.complete) {\n      await this.triggerCompletion(doc, info)\n      return\n    }\n    if (this.complete.isEmpty) {\n      // triggering without results\n      this.triggerTimer = setTimeout(async () => {\n        await this.triggerCompletion(doc, info)\n      }, TRIGGER_TIMEOUT)\n      return\n    }\n    await this.filterResults(info)\n  }\n\n  private getTriggerSources(doc: Document, pretext: string): ISource[] {\n    let disabled = doc.getVar('disabled_sources', [])\n    if (this.config.autoTrigger === 'none') return []\n    return sources.getTriggerSources(pretext, doc.filetype, doc.uri, disabled)\n  }\n\n  private async triggerCompletion(doc: Document, info: InsertChange, sources?: ISource[]): Promise<boolean> {\n    let { minTriggerInputLength, autoTrigger } = this.config\n    let { pre } = info\n    // check trigger\n    if (autoTrigger === 'none') return false\n    if (!sources && !this.shouldTrigger(doc, pre)) return false\n    const option = this.getCompleteOption(doc, info)\n    if (sources == null && option.input.length < minTriggerInputLength) {\n      logger.trace(`Suggest not triggered with input \"${option.input}\", minimal trigger input length: ${minTriggerInputLength}`)\n      return false\n    }\n    if (checkIgnoreRegexps(this.config.ignoreRegexps, option.input)) return false\n    await this._startCompletion(option, sources)\n    return true\n  }\n\n  private getCompleteOption(doc: Document, info: InsertChange, manual = false, col?: number): CompleteOption {\n    let { pre } = info\n    let input: string\n    if (Is.number(col)) {\n      input = byteSlice(info.line, col - 1, info.col - 1)\n    } else {\n      input = getInput(doc.chars, info.pre, this.config.asciiCharactersOnly)\n    }\n    let followWord = doc.getStartWord(info.line.slice(info.pre.length))\n    return {\n      input,\n      position: Position.create(info.lnum - 1, info.pre.length),\n      line: info.line,\n      followWord,\n      filetype: doc.filetype,\n      linenr: info.lnum,\n      col: info.col - 1 - byteLength(input),\n      colnr: info.col,\n      bufnr: doc.bufnr,\n      word: input + followWord,\n      changedtick: info.changedtick,\n      synname: '',\n      filepath: doc.schema === 'file' ? URI.parse(doc.uri).fsPath : '',\n      triggerCharacter: manual ? undefined : toText(pre[pre.length - 1])\n    }\n  }\n\n  public addMruItem(): void {\n    let { selectedItem, complete } = this\n    if (!selectedItem) return\n    let character = selectedItem.character\n    this._mru.add(complete.getTrigger(character), selectedItem)\n  }\n\n  public cancelAndClose(close = true): void {\n    clearTimeout(this.triggerTimer)\n    if (!this.complete) return\n    const { linenr, bufnr } = this.complete.option\n    this._onFinish(CompleteFinishKind.Normal, close)\n    events.fire('CompleteDone', [{}, linenr, bufnr]).catch(onUnexpectedError)\n  }\n\n  private _onFinish(kind: CompleteFinishKind, close: boolean) {\n    this.floating.cancel()\n    let inserted = kind === CompleteFinishKind.Confirm || this.popupEvent?.inserted\n    if (inserted) this.addMruItem()\n    let doc = this.complete.document\n    events.completing = false\n    this.cancel()\n    doc._forceSync()\n    if (close) this.nvim.call('coc#pum#_close', [], true)\n  }\n\n  public async stop(kind: CompleteFinishKind, close = false): Promise<void> {\n    let { complete } = this\n    if (complete == null) return\n    const item = this.selectedItem\n    const resolved = complete.resolveItem(item)\n    const option = complete.option\n    this._onFinish(kind, close)\n    if (resolved && kind == CompleteFinishKind.Confirm) {\n      await this.confirmCompletion(resolved.source, resolved.item, option)\n    }\n    events.fire('CompleteDone', [toCompleteDoneItem(item, resolved?.item), option.linenr, option.bufnr]).catch(onUnexpectedError)\n  }\n\n  private async confirmCompletion(source: ISource, item: CompleteItem, option: CompleteOption): Promise<void> {\n    await this.floating.resolveItem(source, item, option, false)\n    if (!Is.func(source.onCompleteDone)) return\n    let { insertMode, snippetsSupport } = this.config\n    let opt: CompleteDoneOption = Object.assign({ insertMode, snippetsSupport }, option)\n    await Promise.resolve(source.onCompleteDone(item, opt))\n  }\n\n  private async onInsertEnter(bufnr: number): Promise<void> {\n    if (!this.config.triggerAfterInsertEnter || this.config.autoTrigger !== 'always') return\n    let doc = workspace.getDocument(bufnr)\n    if (!doc || !doc.attached) return\n    let change = await this.nvim.call('coc#util#change_info') as InsertChange\n    change.pre = byteSlice(change.line, 0, change.col - 1)\n    await this.triggerCompletion(doc, change)\n  }\n\n  public shouldTrigger(doc: Document, pre: string): boolean {\n    let { autoTrigger } = this.config\n    if (autoTrigger == 'none') return false\n    if (sources.shouldTrigger(pre, doc.filetype, doc.uri)) return true\n    if (autoTrigger !== 'always') return false\n    return true\n  }\n\n  public async filterResults(info?: InsertChange): Promise<void> {\n    let { complete, option, pretext } = this\n    let search = getResumeInput(option, pretext)\n    if (search == null || !complete) {\n      this.cancelAndClose()\n      return\n    }\n    let items = await complete.filterResults(search)\n    // cancelled or have inserted text\n    if (items === undefined || !this.option) return\n    let doc = workspace.getDocument(option.bufnr)\n    // trigger completion when trigger source available\n    if (info && info.insertChar && items.length == 0) {\n      let triggerSources = this.getTriggerSources(doc, pretext)\n      if (triggerSources.length > 0) {\n        await this.triggerCompletion(doc, info, triggerSources)\n        return\n      }\n    }\n    if (items.length == 0) {\n      let last = search.slice(-1)\n      if (!complete.isCompleting || last.length === 0 || !doc.chars.isKeywordChar(last)) {\n        this.cancelAndClose()\n      }\n      return\n    }\n    this.activeItems = items\n    this.pum.show(items, search, this.option)\n  }\n\n  public cancel(): void {\n    if (this.complete != null) {\n      this.complete.dispose()\n      this.complete = null\n    }\n    if (this.triggerTimer != null) {\n      clearTimeout(this.triggerTimer)\n      this.triggerTimer = null\n    }\n    this.pretext = undefined\n    this.activeItems = []\n    this.popupEvent = undefined\n  }\n\n  public dispose(): void {\n    disposeAll(this.disposables)\n  }\n}\n\nexport default new Completion()\n"
  },
  {
    "path": "src/completion/keywords.ts",
    "content": "'use strict'\nimport { URI } from 'vscode-uri'\nimport events from '../events'\nimport { SyncItem } from '../model/bufferSync'\nimport Document from '../model/document'\nimport { DidChangeTextDocumentParams } from '../types'\nimport { defaultValue } from '../util'\nimport { forEach } from '../util/async'\nimport { isGitIgnored } from '../util/fs'\nimport { CancellationTokenSource } from '../util/protocol'\n\nexport class KeywordsBuffer implements SyncItem {\n  private lineWords: ReadonlyArray<string>[] = []\n  private _gitIgnored = false\n  private tokenSource: CancellationTokenSource | undefined\n  private minimalCharacterLen = 2\n  constructor(private doc: Document, private segmenterLocales: string) {\n    void this.parseWords(segmenterLocales)\n    let uri = URI.parse(doc.uri)\n    if (uri.scheme === 'file') {\n      void isGitIgnored(uri.fsPath).then(ignored => {\n        this._gitIgnored = ignored\n      })\n    }\n  }\n\n  public getWords(): string[] {\n    let res: string[] = []\n    for (let words of this.lineWords) {\n      words.forEach(word => {\n        if (!res.includes(word)) {\n          res.push(word)\n        }\n      })\n    }\n    return res\n  }\n\n  public cancel(): void {\n    if (this.tokenSource) {\n      this.tokenSource.cancel()\n      this.tokenSource = undefined\n    }\n  }\n\n  public async parseWords(segmenterLocales: string | null): Promise<void> {\n    let { lineWords, doc, minimalCharacterLen } = this\n    let { chars } = doc\n    let tokenSource = this.tokenSource = new CancellationTokenSource()\n    let token = tokenSource.token\n    await forEach(doc.textDocument.lines, line => {\n      let words = chars.matchLine(line, segmenterLocales, minimalCharacterLen)\n      lineWords.push(words)\n    }, token, { yieldAfter: 20 })\n  }\n\n  public get bufnr(): number {\n    return this.doc.bufnr\n  }\n\n  public get gitIgnored(): boolean {\n    return this._gitIgnored\n  }\n\n  public onCompleteDone(idx: number): void {\n    let { doc, segmenterLocales, minimalCharacterLen } = this\n    let line = doc.getline(idx)\n    this.lineWords[idx] = doc.chars.matchLine(line, segmenterLocales, minimalCharacterLen)\n  }\n\n  public onChange(e: DidChangeTextDocumentParams): void {\n    if (events.completing || e.contentChanges.length == 0) return\n    let { lineWords, doc, segmenterLocales, minimalCharacterLen } = this\n    let { range, text } = e.contentChanges[0]\n    let { start, end } = range\n    let sl = start.line\n    let el = end.line\n    let del = el - sl\n    let newLines = doc.textDocument.lines.slice(sl, sl + text.split(/\\n/).length)\n    let arr = newLines.map(line => doc.chars.matchLine(line, segmenterLocales, minimalCharacterLen))\n    lineWords.splice(sl, del + 1, ...arr)\n  }\n\n  public *matchWords(line: number): Iterable<string> {\n    let { lineWords } = this\n    if (line >= lineWords.length) line = lineWords.length - 1\n    for (let i = 0; i < lineWords.length; i++) {\n      let idx = i < line ? line - i - 1 : i\n      let words = defaultValue(lineWords[idx], [])\n      for (let word of words) {\n        yield word\n      }\n    }\n  }\n\n  public dispose(): void {\n    this.cancel()\n    this.lineWords = []\n  }\n}\n"
  },
  {
    "path": "src/completion/match.ts",
    "content": "'use strict'\nimport { findIndex } from '../util/array'\nimport { getCharCodes, wordChar, caseMatch } from '../util/fuzzy'\nimport { getNextWord } from '../util/string'\n\n/**\n * Score and positions\n */\nexport type MatchResult = [number, ReadonlyArray<number>] | undefined\n\nexport function caseScore(input: number, curr: number, divide = 1): number {\n  if (input === curr) return 1 / divide\n  if (caseMatch(input, curr)) return 0.5 / divide\n  return 0\n}\n\n/**\n * Rules:\n * - First strict 5, first case match 2.5\n * - First word character strict 2.5, first word character case 2\n * - First fuzzy match strict 1, first fuzzy case 0.5\n * - Follow strict 1, follow case 0.5\n * - Follow word start 1, follow word case 0.75\n * - First fuzzy strict 0.1, first fuzzy case 0.05\n * @public\n * @param {string} word\n * @param {number[]} input\n * @returns {number}\n */\nexport function matchScore(word: string, input: Uint16Array): number {\n  if (input.length == 0 || word.length < input.length) return 0\n  let next = nextScore(getCharCodes(word), 0, input, [])\n  return next == null ? 0 : next[0]\n}\n\nexport function matchScoreWithPositions(word: string, input: Uint16Array): [number, ReadonlyArray<number>] | undefined {\n  if (input.length == 0 || word.length < input.length) return undefined\n  return nextScore(getCharCodes(word), 0, input, [])\n}\n\n/**\n * Return score and positions.\n */\nfunction nextScore(codes: Uint16Array, index: number, inputCodes: Uint16Array, positions: ReadonlyArray<number>, inputIndex = 0): MatchResult {\n  let input = inputCodes[inputIndex]\n  if (input === undefined) return [0, positions]\n  let len = codes.length\n  // let nextCodes = inputCodes.slice(1)\n  let nextIndex = inputIndex + 1\n  // not alphabet\n  if (!wordChar(input)) {\n    let idx = findIndex(codes, input, index)\n    if (idx == -1) return undefined\n    let score = idx == 0 ? 5 : 1\n    let next = nextScore(codes, idx + 1, inputCodes, [...positions, idx], nextIndex)\n    return next === undefined ? undefined : [score + next[0], next[1]]\n  }\n  // check beginning\n  let isStart = index === 0\n  let score = caseScore(input, codes[index], isStart ? 0.2 : 1)\n  if (score > 0) {\n    let next = nextScore(codes, index + 1, inputCodes, [...positions, index], nextIndex)\n    return next === undefined ? undefined : [score + next[0], next[1]]\n  }\n  // check next word\n  let positionMap: Map<number, ReadonlyArray<number>> = new Map()\n  let word = getNextWord(codes, index + 1)\n  if (word != null) {\n    let score = caseScore(input, word[1], isStart ? 0.5 : 1)\n    if (score > 0) {\n      let ps = [...positions, word[0]]\n      if (score === 0.5) score = 0.75\n      let next = nextScore(codes, word[0] + 1, inputCodes, ps, nextIndex)\n      if (next !== undefined) positionMap.set(score + next[0], next[1])\n    }\n  }\n  // find fuzzy\n  for (let i = index + 1; i < len; i++) {\n    let score = caseScore(input, codes[i], isStart ? 1 : 10)\n    if (score > 0) {\n      let next = nextScore(codes, i + 1, inputCodes, [...positions, i], nextIndex)\n      if (next !== undefined) positionMap.set(score + next[0], next[1])\n      break\n    }\n  }\n  if (positionMap.size == 0) {\n    // Try match previous position\n    if (positions.length > 0) {\n      let last = positions[positions.length - 1]\n      if (last > 0 && codes[last] !== input && codes[last - 1] === input) {\n        let ps = positions.slice()\n        ps.splice(positions.length - 1, 0, last - 1)\n        let next = nextScore(codes, last + 1, inputCodes, ps, nextIndex)\n        if (next === undefined) return undefined\n        return [0.5 + next[0], next[1]]\n      }\n    }\n    return undefined\n  }\n  let max = Math.max(...positionMap.keys())\n  return [max, positionMap.get(max)]\n}\n"
  },
  {
    "path": "src/completion/native/around.ts",
    "content": "'use strict'\nimport BufferSync from '../../model/bufferSync'\nimport { waitImmediate } from '../../util'\nimport { CancellationToken } from '../../util/protocol'\nimport { KeywordsBuffer } from '../keywords'\nimport Source from '../source'\nimport { CompleteOption, CompleteResult, ExtendedCompleteItem, ISource } from '../types'\n\nexport class Around extends Source {\n  constructor(private keywords: BufferSync<KeywordsBuffer>) {\n    super({ name: 'around', filepath: __filename })\n  }\n\n  public async doComplete(opt: CompleteOption, token: CancellationToken): Promise<CompleteResult<ExtendedCompleteItem>> {\n    const shouldRun = await this.checkComplete(opt)\n    if (!shouldRun) return null\n    let { bufnr, input, word, linenr, triggerForInComplete } = opt\n    if (input.length === 0) return null\n    await waitImmediate()\n    let buf = this.keywords.getItem(bufnr)\n    if (!buf) return null\n    if (!triggerForInComplete) this.noMatchWords = new Set()\n    if (token.isCancellationRequested) return null\n    let iterable = buf.matchWords(linenr - 1)\n    let items: Set<string> = new Set()\n    let isIncomplete = await this.getResults([iterable], input, word, items, token)\n    return {\n      isIncomplete,\n      items: Array.from(items, word => ({ word }))\n    }\n  }\n}\n\nexport function register(sourceMap: Map<string, ISource>, keywords: BufferSync<KeywordsBuffer>): void {\n  let source = new Around(keywords)\n  sourceMap.set('around', source)\n}\n"
  },
  {
    "path": "src/completion/native/buffer.ts",
    "content": "'use strict'\nimport BufferSync from '../../model/bufferSync'\nimport { waitImmediate } from '../../util'\nimport { CancellationToken } from '../../util/protocol'\nimport { KeywordsBuffer } from '../keywords'\nimport Source from '../source'\nimport { CompleteOption, CompleteResult, ExtendedCompleteItem, ISource } from '../types'\n\nexport class Buffer extends Source {\n  constructor(private keywords: BufferSync<KeywordsBuffer>) {\n    super({ name: 'buffer', filepath: __filename })\n  }\n\n  public get ignoreGitignore(): boolean {\n    return this.getConfig('ignoreGitignore', true)\n  }\n\n  public async doComplete(opt: CompleteOption, token: CancellationToken): Promise<CompleteResult<ExtendedCompleteItem>> {\n    const shouldRun = await this.checkComplete(opt)\n    if (!shouldRun) return null\n    let { bufnr, input, word, triggerForInComplete } = opt\n    if (input.length === 0) return null\n    await waitImmediate()\n    if (!triggerForInComplete) this.noMatchWords = new Set()\n    if (token.isCancellationRequested) return null\n    let iterables: Iterable<string>[] = []\n    for (let buf of this.keywords.items) {\n      if (buf.bufnr === bufnr || (this.ignoreGitignore && buf.gitIgnored)) continue\n      iterables.push(buf.matchWords(0))\n    }\n    let items: Set<string> = new Set()\n    let isIncomplete = await this.getResults(iterables, input, word, items, token)\n    return {\n      isIncomplete, items: Array.from(items).map(s => {\n        return { word: s }\n      })\n    }\n  }\n}\n\nexport function register(sourceMap: Map<string, ISource>, keywords: BufferSync<KeywordsBuffer>): void {\n  let source = new Buffer(keywords)\n  sourceMap.set('buffer', source)\n}\n"
  },
  {
    "path": "src/completion/native/file.ts",
    "content": "'use strict'\nimport { CompletionItemKind } from 'vscode-languageserver-types'\nimport { statAsync } from '../../util/fs'\nimport { fs, minimatch, path, promisify } from '../../util/node'\nimport { isWindows } from '../../util/platform'\nimport { CancellationToken } from '../../util/protocol'\nimport { byteSlice } from '../../util/string'\nimport workspace from '../../workspace'\nimport Source from '../source'\nimport { CompleteOption, CompleteResult, ExtendedCompleteItem, ISource, VimCompleteItem } from '../types'\nconst pathRe = /(?:\\.{0,2}|~|\\$HOME|([\\w]+)|[a-zA-Z]:|)(\\/|\\\\+)(?:[\\u4E00-\\u9FA5\\u00A0-\\u024F\\w .@()-]+(\\/|\\\\+))*(?:[\\u4E00-\\u9FA5\\u00A0-\\u024F\\w .@()-])*$/\nconst invalid_characters = new Set(['#', '<', '$', '+', '%', '>', '!', '`', '&', '*', \"'\", '|', '{', '?', '\"', '=', '}', '@'])\n\ninterface PathOption {\n  pathstr: string\n  part: string\n  startcol: number\n  input: string\n}\n\nexport function resolveEnvVariables(str: string, env = process.env): string {\n  let replaced = str\n  // windows\n  replaced = replaced.replace(/%([^%]+)%/g, (m, n) => env[n] ?? m)\n  // linux and mac\n  replaced = replaced.replace(\n    /\\$([A-Z_]+[A-Z0-9_]*)|\\${([A-Z0-9_]*)}/gi,\n    (m, a, b) => (env[a || b] ?? m)\n  )\n  return replaced\n}\n\nexport function getLastPart(text: string): string | null {\n  let begin = text.length - 1\n  while (begin >= 0) {\n    let curr = text[begin]\n    if (invalid_characters.has(curr)) {\n      begin++\n      break\n    }\n    if (curr == ' ' && text[begin - 1] !== '\\\\') {\n      begin++\n      break\n    }\n    if (begin == 0) break\n    begin--\n  }\n  if (begin >= text.length) return null\n  return text.slice(begin)\n}\n\nexport async function getFileItem(root: string, filename: string): Promise<VimCompleteItem | null> {\n  let f = path.join(root, filename)\n  let stat = await statAsync(f)\n  if (stat) {\n    let dir = stat.isDirectory()\n    let abbr = dir ? filename + '/' : filename\n    let word = filename\n    return { word, abbr, kind: dir ? CompletionItemKind.Folder : CompletionItemKind.File }\n  }\n  return null\n}\n\nexport function filterFiles(files: string[], ignoreHidden: boolean, ignorePatterns: string[] = []): string[] {\n  return files.filter(f => {\n    if (!f || (ignoreHidden && f.startsWith(\".\"))) return false\n    for (let p of ignorePatterns) {\n      if (minimatch(f, p, { dot: true })) return false\n    }\n    return true\n  })\n}\n\nexport function getDirectory(pathstr: string, root: string): string {\n  let part = /[\\\\/]$/.test(pathstr) ? pathstr : path.dirname(pathstr)\n  return path.isAbsolute(pathstr) ? part : path.join(root, part)\n}\n\nexport async function getItemsFromRoot(pathstr: string, root: string, ignoreHidden: boolean, ignorePatterns: string[]): Promise<VimCompleteItem[]> {\n  let res = []\n  let dir = getDirectory(pathstr, root)\n  let stat = await statAsync(dir)\n  if (stat && stat.isDirectory()) {\n    let files = await promisify(fs.readdir)(dir)\n    files = filterFiles(files, ignoreHidden, ignorePatterns)\n    let items = await Promise.all(files.map(filename => getFileItem(dir, filename)))\n    res = res.concat(items)\n  }\n  res = res.filter(item => item != null)\n  return res\n}\n\nexport class File extends Source {\n  constructor(public isWindows: boolean) {\n    super({\n      name: 'file',\n      filepath: __filename\n    })\n  }\n\n  public get triggerCharacters(): string[] {\n    let characters = this.getConfig('triggerCharacters', [])\n    return this.isWindows ? characters : characters.filter(s => s != '\\\\')\n  }\n\n  private getPathOption(opt: CompleteOption): PathOption | null {\n    let { line, colnr } = opt\n    let part = resolveEnvVariables(byteSlice(line, 0, colnr - 1))\n    let filepath = getLastPart(part)\n    if (!filepath || filepath.endsWith('//')) return null\n    let ms = part.match(pathRe)\n    if (ms && ms.length) {\n      const pathstr = workspace.expand(ms[0])\n      let input = ms[0].match(/[^/\\\\]*$/)[0]\n      return { pathstr, part: ms[1], startcol: colnr - input.length - 1, input }\n    }\n    return null\n  }\n\n  public shouldTrim(ext: string): boolean {\n    let trimSameExts = this.getConfig('trimSameExts', [])\n    return trimSameExts.includes(ext)\n  }\n\n  public async getRoot(pathstr: string, part: string, filepath: string, cwd: string): Promise<string | undefined> {\n    let root: string | undefined\n    let dirname = filepath ? path.dirname(filepath) : ''\n    if (pathstr.startsWith(\".\")) {\n      root = filepath ? dirname : cwd\n    } else if (this.isWindows && /^\\w+:/.test(pathstr)) {\n      root = /[\\\\/]$/.test(pathstr) ? pathstr : path.win32.dirname(pathstr)\n    } else if (!this.isWindows && pathstr.startsWith(\"/\")) {\n      root = pathstr.endsWith(\"/\") ? pathstr : path.posix.dirname(pathstr)\n    } else if (part) {\n      let exists = await promisify(fs.exists)(path.join(dirname, part))\n      if (exists) {\n        root = dirname\n      } else {\n        exists = await promisify(fs.exists)(path.join(cwd, part))\n        if (exists) root = cwd\n      }\n    } else {\n      root = cwd\n    }\n    return root\n  }\n\n  public async doComplete(opt: CompleteOption, token: CancellationToken): Promise<CompleteResult<ExtendedCompleteItem>> {\n    const shouldRun = await this.checkComplete(opt)\n    if (!shouldRun) return null\n    let { filepath } = opt\n    let option = this.getPathOption(opt)\n    if (!option || option.startcol < opt.col) return null\n    let { pathstr, part, startcol } = option\n    let startPart = opt.col == startcol ? '' : byteSlice(opt.line, opt.col, startcol)\n    let ext = path.extname(path.basename(filepath))\n    let cwd = await this.nvim.call('getcwd', []) as string\n    let root = await this.getRoot(pathstr, part, filepath, cwd)\n    if (!root || token.isCancellationRequested) return null\n    let items = await getItemsFromRoot(pathstr, root, this.getConfig('ignoreHidden', true), this.getConfig('ignorePatterns', []))\n    let trimExt = this.shouldTrim(ext)\n    return {\n      items: items.map(item => {\n        let ex = path.extname(item.word)\n        item.word = trimExt && ex === ext ? item.word.replace(ext, '') : item.word\n        return {\n          word: `${startPart}${item.word}`,\n          abbr: `${startPart}${item.abbr}`,\n          menu: this.menu\n        }\n      })\n    }\n  }\n}\n\nexport function register(sourceMap: Map<string, ISource>): void {\n  sourceMap.set('file', new File(isWindows))\n}\n"
  },
  {
    "path": "src/completion/pum.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { CompletionItemKind } from 'vscode-languageserver-types'\nimport { matchSpansReverse } from '../model/fuzzyMatch'\nimport { FloatConfig, HighlightItem } from '../types'\nimport { isFalsyOrEmpty } from '../util/array'\nimport { anyScore } from '../util/filter'\nimport * as Is from '../util/is'\nimport { toNumber } from '../util/numbers'\nimport { byteIndex, byteLength, characterIndex, toText } from '../util/string'\nimport workspace from '../workspace'\nimport { CompleteOption, DurationCompleteItem } from './types'\nimport { getKindHighlight, getKindText, highlightOffset, MruLoader, Selection } from './util'\n\nexport interface PumDimension {\n  readonly height: number\n  readonly width: number\n  readonly row: number\n  readonly col: number\n  readonly scrollbar: boolean\n}\n\n// 0 based col start & end\nexport interface HighlightRange {\n  start: number\n  end: number\n  hlGroup: string\n}\n\nexport interface LabelWithDetail {\n  text: string\n  highlights: HighlightRange[]\n}\n\nexport interface BuildConfig {\n  border: boolean\n  abbrWidth: number\n  menuWidth: number\n  kindWidth: number\n  shortcutWidth: number\n}\n\nexport interface PumConfig {\n  width?: number\n  highlights?: HighlightItem[]\n  highlight?: string\n  borderhighlight?: string\n  title?: string\n  winblend?: number\n  shadow?: boolean\n  border?: [number, number, number, number] | undefined\n  rounded?: number\n  reverse?: boolean\n}\n\nexport interface PopupMenuConfig {\n  kindMap: Map<CompletionItemKind, string>\n  defaultKindText: string\n  noselect: boolean\n  selection: Selection\n  enablePreselect: boolean\n  filterOnBackspace: boolean\n  floatConfig: FloatConfig\n  pumFloatConfig?: FloatConfig\n  formatItems: ReadonlyArray<string>\n  labelMaxLength: number\n  reversePumAboveCursor: boolean\n  snippetIndicator: string\n  virtualText: boolean\n  detailMaxLength: number\n  detailField: string\n  reTriggerAfterIndent: boolean\n  invalidInsertCharacters: string[]\n}\n\nexport enum HighlightGroups {\n  PumDetail = 'CocPumDetail',\n  PumDeprecated = 'CocPumDeprecated',\n  PumMenu = 'CocPumMenu',\n  PumShortcut = 'CocPumShortcut',\n  PumSearch = 'CocPumSearch',\n}\n\nexport enum PumItems {\n  Abbr = 'abbr',\n  Menu = 'menu',\n  Kind = 'kind',\n  Shortcut = 'shortcut'\n}\n\nexport default class PopupMenu {\n  private _search = ''\n  private _pumConfig: PumConfig\n  constructor(\n    private config: PopupMenuConfig,\n    private mruLoader: MruLoader\n  ) {\n  }\n\n  private get nvim(): Neovim {\n    return workspace.nvim\n  }\n\n  public get search(): string {\n    return this._search\n  }\n\n  public reset(): void {\n    this._search = ''\n    this._pumConfig = undefined\n  }\n\n  public get pumConfig(): PumConfig {\n    if (this._pumConfig) return this._pumConfig\n    let { floatConfig, pumFloatConfig, reversePumAboveCursor } = this.config\n    if (!pumFloatConfig) pumFloatConfig = floatConfig\n    let obj: PumConfig = {}\n    if (pumFloatConfig.border) {\n      obj.border = [1, 1, 1, 1]\n      obj.rounded = pumFloatConfig.rounded ? 1 : 0\n      obj.borderhighlight = pumFloatConfig.borderhighlight ?? 'CocFloatBorder'\n    }\n    if (Is.string(pumFloatConfig.highlight)) obj.highlight = pumFloatConfig.highlight\n    if (Is.number(pumFloatConfig.winblend)) obj.winblend = pumFloatConfig.winblend\n    if (Is.string(pumFloatConfig.title)) obj.title = pumFloatConfig.title\n    obj.shadow = pumFloatConfig.shadow === true\n    obj.reverse = reversePumAboveCursor === true\n    this._pumConfig = obj\n    return obj\n  }\n\n  private stringWidth(text: string, cache = false): number {\n    return workspace.getDisplayWidth(text, cache)\n  }\n\n  public show(items: DurationCompleteItem[], search: string, option: CompleteOption): void {\n    this._search = search\n    let { noselect, enablePreselect, invalidInsertCharacters, selection, virtualText, kindMap, defaultKindText } = this.config\n    const invalidInsertCodes = invalidInsertCharacters.map(ch => ch.charCodeAt(0))\n    let selectedIndex = enablePreselect ? items.findIndex(o => o.preselect) : -1\n    let maxMru = -1\n    let abbrWidth = 0\n    let menuWidth = 0\n    let kindWidth = 0\n    let shortcutWidth = 0\n    let checkMru = selectedIndex == -1 && !noselect && selection !== Selection.First\n    let labels: LabelWithDetail[] = []\n    let baseCharacter = characterIndex(option.line, option.col)\n    let minCharacter = baseCharacter\n    // abbr kind, menu\n    for (let i = 0; i < items.length; i++) {\n      let item = items[i]\n      if (checkMru) {\n        let n = this.mruLoader.getScore(search, item, selection)\n        if (n > maxMru) {\n          maxMru = n\n          selectedIndex = i\n        }\n      }\n      if (Is.number(item.character) && item.character < minCharacter) {\n        minCharacter = item.character\n      }\n      let label = this.getLabel(item)\n      labels.push(label)\n      abbrWidth = Math.max(this.stringWidth(label.text, true), abbrWidth)\n      if (item.kind) kindWidth = Math.max(this.stringWidth(getKindText(item.kind, kindMap, defaultKindText), true), kindWidth)\n      if (item.menu) menuWidth = Math.max(this.stringWidth(item.menu, true), menuWidth)\n      if (item.shortcut) shortcutWidth = Math.max(this.stringWidth(item.shortcut, true) + 2, shortcutWidth)\n    }\n    if (selectedIndex !== -1 && search.length > 0) {\n      let item = items[selectedIndex]\n      if (!item.word.startsWith(search) && !item.filterText.startsWith(search)) {\n        selectedIndex = -1\n      }\n    }\n    if (!noselect) {\n      selectedIndex = selectedIndex == -1 ? 0 : selectedIndex\n    } else {\n      if (selectedIndex > 0) {\n        let [item] = items.splice(selectedIndex, 1)\n        items.unshift(item)\n        let [label] = labels.splice(selectedIndex, 1)\n        labels.unshift(label)\n      }\n      selectedIndex = -1\n    }\n    let opt = {\n      input: search,\n      index: selectedIndex,\n      bufnr: option.bufnr,\n      line: option.linenr,\n      // col for pum\n      col: option.col,\n      // col for word insert\n      startcol: byteIndex(option.line, minCharacter),\n      virtualText,\n      words: items.map(o => {\n        let character = o.character\n        let start = Math.max(1, option.position.character - character + 1)\n        let word = getInsertWord(o.word, invalidInsertCodes, start)\n        return prefixWord(word, character, option.line, minCharacter)\n      })\n    }\n    let pumConfig = this.pumConfig\n    let lines: string[] = []\n    let highlights: HighlightItem[] = []\n    // create lines and highlights\n    let width = 0\n    let buildConfig: BuildConfig = { border: !!pumConfig.border, menuWidth, abbrWidth, kindWidth, shortcutWidth }\n    this.adjustAbbrWidth(buildConfig)\n    let lowInput = search.toLowerCase()\n    for (let index = 0; index < items.length; index++) {\n      let [displayWidth, text] = this.buildItem(search, lowInput, items[index], labels[index], highlights, index, buildConfig)\n      width = Math.max(width, displayWidth)\n      lines.push(text)\n    }\n    let config: PumConfig = Object.assign({ width, highlights }, pumConfig)\n    this.nvim.call('coc#pum#create', [lines, opt, config], true)\n    this.nvim.redrawVim()\n  }\n\n  private getLabel(item: DurationCompleteItem): LabelWithDetail {\n    let { labelDetails, detail } = item\n    let { snippetIndicator, labelMaxLength, detailField, detailMaxLength } = this.config\n    let label = item.abbr!\n    let hls: HighlightRange[] = []\n    if (item.isSnippet && !label.endsWith(snippetIndicator)) {\n      label = label + snippetIndicator\n    }\n    if (detailField === 'abbr' && detail && !labelDetails && detail.length < detailMaxLength) {\n      labelDetails = { detail: ' ' + detail.replace(/\\r?\\n\\s*/g, ' ') }\n    }\n    if (labelDetails) {\n      let added = (labelDetails.detail ?? '') + (labelDetails.description ? ` ${labelDetails.description}` : '')\n      if (label.length + added.length <= labelMaxLength) {\n        let start = byteLength(label)\n        hls.push({ start, end: start + byteLength(added), hlGroup: HighlightGroups.PumDetail })\n        label = label + added\n        item.detailRendered = true\n      }\n    }\n    if (label.length > labelMaxLength) {\n      label = label.slice(0, labelMaxLength - 1) + '.'\n    }\n    return { text: label, highlights: hls }\n  }\n\n  private adjustAbbrWidth(config: BuildConfig): void {\n    let { formatItems } = this.config\n    let pumwidth = toNumber(workspace.env.pumwidth, 15)\n    let len = 0\n    for (const item of formatItems) {\n      if (item == PumItems.Abbr) {\n        len += config.abbrWidth + 1\n      } else if (item == PumItems.Menu && config.menuWidth) {\n        len += config.menuWidth + 1\n      } else if (item == PumItems.Kind && config.kindWidth) {\n        len += config.kindWidth + 1\n      } else if (item == PumItems.Shortcut && config.shortcutWidth) {\n        len += config.shortcutWidth + 1\n      }\n    }\n    if (len < pumwidth) {\n      config.abbrWidth = config.abbrWidth + pumwidth - len\n    }\n  }\n\n  private buildItem(input: string, lowInput: string, item: DurationCompleteItem, label: LabelWithDetail, hls: HighlightItem[], index: number, config: BuildConfig): [number, string] {\n    // abbr menu kind shortcut\n    let { labelMaxLength, formatItems, kindMap, defaultKindText } = this.config\n    let text = config.border ? '' : ' '\n    let len = byteLength(text)\n    let displayWidth = text.length\n    let append = (str: string, width: number): void => {\n      let s = this.fillWidth(str, width)\n      displayWidth += width\n      len += byteLength(s)\n      text += s\n    }\n    for (const name of formatItems) {\n      switch (name) {\n        case 'abbr': {\n          if (!isFalsyOrEmpty(item.positions)) {\n            let pre = highlightOffset(len, item)\n            if (pre != -1) {\n              positionHighlights(hls, item.filterText, item.positions, pre, index, labelMaxLength)\n            } else {\n              let score = anyScore(input, lowInput, 0, item.abbr, item.abbr.toLowerCase(), 0)\n              positionHighlights(hls, item.abbr, score, len, index, labelMaxLength)\n            }\n          }\n          let abbr = label.text\n          let start = len\n          append(abbr, config.abbrWidth + 1)\n          label.highlights.forEach(hl => {\n            hls.push({\n              hlGroup: hl.hlGroup,\n              lnum: index,\n              colStart: start + hl.start,\n              colEnd: start + hl.end\n            })\n          })\n          if (item.deprecated) {\n            hls.push({\n              hlGroup: HighlightGroups.PumDeprecated,\n              lnum: index,\n              colStart: start,\n              colEnd: len - 1,\n            })\n          }\n          break\n        }\n        case 'menu': {\n          if (config.menuWidth > 0) {\n            let colStart = len\n            append(toText(item.menu), config.menuWidth + 1)\n            if (item.menu) {\n              hls.push({\n                hlGroup: HighlightGroups.PumMenu,\n                lnum: index,\n                colStart,\n                colEnd: colStart + byteLength(item.menu)\n              })\n            }\n          }\n          break\n        }\n        case 'kind':\n          if (config.kindWidth > 0) {\n            let { kind } = item\n            let kindText = getKindText(kind, kindMap, defaultKindText)\n            let colStart = len\n            append(toText(kindText), config.kindWidth + 1)\n            if (kindText) {\n              hls.push({\n                hlGroup: getKindHighlight(kind),\n                lnum: index,\n                colStart,\n                colEnd: colStart + byteLength(kindText)\n              })\n            }\n          }\n          break\n        case 'shortcut':\n          if (config.shortcutWidth > 0) {\n            let colStart = len\n            let shortcut = item.shortcut\n            append(shortcut ? `[${shortcut}]` : '', config.shortcutWidth + 1)\n            if (shortcut) {\n              hls.push({\n                hlGroup: HighlightGroups.PumShortcut,\n                lnum: index,\n                colStart,\n                colEnd: colStart + byteLength(shortcut) + 2\n              })\n            }\n          }\n          break\n      }\n    }\n    return [displayWidth, text]\n  }\n\n  public fillWidth(text: string, width: number): string {\n    let n = width - this.stringWidth(text)\n    return text + ' '.repeat(Math.max(n, 0))\n  }\n}\n\n/**\n * positions is FuzzyScore\n */\nfunction positionHighlights(hls: HighlightItem[], label: string, positions: ArrayLike<number>, pre: number, line: number, max: number): void {\n  for (let span of matchSpansReverse(label, positions, 2, max)) {\n    hls.push({\n      hlGroup: HighlightGroups.PumSearch,\n      lnum: line,\n      colStart: pre + span[0],\n      colEnd: pre + span[1],\n    })\n  }\n}\n\n/**\n * Exclude part with invalid characters.\n */\nexport function getInsertWord(word: string, codes: number[], start: number): string {\n  if (codes.length === 0) return word\n  for (let i = start; i < word.length; i++) {\n    if (codes.includes(word.charCodeAt(i))) {\n      return word.slice(0, i)\n    }\n  }\n  return word\n}\n\n/**\n * Append previous text to word when necessary\n */\nexport function prefixWord(word: string, character: number, line: string, minCharacter: number): string {\n  return minCharacter < character ? line.slice(minCharacter, character) + word : word\n}\n"
  },
  {
    "path": "src/completion/source-language.ts",
    "content": "'use strict'\nimport { CompletionItem, InsertReplaceEdit, Range, TextEdit } from 'vscode-languageserver-types'\nimport commands from '../commands'\nimport { getLineAndPosition } from '../core/ui'\nimport { createLogger } from '../logger'\nimport Document from '../model/document'\nimport { CompletionItemProvider, DocumentSelector } from '../provider'\nimport snippetManager from '../snippets/manager'\nimport { UltiSnippetOption } from '../types'\nimport { pariedCharacters, waitImmediate } from '../util'\nimport { isFalsyOrEmpty, toArray } from '../util/array'\nimport { CancellationError } from '../util/errors'\nimport * as Is from '../util/is'\nimport { toObject } from '../util/object'\nimport { CancellationToken, CompletionTriggerKind } from '../util/protocol'\nimport { characterIndex } from '../util/string'\nimport workspace from '../workspace'\nimport { CompleteDoneOption, CompleteOption, CompleteResult, InsertMode, ISource, ItemDefaults, SourceType } from './types'\nimport { getReplaceRange, isSnippetItem } from './util'\nconst logger = createLogger('source-language')\n\ninterface TriggerContext {\n  line: string\n  lnum: number\n  character: number\n}\n\nexport default class LanguageSource implements ISource<CompletionItem> {\n  public readonly sourceType = SourceType.Service\n  private _enabled = true\n  private itemDefaults: ItemDefaults = {}\n  private hasDefaultRange: boolean\n  // cursor position on trigger\n  private triggerContext: TriggerContext | undefined\n  // Kept Promise for resolve\n  private resolving: WeakMap<CompletionItem, Promise<void>> = new WeakMap()\n  constructor(\n    public readonly name: string,\n    public readonly shortcut: string,\n    private provider: CompletionItemProvider,\n    public readonly documentSelector: DocumentSelector,\n    public readonly triggerCharacters: string[],\n    public readonly allCommitCharacters: string[],\n    public readonly priority: number | undefined\n  ) {\n  }\n\n  public get enable(): boolean {\n    return this._enabled\n  }\n\n  public toggle(): void {\n    this._enabled = !this._enabled\n  }\n\n  public shouldCommit(item: CompletionItem, character: string): boolean {\n    if (this.allCommitCharacters.includes(character)) return true\n    let commitCharacters = toArray(item.commitCharacters ?? this.itemDefaults.commitCharacters)\n    return commitCharacters.includes(character)\n  }\n\n  public async doComplete(option: CompleteOption, token: CancellationToken): Promise<CompleteResult<CompletionItem> | null> {\n    let { triggerCharacter, bufnr, position } = option\n    let triggerKind: CompletionTriggerKind = this.getTriggerKind(option)\n    this.triggerContext = { lnum: position.line, character: position.character, line: option.line }\n    let context: any = { triggerKind, option }\n    if (triggerKind == CompletionTriggerKind.TriggerCharacter) context.triggerCharacter = triggerCharacter\n    let textDocument = workspace.getDocument(bufnr).textDocument\n    await waitImmediate()\n    let result = await Promise.resolve(this.provider.provideCompletionItems(textDocument, position, token, context))\n    if (!result || token.isCancellationRequested) return null\n    let completeItems = Array.isArray(result) ? result : result.items\n    if (!completeItems || completeItems.length == 0) return null\n    let itemDefaults = this.itemDefaults = toObject<ItemDefaults>(result['itemDefaults'])\n    let isIncomplete = Is.isCompletionList(result) ? result.isIncomplete === true : false\n    this.hasDefaultRange = Is.isEditRange(itemDefaults.editRange)\n    return { isIncomplete, items: completeItems, itemDefaults }\n  }\n\n  public onCompleteResolve(item: CompletionItem, opt: CompleteOption | undefined, token: CancellationToken): Promise<void> | void {\n    let hasResolve = Is.func(this.provider.resolveCompletionItem)\n    if (!hasResolve) return\n    let promise = this.resolving.get(item)\n    if (promise) return promise\n    let invalid = false\n    promise = new Promise(async (resolve, reject) => {\n      let disposable = token.onCancellationRequested(() => {\n        this.resolving.delete(item)\n        reject(new CancellationError())\n      })\n      try {\n        let resolved = await Promise.resolve(this.provider.resolveCompletionItem(item, token))\n        disposable.dispose()\n        if (!token.isCancellationRequested) {\n          if (!resolved) {\n            invalid = true\n            this.resolving.delete(item)\n          } else {\n            if (resolved.textEdit) {\n              let character = characterIndex(opt.line, opt.col)\n              resolved.textEdit = fixTextEdit(character, resolved.textEdit)\n            }\n            // addDocumentation(item, completeItem, opt.filetype)\n            Object.assign(item, resolved)\n          }\n        }\n        resolve()\n      } catch (e) {\n        invalid = true\n        this.resolving.delete(item)\n        // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors\n        reject(e)\n      }\n    })\n    if (!invalid) {\n      this.resolving.set(item, promise)\n    }\n    return promise\n  }\n\n  public async onCompleteDone(item: CompletionItem, opt: CompleteDoneOption): Promise<void> {\n    let doc = workspace.getDocument(opt.bufnr)\n    await doc.patchChange()\n    let additionalEdits = !isFalsyOrEmpty(item.additionalTextEdits)\n    let version = doc.version\n    let isSnippet = await this.applyTextEdit(doc, additionalEdits, item, opt)\n    if (additionalEdits) {\n      // move cursor after edit\n      await doc.applyEdits(item.additionalTextEdits, doc.version != version, !isSnippet)\n      if (isSnippet) await snippetManager.selectCurrentPlaceholder()\n    }\n    if (item.command) {\n      if (commands.has(item.command.command)) {\n        void commands.execute(item.command)\n      } else {\n        logger.warn(`Command \"${item.command.command}\" not registered to coc.nvim`)\n      }\n    }\n  }\n\n  private async applyTextEdit(doc: Document, additionalEdits: boolean, item: CompletionItem, option: CompleteDoneOption): Promise<boolean> {\n    let { linenr, col } = option\n    let { character, line } = this.triggerContext\n    let pos = await getLineAndPosition(workspace.nvim)\n    if (pos.line != linenr - 1) return\n    let { textEdit, textEditText, insertText, label } = item\n    let range = getReplaceRange(item, this.itemDefaults?.editRange, undefined, option.insertMode)\n    if (!range) {\n      // create default replace range\n      let end = character + (option.insertMode == InsertMode.Insert ? 0 : option.followWord.length)\n      range = Range.create(pos.line, characterIndex(line, col), pos.line, end)\n    }\n    // replace range must contains cursor position.\n    let invalidRangeEnd = range.end.character < character\n    if (invalidRangeEnd) range.end.character = character\n    let newText = textEdit ? textEdit.newText : (textEditText && this.hasDefaultRange ? textEditText : insertText) ?? label\n    // adjust range by indent\n    let indentCount = fixIndent(line, pos.text, range)\n    // cursor moved count\n    let delta = pos.character - character - indentCount\n    // fix range by count cursor moved to replace insert word on complete done.\n    if (delta !== 0) range.end.character += delta\n    let next = pos.text[range.end.character]\n    if (invalidRangeEnd && next && newText.endsWith(next) && pariedCharacters.get(newText[0]) === next) {\n      range.end.character += 1\n    }\n    if (option.snippetsSupport !== false && isSnippetItem(item, this.itemDefaults)) {\n      let opts = getUltisnipOption(item)\n      let insertTextMode = item.insertTextMode ?? this.itemDefaults.insertTextMode\n      return await snippetManager.insertSnippet(newText, !additionalEdits, range, insertTextMode, opts)\n    }\n    await doc.applyEdits([TextEdit.replace(range, newText)], false, pos)\n    return false\n  }\n\n  private getTriggerKind(opt: CompleteOption): CompletionTriggerKind {\n    let { triggerCharacters } = this\n    let isTrigger = triggerCharacters.includes(opt.triggerCharacter)\n    let triggerKind: CompletionTriggerKind = CompletionTriggerKind.Invoked\n    if (opt.triggerForInComplete) {\n      triggerKind = CompletionTriggerKind.TriggerForIncompleteCompletions\n    } else if (isTrigger) {\n      triggerKind = CompletionTriggerKind.TriggerCharacter\n    }\n    return triggerKind\n  }\n}\n\nexport function getUltisnipOption(item: CompletionItem): UltiSnippetOption | undefined {\n  let opts = item.data?.ultisnip === true ? {} : item.data?.ultisnip\n  return opts ? opts : undefined\n}\n\nexport function fixIndent(line: string, currline: string, range: Range): number {\n  let oldIndent = line.match(/^\\s*/)[0]\n  let newIndent = currline.match(/^\\s*/)[0]\n  if (oldIndent === newIndent) return 0\n  let d = newIndent.length - oldIndent.length\n  range.start.character += d\n  range.end.character += d\n  return d\n}\n\nexport function fixTextEdit(character: number, edit: TextEdit | InsertReplaceEdit): TextEdit | InsertReplaceEdit {\n  if (TextEdit.is(edit)) {\n    if (character < edit.range.start.character) {\n      edit.range.start.character = character\n    }\n  }\n  if (InsertReplaceEdit.is(edit)) {\n    if (character < edit.insert.start.character) {\n      edit.insert.start.character = character\n    }\n    if (character < edit.replace.start.character) {\n      edit.replace.start.character = character\n    }\n  }\n  return edit\n}\n"
  },
  {
    "path": "src/completion/source-vim.ts",
    "content": "'use strict'\nimport { Range } from 'vscode-languageserver-types'\nimport { getLineAndPosition } from '../core/ui'\nimport snippetManager from '../snippets/manager'\nimport { CancellationToken } from '../util/protocol'\nimport { byteSlice, characterIndex } from '../util/string'\nimport workspace from '../workspace'\nimport Source from './source'\nimport * as Is from '../util/is'\nimport { CompleteOption, CompleteResult, ExtendedCompleteItem } from './types'\n\nexport function getMethodName(name: string, names: ReadonlyArray<string>): string | undefined {\n  if (names.includes(name)) return name\n  let key = name[0].toUpperCase() + name.slice(1)\n  if (names.includes(key)) return key\n  throw new Error(`${name} not exists`)\n}\n\nexport function checkInclude(name: string, fns: ReadonlyArray<string>): boolean {\n  if (fns.includes(name)) return true\n  let key = name[0].toUpperCase() + name.slice(1)\n  return fns.includes(key)\n}\n\nexport default class VimSource extends Source {\n\n  private async callOptionalFunc(fname: string, args: any[], isNotify = false): Promise<any> {\n    let exists = checkInclude(fname, this.remoteFns)\n    if (!exists) return null\n    let name = `coc#source#${this.name}#${getMethodName(fname, this.remoteFns)}`\n    if (isNotify) return this.nvim.call(name, args, true)\n    return await this.nvim.call(name, args)\n  }\n\n  public async checkComplete(opt: CompleteOption): Promise<boolean> {\n    let shouldRun = await super.checkComplete(opt)\n    if (!shouldRun) return false\n    if (!checkInclude('should_complete', this.remoteFns)) return true\n    let res = await this.callOptionalFunc('should_complete', [opt])\n    return !!res\n  }\n\n  public async refresh(): Promise<void> {\n    await this.callOptionalFunc('refresh', [])\n  }\n\n  public async insertSnippet(insertText: string, opt: CompleteOption): Promise<void> {\n    let pos = await getLineAndPosition(this.nvim)\n    let { line, col } = opt\n    let oldIndent = line.match(/^\\s*/)[0]\n    let newIndent = pos.text.match(/^\\s*/)[0]\n    // current insert range\n    let range = Range.create(pos.line, characterIndex(line, col) + newIndent.length - oldIndent.length, pos.line, pos.character)\n    await snippetManager.insertSnippet(insertText, true, range)\n  }\n\n  public async onCompleteDone(item: ExtendedCompleteItem, opt: CompleteOption): Promise<void> {\n    if (checkInclude('on_complete', this.remoteFns)) {\n      await this.callOptionalFunc('on_complete', [item], true)\n    } else if (item.isSnippet && item.insertText) {\n      await this.insertSnippet(item.insertText, opt)\n    }\n  }\n\n  public onEnter(bufnr: number): void {\n    let doc = workspace.getDocument(bufnr)\n    if (!doc || !checkInclude('on_enter', this.remoteFns)) return\n    let { filetypes } = this\n    if (filetypes && !filetypes.includes(doc.filetype)) return\n    void this.callOptionalFunc('on_enter', [{\n      bufnr,\n      uri: doc.uri,\n      languageId: doc.filetype\n    }], true)\n  }\n\n  public async doComplete(opt: CompleteOption, token: CancellationToken): Promise<CompleteResult<ExtendedCompleteItem> | null> {\n    let shouldRun = await this.checkComplete(opt)\n    if (!shouldRun) return null\n    let startcol: number | undefined = await this.callOptionalFunc('get_startcol', [opt])\n    if (token.isCancellationRequested) return null\n    let { col, input, line, colnr } = opt\n    if (Is.number(startcol) && startcol >= 0 && startcol !== col) {\n      input = byteSlice(line, startcol, colnr - 1)\n      opt = Object.assign({}, opt, {\n        col: startcol,\n        changed: col - startcol,\n        input\n      })\n    }\n    const vim9 = this.remoteFns.includes('Complete')\n    let vimItems = await this.nvim.callAsync('coc#_do_complete', [this.name, { ...opt, vim9 }]) as (ExtendedCompleteItem | string)[]\n    if (!vimItems || vimItems.length == 0 || token.isCancellationRequested) return null\n    let checkFirst = this.firstMatch && input.length > 0\n    let inputFirst = checkFirst ? input[0].toLowerCase() : ''\n    let items: ExtendedCompleteItem[] = []\n    vimItems.forEach(item => {\n      let obj: ExtendedCompleteItem = Is.string(item) ? { word: item } : item\n      if (checkFirst) {\n        let ch = (obj.filterText ?? obj.word)[0]\n        if (inputFirst && ch.toLowerCase() !== inputFirst) return\n      }\n      if (this.isSnippet) obj.isSnippet = true\n      items.push(obj)\n    })\n    return { items, startcol: Is.number(startcol) ? startcol : undefined }\n  }\n}\n"
  },
  {
    "path": "src/completion/source.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport events from '../events'\nimport { DocumentSelector } from '../provider'\nimport { defaultValue, disposeAll, getConditionValue, waitImmediate } from '../util'\nimport { isFalsyOrEmpty, toArray } from '../util/array'\nimport { ASCII_END } from '../util/constants'\nimport { caseMatch, fuzzyMatch, getCharCodes } from '../util/fuzzy'\nimport * as Is from '../util/is'\nimport { unidecode } from '../util/node'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { isAlphabet } from '../util/string'\nimport workspace from '../workspace'\nimport { CompleteOption, CompleteResult, ExtendedCompleteItem, ISource, SourceConfig, SourceType } from './types'\nconst WORD_PREFIXES = ['_', '$', '-']\nconst WORD_PREFIXES_CODE = [95, 36, 45]\nconst MAX_DURATION = getConditionValue(80, 20)\nconst MAX_COUNT = 50\n\nexport interface SourceConfiguration {\n  readonly priority?: number\n  readonly triggerCharacters?: string[]\n  readonly firstMatch?: boolean\n  readonly triggerPatterns?: string[]\n  readonly shortcut?: string\n  readonly enable?: boolean\n  readonly filetypes?: string[]\n  readonly disableSyntaxes?: string[]\n}\n\nexport default class Source implements ISource<ExtendedCompleteItem> {\n  public readonly name: string\n  public readonly filepath: string\n  public readonly sourceType: SourceType\n  public readonly isSnippet: boolean\n  public readonly documentSelector: DocumentSelector | undefined\n  /**\n   * Words that not match during session\n   * The word that not match previous input would not match further input\n   */\n  protected noMatchWords: Set<string> = new Set()\n  private config: SourceConfiguration\n  private disposables: Disposable[] = []\n  private _disabled = false\n  private defaults: Partial<SourceConfig>\n  constructor(option: Partial<SourceConfig>) {\n    // readonly properties\n    this.name = option.name\n    this.filepath = option.filepath || ''\n    this.sourceType = option.sourceType || SourceType.Native\n    this.isSnippet = !!option.isSnippet\n    this.defaults = option\n    this.documentSelector = option.documentSelector\n    const key = `coc.source.${option.name}`\n    this.config = defaultValue(workspace.initialConfiguration.get(key) as SourceConfiguration, {})\n    workspace.onDidChangeConfiguration(e => {\n      if (e.affectsConfiguration(key)) {\n        this.config = defaultValue(workspace.initialConfiguration.get(key) as SourceConfiguration, {})\n      }\n    }, null, this.disposables)\n    events.on('CompleteDone', () => {\n      this.noMatchWords.clear()\n    }, null, this.disposables)\n  }\n\n  protected get nvim(): Neovim {\n    return workspace.nvim\n  }\n\n  /**\n   * Priority of source, higher priority makes items lower index.\n   */\n  public get priority(): number {\n    return this.getConfig('priority', 1)\n  }\n\n  public get triggerPatterns(): RegExp[] | null {\n    let patterns = this.getConfig<string[]>('triggerPatterns', null)\n    if (isFalsyOrEmpty(patterns)) return null\n    return patterns.map(s => Is.string(s) ? new RegExp(s + '$') : s)\n  }\n\n  /**\n   * When triggerOnly is true, not trigger completion on keyword character insert.\n   */\n  public get triggerOnly(): boolean {\n    let triggerOnly = this.defaults['triggerOnly']\n    if (Is.boolean(triggerOnly)) return triggerOnly\n    return Array.isArray(this.triggerPatterns) && this.triggerPatterns.length > 0\n  }\n\n  public get triggerCharacters(): string[] {\n    return toArray(this.getConfig('triggerCharacters', []))\n  }\n\n  public get firstMatch(): boolean {\n    return this.getConfig('firstMatch', true)\n  }\n\n  public get remoteFns(): string[] {\n    return toArray(this.defaults.remoteFns)\n  }\n\n  public get shortcut(): string {\n    let shortcut = this.getConfig('shortcut', '')\n    return shortcut ? shortcut : this.name.slice(0, 3)\n  }\n\n  public get enable(): boolean {\n    if (this._disabled) return false\n    return this.getConfig('enable', true)\n  }\n\n  public get filetypes(): string[] | null {\n    return this.getConfig('filetypes', null)\n  }\n\n  public get disableSyntaxes(): string[] {\n    return this.getConfig('disableSyntaxes', [])\n  }\n\n  public getConfig<T>(key: string, defaultValue?: T): T | null {\n    let val = this.config[key]\n    if (Is.func(val) || val == null) return defaultValue ?? null\n    return val as T\n  }\n\n  public toggle(): void {\n    this._disabled = !this._disabled\n  }\n\n  public get menu(): string {\n    return ''\n  }\n\n  public async checkComplete(opt: CompleteOption): Promise<boolean> {\n    let { disableSyntaxes } = this\n    if (!isFalsyOrEmpty(disableSyntaxes) && opt.synname) {\n      let synname = opt.synname.toLowerCase()\n      if (disableSyntaxes.findIndex(s => synname.includes(s.toLowerCase())) !== -1) {\n        return false\n      }\n    }\n    let fn = this.defaults['shouldComplete']\n    if (Is.func(fn)) return !!(await Promise.resolve(fn.call(this, opt)))\n    return true\n  }\n\n  public async refresh(): Promise<void> {\n    let fn = this.defaults['refresh']\n    if (Is.func(fn)) await Promise.resolve(fn.call(this))\n  }\n\n  public async onCompleteDone(item: ExtendedCompleteItem, opt: CompleteOption): Promise<void> {\n    let fn = this.defaults['onCompleteDone']\n    if (Is.func(fn)) await Promise.resolve(fn.call(this, item, opt))\n  }\n\n  public async doComplete(opt: CompleteOption, token: CancellationToken): Promise<CompleteResult<ExtendedCompleteItem> | null> {\n    let shouldRun = await this.checkComplete(opt)\n    if (!shouldRun || token.isCancellationRequested) return null\n    let fn = this.defaults['doComplete']\n    return await Promise.resolve(fn.call(this, opt, token))\n  }\n\n  public async onCompleteResolve(item: ExtendedCompleteItem, opt: CompleteOption, token: CancellationToken): Promise<void> {\n    let fn = this.defaults['onCompleteResolve']\n    if (Is.func(fn)) await Promise.resolve(fn.call(this, item, opt, token))\n  }\n\n  /**\n   * Add words to items with timer.\n   */\n  public async getResults(iterables: Iterable<string>[], input: string, exclude: string, items: Set<string>, token: CancellationToken): Promise<boolean> {\n    let { firstMatch, noMatchWords } = this\n    let start = Date.now()\n    let prev = start\n    let len = input.length\n    let firstCode = input.charCodeAt(0)\n    let codes = getCharCodes(input)\n    let ascii = isAlphabet(firstCode)\n    let i = 0\n    for (let iterable of iterables) {\n      for (let w of iterable) {\n        i++\n        if (i % 100 === 0) {\n          let curr = Date.now()\n          if (curr - prev > 15) {\n            await waitImmediate()\n            prev = curr\n          }\n          if (token.isCancellationRequested || curr - start > MAX_DURATION) return true\n        }\n        if ((w.length <= 1 && w.charCodeAt(0) < 255) || w === exclude || items.has(w) || noMatchWords.has(w)) continue\n        if (firstMatch && !firstMatchFuzzy(firstCode, ascii, w)) {\n          noMatchWords.add(w)\n          continue\n        }\n        if (len > 1) {\n          let matched = fuzzyMatch(codes, ascii && w[0].charCodeAt(0) > ASCII_END ? unidecode(w) : w)\n          if (!matched) {\n            noMatchWords.add(w)\n            continue\n          }\n        }\n        items.add(w)\n        if (items.size == MAX_COUNT) return true\n      }\n    }\n    return false\n  }\n\n  public dispose(): void {\n    disposeAll(this.disposables)\n  }\n}\n\nexport function firstMatchFuzzy(firstCode: number, ascii: boolean, word: string) {\n  let ch = word[0]\n  if (ascii && !WORD_PREFIXES_CODE.includes(firstCode) && WORD_PREFIXES.includes(ch)) ch = word[1]\n  if (ascii && ch.charCodeAt(0) > ASCII_END) ch = unidecode(ch)\n  return caseMatch(firstCode, ch.charCodeAt(0))\n}\n"
  },
  {
    "path": "src/completion/sources.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport events from '../events'\nimport extensions from '../extension'\nimport { createLogger } from '../logger'\nimport BufferSync from '../model/bufferSync'\nimport type { CompletionItemProvider, DocumentSelector } from '../provider'\nimport { disposeAll } from '../util'\nimport { intersect, isFalsyOrEmpty, toArray } from '../util/array'\nimport { readFileLines, statAsync } from '../util/fs'\nimport * as Is from '../util/is'\nimport { fs, path, promisify } from '../util/node'\nimport { Disposable } from '../util/protocol'\nimport { toText } from '../util/string'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { KeywordsBuffer } from './keywords'\nimport Source from './source'\nimport LanguageSource from './source-language'\nimport VimSource, { getMethodName } from './source-vim'\nimport { CompleteItem, CompleteOption, ISource, SourceConfig, SourceStat, SourceType } from './types'\nimport { getPriority } from './util'\nconst logger = createLogger('sources')\n\ninterface VimSourceConfig {\n  filetypes?: string[]\n  isSnippet?: boolean\n  firstMatch?: boolean\n  triggerCharacters?: string[]\n  priority?: number\n  shortcut?: string\n  triggerOnly?: boolean\n}\n\nexport class Sources {\n  private sourceMap: Map<string, ISource> = new Map()\n  private disposables: Disposable[] = []\n  private remoteSourcePaths: string[] = []\n  public keywords: BufferSync<KeywordsBuffer>\n\n  public init(): void {\n    this.keywords = workspace.registerBufferSync(doc => {\n      const segmenterLocales = workspace.getConfiguration('suggest', doc).get<string>('segmenterLocales')\n      return new KeywordsBuffer(doc, segmenterLocales)\n    })\n    this.createNativeSources()\n    this.createRemoteSources()\n    events.on('BufEnter', this.onDocumentEnter, this, this.disposables)\n    events.on('CompleteDone', (_item, linenr, bufnr) => {\n      let item = this.keywords.getItem(bufnr)\n      if (item) item.onCompleteDone(linenr - 1)\n    }, null, this.disposables)\n    workspace.onDidRuntimePathChange(newPaths => {\n      for (let p of newPaths) {\n        this.createVimSources(p).catch(logError)\n      }\n    }, null, this.disposables)\n  }\n\n  private get nvim(): Neovim {\n    return workspace.nvim\n  }\n\n  public getKeywordsBuffer(bufnr: number): KeywordsBuffer {\n    return this.keywords.getItem(bufnr)\n  }\n\n  private createNativeSources(): void {\n    void Promise.all([\n      import('../snippets/util').then(m => this.sourceMap.set(m.wordsSource.name, m.wordsSource)),\n      import('./native/around').then(module => { module.register(this.sourceMap, this.keywords) }),\n      import('./native/buffer').then(module => { module.register(this.sourceMap, this.keywords) }),\n      import('./native/file').then(module => { module.register(this.sourceMap) })\n    ])\n  }\n\n  public createLanguageSource(\n    name: string,\n    shortcut: string,\n    selector: DocumentSelector | null,\n    provider: CompletionItemProvider,\n    triggerCharacters: string[],\n    priority?: number,\n    allCommitCharacters?: string[]\n  ): Disposable {\n    let source = new LanguageSource(\n      name,\n      shortcut,\n      provider,\n      selector,\n      toArray(triggerCharacters),\n      toArray(allCommitCharacters),\n      priority)\n    logger.trace('created service source', name)\n    this.sourceMap.set(name, source)\n    return {\n      dispose: () => {\n        this.sourceMap.delete(name)\n      }\n    }\n  }\n\n  public async createVimSourceExtension(filepath: string): Promise<void> {\n    let { nvim } = this\n    try {\n      let name = path.basename(filepath, '.vim')\n      await nvim.command(`source ${filepath.split(path.sep).join(path.posix.sep)}`)\n      let fns = await nvim.call('coc#_remote_fns', name) as string[]\n      let lowercased = fns.map(fn => fn[0].toLowerCase() + fn.slice(1))\n      for (let fn of ['init', 'complete']) {\n        if (!lowercased.includes(fn)) {\n          throw new Error(`function \"coc#source#${name}#${fn}\" not found`)\n        }\n      }\n      let props = await nvim.call(`coc#source#${name}#${getMethodName('init', fns)}`, []) as VimSourceConfig\n      let packageJSON = {\n        name: `coc-vim-source-${name}`,\n        engines: {\n          coc: \">= 0.0.1\"\n        },\n        activationEvents: props.filetypes ? props.filetypes.map(f => `onLanguage:${f}`) : ['*'],\n        contributes: {\n          configuration: {\n            properties: {\n              [`coc.source.${name}.enable`]: {\n                type: 'boolean',\n                default: true\n              },\n              [`coc.source.${name}.firstMatch`]: {\n                type: 'boolean',\n                default: !!props.firstMatch\n              },\n              [`coc.source.${name}.triggerCharacters`]: {\n                type: 'number',\n                default: props.triggerCharacters ?? []\n              },\n              [`coc.source.${name}.priority`]: {\n                type: 'number',\n                default: props.priority ?? 9\n              },\n              [`coc.source.${name}.shortcut`]: {\n                type: 'string',\n                default: props.shortcut ?? name.slice(0, 3).toUpperCase(),\n                description: 'Shortcut text shown in complete menu.'\n              },\n              [`coc.source.${name}.disableSyntaxes`]: {\n                type: 'array',\n                default: [],\n                items: {\n                  type: 'string'\n                }\n              },\n              [`coc.source.${name}.filetypes`]: {\n                type: 'array',\n                default: props.filetypes || null,\n                description: 'Enabled filetypes.',\n                items: {\n                  type: 'string'\n                }\n              }\n            }\n          }\n        }\n      }\n      let isActive = false\n      let extension: any = {\n        id: packageJSON.name,\n        packageJSON,\n        exports: void 0,\n        extensionPath: filepath,\n        activate: () => {\n          isActive = true\n          let source = new VimSource({\n            name,\n            filepath,\n            isSnippet: props.isSnippet,\n            sourceType: SourceType.Remote,\n            triggerOnly: !!props.triggerOnly,\n            remoteFns: fns\n          })\n          this.addSource(source)\n          return Promise.resolve()\n        }\n      }\n      Object.defineProperty(extension, 'isActive', {\n        get: () => isActive\n      })\n      await extensions.manager.registerInternalExtension(extension, () => {\n        isActive = false\n        this.removeSource(name)\n      })\n    } catch (e) {\n      if (!this.nvim.isVim) {\n        let lines = await readFileLines(filepath, 0, 1)\n        if (lines.length > 0 && lines[0].startsWith('vim9script')) return\n      }\n      void window.showErrorMessage(`Error on create vim source from ${filepath}: ${e}`)\n      // logError(err)\n      logger.error(`Error on create vim source from ${filepath}`, e)\n    }\n  }\n\n  private createRemoteSources(): void {\n    let paths = workspace.env.runtimepath.split(',')\n    for (let path of paths) {\n      this.createVimSources(path).catch(logError)\n    }\n  }\n\n  public async createVimSources(pluginPath: string): Promise<void> {\n    if (this.remoteSourcePaths.includes(pluginPath) || !pluginPath) return\n    this.remoteSourcePaths.push(pluginPath)\n    let folder = path.join(pluginPath, 'autoload/coc/source')\n    let stat = await statAsync(folder)\n    if (stat && stat.isDirectory()) {\n      let arr = await promisify(fs.readdir)(folder)\n      let files = arr.filter(s => s.endsWith('.vim')).map(s => path.join(folder, s))\n      await Promise.allSettled(files.map(p => this.createVimSourceExtension(p)))\n    }\n  }\n\n  public get names(): string[] {\n    return Array.from(this.sourceMap.keys())\n  }\n\n  public get sources(): ISource[] {\n    return Array.from(this.sourceMap.values())\n  }\n\n  public has(name): boolean {\n    return this.names.findIndex(o => o == name) != -1\n  }\n\n  public getSource(name: string): ISource | null {\n    return this.sourceMap.get(name) ?? null\n  }\n\n  public shouldCommit(source: ISource | undefined, item: CompleteItem | undefined, commitCharacter: string): boolean {\n    if (!item || source == null || commitCharacter.length === 0) return false\n    if (Is.func(source.shouldCommit)) {\n      return source.shouldCommit(item, commitCharacter)\n    }\n    return false\n  }\n\n  public getSources(opt: CompleteOption): ISource[] {\n    let { source } = opt\n    if (source) return toArray(this.getSource(source))\n    let uri = workspace.getUri(opt.bufnr)\n    return this.getNormalSources(opt.filetype, uri)\n  }\n\n  /**\n   * Get sources should be used without trigger.\n   * @param {string} filetype\n   * @returns {ISource[]}\n   */\n  public getNormalSources(filetype: string, uri: string): ISource[] {\n    let languageIds = filetype.split('.')\n    let res = this.sources.filter(source => {\n      let { filetypes, triggerOnly, documentSelector, enable } = source\n      if (!enable || triggerOnly || (filetypes && !intersect(filetypes, languageIds))) return false\n      if (documentSelector && languageIds.every(filetype => workspace.match(documentSelector, { uri, languageId: filetype }) == 0)) return false\n      return true\n    })\n    return res\n  }\n\n  private checkTrigger(source: ISource, pre: string, character: string): boolean {\n    let { triggerCharacters, triggerPatterns } = source\n    if (!isFalsyOrEmpty(triggerCharacters) && triggerCharacters.includes(character)) {\n      return true\n    }\n    if (!isFalsyOrEmpty(triggerPatterns) && triggerPatterns.findIndex(p => p.test(pre)) !== -1) {\n      return true\n    }\n    return false\n  }\n\n  public shouldTrigger(pre: string, filetype: string, uri: string): boolean {\n    return this.getTriggerSources(pre, filetype, uri).length > 0\n  }\n\n  public getTriggerSources(pre: string, filetype: string, uri: string, disabled: ReadonlyArray<string> = []): ISource[] {\n    if (!pre) return []\n    let character = pre[pre.length - 1]\n    let languageIds = filetype.split('.')\n    return this.sources.filter(source => {\n      let { filetypes, enable, documentSelector, name } = source\n      if (disabled.includes(name)) return false\n      if (!enable || (Array.isArray(filetypes) && !intersect(filetypes, languageIds))) return false\n      if (documentSelector && languageIds.every(languageId => workspace.match(documentSelector, { uri, languageId }) == 0)) {\n        return false\n      }\n      return this.checkTrigger(source, pre, character)\n    })\n  }\n\n  public addSource(source: ISource): Disposable {\n    let { name } = source\n    if (this.names.includes(name)) {\n      logger.warn(`Recreate source ${name}`)\n    }\n    this.sourceMap.set(name, source)\n    return Disposable.create(() => {\n      this.removeSource(source)\n    })\n  }\n\n  public removeSource(source: ISource | string): void {\n    let name = typeof source == 'string' ? source : source.name\n    let obj = typeof source === 'string' ? this.sourceMap.get(source) : source\n    if (obj && typeof obj.dispose === 'function') obj.dispose()\n    this.sourceMap.delete(name)\n  }\n\n  public async refresh(name?: string): Promise<void> {\n    for (let source of this.sources) {\n      if (!name || source.name == name) {\n        if (typeof source.refresh === 'function') {\n          await Promise.resolve(source.refresh())\n        }\n      }\n    }\n  }\n\n  public toggleSource(name: string): void {\n    let source = this.getSource(name)\n    if (source && typeof source.toggle === 'function') {\n      source.toggle()\n    }\n  }\n\n  public sourceStats(): SourceStat[] {\n    let stats: SourceStat[] = []\n    let languageSourcePriority = workspace.initialConfiguration.get<number>('suggest.languageSourcePriority')\n    for (let item of this.sourceMap.values()) {\n      if (item.name === '$words') continue\n      stats.push({\n        name: item.name,\n        priority: getPriority(item, languageSourcePriority),\n        triggerCharacters: toArray(item.triggerCharacters),\n        shortcut: toText(item.shortcut),\n        filetypes: toArray(item.filetypes ?? item.documentSelector?.map(o => Is.string(o) ? o : o.language)),\n        filepath: toText(item.filepath),\n        type: getSourceType(item.sourceType),\n        disabled: !item.enable\n      })\n    }\n    return stats\n  }\n\n  private onDocumentEnter(bufnr: number): void {\n    let { sources } = this\n    for (let s of sources) {\n      if (s.enable && Is.func(s.onEnter)) {\n        s.onEnter(bufnr)\n      }\n    }\n  }\n\n  public createSource(config: SourceConfig): Disposable {\n    if (typeof config.name !== 'string' || typeof config.doComplete !== 'function') {\n      logger.error(`Bad config for createSource:`, config)\n      throw new TypeError(`name and doComplete required for createSource`)\n    }\n    let source = new Source(Object.assign({ sourceType: SourceType.Service } as any, config))\n    return this.addSource(source)\n  }\n\n  public dispose(): void {\n    disposeAll(this.disposables)\n  }\n}\n\nexport function logError(err: any): void {\n  logger.error('Error on source create', err)\n}\n\nexport function getSourceType(sourceType: SourceType): string {\n  if (sourceType === SourceType.Native) return 'native'\n  if (sourceType === SourceType.Remote) return 'remote'\n  return 'service'\n}\n\nexport default new Sources()\n"
  },
  {
    "path": "src/completion/types.ts",
    "content": "import type { CancellationToken, DocumentSelector } from 'vscode-languageserver-protocol'\nimport type { CompletionItem, CompletionItemKind, CompletionItemLabelDetails, InsertTextFormat, InsertTextMode, Position, Range } from 'vscode-languageserver-types'\nimport type { ProviderResult } from '../provider'\nimport type { Documentation } from '../types'\n\nexport type EditRange = Range | { insert: Range, replace: Range }\n\nexport interface ItemDefaults {\n  commitCharacters?: string[]\n  editRange?: EditRange\n  insertTextFormat?: InsertTextFormat\n  insertTextMode?: InsertTextMode\n  data?: any\n}\n\n// option on complete & should_complete\n// what need change? line, col, input, colnr, changedtick\n// word = '', triggerForInComplete = false\nexport interface CompleteOption {\n  readonly position: Position\n  readonly bufnr: number\n  readonly line: string\n  col: number\n  input: string\n  filetype: string\n  readonly filepath: string\n  readonly word: string\n  readonly followWord: string\n  // cursor position\n  colnr: number\n  synname?: string\n  readonly linenr: number\n  readonly source?: string\n  readonly changedtick: number\n  readonly triggerCharacter?: string\n  triggerForInComplete?: boolean\n}\n\nexport interface CompleteDoneOption extends CompleteOption {\n  readonly snippetsSupport: boolean\n  readonly insertMode: InsertMode\n  readonly itemDefaults?: ItemDefaults\n}\n\n// For filter, render and resolve\nexport interface DurationCompleteItem {\n  word: string\n  abbr: string\n  filterText: string\n  readonly source: ISource\n  readonly priority: number\n  readonly shortcut?: string\n  // start character for word insert\n  character: number\n  isSnippet?: boolean\n  insertText?: string\n  // copied from CompleteItem\n  menu?: string\n  kind?: string | CompletionItemKind\n  dup?: boolean\n  // start character for filter text\n  delta: number\n  preselect?: boolean\n  sortText?: string\n  deprecated?: boolean\n  detail?: string\n  labelDetails?: CompletionItemLabelDetails\n  // Generated\n  localBonus?: number\n  score?: number\n  positions?: ReadonlyArray<number>\n  /**\n   * labelDetail rendered after label\n   */\n  detailRendered?: boolean\n}\n\nexport interface VimCompleteItem {\n  word: string\n  abbr?: string\n  menu?: string\n  info?: string\n  kind?: string | CompletionItemKind\n  equal?: number\n  dup?: number\n  preselect?: boolean\n  user_data?: string\n  detail?: string\n}\n\nexport interface ExtendedCompleteItem extends VimCompleteItem {\n  deprecated?: boolean\n  labelDetails?: CompletionItemLabelDetails\n  sortText?: string\n  filterText?: string\n  // could be snippet\n  insertText?: string\n  isSnippet?: boolean\n  documentation?: Documentation[]\n}\n\nexport enum InsertMode {\n  Insert = 'insert',\n  Replace = 'replace',\n}\n\nexport interface CompleteConfig {\n  asciiMatch: boolean\n  insertMode: InsertMode\n  autoTrigger: string\n  filterGraceful: boolean\n  snippetsSupport: boolean\n  languageSourcePriority: number\n  triggerCompletionWait: number\n  minTriggerInputLength: number\n  triggerAfterInsertEnter: boolean\n  acceptSuggestionOnCommitCharacter: boolean\n  maxItemCount: number\n  timeout: number\n  localityBonus: boolean\n  highPrioritySourceLimit: number\n  lowPrioritySourceLimit: number\n  removeDuplicateItems: boolean\n  removeCurrentWord: boolean\n  defaultSortMethod: SortMethod\n  asciiCharactersOnly: boolean\n  enableFloat: boolean\n  ignoreRegexps: ReadonlyArray<string>\n}\n\nexport enum SortMethod {\n  None = 'none',\n  Alphabetical = 'alphabetical',\n  Length = 'length'\n}\n\n/**\n * Item returned from source\n */\nexport type CompleteItem = ExtendedCompleteItem | CompletionItem\n\nexport interface CompleteResult<T extends CompleteItem> {\n  items: T[]\n  isIncomplete?: boolean\n  itemDefaults?: Readonly<ItemDefaults>\n  startcol?: number\n}\n\nexport enum CompleteFinishKind {\n  Normal = '',\n  Confirm = 'confirm',\n  Cancel = 'cancel',\n}\n\nexport type CompleteDoneItem = CompleteItem & {\n  readonly word: string\n  readonly source: string\n  readonly user_data: string\n}\n\nexport enum SourceType {\n  Native,\n  Remote,\n  Service,\n}\n\nexport interface SourceStat {\n  name: string\n  priority: number\n  triggerCharacters: string[]\n  type: string\n  shortcut: string\n  filepath: string\n  disabled: boolean\n  filetypes: string[]\n}\n\nexport interface SourceConfig<T extends ExtendedCompleteItem = ExtendedCompleteItem> {\n  name: string\n  triggerOnly?: boolean\n  isSnippet?: boolean\n  sourceType?: SourceType\n  filepath?: string\n  documentSelector?: DocumentSelector\n  firstMatch?: boolean\n  remoteFns?: string[]\n  refresh?(): Promise<void>\n  toggle?(): void\n  onEnter?(bufnr: number): void\n  shouldComplete?(opt: CompleteOption): ProviderResult<boolean>\n  doComplete(opt: CompleteOption, token: CancellationToken): ProviderResult<CompleteResult<T>>\n  onCompleteResolve?(item: T, opt: CompleteOption, token: CancellationToken): ProviderResult<void>\n  onCompleteDone?(item: T, opt: CompleteOption): ProviderResult<void>\n  shouldCommit?(item: T, character: string): boolean\n}\n\nexport interface ISource<T extends CompleteItem = CompleteItem> {\n  name: string\n  enable?: boolean\n  shortcut?: string\n  priority?: number\n  sourceType?: SourceType\n  remoteFns?: string[]\n  triggerCharacters?: string[]\n  triggerOnly?: boolean\n  triggerPatterns?: RegExp[]\n  disableSyntaxes?: string[]\n  isSnippet?: boolean\n  filetypes?: string[]\n  documentSelector?: DocumentSelector\n  filepath?: string\n  firstMatch?: boolean\n  refresh?(): Promise<void>\n  toggle?(): void\n  onEnter?(bufnr: number): void\n  shouldComplete?(opt: CompleteOption): ProviderResult<boolean>\n  doComplete(opt: CompleteOption, token: CancellationToken): ProviderResult<CompleteResult<T>>\n  onCompleteResolve?(item: T, opt: CompleteOption, token: CancellationToken): ProviderResult<void>\n  onCompleteDone?(item: T, opt: CompleteDoneOption): ProviderResult<void>\n  shouldCommit?(item: T, character: string): boolean\n  dispose?(): void\n}\n"
  },
  {
    "path": "src/completion/util.ts",
    "content": "'use strict'\nimport { CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionItemTag, InsertReplaceEdit, InsertTextFormat, Range } from 'vscode-languageserver-types'\nimport { InsertChange } from '../events'\nimport { Chars, sameScope } from '../model/chars'\nimport { SnippetParser } from '../snippets/parser'\nimport { Documentation } from '../types'\nimport { pariedCharacters } from '../util'\nimport { isFalsyOrEmpty } from '../util/array'\nimport { CharCode } from '../util/charCode'\nimport { ASCII_END } from '../util/constants'\nimport * as Is from '../util/is'\nimport { LRUCache } from '../util/map'\nimport { unidecode } from '../util/node'\nimport { isEmpty, toObject } from '../util/object'\nimport { byteIndex, byteSlice, characterIndex, getUnicodeClass, isLowSurrogate, toText } from '../util/string'\nimport { CompleteDoneItem, CompleteItem, CompleteOption, DurationCompleteItem, EditRange, ExtendedCompleteItem, InsertMode, ISource, ItemDefaults } from './types'\n\ntype MruItem = Pick<Readonly<DurationCompleteItem>, 'kind' | 'filterText' | 'source'>\ntype PartialOption = Pick<CompleteOption, 'col' | 'colnr' | 'line' | 'position'>\nexport type OptionForWord = Pick<Readonly<CompleteOption>, 'line' | 'position'>\n\nexport enum Selection {\n  First = 'first',\n  RecentlyUsed = 'recentlyUsed',\n  RecentlyUsedByPrefix = 'recentlyUsedByPrefix'\n}\n\nconst INVALID_WORD_CHARS = [CharCode.LineFeed, CharCode.CarriageReturn]\nconst DollarSign = '$'\nconst QuestionMark = '?'\nconst MAX_CODE_POINT = 1114111\nconst MAX_MRU_ITEMS = 100\nconst DEFAULT_HL_GROUP = 'CocSymbolDefault'\n\nexport interface ConvertOption {\n  readonly insertMode: InsertMode\n  readonly source: ISource\n  readonly priority: number\n  readonly range: Range\n  readonly itemDefaults?: ItemDefaults\n  readonly asciiMatch?: boolean\n}\n\nconst highlightsMap = {\n  [CompletionItemKind.Text]: 'CocSymbolText',\n  [CompletionItemKind.Method]: 'CocSymbolMethod',\n  [CompletionItemKind.Function]: 'CocSymbolFunction',\n  [CompletionItemKind.Constructor]: 'CocSymbolConstructor',\n  [CompletionItemKind.Field]: 'CocSymbolField',\n  [CompletionItemKind.Variable]: 'CocSymbolVariable',\n  [CompletionItemKind.Class]: 'CocSymbolClass',\n  [CompletionItemKind.Interface]: 'CocSymbolInterface',\n  [CompletionItemKind.Module]: 'CocSymbolModule',\n  [CompletionItemKind.Property]: 'CocSymbolProperty',\n  [CompletionItemKind.Unit]: 'CocSymbolUnit',\n  [CompletionItemKind.Value]: 'CocSymbolValue',\n  [CompletionItemKind.Enum]: 'CocSymbolEnum',\n  [CompletionItemKind.Keyword]: 'CocSymbolKeyword',\n  [CompletionItemKind.Snippet]: 'CocSymbolSnippet',\n  [CompletionItemKind.Color]: 'CocSymbolColor',\n  [CompletionItemKind.File]: 'CocSymbolFile',\n  [CompletionItemKind.Reference]: 'CocSymbolReference',\n  [CompletionItemKind.Folder]: 'CocSymbolFolder',\n  [CompletionItemKind.EnumMember]: 'CocSymbolEnumMember',\n  [CompletionItemKind.Constant]: 'CocSymbolConstant',\n  [CompletionItemKind.Struct]: 'CocSymbolStruct',\n  [CompletionItemKind.Event]: 'CocSymbolEvent',\n  [CompletionItemKind.Operator]: 'CocSymbolOperator',\n  [CompletionItemKind.TypeParameter]: 'CocSymbolTypeParameter',\n}\n\nexport function useAscii(input: string): boolean {\n  return input.length > 0 && input.charCodeAt(0) < ASCII_END\n}\n\nexport function getKindText(kind: string | CompletionItemKind, kindMap: Map<CompletionItemKind, string>, defaultKindText: string): string {\n  return Is.number(kind) ? kindMap.get(kind) ?? defaultKindText : kind\n}\n\nexport function getKindHighlight(kind: string | number): string {\n  return Is.number(kind) ? highlightsMap[kind] ?? DEFAULT_HL_GROUP : DEFAULT_HL_GROUP\n}\n\nexport function getPriority(source: ISource, defaultValue: number): number {\n  if (Is.number(source.priority)) {\n    return source.priority\n  }\n  return defaultValue\n}\n\nexport function getDetail(item: CompletionItem, filetype: string): { filetype: string, content: string } | undefined {\n  const { detail, labelDetails, label } = item\n  if (!isEmpty(labelDetails)) {\n    let content = (labelDetails.detail ?? '') + (labelDetails.description ? ` ${labelDetails.description}` : '')\n    return { filetype: 'txt', content }\n  }\n  if (detail && detail !== label) {\n    let isText = /^[\\w-\\s.,\\t\\n]+$/.test(detail)\n    return { filetype: isText ? 'txt' : filetype, content: detail }\n  }\n  return undefined\n}\n\n/**\n * Return 1 when next is inserted as paried character\n */\nexport function deltaCount(info: InsertChange): number {\n  if (!info.insertChar || !info.insertChars) return 0\n  if (info.insertChars.length != 2) return 0\n  let pre = info.pre\n  let last = pre[pre.length - 1]\n  if (last !== info.insertChars[0] || !pariedCharacters.has(last)) return 0\n  let next = info.line[pre.length]\n  if (!next || pariedCharacters.get(last) != next) return 0\n  return 1\n}\n\nexport function toCompleteDoneItem(selected: DurationCompleteItem | undefined, item: CompleteItem | undefined): CompleteDoneItem | object {\n  if (!item || !selected) return {}\n  return Object.assign({\n    word: selected.word,\n    abbr: selected.abbr,\n    kind: selected.kind,\n    menu: selected.menu,\n    source: selected.source.name,\n    isSnippet: selected.isSnippet,\n    user_data: `${selected.source.name}:0`\n  }, item)\n}\n\nexport function getDocumentations(completeItem: CompleteItem, filetype: string, detailRendered = false): Documentation[] {\n  let docs: Documentation[] = []\n  if (Is.isCompletionItem(completeItem)) {\n    let { documentation } = completeItem\n    if (!detailRendered) {\n      let doc = getDetail(completeItem, filetype)\n      if (doc) docs.push(doc)\n    }\n    if (documentation) {\n      if (typeof documentation == 'string') {\n        docs.push({ filetype: 'txt', content: documentation })\n      } else if (documentation.value) {\n        docs.push({\n          filetype: documentation.kind == 'markdown' ? 'markdown' : 'txt',\n          content: documentation.value\n        })\n      }\n    }\n  } else {\n    if (completeItem.documentation) {\n      docs = completeItem.documentation\n    } else if (completeItem.info) {\n      docs.push({ content: completeItem.info, filetype: 'txt' })\n    }\n  }\n  return docs\n}\n\nexport function getResumeInput(option: PartialOption | undefined, pretext: string): string {\n  if (!option) return null\n  const { line, col } = option\n  const start = characterIndex(line, col)\n  const pl = pretext.length\n  if (pl < start) return null\n  for (let i = 0; i < start; i++) {\n    // should not change content before start col.\n    if (pretext.charCodeAt(i) !== line.charCodeAt(i)) {\n      return null\n    }\n  }\n  return byteSlice(pretext, option.col)\n}\n\nexport function checkIgnoreRegexps(ignoreRegexps: ReadonlyArray<string>, input: string): boolean {\n  if (!ignoreRegexps || ignoreRegexps.length == 0 || input.length == 0) return false\n  return ignoreRegexps.some(regexp => {\n    try {\n      return new RegExp(regexp).test(input)\n    } catch (e) {\n      return false\n    }\n  })\n}\n\nexport function createKindMap(labels: { [key: string]: string }): Map<CompletionItemKind, string> {\n  return new Map([\n    [CompletionItemKind.Text, labels['text'] ?? 'v'],\n    [CompletionItemKind.Method, labels['method'] ?? 'f'],\n    [CompletionItemKind.Function, labels['function'] ?? 'f'],\n    [CompletionItemKind.Constructor, typeof labels['constructor'] == 'function' ? 'f' : labels['con' + 'structor'] ?? ''],\n    [CompletionItemKind.Field, labels['field'] ?? 'm'],\n    [CompletionItemKind.Variable, labels['variable'] ?? 'v'],\n    [CompletionItemKind.Class, labels['class'] ?? 'C'],\n    [CompletionItemKind.Interface, labels['interface'] ?? 'I'],\n    [CompletionItemKind.Module, labels['module'] ?? 'M'],\n    [CompletionItemKind.Property, labels['property'] ?? 'm'],\n    [CompletionItemKind.Unit, labels['unit'] ?? 'U'],\n    [CompletionItemKind.Value, labels['value'] ?? 'v'],\n    [CompletionItemKind.Enum, labels['enum'] ?? 'E'],\n    [CompletionItemKind.Keyword, labels['keyword'] ?? 'k'],\n    [CompletionItemKind.Snippet, labels['snippet'] ?? 'S'],\n    [CompletionItemKind.Color, labels['color'] ?? 'v'],\n    [CompletionItemKind.File, labels['file'] ?? 'F'],\n    [CompletionItemKind.Reference, labels['reference'] ?? 'r'],\n    [CompletionItemKind.Folder, labels['folder'] ?? 'F'],\n    [CompletionItemKind.EnumMember, labels['enumMember'] ?? 'm'],\n    [CompletionItemKind.Constant, labels['constant'] ?? 'v'],\n    [CompletionItemKind.Struct, labels['struct'] ?? 'S'],\n    [CompletionItemKind.Event, labels['event'] ?? 'E'],\n    [CompletionItemKind.Operator, labels['operator'] ?? 'O'],\n    [CompletionItemKind.TypeParameter, labels['typeParameter'] ?? 'T'],\n  ])\n}\n\nexport function indentChanged(event: { word: string } | undefined, cursor: [number, number, string], line: string): boolean {\n  if (!event) return false\n  let pre = byteSlice(cursor[2], 0, cursor[1] - 1)\n  if (pre.endsWith(event.word) && pre.match(/^\\s*/)[0] != line.match(/^\\s*/)[0]) {\n    return true\n  }\n  return false\n}\n\nexport function shouldStop(bufnr: number, info: InsertChange, option: Pick<CompleteOption, 'bufnr' | 'linenr' | 'line' | 'col'>): boolean {\n  let { pre } = info\n  if (pre.length === 0 || getUnicodeClass(pre[pre.length - 1]) === 'space') return true\n  if (option.bufnr != bufnr || option.linenr != info.lnum) return true\n  let text = byteSlice(option.line, 0, option.col)\n  if (!pre.startsWith(text)) return true\n  return false\n}\n\nexport function getInput(chars: Chars, pre: string, asciiCharactersOnly: boolean): string {\n  let len = 0\n  let prev: number | undefined\n  for (let i = pre.length - 1; i >= 0; i--) {\n    let code = pre.charCodeAt(i)\n    let word = isWordCode(chars, code, asciiCharactersOnly)\n    if (!word || (prev !== undefined && !sameScope(prev, code))) {\n      break\n    }\n    len += 1\n    prev = code\n  }\n  return len == 0 ? '' : pre.slice(-len)\n}\n\nexport function isWordCode(chars: Chars, code: number, asciiCharactersOnly: boolean): boolean {\n  if (!chars.isKeywordCode(code)) return false\n  if (isLowSurrogate(code)) return false\n  if (asciiCharactersOnly && code >= 255) return false\n  return true\n}\n\nexport function shouldIndent(indentkeys: string, pretext: string): boolean {\n  if (!indentkeys || pretext.trim().includes(' ')) return false\n  for (let part of indentkeys.split(',')) {\n    if (part.indexOf('=') > -1) {\n      let [pre, post] = part.split('=')\n      let word = post.startsWith('~') ? post.slice(1) : post\n      if (pretext.length < word.length ||\n        (pretext.length > word.length && !/^\\s/.test(pretext.slice(-word.length - 1)))) {\n        continue\n      }\n      let matched = post.startsWith('~') ? pretext.toLowerCase().endsWith(word) : pretext.endsWith(word)\n      if (!matched) {\n        continue\n      }\n      if (pre == '') return true\n      if (pre == '0' && /^\\s*$/.test(pretext.slice(0, pretext.length - word.length))) {\n        return true\n      }\n    }\n  }\n  return false\n}\n\nexport function highlightOffset<T extends { filterText: string, abbr: string }>(pre: number, item: T): number {\n  let { filterText, abbr } = item\n  let idx = abbr.indexOf(filterText)\n  if (idx == -1) return -1\n  let n = idx == 0 ? 0 : byteIndex(abbr, idx)\n  return pre + n\n}\n\nexport function emptLabelDetails(labelDetails: CompletionItemLabelDetails): boolean {\n  if (!labelDetails) return true\n  return !labelDetails.detail && !labelDetails.description\n}\n\nexport function isSnippetItem(item: CompletionItem, itemDefaults: ItemDefaults): boolean {\n  let insertTextFormat = item.insertTextFormat ?? itemDefaults.insertTextFormat\n  return insertTextFormat === InsertTextFormat.Snippet\n}\n\n/**\n * Snippet or have additionalTextEdits\n */\nexport function hasAction(item: CompletionItem, itemDefaults: ItemDefaults) {\n  return isSnippetItem(item, itemDefaults) || !isFalsyOrEmpty(item.additionalTextEdits)\n}\n\nfunction toValidWord(snippet: string, excludes: number[]): string {\n  for (let i = 0; i < snippet.length; i++) {\n    let code = snippet.charCodeAt(i)\n    if (excludes.includes(code)) {\n      return snippet.slice(0, i)\n    }\n  }\n  return snippet\n}\n\nfunction snippetToWord(text: string, kind: CompletionItemKind | undefined): string {\n  if (kind === CompletionItemKind.Function || kind === CompletionItemKind.Method || kind === CompletionItemKind.Class) {\n    text = text.replace(/\\(.+/, '')\n  }\n  if (!text.includes(DollarSign)) return text\n  return toValidWord((new SnippetParser()).text(text), INVALID_WORD_CHARS)\n}\n\n/**\n * Get the word to insert, it's the word to insert from range or input start position,\n * may not the actual word to insert\n */\nexport function getWord(item: CompletionItem, itemDefaults: ItemDefaults): string {\n  let { label, data, kind } = item\n  if (data && Is.string(data.word)) return data.word\n  let textToInsert = item.textEdit ? item.textEdit.newText : item.insertText\n  if (!Is.string(textToInsert)) return label\n  return isSnippetItem(item, itemDefaults) ? snippetToWord(textToInsert, kind) : toValidWord(textToInsert, INVALID_WORD_CHARS)\n}\n\nexport function getReplaceRange(item: CompletionItem, defaultRange: EditRange | undefined, character?: number, insertMode?: InsertMode): Range | undefined {\n  let editRange: EditRange | undefined\n  if (item.textEdit) {\n    editRange = InsertReplaceEdit.is(item.textEdit) ? item.textEdit : item.textEdit.range\n  } else if (defaultRange) {\n    editRange = defaultRange\n  }\n  let range: Range | undefined\n  if (editRange) {\n    if (Range.is(editRange)) {\n      range = editRange\n    } else {\n      range = insertMode == InsertMode.Insert ? editRange.insert : editRange.replace\n    }\n  }\n  // start character must contains character for completion\n  if (range && Is.number(character) && range.start.character > character) range.start.character = character\n  return range\n}\n\nexport class Converter {\n  // cache the sliced text\n  private previousCache: Map<number, string> = new Map()\n  private postCache: Map<number, string> = new Map()\n  // cursor position\n  private character: number\n  public minCharacter = Number.MAX_SAFE_INTEGER\n  private inputLen: number\n  constructor(\n    // input start character index\n    private readonly inputStart: number,\n    private readonly option: ConvertOption,\n    private readonly opt: OptionForWord\n  ) {\n    this.character = opt.position.character\n    this.inputLen = opt.position.character - inputStart\n  }\n\n  /**\n   * Text before input to replace\n   */\n  public getPrevious(character: number): string {\n    if (this.previousCache.has(character)) return this.previousCache.get(character)\n    let prev = this.opt.line.slice(character, this.inputStart)\n    this.previousCache.set(character, prev)\n    return prev\n  }\n\n  /**\n   * Text after cursor to replace\n   */\n  public getAfter(character: number): string {\n    if (this.postCache.has(character)) return this.postCache.get(character)\n    let text = this.opt.line.slice(this.character, character)\n    this.postCache.set(character, text)\n    return text\n  }\n\n  /**\n   * Exclude follow characters to replace from end of word\n   */\n  public fixFollow(word: string, isSnippet: boolean, endCharacter: number): string {\n    if (isSnippet || endCharacter <= this.character) return word\n    let toReplace = this.getAfter(endCharacter)\n    if (word.length - this.inputLen > toReplace.length && word.endsWith(toReplace)) {\n      return word.slice(0, - toReplace.length)\n    }\n    return word\n  }\n\n  /**\n   * Better filter text with prefix before input removed if exists.\n   */\n  private getDelta(filterText: string, character: number): number {\n    if (character < this.inputStart) {\n      let prev = this.getPrevious(character)\n      if (filterText.startsWith(prev)) return prev.length\n    }\n    return 0\n  }\n\n  public convertToDurationItem(item: CompleteItem): DurationCompleteItem | undefined {\n    if (Is.isCompletionItem(item)) {\n      return this.convertLspCompleteItem(item)\n    } else if (Is.string(item.word)) {\n      return this.convertVimCompleteItem(item)\n    }\n    return undefined\n  }\n\n  private convertVimCompleteItem(item: ExtendedCompleteItem): DurationCompleteItem {\n    const { option } = this\n    const { range, asciiMatch } = option\n    const word = toText(item.word)\n    const character = range.start.character\n    this.minCharacter = Math.min(this.minCharacter, character)\n    let filterText = item.filterText ?? word\n    filterText = asciiMatch ? unidecode(filterText) : filterText\n    const delta = this.getDelta(filterText, character)\n    return {\n      word: this.fixFollow(word, item.isSnippet, range.end.character),\n      abbr: item.abbr ?? word,\n      filterText,\n      delta,\n      character,\n      dup: item.dup === 1,\n      menu: item.menu,\n      kind: item.kind,\n      isSnippet: !!item.isSnippet,\n      insertText: item.insertText,\n      preselect: item.preselect,\n      sortText: item.sortText,\n      deprecated: item.deprecated,\n      detail: item.detail,\n      labelDetails: item.labelDetails,\n      get source() {\n        return option.source\n      },\n      get priority() {\n        return option.source.priority ?? 99\n      },\n      get shortcut() {\n        return toText(option.source.shortcut)\n      }\n    }\n  }\n\n  private convertLspCompleteItem(item: CompletionItem): DurationCompleteItem {\n    const { option, inputStart } = this\n    const label = item.label.trim()\n    const itemDefaults = toObject(option.itemDefaults) as ItemDefaults\n    const word = getWord(item, itemDefaults)\n    const range = getReplaceRange(item, itemDefaults?.editRange, inputStart, this.option.insertMode) ?? option.range\n    const character = range.start.character\n    const data = toObject(item.data)\n    const filterText = item.filterText ?? item.label\n    const delta = this.getDelta(filterText, character)\n    let obj: DurationCompleteItem = {\n      // the word to be insert from it's own character.\n      word: this.fixFollow(word, isSnippetItem(item, itemDefaults), range.end.character),\n      abbr: label,\n      character,\n      delta,\n      kind: item.kind,\n      detail: item.detail,\n      sortText: item.sortText,\n      filterText,\n      preselect: item.preselect === true,\n      deprecated: item.deprecated === true || item.tags?.includes(CompletionItemTag.Deprecated),\n      isSnippet: hasAction(item, itemDefaults),\n      get source() {\n        return option.source\n      },\n      get priority() {\n        return option.priority\n      },\n      get shortcut() {\n        return toText(option.source.shortcut)\n      },\n      dup: data.dup !== 0\n    }\n    this.minCharacter = Math.min(this.minCharacter, character)\n    if (data.optional && !obj.abbr.endsWith(QuestionMark)) obj.abbr += QuestionMark\n    if (!emptLabelDetails(item.labelDetails)) obj.labelDetails = item.labelDetails\n    if (Is.number(item['score']) && !obj.sortText) obj.sortText = String.fromCodePoint(MAX_CODE_POINT - Math.round(item['score']))\n    return obj\n  }\n}\n\nfunction toItemKey(item: MruItem): string {\n  let label = item.filterText\n  let source = item.source.name\n  let kind = item.kind ?? ''\n  return `${label}|${source}|${kind}`\n}\n\nexport class MruLoader {\n  private max = 0\n  private items: LRUCache<string, number> = new LRUCache(MAX_MRU_ITEMS)\n  private itemsNoPrefix: LRUCache<string, number> = new LRUCache(MAX_MRU_ITEMS)\n\n  public getScore(input: string, item: MruItem, selection: Selection): number {\n    let key = toItemKey(item)\n    if (input.length == 0) return this.itemsNoPrefix.get(key) ?? -1\n    if (selection === Selection.RecentlyUsedByPrefix) key = `${input}|${key}`\n    let map = selection === Selection.RecentlyUsed ? this.itemsNoPrefix : this.items\n    return map.get(key) ?? -1\n  }\n\n  public add(prefix: string, item: MruItem): void {\n    if (!Is.number(item.kind)) return\n    let key = toItemKey(item)\n    if (!item.filterText.startsWith(prefix)) {\n      prefix = ''\n    }\n    let line = `${prefix}|${key}`\n    this.items.set(line, this.max)\n    this.itemsNoPrefix.set(key, this.max)\n    this.max += 1\n  }\n\n  public clear(): void {\n    this.max = 0\n    this.items.clear()\n    this.itemsNoPrefix.clear()\n  }\n}\n"
  },
  {
    "path": "src/completion/wordDistance.ts",
    "content": "import { CompletionItemKind, Position, Range, SelectionRange } from 'vscode-languageserver-types'\nimport events from '../events'\nimport languages from '../languages'\nimport { CompleteOption, DurationCompleteItem } from './types'\nimport { binarySearch, isFalsyOrEmpty, toArray } from '../util/array'\nimport { equals, toObject } from '../util/object'\nimport * as Is from '../util/is'\nimport { compareRangesUsingStarts, rangeInRange } from '../util/position'\nimport { CancellationToken } from '../util/protocol'\nimport workspace from '../workspace'\nimport { waitWithToken } from '../util'\n\nexport abstract class WordDistance {\n\n  public static readonly None = new class extends WordDistance {\n    public distance() { return 0 }\n  }()\n\n  public static async create(localityBonus: boolean, opt: Pick<CompleteOption, 'position' | 'bufnr' | 'word' | 'linenr' | 'colnr'>, token: CancellationToken): Promise<WordDistance> {\n    let { position } = opt\n    let cursor: [number, number] = [opt.linenr, opt.colnr]\n    if (!localityBonus) return WordDistance.None\n\n    let doc = workspace.getDocument(opt.bufnr)\n    const selectionRanges = await languages.getSelectionRanges(doc.textDocument, [position], token)\n    if (!selectionRanges || token.isCancellationRequested) return WordDistance.None\n\n    let ranges: Range[] = []\n    const iterate = (r?: SelectionRange) => {\n      if (r && r.range.end.line - r.range.start.line < 2000) {\n        ranges.unshift(r.range)\n        iterate(r.parent)\n      }\n    }\n    iterate(toArray(selectionRanges)[0])\n\n    let wordRanges = ranges.length > 0 ? await Promise.race([waitWithToken(100, token), workspace.computeWordRanges(opt.bufnr, ranges[0], token)]) : undefined\n    if (!Is.objectLiteral(wordRanges)) return WordDistance.None\n\n    // remove current word\n    delete wordRanges[opt.word]\n    return new class extends WordDistance {\n      // Unlike VSCode, word insert position is used here\n      public distance(anchor: Position, item: DurationCompleteItem) {\n        if (!equals([events.cursor.lnum, events.cursor.col], cursor)) {\n          return 0\n        }\n        if (item.kind === CompletionItemKind.Keyword || toObject(item.source)['name'] === 'snippets') {\n          return 2 << 20\n        }\n        const wordLines = wordRanges[item.word]\n        if (isFalsyOrEmpty(wordLines)) {\n          return 2 << 20\n        }\n        const idx = binarySearch(wordLines, Range.create(anchor, anchor), compareRangesUsingStarts)\n        const bestWordRange = idx >= 0 ? wordLines[idx] : wordLines[Math.max(0, ~idx - 1)]\n        let blockDistance = ranges.length\n        for (const range of ranges) {\n          if (!rangeInRange(bestWordRange, range)) {\n            break\n          }\n          blockDistance -= 1\n        }\n        return blockDistance\n      }\n    }()\n  }\n\n  public abstract distance(anchor: Position, item: DurationCompleteItem): number\n}\n"
  },
  {
    "path": "src/configuration/configuration.ts",
    "content": "'use strict'\nimport { URI } from 'vscode-uri'\nimport { distinct } from '../util/array'\nimport { isParentFolder, normalizeFilePath, sameFile } from '../util/fs'\nimport { equals } from '../util/object'\nimport { ConfigurationModel } from './model'\nimport { ConfigurationTarget, IConfigurationChange, IConfigurationData, IConfigurationModel, IConfigurationOverrides } from './types'\nimport { compareConfigurationContents, IConfigurationCompareResult, overrideIdentifiersFromKey } from './util'\n\nexport interface IConfigurationValue<T> {\n  readonly defaultValue?: T\n  readonly userValue?: T\n  readonly workspaceValue?: T\n  readonly workspaceFolderValue?: T\n  readonly memoryValue?: T\n  readonly value?: T\n  readonly default?: { value?: T; override?: T }\n  readonly user?: { value?: T; override?: T }\n  readonly workspace?: { value?: T; override?: T }\n  readonly workspaceFolder?: { value?: T; override?: T }\n  readonly memory?: { value?: T; override?: T }\n  readonly overrideIdentifiers?: string[]\n}\n\nexport class FolderConfigutions {\n  private _folderConfigurations: Map<string, ConfigurationModel> = new Map()\n\n  public get keys(): Iterable<string> {\n    return this._folderConfigurations.keys()\n  }\n\n  public has(folder: string): boolean {\n    for (let key of this.keys) {\n      if (sameFile(folder, key)) return true\n    }\n    return false\n  }\n\n  public set(folder: string, model: ConfigurationModel): void {\n    let key = normalizeFilePath(folder)\n    this._folderConfigurations.set(key, model)\n  }\n\n  public get(folder: string): ConfigurationModel | undefined {\n    let key = normalizeFilePath(folder)\n    return this._folderConfigurations.get(key)\n  }\n\n  public delete(folder: string): void {\n    let key = normalizeFilePath(folder)\n    this._folderConfigurations.delete(key)\n  }\n\n  public forEach(fn: (model: ConfigurationModel, key: string) => void): void {\n    this._folderConfigurations.forEach(fn)\n  }\n\n  public getConfigurationByResource(uri: string): { folder: string, model: ConfigurationModel } | undefined {\n    let u = URI.parse(uri)\n    if (u.scheme !== 'file') return undefined\n    let folders = Array.from(this._folderConfigurations.keys())\n    folders.sort((a, b) => b.length - a.length)\n    let fullpath = u.fsPath\n    for (let folder of folders) {\n      if (isParentFolder(folder, fullpath, true)) {\n        return { folder, model: this._folderConfigurations.get(folder) }\n      }\n    }\n    return undefined\n  }\n}\n\nexport class Configuration {\n  private _workspaceConsolidatedConfiguration: ConfigurationModel | null = null\n  private _resolvedFolderConfigurations: Map<string, string> = new Map()\n  private _memoryConfigurationByResource: Map<string, ConfigurationModel> = new Map()\n\n  constructor(\n    private _defaultConfiguration: ConfigurationModel,\n    private _userConfiguration: ConfigurationModel,\n    private _workspaceConfiguration: ConfigurationModel = new ConfigurationModel(),\n    private _folderConfigurations: FolderConfigutions = new FolderConfigutions(),\n    private _memoryConfiguration: ConfigurationModel = new ConfigurationModel()\n  ) {\n  }\n\n  public updateValue(key: string, value: any, overrides: IConfigurationOverrides = {}): void {\n    let memoryConfiguration: ConfigurationModel | undefined\n    if (overrides.resource) {\n      memoryConfiguration = this._memoryConfigurationByResource.get(overrides.resource)\n      if (!memoryConfiguration) {\n        memoryConfiguration = new ConfigurationModel()\n        this._memoryConfigurationByResource.set(overrides.resource, memoryConfiguration)\n      }\n    } else {\n      memoryConfiguration = this._memoryConfiguration\n    }\n    if (value === undefined) {\n      memoryConfiguration.removeValue(key)\n    } else {\n      memoryConfiguration.setValue(key, value)\n    }\n    if (!overrides.resource) {\n      this._workspaceConsolidatedConfiguration = null\n    }\n  }\n\n  public hasFolder(folder: string): boolean {\n    return this._folderConfigurations.has(folder)\n  }\n\n  public addFolderConfiguration(folder: string, model: ConfigurationModel, resource?: string): void {\n    this._folderConfigurations.set(folder, model)\n    if (resource) {\n      this._resolvedFolderConfigurations.set(resource, folder)\n    }\n  }\n\n  public deleteFolderConfiguration(fsPath: string): void {\n    this._folderConfigurations.delete(fsPath)\n  }\n\n  private getWorkspaceConsolidateConfiguration(): ConfigurationModel {\n    if (!this._workspaceConsolidatedConfiguration) {\n      this._workspaceConsolidatedConfiguration = this._defaultConfiguration.merge(this._userConfiguration, this._workspaceConfiguration, this._memoryConfiguration)\n      this._workspaceConsolidatedConfiguration = this._workspaceConsolidatedConfiguration.freeze()\n    }\n    return this._workspaceConsolidatedConfiguration\n  }\n\n  /**\n   * Get folder configuration fsPath & model\n   * @param uri folder or file uri\n   */\n  public getFolderConfigurationModelForResource(uri: string): ConfigurationModel | undefined {\n    let folder = this._resolvedFolderConfigurations.get(uri)\n    if (folder) return this._folderConfigurations.get(folder)\n    let conf = this._folderConfigurations.getConfigurationByResource(uri)\n    if (!conf) return undefined\n    this._resolvedFolderConfigurations.set(uri, conf.folder)\n    return conf.model\n  }\n\n  public resolveFolder(uri: string): string | undefined {\n    let folder = this._resolvedFolderConfigurations.get(uri)\n    if (folder) return folder\n    let folders = Array.from(this._folderConfigurations.keys)\n    folders.sort((a, b) => b.length - a.length)\n    for (let folder of folders) {\n      if (isParentFolder(folder, URI.parse(uri).fsPath, true)) {\n        this._resolvedFolderConfigurations.set(uri, folder)\n        return folder\n      }\n    }\n    return undefined\n  }\n\n  private getConsolidatedConfigurationModel(overrides: IConfigurationOverrides): ConfigurationModel {\n    let configuration = this.getWorkspaceConsolidateConfiguration()\n    if (overrides.resource) {\n      let folderConfiguration = this.getFolderConfigurationModelForResource(overrides.resource)\n      if (folderConfiguration) {\n        configuration = configuration.merge(folderConfiguration)\n      }\n      const memoryConfigurationForResource = this._memoryConfigurationByResource.get(overrides.resource)\n      if (memoryConfigurationForResource) {\n        configuration = configuration.merge(memoryConfigurationForResource)\n      }\n    }\n    if (overrides.overrideIdentifier) {\n      configuration = configuration.override(overrides.overrideIdentifier)\n    }\n    return configuration\n  }\n\n  public getValue(section: string | undefined, overrides: IConfigurationOverrides): any {\n    let configuration = this.getConsolidatedConfigurationModel(overrides)\n    return configuration.getValue(section)\n  }\n\n  public inspect<C>(key: string, overrides: IConfigurationOverrides): IConfigurationValue<C> {\n    const consolidateConfigurationModel = this.getConsolidatedConfigurationModel(overrides)\n    const folderConfigurationModel = this.getFolderConfigurationModelForResource(overrides.resource)\n    const memoryConfigurationModel = overrides.resource ? this._memoryConfigurationByResource.get(overrides.resource) || this._memoryConfiguration : this._memoryConfiguration\n\n    const defaultValue = overrides.overrideIdentifier ? this._defaultConfiguration.freeze().override(overrides.overrideIdentifier).getValue<C>(key) : this._defaultConfiguration.freeze().getValue<C>(key)\n    const userValue = overrides.overrideIdentifier ? this._userConfiguration.freeze().override(overrides.overrideIdentifier).getValue<C>(key) : this._userConfiguration.freeze().getValue<C>(key)\n    const workspaceValue = overrides.overrideIdentifier ? this._workspaceConfiguration.freeze().override(overrides.overrideIdentifier).getValue<C>(key) : this._workspaceConfiguration.freeze().getValue<C>(key)\n\n    const workspaceFolderValue = folderConfigurationModel ? overrides.overrideIdentifier ? folderConfigurationModel.freeze().override(overrides.overrideIdentifier).getValue<C>(key) : folderConfigurationModel.freeze().getValue<C>(key) : undefined\n    const memoryValue = overrides.overrideIdentifier ? memoryConfigurationModel.override(overrides.overrideIdentifier).getValue<C>(key) : memoryConfigurationModel.getValue<C>(key)\n    const value = consolidateConfigurationModel.getValue<C>(key)\n    const overrideIdentifiers: string[] = distinct(consolidateConfigurationModel.overrides.map(override => override.identifiers).flat()).filter(overrideIdentifier => consolidateConfigurationModel.getOverrideValue(key, overrideIdentifier) !== undefined)\n\n    return {\n      defaultValue,\n      userValue,\n      workspaceValue,\n      workspaceFolderValue,\n      memoryValue,\n      value,\n      default: defaultValue !== undefined ? { value: this._defaultConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this._defaultConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined,\n      user: userValue !== undefined ? { value: this._userConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this._userConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined,\n      workspace: workspaceValue !== undefined ? { value: this._workspaceConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this._workspaceConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined,\n      workspaceFolder: workspaceFolderValue !== undefined ? { value: folderConfigurationModel?.freeze().getValue(key), override: overrides.overrideIdentifier ? folderConfigurationModel?.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined,\n      memory: memoryValue !== undefined ? { value: memoryConfigurationModel.getValue(key), override: overrides.overrideIdentifier ? memoryConfigurationModel.getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined,\n      overrideIdentifiers: overrideIdentifiers.length ? overrideIdentifiers : undefined\n    }\n  }\n\n  public get defaults(): ConfigurationModel {\n    return this._defaultConfiguration\n  }\n\n  public get user(): ConfigurationModel {\n    return this._userConfiguration\n  }\n\n  public get workspace(): ConfigurationModel {\n    return this._workspaceConfiguration\n  }\n\n  public get memory(): ConfigurationModel {\n    return this._memoryConfiguration\n  }\n\n  public getConfigurationModel(target: ConfigurationTarget, folder?: string): ConfigurationModel {\n    switch (target) {\n      case ConfigurationTarget.Default:\n        return this._defaultConfiguration\n      case ConfigurationTarget.User:\n        return this._userConfiguration\n      case ConfigurationTarget.Workspace:\n        return this._workspaceConfiguration\n      case ConfigurationTarget.WorkspaceFolder:\n        return this._folderConfigurations.get(folder) ?? new ConfigurationModel()\n      default:\n        return this._memoryConfiguration\n    }\n  }\n\n  public updateFolderConfiguration(folder: string, model: ConfigurationModel): void {\n    this._folderConfigurations.set(folder, model)\n  }\n\n  public updateUserConfiguration(model: ConfigurationModel): void {\n    this._userConfiguration = model\n    this._workspaceConsolidatedConfiguration = null\n  }\n\n  public updateWorkspaceConfiguration(model: ConfigurationModel): void {\n    this._workspaceConfiguration = model\n    this._workspaceConsolidatedConfiguration = null\n  }\n\n  public updateDefaultConfiguration(model: ConfigurationModel): void {\n    this._defaultConfiguration = model\n    this._workspaceConsolidatedConfiguration = null\n  }\n\n  public updateMemoryConfiguration(model: ConfigurationModel): void {\n    this._memoryConfiguration = model\n    this._workspaceConsolidatedConfiguration = null\n  }\n\n  public compareAndUpdateMemoryConfiguration(memory: ConfigurationModel): IConfigurationChange {\n    const { added, updated, removed, overrides } = compare(this._memoryConfiguration, memory)\n    const keys = [...added, ...updated, ...removed]\n    if (keys.length) {\n      this.updateMemoryConfiguration(memory)\n    }\n    return { keys, overrides }\n  }\n\n  public compareAndUpdateUserConfiguration(user: ConfigurationModel): IConfigurationChange {\n    const { added, updated, removed, overrides } = compare(this._userConfiguration, user)\n    const keys = [...added, ...updated, ...removed]\n    if (keys.length) {\n      this.updateUserConfiguration(user)\n    }\n    return { keys, overrides }\n  }\n\n  public compareAndUpdateDefaultConfiguration(defaults: ConfigurationModel, keys?: string[]): IConfigurationChange {\n    const overrides: [string, string[]][] = []\n    if (!keys) {\n      const { added, updated, removed } = compare(this._defaultConfiguration, defaults)\n      keys = [...added, ...updated, ...removed]\n    }\n    for (const key of keys) {\n      for (const overrideIdentifier of overrideIdentifiersFromKey(key)) {\n        const fromKeys = this._defaultConfiguration.getKeysForOverrideIdentifier(overrideIdentifier)\n        const toKeys = defaults.getKeysForOverrideIdentifier(overrideIdentifier)\n        const keys = [\n          ...toKeys.filter(key => fromKeys.indexOf(key) === -1),\n          ...fromKeys.filter(key => toKeys.indexOf(key) === -1),\n          ...fromKeys.filter(key => !equals(this._defaultConfiguration.override(overrideIdentifier).getValue(key), defaults.override(overrideIdentifier).getValue(key)))\n        ]\n        overrides.push([overrideIdentifier, keys])\n      }\n    }\n    this.updateDefaultConfiguration(defaults)\n    return { keys, overrides }\n  }\n\n  public compareAndUpdateWorkspaceConfiguration(workspaceConfiguration: ConfigurationModel): IConfigurationChange {\n    const { added, updated, removed, overrides } = compare(this._workspaceConfiguration, workspaceConfiguration)\n    const keys = [...added, ...updated, ...removed]\n    if (keys.length) {\n      this.updateWorkspaceConfiguration(workspaceConfiguration)\n    }\n    return { keys, overrides }\n  }\n\n  public compareAndUpdateFolderConfiguration(folder: string, folderConfiguration: ConfigurationModel): IConfigurationChange {\n    const currentFolderConfiguration = this._folderConfigurations.get(folder)\n    const { added, updated, removed, overrides } = compare(currentFolderConfiguration, folderConfiguration)\n    const keys = [...added, ...updated, ...removed]\n    if (keys.length || !currentFolderConfiguration) {\n      this.updateFolderConfiguration(folder, folderConfiguration)\n    }\n    return { keys, overrides }\n  }\n\n  public compareAndDeleteFolderConfiguration(folder: string): IConfigurationChange {\n    const folderConfig = this._folderConfigurations.get(folder)\n    if (!folderConfig) return\n    this.deleteFolderConfiguration(folder)\n    const { added, updated, removed, overrides } = compare(folderConfig, undefined)\n    return { keys: [...added, ...updated, ...removed], overrides }\n  }\n\n  public allKeys(): string[] {\n    const keys: Set<string> = new Set<string>()\n    this._defaultConfiguration.freeze().keys.forEach(key => keys.add(key))\n    this._userConfiguration.freeze().keys.forEach(key => keys.add(key))\n    this._workspaceConfiguration.freeze().keys.forEach(key => keys.add(key))\n    this._folderConfigurations.forEach(folderConfiguration => folderConfiguration.freeze().keys.forEach(key => keys.add(key)))\n    return [...keys.values()]\n  }\n\n  public toData(): IConfigurationData {\n    let { _defaultConfiguration, _memoryConfiguration, _userConfiguration, _workspaceConfiguration, _folderConfigurations } = this\n    let folders: [string, IConfigurationModel][] = []\n    _folderConfigurations.forEach((model, fsPath) => {\n      folders.push([fsPath, model.toJSON()])\n    })\n    return {\n      defaults: _defaultConfiguration.toJSON(),\n      user: _userConfiguration.toJSON(),\n      workspace: _workspaceConfiguration.toJSON(),\n      folders,\n      memory: _memoryConfiguration.toJSON()\n    }\n  }\n\n  public static parse(data: IConfigurationData): Configuration {\n    const defaultConfiguration = this.parseConfigurationModel(data.defaults)\n    const userConfiguration = this.parseConfigurationModel(data.user)\n    const workspaceConfiguration = this.parseConfigurationModel(data.workspace)\n    const folderConfigurations: FolderConfigutions = new FolderConfigutions()\n    const memoryConfiguration = this.parseConfigurationModel(data.memory)\n    data.folders.forEach(value => {\n      folderConfigurations.set(value[0], this.parseConfigurationModel(value[1]))\n    })\n    return new Configuration(defaultConfiguration, userConfiguration, workspaceConfiguration, folderConfigurations, memoryConfiguration)\n  }\n\n  private static parseConfigurationModel(model: IConfigurationModel): ConfigurationModel {\n    return new ConfigurationModel(model.contents, model.keys, model.overrides).freeze()\n  }\n}\n\nfunction compare(from: ConfigurationModel | undefined, to: ConfigurationModel | undefined): IConfigurationCompareResult {\n  const { added, removed, updated } = compareConfigurationContents(to, from)\n  const overrides: [string, string[]][] = []\n\n  const fromOverrideIdentifiers = from?.getAllOverrideIdentifiers() ?? []\n  const toOverrideIdentifiers = to?.getAllOverrideIdentifiers() ?? []\n\n  if (to) {\n    const addedOverrideIdentifiers = toOverrideIdentifiers.filter(key => !fromOverrideIdentifiers.includes(key))\n    for (const identifier of addedOverrideIdentifiers) {\n      overrides.push([identifier, to.getKeysForOverrideIdentifier(identifier)])\n    }\n  }\n\n  if (from) {\n    const removedOverrideIdentifiers = fromOverrideIdentifiers.filter(key => !toOverrideIdentifiers.includes(key))\n    for (const identifier of removedOverrideIdentifiers) {\n      overrides.push([identifier, from.getKeysForOverrideIdentifier(identifier)])\n    }\n  }\n\n  if (to && from) {\n    for (const identifier of fromOverrideIdentifiers) {\n      if (toOverrideIdentifiers.includes(identifier)) {\n        const result = compareConfigurationContents({ contents: from.getOverrideValue(undefined, identifier) || {}, keys: from.getKeysForOverrideIdentifier(identifier) }, { contents: to.getOverrideValue(undefined, identifier) || {}, keys: to.getKeysForOverrideIdentifier(identifier) })\n        overrides.push([identifier, [...result.added, ...result.removed, ...result.updated]])\n      }\n    }\n  }\n\n  return { added, removed, updated, overrides }\n}\n"
  },
  {
    "path": "src/configuration/event.ts",
    "content": "'use strict'\nimport { equals } from '../util/object'\nimport { Configuration } from './configuration'\nimport { ConfigurationModel } from './model'\nimport type { ConfigurationResourceScope, ConfigurationTarget, IConfigurationChange, IConfigurationChangeEvent, IConfigurationData } from './types'\nimport { scopeToOverrides, toValuesTree } from './util'\n\nexport class ConfigurationChangeEvent implements IConfigurationChangeEvent {\n\n  private readonly affectedKeysTree: any\n  public readonly affectedKeys: string[]\n  public source: ConfigurationTarget\n  // public sourceConfig: any\n\n  constructor(public readonly change: IConfigurationChange,\n    private readonly previous: IConfigurationData | undefined,\n    private readonly currentConfiguration: Configuration) {\n    const keysSet = new Set<string>()\n    change.keys.forEach(key => keysSet.add(key))\n    change.overrides.forEach(([, keys]) => keys.forEach(key => keysSet.add(key)))\n    this.affectedKeys = [...keysSet.values()]\n\n    const configurationModel = new ConfigurationModel()\n    this.affectedKeys.forEach(key => configurationModel.setValue(key, {}))\n    this.affectedKeysTree = configurationModel.contents\n  }\n\n  private _previousConfiguration: Configuration | undefined = undefined\n  private get previousConfiguration(): Configuration | undefined {\n    if (!this._previousConfiguration && this.previous) {\n      this._previousConfiguration = Configuration.parse(this.previous)\n    }\n    return this._previousConfiguration\n  }\n\n  public affectsConfiguration(section: string, scope?: ConfigurationResourceScope): boolean {\n    let overrides = scope ? scopeToOverrides(scope) : undefined\n    if (this.doesAffectedKeysTreeContains(this.affectedKeysTree, section)) {\n      if (overrides) {\n        const value1 = this.previousConfiguration ? this.previousConfiguration.getValue(section, overrides) : undefined\n        const value2 = this.currentConfiguration.getValue(section, overrides)\n        return !equals(value1, value2)\n      }\n      return true\n    }\n    return false\n  }\n\n  private doesAffectedKeysTreeContains(affectedKeysTree: any, section: string): boolean {\n    let requestedTree = toValuesTree({ [section]: true }, () => {})\n    let key\n    while (typeof requestedTree === 'object' && (key = Object.keys(requestedTree)[0])) { // Only one key should present, since we added only one property\n      affectedKeysTree = affectedKeysTree[key]\n      if (!affectedKeysTree) {\n        return false // Requested tree is not found\n      }\n      requestedTree = requestedTree[key]\n    }\n    return true\n  }\n}\n\nexport class AllKeysConfigurationChangeEvent extends ConfigurationChangeEvent {\n  constructor(configuration: Configuration, source: ConfigurationTarget) {\n    super({ keys: configuration.allKeys(), overrides: [] }, undefined, configuration)\n    this.source = source\n  }\n}\n"
  },
  {
    "path": "src/configuration/index.ts",
    "content": "'use strict'\nimport { Diagnostic } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport defaultSchema from '../../data/schema.json'\nimport { createLogger } from '../logger'\nimport { disposeAll } from '../util'\nimport { isFalsyOrEmpty } from '../util/array'\nimport { CONFIG_FILE_NAME } from '../util/constants'\nimport { getExtensionDefinitions } from '../util/extensionRegistry'\nimport { findUp, normalizeFilePath, sameFile, watchFile } from '../util/fs'\nimport { objectLiteral } from '../util/is'\nimport { IJSONContributionRegistry, Extensions as JSONExtensions } from '../util/jsonRegistry'\nimport { IJSONSchema } from '../util/jsonSchema'\nimport { fs, os, path } from '../util/node'\nimport { deepFreeze, hasOwnProperty, mixin } from '../util/object'\nimport { Disposable, Emitter, Event } from '../util/protocol'\nimport { convertProperties, Registry } from '../util/registry'\nimport { Configuration } from './configuration'\nimport { ConfigurationChangeEvent } from './event'\nimport { ConfigurationModel } from './model'\nimport { ConfigurationModelParser } from './parser'\nimport { allSettings, Extensions, IConfigurationNode, IConfigurationRegistry, resourceSettings } from './registry'\nimport { IConfigurationShape } from './shape'\nimport { ConfigurationInspect, ConfigurationResourceScope, ConfigurationTarget, ConfigurationUpdateTarget, IConfigurationChange, IConfigurationChangeEvent, IConfigurationOverrides, WorkspaceConfiguration } from './types'\nimport { addToValueTree, convertTarget, lookUp, scopeToOverrides } from './util'\nconst logger = createLogger('configurations')\n\nexport const userSettingsSchemaId = 'vscode://schemas/settings/user'\nexport const folderSettingsSchemaId = 'vscode://schemas/settings/folder'\n\nconst jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution)\nconst configuration = Registry.as<IConfigurationRegistry>(Extensions.Configuration)\n\ninterface ConfigurationErrorEvent {\n  uri: string,\n  diagnostics: Diagnostic[]\n}\n\ninterface MarkdownPreference {\n  excludeImages: boolean\n  breaks: boolean\n}\n\nexport default class Configurations {\n  private _watchedFiles: Set<string> = new Set()\n  private _configuration: Configuration\n  private _errors: Map<string, Diagnostic[]> = new Map()\n  private _onError = new Emitter<ConfigurationErrorEvent>()\n  private _onChange = new Emitter<IConfigurationChangeEvent>()\n  private disposables: Disposable[] = []\n  private _initialized = false\n  private cached: IConfigurationNode[] = []\n  private _initialConfiguration: WorkspaceConfiguration\n\n  public readonly onError: Event<ConfigurationErrorEvent> = this._onError.event\n  public readonly onDidChange: Event<IConfigurationChangeEvent> = this._onChange.event\n\n  constructor(\n    private userConfigFile?: string | undefined,\n    private readonly _proxy?: IConfigurationShape,\n    private noWatch = global.__TEST__,\n    cwd = process.cwd()\n  ) {\n    let defaultConfiguration = this.loadDefaultConfigurations()\n    let userConfiguration = this.parseConfigurationModel(this.userConfigFile)\n    this._configuration = new Configuration(defaultConfiguration, userConfiguration)\n    this.watchFile(this.userConfigFile, ConfigurationTarget.User)\n    let filepath = this.folderToConfigfile(cwd)\n    if (filepath) this.addFolderFile(filepath, true)\n    this._initialConfiguration = this.getConfiguration(undefined, null)\n  }\n\n  /**\n   * Contains default, memory and user configuration only\n   */\n  public get initialConfiguration(): WorkspaceConfiguration {\n    return this._initialConfiguration\n  }\n\n  public get markdownPreference(): MarkdownPreference {\n    let preferences = this._initialConfiguration.get('coc.preferences') as any\n    return {\n      excludeImages: preferences.excludeImageLinksInMarkdownDocument,\n      breaks: preferences.enableGFMBreaksInMarkdownDocument\n    }\n  }\n\n  public get errors(): Map<string, Diagnostic[]> {\n    return this._errors\n  }\n\n  public get configuration(): Configuration {\n    return this._configuration\n  }\n\n  public flushConfigurations(): void {\n    this._initialized = true\n    configuration.registerConfigurations(this.cached)\n    this.cached = []\n  }\n\n  public updateConfigurations(add: IConfigurationNode[], remove?: IConfigurationNode[]): void {\n    if (this._initialized) {\n      if (!isFalsyOrEmpty(remove)) {\n        configuration.updateConfigurations({ add, remove })\n      } else {\n        configuration.registerConfigurations(add)\n      }\n    } else {\n      this.cached.push(...add)\n    }\n  }\n\n  private loadDefaultConfigurations(): ConfigurationModel {\n    // register properties and listen events\n    let node: IConfigurationNode = { properties: convertProperties(defaultSchema.properties) }\n    configuration.registerConfiguration(node)\n    configuration.onDidUpdateConfiguration(e => {\n      if (e.properties.length === 0) return\n      // update default configuration with new value\n      const dict = configuration.getConfigurationProperties()\n      const toRemove: string[] = []\n      const root = Object.create(null)\n      const keys: string[] = []\n      for (let key of e.properties) {\n        let def = dict[key]\n        if (def) {\n          keys.push(key)\n          let val = def.default\n          addToValueTree(root, key, val, msg => {\n            logger.error(`Conflict configuration: ${msg}`)\n          })\n        } else {\n          toRemove.push(key)\n        }\n      }\n      const model = this._configuration.defaults.merge(new ConfigurationModel(root, keys))\n      toRemove.forEach(key => { model.removeValue(key) })\n      if (!this._initialized) {\n        // no change event fired\n        this._configuration.updateDefaultConfiguration(model)\n        this._initialConfiguration = this.getConfiguration(undefined, null)\n      } else {\n        this.changeConfiguration(ConfigurationTarget.Default, model, undefined, e.properties)\n      }\n    }, null, this.disposables)\n    let properties = configuration.getConfigurationProperties()\n    let config = {}\n    let keys: string[] = []\n    Object.keys(properties).forEach(key => {\n      let value = properties[key].default\n      keys.push(key)\n      addToValueTree(config, key, value, undefined)\n    })\n    let model = new ConfigurationModel(config, keys)\n    return model\n  }\n\n  public getDescription(key: string): string | undefined {\n    let property = allSettings.properties[key]\n    return property ? property.description : undefined\n  }\n\n  public getJSONSchema(uri: string): IJSONSchema | undefined {\n    if (uri === userSettingsSchemaId) {\n      return {\n        properties: allSettings.properties,\n        patternProperties: allSettings.patternProperties,\n        definitions: Object.assign(getExtensionDefinitions(), defaultSchema.definitions),\n        additionalProperties: false,\n        allowTrailingCommas: true,\n        allowComments: true\n      }\n    }\n    if (uri === folderSettingsSchemaId) {\n      return {\n        properties: resourceSettings.properties,\n        patternProperties: resourceSettings.patternProperties,\n        definitions: Object.assign(getExtensionDefinitions(), defaultSchema.definitions),\n        errorMessage: 'Configuration property may not work as folder configuration',\n        additionalProperties: false,\n        allowTrailingCommas: true,\n        allowComments: true\n      }\n    }\n    let schemas = jsonRegistry.getSchemaContributions().schemas\n    if (hasOwnProperty(schemas, uri)) return schemas[uri]\n    return undefined\n  }\n\n  public parseConfigurationModel(filepath: string | undefined, filecontents?: string): ConfigurationModel {\n    if (!filepath || !fs.existsSync(filepath)) return new ConfigurationModel()\n    let parser = new ConfigurationModelParser(filepath)\n    let content = filecontents || fs.readFileSync(filepath, 'utf8')\n    let uri = URI.file(filepath).toString()\n    parser.parse(content)\n    if (!isFalsyOrEmpty(parser.errors)) {\n      this._errors.set(uri, parser.errors)\n      this._onError.fire({ uri, diagnostics: parser.errors })\n    } else {\n      this._errors.delete(uri)\n      this._onError.fire({ uri, diagnostics: [] })\n    }\n    return parser.configurationModel\n  }\n\n  public folderToConfigfile(folder: string): string | undefined {\n    if (sameFile(folder, os.homedir())) return undefined\n    let filepath = path.join(folder, '.vim', CONFIG_FILE_NAME)\n    if (sameFile(filepath, this.userConfigFile)) return undefined\n    return filepath\n  }\n\n  // change memory configuration\n  public updateMemoryConfig(props: { [key: string]: any }): void {\n    let keys = Object.keys(props)\n    if (!props || keys.length == 0) return\n    let memoryModel = this._configuration.memory.clone()\n    let properties = configuration.getConfigurationProperties()\n    keys.forEach(key => {\n      let val = props[key]\n      if (val === undefined) {\n        memoryModel.removeValue(key)\n      } else if (properties[key] != null) {\n        memoryModel.setValue(key, val)\n      } else if (objectLiteral(val)) {\n        for (let k of Object.keys(val)) {\n          memoryModel.setValue(`${key}.${k}`, val[k])\n        }\n      } else {\n        memoryModel.setValue(key, val)\n      }\n    })\n    this.changeConfiguration(ConfigurationTarget.Memory, memoryModel, undefined, keys)\n  }\n\n  /**\n   * Add new folder config file.\n   */\n  public addFolderFile(configFilePath: string, fromCwd = false, resource?: string): boolean {\n    let folder = normalizeFilePath(path.resolve(configFilePath, '../..'))\n    if (this._configuration.hasFolder(folder) || !fs.existsSync(configFilePath)) return false\n    let configFile: string\n    try {\n      configFile = fs.readFileSync(configFilePath, 'utf8')\n    } catch (_err) {\n      return false\n    }\n    this.watchFile(configFilePath, ConfigurationTarget.WorkspaceFolder)\n    let model = this.parseConfigurationModel(configFilePath, configFile)\n    this._configuration.addFolderConfiguration(folder, model, resource)\n    logger.info(`Add folder configuration from ${fromCwd ? 'cwd' : 'file'}:`, configFilePath)\n    return true\n  }\n\n  private watchFile(filepath: string, target: ConfigurationTarget): void {\n    if (!fs.existsSync(filepath) || this._watchedFiles.has(filepath) || this.noWatch) return\n    this._watchedFiles.add(filepath)\n    const folder = ConfigurationTarget.WorkspaceFolder ? normalizeFilePath(path.resolve(filepath, '../..')) : undefined\n    let disposable = watchFile(filepath, () => {\n      let model = this.parseConfigurationModel(filepath)\n      this.changeConfiguration(target, model, folder)\n    })\n    this.disposables.push(disposable)\n  }\n\n  /**\n   * Update ConfigurationModel and fire event.\n   */\n  public changeConfiguration(target: ConfigurationTarget, model: ConfigurationModel, folder: string | undefined, keys?: string[]): void {\n    const listOnly = target === ConfigurationTarget.Default && keys && keys.every(key => key.startsWith('list.source'))\n    let configuration = this._configuration\n    let previous = listOnly ? undefined : configuration.toData()\n    let change: IConfigurationChange\n    if (target === ConfigurationTarget.Default) {\n      change = configuration.compareAndUpdateDefaultConfiguration(model, keys)\n    } else if (target === ConfigurationTarget.User) {\n      change = configuration.compareAndUpdateUserConfiguration(model)\n    } else if (target === ConfigurationTarget.Workspace) {\n      change = configuration.compareAndUpdateWorkspaceConfiguration(model)\n    } else if (target === ConfigurationTarget.WorkspaceFolder) {\n      change = configuration.compareAndUpdateFolderConfiguration(folder, model)\n    } else {\n      change = configuration.compareAndUpdateMemoryConfiguration(model)\n    }\n    if (!change || change.keys.length == 0) return\n    if (\n      target !== ConfigurationTarget.WorkspaceFolder,\n      target !== ConfigurationTarget.Workspace\n    ) {\n      this._initialConfiguration = this.getConfiguration(undefined, null)\n    }\n    if (listOnly) return\n    let ev = new ConfigurationChangeEvent(change, previous, configuration)\n    ev.source = target\n    this._onChange.fire(ev)\n  }\n\n  public getDefaultResource(): string | undefined {\n    let root = this._proxy?.root\n    if (!root) return undefined\n    return URI.file(root).toString()\n  }\n\n  /**\n   * Get workspace configuration\n   */\n  public getConfiguration(section?: string, scope?: ConfigurationResourceScope): WorkspaceConfiguration {\n    let configuration = this._configuration\n    let overrides: IConfigurationOverrides = scope ? scopeToOverrides(scope) : { resource: scope === null ? undefined : this.getDefaultResource() }\n    const config = Object.freeze(lookUp(configuration.getValue(undefined, overrides), section))\n\n    const result: WorkspaceConfiguration = {\n      has(key: string): boolean {\n        return typeof lookUp(config, key) !== 'undefined'\n      },\n      get: <T>(key: string, defaultValue?: T) => {\n        let result: T = lookUp(config, key)\n        if (result == null) return defaultValue\n        return result\n      },\n      update: (key: string, value?: any, updateTarget: ConfigurationUpdateTarget | boolean = false): Promise<void> => {\n        const resource = overrides.resource\n        let entry = section ? `${section}.${key}` : key\n        let target: ConfigurationTarget\n        if (typeof updateTarget === 'boolean') {\n          target = updateTarget ? ConfigurationTarget.User : ConfigurationTarget.WorkspaceFolder\n        } else {\n          target = convertTarget(updateTarget)\n        }\n        // let folderConfigFile: string | undefined\n        let folder: string | undefined\n        if (target === ConfigurationTarget.WorkspaceFolder) {\n          folder = this._configuration.resolveFolder(resource) ?? this.resolveWorkspaceFolderForResource(resource)\n          if (!folder) {\n            console.error(`Unable to locate workspace folder configuration for ${resource}`)\n            logger.error(`Unable to locate workspace folder configuration`, resource, Error().stack)\n            return\n          }\n        }\n\n        let model: ConfigurationModel = this._configuration.getConfigurationModel(target, folder).clone()\n        if (value === undefined) {\n          model.removeValue(entry)\n        } else {\n          model.setValue(entry, value)\n        }\n\n        this.changeConfiguration(target, model, folder)\n        let fsPath: string\n        if (target === ConfigurationTarget.WorkspaceFolder) {\n          fsPath = this.folderToConfigfile(folder)\n        } else if (target === ConfigurationTarget.User) {\n          fsPath = this.userConfigFile\n        }\n        return fsPath ? this._proxy?.modifyConfiguration(fsPath, entry, value) : Promise.resolve()\n      },\n      inspect: <T>(key: string): ConfigurationInspect<T> => {\n        key = section ? `${section}.${key}` : key\n        const config = this._configuration.inspect<T>(key, overrides)\n        return {\n          key,\n          defaultValue: config.defaultValue,\n          globalValue: config.userValue,\n          workspaceValue: config.workspaceValue,\n          workspaceFolderValue: config.workspaceFolderValue\n        }\n      }\n    }\n    Object.defineProperty(result, 'has', {\n      enumerable: false\n    })\n    Object.defineProperty(result, 'get', {\n      enumerable: false\n    })\n    Object.defineProperty(result, 'update', {\n      enumerable: false\n    })\n    Object.defineProperty(result, 'inspect', {\n      enumerable: false\n    })\n\n    if (typeof config === 'object') {\n      mixin(result, config, false)\n    }\n    return deepFreeze(result)\n  }\n\n  /**\n   * Resolve folder configuration from uri.\n   */\n  public locateFolderConfigution(uri: string): boolean {\n    let folder = this._configuration.resolveFolder(uri)\n    if (folder) return true\n    let u = URI.parse(uri)\n    if (u.scheme !== 'file') return false\n    let dir = folder = findUp('.vim', u.fsPath)\n    if (!dir) return false\n    folder = path.dirname(dir)\n    let filepath = this.folderToConfigfile(folder)\n    if (filepath) {\n      this.addFolderFile(filepath, false, uri)\n      return true\n    }\n    return false\n  }\n\n  /**\n   * Resolve workspace folder config file path.\n   */\n  public resolveWorkspaceFolderForResource(resource?: string): string | undefined {\n    if (this._proxy && typeof this._proxy.getWorkspaceFolder === 'function') {\n      // fallback to check workspace folder.\n      let uri = this._proxy.getWorkspaceFolder(resource)\n      if (!uri) return undefined\n      let fsPath = uri.fsPath\n      let configFilePath = this.folderToConfigfile(fsPath)\n      if (configFilePath) {\n        if (!fs.existsSync(configFilePath)) {\n          fs.mkdirSync(path.dirname(configFilePath), { recursive: true })\n          fs.writeFileSync(configFilePath, '{}', 'utf8')\n        }\n        this.addFolderFile(configFilePath, false, resource)\n        return fsPath\n      }\n    }\n    return undefined\n  }\n\n  /**\n   * Reset configurations for test, not trigger configuration change event.\n   */\n  public reset(): void {\n    this._errors.clear()\n    let model = new ConfigurationModel()\n    this._configuration.updateMemoryConfiguration(model)\n    this._initialConfiguration = this.getConfiguration(undefined, null)\n  }\n\n  public dispose(): void {\n    this._onError.dispose()\n    this._onChange.dispose()\n    disposeAll(this.disposables)\n  }\n}\n"
  },
  {
    "path": "src/configuration/model.ts",
    "content": "'use strict'\nimport { IConfigurationModel, IOverrides, IStringDictionary } from './types'\nimport { distinct } from '../util/array'\nimport { objectLiteral } from '../util/is'\nimport { deepClone, deepFreeze, equals } from '../util/object'\nimport { addToValueTree, getConfigurationValue, removeFromValueTree } from './util'\n\nexport class ConfigurationModel implements IConfigurationModel {\n\n  private frozen = false\n  private readonly overrideConfigurations = new Map<string, ConfigurationModel>()\n\n  constructor(\n    private _contents: any = {},\n    private readonly _keys: string[] = [],\n    private readonly _overrides: IOverrides[] = []\n  ) {}\n\n  public get contents(): any {\n    return this.checkAndFreeze(this._contents)\n  }\n\n  public get overrides(): IOverrides[] {\n    return this.checkAndFreeze(this._overrides)\n  }\n\n  public get keys(): string[] {\n    return this.checkAndFreeze(this._keys)\n  }\n\n  public get isFrozen(): boolean {\n    return this.frozen\n  }\n\n  private checkAndFreeze<T>(data: T): T {\n    if (this.frozen && !Object.isFrozen(data)) {\n      return deepFreeze(data)\n    }\n    return data\n  }\n\n  public isEmpty(): boolean {\n    return this._keys.length === 0 && Object.keys(this._contents).length === 0 && this._overrides.length === 0\n  }\n\n  public clone(): ConfigurationModel {\n    return new ConfigurationModel(deepClone(this._contents), [...this.keys], deepClone(this.overrides))\n  }\n\n  public toJSON(): IConfigurationModel {\n    return {\n      contents: this.contents,\n      overrides: this.overrides,\n      keys: this.keys\n    }\n  }\n\n  public getValue<V>(section?: string): V {\n    let res = section\n      ? getConfigurationValue<any>(this.contents, section)\n      : this.contents\n    return res\n  }\n\n  public getOverrideValue<V>(section: string | undefined, overrideIdentifier: string): V | undefined {\n    const overrideContents = this.getContentsForOverrideIdentifier(overrideIdentifier)\n    return overrideContents\n      ? section ? getConfigurationValue<any>(overrideContents, section) : overrideContents\n      : undefined\n  }\n\n  public getKeysForOverrideIdentifier(identifier: string): string[] {\n    const keys: string[] = []\n    for (const override of this.overrides) {\n      if (override.identifiers.includes(identifier)) {\n        keys.push(...override.keys)\n      }\n    }\n    return distinct(keys)\n  }\n\n  public getAllOverrideIdentifiers(): string[] {\n    const result: string[] = []\n    for (const override of this.overrides) {\n      result.push(...override.identifiers)\n    }\n    return distinct(result)\n  }\n\n  public override(identifier: string): ConfigurationModel {\n    let overrideConfigurationModel = this.overrideConfigurations.get(identifier)\n    if (!overrideConfigurationModel) {\n      overrideConfigurationModel = this.createOverrideConfigurationModel(identifier)\n      this.overrideConfigurations.set(identifier, overrideConfigurationModel)\n    }\n    return overrideConfigurationModel\n  }\n\n  public merge(...others: ConfigurationModel[]): ConfigurationModel {\n    const contents = deepClone(this._contents)\n    const overrides = deepClone(this._overrides)\n    const keys = [...this._keys]\n\n    for (const other of others) {\n      if (other.isEmpty()) {\n        continue\n      }\n      this.mergeContents(contents, other.contents)\n\n      for (const otherOverride of other.overrides) {\n        const [override] = overrides.filter(o => equals(o.identifiers, otherOverride.identifiers))\n        if (override) {\n          this.mergeContents(override.contents, otherOverride.contents)\n          override.keys.push(...otherOverride.keys)\n          override.keys = distinct(override.keys)\n        } else {\n          overrides.push(deepClone(otherOverride))\n        }\n      }\n      for (const key of other.keys) {\n        if (keys.indexOf(key) === -1) {\n          keys.push(key)\n        }\n      }\n    }\n    return new ConfigurationModel(contents, keys, overrides)\n  }\n\n  public freeze(): ConfigurationModel {\n    this.frozen = true\n    return this\n  }\n\n  private mergeContents(source: any, target: any): void {\n    for (const key of Object.keys(target)) {\n      if (key in source) {\n        if (objectLiteral(source[key]) && objectLiteral(target[key])) {\n          this.mergeContents(source[key], target[key])\n          continue\n        }\n      }\n      source[key] = deepClone(target[key])\n    }\n  }\n\n  // Update methods\n\n  public setValue(key: string, value: any) {\n    this.addKey(key)\n    addToValueTree(this.contents, key, value, e => { console.error(e) })\n  }\n\n  public removeValue(key: string): void {\n    if (this.removeKey(key)) {\n      removeFromValueTree(this.contents, key)\n    }\n  }\n\n  private addKey(key: string): void {\n    let index = this.keys.length\n    for (let i = 0; i < index; i++) {\n      if (key.indexOf(this.keys[i]) === 0) {\n        index = i\n      }\n    }\n    this.keys.splice(index, 1, key)\n  }\n\n  private removeKey(key: string): boolean {\n    const index = this.keys.indexOf(key)\n    if (index !== -1) {\n      this.keys.splice(index, 1)\n      return true\n    }\n    return false\n  }\n\n  private createOverrideConfigurationModel(identifier: string): ConfigurationModel {\n    const overrideContents = this.getContentsForOverrideIdentifier(identifier)\n\n    if (!overrideContents || typeof overrideContents !== 'object' || !Object.keys(overrideContents).length) {\n      // If there are no valid overrides, return self\n      return this\n    }\n\n    const contents: any = {}\n    for (const key of distinct([...Object.keys(this.contents), ...Object.keys(overrideContents)])) {\n\n      let contentsForKey = this.contents[key]\n      const overrideContentsForKey = overrideContents[key]\n\n      // If there are override contents for the key, clone and merge otherwise use base contents\n      if (overrideContentsForKey) {\n        // Clone and merge only if base contents and override contents are of type object otherwise just override\n        if (typeof contentsForKey === 'object' && typeof overrideContentsForKey === 'object') {\n          contentsForKey = deepClone(contentsForKey)\n          this.mergeContents(contentsForKey, overrideContentsForKey)\n        } else {\n          contentsForKey = overrideContentsForKey\n        }\n      }\n\n      contents[key] = contentsForKey\n    }\n\n    return new ConfigurationModel(contents, this._keys, this.overrides)\n  }\n\n  private getContentsForOverrideIdentifier(identifier: string): any {\n    let contentsForIdentifierOnly: IStringDictionary<any> | null = null\n    let contents: IStringDictionary<any> | null = null\n    const mergeContents = (contentsToMerge: any) => {\n      if (contentsToMerge) {\n        if (contents) {\n          this.mergeContents(contents, contentsToMerge)\n        } else {\n          contents = deepClone(contentsToMerge)\n        }\n      }\n    }\n    for (const override of this.overrides) {\n      if (equals(override.identifiers, [identifier])) {\n        contentsForIdentifierOnly = override.contents\n      } else if (override.identifiers.includes(identifier)) {\n        mergeContents(override.contents)\n      }\n    }\n    // Merge contents of the identifier only at the end to take precedence.\n    mergeContents(contentsForIdentifierOnly)\n    return contents\n  }\n}\n"
  },
  {
    "path": "src/configuration/parser.ts",
    "content": "import { ParseError, ParseErrorCode, visit } from 'jsonc-parser'\nimport { Diagnostic, Range } from 'vscode-languageserver-types'\nimport { createLogger } from '../logger'\nimport { ConfigurationScope, IConfigurationModel, IOverrides } from './types'\nimport { ConfigurationModel } from './model'\nimport { convertErrors, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX, toValuesTree } from './util'\nconst logger = createLogger('parser')\n\nexport interface ConfigurationParseOptions {\n  scopes: ConfigurationScope[] | undefined\n  skipRestricted?: boolean\n}\n\nexport interface ConfigurationParseError {\n  startLine?: number\n  startCharacter?: number\n  length?: number\n  message: string\n}\n\nexport class ConfigurationModelParser {\n  private _raw: any = null\n  private _configurationModel: ConfigurationModel | null = null\n  private _parseErrors: Diagnostic[] = []\n\n  constructor(protected readonly _name: string) {}\n\n  public get configurationModel(): ConfigurationModel {\n    return this._configurationModel || new ConfigurationModel()\n  }\n\n  public get errors(): Diagnostic[] {\n    return this._parseErrors\n  }\n\n  public parse(content: string | null | undefined, options?: ConfigurationParseOptions): void {\n    if (content != null) {\n      const raw = this.doParseContent(content)\n      this.parseRaw(raw, options)\n    }\n  }\n\n  public parseRaw(raw: any, options?: ConfigurationParseOptions): void {\n    this._raw = raw\n    const { contents, keys, overrides } = this.doParseRaw(raw, options)\n    this._configurationModel = new ConfigurationModel(contents, keys, overrides)\n    // this._restrictedConfigurations = restricted || []\n  }\n\n  private doParseContent(content: string): any {\n    let raw: any = {}\n    let currentProperty: string | null = null\n    let currentParent: any = []\n    const previousParents: any[] = []\n    const _errors: ParseError[] = []\n\n    function onValue(value: any) {\n      if (Array.isArray(currentParent)) {\n        (currentParent).push(value)\n      } else if (currentProperty !== null) {\n        currentParent[currentProperty] = value\n      }\n    }\n\n    const visitor = {\n      onObjectBegin: () => {\n        const object = {}\n        onValue(object)\n        previousParents.push(currentParent)\n        currentParent = object\n        currentProperty = null\n      },\n      onObjectProperty: (name: string) => {\n        currentProperty = name\n      },\n      onObjectEnd: () => {\n        currentParent = previousParents.pop()\n      },\n      onArrayBegin: () => {\n        const array: any[] = []\n        onValue(array)\n        previousParents.push(currentParent)\n        currentParent = array\n        currentProperty = null\n      },\n      onArrayEnd: () => {\n        currentParent = previousParents.pop()\n      },\n      onLiteralValue: onValue,\n      onError: (error: ParseErrorCode, offset: number, length: number) => {\n        _errors.push({ error, length, offset })\n      }\n    }\n    if (content) {\n      try {\n        visit(content, visitor, { allowTrailingComma: true, allowEmptyContent: true })\n        raw = currentParent[0] ?? {}\n        if (_errors.length > 0) {\n          this._parseErrors = convertErrors(content, _errors)\n        }\n      } catch (e) {\n        this._parseErrors = [{\n          range: Range.create(0, 0, 0, 0),\n          message: `Error on parse configuration file ${this._name}: ${e}`\n        }]\n      }\n    }\n    return raw\n  }\n\n  protected doParseRaw(raw: any, _options?: ConfigurationParseOptions): IConfigurationModel & { restricted?: string[] } {\n    const onError = (message: string) => {\n      console.error(`Conflict in settings file ${this._name}: ${message}`)\n    }\n    const contents = toValuesTree(raw, onError, true)\n    const keys = Object.keys(raw)\n    const overrides = this.toOverrides(raw, onError)\n    return { contents, keys, overrides, restricted: [] }\n  }\n\n  private toOverrides(raw: any, conflictReporter: (message: string) => void): IOverrides[] {\n    const overrides: IOverrides[] = []\n    for (const key of Object.keys(raw)) {\n      if (OVERRIDE_PROPERTY_REGEX.test(key)) {\n        const overrideRaw: any = {}\n        for (const keyInOverrideRaw in raw[key]) {\n          overrideRaw[keyInOverrideRaw] = raw[key][keyInOverrideRaw]\n        }\n        overrides.push({\n          identifiers: overrideIdentifiersFromKey(key),\n          keys: Object.keys(overrideRaw),\n          contents: toValuesTree(overrideRaw, conflictReporter, true)\n        })\n      }\n    }\n    return overrides\n  }\n}\n"
  },
  {
    "path": "src/configuration/registry.ts",
    "content": "import { distinct } from '../util/array'\nimport { Extensions as JSONExtensions, IJSONContributionRegistry } from '../util/jsonRegistry'\nimport { IJSONSchema } from '../util/jsonSchema'\nimport { toObject } from '../util/object'\nimport { Emitter, Event } from '../util/protocol'\nimport { Registry } from '../util/registry'\nimport { ConfigurationScope, IStringDictionary } from './types'\nimport { getDefaultValue, OVERRIDE_PROPERTY_PATTERN, OVERRIDE_PROPERTY_REGEX } from './util'\n\nconst EXCLUDE_KEYS = ['log-path', 'logPath']\n\nexport const Extensions = {\n  Configuration: 'base.contributions.configuration'\n}\n\nexport interface IConfigurationPropertySchema extends IJSONSchema {\n\n  scope?: ConfigurationScope\n\n  /**\n   * When restricted, value of this configuration will be read only from trusted sources.\n   * For eg., If the workspace is not trusted, then the value of this configuration is not read from workspace settings file.\n   */\n  restricted?: boolean\n\n  /**\n   * When `false` this property is excluded from the registry. Default is to include.\n   */\n  included?: boolean\n\n  /**\n   * Labels for enumeration items\n   */\n  enumItemLabels?: string[]\n}\n\nexport interface IExtensionInfo {\n  id: string\n  displayName?: string\n}\n\nexport interface IConfigurationNode {\n  id?: string\n  properties?: IStringDictionary<IConfigurationPropertySchema>\n  scope?: ConfigurationScope\n  extensionInfo?: IExtensionInfo\n}\n\nexport type IRegisteredConfigurationPropertySchema = IConfigurationPropertySchema & {\n  defaultDefaultValue?: any\n  source?: IExtensionInfo // Source of the Property\n  defaultValueSource?: IExtensionInfo | string // Source of the Default Value\n}\n\nexport interface IConfigurationRegistry {\n\n  /**\n   * Register a configuration to the registry.\n   */\n  registerConfiguration(configuration: IConfigurationNode): void\n\n  /**\n   * Register multiple configurations to the registry.\n   */\n  registerConfigurations(configurations: IConfigurationNode[], validate?: boolean): void\n\n  /**\n   * Deregister multiple configurations from the registry.\n   */\n  deregisterConfigurations(configurations: IConfigurationNode[]): void\n\n  /**\n   * update the configuration registry by\n   * - registering the configurations to add\n   * - deregistering the configurations to remove\n   */\n  updateConfigurations(configurations: { add: IConfigurationNode[]; remove: IConfigurationNode[] }): void\n\n  /**\n   * Event that fires whenever a configuration has been\n   * registered.\n   */\n  readonly onDidSchemaChange: Event<void>\n\n  /**\n   * Event that fires whenever a configuration has been\n   * registered.\n   */\n  readonly onDidUpdateConfiguration: Event<{ properties: string[]; defaultsOverrides?: boolean }>\n\n  /**\n   * Returns all configurations settings of all configuration nodes contributed to this registry.\n   */\n  getConfigurationProperties(): IStringDictionary<IRegisteredConfigurationPropertySchema>\n\n  /**\n   * Returns all excluded configurations settings of all configuration nodes contributed to this registry.\n   */\n  getExcludedConfigurationProperties(): IStringDictionary<IRegisteredConfigurationPropertySchema>\n}\n\nexport interface IConfigurationDefaultOverride {\n  readonly value: any\n  readonly source?: IExtensionInfo | string  // Source of the default override\n  readonly valuesSources?: Map<string, IExtensionInfo | string> // Source of each value in default language overrides\n}\n\nexport const allSettings: { properties: IStringDictionary<IConfigurationPropertySchema>; patternProperties: IStringDictionary<IConfigurationPropertySchema> } = { properties: {}, patternProperties: {} }\nexport const resourceSettings: { properties: IStringDictionary<IConfigurationPropertySchema>; patternProperties: IStringDictionary<IConfigurationPropertySchema> } = { properties: {}, patternProperties: {} }\n\nexport const resourceLanguageSettingsSchemaId = 'vscode://schemas/settings/resourceLanguage'\nexport const configurationDefaultsSchemaId = 'vscode://schemas/settings/configurationDefaults'\n\nconst contributionRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution)\n\nclass ConfigurationRegistry implements IConfigurationRegistry {\n  private readonly configurationProperties: IStringDictionary<IRegisteredConfigurationPropertySchema>\n  private readonly excludedConfigurationProperties: IStringDictionary<IRegisteredConfigurationPropertySchema>\n  private readonly resourceLanguageSettingsSchema: IJSONSchema\n  private readonly _onDidSchemaChange = new Emitter<void>()\n  public readonly onDidSchemaChange: Event<void> = this._onDidSchemaChange.event\n  private readonly _onDidUpdateConfiguration = new Emitter<{ properties: string[]; defaultsOverrides?: boolean }>()\n  public readonly onDidUpdateConfiguration = this._onDidUpdateConfiguration.event\n\n  constructor() {\n    this.resourceLanguageSettingsSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown coc.nvim configuration property', allowTrailingCommas: true, allowComments: true }\n    this.configurationProperties = {}\n    this.excludedConfigurationProperties = {}\n    contributionRegistry.registerSchema(resourceLanguageSettingsSchemaId, this.resourceLanguageSettingsSchema)\n    this.registerOverridePropertyPatternKey()\n  }\n\n  public registerConfiguration(configuration: IConfigurationNode, validate = true): void {\n    this.registerConfigurations([configuration], validate)\n  }\n\n  public registerConfigurations(configurations: IConfigurationNode[], validate = true): void {\n    const properties = this.doRegisterConfigurations(configurations, validate)\n\n    contributionRegistry.notifySchemaChanged(resourceLanguageSettingsSchemaId)\n    this._onDidSchemaChange.fire()\n    this._onDidUpdateConfiguration.fire({ properties })\n  }\n\n  public deregisterConfigurations(configurations: IConfigurationNode[]): void {\n    const properties = this.doDeregisterConfigurations(configurations)\n\n    contributionRegistry.notifySchemaChanged(resourceLanguageSettingsSchemaId)\n    this._onDidSchemaChange.fire()\n    this._onDidUpdateConfiguration.fire({ properties })\n  }\n\n  public updateConfigurations({ add, remove }: { add: IConfigurationNode[]; remove: IConfigurationNode[] }): void {\n    const properties = []\n    properties.push(...this.doDeregisterConfigurations(remove))\n    properties.push(...this.doRegisterConfigurations(add, false))\n\n    contributionRegistry.notifySchemaChanged(resourceLanguageSettingsSchemaId)\n    this._onDidSchemaChange.fire()\n    this._onDidUpdateConfiguration.fire({ properties: distinct(properties) })\n  }\n\n  private doRegisterConfigurations(configurations: IConfigurationNode[], validate: boolean): string[] {\n    const properties: string[] = []\n    configurations.forEach(configuration => {\n      properties.push(...this.validateAndRegisterProperties(configuration, validate, configuration.extensionInfo)) // fills in defaults\n      this.registerJSONConfiguration(configuration)\n    })\n    return properties\n  }\n\n  private doDeregisterConfigurations(configurations: IConfigurationNode[]): string[] {\n    const properties: string[] = []\n    const deregisterConfiguration = (configuration: IConfigurationNode) => {\n      for (const key in toObject(configuration.properties)) {\n        properties.push(key)\n        delete this.configurationProperties[key]\n        this.removeFromSchema(key, configuration.properties[key])\n      }\n    }\n    for (const configuration of configurations) {\n      deregisterConfiguration(configuration)\n    }\n    return properties\n  }\n\n  private validateAndRegisterProperties(configuration: IConfigurationNode, validate: boolean, extensionInfo: IExtensionInfo | undefined, scope: ConfigurationScope = ConfigurationScope.APPLICATION): string[] {\n    scope = configuration.scope == null ? scope : configuration.scope\n    const propertyKeys: string[] = []\n    const properties = configuration.properties\n    for (const key in toObject(properties)) {\n      const property: IRegisteredConfigurationPropertySchema = properties[key]\n      if (validate && validateProperty(key, property)) {\n        delete properties[key]\n        continue\n      }\n      property.source = extensionInfo\n      // update default value\n      property.defaultDefaultValue = properties[key].default\n      this.updatePropertyDefaultValue(key, property)\n      // update scope\n      property.scope = property.scope == null ? scope : property.scope\n      if (extensionInfo) property.description = (property.description ? `${property.description}\\n` : '') + `From ${extensionInfo.id}`\n\n      // Add to properties maps\n      // Property is included by default if 'included' is unspecified\n      if (property.hasOwnProperty('included') && !property.included) {\n        this.excludedConfigurationProperties[key] = properties[key]\n        delete properties[key]\n        continue\n      } else {\n        this.configurationProperties[key] = properties[key]\n      }\n      if (!properties[key].deprecationMessage && properties[key].markdownDeprecationMessage) {\n        // If not set, default deprecationMessage to the markdown source\n        properties[key].deprecationMessage = properties[key].markdownDeprecationMessage\n      }\n\n      propertyKeys.push(key)\n    }\n    return propertyKeys\n  }\n\n  public getConfigurationProperties(): IStringDictionary<IRegisteredConfigurationPropertySchema> {\n    return this.configurationProperties\n  }\n\n  public getExcludedConfigurationProperties(): IStringDictionary<IRegisteredConfigurationPropertySchema> {\n    return this.excludedConfigurationProperties\n  }\n\n  private registerJSONConfiguration(configuration: IConfigurationNode) {\n    const register = (configuration: IConfigurationNode) => {\n      const properties = configuration.properties\n      for (const key in toObject(properties)) {\n        this.updateSchema(key, properties[key])\n      }\n    }\n    register(configuration)\n  }\n\n  private updateSchema(key: string, property: IConfigurationPropertySchema): void {\n    allSettings.properties[key] = property\n    switch (property.scope) {\n      case ConfigurationScope.WINDOW:\n      case ConfigurationScope.RESOURCE:\n        resourceSettings.properties[key] = property\n        break\n      case ConfigurationScope.LANGUAGE_OVERRIDABLE:\n        resourceSettings.properties[key] = property\n        this.resourceLanguageSettingsSchema.properties![key] = property\n        break\n    }\n  }\n\n  private removeFromSchema(key: string, property: IConfigurationPropertySchema): void {\n    delete allSettings.properties[key]\n    switch (property.scope) {\n      case ConfigurationScope.WINDOW:\n      case ConfigurationScope.RESOURCE:\n      case ConfigurationScope.LANGUAGE_OVERRIDABLE:\n        delete resourceSettings.properties[key]\n        delete this.resourceLanguageSettingsSchema.properties![key]\n        break\n    }\n  }\n\n  private registerOverridePropertyPatternKey(): void {\n    const resourceLanguagePropertiesSchema: IJSONSchema = {\n      type: 'object',\n      description: 'Configure editor settings to be overridden for a language.',\n      errorMessage: 'This setting does not support per-language configuration.',\n      $ref: resourceLanguageSettingsSchemaId,\n    }\n    allSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema\n    resourceSettings.patternProperties[OVERRIDE_PROPERTY_PATTERN] = resourceLanguagePropertiesSchema\n  }\n\n  private updatePropertyDefaultValue(key: string, property: IRegisteredConfigurationPropertySchema): void {\n    let defaultValue = property.defaultDefaultValue\n    if (typeof defaultValue === 'undefined' && !EXCLUDE_KEYS.some(k => key.includes(k))) {\n      defaultValue = getDefaultValue(property.type)\n    }\n    property.default = defaultValue\n    property.defaultValueSource = undefined\n  }\n}\n\nconst configurationRegistry = new ConfigurationRegistry()\nRegistry.add(Extensions.Configuration, configurationRegistry)\n\nexport function validateProperty(property: string, _schema: IRegisteredConfigurationPropertySchema = undefined): string | null {\n  if (!property.trim()) {\n    return 'Cannot register an empty property'\n  }\n  if (OVERRIDE_PROPERTY_REGEX.test(property)) {\n    return `Cannot register ${property}. This matches property pattern '\\\\\\\\[.*\\\\\\\\]$' for describing language specific editor settings`\n  }\n  if (configurationRegistry.getConfigurationProperties()[property] !== undefined) {\n    return `Cannot register '${property}'. This property is already registered.`\n  }\n  return null\n}\n"
  },
  {
    "path": "src/configuration/shape.ts",
    "content": "'use strict'\nimport { applyEdits, modify } from 'jsonc-parser'\nimport { WorkspaceFolder } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport { createLogger } from '../logger'\nimport { fs, path } from '../util/node'\nconst logger = createLogger('configuration-shape')\n\ninterface IFolderController {\n  root?: string\n  getWorkspaceFolder?: (resource: string) => WorkspaceFolder\n}\nexport interface IConfigurationShape {\n  root?: string\n  /**\n   * Resolve possible workspace config from resource.\n   */\n  getWorkspaceFolder?(resource?: string): URI | undefined\n  modifyConfiguration(fsPath: string, key: string, value?: any): Promise<void>\n}\n\nexport default class ConfigurationProxy implements IConfigurationShape {\n\n  constructor(private resolver: IFolderController, private _test = global.__TEST__) {\n  }\n\n  public get root(): string | undefined {\n    return this.resolver.root\n  }\n\n  public async modifyConfiguration(fsPath: string, key: string, value?: any): Promise<void> {\n    if (this._test) return\n    logger.info(`modify configuration file: ${fsPath}`, key, value)\n    let dir = path.dirname(fsPath)\n    let formattingOptions = { tabSize: 2, insertSpaces: true }\n    if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })\n    let content = fs.readFileSync(fsPath, { flag: 'a+' }).toString('utf8')\n    content = content || '{}'\n    let edits = modify(content, [key], value, { formattingOptions })\n    content = applyEdits(content, edits)\n    fs.writeFileSync(fsPath, content, { encoding: 'utf8' })\n  }\n\n  public getWorkspaceFolder(resource: string): URI | undefined {\n    if (typeof this.resolver.getWorkspaceFolder === 'function') {\n      let workspaceFolder = this.resolver.getWorkspaceFolder(resource)\n      if (workspaceFolder) return URI.parse(workspaceFolder.uri)\n    }\n    return undefined\n  }\n}\n"
  },
  {
    "path": "src/configuration/types.ts",
    "content": "import type { TextDocument } from 'vscode-languageserver-textdocument'\nimport type { WorkspaceFolder } from 'vscode-languageserver-types'\nimport type { URI } from 'vscode-uri'\n\n/**\n * An interface for a JavaScript object that\n * acts a dictionary. The keys are strings.\n */\nexport type IStringDictionary<V> = Record<string, V>\n\nexport enum ConfigurationTarget {\n  Default,\n  User,\n  Workspace,\n  WorkspaceFolder,\n  Memory,\n}\n\nexport interface IConfigurationChange {\n  keys: string[]\n  overrides: [string, string[]][]\n}\n\nexport enum ConfigurationUpdateTarget {\n  Global = 1,\n  Workspace = 2,\n  WorkspaceFolder = 3\n}\n\nexport const enum ConfigurationScope {\n  /**\n   * Application specific configuration, which can be configured only in local user settings.\n   */\n  APPLICATION = 1,\n  /**\n   * Window specific configuration, which can be configured in the user or workspace settings.\n   */\n  WINDOW,\n  /**\n   * Resource specific configuration, which can be configured in the user, workspace or folder settings.\n   */\n  RESOURCE,\n  /**\n   * Resource specific configuration that can be configured in language specific settings\n   */\n  LANGUAGE_OVERRIDABLE,\n}\n\nexport type ConfigurationResourceScope = string | null | URI | TextDocument | WorkspaceFolder | { uri?: string; languageId?: string }\n\nexport interface IConfigurationChangeEvent {\n  readonly source: ConfigurationTarget\n  readonly affectedKeys: string[]\n  readonly change?: IConfigurationChange\n  affectsConfiguration(configuration: string, scope?: ConfigurationResourceScope): boolean\n}\n\nexport interface ConfigurationInspect<T> {\n  key: string\n  defaultValue?: T\n  globalValue?: T\n  workspaceValue?: T\n  workspaceFolderValue?: T\n}\n\nexport interface IConfigurationOverrides {\n  overrideIdentifier?: string | null\n  resource?: string | null\n}\n\nexport interface IOverrides {\n  contents: any\n  keys: string[]\n  identifiers: string[]\n}\n\nexport interface IConfigurationModel {\n  contents: any\n  keys: string[]\n  overrides: IOverrides[]\n}\n\nexport interface IConfigurationData {\n  defaults: IConfigurationModel\n  memory: IConfigurationModel\n  user: IConfigurationModel\n  workspace: IConfigurationModel\n  folders: [string, IConfigurationModel][]\n}\n\nexport interface WorkspaceConfiguration {\n  /**\n   * Return a value from this configuration.\n   * @param section Configuration name, supports _dotted_ names.\n   * @return The value `section` denotes or `undefined`.\n   */\n  get<T>(section: string): T | undefined\n\n  /**\n   * Return a value from this configuration.\n   * @param section Configuration name, supports _dotted_ names.\n   * @param defaultValue A value should be returned when no value could be found, is `undefined`.\n   * @return The value `section` denotes or the default.\n   */\n  get<T>(section: string, defaultValue: T): T\n\n  /**\n   * Check if this configuration has a certain value.\n   * @param section Configuration name, supports _dotted_ names.\n   * @return `true` if the section doesn't resolve to `undefined`.\n   */\n  has(section: string): boolean\n\n  /**\n   * Retrieve all information about a configuration setting. A configuration value\n   * often consists of a *default* value, a global or installation-wide value,\n   * a workspace-specific value\n   *\n   * *Note:* The configuration name must denote a leaf in the configuration tree\n   * (`editor.fontSize` vs `editor`) otherwise no result is returned.\n   * @param section Configuration name, supports _dotted_ names.\n   * @return Information about a configuration setting or `undefined`.\n   */\n  inspect<T>(section: string): ConfigurationInspect<T> | undefined\n  /**\n   * Update a configuration value. The updated configuration values are persisted.\n   * @param section Configuration name, supports _dotted_ names.\n   * @param value The new value.\n   * @param isUser if true, always update user configuration\n   */\n  update(section: string, value: any, isUser?: ConfigurationUpdateTarget | boolean): Thenable<void>\n\n  /**\n   * Readable dictionary that backs this configuration.\n   */\n  readonly [key: string]: any\n}\n"
  },
  {
    "path": "src/configuration/util.ts",
    "content": "'use strict'\nimport { ParseError, printParseErrorCode } from 'jsonc-parser'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { Diagnostic, DiagnosticSeverity, Range } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport { distinct } from '../util/array'\nimport * as Is from '../util/is'\nimport { os } from '../util/node'\nimport { equals, hasOwnProperty } from '../util/object'\nimport { ConfigurationResourceScope, ConfigurationTarget, ConfigurationUpdateTarget, IConfigurationChange, IConfigurationOverrides } from './types'\nconst documentUri = 'file:///1'\n\nexport interface IConfigurationCompareResult {\n  added: string[]\n  removed: string[]\n  updated: string[]\n  overrides: [string, string[]][]\n}\n\nconst OVERRIDE_IDENTIFIER_PATTERN = `\\\\[([^\\\\]]+)\\\\]`\nconst OVERRIDE_IDENTIFIER_REGEX = new RegExp(OVERRIDE_IDENTIFIER_PATTERN, 'g')\nexport const OVERRIDE_PROPERTY_PATTERN = `^(${OVERRIDE_IDENTIFIER_PATTERN})+$`\nexport const OVERRIDE_PROPERTY_REGEX = new RegExp(OVERRIDE_PROPERTY_PATTERN)\n\n/**\n * Basic expand for ${env:value}, ${cwd}, ${userHome}\n */\nexport function expand(input: string): string {\n  return input.replace(/\\$\\{(.*?)\\}/g, (match: string, name: string) => {\n    if (name.startsWith('env:')) {\n      let key = name.split(':')[1]\n      return process.env[key] ?? match\n    }\n    switch (name) {\n      case 'tmpdir':\n        return os.tmpdir()\n      case 'userHome':\n        return os.homedir()\n      case 'cwd':\n        return process.cwd()\n      default:\n        return match\n    }\n  })\n}\n\nexport function expandObject(obj: any): any {\n  if (obj == null) return obj\n  if (typeof obj === 'string') return expand(obj)\n  if (Array.isArray(obj)) return obj.map(obj => expandObject(obj))\n  if (Is.objectLiteral(obj)) {\n    for (let key of Object.keys(obj)) {\n      obj[key] = expandObject(obj[key])\n    }\n    return obj\n  }\n  return obj\n}\n\nexport function convertTarget(updateTarget: ConfigurationUpdateTarget): ConfigurationTarget {\n  let target: ConfigurationTarget\n  switch (updateTarget) {\n    case ConfigurationUpdateTarget.Global:\n      target = ConfigurationTarget.User\n      break\n    case ConfigurationUpdateTarget.Workspace:\n      target = ConfigurationTarget.Workspace\n      break\n    default:\n      target = ConfigurationTarget.WorkspaceFolder\n  }\n  return target\n}\n\nexport function scopeToOverrides(scope: ConfigurationResourceScope): IConfigurationOverrides {\n  let overrides: IConfigurationOverrides\n  if (typeof scope === 'string') {\n    overrides = { resource: scope }\n  } else if (URI.isUri(scope)) {\n    overrides = { resource: scope.toString() }\n  } else if (scope != null) {\n    let uri = scope['uri']\n    let languageId = scope['languageId']\n    overrides = { resource: uri, overrideIdentifier: languageId }\n  }\n  return overrides\n}\n\nexport function overrideIdentifiersFromKey(key: string): string[] {\n  const identifiers: string[] = []\n  if (OVERRIDE_PROPERTY_REGEX.test(key)) {\n    let matches = OVERRIDE_IDENTIFIER_REGEX.exec(key)\n    while (matches?.length) {\n      const identifier = matches[1].trim()\n      if (identifier) {\n        identifiers.push(identifier)\n      }\n      matches = OVERRIDE_IDENTIFIER_REGEX.exec(key)\n    }\n  }\n  return distinct(identifiers)\n}\n\nfunction getOrSet<K, V>(map: Map<K, V>, key: K, value: V): V {\n  let result = map.get(key)\n  if (result === undefined) {\n    result = value\n    map.set(key, result)\n  }\n\n  return result\n}\n\nexport function mergeChanges(...changes: IConfigurationChange[]): IConfigurationChange {\n  if (changes.length === 0) {\n    return { keys: [], overrides: [] }\n  }\n  if (changes.length === 1) {\n    return changes[0]\n  }\n  const keysSet = new Set<string>()\n  const overridesMap = new Map<string, Set<string>>()\n  for (const change of changes) {\n    change.keys.forEach(key => keysSet.add(key))\n    change.overrides.forEach(([identifier, keys]) => {\n      const result = getOrSet(overridesMap, identifier, new Set<string>())\n      keys.forEach(key => result.add(key))\n    })\n  }\n  const overrides: [string, string[]][] = []\n  overridesMap.forEach((keys, identifier) => overrides.push([identifier, [...keys.values()]]))\n  return { keys: [...keysSet.values()], overrides }\n}\n\nexport function mergeConfigProperties(obj: any): any {\n  let res = {}\n  for (let key of Object.keys(obj)) {\n    if (key.indexOf('.') == -1) {\n      res[key] = obj[key]\n    } else {\n      let parts = key.split('.')\n      let pre = res\n      let len = parts.length\n      for (let i = 0; i < len; i++) {\n        let k = parts[i]\n        if (i == len - 1) {\n          pre[k] = obj[key]\n        } else {\n          pre[k] = pre[k] || {}\n          pre = pre[k]\n        }\n      }\n    }\n  }\n  return res\n}\n\nexport function convertErrors(content: string, errors: ParseError[]): Diagnostic[] {\n  let items: Diagnostic[] = []\n  let document = TextDocument.create(documentUri, 'json', 0, content)\n  for (let err of errors) {\n    const range = Range.create(document.positionAt(err.offset), document.positionAt(err.offset + err.length))\n    items.push(Diagnostic.create(range, printParseErrorCode(err.error), DiagnosticSeverity.Error))\n  }\n  return items\n}\n\nexport function toValuesTree(properties: { [qualifiedKey: string]: any }, conflictReporter: (message: string) => void, doExpand = false): any {\n  const root = Object.create(null)\n  for (const key in properties) {\n    addToValueTree(root, key, properties[key], conflictReporter, doExpand)\n  }\n  return root\n}\n\nexport function addToValueTree(settingsTreeRoot: any, key: string, value: any, conflictReporter: (message: string) => void, doExpand = false): void {\n  const segments = key.split('.')\n  const last = segments.pop()!\n\n  let curr = settingsTreeRoot\n  for (let i = 0; i < segments.length; i++) {\n    const s = segments[i]\n    let obj = curr[s]\n    switch (typeof obj) {\n      case 'undefined':\n        obj = curr[s] = Object.create(null)\n        break\n      case 'object':\n        break\n      default:\n        if (conflictReporter) conflictReporter(`Ignoring ${key} as ${segments.slice(0, i + 1).join('.')} is ${JSON.stringify(obj)}`)\n        return\n    }\n    curr = obj\n  }\n\n  if (typeof curr === 'object' && curr !== null) {\n    if (doExpand) {\n      curr[last] = expandObject(value)\n    } else {\n      curr[last] = value\n    }\n  } else {\n    if (conflictReporter) conflictReporter(`Ignoring ${key} as ${segments.join('.')} is ${JSON.stringify(curr)}`)\n  }\n}\n\nexport function removeFromValueTree(valueTree: any, key: string): void {\n  const segments = key.split('.')\n  doRemoveFromValueTree(valueTree, segments)\n}\n\nfunction doRemoveFromValueTree(valueTree: any, segments: string[]): void {\n  const first = segments.shift()\n  if (segments.length === 0) {\n    // Reached last segment\n    delete valueTree[first]\n    return\n  }\n\n  if (Object.keys(valueTree).includes(first)) {\n    const value = valueTree[first]\n    if (typeof value === 'object' && !Array.isArray(value)) {\n      doRemoveFromValueTree(value, segments)\n      if (Object.keys(value).length === 0) {\n        delete valueTree[first]\n      }\n    }\n  }\n}\n\nexport function getConfigurationValue<T>(\n  config: any,\n  settingPath: string,\n  defaultValue?: T\n): T {\n  function accessSetting(config: any, path: string[]): any {\n    let current = config\n    for (let i = 0; i < path.length; i++) {\n      if (typeof current !== 'object' || current === null) {\n        return undefined\n      }\n      current = current[path[i]]\n    }\n    return current as T\n  }\n  const path = settingPath.split('.')\n  const result = accessSetting(config, path)\n  return typeof result === 'undefined' ? defaultValue : result\n}\n\nexport function toJSONObject(obj: any): any {\n  if (obj) {\n    if (Array.isArray(obj)) {\n      return obj.map(toJSONObject)\n    } else if (typeof obj === 'object') {\n      const res = Object.create(null)\n      for (const key in obj) {\n        if (Object.prototype.hasOwnProperty.call(obj, key)) {\n          res[key] = toJSONObject(obj[key])\n        }\n      }\n      return res\n    }\n  }\n  return obj\n}\n\n/**\n * Compare too configuration contents\n */\nexport function compareConfigurationContents(to: { keys: string[]; contents: any } | undefined, from: { keys: string[]; contents: any } | undefined) {\n  const added = to\n    ? from ? to.keys.filter(key => from.keys.indexOf(key) === -1) : [...to.keys]\n    : []\n  const removed = from\n    ? to ? from.keys.filter(key => to.keys.indexOf(key) === -1) : [...from.keys]\n    : []\n  const updated: string[] = []\n\n  if (to && from) {\n    for (const key of from.keys) {\n      if (to.keys.indexOf(key) !== -1) {\n        const value1 = getConfigurationValue(from.contents, key)\n        const value2 = getConfigurationValue(to.contents, key)\n        if (!equals(value1, value2)) {\n          updated.push(key)\n        }\n      }\n    }\n  }\n  return { added, removed, updated }\n}\n\nexport function getDefaultValue(type: string | string[] | undefined): any {\n  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n  const t = Array.isArray(type) ? (<string[]>type)[0] : <string>type\n  switch (t) {\n    case 'boolean':\n      return false\n    case 'integer':\n    case 'number':\n      return 0\n    case 'string':\n      return ''\n    case 'array':\n      return []\n    case 'object':\n      return {}\n    default:\n      return null\n  }\n}\n\nexport function lookUp(tree: any, key: string): any {\n  if (key) {\n    if (tree && hasOwnProperty(tree, key)) return tree[key]\n    const parts = key.split('.')\n    let node = tree\n    for (let i = 0; node && i < parts.length; i++) {\n      node = node[parts[i]]\n    }\n    return node\n  }\n  return tree\n}\n"
  },
  {
    "path": "src/core/autocmds.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { createLogger } from '../logger'\nimport { Autocmd } from '../types'\nimport { isFalsyOrEmpty } from '../util/array'\nimport { parseExtensionName } from '../util/extensionRegistry'\nimport { omit } from '../util/lodash'\nimport { CancellationTokenSource, Disposable } from '../util/protocol'\nconst logger = createLogger('autocmds')\n\ninterface AutocmdOption {\n  group?: string | number\n  pattern?: string | string[]\n  buffer?: number\n  desc?: string\n  command?: string\n  once?: boolean\n  nested?: boolean\n  replace?: boolean\n}\n\nexport interface AutocmdOptionWithStack extends Autocmd {\n  stack: string\n}\n\nexport class AutocmdItem {\n  private _extensiionName: string | undefined\n  constructor(\n    public readonly id: number,\n    public readonly option: AutocmdOptionWithStack\n  ) {\n  }\n\n  public get extensiionName(): string {\n    if (this._extensiionName) return this._extensiionName\n    this._extensiionName = parseExtensionName(this.option.stack)\n    return this._extensiionName\n  }\n}\n\nconst groupName = 'coc_dynamic_autocmd'\n\nexport function toAutocmdOption(item: AutocmdItem): AutocmdOption {\n  let { id, option } = item\n  let opt: AutocmdOption = { group: groupName }\n  if (option.buffer) opt.buffer = option.buffer\n  if (option.pattern) opt.pattern = option.pattern\n  if (option.once) opt.once = true\n  if (option.nested) opt.nested = true\n  let method = option.request ? 'request' : 'notify'\n  let args = isFalsyOrEmpty(option.arglist) ? '' : ', ' + option.arglist.join(', ')\n  let command = `call coc#rpc#${method}('doAutocmd', [${id}${args}])`\n  opt.command = command\n  return opt\n}\n\nexport default class Autocmds implements Disposable {\n  public readonly autocmds: Map<number, AutocmdItem> = new Map()\n  private nvim: Neovim\n  private id = 0\n\n  public attach(nvim: Neovim): void {\n    this.nvim = nvim\n  }\n\n  public async doAutocmd(id: number, args: any[], timeout = 1000): Promise<void> {\n    let autocmd = this.autocmds.get(id)\n    if (autocmd) {\n      let option = autocmd.option\n      logger.trace(`Invoke autocmd from \"${autocmd.extensiionName}\"`, option)\n      try {\n        let tokenSource = new CancellationTokenSource()\n        let promise = Promise.resolve(option.callback.apply(option.thisArg, [...args, tokenSource.token]))\n        if (option.request) {\n          let timer\n          let tp = new Promise(resolve => {\n            timer = setTimeout(() => {\n              tokenSource.cancel()\n              logger.error(`Autocmd timeout after ${timeout}ms`, omit(option, ['callback', 'stack']), autocmd.option.stack)\n              resolve(undefined)\n            }, timeout)\n          })\n          await Promise.race([tp, promise])\n          clearTimeout(timer)\n          tokenSource.dispose()\n        } else {\n          await promise\n        }\n      } catch (e) {\n        logger.error(`Error on autocmd \"${option.event}\"`, omit(option, ['callback', 'stack']), e)\n      }\n    }\n  }\n\n  public registerAutocmd(autocmd: AutocmdOptionWithStack): Disposable {\n    // Used as group name as well\n    let id = ++this.id\n    let item = new AutocmdItem(id, autocmd)\n    this.autocmds.set(id, item)\n    this.createAutocmd(item)\n    return Disposable.create(() => {\n      // only remove the item from autocmds\n      this.autocmds.delete(id)\n    })\n  }\n\n  private createAutocmd(item: AutocmdItem): void {\n    let { option } = item\n    let event = Array.isArray(option.event) ? option.event.join(',') : option.event\n    if (/\\buser\\b/i.test(event)) {\n      let cmd = createCommand(item.id, event, option)\n      this.nvim.command(cmd, true)\n    } else {\n      let opt = toAutocmdOption(item)\n      this.nvim.createAutocmd(\n        Array.isArray(item.option.event)\n          ? item.option.event\n          : item.option.event.split(\",\"),\n        opt,\n        true\n      )\n    }\n  }\n\n  public removeExtensionAutocmds(extensiionName: string): void {\n    let { nvim, autocmds } = this\n    nvim.pauseNotification()\n    nvim.command(`autocmd! ${groupName}`, true)\n    let items = autocmds.values()\n    for (const item of items) {\n      if (item.extensiionName === extensiionName) {\n        autocmds.delete(item.id)\n        continue\n      }\n      this.createAutocmd(item)\n    }\n    nvim.resumeNotification(false, true)\n  }\n\n  public dispose(): void {\n    this.autocmds.clear()\n  }\n}\n\n/**\n * Only used for user autocmd, which can't be used for nvim_create_autocmd\n */\nexport function createCommand(id: number, event: string, autocmd: Autocmd): string {\n  let args = isFalsyOrEmpty(autocmd.arglist) ? '' : ', ' + autocmd.arglist.join(', ')\n  let method = autocmd.request ? 'request' : 'notify'\n  let opt = ''\n  if (autocmd.once) opt += ' ++once'\n  if (autocmd.nested) opt += ' ++nested'\n  return `autocmd ${groupName} ${event}${opt}  call coc#rpc#${method}('doAutocmd', [${id}${args}])`\n}\n"
  },
  {
    "path": "src/core/channels.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { URI } from 'vscode-uri'\nimport events from '../events'\nimport BufferChannel from '../model/outputChannel'\nimport { TextDocumentContentProvider } from '../provider'\nimport { OutputChannel } from '../types'\nimport { Disposable } from '../util/protocol'\n\nclass Channels {\n  private outputChannels: Map<string, BufferChannel> = new Map()\n  private bufnrs: Map<number, string> = new Map()\n  private disposable: Disposable\n  constructor() {\n    this.disposable = events.on('BufUnload', bufnr => {\n      let name = this.bufnrs.get(bufnr)\n      if (name) {\n        let channel = this.outputChannels.get(name)\n        if (channel) channel.created = false\n      }\n    })\n  }\n\n  /**\n   * Get text document provider\n   */\n  public getProvider(nvim: Neovim): TextDocumentContentProvider {\n    let provider: TextDocumentContentProvider = {\n      onDidChange: null,\n      provideTextDocumentContent: async (uri: URI) => {\n        let channel = this.get(uri.path.slice(1))\n        if (!channel) return ''\n        nvim.pauseNotification()\n        nvim.call('bufnr', ['%'], true)\n        nvim.command('setlocal nospell nofoldenable nowrap noswapfile', true)\n        nvim.command('setlocal buftype=nofile bufhidden=hide', true)\n        nvim.command('setfiletype log', true)\n        let res = await nvim.resumeNotification()\n        this.bufnrs.set(res[0][0] as number, channel.name)\n        channel.created = true\n        return channel.content\n      }\n    }\n    return provider\n  }\n\n  public get names(): string[] {\n    return Array.from(this.outputChannels.keys())\n  }\n\n  public get(channelName: string): BufferChannel | null {\n    return this.outputChannels.get(channelName)\n  }\n\n  public create(name: string, nvim: Neovim): OutputChannel | null {\n    if (this.outputChannels.has(name)) return this.outputChannels.get(name)\n    let channel = new BufferChannel(name, nvim, () => {\n      this.outputChannels.delete(name)\n    })\n    this.outputChannels.set(name, channel)\n    return channel\n  }\n\n  public show(name: string, cmd: string, preserveFocus?: boolean): void {\n    let channel = this.outputChannels.get(name)\n    if (!channel) return\n    channel.show(preserveFocus, cmd)\n  }\n\n  public dispose(): void {\n    this.disposable.dispose()\n    for (let channel of this.outputChannels.values()) {\n      channel.dispose()\n    }\n    this.outputChannels.clear()\n  }\n}\n\nexport default new Channels()\n"
  },
  {
    "path": "src/core/contentProvider.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { URI } from 'vscode-uri'\nimport events from '../events'\nimport { TextDocumentContentProvider } from '../provider'\nimport { disposeAll } from '../util'\nimport { CancellationTokenSource, Disposable, Emitter, Event } from '../util/protocol'\nimport { toText } from '../util/string'\nimport Documents from './documents'\n\nexport default class ContentProvider implements Disposable {\n  private nvim: Neovim\n  private disposables: Disposable[] = []\n  private providers: Map<string, TextDocumentContentProvider> = new Map()\n  private readonly _onDidProviderChange = new Emitter<void>()\n  public readonly onDidProviderChange: Event<void> = this._onDidProviderChange.event\n  constructor(\n    private documents: Documents\n  ) {\n  }\n\n  public attach(nvim: Neovim): void {\n    this.nvim = nvim\n    events.on('BufReadCmd', this.onBufReadCmd, this, this.disposables)\n  }\n\n  public get schemes(): string[] {\n    return Array.from(this.providers.keys())\n  }\n\n  public async onBufReadCmd(scheme: string, uri: string): Promise<void> {\n    let provider = this.providers.get(scheme)\n    if (!provider) return\n    let tokenSource = new CancellationTokenSource()\n    let content = await Promise.resolve(provider.provideTextDocumentContent(URI.parse(uri), tokenSource.token))\n    let buf = await this.nvim.buffer\n    await buf.setLines(toText(content).split(/\\r?\\n/), {\n      start: 0,\n      end: -1,\n      strictIndexing: false\n    })\n    process.nextTick(() => {\n      void events.fire('BufCreate', [buf.id])\n    })\n  }\n\n  private resetAutocmds(): void {\n    let { nvim, schemes } = this\n    nvim.pauseNotification()\n    nvim.command(`autocmd! coc_dynamic_content`, true)\n    for (let scheme of schemes) {\n      nvim.command(getAutocmdCommand(scheme), true)\n    }\n    nvim.resumeNotification(false, true)\n  }\n\n  public registerTextDocumentContentProvider(scheme: string, provider: TextDocumentContentProvider): Disposable {\n    this.providers.set(scheme, provider)\n    this._onDidProviderChange.fire()\n    let disposables: Disposable[] = []\n    if (provider.onDidChange) {\n      provider.onDidChange(async uri => {\n        let doc = this.documents.getDocument(uri.toString())\n        if (!doc) return\n        let tokenSource = new CancellationTokenSource()\n        let content = await Promise.resolve(provider.provideTextDocumentContent(uri, tokenSource.token))\n        await doc.buffer.setLines(content.split(/\\r?\\n/), {\n          start: 0,\n          end: -1,\n          strictIndexing: false\n        })\n      }, null, disposables)\n    }\n    this.nvim.command(getAutocmdCommand(scheme), true)\n    return Disposable.create(() => {\n      this.providers.delete(scheme)\n      disposeAll(disposables)\n      this.resetAutocmds()\n      this._onDidProviderChange.fire()\n    })\n  }\n\n  public dispose(): void {\n    disposeAll(this.disposables)\n    this._onDidProviderChange.dispose()\n    this.providers.clear()\n  }\n}\n\nfunction getAutocmdCommand(scheme: string): string {\n  let rhs = `call coc#rpc#request('CocAutocmd', ['BufReadCmd','${scheme}', expand('<afile>')]) | filetype detect`\n  return `autocmd! coc_dynamic_content BufReadCmd,FileReadCmd,SourceCmd ${scheme}:/* ${rhs}`\n}\n"
  },
  {
    "path": "src/core/dialogs.ts",
    "content": "import type { Neovim } from '@chemzqm/neovim'\nimport type { WorkspaceConfiguration } from '../configuration/types'\nimport events from '../events'\nimport { Dialog, DialogConfig, DialogPreferences } from '../model/dialog'\nimport InputBox, { InputPreference } from '../model/input'\nimport Menu, { MenuItem } from '../model/menu'\nimport Picker, { toPickerItems } from '../model/picker'\nimport QuickPick from '../model/quickpick'\nimport { Env, QuickPickItem } from '../types'\nimport { defaultValue } from '../util'\nimport { isFalsyOrEmpty } from '../util/array'\nimport { floatHighlightGroup } from '../util/constants'\nimport { Mutex } from '../util/mutex'\nimport { toNumber } from '../util/numbers'\nimport { isWindows } from '../util/platform'\nimport { CancellationToken } from '../util/protocol'\nimport { toText } from '../util/string'\nimport { callAsync } from './funcs'\nimport { showPrompt } from './ui'\nexport type Item = QuickPickItem | string\nexport type InputOptions = Pick<InputPreference, 'borderhighlight' | 'position' | 'marginTop' | 'placeHolder'>\n\nexport interface QuickPickConfig<T extends QuickPickItem> {\n  placeholder?: string\n  title?: string\n  items?: readonly T[]\n  value?: string\n  canSelectMany?: boolean\n  matchOnDescription?: boolean\n}\n\n/**\n * Options to configure the behavior of the quick pick UI.\n */\nexport interface QuickPickOptions {\n\n  placeHolder?: string\n  /**\n   * An optional string that represents the title of the quick pick.\n   */\n  title?: string\n\n  /**\n   * An optional flag to include the description when filtering the picks.\n   */\n  matchOnDescription?: boolean\n\n  /**\n   * An optional flag to make the picker accept multiple selections, if true the result is an array of picks.\n   */\n  canPickMany?: boolean\n}\n\nexport type MenuOption = {\n  title?: string,\n  content?: string\n  /**\n   * Create and highlight shortcut characters.\n   */\n  shortcuts?: boolean\n  /**\n   * Position of menu picker, default to 'cursor'\n   */\n  position?: 'cursor' | 'center'\n  /**\n   * Border highlight that override user configuration.\n   */\n  borderhighlight?: string\n} | string\n\nexport class Dialogs {\n  public mutex = new Mutex()\n  public nvim: Neovim\n  public configuration: WorkspaceConfiguration\n  constructor() {\n  }\n\n  public async showDialog(config: DialogConfig): Promise<Dialog | null> {\n    return await this.mutex.use(async () => {\n      let dialog = new Dialog(this.nvim, config)\n      await dialog.show(this.dialogPreference)\n      return dialog\n    })\n  }\n\n  public async showPrompt(title: string): Promise<boolean> {\n    return await this.mutex.use(() => {\n      return showPrompt(this.nvim, title)\n    })\n  }\n\n  public async createQuickPick<T extends QuickPickItem>(config: QuickPickConfig<T>): Promise<QuickPick<T>> {\n    return await this.mutex.use(async () => {\n      let quickpick = new QuickPick<T>(this.nvim, this.dialogPreference)\n      Object.assign(quickpick, config)\n      return quickpick\n    })\n  }\n\n  public async showMenuPicker(items: string[] | MenuItem[], option?: MenuOption, token?: CancellationToken): Promise<number> {\n    return await this.mutex.use(async () => {\n      if (token && token.isCancellationRequested) return -1\n      option = option || {}\n      if (typeof option === 'string') option = { title: option }\n      let menu = new Menu(this.nvim, { items, ...option }, token)\n      let promise = new Promise<number>(resolve => {\n        menu.onDidClose(selected => {\n          void events.race(['BufHidden'], 20).finally(() => {\n            resolve(selected)\n          })\n        })\n      })\n      await menu.show(this.dialogPreference)\n      return await promise\n    })\n  }\n\n  /**\n   * Shows a selection list.\n   */\n  public async showQuickPick(itemsOrItemsPromise: Item[] | Promise<Item[]>, options: QuickPickOptions, token: CancellationToken): Promise<Item | Item[] | undefined> {\n    options = defaultValue(options, {})\n    const items = await Promise.resolve(itemsOrItemsPromise)\n    if (isFalsyOrEmpty(items)) return undefined\n    let isText = items.some(s => typeof s === 'string')\n    return await this.mutex.use(() => {\n      return new Promise<Item | Item[] | undefined>((resolve, reject) => {\n        if (token.isCancellationRequested) return resolve(undefined)\n        let quickpick = new QuickPick<QuickPickItem>(this.nvim, this.dialogPreference)\n        quickpick.items = items.map(o => typeof o === 'string' ? { label: o } : o)\n        quickpick.title = toText(options.title)\n        quickpick.placeholder = options.placeHolder ?? options['placeholder']\n        quickpick.canSelectMany = !!options.canPickMany\n        quickpick.matchOnDescription = options.matchOnDescription\n        quickpick.onDidFinish(items => {\n          if (items == null) return resolve(undefined)\n          let arr = isText ? items.map(o => o.label) : items\n          if (options.canPickMany) return resolve(arr)\n          resolve(arr[0])\n        })\n        quickpick.show().catch(reject)\n      })\n    })\n  }\n\n  public async showPickerDialog(items: string[], title: string, token?: CancellationToken): Promise<string[] | undefined>\n  public async showPickerDialog<T extends QuickPickItem>(items: T[], title: string, token?: CancellationToken): Promise<T[] | undefined>\n  public async showPickerDialog(items: any, title: string, token?: CancellationToken): Promise<any | undefined> {\n    return await this.mutex.use(async () => {\n      if (token && token.isCancellationRequested) {\n        return undefined\n      }\n      const picker = new Picker(this.nvim, {\n        title,\n        items: toPickerItems(items),\n      }, token)\n      let promise = new Promise<number[]>(resolve => {\n        picker.onDidClose(selected => {\n          resolve(selected)\n        })\n      })\n      await picker.show(this.dialogPreference)\n      let picked = await promise\n      return picked == undefined ? undefined : items.filter((_, i) => picked.includes(i))\n    })\n  }\n\n  public async requestInput(title: string, env: Env, value?: string, option?: InputOptions): Promise<string | undefined> {\n    let { nvim } = this\n    let noPrompt = !env.terminal || !env.dialog || (env.isVim && isWindows && !env.isCygwin)\n    const promptInput = this.configuration.get('coc.preferences.promptInput')\n    if (promptInput && !noPrompt) {\n      return await this.mutex.use(async () => {\n        let input = new InputBox(nvim, toText(value))\n        await input.show(title, Object.assign(this.inputPreference, defaultValue(option, {})))\n        return await new Promise<string>(resolve => {\n          input.onDidFinish(text => {\n            setTimeout(() => {\n              resolve(text)\n            }, 20)\n          })\n        })\n      })\n    } else {\n      let res = await callAsync<string>(this.nvim, 'input', [title + ': ', toText(value)])\n      nvim.command('normal! :<C-u>', true)\n      return res\n    }\n  }\n\n  /**\n   * Request selection by use inputlist of vim.\n   */\n  public async requestInputList(prompt: string, items: string[]): Promise<number> {\n    let { nvim } = this\n    let list = items.map((text, i) => `${i + 1}. ${text}`)\n    let res = await callAsync<number>(this.nvim, 'inputlist', [[`${prompt}:`, ...list]])\n    nvim.command('normal! :<C-u>', true)\n    return res >= 1 && res <= items.length ? res - 1 : -1\n  }\n\n  public async createInputBox(title: string, value: string | undefined, option?: InputPreference): Promise<InputBox> {\n    let input = new InputBox(this.nvim, toText(value))\n    await input.show(title, Object.assign(this.inputPreference, defaultValue(option, {})))\n    return input\n  }\n\n  private get inputPreference(): InputPreference {\n    let config = this.configuration.get<any>('dialog')\n    return {\n      rounded: !!config.rounded,\n      maxWidth: toNumber(config.maxWidth, 80),\n      highlight: defaultValue(config.floatHighlight, floatHighlightGroup),\n      borderhighlight: defaultValue(config.floatBorderHighlight, floatHighlightGroup)\n    }\n  }\n\n  private get dialogPreference(): DialogPreferences {\n    let config = this.configuration.get<any>('dialog')\n    return {\n      rounded: !!config.rounded,\n      maxWidth: toNumber(config.maxWidth, 80),\n      maxHeight: config.maxHeight,\n      floatHighlight: defaultValue(config.floatHighlight, floatHighlightGroup),\n      floatBorderHighlight: defaultValue(config.floatBorderHighlight, floatHighlightGroup),\n      pickerButtons: config.pickerButtons,\n      pickerButtonShortcut: config.pickerButtonShortcut,\n      confirmKey: toText(config.confirmKey),\n      shortcutHighlight: toText(config.shortcutHighlight)\n    }\n  }\n}\n"
  },
  {
    "path": "src/core/documents.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { FormattingOptions, Location, LocationLink, TextEdit } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport commands from '../commands'\nimport Configurations from '../configuration'\nimport { IConfigurationChangeEvent } from '../configuration/types'\nimport events from '../events'\nimport languages from '../languages'\nimport { createLogger } from '../logger'\nimport Document from '../model/document'\nimport { LinesTextDocument } from '../model/textdocument'\nimport { BufferOption, DidChangeTextDocumentParams, Env, LocationWithTarget, QuickfixItem } from '../types'\nimport { defaultValue, disposeAll, getConditionValue } from '../util'\nimport { isFalsyOrEmpty } from '../util/array'\nimport { isVim } from '../util/constants'\nimport { convertFormatOptions, VimFormatOption } from '../util/convert'\nimport { normalizeFilePath, readFile, readFileLine, resolveRoot } from '../util/fs'\nimport { emptyObject } from '../util/is'\nimport { fs, os, path } from '../util/node'\nimport { hasOwnProperty, toObject } from '../util/object'\nimport * as platform from '../util/platform'\nimport { CancellationTokenSource, Disposable, Emitter, Event, TextDocumentSaveReason } from '../util/protocol'\nimport { byteIndex, toText } from '../util/string'\nimport type { TextDocumentWillSaveEvent } from './files'\nimport WorkspaceFolder from './workspaceFolder'\nconst logger = createLogger('core-documents')\n\ninterface StateInfo {\n  bufnr: number\n  winid: number\n  bufnrs: number[]\n  winids: number[]\n}\n\ninterface DocumentsConfig {\n  maxFileSize: number\n  willSaveHandlerTimeout: number\n  formatOnSaveTimeout: number\n  useQuickfixForLocations: boolean\n}\n\nconst cwd = normalizeFilePath(process.cwd())\n// Many FileType events may emitted on buffer reload\nconst filetypeDelay = getConditionValue(50, 10)\n\nexport default class Documents implements Disposable {\n  private _cwd: string\n  private _env: Env\n  private _bufnr: number\n  private _root: string\n  private _attached = false\n  private _currentResolve = false\n  private nvim: Neovim\n  private config: DocumentsConfig\n  private disposables: Disposable[] = []\n  private _filetypeTimer: Map<number, NodeJS.Timeout> = new Map()\n  private creating: Map<number, Promise<Document | undefined>> = new Map()\n  public buffers: Map<number, Document> = new Map()\n  private resolves: ((doc: Document) => void)[] = []\n  private readonly _onDidOpenTextDocument = new Emitter<LinesTextDocument>()\n  private readonly _onDidCloseDocument = new Emitter<LinesTextDocument>()\n  private readonly _onDidChangeDocument = new Emitter<DidChangeTextDocumentParams>()\n  private readonly _onDidSaveDocument = new Emitter<LinesTextDocument>()\n  private readonly _onWillSaveDocument = new Emitter<TextDocumentWillSaveEvent>()\n\n  public readonly onDidOpenTextDocument: Event<LinesTextDocument> = this._onDidOpenTextDocument.event\n  public readonly onDidCloseDocument: Event<LinesTextDocument> = this._onDidCloseDocument.event\n  public readonly onDidChangeDocument: Event<DidChangeTextDocumentParams> = this._onDidChangeDocument.event\n  public readonly onDidSaveTextDocument: Event<LinesTextDocument> = this._onDidSaveDocument.event\n  public readonly onWillSaveTextDocument: Event<TextDocumentWillSaveEvent> = this._onWillSaveDocument.event\n\n  constructor(\n    private readonly configurations: Configurations,\n    private readonly workspaceFolder: WorkspaceFolder,\n  ) {\n    this._cwd = cwd\n    this.getConfiguration()\n    this.configurations.onDidChange(this.getConfiguration, this, this.disposables)\n  }\n\n  public async attach(nvim: Neovim, env: Env): Promise<void> {\n    if (this._attached) return\n    this.nvim = nvim\n    this._env = env\n    this._attached = true\n    let { bufnrs, bufnr } = await this.nvim.call('coc#util#all_state') as StateInfo\n    this._bufnr = bufnr\n    await Promise.all(bufnrs.map(bufnr => this.createDocument(bufnr)))\n    if (isVim) {\n      const checkedTick: Map<number, number> = new Map()\n      events.on('CursorHold', async bufnr => {\n        let doc = this.getDocument(bufnr)\n        if (doc && doc.attached && checkedTick.get(bufnr) != doc.changedtick) {\n          let sha256 = doc.getSha256()\n          let same = await nvim.callVim('coc#vim9#Check_sha256', [bufnr, sha256])\n          checkedTick.set(bufnr, doc.changedtick)\n          if (!same) await doc.fetchLines()\n        }\n      }, null, this.disposables)\n    }\n    events.on('BufDetach', this.onBufDetach, this, this.disposables)\n    events.on('BufRename', async bufnr => {\n      this.detachBuffer(bufnr)\n      await this.createDocument(bufnr)\n    }, null, this.disposables)\n    events.on('DirChanged', cwd => {\n      this._cwd = normalizeFilePath(cwd)\n    }, null, this.disposables)\n    const checkCurrentBuffer = (bufnr: number) => {\n      this._bufnr = bufnr\n      void this.createDocument(bufnr)\n    }\n    events.on('CursorMoved', checkCurrentBuffer, null, this.disposables)\n    events.on('CursorMovedI', checkCurrentBuffer, null, this.disposables)\n    events.on('BufUnload', this.onBufUnload, this, this.disposables)\n    events.on('BufEnter', this.onBufEnter, this, this.disposables)\n    events.on('BufCreate', this.onBufCreate, this, this.disposables)\n    events.on('TermOpen', this.onBufCreate, this, this.disposables)\n    events.on('BufWritePost', this.onBufWritePost, this, this.disposables)\n    events.on('BufWritePre', this.onBufWritePre, this, this.disposables)\n    events.on('FileType', this.onFileTypeChange, this, this.disposables)\n    events.on('BufEnter', (bufnr: number) => {\n      void this.createDocument(bufnr)\n    }, null, this.disposables)\n    events.on('TextChanged', this.onTextChange, this, this.disposables)\n    events.on('TextChangedI', this.onTextChange, this, this.disposables)\n  }\n\n  private onTextChange(bufnr: number): void {\n    let doc = this.getDocument(bufnr)\n    if (doc) doc.onTextChange()\n  }\n\n  private getConfiguration(e?: IConfigurationChangeEvent): void {\n    if (!e || e.affectsConfiguration('coc.preferences')) {\n      let config = this.configurations.initialConfiguration.get('coc.preferences') as any\n      const bytes = require('bytes')\n      this.config = {\n        maxFileSize: bytes.parse(config.maxFileSize),\n        willSaveHandlerTimeout: defaultValue(config.willSaveHandlerTimeout, 500),\n        formatOnSaveTimeout: defaultValue(config.formatOnSaveTimeout, 500),\n        useQuickfixForLocations: config.useQuickfixForLocations\n      }\n    }\n  }\n\n  public get bufnr(): number {\n    return this._bufnr\n  }\n\n  public get root(): string {\n    return this._root\n  }\n\n  public get cwd(): string {\n    return this._cwd\n  }\n\n  public get documents(): Document[] {\n    return Array.from(this.buffers.values()).filter(o => o.attached)\n  }\n\n  public async getCurrentUri(): Promise<string | undefined> {\n    let bufnr = await this.nvim.call('bufnr', ['%']) as number\n    let doc = this.getDocument(bufnr)\n    return doc ? doc.uri : undefined\n  }\n\n  public *attached(schema?: string): Iterable<Document> {\n    for (let doc of this.buffers.values()) {\n      if (!doc.attached) continue\n      if (schema && doc.schema !== schema) continue\n      yield doc\n    }\n  }\n\n  public get bufnrs(): Iterable<number> {\n    return this.buffers.keys()\n  }\n\n  public detach(): void {\n    this._attached = false\n    for (let bufnr of this.buffers.keys()) {\n      this.onBufUnload(bufnr)\n    }\n  }\n\n  public resolveRoot(rootPatterns: string[], requireRootPattern = false): string | undefined {\n    let doc = this.getDocument(this.bufnr)\n    let resolved: string | undefined\n    if (doc && doc.schema == 'file') {\n      let dir = path.dirname(URI.parse(doc.uri).fsPath)\n      resolved = resolveRoot(dir, rootPatterns, this.cwd)\n    } else {\n      resolved = resolveRoot(this.cwd, rootPatterns)\n    }\n    if (requireRootPattern && !resolved) {\n      throw new Error(`Required root pattern not resolved.`)\n    }\n    return resolved\n  }\n\n  public get textDocuments(): LinesTextDocument[] {\n    let docs: LinesTextDocument[] = []\n    for (let b of this.buffers.values()) {\n      if (b.attached) docs.push(b.textDocument)\n    }\n    return docs\n  }\n\n  public getDocument(uri: number | string, caseInsensitive = platform.isWindows || platform.isMacintosh): Document | null | undefined {\n    if (typeof uri === 'number') {\n      return this.buffers.get(uri)\n    }\n    let u = URI.parse(uri)\n    uri = u.toString()\n    let isFile = u.scheme === 'file'\n    for (let doc of this.buffers.values()) {\n      if (doc.uri === uri) return doc\n      if (isFile && caseInsensitive && doc.uri.toLowerCase() === uri.toLowerCase()) return doc\n    }\n    return null\n  }\n\n  /**\n   * Expand filepath with `~` and/or environment placeholders\n   */\n  public expand(input: string): string {\n    if (input.startsWith('~')) {\n      input = os.homedir() + input.slice(1)\n    }\n    if (input.includes('$')) {\n      let doc = this.getDocument(this.bufnr)\n      let fsPath = doc ? URI.parse(doc.uri).fsPath : ''\n      const root = this._root || this._cwd\n      input = input.replace(/\\$\\{(.*?)\\}/g, (match: string, name: string) => {\n        if (name.startsWith('env:')) {\n          let key = name.split(':')[1]\n          let val = key ? process.env[key] : ''\n          return val\n        }\n        switch (name) {\n          case 'tmpdir':\n            return os.tmpdir()\n          case 'userHome':\n            return os.homedir()\n          case 'workspace':\n          case 'workspaceRoot':\n          case 'workspaceFolder':\n            return root\n          case 'workspaceFolderBasename':\n            return path.basename(root)\n          case 'cwd':\n            return this._cwd\n          case 'file':\n            return fsPath\n          case 'fileDirname':\n            return fsPath ? path.dirname(fsPath) : ''\n          case 'fileExtname':\n            return fsPath ? path.extname(fsPath) : ''\n          case 'fileBasename':\n            return fsPath ? path.basename(fsPath) : ''\n          case 'fileBasenameNoExtension': {\n            let base = fsPath ? path.basename(fsPath) : ''\n            return base ? base.slice(0, base.length - path.extname(base).length) : ''\n          }\n          default:\n            return match\n        }\n      })\n      input = input.replace(/\\$[\\w]+/g, match => {\n        if (match == '$HOME') return os.homedir()\n        return process.env[match.slice(1)] || match\n      })\n    }\n    return input\n  }\n\n  /**\n   * Current document.\n   */\n  public get document(): Promise<Document | undefined> {\n    if (this._currentResolve) {\n      return new Promise<Document>(resolve => {\n        this.resolves.push(resolve)\n      })\n    }\n    this._currentResolve = true\n    return new Promise<Document>(resolve => {\n      this.nvim.eval(`coc#util#get_bufoptions(bufnr(\"%\"),${this.config.maxFileSize})`).then((opts: any) => {\n        let doc: Document | undefined\n        if (opts != null) {\n          this.creating.delete(opts.bufnr)\n          doc = this._createDocument(opts)\n        }\n        this.resolveCurrent(doc)\n        resolve(doc)\n        this._currentResolve = false\n      }, () => {\n        resolve(undefined)\n        this._currentResolve = false\n      })\n    })\n  }\n\n  private resolveCurrent(document: Document | undefined): void {\n    if (this.resolves.length > 0) {\n      while (this.resolves.length) {\n        const fn = this.resolves.pop()\n        if (fn) fn(document)\n      }\n    }\n  }\n\n  public get uri(): string {\n    let { bufnr } = this\n    if (bufnr) {\n      let doc = this.getDocument(bufnr)\n      if (doc) return doc.uri\n    }\n    return null\n  }\n\n  /**\n   * Current filetypes.\n   */\n  public get filetypes(): Set<string> {\n    let res = new Set<string>()\n    for (let doc of this.attached()) {\n      res.add(doc.filetype)\n    }\n    return res\n  }\n\n  /**\n   * Get filetype by check same extension name buffer.\n   */\n  public getLanguageId(filepath: string): string {\n    let ext = path.extname(filepath)\n    if (!ext) return ''\n    for (let doc of this.attached()) {\n      let fsPath = URI.parse(doc.uri).fsPath\n      if (path.extname(fsPath) == ext) {\n        return doc.languageId\n      }\n    }\n    return ''\n  }\n\n  public async getLines(uri: string): Promise<readonly string[]> {\n    let doc = this.getDocument(uri)\n    if (doc) return doc.textDocument.lines\n    let u = URI.parse(uri)\n    if (u.scheme !== 'file') return []\n    try {\n      let content = await readFile(u.fsPath, 'utf8')\n      return content.split(/\\r?\\n/)\n    } catch (e) {\n      return []\n    }\n  }\n\n  /**\n   * Current languageIds.\n   */\n  public get languageIds(): Set<string> {\n    let res = new Set<string>()\n    for (let doc of this.attached()) {\n      res.add(doc.languageId)\n    }\n    return res\n  }\n\n  /**\n   * Get format options\n   */\n  public async getFormatOptions(uri?: string | number): Promise<FormattingOptions> {\n    let bufnr = typeof uri === 'number' ? uri : this.getBufnr(uri)\n    let res = await this.nvim.call('coc#util#get_format_opts', [bufnr]) as VimFormatOption\n    return convertFormatOptions(res)\n  }\n\n  public getBufnr(uri?: string): number {\n    if (!uri) return 0\n    let doc = this.getDocument(uri)\n    return doc ? doc.bufnr : 0\n  }\n\n  /**\n   * Create document by bufnr.\n   */\n  public async createDocument(bufnr: number): Promise<Document | undefined> {\n    let doc = this.buffers.get(bufnr)\n    if (doc) return doc\n    if (this.creating.has(bufnr)) return await this.creating.get(bufnr)\n    let promise = new Promise<Document | undefined>(resolve => {\n      this.nvim.call('coc#util#get_bufoptions', [bufnr, this.config.maxFileSize]).then(opts => {\n        if (!this.creating.has(bufnr)) {\n          resolve(undefined)\n          return\n        }\n        this.creating.delete(bufnr)\n        if (!opts) {\n          resolve(undefined)\n          return\n        }\n        doc = this._createDocument(opts as BufferOption)\n        resolve(doc)\n      }, () => {\n        this.creating.delete(bufnr)\n        resolve(undefined)\n      })\n    })\n    this.creating.set(bufnr, promise)\n    return await promise\n  }\n\n  public async onBufCreate(bufnr: number): Promise<void> {\n    this.onBufUnload(bufnr)\n    await this.createDocument(bufnr)\n  }\n\n  private _createDocument(opts: BufferOption): Document {\n    let { bufnr } = opts\n    if (this.buffers.has(bufnr)) return this.buffers.get(bufnr)\n    let buffer = this.nvim.createBuffer(bufnr)\n    let doc = new Document(buffer, this.nvim, this.convertFiletype(opts.filetype), opts)\n    if (opts.size > this.config.maxFileSize) logger.warn(`buffer ${opts.bufnr} size exceed maxFileSize ${this.config.maxFileSize}, not attached.`)\n    this.buffers.set(bufnr, doc)\n    if (doc.attached) {\n      if (doc.schema == 'file') {\n        // TODO use workspaceFolder for root when exists\n        this.configurations.locateFolderConfigution(doc.uri)\n        let root = this.workspaceFolder.resolveRoot(doc, this._cwd, true, this.expand.bind(this))\n        if (root && bufnr == this._bufnr) this.changeRoot(root)\n      }\n      this._onDidOpenTextDocument.fire(doc.textDocument)\n      doc.onDocumentChange(e => this._onDidChangeDocument.fire(e))\n    }\n    logger.debug('buffer created', bufnr, doc.attached, doc.uri)\n    return doc\n  }\n\n  private onBufEnter(bufnr: number): void {\n    this._bufnr = bufnr\n    let doc = this.buffers.get(bufnr)\n    if (doc) {\n      let workspaceFolder = this.workspaceFolder.getWorkspaceFolder(URI.parse(doc.uri))\n      if (workspaceFolder) this._root = URI.parse(workspaceFolder.uri).fsPath\n    }\n  }\n\n  private onBufUnload(bufnr: number): void {\n    this.creating.delete(bufnr)\n    void this.onBufDetach(bufnr, false)\n  }\n\n  private async onBufDetach(bufnr: number, checkReload = true): Promise<void> {\n    this.clearTimer(bufnr)\n    this.detachBuffer(bufnr)\n    if (checkReload) {\n      let loaded = await this.nvim.call('bufloaded', [bufnr])\n      if (loaded) await this.createDocument(bufnr)\n    }\n  }\n\n  public detachBuffer(bufnr: number): void {\n    let doc = this.buffers.get(bufnr)\n    this.buffers.delete(bufnr)\n    if (!doc || !doc.attached) return\n    logger.debug('document detach', bufnr, doc.uri)\n    this._onDidCloseDocument.fire(doc.textDocument)\n    doc.detach()\n    const uris = this.textDocuments.map(o => URI.parse(o.uri))\n    this.workspaceFolder.onDocumentDetach(uris)\n  }\n\n  private async onBufWritePost(bufnr: number, changedtick: number): Promise<void> {\n    let doc = this.buffers.get(bufnr)\n    if (doc) {\n      if (doc.changedtick != changedtick) await doc.patchChange()\n      this._onDidSaveDocument.fire(doc.textDocument)\n    }\n  }\n\n  private async onBufWritePre(bufnr: number, bufname: string, changedtick: number): Promise<void> {\n    let doc = this.buffers.get(bufnr)\n    if (!doc || !doc.attached) return\n    if (doc.bufname != bufname) {\n      this.detachBuffer(bufnr)\n      doc = await this.createDocument(bufnr)\n      if (!doc.attached) return\n    }\n    if (doc.changedtick != changedtick) {\n      await doc.synchronize()\n    } else {\n      await doc.patchChange()\n    }\n    let firing = true\n    let thenables: Thenable<TextEdit[] | any>[] = []\n    let event: TextDocumentWillSaveEvent = {\n      bufnr: doc.bufnr,\n      document: doc.textDocument,\n      reason: TextDocumentSaveReason.Manual,\n      waitUntil: (thenable: Thenable<any>) => {\n        if (!firing) {\n          this.nvim.echoError(`waitUntil can't be used in async manner, check log for details`)\n        } else {\n          thenables.push(thenable)\n        }\n      }\n    }\n    this._onWillSaveDocument.fire(event)\n    firing = false\n    let total = thenables.length\n    if (total) {\n      let promise = new Promise<TextEdit[] | undefined>(resolve => {\n        const willSaveHandlerTimeout = this.config.willSaveHandlerTimeout\n        let timer = setTimeout(() => {\n          this.nvim.outWriteLine(`Will save handler timeout after ${willSaveHandlerTimeout}ms`)\n          resolve(undefined)\n        }, willSaveHandlerTimeout)\n        let i = 0\n        let called = false\n        for (let p of thenables) {\n          let cb = (res: any) => {\n            if (called) return\n            called = true\n            clearTimeout(timer)\n            resolve(res)\n          }\n          p.then(res => {\n            if (Array.isArray(res) && res.length && TextEdit.is(res[0])) {\n              return cb(res)\n            }\n            i = i + 1\n            if (i == total) cb(undefined)\n          }, e => {\n            logger.error(`Error on will save handler:`, e)\n            i = i + 1\n            if (i == total) cb(undefined)\n          })\n        }\n      })\n      let edits = await promise\n      if (edits) await doc.applyEdits(edits, false, this.bufnr === doc.bufnr)\n    }\n    await this.tryCodeActionsOnSave(doc)\n    await this.tryFormatOnSave(doc)\n  }\n\n  public async tryFormatOnSave(document: Document): Promise<void> {\n    if (!this.shouldFormatOnSave(document)) return\n    let options = await this.getFormatOptions(document.uri)\n    let formatOnSaveTimeout = this.config.formatOnSaveTimeout\n    let timer: NodeJS.Timeout\n    let tokenSource = new CancellationTokenSource()\n    const tp = new Promise<undefined>(c => {\n      timer = setTimeout(() => {\n        logger.warn(`Format on save timeout after ${formatOnSaveTimeout}ms`, document.uri)\n        tokenSource.cancel()\n        c(undefined)\n      }, formatOnSaveTimeout)\n    })\n    const provideEdits = languages.provideDocumentFormattingEdits(document.textDocument, options, tokenSource.token)\n    let textEdits = await Promise.race([tp, provideEdits])\n    clearTimeout(timer)\n    if (isFalsyOrEmpty(textEdits)) return\n    await document.applyEdits(textEdits)\n    let extensionName = textEdits['__extensionName']\n    logger.info(`Format buffer ${document.bufnr} by ${toText(extensionName)}`)\n  }\n\n  public async tryCodeActionsOnSave(doc: Document): Promise<boolean> {\n    let editorConfig = this.configurations.getConfiguration('editor', doc.textDocument)\n    let conf = editorConfig.get('codeActionsOnSave', {})\n    if (emptyObject(conf)) return false\n    const actions: string[] = []\n    for (const key of Object.keys(conf)) {\n      if (conf[key] === true || conf[key] === 'always') {\n        actions.push(key)\n      }\n    }\n    if (actions.length === 0) return false\n    await commands.executeCommand('editor.action.executeCodeActions', doc, undefined, actions, this.config.willSaveHandlerTimeout)\n    return true\n  }\n\n  public shouldFormatOnSave(document: Document): boolean {\n    if (!languages.hasFormatProvider(document)) {\n      logger.warn(`Format provider not found for ${document.uri}`)\n      return false\n    }\n    if (!document || document.getVar('disable_autoformat', 0)) {\n      logger.warn(`Format ${document.uri} disabled by b:coc_disable_autoformat`)\n      return false\n    }\n    let config = this.configurations.getConfiguration('coc.preferences', document)\n    let filetypes = config.get<string[] | null>('formatOnSaveFiletypes', null)\n    if (Array.isArray(filetypes)) return filetypes.includes('*') || filetypes.includes(document.languageId)\n    let formatOnSave = config.get<boolean>('formatOnSave', false)\n    return formatOnSave\n  }\n\n  public onFileTypeChange(filetype: string, bufnr: number): void {\n    let doc = this.getDocument(bufnr)\n    if (!doc) return\n    this.clearTimer(bufnr)\n    let timer = setTimeout(() => {\n      if (this.creating.has(bufnr) || !doc.attached) return\n      let converted = this.convertFiletype(filetype)\n      if (converted === doc.filetype) return\n      this._onDidCloseDocument.fire(doc.textDocument)\n      doc.setFiletype(filetype)\n      this._onDidOpenTextDocument.fire(doc.textDocument)\n    }, filetypeDelay)\n    this._filetypeTimer.set(bufnr, timer)\n  }\n\n  public async getQuickfixList(locations: LocationWithTarget[]): Promise<ReadonlyArray<QuickfixItem>> {\n    let filesLines: { [fsPath: string]: string[] } = {}\n    let filepathList = locations.reduce<string[]>((pre: string[], curr) => {\n      let u = URI.parse(curr.uri)\n      if (u.scheme == 'file' && !pre.includes(u.fsPath) && !this.getDocument(curr.uri)) {\n        pre.push(u.fsPath)\n      }\n      return pre\n    }, [])\n\n    await Promise.all(filepathList.map(fsPath => {\n      return new Promise<void>(resolve => {\n        readFile(fsPath, 'utf8').then(content => {\n          filesLines[fsPath] = content.split(/\\r?\\n/)\n          resolve(undefined)\n        }, () => {\n          resolve()\n        })\n      })\n    }))\n    return await Promise.all(locations.map(loc => {\n      let { uri, range } = loc\n      let { fsPath } = URI.parse(uri)\n      let text: string | undefined\n      let lines = filesLines[fsPath]\n      if (lines) text = lines[range.start.line]\n      return this.getQuickfixItem(loc, text)\n    }))\n  }\n\n  /**\n   * Populate locations to UI.\n   */\n  public async showLocations(locations: LocationWithTarget[]): Promise<void> {\n    let { nvim } = this\n    let items = await this.getQuickfixList(locations)\n    if (this.config.useQuickfixForLocations) {\n      let openCommand = await nvim.getVar('coc_quickfix_open_command') as string\n      if (typeof openCommand != 'string') {\n        openCommand = items.length < 10 ? `copen ${items.length}` : 'copen'\n      }\n      nvim.pauseNotification()\n      nvim.call('setqflist', [items], true)\n      nvim.command(openCommand, true)\n      nvim.resumeNotification(false, true)\n    } else {\n      await nvim.setVar('coc_jump_locations', items)\n      if (this._env.locationlist) {\n        nvim.command('CocList --normal --auto-preview location', true)\n      } else {\n        nvim.call('coc#util#do_autocmd', ['CocLocationsChange'], true)\n      }\n    }\n  }\n\n  public fixUnixPrefix(filepath: string): string {\n    if (!this._env.isCygwin || !/^\\w:/.test(filepath)) return filepath\n    return this._env.unixPrefix + filepath[0].toLowerCase() + filepath.slice(2).replace(/\\\\/g, '/')\n  }\n\n  /**\n   * Convert location to quickfix item.\n   */\n  public async getQuickfixItem(loc: LocationWithTarget | LocationLink, text?: string, type = '', module?: string): Promise<QuickfixItem> {\n    let targetRange = loc.targetRange\n    if (LocationLink.is(loc)) {\n      loc = Location.create(loc.targetUri, loc.targetRange)\n    }\n    let doc = this.getDocument(loc.uri)\n    let { uri, range } = loc\n    let { start, end } = range\n    let u = URI.parse(uri)\n    if (!text && u.scheme == 'file') {\n      text = await this.getLine(uri, start.line)\n    }\n    let endLine = start.line == end.line ? text : await this.getLine(uri, end.line)\n    let item: QuickfixItem = {\n      uri,\n      filename: u.scheme == 'file' ? this.fixUnixPrefix(u.fsPath) : uri,\n      lnum: start.line + 1,\n      end_lnum: end.line + 1,\n      col: text ? byteIndex(text, start.character) + 1 : start.character + 1,\n      end_col: endLine ? byteIndex(endLine, end.character) + 1 : end.character + 1,\n      text: text || '',\n      range\n    }\n    if (targetRange) item.targetRange = targetRange\n    if (module) item.module = module\n    if (type) item.type = type\n    if (doc) item.bufnr = doc.bufnr\n    return item\n  }\n\n  /**\n   * Get content of line by uri and line.\n   */\n  public async getLine(uri: string, line: number): Promise<string> {\n    let document = this.getDocument(uri)\n    if (document && document.attached) return document.getline(line) || ''\n    if (!uri.startsWith('file:')) return ''\n    let fsPath = URI.parse(uri).fsPath\n    if (!fs.existsSync(fsPath)) return ''\n    return await readFileLine(fsPath, line)\n  }\n\n  /**\n   * Get content from buffer or file by uri.\n   */\n  public async readFile(uri: string): Promise<string> {\n    let document = this.getDocument(uri)\n    if (document) {\n      await document.patchChange()\n      return document.content\n    }\n    let u = URI.parse(uri)\n    if (u.scheme != 'file') return ''\n    let lines = await this.nvim.call('readfile', [u.fsPath]) as string[]\n    return lines.join('\\n') + '\\n'\n  }\n\n  private clearTimer(bufnr: number): void {\n    let timer = this._filetypeTimer.get(bufnr)\n    if (timer) clearTimeout(timer)\n  }\n\n  public convertFiletype(filetype: string): string {\n    switch (filetype) {\n      case 'javascript.jsx':\n        return 'javascriptreact'\n      case 'typescript.jsx':\n      case 'typescript.tsx':\n        return 'typescriptreact'\n      case 'tex':\n        // Vim filetype 'tex' means LaTeX, which has LSP language ID 'latex'\n        return 'latex'\n      default: {\n        let map = toObject(this._env.filetypeMap)\n        return toText(hasOwnProperty(map, filetype) ? map[filetype] : filetype)\n      }\n    }\n  }\n\n  public reset(): void {\n    this.creating.clear()\n    for (let bufnr of this.buffers.keys()) {\n      this.onBufUnload(bufnr)\n    }\n    this.buffers.clear()\n    this.changeRoot(process.cwd())\n  }\n\n  private changeRoot(dir: string): void {\n    this._root = normalizeFilePath(dir)\n  }\n\n  public dispose(): void {\n    for (let bufnr of this.buffers.keys()) {\n      this.onBufUnload(bufnr)\n    }\n    this._attached = false\n    this.buffers.clear()\n    disposeAll(this.disposables)\n  }\n}\n"
  },
  {
    "path": "src/core/editors.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { FormattingOptions, Range } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport events from '../events'\nimport { createLogger } from '../logger'\nimport type Document from '../model/document'\nimport { convertFormatOptions, VimFormatOption } from '../util/convert'\nimport { onUnexpectedError } from '../util/errors'\nimport { sameFile } from '../util/fs'\nimport { Mutex } from '../util/mutex'\nimport { Disposable, Emitter, Event } from '../util/protocol'\nimport Documents from './documents'\nconst logger = createLogger('core-editors')\n\ninterface EditorOption {\n  bufnr: number\n  winid: number\n  tabpageid: number\n  winnr: number\n  visibleRanges: [number, number][]\n  tabSize: number\n  insertSpaces: boolean\n  formatOptions: VimFormatOption\n}\n\ninterface EditorInfo {\n  readonly winid: number\n  readonly bufnr: number\n  readonly tabid: number\n  readonly fullpath: string\n}\n\nexport interface TextEditor {\n  readonly id: string\n  readonly tabpageid: number\n  readonly winid: number\n  readonly winnr: number\n  readonly document: Document\n  readonly visibleRanges: readonly Range[]\n  readonly uri: string\n  readonly bufnr: number\n  readonly options: FormattingOptions\n}\n\nexport function renamed(editor: TextEditor, info: EditorInfo): boolean {\n  let { document, uri } = editor\n  if (document.bufnr != info.bufnr) return false\n  let u = URI.parse(uri)\n  if (u.scheme === 'file') return !sameFile(u.fsPath, info.fullpath)\n  return false\n}\n\nexport default class Editors {\n  private disposables: Disposable[] = []\n  private winid = -1\n  private mutex: Mutex = new Mutex()\n  private previousId: string | undefined\n  private nvim: Neovim\n  private editors: Map<number, TextEditor> = new Map()\n  private tabIds: Set<number> = new Set()\n  private creating: Set<number> = new Set()\n  private readonly _onDidTabClose = new Emitter<number>()\n  private readonly _onDidChangeActiveTextEditor = new Emitter<TextEditor | undefined>()\n  private readonly _onDidChangeVisibleTextEditors = new Emitter<ReadonlyArray<TextEditor>>()\n  public readonly onDidTabClose: Event<number> = this._onDidTabClose.event\n  public readonly onDidChangeActiveTextEditor: Event<TextEditor | undefined> = this._onDidChangeActiveTextEditor.event\n  public readonly onDidChangeVisibleTextEditors: Event<ReadonlyArray<TextEditor>> = this._onDidChangeVisibleTextEditors.event\n  constructor(private documents: Documents) {\n  }\n\n  public get activeTextEditor(): TextEditor | undefined {\n    return this.editors.get(this.winid)\n  }\n\n  public get visibleTextEditors(): TextEditor[] {\n    return Array.from(this.editors.values())\n  }\n\n  public getFormatOptions(bufnr: number | string): FormattingOptions | undefined {\n    for (let editor of this.editors.values()) {\n      if (editor.bufnr === bufnr || editor.uri === bufnr) return editor.options\n    }\n    return undefined\n  }\n\n  public getBufWinids(bufnr: number): number[] {\n    let winids: number[] = []\n    for (let editor of this.editors.values()) {\n      if (editor.bufnr == bufnr) winids.push(editor.winid)\n    }\n    return winids\n  }\n\n  private onChangeCurrent(editor: TextEditor | undefined): void {\n    if (!editor) return\n    let id = editor.id\n    if (id === this.previousId) return\n    this.previousId = id\n    this._onDidChangeActiveTextEditor.fire(editor)\n  }\n\n  public async attach(nvim: Neovim): Promise<void> {\n    this.nvim = nvim\n    let [winid, infos] = await nvim.eval(`[win_getid(),coc#util#editor_infos()]`) as [number, EditorInfo[]]\n    await Promise.allSettled(infos.map(info => {\n      return this.createTextEditor(info.winid)\n    }))\n    this.winid = winid\n    events.on('CursorHold', this.checkEditors, this, this.disposables)\n    events.on('TabNew', (tabid: number) => {\n      this.tabIds.add(tabid)\n    }, null, this.disposables)\n    events.on('TabClosed', this.checkTabs, this, this.disposables)\n    events.on('WinEnter', (winid: number) => {\n      this.winid = winid\n      let editor = this.editors.get(winid)\n      if (editor) this.onChangeCurrent(editor)\n    }, null, this.disposables)\n    events.on('WinClosed', (winid: number) => {\n      if (this.editors.has(winid)) {\n        this.editors.delete(winid)\n        this._onDidChangeVisibleTextEditors.fire(this.visibleTextEditors)\n      }\n    }, null, this.disposables)\n    events.on('BufWinEnter', async (_: number, winid: number) => {\n      this.winid = winid\n      let changed = await this.createTextEditor(winid)\n      if (changed) this._onDidChangeVisibleTextEditors.fire(this.visibleTextEditors)\n    }, null, this.disposables)\n    this.documents.onDidOpenTextDocument(async e => {\n      let document = this.documents.getDocument(e.bufnr)\n      let changed = false\n      for (let winid of document.winids) {\n        let editor = this.editors.get(winid)\n        // buffer can be reloaded\n        if (editor?.document !== document) {\n          let res = await this.createTextEditor(winid).catch(onUnexpectedError)\n          if (res) changed = true\n        }\n      }\n      if (changed) this._onDidChangeVisibleTextEditors.fire(this.visibleTextEditors)\n    }, null, this.disposables)\n  }\n\n  public checkTabs(ids: number[]): void {\n    let changed = false\n    for (let editor of this.editors.values()) {\n      if (!ids.includes(editor.tabpageid)) {\n        changed = true\n        this.editors.delete(editor.winid)\n      }\n    }\n    for (let id of Array.from(this.tabIds)) {\n      if (!ids.includes(id)) this._onDidTabClose.fire(id)\n    }\n    this.tabIds = new Set(ids)\n    if (changed) this._onDidChangeVisibleTextEditors.fire(this.visibleTextEditors)\n  }\n\n  public checkUnloadedBuffers(bufnrs: number[]): void {\n    for (let bufnr of this.documents.bufnrs) {\n      if (!bufnrs.includes(bufnr)) {\n        void events.fire('BufUnload', [bufnr])\n      }\n    }\n  }\n\n  public async checkEditors(): Promise<void> {\n    let { documents } = this\n    await this.mutex.use(async () => {\n      let [winid, bufnrs, infos] = await this.nvim.eval(`[win_getid(),coc#util#get_loaded_bufs(),coc#util#editor_infos()]`) as [number, number[], EditorInfo[]]\n      this.winid = winid\n      this.checkUnloadedBuffers(bufnrs)\n      let changed = false\n      let winids: Set<number> = new Set()\n      for (let info of infos) {\n        let editor = this.editors.get(info.winid)\n        let create = false\n        if (!editor) {\n          create = true\n        } else if (renamed(editor, info)) {\n          void events.fire('BufRename', [info.bufnr])\n          create = true\n        } else if (editor.document.bufnr != info.bufnr\n          || editor.document !== documents.getDocument(info.bufnr)\n          || editor.tabpageid != info.tabid) {\n          create = true\n        }\n        if (create) {\n          await this.createTextEditor(info.winid)\n          changed = true\n        }\n        winids.add(info.winid)\n      }\n      if (this.cleanUpEditors(winids)) {\n        changed = true\n      }\n      this.onChangeCurrent(this.activeTextEditor)\n      if (changed) this._onDidChangeVisibleTextEditors.fire(this.visibleTextEditors)\n    })\n  }\n\n  public cleanUpEditors(winids: Set<number>): boolean {\n    let changed = false\n    for (let winid of Array.from(this.editors.keys())) {\n      if (!winids.has(winid)) {\n        changed = true\n        this.editors.delete(winid)\n      }\n    }\n    return changed\n  }\n\n  private async createTextEditor(winid: number): Promise<boolean> {\n    let { documents, creating, nvim } = this\n    if (creating.has(winid)) return false\n    let changed = false\n    creating.add(winid)\n    let opts = await nvim.call('coc#util#get_editoroption', [winid]) as EditorOption\n    if (opts) {\n      this.tabIds.add(opts.tabpageid)\n      let doc = documents.getDocument(opts.bufnr)\n      if (doc && doc.attached) {\n        let editor = this.fromOptions(opts)\n        this.editors.set(winid, editor)\n        if (winid == this.winid) this.onChangeCurrent(editor)\n        logger.debug('editor created winid & bufnr & tabpageid: ', winid, opts.bufnr, opts.tabpageid)\n        changed = true\n      } else if (this.editors.has(winid)) {\n        this.editors.delete(winid)\n        changed = true\n      }\n    }\n    creating.delete(winid)\n    return changed\n  }\n\n  private fromOptions(opts: EditorOption): TextEditor {\n    let { visibleRanges, bufnr, formatOptions } = opts\n    let { documents } = this\n    let document = documents.getDocument(bufnr)\n    return {\n      id: `${opts.tabpageid}-${opts.winid}-${document.uri}`,\n      tabpageid: opts.tabpageid,\n      winid: opts.winid,\n      winnr: opts.winnr,\n      uri: document.uri,\n      bufnr: document.bufnr,\n      document,\n      visibleRanges: visibleRanges.map(o => Range.create(o[0] - 1, 0, o[1], 0)),\n      options: convertFormatOptions(formatOptions)\n    }\n  }\n}\n"
  },
  {
    "path": "src/core/fileSystemWatcher.ts",
    "content": "'use strict'\nimport { WorkspaceFolder } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport { createLogger } from '../logger'\nimport { FileWatchConfig, GlobPattern, IFileSystemWatcher, OutputChannel } from '../types'\nimport { disposeAll } from '../util'\nimport { splitArray } from '../util/array'\nimport { isFolderIgnored, isParentFolder, sameFile } from '../util/fs'\nimport { minimatch, path, which } from '../util/node'\nimport { Disposable, Emitter, Event } from '../util/protocol'\nimport Watchman, { FileChange } from './watchman'\nimport type WorkspaceFolderControl from './workspaceFolder'\nconst logger = createLogger('fileSystemWatcher')\nconst WATCHMAN_COMMAND = 'watchman'\n\nexport interface RenameEvent {\n  oldUri: URI\n  newUri: URI\n}\n\nexport class FileSystemWatcherManager {\n  private clientsMap: Map<string, Watchman> = new Map()\n  private disposables: Disposable[] = []\n  private channel: OutputChannel | undefined\n  private creating: Set<string> = new Set()\n  public static watchers: Set<FileSystemWatcher> = new Set()\n  private readonly _onDidCreateClient = new Emitter<string>()\n  public disabled = global.__TEST__\n  public readonly onDidCreateClient: Event<string> = this._onDidCreateClient.event\n  constructor(\n    private workspaceFolder: WorkspaceFolderControl,\n    private config: FileWatchConfig\n  ) {\n    this.disabled = config.enable === false\n  }\n\n  public attach(channel: OutputChannel): void {\n    this.channel = channel\n    let createClient = (folder: WorkspaceFolder) => {\n      let root = URI.parse(folder.uri).fsPath\n      void this.createClient(root)\n    }\n    this.workspaceFolder.workspaceFolders.forEach(folder => {\n      createClient(folder)\n    })\n    this.workspaceFolder.onDidChangeWorkspaceFolders(e => {\n      e.added.forEach(folder => {\n        createClient(folder)\n      })\n      e.removed.forEach(folder => {\n        let root = URI.parse(folder.uri).fsPath\n        let client = this.clientsMap.get(root)\n        if (client) {\n          this.clientsMap.delete(root)\n          client.dispose()\n        }\n      })\n    }, null, this.disposables)\n  }\n\n  public waitClient(root: string): Promise<Watchman> {\n    if (this.clientsMap.has(root)) return Promise.resolve(this.clientsMap.get(root))\n    return new Promise(resolve => {\n      let disposable = this.onDidCreateClient(r => {\n        if (r == root) {\n          disposable.dispose()\n          resolve(this.clientsMap.get(r))\n        }\n      })\n    })\n  }\n\n  public async createClient(root: string, skipCheck = false): Promise<Watchman | false | undefined> {\n    if (!skipCheck && (this.disabled || isFolderIgnored(root, this.config.ignoredFolders))) return\n    if (this.has(root)) return this.waitClient(root)\n    try {\n      this.creating.add(root)\n      let watchmanPath = await this.getWatchmanPath()\n      let client = await Watchman.createClient(watchmanPath, root, this.channel)\n      this.creating.delete(root)\n      this.clientsMap.set(root, client)\n      for (let watcher of FileSystemWatcherManager.watchers) {\n        watcher.listen(root, client)\n      }\n      this._onDidCreateClient.fire(root)\n      return client\n    } catch (e) {\n      this.creating.delete(root)\n      if (this.channel) this.channel.appendLine(`Error on create watchman client: ${e}`)\n      return false\n    }\n  }\n\n  public async getWatchmanPath(): Promise<string> {\n    let watchmanPath = this.config.watchmanPath ?? WATCHMAN_COMMAND\n    if (!process.env.WATCHMAN_SOCK) {\n      watchmanPath = await which(watchmanPath, { all: false })\n    }\n    return watchmanPath\n  }\n\n  private has(root: string): boolean {\n    let curr = Array.from(this.clientsMap.keys())\n    curr.push(...this.creating)\n    return curr.some(r => sameFile(r, root))\n  }\n\n  public createFileSystemWatcher(globPattern: GlobPattern, ignoreCreateEvents: boolean, ignoreChangeEvents: boolean, ignoreDeleteEvents: boolean): FileSystemWatcher {\n    let fileWatcher = new FileSystemWatcher(globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents)\n    let base = typeof globPattern === 'string' ? undefined : globPattern.baseUri.fsPath\n    for (let [root, client] of this.clientsMap.entries()) {\n      if (base && isParentFolder(root, base, true)) {\n        base = undefined\n      }\n      fileWatcher.listen(root, client)\n    }\n    if (base) void this.createClient(base)\n    FileSystemWatcherManager.watchers.add(fileWatcher)\n    return fileWatcher\n  }\n\n  public dispose(): void {\n    this._onDidCreateClient.dispose()\n    for (let client of this.clientsMap.values()) {\n      if (client) client.dispose()\n    }\n    this.clientsMap.clear()\n    FileSystemWatcherManager.watchers.clear()\n    disposeAll(this.disposables)\n  }\n}\n\n/*\n * FileSystemWatcher for watch workspace folders.\n */\nexport class FileSystemWatcher implements IFileSystemWatcher {\n  private _onDidCreate = new Emitter<URI>()\n  private _onDidChange = new Emitter<URI>()\n  private _onDidDelete = new Emitter<URI>()\n  private _onDidRename = new Emitter<RenameEvent>()\n  private disposables: Disposable[] = []\n  public subscribe: string\n  public readonly onDidCreate: Event<URI> = this._onDidCreate.event\n  public readonly onDidChange: Event<URI> = this._onDidChange.event\n  public readonly onDidDelete: Event<URI> = this._onDidDelete.event\n  public readonly onDidRename: Event<RenameEvent> = this._onDidRename.event\n  private readonly _onDidListen = new Emitter<void>()\n  public readonly onDidListen: Event<void> = this._onDidListen.event\n\n  constructor(\n    private globPattern: GlobPattern,\n    public ignoreCreateEvents: boolean,\n    public ignoreChangeEvents: boolean,\n    public ignoreDeleteEvents: boolean,\n  ) {\n  }\n\n  public listen(root: string, client: Watchman): void {\n    let { globPattern,\n      ignoreCreateEvents,\n      ignoreChangeEvents,\n      ignoreDeleteEvents } = this\n    let pattern: string\n    let basePath: string | undefined\n    if (typeof globPattern === 'string') {\n      pattern = globPattern\n    } else {\n      pattern = globPattern.pattern\n      basePath = globPattern.baseUri.fsPath\n      // ignore client\n      if (!isParentFolder(root, basePath, true)) return\n    }\n    const onChange = (change: FileChange) => {\n      let { root, files } = change\n      if (basePath && !sameFile(root, basePath)) {\n        files = files.filter(f => {\n          if (f.type != 'f') return false\n          let fullpath = path.join(root, f.name)\n          if (!isParentFolder(basePath, fullpath)) return false\n          return minimatch(path.relative(basePath, fullpath), pattern, { dot: true })\n        })\n      } else {\n        files = files.filter(f => f.type == 'f' && minimatch(f.name, pattern, { dot: true }))\n      }\n      for (let file of files) {\n        let uri = URI.file(path.join(root, file.name))\n        if (!file.exists) {\n          if (!ignoreDeleteEvents) this._onDidDelete.fire(uri)\n        } else {\n          if (file.new === true) {\n            if (!ignoreCreateEvents) this._onDidCreate.fire(uri)\n          } else {\n            if (!ignoreChangeEvents) this._onDidChange.fire(uri)\n          }\n        }\n      }\n      // file rename\n      if (files.length == 2 && files[0].exists !== files[1].exists) {\n        let oldFile = files.find(o => o.exists !== true)\n        let newFile = files.find(o => o.exists === true)\n        if (oldFile.size == newFile.size) {\n          this._onDidRename.fire({\n            oldUri: URI.file(path.join(root, oldFile.name)),\n            newUri: URI.file(path.join(root, newFile.name))\n          })\n        }\n      }\n      // detect folder rename\n      if (files.length > 2 && files.length % 2 == 0) {\n        let [oldFiles, newFiles] = splitArray(files, o => o.exists === false)\n        if (oldFiles.length == newFiles.length) {\n          for (let oldFile of oldFiles) {\n            let newFile = newFiles.find(o => o.size == oldFile.size && o.mtime_ms == oldFile.mtime_ms)\n            if (newFile) {\n              this._onDidRename.fire({\n                oldUri: URI.file(path.join(root, oldFile.name)),\n                newUri: URI.file(path.join(root, newFile.name))\n              })\n            }\n          }\n        }\n      }\n    }\n    this.subscribe = client.subscription\n    let disposable = client.subscribe(pattern, onChange)\n    this._onDidListen.fire()\n    this.disposables.push(disposable)\n  }\n\n  public dispose(): void {\n    FileSystemWatcherManager.watchers.delete(this)\n    this._onDidRename.dispose()\n    this._onDidCreate.dispose()\n    this._onDidChange.dispose()\n    disposeAll(this.disposables)\n  }\n}\n"
  },
  {
    "path": "src/core/files.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport type { TextDocument } from 'vscode-languageserver-textdocument'\nimport { ChangeAnnotation, CreateFile, CreateFileOptions, DeleteFile, DeleteFileOptions, Position, RenameFile, RenameFileOptions, SnippetTextEdit, TextDocumentEdit, TextEdit, WorkspaceEdit } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport commands from '../commands'\nimport Configurations from '../configuration'\nimport events from '../events'\nimport { createLogger } from '../logger'\nimport Document from '../model/document'\nimport EditInspect, { EditState, RecoverFunc } from '../model/editInspect'\nimport type { SnippetEdit } from '../snippets/session'\nimport { DocumentChange, Env, GlobPattern } from '../types'\nimport * as errors from '../util/errors'\nimport { isFile, isParentFolder, normalizeFilePath, statAsync } from '../util/fs'\nimport { crypto, fs, glob, minimatch, os, path } from '../util/node'\nimport { CancellationToken, CancellationTokenSource, Emitter, Event, TextDocumentSaveReason } from '../util/protocol'\nimport { byteIndex } from '../util/string'\nimport { createFilteredChanges, getConfirmAnnotations, getRevertEdit, mergeSortEdits, toDocumentChanges } from '../util/textedit'\nimport type { Window } from '../window'\nimport Documents from './documents'\nimport type Keymaps from './keymaps'\nimport WorkspaceFolderController from './workspaceFolder'\nconst logger = createLogger('core-files')\n\nexport interface LinesChange {\n  uri: string\n  lnum: number\n  oldLines: ReadonlyArray<string>\n  newLines: ReadonlyArray<string>\n}\n\n/**\n * An event that is fired when a [document](#TextDocument) will be saved.\n *\n * To make modifications to the document before it is being saved, call the\n * [`waitUntil`](#TextDocumentWillSaveEvent.waitUntil)-function with a thenable\n * that resolves to an array of [text edits](#TextEdit).\n */\nexport interface TextDocumentWillSaveEvent {\n  bufnr: number\n  /**\n   * The document that will be saved.\n   */\n  document: TextDocument\n\n  /**\n   * The reason why save was triggered.\n   */\n  reason: TextDocumentSaveReason\n\n  /**\n   * Allows to pause the event loop and to apply [pre-save-edits](#TextEdit).\n   * Edits of subsequent calls to this function will be applied in order. The\n   * edits will be *ignored* if concurrent modifications of the document happened.\n   *\n   * *Note:* This function can only be called during event dispatch and not\n   * in an asynchronous manner:\n   * @param thenable A thenable that resolves to [pre-save-edits](#TextEdit).\n   */\n  waitUntil(thenable: Thenable<TextEdit[] | any>): void\n}\n\n/**\n * An event that is fired when files are going to be renamed.\n *\n * To make modifications to the workspace before the files are renamed,\n * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a\n * thenable that resolves to a [workspace edit](#WorkspaceEdit).\n */\nexport interface FileWillRenameEvent {\n\n  /**\n   * The files that are going to be renamed.\n   */\n  readonly files: ReadonlyArray<{ oldUri: URI, newUri: URI }>\n\n  /**\n   * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit).\n   *\n   * *Note:* This function can only be called during event dispatch and not\n   * in an asynchronous manner:\n   *\n   * ```ts\n   * workspace.onWillCreateFiles(event => {\n   * // async, will *throw* an error\n   * setTimeout(() => event.waitUntil(promise));\n   *\n   * // sync, OK\n   * event.waitUntil(promise);\n   * })\n   * ```\n   * @param thenable A thenable that delays saving.\n   */\n  waitUntil(thenable: Thenable<WorkspaceEdit | any>): void\n}\n\n/**\n * An event that is fired after files are renamed.\n */\nexport interface FileRenameEvent {\n\n  /**\n   * The files that got renamed.\n   */\n  readonly files: ReadonlyArray<{ oldUri: URI, newUri: URI }>\n}\n\n/**\n * An event that is fired when files are going to be created.\n *\n * To make modifications to the workspace before the files are created,\n * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a\n * thenable that resolves to a [workspace edit](#WorkspaceEdit).\n */\nexport interface FileWillCreateEvent {\n\n  /**\n   * A cancellation token.\n   */\n  readonly token: CancellationToken\n\n  /**\n   * The files that are going to be created.\n   */\n  readonly files: ReadonlyArray<URI>\n\n  /**\n   * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit).\n   *\n   * *Note:* This function can only be called during event dispatch and not\n   * in an asynchronous manner:\n   *\n   * ```ts\n   * workspace.onWillCreateFiles(event => {\n   * // async, will *throw* an error\n   * setTimeout(() => event.waitUntil(promise));\n   *\n   * // sync, OK\n   * event.waitUntil(promise);\n   * })\n   * ```\n   * @param thenable A thenable that delays saving.\n   */\n  waitUntil(thenable: Thenable<WorkspaceEdit | any>): void\n}\n\n/**\n * An event that is fired after files are created.\n */\nexport interface FileCreateEvent {\n\n  /**\n   * The files that got created.\n   */\n  readonly files: ReadonlyArray<URI>\n}\n\n/**\n * An event that is fired when files are going to be deleted.\n *\n * To make modifications to the workspace before the files are deleted,\n * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a\n * thenable that resolves to a [workspace edit](#WorkspaceEdit).\n */\nexport interface FileWillDeleteEvent {\n\n  /**\n   * The files that are going to be deleted.\n   */\n  readonly files: ReadonlyArray<URI>\n\n  /**\n   * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit).\n   *\n   * *Note:* This function can only be called during event dispatch and not\n   * in an asynchronous manner:\n   *\n   * ```ts\n   * workspace.onWillCreateFiles(event => {\n   * // async, will *throw* an error\n   * setTimeout(() => event.waitUntil(promise));\n   *\n   * // sync, OK\n   * event.waitUntil(promise);\n   * })\n   * ```\n   * @param thenable A thenable that delays saving.\n   */\n  waitUntil(thenable: Thenable<WorkspaceEdit | any>): void\n}\n\n/**\n * An event that is fired after files are deleted.\n */\nexport interface FileDeleteEvent {\n\n  /**\n   * The files that got deleted.\n   */\n  readonly files: ReadonlyArray<URI>\n}\n\ninterface WaitUntilEvent {\n  waitUntil(thenable: Thenable<WorkspaceEdit | any>): void\n}\n\nexport default class Files {\n  private nvim: Neovim\n  private env: Env\n  private window: Window\n  private editState: EditState | undefined\n  private operationTimeout = 500\n  private _onDidCreateFiles = new Emitter<FileCreateEvent>()\n  private _onDidRenameFiles = new Emitter<FileRenameEvent>()\n  private _onDidDeleteFiles = new Emitter<FileDeleteEvent>()\n  private _onWillCreateFiles = new Emitter<FileWillCreateEvent>()\n  private _onWillRenameFiles = new Emitter<FileWillRenameEvent>()\n  private _onWillDeleteFiles = new Emitter<FileWillDeleteEvent>()\n\n  public readonly onDidCreateFiles: Event<FileCreateEvent> = this._onDidCreateFiles.event\n  public readonly onDidRenameFiles: Event<FileRenameEvent> = this._onDidRenameFiles.event\n  public readonly onDidDeleteFiles: Event<FileDeleteEvent> = this._onDidDeleteFiles.event\n  public readonly onWillCreateFiles: Event<FileWillCreateEvent> = this._onWillCreateFiles.event\n  public readonly onWillRenameFiles: Event<FileWillRenameEvent> = this._onWillRenameFiles.event\n  public readonly onWillDeleteFiles: Event<FileWillDeleteEvent> = this._onWillDeleteFiles.event\n  constructor(\n    private documents: Documents,\n    private configurations: Configurations,\n    private workspaceFolderControl: WorkspaceFolderController,\n    private keymaps: Keymaps\n  ) {\n  }\n\n  public attach(nvim: Neovim, env: Env, window: Window): void {\n    this.nvim = nvim\n    this.env = env\n    this.window = window\n  }\n\n  public async openTextDocument(uri: URI | string): Promise<Document> {\n    uri = typeof uri === 'string' ? URI.file(uri) : uri\n    let doc = this.documents.getDocument(uri.toString())\n    if (doc) return doc\n    const scheme = uri.scheme\n    if (scheme == 'file') {\n      if (!fs.existsSync(uri.fsPath)) throw errors.fileNotExists(uri.fsPath)\n      fs.accessSync(uri.fsPath, fs.constants.R_OK)\n    }\n    if (scheme == 'untitled') {\n      await this.nvim.call('coc#util#open_file', ['tab drop', uri.path])\n      return await this.documents.document\n    }\n    return await this.loadResource(uri.toString(), null)\n  }\n\n  public async jumpTo(uri: string | URI, position?: Position | null, openCommand?: string): Promise<void> {\n    if (!openCommand) openCommand = this.configurations.initialConfiguration.get<string>('coc.preferences.jumpCommand', 'edit')\n    let { nvim } = this\n    let u = uri instanceof URI ? uri : URI.parse(uri)\n    let doc = this.documents.getDocument(u.with({ fragment: '' }).toString())\n    let bufnr = doc ? doc.bufnr : -1\n    if (!position && u.scheme === 'file' && u.fragment) {\n      let parts = u.fragment.split(',')\n      let lnum = parseInt(parts[0], 10)\n      if (!isNaN(lnum)) {\n        let col = parts.length > 0 && /^\\d+$/.test(parts[1]) ? parseInt(parts[1], 10) : undefined\n        position = Position.create(lnum - 1, col == null ? 0 : col - 1)\n      }\n    }\n    if (bufnr != -1 && openCommand == 'edit') {\n      // use buffer command since edit command would reload the buffer\n      nvim.pauseNotification()\n      nvim.command(`silent! normal! m'`, true)\n      nvim.command(`buffer ${bufnr}`, true)\n      nvim.command(`if &filetype ==# '' | filetype detect | endif`, true)\n      if (position) {\n        let line = doc.getline(position.line)\n        let col = byteIndex(line, position.character) + 1\n        nvim.call('cursor', [position.line + 1, col], true)\n      }\n      await nvim.resumeNotification(true)\n    } else {\n      let { fsPath, scheme } = u\n      let pos = position == null ? null : [position.line, position.character]\n      if (scheme == 'file') {\n        let bufname = normalizeFilePath(fsPath)\n        await this.nvim.call('coc#util#jump', [openCommand, bufname, pos])\n      } else {\n        await this.nvim.call('coc#util#jump', [openCommand, uri.toString(), pos])\n      }\n    }\n  }\n\n  /**\n   * Open resource by uri\n   */\n  public async openResource(uri: string): Promise<void> {\n    let { nvim } = this\n    let u = URI.parse(uri)\n    if (/^https?/.test(u.scheme)) {\n      await nvim.call('coc#ui#open_url', uri)\n      return\n    }\n    await this.jumpTo(uri)\n    await this.documents.document\n  }\n\n  /**\n   * Load uri as document.\n   */\n  public async loadResource(uri: string, cmd?: string): Promise<Document> {\n    let doc = this.documents.getDocument(uri)\n    if (doc) return doc\n    if (cmd === undefined) {\n      const preferences = this.configurations.getConfiguration('workspace')\n      cmd = preferences.get<string>('openResourceCommand', 'tab drop')\n    }\n    let u = URI.parse(uri)\n    let bufname = u.scheme === 'file' ? u.fsPath : uri\n    let bufnr: number\n    if (cmd) {\n      let winid = await this.nvim.call('win_getid') as number\n      bufnr = await this.nvim.call('coc#util#open_file', [cmd, bufname]) as number\n      await this.nvim.call('win_gotoid', [winid])\n    } else {\n      let arr = await this.nvim.call('coc#ui#open_files', [[bufname]])\n      bufnr = arr[0]\n    }\n    return await this.documents.createDocument(bufnr)\n  }\n\n  /**\n   * Load the files that not loaded\n   */\n  public async loadResources(uris: string[]): Promise<(Document | undefined)[]> {\n    let { documents } = this\n    let files = uris.map(uri => {\n      let u = URI.parse(uri)\n      return u.scheme == 'file' ? u.fsPath : uri\n    })\n    let bufnrs = await this.nvim.call('coc#ui#open_files', [files]) as number[]\n    return await Promise.all(bufnrs.map(bufnr => {\n      return documents.createDocument(bufnr)\n    }))\n  }\n\n  /**\n   * Create a file in vim and disk\n   */\n  public async createFile(filepath: string, opts: CreateFileOptions = {}, recovers?: RecoverFunc[]): Promise<void> {\n    let { nvim } = this\n    let exists = fs.existsSync(filepath)\n    if (exists && !opts.overwrite && !opts.ignoreIfExists) {\n      throw errors.fileExists(filepath)\n    }\n    if (!exists || opts.overwrite) {\n      let tokenSource = new CancellationTokenSource()\n      await this.fireWaitUntilEvent(this._onWillCreateFiles, {\n        files: [URI.file(filepath)],\n        token: tokenSource.token\n      }, recovers)\n      tokenSource.cancel()\n      let dir = path.dirname(filepath)\n      if (!fs.existsSync(dir)) {\n        let folder: string\n        let curr = dir\n        while (!['.', '/', path.parse(dir).root].includes(curr)) {\n          if (fs.existsSync(path.dirname(curr))) {\n            folder = curr\n            break\n          }\n          curr = path.dirname(curr)\n        }\n        fs.mkdirSync(dir, { recursive: true })\n        if (Array.isArray(recovers)) {\n          recovers.push(() => {\n            fs.rmSync(folder, { force: true, recursive: true })\n          })\n        }\n      }\n      fs.writeFileSync(filepath, '', 'utf8')\n      if (Array.isArray(recovers)) {\n        recovers.push(() => {\n          fs.rmSync(filepath, { force: true, recursive: true })\n        })\n      }\n      let doc = await this.loadResource(filepath)\n      let bufnr = doc.bufnr\n      if (Array.isArray(recovers)) {\n        recovers.push(() => {\n          void events.fire('BufUnload', [bufnr])\n          return nvim.command(`silent! bd! ${bufnr}`)\n        })\n      }\n      this._onDidCreateFiles.fire({ files: [URI.file(filepath)] })\n    }\n  }\n\n  /**\n   * Delete a file or folder from vim and disk.\n   */\n  public async deleteFile(filepath: string, opts: DeleteFileOptions = {}, recovers?: RecoverFunc[]): Promise<void> {\n    let { ignoreIfNotExists, recursive } = opts\n    let stat = await statAsync(filepath)\n    let isDir = stat && stat.isDirectory()\n    if (!stat && !ignoreIfNotExists) {\n      throw errors.fileNotExists(filepath)\n    }\n    if (stat == null) return\n    let uri = URI.file(filepath)\n    await this.fireWaitUntilEvent(this._onWillDeleteFiles, { files: [uri] }, recovers)\n    if (!isDir) {\n      let bufnr = await this.nvim.call('bufnr', [filepath])\n      if (bufnr) {\n        void events.fire('BufUnload', [bufnr])\n        await this.nvim.command(`silent! bwipeout ${bufnr}`)\n        if (Array.isArray(recovers)) {\n          recovers.push(() => {\n            return this.loadResource(uri.toString())\n          })\n        }\n      }\n    }\n    let folder = path.join(os.tmpdir(), 'coc-' + process.pid)\n    fs.mkdirSync(folder, { recursive: true })\n    let md5 = crypto.createHash('md5').update(filepath).digest('hex')\n    if (isDir && recursive) {\n      let dest = path.join(folder, md5)\n      let dir = path.dirname(filepath)\n      fs.renameSync(filepath, dest)\n      if (Array.isArray(recovers)) {\n        recovers.push(async () => {\n          fs.mkdirSync(dir, { recursive: true })\n          fs.renameSync(dest, filepath)\n        })\n      }\n    } else if (isDir) {\n      fs.rmdirSync(filepath)\n      if (Array.isArray(recovers)) {\n        recovers.push(() => {\n          fs.mkdirSync(filepath)\n        })\n      }\n    } else {\n      let dest = path.join(folder, md5)\n      let dir = path.dirname(filepath)\n      fs.renameSync(filepath, dest)\n      if (Array.isArray(recovers)) {\n        recovers.push(() => {\n          fs.mkdirSync(dir, { recursive: true })\n          fs.renameSync(dest, filepath)\n        })\n      }\n    }\n    this._onDidDeleteFiles.fire({ files: [uri] })\n  }\n\n  /**\n   * Rename a file or folder on vim and disk\n   */\n  public async renameFile(oldPath: string, newPath: string, opts: RenameFileOptions & { skipEvent?: boolean } = {}, recovers?: RecoverFunc[]): Promise<void> {\n    let { nvim } = this\n    let { overwrite, ignoreIfExists } = opts\n    if (newPath === oldPath) return\n    let exists = fs.existsSync(newPath)\n    if (exists && ignoreIfExists && !overwrite) return\n    if (exists && !overwrite) throw errors.fileExists(newPath)\n    let oldStat = await statAsync(oldPath)\n    let loaded = (oldStat && oldStat.isDirectory()) ? 0 : await nvim.call('bufloaded', [oldPath])\n    if (!loaded && !oldStat) throw errors.fileNotExists(oldPath)\n    let file = { newUri: URI.parse(newPath), oldUri: URI.parse(oldPath) }\n    if (!opts.skipEvent) await this.fireWaitUntilEvent(this._onWillRenameFiles, { files: [file] }, recovers)\n    if (loaded) {\n      let bufnr = await nvim.call('coc#ui#rename_file', [oldPath, newPath, oldStat != null]) as number\n      await this.documents.onBufCreate(bufnr)\n    } else {\n      if (oldStat.isDirectory()) {\n        for (let doc of this.documents.attached('file')) {\n          let u = URI.parse(doc.uri)\n          if (isParentFolder(oldPath, u.fsPath, false)) {\n            let filepath = u.fsPath.replace(oldPath, newPath)\n            let bufnr = await nvim.call('coc#ui#rename_file', [u.fsPath, filepath, false]) as number\n            await this.documents.onBufCreate(bufnr)\n          }\n        }\n      }\n      fs.renameSync(oldPath, newPath)\n    }\n    if (Array.isArray(recovers)) {\n      recovers.push(() => {\n        return this.renameFile(newPath, oldPath, { skipEvent: true })\n      })\n    }\n    if (!opts.skipEvent) this._onDidRenameFiles.fire({ files: [file] })\n  }\n\n  /**\n   * Return denied annotations\n   */\n  private async promptAnnotations(documentChanges: DocumentChange[], changeAnnotations: { [id: string]: ChangeAnnotation } | undefined): Promise<string[]> {\n    let toConfirm = changeAnnotations ? getConfirmAnnotations(documentChanges, changeAnnotations) : []\n    let denied: string[] = []\n    for (let key of toConfirm) {\n      let annotation = changeAnnotations[key]\n      let res = await this.window.showMenuPicker(['Yes', 'No'], {\n        position: 'center',\n        title: 'Confirm edits',\n        content: annotation.label + (annotation.description ? ' ' + annotation.description : '')\n      })\n      if (res !== 0) denied.push(key)\n    }\n    return denied\n  }\n\n  /**\n   * Apply WorkspaceEdit.\n   */\n  public async applyEdit(edit: WorkspaceEdit, nested?: boolean): Promise<boolean> {\n    let documentChanges = toDocumentChanges(edit)\n    let recovers: RecoverFunc[] = []\n    let currentOnly = false\n    try {\n      let denied = await this.promptAnnotations(documentChanges, edit.changeAnnotations)\n      if (denied.length > 0) documentChanges = createFilteredChanges(documentChanges, denied)\n      let changes: { [uri: string]: LinesChange } = {}\n      let currentUri = await this.documents.getCurrentUri()\n      currentOnly = documentChanges.every(o => TextDocumentEdit.is(o) && o.textDocument.uri === currentUri)\n      this.validateChanges(documentChanges)\n      for (const change of documentChanges) {\n        if (TextDocumentEdit.is(change)) {\n          let { textDocument, edits } = change\n          let { uri } = textDocument\n          let doc = await this.loadResource(uri)\n          let revertEdit: TextEdit | undefined\n          if (edits.some(o => SnippetTextEdit.is(o))) {\n            // convert all to SnippetEdit\n            let snippetEdits: SnippetEdit[] = mergeSortEdits(edits.map(edit => {\n              if (SnippetTextEdit.is(edit)) {\n                return { range: edit.range, snippet: edit.snippet.value }\n              }\n              return { range: edit.range, snippet: edit.newText }\n            }))\n            let oldLines = doc.textDocument.lines\n            await commands.executeCommand('editor.action.insertBufferSnippets', doc.bufnr, snippetEdits, doc.bufnr === events.bufnr)\n            let startLine = snippetEdits[0].range.start.line\n            revertEdit = getRevertEdit(oldLines, doc.textDocument.lines, startLine)\n          } else {\n            revertEdit = await doc.applyEdits(edits as TextEdit[], false, uri === currentUri)\n          }\n          if (revertEdit) {\n            let version = doc.version\n            let { newText, range } = revertEdit\n            changes[uri] = {\n              uri,\n              lnum: range.start.line + 1,\n              newLines: doc.getLines(range.start.line, range.end.line),\n              oldLines: newText.endsWith('\\n') ? newText.slice(0, -1).split('\\n') : newText.split('\\n')\n            }\n            recovers.push(async () => {\n              let doc = this.documents.getDocument(uri)\n              if (!doc || !doc.attached || doc.version !== version) return\n              await doc.applyEdits([revertEdit])\n              textDocument.version = doc.version\n            })\n          }\n        } else if (CreateFile.is(change)) {\n          await this.createFile(fsPath(change.uri), change.options, recovers)\n        } else if (DeleteFile.is(change)) {\n          await this.deleteFile(fsPath(change.uri), change.options, recovers)\n        } else if (RenameFile.is(change)) {\n          await this.renameFile(fsPath(change.oldUri), fsPath(change.newUri), change.options, recovers)\n        }\n      }\n      // nothing changed\n      if (recovers.length === 0) return true\n      if (!nested) this.editState = { edit: { documentChanges, changeAnnotations: edit.changeAnnotations }, changes, recovers, applied: true }\n      this.nvim.redrawVim()\n    } catch (e) {\n      logger.error('Error on applyEdits:', edit, e)\n      if (!nested) void this.window.showErrorMessage(`Error on applyEdits: ${e}`)\n      await this.undoChanges(recovers)\n      return false\n    }\n    // avoid message when change current file only.\n    if (nested || currentOnly) return true\n    void this.window.showInformationMessage(`Use ':wa' to save changes or ':CocCommand workspace.inspectEdit' to inspect.`)\n    return true\n  }\n\n  private async undoChanges(recovers: RecoverFunc[]): Promise<void> {\n    while (recovers.length > 0) {\n      let fn = recovers.pop()\n      await Promise.resolve(fn())\n    }\n  }\n\n  public async inspectEdit(): Promise<void> {\n    if (!this.editState) {\n      void this.window.showWarningMessage('No workspace edit to inspect')\n      return\n    }\n    let inspect = new EditInspect(this.nvim, this.keymaps)\n    await inspect.show(this.editState)\n  }\n\n  public async undoWorkspaceEdit(): Promise<void> {\n    let { editState } = this\n    if (!editState || !editState.applied) {\n      void this.window.showWarningMessage(`No workspace edit to undo`)\n      return\n    }\n    editState.applied = false\n    await this.undoChanges(editState.recovers)\n  }\n\n  public async redoWorkspaceEdit(): Promise<void> {\n    let { editState } = this\n    if (!editState || editState.applied) {\n      void this.window.showWarningMessage(`No workspace edit to redo`)\n      return\n    }\n    this.editState = undefined\n    await this.applyEdit(editState.edit)\n  }\n\n  public validateChanges(documentChanges: ReadonlyArray<DocumentChange>): void {\n    let { documents } = this\n    for (let change of documentChanges) {\n      if (TextDocumentEdit.is(change)) {\n        let { uri, version } = change.textDocument\n        let doc = documents.getDocument(uri)\n        if (typeof version === 'number' && version > 0) {\n          if (!doc) throw errors.notLoaded(uri)\n          if (doc.version != version) throw new Error(`${uri} changed before apply edit`)\n        } else if (!doc && !isFile(uri)) {\n          throw errors.badScheme(uri)\n        }\n      } else if (CreateFile.is(change) || DeleteFile.is(change)) {\n        if (!isFile(change.uri)) throw errors.badScheme(change.uri)\n      } else if (RenameFile.is(change)) {\n        if (!isFile(change.oldUri) || !isFile(change.newUri)) {\n          throw errors.badScheme(change.oldUri)\n        }\n      }\n    }\n  }\n\n  public async findFiles(include: GlobPattern, exclude?: GlobPattern | null, maxResults?: number, token?: CancellationToken): Promise<URI[]> {\n    let folders = this.workspaceFolderControl.workspaceFolders\n    if (token?.isCancellationRequested || !folders.length || maxResults === 0) return []\n    maxResults = maxResults ?? Infinity\n    let roots = folders.map(o => URI.parse(o.uri).fsPath)\n    let pattern: string\n    if (typeof include !== 'string') {\n      pattern = include.pattern\n      roots = [include.baseUri.fsPath]\n    } else {\n      pattern = include\n    }\n    let res: URI[] = []\n    let exceed = false\n    const ac = new AbortController()\n    if (token) {\n      token.onCancellationRequested(() => {\n        if (!ac.signal.aborted) ac.abort()\n      })\n    }\n    for (let root of roots) {\n      try {\n        let files = await glob.glob(pattern, {\n          signal: ac.signal,\n          dot: true,\n          cwd: root,\n          nodir: true,\n          absolute: false\n        })\n        if (token?.isCancellationRequested) break\n        for (let file of files) {\n          if (exclude && fileMatch(root, file, exclude)) continue\n          res.push(URI.file(path.join(root, file)))\n          if (res.length === maxResults) {\n            exceed = true\n            break\n          }\n        }\n        if (exceed) break\n      } catch (e) {\n        if (e['name'] === 'AbortError') {\n          break\n        }\n      }\n    }\n    return res\n  }\n\n  private async fireWaitUntilEvent<T extends WaitUntilEvent>(emitter: Emitter<T>, properties: Omit<T, 'waitUntil'>, recovers?: RecoverFunc[]): Promise<void> {\n    let firing = true\n    let promises: Promise<any>[] = []\n    emitter.fire({\n      ...properties,\n      waitUntil: thenable => {\n        if (!firing) throw errors.shouldNotAsync('waitUntil')\n        let tp = new Promise(resolve => {\n          setTimeout(resolve, this.operationTimeout)\n        })\n        let promise = Promise.race([thenable, tp]).then(edit => {\n          if (edit && WorkspaceEdit.is(edit)) {\n            return this.applyEdit(edit, true)\n          }\n        })\n        promises.push(promise)\n      }\n    } as any)\n    firing = false\n    await Promise.all(promises)\n  }\n}\n\nfunction fileMatch(root: string, relpath: string, pattern: GlobPattern): boolean {\n  let filepath = path.join(root, relpath)\n  if (typeof pattern !== 'string') {\n    let base = pattern.baseUri.fsPath\n    if (!isParentFolder(base, filepath)) return false\n    let rp = path.relative(base, filepath)\n    return minimatch(rp, pattern.pattern, { dot: true })\n  }\n  return minimatch(relpath, pattern, { dot: true })\n}\n\nfunction fsPath(uri: string): string {\n  return URI.parse(uri).fsPath\n}\n"
  },
  {
    "path": "src/core/funcs.ts",
    "content": "'use strict'\nimport type { Neovim } from '@chemzqm/neovim'\nimport type { DocumentFilter, DocumentSelector } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport Configurations from '../configuration'\nimport Resolver from '../model/resolver'\nimport { isVim } from '../util/constants'\nimport { onUnexpectedError } from '../util/errors'\nimport * as fs from '../util/fs'\nimport { Mutex } from '../util/mutex'\nimport { minimatch, os, path, semver, which } from '../util/node'\nimport * as platform from '../util/platform'\nimport { RelativePattern, TextDocumentFilter } from '../util/protocol'\nlet NAME_SPACE = 2000\nconst resolver = new Resolver()\n\nconst namespaceMap: Map<string, number> = new Map()\nconst mutex: Mutex = new Mutex()\n\nexport interface PartialEnv {\n  isVim: boolean\n  version: string\n}\n\n/**\n * Like vim's has(), but for version check only.\n * Check patch on neovim and check nvim on vim would return false.\n *\n * For example:\n * - has('nvim-0.6.0')\n * - has('patch-7.4.248')\n */\nexport function has(env: PartialEnv, feature: string): boolean {\n  if (!feature.startsWith('nvim-') && !feature.startsWith('patch-')) {\n    throw new Error('Feature param could only starts with nvim and patch')\n  }\n  if (!env.isVim && feature.startsWith('patch-')) {\n    return false\n  }\n  if (env.isVim && feature.startsWith('nvim-')) {\n    return false\n  }\n  if (env.isVim) {\n    let [_, major, minor, patch] = env.version.match(/^(\\d)(\\d{2})(\\d+)$/)\n    let version = `${major}.${parseInt(minor, 10)}.${parseInt(patch, 10)}`\n    return semver.gte(version, convertVersion(feature.slice(6)))\n  }\n  return semver.gte(env.version, feature.slice(5))\n}\n\n// convert to valid semver version 9.0.0138 to 9.0.138\nfunction convertVersion(version: string): string {\n  let parts = version.split('.')\n  return `${parseInt(parts[0], 10)}.${parseInt(parts[1], 10)}.${parseInt(parts[2], 10)}`\n}\n\nexport function callAsync<T>(nvim: Neovim, method: string, args: any[]): Promise<T> {\n  return mutex.use<T>(() => {\n    if (!isVim) return nvim.call(method, args) as Promise<T>\n    return nvim.callAsync('coc#util#with_callback', [method, args]).catch(onUnexpectedError) as Promise<T>\n  })\n}\n\n/**\n * @deprecated\n */\nexport function createNameSpace(name: string): number {\n  if (namespaceMap.has(name)) return namespaceMap.get(name)\n  NAME_SPACE = NAME_SPACE + 1\n  namespaceMap.set(name, NAME_SPACE)\n  return NAME_SPACE\n}\n\n/**\n * Resolve watchman path.\n */\nexport function getWatchmanPath(configurations: Configurations): string | null {\n  const watchmanPath = configurations.initialConfiguration.get<string>('coc.preferences.watchmanPath', 'watchman')\n  return which.sync(watchmanPath, { nothrow: true })\n}\n\nexport async function findUp(nvim: Neovim, cwd: string, filename: string | string[]): Promise<string | null> {\n  let filepath = await nvim.call('coc#util#get_fullpath') as string\n  filepath = path.normalize(filepath)\n  let isFile = filepath && path.isAbsolute(filepath)\n  if (isFile && !fs.isParentFolder(cwd, filepath, true)) {\n    // can't use cwd\n    return fs.findUp(filename, path.dirname(filepath))\n  }\n  let res = fs.findUp(filename, cwd)\n  if (res && res != os.homedir()) return res\n  if (isFile) return fs.findUp(filename, path.dirname(filepath))\n  return null\n}\n\nexport function resolveModule(name: string): Promise<string> {\n  return resolver.resolveModule(name)\n}\n\nexport function score(selector: DocumentSelector | DocumentFilter | string, uri: string, languageId: string, caseInsensitive = platform.isWindows || platform.isMacintosh): number {\n  let u = URI.parse(uri)\n  if (Array.isArray(selector)) {\n    // array -> take max individual value\n    let ret = 0\n    for (const filter of selector) {\n      const value = score(filter, uri, languageId)\n      if (value === 10) {\n        return value // already at the highest\n      }\n      if (value > ret) {\n        ret = value\n      }\n    }\n    return ret\n  } else if (typeof selector === 'string') {\n    // short-hand notion, desugars to\n    // 'fooLang' -> { language: 'fooLang'}\n    // '*' -> { language: '*' }\n    if (selector === '*') {\n      return 5\n    } else if (selector === languageId) {\n      return 10\n    } else {\n      return 0\n    }\n  } else if (selector && TextDocumentFilter.is(selector)) {\n    // filter -> select accordingly, use defaults for scheme\n    const { language, pattern, scheme } = selector\n    let ret = 0\n    if (scheme) {\n      if (scheme === u.scheme) {\n        ret = 5\n      } else if (scheme === '*') {\n        ret = 3\n      } else {\n        return 0\n      }\n    }\n\n    if (language) {\n      if (language === languageId) {\n        ret = 10\n      } else if (language === '*') {\n        ret = Math.max(ret, 5)\n      } else {\n        return 0\n      }\n    }\n    if (pattern) {\n      let relativePattern: string\n      if (RelativePattern.is(pattern)) {\n        relativePattern = pattern.pattern\n        let baseUri = URI.parse(typeof pattern.baseUri === 'string' ? pattern.baseUri : pattern.baseUri.uri)\n        if (u.scheme !== 'file' || !fs.isParentFolder(baseUri.fsPath, u.fsPath, true)) {\n          return 0\n        }\n      } else {\n        relativePattern = pattern\n      }\n      let p = caseInsensitive ? relativePattern.toLowerCase() : relativePattern\n      let f = caseInsensitive ? u.fsPath.toLowerCase() : u.fsPath\n      if (p === f || minimatch(f, p, { dot: true })) {\n        ret = Math.max(ret, 5)\n      } else {\n        return 0\n      }\n    }\n    return ret\n  } else {\n    return 0\n  }\n}\n"
  },
  {
    "path": "src/core/highlights.ts",
    "content": "import { Neovim } from '@chemzqm/neovim'\nimport { HighlightItem } from '../types'\nimport { defaultValue } from '../util'\nimport { CancellationToken } from '../util/protocol'\n\nexport type HighlightItemResult = [string, number, number, number, number?]\nexport type HighlightItemDef = [string, number, number, number, number?, number?, number?]\n\nexport interface HighlightDiff {\n  remove: number[]\n  removeMarkers: number[]\n  add: HighlightItemDef[]\n}\n\nexport function convertHighlightItem(item: HighlightItem): HighlightItemDef {\n  return [item.hlGroup, item.lnum, item.colStart, item.colEnd, item.combine ? 1 : 0, item.start_incl ? 1 : 0, item.end_incl ? 1 : 0]\n}\n\nfunction isSame(item: HighlightItem, curr: HighlightItemResult): boolean {\n  return curr[0] == item.hlGroup && curr[1] === item.lnum && curr[2] === item.colStart && curr[3] === item.colEnd\n}\n\nexport class Highlights {\n  public nvim: Neovim\n\n  public async diffHighlights(bufnr: number, ns: string, items: HighlightItem[], region?: [number, number], token?: CancellationToken): Promise<HighlightDiff | null> {\n    let args = [bufnr, ns, Array.isArray(region) ? region[0] : 0, Array.isArray(region) ? region[1] : -1]\n    let curr = await this.nvim.call('coc#highlight#get_highlights', args) as HighlightItemResult[]\n    if (!curr || token?.isCancellationRequested) return null\n    items.sort((a, b) => {\n      if (a.lnum != b.lnum) return a.lnum - b.lnum\n      if (a.colStart != b.colStart) return a.colStart - b.colStart\n      return a.hlGroup > b.hlGroup ? 1 : -1\n    })\n    let removeMarkers = []\n    let newItems: HighlightItemDef[] = []\n    let itemIndex = 0\n    let maxIndex = items.length - 1\n    let maxLnum = 0\n    // highlights on vim\n    let map: Map<number, HighlightItemResult[]> = new Map()\n    curr.forEach(o => {\n      maxLnum = Math.max(maxLnum, o[1])\n      let arr = map.get(o[1])\n      if (arr) {\n        arr.push(o)\n      } else {\n        map.set(o[1], [o])\n      }\n    })\n    if (curr.length > 0) {\n      let start = Array.isArray(region) ? region[0] : 0\n      for (let i = start; i <= maxLnum; i++) {\n        let exists = defaultValue(map.get(i), [])\n        exists.sort((a, b) => {\n          if (a[2] != b[2]) return a[2] - b[2]\n          return a[0] > b[0] ? 1 : -1\n        })\n        let added: HighlightItem[] = []\n        for (let j = itemIndex; j <= maxIndex; j++) {\n          let o = items[j]\n          if (o.lnum == i) {\n            itemIndex = j + 1\n            added.push(o)\n          } else {\n            itemIndex = j\n            break\n          }\n        }\n        if (added.length == 0) {\n          removeMarkers.push(...exists.map(o => o[4]))\n        } else {\n          if (exists.length == 0) {\n            newItems.push(...added.map(o => convertHighlightItem(o)))\n          } else {\n            // skip same markers at beginning of exists and removeMarkers\n            let skip = 0\n            let min = Math.min(exists.length, added.length)\n            while (skip < min) {\n              if (isSame(added[skip], exists[skip])) {\n                skip++\n              } else {\n                break\n              }\n            }\n            let toRemove = exists.slice(skip).map(o => o[4])\n            removeMarkers.push(...toRemove)\n            newItems.push(...added.slice(skip).map(o => convertHighlightItem(o)))\n          }\n        }\n      }\n    }\n    for (let i = itemIndex; i <= maxIndex; i++) {\n      newItems.push(convertHighlightItem(items[i]))\n    }\n    return { remove: [], add: newItems, removeMarkers }\n  }\n\n  public async applyDiffHighlights(bufnr: number, ns: string, priority: number, diff: HighlightDiff, notify: boolean): Promise<void> {\n    let { nvim } = this\n    let { remove, add, removeMarkers } = diff\n    if (remove.length === 0 && add.length === 0 && removeMarkers.length === 0) return\n    nvim.pauseNotification()\n    if (add.length) {\n      nvim.call('coc#highlight#set', [bufnr, ns, add, priority], true)\n    }\n    if (removeMarkers.length) {\n      nvim.call('coc#highlight#del_markers', [bufnr, ns, removeMarkers], true)\n    }\n    if (notify) {\n      nvim.resumeNotification(true, true)\n    } else {\n      await nvim.resumeNotification(true)\n    }\n  }\n}\n"
  },
  {
    "path": "src/core/keymaps.ts",
    "content": "'use strict'\nimport { Neovim, KeymapOption as VimKeymapOption } from '@chemzqm/neovim'\nimport { createLogger } from '../logger'\nimport { KeymapOption } from '../types'\nimport { isVim } from '../util/constants'\nimport { Disposable } from '../util/protocol'\nimport { toBase64 } from '../util/string'\nconst logger = createLogger('core-keymaps')\n\nexport type MapMode = 'n' | 'i' | 'v' | 'x' | 's' | 'o' | '!' | 't' | 'c' | 'l'\nexport type LocalMode = 'n' | 'i' | 'v' | 's' | 'x'\nexport type KeymapCallback = () => Promise<string> | string | void | Promise<void>\n\nexport function getKeymapModifier(mode: MapMode, cmd?: boolean): string {\n  if (cmd) return '<Cmd>'\n  if (mode == 'n' || mode == 'o' || mode == 'x' || mode == 'v') return '<C-U>'\n  if (mode == 'i') return '<C-o>'\n  if (mode == 's') return '<Esc>'\n  return '<Cmd>'\n}\n\nexport function getBufnr(buffer: number | boolean): number {\n  return typeof buffer === 'number' ? buffer : 0\n}\n\nexport default class Keymaps {\n  private readonly keymaps: Map<string, [KeymapCallback, boolean]> = new Map()\n  private nvim: Neovim\n\n  public attach(nvim: Neovim): void {\n    this.nvim = nvim\n  }\n\n  public async doKeymap(key: string, defaultReturn: string): Promise<string> {\n    let keymap = this.keymaps.get(key) ?? this.keymaps.get('coc-' + key)\n    if (!keymap) {\n      logger.error(`keymap for ${key} not found`)\n      return defaultReturn\n    }\n    let [fn, repeat] = keymap\n    let res = await Promise.resolve(fn())\n    if (repeat) await this.nvim.command(`silent! call repeat#set(\"\\\\<Plug>(coc-${key})\", -1)`)\n    if (res == null) return defaultReturn\n    return res as string\n  }\n\n  /**\n   * Register global <Plug>(coc-${key}) key mapping.\n   */\n  public registerKeymap(modes: MapMode[], name: string, fn: KeymapCallback, opts: KeymapOption = {}): Disposable {\n    if (!name) throw new Error(`Invalid key ${name} of registerKeymap`)\n    let key = `coc-${name}`\n    if (this.keymaps.has(key)) throw new Error(`keymap: \"${name}\" already exists.`)\n    const lhs = `<Plug>(${key})`\n    opts = Object.assign({ sync: true, cancel: true, silent: true, repeat: false }, opts)\n    let { nvim } = this\n    this.keymaps.set(key, [fn, !!opts.repeat])\n    let method = opts.sync ? 'request' : 'notify'\n    for (let mode of modes) {\n      if (mode == 'i') {\n        const cancel = opts.cancel ? 1 : 0\n        nvim.setKeymap(mode, lhs, `coc#_insert_key('${method}', '${key}', ${cancel})`, {\n          expr: true,\n          noremap: true,\n          silent: opts.silent\n        })\n      } else {\n        nvim.setKeymap(mode, lhs, `:${getKeymapModifier(mode, opts.cmd)}call coc#rpc#${method}('doKeymap', ['${key}'])<cr>`, {\n          noremap: true,\n          silent: opts.silent\n        })\n      }\n    }\n    return Disposable.create(() => {\n      this.keymaps.delete(key)\n      for (let m of modes) {\n        nvim.deleteKeymap(m, lhs)\n      }\n    })\n  }\n\n  public registerExprKeymap(mode: MapMode, lhs: string, fn: KeymapCallback, buffer: number | boolean = false, cancel = true): Disposable {\n    let bufnr = getBufnr(buffer)\n    let id = `${mode}-${toBase64(lhs)}${buffer ? `-${bufnr}` : ''}`\n    let { nvim } = this\n    let rhs: string\n    if (mode == 'i') {\n      rhs = `coc#_insert_key('request', '${id}', ${cancel ? '1' : '0'})`\n    } else {\n      rhs = `coc#rpc#request('doKeymap', ['${id}'])`\n    }\n    let opts = { noremap: true, silent: true, expr: true, nowait: true }\n    if (buffer !== false) {\n      nvim.call('coc#compat#buf_add_keymap', [bufnr, mode, lhs, rhs, opts], true)\n    } else {\n      nvim.setKeymap(mode, lhs, rhs, opts)\n    }\n    this.keymaps.set(id, [fn, false])\n    return Disposable.create(() => {\n      this.keymaps.delete(id)\n      if (buffer) {\n        nvim.call('coc#compat#buf_del_keymap', [bufnr, mode, lhs], true)\n      } else {\n        nvim.deleteKeymap(mode, lhs)\n      }\n    })\n  }\n\n  public registerLocalKeymap(bufnr: number, mode: LocalMode, lhs: string, fn: KeymapCallback, option: boolean | KeymapOption): Disposable {\n    let { nvim } = this\n    let buffer = nvim.createBuffer(bufnr)\n    let id = `local-${bufnr}-${mode}-${toBase64(lhs)}`\n    const opts = toKeymapOption(option)\n    this.keymaps.set(id, [fn, !!opts.repeat])\n    const method = opts.sync ? 'request' : 'notify'\n    const opt: VimKeymapOption = { noremap: true, silent: opts.silent !== false }\n    if (isVim && opts.special) opt.special = true\n    if (mode == 'i') {\n      const cancel = opts.cancel ? 1 : 0\n      opt.expr = true\n      buffer.setKeymap(mode, lhs, `coc#_insert_key('${method}', '${id}', ${cancel})`, opt)\n    } else {\n      opt.nowait = true\n      const modify = getKeymapModifier(mode, opts.cmd)\n      buffer.setKeymap(mode, lhs, `:${modify}call coc#rpc#${method}('doKeymap', ['${id}'])<CR>`, opt)\n    }\n    return Disposable.create(() => {\n      this.keymaps.delete(id)\n      buffer.deleteKeymap(mode, lhs)\n    })\n  }\n}\n\nfunction toKeymapOption(option: KeymapOption | boolean): KeymapOption {\n  const conf = typeof option == 'boolean' ? { sync: !option } : option\n  return Object.assign({ sync: true, cancel: true, silent: true }, conf)\n}\n"
  },
  {
    "path": "src/core/notifications.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { WorkspaceConfiguration } from '../configuration/types'\nimport Notification, { MessageItem, NotificationConfig, NotificationKind, NotificationPreferences, toButtons, toTitles } from '../model/notification'\nimport ProgressNotification, { formatMessage, Progress } from '../model/progress'\nimport StatusLine from '../model/status'\nimport { defaultValue } from '../util'\nimport { parseExtensionName } from '../util/extensionRegistry'\nimport { toNumber } from '../util/numbers'\nimport { CancellationToken } from '../util/protocol'\nimport { toText } from '../util/string'\nimport { callAsync } from './funcs'\nimport { echoMessages, MsgTypes } from './ui'\nimport { Dialogs } from './dialogs'\n\nexport type MessageKind = 'Error' | 'Warning' | 'Info'\n\ninterface NotificationItem {\n  time: string\n  message: string\n  kind: MessageKind\n}\n\ninterface NotificationConfiguration {\n  statusLineProgress: boolean\n  border: boolean\n  disabledProgressSources: string[]\n  focusable: boolean\n  highlightGroup: string\n  marginRight: number\n  maxHeight: number\n  maxWidth: number\n  minProgressWidth: number\n  timeout: number\n  winblend: number\n}\n\n/**\n * Value-object describing where and how progress should show.\n */\nexport interface ProgressOptions {\n\n  /**\n   * A human-readable string which will be used to describe the\n   * operation.\n   */\n  title?: string\n\n  /**\n   * Controls if a cancel button should show to allow the user to\n   * cancel the long running operation.\n   */\n  cancellable?: boolean\n  /**\n   * Extension or language-client id\n   */\n  source?: string\n}\n\nexport class Notifications {\n  public nvim: Neovim\n  public configuration: WorkspaceConfiguration\n  public statusLine: StatusLine\n  private _history: NotificationItem[] = []\n\n  constructor(private dialogs: Dialogs) {\n  }\n\n  private getCurrentTimestamp(): string {\n    const now = new Date()\n    const year = now.getFullYear()\n    const month = (now.getMonth() + 1).toString().padStart(2, '0')\n    const day = now.getDate().toString().padStart(2, '0')\n    const hours = now.getHours().toString().padStart(2, '0')\n    const minutes = now.getMinutes().toString().padStart(2, '0')\n    const seconds = now.getSeconds().toString().padStart(2, '0')\n    const ms = now.getMilliseconds().toString().padStart(3, '0')\n\n    return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${ms}`\n  }\n\n  public async _showMessage<T extends MessageItem | string>(kind: MessageKind, message: string, items: T[]): Promise<T | undefined> {\n    this._history.push({ time: this.getCurrentTimestamp(), kind, message })\n\n    let msgDialogKind = this.messageDialogKind\n    if (this.enableMessageDialog === true) {\n      // maintain backwards compatibility, with the original implementation, set the default message kind to use\n      // notification interface even when action items are present.\n      msgDialogKind = 'notification'\n    }\n    if (items.length > 0) {\n      switch (msgDialogKind) {\n        case 'confirm':\n          return await this.showConfirm(message, items, kind)\n        case 'menu':\n          return await this.showMenuPicker(`Choose an action`, message, `Coc${kind}Float`, items)\n        case 'notification': {\n          let texts = items.map(o => typeof o === 'string' ? o : o.title)\n          let idx = await this.createNotification(kind.toLowerCase() as NotificationKind, message, texts)\n          return items[idx]\n        }\n        default:\n          throw new Error(`Unexpected messageDialogKind: ${this.messageDialogKind}`)\n      }\n    } else {\n      // by default the report kind will be echo, meaning that we still keep backwards compatibility with the original\n      // behavior where the user expects that messages are printed to the echo area, with the added caveat that all\n      // message kinds will go there now, information, warning or error\n      let msgReportKind = this.messageReportKind\n      switch (msgReportKind) {\n        case 'echo': {\n          let msgType: MsgTypes = kind == 'Info' ? 'more' : kind == 'Error' ? 'error' : 'warning'\n          this.echoMessages(message, msgType)\n          break\n        }\n        case 'notification': {\n          await this.createNotification(kind.toLowerCase() as NotificationKind, message, [])\n          break\n        }\n        default:\n          throw new Error(`Unexpected messageReportKind: ${msgReportKind}`)\n      }\n      return undefined\n    }\n  }\n\n  public get history(): NotificationItem[] {\n    return this._history\n  }\n\n  public clearHistory(): void {\n    this._history = []\n  }\n\n  public createNotification(kind: NotificationKind, message: string, items: string[]): Promise<number> {\n    return new Promise((resolve, reject) => {\n      let config: NotificationConfig = {\n        kind,\n        content: message,\n        buttons: toButtons(items),\n        callback: idx => {\n          resolve(idx)\n        }\n      }\n      let notification = new Notification(this.nvim, config)\n      notification.show(this.getNotificationPreference()).catch(reject)\n      if (items.length == 0) {\n        resolve(-1)\n      }\n    })\n  }\n\n  public async showConfirm<T extends MessageItem | string>(message: string, items: T[], kind: MessageKind): Promise<T> {\n    let titles = toTitles(items)\n    let choices = titles.map((s, i) => `${i + 1}${s}`)\n    let res = await callAsync(this.nvim, 'confirm', [message, choices.join('\\n'), 1, kind]) as number\n    return items[res - 1]\n  }\n\n  public async showMenuPicker<T extends MessageItem | string>(title: string, content: string, hlGroup: string, items: T[]): Promise<T> {\n    let texts = items.map(o => typeof o === 'string' ? o : o.title)\n    let res = await this.dialogs.showMenuPicker(texts, {\n      position: 'center',\n      content,\n      title: title.replace(/\\r?\\n/, ' '),\n      borderhighlight: hlGroup\n    })\n    return items[res]\n  }\n\n  public async showNotification(config: NotificationConfig, stack: string): Promise<void> {\n    let notification = new Notification(this.nvim, config)\n    await notification.show(this.getNotificationPreference(stack))\n  }\n\n  public echoMessages(msg: string, messageType: MsgTypes): void {\n    let level = this.configuration.get<string>('coc.preferences.messageLevel', 'more')\n    echoMessages(this.nvim, msg, messageType, level)\n  }\n\n  public async withProgress<R>(options: ProgressOptions, task: (progress: Progress, token: CancellationToken) => Thenable<R>): Promise<R> {\n    let config = this.configuration.get<NotificationConfiguration>('notification')\n    if (!options.cancellable && config.statusLineProgress) {\n      return await this.createStatusLineProgress(options, task)\n    }\n    let progress = new ProgressNotification(this.nvim, {\n      task,\n      title: options.title,\n      cancellable: options.cancellable\n    })\n    let minWidth = toNumber(config.minProgressWidth, 40)\n    let promise = new Promise<R>(resolve => {\n      progress.onDidFinish(resolve)\n    })\n    await progress.show(Object.assign(this.getNotificationPreference(options.source, true), { minWidth }))\n    return await promise\n  }\n\n  private async createStatusLineProgress<R>(options: ProgressOptions, task: (progress: Progress, token: CancellationToken) => Thenable<R>): Promise<R> {\n    let { title } = options\n    let statusItem = this.statusLine.createStatusBarItem(0, true)\n    statusItem.text = toText(title)\n    statusItem.show()\n    let total = 0\n    let result = await task({\n      report: p => {\n        if (p.increment) {\n          total += p.increment\n        }\n        statusItem.text = formatMessage(title, p.message, total).replace(/\\r?\\n/g, ' ')\n      }\n    }, CancellationToken.None)\n    statusItem.dispose()\n    return result\n  }\n\n  private get enableMessageDialog(): boolean {\n    return this.configuration.get<boolean>('coc.preferences.enableMessageDialog', false)\n  }\n\n  private get messageDialogKind(): string {\n    return this.configuration.get<string>('coc.preferences.messageDialogKind', 'confirm')\n  }\n\n  private get messageReportKind(): string {\n    return this.configuration.get<string>('coc.preferences.messageReportKind', 'echo')\n  }\n\n  private getNotificationPreference(source?: string, isProgress = false): NotificationPreferences {\n    if (!source) source = parseExtensionName(Error().stack)\n    let config = this.configuration.get<NotificationConfiguration>('notification')\n    let disabled = false\n    if (isProgress) {\n      let disabledList = defaultValue(config.disabledProgressSources, []) as string[]\n      disabled = Array.isArray(disabledList) && (disabledList.includes('*') || disabledList.includes(source))\n    }\n    return {\n      border: config.border,\n      focusable: config.focusable,\n      marginRight: toNumber(config.marginRight, 10),\n      timeout: toNumber(config.timeout, 10000),\n      maxWidth: toNumber(config.maxWidth, 60),\n      maxHeight: toNumber(config.maxHeight, 10),\n      highlight: config.highlightGroup,\n      winblend: toNumber(config.winblend, 30),\n      disabled,\n      source,\n    }\n  }\n}\n"
  },
  {
    "path": "src/core/terminals.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport events from '../events'\nimport { TerminalModel, TerminalOptions } from '../model/terminal'\nimport { disposeAll } from '../util'\nimport { toObject } from '../util/object'\nimport { Disposable, Emitter, Event } from '../util/protocol'\n\nexport interface TerminalResult {\n  bufnr: number\n  success: boolean\n  content?: string\n}\n\nexport interface OpenTerminalOption {\n  /**\n   * Cwd of terminal, default to result of |getcwd()|\n   */\n  cwd?: string\n  /**\n   * Close terminal on job finish, default to true.\n   */\n  autoclose?: boolean\n  /**\n   * Keep focus current window, default to false.\n   */\n  keepfocus?: boolean\n  /**\n   * Position of terminal window, default to 'right'.\n   */\n  position?: 'bottom' | 'right'\n}\n\nexport default class Terminals {\n  private _terminals: Map<number, TerminalModel> = new Map()\n  private disposables: Disposable[] = []\n  private readonly _onDidOpenTerminal = new Emitter<TerminalModel>()\n  private readonly _onDidCloseTerminal = new Emitter<TerminalModel>()\n  public readonly onDidCloseTerminal: Event<TerminalModel> = this._onDidCloseTerminal.event\n  public readonly onDidOpenTerminal: Event<TerminalModel> = this._onDidOpenTerminal.event\n\n  constructor() {\n    events.on('BufUnload', bufnr => {\n      if (this._terminals.has(bufnr)) {\n        let terminal = this._terminals.get(bufnr)\n        this._onDidCloseTerminal.fire(terminal)\n        this._terminals.delete(bufnr)\n      }\n    }, null, this.disposables)\n    events.on('TermExit', (bufnr, status) => {\n      let terminal = this._terminals.get(bufnr)\n      if (terminal) {\n        terminal.onExit(status)\n        terminal.dispose()\n      }\n    }, null, this.disposables)\n  }\n\n  public get terminals(): ReadonlyArray<TerminalModel> {\n    return Array.from(this._terminals.values())\n  }\n\n  public async createTerminal(nvim: Neovim, opts: TerminalOptions): Promise<TerminalModel> {\n    let cwd = opts.cwd\n    let cmd = opts.shellPath\n    let args = opts.shellArgs\n    if (!cmd) cmd = await nvim.getOption('shell') as string\n    if (!cwd) cwd = await nvim.call('getcwd') as string\n    let terminal = new TerminalModel(cmd, args || [], nvim, opts.name, opts.strictEnv)\n    await terminal.start(cwd, opts.env)\n    this._terminals.set(terminal.bufnr, terminal)\n    this._onDidOpenTerminal.fire(terminal)\n    return terminal\n  }\n\n  public async runTerminalCommand(nvim: Neovim, cmd: string, cwd: string | undefined, keepfocus: boolean): Promise<TerminalResult> {\n    return await nvim.callAsync('coc#ui#run_terminal', { cmd, cwd, keepfocus: keepfocus ? 1 : 0 }) as TerminalResult\n  }\n\n  public async openTerminal(nvim: Neovim, cmd: string, opts?: OpenTerminalOption): Promise<number> {\n    return await nvim.call('coc#ui#open_terminal', { cmd, ...toObject(opts) }) as number\n  }\n\n  public reset(): void {\n    for (let terminal of this._terminals.values()) {\n      terminal.dispose()\n    }\n    this._terminals.clear()\n  }\n\n  public dispose(): void {\n    this._onDidOpenTerminal.dispose()\n    this._onDidCloseTerminal.dispose()\n    disposeAll(this.disposables)\n    this.reset()\n  }\n}\n"
  },
  {
    "path": "src/core/ui.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { Position, Range } from 'vscode-languageserver-types'\nimport FloatFactoryImpl, { FloatWinConfig } from '../model/floatFactory'\nimport Regions from '../model/regions'\nimport { Documentation, FloatConfig, FloatFactory, FloatOptions } from '../types'\nimport { isVim } from '../util/constants'\nimport { byteIndex, byteLength } from '../util/string'\n\nexport interface ScreenPosition {\n  row: number\n  col: number\n}\n\nconst operateModes = ['char', 'line', 'block']\nexport type MsgTypes = 'error' | 'warning' | 'more'\n\nexport enum MessageLevel {\n  More,\n  Warning,\n  Error\n}\n\nexport async function getCursorPosition(nvim: Neovim): Promise<Position> {\n  // vim can't count utf16\n  let [line, content] = await nvim.eval(`[line('.')-1, strpart(getline('.'), 0, col('.') - 1)]`) as [number, string]\n  return Position.create(line, content.length)\n}\n\nexport async function getVisibleRanges(nvim: Neovim, bufnr: number, winid?: number): Promise<[number, number][]> {\n  if (winid == null) {\n    const spans = await nvim.call('coc#window#visible_ranges', [bufnr]) as [number, number][]\n    if (spans.length === 0) return []\n    return Regions.mergeSpans(spans)\n  }\n  const span = await nvim.call('coc#window#visible_range', [winid]) as [number, number] | null\n  return span == null ? [] : [span]\n}\n\nexport async function getLineAndPosition(nvim: Neovim): Promise<{ text: string, line: number, character: number }> {\n  let [text, lnum, content] = await nvim.eval(`[getline('.'), line('.'), strpart(getline('.'), 0, col('.') - 1)]`) as [string, number, string]\n  return { text, line: lnum - 1, character: content.length }\n}\n\nexport function createFloatFactory(nvim: Neovim, conf: FloatWinConfig, defaults: FloatConfig): FloatFactory {\n  let opts = Object.assign({}, defaults, conf)\n  let factory = new FloatFactoryImpl(nvim)\n  return {\n    get window() {\n      return factory.window\n    },\n    show: (docs: Documentation[], option?: FloatOptions) => {\n      return factory.show(docs, option ? Object.assign({}, opts, option) : opts)\n    },\n    activated: () => {\n      return factory.activated()\n    },\n    dispose: () => {\n      factory.dispose()\n    },\n    checkRetrigger: bufnr => {\n      return factory.checkRetrigger(bufnr)\n    },\n    close: () => {\n      factory.close()\n    }\n  }\n}\n\n/**\n * Prompt user for confirm, a float/popup window would be used when possible,\n * use vim's |confirm()| function as callback.\n * @param title The prompt text.\n * @returns Result of confirm.\n */\nexport async function showPrompt(nvim: Neovim, title: string): Promise<boolean> {\n  let res = await nvim.callAsync('coc#dialog#prompt_confirm', [title])\n  return res == 1\n}\n\n/**\n * Move cursor to position.\n * @param position LSP position.\n */\nexport async function moveTo(nvim: Neovim, position: Position, redraw: boolean): Promise<void> {\n  await nvim.call('coc#cursor#move_to', [position.line, position.character])\n  if (redraw) nvim.command('redraw', true)\n}\n\n/**\n * Get current cursor character offset in document,\n * length of line break would always be 1.\n * @returns Character offset.\n */\nexport async function getOffset(nvim: Neovim): Promise<number> {\n  return await nvim.call('coc#cursor#char_offset') as number\n}\n\n/**\n * Get screen position of current cursor(relative to editor),\n * both `row` and `col` are 0 based.\n * @returns Cursor screen position.\n */\nexport async function getCursorScreenPosition(nvim: Neovim): Promise<ScreenPosition> {\n  let [row, col] = await nvim.call('coc#cursor#screen_pos') as [number, number]\n  return { row, col }\n}\n\nexport async function echoLines(nvim: Neovim, env: { cmdheight: number, columns: number }, lines: string[], truncate: boolean): Promise<void> {\n  let cmdHeight = env.cmdheight\n  if (lines.length > cmdHeight && truncate) {\n    lines = lines.slice(0, cmdHeight)\n  }\n  let maxLen = env.columns - 12\n  lines = lines.map(line => {\n    line = line.replace(/\\n/g, ' ')\n    if (truncate) line = line.slice(0, maxLen)\n    return line\n  })\n  if (truncate && lines.length == cmdHeight) {\n    let last = lines[lines.length - 1]\n    lines[cmdHeight - 1] = `${last.length >= maxLen ? last.slice(0, -4) : last} ...`\n  }\n  await nvim.call('coc#ui#echo_lines', [lines])\n}\n\n/**\n * Reveal message with highlight.\n */\nexport function echoMessages(nvim: Neovim, msg: string, messageType: MsgTypes, messageLevel: string): void {\n  let hl: 'Error' | 'MoreMsg' | 'WarningMsg' = 'Error'\n  let level = MessageLevel.Error\n  switch (messageType) {\n    case 'more':\n      level = MessageLevel.More\n      hl = 'MoreMsg'\n      break\n    case 'warning':\n      level = MessageLevel.Warning\n      hl = 'WarningMsg'\n      break\n  }\n  if (level >= toMessageLevel(messageLevel)) {\n    let method = isVim ? 'callTimer' : 'call'\n    nvim[method]('coc#ui#echo_messages', [hl, ('[coc.nvim] ' + msg).split('\\n')], true)\n  }\n}\n\nexport function toMessageLevel(level: string): MessageLevel {\n  switch (level) {\n    case 'error':\n      return MessageLevel.Error\n    case 'warning':\n      return MessageLevel.Warning\n    default:\n      return MessageLevel.More\n  }\n}\n\n/**\n * Mode could be 'char', 'line', 'cursor', 'v', 'V', '\\x16'\n */\nexport async function getSelection(nvim: Neovim, mode: string): Promise<Range | null> {\n  if (mode === 'currline') {\n    let line = await nvim.call('line', ['.']) as number\n    return Range.create(line - 1, 0, line, 0)\n  }\n  if (mode === 'cursor') {\n    let position = await getCursorPosition(nvim)\n    return Range.create(position, position)\n  }\n  let res = await nvim.call('coc#cursor#get_selection', [operateModes.includes(mode) ? 1 : 0])\n  if (!res || res[0] == -1) return null\n  return Range.create(res[0], res[1], res[2], res[3])\n}\n\nexport async function selectRange(nvim: Neovim, range: Range, redraw: boolean): Promise<void> {\n  let { start, end } = range\n  let [line, endLine] = await nvim.eval(`[getline(${start.line + 1}),getline(${end.line + 1})]`) as [string, string]\n  let col = line.length > 0 ? byteIndex(line, start.character) : 0\n  let endCol: number\n  let endLnum: number\n  let toEnd = end.character == 0\n  if (toEnd) {\n    endLnum = end.line == 0 ? 0 : end.line - 1\n    let pre = await nvim.call('getline', [endLnum + 1]) as string\n    endCol = byteLength(pre)\n  } else {\n    endLnum = end.line\n    endCol = endLine.length > 0 ? byteIndex(endLine, end.character) : 0\n  }\n  nvim.pauseNotification()\n  nvim.command(`noa call cursor(${start.line + 1},${col + 1})`, true)\n  nvim.command('normal! v', true)\n  nvim.command(`noa call cursor(${endLnum + 1},${endCol})`, true)\n  if (toEnd) nvim.command('normal! $', true)\n  await nvim.resumeNotification(redraw)\n}\n"
  },
  {
    "path": "src/core/watchers.ts",
    "content": "'use strict'\nimport type { Neovim } from '@chemzqm/neovim'\nimport events from '../events'\nimport { createLogger } from '../logger'\nimport { ProviderResult } from '../provider'\nimport { Env } from '../types'\nimport { disposeAll } from '../util'\nimport { Disposable } from '../util/protocol'\nimport { toErrorText } from '../util/string'\nconst logger = createLogger('watchers')\n\nexport default class Watchers implements Disposable {\n  private nvim: Neovim\n  private optionCallbacks: Map<string, Set<(oldValue: any, newValue: any) => ProviderResult<void>>> = new Map()\n  private globalCallbacks: Map<string, Set<(oldValue: any, newValue: any) => ProviderResult<void>>> = new Map()\n  private disposables: Disposable[] = []\n  constructor() {\n    events.on('OptionSet', async (changed: string, oldValue: any, newValue: any) => {\n      let cbs = Array.from(this.optionCallbacks.get(changed) ?? [])\n      await Promise.allSettled(cbs.map(cb => {\n        return (async () => {\n          try {\n            await Promise.resolve(cb(oldValue, newValue))\n          } catch (e) {\n            this.nvim.errWriteLine(`Error on OptionSet '${changed}': ${toErrorText(e)}`)\n            logger.error(`Error on OptionSet callback:`, e)\n          }\n        })()\n      }))\n    }, null, this.disposables)\n    events.on('GlobalChange', async (changed: string, oldValue: any, newValue: any) => {\n      let cbs = Array.from(this.globalCallbacks.get(changed) ?? [])\n      await Promise.allSettled(cbs.map(cb => {\n        return (async () => {\n          try {\n            await Promise.resolve(cb(oldValue, newValue))\n          } catch (e) {\n            this.nvim.errWriteLine(`Error on GlobalChange '${changed}': ${toErrorText(e)}`)\n            logger.error(`Error on GlobalChange callback:`, e)\n          }\n        })()\n      }))\n    }, null, this.disposables)\n  }\n\n  public get options(): string[] {\n    return Array.from(this.optionCallbacks.keys())\n  }\n\n  public attach(nvim: Neovim, _env: Env): void {\n    this.nvim = nvim\n  }\n\n  /**\n   * Watch for option change.\n   */\n  public watchOption(key: string, callback: (oldValue: any, newValue: any) => Thenable<void> | void, disposables?: Disposable[]): Disposable {\n    let cbs = this.optionCallbacks.get(key)\n    if (!cbs) {\n      cbs = new Set()\n      this.optionCallbacks.set(key, cbs)\n    }\n    cbs.add(callback)\n    let cmd = `autocmd! coc_dynamic_option OptionSet ${key} call coc#rpc#notify('OptionSet',[expand('<amatch>'), v:option_old, v:option_new])`\n    this.nvim.command(cmd, true)\n    let disposable = Disposable.create(() => {\n      let cbs = this.optionCallbacks.get(key)\n      cbs.delete(callback)\n      if (cbs.size === 0) this.nvim.command(`autocmd! coc_dynamic_option OptionSet ${key}`, true)\n    })\n    if (disposables) disposables.push(disposable)\n    return disposable\n  }\n\n  /**\n   * Watch global variable, works on neovim only.\n   */\n  public watchGlobal(key: string, callback: (oldValue: any, newValue: any) => Thenable<void> | void, disposables?: Disposable[]): Disposable {\n    let { nvim } = this\n    let cbs = this.globalCallbacks.get(key)\n    if (!cbs) {\n      cbs = new Set()\n      this.globalCallbacks.set(key, cbs)\n    }\n    cbs.add(callback)\n    nvim.call('coc#_watch', key, true)\n    let disposable = Disposable.create(() => {\n      let cbs = this.globalCallbacks.get(key)\n      cbs.delete(callback)\n      if (cbs.size === 0) nvim.call('coc#_unwatch', key, true)\n    })\n    if (disposables) disposables.push(disposable)\n    return disposable\n  }\n\n  public dispose(): void {\n    disposeAll(this.disposables)\n  }\n}\n"
  },
  {
    "path": "src/core/watchman.ts",
    "content": "'use strict'\nimport type { Client } from 'fb-watchman'\nimport { v1 as uuidv1 } from 'uuid'\nimport { createLogger } from '../logger'\nimport { OutputChannel } from '../types'\nimport { minimatch, path } from '../util/node'\nimport { Disposable } from '../util/protocol'\nconst logger = createLogger('core-watchman')\nconst requiredCapabilities = ['relative_root', 'cmd-watch-project', 'wildmatch', 'field-new']\n\nexport interface WatchResponse {\n  warning?: string\n  watcher: string\n  watch: string\n  relative_path?: string\n}\n\nexport interface FileChangeItem {\n  size: number\n  name: string\n  exists: boolean\n  new: boolean\n  type: 'f' | 'd'\n  mtime_ms: number\n}\n\nexport interface FileChange {\n  root: string\n  subscription: string\n  files: FileChangeItem[]\n}\n\nexport type ChangeCallback = (FileChange) => void\n\n/**\n * Watchman wrapper for fb-watchman client\n * @public\n */\nexport default class Watchman {\n  private client: Client\n  private relative_path: string | undefined\n  private _listeners: ((change: FileChange) => void)[] = []\n  private _root: string\n  public subscription: string | undefined\n\n  constructor(binaryPath: string, private channel?: OutputChannel) {\n    const watchman = require('fb-watchman')\n    this.client = new watchman.Client({\n      watchmanBinaryPath: binaryPath\n    })\n    this.client.setMaxListeners(300)\n  }\n\n  public get root(): string {\n    return this._root\n  }\n\n  public checkCapability(): Promise<boolean> {\n    let { client } = this\n    return new Promise(resolve => {\n      client.capabilityCheck({\n        optional: [],\n        required: requiredCapabilities\n      }, (error, resp) => {\n        if (error) return resolve(false)\n        let { capabilities } = resp\n        for (let key of Object.keys(capabilities)) {\n          if (!capabilities[key]) return resolve(false)\n        }\n        resolve(true)\n      })\n    })\n  }\n\n  public async watchProject(root: string): Promise<boolean> {\n    this._root = root\n    let resp = await this.command(['watch-project', root])\n    let { watch, warning, relative_path } = resp as WatchResponse\n    if (!watch) return false\n    if (warning) {\n      logger.warn(warning)\n      this.appendOutput(warning, 'Warning')\n    }\n    this.relative_path = relative_path\n    logger.info(`watchman watching project: ${root}`)\n    this.appendOutput(`watchman watching project: ${root}`)\n    let { clock } = await this.command(['clock', watch])\n    let sub: any = {\n      expression: ['allof', ['type', 'f', 'wholename']],\n      fields: ['name', 'size', 'new', 'exists', 'type', 'mtime_ms', 'ctime_ms'],\n      since: clock,\n    }\n    if (relative_path) {\n      sub.relative_root = relative_path\n      root = path.join(watch, relative_path)\n    }\n    let uid = uuidv1()\n    let { subscribe } = await this.command(['subscribe', watch, uid, sub])\n    this.subscription = subscribe\n    this.appendOutput(`subscribing events in ${root}`)\n    this.client.on('subscription', resp => {\n      if (!resp || resp.subscription != uid || !resp.files) return\n      for (let listener of this._listeners) {\n        // @ts-expect-error file change item\n        listener(resp)\n      }\n    })\n    return true\n  }\n\n  private command(args: any[]): Promise<any> {\n    return new Promise((resolve, reject) => {\n      // @ts-expect-error any type\n      this.client.command(args, (error, resp) => {\n        // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors\n        if (error) return reject(error)\n        resolve(resp)\n      })\n    })\n  }\n\n  public subscribe(globPattern: string, cb: ChangeCallback): Disposable {\n    let fn = (change: FileChange) => {\n      let { files } = change\n      files = files.filter(f => f.type == 'f' && minimatch(f.name, globPattern, { dot: true }))\n      if (!files.length) return\n      let ev: FileChange = Object.assign({}, change)\n      if (this.relative_path) ev.root = path.resolve(change.root, this.relative_path)\n      this.appendOutput(`file change of \"${globPattern}\" detected: ${JSON.stringify(ev, null, 2)}`)\n      cb(ev)\n    }\n    this._listeners.push(fn)\n    return {\n      dispose: () => {\n        let idx = this._listeners.indexOf(fn)\n        if (idx !== -1) this._listeners.splice(idx, 1)\n      },\n    }\n  }\n\n  public dispose(): void {\n    if (this.client) {\n      this.client.end()\n      this.client = undefined\n    }\n  }\n\n  private appendOutput(message: string, type = \"Info\"): void {\n    if (this.channel) {\n      this.channel.appendLine(`[${type}  - ${(new Date().toLocaleTimeString())}] ${message}`)\n    }\n  }\n\n  public static async createClient(binaryPath: string, root: string, channel?: OutputChannel): Promise<Watchman> {\n    let watchman: Watchman\n    try {\n      watchman = new Watchman(binaryPath, channel)\n      let valid = await watchman.checkCapability()\n      if (!valid) throw new Error('required capabilities do not exist.')\n      let watching = await watchman.watchProject(root)\n      if (!watching) throw new Error('unable to watch')\n      return watchman\n    } catch (e) {\n      if (watchman) watchman.dispose()\n      throw e\n    }\n  }\n}\n"
  },
  {
    "path": "src/core/workspaceFolder.ts",
    "content": "'use strict'\nimport type { WorkspaceFoldersChangeEvent } from 'vscode-languageserver-protocol'\nimport { WorkspaceFolder } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport Configurations from '../configuration'\nimport events from '../events'\nimport { createLogger } from '../logger'\nimport Document from '../model/document'\nimport { getConditionValue } from '../util'\nimport { distinct, isFalsyOrEmpty, toArray } from '../util/array'\nimport { isCancellationError } from '../util/errors'\nimport { Extensions as ExtensionsInfo, IExtensionRegistry } from '../util/extensionRegistry'\nimport { checkFolder, isDirectory, isFolderIgnored, isParentFolder, resolveRoot } from '../util/fs'\nimport { path } from '../util/node'\nimport { toObject } from '../util/object'\nimport { CancellationToken, CancellationTokenSource, Emitter, Event } from '../util/protocol'\nimport { Registry } from '../util/registry'\nimport type { LanguageServerConfig } from '../services'\n\nexport enum PatternType {\n  Buffer,\n  LanguageServer,\n  Global,\n}\n\nconst logger = createLogger('core-workspaceFolder')\nconst PatternTypes = [PatternType.Buffer, PatternType.LanguageServer, PatternType.Global]\nconst checkPatternTimeout = getConditionValue(5000, 50)\n\nfunction toWorkspaceFolder(fsPath: string): WorkspaceFolder | undefined {\n  if (!fsPath || !path.isAbsolute(fsPath)) {\n    logger.error(`Invalid folder: ${fsPath}, full path required.`)\n    return undefined\n  }\n  return {\n    name: path.basename(fsPath),\n    uri: URI.file(fsPath).toString()\n  }\n}\n\nconst extensionRegistry = Registry.as<IExtensionRegistry>(ExtensionsInfo.ExtensionContribution)\n\ninterface WorkspaceConfig {\n  readonly ignoredFiletypes: string[]\n  readonly bottomUpFiletypes: string[]\n  readonly ignoredFolders: string[]\n  readonly workspaceFolderCheckCwd: boolean\n  readonly workspaceFolderFallbackCwd: boolean\n  rootPatterns: string[]\n}\n\nexport default class WorkspaceFolderController {\n  public config: WorkspaceConfig\n  private _onDidChangeWorkspaceFolders = new Emitter<WorkspaceFoldersChangeEvent>()\n  public readonly onDidChangeWorkspaceFolders: Event<WorkspaceFoldersChangeEvent> = this._onDidChangeWorkspaceFolders.event\n  // filetype => patterns\n  private rootPatterns: Map<string, string[]> = new Map()\n  private _workspaceFolders: WorkspaceFolder[] = []\n  private _tokenSources: Set<CancellationTokenSource> = new Set()\n  constructor(private configurations: Configurations) {\n    events.on('VimLeavePre', this.cancelAll, this)\n    this.updateConfiguration()\n    this.updateServerRootPatterns()\n    this.configurations.onDidChange(e => {\n      if (e.affectsConfiguration('workspace') || e.affectsConfiguration('coc.preferences')) {\n        this.updateConfiguration()\n      }\n      if (e.affectsConfiguration('languageserver')) {\n        this.updateServerRootPatterns()\n      }\n    })\n  }\n\n  private updateConfiguration(): void {\n    const allConfig = this.configurations.initialConfiguration\n    let config = allConfig.get<WorkspaceConfig>('workspace')\n    let oldConfig = allConfig.get<string[] | null>('coc.preferences.rootPatterns')\n    this.config = {\n      rootPatterns: isFalsyOrEmpty(oldConfig) ? toArray(config.rootPatterns) : oldConfig,\n      ignoredFiletypes: toArray(config.ignoredFiletypes),\n      bottomUpFiletypes: toArray(config.bottomUpFiletypes),\n      ignoredFolders: toArray(config.ignoredFolders),\n      workspaceFolderCheckCwd: !!config.workspaceFolderCheckCwd,\n      workspaceFolderFallbackCwd: !!config.workspaceFolderFallbackCwd\n    }\n  }\n\n  private updateServerRootPatterns(): void {\n    let lspConfig = this.configurations.getConfiguration('languageserver', null)\n    this.rootPatterns.clear()\n    for (let config of Object.values(toObject<Record<string, LanguageServerConfig>>(lspConfig))) {\n      let { filetypes, rootPatterns } = config\n      if (Array.isArray(filetypes) && !isFalsyOrEmpty(rootPatterns)) {\n        filetypes.filter(s => typeof s === 'string').forEach(filetype => {\n          this.addRootPattern(filetype, rootPatterns)\n        })\n      }\n    }\n  }\n\n  public cancelAll(): void {\n    for (let tokenSource of this._tokenSources) {\n      tokenSource.cancel()\n    }\n  }\n\n  public setWorkspaceFolders(folders: string[] | undefined): void {\n    if (!folders || !Array.isArray(folders)) return\n    let arr = folders.filter(f => f.length > 0).map(f => toWorkspaceFolder(f))\n    this._workspaceFolders = arr.filter(o => o != null)\n  }\n\n  public getWorkspaceFolder(uri: URI): WorkspaceFolder | undefined {\n    if (uri.scheme !== 'file') return undefined\n    let folders = Array.from(this._workspaceFolders).map(o => URI.parse(o.uri).fsPath)\n    folders.sort((a, b) => b.length - a.length)\n    let fsPath = uri.fsPath\n    let folder = folders.find(f => isParentFolder(f, fsPath, true))\n    return toWorkspaceFolder(folder)\n  }\n\n  public getRelativePath(pathOrUri: string | URI, includeWorkspace?: boolean): string {\n    let resource: URI | undefined\n    let p = ''\n    if (typeof pathOrUri === 'string') {\n      resource = URI.file(pathOrUri)\n      p = pathOrUri\n    } else if (pathOrUri != null) {\n      resource = pathOrUri\n      p = pathOrUri.fsPath\n    }\n    if (!resource) return p\n    const folder = this.getWorkspaceFolder(resource)\n    if (!folder) return p\n    if (typeof includeWorkspace === 'undefined' && this._workspaceFolders) {\n      includeWorkspace = this._workspaceFolders.length > 1\n    }\n    let result = path.relative(URI.parse(folder.uri).fsPath, resource.fsPath)\n    result = result == '' ? resource.fsPath : result\n    if (includeWorkspace && folder.name) {\n      result = `${folder.name}/${result}`\n    }\n    return result!\n  }\n\n  public get workspaceFolders(): ReadonlyArray<WorkspaceFolder> {\n    return this._workspaceFolders\n  }\n\n  public addRootPattern(filetype: string, rootPatterns: string[]): void {\n    let patterns = this.rootPatterns.get(filetype) ?? []\n    for (let p of rootPatterns) {\n      if (!patterns.includes(p)) {\n        patterns.push(p)\n      }\n    }\n    this.rootPatterns.set(filetype, patterns)\n  }\n\n  public resolveRoot(document: Document, cwd: string, fireEvent: boolean, expand: ((input: string) => string)): string | null {\n    if (document.buftype !== '' || document.schema !== 'file') return null\n    let u = URI.parse(document.uri)\n    let dir = isDirectory(u.fsPath) ? path.normalize(u.fsPath) : path.dirname(u.fsPath)\n    let { ignoredFiletypes, ignoredFolders, workspaceFolderCheckCwd, workspaceFolderFallbackCwd, bottomUpFiletypes } = this.config\n    if (ignoredFiletypes?.includes(document.filetype)) return null\n    ignoredFolders = Array.isArray(ignoredFolders) ? ignoredFolders.filter(s => s && s.length > 0).map(s => expand(s)) : []\n    let res: string | null = null\n    for (let patternType of PatternTypes) {\n      let patterns = this.getRootPatterns(document, patternType)\n      if (patterns && patterns.length) {\n        let isBottomUp = bottomUpFiletypes.includes('*') || bottomUpFiletypes.includes(document.filetype)\n        let root = resolveRoot(dir, patterns, cwd, isBottomUp, workspaceFolderCheckCwd, ignoredFolders)\n        if (root) {\n          res = root\n          break\n        }\n      }\n    }\n    if (!res && workspaceFolderFallbackCwd && !isFolderIgnored(cwd, ignoredFolders) && isParentFolder(cwd, dir, true)) {\n      res = cwd\n    }\n    if (res) this.addWorkspaceFolder(res, fireEvent)\n    return res\n  }\n\n  public addWorkspaceFolder(folder: string, fireEvent: boolean): WorkspaceFolder | undefined {\n    let workspaceFolder: WorkspaceFolder = toWorkspaceFolder(folder)\n    if (!workspaceFolder) return undefined\n    if (this._workspaceFolders.findIndex(o => o.uri == workspaceFolder.uri) == -1) {\n      this._workspaceFolders.push(workspaceFolder)\n      if (fireEvent) {\n        this._onDidChangeWorkspaceFolders.fire({\n          added: [workspaceFolder],\n          removed: []\n        })\n      }\n    }\n    return workspaceFolder\n  }\n\n  public renameWorkspaceFolder(oldPath: string, newPath: string): void {\n    let added: WorkspaceFolder = toWorkspaceFolder(newPath)\n    if (!added) return\n    let idx = this._workspaceFolders.findIndex(f => URI.parse(f.uri).fsPath == oldPath)\n    if (idx == -1) return\n    let removed = this.workspaceFolders[idx]\n    this._workspaceFolders.splice(idx, 1, added)\n    this._onDidChangeWorkspaceFolders.fire({\n      removed: [removed],\n      added: [added]\n    })\n  }\n\n  public removeWorkspaceFolder(fsPath: string): void {\n    let removed = toWorkspaceFolder(fsPath)\n    if (!removed) return\n    let idx = this._workspaceFolders.findIndex(f => f.uri == removed.uri)\n    if (idx == -1) return\n    this._workspaceFolders.splice(idx, 1)\n    this._onDidChangeWorkspaceFolders.fire({\n      removed: [removed],\n      added: []\n    })\n  }\n\n  public onDocumentDetach(uris: URI[]): void {\n    let shouldCheck = this.configurations.initialConfiguration.get('workspace.removeEmptyWorkspaceFolder', false)\n    if (!shouldCheck) return\n    let filepaths: string[] = []\n    for (const uri of uris) {\n      if (uri.scheme === 'file') {\n        filepaths.push(uri.fsPath)\n      }\n    }\n    for (const item of this.workspaceFolders) {\n      const folder = URI.parse(item.uri).fsPath\n      if (!filepaths.some(f => isParentFolder(folder, f))) {\n        this.removeWorkspaceFolder(folder)\n        return\n      }\n    }\n  }\n\n  public getRootPatterns(document: Document, patternType: PatternType): ReadonlyArray<string> {\n    if (patternType == PatternType.Buffer) return document.getVar('root_patterns', [])\n    if (patternType == PatternType.LanguageServer) return this.getServerRootPatterns(document.languageId)\n    return this.config.rootPatterns\n  }\n\n  public reset(): void {\n    this.rootPatterns.clear()\n    this._workspaceFolders = []\n  }\n\n  /**\n   * Get rootPatterns of filetype by languageserver configuration and extension configuration.\n   */\n  public getServerRootPatterns(filetype: string): string[] {\n    let patterns = extensionRegistry.getRootPatternsByFiletype(filetype)\n    patterns = patterns.concat(toArray(this.rootPatterns.get(filetype)))\n    return distinct(patterns)\n  }\n\n  public checkFolder(dir: string, patterns: string[], token?: CancellationToken): Promise<boolean> {\n    return checkFolder(dir, patterns, token)\n  }\n\n  public async checkPatterns(folders: ReadonlyArray<WorkspaceFolder>, patterns: string[]): Promise<boolean> {\n    if (isFalsyOrEmpty(folders)) return false\n    let dirs = folders.map(f => URI.parse(f.uri).fsPath)\n    let find = false\n    let tokenSource = new CancellationTokenSource()\n    this._tokenSources.add(tokenSource)\n    let token = tokenSource.token\n    let timer = setTimeout(() => {\n      tokenSource.cancel()\n    }, checkPatternTimeout)\n    let results = await Promise.allSettled(dirs.map(dir => {\n      return this.checkFolder(dir, patterns, token).then(checked => {\n        this._tokenSources.delete(tokenSource)\n        if (checked) {\n          find = true\n          clearTimeout(timer)\n          tokenSource.cancel()\n        }\n      })\n    }))\n    clearTimeout(timer)\n    results.forEach(res => {\n      if (res.status === 'rejected' && !isCancellationError(res.reason)) {\n        logger.error(`checkPatterns error:`, patterns, res.reason)\n      }\n    })\n    return find\n  }\n}\n"
  },
  {
    "path": "src/cursors/index.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { Range } from 'vscode-languageserver-types'\nimport commands from '../commands'\nimport Document from '../model/document'\nimport { IConfigurationChangeEvent } from '../types'\nimport { Disposable } from '../util/protocol'\nimport window from '../window'\nimport workspace from '../workspace'\nimport CursorSession, { CursorsConfig } from './session'\nimport { getVisualRanges, splitRange } from './util'\n\nexport type CursorPosition = [number, number, number, number]\n\nexport default class Cursors {\n  private sessionsMap: Map<number, CursorSession> = new Map()\n  private disposables: Disposable[] = []\n  private config: CursorsConfig\n  constructor(private nvim: Neovim) {\n    this.loadConfiguration()\n    workspace.onDidChangeConfiguration(this.loadConfiguration, this, this.disposables)\n    workspace.onDidCloseTextDocument(e => {\n      let session = this.getSession(e.bufnr)\n      if (!session) return\n      this.sessionsMap.delete(e.bufnr)\n      session.dispose()\n    }, null, this.disposables)\n    this.disposables.push(commands.registerCommand('editor.action.addRanges', async (ranges: Range[]) => {\n      await this.addRanges(ranges)\n    }, null, true))\n  }\n\n  private loadConfiguration(e?: IConfigurationChangeEvent): void {\n    if (!e || e.affectsConfiguration('cursors')) {\n      let config = workspace.initialConfiguration\n      this.config = config.get<CursorsConfig>('cursors')\n    }\n  }\n\n  public cancel(uri: number | string): void {\n    let doc = workspace.getDocument(uri)\n    if (!doc) return\n    let session = this.getSession(doc.bufnr)\n    if (session) session.cancel()\n  }\n\n  public getSession(bufnr: number): CursorSession | undefined {\n    return this.sessionsMap.get(bufnr)\n  }\n\n  public async isActivated(): Promise<boolean> {\n    let bufnr = await this.nvim.call('bufnr', ['%']) as number\n    return this.sessionsMap.get(bufnr) != null\n  }\n\n  public async select(bufnr: number, kind: string, mode: string): Promise<void> {\n    let doc = workspace.getAttachedDocument(bufnr)\n    let { nvim } = this\n    let session = this.createSession(doc)\n    let range: Range\n    if (kind == 'operator') {\n      let res = await nvim.eval(`[getpos(\"'[\"),getpos(\"']\")]`) as [CursorPosition, CursorPosition]\n      if (mode == 'char') {\n        let start = doc.getPosition(res[0][1], res[0][2])\n        let end = doc.getPosition(res[1][1], res[1][2] + 1)\n        let ranges = splitRange(doc, Range.create(start, end))\n        session.addRanges(ranges)\n      } else {\n        let ranges: Range[] = []\n        for (let i = res[0][1] - 1; i <= res[1][1] - 1; i++) {\n          let line = doc.getline(i)\n          ranges.push(Range.create(i, 0, i, line.length))\n        }\n        session.addRanges(ranges)\n      }\n    } else if (kind == 'word') {\n      let pos = await window.getCursorPosition()\n      range = doc.getWordRangeAtPosition(pos)\n      if (!range) {\n        let line = doc.getline(pos.line)\n        if (pos.character == line.length) {\n          range = Range.create(pos.line, Math.max(0, line.length - 1), pos.line, line.length)\n        } else {\n          range = Range.create(pos.line, pos.character, pos.line, pos.character + 1)\n        }\n      }\n      session.addRange(range)\n      await nvim.command(`silent! call repeat#set(\"\\\\<Plug>(coc-cursors-${kind})\", -1)`)\n    } else if (kind == 'position') {\n      let pos = await window.getCursorPosition()\n      // make sure range contains character for highlight\n      let line = doc.getline(pos.line)\n      if (pos.character >= line.length) {\n        range = Range.create(pos.line, Math.max(0, line.length - 1), pos.line, line.length)\n      } else {\n        range = Range.create(pos.line, pos.character, pos.line, pos.character + 1)\n      }\n      session.addRange(range)\n      await nvim.command(`silent! call repeat#set(\"\\\\<Plug>(coc-cursors-${kind})\", -1)`)\n    } else if (kind == 'range') {\n      await nvim.call('eval', 'feedkeys(\"\\\\<esc>\", \"in\")')\n      let range = await window.getSelectedRange(mode)\n      if (range) {\n        let ranges = mode == '\\x16' ? getVisualRanges(doc, range) : splitRange(doc, range)\n        for (let r of ranges) {\n          session.addRange(r)\n        }\n      }\n    } else {\n      throw new Error(`select kind \"${kind}\" not supported`)\n    }\n    session.checkRanges()\n  }\n\n  public createSession(doc: Document): CursorSession {\n    let { bufnr } = doc\n    let session = this.getSession(bufnr)\n    if (session) return session\n    session = new CursorSession(this.nvim, doc, this.config)\n    this.sessionsMap.set(bufnr, session)\n    session.onDidCancel(() => {\n      session.dispose()\n      this.sessionsMap.delete(bufnr)\n    })\n    return session\n  }\n\n  // Add ranges to current document\n  public async addRanges(ranges: Range[]): Promise<boolean> {\n    let { nvim } = this\n    let bufnr = await nvim.call('bufnr', ['%']) as number\n    let doc = workspace.getAttachedDocument(bufnr)\n    let session = this.createSession(doc)\n    return session.addRanges(ranges)\n  }\n\n  public reset(): void {\n    for (let session of this.sessionsMap.values()) {\n      session.cancel()\n    }\n    this.sessionsMap.clear()\n  }\n}\n"
  },
  {
    "path": "src/cursors/session.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { Range, TextEdit } from 'vscode-languageserver-types'\nimport { createLogger } from '../logger'\nimport Document from '../model/document'\nimport { DidChangeTextDocumentParams, HighlightItem } from '../types'\nimport { disposeAll } from '../util'\nimport { fastDiff } from '../util/node'\nimport { comparePosition, emptyRange, rangeAdjacent, rangeInRange, rangeIntersect, rangeOverlap } from '../util/position'\nimport { Disposable, Emitter, Event } from '../util/protocol'\nimport { lineCountChange } from '../util/textedit'\nimport window from '../window'\nimport workspace from '../workspace'\nimport TextRange from './textRange'\nimport { getBeforeCount, getChange, getDelta, SurroundChange, TextChange } from './util'\nconst logger = createLogger('cursors-session')\n\nexport interface CursorsConfig {\n  cancelKey: string\n  previousKey: string\n  nextKey: string\n  wrapscan: boolean\n}\n\nexport interface DiffItem {\n  offset: number\n  add?: string\n  remove?: string\n}\n\n/**\n * Cursor session for single buffer\n */\nexport default class CursorSession {\n  private readonly _onDidCancel = new Emitter<void>()\n  private readonly _onDidUpdate = new Emitter<void>()\n  public readonly onDidCancel: Event<void> = this._onDidCancel.event\n  public readonly onDidUpdate: Event<void> = this._onDidUpdate.event\n  private disposables: Disposable[] = []\n  private ranges: TextRange[] = []\n  private activated = true\n  private changing = false\n  constructor(private nvim: Neovim, private doc: Document, private config: CursorsConfig) {\n    let { bufnr } = doc\n    doc.buffer.setVar('coc_cursors_activated', 1, true)\n    let { cancelKey, nextKey, previousKey } = this.config\n    this.disposables.push(workspace.registerLocalKeymap(bufnr, 'n', cancelKey, () => {\n      this.cancel()\n    }))\n    this.disposables.push(workspace.registerLocalKeymap(bufnr, 'n', nextKey, async () => {\n      let ranges = this.ranges.map(o => o.range)\n      let curr = await window.getCursorPosition()\n      for (let r of ranges) {\n        if (comparePosition(r.start, curr) > 0) {\n          await window.moveTo(r.start)\n          return\n        }\n      }\n      let wrap = this.config.wrapscan\n      if (ranges.length && wrap) await window.moveTo(ranges[0].start)\n    }))\n    this.disposables.push(workspace.registerLocalKeymap(bufnr, 'n', previousKey, async () => {\n      let ranges = this.ranges.map(o => o.range)\n      let curr = await window.getCursorPosition()\n      for (let i = ranges.length - 1; i >= 0; i--) {\n        let r = ranges[i]\n        if (comparePosition(r.end, curr) < 0) {\n          await window.moveTo(r.start)\n          return\n        }\n      }\n      let wrap = this.config.wrapscan\n      if (ranges.length && wrap) await window.moveTo(ranges[ranges.length - 1].start)\n    }))\n    this.doc.onDocumentChange(async e => {\n      await this.onChange(e)\n      if (this.activated && !this.changing) {\n        this._onDidUpdate.fire()\n      }\n    }, this, this.disposables)\n  }\n\n  public checkRanges(): void {\n    if (this.ranges.length == 0) {\n      this.cancel()\n    }\n  }\n\n  /**\n   * Add or remove range.\n   */\n  public addRange(range: Range): void {\n    let { ranges } = this\n    let idx = ranges.findIndex(o => rangeIntersect(o.range, range))\n    // remove range when intersect\n    if (idx !== -1) {\n      ranges.splice(idx, 1)\n    } else {\n      this.createRange(range)\n      ranges.sort((a, b) => comparePosition(a.range.start, b.range.start))\n    }\n    if (this.ranges.length == 0) {\n      this.cancel()\n    } else {\n      this.doHighlights()\n    }\n  }\n\n  public addRanges(ranges: Range[]): boolean {\n    this.doc._forceSync()\n    // filter overlap ranges\n    this.ranges = this.ranges.filter(r => {\n      return !ranges.some(range => rangeOverlap(range, r.range))\n    })\n    for (let range of ranges) {\n      this.createRange(range)\n    }\n    this.ranges.sort((a, b) => comparePosition(a.range.start, b.range.start))\n    this.doHighlights()\n    return true\n  }\n\n  private createRange(range: Range): void {\n    let { textDocument } = this.doc\n    let { line, character } = range.start\n    let text = textDocument.getText(range)\n    this.ranges.push(new TextRange(line, character, text))\n  }\n\n  public async onChange(e: DidChangeTextDocumentParams): Promise<void> {\n    if (!this.activated || this.changing) return\n    if (e.contentChanges.length === 0) {\n      this.doHighlights()\n      return\n    }\n    let change = e.contentChanges[0]\n    let { text, range } = change\n    let affected = this.ranges.filter(r => {\n      if (!rangeIntersect(range, r.range)) return false\n      if (rangeAdjacent(range, r.range)) {\n        if (text.includes('\\n') || !emptyRange(range)) return false\n      }\n      return true\n    })\n    if (emptyRange(range) && affected.length > 0) {\n      affected = affected.slice(0, 1)\n    }\n    if (affected.length == 0) {\n      logger.debug('no affected ranges')\n      this.ranges.forEach(r => {\n        r.adjustFromEdit({ range, newText: text })\n      })\n      this.doHighlights()\n    } else if (affected.length == 1 && rangeInRange(range, affected[0].range)) {\n      logger.debug('affected single range')\n      if (text.includes('\\n')) {\n        this.cancel()\n        return\n      }\n      // change textRange\n      await this.applySingleEdit(affected[0], { range, newText: text })\n    } else if (!text.length || !this.validChange(range, text)) {\n      logger.debug('filter affected ranges.')\n      let ranges = this.ranges.filter(r => !affected.includes(r))\n      if (ranges.length > 0) {\n        this.ranges = ranges\n        ranges.forEach(r => {\n          r.adjustFromEdit({ range, newText: text })\n        })\n        this.doHighlights()\n      } else {\n        this.cancel()\n      }\n    } else {\n      logger.debug('Check undo & redo')\n      let first = this.ranges[0]\n      let last = this.ranges[this.ranges.length - 1]\n      let originalLines = e.originalLines.slice(first.line, last.line + 1)\n      let newLines = this.doc.textDocument.lines.slice(first.line, last.line + 1)\n      this.applyComposedEdit(originalLines, newLines)\n    }\n  }\n\n  public validChange(range: Range, text: string): boolean {\n    if (lineCountChange(TextEdit.replace(range, text)) != 0) return false\n    if (!rangeInRange(range, this.range)) return false\n    let first = this.ranges[0]\n    let last = this.ranges[this.ranges.length - 1]\n    if (range.start.line != first.position.line || range.end.line != last.position.line) return false\n    return true\n  }\n\n  public get range(): Range {\n    let first = this.ranges[0]\n    let last = this.ranges[this.ranges.length - 1]\n    return Range.create(first.position, last.range.end)\n  }\n\n  private doHighlights(): void {\n    let { nvim, ranges, doc } = this\n    let buffer = doc.buffer\n    let items: HighlightItem[] = []\n    ranges.forEach(r => {\n      doc.addHighlights(items, 'CocCursorRange', r.range, {\n        combine: false,\n        start_incl: true,\n        end_incl: true\n      })\n    })\n    items.sort((a, b) => {\n      if (a.lnum != b.lnum) return a.lnum - b.lnum\n      return a.colStart - b.colStart\n    })\n    buffer.updateHighlights('cursors', items, { priority: 4096 })\n    nvim.redrawVim()\n  }\n\n  public get currentRanges(): Range[] {\n    return this.ranges.map(r => r.range)\n  }\n\n  /**\n   * Cancel session and highlights\n   */\n  public cancel(): void {\n    if (!this.activated) return\n    logger.debug('cursors cancel')\n    let buffer = this.doc.buffer\n    this.activated = false\n    this.ranges = []\n    buffer.clearNamespace('cursors')\n    buffer.setVar('coc_cursors_activated', 0, true)\n    this._onDidUpdate.fire()\n    this._onDidCancel.fire()\n  }\n\n  /**\n   * Called on buffer unload or cancel\n   */\n  public dispose(): void {\n    if (!this.doc) return\n    this._onDidCancel.dispose()\n    this._onDidUpdate.dispose()\n    disposeAll(this.disposables)\n    this.ranges = []\n    this.doc = null\n  }\n\n  private async applySingleEdit(textRange: TextRange, edit: TextEdit): Promise<void> {\n    // single range change, calculate & apply changes for all ranges\n    let { doc, ranges } = this\n    let after = ranges.filter(r => r !== textRange && r.position.line == textRange.position.line)\n    after.forEach(r => r.adjustFromEdit(edit))\n    let change = getChange(textRange, edit.range, edit.newText)\n    let delta = getDelta(change)\n    ranges.forEach(r => r.applyChange(change))\n    let edits = ranges.filter(r => r !== textRange).map(o => o.textEdit)\n    this.changing = true\n    await doc.applyEdits(edits, true, true)\n    this.changing = false\n    if (delta != 0) {\n      for (let r of ranges) {\n        let n = getBeforeCount(r, this.ranges, textRange)\n        r.move(n * delta)\n      }\n    }\n    this.doHighlights()\n  }\n\n  public applyComposedEdit(originalLines: string[], newLines: string[]): boolean {\n    // check complex edit\n    let diffs = fastDiff(originalLines[0], newLines[0])\n    let first = this.ranges[0]\n    // let ranges = this.ranges.filter(o => o.line == first.line)\n    let s = first.position.character\n    let firstLine = first.position.line\n    let len = first.text.length\n    let diff = diffs[0]\n    if (s > 0 && (diff[0] != fastDiff.EQUAL || !diff[1].startsWith(originalLines[0].slice(0, s)))) {\n      this.cancel()\n      return false\n    }\n    let used = 0\n    let invalid = false\n    let changes: DiffItem[] = []\n    for (let i = 0; i < diffs.length; i++) {\n      let [kind, text] = diffs[i]\n      if (i == 0 && s > 0) {\n        text = text.slice(s)\n      }\n      if (kind == fastDiff.EQUAL) {\n        used += text.length\n        if (used > len) break\n      } else if (kind == fastDiff.DELETE) {\n        let offset = used\n        used += text.length\n        if (used > len) {\n          invalid = true\n          break\n        }\n        changes.push({ offset, remove: text })\n      } else {\n        let prev = diffs[i - 1]\n        if (prev && prev[0] == fastDiff.DELETE) {\n          changes[changes.length - 1].add = text\n        } else {\n          changes.push({ offset: used, add: text })\n        }\n      }\n    }\n    if (invalid || !changes.length) {\n      this.cancel()\n      return false\n    }\n    let doc = TextDocument.create('file:///1', '', 0, originalLines.join('\\n'))\n    let change: TextChange | SurroundChange\n    if (changes.length == 1) {\n      change = {\n        offset: changes[0].offset,\n        remove: changes[0].remove ? changes[0].remove.length : 0,\n        insert: changes[0].add ?? ''\n      }\n    } else if (surroundChanges(changes, len)) {\n      change = {\n        prepend: [changes[0].remove ? changes[0].remove.length : 0, changes[0].add ?? ''],\n        append: [changes[1].remove ? changes[1].remove.length : 0, changes[1].add ?? ''],\n        remove: false\n      }\n    } else {\n      let text = first.text\n      let oldText = ''\n      let newText = ''\n      let offset = changes[0].offset\n      for (let c of changes) {\n        if (c.offset > offset + oldText.length) {\n          let s = text.slice(offset + oldText.length, c.offset)\n          oldText += s\n          newText += s\n        }\n        if (c.add) {\n          newText += c.add\n        }\n        if (c.remove) {\n          oldText += c.remove\n        }\n      }\n      change = {\n        offset,\n        remove: oldText.length,\n        insert: newText\n      }\n    }\n    let edits: TextEdit[] = this.ranges.map(o => {\n      let line = o.position.line - firstLine\n      let { start, end } = o.range\n      let range = Range.create(line, start.character, line, end.character)\n      o.applyChange(change)\n      return TextEdit.replace(range, o.text)\n    })\n    let content = TextDocument.applyEdits(doc, edits)\n    if (content !== newLines.join('\\n')) {\n      this.cancel()\n      return false\n    }\n\n    let delta = getDelta(change)\n    if (delta != 0) {\n      for (let r of this.ranges) {\n        let n = getBeforeCount(r, this.ranges)\n        r.move(n * delta)\n      }\n    }\n    this.doHighlights()\n    return true\n  }\n}\n\nexport function surroundChanges(changes: DiffItem[], len: number): boolean {\n  if (changes.length != 2 || changes[0].offset != 0) return false\n  let end = changes[1].offset + (changes[1].remove ? changes[1].remove.length : 0)\n  if (end !== len) return false\n  return true\n}\n"
  },
  {
    "path": "src/cursors/textRange.ts",
    "content": "'use strict'\nimport { Position, Range, TextEdit } from 'vscode-languageserver-types'\nimport { getEnd } from '../util/position'\nimport { getChangedPosition } from '../util/textedit'\nimport { isSurroundChange, SurroundChange, TextChange } from './util'\n\nexport default class TextRange {\n  private start: Position\n  private end: Position\n  private _text: string\n\n  constructor(line: number, character: number, text: string) {\n    this.start = Position.create(line, character)\n    this._text = text\n    this.end = getEnd(this.start, this._text)\n  }\n\n  public get position(): Position {\n    return this.start\n  }\n\n  public get line(): number {\n    return this.start.line\n  }\n\n  public get text(): string {\n    return this._text\n  }\n\n  public get range(): Range {\n    return Range.create(this.start, this.end)\n  }\n\n  public get textEdit(): TextEdit {\n    return {\n      range: this.range,\n      newText: this.text\n    }\n  }\n\n  public applyChange(change: SurroundChange | TextChange): void {\n    if (isSurroundChange(change)) {\n      this.applySurroundChange(change)\n    } else {\n      this.applyTextChange(change)\n    }\n  }\n\n  public applySurroundChange(change: SurroundChange): void {\n    let { prepend, append, remove } = change\n    if (remove) {\n      this._text = ''\n    } else {\n      let len = this._text.length\n      let text = this._text.substring(prepend[0], len - append[0])\n      this._text = `${prepend[1]}${text}${append[1]}`\n    }\n  }\n\n  public applyTextChange(change: TextChange): void {\n    let { text } = this\n    let { offset, remove, fromEnd, insert } = change\n    if (fromEnd) offset = -offset\n    let pre = text.slice(0, fromEnd && offset == 0 ? text.length : offset)\n    let after = text.slice(pre.length)\n    if (remove) after = after.slice(remove)\n    this._text = `${pre}${insert || ''}${after}`\n  }\n\n  /**\n   * Adjust range\n   */\n  public move(delta: number): void {\n    if (delta != 0) {\n      let { line, character } = this.start\n      this.start = Position.create(line, character + delta)\n    }\n    this.end = getEnd(this.start, this._text)\n  }\n\n  public adjustFromEdit(edit: TextEdit): number {\n    let changed = getChangedPosition(this.start, edit)\n    if (changed.line || changed.character) {\n      let { line, character } = this.start\n      this.start = Position.create(line + changed.line, character + changed.character)\n      this.end = getEnd(this.start, this._text)\n    }\n    return changed.character\n  }\n\n  public isBefore(range: TextRange): boolean {\n    let { position } = range\n    let { line, character } = this.start\n    return position.line == line && position.character > character\n  }\n}\n"
  },
  {
    "path": "src/cursors/util.ts",
    "content": "'use strict'\nimport { Range } from 'vscode-languageserver-types'\nimport Document from '../model/document'\nimport { equals } from '../util/object'\nimport { toText } from '../util/string'\nimport { getWellformedRange } from '../util/textedit'\nimport type TextRange from './textRange'\n\nexport interface TextChange {\n  offset: number\n  remove: number\n  insert: string\n  fromEnd?: boolean\n}\n\nexport interface SurroundChange {\n  /**\n   * delete count & insert text\n   */\n  prepend: [number, string]\n  /**\n   * delete count & insert text\n   */\n  append: [number, string]\n  /**\n   * Remove the range when true\n   */\n  remove: boolean\n}\n\n/**\n * Split to single line ranges\n */\nexport function splitRange(doc: Document, range: Range): Range[] {\n  let splited: Range[] = []\n  for (let i = range.start.line; i <= range.end.line; i++) {\n    let curr = toText(doc.getline(i))\n    let sc = i == range.start.line ? range.start.character : 0\n    let ec = i == range.end.line ? range.end.character : curr.length\n    if (sc == ec) continue\n    splited.push(Range.create(i, sc, i, ec))\n  }\n  return splited\n}\n\n/**\n * Get ranges of visual block\n */\nexport function getVisualRanges(doc: Document, range: Range): Range[] {\n  let { start, end } = getWellformedRange(range)\n  let sc = start.character < end.character ? start.character : end.character\n  let ec = start.character < end.character ? end.character : start.character\n  let ranges: Range[] = []\n  for (let i = start.line; i <= end.line; i++) {\n    let line = doc.getline(i)\n    ranges.push(Range.create(i, sc, i, Math.min(line.length, ec)))\n  }\n  return ranges\n}\n\nexport function isSurroundChange(change: TextChange | SurroundChange): change is SurroundChange {\n  return Array.isArray(change['prepend']) && Array.isArray(change['append'])\n}\n\nexport function isTextChange(change: TextChange | SurroundChange): change is TextChange {\n  return typeof change['offset'] === 'number' && typeof change['remove'] === 'number'\n}\n\nexport function getDelta(change: TextChange | SurroundChange): number {\n  if (isSurroundChange(change)) {\n    return change.append[1].length + change.prepend[1].length - change.append[0] - change.prepend[0]\n  }\n  return change.insert.length - change.remove\n}\n\nexport function getChange(r: TextRange, range: Range, newText: string): TextChange | SurroundChange {\n  let text = r.text\n  if (equals(r.range, range)) {\n    // surround\n    let idx = text.indexOf(newText)\n    if (idx !== -1) {\n      let prepend: [number, string] = [idx, '']\n      let n = text.length - newText.length - idx\n      let append: [number, string] = [n, '']\n      return { prepend, append, remove: r.text.length === n }\n    }\n    idx = newText.indexOf(text)\n    if (idx !== -1) {\n      let prepend: [number, string] = [0, newText.slice(0, idx)]\n      let append: [number, string] = [0, newText.slice(- (newText.length - text.length - idx))]\n      return { prepend, append, remove: false }\n    }\n  }\n  if (equals(r.range.end, range.end)) {\n    // end change\n    let remove = range.end.character - range.start.character\n    return { offset: remove, remove, insert: newText, fromEnd: true }\n  }\n  let remove = range.end.character - range.start.character\n  let offset = range.start.character - r.range.start.character\n  return { offset, remove, insert: newText }\n}\n\nexport function getBeforeCount(textRange: TextRange, ranges: TextRange[], exclude?: TextRange): number {\n  let n = 0\n  for (let idx = 0; idx < ranges.length; idx++) {\n    const r = ranges[idx]\n    if (r.position.line < textRange.position.line || r === exclude) continue\n    if (r.isBefore(textRange)) {\n      n++\n      continue\n    }\n    break\n  }\n  return n\n}\n"
  },
  {
    "path": "src/diagnostic/buffer.ts",
    "content": "'use strict'\nimport type { Buffer, Neovim, VirtualTextOption } from '@chemzqm/neovim'\nimport { Diagnostic, DiagnosticSeverity, Location, Position, TextEdit } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport events from '../events'\nimport { SyncItem } from '../model/bufferSync'\nimport Document from '../model/document'\nimport { DiagnosticWithFileType, DidChangeTextDocumentParams, Documentation, FloatFactory, HighlightItem } from '../types'\nimport { getConditionValue } from '../util'\nimport { stripAnsiColoring } from '../util/ansiparse'\nimport { isFalsyOrEmpty } from '../util/array'\nimport { onUnexpectedError } from '../util/errors'\nimport { path } from '../util/node'\nimport { lineInRange, positionInRange } from '../util/position'\nimport { Emitter, Event } from '../util/protocol'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { DiagnosticItem } from './manager'\nimport { adjustDiagnostics, DiagnosticConfig, formatDiagnostic, getHighlightGroup, getLocationListItem, getNameFromSeverity, getSeverityName, getSeverityType, LocationListItem, severityLevel, sortDiagnostics } from './util'\nconst signGroup = 'CocDiagnostic'\nconst NAMESPACE = 'diagnostic'\n// higher priority first\nconst hlGroups = ['CocErrorHighlight', 'CocWarningHighlight', 'CocInfoHighlight', 'CocHintHighlight', 'CocDeprecatedHighlight', 'CocUnusedHighlight']\n\ninterface DiagnosticInfo {\n  /**\n   * current bufnr\n   */\n  bufnr: number\n  lnum: number\n  winid: number\n  locationlist: string\n}\n\ninterface SignItem {\n  name: string\n  lnum: number\n  priority?: number\n}\n\nconst delay = getConditionValue(100, 10)\nconst aleMethod = getConditionValue('ale#other_source#ShowResults', 'MockAleResults')\nlet virtualTextSrcId: number | undefined\n\nlet floatFactory: FloatFactory\n\n/**\n * Manage diagnostics of buffer, including:\n *\n * - highlights\n * - variable\n * - signs\n * - location list\n * - virtual text\n */\nexport class DiagnosticBuffer implements SyncItem {\n  private diagnosticsMap: Map<string, ReadonlyArray<Diagnostic>> = new Map()\n  private _disposed = false\n  private _dirties: Set<string> = new Set()\n  private _refreshing = false\n  private _config: DiagnosticConfig\n  public refreshHighlights: (() => void) & { clear(): void }\n  private readonly _onDidRefresh = new Emitter<ReadonlyArray<Diagnostic>>()\n  public readonly onDidRefresh: Event<ReadonlyArray<Diagnostic>> = this._onDidRefresh.event\n  constructor(\n    private readonly nvim: Neovim,\n    public readonly doc: Document\n  ) {\n    this.loadConfiguration()\n    let timer: NodeJS.Timeout\n    let fn: any = () => {\n      clearTimeout(timer)\n      this._refreshing = true\n      timer = setTimeout(() => {\n        this._refreshing = false\n        if (!this._autoRefresh) return\n        void this._refresh(true)\n      }, delay)\n    }\n    fn.clear = () => {\n      this._refreshing = false\n      clearTimeout(timer)\n    }\n    this.refreshHighlights = fn\n  }\n\n  private get _autoRefresh(): boolean {\n    return this.config.enable && this.config.autoRefresh && this.dirty && !this.doc.hasChanged\n  }\n\n  public get config(): Readonly<DiagnosticConfig> {\n    return this._config\n  }\n\n  public loadConfiguration(): void {\n    let config = workspace.getConfiguration('diagnostic', this.doc)\n    let changed = this._config && config.enable != this._config.enable\n    this._config = {\n      enable: config.get<boolean>('enable', true),\n      floatConfig: config.get('floatConfig', {}),\n      messageTarget: config.get<string>('messageTarget', 'float'),\n      enableHighlightLineNumber: config.get<boolean>('enableHighlightLineNumber', true),\n      highlightLimit: config.get<number>('highlightLimit', 1000),\n      highlightPriority: config.get<number>('highlightPriority'),\n      autoRefresh: config.get<boolean>('autoRefresh', true),\n      checkCurrentLine: config.get<boolean>('checkCurrentLine', false),\n      enableSign: workspace.env.sign && config.get<boolean>('enableSign', true),\n      locationlistUpdate: config.get<boolean>('locationlistUpdate', true),\n      enableMessage: config.get<string>('enableMessage', 'always'),\n      virtualText: config.get<boolean>('virtualText', false),\n      virtualTextAlign: config.get<VirtualTextOption['text_align']>('virtualTextAlign', 'after'),\n      virtualTextWinCol: config.get<number | null>('virtualTextWinCol', null),\n      virtualTextCurrentLineOnly: config.get<boolean>('virtualTextCurrentLineOnly'),\n      virtualTextPrefix: config.get<string>('virtualTextPrefix', \" \"),\n      virtualTextFormat: config.get<string>('virtualTextFormat', \"%message\"),\n      virtualTextLimitInOneLine: config.get<number>('virtualTextLimitInOneLine', 999),\n      virtualTextLineSeparator: config.get<string>('virtualTextLineSeparator', \" \\\\ \"),\n      virtualTextLines: config.get<number>('virtualTextLines', 3),\n      displayByAle: config.get<boolean>('displayByAle', false),\n      displayByVimDiagnostic: config.get<boolean>('displayByVimDiagnostic', false),\n      level: severityLevel(config.get<string>('level', 'hint')),\n      locationlistLevel: severityLevel(config.get<string>('locationlistLevel')),\n      signLevel: severityLevel(config.get<string>('signLevel')),\n      virtualTextLevel: severityLevel(config.get<string>('virtualTextLevel')),\n      messageLevel: severityLevel(config.get<string>('messageLevel')),\n      signPriority: config.get<number>('signPriority', 10),\n      refreshOnInsertMode: config.get<boolean>('refreshOnInsertMode', false),\n      filetypeMap: config.get<object>('filetypeMap', {}),\n      showUnused: config.get<boolean>('showUnused', true),\n      showDeprecated: config.get<boolean>('showDeprecated', true),\n      format: config.get<string>('format', '[%source%code] [%severity] %message'),\n      showRelatedInformation: config.get<boolean>('showRelatedInformation', true),\n    }\n    if (this._config.virtualText && !virtualTextSrcId) {\n      void this.nvim.createNamespace('coc-diagnostic-virtualText').then(id => {\n        virtualTextSrcId = id\n      })\n    }\n    if (changed) {\n      if (this.config.enable) {\n        void this._refresh(false)\n      } else {\n        this.clear()\n      }\n    }\n  }\n\n  public async setState(enable: boolean): Promise<void> {\n    let curr = this._config.enable\n    if (curr == enable) return\n    this._config.enable = enable\n    if (enable) {\n      await this._refresh(false)\n    } else {\n      this.clear()\n    }\n  }\n\n  public get dirty(): boolean {\n    return this._dirties.size > 0\n  }\n\n  public get bufnr(): number {\n    return this.doc.bufnr\n  }\n\n  public get uri(): string {\n    return this.doc.uri\n  }\n\n  public onChange(e: DidChangeTextDocumentParams): void {\n    let changes = e.contentChanges\n    if (changes.length > 0) {\n      let edit = TextEdit.replace(changes[0].range, changes[0].text)\n      for (let [collection, diagnostics] of this.diagnosticsMap.entries()) {\n        let arr = adjustDiagnostics(diagnostics, edit)\n        this.diagnosticsMap.set(collection, arr)\n      }\n      this._dirties = new Set(this.diagnosticsMap.keys())\n    }\n    if (!this.config.autoRefresh) return\n    this.refreshHighlights()\n  }\n\n  public onTextChange(): void {\n    this._dirties = new Set(this.diagnosticsMap.keys())\n    this.refreshHighlights.clear()\n  }\n\n  private get displayByAle(): boolean {\n    return this._config.displayByAle\n  }\n\n  private get displayByVimDiagnostic(): boolean {\n    return this.nvim.isVim === false && this._config.displayByVimDiagnostic\n  }\n\n  public clearHighlight(collection: string): void {\n    this.buffer.clearNamespace(NAMESPACE + collection)\n  }\n\n  private clearSigns(collection: string): void {\n    this.buffer.unplaceSign({ group: signGroup + collection })\n  }\n\n  private get diagnostics(): Diagnostic[] {\n    let res: Diagnostic[] = []\n    for (let diags of this.diagnosticsMap.values()) {\n      res.push(...diags)\n    }\n    return res\n  }\n\n  private get buffer(): Buffer {\n    return this.nvim.createBuffer(this.bufnr)\n  }\n\n  private refreshAle(collection: string, diagnostics: ReadonlyArray<Diagnostic>): void {\n    let aleItems = diagnostics.map(o => {\n      let range = o.range\n      return {\n        text: o.message,\n        code: o.code,\n        lnum: range.start.line + 1,\n        col: range.start.character + 1,\n        end_lnum: range.end.line + 1,\n        end_col: range.end.character,\n        type: getSeverityType(o.severity)\n      }\n    })\n    this.nvim.call(aleMethod, [this.bufnr, 'coc' + collection, aleItems], true)\n  }\n\n  /**\n   * Update diagnostics when diagnostics change on collection.\n   * @param {string} collection\n   * @param {Diagnostic[]} diagnostics\n   */\n  public async update(collection: string, diagnostics: ReadonlyArray<Diagnostic>): Promise<void> {\n    let { diagnosticsMap } = this\n    let curr = diagnosticsMap.get(collection)\n    if (!this._dirties.has(collection) && isFalsyOrEmpty(diagnostics) && isFalsyOrEmpty(curr)) return\n    diagnosticsMap.set(collection, diagnostics)\n    void this.checkFloat()\n    if (!this.config.enable || (diagnostics.length > 0 && this._refreshing)) {\n      this._dirties.add(collection)\n      return\n    }\n    let info = await this.getDiagnosticInfo(diagnostics.length === 0)\n    // avoid highlights on invalid state or buffer hidden.\n    if (!info || info.winid == -1) {\n      this._dirties.add(collection)\n      return\n    }\n    let map: Map<string, ReadonlyArray<Diagnostic>> = new Map()\n    map.set(collection, diagnostics)\n    this.refresh(map, info)\n  }\n\n  private async checkFloat(): Promise<void> {\n    if (workspace.bufnr != this.bufnr || !floatFactory) return\n    let pos = await window.getCursorPosition()\n    let diagnostics = this.getDiagnosticsAtPosition(pos)\n    if (diagnostics.length == 0) {\n      floatFactory.close()\n    }\n  }\n\n  /**\n   * Reset all diagnostics of current buffer\n   */\n  public async reset(diagnostics: { [collection: string]: Diagnostic[] }): Promise<void> {\n    this.refreshHighlights.clear()\n    let { diagnosticsMap } = this\n    for (let key of diagnosticsMap.keys()) {\n      // make sure clear collection when it's empty.\n      if (isFalsyOrEmpty(diagnostics[key])) diagnostics[key] = []\n    }\n    for (let [key, value] of Object.entries(diagnostics)) {\n      diagnosticsMap.set(key, value)\n    }\n    this._dirties = new Set(diagnosticsMap.keys())\n    await this._refresh(false)\n  }\n\n  public async onCursorHold(lnum: number, col: number): Promise<void> {\n    if (this.config.enableMessage !== 'always') return\n    let pos = this.doc.getPosition(lnum, col)\n    await this.echoMessage(true, pos)\n  }\n\n  /**\n   * Echo diagnostic message under cursor.\n   */\n  public async echoMessage(truncate = false, position: Position, target?: string): Promise<boolean> {\n    const config = this.config\n    if (!config.enable || config.enableMessage === 'never' || this.displayByAle || this.displayByVimDiagnostic) return false\n    if (!target) target = config.messageTarget\n    let useFloat = target == 'float'\n    let diagnostics = this.getDiagnosticsAtPosition(position)\n    if (config.messageLevel) {\n      diagnostics = diagnostics.filter(diagnostic => {\n        return diagnostic.severity && diagnostic.severity <= config.messageLevel\n      })\n    }\n    if (useFloat) {\n      await this.showFloat(diagnostics)\n    } else {\n      const lines = []\n      diagnostics.forEach(diagnostic => {\n        lines.push(formatDiagnostic(config.format, diagnostic))\n      })\n      if (lines.length) {\n        await this.nvim.command('echo \"\"')\n        await window.echoLines(lines, truncate)\n      }\n    }\n    return true\n  }\n\n  public async showVirtualTextCurrentLine(lnum: number): Promise<boolean> {\n    let { config } = this\n    if (!config.virtualTextCurrentLineOnly || (events.insertMode && !config.refreshOnInsertMode)) return false\n    let enabled = await this.isEnabled()\n    if (!enabled) return false\n    this.showVirtualText(lnum)\n    return true\n  }\n\n  public async showFloat(diagnostics: DiagnosticWithFileType[], target = 'float'): Promise<boolean> {\n    if (target !== 'float') return false\n    if (!floatFactory) floatFactory = window.createFloatFactory({ modes: ['n'], autoHide: true })\n    if (diagnostics.length == 0) {\n      floatFactory.close()\n      return false\n    }\n    if (events.insertMode) return false\n    let config = this.config\n    let ft = ''\n    let docs: Documentation[] = []\n    if (Object.keys(config.filetypeMap).length > 0) {\n      let filetype = this.doc.filetype\n      const defaultFiletype = config.filetypeMap['default'] || ''\n      ft = config.filetypeMap[filetype] || (defaultFiletype == 'bufferType' ? filetype : defaultFiletype)\n    }\n    diagnostics.forEach(diagnostic => {\n      let filetype = 'Error'\n      if (diagnostic.filetype) {\n        filetype = diagnostic.filetype\n      } else if (ft === '') {\n        switch (diagnostic.severity) {\n          case DiagnosticSeverity.Hint:\n            filetype = 'Hint'\n            break\n          case DiagnosticSeverity.Warning:\n            filetype = 'Warning'\n            break\n          case DiagnosticSeverity.Information:\n            filetype = 'Info'\n            break\n        }\n      } else {\n        filetype = ft\n      }\n      let msg = diagnostic.message\n      let link = diagnostic.codeDescription?.href ?? ''\n      if (config.showRelatedInformation && diagnostic.relatedInformation?.length) {\n        msg = `${diagnostic.message}\\n\\nRelated information:\\n`\n        for (const info of diagnostic.relatedInformation) {\n          const fsPath = URI.parse(info.location.uri).fsPath\n          const basename = path.basename(fsPath)\n          const line = info.location.range.start.line + 1\n          const column = info.location.range.start.character + 1\n          msg = `${msg}\\n  * ${basename}#${line},${column}: ${info.message}`\n        }\n        msg = msg + \"\\n\\n\"\n      }\n      docs.push({\n        filetype, content: formatDiagnostic(config.format, {\n          ...diagnostic,\n          message: msg\n        })\n      })\n      if (link) docs.push({ filetype: 'txt', content: link })\n    })\n    await floatFactory.show(docs, this.config.floatConfig)\n    return true\n  }\n\n  /**\n   * Get buffer info needed for refresh.\n   */\n  private async getDiagnosticInfo(force?: boolean): Promise<DiagnosticInfo | undefined> {\n    let { refreshOnInsertMode } = this._config\n    let { nvim, bufnr } = this\n    let checkInsert = !refreshOnInsertMode\n    if (force) {\n      checkInsert = false\n    } else {\n      let disabledByInsert = events.insertMode && !refreshOnInsertMode\n      if (disabledByInsert) return undefined\n    }\n    return await nvim.call('coc#util#diagnostic_info', [bufnr, checkInsert]).catch(onUnexpectedError) as DiagnosticInfo | undefined\n  }\n\n  /**\n   * Refresh changed diagnostics to UI.\n   */\n  private refresh(diagnosticsMap: Map<string, ReadonlyArray<Diagnostic>>, info: DiagnosticInfo): void {\n    let { nvim, displayByAle, displayByVimDiagnostic } = this\n    for (let collection of diagnosticsMap.keys()) {\n      this._dirties.delete(collection)\n    }\n    if (displayByAle) {\n      nvim.pauseNotification()\n      for (let [collection, diagnostics] of diagnosticsMap.entries()) {\n        this.refreshAle(collection, diagnostics)\n      }\n      nvim.resumeNotification(true, true)\n    } else if (displayByVimDiagnostic) {\n      nvim.pauseNotification()\n      this.setDiagnosticInfo(true)\n      nvim.resumeNotification(true, true)\n    } else {\n      let emptyCollections: string[] = []\n      nvim.pauseNotification()\n      for (let [collection, diagnostics] of diagnosticsMap.entries()) {\n        if (diagnostics.length == 0) emptyCollections.push(collection)\n        this.addSigns(collection, diagnostics)\n        this.updateHighlights(collection, diagnostics)\n      }\n      this.showVirtualText(info.lnum)\n      this.updateLocationList(info.winid, info.locationlist)\n      this.setDiagnosticInfo()\n      nvim.resumeNotification(true, true)\n      // cleanup unnecessary collections\n      emptyCollections.forEach(name => {\n        this.diagnosticsMap.delete(name)\n      })\n    }\n    this._onDidRefresh.fire(this.diagnostics)\n  }\n\n  public updateLocationList(winid: number, title: string): void {\n    if (!this._config.locationlistUpdate || winid == -1 || title !== 'Diagnostics of coc') return\n    let items = this.toLocationListItems(this.diagnostics)\n    this.nvim.call('coc#ui#setloclist', [winid, items, 'r', 'Diagnostics of coc'], true)\n  }\n\n  public toLocationListItems(diagnostics: Diagnostic[]): LocationListItem[] {\n    let { locationlistLevel } = this._config\n    let items: LocationListItem[] = []\n    let lines = this.doc.textDocument.lines\n    diagnostics.sort(sortDiagnostics)\n    for (let diagnostic of diagnostics) {\n      if (locationlistLevel && diagnostic.severity && diagnostic.severity > locationlistLevel) continue\n      items.push(getLocationListItem(this.bufnr, diagnostic, lines))\n    }\n    return items\n  }\n\n  public addSigns(collection: string, diagnostics: ReadonlyArray<Diagnostic>): void {\n    let { enableSign, signLevel } = this._config\n    if (!enableSign) return\n    let group = signGroup + collection\n    let signs: SignItem[] = []\n    // this.buffer.unplaceSign({ group })\n    let signsMap: Map<number, DiagnosticSeverity[]> = new Map()\n    for (let diagnostic of diagnostics) {\n      let { range, severity } = diagnostic\n      if (!severity || (signLevel && severity > signLevel)) {\n        continue\n      }\n      let line = range.start.line\n      let exists = signsMap.get(line) || []\n      if (exists.includes(severity)) {\n        continue\n      }\n      exists.push(severity)\n      signsMap.set(line, exists)\n      let priority = this._config.signPriority + 4 - severity\n      signs.push({ name: getNameFromSeverity(severity), lnum: line + 1, priority })\n    }\n    this.nvim.call('coc#ui#update_signs', [this.bufnr, group, signs], true)\n  }\n\n  public setDiagnosticInfo(full = false): void {\n    let lnums = [0, 0, 0, 0]\n    let info = { error: 0, warning: 0, information: 0, hint: 0, lnums }\n    let items: DiagnosticItem[] = []\n    for (let diagnostics of this.diagnosticsMap.values()) {\n      for (let diagnostic of diagnostics) {\n        let lnum = diagnostic.range.start.line + 1\n        switch (diagnostic.severity) {\n          case DiagnosticSeverity.Warning:\n            info.warning = info.warning + 1\n            lnums[1] = lnums[1] ? Math.min(lnums[1], lnum) : lnum\n            break\n          case DiagnosticSeverity.Information:\n            info.information = info.information + 1\n            lnums[2] = lnums[2] ? Math.min(lnums[2], lnum) : lnum\n            break\n          case DiagnosticSeverity.Hint:\n            info.hint = info.hint + 1\n            lnums[3] = lnums[3] ? Math.min(lnums[3], lnum) : lnum\n            break\n          default:\n            lnums[0] = lnums[0] ? Math.min(lnums[0], lnum) : lnum\n            info.error = info.error + 1\n        }\n\n        if (full) {\n          let { start, end } = diagnostic.range\n          items.push({\n            file: URI.parse(this.doc.uri).fsPath,\n            lnum: start.line + 1,\n            end_lnum: end.line + 1,\n            col: start.character + 1,\n            end_col: end.character + 1,\n            code: diagnostic.code,\n            source: diagnostic.source,\n            message: diagnostic.message,\n            severity: getSeverityName(diagnostic.severity),\n            level: diagnostic.severity ?? 0,\n            location: Location.create(this.doc.uri, diagnostic.range)\n          })\n        }\n      }\n    }\n    let buf = this.nvim.createBuffer(this.bufnr)\n    buf.setVar('coc_diagnostic_info', info, true)\n    buf.setVar('coc_diagnostic_map', items, true)\n    this.nvim.call('coc#util#do_autocmd', ['CocDiagnosticChange'], true)\n  }\n\n  public showVirtualText(lnum: number): void {\n    let { _config: config } = this\n    let { virtualText, virtualTextLevel } = config\n    if (!virtualText || lnum < 0) return\n    let { virtualTextPrefix, virtualTextLimitInOneLine, virtualTextCurrentLineOnly } = this._config\n    let { diagnostics, buffer } = this\n    if (virtualTextCurrentLineOnly) {\n      diagnostics = diagnostics.filter(d => {\n        let { start, end } = d.range\n        return start.line <= lnum - 1 && end.line >= lnum - 1\n      })\n    }\n    diagnostics.sort(sortDiagnostics)\n    buffer.clearNamespace(virtualTextSrcId)\n    let map: Map<number, [string, string][]> = new Map()\n    let opts: VirtualTextOption = {\n      text_align: config.virtualTextAlign,\n      virt_text_win_col: config.virtualTextWinCol\n    }\n    for (let i = diagnostics.length - 1; i >= 0; i--) {\n      let diagnostic = diagnostics[i]\n      if (virtualTextLevel && diagnostic.severity && diagnostic.severity > virtualTextLevel) {\n        continue\n      }\n      let { line } = diagnostic.range.start\n      let highlight = getNameFromSeverity(diagnostic.severity) + 'VirtualText'\n      let msg = diagnostic.message.split(/\\n/)\n        .map((l: string) => l.trim())\n        .filter((l: string) => l.length > 0)\n        .slice(0, this._config.virtualTextLines)\n        .join(this._config.virtualTextLineSeparator)\n      let arr = map.get(line) ?? []\n      const formattedDiagnostic = formatDiagnostic(this._config.virtualTextFormat, {\n        ...diagnostic,\n        message: msg\n      })\n      arr.unshift([virtualTextPrefix + stripAnsiColoring(formattedDiagnostic), highlight])\n      map.set(line, arr)\n    }\n    for (let [line, blocks] of map.entries()) {\n      buffer.setVirtualText(virtualTextSrcId, line, blocks.slice(0, virtualTextLimitInOneLine), opts)\n    }\n  }\n\n  public updateHighlights(collection: string, diagnostics: ReadonlyArray<Diagnostic>): void {\n    if (!diagnostics.length) {\n      this.clearHighlight(collection)\n    } else {\n      let items = this.getHighlightItems(diagnostics)\n      let priority = this._config.highlightPriority\n      this.buffer.updateHighlights(NAMESPACE + collection, items, { priority })\n    }\n  }\n\n  /**\n   * Refresh all diagnostics\n   */\n  private async _refresh(dirtyOnly: boolean): Promise<void> {\n    let info = await this.getDiagnosticInfo(!dirtyOnly)\n    if (!info || info.winid == -1 || !this.config.enable) return\n    let { _dirties } = this\n    if (dirtyOnly) {\n      let map: Map<string, ReadonlyArray<Diagnostic>> = new Map()\n      for (let [key, diagnostics] of this.diagnosticsMap.entries()) {\n        if (!_dirties.has(key)) continue\n        map.set(key, diagnostics)\n      }\n      this.refresh(map, info)\n    } else {\n      this.refresh(this.diagnosticsMap, info)\n    }\n  }\n\n  public getHighlightItems(diagnostics: ReadonlyArray<Diagnostic>): HighlightItem[] {\n    let res: HighlightItem[] = []\n    for (let i = 0; i < Math.min(this._config.highlightLimit, diagnostics.length); i++) {\n      let diagnostic = diagnostics[i]\n      let hlGroups = getHighlightGroup(diagnostic)\n      for (const hlGroup of hlGroups) {\n        this.doc.addHighlights(res, hlGroup, diagnostic.range)\n      }\n    }\n    // needed for iteration performance and since diagnostic highlight may cross lines.\n    res.sort((a, b) => {\n      if (a.lnum != b.lnum) return a.lnum - b.lnum\n      if (a.colStart != b.colStart) return a.colStart - b.colStart\n      return hlGroups.indexOf(b.hlGroup) - hlGroups.indexOf(a.hlGroup)\n    })\n    return res\n  }\n\n  /**\n   * Clear all diagnostics from UI.\n   */\n  public clear(): void {\n    let { nvim } = this\n    let collections = Array.from(this.diagnosticsMap.keys())\n    this.refreshHighlights.clear()\n    this._dirties.clear()\n    if (this.displayByAle) {\n      for (let collection of collections) {\n        this.nvim.call(aleMethod, [this.bufnr, collection, []], true)\n      }\n    } else {\n      nvim.pauseNotification()\n      this.buffer.deleteVar('coc_diagnostic_info')\n      this.buffer.deleteVar('coc_diagnostic_map')\n      for (let collection of collections) {\n        this.clearHighlight(collection)\n        this.clearSigns(collection)\n      }\n      if (this._config.virtualText) {\n        this.buffer.clearNamespace(virtualTextSrcId)\n      }\n      nvim.resumeNotification(true, true)\n    }\n  }\n\n  /**\n   * Get diagnostics at cursor position.\n   */\n  public getDiagnosticsAt(pos: Position, checkCurrentLine: boolean): Diagnostic[] {\n    let diagnostics: Diagnostic[] = []\n    for (let diags of this.diagnosticsMap.values()) {\n      if (checkCurrentLine) {\n        diagnostics.push(...diags.filter(o => lineInRange(pos.line, o.range)))\n      } else {\n        diagnostics.push(...diags.filter(o => positionInRange(pos, o.range) == 0))\n      }\n    }\n    diagnostics.sort(sortDiagnostics)\n    return diagnostics\n  }\n\n  public getDiagnosticsAtPosition(pos: Position): Diagnostic[] {\n    let { config, doc } = this\n    let res = this.getDiagnosticsAt(pos, config.checkCurrentLine)\n    if (config.checkCurrentLine || res.length) return res\n    // check next character when cursor at end of line.\n    let total = doc.getline(pos.line).length\n    if (pos.character + 1 == total) {\n      res = this.getDiagnosticsAt(Position.create(pos.line, pos.character + 1), false)\n      if (res.length) return res\n    }\n    // check next line when cursor at the beginning of last line.\n    if (pos.line === doc.lineCount - 1 && pos.character == 0) {\n      pos = Position.create(pos.line + 1, 0)\n      res = this.getDiagnosticsAt(pos, true)\n    }\n    return res\n  }\n\n  public async isEnabled(): Promise<boolean> {\n    if (this._disposed || !this.config.enable) return false\n    let buf = this.nvim.createBuffer(this.bufnr)\n    let res = await buf.getVar('coc_diagnostic_disable')\n    return res != 1\n  }\n\n  public dispose(): void {\n    this.clear()\n    this.diagnosticsMap.clear()\n    this._onDidRefresh.dispose()\n    this._disposed = true\n  }\n}\n"
  },
  {
    "path": "src/diagnostic/collection.ts",
    "content": "'use strict'\nimport { Diagnostic, DiagnosticSeverity, DiagnosticTag, Range } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport { intersect } from '../util/array'\nimport { Emitter, Event } from '../util/protocol'\nimport workspace from '../workspace'\nconst HintTags = [DiagnosticTag.Deprecated, DiagnosticTag.Unnecessary]\n\nexport default class DiagnosticCollection {\n  private diagnosticsMap: Map<string, Diagnostic[]> = new Map()\n  private _onDidDiagnosticsChange = new Emitter<string>()\n  public readonly onDidDiagnosticsChange: Event<string> = this._onDidDiagnosticsChange.event\n\n  constructor(\n    public readonly name: string,\n    private onDispose?: () => void) {\n  }\n\n  public set(uri: string, diagnostics: Diagnostic[] | undefined): void\n  public set(entries: [string, Diagnostic[] | undefined][]): void\n  public set(entries: [string, Diagnostic[] | undefined][] | string, diagnostics?: Diagnostic[]): void {\n    let diagnosticsPerFile: Map<string, Diagnostic[]> = new Map()\n    if (!Array.isArray(entries)) {\n      let doc = workspace.getDocument(entries)\n      let uri = doc ? doc.uri : entries\n      diagnosticsPerFile.set(uri, diagnostics || [])\n    } else {\n      for (let item of entries) {\n        let [uri, diagnostics] = item\n        let doc = workspace.getDocument(uri)\n        uri = doc ? doc.uri : uri\n        if (diagnostics == null) {\n          // clear previous diagnostics if entry contains null\n          diagnostics = []\n        } else {\n          diagnostics = (diagnosticsPerFile.get(uri) || []).concat(diagnostics)\n        }\n        diagnosticsPerFile.set(uri, diagnostics)\n      }\n    }\n    for (let item of diagnosticsPerFile) {\n      let [uri, diagnostics] = item\n      uri = URI.parse(uri).toString()\n      diagnostics.forEach(o => {\n        // should be message for the file, but we need range\n        o.range = o.range ?? Range.create(0, 0, 0, 0)\n        o.message = o.message ?? ''\n        o.source = o.source || this.name\n        if (!o.severity && Array.isArray(o.tags) && intersect(o.tags, HintTags)) {\n          o.severity = DiagnosticSeverity.Hint\n        }\n      })\n      this.diagnosticsMap.set(uri, diagnostics)\n      this._onDidDiagnosticsChange.fire(uri)\n    }\n  }\n\n  public delete(uri: string): void {\n    this.diagnosticsMap.delete(uri)\n    this._onDidDiagnosticsChange.fire(uri)\n  }\n\n  public clear(): void {\n    let uris = Array.from(this.diagnosticsMap.keys())\n    uris = uris.filter(uri => this.diagnosticsMap.get(uri).length > 0)\n    this.diagnosticsMap.clear()\n    for (let uri of uris) {\n      this._onDidDiagnosticsChange.fire(uri)\n    }\n  }\n\n  public forEach(callback: (uri: string, diagnostics: Diagnostic[], collection: DiagnosticCollection) => any, thisArg?: any): void {\n    for (let uri of this.diagnosticsMap.keys()) {\n      let diagnostics = this.diagnosticsMap.get(uri)\n      callback.call(thisArg, uri, diagnostics, this)\n    }\n  }\n\n  public entries(): IterableIterator<[string, Diagnostic[]]> {\n    return this.diagnosticsMap.entries()\n  }\n\n  public get(uri: string): Diagnostic[] {\n    let arr = this.diagnosticsMap.get(uri)\n    return arr == null ? [] : arr.slice()\n  }\n\n  public has(uri: string): boolean {\n    return this.diagnosticsMap.has(uri)\n  }\n\n  public dispose(): void {\n    this.clear()\n    if (this.onDispose) this.onDispose()\n    this._onDidDiagnosticsChange.dispose()\n  }\n}\n"
  },
  {
    "path": "src/diagnostic/manager.ts",
    "content": "'use strict'\nimport type { Neovim } from '@chemzqm/neovim'\nimport { Diagnostic, DiagnosticSeverity, DiagnosticTag, Location, Position, Range, TextDocumentIdentifier } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport commands from '../commands'\nimport events from '../events'\nimport BufferSync from '../model/bufferSync'\nimport { disposeAll } from '../util'\nimport { isFalsyOrEmpty } from '../util/array'\nimport { isVim } from '../util/constants'\nimport { readFileLines } from '../util/fs'\nimport { comparePosition, rangeIntersect } from '../util/position'\nimport { Disposable, Emitter, Event } from '../util/protocol'\nimport { byteIndex } from '../util/string'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { DiagnosticBuffer } from './buffer'\nimport DiagnosticCollection from './collection'\nimport { getSeverityName, severityLevel } from './util'\n\nexport interface DiagnosticEventParams {\n  bufnr: number\n  uri: string\n  diagnostics: ReadonlyArray<Diagnostic>\n}\n\ninterface DiagnosticSignConfig {\n  messageDelay?: number\n  errorSign?: string\n  warningSign?: string\n  infoSing?: string\n  hintSign?: string\n  enableHighlightLineNumber?: boolean\n}\n\nexport interface DiagnosticItem {\n  file: string\n  bufnr?: number\n  lnum: number\n  end_lnum: number\n  col: number\n  end_col: number\n  source: string\n  code: string | number\n  message: string\n  severity: string\n  level: number\n  location: Location\n}\n\ninterface PrepareResult {\n  item: DiagnosticBuffer\n  wrapscan: boolean\n  ranges: ReadonlyArray<Range>\n  curpos: Position\n}\n\nclass DiagnosticManager implements Disposable {\n  private readonly _onDidRefresh = new Emitter<DiagnosticEventParams>()\n  public readonly onDidRefresh: Event<DiagnosticEventParams> = this._onDidRefresh.event\n  private enabled = true\n  private buffers: BufferSync<DiagnosticBuffer> | undefined\n  private collections: DiagnosticCollection[] = []\n  private disposables: Disposable[] = []\n  private messageTimer: NodeJS.Timeout\n\n  public init(): void {\n    commands.register({\n      id: 'workspace.diagnosticRelated',\n      execute: () => this.jumpRelated()\n    }, false, 'jump to related locations of current diagnostic.')\n    this.defineSigns(workspace.initialConfiguration.get<DiagnosticSignConfig>('diagnostic'))\n    let globalValue = workspace.initialConfiguration.inspect('diagnostic.enable').globalValue\n    this.enabled = globalValue !== false\n    this.buffers = workspace.registerBufferSync(doc => {\n      let buf = new DiagnosticBuffer(this.nvim, doc)\n      buf.onDidRefresh(diagnostics => {\n        this._onDidRefresh.fire({ diagnostics, uri: buf.uri, bufnr: buf.bufnr })\n      })\n      let diagnostics = this.getDiagnostics(buf)\n      // ignore empty diagnostics on first time.\n      if (Object.keys(diagnostics).length > 0 && buf.config.autoRefresh) {\n        void buf.reset(diagnostics)\n      }\n      return buf\n    })\n    workspace.onDidChangeConfiguration(e => {\n      if (this.buffers && e.affectsConfiguration('diagnostic')) {\n        for (let item of this.buffers.items) {\n          item.loadConfiguration()\n        }\n      }\n    }, null, this.disposables)\n    let config = workspace.initialConfiguration.get<any>('diagnostic')\n    events.on('CursorMoved', (bufnr, cursor) => {\n      if (this.messageTimer) clearTimeout(this.messageTimer)\n      this.messageTimer = setTimeout(() => {\n        let buf = this.buffers.getItem(bufnr)\n        if (buf == null || buf.dirty) return\n        void Promise.allSettled([\n          buf.onCursorHold(cursor[0], cursor[1]),\n          buf.showVirtualTextCurrentLine(cursor[0])])\n      }, config.messageDelay)\n    }, null, this.disposables)\n    events.on(['InsertEnter', 'BufEnter'], () => {\n      clearTimeout(this.messageTimer)\n    }, null, this.disposables)\n    events.on('InsertLeave', bufnr => {\n      let buf = this.buffers.getItem(bufnr)\n      if (!buf || buf.config.refreshOnInsertMode) return\n      for (let buf of this.buffers.items) {\n        buf.refreshHighlights()\n      }\n    }, null, this.disposables)\n    events.on('BufWinEnter', (bufnr: number) => {\n      let buf = this.buffers.getItem(bufnr)\n      if (buf) buf.refreshHighlights()\n    }, null, this.disposables)\n    this.checkConfigurationErrors()\n    workspace.configurations.onError(ev => {\n      const collection = this.create('config')\n      collection.set(ev.uri, ev.diagnostics)\n    }, null, this.disposables)\n  }\n\n  public checkConfigurationErrors(): void {\n    const errors = workspace.configurations.errors\n    if (!isFalsyOrEmpty(errors)) {\n      const collection = this.create('config')\n      for (let [uri, diagnostics] of errors.entries()) {\n        let fsPath = URI.parse(uri).fsPath\n        void window.showErrorMessage(`Error detected for config file ${fsPath}, please check diagnostics list.`)\n        collection.set(uri, diagnostics)\n      }\n    }\n  }\n\n  public defineSigns(config: DiagnosticSignConfig): void {\n    let { nvim } = this\n    nvim.pauseNotification()\n    for (let kind of ['Error', 'Warning', 'Info', 'Hint']) {\n      let cmd = `sign define Coc${kind} linehl=Coc${kind}Line`\n      let signText = config[kind.toLowerCase() + 'Sign']\n      if (signText) cmd += ` texthl=Coc${kind}Sign text=${signText}`\n      if (!isVim && config.enableHighlightLineNumber) cmd += ` numhl=Coc${kind}Sign`\n      nvim.command(cmd, true)\n    }\n    nvim.resumeNotification(false, true)\n  }\n\n  public getItem(bufnr: number): DiagnosticBuffer | undefined {\n    return this.buffers.getItem(bufnr)\n  }\n\n  /**\n   * Fill location list with diagnostics\n   */\n  public async setLocationlist(bufnr: number): Promise<void> {\n    let doc = workspace.getAttachedDocument(bufnr)\n    let buf = this.getItem(doc.bufnr)\n    let diagnostics: Diagnostic[] = []\n    for (let diags of Object.values(this.getDiagnostics(buf))) {\n      diagnostics.push(...diags)\n    }\n    let items = buf.toLocationListItems(diagnostics)\n    await this.nvim.call('coc#ui#setloclist', [0, items, ' ', 'Diagnostics of coc'])\n  }\n\n  /**\n   * Create collection by name\n   */\n  public create(name: string): DiagnosticCollection {\n    let collection = this.getCollectionByName(name)\n    if (collection) return collection\n    collection = new DiagnosticCollection(name, () => {\n      let idx = this.collections.findIndex(o => o == collection)\n      if (idx !== -1) this.collections.splice(idx, 1)\n    })\n    this.collections.push(collection)\n    collection.onDidDiagnosticsChange(uri => {\n      let buf = this.buffers?.getItem(uri)\n      if (buf && buf.config.autoRefresh) void buf.update(name, this.getDiagnosticsByCollection(buf, collection))\n    })\n    return collection\n  }\n\n  /**\n   * Get diagnostics ranges from document\n   */\n  public getSortedRanges(uri: string, minLevel: number | undefined, severity?: string): Range[] {\n    let collections = this.getCollections(uri)\n    let res: Range[] = []\n    let level = severity ? severityLevel(severity) : 0\n    for (let collection of collections) {\n      let diagnostics = collection.get(uri)\n      if (level) {\n        diagnostics = diagnostics.filter(o => o.severity == level)\n      } else {\n        if (minLevel && minLevel < DiagnosticSeverity.Hint) {\n          diagnostics = diagnostics.filter(o => {\n            return o.severity && o.severity > minLevel ? false : true\n          })\n        }\n      }\n      let ranges = diagnostics.map(o => o.range)\n      res.push(...ranges)\n    }\n    res.sort((a, b) => {\n      if (a.start.line != b.start.line) {\n        return a.start.line - b.start.line\n      }\n      return a.start.character - b.start.character\n    })\n    return res\n  }\n\n  /**\n   * Get readonly diagnostics for a buffer\n   */\n  public getDiagnostics(buf: DiagnosticBuffer): { [collection: string]: Diagnostic[] } {\n    let res: { [collection: string]: Diagnostic[] } = {}\n    for (let collection of this.collections) {\n      if (!collection.has(buf.uri)) continue\n      res[collection.name] = this.getDiagnosticsByCollection(buf, collection)\n    }\n    return res\n  }\n\n  /**\n   * Get filtered diagnostics by collection.\n   */\n  public getDiagnosticsByCollection(buf: DiagnosticBuffer, collection: DiagnosticCollection): Diagnostic[] {\n    // let config = this.buffers.getItem(uri)\n    let { level, showUnused, showDeprecated } = buf.config\n    let items = collection.get(buf.uri) ?? []\n    if (items.length) {\n      items = items.filter(d => {\n        if (level && d.severity && d.severity > level) {\n          return false\n        }\n        if (!showUnused && d.tags?.includes(DiagnosticTag.Unnecessary)) {\n          return false\n        }\n        if (!showDeprecated && d.tags?.includes(DiagnosticTag.Deprecated)) {\n          return false\n        }\n        return true\n      })\n      items.sort((a, b) => {\n        return comparePosition(a.range.start, b.range.start)\n      })\n    }\n    return items\n  }\n\n  public getDiagnosticsInRange(document: TextDocumentIdentifier, range: Range): Diagnostic[] {\n    let res: Diagnostic[] = []\n    for (let collection of this.collections) {\n      for (let item of collection.get(document.uri) ?? []) {\n        if (rangeIntersect(item.range, range)) {\n          res.push(item)\n        }\n      }\n    }\n    return res\n  }\n\n  /**\n   * Show diagnostics under cursor in preview window\n   */\n  public async preview(): Promise<void> {\n    let diagnostics = await this.getCurrentDiagnostics()\n    if (diagnostics.length == 0) {\n      this.nvim.command('pclose', true)\n      return\n    }\n    let lines: string[] = []\n    for (let diagnostic of diagnostics) {\n      let { source, code, severity, message } = diagnostic\n      let s = getSeverityName(severity)[0]\n      lines.push(`[${source}${code ? ' ' + code : ''}] [${s}]`)\n      lines.push(...message.split(/\\r?\\n/))\n      lines.push('')\n    }\n    this.nvim.call('coc#ui#preview_info', [lines, 'txt'], true)\n  }\n\n  private async prepareJump(severity?: string): Promise<PrepareResult | undefined> {\n    let bufnr = await this.nvim.call('bufnr', ['%']) as number\n    let item = this.buffers.getItem(bufnr)\n    if (!item) return\n    let ranges = this.getSortedRanges(item.uri, item.config.level, severity)\n    if (isFalsyOrEmpty(ranges)) return\n    let curpos = await window.getCursorPosition()\n    let wrapscan = await this.nvim.getOption('wrapscan')\n    return {\n      item,\n      curpos,\n      wrapscan: wrapscan != 0,\n      ranges\n    }\n  }\n\n  /**\n   * Jump to previous diagnostic position\n   */\n  public async jumpPrevious(severity?: string): Promise<void> {\n    let result = await this.prepareJump(severity)\n    if (!result) return\n    let { curpos, item, wrapscan, ranges } = result\n    let pos: Position\n    for (let i = ranges.length - 1; i >= 0; i--) {\n      let end = ranges[i].end\n      if (comparePosition(end, curpos) < 0) {\n        pos = ranges[i].start\n        break\n      }\n    }\n    if (!pos && wrapscan) pos = ranges[ranges.length - 1].start\n    if (pos) {\n      await window.moveTo(pos)\n      await item.echoMessage(false, pos)\n    } else {\n      void window.showWarningMessage(`No more diagnostic before cursor position`)\n    }\n  }\n\n  /**\n   * Jump to next diagnostic position\n   */\n  public async jumpNext(severity?: string): Promise<void> {\n    let result = await this.prepareJump(severity)\n    if (!result) return\n    let { curpos, item, wrapscan, ranges } = result\n    let pos: Position\n    for (let i = 0; i <= ranges.length - 1; i++) {\n      let start = ranges[i].start\n      if (comparePosition(start, curpos) > 0) {\n        // The position could be invalid (ex: exceed end of line)\n        let arr = await this.nvim.call('coc#util#valid_position', [start.line, start.character])\n        if ((arr[0] != start.line || arr[1] != start.character)\n          && comparePosition(Position.create(arr[0], arr[1]), curpos) <= 0) {\n          continue\n        }\n        pos = Position.create(arr[0], arr[1])\n        break\n      }\n    }\n    if (!pos && wrapscan) pos = ranges[0].start\n    if (pos) {\n      await window.moveTo(pos)\n      await item.echoMessage(false, pos)\n    } else {\n      void window.showWarningMessage(`No more diagnostic after cursor position`)\n    }\n  }\n\n  /**\n   * Get all sorted diagnostics\n   */\n  public async getDiagnosticList(): Promise<DiagnosticItem[]> {\n    let res: DiagnosticItem[] = []\n    let config = workspace.getConfiguration('diagnostic')\n    let level = severityLevel(config.get<string>('level', 'hint'))\n    for (let collection of this.collections) {\n      for (let [uri, diagnostics] of collection.entries()) {\n        if (diagnostics.length == 0) continue\n        let u = URI.parse(uri)\n        let doc = workspace.getDocument(uri)\n        let lines = doc && doc.attached ? doc.textDocument.lines : undefined\n        if (!lines && u.scheme === 'file') {\n          try {\n            const max = diagnostics.reduce((p, c) => {\n              return Math.max(c.range.end.line, p)\n            }, 0)\n            lines = await readFileLines(u.fsPath, 0, max)\n          } catch (e) {}\n        }\n        for (let diagnostic of diagnostics) {\n          if (diagnostic.severity && diagnostic.severity > level) continue\n          let { start, end } = diagnostic.range\n          let o: DiagnosticItem = {\n            file: u.fsPath,\n            bufnr: doc ? doc.bufnr : undefined,\n            lnum: start.line + 1,\n            end_lnum: end.line + 1,\n            col: Array.isArray(lines) ? byteIndex(lines[start.line] ?? '', start.character) + 1 : start.character + 1,\n            end_col: Array.isArray(lines) ? byteIndex(lines[end.line] ?? '', end.character) + 1 : end.character + 1,\n            code: diagnostic.code,\n            source: diagnostic.source ?? collection.name,\n            message: diagnostic.message,\n            severity: getSeverityName(diagnostic.severity),\n            level: diagnostic.severity ?? 0,\n            location: Location.create(uri, diagnostic.range)\n          }\n          res.push(o)\n        }\n      }\n    }\n    res.sort((a, b) => {\n      if (a.level !== b.level) {\n        return a.level - b.level\n      }\n      if (a.file !== b.file) {\n        return a.file > b.file ? 1 : -1\n      } else {\n        if (a.lnum != b.lnum) {\n          return a.lnum - b.lnum\n        }\n        return a.col - b.col\n      }\n    })\n    return res\n  }\n\n  private async getBufferAndPosition(): Promise<[DiagnosticBuffer, Position] | undefined> {\n    let [bufnr, lnum, col] = await this.nvim.eval(`[bufnr(\"%\"),line('.'),col('.')]`) as [number, number, number]\n    let item = this.buffers.getItem(bufnr)\n    if (!item) return\n    let pos = item.doc.getPosition(lnum, col)\n    return [item, pos]\n  }\n\n  public async getCurrentDiagnostics(): Promise<Diagnostic[] | undefined> {\n    let res = await this.getBufferAndPosition()\n    if (!res) return\n    return res[0].getDiagnosticsAtPosition(res[1])\n  }\n\n  public async echoCurrentMessage(target?: string): Promise<void> {\n    let res = await this.getBufferAndPosition()\n    if (!res) return\n    let [item, position] = res\n    await item.echoMessage(false, position, target)\n  }\n\n  public async jumpRelated(): Promise<void> {\n    let locations = await this.relatedInformation()\n    if (locations.length == 1) {\n      await workspace.jumpTo(locations[0].uri, locations[0].range.start)\n    } else if (locations.length > 1) {\n      await workspace.showLocations(locations)\n    } else {\n      void window.showWarningMessage('No related information found.')\n    }\n  }\n\n  public async relatedInformation(): Promise<Location[]> {\n    let diagnostics = await this.getCurrentDiagnostics()\n    let diagnostic = diagnostics.find(o => o.relatedInformation != null)\n    let locations = diagnostic ? diagnostic.relatedInformation.map(o => o.location) : []\n    return locations\n  }\n\n  public reset(): void {\n    clearTimeout(this.messageTimer)\n    this.buffers.reset()\n    for (let collection of this.collections) {\n      collection.dispose()\n    }\n    this.collections = []\n  }\n\n  public dispose(): void {\n    clearTimeout(this.messageTimer)\n    this.buffers.dispose()\n    for (let collection of this.collections) {\n      collection.dispose()\n    }\n    this.collections = []\n    disposeAll(this.disposables)\n  }\n\n  private get nvim(): Neovim {\n    return workspace.nvim\n  }\n\n  public getCollectionByName(name: string): DiagnosticCollection {\n    return this.collections.find(o => o.name == name)\n  }\n\n  private getCollections(uri: string): DiagnosticCollection[] {\n    return this.collections.filter(c => c.has(uri))\n  }\n\n  public async toggleDiagnostic(enable?: number): Promise<void> {\n    this.enabled = enable == undefined ? !this.enabled : enable != 0\n    await Promise.allSettled(this.buffers.items.map(buf => {\n      return buf.setState(this.enabled)\n    }))\n  }\n\n  public async toggleDiagnosticBuffer(bufnr?: number, enable?: number): Promise<void> {\n    bufnr = bufnr ?? workspace.bufnr\n    let buf = this.buffers.getItem(bufnr)\n    if (buf) {\n      let isEnabled = enable == undefined ? await buf.isEnabled() : enable == 0\n      await this.nvim.call('setbufvar', [bufnr, 'coc_diagnostic_disable', isEnabled ? 1 : 0])\n      await buf.setState(!isEnabled)\n    }\n  }\n\n  /**\n   * Refresh diagnostics by uri or bufnr\n   */\n  public async refreshBuffer(uri: string | number): Promise<boolean> {\n    let buf = this.buffers.getItem(uri)\n    if (!buf) return false\n    await buf.reset(this.getDiagnostics(buf))\n    return true\n  }\n\n  /**\n   * Force diagnostics refresh.\n   */\n  public async refresh(bufnr?: number): Promise<void> {\n    let items: Iterable<DiagnosticBuffer>\n    if (!bufnr) {\n      items = this.buffers.items\n    } else {\n      let item = this.buffers.getItem(bufnr)\n      items = item ? [item] : []\n    }\n    for (let item of items) {\n      await this.refreshBuffer(item.uri)\n    }\n  }\n}\n\nexport default new DiagnosticManager()\n"
  },
  {
    "path": "src/diagnostic/util.ts",
    "content": "'use strict'\nimport type { VirtualTextOption } from '@chemzqm/neovim'\nimport { Diagnostic, DiagnosticSeverity, DiagnosticTag, Range, TextEdit } from 'vscode-languageserver-types'\nimport { FloatConfig } from '../types'\nimport { comparePosition, rangeOverlap } from '../util/position'\nimport { byteIndex } from '../util/string'\nimport { getPosition } from '../util/textedit'\n\nexport interface LocationListItem {\n  bufnr: number\n  lnum: number\n  end_lnum: number\n  col: number\n  end_col: number\n  text: string\n  type: string\n}\n\nexport enum DiagnosticHighlight {\n  Error = 'CocErrorHighlight',\n  Warning = 'CocWarningHighlight',\n  Information = 'CocInfoHighlight',\n  Hint = 'CocHintHighlight',\n  Deprecated = 'CocDeprecatedHighlight',\n  Unused = 'CocUnusedHighlight'\n}\n\n/**\n * Local diagnostic config\n */\nexport interface DiagnosticConfig {\n  enable: boolean\n  highlightLimit: number\n  highlightPriority: number\n  autoRefresh: boolean\n  enableSign: boolean\n  locationlistUpdate: boolean\n  enableHighlightLineNumber: boolean\n  checkCurrentLine: boolean\n  enableMessage: string\n  displayByAle: boolean\n  displayByVimDiagnostic: boolean\n  signPriority: number\n  level: number\n  locationlistLevel: number | undefined\n  signLevel: number | undefined\n  messageLevel: number | undefined\n  messageTarget: string\n  refreshOnInsertMode: boolean\n  virtualText: boolean\n  virtualTextAlign: VirtualTextOption['text_align']\n  virtualTextLevel: number | undefined\n  virtualTextWinCol: number | null\n  virtualTextCurrentLineOnly: boolean\n  virtualTextPrefix: string\n  virtualTextFormat: string\n  virtualTextLimitInOneLine: number\n  virtualTextLines: number\n  virtualTextLineSeparator: string\n  filetypeMap: object\n  showUnused: boolean\n  showDeprecated: boolean\n  format: string\n  floatConfig: FloatConfig\n  showRelatedInformation: boolean\n}\n\nexport function formatDiagnostic(format: string, diagnostic: Diagnostic): string {\n  let { source, code, severity, message } = diagnostic\n  let s = getSeverityName(severity)[0]\n  const codeStr = code ? ' ' + code : ''\n  return format.replace('%source', source)\n    .replace('%code', codeStr)\n    .replace('%severity', s)\n    .replace('%message', message)\n}\n\nexport function getSeverityName(severity: DiagnosticSeverity): string {\n  switch (severity) {\n    case DiagnosticSeverity.Warning:\n      return 'Warning'\n    case DiagnosticSeverity.Information:\n      return 'Information'\n    case DiagnosticSeverity.Hint:\n      return 'Hint'\n    default:\n      return 'Error'\n  }\n}\n\nexport function getSeverityType(severity: DiagnosticSeverity): string {\n  switch (severity) {\n    case DiagnosticSeverity.Warning:\n      return 'W'\n    case DiagnosticSeverity.Information:\n      return 'I'\n    case DiagnosticSeverity.Hint:\n      return 'I'\n    default:\n      return 'E'\n  }\n}\n\nexport function severityLevel(level: string | null | undefined): number | undefined {\n  if (level == null) return undefined\n  switch (level) {\n    case 'hint':\n      return DiagnosticSeverity.Hint\n    case 'information':\n      return DiagnosticSeverity.Information\n    case 'warning':\n      return DiagnosticSeverity.Warning\n    case 'error':\n      return DiagnosticSeverity.Error\n    default:\n      return DiagnosticSeverity.Hint\n  }\n}\n\nexport function getNameFromSeverity(severity: DiagnosticSeverity): string {\n  switch (severity) {\n    case DiagnosticSeverity.Error:\n      return 'CocError'\n    case DiagnosticSeverity.Warning:\n      return 'CocWarning'\n    case DiagnosticSeverity.Information:\n      return 'CocInfo'\n    case DiagnosticSeverity.Hint:\n      return 'CocHint'\n    default:\n      return 'CocError'\n  }\n}\n\nexport function getLocationListItem(bufnr: number, diagnostic: Diagnostic, lines?: ReadonlyArray<string>): LocationListItem {\n  let { start, end } = diagnostic.range\n  let owner = diagnostic.source || 'coc.nvim'\n  let msg = diagnostic.message.split('\\n')[0]\n  let type = getSeverityName(diagnostic.severity).slice(0, 1).toUpperCase()\n  return {\n    bufnr,\n    lnum: start.line + 1,\n    end_lnum: end.line + 1,\n    col: Array.isArray(lines) ? byteIndex(lines[start.line] ?? '', start.character) + 1 : start.character + 1,\n    end_col: Array.isArray(lines) ? byteIndex(lines[end.line] ?? '', end.character) + 1 : end.character + 1,\n    text: `[${owner}${diagnostic.code ? ' ' + diagnostic.code : ''}] ${msg} [${type}]`,\n    type\n  }\n}\n\n/**\n * Sort by severity and position\n */\nexport function sortDiagnostics(a: Diagnostic, b: Diagnostic): number {\n  let sa = a.severity ?? 1\n  let sb = b.severity ?? 1\n  if (sa != sb) return sa - sb\n  let d = comparePosition(a.range.start, b.range.start)\n  if (d != 0) return d\n  return a.source > b.source ? 1 : -1\n}\n\nexport function getHighlightGroup(diagnostic: Diagnostic): DiagnosticHighlight[] {\n  let hlGroups: DiagnosticHighlight[] = []\n  let tags = diagnostic.tags || []\n  if (tags.includes(DiagnosticTag.Deprecated)) {\n    hlGroups.push(DiagnosticHighlight.Deprecated)\n  }\n  if (tags.includes(DiagnosticTag.Unnecessary)) {\n    hlGroups.push(DiagnosticHighlight.Unused)\n  }\n  switch (diagnostic.severity) {\n    case DiagnosticSeverity.Hint:\n      hlGroups.push(DiagnosticHighlight.Hint)\n      break\n    case DiagnosticSeverity.Information:\n      hlGroups.push(DiagnosticHighlight.Information)\n      break\n    case DiagnosticSeverity.Warning:\n      hlGroups.push(DiagnosticHighlight.Warning)\n      break\n    case DiagnosticSeverity.Error:\n      hlGroups.push(DiagnosticHighlight.Error)\n      break\n  }\n\n  return hlGroups\n}\n\nexport function adjustDiagnostics(diagnostics: ReadonlyArray<Diagnostic>, edit: TextEdit): ReadonlyArray<Diagnostic> {\n  let res: Diagnostic[] = []\n  let { range } = edit\n  for (let diag of diagnostics) {\n    let r = diag.range\n    if (rangeOverlap(range, r)) continue\n    if (comparePosition(r.start, range.end) > 0) {\n      let s = getPosition(r.start, edit)\n      let e = getPosition(r.end, edit)\n      if (s.line >= 0 && s.character >= 0 && e.line >= 0 && e.character >= 0) {\n        diag.range = Range.create(s, e)\n      }\n    }\n    res.push(diag)\n  }\n  return res\n}\n"
  },
  {
    "path": "src/events.ts",
    "content": "'use strict'\nimport type { CompleteDoneItem, CompleteOption } from './completion/types'\nimport { createLogger } from './logger'\nimport { JumpInfo } from './types'\nimport { disposeAll, getConditionValue } from './util'\nimport { onUnexpectedError, shouldIgnore } from './util/errors'\nimport * as Is from './util/is'\nimport { equals } from './util/object'\nimport { CancellationToken, Disposable } from './util/protocol'\nimport { byteSlice } from './util/string'\nconst logger = createLogger('events')\nconst debounceTime = getConditionValue(100, 10)\n\nexport type Result = void | Promise<void>\n\nexport interface PopupChangeEvent {\n  readonly startcol: number\n  readonly index: number\n  readonly word: string\n  readonly height: number\n  readonly width: number\n  readonly row: number\n  readonly col: number\n  readonly size: number\n  readonly scrollbar: boolean\n  readonly inserted: boolean\n  readonly move: boolean\n}\n\nexport interface VisibleEvent {\n  winid: number\n  bufnr: number\n  /**\n   * 1 based, end inclusive topline, botline\n   */\n  region: [number, number]\n}\n\nexport interface ModeChangedEvent {\n  readonly old_mode: string\n  readonly new_mode: string\n}\n\nexport interface InsertChange {\n  readonly lnum: number\n  readonly col: number\n  readonly line: string\n  readonly changedtick: number\n  pre: string\n  /**\n   * Insert character that cause change of this time.\n   */\n  insertChar?: string\n  insertChars?: string[]\n}\n\nexport enum EventName {\n  Ready = 'ready',\n  InsertEnter = 'InsertEnter',\n  InsertLeave = 'InsertLeave',\n  CursorHoldI = 'CursorHoldI',\n  CursorMovedI = 'CursorMovedI',\n  CursorHold = 'CursorHold',\n  CursorMoved = 'CursorMoved',\n  MenuPopupChanged = 'MenuPopupChanged',\n  InsertCharPre = 'InsertCharPre',\n  TextChanged = 'TextChanged',\n  BufEnter = 'BufEnter',\n  TextChangedI = 'TextChangedI',\n  TextChangedP = 'TextChangedP',\n  TextInsert = 'TextInsert',\n  BufWinEnter = 'BufWinEnter',\n  WinScrolled = 'WinScrolled',\n  WinClosed = 'WinClosed',\n  BufWinLeave = 'BufWinLeave',\n  ModeChanged = 'ModeChanged'\n}\n\nexport type BufEvents = 'BufHidden' | 'BufEnter' | 'BufRename'\n  | 'InsertLeave' | 'TermOpen' | 'InsertEnter' | 'BufCreate' | 'BufUnload'\n  | 'BufDetach' | 'Enter' | 'LinesChanged' | 'PumNavigate'\n\nexport type EmptyEvents = 'FocusGained' | 'ColorScheme' | 'FocusLost' | 'InsertSnippet' | 'VimLeavePre' | 'ready'\n\nexport type InsertChangeEvents = 'TextChangedP' | 'TextChangedI'\n\nexport type TaskEvents = 'TaskExit' | 'TaskStderr' | 'TaskStdout'\n\nexport type WindowEvents = 'WinLeave' | 'WinEnter' | 'WinClosed'\n\nexport type TabEvents = 'TabNew' | 'TabClosed'\n\nexport type AllEvents = BufEvents | EmptyEvents | CursorEvents | TaskEvents | WindowEvents | TabEvents\n  | InsertChangeEvents | 'CompleteDone' | 'TextChanged' | 'MenuPopupChanged' | 'BufWritePost' | 'BufWritePre' | 'ModeChanged'\n  | 'InsertCharPre' | 'FileType' | 'BufWinEnter' | 'BufWinLeave' | 'VimResized' | 'TermExit' | 'WinScrolled' | 'CompleteStart'\n  | 'DirChanged' | 'OptionSet' | 'Command' | 'BufReadCmd' | 'GlobalChange' | 'InputChar' | 'PlaceholderJump' | 'InputListSelect'\n  | 'WinLeave' | 'MenuInput' | 'PromptInsert' | 'PromptExit' | 'FloatBtnClick' | 'InsertSnippet' | 'TextInsert' | 'PromptKeyPress' | 'WindowVisible'\n\nexport type CursorEvents = CursorHoldEvents | CursorMoveEvents\nexport type CursorHoldEvents = 'CursorHold' | 'CursorHoldI'\nexport type CursorMoveEvents = 'CursorMoved' | 'CursorMovedI'\n\nexport type OptionValue = string | number | boolean\n\nexport interface CursorPosition {\n  readonly bufnr: number\n  readonly lnum: number\n  readonly col: number\n  readonly insert: boolean\n}\n\nexport interface LatestInsert {\n  readonly bufnr: number\n  readonly character: string\n  readonly timestamp: number\n}\n\nclass Events {\n\n  private handlers: Map<string, ((...args: any[]) => Promise<unknown>)[]> = new Map()\n  private _cursor: CursorPosition\n  private _bufnr = 1\n  private timeoutMap: Map<number, NodeJS.Timeout> = new Map()\n  // bufnr & character\n  private _recentInserts: [number, string][] = []\n  private _lastChange = 0\n  private _insertMode = false\n  private _pumAlignTop = false\n  private _pumVisible = false\n  private _completing = false\n  private _requesting = false\n  private _ready = false\n  private _mode = 'n'\n  private _pumInserted = false\n  public timeout = 1000\n  // public completing = false\n\n  public set requesting(val: boolean) {\n    this._requesting = val\n  }\n\n  public get requesting(): boolean {\n    return this._requesting\n  }\n\n  public get ready(): boolean {\n    return this._ready\n  }\n\n  public get mode(): string {\n    return this._mode\n  }\n\n  public get pumInserted(): boolean {\n    return this._pumInserted\n  }\n\n  private fireVisibleEvent(ev: VisibleEvent): void {\n    let { winid } = ev\n    let timer = this.timeoutMap.get(winid)\n    if (timer) clearTimeout(timer)\n    timer = setTimeout(() => {\n      this.fire('WindowVisible', [ev]).catch(onUnexpectedError)\n    }, debounceTime)\n    this.timeoutMap.set(winid, timer)\n  }\n\n  private clearVisibleTimer(winid: number): void {\n    let timer = this.timeoutMap.get(winid)\n    if (timer) {\n      this.timeoutMap.delete(winid)\n      clearTimeout(timer)\n    }\n  }\n\n  public set completing(completing: boolean) {\n    this._completing = completing\n    this._pumVisible = completing\n    this._pumInserted = false\n  }\n\n  public get completing(): boolean {\n    return this._completing\n  }\n\n  public get cursor(): CursorPosition {\n    return this._cursor ?? { bufnr: this._bufnr, col: 1, lnum: 1, insert: false }\n  }\n\n  public get bufnr(): number {\n    return this._bufnr\n  }\n\n  public get pumvisible(): boolean {\n    return this._pumVisible\n  }\n\n  public get pumAlignTop(): boolean {\n    return this._pumAlignTop\n  }\n\n  public get insertMode(): boolean {\n    return this._insertMode && this._mode.startsWith('i')\n  }\n\n  public get lastChangeTs(): number {\n    return this._lastChange\n  }\n\n  /**\n   * Resolved when first event fired or timeout\n   */\n  public race(events: AllEvents[], token?: number | CancellationToken): Promise<{ name: AllEvents, args: unknown[] } | undefined> {\n    let disposables: Disposable[] = []\n    return new Promise(resolve => {\n      if (Is.number(token)) {\n        let timer = setTimeout(() => {\n          disposeAll(disposables)\n          resolve(undefined)\n        }, token)\n        disposables.push(Disposable.create(() => {\n          clearTimeout(timer)\n        }))\n      } else if (CancellationToken.is(token)) {\n        token.onCancellationRequested(() => {\n          disposeAll(disposables)\n          resolve(undefined)\n        }, null, disposables)\n      }\n      events.forEach(ev => {\n        this.on(ev, (...args) => {\n          disposeAll(disposables)\n          resolve({ name: ev, args })\n        }, null, disposables)\n      })\n    })\n  }\n\n  public async fire(event: string, args: any[]): Promise<void> {\n    if (event === EventName.Ready) {\n      this._ready = true\n    } else if (event == EventName.InsertEnter) {\n      this._insertMode = true\n    } else if (event == EventName.InsertLeave) {\n      this._insertMode = false\n      this._pumVisible = false\n      this._recentInserts = []\n    } else if (event == EventName.CursorHoldI || event == EventName.CursorMovedI) {\n      this._bufnr = args[0]\n      if (!this._insertMode) {\n        this._insertMode = true\n        void this.fire(EventName.InsertEnter, [args[0]])\n      }\n    } else if (event == EventName.CursorHold || event == EventName.CursorMoved) {\n      this._bufnr = args[0]\n      if (this._insertMode) {\n        this._insertMode = false\n        void this.fire(EventName.InsertLeave, [args[0]])\n      }\n    } else if (event == EventName.MenuPopupChanged) {\n      this._pumVisible = true\n      this._pumAlignTop = args[1] > args[0].row\n      this._pumInserted = args[0].inserted\n    } else if (event == EventName.InsertCharPre) {\n      this._recentInserts.push([args[1], args[0]])\n    } else if (event == EventName.TextChanged) {\n      this._lastChange = Date.now()\n    } else if (event == EventName.BufEnter) {\n      this._bufnr = args[0]\n    } else if (event == EventName.TextChangedI || event == EventName.TextChangedP) {\n      let info: InsertChange = args[1]\n      let pre = byteSlice(info.line ?? '', 0, info.col - 1)\n      let arr: [number, string][]\n      arr = this._recentInserts.filter(o => o[0] == args[0])\n      this._bufnr = args[0]\n      this._recentInserts = []\n      this._lastChange = Date.now()\n      info.pre = pre\n      info.insertChars = arr.map(o => o[1])\n      // fix cursor since vim may not send CursorMovedI event\n      this._cursor = Object.freeze({\n        bufnr: args[0],\n        lnum: info.lnum,\n        col: info.col,\n        insert: true\n      })\n      if (arr.length && pre.length) {\n        let character = pre.slice(-1)\n        if (arr.findIndex(o => o[1] == character) !== -1) {\n          info.insertChar = character\n          // make it fires after TextChangedI & TextChangedP\n          process.nextTick(() => {\n            void this.fire(EventName.TextInsert, [...args, character])\n          })\n        }\n      }\n    } else if (event == EventName.BufWinEnter) {\n      const [bufnr, winid, region] = args\n      this.fireVisibleEvent({ bufnr, winid, region })\n    } else if (event == EventName.WinScrolled) {\n      const [winid, bufnr, region] = args\n      this.fireVisibleEvent({ bufnr, winid, region })\n    } else if (event == EventName.WinClosed) {\n      this.clearVisibleTimer(args[0])\n    } else if (event == EventName.BufWinLeave) {\n      this.clearVisibleTimer(args[1])\n    } else if (event == EventName.ModeChanged) {\n      this._mode = args[0].new_mode\n    }\n    if (event == EventName.CursorMoved || event == EventName.CursorMovedI) {\n      args.push(this._recentInserts.length > 0)\n      let cursor = {\n        bufnr: args[0],\n        lnum: args[1][0],\n        col: args[1][1],\n        insert: event == EventName.CursorMovedI\n      }\n      // Avoid CursorMoved event when it's not moved at all\n      if ((this._cursor && equals(this._cursor, cursor))) return\n      this._cursor = cursor\n    }\n    let cbs = this.handlers.get(event)\n    if (cbs?.length) {\n      let fns = cbs.slice()\n      let traceSlow = this.requesting\n      await Promise.allSettled(fns.map(fn => {\n        let promiseFn = async () => {\n          let timer: NodeJS.Timeout\n          if (traceSlow) {\n            timer = setTimeout(() => {\n              logger.warn(`Slow \"${event}\" handler detected`, fn['stack'])\n            }, this.timeout)\n          }\n          try {\n            await fn(args)\n          } catch (e) {\n            if (!shouldIgnore(e)) logger.error(`Error on event: ${event}`, e, fn['stack'])\n          }\n          clearTimeout(timer)\n        }\n        return promiseFn()\n      }))\n    }\n  }\n\n  public on(event: BufEvents | 'PromptExit', handler: (bufnr: number) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: CursorHoldEvents, handler: (bufnr: number, cursor: [number, number], winid: number) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: InsertChangeEvents, handler: (bufnr: number, info: InsertChange) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: WindowEvents, handler: (winid: number) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: CursorMoveEvents, handler: (bufnr: number, cursor: [number, number], hasInsert: boolean) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'WindowVisible', handler: (event: VisibleEvent) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'WinScrolled', handler: (winid: number, bufnr: number, region: Readonly<[number, number]>) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'TabClosed', handler: (tabids: number[]) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'TabNew', handler: (tabid: number) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'TextInsert', handler: (bufnr: number, info: InsertChange, character: string) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'FloatBtnClick', handler: (bufnr: number, index: number) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'PromptKeyPress', handler: (bufnr: number, key: string) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'BufWritePre', handler: (bufnr: number, bufname: string, changedtick: number) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'TextChanged' | 'BufWritePost', handler: (bufnr: number, changedtick: number) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'TaskExit', handler: (id: string, code: number) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'TaskStderr' | 'TaskStdout', handler: (id: string, lines: string[]) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'BufReadCmd', handler: (scheme: string, fullpath: string) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'VimResized', handler: (columns: number, lines: number) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'Command', handler: (name: string) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'MenuPopupChanged', handler: (event: PopupChangeEvent, cursorline: number) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'CompleteDone', handler: (item: CompleteDoneItem | object, line: number, bufnr: number) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'CompleteStart', handler: (option: Readonly<CompleteOption>) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'InsertCharPre', handler: (character: string, bufnr: number) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'FileType', handler: (filetype: string, bufnr: number) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'BufWinEnter' | 'BufWinLeave', handler: (bufnr: number, winid: number) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'TermExit', handler: (bufnr: number, status: number) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'DirChanged', handler: (cwd: string) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'OptionSet' | 'GlobalChange', handler: (option: string, oldVal: OptionValue, newVal: OptionValue) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'InputChar', handler: (session: string, character: string, mode: number) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'PromptInsert', handler: (value: string, bufnr: number) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'PlaceholderJump', handler: (bufnr: number, info: JumpInfo) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'InputListSelect', handler: (index: number) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: 'ModeChanged', handler: (event: ModeChangedEvent) => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: EmptyEvents, handler: () => Result, thisArg?: any, disposables?: Disposable[]): Disposable\n  public on(event: AllEvents | AllEvents[], handler: (...args: unknown[]) => Result, thisArg?: any, disposables?: Disposable[] | true): Disposable\n  public on(event: AllEvents[] | AllEvents, handler: (...args: any[]) => Result, thisArg?: any, disposables?: Disposable[] | true): Disposable {\n    if (Array.isArray(event)) {\n      let arr: Disposable[] = []\n      for (let ev of event) {\n        this.on(ev as any, handler, thisArg, arr)\n      }\n      let dis = Disposable.create(() => {\n        disposeAll(arr)\n      })\n      if (Array.isArray(disposables)) {\n        disposables.push(dis)\n      }\n      return dis\n    } else {\n      let arr = this.handlers.get(event) ?? []\n      let onFinish = () => {\n        if (disposables === true && disposable) disposable.dispose()\n      }\n      let wrappedhandler = args => new Promise((resolve, reject) => {\n        try {\n          Promise.resolve(handler.apply(thisArg ?? null, args)).then(() => {\n            onFinish()\n            resolve(undefined)\n          }, (e: Error) => {\n            onFinish()\n            reject(e)\n          })\n        } catch (e) {\n          onFinish()\n          reject(e as Error)\n        }\n      })\n      Error.captureStackTrace(wrappedhandler)\n      arr.push(wrappedhandler)\n      this.handlers.set(event, arr)\n      let disposable = Disposable.create(() => {\n        let idx = arr.indexOf(wrappedhandler)\n        if (idx !== -1) {\n          arr.splice(idx, 1)\n        }\n      })\n      if (Array.isArray(disposables)) {\n        disposables.push(disposable)\n      }\n      return disposable\n    }\n  }\n\n  public once(event: AllEvents, handler: (...args: any[]) => Result, thisArg?: any): Disposable {\n    return this.on(event, handler, thisArg, true)\n  }\n}\n\nexport default new Events()\n"
  },
  {
    "path": "src/extension/index.ts",
    "content": "'use strict'\nimport { WorkspaceFolder } from 'vscode-languageserver-types'\nimport commands from '../commands'\nimport { ConfigurationUpdateTarget } from '../configuration/types'\nimport events from '../events'\nimport { createLogger } from '../logger'\nimport type { OutputChannel } from '../types'\nimport { concurrent } from '../util'\nimport { distinct, isFalsyOrEmpty, toArray } from '../util/array'\nimport { VERSION, dataHome } from '../util/constants'\nimport { isUrl } from '../util/is'\nimport { fs, path, which } from '../util/node'\nimport { toObject } from '../util/object'\nimport { executable } from '../util/processes'\nimport { Event } from '../util/protocol'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { IInstaller, Installer } from './installer'\nimport { API, Extension, ExtensionInfo, ExtensionItem, ExtensionManager, ExtensionState, ExtensionToLoad } from './manager'\nimport { ExtensionStat, checkExtensionRoot, loadExtensionJson, loadGlobalJsonAsync } from './stat'\nimport { InstallBuffer, InstallChannel, InstallUI } from './ui'\nconst logger = createLogger('extensions-index')\n\nexport interface PropertyScheme {\n  type: string\n  default: any\n  description: string\n  enum?: string[]\n  items?: any\n  [key: string]: any\n}\n\nexport interface UpdateSettings {\n  updateCheck: string\n  updateUIInTab: boolean\n  silentAutoupdate: boolean\n}\n\nconst EXTENSIONS_FOLDER = path.join(dataHome, 'extensions')\n\n// global local file native\nexport class Extensions {\n  public readonly manager: ExtensionManager\n  public readonly states: ExtensionStat\n  public modulesFolder = path.join(EXTENSIONS_FOLDER, 'node_modules')\n  private globalPromise: Promise<ExtensionToLoad[]>\n  constructor() {\n    checkExtensionRoot(EXTENSIONS_FOLDER)\n    this.states = new ExtensionStat(EXTENSIONS_FOLDER)\n    this.manager = new ExtensionManager(this.states, EXTENSIONS_FOLDER)\n    commands.register({\n      id: 'extensions.forceUpdateAll',\n      execute: async () => {\n        let arr = await this.manager.cleanExtensions()\n        logger.info(`Force update extensions: ${arr}`)\n        await this.installExtensions(arr)\n      }\n    }, false, 'remove all global extensions and install them')\n    this.globalPromise = this.globalExtensions()\n\n    commands.register({\n      id: 'extensions.toggleAutoUpdate',\n      execute: async () => {\n        let settings = this.getUpdateSettings()\n        let target = ConfigurationUpdateTarget.Global\n        let config = workspace.getConfiguration(null, null)\n        if (settings.updateCheck == 'never') {\n          await config.update('extensions.updateCheck', 'daily', target)\n          void window.showInformationMessage('Extension auto update enabled.')\n        } else {\n          await config.update('extensions.updateCheck', 'never', target)\n          void window.showInformationMessage('Extension auto update disabled.')\n        }\n        await config.update('coc.preferences.extensionUpdateCheck', undefined, target)\n      }\n    }, false, 'toggle auto update of extensions.')\n    events.once('ready', () => {\n      void this.checkRecommendation(workspace.workspaceFolders[0])\n      workspace.onDidChangeWorkspaceFolders(e => {\n        void this.checkRecommendation(e.added[0])\n      })\n    })\n  }\n\n  public async checkRecommendation(workspaceFolder: WorkspaceFolder | undefined): Promise<void> {\n    if (!workspaceFolder) return\n    let config = workspace.getConfiguration('extensions', workspaceFolder)\n    let recommendations = toArray(config.inspect('recommendations').workspaceFolderValue) as string[]\n    const unInstalled = recommendations.filter(name => !this.states.hasExtension(name))\n    let uri = workspaceFolder.uri\n    if (!this.manager.states.shouldPrompt(uri) || unInstalled.length === 0) return\n    let items = [{\n      title: `Install ${unInstalled.join(', ')}`,\n      index: 1\n    }, {\n      title: 'Don\\'t show again',\n      isCloseAffordance: true,\n      index: 2\n    }]\n    const item = await window.showInformationMessage(`Install recommend extensions?`, ...items)\n    if (!item) return\n    if (item.index === 1) {\n      await this.installExtensions(unInstalled)\n    } else {\n      this.manager.states.addNoPromptFolder(uri)\n    }\n  }\n\n  public getUpdateSettings(): UpdateSettings {\n    let config = workspace.getConfiguration(null, null)\n    let extensionsConfig = toObject<Partial<UpdateSettings>>(config.inspect('extensions').globalValue)\n    return {\n      updateCheck: extensionsConfig.updateCheck ?? config.get<string>('coc.preferences.extensionUpdateCheck', 'never'),\n      updateUIInTab: extensionsConfig.updateUIInTab ?? config.get<boolean>('coc.preferences.extensionUpdateUIInTab', false),\n      silentAutoupdate: extensionsConfig.silentAutoupdate ?? config.get<boolean>('coc.preferences.silentAutoupdate', true)\n    }\n  }\n\n  public async init(runtimepath: string): Promise<void> {\n    if (process.env.COC_NO_PLUGINS == '1') return\n    let stats = await this.globalPromise\n    this.manager.registerExtensions(stats)\n    let localStats = this.runtimeExtensionStats(runtimepath.split(','))\n    this.manager.registerExtensions(localStats)\n    void this.manager.loadFileExtensions()\n  }\n\n  public async activateExtensions(): Promise<void> {\n    await this.manager.activateExtensions()\n    if (process.env.COC_NO_PLUGINS == '1') {\n      logger.warn('Extensions disabled by env COC_NO_PLUGINS')\n      return\n    }\n    let names = this.states.filterGlobalExtensions(workspace.env.globalExtensions)\n    void this.installExtensions(names)\n    // check extensions need watch & install\n    let settings = this.getUpdateSettings()\n    if (this.states.shouldUpdate(settings.updateCheck)) {\n      this.outputChannel.appendLine('Start auto update...')\n      this.updateExtensions(settings.silentAutoupdate, settings.updateUIInTab).catch(e => {\n        this.outputChannel.appendLine(`Error on updateExtensions ${e}`)\n      })\n    }\n  }\n\n  public get onDidLoadExtension(): Event<Extension<API>> {\n    return this.manager.onDidLoadExtension\n  }\n\n  public get onDidActiveExtension(): Event<Extension<API>> {\n    return this.manager.onDidActiveExtension\n  }\n\n  public get onDidUnloadExtension(): Event<string> {\n    return this.manager.onDidUnloadExtension\n  }\n\n  private get outputChannel(): OutputChannel {\n    return window.createOutputChannel('extensions')\n  }\n\n  /**\n   * Get all loaded extensions.\n   */\n  public get all(): Extension<API>[] {\n    return this.manager.all\n  }\n\n  public has(id: string): boolean {\n    return this.manager.has(id)\n  }\n\n  public getExtension(id: string): ExtensionItem | undefined {\n    return this.manager.getExtension(id)\n  }\n\n  public getExtensionById(extensionId: string): Extension<API> | undefined {\n    let item = this.manager.getExtension(extensionId)\n    return item ? item.extension : undefined\n  }\n\n  /**\n   * @deprecated Used by old version coc-json.\n   */\n  public get schemes(): { [key: string]: PropertyScheme } {\n    return {}\n  }\n\n  /**\n   * @deprecated Used by old version coc-json.\n   */\n  public addSchemeProperty(key: string, def: PropertyScheme): void {\n    // workspace.configurations.extendsDefaults({ [key]: def.default }, id)\n  }\n\n  /**\n   * @public Get state of extension\n   */\n  public getExtensionState(id: string): ExtensionState {\n    return this.manager.getExtensionState(id)\n  }\n\n  public isActivated(id: string): boolean {\n    let item = this.manager.getExtension(id)\n    return item != null && item.extension.isActive\n  }\n\n  public async call(id: string, method: string, args: any[]): Promise<any> {\n    return await this.manager.call(id, method, args)\n  }\n\n  public get npm(): string {\n    let npm = workspace.initialConfiguration.get<string>('npm.binPath')\n    npm = workspace.expand(npm)\n    for (let exe of [npm, 'npm']) {\n      if (executable(exe)) return which.sync(exe)\n    }\n    void window.showErrorMessage(`Can't find ${npm} or npm in your $PATH`)\n    return null\n  }\n\n  private createInstallerUI(isUpdate: boolean, silent: boolean, updateUIInTab: boolean): InstallUI {\n    return silent ? new InstallChannel({ isUpdate }, this.outputChannel) : new InstallBuffer({ isUpdate, updateUIInTab })\n  }\n\n  public createInstaller(npm: string, def: string): IInstaller {\n    return new Installer(this.modulesFolder, npm, def)\n  }\n\n  /**\n   * Install extensions, can be called without initialize.\n   */\n  public async installExtensions(list: string[]): Promise<void> {\n    if (isFalsyOrEmpty(list) || !this.npm) return\n    let { npm } = this\n    list = distinct(list)\n    let installBuffer = this.createInstallerUI(false, false, false)\n    await Promise.resolve(installBuffer.start(list))\n    let fn = async (key: string): Promise<void> => {\n      try {\n        installBuffer.startProgress(key)\n        let installer = this.createInstaller(npm, key)\n        installer.on('message', (msg, isProgress) => {\n          installBuffer.addMessage(key, msg, isProgress)\n        })\n        let result = await installer.install()\n        installBuffer.finishProgress(key, true)\n        this.states.addExtension(result.name, result.url ? result.url : `>=${result.version}`)\n        let ms = key.match(/@[\\d.]+$/)\n        if (ms != null) this.states.setLocked(result.name, true)\n        await this.manager.loadExtension(result.folder)\n      } catch (err: any) {\n        installBuffer.addMessage(key, err.message)\n        installBuffer.finishProgress(key, false)\n        void window.showErrorMessage(`Error on install ${key}: ${err}`)\n        logger.error(`Error on install ${key}`, err)\n      }\n    }\n    await concurrent(list, fn)\n  }\n\n  /**\n   * Update global extensions\n   */\n  public async updateExtensions(silent = false, updateUIInTab = false): Promise<void> {\n    let { npm } = this\n    if (!npm) return\n    let stats = this.globalExtensionStats()\n    stats = stats.filter(s => {\n      if (s.isLocked || s.state === 'disabled') {\n        this.outputChannel.appendLine(`Skipped update for ${s.isLocked ? 'locked' : 'disabled'} extension \"${s.id}\"`)\n        return false\n      }\n      return true\n    })\n    this.states.setLastUpdate()\n    this.cleanModulesFolder()\n    let installBuffer = this.createInstallerUI(true, silent, updateUIInTab)\n    await Promise.resolve(installBuffer.start(stats.map(o => o.id)))\n    let fn = async (stat: ExtensionInfo): Promise<void> => {\n      let { id } = stat\n      try {\n        installBuffer.startProgress(id)\n        let url = stat.exotic ? stat.uri : null\n        let installer = this.createInstaller(npm, id)\n        installer.on('message', (msg, isProgress) => {\n          installBuffer.addMessage(id, msg, isProgress)\n        })\n        let directory = await installer.update(url)\n        installBuffer.finishProgress(id, true)\n        if (directory) await this.manager.loadExtension(directory)\n      } catch (err: any) {\n        installBuffer.addMessage(id, err.message)\n        installBuffer.finishProgress(id, false)\n        void window.showErrorMessage(`Error on update ${id}: ${err}`)\n        logger.error(`Error on update ${id}`, err)\n      }\n    }\n    await concurrent(stats, fn, silent ? 1 : 3)\n  }\n\n  /**\n   * Get all extension states\n   */\n  public async getExtensionStates(): Promise<ExtensionInfo[]> {\n    let runtimepaths = await workspace.nvim.runtimePaths\n    let localStats = this.runtimeExtensionStats(runtimepaths)\n    let globalStats = this.globalExtensionStats()\n    return localStats.concat(globalStats)\n  }\n\n  public async globalExtensions(): Promise<ExtensionToLoad[]> {\n    if (process.env.COC_NO_PLUGINS == '1') return []\n    let res: ExtensionToLoad[] = []\n    for (let key of this.states.activated()) {\n      let root = path.join(this.modulesFolder, key)\n      try {\n        let json = await loadGlobalJsonAsync(root, VERSION)\n        res.push({ root, isLocal: false, packageJSON: json })\n      } catch (err) {\n        logger.error(`Error on load package.json of ${key}`, err)\n      }\n    }\n    return res\n  }\n\n  public globalExtensionStats(): ExtensionInfo[] {\n    let dependencies = this.states.dependencies\n    let lockedExtensions = this.states.lockedExtensions\n    let infos: ExtensionInfo[] = []\n    Object.entries(dependencies).map(([key, val]) => {\n      let root = path.join(this.modulesFolder, key)\n      let errors: string[] = []\n      let obj = loadExtensionJson(root, VERSION, errors)\n      if (errors.length > 0) {\n        this.outputChannel.appendLine(`Error on load ${key} at ${root}: ${errors.join('\\n')}`)\n        return\n      }\n      obj.name = key\n      infos.push({\n        id: key,\n        root,\n        isLocal: false,\n        version: obj.version,\n        description: obj.description ?? '',\n        isLocked: lockedExtensions.includes(key),\n        exotic: /^https?:/.test(val),\n        uri: toUrl(val),\n        state: this.getExtensionState(key),\n        packageJSON: obj\n      })\n    })\n    logger.debug('globalExtensionStats:', infos.length)\n    return infos\n  }\n\n  public runtimeExtensionStats(runtimepaths: string[]): ExtensionInfo[] {\n    let lockedExtensions = this.states.lockedExtensions\n    let infos: ExtensionInfo[] = []\n    let localIds: Set<string> = new Set()\n    runtimepaths.map(root => {\n      let errors: string[] = []\n      let obj = loadExtensionJson(root, workspace.version, errors)\n      if (errors.length > 0) return\n      let { name } = obj\n      if (!name || this.states.hasExtension(name) || localIds.has(name)) return\n      this.states.addLocalExtension(name, root)\n      localIds.add(name)\n      infos.push(({\n        id: obj.name,\n        isLocal: true,\n        isLocked: lockedExtensions.includes(name),\n        version: obj.version,\n        description: obj.description ?? '',\n        exotic: false,\n        root,\n        state: this.getExtensionState(obj.name),\n        packageJSON: Object.freeze(obj)\n      }))\n    })\n    return infos\n  }\n\n  /**\n   * Remove unnecessary folders in node_modules\n   */\n  public cleanModulesFolder(): void {\n    let globalIds = this.states.globalIds\n    let folders = globalIds.map(s => s.replace(/\\/.*$/, ''))\n    if (!fs.existsSync(this.modulesFolder)) return\n    let files = fs.readdirSync(this.modulesFolder)\n    for (let file of files) {\n      if (folders.includes(file)) continue\n      let p = path.join(this.modulesFolder, file)\n      let stat = fs.lstatSync(p)\n      if (stat.isSymbolicLink()) {\n        fs.unlinkSync(p)\n      } else if (stat.isDirectory()) {\n        fs.rmSync(p, { recursive: true, force: true })\n      }\n    }\n  }\n\n  public dispose(): void {\n    this.manager.dispose()\n  }\n}\n\nexport function toUrl(val: string): string {\n  return isUrl(val) ? val.replace(/\\.git(#master|#main)?$/, '') : ''\n}\n\nexport default new Extensions()\n"
  },
  {
    "path": "src/extension/installer.ts",
    "content": "'use strict'\nimport { EventEmitter } from 'events'\nimport { URL } from 'url'\nimport { v4 as uuid } from 'uuid'\nimport { createLogger } from '../logger'\nimport download, { DownloadOptions } from '../model/download'\nimport fetch, { FetchOptions } from '../model/fetch'\nimport { loadJson } from '../util/fs'\nimport { child_process, fs, os, path, readline, semver } from '../util/node'\nimport { toText } from '../util/string'\nimport workspace from '../workspace'\nconst logger = createLogger('extension-installer')\nconst local_dependencies = ['coc.nvim', 'esbuild', 'webpack', '@types/node']\n\nexport interface Info {\n  'dist.tarball'?: string\n  'engines.coc'?: string\n  version?: string\n  name?: string\n}\n\nexport type Dependencies = Record<string, string>\n\nexport interface InstallResult {\n  name: string\n  folder: string\n  updated: boolean\n  version: string\n  url?: string\n}\n\nexport function registryUrl(home = os.homedir()): URL {\n  let res: URL\n  let filepath = path.join(home, '.npmrc')\n  if (fs.existsSync(filepath)) {\n    try {\n      let content = fs.readFileSync(filepath, 'utf8')\n      let uri: string\n      for (let line of content.split(/\\r?\\n/)) {\n        if (line.startsWith('#')) continue\n        let ms = line.match(/^(.*?)=(.*)$/)\n        if (ms && ms[1] === 'coc.nvim:registry') {\n          uri = ms[2]\n        }\n      }\n      if (uri) res = new URL(uri)\n    } catch (e) {\n      logger.debug('Error on parse .npmrc:', e)\n    }\n  }\n  return res ?? new URL('https://registry.npmjs.org')\n}\n\nexport function isNpmCommand(exePath: string): boolean {\n  let name = path.basename(exePath)\n  return name === 'npm' || name === 'npm.CMD'\n}\n\nexport function isYarn(exePath: string) {\n  let name = path.basename(exePath)\n  return ['yarn', 'yarn.CMD', 'yarnpkg', 'yarnpkg.CMD'].includes(name)\n}\n\nfunction isPnpm(exePath: string) {\n  let name = path.basename(exePath)\n  return name === 'pnpm' || name === 'pnpm.CMD'\n}\n\nfunction isSymbolicLink(folder: string): boolean {\n  if (fs.existsSync(folder)) {\n    let stat = fs.lstatSync(folder)\n    if (stat.isSymbolicLink()) {\n      return true\n    }\n  }\n  return false\n}\n\nexport interface IInstaller {\n  on(event: 'message', cb: (msg: string, isProgress: boolean) => void): void\n  install(): Promise<InstallResult>\n  update(url?: string): Promise<string | undefined>\n}\n\nexport class Installer extends EventEmitter implements IInstaller {\n  private name: string\n  private url: string\n  private version: string\n  constructor(\n    private root: string,\n    private npm: string,\n    // could be url or name@version or name\n    private def: string\n  ) {\n    super()\n    if (/^https?:/.test(def)) {\n      this.url = def\n    } else {\n      let ms = def.match(/(.+)@([^/]+)$/)\n      if (ms) {\n        this.name = ms[1]\n        this.version = ms[2]\n      } else {\n        this.name = def\n      }\n    }\n  }\n\n  public get info() {\n    return { name: this.name, version: this.version }\n  }\n\n  public async getInfo(): Promise<Info> {\n    if (this.url) return await this.getInfoFromUri()\n    let registry = registryUrl()\n    this.log(`Get info from ${registry}`)\n    let buffer = await this.fetch(new URL(this.name, registry), { timeout: 10000, buffer: true })\n    let res = JSON.parse(buffer.toString())\n    if (!this.version) this.version = res['dist-tags']['latest']\n    let obj = res['versions'][this.version]\n    if (!obj) throw new Error(`${this.def} doesn't exists in ${registry}.`)\n    let requiredVersion = obj['engines'] && obj['engines']['coc']\n    if (!requiredVersion) throw new Error(`${this.def} is not a valid coc extension, \"engines\" field with coc property required.`)\n    return {\n      'dist.tarball': obj['dist']['tarball'],\n      'engines.coc': requiredVersion,\n      version: obj['version'],\n      name: res.name\n    } as Info\n  }\n\n  public async getInfoFromUri(): Promise<Info> {\n    let { url } = this\n    if (!url.startsWith('https://github.com')) {\n      throw new Error(`\"${url}\" is not supported, coc.nvim support github.com only`)\n    }\n    url = url.replace(/\\/$/, '')\n    let branch = 'master'\n    if (url.includes('@')) {\n      // https://github.com/sdras/vue-vscode-snippets@main\n      let idx = url.indexOf('@')\n      branch = url.substr(idx + 1)\n      url = url.substring(0, idx)\n    }\n    let fileUrl = url.replace('github.com', 'raw.githubusercontent.com') + `/${branch}/package.json`\n    this.log(`Get info from ${fileUrl}`)\n    let content = await this.fetch(fileUrl, { timeout: 10000 })\n    let obj = typeof content == 'string' ? JSON.parse(content) : content\n    this.name = obj.name\n    return {\n      'dist.tarball': `${url}/archive/${branch}.tar.gz`,\n      'engines.coc': obj['engines'] ? obj['engines']['coc'] : null,\n      name: obj.name,\n      version: obj.version\n    }\n  }\n\n  private log(msg: string, isProgress = false): void {\n    this.emit('message', msg, isProgress)\n  }\n\n  public async install(): Promise<InstallResult> {\n    this.log(`Using npm from: ${this.npm}`)\n    let info = await this.getInfo()\n    logger.info(`Fetched info of ${this.def}`, info)\n    let { name, version } = info\n    let required = toText(info['engines.coc']).replace(/^\\^/, '>=')\n    if (required && !semver.satisfies(workspace.version, required)) {\n      throw new Error(`${name} ${info.version} requires coc.nvim >= ${required}, please update coc.nvim.`)\n    }\n    let updated = await this.doInstall(info, new Set())\n    return { name, updated, version, url: this.url, folder: path.join(this.root, info.name) }\n  }\n\n  public async update(url?: string): Promise<string | undefined> {\n    if (url) this.url = url\n    let version: string | undefined\n    if (this.name) {\n      let folder = path.join(this.root, this.name)\n      if (isSymbolicLink(folder)) {\n        this.log(`Skipped update for symbol link`)\n        return\n      }\n      let obj = loadJson(path.join(folder, 'package.json')) as any\n      version = obj.version\n    }\n    this.log(`Using npm from: ${this.npm}`)\n    let info = await this.getInfo()\n    if (version && info.version && semver.gte(version, info.version)) {\n      this.log(`Current version ${version} is up to date.`)\n      return\n    }\n    let required = info['engines.coc'] ? info['engines.coc'].replace(/^\\^/, '>=') : ''\n    if (required && !semver.satisfies(workspace.version, required)) {\n      throw new Error(`${info.version} requires coc.nvim ${required}, please update coc.nvim.`)\n    }\n    let succeed = await this.doInstall(info, new Set())\n    if (!succeed) return\n    let jsonFile = path.join(this.root, info.name, 'package.json')\n    this.log(`Updated to v${info.version}`)\n    return path.dirname(jsonFile)\n  }\n\n  public getInstallArguments(exePath: string, url: string | undefined): { env: string, args: string[] } {\n    let env = 'production'\n    let args = ['install', '--ignore-scripts']\n    if (url && url.startsWith('https://github.com')) {\n      args = ['install']\n      env = 'development'\n    } else {\n      if (isNpmCommand(exePath)) {\n        args.push('--no-package-lock')\n        args.push('--omit=dev')\n        args.push('--legacy-peer-deps')\n        args.push('--no-global')\n      }\n      if (isYarn(exePath)) {\n        args.push('--no-lockfile')\n        args.push('--production')\n        args.push('--ignore-engines')\n      }\n      if (isPnpm(exePath)) {\n        args.push('--no-lockfile')\n        args.push('--production')\n        args.push('--config.strict-peer-dependencies=false')\n      }\n    }\n    return { env, args }\n  }\n\n  private readLines(key: string, stream: NodeJS.ReadableStream): void {\n    const rl = readline.createInterface({\n      input: stream\n    })\n    rl.on('line', line => {\n      this.log(`${key} ${line}`, true)\n    })\n  }\n\n  public installDependencies(folder: string, dependencies: string[]): Promise<void> {\n    if (dependencies.length == 0) return Promise.resolve()\n    return new Promise<void>((resolve, reject) => {\n      let { env, args } = this.getInstallArguments(this.npm, this.url)\n      this.log(`Installing dependencies by: ${this.npm} ${args.join(' ')}.`)\n      const cmd = process.platform === 'win32' && this.npm.includes(' ') ? `\"${this.npm}\"` : this.npm\n      const child = child_process.spawn(cmd, args, {\n        cwd: folder,\n        shell: process.platform === 'win32',\n        env: Object.assign(process.env, { NODE_ENV: env })\n      })\n      this.readLines('[npm stdout]', child.stdout)\n      this.readLines('[npm stderr]', child.stderr)\n      child.stderr.setEncoding('utf8')\n      child.stdout.setEncoding('utf8')\n      child.on('error', reject)\n      child.on('exit', code => {\n        if (code) {\n          reject(new Error(`${this.npm} install exited with ${code}`))\n          return\n        }\n        resolve()\n      })\n    })\n  }\n\n  public async doInstall(info: Info, installing: Set<string> = new Set()): Promise<boolean> {\n    let dest = path.join(this.root, info.name)\n    if (isSymbolicLink(dest)) return false\n    if (installing.has(info.name)) {\n      this.log(`Skipping circular dependency: ${info.name}`)\n      return false\n    }\n    installing.add(info.name)\n\n    let key = info.name.replace(/\\//g, '_')\n    let downloadFolder = path.join(this.root, `${key}-${uuid()}`)\n    let url = info['dist.tarball']\n    this.log(`Downloading from ${url}`)\n    let etagAlgorithm = url.startsWith('https://registry.npmjs.org') ? 'md5' : undefined\n    let obj: any\n    try {\n      await this.download(url, {\n        dest: downloadFolder,\n        etagAlgorithm,\n        extract: 'untar',\n        onProgress: p => this.log(`Download progress ${p}%`, true),\n      })\n      this.log(`Extension download at ${downloadFolder}`)\n      obj = loadJson(path.join(downloadFolder, 'package.json')) as any\n      await this.installDependencies(downloadFolder, getDependencies(obj))\n    } catch (e) {\n      fs.rmSync(downloadFolder, { recursive: true, force: true })\n      throw e\n    }\n    this.log(`Download extension ${info.name}@${info.version} at ${downloadFolder}`)\n    fs.mkdirSync(path.dirname(dest), { recursive: true })\n    if (fs.existsSync(dest)) fs.rmSync(dest, { force: true, recursive: true })\n    fs.renameSync(downloadFolder, dest)\n    this.log(`Move extension ${info.name}@${info.version} to ${dest}`)\n\n    const extensionDependencies = getExtensionDependencies(obj)\n    if (extensionDependencies.length > 0) {\n      this.log(`Installing extension dependencies: ${extensionDependencies.join(', ')}`)\n      for (const dependency of extensionDependencies) {\n        const installer = new Installer(this.root, this.npm, dependency)\n        installer.on('message', (msg, isProgress) => {\n          this.log(msg, isProgress)\n        })\n        await installer.doInstall(await installer.getInfo(), installing)\n      }\n    }\n\n    return true\n  }\n\n  public async download(url: string, options: DownloadOptions): Promise<any> {\n    return await download(url, options)\n  }\n\n  public async fetch(url: string | URL, options: FetchOptions = {}): Promise<any> {\n    return await fetch(url, options)\n  }\n}\n\nexport function getDependencies(obj: { dependencies?: { [key: string]: string } }): string[] {\n  return Object.keys(obj.dependencies ?? {}).filter(id => !local_dependencies.includes(id))\n}\n\nexport function getExtensionDependencies(obj: { extensionDependencies?: string[] }): string[] {\n  if (obj.extensionDependencies?.length > 0) {\n    return [...new Set(obj.extensionDependencies)]\n  }\n  return []\n}\n"
  },
  {
    "path": "src/extension/manager.ts",
    "content": "import { URI } from 'vscode-uri'\nimport { Extensions, IConfigurationNode, IConfigurationRegistry } from '../configuration/registry'\nimport { ConfigurationScope } from '../configuration/types'\nimport events from '../events'\nimport { createLogger } from '../logger'\nimport Memos from '../model/memos'\nimport { disposeAll, wait } from '../util'\nimport { splitArray, toArray } from '../util/array'\nimport { configHome, dataHome } from '../util/constants'\nimport { onUnexpectedError } from '../util/errors'\nimport { Extensions as ExtensionsInfo, IExtensionRegistry, IStringDictionary, getProperties } from '../util/extensionRegistry'\nimport { ExtensionExport, createExtension } from '../util/factory'\nimport { isDirectory, loadJson, remove, statAsync, watchFile } from '../util/fs'\nimport * as Is from '../util/is'\nimport type { IJSONSchema } from '../util/jsonSchema'\nimport { omit } from '../util/lodash'\nimport { path } from '../util/node'\nimport { deepClone, deepIterate, isEmpty } from '../util/object'\nimport { Disposable, Emitter, Event } from '../util/protocol'\nimport { Registry, convertProperties } from '../util/registry'\nimport { createTiming } from '../util/timing'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { ExtensionJson, ExtensionStat, getJsFiles, loadExtensionJson, validExtensionFolder } from './stat'\n\ninterface ExportExtension {\n  readonly name: string\n  readonly isActive: boolean\n  unload: () => Promise<void>\n  /**\n   * API returned by activate function\n   */\n  readonly api: any\n  /**\n   * The object of module.exports of the extension entry without activate & deactivate function.\n   */\n  readonly exports: any\n}\n\nexport type ExtensionState = 'disabled' | 'loaded' | 'activated' | 'unknown'\nconst logger = createLogger('extensions-manager')\n\nexport enum ExtensionType {\n  Global,\n  Local,\n  SingleFile,\n  Internal\n}\n\nexport enum ActivateEvents {\n  OnLanguage = 'onLanguage',\n  OnFileSystem = 'onFileSystem',\n  OnCommand = 'onCommand',\n  WorkspaceContains = 'workspaceContains',\n}\n\nexport interface ExtensionInfo {\n  id: string\n  version?: string\n  description?: string\n  root: string\n  exotic: boolean\n  uri?: string\n  state: ExtensionState\n  isLocal: boolean\n  isLocked: boolean\n  packageJSON: Readonly<ExtensionJson>\n}\n\nexport type ExtensionToLoad = Pick<Readonly<ExtensionInfo>, 'root' | 'packageJSON' | 'isLocal'>\n\nexport interface Extension<T> {\n  readonly id: string\n  readonly extensionPath: string\n  readonly extensionUri: URI\n  readonly isActive: boolean\n  readonly packageJSON: ExtensionJson\n  readonly exports: T\n  readonly module: object\n  activate(): Promise<T>\n}\n\nexport type API = { [index: string]: any } | void | null | undefined\n\nexport interface ExtensionItem {\n  readonly id: string\n  readonly type: ExtensionType\n  readonly events: ReadonlyArray<string>\n  extension: Extension<API>\n  deactivate: () => void | Promise<void>\n  filepath?: string\n  directory: string\n  readonly isLocal: boolean\n}\n\nconst extensionRegistry = Registry.as<IExtensionRegistry>(ExtensionsInfo.ExtensionContribution)\nconst memos = new Memos(path.resolve(dataHome, 'memos.json'))\nmemos.merge(path.resolve(dataHome, '../memos.json'))\n\nconst configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration)\n\n/**\n * Manage loaded extensions\n */\nexport class ExtensionManager {\n  private activated = false\n  private disposables: Disposable[] = []\n  public readonly configurationNodes: IConfigurationNode[] = []\n  private extensions: Map<string, ExtensionItem> = new Map()\n  private _onDidLoadExtension = new Emitter<Extension<API>>()\n  private _onDidActiveExtension = new Emitter<Extension<API>>()\n  private _onDidUnloadExtension = new Emitter<string>()\n  private singleExtensionsRoot = path.join(configHome, 'coc-extensions')\n  private modulesFolder: string\n\n  public readonly onDidLoadExtension: Event<Extension<API>> = this._onDidLoadExtension.event\n  public readonly onDidActiveExtension: Event<Extension<API>> = this._onDidActiveExtension.event\n  public readonly onDidUnloadExtension: Event<string> = this._onDidUnloadExtension.event\n  constructor(public readonly states: ExtensionStat, private folder: string) {\n    this.modulesFolder = path.join(this.folder, 'node_modules')\n  }\n\n  public activateExtensions(): Promise<PromiseSettledResult<void>[]> {\n    this.activated = true\n    if (process.env.COC_NO_PLUGINS == '1') return\n    configurationRegistry.registerConfigurations(this.configurationNodes)\n    this.attachEvents()\n    let promises: Promise<void>[] = []\n    for (let key of this.extensions.keys()) {\n      // wait extensions that always activated only\n      const { extension } = this.extensions.get(key)\n      const activationEvents = extension.packageJSON.activationEvents\n      if (!activationEvents || activationEvents.includes('*')) {\n        promises.push(void this.activate(key))\n      } else {\n        void this.autoActivate(key, extension)\n      }\n    }\n    return Promise.allSettled(promises)\n  }\n\n  public async loadFileExtensions(): Promise<void> {\n    let folder = this.singleExtensionsRoot\n    let files = await getJsFiles(folder)\n    await Promise.allSettled(files.map(file => {\n      return this.loadExtensionFile(path.join(folder, file))\n    }))\n  }\n\n  public attachEvents(): void {\n    workspace.onDidRuntimePathChange(async paths => {\n      let folders = paths.filter(p => p && validExtensionFolder(p, workspace.version))\n      let outputChannel = window.createOutputChannel('extensions')\n      await Promise.allSettled(folders.map(folder => {\n        outputChannel.appendLine(`Loading extension from runtimepath: ${folder}`)\n        return this.loadExtension(folder)\n      }))\n    }, null, this.disposables)\n    workspace.onDidOpenTextDocument(document => {\n      let doc = workspace.getDocument(document.bufnr)\n      this.tryActivateExtensions(ActivateEvents.OnLanguage, events => {\n        return checkLanguageId(doc, events)\n      })\n      this.tryActivateExtensions(ActivateEvents.OnFileSystem, events => {\n        return checkFileSystem(doc.uri, events)\n      })\n    }, null, this.disposables)\n    events.on('Command', async command => {\n      let fired = false\n      this.tryActivateExtensions(ActivateEvents.OnCommand, events => {\n        let result = checkCommand(command, events)\n        if (result) fired = true\n        return result\n      })\n      if (fired) await wait(50)\n    }, null, this.disposables)\n    workspace.onDidChangeWorkspaceFolders(e => {\n      if (e.added.length > 0) {\n        this.tryActivateExtensions(ActivateEvents.WorkspaceContains, events => {\n          let patterns = toWorkspaceContainsPatterns(events)\n          return workspace.checkPatterns(patterns, e.added)\n        })\n      }\n    }, null, this.disposables)\n  }\n\n  /**\n   * Unload & remove all global extensions, return removed extensions.\n   */\n  public async cleanExtensions(): Promise<string[]> {\n    let { globalIds } = this.states\n    await remove(this.modulesFolder)\n    return globalIds.filter(id => !this.states.isDisabled(id))\n  }\n\n  public tryActivateExtensions(event: string, check: (activationEvents: string[]) => boolean | Promise<boolean>): void {\n    for (let item of this.extensions.values()) {\n      if (item.extension.isActive) continue\n      let events = item.events\n      if (!events.includes(event)) continue\n      let { extension } = item\n      let activationEvents = getActivationEvents(extension.packageJSON)\n      void Promise.resolve(check(activationEvents)).then(checked => {\n        if (checked) void Promise.resolve(this.activate(extension.id))\n      })\n    }\n  }\n\n  private async checkAutoActivate(packageJSON: ExtensionJson): Promise<boolean> {\n    let activationEvents = getActivationEvents(packageJSON)\n    if (activationEvents.length === 0 || activationEvents.includes('*')) {\n      return true\n    }\n    let patterns: string[] = []\n    for (let eventName of activationEvents as string[]) {\n      let parts = eventName.split(':')\n      let ev = parts[0]\n      if (ev === ActivateEvents.OnLanguage) {\n        if (workspace.languageIds.has(parts[1]) || workspace.filetypes.has(parts[1])) {\n          return true\n        }\n      } else if (ev === ActivateEvents.WorkspaceContains && parts[1]) {\n        patterns.push(parts[1])\n      } else if (ev === ActivateEvents.OnFileSystem) {\n        for (let doc of workspace.documents) {\n          let u = URI.parse(doc.uri)\n          if (u.scheme == parts[1]) {\n            return true\n          }\n        }\n      }\n    }\n    if (patterns.length > 0) {\n      let res = await workspace.checkPatterns(patterns)\n      if (res) return true\n    }\n    return false\n  }\n\n  public has(id: string): boolean {\n    return this.extensions.has(id)\n  }\n\n  public getExtension(id: string): ExtensionItem | undefined {\n    return this.extensions.get(id)\n  }\n\n  public get loadedExtensions(): string[] {\n    return Array.from(this.extensions.keys())\n  }\n\n  public get all(): Extension<API>[] {\n    return Array.from(this.extensions.values()).map(o => o.extension)\n  }\n\n  /**\n   * Activate extension, throw error if disabled or doesn't exist.\n   * Returns true if extension successfully activated.\n   */\n  public async activate(id: string, activating: Set<string> = new Set()): Promise<boolean> {\n    let item = this.extensions.get(id)\n    if (!item) throw new Error(`Extension ${id} not registered!`)\n    let { extension } = item\n    if (extension.isActive) return true\n    if (activating.has(id)) {\n      logger.warn(`Circular dependency detected: ${id}`)\n      return false\n    }\n    activating = new Set([...activating, id])\n    const { packageJSON } = extension\n    if (packageJSON.extensionDependencies?.length > 0) {\n      const results = await Promise.allSettled(packageJSON.extensionDependencies.map(dep => this.activate(dep, activating)))\n      for (const result of results) {\n        if (result.status === 'rejected' || (result.status === 'fulfilled' && !result.value)) {\n          logger.error(`Could not activate dependency for ${id}, activation failed.`)\n          return false\n        }\n      }\n    }\n\n    await extension.activate()\n    return extension.isActive === true\n  }\n\n  public async deactivate(id: string): Promise<void> {\n    let item = this.extensions.get(id)\n    if (!item || !item.extension.isActive) return\n    await Promise.resolve(item.deactivate())\n  }\n\n  /**\n   * Load extension from folder, folder should contains coc extension.\n   */\n  public async loadExtension(folder: string | string[], noActive = false): Promise<boolean> {\n    if (Array.isArray(folder)) {\n      let results = await Promise.allSettled(folder.map(f => {\n        return this.loadExtension(f, noActive)\n      }))\n      results.forEach(res => {\n        if (res.status === 'rejected') throw new Error(`Error on loadExtension ${res.reason}`)\n      })\n      return true\n    }\n    let errors: string[] = []\n    let obj = loadExtensionJson(folder, workspace.version, errors)\n    if (errors.length > 0) throw new Error(errors[0])\n    let { name } = obj\n    if (this.states.isDisabled(name)) return false\n    // unload if loaded\n    await this.unloadExtension(name)\n    let isLocal = !this.states.hasExtension(name)\n    if (isLocal) this.states.addLocalExtension(name, folder)\n    await this.registerExtension(folder, Object.freeze(obj), isLocal ? ExtensionType.Local : ExtensionType.Global, noActive)\n    return true\n  }\n\n  /**\n   * Deactivate & unregist extension\n   */\n  public async unloadExtension(id: string): Promise<void> {\n    let item = this.extensions.get(id)\n    if (item) {\n      await this.deactivate(id)\n      this.extensions.delete(id)\n      this._onDidUnloadExtension.fire(id)\n    }\n  }\n\n  public async reloadExtension(id: string): Promise<void> {\n    let item = this.extensions.get(id)\n    if (!item || item.type == ExtensionType.Internal) {\n      throw new Error(`Extension ${id} not registered`)\n    }\n    if (item.type == ExtensionType.SingleFile) {\n      await this.loadExtensionFile(item.filepath)\n    } else {\n      await this.loadExtension(item.directory)\n    }\n  }\n\n  public async call(id: string, method: string, args: any[]): Promise<any> {\n    let item = this.extensions.get(id)\n    if (!item) throw new Error(`extension ${id} not registered`)\n    let { extension } = item\n    if (!extension.isActive) {\n      await this.activate(id)\n    }\n    let { exports } = extension\n    if (!exports || typeof exports[method] !== 'function') {\n      throw new Error(`method ${method} not found on extension ${id}`)\n    }\n    return await Promise.resolve(exports[method].apply(null, args))\n  }\n\n  public registContribution(id: string, packageJSON: any, directory: string, filepath?: string): void {\n    let { contributes, activationEvents } = packageJSON\n    let { configuration, rootPatterns, commands } = contributes ?? {}\n    let definitions: IStringDictionary<IJSONSchema> | undefined\n    let props = getProperties(configuration ?? {})\n    if (!isEmpty(props)) {\n      // /configuration\n      let properties = convertProperties(props, ConfigurationScope.WINDOW)\n      if (Is.objectLiteral(configuration.definitions)) {\n        let prefix = id.replace(/[^\\w]/g, '')\n        const addPrefix = (obj: object, key: string) => {\n          if (key == '$ref') {\n            let val = obj[key]\n            if (Is.string(val) && val.startsWith('#/definitions/')) {\n              obj[key] = val.slice(0, 14) + prefix + '.' + val.slice(14)\n            }\n          }\n        }\n        deepIterate(properties, addPrefix)\n        definitions = {}\n        Object.entries(deepClone(configuration.definitions)).forEach(([key, val]) => {\n          if (Is.objectLiteral(val)) {\n            definitions[prefix + '.' + key] = val\n            deepIterate(val, addPrefix)\n          }\n        })\n      }\n      let node: IConfigurationNode = { properties, extensionInfo: { id, displayName: packageJSON.displayName } }\n      this.configurationNodes.push(node)\n      if (this.activated) {\n        let toRemove = []\n        let idx = this.configurationNodes.findIndex(o => o.extensionInfo!.id === id)\n        if (idx !== -1) {\n          toRemove.push(this.configurationNodes[idx])\n          this.configurationNodes.splice(idx, 1)\n        }\n        workspace.configurations.updateConfigurations([node], toRemove)\n      }\n    }\n    extensionRegistry.registerExtension(id, {\n      name: id,\n      directory,\n      filepath,\n      commands,\n      definitions,\n      rootPatterns,\n      onCommands: getOnCommandList(activationEvents)\n    })\n  }\n\n  public getExtensionState(id: string): ExtensionState {\n    let disabled = this.states.isDisabled(id)\n    if (disabled) return 'disabled'\n    let item = this.getExtension(id)\n    if (!item) return 'unknown'\n    let { extension } = item\n    return extension.isActive ? 'activated' : 'loaded'\n  }\n\n  public async autoActivate(id: string, extension: Extension<API>): Promise<void> {\n    try {\n      let checked = await this.checkAutoActivate(extension.packageJSON)\n      if (checked) await Promise.resolve(this.activate(id))\n    } catch (e) {\n      logger.error(`Error on activate ${id}`, e)\n    }\n  }\n\n  public async loadExtensionFile(filepath: string, noActive = false): Promise<string> {\n    let stat = await statAsync(filepath)\n    if (!stat || !stat.isFile()) return\n    let filename = path.basename(filepath)\n    let basename = path.basename(filepath, '.js')\n    let name = 'single-' + basename\n    let root = path.dirname(filepath)\n    let packageJSON = { name, main: filename, engines: { coc: '>=0.0.82' } }\n    let confpath = path.join(root, basename + '.json')\n    let obj = loadJson(confpath) as any\n    for (const attr of ['activationEvents', 'contributes']) {\n      packageJSON[attr] = obj[attr]\n    }\n    await this.unloadExtension(name)\n    await this.registerExtension(root, packageJSON, ExtensionType.SingleFile, noActive)\n    return name\n  }\n\n  public registerExtensions(stats: ExtensionToLoad[]): void {\n    for (let stat of stats) {\n      try {\n        let extensionType = stat.isLocal ? ExtensionType.Local : ExtensionType.Global\n        void this.registerExtension(stat.root, stat.packageJSON, extensionType)\n      } catch (e) {\n        logger.error(`Error on regist extension from ${stat.root}: `, e)\n      }\n    }\n  }\n\n  public async registerExtension(root: string, packageJSON: ExtensionJson, extensionType: ExtensionType, noActive = false): Promise<void> {\n    let id = packageJSON.name\n    if (this.states.isDisabled(id)) return\n    let isActive = false\n    let result: Promise<API> | undefined\n    let filename = path.join(root, packageJSON.main || 'index.js')\n    let extensionPath = extensionType === ExtensionType.SingleFile ? filename : root\n    let exports: any\n    let ext: ExtensionExport\n    let subscriptions: Disposable[] = []\n    const timing = createTiming(`activate ${id}`, 5000)\n    let extension: Extension<API> = {\n      activate: (): Promise<API> => {\n        if (result) return result\n        result = new Promise(async (resolve, reject) => {\n          timing.start()\n          try {\n            let isEmpty = typeof packageJSON.engines.coc === 'undefined'\n            ext = createExtension(id, filename, isEmpty)\n            let context = {\n              subscriptions,\n              extensionPath,\n              globalState: memos.createMemento(`${id}|global`),\n              workspaceState: memos.createMemento(`${id}|${workspace.rootPath}`),\n              asAbsolutePath: relativePath => path.join(root, relativePath),\n              storagePath: path.join(this.folder, `${id}-data`),\n              logger: createLogger(`extension:${id}`)\n            }\n            let res = await Promise.resolve(ext.activate(context))\n            isActive = true\n            exports = res\n            this._onDidActiveExtension.fire(extension)\n            timing.stop()\n            resolve(res)\n          } catch (e) {\n            logger.error(`Error on active extension ${id}:`, e)\n            reject(e as Error)\n          }\n        })\n        return result\n      },\n      id,\n      packageJSON,\n      extensionPath,\n      extensionUri: URI.parse(extensionPath),\n      get isActive() {\n        return isActive\n      },\n      get module() {\n        return ext\n      },\n      get exports() {\n        if (!isActive) throw new Error(`Invalid access to exports, extension \"${id}\" not activated`)\n        return exports\n      }\n    }\n    Object.freeze(extension)\n    this.extensions.set(id, {\n      id,\n      type: extensionType,\n      isLocal: extensionType == ExtensionType.Local,\n      extension,\n      directory: root,\n      filepath: filename,\n      events: getEvents(packageJSON.activationEvents),\n      deactivate: async () => {\n        if (!isActive) return\n        isActive = false\n        result = undefined\n        exports = undefined\n        disposeAll(subscriptions)\n        if (ext && typeof ext.deactivate === 'function') {\n          try {\n            await Promise.resolve(ext.deactivate())\n            ext = undefined\n          } catch (e) {\n            logger.error(`Error on ${id} deactivate: `, e)\n          }\n        }\n      }\n    })\n    this.registContribution(id, packageJSON, root, filename)\n    this._onDidLoadExtension.fire(extension)\n    if (this.activated && !noActive) await this.autoActivate(id, extension)\n  }\n\n  public unregistContribution(id: string): void {\n    let idx = this.configurationNodes.findIndex(o => o.extensionInfo!.id === id)\n    extensionRegistry.unregistExtension(id)\n    if (idx !== -1) {\n      let node = this.configurationNodes[idx]\n      this.configurationNodes.splice(idx, 1)\n      configurationRegistry.deregisterConfigurations([node])\n    }\n  }\n\n  public async registerInternalExtension(extension: Extension<API>, deactivate?: () => void): Promise<void> {\n    let { id, packageJSON } = extension\n    this.extensions.set(id, {\n      id,\n      directory: __dirname,\n      type: ExtensionType.Internal,\n      events: getEvents(packageJSON.activationEvents),\n      extension,\n      deactivate,\n      isLocal: true\n    })\n    this.registContribution(id, packageJSON, __dirname)\n    this._onDidLoadExtension.fire(extension)\n    await this.autoActivate(id, extension)\n  }\n\n  /**\n   * Only global extensions can be uninstalled\n   */\n  public async uninstallExtensions(ids: string[]): Promise<void> {\n    let [globals, filtered] = splitArray(ids, id => this.states.hasExtension(id))\n    for (let id of globals) {\n      await this.unloadExtension(id)\n      this.states.removeExtension(id)\n      extensionRegistry.unregistExtension(id)\n      await remove(path.join(this.modulesFolder, id))\n    }\n    if (filtered.length > 0) {\n      void window.showWarningMessage(`Global extensions ${filtered.join(', ')} not found`)\n    }\n    if (globals.length > 0) {\n      void window.showInformationMessage(`Removed extensions: ${globals.join(' ')}`)\n    }\n  }\n\n  public async toggleExtension(id: string): Promise<void> {\n    let state = this.getExtensionState(id)\n    if (state == 'activated') await this.deactivate(id)\n    if (state != 'disabled') {\n      this.states.setDisable(id, true)\n      this.unregistContribution(id)\n      await this.unloadExtension(id)\n    } else {\n      this.states.setDisable(id, false)\n      if (id.startsWith('single-')) {\n        let filepath = path.join(this.singleExtensionsRoot, `${id.replace(/^single-/, '')}.js`)\n        await this.loadExtensionFile(filepath)\n      } else {\n        let folder = this.states.getFolder(id)\n        if (folder) {\n          await this.loadExtension(folder)\n        } else {\n          void window.showWarningMessage(`Extension ${id} not found`)\n        }\n      }\n    }\n  }\n\n  public async watchExtension(id: string): Promise<void> {\n    let item = this.getExtension(id)\n    if (!item) throw new Error(`extension ${id} not found`)\n    if (id.startsWith('single-')) {\n      void window.showInformationMessage(`watching ${item.filepath}`)\n      this.disposables.push(watchFile(item.filepath, async () => {\n        await this.loadExtensionFile(item.filepath)\n        void window.showInformationMessage(`reloaded ${id}`)\n      }, global.__TEST__ === true))\n    } else {\n      let client = await workspace.fileSystemWatchers.createClient(item.directory, true)\n      if (!client) throw new Error('watchman not found')\n      void window.showInformationMessage(`watching ${item.directory}`)\n      client.subscribe('**/*.js', async () => {\n        this.reloadExtension(id).then(() => {\n          void window.showInformationMessage(`reloaded ${id}`)\n        }, onUnexpectedError)\n      })\n    }\n  }\n\n  /**\n   * load extension in folder or file\n   */\n  public async load(filepath: string, active: boolean): Promise<ExportExtension> {\n    let name: string\n    if (isDirectory(filepath)) {\n      let obj = loadJson(path.join(filepath, 'package.json')) as any\n      name = obj.name\n      await this.loadExtension(filepath, true)\n    } else {\n      name = await this.loadExtensionFile(filepath, true)\n    }\n    if (!name) throw new Error(`Unable to load extension at ${filepath}`)\n    let disabled = this.states.isDisabled(name)\n    if (disabled) throw new Error(`extension ${name} is disabled`)\n    let item = this.getExtension(name)\n    if (active) await this.activate(name)\n    return {\n      get isActive() {\n        return item.extension.isActive\n      },\n      get name() {\n        return name\n      },\n      get api() {\n        return item.extension.exports\n      },\n      get exports() {\n        let module = item.extension.module ?? {}\n        return omit(module, ['activate'])\n      },\n      unload: () => {\n        return this.unloadExtension(name)\n      }\n    }\n  }\n\n  public dispose(): void {\n    disposeAll(this.disposables)\n  }\n}\n\nexport function getEvents(activationEvents: string[] | undefined): string[] {\n  let res: string[] = []\n  for (let ev of toArray(activationEvents)) {\n    let [name] = ev.split(':', 2)\n    if (name && !res.includes(name)) res.push(name)\n  }\n  return res\n}\n\nexport function getOnCommandList(activationEvents: string[] | undefined): string[] {\n  let res: string[] = []\n  for (let ev of toArray(activationEvents)) {\n    let [name, command] = ev.split(':', 2)\n    if (name === ActivateEvents.OnCommand && command) res.push(command)\n  }\n  return res\n}\n\nexport function checkLanguageId(document: { languageId: string, filetype: string }, activationEvents: string[]): boolean {\n  for (let eventName of activationEvents as string[]) {\n    let parts = eventName.split(':')\n    let ev = parts[0]\n    if (ev == ActivateEvents.OnLanguage && (document.languageId == parts[1] || document.filetype == parts[1])) {\n      return true\n    }\n  }\n  return false\n}\n\nexport function checkCommand(command: string, activationEvents: string[]): boolean {\n  for (let eventName of activationEvents as string[]) {\n    let parts = eventName.split(':')\n    let ev = parts[0]\n    if (ev == ActivateEvents.OnCommand && command == parts[1]) {\n      return true\n    }\n  }\n  return false\n}\n\nexport function checkFileSystem(uri: string, activationEvents: string[]): boolean {\n  let scheme = URI.parse(uri).scheme\n  for (let eventName of activationEvents as string[]) {\n    let parts = eventName.split(':')\n    let ev = parts[0]\n    if (ev == ActivateEvents.OnFileSystem && scheme == parts[1]) {\n      return true\n    }\n  }\n  return false\n}\n\nexport function getActivationEvents(json: ExtensionJson): string[] {\n  return toArray(json.activationEvents).filter(key => typeof key === 'string' && key.length > 0)\n}\n\n/**\n * Convert globl patterns\n */\nexport function toWorkspaceContainsPatterns(activationEvents: string[]): string[] {\n  let patterns: string[] = []\n  for (let eventName of activationEvents) {\n    let parts = eventName.split(':')\n    if (parts[0] == ActivateEvents.WorkspaceContains && parts[1]) {\n      patterns.push(parts[1])\n    }\n  }\n  return patterns\n}\n"
  },
  {
    "path": "src/extension/stat.ts",
    "content": "import { createLogger } from '../logger'\nimport { toArray } from '../util/array'\nimport { readFile, writeJson } from '../util/fs'\nimport { objectLiteral } from '../util/is'\nimport { fs, path, promisify, semver } from '../util/node'\nimport { toObject } from '../util/object'\nconst logger = createLogger('extension-stat')\n\ninterface DataBase {\n  extension?: {\n    [key: string]: {\n      disabled?: boolean\n      locked?: boolean\n    }\n  },\n  lastUpdate?: number\n}\n\ninterface PackageJson {\n  disabled?: string[]\n  locked?: string[]\n  lastUpdate?: number\n  dependencies?: {\n    [key: string]: string\n  }\n}\n\nexport interface ExtensionJson {\n  name: string\n  main?: string\n  engines: {\n    [key: string]: string\n  }\n  activationEvents?: string[]\n  extensionDependencies?: string[]\n  version?: string\n  [key: string]: any\n}\n\nexport enum ExtensionStatus {\n  Normal,\n  Disabled,\n  Locked,\n}\n\nconst ONE_DAY = 24 * 60 * 60 * 1000\nconst DISABLE_PROMPT_KEY = 'disablePrompt'\n\n/**\n * Stat for global extensions\n */\nexport class ExtensionStat {\n  private disabled: Set<string> = new Set()\n  private locked: Set<string> = new Set()\n  private extensions: Set<string> = new Set()\n  private localExtensions: Map<string, string> = new Map()\n  constructor(private folder: string) {\n    try {\n      this.migrate()\n    } catch (e) {\n      logger.error(`Error on update package.json at ${folder}`, e)\n    }\n  }\n\n  private migrate(): void {\n    let curr = loadJson(this.jsonFile) as PackageJson\n    let db = path.join(this.folder, 'db.json')\n    let changed = false\n    if (fs.existsSync(db)) {\n      let obj = loadJson(db) as DataBase\n      let def = obj.extension ?? {}\n      for (let [key, o] of Object.entries(def)) {\n        if (o.disabled) this.disabled.add(key)\n        if (o.locked) this.locked.add(key)\n      }\n      curr.disabled = Array.from(this.disabled)\n      curr.locked = Array.from(this.locked)\n      curr.lastUpdate = obj.lastUpdate\n      fs.unlinkSync(db)\n      changed = true\n    } else {\n      this.disabled = new Set(curr.disabled ?? [])\n      this.locked = new Set(curr.locked ?? [])\n    }\n    if (changed) writeJson(this.jsonFile, curr)\n    let ids = Object.keys(curr.dependencies ?? {})\n    this.extensions = new Set(ids)\n  }\n\n  public addNoPromptFolder(uri: string): void {\n    let curr = loadJson(this.jsonFile) as PackageJson\n    curr[DISABLE_PROMPT_KEY] = curr[DISABLE_PROMPT_KEY] ?? []\n    curr[DISABLE_PROMPT_KEY].push(uri)\n    writeJson(this.jsonFile, curr)\n  }\n\n  public shouldPrompt(uri: string): boolean {\n    let curr = loadJson(this.jsonFile) as PackageJson\n    let arr = curr[DISABLE_PROMPT_KEY] as string[] ?? []\n    return !arr.includes(uri)\n  }\n\n  public reset(): void {\n    writeJson(this.jsonFile, {})\n  }\n\n  public *activated(): Iterable<string> {\n    let { disabled } = this\n    for (let key of Object.keys(this.dependencies)) {\n      if (!disabled.has(key)) {\n        yield key\n      }\n    }\n  }\n\n  public addLocalExtension(name: string, folder: string): void {\n    this.localExtensions.set(name, folder)\n  }\n\n  public getFolder(name: string): string | undefined {\n    if (this.extensions.has(name)) return path.join(this.folder, 'node_modules', name)\n    return this.localExtensions.get(name)\n  }\n\n  public getExtensionsStat(): Record<string, ExtensionStatus> {\n    let res: Record<string, ExtensionStatus> = {}\n    for (let id of this.extensions) {\n      if (this.disabled.has(id)) {\n        res[id] = ExtensionStatus.Disabled\n      } else if (this.locked.has(id)) {\n        res[id] = ExtensionStatus.Locked\n      } else {\n        res[id] = ExtensionStatus.Normal\n      }\n    }\n    return res\n  }\n\n  public hasExtension(id: string): boolean {\n    return this.extensions.has(id)\n  }\n\n  public addExtension(id: string, val: string): void {\n    let curr = loadJson(this.jsonFile) as PackageJson\n    curr.dependencies = curr.dependencies ?? {}\n    curr.dependencies[id] = val\n    this.extensions.add(id)\n    writeJson(this.jsonFile, curr)\n  }\n\n  public removeExtension(id: string): void {\n    let curr = loadJson(this.jsonFile) as PackageJson\n    if (curr.disabled) curr.disabled = curr.disabled.filter(key => key !== id)\n    if (curr.locked) curr.locked = curr.locked.filter(key => key !== id)\n    curr.dependencies = curr.dependencies ?? {}\n    delete curr.dependencies[id]\n    this.extensions.delete(id)\n    writeJson(this.jsonFile, curr)\n  }\n\n  public isDisabled(id: string): boolean {\n    return this.disabled.has(id)\n  }\n\n  public get lockedExtensions(): string[] {\n    return Array.from(this.locked)\n  }\n\n  public get disabledExtensions(): string[] {\n    return Array.from(this.disabled)\n  }\n\n  public get dependencies(): { [key: string]: string } {\n    let curr = loadJson(this.jsonFile) as PackageJson\n    return curr.dependencies ?? {}\n  }\n\n  public setDisable(id: string, disable: boolean): void {\n    if (disable) {\n      this.disabled.add(id)\n    } else {\n      this.disabled.delete(id)\n    }\n    this.update('disabled', Array.from(this.disabled))\n  }\n\n  public setLocked(id: string, locked: boolean): void {\n    if (locked) {\n      this.locked.add(id)\n    } else {\n      this.locked.delete(id)\n    }\n    this.update('locked', Array.from(this.disabled))\n  }\n\n  public setLastUpdate(): void {\n    this.update('lastUpdate', Date.now())\n  }\n\n  public shouldUpdate(opt: string): boolean {\n    if (opt === 'never') return false\n    let interval = toInterval(opt)\n    let curr = loadJson(this.jsonFile) as PackageJson\n    return curr.lastUpdate == null || (Date.now() - curr.lastUpdate) > interval\n  }\n\n  public get globalIds(): ReadonlyArray<string> {\n    let curr = loadJson(this.jsonFile) as PackageJson\n    return Object.keys(curr.dependencies ?? {})\n  }\n\n  /**\n   * Filter out global extensions that needs install\n   */\n  public filterGlobalExtensions(names: string[] | undefined): string[] {\n    let disabledExtensions = this.disabledExtensions\n    let dependencies = this.dependencies\n    let map: Map<string, string> = new Map()\n    toArray(names).forEach(def => {\n      if (!def || typeof def !== 'string') return\n      let name = getExtensionName(def)\n      map.set(name, def)\n    })\n    let currentUrls: string[] = []\n    let exists: string[] = []\n    for (let [key, val] of Object.entries(dependencies)) {\n      if (fs.existsSync(path.join(this.folder, 'node_modules', key, 'package.json'))) {\n        exists.push(key)\n        if (typeof val === 'string' && /^https?:/.test(val)) {\n          currentUrls.push(val)\n        }\n      }\n    }\n    for (let name of map.keys()) {\n      if (disabledExtensions.includes(name) || this.extensions.has(name)) {\n        map.delete(name)\n        continue\n      }\n      if ((/^https?:/.test(name) && currentUrls.some(url => url.startsWith(name))) || exists.includes(name)) {\n        map.delete(name)\n      }\n    }\n    return Array.from(map.values())\n  }\n\n  private update(key: keyof PackageJson, value: any): void {\n    let curr = loadJson(this.jsonFile) as PackageJson\n    curr[key] = value\n    writeJson(this.jsonFile, curr)\n  }\n\n  private get jsonFile(): string {\n    return path.join(this.folder, 'package.json')\n  }\n}\n\nexport function toInterval(opt: string): number {\n  return opt === 'daily' ? ONE_DAY : ONE_DAY * 7\n}\n\nexport function validExtensionFolder(folder: string, version: string): boolean {\n  let errors: string[] = []\n  let res = loadExtensionJson(folder, version, errors)\n  return res != null && errors.length == 0\n}\n\nfunction getEntryFile(main: string | undefined): string {\n  if (!main) return 'index.js'\n  if (!main.endsWith('.js')) return main + '.js'\n  return main\n}\n\nexport async function loadGlobalJsonAsync(folder: string, version: string): Promise<ExtensionJson> {\n  let jsonFile = path.join(folder, 'package.json')\n  let content = await readFile(jsonFile, 'utf8')\n  let packageJSON = JSON.parse(content) as ExtensionJson\n  let { engines } = packageJSON\n  let main = getEntryFile(packageJSON.main)\n  if (!engines || (typeof engines.coc !== 'string' && typeof engines.vscode !== 'string')) throw new Error('Invalid engines field')\n  let keys = Object.keys(engines)\n  if (keys.includes('coc') && !semver.satisfies(version, engines['coc'].replace(/^\\^/, '>='))) {\n    throw new Error(`coc.nvim version not match, required ${engines['coc']}`)\n  }\n  if (!engines.vscode && !fs.existsSync(path.join(folder, main))) {\n    throw new Error(`main file ${main} not found, you may need to build the project.`)\n  }\n  return packageJSON\n}\n\nexport function loadExtensionJson(folder: string, version: string, errors: string[]): ExtensionJson | undefined {\n  let jsonFile = path.join(folder, 'package.json')\n  if (!fs.existsSync(jsonFile)) {\n    errors.push(`package.json not found in ${folder}`)\n    return undefined\n  }\n  let packageJSON = loadJson(jsonFile) as ExtensionJson\n  let { name, engines } = packageJSON\n  let main = getEntryFile(packageJSON.main)\n  if (!name) errors.push(`can't find name in package.json`)\n  if (!engines || !objectLiteral(engines)) {\n    errors.push(`invalid engines in ${jsonFile}`)\n  }\n  if (engines && !engines.vscode && !fs.existsSync(path.join(folder, main))) {\n    errors.push(`main file ${main} not found, you may need to build the project.`)\n  }\n  if (engines) {\n    let keys = Object.keys(engines)\n    if (!keys.includes('coc') && !keys.includes('vscode')) {\n      errors.push(`Engines in package.json doesn't have coc or vscode`)\n    }\n    if (keys.includes('coc')) {\n      let required = engines['coc'].replace(/^\\^/, '>=')\n      if (!semver.satisfies(version, required)) {\n        errors.push(`Please update coc.nvim, ${packageJSON.name} requires coc.nvim ${engines['coc']}`)\n      }\n    }\n  }\n  return packageJSON\n}\n\n/**\n * Name of extension\n */\nexport function getExtensionName(def: string): string {\n  if (/^https?:/.test(def)) return def\n  if (!def.includes('@')) return def\n  return def.replace(/@[\\d.]+$/, '')\n}\n\nexport function checkExtensionRoot(root: string): boolean {\n  try {\n    if (!fs.existsSync(root)) {\n      fs.mkdirSync(root, { recursive: true })\n    }\n    let stat = fs.statSync(root)\n    if (!stat.isDirectory()) {\n      logger.info(`Trying to delete ${root}`)\n      fs.unlinkSync(root)\n      fs.mkdirSync(root, { recursive: true })\n    }\n    let jsonFile = path.join(root, 'package.json')\n    if (!fs.existsSync(jsonFile)) {\n      fs.writeFileSync(jsonFile, '{\"dependencies\":{}}', 'utf8')\n    }\n  } catch (e) {\n    console.error(`Unexpected error when check data home ${root}: ${e}`)\n    return false\n  }\n  return true\n}\n\nexport async function getJsFiles(folder: string): Promise<string[]> {\n  if (!fs.existsSync(folder)) return []\n  let files = await promisify(fs.readdir)(folder)\n  return files.filter(f => f.endsWith('.js'))\n}\n\nfunction loadJson(filepath: string): object {\n  try {\n    let text = fs.readFileSync(filepath, 'utf8')\n    let data = JSON.parse(text)\n    return toObject(data)\n  } catch (e) {\n    logger.error(`Error on parse json file ${filepath}`, e)\n    return {}\n  }\n}\n"
  },
  {
    "path": "src/extension/ui.ts",
    "content": "'use strict'\nimport events from '../events'\nimport { frames } from '../model/status'\nimport { HighlightItem, OutputChannel } from '../types'\nimport { disposeAll, getConditionValue } from '../util'\nimport { debounce } from '../util/node'\nimport { Disposable } from '../util/protocol'\nimport { byteLength } from '../util/string'\nimport window from '../window'\nimport workspace from '../workspace'\n\nconst interval = getConditionValue(100, 1)\n\nexport enum State {\n  Waiting,\n  Failed,\n  Progressing,\n  Success,\n}\n\ninterface InstallSettings {\n  isUpdate: boolean\n  updateUIInTab?: boolean\n}\n\nexport interface InstallUI {\n  start(names: string[]): void | Promise<void>\n  addMessage(name: string, msg: string, isProgress?: boolean): void\n  startProgress(name: string): void\n  finishProgress(name: string, succeed?: boolean): void\n}\n\nexport class InstallChannel implements InstallUI {\n  constructor(private settings: InstallSettings, private channel: OutputChannel) {\n  }\n\n  private get isUpdate(): boolean {\n    return this.settings.isUpdate\n  }\n\n  public getText(): string {\n    return this.isUpdate ? 'update' : 'install'\n  }\n\n  public start(names: string[]): void {\n    this.channel.appendLine(`${this.isUpdate ? 'Updating' : 'Installing'} ${names.join(', ')}`)\n  }\n\n  public addMessage(name: string, msg: string, isProgress?: boolean): void {\n    if (!isProgress) {\n      this.channel.appendLine(`${name} - ${msg}`)\n    }\n  }\n\n  public startProgress(name: string): void {\n    this.channel.appendLine(`Start ${this.getText()} ${name}`)\n  }\n\n  public finishProgress(name: string, succeed?: boolean): void {\n    if (succeed) {\n      this.channel.appendLine(`${name} ${this.getText()} succeed!`)\n    } else {\n      this.channel.appendLine(`${name} ${this.getText()} failed!`)\n    }\n  }\n}\n\nconst debounceTime = getConditionValue(500, 10)\n\nexport class InstallBuffer implements InstallUI {\n  private statMap: Map<string, State> = new Map()\n  private updated: Set<string> = new Set()\n  private messagesMap: Map<string, string[]> = new Map()\n  private disposables: Disposable[] = []\n  private names: string[] = []\n  private interval: NodeJS.Timeout\n  public bufnr: number\n\n  constructor(private settings: InstallSettings) {\n    let floatFactory = window.createFloatFactory({ modes: ['n'] })\n    this.disposables.push(floatFactory)\n    let fn = debounce(async (bufnr, cursor) => {\n      if (bufnr == this.bufnr) {\n        let msgs = this.getMessages(cursor[0] - 1)\n        let docs = msgs.length > 0 ? [{ content: msgs.join('\\n'), filetype: 'txt' }] : []\n        await floatFactory.show(docs)\n      }\n    }, debounceTime)\n    this.disposables.push(Disposable.create(() => {\n      fn.clear()\n    }))\n    events.on('CursorMoved', fn, this.disposables)\n    events.on('BufUnload', bufnr => {\n      if (bufnr === this.bufnr) {\n        this.dispose()\n      }\n    }, null, this.disposables)\n  }\n\n  public async start(names: string[]): Promise<void> {\n    this.statMap.clear()\n    this.names = names\n    for (let name of names) {\n      this.statMap.set(name, State.Waiting)\n    }\n    await this.show()\n  }\n\n  public addMessage(name: string, msg: string): void {\n    let lines = this.messagesMap.get(name) || []\n    this.messagesMap.set(name, lines.concat(msg.trim().split(/\\r?\\n/)))\n    if ((msg.startsWith('Updated to') || msg.startsWith('Installed extension'))) {\n      this.updated.add(name)\n    }\n  }\n\n  public startProgress(name: string): void {\n    this.statMap.set(name, State.Progressing)\n  }\n\n  public finishProgress(name: string, succeed?: boolean): void {\n    this.statMap.set(name, succeed ? State.Success : State.Failed)\n  }\n\n  public get remains(): number {\n    let count = 0\n    for (let name of this.names) {\n      let stat = this.statMap.get(name)\n      if (![State.Success, State.Failed].includes(stat)) {\n        count = count + 1\n      }\n    }\n    return count\n  }\n\n  private getLinesAndHighlights(start: number): { lines: string[], highlights: HighlightItem[] } {\n    let lines: string[] = []\n    let highlights: HighlightItem[] = []\n    for (let name of this.names) {\n      let state = this.statMap.get(name)\n      let processText = '*'\n      let hlGroup: string | undefined\n      let lnum = start + lines.length\n      switch (state) {\n        case State.Progressing: {\n          let d = new Date()\n          let idx = Math.floor(d.getMilliseconds() / 100)\n          processText = frames[idx]\n          hlGroup = undefined\n          break\n        }\n        case State.Failed:\n          processText = '✗'\n          hlGroup = 'ErrorMsg'\n          break\n        case State.Success:\n          processText = '✓'\n          hlGroup = this.updated.has(name) ? 'MoreMsg' : 'Comment'\n          break\n      }\n      let msgs = this.messagesMap.get(name) || []\n      let pre = `- ${processText} `\n      let len = byteLength(pre)\n      if (hlGroup) {\n        highlights.push({ hlGroup, lnum, colStart: len, colEnd: len + byteLength(name) })\n      }\n      lines.push(`${pre}${name} ${msgs.length ? msgs[msgs.length - 1] : ''}`)\n    }\n    return { lines, highlights }\n  }\n\n  public getMessages(line: number): string[] {\n    let name = this.names[line - 2]\n    return this.messagesMap.get(name) ?? []\n  }\n\n  public get stopped(): boolean {\n    return this.interval == null\n  }\n\n  private get isUpdate(): boolean {\n    return this.settings.isUpdate\n  }\n\n  // draw frame\n  public draw(): void {\n    let { remains, bufnr } = this\n    let { nvim } = workspace\n    if (!bufnr) return\n    let buffer = nvim.createBuffer(bufnr)\n    let first = remains == 0 ? `${this.isUpdate ? 'Update' : 'Install'} finished` : `Installing, ${remains} remaining...`\n    let { lines, highlights } = this.getLinesAndHighlights(2)\n    nvim.pauseNotification()\n    buffer.setLines([first, '', ...lines], { start: 0, end: -1, strictIndexing: false }, true)\n    buffer.updateHighlights('coc-extensions', highlights, { priority: 99 })\n    if (remains == 0 && this.interval) {\n      clearInterval(this.interval)\n      this.interval = null\n    }\n    nvim.resumeNotification(true, true)\n  }\n\n  private highlight(): void {\n    let { nvim } = workspace\n    nvim.call('matchadd', ['CocListFgCyan', '^\\\\-\\\\s\\\\zs\\\\*'], true)\n    nvim.call('matchadd', ['CocListFgGreen', '^\\\\-\\\\s\\\\zs✓'], true)\n    nvim.call('matchadd', ['CocListFgRed', '^\\\\-\\\\s\\\\zs✗'], true)\n  }\n\n  private async show(): Promise<void> {\n    let isSync = events.requesting === true\n    let { nvim } = workspace\n    nvim.pauseNotification()\n    nvim.command(isSync ? 'enew' : (this.settings.updateUIInTab ? 'tabnew' : 'vs +enew'), true)\n    nvim.call('bufnr', ['%'], true)\n    nvim.command('setl buftype=nofile bufhidden=wipe noswapfile nobuflisted wrap undolevels=-1', true)\n    if (!isSync) nvim.command('nnoremap <silent><nowait><buffer> q :q<CR>', true)\n    this.highlight()\n    let res = await nvim.resumeNotification()\n    this.bufnr = res[0][1] as number\n    this.interval = setInterval(() => {\n      this.draw()\n    }, interval)\n  }\n\n  public dispose(): void {\n    this.bufnr = undefined\n    this.messagesMap.clear()\n    this.statMap.clear()\n    disposeAll(this.disposables)\n    clearInterval(this.interval)\n    this.interval = null\n  }\n}\n"
  },
  {
    "path": "src/handler/callHierarchy.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { CallHierarchyIncomingCall, CallHierarchyItem, CallHierarchyOutgoingCall, Position, Range } from 'vscode-languageserver-types'\nimport commands from '../commands'\nimport events from '../events'\nimport languages, { ProviderName } from '../languages'\nimport { TreeDataProvider } from '../tree/index'\nimport LocationsDataProvider from '../tree/LocationsDataProvider'\nimport BasicTreeView from '../tree/TreeView'\nimport { IConfigurationChangeEvent } from '../types'\nimport { disposeAll } from '../util'\nimport { isFalsyOrEmpty } from '../util/array'\nimport { omit } from '../util/lodash'\nimport { CancellationToken, CancellationTokenSource, Disposable } from '../util/protocol'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { HandlerDelegate } from './types'\n\ninterface CallHierarchyDataItem extends CallHierarchyItem {\n  parent?: CallHierarchyDataItem\n  ranges?: Range[]\n  sourceUri?: string\n  children?: CallHierarchyItem[]\n}\n\ninterface CallHierarchyConfig {\n  splitCommand: string\n  openCommand: string\n  enableTooltip: boolean\n}\n\nenum ShowHierarchyAction {\n  Incoming = 'Show Incoming Calls',\n  Outgoing = 'Show Outgoing Calls'\n}\n\ninterface CallHierarchyProvider extends TreeDataProvider<CallHierarchyDataItem> {\n  meta: 'incoming' | 'outgoing'\n  dispose: () => void\n}\n\n/**\n * Cleanup properties used by treeview\n */\nfunction toCallHierarchyItem(item: CallHierarchyDataItem): CallHierarchyItem {\n  return omit(item, ['children', 'parent', 'ranges', 'sourceUri'])\n}\n\nfunction isCallHierarchyItem(item: any): item is CallHierarchyItem {\n  if (item && typeof item.name === 'string' && item.kind && Range.is(item.range)) return true\n  return false\n}\n\nconst HIGHLIGHT_GROUP = 'CocSelectedRange'\n\nexport default class CallHierarchyHandler {\n  private config: CallHierarchyConfig\n  private disposables: Disposable[] = []\n  public static commandId = 'callHierarchy.reveal'\n  private highlightWinids: Set<number> = new Set()\n  constructor(private nvim: Neovim, private handler: HandlerDelegate) {\n    this.loadConfiguration()\n    workspace.onDidChangeConfiguration(this.loadConfiguration, this, this.disposables)\n    this.disposables.push(commands.registerCommand(CallHierarchyHandler.commandId, async (winid: number, item: CallHierarchyDataItem, openCommand?: string) => {\n      let { nvim } = this\n      await nvim.call('win_gotoid', [winid])\n      await workspace.jumpTo(item.uri, item.selectionRange.start, openCommand)\n      let win = await nvim.window\n      win.clearMatchGroup(HIGHLIGHT_GROUP)\n      win.highlightRanges(HIGHLIGHT_GROUP, [item.selectionRange], 10, true)\n      if (isFalsyOrEmpty(item.ranges)) return\n      if (item.sourceUri) {\n        let doc = workspace.getDocument(item.sourceUri)\n        if (!doc) return\n        let winid = await nvim.call('coc#compat#buf_win_id', [doc.bufnr]) as number\n        if (winid == -1) return\n        if (winid != win.id) {\n          win = nvim.createWindow(winid)\n          win.clearMatchGroup(HIGHLIGHT_GROUP)\n        }\n      }\n      win.highlightRanges(HIGHLIGHT_GROUP, item.ranges, 100, true)\n      this.highlightWinids.add(win.id)\n    }, null, true))\n    events.on('BufWinEnter', (_, winid) => {\n      if (this.highlightWinids.has(winid)) {\n        this.highlightWinids.delete(winid)\n        let win = nvim.createWindow(winid)\n        win.clearMatchGroup(HIGHLIGHT_GROUP)\n      }\n    }, null, this.disposables)\n\n    commands.register({\n      id: 'document.showIncomingCalls',\n      execute: async () => {\n        await this.showCallHierarchyTree('incoming')\n      }\n    }, false, 'show incoming calls in tree view.')\n    commands.register({\n      id: 'document.showOutgoingCalls',\n      execute: async () => {\n        await this.showCallHierarchyTree('outgoing')\n      }\n    }, false, 'show outgoing calls in tree view.')\n  }\n\n  private loadConfiguration(e?: IConfigurationChangeEvent): void {\n    if (!e || e.affectsConfiguration('callHierarchy')) {\n      let c = workspace.getConfiguration('callHierarchy', null)\n      this.config = {\n        splitCommand: c.get<string>('splitCommand'),\n        openCommand: c.get<string>('openCommand'),\n        enableTooltip: c.get<boolean>('enableTooltip')\n      }\n    }\n  }\n\n  private createProvider(rootItems: CallHierarchyDataItem[], doc: TextDocument, winid: number, kind: 'incoming' | 'outgoing'): CallHierarchyProvider {\n    let provider = new LocationsDataProvider<CallHierarchyDataItem, 'incoming' | 'outgoing'>(\n      kind,\n      winid,\n      this.config,\n      CallHierarchyHandler.commandId,\n      rootItems,\n      kind => this.handler.getIcon(kind),\n      (el, meta, token) => this.getChildren(doc, el, meta, token)\n    )\n    for (let kind of ['incoming', 'outgoing']) {\n      let name = kind === 'incoming' ? ShowHierarchyAction.Incoming : ShowHierarchyAction.Outgoing\n      provider.addAction(name, (el: CallHierarchyDataItem) => {\n        provider.meta = kind as 'incoming' | 'outgoing'\n        let rootItems = [toCallHierarchyItem(el)]\n        provider.reset(rootItems)\n      })\n    }\n    return provider\n  }\n\n  private async getChildren(doc: TextDocument, item: CallHierarchyDataItem, kind: 'incoming' | 'outgoing', token: CancellationToken): Promise<CallHierarchyDataItem[]> {\n    let items: CallHierarchyDataItem[] = []\n    let callHierarchyItem = toCallHierarchyItem(item)\n    if (kind == 'incoming') {\n      let res = await languages.provideIncomingCalls(doc, callHierarchyItem, token)\n      if (res) items = res.map(o => Object.assign(o.from, { ranges: o.fromRanges }))\n    } else {\n      let res = await languages.provideOutgoingCalls(doc, callHierarchyItem, token)\n      if (res) items = res.map(o => Object.assign(o.to, { ranges: o.fromRanges, sourceUri: item.uri }))\n    }\n    return items\n  }\n\n  private async prepare(doc: TextDocument, position: Position, token: CancellationToken): Promise<CallHierarchyItem[] | undefined> {\n    this.handler.checkProvider(ProviderName.CallHierarchy, doc)\n    const res = await languages.prepareCallHierarchy(doc, position, token)\n    return isCallHierarchyItem(res) ? [res] : res\n  }\n\n  private async getCallHierarchyItems(item: CallHierarchyItem | undefined, kind: 'outgoing'): Promise<CallHierarchyOutgoingCall[]>\n  private async getCallHierarchyItems(item: CallHierarchyItem | undefined, kind: 'incoming'): Promise<CallHierarchyIncomingCall[]>\n  private async getCallHierarchyItems(item: CallHierarchyItem | undefined, kind: 'incoming' | 'outgoing'): Promise<(CallHierarchyIncomingCall | CallHierarchyOutgoingCall)[]> {\n    const { doc, position } = await this.handler.getCurrentState()\n    const source = new CancellationTokenSource()\n    if (!item) {\n      await doc.synchronize()\n      let res = await this.prepare(doc.textDocument, position, source.token)\n      item = res ? res[0] : undefined\n      if (!res) throw new Error('Unable to getCallHierarchyItem at current position')\n    }\n    let method = kind == 'incoming' ? 'provideIncomingCalls' : 'provideOutgoingCalls'\n    return await languages[method](doc.textDocument, item, source.token)\n  }\n\n  public async getIncoming(item?: CallHierarchyItem): Promise<CallHierarchyIncomingCall[] | undefined> {\n    return await this.getCallHierarchyItems(item, 'incoming')\n  }\n\n  public async getOutgoing(item?: CallHierarchyItem): Promise<CallHierarchyOutgoingCall[] | undefined> {\n    return await this.getCallHierarchyItems(item, 'outgoing')\n  }\n\n  public async showCallHierarchyTree(kind: 'incoming' | 'outgoing'): Promise<void> {\n    const { doc, position, winid } = await this.handler.getCurrentState()\n    await doc.synchronize()\n    if (!languages.hasProvider(ProviderName.CallHierarchy, doc.textDocument)) {\n      void window.showErrorMessage(`CallHierarchy provider not found for current document, it's not supported by your languageserver`)\n      return\n    }\n    const res = await languages.prepareCallHierarchy(doc.textDocument, position, CancellationToken.None)\n    const rootItems: CallHierarchyItem[] = isCallHierarchyItem(res) ? [res] : res\n    if (isFalsyOrEmpty(rootItems)) {\n      void window.showWarningMessage('Unable to get CallHierarchyItem at cursor position.')\n      return\n    }\n    let provider = this.createProvider(rootItems, doc.textDocument, winid, kind)\n    let treeView = new BasicTreeView('CALLS', { treeDataProvider: provider })\n    treeView.title = getTitle(kind)\n    provider.onDidChangeTreeData(e => {\n      if (!e) treeView.title = getTitle(provider.meta)\n    })\n    treeView.onDidChangeVisibility(e => {\n      if (!e.visible) provider.dispose()\n    })\n    this.disposables.push(treeView)\n    await treeView.show(this.config.splitCommand)\n  }\n\n  public dispose(): void {\n    this.highlightWinids.clear()\n    disposeAll(this.disposables)\n  }\n}\n\nfunction getTitle(kind: string): string {\n  return `${kind.toUpperCase()} CALLS`\n}\n"
  },
  {
    "path": "src/handler/codeActions.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { CodeAction, CodeActionContext, CodeActionKind, CodeActionTriggerKind, Range } from 'vscode-languageserver-types'\nimport commandManager from '../commands'\nimport diagnosticManager from '../diagnostic/manager'\nimport languages from '../languages'\nimport { createLogger } from '../logger'\nimport Document from '../model/document'\nimport { isFalsyOrEmpty } from '../util/array'\nimport { boolToNumber } from '../util/numbers'\nimport { CancellationToken, CancellationTokenSource } from '../util/protocol'\nimport { createTiming } from '../util/timing'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { HandlerDelegate } from './types'\nconst logger = createLogger('handler-codeActions')\n\n/**\n * Handle codeActions related methods.\n */\nexport default class CodeActions {\n  constructor(\n    private nvim: Neovim,\n    private handler: HandlerDelegate\n  ) {\n    handler.addDisposable(commandManager.registerCommand('editor.action.organizeImport', async () => {\n      let succeed = await this.organizeImport()\n      if (!succeed) void window.showWarningMessage(`Organize import action not found`)\n    }))\n    commandManager.titles.set('editor.action.organizeImport', 'Run organize import code action, show warning when not exists')\n\n    handler.addDisposable(commandManager.registerCommand('editor.action.executeCodeActions', async (doc: Document, range: Range | undefined, actionKinds: CodeActionKind[], timeout: number) => {\n      await this.executeCodeActions(doc, range, actionKinds, timeout)\n    }, this, true))\n  }\n\n  public async executeCodeActions(doc: Document, range: Range | undefined, actionKinds: CodeActionKind[], timeout: number): Promise<string[]> {\n    let timing = createTiming('Execute code action', timeout)\n    let applied: string[] = []\n    for (const kind of actionKinds) {\n      let codeActions = await this.getCodeActions(doc, range, [kind])\n      let codeAction = codeActions.find(o => !o.disabled)\n      if (codeAction) {\n        logger.info(`Apply code action \"${kind}\" to buffer ${doc.bufnr}`)\n        timing.start(`\"${kind}\"`)\n        let tokenSource = new CancellationTokenSource()\n        let timer: NodeJS.Timeout\n        let _resolve: (value: any) => void\n        const tp = new Promise<undefined>(c => {\n          timer = setTimeout(() => {\n            logger.warn(`Apply code action \"${kind}\" timeout after ${timeout}ms`)\n            tokenSource.cancel()\n            c(undefined)\n          }, timeout)\n          _resolve = c\n        })\n        await Promise.race([tp, this.applyCodeAction(codeAction, tokenSource.token)])\n        if (!tokenSource.token.isCancellationRequested) {\n          applied.push(kind)\n        }\n        timing.stop()\n        clearTimeout(timer)\n        _resolve(undefined)\n        tokenSource.dispose()\n        await doc.synchronize()\n      }\n    }\n    return applied\n  }\n\n  public async codeActionRange(start: number, end: number, only?: string): Promise<void> {\n    let { doc } = await this.handler.getCurrentState()\n    await doc.synchronize()\n    let line = doc.getline(end - 1)\n    let range = Range.create(start - 1, 0, end - 1, line.length)\n    let codeActions = await this.getCodeActions(doc, range, only ? [only] : null)\n    codeActions = codeActions.filter(o => !o.disabled)\n    if (!codeActions || codeActions.length == 0) {\n      void window.showWarningMessage(`No${only ? ' ' + only : ''} code action available`)\n      return\n    }\n    let idx = await window.showMenuPicker(codeActions.map(o => o.title), 'Choose action')\n    let action = codeActions[idx]\n    if (action) await this.applyCodeAction(action)\n  }\n\n  public async organizeImport(): Promise<boolean> {\n    let { doc } = await this.handler.getCurrentState()\n    await doc.synchronize()\n    let actions = await this.getCodeActions(doc, undefined, [CodeActionKind.SourceOrganizeImports])\n    if (actions && actions.length) {\n      await this.applyCodeAction(actions[0])\n      return true\n    }\n    return false\n  }\n\n  public async getCodeActions(doc: Document, range?: Range, only?: CodeActionKind[]): Promise<CodeAction[]> {\n    let excludeSourceAction = range !== null && (!only || only.findIndex(o => o.startsWith(CodeActionKind.Source)) == -1)\n    range = range ?? Range.create(0, 0, doc.lineCount, 0)\n    let diagnostics = diagnosticManager.getDiagnosticsInRange(doc.textDocument, range)\n    let context: CodeActionContext = { diagnostics, triggerKind: CodeActionTriggerKind.Invoked }\n    if (!isFalsyOrEmpty(only)) context.only = only\n    let tokenSource = new CancellationTokenSource()\n    let codeActions = await languages.getCodeActions(doc.textDocument, range, context, tokenSource.token)\n    if (!codeActions || codeActions.length == 0) return []\n    if (excludeSourceAction) {\n      codeActions = codeActions.filter(o => !o.kind || !o.kind.startsWith(CodeActionKind.Source))\n    }\n    codeActions.sort((a, b) => {\n      if (a.disabled && !b.disabled) return 1\n      if (b.disabled && !a.disabled) return -1\n      if (a.isPreferred != b.isPreferred) return boolToNumber(b.isPreferred) - boolToNumber(a.isPreferred)\n      if (!only) {\n        if (isQuickfix(a) && !isQuickfix(b)) return -1\n        if (isQuickfix(b) && !isQuickfix(a)) return 1\n      }\n      return 0\n    })\n    return codeActions\n  }\n\n  private get floatActions(): boolean {\n    return workspace.initialConfiguration.get<boolean>('coc.preferences.floatActions', true)\n  }\n\n  public async doCodeAction(mode: string | null, only: CodeActionKind[] | string, showDisable = false): Promise<void> {\n    let { doc, position } = await this.handler.getCurrentState()\n    let range: Range | undefined\n    if (mode) {\n      range = await window.getSelectedRange(mode)\n    } else {\n      range = Range.create(position, position)\n    }\n    await doc.synchronize()\n    let codeActions = await this.getCodeActions(doc, range, Array.isArray(only) ? only : null)\n    if (typeof only == 'string') {\n      codeActions = codeActions.filter(o => o.title == only || (o.command && o.command.title == only))\n    } else if (Array.isArray(only)) {\n      codeActions = codeActions.filter(o => only.some(k => o.kind && o.kind.startsWith(k)))\n    }\n    if (!this.floatActions || !showDisable) codeActions = codeActions.filter(o => !o.disabled)\n    if (!codeActions || codeActions.length == 0) {\n      void window.showWarningMessage(`No${only ? ' ' + only : ''} code action available`)\n      return\n    }\n    if (codeActions.length == 1 && !codeActions[0].disabled && shouldAutoApply(only)) {\n      await this.applyCodeAction(codeActions[0])\n      return\n    }\n    let idx = this.floatActions\n      ? await window.showMenuPicker(\n        codeActions.map(o => {\n          return { text: o.title, disabled: o.disabled }\n        }),\n        'Choose action'\n      )\n      : await window.requestInputList('Choose action by number', codeActions.map(o => o.title))\n    let action = codeActions[idx]\n    if (action) await this.applyCodeAction(action)\n  }\n\n  /**\n   * Get current codeActions\n   */\n  public async getCurrentCodeActions(mode?: string, only?: CodeActionKind[]): Promise<CodeAction[]> {\n    let { doc } = await this.handler.getCurrentState()\n    let range: Range\n    if (mode) range = await window.getSelectedRange(mode)\n    let codeActions = await this.getCodeActions(doc, range, only)\n    return codeActions.filter(o => !o.disabled)\n  }\n\n  /**\n   * Invoke preferred quickfix at current position\n   */\n  public async doQuickfix(): Promise<void> {\n    let actions = await this.getCurrentCodeActions('currline', [CodeActionKind.QuickFix])\n    if (!actions || actions.length == 0) {\n      void window.showWarningMessage(`No quickfix action available`)\n      return\n    }\n    await this.applyCodeAction(actions[0])\n    this.nvim.command(`silent! call repeat#set(\"\\\\<Plug>(coc-fix-current)\", -1)`, true)\n  }\n\n  public async applyCodeAction(action: CodeAction, token?: CancellationToken): Promise<void> {\n    if (action.disabled) {\n      throw new Error(`Action \"${action.title}\" is disabled: ${action.disabled.reason}`)\n    }\n    token = token == null ? CancellationToken.None : token\n    let resolved = await languages.resolveCodeAction(action, token)\n    if (!resolved || token.isCancellationRequested) return\n    let { edit, command } = resolved\n    if (edit) await workspace.applyEdit(edit)\n    if (command) await commandManager.execute(command)\n  }\n}\n\nexport function shouldAutoApply(only: CodeActionKind[] | string | undefined): boolean {\n  if (!only) return false\n  if (typeof only === 'string' || only[0] === CodeActionKind.QuickFix || only[0] === CodeActionKind.SourceFixAll) {\n    return workspace.initialConfiguration.get('coc.preferences.autoApplySingleQuickfix', true)\n  }\n  return false\n}\n\nfunction isQuickfix(codeAction: CodeAction): boolean {\n  return codeAction.kind && codeAction.kind.startsWith('quickfix')\n}\n"
  },
  {
    "path": "src/handler/codelens/buffer.ts",
    "content": "'use strict'\nimport type { Neovim } from '@chemzqm/neovim'\nimport { CodeLens, Command } from 'vscode-languageserver-types'\nimport commandManager from '../../commands'\nimport languages, { ProviderName } from '../../languages'\nimport { createLogger } from '../../logger'\nimport { SyncItem } from '../../model/bufferSync'\nimport Document from '../../model/document'\nimport { DidChangeTextDocumentParams } from '../../types'\nimport { defaultValue, getConditionValue } from '../../util'\nimport { isFalsyOrEmpty } from '../../util/array'\nimport { onUnexpectedError } from '../../util/errors'\nimport { isCommand } from '../../util/is'\nimport { debounce } from '../../util/node'\nimport { CancellationTokenSource } from '../../util/protocol'\nimport window from '../../window'\nimport workspace from '../../workspace'\nconst logger = createLogger('codelens-buffer')\n\nexport interface CodeLensInfo {\n  codeLenses: CodeLens[]\n  version: number\n}\n\nexport interface CodeLensConfig {\n  position: 'top' | 'eol' | 'right_align'\n  enabled: boolean\n  display: boolean\n  separator: string\n  subseparator: string\n}\n\nexport enum TextAlign {\n  After = 'after',\n  Right = 'right',\n  Below = 'below',\n  Above = 'above',\n}\n\nlet srcId: number | undefined\nconst debounceTime = getConditionValue(200, 20)\nconst CODELENS_HL = 'CocCodeLens'\nconst NORMAL_HL = 'Normal'\n\n/**\n * CodeLens buffer\n */\nexport default class CodeLensBuffer implements SyncItem {\n  private codeLenses: CodeLensInfo | undefined\n  private tokenSource: CancellationTokenSource\n  private resolveTokenSource: CancellationTokenSource\n  private _config: CodeLensConfig | undefined\n  public resolveCodeLens: (() => void) & { clear(): void }\n  public debounceFetch: (() => void) & { clear(): void }\n  constructor(\n    private nvim: Neovim,\n    public readonly document: Document\n  ) {\n    this.resolveCodeLens = debounce(() => {\n      this._resolveCodeLenses().catch(onUnexpectedError)\n    }, debounceTime)\n    this.debounceFetch = debounce(() => {\n      this.fetchCodeLenses().catch(onUnexpectedError)\n    }, debounceTime)\n    if (this.hasProvider) this.debounceFetch()\n  }\n\n  public get config(): CodeLensConfig {\n    if (this._config) return this._config\n    this.loadConfiguration()\n    return this._config\n  }\n\n  public loadConfiguration(): void {\n    let config = workspace.getConfiguration('codeLens', this.document)\n    this._config = {\n      enabled: config.get<boolean>('enable', false),\n      display: config.get<boolean>('display', true),\n      position: config.get<'top' | 'eol' | 'right_align'>('position', 'top'),\n      separator: config.get<string>('separator', ''),\n      subseparator: config.get<string>('subseparator', ' ')\n    }\n  }\n\n  public async toggleDisplay(): Promise<void> {\n    if (!this.hasProvider || !this.config.enabled) return\n    if (this.config.display) {\n      this.config.display = false\n      this.clear()\n    } else {\n      this.config.display = true\n      this.resolveCodeLens.clear()\n      await this._resolveCodeLenses()\n    }\n  }\n\n  public get bufnr(): number {\n    return this.document.bufnr\n  }\n\n  public onChange(e: DidChangeTextDocumentParams): void {\n    if (e.contentChanges.length === 0 && this.codeLenses != null) {\n      this.resolveCodeLens.clear()\n      this._resolveCodeLenses().catch(onUnexpectedError)\n    } else {\n      this.cancel()\n      this.debounceFetch()\n    }\n  }\n\n  public get currentCodeLens(): CodeLens[] | undefined {\n    return this.codeLenses?.codeLenses\n  }\n\n  private get hasProvider(): boolean {\n    return languages.hasProvider(ProviderName.CodeLens, this.document)\n  }\n\n  public async forceFetch(): Promise<void> {\n    if (!this.config.enabled || !this.hasProvider) return\n    await this.document.synchronize()\n    this.cancel()\n    await this.fetchCodeLenses()\n  }\n\n  public async fetchCodeLenses(): Promise<void> {\n    if (!this.hasProvider || !this.config.enabled) return\n    let noFetch = this.codeLenses?.version == this.document.version\n    if (!noFetch) {\n      let empty = this.codeLenses == null\n      let { textDocument } = this.document\n      let version = textDocument.version\n      this.cancelFetch()\n      let tokenSource = this.tokenSource = new CancellationTokenSource()\n      let token = tokenSource.token\n      if (!srcId) srcId = await this.nvim.createNamespace('coc-codelens')\n      let codeLenses = await languages.getCodeLens(textDocument, token)\n      if (token.isCancellationRequested) return\n      codeLenses = defaultValue(codeLenses, [])\n      codeLenses = codeLenses.filter(o => o != null)\n      if (isFalsyOrEmpty(codeLenses)) {\n        this.clear()\n        return\n      }\n      this.codeLenses = { version, codeLenses }\n      if (empty) this.setVirtualText(codeLenses)\n    }\n    this.resolveCodeLens.clear()\n    await this._resolveCodeLenses()\n  }\n\n  /**\n   * Resolve visible codeLens\n   */\n  private async _resolveCodeLenses(): Promise<void> {\n    if (!this.codeLenses || this.isChanged) return\n    let { codeLenses } = this.codeLenses\n    let [bufnr, start, end, total] = await this.nvim.eval(`[bufnr('%'),line('w0'),line('w$'),line('$')]`) as [number, number, number, number]\n    // only resolve current buffer\n    if (this.isChanged || bufnr != this.bufnr) return\n    this.cancel()\n    codeLenses = codeLenses.filter(o => {\n      let lnum = o.range.start.line + 1\n      return lnum >= start && lnum <= end\n    })\n    if (codeLenses.length) {\n      let tokenSource = this.resolveTokenSource = new CancellationTokenSource()\n      let token = tokenSource.token\n      await Promise.all(codeLenses.map(codeLens => {\n        if (isCommand(codeLens.command)) return Promise.resolve()\n        codeLens.command = undefined\n        return languages.resolveCodeLens(codeLens, token)\n      }))\n      this.resolveTokenSource = undefined\n      if (token.isCancellationRequested || this.isChanged) return\n    }\n    // nvim could have extmarks exceeded last line.\n    if (end == total) end = -1\n    this.nvim.pauseNotification()\n    this.clear()\n    this.setVirtualText(codeLenses)\n    this.nvim.resumeNotification(true, true)\n  }\n\n  private get isChanged(): boolean {\n    if (!this.codeLenses || this.document.dirty) return true\n    let { version } = this.codeLenses\n    return this.document.textDocument.version !== version\n  }\n\n  /**\n   * Attach resolved codeLens\n   */\n  private setVirtualText(codeLenses: CodeLens[]): void {\n    let { document } = this\n    if (!srcId || !document || !codeLenses.length || !this.config.display) return\n    let top = this.config.position === 'top'\n    let list: Map<number, CodeLens[]> = new Map()\n    for (let codeLens of codeLenses) {\n      let { line } = codeLens.range.start\n      let curr = list.get(line) ?? []\n      curr.push(codeLens)\n      list.set(line, curr)\n    }\n    for (let lnum of list.keys()) {\n      let codeLenses = list.get(lnum)\n      let commands = codeLenses.reduce((p, c) => {\n        if (c && c.command && c.command.title) p.push(c.command.title.replace(/\\s+/g, ' '))\n        return p\n      }, [] as string[])\n      let chunks: [string, string][] = []\n      let len = commands.length\n      for (let i = 0; i < len; i++) {\n        let title = commands[i]\n        chunks.push([title, CODELENS_HL] as [string, string])\n        if (i != len - 1) {\n          chunks.push([this.config.subseparator, CODELENS_HL] as [string, string])\n        }\n      }\n      if (chunks.length > 0 && this.config.separator) {\n        chunks.unshift([`${this.config.separator} `, CODELENS_HL])\n      }\n      if (top && chunks.length == 0) {\n        chunks.push([' ', NORMAL_HL])\n      }\n      if (chunks.length > 0) {\n        document.buffer.setVirtualText(srcId, lnum, chunks, {\n          text_align: getTextAlign(this.config.position),\n          indent: true\n        })\n      }\n    }\n  }\n\n  public clear(start = 0, end = -1): void {\n    if (!srcId) return\n    let buf = this.nvim.createBuffer(this.bufnr)\n    buf.clearNamespace(srcId, start, end)\n  }\n\n  public async doAction(line: number): Promise<void> {\n    let commands = getCommands(line, this.codeLenses?.codeLenses)\n    if (commands.length == 1) {\n      await commandManager.execute(commands[0])\n    } else if (commands.length > 1) {\n      let res = await window.showMenuPicker(commands.map(c => c.title))\n      if (res != -1) await commandManager.execute(commands[res])\n    }\n  }\n\n  private cancelFetch(): void {\n    this.debounceFetch.clear()\n    if (this.tokenSource) {\n      this.tokenSource.cancel()\n      this.tokenSource = null\n    }\n  }\n\n  private cancelResolve(): void {\n    if (this.resolveTokenSource) {\n      this.resolveTokenSource.cancel()\n      this.resolveTokenSource = null\n    }\n  }\n\n  private cancel(): void {\n    this.resolveCodeLens.clear()\n    this.cancelResolve()\n    this.cancelFetch()\n  }\n\n  public abandonResult(): void {\n    this.codeLenses = undefined\n  }\n\n  public dispose(): void {\n    this.cancel()\n    this.codeLenses = undefined\n  }\n}\n\nexport function getTextAlign(position: 'top' | 'eol' | 'right_align'): TextAlign {\n  if (position == 'top') return TextAlign.Above\n  if (position == 'eol') return TextAlign.After\n  if (position === 'right_align') return TextAlign.Right\n  return TextAlign.Above\n}\n\nexport function getCommands(line: number, codeLenses: CodeLens[] | undefined): Command[] {\n  if (!codeLenses?.length) return []\n  let commands: Command[] = []\n  for (let codeLens of codeLenses) {\n    let { range, command } = codeLens\n    if (!isCommand(command)) continue\n    if (line == range.start.line) {\n      commands.push(command)\n    }\n  }\n  return commands\n}\n"
  },
  {
    "path": "src/handler/codelens/index.ts",
    "content": "'use strict'\nimport type { Neovim } from '@chemzqm/neovim'\nimport type { DocumentSelector } from 'vscode-languageserver-protocol'\nimport { debounce } from '../..//util/node'\nimport commands from '../../commands'\nimport events from '../../events'\nimport languages from '../../languages'\nimport BufferSync from '../../model/bufferSync'\nimport { disposeAll, getConditionValue } from '../../util'\nimport { Disposable } from '../../util/protocol'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport CodeLensBuffer from './buffer'\n\nconst debounceTime = getConditionValue(200, 0)\n/**\n * Show codeLens of document\n */\nexport default class CodeLensManager {\n  private disposables: Disposable[] = []\n  public buffers: BufferSync<CodeLensBuffer>\n  constructor(private nvim: Neovim) {\n    workspace.onDidChangeConfiguration(e => {\n      if (e.affectsConfiguration('codeLens')) {\n        for (let item of this.buffers.items) {\n          item.loadConfiguration()\n        }\n      }\n    }, this, this.disposables)\n    this.buffers = workspace.registerBufferSync(doc => {\n      if (doc.buftype != '') return undefined\n      return new CodeLensBuffer(nvim, doc)\n    })\n    this.disposables.push(this.buffers)\n    events.on('CursorHold', async (bufnr: number) => {\n      let item = this.buffers.getItem(bufnr)\n      if (item && item.config.enabled && !item.currentCodeLens) await item.forceFetch()\n    }, null, this.disposables)\n    events.on('CursorMoved', bufnr => {\n      let buf = this.buffers.getItem(bufnr)\n      if (buf) buf.resolveCodeLens()\n    }, null, this.disposables)\n    let debounced = debounce(async (selector: DocumentSelector) => {\n      for (let item of this.buffers.items) {\n        if (!workspace.match(selector, item.document)) continue\n        item.abandonResult()\n        await item.forceFetch()\n      }\n    }, debounceTime)\n    this.disposables.push(Disposable.create(() => {\n      debounced.clear()\n    }))\n    languages.onDidCodeLensRefresh(debounced, null, this.disposables)\n    commands.register({\n      id: 'document.toggleCodeLens',\n      execute: () => {\n        return this.toggle(workspace.bufnr)\n      },\n    }, false, 'toggle codeLens display of current buffer')\n  }\n\n  public async toggle(bufnr: number): Promise<void> {\n    let item = this.buffers.getItem(bufnr)\n    try {\n      workspace.getAttachedDocument(bufnr)\n      await item.toggleDisplay()\n    } catch (e) {\n      void window.showErrorMessage((e as Error).message)\n    }\n  }\n  /**\n   * Check provider for buf that not fetched\n   */\n  public async checkProvider(): Promise<void> {\n    for (let buf of this.buffers.items) {\n      await buf.forceFetch()\n    }\n  }\n\n  public async doAction(): Promise<void> {\n    let [bufnr, line] = await this.nvim.eval(`[bufnr(\"%\"),line(\".\")-1]`) as [number, number]\n    let buf = this.buffers.getItem(bufnr)\n    if (buf) await buf.doAction(line)\n  }\n\n  public dispose(): void {\n    disposeAll(this.disposables)\n  }\n}\n"
  },
  {
    "path": "src/handler/colors/colorBuffer.ts",
    "content": "'use strict'\nimport { Buffer, Neovim } from '@chemzqm/neovim'\nimport { Color, ColorInformation, Position, Range } from 'vscode-languageserver-types'\nimport languages, { ProviderName } from '../../languages'\nimport { SyncItem } from '../../model/bufferSync'\nimport Document from '../../model/document'\nimport { HighlightItem } from '../../types'\nimport { getConditionValue } from '../../util'\nimport { isDark, toHexString } from '../../util/color'\nimport * as Is from '../../util/is'\nimport { debounce } from '../../util/node'\nimport { comparePosition, positionInRange } from '../../util/position'\nimport { CancellationTokenSource } from '../../util/protocol'\nimport window from '../../window'\nimport workspace from '../../workspace'\nconst NAMESPACE = 'color'\n\nexport interface ColorRanges {\n  color: Color\n  ranges: Range[]\n}\n\nexport interface ColorConfig {\n  filetypes: string[] | null\n  highlightPriority: number\n}\n\nconst debounceTime = getConditionValue(200, 10)\n\nexport default class ColorBuffer implements SyncItem {\n  private _colors: ColorInformation[] = []\n  private tokenSource: CancellationTokenSource | undefined\n  public highlight: (() => void) & { clear(): void }\n  private _enable: boolean | undefined\n  // last highlight version\n  constructor(\n    private nvim: Neovim,\n    public readonly doc: Document,\n    private config: ColorConfig,\n    private usedColors: Set<string>) {\n    this.highlight = debounce(() => {\n      void this.doHighlight()\n    }, debounceTime)\n    if (this.hasProvider) this.highlight()\n  }\n\n  public get enable(): boolean {\n    if (Is.boolean(this._enable)) return this._enable\n    this._enable = workspace.getConfiguration('colors', this.doc).get('enable', false)\n    return this._enable\n  }\n\n  public updateDocumentConfig(): void {\n    let enable = this.enabled\n    this._enable = workspace.getConfiguration('colors', this.doc).get('enable', false)\n    if (enable != this.enabled) {\n      if (enable) {\n        this.clearHighlight()\n      } else {\n        void this.doHighlight()\n      }\n    }\n  }\n\n  public toggle(): void {\n    if (this._enable) {\n      this._enable = false\n      this.clearHighlight()\n    } else {\n      this._enable = true\n      void this.doHighlight()\n    }\n  }\n\n  private get hasProvider(): boolean {\n    return languages.hasProvider(ProviderName.DocumentColor, this.doc)\n  }\n\n  public get enabled(): boolean {\n    let { filetypes } = this.config\n    let { filetype } = this.doc\n    if (!this.hasProvider) return false\n    if (Array.isArray(filetypes) && (filetypes.includes('*') || filetypes.includes(filetype))) return true\n    return this.enable\n  }\n\n  public onChange(): void {\n    this.cancel()\n    this.highlight()\n  }\n\n  public get buffer(): Buffer {\n    return this.doc.buffer\n  }\n\n  public get colors(): ColorInformation[] {\n    return this._colors\n  }\n\n  public hasColor(): boolean {\n    return this._colors.length > 0\n  }\n\n  public async doHighlight(): Promise<void> {\n    if (!this.enabled) return\n    let { nvim, doc } = this\n    this.tokenSource = new CancellationTokenSource()\n    let { token } = this.tokenSource\n    let colors: ColorInformation[]\n    colors = await languages.provideDocumentColors(doc.textDocument, token)\n    if (token.isCancellationRequested) return\n    colors = colors || []\n    colors.sort((a, b) => comparePosition(a.range.start, b.range.start))\n    this._colors = colors\n    let items: HighlightItem[] = []\n    colors.forEach(o => {\n      let hlGroup = getHighlightGroup(o.color)\n      doc.addHighlights(items, hlGroup, o.range, { combine: false })\n    })\n    let diff = await window.diffHighlights(doc.bufnr, NAMESPACE, items)\n    if (token.isCancellationRequested || !diff) return\n    nvim.pauseNotification()\n    this.defineColors(colors)\n    nvim.resumeNotification(false, true)\n    await window.applyDiffHighlights(doc.bufnr, NAMESPACE, this.config.highlightPriority, diff, true)\n  }\n\n  private defineColors(colors: ColorInformation[]): void {\n    for (let color of colors) {\n      let hex = toHexString(color.color)\n      if (!this.usedColors.has(hex)) {\n        this.nvim.command(`hi BG${hex} guibg=#${hex} guifg=#${isDark(color.color) ? 'ffffff' : '000000'}`, true)\n        this.usedColors.add(hex)\n      }\n    }\n  }\n\n  public hasColorAtPosition(position: Position): boolean {\n    return this.colors.some(o => positionInRange(position, o.range) == 0)\n  }\n\n  public clearHighlight(): void {\n    this.highlight.clear()\n    this._colors = []\n    this.buffer.clearNamespace('color')\n  }\n\n  public cancel(): void {\n    if (this.tokenSource) {\n      this.tokenSource.cancel()\n      this.tokenSource = null\n    }\n  }\n\n  public dispose(): void {\n    this._colors = []\n    this.highlight.clear()\n    this.cancel()\n  }\n}\n\nfunction getHighlightGroup(color: Color): string {\n  return `BG${toHexString(color)}`\n}\n"
  },
  {
    "path": "src/handler/colors/index.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { ColorInformation, Position } from 'vscode-languageserver-types'\nimport commandManager from '../../commands'\nimport events from '../../events'\nimport languages, { ProviderName } from '../../languages'\nimport BufferSync from '../../model/bufferSync'\nimport { IConfigurationChangeEvent } from '../../types'\nimport { defaultValue, disposeAll } from '../../util'\nimport { toHexString } from '../../util/color'\nimport { CancellationTokenSource, Disposable } from '../../util/protocol'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport { HandlerDelegate } from '../types'\nimport ColorBuffer, { ColorConfig } from './colorBuffer'\n\nexport default class Colors {\n  private config: ColorConfig\n  private disposables: Disposable[] = []\n  private highlighters: BufferSync<ColorBuffer>\n\n  constructor(private nvim: Neovim, private handler: HandlerDelegate) {\n    this.loadConfiguration()\n    workspace.onDidChangeConfiguration(this.loadConfiguration, this, this.disposables)\n    let usedColors: Set<string> = new Set()\n    this.highlighters = workspace.registerBufferSync(doc => {\n      return new ColorBuffer(this.nvim, doc, this.config, usedColors)\n    })\n    events.on('ColorScheme', () => {\n      usedColors.clear()\n      for (let item of this.highlighters.items) {\n        item.cancel()\n        void item.doHighlight()\n      }\n    }, null, this.disposables)\n    languages.onDidColorsRefresh(selector => {\n      for (let item of this.highlighters.items) {\n        if (workspace.match(selector, item.doc)) {\n          item.highlight()\n        }\n      }\n    })\n    commandManager.register({\n      id: 'editor.action.pickColor',\n      execute: async () => {\n        await this.pickColor()\n      }\n    }, false, 'pick color from system color picker when possible.')\n    commandManager.register({\n      id: 'editor.action.colorPresentation',\n      execute: async () => {\n        await this.pickPresentation()\n      }\n    }, false, 'change color presentation.')\n    commandManager.register({\n      id: 'document.toggleColors',\n      execute: async () => {\n        let bufnr = await nvim.call('bufnr', ['%']) as number\n        let item = this.highlighters.getItem(bufnr)\n        workspace.getAttachedDocument(bufnr)\n        item.toggle()\n      }\n    }, false, 'toggle colors for current buffer')\n  }\n\n  private loadConfiguration(e?: IConfigurationChangeEvent): void {\n    if (!e || e.affectsConfiguration('colors')) {\n      let c = workspace.initialConfiguration.get('colors') as any\n      this.config = Object.assign(this.config ?? {}, {\n        filetypes: c.filetypes,\n        highlightPriority: defaultValue(c.highlightPriority, 1000)\n      })\n      if (e) {\n        for (let item of this.highlighters.items) {\n          item.updateDocumentConfig()\n        }\n      }\n    }\n  }\n\n  public async pickPresentation(): Promise<void> {\n    let { doc } = await this.handler.getCurrentState()\n    this.handler.checkProvider(ProviderName.DocumentColor, doc.textDocument)\n    let info = await this.getColorInformation(doc.bufnr)\n    if (!info) return void window.showWarningMessage('Color not found at current position')\n    let tokenSource = new CancellationTokenSource()\n    let presentations = await languages.provideColorPresentations(info, doc.textDocument, tokenSource.token)\n    if (!presentations?.length) return void window.showWarningMessage('No color presentations found')\n    let res = await window.showMenuPicker(presentations.map(o => o.label), 'Choose color:')\n    if (res == -1) return\n    let presentation = presentations[res]\n    let { textEdit, additionalTextEdits, label } = presentation\n    if (!textEdit) textEdit = { range: info.range, newText: label }\n    await doc.applyEdits([textEdit])\n    if (additionalTextEdits) await doc.applyEdits(additionalTextEdits)\n\n  }\n\n  public async pickColor(): Promise<void> {\n    let { doc } = await this.handler.getCurrentState()\n    this.handler.checkProvider(ProviderName.DocumentColor, doc.textDocument)\n    let info = await this.getColorInformation(doc.bufnr)\n    if (!info) return void window.showWarningMessage('Color not found at current position')\n    let { color } = info\n    let colorArr = [(color.red * 255).toFixed(0), (color.green * 255).toFixed(0), (color.blue * 255).toFixed(0)]\n    let res = await this.nvim.call('coc#color#pick_color', [colorArr])\n    if (!res) return\n    let hex = toHexString({\n      red: (res[0] / 65535),\n      green: (res[1] / 65535),\n      blue: (res[2] / 65535),\n      alpha: 1\n    })\n    await doc.applyEdits([{\n      range: info.range,\n      newText: `#${hex}`\n    }])\n  }\n\n  public isEnabled(bufnr: number): boolean {\n    let highlighter = this.highlighters.getItem(bufnr)\n    return highlighter != null && highlighter.enabled === true\n  }\n\n  public clearHighlight(bufnr: number): void {\n    let highlighter = this.highlighters.getItem(bufnr)\n    if (highlighter) highlighter.clearHighlight()\n  }\n\n  public hasColor(bufnr: number): boolean {\n    let highlighter = this.highlighters.getItem(bufnr)\n    if (!highlighter) return false\n    return highlighter.hasColor()\n  }\n\n  public hasColorAtPosition(bufnr: number, position: Position): boolean {\n    let highlighter = this.highlighters.getItem(bufnr)\n    if (!highlighter) return false\n    return highlighter.hasColorAtPosition(position)\n  }\n\n  public highlightAll(): void {\n    for (let buf of this.highlighters.items) {\n      buf.highlight()\n    }\n  }\n\n  public async doHighlight(bufnr: number): Promise<void> {\n    let highlighter = this.highlighters.getItem(bufnr)\n    if (highlighter) await highlighter.doHighlight()\n  }\n\n  public async getColorInformation(bufnr: number): Promise<ColorInformation | null> {\n    let highlighter = this.highlighters.getItem(bufnr)\n    if (!highlighter) return null\n    let position = await window.getCursorPosition()\n    for (let info of highlighter.colors) {\n      let { range } = info\n      let { start, end } = range\n      if (position.line == start.line\n        && position.character >= start.character\n        && position.character <= end.character) {\n        return info\n      }\n    }\n    return null\n  }\n\n  public dispose(): void {\n    this.highlighters.dispose()\n    disposeAll(this.disposables)\n  }\n}\n"
  },
  {
    "path": "src/handler/commands.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport commandManager from '../commands'\nimport listManager from '../list/manager'\nimport workspace from '../workspace'\nimport * as Is from '../util/is'\n\nfunction validCommand(command: any): boolean {\n  return command && Is.string(command.id) && Is.string(command.cmd) && command.id.length > 0 && command.cmd.length > 0\n}\n\nexport default class Commands {\n  constructor(private nvim: Neovim) {\n    for (let item of workspace.env.vimCommands) {\n      this.addVimCommand(item)\n    }\n  }\n\n  public addVimCommand(cmd: { id: string; cmd: string; title?: string }): void {\n    if (!validCommand(cmd)) return\n    let id = `vim.${cmd.id}`\n    commandManager.registerCommand(id, () => {\n      this.nvim.command(cmd.cmd, true)\n      this.nvim.redrawVim()\n    })\n    if (cmd.title) commandManager.titles.set(id, cmd.title)\n  }\n\n  public getCommandList(): string[] {\n    return commandManager.commandList.map(o => o.id)\n  }\n\n  public async repeat(): Promise<void> {\n    await commandManager.repeatCommand()\n  }\n\n  public async runCommand(id?: string, ...args: any[]): Promise<unknown> {\n    if (id) return await commandManager.fireCommand(id, ...args)\n    await listManager.start(['commands'])\n  }\n}\n"
  },
  {
    "path": "src/handler/fold.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport type { FoldingRangeKind } from 'vscode-languageserver-types'\nimport languages, { ProviderName } from '../languages'\nimport { HandlerDelegate } from './types'\n\nexport default class FoldHandler {\n  constructor(private nvim: Neovim, private handler: HandlerDelegate) {\n  }\n\n  public async fold(kind?: FoldingRangeKind): Promise<boolean> {\n    let { doc, winid } = await this.handler.getCurrentState()\n    this.handler.checkProvider(ProviderName.FoldingRange, doc.textDocument)\n    await doc.synchronize()\n    let win = this.nvim.createWindow(winid)\n    let foldlevel = await this.nvim.eval('&foldlevel') as number\n    let ranges = await this.handler.withRequestToken('foldingrange', token => {\n      return languages.provideFoldingRanges(doc.textDocument, {}, token)\n    }, true)\n    if (!ranges || !ranges.length) return false\n    if (kind) ranges = ranges.filter(o => o.kind == kind)\n    ranges.sort((a, b) => b.startLine - a.startLine)\n    this.nvim.pauseNotification()\n    win.setOption('foldmethod', 'manual', true)\n    this.nvim.command('normal! zE', true)\n    for (let range of ranges) {\n      let { startLine, endLine } = range\n      let cmd = `${startLine + 1}, ${endLine + 1}fold`\n      this.nvim.command(cmd, true)\n    }\n    win.setOption('foldenable', true, true)\n    win.setOption('foldlevel', foldlevel, true)\n    await this.nvim.resumeNotification(true)\n    return true\n  }\n}\n"
  },
  {
    "path": "src/handler/format.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { Position, Range, TextEdit } from 'vscode-languageserver-types'\nimport commandManager from '../commands'\nimport events from '../events'\nimport languages, { ProviderName } from '../languages'\nimport { createLogger } from '../logger'\nimport Document from '../model/document'\nimport { IConfigurationChangeEvent } from '../types'\nimport { isFalsyOrEmpty } from '../util/array'\nimport { pariedCharacters } from '../util/index'\nimport { isAlphabet } from '../util/string'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { HandlerDelegate } from './types'\nconst logger = createLogger('handler-format')\n\ninterface FormatPreferences {\n  formatOnType: boolean\n  formatOnTypeFiletypes: string[] | null\n  bracketEnterImprove: boolean\n}\n\nexport default class FormatHandler {\n  private preferences: FormatPreferences\n  constructor(\n    private nvim: Neovim,\n    private handler: HandlerDelegate\n  ) {\n    this.setConfiguration()\n    handler.addDisposable(workspace.onDidChangeConfiguration(this.setConfiguration, this))\n    handler.addDisposable(window.onDidChangeActiveTextEditor(() => {\n      this.setConfiguration()\n    }))\n    handler.addDisposable(events.on('Enter', async bufnr => {\n      let res = await events.race(['CursorMovedI'], 100)\n      if (res.args && res.args[0] === bufnr) {\n        await this.handleEnter(bufnr)\n      }\n    }))\n    handler.addDisposable(events.on('TextInsert', async (bufnr: number, _info, character: string) => {\n      let doc = workspace.getDocument(bufnr)\n      if (!events.completing && doc && doc.attached) await this.tryFormatOnType(character, doc)\n    }))\n    handler.addDisposable(commandManager.registerCommand('editor.action.formatDocument', async (uri?: string | number) => {\n      let doc: Document | undefined\n      if (uri) {\n        doc = workspace.getAttachedDocument(uri)\n      } else {\n        let buf = await nvim.buffer\n        doc = workspace.getAttachedDocument(buf.id)\n      }\n      await this.documentFormat(doc)\n    }))\n    commandManager.titles.set('editor.action.formatDocument', 'Format Document')\n  }\n\n  private setConfiguration(e?: IConfigurationChangeEvent): void {\n    if (!e || e.affectsConfiguration('coc.preferences')) {\n      let doc = window.activeTextEditor?.document\n      let config = workspace.getConfiguration('coc.preferences', doc)\n      this.preferences = {\n        formatOnType: config.get<boolean>('formatOnType', false),\n        formatOnTypeFiletypes: config.get('formatOnTypeFiletypes', null),\n        bracketEnterImprove: config.get<boolean>('bracketEnterImprove', true),\n      }\n    }\n  }\n\n  public shouldFormatOnType(filetype: string): boolean {\n    const filetypes = this.preferences.formatOnTypeFiletypes\n    return isFalsyOrEmpty(filetypes) || filetypes.includes(filetype) || filetypes.includes('*')\n  }\n\n  public async tryFormatOnType(ch: string, doc: Document): Promise<boolean> {\n    if (doc.getVar('disable_autoformat', 0)) return false\n    if (!this.preferences.formatOnType) return false\n    if (!ch || isAlphabet(ch.charCodeAt(0))) return false\n    if (!this.shouldFormatOnType(doc.filetype)) return false\n    if (!languages.hasProvider(ProviderName.FormatOnType, doc.textDocument)) {\n      logger.warn(`Format on type provider not found for buffer: ${doc.uri}`)\n      return false\n    }\n    if (!languages.canFormatOnType(ch, doc.textDocument)) return false\n    let position: Position\n    let edits = await this.handler.withRequestToken('Format on type', async token => {\n      position = await window.getCursorPosition()\n      await doc.synchronize()\n      return await languages.provideDocumentOnTypeEdits(ch, doc.textDocument, position, token)\n    })\n    if (edits == null || events.completing) return false\n    if (edits.length === 0) return true\n    await doc.applyEdits(edits, false, true)\n    this.logProvider(doc.bufnr, edits)\n    return true\n  }\n\n  public async formatCurrentBuffer(): Promise<boolean> {\n    let { doc } = await this.handler.getCurrentState()\n    return await this.documentFormat(doc)\n  }\n\n  public async formatCurrentRange(mode: string): Promise<number> {\n    let { doc } = await this.handler.getCurrentState()\n    return await this.documentRangeFormat(doc, mode)\n  }\n\n  public async documentFormat(doc: Document): Promise<boolean> {\n    await doc.synchronize()\n    if (!languages.hasFormatProvider(doc.textDocument)) {\n      throw new Error(`Format provider not found for buffer: ${doc.bufnr}`)\n    }\n    let options = await workspace.getFormatOptions(doc.uri)\n    let textEdits = await this.handler.withRequestToken('format', token => {\n      return languages.provideDocumentFormattingEdits(doc.textDocument, options, token)\n    })\n    if (textEdits && textEdits.length > 0) {\n      await doc.applyEdits(textEdits, false, true)\n      this.logProvider(doc.bufnr, textEdits)\n      return true\n    }\n    return false\n  }\n\n  public async handleEnter(bufnr: number): Promise<void> {\n    let { nvim } = this\n    let { bracketEnterImprove } = this.preferences\n    let doc = workspace.getDocument(bufnr)\n    if (!doc || !doc.attached) return\n    await this.tryFormatOnType('\\n', doc)\n    if (bracketEnterImprove) {\n      let line = (await nvim.call('line', '.') as number) - 1\n      await doc.patchChange()\n      let pre = doc.getline(line - 1)\n      let curr = doc.getline(line)\n      let firstLine = doc.getline(0)\n      let prevChar = pre[pre.length - 1]\n      if (prevChar && pariedCharacters.has(prevChar)) {\n        let nextChar = curr.trim()[0]\n        if (nextChar && pariedCharacters.get(prevChar) == nextChar) {\n          let edits: TextEdit[] = []\n          let pos: Position = Position.create(line - 1, pre.length)\n          let opts = await workspace.getFormatOptions(doc.uri)\n          let space = opts.insertSpaces ? ' '.repeat(opts.tabSize) : '\\t'\n          let preIndent = pre.match(/^\\s*/)[0]\n          let currIndent = curr.match(/^\\s*/)[0]\n          let newText = '\\n' + preIndent + space\n          // vim legacy script needs continuation markers\n          if (doc.filetype == 'vim' && !firstLine.startsWith('vim9script')) {\n            edits.push({ range: Range.create(line, currIndent.length, line, currIndent.length), newText: '  \\\\ ' })\n            newText = '\\n' + currIndent + space + '\\\\ '\n          }\n          edits.push({ range: Range.create(pos, pos), newText })\n          await doc.applyEdits(edits, true)\n          await window.moveTo(Position.create(line, newText.length - 1))\n        }\n      }\n    }\n  }\n\n  public logProvider(bufnr: number, edits: TextEdit[] | undefined): void {\n    if (!Array.isArray(edits) || edits.length === 0) return\n    let extensionName = edits['__extensionName']\n    if (extensionName) logger.info(`Format buffer ${bufnr} by ${extensionName}`)\n  }\n\n  public async documentRangeFormat(doc: Document, mode?: string): Promise<number> {\n    this.handler.checkProvider(ProviderName.FormatRange, doc.textDocument)\n    await doc.synchronize()\n    let range: Range\n    if (mode) {\n      range = await window.getSelectedRange(mode)\n      if (!range) return -1\n    } else {\n      let [lnum, count, mode] = await this.nvim.eval(\"[v:lnum,v:count,mode()]\") as [number, number, string]\n      // we can't handle\n      if (count == 0 || mode == 'i' || mode == 'R') return -1\n      range = Range.create(lnum - 1, 0, lnum - 1 + count, 0)\n    }\n    let options = await workspace.getFormatOptions(doc.uri)\n    let textEdits = await this.handler.withRequestToken('Format range', token => {\n      return languages.provideDocumentRangeFormattingEdits(doc.textDocument, range, options, token)\n    })\n    if (!isFalsyOrEmpty(textEdits)) {\n      await doc.applyEdits(textEdits, false, true)\n      this.logProvider(doc.bufnr, textEdits)\n      return 0\n    }\n    return -1\n  }\n}\n"
  },
  {
    "path": "src/handler/highlights.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { DocumentHighlight, DocumentHighlightKind, Position, Range } from 'vscode-languageserver-types'\nimport commands from '../commands'\nimport events from '../events'\nimport languages, { ProviderName } from '../languages'\nimport Document from '../model/document'\nimport { IConfigurationChangeEvent } from '../types'\nimport { disposeAll } from '../util'\nimport { comparePosition, compareRangesUsingStarts } from '../util/position'\nimport { CancellationTokenSource, Disposable } from '../util/protocol'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { HandlerDelegate } from './types'\n\ninterface HighlightConfig {\n  limit: number\n  priority: number\n  timeout: number\n}\n\n/**\n * Highlight same symbols on current window.\n * Highlights are added to window by matchaddpos.\n */\nexport default class Highlights {\n  private config: HighlightConfig\n  private disposables: Disposable[] = []\n  private tokenSource: CancellationTokenSource\n  private highlights: Map<number, DocumentHighlight[]> = new Map()\n  private timer: NodeJS.Timeout\n  constructor(private nvim: Neovim, private handler: HandlerDelegate) {\n    events.on(['CursorMoved', 'CursorMovedI'], () => {\n      this.cancel()\n      this.clearHighlights()\n    }, null, this.disposables)\n    this.loadConfiguration()\n    workspace.onDidChangeConfiguration(this.loadConfiguration, this, this.disposables)\n    window.onDidChangeActiveTextEditor(() => {\n      this.loadConfiguration()\n    }, null, this.disposables)\n    commands.register({\n      id: 'document.jumpToNextSymbol',\n      execute: async () => {\n        await this.jumpSymbol('next')\n      }\n    }, false, 'Jump to next symbol highlight position.')\n    commands.register({\n      id: 'document.jumpToPrevSymbol',\n      execute: async () => {\n        await this.jumpSymbol('previous')\n      }\n    }, false, 'Jump to previous symbol highlight position.')\n  }\n\n  private loadConfiguration(e?: IConfigurationChangeEvent): void {\n    let config = workspace.getConfiguration('documentHighlight', this.handler.uri)\n    if (!e || e.affectsConfiguration('documentHighlight')) {\n      this.config = Object.assign(this.config || {}, {\n        limit: config.get<number>('limit', 200),\n        priority: config.get<number>('priority', -1),\n        timeout: config.get<number>('timeout', 300)\n      })\n    }\n  }\n\n  public isEnabled(bufnr: number, cursors: number): boolean {\n    let doc = workspace.getDocument(bufnr)\n    if (!doc || !doc.attached || cursors) return false\n    if (!languages.hasProvider(ProviderName.DocumentHighlight, doc.textDocument)) return false\n    return true\n  }\n\n  public clearHighlights(): void {\n    if (this.highlights.size == 0) return\n    for (let winid of this.highlights.keys()) {\n      let win = this.nvim.createWindow(winid)\n      win.clearMatchGroup('^CocHighlight')\n    }\n    this.highlights.clear()\n  }\n\n  public async highlight(): Promise<void> {\n    let { nvim } = this\n    this.cancel()\n    let [bufnr, winid, pos, cursors] = await nvim.eval(`[bufnr(\"%\"),win_getid(),coc#cursor#position(),get(b:,'coc_cursors_activated',0)]`) as [number, number, [number, number], number]\n    if (!this.isEnabled(bufnr, cursors)) return\n    let doc = workspace.getDocument(bufnr)\n    let highlights = await this.getHighlights(doc, Position.create(pos[0], pos[1]))\n    if (!highlights) return\n    let groups: { [index: string]: Range[] } = {}\n    for (let hl of highlights) {\n      if (!Range.is(hl.range)) continue\n      let hlGroup = hl.kind == DocumentHighlightKind.Text\n        ? 'CocHighlightText'\n        : hl.kind == DocumentHighlightKind.Read ? 'CocHighlightRead' : 'CocHighlightWrite'\n      groups[hlGroup] = groups[hlGroup] || []\n      groups[hlGroup].push(hl.range)\n    }\n    let win = nvim.createWindow(winid)\n    nvim.pauseNotification()\n    win.clearMatchGroup('^CocHighlight')\n    for (let [hlGroup, ranges] of Object.entries(groups)) {\n      win.highlightRanges(hlGroup, ranges, -1, true)\n    }\n    nvim.resumeNotification(true, true)\n    this.highlights.set(winid, highlights)\n  }\n\n  public async jumpSymbol(direction: 'previous' | 'next'): Promise<void> {\n    let ranges = await this.getSymbolsRanges()\n    if (!ranges) return\n    let pos = await window.getCursorPosition()\n    if (direction == 'next') {\n      for (let i = 0; i <= ranges.length - 1; i++) {\n        if (comparePosition(ranges[i].start, pos) > 0) {\n          await window.moveTo(ranges[i].start)\n          return\n        }\n      }\n      await window.moveTo(ranges[0].start)\n    } else {\n      for (let i = ranges.length - 1; i >= 0; i--) {\n        if (comparePosition(ranges[i].end, pos) < 0) {\n          await window.moveTo(ranges[i].start)\n          return\n        }\n      }\n      await window.moveTo(ranges[ranges.length - 1].start)\n    }\n  }\n\n  public async getSymbolsRanges(): Promise<Range[]> {\n    let { doc, position } = await this.handler.getCurrentState()\n    this.handler.checkProvider(ProviderName.DocumentHighlight, doc.textDocument)\n    let highlights = await this.getHighlights(doc, position)\n    if (!highlights) return null\n    return highlights.filter(o => Range.is(o.range)).map(o => o.range).sort((a, b) => {\n      return compareRangesUsingStarts(a, b)\n    })\n  }\n\n  public hasHighlights(winid: number): boolean {\n    return this.highlights.get(winid) != null\n  }\n\n  public async getHighlights(doc: Document, position: Position): Promise<DocumentHighlight[]> {\n    let line = doc.getline(position.line)\n    let ch = line[position.character]\n    if (!ch || !doc.isWord(ch)) return null\n    await doc.synchronize()\n    this.cancel()\n    let source = this.tokenSource = new CancellationTokenSource()\n    let timer = this.timer = setTimeout(() => {\n      source.cancel()\n    }, this.config.timeout)\n    let highlights = await languages.getDocumentHighLight(doc.textDocument, position, source.token)\n    clearTimeout(timer)\n    if (source.token.isCancellationRequested) return null\n    return highlights\n  }\n\n  private cancel(): void {\n    if (this.tokenSource) {\n      this.tokenSource.cancel()\n      this.tokenSource.dispose()\n      this.tokenSource = null\n    }\n  }\n\n  public dispose(): void {\n    if (this.timer) clearTimeout(this.timer)\n    this.cancel()\n    this.highlights.clear()\n    disposeAll(this.disposables)\n  }\n}\n"
  },
  {
    "path": "src/handler/hover.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { DefinitionLink, Hover, MarkedString, MarkupContent, Position, Range } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport { IConfigurationChangeEvent } from '../configuration/types'\nimport languages, { ProviderName } from '../languages'\nimport Document from '../model/document'\nimport { TextDocumentContentProvider } from '../provider'\nimport { Documentation, FloatConfig, FloatFactory, HoverTarget } from '../types'\nimport { disposeAll, getConditionValue } from '../util'\nimport { isFalsyOrEmpty } from '../util/array'\nimport { readFileLines } from '../util/fs'\nimport { isMarkdown } from '../util/is'\nimport { fs } from '../util/node'\nimport { CancellationTokenSource, Disposable } from '../util/protocol'\nimport { characterIndex } from '../util/string'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { HandlerDelegate } from './types'\n\ninterface HoverConfig {\n  target: HoverTarget\n  floatConfig: FloatConfig\n  previewMaxHeight: number\n  autoHide: boolean\n}\n\ninterface HoverLocation {\n  bufnr?: number\n  line: number\n  col: number\n}\n\nconst highlightDelay = getConditionValue(500, 10)\n\nexport default class HoverHandler {\n  private hoverFactory: FloatFactory\n  private disposables: Disposable[] = []\n  private documentLines: string[] = []\n  private config: HoverConfig\n  private timer: NodeJS.Timeout\n  private hasProvider = false\n  constructor(private nvim: Neovim, private handler: HandlerDelegate) {\n    this.loadConfiguration()\n    workspace.onDidChangeConfiguration(this.loadConfiguration, this, this.disposables)\n    this.hoverFactory = window.createFloatFactory({\n      modes: ['n'],\n      autoHide: this.config.autoHide\n    })\n    this.disposables.push(this.hoverFactory)\n    window.onDidChangeActiveTextEditor(() => {\n      this.loadConfiguration()\n    }, null, this.disposables)\n  }\n\n  private registerProvider(): void {\n    if (this.hasProvider) return\n    this.hasProvider = true\n    let { nvim } = this\n    let provider: TextDocumentContentProvider = {\n      onDidChange: null,\n      provideTextDocumentContent: async () => {\n        nvim.pauseNotification()\n        nvim.command('setlocal conceallevel=2 nospell nofoldenable wrap', true)\n        nvim.command('setlocal bufhidden=wipe nobuflisted', true)\n        nvim.command('setfiletype markdown', true)\n        nvim.command(`if winnr('j') != winnr('k') | exe \"normal! z${Math.min(this.documentLines.length, this.config.previewMaxHeight)}\\\\<cr>\" | endif`, true)\n        await nvim.resumeNotification()\n        return this.documentLines.join('\\n')\n      }\n    }\n    this.disposables.push(workspace.registerTextDocumentContentProvider('coc', provider))\n  }\n\n  private loadConfiguration(e?: IConfigurationChangeEvent): void {\n    if (!e || e.affectsConfiguration('hover')) {\n      let config = workspace.getConfiguration('hover', this.handler.uri)\n      this.config = {\n        floatConfig: config.get('floatConfig', {}),\n        autoHide: config.get('autoHide', true),\n        target: config.get<HoverTarget>('target', 'float'),\n        previewMaxHeight: config.get<number>('previewMaxHeight', 12)\n      }\n      if (this.config.target == 'preview') {\n        this.registerProvider()\n      }\n    }\n  }\n\n  public async onHover(hoverTarget?: HoverTarget): Promise<boolean> {\n    let { doc, position, winid } = await this.handler.getCurrentState()\n    if (hoverTarget == 'preview') this.registerProvider()\n    this.handler.checkProvider(ProviderName.Hover, doc.textDocument)\n    await doc.synchronize()\n    let hovers = await this.handler.withRequestToken('hover', token => {\n      return languages.getHover(doc.textDocument, position, token)\n    }, true)\n    if (hovers == null || !hovers.length) return false\n    let hover = hovers.find(o => Range.is(o.range))\n    if (hover?.range) {\n      let win = this.nvim.createWindow(winid)\n      win.highlightRanges('CocHoverRange', [hover.range], 1024, true)\n      this.timer = setTimeout(() => {\n        win.clearMatchGroup('CocHoverRange')\n        this.nvim.redrawVim()\n      }, 500)\n    }\n    await this.previewHover(hovers, hoverTarget)\n    return true\n  }\n\n  public async definitionHover(hoverTarget: HoverTarget): Promise<boolean> {\n    const { doc, position, winid } = await this.handler.getCurrentState()\n    if (hoverTarget == 'preview') this.registerProvider()\n    this.handler.checkProvider(ProviderName.Hover, doc.textDocument)\n    await doc.synchronize()\n    const hovers: (Hover | Documentation)[] = await this.handler.withRequestToken('hover', token => {\n      return languages.getHover(doc.textDocument, position, token)\n    }, true)\n    if (isFalsyOrEmpty(hovers)) return false\n    const defs = await this.handler.withRequestToken('definitionHover', token => {\n      return languages.getDefinitionLinks(doc.textDocument, position, token)\n    }, false)\n    // could be cancelled\n    if (defs == null) return false\n    await addDefinitions(hovers, defs, doc.filetype)\n    let hover = hovers.find(o => Hover.is(o) && Range.is(o.range)) as Hover | undefined\n    if (hover?.range) {\n      let win = this.nvim.createWindow(winid)\n      win.highlightRanges('CocHoverRange', [hover.range], 1024, true)\n      this.timer = setTimeout(() => {\n        win.clearMatchGroup('CocHoverRange')\n        this.nvim.redrawVim()\n      }, highlightDelay)\n    }\n    await this.previewHover(hovers, hoverTarget)\n    return true\n  }\n\n  private async previewHover(hovers: (Hover | Documentation)[], target?: string): Promise<void> {\n    let docs: Documentation[] = []\n    target = target ?? this.config.target\n    let isPreview = target === 'preview'\n    for (let hover of hovers) {\n      if (isDocumentation(hover)) {\n        docs.push(hover)\n        continue\n      }\n      let { contents } = hover\n      if (Array.isArray(contents)) {\n        for (let item of contents) {\n          if (typeof item === 'string') {\n            addDocument(docs, item, 'markdown', isPreview)\n          } else {\n            addDocument(docs, item.value, item.language, isPreview)\n          }\n        }\n      } else if (MarkedString.is(contents)) {\n        if (typeof contents == 'string') {\n          addDocument(docs, contents, 'markdown', isPreview)\n        } else {\n          addDocument(docs, contents.value, contents.language, isPreview)\n        }\n      } else if (MarkupContent.is(contents)) {\n        addDocument(docs, contents.value, isMarkdown(contents) ? 'markdown' : 'txt', isPreview)\n      }\n    }\n    if (target == 'float') {\n      await this.hoverFactory.show(docs, this.config.floatConfig)\n      return\n    }\n    let lines = docs.reduce((p, c) => {\n      let arr = c.content.split(/\\r?\\n/)\n      if (p.length > 0) p.push('')\n      p.push(...arr)\n      return p\n    }, [])\n    if (target == 'echo') {\n      const msg = lines.join('\\n').trim()\n      await this.nvim.call('coc#ui#echo_hover', [msg])\n    } else {\n      this.documentLines = lines\n      await this.nvim.command(`noswapfile pedit coc://document`)\n    }\n  }\n\n  /**\n   * Get hover text array\n   */\n  public async getHover(loc?: HoverLocation): Promise<string[]> {\n    let result: string[] = []\n    let doc: Document\n    let position: Position\n    if (!loc) {\n      let state = await this.handler.getCurrentState()\n      doc = state.doc\n      position = state.position\n    } else {\n      doc = loc.bufnr ? workspace.getAttachedDocument(loc.bufnr) : await workspace.document\n      let line = doc.getline(loc.line - 1)\n      let character = characterIndex(line, loc.col - 1)\n      position = Position.create(loc.line - 1, character)\n    }\n    this.handler.checkProvider(ProviderName.Hover, doc.textDocument)\n    await doc.synchronize()\n    let tokenSource = new CancellationTokenSource()\n    let hovers = await languages.getHover(doc.textDocument, position, tokenSource.token)\n    for (let h of hovers) {\n      let { contents } = h\n      if (Array.isArray(contents)) {\n        contents.forEach(c => {\n          result.push(typeof c === 'string' ? c : c.value)\n        })\n      } else if (MarkupContent.is(contents)) {\n        result.push(contents.value)\n      } else {\n        result.push(typeof contents === 'string' ? contents : contents.value)\n      }\n    }\n    result = result.filter(s => s != null && s.length > 0)\n    return result\n  }\n\n  public dispose(): void {\n    if (this.timer) clearTimeout(this.timer)\n    disposeAll(this.disposables)\n  }\n}\n\nexport async function addDefinitions(hovers: (Hover | Documentation)[], definitions: DefinitionLink[], filetype: string): Promise<void> {\n  for (const def of definitions) {\n    if (!def?.targetRange) continue\n    const { start, end } = def.targetRange\n    // def.targetSelectionRange\n    const endLine = end.line - start.line >= 100 ? start.line + 100 : (end.character == 0 ? end.line - 1 : end.line)\n    let lines = await readLines(def.targetUri, start.line, endLine)\n    if (lines.length) {\n      let indent = lines[0].match(/^\\s*/)[0]\n      if (indent) lines = lines.map(l => l.startsWith(indent) ? l.substring(indent.length) : l)\n      hovers.push({ content: lines.join('\\n'), filetype })\n    }\n  }\n}\n\nexport function addDocument(docs: Documentation[], text: string, filetype: string, isPreview = false): void {\n  let content = text.trim()\n  if (!content.length) return\n  if (isPreview && filetype !== 'markdown') {\n    content = '``` ' + filetype + '\\n' + content + '\\n```'\n  }\n  docs.push({ content, filetype })\n}\n\nexport function isDocumentation(obj: any): obj is Documentation {\n  if (!obj) return false\n  return typeof obj.filetype === 'string' && typeof obj.content === 'string'\n}\n\nexport async function readLines(uri: string, start: number, end: number): Promise<string[]> {\n  let doc = workspace.getDocument(uri)\n  if (doc) return doc.getLines(start, end + 1)\n  let fsPath = URI.parse(uri).fsPath\n  if (!fs.existsSync(fsPath)) return []\n  return await readFileLines(fsPath, start, end)\n}\n"
  },
  {
    "path": "src/handler/index.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { CodeAction, CodeActionKind, Location, Position, Range, SymbolKind } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport commands from '../commands'\nimport events from '../events'\nimport languages, { ProviderName } from '../languages'\nimport { createLogger } from '../logger'\nimport Document from '../model/document'\nimport { StatusBarItem } from '../model/status'\nimport { TextDocumentMatch } from '../types'\nimport { disposeAll, getConditionValue } from '../util'\nimport { getSymbolKind } from '../util/convert'\nimport * as Is from '../util/is'\nimport { hasOwnProperty, toObject } from '../util/object'\nimport { CancellationToken, CancellationTokenSource, Disposable } from '../util/protocol'\nimport { getRangesFromEdit } from '../util/textedit'\nimport window from '../window'\nimport workspace from '../workspace'\nimport CallHierarchy from './callHierarchy'\nimport CodeActions from './codeActions'\nimport CodeLens from './codelens/index'\nimport Colors from './colors/index'\nimport Commands from './commands'\nimport Fold from './fold'\nimport Format from './format'\nimport Highlights from './highlights'\nimport HoverHandler from './hover'\nimport InlayHintHandler from './inlayHint/index'\nimport InlineCompletion, { InlineSuggestOption } from './inline'\nimport LinkedEditingHandler from './linkedEditing'\nimport Links from './links'\nimport Locations from './locations'\nimport Refactor from './refactor/index'\nimport Rename from './rename'\nimport SelectionRange from './selectionRange'\nimport SemanticTokens from './semanticTokens/index'\nimport Signature from './signature'\nimport Symbols from './symbols/index'\nimport TypeHierarchy from './typeHierarchy'\nimport { HandlerDelegate } from './types'\nimport WorkspaceHandler from './workspace'\nconst logger = createLogger('Handler')\nconst requestTimeout = getConditionValue(500, 10)\n\nexport interface CurrentState {\n  doc: Document\n  winid: number\n  position: Position\n  // :h mode()\n  mode: string\n}\n\nexport default class Handler implements HandlerDelegate {\n  public readonly documentHighlighter: Highlights\n  public readonly colors: Colors\n  public readonly signature: Signature\n  public readonly locations: Locations\n  public readonly symbols: Symbols\n  public readonly refactor: Refactor\n  public readonly codeActions: CodeActions\n  public readonly format: Format\n  public readonly hover: HoverHandler\n  public readonly codeLens: CodeLens\n  public readonly commands: Commands\n  public readonly links: Links\n  public readonly rename: Rename\n  public readonly fold: Fold\n  public readonly selectionRange: SelectionRange\n  public readonly callHierarchy: CallHierarchy\n  public readonly typeHierarchy: TypeHierarchy\n  public readonly inlineCompletion: InlineCompletion\n  public readonly semanticHighlighter: SemanticTokens\n  public readonly workspace: WorkspaceHandler\n  public readonly linkedEditingHandler: LinkedEditingHandler\n  public readonly inlayHintHandler: InlayHintHandler\n  private _requestStatusItem: StatusBarItem\n  private requestTokenSource: CancellationTokenSource | undefined\n  private requestTimer: NodeJS.Timeout\n  private disposables: Disposable[] = []\n\n  constructor(private nvim: Neovim) {\n    events.on(['CursorMoved', 'CursorMovedI', 'InsertEnter', 'InsertSnippet', 'InsertLeave'], () => {\n      if (this.requestTokenSource) {\n        this.requestTokenSource.cancel()\n        this.requestTokenSource = null\n      }\n    }, null, this.disposables)\n    this.fold = new Fold(nvim, this)\n    this.links = new Links(nvim, this)\n    this.codeLens = new CodeLens(nvim)\n    this.colors = new Colors(nvim, this)\n    this.format = new Format(nvim, this)\n    this.symbols = new Symbols(nvim, this)\n    this.refactor = new Refactor(nvim, this)\n    this.hover = new HoverHandler(nvim, this)\n    this.locations = new Locations(nvim, this)\n    this.signature = new Signature(nvim, this)\n    this.rename = new Rename(nvim, this)\n    this.workspace = new WorkspaceHandler(nvim)\n    this.codeActions = new CodeActions(nvim, this)\n    this.commands = new Commands(nvim)\n    this.callHierarchy = new CallHierarchy(nvim, this)\n    this.typeHierarchy = new TypeHierarchy(nvim, this)\n    this.documentHighlighter = new Highlights(nvim, this)\n    this.semanticHighlighter = new SemanticTokens(nvim)\n    this.selectionRange = new SelectionRange(nvim, this)\n    this.linkedEditingHandler = new LinkedEditingHandler(nvim, this)\n    this.inlayHintHandler = new InlayHintHandler(nvim, this)\n    this.inlineCompletion = new InlineCompletion(nvim, this)\n    this.disposables.push({\n      dispose: () => {\n        this.callHierarchy.dispose()\n        this.typeHierarchy.dispose()\n        this.codeLens.dispose()\n        this.links.dispose()\n        this.refactor.dispose()\n        this.signature.dispose()\n        this.symbols.dispose()\n        this.hover.dispose()\n        this.colors.dispose()\n        this.documentHighlighter.dispose()\n        this.semanticHighlighter.dispose()\n        this.inlineCompletion.dispose()\n      }\n    })\n    this.registerCommands()\n  }\n\n  private registerCommands(): void {\n    commands.register({\n      id: 'document.renameCurrentWord',\n      execute: async () => {\n        let doc = await workspace.document\n        let edit = await this.rename.getWordEdit()\n        let ranges = getRangesFromEdit(doc.uri, toObject(edit))\n        if (!ranges) return window.showWarningMessage('Invalid position')\n        await commands.executeCommand('editor.action.addRanges', ranges)\n      }\n    }, false, 'rename word under cursor in current buffer by multiple cursors.')\n    commands.register({\n      id: ['workbench.action.reloadWindow', 'editor.action.restart'],\n      execute: () => {\n        this.nvim.command('CocRestart', true)\n      }\n    }, true)\n    commands.register({\n      id: 'workbench.action.openSettingsJson',\n      execute: () => {\n        this.nvim.command('CocConfig', true)\n      }\n    }, true)\n\n    this.register('vscode.open', async (url: string | URI) => {\n      await workspace.openResource(url.toString())\n    })\n    this.register('editor.action.doCodeAction', async (action: CodeAction) => {\n      await this.codeActions.applyCodeAction(action)\n    })\n    this.register('editor.action.triggerParameterHints', async () => {\n      await this.signature.triggerSignatureHelp()\n    })\n    this.register('editor.action.showReferences', async (uri: string | URI, position: Position, references: Location[]) => {\n      await workspace.jumpTo(uri, position)\n      await workspace.showLocations(references)\n    })\n    this.register('editor.action.rename', async (uri: string | URI | [URI, Position], position: Position, newName?: string) => {\n      if (Array.isArray(uri)) {\n        position = uri[1]\n        uri = uri[0]\n      }\n      await workspace.jumpTo(uri, position)\n      return await this.rename.rename(newName)\n    })\n    this.register('editor.action.format', async () => {\n      await this.format.formatCurrentBuffer()\n    })\n    this.register('editor.action.showRefactor', async (locations: Location[]) => {\n      let locs = locations.filter(o => Location.is(o))\n      return await this.refactor.fromLocations(locs)\n    })\n    this.register('editor.action.triggerInlineCompletion', async (option?: InlineSuggestOption) => {\n      let bufnr = await this.nvim.eval('bufnr(\"%\")') as number\n      return await this.inlineCompletion.trigger(bufnr, option)\n    })\n  }\n\n  private register<T>(key, handler: (...args: any[]) => T | Promise<T>): void {\n    this.disposables.push(commands.registerCommand(key, handler, null, true))\n  }\n\n  private get requestStatusItem(): StatusBarItem {\n    if (this._requestStatusItem) return this._requestStatusItem\n    this._requestStatusItem = window.createStatusBarItem(0, { progress: true })\n    return this._requestStatusItem\n  }\n\n  private get labels(): { [key: string]: string } {\n    let configuration = workspace.initialConfiguration\n    return configuration.get('suggest.completionItemKindLabels', {})\n  }\n\n  public get uri(): string | undefined {\n    return window.activeTextEditor?.uri\n  }\n\n  public async getCurrentState(): Promise<CurrentState> {\n    let { nvim } = this\n    let [bufnr, [line, character], winid, mode] = await nvim.eval(\"[bufnr('%'),coc#cursor#position(),win_getid(),mode()]\") as [number, [number, number], number, string]\n    let doc = workspace.getAttachedDocument(bufnr)\n    return {\n      doc,\n      mode,\n      position: Position.create(line, character),\n      winid\n    }\n  }\n\n  public addDisposable(disposable: Disposable): void {\n    this.disposables.push(disposable)\n  }\n\n  /**\n   * Throw error when provider doesn't exist.\n   */\n  public checkProvider(id: ProviderName, document: TextDocumentMatch): void {\n    if (!languages.hasProvider(id, document)) {\n      throw new Error(`${id} provider not found for current buffer, your language server doesn't support it.`)\n    }\n  }\n\n  public async withRequestToken<T>(name: string, fn: (token: CancellationToken) => Thenable<T>, checkEmpty?: boolean): Promise<T | null> {\n    if (this.requestTokenSource) {\n      this.requestTokenSource.cancel()\n      this.requestTokenSource.dispose()\n    }\n    clearTimeout(this.requestTimer)\n    let statusItem = this.requestStatusItem\n    this.requestTokenSource = new CancellationTokenSource()\n    let { token } = this.requestTokenSource\n    token.onCancellationRequested(() => {\n      statusItem.text = `${name} request canceled`\n      statusItem.isProgress = false\n      this.requestTimer = setTimeout(() => {\n        statusItem.hide()\n      }, requestTimeout)\n    })\n    statusItem.isProgress = true\n    statusItem.text = `requesting ${name}`\n    statusItem.show()\n    let res: T\n    try {\n      res = await Promise.resolve(fn(token))\n    } catch (e) {\n      logger.error(`Error on request ${name}`, e)\n      this.nvim.errWriteLine(`Error on ${name}: ${e}`)\n    }\n    if (this.requestTokenSource) {\n      this.requestTokenSource.dispose()\n      this.requestTokenSource = undefined\n    }\n    if (token.isCancellationRequested) return null\n    statusItem.hide()\n    if (checkEmpty && (!res || (Array.isArray(res) && res.length == 0))) {\n      void window.showWarningMessage(`${name} not found`)\n      return null\n    }\n    return res\n  }\n\n  public getIcon(kind: SymbolKind): { text: string, hlGroup: string } {\n    let { labels } = this\n    let kindText = getSymbolKind(kind)\n    const key = kindText[0].toLowerCase() + kindText.slice(1)\n    let text = hasOwnProperty(labels, key) ? labels[key] : undefined\n    if (!Is.string(text) || !text.length) text = Is.string(labels['default']) ? labels['default'] : kindText[0].toLowerCase()\n    return {\n      text,\n      hlGroup: kindText == 'Unknown' ? 'CocSymbolDefault' : `CocSymbol${kindText}`\n    }\n  }\n\n  public async getCodeActions(doc: Document, range?: Range, only?: CodeActionKind[]): Promise<CodeAction[]> {\n    let codeActions = await this.codeActions.getCodeActions(doc, range, only)\n    return codeActions.filter(o => !o.disabled)\n  }\n\n  public async applyCodeAction(action: CodeAction): Promise<void> {\n    await this.codeActions.applyCodeAction(action)\n  }\n\n  public async hasProvider(id: string, bufnr?: number): Promise<boolean> {\n    if (!bufnr) bufnr = await this.nvim.call('bufnr', '%') as number\n    let doc = workspace.getDocument(bufnr)\n    if (!doc || !doc.attached) return false\n    return languages.hasProvider(id as ProviderName, doc.textDocument)\n  }\n\n  public dispose(): void {\n    if (this.requestTimer) {\n      clearTimeout(this.requestTimer)\n    }\n    disposeAll(this.disposables)\n  }\n}\n"
  },
  {
    "path": "src/handler/inlayHint/buffer.ts",
    "content": "'use strict'\nimport { Neovim, VirtualTextOption } from '@chemzqm/neovim'\nimport { InlayHintKind, Range } from 'vscode-languageserver-types'\nimport events from '../../events'\nimport languages, { ProviderName } from '../../languages'\nimport { createLogger } from '../../logger'\nimport { SyncItem } from '../../model/bufferSync'\nimport Document from '../../model/document'\nimport Regions from '../../model/regions'\nimport { getLabel, InlayHintWithProvider } from '../../provider/inlayHintManager'\nimport { getConditionValue, waitWithToken } from '../../util'\nimport { CancellationError, onUnexpectedError } from '../../util/errors'\nimport { positionInRange } from '../../util/position'\nimport { CancellationToken, CancellationTokenSource, Emitter, Event } from '../../util/protocol'\nimport { byteIndex } from '../../util/string'\nimport window from '../../window'\nimport workspace from '../../workspace'\nconst logger = createLogger('inlayHint-buffer')\n\nexport interface InlayHintConfig {\n  enable: boolean\n  position: InlayHintPosition,\n  display: boolean\n  filetypes: string[]\n  refreshOnInsertMode: boolean\n  enableParameter: boolean\n  maximumLength: number\n}\n\nexport interface VirtualTextItem extends VirtualTextOption {\n  /**\n   * Zero based line number\n   */\n  line: number\n  /**\n   * List with [text, hl_group]\n   */\n  blocks: [string, string][]\n}\n\nexport enum InlayHintPosition {\n  Inline = \"inline\",\n  Eol = \"eol\",\n}\n\nexport interface RenderConfig {\n  winid: number\n  region: [number, number]\n}\n\nlet srcId: number | undefined\nconst debounceInterval = getConditionValue(150, 10)\nconst requestDelay = getConditionValue(500, 10)\n\nfunction getHighlightGroup(kind: InlayHintKind): string {\n  switch (kind) {\n    case InlayHintKind.Parameter:\n      return 'CocInlayHintParameter'\n    case InlayHintKind.Type:\n      return 'CocInlayHintType'\n    default:\n      return 'CocInlayHint'\n  }\n}\n\n/**\n * Full Virtual text render when first render.\n * Update visible regions on TextChange and visible lines change.\n */\nexport default class InlayHintBuffer implements SyncItem {\n  private tokenSource: CancellationTokenSource\n  private regions = new Regions()\n  private _config: InlayHintConfig | undefined\n  private _dirty = false\n  private _changedtick: number\n  // Saved for resolve and TextEdits in the future.\n  private currentHints: InlayHintWithProvider[] = []\n  private readonly _onDidRefresh = new Emitter<void>()\n  public readonly onDidRefresh: Event<void> = this._onDidRefresh.event\n  constructor(\n    private readonly nvim: Neovim,\n    public readonly doc: Document\n  ) {\n    this.render().catch(onUnexpectedError)\n  }\n\n  public get config(): InlayHintConfig {\n    if (this._config) return this._config\n    this.loadConfiguration()\n    return this._config\n  }\n\n  public loadConfiguration(): void {\n    let config = workspace.getConfiguration('inlayHint', this.doc)\n    let changeEnable = this._config && this._config.enable !== config.enable\n    let changeDisplay = this._config && this._config.display !== config.display\n    this._config = {\n      enable: config.get<boolean>('enable'),\n      position: config.get<InlayHintPosition>('position'),\n      display: config.get<boolean>('display', true),\n      filetypes: config.get<string[]>('filetypes'),\n      refreshOnInsertMode: config.get<boolean>('refreshOnInsertMode'),\n      enableParameter: config.get<boolean>('enableParameter'),\n      maximumLength: config.get<number>('maximumLength', 0),\n    }\n    if (changeEnable || changeDisplay) {\n      let { enable, display } = this._config\n      if (enable && display) {\n        this.render(undefined, 0).catch(onUnexpectedError)\n      } else {\n        this.clearCache()\n        this.clearVirtualText()\n      }\n    }\n  }\n\n  public onInsertLeave(): void {\n    if (this.config.refreshOnInsertMode || this.doc.changedtick === this._changedtick) return\n    this.render().catch(onUnexpectedError)\n  }\n\n  public onInsertEnter(): void {\n    this._changedtick = this.doc.changedtick\n    if (this.config.refreshOnInsertMode) return\n    this.cancel()\n  }\n\n  public get current(): ReadonlyArray<InlayHintWithProvider> {\n    return this.currentHints\n  }\n\n  public get enabled(): boolean {\n    if (!this.config.display || !this.configEnabled) return false\n    return this.hasProvider\n  }\n\n  private get hasProvider(): boolean {\n    return languages.hasProvider(ProviderName.InlayHint, this.doc)\n  }\n\n  public get configEnabled(): boolean {\n    let { filetypes, enable } = this.config\n    if (Array.isArray(filetypes)) return filetypes.includes('*') || filetypes.includes(this.doc.filetype)\n    return enable === true\n  }\n\n  public enable() {\n    this.checkState()\n    this.config.display = true\n    this.render(undefined, 0).catch(onUnexpectedError)\n  }\n\n  public disable() {\n    this.checkState()\n    this.config.display = false\n    this.clearCache()\n    this.clearVirtualText()\n  }\n\n  private checkState(): void {\n    if (!languages.hasProvider(ProviderName.InlayHint, this.doc.textDocument)) throw new Error('Inlay hint provider not found for current document')\n    if (!this.configEnabled) throw new Error(`Filetype \"${this.doc.filetype}\" not enabled by inlayHint configuration, see ':h coc-config-inlayHint'`)\n  }\n\n  public toggle(): void {\n    if (this.config.display) {\n      this.disable()\n    } else {\n      this.enable()\n    }\n  }\n\n  public clearCache(): void {\n    this.cancel()\n    this.currentHints = []\n    this.regions.clear()\n  }\n\n  public onTextChange(): void {\n    this.clearCache()\n  }\n\n  public onChange(): void {\n    this.render().catch(onUnexpectedError)\n  }\n\n  public cancel(): void {\n    if (this.tokenSource) {\n      this.tokenSource.cancel()\n      this.tokenSource = null\n    }\n  }\n\n  public onVisible(winid: number, region: Readonly<[number, number]>): void {\n    // Ensure rendered once before range render.\n    if (!this._dirty) return\n    // already debounced\n    this.render({\n      winid,\n      region: [region[0], region[1]]\n    }, 0).catch(onUnexpectedError)\n  }\n\n  public async render(config?: RenderConfig, delay?: number): Promise<void> {\n    if (!this.enabled) return\n    if (!this.config.refreshOnInsertMode && events.bufnr === this.doc.bufnr && events.insertMode) return\n    this.cancel()\n    this.tokenSource = new CancellationTokenSource()\n    let token = this.tokenSource.token\n    await waitWithToken(typeof delay === 'number' ? delay : debounceInterval, token)\n    if (!srcId) srcId = await this.nvim.createNamespace('coc-inlayHint')\n    if (token.isCancellationRequested || this.doc.dirty) return\n    if (!this._dirty) {\n      await this.renderAll(token)\n    } else if (config) {\n      let region = config.region\n      await this.renderRange([region[0] - 1, region[1] - 1], token)\n    } else {\n      // Could be text change or provider change.\n      const spans = await window.getVisibleRanges(this.doc.bufnr)\n      for (const [topline, botline] of spans) {\n        if (token.isCancellationRequested) break\n        await this.renderRange([topline - 1, botline - 1], token)\n      }\n    }\n  }\n\n  private async renderAll(token: CancellationToken): Promise<void> {\n    const lineCount = this.doc.lineCount\n    const range = Range.create(0, 0, lineCount, 0)\n    const inlayHints = await this.request(range, token)\n    if (!inlayHints) return\n    this.currentHints = inlayHints\n    this.setVirtualText(range, inlayHints)\n    this.regions.add(0, lineCount)\n    this._dirty = true\n  }\n\n  /**\n   * 0 based startLine and endLine\n   */\n  public async renderRange(lines: [number, number], token: CancellationToken): Promise<void> {\n    let span = this.regions.toUncoveredSpan(lines, workspace.env.lines, this.doc.lineCount)\n    if (!span) return\n    const [startLine, endLine] = span\n    const range = this.doc.textDocument.intersectWith(Range.create(startLine, 0, endLine + 1, 0))\n    const inlayHints = await this.request(range, token)\n    if (!inlayHints) return\n    this.currentHints = this.currentHints.filter(o => positionInRange(o.position, range) !== 0)\n    this.currentHints.push(...inlayHints)\n    this.setVirtualText(range, inlayHints)\n    this.regions.add(startLine, endLine)\n  }\n\n  private async request(range: Range, token: CancellationToken): Promise<InlayHintWithProvider[] | undefined> {\n    let inlayHints: InlayHintWithProvider[]\n    try {\n      inlayHints = await languages.provideInlayHints(this.doc.textDocument, range, token)\n    } catch (e) {\n      if (!token.isCancellationRequested && e instanceof CancellationError) {\n        // server cancel, wait for more time\n        this.render(undefined, requestDelay).catch(onUnexpectedError)\n        return\n      }\n    }\n    if (inlayHints == null || token.isCancellationRequested) return\n    if (!this.config.enableParameter) {\n      inlayHints = inlayHints.filter(o => o.kind !== InlayHintKind.Parameter)\n    }\n    return inlayHints\n  }\n\n  public setVirtualText(range: Range, inlayHints: InlayHintWithProvider[]): void {\n    let { nvim, doc } = this\n    let buffer = doc.buffer\n    const { maximumLength } = this.config\n    nvim.pauseNotification()\n    const end = range.end.line >= doc.lineCount ? -1 : range.end.line + 1\n    buffer.clearNamespace(srcId, range.start.line, end)\n    let lineInfo = { lineNum: 0, totalLineLen: 0 }\n    const vitems: VirtualTextItem[] = []\n    for (const item of inlayHints) {\n      const blocks = []\n      let { position } = item\n      if (lineInfo.lineNum !== position.line) {\n        lineInfo = { lineNum: position.line, totalLineLen: 0 }\n      }\n      if (maximumLength > 0 && lineInfo.totalLineLen > maximumLength) {\n        logger.warn(`Inlay hint of ${lineInfo.lineNum} too long, max length: ${maximumLength}, current line total length: ${lineInfo.totalLineLen}`)\n        continue\n      }\n\n      let line = this.doc.getline(position.line)\n      let col = byteIndex(line, position.character) + 1\n\n      let label = getLabel(item)\n      lineInfo.totalLineLen += label.length\n      const over = maximumLength > 0 ? lineInfo.totalLineLen - maximumLength : 0\n      if (over > 0) {\n        label = label.slice(0, -over) + '…'\n      }\n\n      if (item.paddingLeft) blocks.push([' ', 'Normal'])\n      blocks.push([label, getHighlightGroup(item.kind)])\n      if (item.paddingRight) blocks.push([' ', 'Normal'])\n      if (this.config.position == InlayHintPosition.Eol) {\n        col = 0\n      }\n      let opts: VirtualTextItem = { line: position.line, blocks, col, hl_mode: 'replace' }\n      if (item.kind == InlayHintKind.Parameter) {\n        opts.right_gravity = false\n      }\n      vitems.push(opts)\n    }\n    nvim.call('coc#vtext#set', [buffer.id, srcId, vitems, false, 200], true)\n    nvim.resumeNotification(true, true)\n    this._onDidRefresh.fire()\n  }\n\n  public clearVirtualText(): void {\n    if (srcId) this.doc.buffer.clearNamespace(srcId)\n  }\n\n  public dispose(): void {\n    this.clearCache()\n  }\n}\n"
  },
  {
    "path": "src/handler/inlayHint/index.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport commands from '../../commands'\nimport events from '../../events'\nimport languages, { ProviderName } from '../../languages'\nimport BufferSync from '../../model/bufferSync'\nimport { disposeAll } from '../../util'\nimport { Disposable } from '../../util/protocol'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport { HandlerDelegate } from '../types'\nimport InlayHintBuffer from './buffer'\n\nexport type StateMethods = 'enable' | 'disable' | 'toggle'\n\nexport default class InlayHintHandler {\n  private buffers: BufferSync<InlayHintBuffer> | undefined\n  private disposables: Disposable[] = []\n  constructor(nvim: Neovim, handler: HandlerDelegate) {\n    this.buffers = workspace.registerBufferSync(doc => {\n      return new InlayHintBuffer(nvim, doc)\n    })\n    this.disposables.push(this.buffers)\n    workspace.onDidChangeConfiguration(e => {\n      for (let item of this.buffers.items) {\n        if (e.affectsConfiguration('inlayHint', item.doc)) {\n          item.loadConfiguration()\n        }\n      }\n    }, null, this.disposables)\n    languages.onDidInlayHintRefresh(async e => {\n      for (let item of this.buffers.items) {\n        if (workspace.match(e, item.doc.textDocument)) {\n          item.clearCache()\n          if (languages.hasProvider(ProviderName.InlayHint, item.doc.textDocument)) {\n            await item.render()\n          } else {\n            item.clearVirtualText()\n          }\n        }\n      }\n    }, null, this.disposables)\n    events.on('InsertLeave', bufnr => {\n      let item = this.buffers.getItem(bufnr)\n      if (item) item.onInsertLeave()\n    }, null, this.disposables)\n    events.on('InsertEnter', bufnr => {\n      let item = this.buffers.getItem(bufnr)\n      if (item) item.onInsertEnter()\n    }, null, this.disposables)\n    commands.register({\n      id: 'document.toggleInlayHint',\n      execute: (bufnr?: number) => {\n        this.setState('toggle', bufnr)\n      },\n    }, false, 'Toggle inlayHint display of current buffer')\n    commands.register({\n      id: 'document.enableInlayHint',\n      execute: (bufnr?: number) => {\n        this.setState('enable', bufnr)\n      },\n    }, false, 'Enable inlayHint display of current buffer')\n    commands.register({\n      id: 'document.disableInlayHint',\n      execute: (bufnr?: number) => {\n        this.setState('disable', bufnr)\n      },\n    }, false, 'Disable inlayHint display of current buffer')\n    handler.addDisposable(Disposable.create(() => {\n      disposeAll(this.disposables)\n    }))\n  }\n\n  public setState(method: StateMethods, bufnr?: number): void {\n    try {\n      bufnr = bufnr ?? workspace.bufnr\n      workspace.getAttachedDocument(bufnr)\n      let item = this.getItem(bufnr)\n      item[method]()\n    } catch (e) {\n      void window.showErrorMessage((e as Error).message)\n    }\n  }\n\n  public getItem(bufnr: number): InlayHintBuffer {\n    return this.buffers.getItem(bufnr)\n  }\n}\n"
  },
  {
    "path": "src/handler/inline.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { FormattingOptions, InlineCompletionTriggerKind, Position, Range, StringValue, TextEdit } from 'vscode-languageserver-types'\nimport commands from '../commands'\nimport completion from '../completion'\nimport { IConfigurationChangeEvent } from '../configuration/types'\nimport events from '../events'\nimport languages, { ProviderName } from '../languages'\nimport { createLogger } from '../logger'\nimport Document from '../model/document'\nimport { SnippetParser } from '../snippets/parser'\nimport { defaultValue, disposeAll, waitWithToken } from '../util'\nimport { onUnexpectedError } from '../util/errors'\nimport { comparePosition, emptyRange, getEnd, positionInRange } from '../util/position'\nimport { CancellationTokenSource, Disposable, InlineCompletionItem } from '../util/protocol'\nimport { byteIndex, toText } from '../util/string'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { HandlerDelegate } from './types'\nconst logger = createLogger('handler-inline')\n\nconst NAMESPACE = 'inlineSuggest'\n\nexport interface InlineSuggestOption {\n  silent?: boolean\n  provider?: string\n  autoTrigger?: boolean\n}\n\nexport interface InlineSuggestConfig {\n  autoTrigger: boolean\n  triggerCompletionWait: number\n}\n\nexport type AcceptKind = 'all' | 'word' | 'line'\n\nexport function formatInsertText(text: string, opts: FormattingOptions): string {\n  let lines = text.split(/\\r?\\n/)\n  let tabSize = defaultValue(opts.tabSize, 2)\n  let ind = opts.insertSpaces ? ' '.repeat(opts.tabSize) : '\\t'\n  lines = lines.map(line => {\n    let space = line.match(/^\\s*/)[0]\n    let isTab = space.startsWith('\\t')\n    let len = space.length\n    if (isTab && opts.insertSpaces) {\n      space = ind.repeat(space.length)\n    } else if (!isTab && !opts.insertSpaces) {\n      space = ind.repeat(space.length / tabSize)\n    }\n    return space + line.slice(len)\n  })\n  return lines.join('\\n')\n}\n\nexport function getInsertText(item: InlineCompletionItem, formatOptions: FormattingOptions): string {\n  if (StringValue.isSnippet(item.insertText)) {\n    const parser = new SnippetParser(false)\n    const snippet = parser.parse(item.insertText.value, true)\n    return formatInsertText(snippet.toString(), formatOptions)\n  }\n  return formatInsertText(item.insertText, formatOptions)\n}\n\nexport function getInserted(curr: string, synced: string, character: number): { start: number, text: string } | undefined {\n  if (curr.length < synced.length) return undefined\n  let after = curr.slice(character)\n  if (!synced.endsWith(after)) return undefined\n  let start = synced.length - after.length\n  if (!curr.startsWith(synced.slice(0, start))) return undefined\n  return { start, text: curr.slice(start, character) }\n}\n\nexport function getPumInserted(document: Document, cursor: Position): string | undefined {\n  const { line, character } = cursor\n  let synced = toText(document.textDocument.lines[line])\n  let curr = document.getline(cursor.line)\n  if (synced === curr) return ''\n  let change = getInserted(curr, synced, character)\n  return change ? change.text : undefined\n}\n\nexport function checkInsertedAtBeginning(currentLine: string, triggerCharacter: number, inserted: string, item: InlineCompletionItem): boolean {\n  if (!item.range) {\n    // check if inserted string is at the beginning item's insertText\n    if (StringValue.isSnippet(item.insertText)) {\n      return item.insertText.value.startsWith(inserted)\n    }\n    return item.insertText.startsWith(inserted)\n  }\n  // check if inserted string is at the beginning of item's range\n  let current = currentLine.slice(item.range.start.character, triggerCharacter + inserted.length)\n  if (StringValue.isSnippet(item.insertText)) {\n    return item.insertText.value.startsWith(current)\n  }\n  return item.insertText.startsWith(current)\n}\n\nfunction fixRange(range: Range | undefined, inserted: string | undefined): Range | undefined {\n  if (!inserted || !range) return range\n  return Range.create(range.start, Position.create(range.end.line, range.end.character + inserted.length))\n}\n\nexport class InlineSession {\n  constructor(\n    public readonly bufnr: number,\n    public readonly cursor: Position,\n    public readonly items: InlineCompletionItem[],\n    public index = 0,\n    public vtext: string | undefined = undefined\n  ) {\n  }\n\n  public get length(): number {\n    return this.items.length\n  }\n\n  public get selected(): InlineCompletionItem | undefined {\n    return this.items[this.index]\n  }\n\n  public clearNamespace(): void {\n    if (this.vtext) {\n      workspace.nvim.createBuffer(this.bufnr).clearNamespace(NAMESPACE)\n      this.vtext = undefined\n    }\n  }\n\n  public get extra(): string {\n    return this.length > 1 ? `(${this.index + 1}/${this.length})` : ''\n  }\n\n  public get nextIndex(): number {\n    return this.index === this.length - 1 ? 0 : this.index + 1\n  }\n\n  public get prevIndex(): number {\n    return this.index === 0 ? this.length - 1 : this.index - 1\n  }\n}\n\nexport default class InlineCompletion {\n  public session: InlineSession | undefined\n  private bufnr: number\n  private tokenSource: CancellationTokenSource\n  private disposables: Disposable[] = []\n  private config: InlineSuggestConfig\n  private _applying = false\n  private _inserted: string | undefined\n\n  constructor(private nvim: Neovim, private handler: HandlerDelegate) {\n    this.loadConfiguration()\n    workspace.onDidChangeConfiguration(this.loadConfiguration, this, this.disposables)\n    window.onDidChangeActiveTextEditor(() => {\n      this.loadConfiguration()\n    }, this, this.disposables)\n    const triggerOption = { autoTrigger: true }\n    workspace.onDidChangeTextDocument(e => {\n      if (languages.inlineCompletionItemManager.isEmpty === false\n        && this.config.autoTrigger\n        && e.bufnr === defaultValue(window.activeTextEditor, {} as any).bufnr\n        && !this._applying\n        && events.insertMode\n      ) {\n        const wait = this.config.triggerCompletionWait\n        this.trigger(e.bufnr, triggerOption, wait).catch(onUnexpectedError)\n      }\n    }, null, this.disposables)\n    events.on('TextChangedI', bufnr => {\n      // Try trigger on pum navigate.\n      if (events.pumInserted && !languages.inlineCompletionItemManager.isEmpty) {\n        const wait = this.config.triggerCompletionWait\n        this.trigger(bufnr, triggerOption, wait).catch(onUnexpectedError)\n      }\n    }, null, this.disposables)\n    events.on('ModeChanged', ev => {\n      if (!ev.new_mode.startsWith('i')) {\n        this.cancel()\n      }\n    }, null, this.disposables)\n    events.on('InsertCharPre', () => {\n      this.cancel()\n    }, null, this.disposables)\n    events.on('LinesChanged', bufnr => {\n      if (bufnr === this.bufnr) {\n        this.cancel()\n      }\n    }, null, this.disposables)\n    workspace.onDidCloseTextDocument(e => {\n      if (e.bufnr === this.bufnr) {\n        this.cancel()\n      }\n    }, null, this.disposables)\n\n    commands.titles.set('document.checkInlineCompletion', 'check inline completion state of current buffer')\n    this.handler.addDisposable(commands.registerCommand('document.checkInlineCompletion', async () => {\n      if (!this.supported) {\n        void window.showWarningMessage(`Inline completion is not supported on current vim ${workspace.env.version}`)\n        return\n      }\n      let bufnr = await this.nvim.eval('bufnr(\"%\")') as number\n      let doc = workspace.getDocument(bufnr)\n      if (!doc || !doc.attached) {\n        void window.showWarningMessage(`Buffer ${bufnr} is not attached, see ':h coc-document-attached'.`)\n        return\n      }\n      let disable = await nvim.createBuffer(bufnr).getVar('coc_inline_disable') as number\n      if (disable == 1) {\n        void window.showWarningMessage(`Trigger inline completion is disabled by b:coc_inline_disable.`)\n        return\n      }\n      let providers = languages.inlineCompletionItemManager.getProviders(doc.textDocument)\n      if (providers.length === 0) {\n        void window.showWarningMessage(`Inline completion provider not found for buffer ${bufnr}.`)\n        return\n      }\n      let names = providers.map(item => item.provider['__extensionName'] ?? 'unknown')\n      void window.showInformationMessage(`Inline completion is supported by ${names.join(', ')}.`)\n    }))\n  }\n\n  private loadConfiguration(e?: IConfigurationChangeEvent): void {\n    if (!e || e.affectsConfiguration('inlineSuggest')) {\n      let doc = defaultValue<any>(window.activeTextEditor, {}).document\n      let config = workspace.getConfiguration('inlineSuggest', doc)\n      let autoTrigger = defaultValue<boolean>(config.inspect('autoTrigger').globalValue as boolean, true)\n      this.config = Object.assign(this.config ?? {}, {\n        autoTrigger,\n        triggerCompletionWait: defaultValue(config.inspect('triggerCompletionWait').globalValue as number, 10)\n      })\n    }\n  }\n\n  public get supported(): boolean {\n    return workspace.has('patch-9.0.0185') || workspace.has('nvim-0.7.0')\n  }\n\n  public get selected(): InlineCompletionItem | undefined {\n    return this.session?.selected\n  }\n\n  public async visible(): Promise<boolean> {\n    let result = await this.nvim.call('coc#inline#visible') as number\n    return !!result\n  }\n\n  public get vtextBufnr(): number {\n    return this.session?.vtext == null ? -1 : this.session.bufnr\n  }\n\n  public async trigger(bufnr: number, option?: InlineSuggestOption, delay?: number): Promise<boolean> {\n    if (!this.supported) return false\n    this.cancel()\n    option = option ?? {}\n    let document = workspace.getDocument(bufnr)\n    if (!document\n      || !document.attached\n      || !languages.hasProvider(ProviderName.InlineCompletion, document)) return false\n    let tokenSource = this.tokenSource = new CancellationTokenSource()\n    this.bufnr = bufnr\n    this._inserted = undefined\n    let token = tokenSource.token\n    if (delay) await waitWithToken(delay, token)\n    if (option.autoTrigger !== true && document.hasChanged) {\n      this._applying = true\n      await document.synchronize()\n      this._applying = false\n    }\n    if (token.isCancellationRequested) return false\n    let state = await this.handler.getCurrentState()\n    let disable = await document.buffer.getVar('coc_inline_disable') as number\n    if (disable == 1\n      || state.doc.bufnr !== bufnr\n      || !state.mode.startsWith('i')\n      || token.isCancellationRequested) return false\n    let cursor = state.position\n    let triggerPosition = cursor\n    let curr = document.getline(cursor.line)\n    if (option.autoTrigger) {\n      let inserted = this._inserted = getPumInserted(document, cursor)\n      if (inserted == null) return false\n      triggerPosition = Position.create(cursor.line, cursor.character - inserted.length)\n    }\n    const selectedCompletionInfo = completion.selectedCompletionInfo\n    if (selectedCompletionInfo && this._inserted) selectedCompletionInfo.range.end.character -= this._inserted.length\n    let items = await languages.provideInlineCompletionItems(document.textDocument, triggerPosition, {\n      provider: option.provider,\n      selectedCompletionInfo,\n      triggerKind: option.autoTrigger ? InlineCompletionTriggerKind.Automatic : InlineCompletionTriggerKind.Invoked\n    }, token)\n    this.tokenSource = undefined\n    if (!Array.isArray(items) || token.isCancellationRequested) return false\n    items = items.filter(item => !item.range || positionInRange(triggerPosition, item.range) === 0)\n    // Inserted by pum navigate\n    if (this._inserted) items = items.filter(item => checkInsertedAtBeginning(curr, triggerPosition.character, this._inserted, item))\n    if (items.length === 0) {\n      if (!option.autoTrigger && !option.silent) {\n        void window.showWarningMessage(`No inline completion items from provider.`)\n      }\n      return false\n    }\n    this.session = new InlineSession(bufnr, cursor, items)\n    await this.insertVtext(items[0])\n    return true\n  }\n\n  public async accept(bufnr: number, kind: AcceptKind = 'all'): Promise<boolean> {\n    if (bufnr !== this.vtextBufnr || !this.selected) return false\n    let item = this.selected\n    let cursor = this.session.cursor\n    let insertedText = this.session.vtext\n    this.cancel()\n    let doc = workspace.getAttachedDocument(bufnr)\n    let insertedLength = 0\n    const itemRange = fixRange(item.range, this._inserted)\n    if (StringValue.isSnippet(item.insertText) && kind == 'all') {\n      let range = defaultValue(itemRange, Range.create(cursor, cursor))\n      let text = item.insertText.value\n      if (!itemRange && this._inserted) text = text.slice(this._inserted.length)\n      let edit = TextEdit.replace(range, text)\n      await commands.executeCommand('editor.action.insertSnippet', edit)\n    } else {\n      let range = Range.create(cursor, cursor)\n      if (kind == 'word') {\n        let total = 0\n        for (let i = 1; i < insertedText.length; i++) {\n          if (doc.isWord(insertedText[i])) {\n            total = i\n          } else {\n            break\n          }\n        }\n        insertedText = insertedText.slice(0, total + 1)\n        insertedLength = insertedText.length\n      } else if (kind == 'line') {\n        // get the first line of insertedText\n        const insertText = insertedText.split('\\n')[0]\n        insertedLength = insertText.length\n      } else {\n        insertedText = getInsertText(item, window.activeTextEditor.options)\n        if (itemRange) {\n          range = itemRange\n        } else if (this._inserted) {\n          insertedText = insertedText.slice(this._inserted.length)\n        }\n      }\n      await doc.applyEdits([TextEdit.replace(range, insertedText)], false, false)\n      await window.moveTo(getEnd(range.start, insertedText))\n    }\n    if (item.command) {\n      try {\n        await commands.execute(item.command)\n      } catch (err) {\n        logger.error(`Error on execute command \"${item.command.command}\"`, err)\n      }\n    }\n    await events.fire('InlineAccept', [insertedLength, item])\n    return true\n  }\n\n  public async next(bufnr: number): Promise<void> {\n    await this._navigate(true, bufnr)\n  }\n\n  public async prev(bufnr: number): Promise<void> {\n    await this._navigate(false, bufnr)\n  }\n\n  private async _navigate(next: boolean, bufnr: number): Promise<void> {\n    if (bufnr !== this.vtextBufnr || this.session.length <= 1) return\n    let idx = next ? this.session.nextIndex : this.session.prevIndex\n    this.session.index = idx\n    await this.insertVtext(this.session.selected)\n  }\n\n  public async insertVtext(item: InlineCompletionItem): Promise<void> {\n    if (!this.session || !item) return\n    const { bufnr, extra, cursor } = this.session\n    let doc = workspace.getDocument(bufnr)\n    let formatOptions = window.activeTextEditor.options\n    let text = getInsertText(item, formatOptions)\n    const line = doc.getline(cursor.line)\n    const itemRange = fixRange(item.range, this._inserted)\n    if (itemRange && !emptyRange(itemRange)) {\n      let current = line.slice(itemRange.start.character, cursor.character)\n      text = text.slice(current.length)\n      if (comparePosition(cursor, itemRange.end) !== 0) {\n        let after = line.slice(cursor.character, itemRange.end.character)\n        if (text.endsWith(after)) {\n          text = text.slice(0, -after.length)\n        }\n      }\n    } else if (this._inserted) {\n      text = text.slice(this._inserted.length)\n    }\n    const col = byteIndex(line, cursor.character) + 1\n    let shown = await this.nvim.call('coc#inline#_insert', [bufnr, cursor.line, col, text.split('\\n'), extra])\n    if (!this.session) return\n    if (shown) {\n      this.session.vtext = text\n      this.nvim.redrawVim()\n      void events.fire('InlineShown', [item])\n    } else {\n      this.session.clearNamespace()\n      this.session = undefined\n    }\n  }\n\n  public cancel(): void {\n    if (this.tokenSource) {\n      this.tokenSource.cancel()\n      this.tokenSource.dispose()\n      this.tokenSource = undefined\n    }\n    if (this.session) {\n      this.session.clearNamespace()\n      this.session = undefined\n    }\n    this.bufnr = undefined\n  }\n\n  public dispose(): void {\n    disposeAll(this.disposables)\n  }\n}\n"
  },
  {
    "path": "src/handler/linkedEditing.ts",
    "content": "'use strict'\nimport { Neovim, Window } from '@chemzqm/neovim'\nimport { Position, TextEdit } from 'vscode-languageserver-types'\nimport TextRange from '../cursors/textRange'\nimport { getBeforeCount, getChange, getDelta } from '../cursors/util'\nimport events from '../events'\nimport languages, { ProviderName } from '../languages'\nimport Document from '../model/document'\nimport { DidChangeTextDocumentParams } from '../types'\nimport { getConditionValue } from '../util'\nimport { debounce } from '../util/node'\nimport { emptyRange, positionInRange, rangeAdjacent, rangeInRange, rangeIntersect } from '../util/position'\nimport { CancellationTokenSource } from '../util/protocol'\nimport { characterIndex } from '../util/string'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { HandlerDelegate } from './types'\n\nconst debounceTime = getConditionValue(200, 10)\n\nexport default class LinkedEditingHandler {\n  private changing = false\n  private window: Window | undefined\n  private bufnr: number | undefined\n  private ranges: TextRange[] | undefined\n  private wordPattern: string | undefined\n  private tokenSource: CancellationTokenSource | undefined\n  public checkPosition: ((bufnr: number, cursor: [number, number]) => void) & { clear(): void }\n  constructor(private nvim: Neovim, handler: HandlerDelegate) {\n    this.checkPosition = debounce(this._checkPosition, debounceTime)\n    handler.addDisposable(events.on('CursorMoved', (bufnr, cursor) => {\n      this.cancel()\n      this.checkPosition(bufnr, [cursor[0], cursor[1]])\n    }))\n    handler.addDisposable(events.on('CursorMovedI', (bufnr, cursor) => {\n      this.cancel()\n      this.checkPosition(bufnr, [cursor[0], cursor[1]])\n    }))\n    handler.addDisposable(window.onDidChangeActiveTextEditor(() => {\n      this.cancel()\n      this.cancelEdit()\n    }))\n    handler.addDisposable(events.on('InsertCharPre', (character, bufnr) => {\n      if (bufnr !== this.bufnr) return\n      let doc = workspace.getDocument(bufnr)\n      if (!this.wordPattern) {\n        if (!doc.isWord(character) && character !== '-') this.cancelEdit()\n      } else {\n        let r = new RegExp(this.wordPattern)\n        if (!r.test(character)) this.cancelEdit()\n      }\n    }))\n    handler.addDisposable(workspace.onDidChangeTextDocument(async e => {\n      await this.onChange(e)\n    }))\n  }\n\n  private cancelEdit(): void {\n    this.window?.clearMatchGroup('^CocLinkedEditing')\n    this.ranges = undefined\n    this.window = undefined\n    this.bufnr = undefined\n  }\n\n  public async onChange(e: DidChangeTextDocumentParams): Promise<void> {\n    if (e.bufnr !== this.bufnr || this.changing || !this.ranges) return\n    if (e.contentChanges.length === 0) {\n      this.doHighlights()\n      return\n    }\n    let change = e.contentChanges[0]\n    let { text, range } = change\n    let affected = this.ranges.filter(r => {\n      if (!rangeIntersect(range, r.range)) return false\n      if (rangeAdjacent(range, r.range)) {\n        if (text.includes('\\n') || !emptyRange(range)) return false\n      }\n      return true\n    })\n    if (affected.length == 1 && rangeInRange(range, affected[0].range)) {\n      if (text.includes('\\n')) {\n        this.cancelEdit()\n        return\n      }\n      // change textRange\n      await this.applySingleEdit(affected[0], { range, newText: text })\n    } else {\n      this.cancelEdit()\n    }\n  }\n\n  private async applySingleEdit(textRange: TextRange, edit: TextEdit): Promise<void> {\n    // single range change, calculate & apply changes for all ranges\n    let { bufnr, ranges } = this\n    let doc = workspace.getDocument(bufnr)\n    let after = ranges.filter(r => r !== textRange && r.position.line == textRange.position.line)\n    after.forEach(r => r.adjustFromEdit(edit))\n    let change = getChange(textRange, edit.range, edit.newText)\n    let delta = getDelta(change)\n    ranges.forEach(r => r.applyChange(change))\n    let edits = ranges.filter(r => r !== textRange).map(o => o.textEdit)\n    // logger.debug('edits:', JSON.stringify(edits, null, 2))\n    this.changing = true\n    await doc.applyEdits(edits, true, true)\n    this.changing = false\n    if (delta != 0) {\n      for (let r of ranges) {\n        let n = getBeforeCount(r, this.ranges, textRange)\n        r.move(n * delta)\n      }\n    }\n    this.doHighlights()\n  }\n\n  private doHighlights(): void {\n    let { window, ranges, nvim } = this\n    if (window && ranges) {\n      nvim.pauseNotification()\n      window.clearMatchGroup('^CocLinkedEditing')\n      window.highlightRanges('CocLinkedEditing', ranges.map(o => o.range), 99, true)\n      nvim.resumeNotification(true, true)\n    }\n  }\n\n  private _checkPosition(bufnr: number, cursor: [number, number]): void {\n    if (events.completing || !workspace.isAttached(bufnr)) return\n    let doc = workspace.getDocument(bufnr)\n    let config = workspace.getConfiguration('coc.preferences', doc)\n    let enabled = config.get<boolean>('enableLinkedEditing', false)\n    if (!enabled || !languages.hasProvider(ProviderName.LinkedEditing, doc.textDocument)) return\n    let character = characterIndex(doc.getline(cursor[0] - 1), cursor[1] - 1)\n    let position = Position.create(cursor[0] - 1, character)\n    if (this.ranges) {\n      if (this.ranges.some(r => positionInRange(position, r.range) == 0)) {\n        return\n      }\n      this.cancelEdit()\n    }\n    void this.enable(doc, position)\n  }\n\n  public async enable(doc: Document, position: Position): Promise<void> {\n    let textDocument = doc.textDocument\n    let tokenSource = this.tokenSource = new CancellationTokenSource()\n    let token = tokenSource.token\n    let win = await this.nvim.window\n    let linkedRanges = await languages.provideLinkedEdits(textDocument, position, token)\n    if (token.isCancellationRequested || !linkedRanges || linkedRanges.ranges.length == 0) return\n    let ranges = linkedRanges.ranges.map(o => new TextRange(o.start.line, o.start.character, textDocument.getText(o)))\n    this.wordPattern = linkedRanges.wordPattern\n    this.bufnr = doc.bufnr\n    this.window = win\n    this.ranges = ranges\n    this.doHighlights()\n  }\n\n  private cancel(): void {\n    if (this.tokenSource) {\n      this.tokenSource.cancel()\n      this.tokenSource = null\n    }\n  }\n}\n"
  },
  {
    "path": "src/handler/links.ts",
    "content": "'use strict'\nimport { Buffer, Neovim } from '@chemzqm/neovim'\nimport debounce from 'debounce'\nimport { DocumentLink, Range } from 'vscode-languageserver-types'\nimport { IConfigurationChangeEvent } from '../configuration/types'\nimport events from '../events'\nimport languages, { ProviderName } from '../languages'\nimport BufferSync, { SyncItem } from '../model/bufferSync'\nimport Document from '../model/document'\nimport { DidChangeTextDocumentParams, Documentation, FloatFactory, HighlightItem } from '../types'\nimport { disposeAll, getConditionValue } from '../util'\nimport { isFalsyOrEmpty, toArray } from '../util/array'\nimport { equals } from '../util/object'\nimport { positionInRange } from '../util/position'\nimport { CancellationTokenSource, Disposable } from '../util/protocol'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { HandlerDelegate } from './types'\n\n// const regex = /CocAction(Async)?\\([\"']openLink[\"']\\)/\nlet floatFactory: FloatFactory | undefined\nconst debounceTime = getConditionValue(200, 10)\nconst NAMESPACE = 'links'\nconst highlightGroup = 'CocLink'\n\ninterface LinkConfig {\n  enable: boolean\n  highlight: boolean\n}\n\nexport default class Links implements Disposable {\n  private disposables: Disposable[] = []\n  private tooltip: boolean\n  private tokenSource: CancellationTokenSource\n  private buffers: BufferSync<LinkBuffer>\n  constructor(private nvim: Neovim, private handler: HandlerDelegate) {\n    this.setConfiguration()\n    workspace.onDidChangeConfiguration(this.setConfiguration, this, this.disposables)\n    events.on('CursorHold', async () => {\n      await this.showTooltip()\n    }, null, this.disposables)\n    events.on(['CursorMoved', 'InsertEnter'], () => {\n      this.cancel()\n    }, null, this.disposables)\n    this.buffers = workspace.registerBufferSync(doc => {\n      return new LinkBuffer(doc)\n    })\n    this.disposables.push(this.buffers)\n    languages.onDidLinksRefresh(selector => {\n      for (let item of this.buffers.items) {\n        if (workspace.match(selector, item.doc)) {\n          item.fetchLinks()\n        }\n      }\n    }, null, this.disposables)\n  }\n\n  private setConfiguration(e?: IConfigurationChangeEvent): void {\n    if (!e || e.affectsConfiguration('links')) {\n      this.tooltip = workspace.initialConfiguration.get<boolean>('links.tooltip', false)\n      if (e) {\n        for (let item of this.buffers.items) {\n          item.updateDocumentConfig()\n        }\n      }\n    }\n  }\n\n  public async showTooltip(): Promise<void> {\n    if (!this.tooltip) return\n    let link = await this.getCurrentLink()\n    if (!link || !link.target) return\n    let text = link.target\n    if (link.tooltip) text += ' ' + link.tooltip\n    let doc: Documentation = { content: text, filetype: 'txt' }\n    if (!floatFactory) floatFactory = window.createFloatFactory({})\n    await floatFactory.show([doc])\n  }\n\n  public async getLinks(): Promise<ReadonlyArray<DocumentLink>> {\n    let { doc } = await this.handler.getCurrentState()\n    let buf = this.buffers.getItem(doc.bufnr)\n    await buf.getLinks()\n    return toArray(buf.links)\n  }\n\n  public async getCurrentLink(): Promise<DocumentLink | undefined> {\n    let links = await this.getLinks()\n    let pos = await window.getCursorPosition()\n    if (links && links.length) {\n      for (let link of links) {\n        if (positionInRange(pos, link.range) == 0) {\n          if (!link.target) {\n            let tokenSource = this.tokenSource = this.tokenSource || new CancellationTokenSource()\n            link = await languages.resolveDocumentLink(link, this.tokenSource.token)\n            this.tokenSource = undefined\n            if (!link.target || tokenSource.token.isCancellationRequested) continue\n          }\n          return link\n        }\n      }\n    }\n    let line = await this.nvim.call('getline', ['.']) as string\n    let regex = /\\w+?:\\/\\/[-a-zA-Z0-9@:%._\\\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b[-a-zA-Z0-9@:%_\\\\+.~#?&//=]*/gi\n    let arr\n    let link: DocumentLink | undefined\n    while ((arr = regex.exec(line)) !== null) {\n      let start = arr.index\n      if (start <= pos.character && start + arr[0].length >= pos.character) {\n        link = DocumentLink.create(Range.create(pos.line, start, pos.line, start + arr[0].length), arr[0])\n        break\n      }\n    }\n    return link\n  }\n\n  public async openCurrentLink(): Promise<boolean> {\n    let link = await this.getCurrentLink()\n    if (link) {\n      await this.openLink(link)\n      return true\n    }\n    return false\n  }\n\n  public async openLink(link: DocumentLink): Promise<void> {\n    if (!link.target) throw new Error(`Failed to resolve link target`)\n    await workspace.openResource(link.target)\n  }\n\n  public getBuffer(bufnr: number): LinkBuffer | undefined {\n    return this.buffers.getItem(bufnr)\n  }\n\n  private cancel(): void {\n    if (this.tokenSource) {\n      this.tokenSource.cancel()\n      this.tokenSource = null\n    }\n  }\n\n  public dispose(): void {\n    disposeAll(this.disposables)\n  }\n}\n\nclass LinkBuffer implements SyncItem {\n  private currentVersion = -1\n  private tokenSource: CancellationTokenSource | undefined\n  private _config: LinkConfig | undefined\n  public links: DocumentLink[] = []\n  public fetchLinks: (() => void) & { clear(): void }\n  // last highlight version\n  constructor(public readonly doc: Document) {\n    this.fetchLinks = debounce(() => {\n      void this.getLinks()\n    }, debounceTime)\n    if (this.hasProvider) this.fetchLinks()\n  }\n\n  public get config(): LinkConfig {\n    if (this._config) return this._config\n    this.updateDocumentConfig()\n    return this._config\n  }\n\n  private get hasProvider(): boolean {\n    return languages.hasProvider(ProviderName.DocumentLink, this.doc)\n  }\n\n  public updateDocumentConfig(): void {\n    let configuration = workspace.getConfiguration('links', this.doc)\n    this._config = {\n      enable: configuration.get('enable', true),\n      highlight: configuration.get('highlight', false),\n    }\n  }\n\n  public onChange(e: DidChangeTextDocumentParams): void {\n    if (e.contentChanges.length == 0) {\n      this.highlight()\n    } else {\n      this.cancel()\n      this.fetchLinks()\n    }\n  }\n\n  public highlight(): void {\n    if (!this.config.highlight || !this.links) return\n    let { links, doc } = this\n    if (isFalsyOrEmpty(links)) {\n      this.clearHighlight()\n    } else {\n      let highlights: HighlightItem[] = []\n      links.forEach(link => {\n        doc.addHighlights(highlights, highlightGroup, link.range)\n      })\n      this.doc.buffer.updateHighlights(NAMESPACE, highlights, { priority: 2048 })\n    }\n  }\n\n  public clearHighlight(): void {\n    this.buffer.clearNamespace(NAMESPACE)\n  }\n\n  public get buffer(): Buffer {\n    return this.doc.buffer\n  }\n\n  public cancel(): void {\n    this.fetchLinks.clear()\n    if (this.tokenSource) {\n      this.tokenSource.cancel()\n      this.tokenSource = null\n    }\n  }\n\n  public async getLinks(): Promise<void> {\n    if (!this.hasProvider || !this.config.enable || this.currentVersion === this.doc.version) return\n    this.currentVersion = this.doc.version\n\n    this.cancel()\n    let tokenSource = this.tokenSource = new CancellationTokenSource()\n    let token = tokenSource.token\n    let links = await languages.getDocumentLinks(this.doc.textDocument, token)\n    this.tokenSource = undefined\n    if (token.isCancellationRequested || sameLinks(toArray(this.links), toArray(links))) return\n    this.links = toArray(links)\n    this.highlight()\n  }\n\n  public dispose(): void {\n    this.cancel()\n  }\n}\n\nexport function sameLinks(links: ReadonlyArray<DocumentLink>, other: ReadonlyArray<DocumentLink>): boolean {\n  if (links.length != other.length) return false\n  for (let i = 0; i < links.length; i++) {\n    if (!equals(links[i].range, other[i].range)) {\n      return false\n    }\n  }\n  return true\n}\n"
  },
  {
    "path": "src/handler/locations.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { Location, LocationLink, Position } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport languages, { ProviderName } from '../languages'\nimport services from '../services'\nimport { LocationWithTarget } from '../types'\nimport { isFalsyOrEmpty } from '../util/array'\nimport { hasOwnProperty } from '../util/object'\nimport { CancellationToken, CancellationTokenSource } from '../util/protocol'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { HandlerDelegate } from './types'\n\nexport interface TagDefinition {\n  name: string\n  cmd: string\n  filename: string\n}\n\nexport type RequestFunc<T> = (doc: TextDocument, position: Position, token: CancellationToken) => Thenable<T>\n\nexport default class LocationsHandler {\n  constructor(private nvim: Neovim, private handler: HandlerDelegate) {\n  }\n\n  private async request<T>(method: ProviderName, fn: RequestFunc<T>): Promise<T | null> {\n    let { doc, position } = await this.handler.getCurrentState()\n    this.handler.checkProvider(method, doc.textDocument)\n    await doc.synchronize()\n    return await this.handler.withRequestToken(method, token => {\n      return fn(doc.textDocument, position, token)\n    }, true)\n  }\n\n  public async definitions(): Promise<Location[]> {\n    const { doc, position } = await this.handler.getCurrentState()\n    this.handler.checkProvider(ProviderName.Definition, doc.textDocument)\n    await doc.synchronize()\n    const tokenSource = new CancellationTokenSource()\n    return languages.getDefinition(doc.textDocument, position, tokenSource.token)\n  }\n\n  public async declarations(): Promise<Location | Location[] | LocationLink[]> {\n    const { doc, position } = await this.handler.getCurrentState()\n    this.handler.checkProvider(ProviderName.Declaration, doc.textDocument)\n    await doc.synchronize()\n    const tokenSource = new CancellationTokenSource()\n    return languages.getDeclaration(doc.textDocument, position, tokenSource.token)\n  }\n\n  public async typeDefinitions(): Promise<Location[]> {\n    const { doc, position } = await this.handler.getCurrentState()\n    this.handler.checkProvider(ProviderName.TypeDefinition, doc.textDocument)\n    await doc.synchronize()\n    const tokenSource = new CancellationTokenSource()\n    return languages.getTypeDefinition(doc.textDocument, position, tokenSource.token)\n  }\n\n  public async implementations(): Promise<Location[]> {\n    const { doc, position } = await this.handler.getCurrentState()\n    this.handler.checkProvider(ProviderName.Implementation, doc.textDocument)\n    await doc.synchronize()\n    const tokenSource = new CancellationTokenSource()\n    return languages.getImplementation(doc.textDocument, position, tokenSource.token)\n  }\n\n  public async references(excludeDeclaration?: boolean): Promise<Location[]> {\n    const { doc, position } = await this.handler.getCurrentState()\n    this.handler.checkProvider(ProviderName.Reference, doc.textDocument)\n    await doc.synchronize()\n    const tokenSource = new CancellationTokenSource()\n    return languages.getReferences(doc.textDocument, { includeDeclaration: !excludeDeclaration }, position, tokenSource.token)\n  }\n\n  public async gotoDefinition(openCommand?: string | false): Promise<boolean> {\n    let definition = await this.request(ProviderName.Definition, (doc, position, token) => {\n      return languages.getDefinition(doc, position, token)\n    })\n    await this.handleLocations(definition, openCommand)\n    return !isFalsyOrEmpty(definition)\n  }\n\n  public async gotoDeclaration(openCommand?: string | false): Promise<boolean> {\n    let definition = await this.request(ProviderName.Declaration, (doc, position, token) => {\n      return languages.getDeclaration(doc, position, token)\n    })\n    await this.handleLocations(definition, openCommand)\n    return !isFalsyOrEmpty(definition)\n  }\n\n  public async gotoTypeDefinition(openCommand?: string | false): Promise<boolean> {\n    let definition = await this.request(ProviderName.TypeDefinition, (doc, position, token) => {\n      return languages.getTypeDefinition(doc, position, token)\n    })\n    await this.handleLocations(definition, openCommand)\n    return !isFalsyOrEmpty(definition)\n  }\n\n  public async gotoImplementation(openCommand?: string | false): Promise<boolean> {\n    let definition = await this.request(ProviderName.Implementation, (doc, position, token) => {\n      return languages.getImplementation(doc, position, token)\n    })\n    await this.handleLocations(definition, openCommand)\n    return !isFalsyOrEmpty(definition)\n  }\n\n  public async gotoReferences(openCommand?: string | false, includeDeclaration = true): Promise<boolean> {\n    let definition = await this.request(ProviderName.Reference, (doc, position, token) => {\n      return languages.getReferences(doc, { includeDeclaration }, position, token)\n    })\n    await this.handleLocations(definition, openCommand)\n    return !isFalsyOrEmpty(definition)\n  }\n\n  public async getTagList(): Promise<TagDefinition[] | null> {\n    let bufnr = await this.nvim.call('bufnr', '%') as number\n    let doc = workspace.getDocument(bufnr)\n    if (!doc || !doc.attached) return null\n    let position = await window.getCursorPosition()\n    let word = await this.nvim.call('expand', '<cword>') as string\n    if (!word) return null\n    if (!languages.hasProvider(ProviderName.Definition, doc.textDocument)) return null\n    let tokenSource = new CancellationTokenSource()\n    let definitions = []\n    try {\n      let timeout = workspace.initialConfiguration.get<number>('coc.preferences.tagDefinitionTimeout', 0)\n      if (timeout > 0) {\n        const abort = new Promise<[]>((_, rej) => setTimeout(() => rej(new Error('timeout')), timeout))\n        definitions = await Promise.race([languages.getDefinition(doc.textDocument, position, tokenSource.token), abort])\n      } else {\n        definitions = await languages.getDefinition(doc.textDocument, position, tokenSource.token)\n      }\n    } catch (e) {\n      return null\n    }\n    if (!definitions || !definitions.length) return null\n    return definitions.map(location => {\n      let parsedURI = URI.parse(location.uri)\n      const filename = parsedURI.scheme == 'file' ? parsedURI.fsPath : parsedURI.toString()\n      return {\n        name: word,\n        cmd: `silent keepjumps call coc#cursor#move_to(${location.range.start.line}, ${location.range.start.character})`,\n        filename,\n      }\n    })\n  }\n\n  /**\n   * Send custom request for locations to services.\n   */\n  public async findLocations(id: string, method: string, params: any, openCommand: string | false = false): Promise<boolean> {\n    let { doc, position } = await this.handler.getCurrentState()\n    params = params || {}\n    Object.assign(params, {\n      textDocument: { uri: doc.uri },\n      position\n    })\n    let res: any = await services.sendRequest(id, method, params)\n    let locations = this.toLocations(res)\n    await this.handleLocations(locations, openCommand)\n    return locations.length > 0\n  }\n\n  public toLocations(location: Location | LocationLink | Location[] | LocationLink[] | null): LocationWithTarget[] {\n    let res: LocationWithTarget[] = []\n    if (location && hasOwnProperty(location, 'location') && hasOwnProperty(location, 'children')) {\n      let getLocation = (item: any): void => {\n        if (!item) return\n        if (Location.is(item.location)) {\n          res.push(item.location)\n        } else if (LocationLink.is(item.location)) {\n          let loc = item.location as LocationLink\n          res.push({\n            uri: loc.targetUri,\n            range: loc.targetSelectionRange,\n            targetRange: loc.targetRange\n          })\n        }\n        if (item.children && item.children.length) {\n          for (let loc of item.children) {\n            getLocation(loc)\n          }\n        }\n      }\n      getLocation(location)\n      return res\n    }\n    if (Location.is(location)) {\n      res.push(location)\n    } else if (LocationLink.is(location)) {\n      res.push({\n        uri: location.targetUri,\n        range: location.targetSelectionRange,\n        targetRange: location.targetRange\n      })\n    } else if (Array.isArray(location)) {\n      for (let loc of location) {\n        if (Location.is(loc)) {\n          res.push(loc)\n        } else if (loc && typeof loc.targetUri === 'string') {\n          res.push({\n            uri: loc.targetUri,\n            range: loc.targetSelectionRange,\n            targetRange: loc.targetRange\n          })\n        }\n      }\n    }\n    return res\n  }\n\n  public async handleLocations(locations: LocationWithTarget[] | null | undefined, openCommand?: string | false): Promise<void> {\n    if (!locations) return\n    let len = locations.length\n    if (len == 0) return\n    if (len == 1 && openCommand !== false) {\n      let { uri, range } = locations[0]\n      await workspace.jumpTo(uri, range.start, openCommand)\n    } else {\n      await workspace.showLocations(locations)\n    }\n  }\n}\n"
  },
  {
    "path": "src/handler/refactor/buffer.ts",
    "content": "'use strict'\nimport { Buffer, Neovim } from '@chemzqm/neovim'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { Position, Range, TextEdit } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport { createLogger } from '../../logger'\nimport Document from '../../model/document'\nimport Highlighter from '../../model/highlighter'\nimport { DidChangeTextDocumentParams, Optional, TextDocumentContentChange } from '../../types'\nimport { disposeAll } from '../../util'\nimport { isParentFolder, readFileLines, sameFile } from '../../util/fs'\nimport { omit } from '../../util/lodash'\nimport { Mutex } from '../../util/mutex'\nimport { fastDiff, path } from '../../util/node'\nimport { equals } from '../../util/object'\nimport { adjustRangePosition, emptyRange } from '../../util/position'\nimport { Disposable } from '../../util/protocol'\nimport { byteLength } from '../../util/string'\nimport { getChangedLineCount, lineCountChange } from '../../util/textedit'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport Changes, { LineInfo } from './changes'\nconst logger = createLogger('handler-refactorBuffer')\n\nexport const SEPARATOR = '\\u3000'\n\nexport interface LineChange {\n  // zero indexed\n  lnum: number\n  delta: number\n}\n\nexport interface FileRangeInfo {\n  // line number of filepath\n  lnum: number\n  filepath: string\n  lines: string[]\n}\n\nexport interface FileChange extends FileRangeInfo {\n  // start line 0 indexed\n  start: number\n  // end line 0 indexed, excluded\n  end: number\n}\n\nexport interface FileRange {\n  // start lnum in refactor buffer, 1 indexed\n  lnum: number\n  // start line 0 indexed\n  start: number\n  lines: string[]\n  // range relatived to new range\n  highlights?: Range[]\n}\n\nexport type FileRangeDef = Optional<FileRange, 'lines' | 'lnum'> & { end?: number }\n\nexport interface FileItem {\n  filepath: string\n  ranges: FileRange[]\n}\n\nexport interface FileItemDef {\n  filepath: string\n  ranges: FileRangeDef[]\n}\n\nexport interface RefactorConfig {\n  openCommand: string\n  beforeContext: number\n  afterContext: number\n  saveToFile: boolean\n  showMenu: string\n}\n\nexport interface RefactorBufferOpts {\n  cwd: string\n  winid: number\n  fromWinid: number\n}\n\nexport default class RefactorBuffer {\n  private _disposed = false\n  private _fileItems: FileItem[] = []\n  private mutex = new Mutex()\n  private disposables: Disposable[] = []\n  private matchIds: Set<number> = new Set()\n  private changes: Changes\n  private changing = false\n  constructor(\n    public readonly bufnr: number,\n    private srcId: number,\n    private nvim: Neovim,\n    public readonly config: RefactorConfig,\n    private opts: RefactorBufferOpts\n  ) {\n    this.changes = new Changes()\n    this.disposables.push(workspace.registerLocalKeymap(bufnr, 'n', '<CR>', this.splitOpen.bind(this), true))\n    if (config.showMenu) {\n      this.disposables.push(workspace.registerLocalKeymap(bufnr, 'n', config.showMenu, this.showMenu.bind(this), true))\n    }\n    workspace.onDidChangeTextDocument(this.onDocumentChange, this, this.disposables)\n  }\n\n  public async showMenu(): Promise<void> {\n    let res = await window.showMenuPicker(['Tab open', 'Remove block'])\n    if (res == -1) return\n    let fileRange = await this.searchCurrentRange()\n    if (!fileRange) return\n    if (res == 0) {\n      let before = await this.nvim.eval(`strpart(getline('.'), 0 ,col('.') - 1)`) as string\n      let character = before.length\n      let bufname = this.getAbsolutePath(fileRange.filepath)\n      this.nvim.call('coc#util#jump', ['tabe', bufname, [fileRange.line, character]], true)\n    }\n    if (res == 1) {\n      let range = this.getDeleteRange(fileRange)\n      await this.document.applyEdits([TextEdit.del(range)])\n    }\n  }\n\n  public get fileItems(): FileItem[] {\n    return this._fileItems\n  }\n\n  public getFileItem(uri: string): FileItem | undefined {\n    let filepath = URI.parse(uri).fsPath\n    return this._fileItems.find(o => sameFile(o.filepath, filepath))\n  }\n\n  public getFileRange(lnum: number): FileRange & { filepath: string } {\n    for (let item of this._fileItems) {\n      for (let r of item.ranges) {\n        if (r.lnum == lnum) {\n          return Object.assign(omit(r, ['highlights']), { filepath: item.filepath })\n        }\n      }\n    }\n    throw new Error(`File range not found at lnum: ${lnum}`)\n  }\n\n  public onChange(e: DidChangeTextDocumentParams): void {\n    if (this.changing) return\n    if (e.contentChanges.length === 0) {\n      this.highlightLineNr()\n      this.nvim.redrawVim()\n      return\n    }\n    let { nvim } = this\n    e = fixChangeParams(e)\n    let change = e.contentChanges[0]\n    let { original } = e\n    if (change.range.end.line > 2) {\n      nvim.call('setbufvar', [e.bufnr, '&modified', 1], true)\n    }\n    let { range, text } = change\n    let lineChange = lineCountChange(TextEdit.replace(range, text))\n    if (lineChange == 0) return\n    let edits: TextEdit[] = [TextEdit.replace(range, text)]\n    let addRanges: LineInfo[] = []\n    // Check removed ranges\n    if (!emptyRange(range) && !text.includes('\\u3000')) {\n      let sl = range.start.line\n      let lnums: number[] = []\n      let lines = original.split(/\\r?\\n/)\n      for (let i = 0; i < lines.length; i++) {\n        let line = lines[i]\n        if (line.length > 1 && line.includes('\\u3000')) {\n          lnums.push(sl + i + 1)\n        }\n      }\n      if (lnums.length) {\n        let infos: LineInfo[] = lnums.map(lnum => {\n          return this.getFileRange(lnum)\n        })\n        for (let item of this._fileItems) {\n          item.ranges = item.ranges.filter(o => !lnums.includes(o.lnum))\n        }\n        this.changes.add(infos)\n      }\n    } else if (emptyRange(range) && text.includes('\\u3000')) {\n      // check undo\n      let lines = text.split(/\\r?\\n/)\n      let lnums: number[] = []\n      let sl = range.start.line\n      for (let i = 0; i < lines.length; i++) {\n        let line = lines[i]\n        if (line.length > 1 && line.includes('\\u3000')) {\n          lnums.push(sl + i + 1)\n        }\n      }\n      if (lnums.length) {\n        let res = this.changes.checkInsert(lnums)\n        if (res) addRanges = res\n      }\n    } else if (text.includes('\\u3000')) {\n      // check multiple ranges change\n      edits = this.diffChanges(original, text)\n      edits.forEach(e => {\n        e.range = adjustRangePosition(e.range, range.start)\n      })\n    }\n    this.adjustLnums(edits)\n    nvim.pauseNotification()\n    this.highlightLineNr()\n    nvim.resumeNotification(true, true)\n    if (addRanges.length) {\n      addRanges.forEach(info => {\n        let item = this._fileItems.find(o => o.filepath == info.filepath)\n        item.ranges.push(info)\n      })\n    }\n  }\n\n  private diffChanges(original: string, text: string): TextEdit[] {\n    let edits: TextEdit[] = []\n    let diffs = fastDiff(original, text)\n    let offset = 0\n    let orig = TextDocument.create('file:///1', '', 0, original)\n    for (let i = 0; i < diffs.length; i++) {\n      let diff = diffs[i]\n      let pos = orig.positionAt(offset)\n      if (diff[0] == fastDiff.EQUAL) {\n        offset = offset + diff[1].length\n      } else if (diff[0] == fastDiff.DELETE) {\n        let end = orig.positionAt(offset + diff[1].length)\n        if (diffs[i + 1] && diffs[i + 1][0] == fastDiff.INSERT) {\n          let text = diffs[i + 1][1]\n          edits.push(TextEdit.replace(Range.create(pos, end), text))\n          i = i + 1\n        } else {\n          edits.push(TextEdit.replace(Range.create(pos, end), ''))\n        }\n        offset = offset + diff[1].length\n      } else if (diff[0] == fastDiff.INSERT) {\n        edits.push(TextEdit.insert(pos, diff[1]))\n      }\n    }\n    return edits\n  }\n\n  /**\n   * Handle changes of other buffers.\n   */\n  private async onDocumentChange(e: DidChangeTextDocumentParams): Promise<void> {\n    if (this.changing || e.contentChanges.length === 0) return\n    let { uri } = e.textDocument\n    let fileItem = this.getFileItem(uri)\n    // not affected\n    if (!fileItem) return\n    let { range, text } = e.contentChanges[0]\n    let lineChange = lineCountChange(TextEdit.replace(range, text))\n    let edits: TextEdit[] = []\n    let deleteIndexes: number[] = []\n    // 4 cases: ignore, change lineNr, reload, remove\n    for (let i = 0; i < fileItem.ranges.length; i++) {\n      let r = fileItem.ranges[i]\n      // change after range\n      if (range.start.line >= r.start + r.lines.length) continue\n      // change before range\n      if (range.end.line < r.start) {\n        r.start = r.start + lineChange\n        continue\n      }\n      let textDocument = workspace.getDocument(uri).textDocument\n      let end = r.start + r.lines.length + lineChange\n      let newLines = textDocument.lines.slice(r.start, end)\n      if (!newLines.length) {\n        deleteIndexes.push(i)\n        let replaceRange = this.getDeleteRange(r)\n        edits.push(TextEdit.replace(replaceRange, ''))\n      } else {\n        r.lines = newLines\n        let replaceRange = this.getReplaceRange(r)\n        edits.push(TextEdit.replace(replaceRange, newLines.join('\\n')))\n      }\n    }\n    if (deleteIndexes.length) {\n      fileItem.ranges = fileItem.ranges.filter((_, i) => !deleteIndexes.includes(i))\n    }\n    // clean fileItem with empty ranges\n    this._fileItems = this._fileItems.filter(o => o.ranges && o.ranges.length > 0)\n    if (edits.length) {\n      this.adjustLnums(edits)\n      this.changing = true\n      await this.document.applyEdits(edits)\n      this.changing = false\n    }\n    this.nvim.pauseNotification()\n    this.highlightLineNr()\n    this.buffer.setOption('modified', false, true)\n    await this.nvim.resumeNotification(true)\n  }\n\n  private adjustLnums(edits: TextEdit[]): void {\n    for (let item of this._fileItems) {\n      for (let fileRange of item.ranges) {\n        let line = fileRange.lnum - 1\n        fileRange.lnum += getChangedLineCount(Position.create(line, 0), edits)\n      }\n    }\n  }\n\n  /**\n   * Current changed file ranges\n   */\n  public async getFileChanges(): Promise<FileRangeInfo[]> {\n    let changes: FileRangeInfo[] = []\n    let lines = await this.buffer.lines\n    lines.push(SEPARATOR)\n    // current lines\n    let arr: string[] = []\n    let fsPath: string\n    let lnum: number\n    for (let i = 0; i < lines.length; i++) {\n      let line = lines[i]\n      if (line.startsWith(SEPARATOR)) {\n        if (fsPath) {\n          changes.push({\n            filepath: fsPath,\n            lines: arr.slice(),\n            lnum\n          })\n          fsPath = undefined\n          arr = []\n        }\n        if (line.length > 1) {\n          let ms = line.match(/^\\u3000(.*)/)\n          if (ms) {\n            fsPath = this.getAbsolutePath(ms[1].replace(/\\s+$/, ''))\n            lnum = i + 1\n            arr = []\n          }\n        }\n      } else {\n        arr.push(line)\n      }\n    }\n    return changes\n  }\n\n  /**\n   * Open line under cursor in split window\n   */\n  public async splitOpen(): Promise<void> {\n    let { nvim } = this\n    let win = nvim.createWindow(this.opts.fromWinid)\n    let valid = await win.valid\n    let before = await nvim.eval(`strpart(getline('.'), 0 ,col('.') - 1)`) as string\n    let character = before.length\n    let fileRange = await this.searchCurrentRange()\n    if (fileRange) {\n      let bufname = this.getAbsolutePath(fileRange.filepath)\n      nvim.pauseNotification()\n      if (valid) {\n        nvim.call('win_gotoid', [this.opts.fromWinid], true)\n        this.nvim.call('coc#util#jump', ['edit', bufname, [fileRange.line, character]], true)\n      } else {\n        this.nvim.call('coc#util#jump', ['belowright vs', bufname, [fileRange.line, character]], true)\n      }\n      nvim.command('normal! zz', true)\n      await nvim.resumeNotification(true)\n      if (!valid) {\n        this.opts.fromWinid = await nvim.call('win_getid') as number\n      }\n    }\n  }\n\n  public async searchCurrentRange(): Promise<FileRange & { filepath: string, line: number }> {\n    let { nvim } = this\n    let lines = await nvim.eval('getline(1,line(\".\"))') as string[]\n    let len = lines.length\n    for (let i = 0; i < len; i++) {\n      let line = lines[len - i - 1]\n      let ms = line.match(/^\\u3000(.+)/)\n      if (ms) {\n        let r = this.getFileRange(len - i)\n        return Object.assign({ line: r.start + (i == 0 ? 1 : i) - 1 }, r)\n      }\n    }\n    return undefined\n  }\n\n  /**\n   * Add FileItem to refactor buffer.\n   */\n  public async addFileItems(items: FileItemDef[]): Promise<void> {\n    if (this._disposed) return\n    let { cwd } = this.opts\n    let { document } = this\n    const release = await this.mutex.acquire()\n    try {\n      await document.synchronize()\n      let count = document.lineCount\n      let highlighter = new Highlighter()\n      let hlRanges: Range[] = []\n      for (let item of items) {\n        let ranges: FileRange[] = []\n        for (let range of item.ranges) {\n          highlighter.addLine(SEPARATOR)\n          highlighter.addLine(SEPARATOR)\n          let lnum = count + highlighter.length\n          highlighter.addText(`${isParentFolder(cwd, item.filepath) ? path.relative(cwd, item.filepath) : item.filepath}`)\n          // white spaces for conceal texts\n          let n = String(range.start + 1).length + String(range.end).length + 4\n          if (!this.srcId) highlighter.addText(' '.repeat(n))\n          let base = 0 - highlighter.length - count\n          if (range.highlights) {\n            hlRanges.push(...range.highlights.map(r => adjustRange(r, base)))\n          }\n          let { lines, start, end, highlights } = range\n          if (!lines) {\n            lines = await this.getLines(item.filepath, start, end)\n          }\n          ranges.push({ lines, lnum, start, highlights })\n          highlighter.addLines(lines)\n        }\n        if (ranges.length) {\n          let newItem: FileItem = { filepath: item.filepath, ranges }\n          let fileItem = this._fileItems.find(o => o.filepath == item.filepath)\n          if (fileItem) {\n            fileItem.ranges.push(...newItem.ranges)\n          } else {\n            this._fileItems.push(newItem)\n          }\n        }\n      }\n      let { nvim, buffer } = this\n      this.changing = true\n      nvim.pauseNotification()\n      highlighter.render(buffer, count)\n      this.highlightLineNr()\n      buffer.setOption('modified', false, true)\n      buffer.setOption('undolevels', 1000, true)\n      if (count == 2 && hlRanges.length) {\n        let pos = hlRanges[0].start\n        nvim.call('coc#cursor#move_to', [pos.line, pos.character], true)\n      }\n      await nvim.resumeNotification(true)\n      await document.patchChange()\n      this.changing = false\n      await window.cursors.addRanges(hlRanges)\n    } catch (e) {\n      this.changing = false\n      logger.error(`Error on add file item:`, e)\n    }\n    release()\n  }\n\n  public findRange(filepath: string, lnum: number): FileRange {\n    let item = this.fileItems.find(o => sameFile(this.getAbsolutePath(o.filepath), filepath))\n    let range = item.ranges.find(o => o.lnum == lnum)\n    if (!range) throw new Error(`File range not found at lnum: ${lnum}`)\n    return range\n  }\n\n  /**\n   * Save changes to buffers/files, return false when no change made.\n   */\n  public async save(): Promise<boolean> {\n    let { nvim } = this\n    let doc = this.document\n    let { buffer } = doc\n    await doc.patchChange()\n    let changes = await this.getFileChanges()\n    if (!changes) return\n    changes.sort((a, b) => a.lnum - b.lnum)\n    // filter changes that not change\n    let fileChanges: FileChange[] = []\n    for (let i = 0; i < changes.length; i++) {\n      let change = changes[i]\n      let range = this.findRange(change.filepath, change.lnum)\n      if (equals(range.lines, change.lines)) continue\n      fileChanges.push(Object.assign({ start: range.start, end: range.start + range.lines.length }, change))\n      range.lines = change.lines\n    }\n    if (fileChanges.length == 0) {\n      await window.showInformationMessage('No change.')\n      await buffer.setOption('modified', false)\n      return false\n    }\n    let changeMap: { [uri: string]: TextEdit[] } = {}\n    for (let change of fileChanges) {\n      let uri = URI.file(change.filepath).toString()\n      let edits = changeMap[uri] || []\n      edits.push({\n        range: Range.create(change.start, 0, change.end, 0),\n        newText: change.lines.join('\\n') + '\\n'\n      })\n      changeMap[uri] = edits\n    }\n    this.changing = true\n    await workspace.applyEdit({ changes: changeMap })\n    this.changing = false\n    for (let item of this.fileItems) {\n      let uri = URI.file(this.getAbsolutePath(item.filepath)).toString()\n      let edits = changeMap[uri]\n      if (edits && edits.length > 0) {\n        item.ranges.forEach(r => {\n          r.start += getChangedLineCount(Position.create(r.start, 0), edits)\n        })\n      }\n    }\n    nvim.pauseNotification()\n    buffer.setOption('modified', false, true)\n    if (this.config.saveToFile) {\n      nvim.command('silent noa wa', true)\n    }\n    this.highlightLineNr()\n    await nvim.resumeNotification()\n    return true\n  }\n\n  private async getLines(fsPath: string, start: number, end: number): Promise<string[]> {\n    let uri = URI.file(fsPath).toString()\n    let doc = workspace.getDocument(uri)\n    if (doc) return doc.getLines(start, end)\n    return await readFileLines(fsPath, start, end - 1)\n  }\n\n  private getAbsolutePath(filepath: string): string {\n    if (path.isAbsolute(filepath)) return filepath\n    return path.join(this.opts.cwd, filepath)\n  }\n\n  /**\n   * Use conceal/virtual text to add lineNr\n   */\n  private highlightLineNr(): void {\n    let { fileItems, nvim, srcId, bufnr } = this\n    let { winid, cwd } = this.opts\n    let info = {}\n    if (srcId) {\n      nvim.call('nvim_buf_clear_namespace', [bufnr, srcId, 0, -1], true)\n      for (let item of fileItems) {\n        for (let range of item.ranges) {\n          let end = range.start + range.lines.length\n          let text = `${range.start + 1}:${end}`\n          info[range.lnum] = [range.start + 1, end]\n          nvim.call('nvim_buf_set_virtual_text', [bufnr, srcId, range.lnum - 1, [[text, 'LineNr']], {}], true)\n        }\n      }\n    } else {\n      if (this.matchIds.size) {\n        nvim.call('coc#highlight#clear_matches', [winid, Array.from(this.matchIds)], true)\n        this.matchIds.clear()\n      }\n      let id = 2000\n      for (let item of fileItems) {\n        let filename = `${cwd ? path.relative(cwd, item.filepath) : item.filepath}`\n        let col = byteLength(filename) + 1\n        for (let range of item.ranges) {\n          let end = range.start + range.lines.length\n          let text = `:${range.start + 1}:${end}`\n          for (let i = 0; i < text.length; i++) {\n            let ch = text[i]\n            this.matchIds.add(id)\n            info[range.lnum] = [range.start + 1, end]\n            nvim.call('matchaddpos', ['Conceal', [[range.lnum, col + i]], 99, id, { conceal: ch, window: winid }], true)\n            id++\n          }\n        }\n      }\n    }\n    this.buffer.setVar('line_infos', info, true)\n  }\n\n  public getDeleteRange(r: FileRange): Range {\n    let { document } = this\n    let start = r.lnum - 1\n    let end: Position\n    let total = document.lineCount\n    for (let i = start; i < total; i++) {\n      if (i + 1 == total) {\n        end = Position.create(total, 0)\n        break\n      }\n      let line = document.getline(i)\n      if (line === SEPARATOR) {\n        end = Position.create(i + 1, 0)\n        break\n      }\n      if (i != start && line.startsWith(SEPARATOR)) {\n        end = Position.create(i, 0)\n        break\n      }\n    }\n    return Range.create(Position.create(start, 0), end)\n  }\n\n  public getReplaceRange(r: FileRange): Range {\n    let { document } = this\n    let start = r.lnum\n    let end: Position\n    let total = document.lineCount\n    for (let i = start; i < total; i++) {\n      let line = document.getline(i)\n      if (i + 1 == total) {\n        end = Position.create(i, line.length)\n        break\n      }\n      let next = document.getline(i + 1)\n      if (next.startsWith('\\u3000')) {\n        end = Position.create(i, line.length)\n        break\n      }\n    }\n    return Range.create(Position.create(start, 0), end)\n  }\n\n  public get valid(): Promise<boolean> {\n    return this.buffer.valid\n  }\n\n  public get buffer(): Buffer {\n    return this.nvim.createBuffer(this.bufnr)\n  }\n\n  public get document(): Document | null {\n    return workspace.getDocument(this.bufnr)\n  }\n\n  public dispose(): void {\n    this._disposed = true\n    disposeAll(this.disposables)\n  }\n}\n\nfunction adjustRange(range: Range, offset: number): Range {\n  let { start, end } = range\n  return Range.create(start.line - offset, start.character, end.line - offset, end.character)\n}\n\nexport function fixChangeParams(e: DidChangeTextDocumentParams): DidChangeTextDocumentParams {\n  let { contentChanges, bufnr, textDocument, original, originalLines, document } = e\n  let { range, text } = contentChanges[0]\n  let changes: TextDocumentContentChange[] = [{ range, text }]\n  if (!original) {\n    if (emptyRange(range) && range.start.character != 0) {\n      let lines = text.split(/\\r?\\n/)\n      let last = lines[lines.length - 1]\n      let before = originalLines[range.start.line].slice(0, range.start.character)\n      if (last.startsWith(SEPARATOR) && before == last) {\n        changes[0].text = before + lines.slice(0, -1).join('\\n') + '\\n'\n        let { start, end } = range\n        changes[0].range = Range.create(start.line, 0, end.line, 0)\n      }\n    }\n  } else {\n    let lines = original.split(/\\r?\\n/)\n    let last = lines[lines.length - 1]\n    if (last.startsWith(SEPARATOR)) {\n      let before = originalLines[range.start.line].slice(0, range.start.character)\n      if (before == last) {\n        original = before + lines.slice(0, -1).join('\\n') + '\\n'\n        let { start, end } = range\n        changes[0].range = Range.create(start.line, 0, end.line, 0)\n      }\n    }\n    let prev = originalLines[range.start.line - 1]\n    let nest = lines.length > 1 ? lines[lines.length - 2] : ''\n    if (last == '' &&\n      nest.startsWith(SEPARATOR) &&\n      prev == nest &&\n      range.start.character == 0 && range.end.character == 0) {\n      original = prev + '\\n' + lines.slice(0, -2).join('\\n') + '\\n'\n      let { start, end } = range\n      changes[0].range = Range.create(start.line - 1, 0, end.line - 1, 0)\n    }\n  }\n  return { contentChanges: changes, bufnr, textDocument, document, original, originalLines }\n}\n"
  },
  {
    "path": "src/handler/refactor/changes.ts",
    "content": "'use strict'\nimport { equals } from '../../util/object'\n\nexport interface LineInfo {\n  filepath: string\n  // start lnum in refactor buffer, 1 indexed\n  lnum: number\n  start: number\n  lines: string[]\n}\n\nexport type DeleteChange = Map<number, LineInfo>\n\nexport default class Changes {\n  private stack: DeleteChange[] = []\n\n  public add(infos: LineInfo[]): void {\n    let map: Map<number, LineInfo> = new Map()\n    for (let info of infos) {\n      map.set(info.lnum, info)\n    }\n    this.stack.push(map)\n  }\n\n  public checkInsert(lnums: number[]): LineInfo[] | undefined {\n    if (!this.stack.length) return undefined\n    let last = this.stack[this.stack.length - 1]\n    let arr = Array.from(last.keys()).sort((a, b) => a - b)\n    if (!equals(arr, lnums)) return undefined\n    this.stack.pop()\n    return Array.from(last.values())\n  }\n}\n"
  },
  {
    "path": "src/handler/refactor/index.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { Location, Range, TextDocumentEdit, TextEdit, WorkspaceEdit } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport { IConfigurationChangeEvent } from '../../configuration/types'\nimport events from '../../events'\nimport languages, { ProviderName } from '../../languages'\nimport { disposeAll } from '../../util'\nimport { getFileLineCount } from '../../util/fs'\nimport { compareRangesUsingStarts } from '../../util/position'\nimport { Disposable, Emitter, Event } from '../../util/protocol'\nimport { emptyWorkspaceEdit } from '../../util/textedit'\nimport workspace from '../../workspace'\nimport { HandlerDelegate } from '../types'\nimport RefactorBuffer, { FileItemDef, FileRangeDef, RefactorConfig, SEPARATOR } from './buffer'\nimport Search from './search'\n\nconst name = '__coc_refactor__'\nlet refactorId = 0\nlet srcId: number\n\nexport default class Refactor {\n  private buffers: Map<number, RefactorBuffer> = new Map()\n  public config: RefactorConfig\n  private disposables: Disposable[] = []\n  private readonly _onCreate = new Emitter<number>()\n  public readonly onCreate: Event<number> = this._onCreate.event\n  constructor(\n    private nvim: Neovim,\n    private handler: HandlerDelegate\n  ) {\n    this.setConfiguration()\n    workspace.onDidChangeConfiguration(this.setConfiguration, this, this.disposables)\n    events.on('BufUnload', bufnr => {\n      let buf = this.buffers.get(bufnr)\n      if (buf) {\n        buf.dispose()\n        this.buffers.delete(bufnr)\n      }\n    }, null, this.disposables)\n    workspace.onDidChangeTextDocument(e => {\n      let buf = this.buffers.get(e.bufnr)\n      if (buf) buf.onChange(e)\n    }, null, this.disposables)\n  }\n\n  public has(bufnr: number): boolean {\n    return this.buffers.has(bufnr)\n  }\n\n  private setConfiguration(e?: IConfigurationChangeEvent): void {\n    if (e && !e.affectsConfiguration('refactor')) return\n    let config = workspace.getConfiguration('refactor', null)\n    this.config = Object.assign(this.config || {}, {\n      afterContext: config.get('afterContext', 3),\n      beforeContext: config.get('beforeContext', 3),\n      openCommand: config.get('openCommand', 'vsplit'),\n      saveToFile: config.get('saveToFile', true),\n      showMenu: config.get('showMenu', '<Tab>')\n    })\n  }\n\n  /**\n   * Refactor of current symbol\n   */\n  public async doRefactor(): Promise<void> {\n    let { doc, position } = await this.handler.getCurrentState()\n    if (!languages.hasProvider(ProviderName.Rename, doc.textDocument)) {\n      throw new Error(`Rename provider not found for current buffer`)\n    }\n    await doc.synchronize()\n    let edit = await this.handler.withRequestToken('refactor', async token => {\n      let res = await languages.prepareRename(doc.textDocument, position, token)\n      if (token.isCancellationRequested) return null\n      if (res === false) throw new Error(`Provider returns null on prepare, unable to rename at current position`)\n      let edit = await languages.provideRenameEdits(doc.textDocument, position, 'NewName', token)\n      if (token.isCancellationRequested) return null\n      if (!edit) throw new Error('Provider returns null for rename edits.')\n      return edit\n    })\n    if (edit) {\n      await this.fromWorkspaceEdit(edit, doc.filetype)\n    }\n  }\n\n  /**\n   * Search by rg\n   */\n  public async search(args: string[]): Promise<void> {\n    let buf = await this.createRefactorBuffer()\n    let cwd = await this.nvim.call('getcwd', []) as string\n    let search = new Search(this.nvim)\n    await search.run(args, cwd, buf)\n  }\n\n  public async save(bufnr: number): Promise<boolean> {\n    let buf = this.buffers.get(bufnr)\n    if (buf) return await buf.save()\n  }\n\n  public getBuffer(bufnr: number): RefactorBuffer {\n    return this.buffers.get(bufnr)\n  }\n\n  /**\n   * Create initialized refactor buffer\n   */\n  public async createRefactorBuffer(filetype?: string, conceal = false): Promise<RefactorBuffer> {\n    let { nvim } = this\n    let [fromWinid, cwd] = await nvim.eval('[win_getid(),getcwd()]') as [number, string]\n    let { openCommand } = this.config\n    if (!nvim.isVim && !srcId) srcId = await this.nvim.createNamespace('coc-refactor')\n    nvim.pauseNotification()\n    nvim.command(`${openCommand} ${name}${refactorId++}`, true)\n    nvim.command(`setl buftype=acwrite nobuflisted bufhidden=wipe nofen wrap conceallevel=2 concealcursor=n`, true)\n    nvim.command(`setl undolevels=-1 nolist nospell noswapfile foldmethod=expr foldexpr=coc#util#refactor_foldlevel(v:lnum)`, true)\n    nvim.command(`setl foldtext=coc#util#refactor_fold_text(v:foldstart)`, true)\n    nvim.call('setline', [1, ['Save current buffer to make changes', SEPARATOR]], true)\n    nvim.call('matchadd', ['Comment', '\\\\%1l'], true)\n    nvim.call('matchadd', ['Conceal', '^\\\\%u3000'], true)\n    nvim.call('matchadd', ['Label', '^\\\\%u3000\\\\zs\\\\S\\\\+'], true)\n    nvim.command('setl nomod', true)\n    if (filetype) nvim.command(`runtime! syntax/${filetype}.vim`, true)\n    nvim.call('coc#util#do_autocmd', ['CocRefactorOpen'], true)\n    await nvim.resumeNotification()\n    let [bufnr, win] = await nvim.eval('[bufnr(\"%\"),win_getid()]') as [number, number]\n    let opts = { fromWinid, winid: win, cwd }\n    await workspace.document\n    let buf = new RefactorBuffer(bufnr, conceal ? undefined : srcId, this.nvim, this.config, opts)\n    this.buffers.set(bufnr, buf)\n    return buf\n  }\n\n  /**\n   * Create refactor buffer from lines\n   */\n  public async fromLines(lines: string[]): Promise<RefactorBuffer> {\n    let buf = await this.createRefactorBuffer()\n    await buf.buffer.setLines(lines, { start: 0, end: -1, strictIndexing: false })\n    return buf\n  }\n\n  /**\n   * Create refactor buffer from locations\n   */\n  public async fromLocations(locations: Location[], filetype?: string): Promise<RefactorBuffer> {\n    if (!locations || locations.length == 0) return undefined\n    let changes: { [uri: string]: TextEdit[] } = {}\n    let edit: WorkspaceEdit = { changes }\n    for (let location of locations) {\n      let edits: TextEdit[] = changes[location.uri] || []\n      edits.push({ range: location.range, newText: '' })\n      changes[location.uri] = edits\n    }\n    return await this.fromWorkspaceEdit(edit, filetype)\n  }\n\n  /**\n   * Start refactor from workspaceEdit\n   */\n  public async fromWorkspaceEdit(edit: WorkspaceEdit, filetype?: string): Promise<RefactorBuffer> {\n    if (!edit || emptyWorkspaceEdit(edit)) return undefined\n    let items: FileItemDef[] = []\n    let { beforeContext, afterContext } = this.config\n    let { changes, documentChanges } = edit\n    const rangesMap: Map<string, Range[]> = new Map()\n    if (documentChanges) {\n      for (let change of documentChanges || []) {\n        if (TextDocumentEdit.is(change)) {\n          let { textDocument, edits } = change\n          rangesMap.set(textDocument.uri, edits.map(o => o.range))\n        }\n      }\n    } else if (changes) {\n      for (let [uri, edits] of Object.entries(changes)) {\n        rangesMap.set(uri, edits.map(o => o.range))\n      }\n    }\n    for (let [key, editRanges] of rangesMap.entries()) {\n      let max = await this.getLineCount(key)\n      let ranges: FileRangeDef[] = []\n      // start end highlights\n      let start = null\n      let end = null\n      let highlights: Range[] = []\n      editRanges.sort(compareRangesUsingStarts)\n      for (let range of editRanges) {\n        let { line } = range.start\n        let s = Math.max(0, line - beforeContext)\n        if (start != null && s < end) {\n          end = Math.min(max, line + afterContext + 1)\n          highlights.push(adjustRange(range, start))\n        } else {\n          if (start != null) ranges.push({ start, end, highlights })\n          start = s\n          end = Math.min(max, line + afterContext + 1)\n          highlights = [adjustRange(range, start)]\n        }\n      }\n      if (start != null) ranges.push({ start, end, highlights })\n      items.push({ ranges, filepath: URI.parse(key).fsPath })\n    }\n    let buf = await this.createRefactorBuffer(filetype)\n    await buf.addFileItems(items)\n    return buf\n  }\n\n  private async getLineCount(uri: string): Promise<number> {\n    let doc = workspace.getDocument(uri)\n    if (doc) return doc.lineCount\n    return await getFileLineCount(URI.parse(uri).fsPath)\n  }\n\n  public reset(): void {\n    for (let buf of this.buffers.values()) {\n      buf.dispose()\n    }\n    this.buffers.clear()\n  }\n\n  public dispose(): void {\n    this._onCreate.dispose()\n    this.buffers.clear()\n    disposeAll(this.disposables)\n  }\n}\n\nfunction adjustRange(range: Range, offset: number): Range {\n  let { start, end } = range\n  return Range.create(start.line - offset, start.character, end.line - offset, end.character)\n}\n"
  },
  {
    "path": "src/handler/refactor/search.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport type { ChildProcess } from 'child_process'\nimport { EventEmitter } from 'events'\nimport { Range } from 'vscode-languageserver-types'\nimport { createLogger } from '../../logger'\nimport Highlighter from '../../model/highlighter'\nimport { ansiparse } from '../../util/ansiparse'\nimport { Mutex } from '../../util/mutex'\nimport { child_process, path, readline } from '../../util/node'\nimport window from '../../window'\nimport RefactorBuffer, { FileItem, FileItemDef } from './buffer'\nconst { spawn } = child_process\nconst logger = createLogger('handler-search')\n\nconst defaultArgs = ['--color', 'ansi', '--colors', 'path:fg:black', '--colors', 'line:fg:green', '--colors', 'match:fg:red', '--no-messages', '--heading', '-n']\nconst controlCode = '\\x1b'\n\n// emit FileItem\nclass Task extends EventEmitter {\n  private process: ChildProcess\n  public start(cmd: string, args: string[], cwd: string): void {\n    this.process = spawn(cmd, args, { cwd, shell: process.platform === 'win32' })\n    this.process.on('error', e => {\n      this.emit('error', e.message)\n    })\n    const rl = readline.createInterface(this.process.stdout)\n    let start: number\n    let fileItem: FileItemDef\n    let lines: string[] = []\n    let highlights: Range[] = []\n    let create = true\n    rl.on('line', content => {\n      if (content.includes(controlCode)) {\n        let items = ansiparse(content)\n        if (items.length == 0) return\n        if (items[0].foreground == 'black') {\n          fileItem = { filepath: path.join(cwd, items[0].text), ranges: [] }\n          return\n        }\n        let normalLine = items[0].foreground == 'green'\n        if (normalLine) {\n          let lnum = parseInt(items[0].text, 10) - 1\n          let padlen = items[0].text.length + 1\n          if (create) {\n            start = lnum\n            create = false\n          }\n          let line = ''\n          for (let item of items) {\n            if (item.foreground == 'red') {\n              let l = lnum - start\n              let c = line.length - padlen\n              highlights.push(Range.create(l, c, l, c + item.text.length))\n            }\n            line += item.text\n          }\n          let currline = line.slice(padlen)\n          lines.push(currline)\n        }\n      } else {\n        let fileEnd = content.trim().length == 0\n        if (fileItem && (fileEnd || content.trim() == '--')) {\n          fileItem.ranges.push({ lines, highlights, start })\n        }\n        if (fileEnd) {\n          this.emit('item', fileItem)\n          fileItem = null\n        }\n        lines = []\n        highlights = []\n        create = true\n      }\n    })\n    rl.on('close', () => {\n      if (fileItem) {\n        if (lines.length) {\n          fileItem.ranges.push({ lines, highlights, start, })\n        }\n        this.emit('item', fileItem)\n      }\n      lines = highlights = fileItem = null\n      this.emit('end')\n    })\n  }\n\n  public dispose(): void {\n    if (this.process) {\n      this.process.kill()\n    }\n  }\n}\n\nexport default class Search {\n  private task: Task\n  constructor(private nvim: Neovim, private cmd = 'rg') {\n  }\n\n  public run(args: string[], cwd: string, refactorBuf: RefactorBuffer): Promise<void> {\n    let { nvim, cmd } = this\n    let { afterContext, beforeContext } = refactorBuf.config\n    let argList = ['-A', afterContext.toString(), '-B', beforeContext.toString()].concat(defaultArgs, args)\n    let p = getPathFromArgs(args)\n    if (p) argList.pop()\n    argList.push('--', p ? path.isAbsolute(p) ? p : `./${p.replace(/^\\.\\//, '')}` : './')\n    this.task = new Task()\n    this.task.start(cmd, argList, cwd)\n    let mutex: Mutex = new Mutex()\n    let files = 0\n    let matches = 0\n    let start = Date.now()\n    // remaining items\n    let fileItems: FileItem[] = []\n    const addFileItems = async () => {\n      if (fileItems.length == 0) return\n      let items = fileItems.slice()\n      fileItems = []\n      const release = await mutex.acquire()\n      try {\n        await refactorBuf.addFileItems(items)\n      } catch (e) {\n        logger.error(e)\n      }\n      release()\n    }\n    return new Promise((resolve, reject) => {\n      let interval = setInterval(addFileItems, 300)\n      this.task.on('item', async (fileItem: FileItem) => {\n        files++\n        matches = matches + fileItem.ranges.reduce((p, r) => p + r.highlights.length, 0)\n        fileItems.push(fileItem)\n      })\n      this.task.on('error', message => {\n        clearInterval(interval)\n        void window.showErrorMessage(`Error on command \"${cmd}\": ${message}`)\n        this.task = null\n        reject(new Error(message))\n      })\n      this.task.on('end', async () => {\n        clearInterval(interval)\n        try {\n          await addFileItems()\n          const release = await mutex.acquire()\n          release()\n          this.task.removeAllListeners()\n          this.task = null\n          let buf = refactorBuf.buffer\n          if (buf) {\n            nvim.pauseNotification()\n            if (files == 0) {\n              buf.setLines(['No match found'], { start: 1, end: 2, strictIndexing: false }, true)\n              // eslint-disable-next-line @typescript-eslint/no-floating-promises\n              buf.addHighlight({ line: 1, srcId: -1, colEnd: -1, colStart: 0, hlGroup: 'Error' })\n              buf.setOption('modified', false, true)\n            } else {\n              let highlighter = new Highlighter()\n              highlighter.addText('Files', 'MoreMsg')\n              highlighter.addText(': ')\n              highlighter.addText(`${files} `, 'Number')\n              highlighter.addText('Matches', 'MoreMsg')\n              highlighter.addText(': ')\n              highlighter.addText(`${matches} `, 'Number')\n              highlighter.addText('Duration', 'MoreMsg')\n              highlighter.addText(': ')\n              highlighter.addText(`${Date.now() - start}ms`, 'Number')\n              highlighter.render(buf, 1, 2)\n            }\n            buf.setOption('modified', false, true)\n            nvim.resumeNotification(false, true)\n          }\n        } catch (e) {\n          // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors\n          reject(e)\n          return\n        }\n        resolve()\n      })\n    })\n  }\n\n  public abort(): void {\n    this.task?.dispose()\n  }\n}\n\n// rg requires `-- [path]` at the end\nexport function getPathFromArgs(args: string[]): string | undefined {\n  if (args.length < 2) return undefined\n  let len = args.length\n  if (args[len - 1].startsWith('-')) return undefined\n  if (args[len - 2].startsWith('-')) return undefined\n  return args[len - 1]\n}\n"
  },
  {
    "path": "src/handler/rename.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { Range, WorkspaceEdit } from 'vscode-languageserver-types'\nimport languages, { ProviderName } from '../languages'\nimport { emptyRange } from '../util/position'\nimport { CancellationTokenSource } from '../util/protocol'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { HandlerDelegate } from './types'\n\nexport default class Rename {\n  constructor(\n    private nvim: Neovim,\n    private handler: HandlerDelegate) {\n  }\n\n  public async getWordEdit(): Promise<WorkspaceEdit> {\n    let { doc, position } = await this.handler.getCurrentState()\n    let range = doc.getWordRangeAtPosition(position)\n    if (!range || emptyRange(range)) return null\n    let curname = doc.textDocument.getText(range)\n    if (languages.hasProvider(ProviderName.Rename, doc.textDocument)) {\n      await doc.synchronize()\n      let requestTokenSource = new CancellationTokenSource()\n      let res = await languages.prepareRename(doc.textDocument, position, requestTokenSource.token)\n      if (res !== false) {\n        let newName = curname.startsWith('a') ? 'b' : 'a'\n        let edit = await languages.provideRenameEdits(doc.textDocument, position, newName, requestTokenSource.token)\n        if (edit) return edit\n      }\n    }\n    void window.showInformationMessage('Rename provider not found, extract word ranges from current buffer')\n    let ranges = doc.getSymbolRanges(curname)\n    return {\n      changes: {\n        [doc.uri]: ranges.map(r => ({ range: r, newText: curname }))\n      }\n    }\n  }\n\n  public async rename(newName?: string): Promise<boolean> {\n    let { doc, position } = await this.handler.getCurrentState()\n    this.handler.checkProvider(ProviderName.Rename, doc.textDocument)\n    await doc.synchronize()\n    let token = (new CancellationTokenSource()).token\n    let res = await languages.prepareRename(doc.textDocument, position, token)\n    if (res === false) {\n      void window.showWarningMessage('Invalid position for rename')\n      return false\n    }\n    let curname: string\n    if (!newName) {\n      if (Range.is(res)) {\n        curname = doc.textDocument.getText(res)\n        await window.moveTo(res.start)\n      } else if (res && typeof res.placeholder === 'string') {\n        curname = res.placeholder\n      } else {\n        curname = await this.nvim.eval('expand(\"<cword>\")') as string\n      }\n      const config = workspace.getConfiguration('coc.preferences', null)\n      newName = await window.requestInput('New name', config.get<boolean>('renameFillCurrent', true) ? curname : '')\n    }\n    if (newName === '') void window.showWarningMessage('Empty word, rename canceled')\n    if (!newName) return false\n    let edit = await languages.provideRenameEdits(doc.textDocument, position, newName, token)\n    if (token.isCancellationRequested || !edit) return false\n    await workspace.applyEdit(edit)\n    return true\n  }\n}\n"
  },
  {
    "path": "src/handler/selectionRange.ts",
    "content": "'use strict'\nimport type { Neovim } from '@chemzqm/neovim'\nimport { Position, Range, SelectionRange } from 'vscode-languageserver-types'\nimport languages, { ProviderName } from '../languages'\nimport { isFalsyOrEmpty } from '../util/array'\nimport { equals } from '../util/object'\nimport { positionInRange } from '../util/position'\nimport window from '../window'\nimport { HandlerDelegate } from './types'\n\nexport default class SelectionRangeHandler {\n  private selectionRange: SelectionRange = null\n  constructor(private nvim: Neovim, private handler: HandlerDelegate) {\n  }\n\n  public async getSelectionRanges(): Promise<SelectionRange[] | null> {\n    let { doc, position } = await this.handler.getCurrentState()\n    this.handler.checkProvider(ProviderName.SelectionRange, doc.textDocument)\n    await doc.synchronize()\n    return await this.handler.withRequestToken('selection ranges', token => {\n      return languages.getSelectionRanges(doc.textDocument, [position], token)\n    })\n  }\n\n  public async selectRange(visualmode: string, forward: boolean): Promise<boolean> {\n    let { doc } = await this.handler.getCurrentState()\n    this.handler.checkProvider(ProviderName.SelectionRange, doc.textDocument)\n    let positions: Position[] = []\n    if (!forward && (!this.selectionRange || !visualmode)) return\n    if (visualmode) {\n      let range = await window.getSelectedRange(visualmode)\n      positions.push(range.start, range.end)\n    } else {\n      let position = await window.getCursorPosition()\n      positions.push(position)\n    }\n    if (!forward) {\n      let curr = Range.create(positions[0], positions[1])\n      let { selectionRange } = this\n      while (selectionRange && selectionRange.parent) {\n        if (equals(selectionRange.parent.range, curr)) {\n          break\n        }\n        selectionRange = selectionRange.parent\n      }\n      if (selectionRange && selectionRange.parent) {\n        await window.selectRange(selectionRange.range)\n      }\n      return\n    }\n    await doc.synchronize()\n    let selectionRanges: SelectionRange[] = await this.handler.withRequestToken('selection ranges', token => {\n      return languages.getSelectionRanges(doc.textDocument, positions, token)\n    })\n    if (isFalsyOrEmpty(selectionRanges)) return false\n    let selectionRange: SelectionRange\n    if (selectionRanges.length == 1) {\n      selectionRange = selectionRanges[0]\n    } else {\n      let end = positions[1] ?? positions[0]\n      let r = Range.create(positions[0], end)\n      selectionRange = selectionRanges[0]\n      while (selectionRange) {\n        if (equals(r, selectionRange.range)) {\n          selectionRange = selectionRange.parent\n          continue\n        }\n        if (\n          positionInRange(positions[0], selectionRange.range) == 0 &&\n          positionInRange(end, selectionRange.range) == 0) {\n          break\n        }\n        selectionRange = selectionRange.parent\n      }\n    }\n    if (!selectionRange) return false\n    this.selectionRange = selectionRanges[0]\n    await window.selectRange(selectionRange.range)\n    return true\n  }\n}\n"
  },
  {
    "path": "src/handler/semanticTokens/buffer.ts",
    "content": "'use strict'\nimport { Buffer, Neovim } from '@chemzqm/neovim'\nimport { Range, SemanticTokens, SemanticTokensDelta, SemanticTokensLegend, uinteger } from 'vscode-languageserver-types'\nimport languages, { ProviderName } from '../../languages'\nimport { createLogger } from '../../logger'\nimport { SyncItem } from '../../model/bufferSync'\nimport Document from '../../model/document'\nimport Regions from '../../model/regions'\nimport { HighlightItem } from '../../types'\nimport { getConditionValue, waitWithToken } from '../../util'\nimport { toArray } from '../../util/array'\nimport { CancellationError, onUnexpectedError } from '../../util/errors'\nimport { waitImmediate } from '../../util/index'\nimport { toNumber } from '../../util/numbers'\nimport { CancellationToken, CancellationTokenSource, Emitter, Event } from '../../util/protocol'\nimport { bytes, isHighlightGroupCharCode, toText } from '../../util/string'\nimport window from '../../window'\nimport workspace from '../../workspace'\nconst logger = createLogger('semanticTokens-buffer')\nconst yieldEveryMilliseconds = getConditionValue(15, 5)\n\nexport const HLGROUP_PREFIX = 'CocSem'\nexport const NAMESPACE = 'semanticTokens'\n\nexport type TokenRange = [number, number, number] // line, startCol, endCol\n\nexport interface SemanticTokensConfig {\n  enable: boolean\n  highlightPriority: number\n  incrementTypes: string[]\n  combinedModifiers: string[]\n}\n\nexport interface SemanticTokenRange {\n  range: TokenRange\n  tokenType: string\n  tokenModifiers: string[]\n  hlGroup?: string\n  combine: boolean\n}\n\ninterface SemanticTokensPreviousResult {\n  readonly version: number\n  readonly resultId: string | undefined,\n  readonly tokens?: uinteger[],\n}\n\ninterface RangeHighlights {\n  highlights: SemanticTokenRange[]\n  /**\n   * 0 based\n   */\n  start: number\n  /**\n   * 0 based exclusive\n   */\n  end: number\n}\n\n// should be higher than document debounce\nconst debounceInterval = getConditionValue(100, 20)\nconst requestDelay = getConditionValue(500, 20)\nconst highlightGroupMap: Map<string, string> = new Map()\n\nexport interface StaticConfig {\n  filetypes: string[] | null\n}\n\nexport default class SemanticTokensBuffer implements SyncItem {\n  private _config: SemanticTokensConfig\n  private _dirty = false\n  private _version: number | undefined\n  public readonly regions = new Regions()\n  private tokenSource: CancellationTokenSource\n  private rangeTokenSource: CancellationTokenSource\n  private previousResults: SemanticTokensPreviousResult | undefined\n  private _highlights: [number, SemanticTokenRange[]]\n  private readonly _onDidRefresh = new Emitter<void>()\n  public readonly onDidRefresh: Event<void> = this._onDidRefresh.event\n  constructor(private nvim: Neovim, public readonly doc: Document, private staticConfig: StaticConfig) {\n    if (this.hasProvider) this.doHighlight().catch(onUnexpectedError)\n  }\n\n  public get config(): SemanticTokensConfig {\n    if (this._config) return this._config\n    this.loadConfiguration()\n    return this._config\n  }\n\n  public loadConfiguration(): void {\n    let config = workspace.getConfiguration('semanticTokens', this.doc)\n    let changed = this._config != null && this._config.enable != config.enable\n    this._config = {\n      enable: config.get<boolean>('enable'),\n      highlightPriority: config.get<number>('highlightPriority'),\n      incrementTypes: config.get<string[]>('incrementTypes'),\n      combinedModifiers: config.get<string[]>('combinedModifiers')\n    }\n    if (changed) {\n      if (this._config.enable) {\n        this.forceHighlight().catch(onUnexpectedError)\n      } else {\n        this.clearHighlight()\n      }\n    }\n  }\n\n  public get configEnabled(): boolean {\n    let { enable } = this.config\n    let { filetypes } = this.staticConfig\n    // should be null when not specified\n    if (Array.isArray(filetypes)) return filetypes.includes('*') || filetypes.includes(this.doc.filetype)\n    return enable\n  }\n\n  public get bufnr(): number {\n    return this.doc.bufnr\n  }\n\n  public onChange(): void {\n    // need debounce for document synchronize\n    this.doHighlight().catch(onUnexpectedError)\n  }\n\n  public onTextChange(): void {\n    this._version = undefined\n    this.cancel()\n  }\n\n  public async forceHighlight(): Promise<void> {\n    this.clearHighlight()\n    await this.doHighlight(true, 0)\n  }\n\n  public async onShown(winid: number): Promise<void> {\n    if (!this.enabled) return\n    await this.doHighlight(false, debounceInterval, winid)\n  }\n\n  public async onWinScroll(winid: number): Promise<void> {\n    if (!this.shouldHighlight) return\n    this.cancel(true)\n    let rangeTokenSource = this.rangeTokenSource = new CancellationTokenSource()\n    let token = rangeTokenSource.token\n    await waitWithToken(debounceInterval, token)\n    if (token.isCancellationRequested) return\n    if (this.shouldRangeHighlight) {\n      await this.doRangeHighlight(winid, undefined, token)\n    } else {\n      await this.highlightRegions(winid, token)\n    }\n  }\n\n  /**\n   * Highlight the span without check regions\n   */\n  public async onCursorHold(winid: number, lnum: number): Promise<void> {\n    if (!this.enabled) return\n    this.cancel(true)\n    let rangeTokenSource = this.rangeTokenSource = new CancellationTokenSource()\n    let token = rangeTokenSource.token\n    let height = workspace.env.lines\n    let span: [number, number] = [Math.max(0, lnum - height), Math.min(this.doc.lineCount, lnum + height)]\n    if (this.shouldRangeHighlight) {\n      await this.doRangeHighlight(winid, span, token)\n    } else if (this.highlights) {\n      await this.addHighlights(this.highlights, span, token)\n    }\n  }\n\n  public get hasProvider(): boolean {\n    return languages.hasProvider(ProviderName.SemanticTokens, this.doc)\n      || languages.hasProvider(ProviderName.SemanticTokensRange, this.doc)\n  }\n\n  private get hasLegend(): boolean {\n    let { textDocument } = this.doc\n    return languages.getLegend(textDocument) != null || languages.getLegend(textDocument, true) != null\n  }\n\n  public get rangeProviderOnly(): boolean {\n    return !languages.hasProvider(ProviderName.SemanticTokens, this.doc)\n      && languages.hasProvider(ProviderName.SemanticTokensRange, this.doc)\n  }\n\n  public get shouldRangeHighlight(): boolean {\n    let { textDocument } = this.doc\n    return languages.hasProvider(ProviderName.SemanticTokensRange, textDocument) && this.previousResults == null\n  }\n\n  /**\n   * Get current highlight items\n   */\n  public get highlights(): SemanticTokenRange[] | undefined {\n    if (!this._highlights) return undefined\n    return this._highlights[1]\n  }\n\n  private get buffer(): Buffer {\n    return this.nvim.createBuffer(this.bufnr)\n  }\n\n  public get enabled(): boolean {\n    if (!this.configEnabled || !this.hasLegend) return false\n    return this.hasProvider\n  }\n\n  private get shouldHighlight(): boolean {\n    if (!this.enabled) return false\n    const { doc } = this\n    if (doc.dirty || doc.version === this._version) return false\n    return true\n  }\n\n  public checkState(): void {\n    if (!this.configEnabled) throw new Error(`Semantic tokens highlight not enabled for current filetype: ${this.doc.filetype}`)\n    if (!this.hasProvider || !this.hasLegend) throw new Error(`SemanticTokens provider not found for ${this.doc.uri}`)\n  }\n\n  public async getTokenRanges(\n    tokens: number[],\n    legend: SemanticTokensLegend,\n    token: CancellationToken): Promise<SemanticTokenRange[] | null> {\n    let currentLine = 0\n    let currentCharacter = 0\n    let highlights: SemanticTokenRange[] = []\n    let toBytes: (characterIndex: number) => number | undefined\n    let textDocument = this.doc.textDocument\n    let tickStart = Date.now()\n    for (let i = 0; i < tokens.length; i += 5) {\n      if (i == 0 || Date.now() - tickStart > yieldEveryMilliseconds) {\n        await waitImmediate()\n        if (token.isCancellationRequested) break\n        tickStart = Date.now()\n      }\n      const deltaLine = tokens[i]\n      const deltaCharacter = tokens[i + 1]\n      const length = tokens[i + 2]\n      const tokenType = legend.tokenTypes[tokens[i + 3]]\n      const tokenModifiers = legend.tokenModifiers.filter((_, m) => tokens[i + 4] & (1 << m))\n      const lnum = currentLine + deltaLine\n      if (deltaLine != 0 || !toBytes) {\n        toBytes = bytes(toText(textDocument.lines[lnum]))\n      }\n      const sc = deltaLine === 0 ? currentCharacter + deltaCharacter : deltaCharacter\n      const ec = sc + length\n      currentLine = lnum\n      currentCharacter = sc\n      this.addHighlightItems(highlights, [lnum, toBytes(sc), toBytes(ec)], tokenType, tokenModifiers)\n    }\n    if (token.isCancellationRequested) return null\n    return highlights\n  }\n\n  /**\n   * Single line only.\n   */\n  private addHighlightItems(highlights: SemanticTokenRange[], range: [number, number, number], tokenType: string, tokenModifiers: string[]): void {\n    // highlight groups:\n    // CocSem + Type + type\n    // CocSem + TypeMod + type + modifier\n\n    let { combinedModifiers } = this.config\n    let combine = false\n\n    highlights.push({\n      range,\n      tokenType,\n      combine,\n      hlGroup: HLGROUP_PREFIX + 'Type' + toHighlightPart(tokenType),\n      tokenModifiers,\n    })\n\n    if (tokenModifiers.length) {\n      // only use first modifier to avoid highlight flicking\n      const modifier = tokenModifiers[0]\n      combine = combinedModifiers.includes(modifier)\n      highlights.push({\n        range,\n        tokenType,\n        combine,\n        hlGroup: HLGROUP_PREFIX + 'TypeMod' + toHighlightPart(tokenType) + toHighlightPart(modifier),\n        tokenModifiers,\n      })\n    }\n  }\n\n  private toHighlightItems(highlights: ReadonlyArray<SemanticTokenRange>, span?: [number, number]): HighlightItem[] {\n    let { incrementTypes } = this.config\n    let filter = Array.isArray(span)\n    let res: HighlightItem[] = []\n    for (let hi of highlights) {\n      if (!hi.hlGroup) continue\n      let lnum = hi.range[0]\n      if (filter && (lnum < span[0] || lnum > span[1])) continue\n      let item: HighlightItem = {\n        lnum,\n        hlGroup: hi.hlGroup,\n        colStart: hi.range[1],\n        colEnd: hi.range[2],\n        combine: hi.combine\n      }\n      if (incrementTypes.includes(hi.tokenType)) {\n        item.end_incl = true\n        item.start_incl = true\n      }\n      res.push(item)\n    }\n    return res\n  }\n\n  public async doHighlight(forceFull = false, wait: number = debounceInterval, winid?: number): Promise<void> {\n    this.cancel()\n    let tokenSource = this.tokenSource = new CancellationTokenSource()\n    let token = tokenSource.token\n    await waitWithToken(wait, token)\n    if (token.isCancellationRequested) return\n    const winids = winid == null ? workspace.editors.getBufWinids(this.bufnr) : [winid]\n    if (!this.enabled || winids.length === 0) return\n    if (this.shouldRangeHighlight) {\n      this.cancel(true)\n      let rangeTokenSource = this.rangeTokenSource = new CancellationTokenSource()\n      let rangeToken = rangeTokenSource.token\n      for (const win of winids) {\n        await this.doRangeHighlight(win, undefined, rangeToken)\n        if (rangeToken.isCancellationRequested) break\n      }\n    }\n    if (token.isCancellationRequested || this.rangeProviderOnly) return\n    this.cancel(true)\n    const { doc } = this\n    const version = doc.version\n    let tokenRanges: SemanticTokenRange[] | undefined\n    // TextDocument not changed, need perform highlight since lines possible replaced.\n    if (version === this.previousResults?.version) {\n      if (this._highlights && this._highlights[0] == version) {\n        tokenRanges = this._highlights[1]\n      } else {\n        // possible cancelled.\n        const tokens = this.previousResults.tokens\n        const legend = languages.getLegend(doc.textDocument)\n        tokenRanges = await this.getTokenRanges(tokens, legend, token)\n      }\n    } else {\n      tokenRanges = await this.sendRequest(() => {\n        return this.requestAllHighlights(token, forceFull)\n      }, token)\n    }\n    // request cancelled or can't work\n    if (token.isCancellationRequested || !tokenRanges) return\n    this._highlights = [version, tokenRanges]\n    // Full highlight when no highlight added before or token length < 500\n    if (!this._dirty || tokenRanges.length < 500) {\n      let succeed = await this.addHighlights(tokenRanges, undefined, token)\n      if (succeed) this._version = version\n    } else {\n      this.regions.clear()\n      await this.highlightRegions(winid, token)\n    }\n    this._onDidRefresh.fire()\n  }\n\n  private async addHighlights(highlights: ReadonlyArray<SemanticTokenRange>, span: [number, number] | undefined, token: CancellationToken): Promise<boolean> {\n    const { bufnr, regions, doc, config } = this\n    let items = this.toHighlightItems(highlights, span)\n    let diff = await window.diffHighlights(bufnr, NAMESPACE, items, span, token)\n    if (!diff || token.isCancellationRequested) return false\n    const priority = config.highlightPriority\n    await window.applyDiffHighlights(bufnr, NAMESPACE, priority, diff, true)\n    this._dirty = true\n    if (span) {\n      regions.add(span[0], span[1])\n    } else {\n      regions.add(0, doc.lineCount)\n    }\n    return true\n  }\n\n  private async sendRequest<R>(fn: () => Promise<R>, token: CancellationToken): Promise<R | undefined> {\n    try {\n      return await fn()\n    } catch (e) {\n      if (!token.isCancellationRequested) {\n        // Retry on server cancel\n        if (e instanceof CancellationError) {\n          this.doHighlight(true, requestDelay).catch(onUnexpectedError)\n        } else {\n          logger.error('Error on request semanticTokens: ', e)\n        }\n      }\n      return undefined\n    }\n  }\n\n  /**\n   * Perform range highlight request and update.\n   */\n  public async doRangeHighlight(winid: number, span: [number, number] | undefined, token: CancellationToken): Promise<void> {\n    const { version } = this.doc\n    let res = await this.sendRequest(() => {\n      return this.requestRangeHighlights(winid, span, token)\n    }, token)\n    if (res == null || token.isCancellationRequested) return\n    const { highlights, start, end } = res\n    if (this.rangeProviderOnly || !this.previousResults) {\n      if (!this._highlights || version !== this._highlights[0]) {\n        this._highlights = [version, []]\n      }\n      let tokenRanges = this._highlights[1]\n      let usedLines: Set<number> = tokenRanges.reduce((p, c) => p.add(c.range[0]), new Set<number>())\n      highlights.forEach(hi => {\n        if (!usedLines.has(hi.range[0])) {\n          tokenRanges.push(hi)\n        }\n      })\n    }\n    await this.addHighlights(highlights, [start, end], token)\n  }\n\n  /**\n   * highlight current visible regions, highlight all associated winids when winid is undefined\n   */\n  public async highlightRegions(winid: number | undefined, token: CancellationToken): Promise<void> {\n    let { regions, highlights, doc, bufnr } = this\n    if (!highlights) return\n    let spans = await window.getVisibleRanges(bufnr, winid)\n    if (token.isCancellationRequested) return\n    for (let lines of spans) {\n      let span = regions.toUncoveredSpan([lines[0] - 1, lines[1] - 1], workspace.env.lines, doc.lineCount)\n      if (span) await this.addHighlights(highlights, span, token)\n    }\n  }\n\n  /**\n   * Request highlights for visible range of winid.\n   */\n  public async requestRangeHighlights(winid: number, span: [number, number] | undefined, token: CancellationToken): Promise<RangeHighlights | null> {\n    let { nvim, doc } = this\n    if (!span) {\n      let region = await nvim.call('coc#window#visible_range', [winid]) as [number, number]\n      if (!region || token.isCancellationRequested) return null\n      // convert to 0 based\n      span = this.regions.toUncoveredSpan([region[0] - 1, region[1] - 1], workspace.env.lines, doc.lineCount)\n    }\n    if (!span) return null\n    const startLine = span[0]\n    const endLine = span[1]\n    let range = doc.textDocument.intersectWith(Range.create(startLine, 0, endLine + 1, 0))\n    let res = await languages.provideDocumentRangeSemanticTokens(doc.textDocument, range, token)\n    if (!res || !SemanticTokens.is(res) || token.isCancellationRequested) return null\n    let legend = languages.getLegend(doc.textDocument, true)\n    let highlights = await this.getTokenRanges(res.data, legend, token)\n    if (!highlights) return null\n    return { highlights, start: startLine, end: endLine }\n  }\n\n  /**\n   * Request highlights from provider, return undefined when can't request or request cancelled\n   * Use range provider only when not semanticTokens provider exists.\n   */\n  public async requestAllHighlights(token: CancellationToken, forceFull: boolean): Promise<SemanticTokenRange[] | null> {\n    const textDocument = this.doc.textDocument\n    const legend = languages.getLegend(textDocument)\n    const hasEditProvider = languages.hasSemanticTokensEdits(textDocument)\n    const previousResult = forceFull ? null : this.previousResults\n    const version = textDocument.version\n    let result: SemanticTokens | SemanticTokensDelta\n    if (hasEditProvider && previousResult?.resultId) {\n      result = await languages.provideDocumentSemanticTokensEdits(textDocument, previousResult.resultId, token)\n    } else {\n      result = await languages.provideDocumentSemanticTokens(textDocument, token)\n    }\n    if (token.isCancellationRequested || result == null) return\n    let tokens: uinteger[] = []\n    if (SemanticTokens.is(result)) {\n      tokens = result.data\n    } else if (previousResult && Array.isArray(result.edits)) {\n      tokens = previousResult.tokens\n      result.edits.forEach(e => {\n        tokens.splice(e.start, toNumber(e.deleteCount), ...toArray(e.data))\n      })\n    }\n    this.previousResults = { resultId: result.resultId, tokens, version }\n    return await this.getTokenRanges(tokens, legend, token)\n  }\n\n  public clearHighlight(): void {\n    this.reset()\n    this.buffer.clearNamespace(NAMESPACE)\n  }\n\n  public onProviderChange(): void {\n    if (!this.hasProvider) {\n      this.cancel()\n      this.clearHighlight()\n    } else {\n      this.reset()\n      this.doHighlight(true, 0).catch(onUnexpectedError)\n    }\n  }\n\n  public cancel(rangeOnly = false): void {\n    if (this.rangeTokenSource) {\n      this.rangeTokenSource.cancel()\n      this.rangeTokenSource = null\n    }\n    if (rangeOnly) return\n    this.regions.clear()\n    if (this.tokenSource) {\n      this.tokenSource.cancel()\n      this.tokenSource = null\n    }\n  }\n\n  private reset(): void {\n    this.previousResults = undefined\n    this._highlights = undefined\n    this._version = undefined\n    this.regions.clear()\n  }\n\n  public dispose(): void {\n    this.cancel()\n    this.reset()\n    this._onDidRefresh.dispose()\n  }\n}\n\nexport function toHighlightPart(token: string): string {\n  if (!token) return ''\n  if (highlightGroupMap.has(token)) return highlightGroupMap.get(token)\n  let chars: string[] = []\n  for (let i = 0; i < token.length; i++) {\n    let ch = token[i]\n    ch = isHighlightGroupCharCode(ch.charCodeAt(0)) ? ch : '_'\n    chars.push(i == 0 ? ch.toUpperCase() : ch)\n  }\n  let part = chars.join('')\n  highlightGroupMap.set(token, part)\n  return part\n}\n"
  },
  {
    "path": "src/handler/semanticTokens/index.ts",
    "content": "'use strict'\nimport type { Neovim } from '@chemzqm/neovim'\nimport commands from '../../commands'\nimport events from '../../events'\nimport languages from '../../languages'\nimport BufferSync from '../../model/bufferSync'\nimport Highlighter from '../../model/highlighter'\nimport { Documentation, FloatFactory } from '../../types'\nimport { disposeAll } from '../../util'\nimport { distinct, toArray } from '../../util/array'\nimport type { Disposable } from '../../util/protocol'\nimport { toErrorText, toText } from '../../util/string'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport SemanticTokensBuffer, { HLGROUP_PREFIX, NAMESPACE, StaticConfig, toHighlightPart } from './buffer'\nconst headGroup = 'Statement'\n\nlet floatFactory: FloatFactory | undefined\n\nexport default class SemanticTokens {\n  private disposables: Disposable[] = []\n  private highlighters: BufferSync<SemanticTokensBuffer>\n  public staticConfig: StaticConfig\n\n  constructor(private nvim: Neovim) {\n    this.setStaticConfiguration()\n    workspace.onDidChangeConfiguration(e => {\n      if (e.affectsConfiguration('semanticTokens')) {\n        this.setStaticConfiguration()\n        for (let item of this.highlighters.items) {\n          item.loadConfiguration()\n        }\n      }\n    }, this, this.disposables)\n    commands.register({\n      id: 'semanticTokens.checkCurrent',\n      execute: async () => {\n        await this.showHighlightInfo()\n      }\n    }, false, 'show semantic tokens highlight information of current buffer')\n    commands.register({\n      id: 'semanticTokens.refreshCurrent',\n      execute: () => {\n        return this.highlightCurrent()\n      }\n    }, false, 'refresh semantic tokens highlight of current buffer.')\n    commands.register({\n      id: 'semanticTokens.inspect',\n      execute: () => {\n        return this.inspectSemanticToken()\n      }\n    }, false, 'Inspect semantic token information at cursor position.')\n    commands.register({\n      id: 'semanticTokens.clearCurrent',\n      execute: async () => {\n        let buf = await nvim.buffer\n        buf.clearNamespace(NAMESPACE, 0, -1)\n      }\n    }, false, 'clear semantic tokens highlight of current buffer')\n    commands.register({\n      id: 'semanticTokens.clearAll',\n      execute: async () => {\n        let bufs = await nvim.buffers\n        for (let buf of bufs) {\n          buf.clearNamespace(NAMESPACE, 0, -1)\n        }\n      }\n    }, false, 'clear semantic tokens highlight of all buffers')\n    this.highlighters = workspace.registerBufferSync(doc => {\n      return new SemanticTokensBuffer(this.nvim, doc, this.staticConfig)\n    })\n    languages.onDidSemanticTokensRefresh(async selector => {\n      let visibleBufs = window.visibleTextEditors.map(o => o.document.bufnr)\n      for (let item of this.highlighters.items) {\n        if (workspace.match(selector, item.doc) && visibleBufs.includes(item.bufnr)) {\n          item.onProviderChange()\n        }\n      }\n    }, null, this.disposables)\n    events.on('BufWinEnter', async (bufnr: number, winid: number) => {\n      let item = this.highlighters.getItem(bufnr)\n      if (item) await item.onShown(winid)\n    }, null, this.disposables)\n    events.on('WinScrolled', async (winid: number, bufnr: number) => {\n      let item = this.highlighters.getItem(bufnr)\n      if (item) await item.onWinScroll(winid)\n    }, null, this.disposables)\n    events.on('CursorHold', async (bufnr: number, cursor: [number, number], winid: number) => {\n      let item = this.highlighters.getItem(bufnr)\n      if (item && winid) await item.onCursorHold(winid, cursor[0])\n    }, null, this.disposables)\n  }\n\n  public setStaticConfiguration(): void {\n    const filetypes = workspace.initialConfiguration.get<string[] | null>('semanticTokens.filetypes', null)\n    this.staticConfig = Object.assign(this.staticConfig ?? {}, { filetypes })\n  }\n\n  public async inspectSemanticToken(): Promise<void> {\n    let item = await this.getCurrentItem()\n    if (!item || !item.enabled) {\n      if (!item) {\n        let doc = await workspace.document\n        void window.showErrorMessage(`Document not attached, ${doc.notAttachReason}`)\n      } else {\n        try {\n          item.checkState()\n        } catch (e) {\n          void window.showErrorMessage((e as Error).message)\n        }\n      }\n      this.closeFloat()\n      return\n    }\n    let [_, line, col] = await this.nvim.call('getcurpos', []) as [number, number, number]\n    let highlights = toArray(item.highlights)\n    let highlight = highlights.find(o => {\n      let column = col - 1\n      return o.range[0] === line - 1 && column >= o.range[1] && column < o.range[2]\n    })\n    if (highlight) {\n      let modifiers = toArray(highlight.tokenModifiers)\n      let highlights = []\n      if (highlight.hlGroup) {\n        let s = 'Highlight group: '.length\n        highlights.push({\n          lnum: 2,\n          colStart: s,\n          colEnd: s + highlight.hlGroup.length,\n          hlGroup: highlight.hlGroup\n        })\n      }\n      let docs: Documentation[] = [{\n        filetype: 'txt',\n        content: `Type: ${highlight.tokenType}\\nModifiers: ${modifiers.join(', ')}\\nHighlight group: ${toText(highlight.hlGroup)}`,\n        highlights\n      }]\n      if (!floatFactory) {\n        floatFactory = window.createFloatFactory({\n          title: 'Semantic token info',\n          highlight: 'Normal',\n          borderhighlight: 'MoreMsg',\n          border: [1, 1, 1, 1]\n        })\n      }\n      await floatFactory.show(docs, { winblend: 0 })\n    } else {\n      this.closeFloat()\n    }\n  }\n\n  public closeFloat(): void {\n    floatFactory?.close()\n  }\n\n  public async getCurrentItem(): Promise<SemanticTokensBuffer | undefined> {\n    let buf = await this.nvim.buffer\n    return this.getItem(buf.id)\n  }\n\n  public getItem(bufnr: number): SemanticTokensBuffer | undefined {\n    return this.highlighters.getItem(bufnr)\n  }\n  /**\n   * Force highlight of current buffer\n   */\n  public async highlightCurrent(): Promise<void> {\n    let item = await this.getCurrentItem()\n    if (!item || !item.enabled) throw new Error(`Unable to perform semantic highlights for current buffer.`)\n    await item.forceHighlight()\n  }\n\n  /**\n   * Show semantic highlight info in temporarily buffer\n   */\n  public async showHighlightInfo(): Promise<void> {\n    let bufnr = await this.nvim.call('bufnr', ['%']) as number\n    workspace.getAttachedDocument(bufnr)\n    let { nvim } = this\n    let item = this.highlighters.getItem(bufnr)\n    let hl = new Highlighter()\n    nvim.pauseNotification()\n    nvim.command(`vs +setl\\\\ buftype=nofile __coc_semantic_highlights_${bufnr}__`, true)\n    nvim.command(`setl bufhidden=wipe noswapfile nobuflisted wrap undolevels=-1`, true)\n    nvim.call('bufnr', ['%'], true)\n    let res = await nvim.resumeNotification()\n    hl.addLine('Semantic highlights info', headGroup)\n    hl.addLine('')\n    try {\n      item.checkState()\n      let highlights = item.highlights ?? []\n      hl.addLine('The number of semantic tokens: ')\n      hl.addText(String(highlights.length), 'Number')\n      hl.addLine('')\n      hl.addLine('Semantic highlight groups used by current buffer', headGroup)\n      hl.addLine('')\n      const groups = distinct(highlights.filter(o => o.hlGroup != null).map(({ hlGroup }) => hlGroup))\n      for (const hlGroup of groups) {\n        hl.addTexts([{ text: '-', hlGroup: 'Comment' }, { text: ' ' }, { text: hlGroup, hlGroup }])\n      }\n      hl.addLine('')\n      hl.addLine('Tokens types that current Language Server supported:', headGroup)\n      hl.addLine('')\n      let doc = workspace.getDocument(item.bufnr)\n      let legend = languages.getLegend(doc.textDocument) ?? languages.getLegend(doc.textDocument, true)\n      if (legend.tokenTypes.length) {\n        for (const t of [...new Set(legend.tokenTypes)]) {\n          let text = HLGROUP_PREFIX + 'Type' + toHighlightPart(t)\n          hl.addTexts([{ text: '-', hlGroup: 'Comment' }, { text: ' ' }, { text, hlGroup: text }])\n        }\n        hl.addLine('')\n      } else {\n        hl.addLine('No token types supported', 'Comment')\n        hl.addLine('')\n      }\n      // modifiers are added to one token type, we can't list them directly\n    } catch (e) {\n      hl.addLine(toErrorText(e))\n    }\n    nvim.pauseNotification()\n    hl.render(nvim.createBuffer(res[0][2] as number))\n    nvim.resumeNotification(true, true)\n  }\n\n  public dispose(): void {\n    this.highlighters.dispose()\n    disposeAll(this.disposables)\n  }\n}\n"
  },
  {
    "path": "src/handler/signature.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { MarkupContent, Position, SignatureHelp } from 'vscode-languageserver-types'\nimport { IConfigurationChangeEvent } from '../configuration/types'\nimport events from '../events'\nimport languages, { ProviderName } from '../languages'\nimport Document from '../model/document'\nimport { FloatConfig, FloatFactory } from '../types'\nimport { disposeAll, getConditionValue, wait } from '../util'\nimport { isFalsyOrEmpty } from '../util/array'\nimport { debounce } from '../util/node'\nimport { CancellationTokenSource, Disposable, SignatureHelpTriggerKind } from '../util/protocol'\nimport { byteLength, byteSlice } from '../util/string'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { HandlerDelegate } from './types'\nimport { toDocumentation } from './util'\n\ninterface SignatureConfig {\n  wait: number\n  enableTrigger: boolean\n  target: string\n  preferAbove: boolean\n  hideOnChange: boolean\n  floatConfig: FloatConfig\n}\n\ninterface SignaturePosition {\n  bufnr: number\n  lnum: number\n  col: number\n}\n\ninterface SignaturePart {\n  text: string\n  type: 'Label' | 'MoreMsg' | 'Normal'\n}\n\nconst debounceTime = getConditionValue(100, 10)\n\nexport default class Signature {\n  private timer: NodeJS.Timeout\n  private config: SignatureConfig\n  private signatureFactory: FloatFactory\n  private lastPosition: SignaturePosition | undefined\n  private disposables: Disposable[] = []\n  private tokenSource: CancellationTokenSource | undefined\n  constructor(private nvim: Neovim, private handler: HandlerDelegate) {\n    this.loadConfiguration()\n    this.signatureFactory = window.createFloatFactory(Object.assign({\n      preferTop: this.config.preferAbove,\n      autoHide: false,\n      modes: ['i', 'ic', 's'],\n    }, this.config.floatConfig))\n    this.disposables.push(this.signatureFactory)\n    workspace.onDidChangeConfiguration(this.loadConfiguration, this, this.disposables)\n    events.on('CursorMovedI', debounce(this.checkCursor.bind(this), debounceTime), null, this.disposables)\n    events.on('BufEnter', () => {\n      this.tokenSource?.cancel()\n    }, null, this.disposables)\n    events.on('TextChangedI', () => {\n      if (this.config.hideOnChange) {\n        this.signatureFactory.close()\n      }\n    }, null, this.disposables)\n    events.on('TextInsert', async (bufnr, info, character) => {\n      if (!this.shouldAutoTrigger(bufnr, character)) return\n      let doc = workspace.getDocument(bufnr)\n      await this._triggerSignatureHelp(doc, { line: info.lnum - 1, character: info.pre.length }, false)\n    }, null, this.disposables)\n    events.on('PlaceholderJump', async (bufnr, info) => {\n      if (workspace.env.jumpAutocmd || info.charbefore === '') return\n      // need wait for CursorMoved events on placeholder select\n      await wait(50)\n      if (!this.shouldAutoTrigger(bufnr, info.charbefore)) return\n      let doc = workspace.getDocument(bufnr)\n      await this._triggerSignatureHelp(doc, info.range.start, false)\n    }, null, this.disposables)\n    window.onDidChangeActiveTextEditor(() => {\n      this.loadConfiguration()\n    }, null, this.disposables)\n  }\n\n  public shouldAutoTrigger(bufnr: number, character: string): boolean {\n    if (!this.config.enableTrigger) return false\n    let doc = workspace.getDocument(bufnr)\n    if (!doc || !doc.attached || !languages.shouldTriggerSignatureHelp(doc.textDocument, character)) return false\n    return true\n  }\n\n  private checkCursor(bufnr: number, cursor: [number, number]): void {\n    let pos = this.lastPosition\n    let floatFactory = this.signatureFactory\n    if (!pos || bufnr !== pos.bufnr || floatFactory.window == null) return\n    let doc = workspace.getDocument(bufnr)\n    if (!doc || cursor[0] != pos.lnum || cursor[1] < pos.col) {\n      floatFactory.close()\n      return\n    }\n    let line = doc.getline(pos.lnum - 1)\n    let text = byteSlice(line, pos.col - 1, cursor[1] - 1)\n    if (text.endsWith(')')) return floatFactory.close()\n  }\n\n  public loadConfiguration(e?: IConfigurationChangeEvent): void {\n    if (!e || e.affectsConfiguration('signature')) {\n      let doc = window.activeTextEditor?.document\n      let config = workspace.getConfiguration('signature', doc)\n      this.config = {\n        target: config.get<string>('target', 'float'),\n        floatConfig: config.get('floatConfig', {}),\n        enableTrigger: config.get<boolean>('enable', true),\n        wait: Math.max(config.get<number>('triggerSignatureWait', 500), 200),\n        preferAbove: config.get<boolean>('preferShownAbove', true),\n        hideOnChange: config.get<boolean>('hideOnTextChange', false),\n      }\n    }\n  }\n\n  public async triggerSignatureHelp(): Promise<boolean> {\n    let { doc, position } = await this.handler.getCurrentState()\n    if (!languages.hasProvider(ProviderName.Signature, doc.textDocument)) return false\n    return await this._triggerSignatureHelp(doc, position, true, 0)\n  }\n\n  private async _triggerSignatureHelp(doc: Document, position: Position, invoke: boolean, offset = 0): Promise<boolean> {\n    this.tokenSource?.cancel()\n    let tokenSource = this.tokenSource = new CancellationTokenSource()\n    let token = tokenSource.token\n    token.onCancellationRequested(() => {\n      tokenSource.dispose()\n      this.tokenSource = undefined\n    })\n    let { target } = this.config\n    let timer = this.timer = setTimeout(() => {\n      tokenSource.cancel()\n    }, this.config.wait)\n    await doc.patchChange()\n    let signatureHelp = await languages.getSignatureHelp(doc.textDocument, position, token, {\n      isRetrigger: this.signatureFactory.checkRetrigger(doc.bufnr),\n      triggerKind: invoke ? SignatureHelpTriggerKind.Invoked : SignatureHelpTriggerKind.TriggerCharacter\n    })\n    clearTimeout(timer)\n    if (token.isCancellationRequested) return false\n    if (!signatureHelp || signatureHelp.signatures.length == 0) {\n      this.signatureFactory.close()\n      return false\n    }\n    let { activeSignature, signatures } = signatureHelp\n    if (activeSignature) {\n      // make active first\n      let [active] = signatures.splice(activeSignature, 1)\n      if (active) signatures.unshift(active)\n    }\n    if (target == 'echo') {\n      this.echoSignature(signatureHelp)\n    } else {\n      await this.showSignatureHelp(doc, position, signatureHelp, offset)\n    }\n    return true\n  }\n\n  private async showSignatureHelp(doc: Document, position: Position, signatureHelp: SignatureHelp, offset: number): Promise<void> {\n    let { signatures, activeParameter } = signatureHelp\n    activeParameter = typeof activeParameter === 'number' ? activeParameter : undefined\n    let paramDoc: string | MarkupContent = null\n    let startOffset = offset\n    let docs = signatures.reduce((p, c, idx) => {\n      let activeIndexes: [number, number] = null\n      let activeIndex = c.activeParameter ?? activeParameter\n      if (activeIndex === undefined && !isFalsyOrEmpty(c.parameters)) {\n        activeIndex = 0\n      }\n      let nameIndex = c.label.indexOf('(')\n      if (idx == 0 && typeof activeIndex === 'number') {\n        let active = c.parameters?.[activeIndex]\n        if (active) {\n          let after = c.label.slice(nameIndex == -1 ? 0 : nameIndex)\n          paramDoc = active.documentation\n          if (typeof active.label === 'string') {\n            let str = after.slice(0)\n            let ms = str.match(new RegExp('\\\\b' + active.label.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&') + '\\\\b'))\n            let index = ms ? ms.index : str.indexOf(active.label)\n            if (index != -1) {\n              activeIndexes = [\n                index + nameIndex,\n                index + active.label.length + nameIndex\n              ]\n            }\n          } else {\n            activeIndexes = active.label\n          }\n        }\n      }\n      if (activeIndexes == null) {\n        activeIndexes = [nameIndex + 1, nameIndex + 1]\n      }\n      if (offset == startOffset) {\n        offset = offset + activeIndexes[0] + 1\n      }\n      p.push({\n        content: c.label,\n        filetype: doc.filetype,\n        active: activeIndexes\n      })\n      if (paramDoc) {\n        p.push(toDocumentation(paramDoc))\n      }\n      if (idx == 0 && c.documentation) {\n        p.push(toDocumentation(c.documentation))\n      }\n      return p\n    }, [])\n    let content = doc.getline(position.line, false).slice(0, position.character)\n    this.lastPosition = { bufnr: doc.bufnr, lnum: position.line + 1, col: byteLength(content) + 1 }\n    await this.signatureFactory.show(docs, { offsetX: offset })\n  }\n\n  public echoSignature(signatureHelp: SignatureHelp): void {\n    let { signatures, activeParameter } = signatureHelp\n    let columns = workspace.env.columns\n    signatures = signatures.slice(0, workspace.env.cmdheight)\n    let signatureList: SignaturePart[][] = []\n    for (let signature of signatures) {\n      let parts: SignaturePart[] = []\n      let { label } = signature\n      label = label.replace(/\\n/g, ' ')\n      if (label.length >= columns - 16) {\n        label = label.slice(0, columns - 16) + '...'\n      }\n      let nameIndex = label.indexOf('(')\n      if (nameIndex == -1) {\n        parts = [{ text: label, type: 'Normal' }]\n      } else {\n        parts.push({\n          text: label.slice(0, nameIndex),\n          type: 'Label'\n        })\n        let after = label.slice(nameIndex)\n        if (signatureList.length == 0 && activeParameter != null) {\n          let active = signature.parameters?.[activeParameter]\n          if (active) {\n            let start: number\n            let end: number\n            if (typeof active.label === 'string') {\n              let str = after.slice(0)\n              let ms = str.match(new RegExp('\\\\b' + active.label.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&') + '\\\\b'))\n              let idx = ms ? ms.index : str.indexOf(active.label)\n              if (idx == -1) {\n                parts.push({ text: after, type: 'Normal' })\n              } else {\n                start = idx\n                end = idx + active.label.length\n              }\n            } else {\n              [start, end] = active.label\n              start = start - nameIndex\n              end = end - nameIndex\n            }\n            if (start != null && end != null) {\n              parts.push({ text: after.slice(0, start), type: 'Normal' })\n              parts.push({ text: after.slice(start, end), type: 'MoreMsg' })\n              parts.push({ text: after.slice(end), type: 'Normal' })\n            }\n          }\n        } else {\n          parts.push({\n            text: after,\n            type: 'Normal'\n          })\n        }\n      }\n      signatureList.push(parts)\n    }\n    this.nvim.callTimer('coc#ui#echo_signatures', [signatureList], true)\n  }\n\n  public dispose(): void {\n    disposeAll(this.disposables)\n    if (this.timer) {\n      clearTimeout(this.timer)\n    }\n  }\n}\n"
  },
  {
    "path": "src/handler/symbols/buffer.ts",
    "content": "'use strict'\nimport { DocumentSymbol } from 'vscode-languageserver-types'\nimport languages from '../../languages'\nimport { createLogger } from '../../logger'\nimport { SyncItem } from '../../model/bufferSync'\nimport Document from '../../model/document'\nimport { DidChangeTextDocumentParams } from '../../types'\nimport { disposeAll, getConditionValue } from '../../util'\nimport { onUnexpectedError } from '../../util/errors'\nimport { debounce } from '../../util/node'\nimport { CancellationTokenSource, Disposable, Emitter, Event } from '../../util/protocol'\nconst logger = createLogger('symbols-buffer')\n\nconst DEBEBOUNCE_INTERVAL = getConditionValue(500, 10)\n\nexport default class SymbolsBuffer implements SyncItem {\n  private disposables: Disposable[] = []\n  public fetchSymbols: (() => void) & { clear(): void }\n  private version: number\n  public symbols: DocumentSymbol[]\n  private tokenSource: CancellationTokenSource\n  private readonly _onDidUpdate = new Emitter<DocumentSymbol[]>()\n  public readonly onDidUpdate: Event<DocumentSymbol[]> = this._onDidUpdate.event\n  constructor(public readonly doc: Document, private autoUpdateBufnrs: Set<number>) {\n    this.fetchSymbols = debounce(() => {\n      this._fetchSymbols().catch(onUnexpectedError)\n    }, DEBEBOUNCE_INTERVAL)\n  }\n\n  /**\n   * Enable autoUpdate when invoked.\n   */\n  public async getSymbols(): Promise<DocumentSymbol[]> {\n    let { doc } = this\n    await doc.patchChange()\n    this.autoUpdateBufnrs.add(doc.bufnr)\n    // refresh for empty symbols since some languages server could be buggy first time.\n    if (doc.version == this.version && this.symbols?.length) return this.symbols\n    this.cancel()\n    await this._fetchSymbols()\n    return this.symbols\n  }\n\n  public onChange(e: DidChangeTextDocumentParams): void {\n    if (e.contentChanges.length === 0) return\n    this.cancel()\n    if (this.autoUpdateBufnrs.has(this.doc.bufnr)) {\n      this.fetchSymbols()\n    }\n  }\n\n  private async _fetchSymbols(): Promise<void> {\n    let { textDocument } = this.doc\n    let { version } = textDocument\n    let tokenSource = this.tokenSource = new CancellationTokenSource()\n    let { token } = tokenSource\n    let symbols = await languages.getDocumentSymbol(textDocument, token)\n    this.tokenSource = undefined\n    if (symbols == null || token.isCancellationRequested) return\n    this.version = version\n    this.symbols = symbols\n    this._onDidUpdate.fire(symbols)\n  }\n\n  public cancel(): void {\n    this.fetchSymbols.clear()\n    if (this.tokenSource) {\n      this.tokenSource.cancel()\n      this.tokenSource.dispose()\n      this.tokenSource = null\n    }\n  }\n\n  public dispose(): void {\n    this.cancel()\n    this.symbols = undefined\n    this._onDidUpdate.dispose()\n    disposeAll(this.disposables)\n  }\n}\n"
  },
  {
    "path": "src/handler/symbols/index.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { Position, Range, WorkspaceSymbol } from 'vscode-languageserver-types'\nimport events from '../../events'\nimport languages, { ProviderName } from '../../languages'\nimport BufferSync from '../../model/bufferSync'\nimport { disposeAll, getConditionValue } from '../../util/index'\nimport { debounce } from '../../util/node'\nimport { equals } from '../../util/object'\nimport { positionInRange, rangeInRange } from '../../util/position'\nimport { CancellationTokenSource, Disposable } from '../../util/protocol'\nimport { characterIndex } from '../../util/string'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport { HandlerDelegate } from '../types'\nimport SymbolsBuffer from './buffer'\nimport Outline from './outline'\nimport { convertSymbols, SymbolInfo } from './util'\n\nexport default class Symbols {\n  private buffers: BufferSync<SymbolsBuffer>\n  private disposables: Disposable[] = []\n  private outline: Outline\n  private autoUpdateBufnrs: Set<number> = new Set()\n\n  constructor(\n    private nvim: Neovim,\n    private handler: HandlerDelegate\n  ) {\n    this.buffers = workspace.registerBufferSync(doc => {\n      let { bufnr } = doc\n      let buf = new SymbolsBuffer(doc, this.autoUpdateBufnrs)\n      buf.onDidUpdate(symbols => {\n        if (!this.outline) return\n        this.outline.onSymbolsUpdate(bufnr, symbols)\n      })\n      return buf\n    })\n    this.outline = new Outline(nvim, this.buffers, handler)\n    const debounceTime = workspace.initialConfiguration.get<number>('coc.preferences.currentFunctionSymbolDebounceTime', 300)\n    let prev = ''\n    let debounced = debounce(async (bufnr: number, cursor: [number, number]) => {\n      if (!this.buffers.getItem(bufnr) || !this.autoUpdate(bufnr)) return\n      let doc = workspace.getDocument(bufnr)\n      let character = characterIndex(doc.getline(cursor[0] - 1), cursor[1] - 1)\n      let pos = Position.create(cursor[0] - 1, character)\n      let func = await this.getFunctionSymbol(bufnr, pos)\n      let buffer = nvim.createBuffer(bufnr)\n      if (func != prev) {\n        prev = func\n        buffer.setVar('coc_current_function', func, true)\n        this.nvim.callTimer('coc#util#do_autocmd', ['CocStatusChange'], true)\n      }\n    }, getConditionValue(debounceTime, 0))\n    events.on('CursorMoved', debounced, this, this.disposables)\n    this.disposables.push(Disposable.create(() => {\n      debounced.clear()\n    }))\n    events.on('InsertEnter', (bufnr: number) => {\n      debounced.clear()\n      let buf = this.buffers.getItem(bufnr)\n      if (buf) buf.cancel()\n    }, null, this.disposables)\n  }\n\n  public autoUpdate(bufnr: number): boolean {\n    let doc = workspace.getDocument(bufnr)\n    let config = workspace.getConfiguration('coc.preferences', doc)\n    return config.get<boolean>('currentFunctionSymbolAutoUpdate', false)\n  }\n\n  public get labels(): { [key: string]: string } {\n    return workspace.getConfiguration('suggest').get<any>('completionItemKindLabels', {})\n  }\n\n  public async getWorkspaceSymbols(input: string): Promise<WorkspaceSymbol[]> {\n    this.handler.checkProvider(ProviderName.WorkspaceSymbols, null)\n    let tokenSource = new CancellationTokenSource()\n    return await languages.getWorkspaceSymbols(input, tokenSource.token)\n  }\n\n  public async resolveWorkspaceSymbol(symbolInfo: WorkspaceSymbol): Promise<WorkspaceSymbol> {\n    if (symbolInfo.location?.uri) return symbolInfo\n    let tokenSource = new CancellationTokenSource()\n    return await languages.resolveWorkspaceSymbol(symbolInfo, tokenSource.token)\n  }\n\n  public async getDocumentSymbols(bufnr?: number): Promise<SymbolInfo[] | undefined> {\n    if (!bufnr) {\n      bufnr = await this.nvim.call('bufnr', ['%']) as number\n      let doc = workspace.getDocument(bufnr)\n      if (!doc || !doc.attached) return undefined\n    }\n    let buf = this.buffers.getItem(bufnr)\n    if (!buf) return\n    let res = await buf.getSymbols()\n    return res ? convertSymbols(res) : undefined\n  }\n\n  public async getFunctionSymbol(bufnr: number, position: Position): Promise<string> {\n    let symbols = await this.getDocumentSymbols(bufnr)\n    if (!symbols || symbols.length === 0) return ''\n    symbols = symbols.filter(s => [\n      'Class',\n      'Method',\n      'Function',\n      'Struct',\n    ].includes(s.kind))\n    let functionName = ''\n    let labels = this.labels\n    for (let sym of symbols.reverse()) {\n      if (sym.range\n        && positionInRange(position, sym.range) == 0\n        && !sym.text.endsWith(') callback')) {\n        functionName = sym.text\n        let label = labels[sym.kind.toLowerCase()]\n        if (label) functionName = `${label} ${functionName}`\n        break\n      }\n    }\n    return functionName\n  }\n\n  public async getCurrentFunctionSymbol(): Promise<string> {\n    let bufnr = await this.nvim.call('bufnr', ['%']) as number\n    let doc = workspace.getDocument(bufnr)\n    if (!doc || !doc.attached || !languages.hasProvider(ProviderName.DocumentSymbol, doc.textDocument)) return\n    let position = await window.getCursorPosition()\n    return await this.getFunctionSymbol(bufnr, position)\n  }\n\n  /*\n   * supportedSymbols must be string values of symbolKind\n   */\n  public async selectSymbolRange(inner: boolean, visualmode: string, supportedSymbols: string[]): Promise<void> {\n    let { doc } = await this.handler.getCurrentState()\n    this.handler.checkProvider(ProviderName.DocumentSymbol, doc.textDocument)\n    let range: Range\n    if (visualmode) {\n      range = await window.getSelectedRange(visualmode)\n    } else {\n      let pos = await window.getCursorPosition()\n      range = Range.create(pos, pos)\n    }\n    let symbols = await this.getDocumentSymbols(doc.bufnr)\n    if (!symbols || symbols.length === 0) {\n      void window.showWarningMessage('No symbols found')\n      return\n    }\n    symbols = symbols.filter(s => supportedSymbols.includes(s.kind))\n    let selectRange: Range\n    for (let sym of symbols.reverse()) {\n      if (sym.range && !equals(sym.range, range) && rangeInRange(range, sym.range)) {\n        selectRange = sym.range\n        break\n      }\n    }\n    if (inner && selectRange) {\n      let { start, end } = selectRange\n      let line = doc.getline(start.line + 1)\n      // https://github.com/neoclide/coc.nvim/issues/1847\n      // https://github.com/neoclide/coc.nvim/pull/4488#issuecomment-1409717682\n      // don't decrease end.line for python\n      let endDelta = doc.filetype === 'python' ? 0 : 1\n      let endLine = doc.getline(end.line - endDelta)\n      selectRange = Range.create(start.line + 1, line.match(/^\\s*/)[0].length, end.line - endDelta, endLine.length)\n    }\n    if (selectRange) {\n      await window.selectRange(selectRange)\n    } else if (['v', 'V', '\\x16'].includes(visualmode)) {\n      await this.nvim.command('normal! gv')\n    }\n  }\n\n  public async showOutline(keep?: number): Promise<void> {\n    await this.outline.show(keep)\n  }\n\n  public async hideOutline(): Promise<void> {\n    await this.outline.hide()\n  }\n\n  public hasOutline(bufnr: number): boolean {\n    return this.outline.has(bufnr)\n  }\n\n  public dispose(): void {\n    this.outline.dispose()\n    this.buffers.dispose()\n    disposeAll(this.disposables)\n  }\n}\n"
  },
  {
    "path": "src/handler/symbols/outline.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { CodeActionKind, DocumentSymbol, Position, Range, SymbolKind, SymbolTag } from 'vscode-languageserver-types'\nimport type { IConfigurationChangeEvent } from '../../configuration/types'\nimport events from '../../events'\nimport languages, { ProviderName } from '../../languages'\nimport BufferSync from '../../model/bufferSync'\nimport BasicDataProvider, { TreeNode } from '../../tree/BasicDataProvider'\nimport BasicTreeView from '../../tree/TreeView'\nimport { disposeAll, getConditionValue } from '../../util'\nimport { comparePosition, positionInRange } from '../../util/position'\nimport type { Disposable } from '../../util/protocol'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport { HandlerDelegate } from '../types'\nimport SymbolsBuffer from './buffer'\n\n// Support expand level.\ninterface OutlineNode extends TreeNode {\n  kind: SymbolKind\n  range: Range\n  selectRange: Range\n}\n\ninterface OutlineConfig {\n  splitCommand: string\n  switchSortKey: string\n  togglePreviewKey: string\n  followCursor: boolean\n  keepWindow: boolean\n  expandLevel: number\n  checkBufferSwitch: boolean\n  showLineNumber: boolean\n  autoWidth: boolean\n  detailAsDescription: boolean\n  codeActionKinds: CodeActionKind[]\n  sortBy: 'position' | 'name' | 'category'\n  autoHide: boolean\n  autoPreview: boolean\n  previewMaxWidth: number\n  previewBorder: boolean\n  previewBorderRounded: boolean\n  previewHighlightGroup: string\n  previewBorderHighlightGroup: string\n  previewWinblend: number\n}\n\nconst hoverTimeout = getConditionValue(300, 10)\n\n/**\n * Manage TreeViews and Providers of outline.\n */\nexport default class SymbolsOutline {\n  private treeViewList: BasicTreeView<OutlineNode>[] = []\n  private providersMap: Map<number, BasicDataProvider<OutlineNode>> = new Map()\n  private sortByMap: Map<number, string> = new Map()\n  private config: OutlineConfig\n  private disposables: Disposable[] = []\n  constructor(\n    private nvim: Neovim,\n    private buffers: BufferSync<SymbolsBuffer>,\n    private handler: HandlerDelegate\n  ) {\n    this.loadConfiguration()\n    workspace.onDidChangeConfiguration(this.loadConfiguration, this, this.disposables)\n    workspace.onDidCloseTextDocument(async e => {\n      let { bufnr } = e\n      let provider = this.providersMap.get(bufnr)\n      if (!provider) return\n      let loaded = await nvim.call('bufloaded', [bufnr]) as number\n      // reload detected\n      if (loaded) return\n      this.providersMap.delete(bufnr)\n      provider.dispose()\n    }, null, this.disposables)\n    window.onDidChangeActiveTextEditor(async editor => {\n      if (!this.config.checkBufferSwitch) return\n      let view = this.treeViewList.find(v => v.visible && v.targetTabId == editor.tabpageid)\n      if (view) {\n        await this.showOutline(editor.document.bufnr, editor.tabpageid)\n        await nvim.command(`noa call win_gotoid(${editor.winid})`)\n      }\n    }, null, this.disposables)\n    events.on('CursorHold', async (bufnr: number, cursor: [number, number]) => {\n      if (!this.config.followCursor) return\n      let provider = this.providersMap.get(bufnr)\n      if (!provider) return\n      let tabpage = await nvim.tabpage\n      let view = this.treeViewList.find(o => o.visible && o.targetBufnr == bufnr && o.targetTabId == tabpage.id)\n      if (!view) return\n      await this.revealPosition(bufnr, view, Position.create(cursor[0] - 1, cursor[1] - 1))\n    }, null, this.disposables)\n  }\n\n  private async revealPosition(bufnr: number, treeView: BasicTreeView<OutlineNode>, position: Position): Promise<void> {\n    let provider = this.providersMap.get(bufnr)\n    let nodes = await Promise.resolve(provider.getChildren())\n    let curr = getNodeByPosition(position, nodes)\n    if (curr) await treeView.reveal(curr)\n  }\n\n  private loadConfiguration(e?: IConfigurationChangeEvent): void {\n    if (!e || e.affectsConfiguration('outline')) {\n      let c = workspace.getConfiguration('outline', null)\n      this.config = {\n        splitCommand: c.get<string>('splitCommand'),\n        switchSortKey: c.get<string>('switchSortKey'),\n        togglePreviewKey: c.get<string>('togglePreviewKey'),\n        followCursor: c.get<boolean>('followCursor'),\n        keepWindow: c.get<boolean>('keepWindow'),\n        expandLevel: c.get<number>('expandLevel'),\n        autoWidth: c.get<boolean>('autoWidth'),\n        checkBufferSwitch: c.get<boolean>('checkBufferSwitch'),\n        detailAsDescription: c.get<boolean>('detailAsDescription'),\n        sortBy: c.get<'position' | 'name' | 'category'>('sortBy'),\n        showLineNumber: c.get<boolean>('showLineNumber'),\n        codeActionKinds: c.get<string[]>('codeActionKinds'),\n        autoHide: c.get<boolean>('autoHide'),\n        autoPreview: c.get<boolean>('autoPreview'),\n        previewMaxWidth: c.get<number>('previewMaxWidth'),\n        previewBorder: c.get<boolean>('previewBorder'),\n        previewBorderRounded: c.get<boolean>('previewBorderRounded'),\n        previewHighlightGroup: c.get<string>('previewHighlightGroup'),\n        previewBorderHighlightGroup: c.get<string>('previewBorderHighlightGroup'),\n        previewWinblend: c.get<number>('previewWinblend'),\n      }\n    }\n  }\n\n  private convertSymbolToNode(documentSymbol: DocumentSymbol, sortFn: (a: OutlineNode, b: OutlineNode) => number): OutlineNode {\n    let descs = []\n    let { detailAsDescription, showLineNumber } = this.config\n    if (detailAsDescription && documentSymbol.detail) descs.push(documentSymbol.detail)\n    if (showLineNumber) descs.push(`${documentSymbol.selectionRange.start.line + 1}`)\n    return {\n      label: documentSymbol.name,\n      tooltip: detailAsDescription ? undefined : documentSymbol.detail,\n      description: descs.join(' '),\n      icon: this.handler.getIcon(documentSymbol.kind),\n      deprecated: documentSymbol.tags?.includes(SymbolTag.Deprecated),\n      kind: documentSymbol.kind,\n      range: documentSymbol.range,\n      selectRange: documentSymbol.selectionRange,\n      children: Array.isArray(documentSymbol.children) ? documentSymbol.children.map(o => {\n        return this.convertSymbolToNode(o, sortFn)\n      }).sort(sortFn) : undefined\n    }\n  }\n\n  private setMessage(bufnr: number, msg: string | undefined): void {\n    this.treeViewList.forEach(v => {\n      if (v.valid && v.targetBufnr == bufnr) {\n        v.message = msg\n      }\n    })\n  }\n\n  private convertSymbols(bufnr: number, symbols: DocumentSymbol[]): OutlineNode[] {\n    let sortBy = this.getSortBy(bufnr)\n    let sortFn = (a: OutlineNode, b: OutlineNode): number => {\n      if (sortBy === 'name') {\n        return a.label < b.label ? -1 : 1\n      }\n      if (sortBy === 'category') {\n        if (a.kind == b.kind) return a.label < b.label ? -1 : 1\n        return a.kind - b.kind\n      }\n      return comparePosition(a.selectRange.start, b.selectRange.start)\n    }\n    return symbols.map(s => this.convertSymbolToNode(s, sortFn)).sort(sortFn)\n  }\n\n  public onSymbolsUpdate(bufnr: number, symbols: DocumentSymbol[]): void {\n    let provider = this.providersMap.get(bufnr)\n    if (provider) provider.update(this.convertSymbols(bufnr, symbols))\n  }\n\n  private createProvider(bufnr: number): BasicDataProvider<OutlineNode> {\n    let { nvim } = this\n    let provider = new BasicDataProvider({\n      expandLevel: this.config.expandLevel,\n      provideData: async () => {\n        let buf = this.buffers.getItem(bufnr)\n        if (!buf) throw new Error('Document not attached')\n        let doc = workspace.getDocument(bufnr)\n        if (!languages.hasProvider(ProviderName.DocumentSymbol, doc.textDocument)) {\n          throw new Error('Document symbol provider not found')\n        }\n        let meta = languages.getDocumentSymbolMetadata(doc.textDocument)\n        if (meta && meta.label) {\n          let views = this.treeViewList.filter(v => v.valid && v.targetBufnr == bufnr)\n          views.forEach(view => view.description = meta.label)\n        }\n        this.setMessage(bufnr, 'Loading document symbols')\n        let arr = await buf.getSymbols()\n        if (!arr || arr.length == 0) {\n          // server may return empty symbols on buffer initialize, throw error to force reload.\n          throw new Error('Empty symbols returned from language server. ')\n        }\n        this.setMessage(bufnr, undefined)\n        return this.convertSymbols(bufnr, arr)\n      },\n      handleClick: async item => {\n        let winnr = await nvim.call('bufwinnr', [bufnr])\n        if (winnr == -1) return\n        nvim.pauseNotification()\n        nvim.command(`${winnr}wincmd w`, true)\n        let pos = item.selectRange.start\n        nvim.call('coc#cursor#move_to', [pos.line, pos.character], true)\n        nvim.command(`normal! zz`, true)\n        let buf = nvim.createBuffer(bufnr)\n        buf.highlightRanges('outline-hover', 'CocHoverRange', [item.selectRange])\n        nvim.command('redraw', true)\n        await nvim.resumeNotification()\n        setTimeout(() => {\n          buf.clearNamespace('outline-hover')\n          nvim.command('redraw', true)\n        }, hoverTimeout)\n        if (this.config.autoHide) {\n          await this.hide()\n        }\n      },\n      resolveActions: async (_, element) => {\n        let winnr = await nvim.call('bufwinnr', [bufnr])\n        if (winnr == -1) return\n        let doc = workspace.getDocument(bufnr)\n        let actions = await this.handler.getCodeActions(doc, element.range, this.config.codeActionKinds)\n        let arr = actions.map(o => {\n          return {\n            title: o.title,\n            handler: async () => {\n              let position = element.range.start\n              await nvim.command(`${winnr}wincmd w`)\n              await this.nvim.call('coc#cursor#move_to', [position.line, position.character])\n              await this.handler.applyCodeAction(o)\n            }\n          }\n        })\n        return [...arr, {\n          title: 'Visual Select',\n          handler: async item => {\n            await nvim.command(`${winnr}wincmd w`)\n            await window.selectRange(item.range)\n          }\n        }]\n      },\n      onDispose: () => {\n        for (let view of this.treeViewList.slice()) {\n          if (view.provider === provider) {\n            view.dispose()\n          }\n        }\n      }\n    })\n    return provider\n  }\n\n  private getSortBy(bufnr: number): string {\n    return this.sortByMap.get(bufnr) ?? this.config.sortBy\n  }\n\n  private async showOutline(bufnr: number, tabId: number): Promise<BasicTreeView<OutlineNode>> {\n    if (!this.providersMap.has(bufnr)) {\n      this.providersMap.set(bufnr, this.createProvider(bufnr))\n    }\n    let treeView = this.treeViewList.find(v => v.valid && v.targetBufnr == bufnr && v.targetTabId == tabId)\n    if (!treeView) {\n      let { switchSortKey, togglePreviewKey } = this.config\n      let autoPreview = this.config.autoPreview\n      let previewBufnr: number | undefined\n      treeView = new BasicTreeView('OUTLINE', {\n        autoWidth: this.config.autoWidth,\n        bufhidden: 'hide',\n        enableFilter: true,\n        treeDataProvider: this.providersMap.get(bufnr),\n      })\n      let sortBy = this.getSortBy(bufnr)\n      let prev: OutlineNode | undefined\n      treeView.description = `${sortBy[0].toUpperCase()}${sortBy.slice(1)}`\n      this.treeViewList.push(treeView)\n      let disposable = events.on('BufEnter', bufnr => {\n        if (previewBufnr && bufnr !== previewBufnr) {\n          prev = undefined\n          this.closePreview()\n        }\n      })\n      treeView.onDispose(() => {\n        let idx = this.treeViewList.findIndex(v => v === treeView)\n        if (idx !== -1) this.treeViewList.splice(idx, 1)\n        disposable.dispose()\n        this.closePreview()\n      })\n      treeView.onDidChangeVisibility(visible => {\n        if (this.nvim.isVim && visible && previewBufnr) {\n          prev = undefined\n          this.closePreview()\n        }\n      })\n      treeView.onDidCursorMoved(async node => {\n        if (autoPreview && prev !== node) {\n          prev = node\n          previewBufnr = await this.doPreview(bufnr, node)\n        }\n      })\n      treeView.registerLocalKeymap('n', switchSortKey, async () => {\n        let arr = ['category', 'name', 'position']\n        let curr = this.getSortBy(bufnr)\n        let items = arr.map(s => {\n          return { text: s, disabled: s === curr }\n        })\n        let res = await window.showMenuPicker(items, { title: 'Choose sort method' })\n        if (res < 0) return\n        let sortBy = arr[res]\n        this.sortByMap.set(bufnr, sortBy)\n        let views = this.treeViewList.filter(o => o.targetBufnr == bufnr)\n        views.forEach(view => {\n          view.description = `${sortBy[0].toUpperCase()}${sortBy.slice(1)}`\n        })\n        let item = this.buffers.getItem(bufnr)\n        this.onSymbolsUpdate(bufnr, item.symbols)\n      }, true)\n      treeView.registerLocalKeymap('n', togglePreviewKey, async node => {\n        autoPreview = !autoPreview\n        if (!autoPreview) {\n          prev = undefined\n          this.closePreview()\n        } else {\n          previewBufnr = await this.doPreview(bufnr, node)\n        }\n      }, true)\n    }\n    await treeView.show(this.config.splitCommand, false)\n    return treeView\n  }\n\n  public async doPreview(bufnr: number, node: OutlineNode | undefined): Promise<undefined | number> {\n    if (!node) {\n      this.closePreview()\n      return\n    }\n    let config: any = {\n      bufnr,\n      range: node.range,\n      border: this.config.previewBorder,\n      rounded: this.config.previewBorderRounded,\n      maxWidth: this.config.previewMaxWidth,\n      highlight: this.config.previewHighlightGroup,\n      borderhighlight: this.config.previewBorderHighlightGroup,\n      winblend: this.config.previewWinblend\n    }\n    return await this.nvim.call('coc#ui#outline_preview', [config]) as number\n  }\n\n  private closePreview(): void {\n    this.nvim.call('coc#ui#outline_close_preview', [], true)\n  }\n\n  /**\n   * Create outline view.\n   */\n  public async show(keep?: number): Promise<void> {\n    let [bufnr, winid] = await this.nvim.eval('[bufnr(\"%\"),win_getid()]') as [number, number]\n    let tabpage = await this.nvim.tabpage\n    let doc = workspace.getDocument(bufnr)\n    if (doc && !doc.attached) {\n      void window.showErrorMessage(`Unable to show outline, ${doc.notAttachReason}`)\n      return\n    }\n    let position = await window.getCursorPosition()\n    let treeView = await this.showOutline(bufnr, tabpage.id)\n    if (keep == 1 || (keep === undefined && this.config.keepWindow)) {\n      await this.nvim.command(`noa call win_gotoid(${winid})`)\n    } else if (this.config.followCursor) {\n      let disposable = treeView.onDidRefrash(async () => {\n        disposable.dispose()\n        let curr = await this.nvim.eval('bufnr(\"%\")')\n        if (curr == bufnr && treeView.visible) {\n          await this.revealPosition(bufnr, treeView, position)\n        }\n      })\n    }\n  }\n\n  public has(bufnr: number): boolean {\n    return this.providersMap.has(bufnr)\n  }\n\n  /**\n   * Hide outline of current tab.\n   */\n  public async hide(): Promise<void> {\n    let winid = await this.nvim.call('coc#window#find', ['cocViewId', 'OUTLINE']) as number\n    if (winid == -1) return\n    await this.nvim.call('coc#window#close', [winid])\n  }\n\n  public dispose(): void {\n    for (let view of this.treeViewList) {\n      view.dispose()\n    }\n    this.treeViewList = []\n    for (let provider of this.providersMap.values()) {\n      provider.dispose()\n    }\n    this.providersMap.clear()\n    disposeAll(this.disposables)\n  }\n}\n\nfunction getNodeByPosition(position: Position, nodes: ReadonlyArray<OutlineNode>): OutlineNode | undefined {\n  let curr: OutlineNode | undefined\n  let checkNodes = (nodes: ReadonlyArray<OutlineNode>): void => {\n    for (let node of nodes) {\n      if (positionInRange(position, node.range) == 0) {\n        curr = node\n        if (Array.isArray(node.children)) {\n          checkNodes(node.children)\n        }\n        break\n      }\n    }\n  }\n  checkNodes(nodes)\n  return curr\n}\n"
  },
  {
    "path": "src/handler/symbols/util.ts",
    "content": "'use strict'\nimport { DocumentSymbol, Range, SymbolTag } from 'vscode-languageserver-types'\nimport { defaultValue } from '../../util'\nimport { getSymbolKind } from '../../util/convert'\nimport { comparePosition } from '../../util/position'\n\nexport interface SymbolInfo {\n  filepath?: string\n  lnum: number\n  col: number\n  text: string\n  kind: string\n  level?: number\n  detail?: string\n  deprecated?: boolean\n  containerName?: string\n  range: Range\n  selectionRange?: Range\n}\n\nexport function convertSymbols(symbols: DocumentSymbol[]): SymbolInfo[] {\n  let res: SymbolInfo[] = []\n  let arr = symbols.slice()\n  arr.sort(sortDocumentSymbols)\n  arr.forEach(s => addDocumentSymbol(res, s, 0))\n  return res\n}\n\nfunction sortDocumentSymbols(a: DocumentSymbol, b: DocumentSymbol): number {\n  let ra = a.selectionRange\n  let rb = b.selectionRange\n  return comparePosition(ra.start, rb.start)\n}\n\nfunction addDocumentSymbol(res: SymbolInfo[], sym: DocumentSymbol, level: number): void {\n  let { name, selectionRange, detail, kind, children, range, tags } = sym\n  let { start } = defaultValue(selectionRange, range)\n  let obj: SymbolInfo = {\n    col: start.character + 1,\n    lnum: start.line + 1,\n    text: name,\n    level,\n    kind: getSymbolKind(kind),\n    range,\n    selectionRange\n  }\n  if (detail) obj.detail = detail\n  if (tags && tags.includes(SymbolTag.Deprecated)) obj.deprecated = true\n  res.push(obj)\n  if (children && children.length) {\n    children.sort(sortDocumentSymbols)\n    for (let sym of children) {\n      addDocumentSymbol(res, sym, level + 1)\n    }\n  }\n}\n"
  },
  {
    "path": "src/handler/typeHierarchy.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { Position, TypeHierarchyItem } from 'vscode-languageserver-types'\nimport commands from '../commands'\nimport type { IConfigurationChangeEvent } from '../configuration/types'\nimport events from '../events'\nimport languages, { ProviderName } from '../languages'\nimport { TreeDataProvider } from '../tree/index'\nimport LocationsDataProvider from '../tree/LocationsDataProvider'\nimport BasicTreeView from '../tree/TreeView'\nimport { disposeAll } from '../util'\nimport { isFalsyOrEmpty } from '../util/array'\nimport { omit } from '../util/lodash'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { HandlerDelegate } from './types'\n\ninterface TypeHierarchyDataItem extends TypeHierarchyItem {\n  parent?: TypeHierarchyDataItem\n  children?: TypeHierarchyItem[]\n}\n\ninterface TypeHierarchyConfig {\n  splitCommand: string\n  openCommand: string\n  enableTooltip: boolean\n}\n\ntype TypeHierarchyKind = 'supertypes' | 'subtypes'\n\ninterface TypeHierarchyProvider extends TreeDataProvider<TypeHierarchyDataItem> {\n  meta: TypeHierarchyKind\n  dispose: () => void\n}\n\n/**\n * Cleanup properties used by treeview\n */\nfunction toTypeHierarchyItem(item: TypeHierarchyDataItem): TypeHierarchyItem {\n  return omit(item, ['children', 'parent'])\n}\n\nexport default class TypeHierarchyHandler {\n  private config: TypeHierarchyConfig\n  private disposables: Disposable[] = []\n  public static rangesHighlight = 'CocSelectedRange'\n  private highlightWinids: Set<number> = new Set()\n  public static commandId = 'typeHierarchy.reveal'\n  constructor(private nvim: Neovim, private handler: HandlerDelegate) {\n    this.loadConfiguration()\n    workspace.onDidChangeConfiguration(this.loadConfiguration, this, this.disposables)\n    events.on('BufWinEnter', (_, winid) => {\n      if (this.highlightWinids.has(winid)) {\n        this.highlightWinids.delete(winid)\n        let win = nvim.createWindow(winid)\n        win.clearMatchGroup(TypeHierarchyHandler.rangesHighlight)\n      }\n    }, null, this.disposables)\n    this.disposables.push(commands.registerCommand(TypeHierarchyHandler.commandId, async (winid: number, item: TypeHierarchyDataItem, openCommand?: string) => {\n      let { nvim } = this\n      await nvim.call('win_gotoid', [winid])\n      await workspace.jumpTo(item.uri, item.range.start, openCommand)\n      let win = await nvim.window\n      win.clearMatchGroup(TypeHierarchyHandler.rangesHighlight)\n      win.highlightRanges(TypeHierarchyHandler.rangesHighlight, [item.selectionRange], 10, true)\n      this.highlightWinids.add(win.id)\n    }, null, true))\n  }\n\n  private loadConfiguration(e?: IConfigurationChangeEvent): void {\n    if (!e || e.affectsConfiguration('typeHierarchy')) {\n      let c = workspace.getConfiguration('typeHierarchy', null)\n      this.config = {\n        splitCommand: c.get<string>('splitCommand'),\n        openCommand: c.get<string>('openCommand'),\n        enableTooltip: c.get<boolean>('enableTooltip')\n      }\n    }\n  }\n\n  private createProvider(rootItems: TypeHierarchyDataItem[], winid: number, kind: TypeHierarchyKind): TypeHierarchyProvider {\n    let provider = new LocationsDataProvider<TypeHierarchyDataItem, TypeHierarchyKind>(\n      kind,\n      winid,\n      this.config,\n      TypeHierarchyHandler.commandId,\n      rootItems,\n      kind => this.handler.getIcon(kind),\n      (el, meta, token) => this.getChildren(el, meta, token)\n    )\n    provider.addAction(`Show Super Types`, (el: TypeHierarchyDataItem) => {\n      provider.meta = 'supertypes'\n      let rootItems = [omit(el, ['children', 'parent'])]\n      provider.reset(rootItems)\n    })\n    provider.addAction(`Show Sub Types`, (el: TypeHierarchyDataItem) => {\n      provider.meta = 'subtypes'\n      let rootItems = [omit(el, ['children', 'parent'])]\n      provider.reset(rootItems)\n    })\n    return provider\n  }\n\n  private async getChildren(item: TypeHierarchyDataItem, kind: TypeHierarchyKind, token: CancellationToken): Promise<TypeHierarchyDataItem[]> {\n    let res: TypeHierarchyDataItem[] = []\n    let typeHierarchyItem = toTypeHierarchyItem(item)\n    if (kind == 'supertypes') {\n      res = await languages.provideTypeHierarchySupertypes(typeHierarchyItem, token)\n    } else {\n      res = await languages.provideTypeHierarchySubtypes(typeHierarchyItem, token)\n    }\n    return res\n  }\n\n  private async prepare(doc: TextDocument, position: Position): Promise<TypeHierarchyItem[] | undefined | null> {\n    this.handler.checkProvider(ProviderName.TypeHierarchy, doc)\n    return await this.handler.withRequestToken('typeHierarchy', async token => {\n      return await languages.prepareTypeHierarchy(doc, position, token)\n    }, false)\n  }\n\n  public async showTypeHierarchyTree(kind: TypeHierarchyKind): Promise<void> {\n    const { doc, position, winid } = await this.handler.getCurrentState()\n    await doc.synchronize()\n    const rootItems = await this.prepare(doc.textDocument, position)\n    if (isFalsyOrEmpty(rootItems)) {\n      void window.showWarningMessage('Unable to get TypeHierarchyItems at cursor position.')\n      return\n    }\n    let provider = this.createProvider(rootItems, winid, kind)\n    let treeView = new BasicTreeView('TYPES', { treeDataProvider: provider })\n    treeView.title = getTitle(kind)\n    provider.onDidChangeTreeData(e => {\n      if (!e) treeView.title = getTitle(provider.meta)\n    })\n    treeView.onDidChangeVisibility(e => {\n      if (!e.visible) provider.dispose()\n    })\n    this.disposables.push(treeView)\n    await treeView.show(this.config.splitCommand)\n  }\n\n  public dispose(): void {\n    this.highlightWinids.clear()\n    disposeAll(this.disposables)\n  }\n}\n\nfunction getTitle(kind: TypeHierarchyKind): string {\n  return kind === 'supertypes' ? 'Super types' : 'Sub types'\n}\n"
  },
  {
    "path": "src/handler/types.ts",
    "content": "import type { CodeAction, CodeActionKind, Position, Range, SymbolKind } from 'vscode-languageserver-types'\nimport type { ProviderName } from '../languages'\nimport type Document from '../model/document'\nimport type { TextDocumentMatch } from '../types'\nimport type { CancellationToken, Disposable } from '../util/protocol'\n\nexport interface CurrentState {\n  doc: Document\n  winid: number\n  position: Position\n  // :h mode()\n  mode: string\n}\n\nexport interface HandlerDelegate {\n  uri: string | undefined\n  checkProvider: (id: ProviderName, document: TextDocumentMatch) => void\n  withRequestToken: <T> (name: string, fn: (token: CancellationToken) => Thenable<T>, checkEmpty?: boolean) => Promise<T>\n  getCurrentState: () => Promise<CurrentState>\n  addDisposable: (disposable: Disposable) => void\n  getIcon(kind: SymbolKind): { text: string, hlGroup: string }\n  getCodeActions(doc: Document, range?: Range, only?: CodeActionKind[]): Promise<CodeAction[]>\n  applyCodeAction(action: CodeAction): Promise<void>\n}\n"
  },
  {
    "path": "src/handler/util.ts",
    "content": "import { MarkupContent } from 'vscode-languageserver-types'\nimport { Documentation } from '../types'\nimport { isMarkdown } from '../util/is'\n\nexport function toDocumentation(doc: string | MarkupContent): Documentation {\n  return {\n    content: typeof doc === 'string' ? doc : doc.value,\n    filetype: isMarkdown(doc) ? 'markdown' : 'txt'\n  }\n}\n"
  },
  {
    "path": "src/handler/workspace.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { v4 as uuid } from 'uuid'\nimport { writeHeapSnapshot } from 'v8'\nimport { Location } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport commands from '../commands'\nimport type { WorkspaceConfiguration } from '../configuration/types'\nimport { callAsync } from '../core/funcs'\nimport { PatternType } from '../core/workspaceFolder'\nimport extensions from '../extension'\nimport languages, { ProviderName } from '../languages'\nimport { getLoggerFile } from '../logger'\nimport Highlighter from '../model/highlighter'\nimport snippetManager from '../snippets/manager'\nimport { defaultValue } from '../util'\nimport { CONFIG_FILE_NAME, isVim } from '../util/constants'\nimport { directoryNotExists } from '../util/errors'\nimport { isDirectory } from '../util/fs'\nimport * as Is from '../util/is'\nimport { fs, os, path } from '../util/node'\nimport { toText } from '../util/string'\nimport window from '../window'\nimport workspace from '../workspace'\n\ndeclare const REVISION\n\ninterface RootPatterns {\n  buffer: ReadonlyArray<string>\n  server: ReadonlyArray<string>\n  global: ReadonlyArray<string>\n}\n\nexport default class WorkspaceHandler {\n  constructor(\n    private nvim: Neovim\n  ) {\n    // exported by window.\n    Object.defineProperty(window, 'openLocalConfig', {\n      get: () => this.openLocalConfig.bind(this)\n    })\n    extensions.onDidUnloadExtension(name => {\n      workspace.autocmds.removeExtensionAutocmds(name)\n    })\n    commands.register({\n      id: 'workspace.openLocation',\n      execute: async (winid: number, loc: Location, openCommand?: string) => {\n        await nvim.call('win_gotoid', [winid])\n        await workspace.jumpTo(loc.uri, loc.range.start, openCommand)\n      }\n    }, true)\n    commands.register({\n      id: 'workspace.openLocalConfig',\n      execute: async () => {\n        await this.openLocalConfig()\n      }\n    }, false, 'Open config file of current workspace folder')\n    commands.register({\n      id: 'workspace.undo',\n      execute: async () => {\n        await workspace.files.undoWorkspaceEdit()\n      }\n    }, false, 'Undo previous this.workspace edit')\n    commands.register({\n      id: 'workspace.redo',\n      execute: async () => {\n        await workspace.files.redoWorkspaceEdit()\n      }\n    }, false, 'Redo previous this.workspace edit')\n    commands.register({\n      id: 'workspace.inspectEdit',\n      execute: async () => {\n        await workspace.files.inspectEdit()\n      }\n    }, false, 'Inspect previous this.workspace edit in new tab')\n    commands.register({\n      id: 'workspace.renameCurrentFile',\n      execute: async () => {\n        await this.renameCurrent()\n      }\n    }, false, 'change current filename to a new name and reload it.')\n    commands.register({\n      id: 'document.checkBuffer',\n      execute: async () => {\n        await this.bufferCheck()\n      }\n    }, false, 'Check providers for current buffer.')\n    commands.register({\n      id: 'document.echoFiletype',\n      execute: async () => {\n        let bufnr = await nvim.call('bufnr', '%') as number\n        let doc = workspace.getAttachedDocument(bufnr)\n        await window.echoLines([doc.filetype])\n      }\n    }, false, 'echo the mapped filetype of the current buffer')\n    commands.register({\n      id: 'workspace.workspaceFolders',\n      execute: async () => {\n        let folders = workspace.workspaceFolders\n        let lines = folders.map(folder => URI.parse(folder.uri).fsPath)\n        await window.echoLines(lines)\n      }\n    }, false, 'show opened workspaceFolders.')\n    commands.register({\n      id: 'workspace.writeHeapSnapshot',\n      execute: async () => {\n        let filepath = path.join(os.homedir(), `${uuid()}-${process.pid}.heapsnapshot`)\n        writeHeapSnapshot(filepath)\n        void window.showInformationMessage(`Create heapdump at: ${filepath}`)\n        return filepath\n      }\n    }, false, 'Generates a snapshot of the current V8 heap and writes it to a JSON file.')\n    commands.register({\n      id: 'workspace.showOutput',\n      execute: async (name?: string, cmd?: string) => {\n        if (!name) name = await window.showQuickPick(workspace.channelNames, { title: 'Choose output name' }) as string\n        window.showOutputChannel(toText(name), cmd)\n      }\n    }, false, 'open output buffer to show output from languageservers or extensions.')\n    commands.register({\n      id: 'workspace.clearWatchman',\n      execute: async () => {\n        let res = await window.runTerminalCommand('watchman watch-del-all')\n        if (res.success) void window.showInformationMessage('Cleared watchman watching directories.')\n        return res.success\n      }\n    }, false, 'run watch-del-all for watchman to free up memory.')\n  }\n\n  public async openLog(): Promise<void> {\n    let file = getLoggerFile()\n    await workspace.jumpTo(URI.file(file).toString())\n  }\n\n  /**\n   * Open local config file\n   */\n  public async openLocalConfig(): Promise<void> {\n    let fsPath = await this.nvim.call('coc#util#get_fullpath', []) as string\n    let filetype = await this.nvim.eval('&filetype') as string\n    if (!fsPath || !path.isAbsolute(fsPath)) {\n      void window.showWarningMessage(`Current buffer doesn't have valid file path.`)\n      return\n    }\n    let folder = workspace.getWorkspaceFolder(URI.file(fsPath).toString())\n    if (!folder) {\n      let c = workspace.initialConfiguration.get<any>('workspace')\n      let patterns = defaultValue<string[]>(c.rootPatterns, [])\n      let ignored = defaultValue<string[]>(c.ignoredFiletypes, [])\n      let msg: string\n      if (ignored.includes(filetype)) msg = `Filetype '${filetype}' is ignored for workspace folder resolve.`\n      if (!msg) msg = `Can't resolve workspace folder for file '${fsPath}, consider create one of ${patterns.join(', ')} in your project root.'.`\n      void window.showWarningMessage(msg)\n      return\n    }\n    let root = URI.parse(folder.uri).fsPath\n    let dir = path.join(root, '.vim')\n    if (!fs.existsSync(dir)) {\n      let res = await window.showPrompt(`Would you like to create folder'${root}/.vim'?`)\n      if (!res) return\n      fs.mkdirSync(dir)\n    }\n    await workspace.jumpTo(URI.file(path.join(dir, CONFIG_FILE_NAME)))\n  }\n\n  public async renameCurrent(): Promise<void> {\n    let { nvim } = this\n    let oldPath = await nvim.call('coc#util#get_fullpath', []) as string\n    let newPath = await callAsync(nvim, 'input', ['New path: ', oldPath, 'file']) as string\n    newPath = newPath.trim()\n    if (newPath === oldPath || !newPath) return\n    if (oldPath.toLowerCase() != newPath.toLowerCase() && fs.existsSync(newPath)) {\n      let overwrite = await window.showPrompt(`${newPath} exists, overwrite?`)\n      if (!overwrite) return\n    }\n    await workspace.renameFile(oldPath, newPath, { overwrite: true })\n  }\n\n  public addWorkspaceFolder(folder: string): void {\n    if (!Is.string(folder)) throw TypeError(`folder should be string`)\n    folder = workspace.expand(folder)\n    if (!isDirectory(folder)) throw directoryNotExists(folder)\n    workspace.workspaceFolderControl.addWorkspaceFolder(folder, true)\n  }\n\n  public removeWorkspaceFolder(folder: string): void {\n    if (!Is.string(folder)) throw TypeError(`folder should be string`)\n    folder = workspace.expand(folder)\n    if (!isDirectory(folder)) throw directoryNotExists(folder)\n    workspace.workspaceFolderControl.removeWorkspaceFolder(folder)\n  }\n\n  public async bufferCheck(): Promise<void> {\n    let doc = await workspace.document\n    if (!doc.attached) {\n      await window.showDialog({\n        title: 'Buffer check result',\n        content: `Document not attached, ${doc.notAttachReason}`,\n        highlight: 'WarningMsg'\n      })\n      return\n    }\n    let hi = new Highlighter()\n    hi.addLine('Provider state', 'Title')\n    hi.addLine('')\n    for (let name of Object.values(ProviderName)) {\n      if (name === ProviderName.OnTypeEdit) continue\n      let exists = languages.hasProvider(name, doc.textDocument)\n      hi.addTexts([\n        { text: '-', hlGroup: 'Comment' },\n        { text: ' ' },\n        exists ? { text: '✓', hlGroup: 'CocListFgGreen' } : { text: '✗', hlGroup: 'CocListFgRed' },\n        { text: ' ' },\n        { text: name, hlGroup: exists ? 'Normal' : 'CocFadeOut' }\n      ])\n    }\n    await window.showDialog({\n      title: 'Buffer check result',\n      content: hi.content,\n      highlights: hi.highlights\n    })\n  }\n\n  public async doAutocmd(id: number, args: any[]): Promise<void> {\n    let timeout = workspace.getConfiguration('editor', null).get<number>('timeout', 1000)\n    await workspace.autocmds.doAutocmd(id, args, timeout)\n  }\n\n  public async getConfiguration(key: string): Promise<WorkspaceConfiguration> {\n    let document = await workspace.document\n    return workspace.getConfiguration(key, document)\n  }\n\n  public getRootPatterns(bufnr: number): RootPatterns | null {\n    let doc = workspace.getDocument(bufnr)\n    if (!doc) return null\n    return {\n      buffer: workspace.workspaceFolderControl.getRootPatterns(doc, PatternType.Buffer),\n      server: workspace.workspaceFolderControl.getRootPatterns(doc, PatternType.LanguageServer),\n      global: workspace.workspaceFolderControl.getRootPatterns(doc, PatternType.Global)\n    }\n  }\n\n  public async ensureDocument(bufnr?: number): Promise<boolean> {\n    let doc = bufnr ? workspace.getDocument(bufnr) : await workspace.document\n    return doc && doc.attached\n  }\n\n  public async doKeymap(key: string, defaultReturn = ''): Promise<string> {\n    return await workspace.keymaps.doKeymap(key, defaultReturn)\n  }\n\n  public async snippetCheck(checkExpand: boolean, checkJump: boolean): Promise<boolean> {\n    if (checkJump) {\n      let jumpable = snippetManager.jumpable()\n      if (jumpable) return true\n    }\n    if (checkExpand) {\n      let expandable = await Promise.resolve(extensions.manager.call('coc-snippets', 'expandable', []))\n      if (expandable) return true\n    }\n    return false\n  }\n\n  public async showInfo(): Promise<void> {\n    let lines: string[] = []\n    let version = workspace.version + (typeof REVISION === 'string' ? '-' + REVISION : '')\n    lines.push('## versions')\n    lines.push('')\n    let out = await this.nvim.call('execute', ['version']) as string\n    let first = out.trim().split(/\\r?\\n/, 2)[0].replace(/\\(.*\\)/, '').trim()\n    lines.push('vim version: ' + first + `${isVim ? ' ' + workspace.env.version : ''}`)\n    lines.push('node version: ' + process.version)\n    lines.push('coc.nvim version: ' + version)\n    lines.push('coc.nvim directory: ' + path.dirname(__dirname))\n    lines.push('term: ' + defaultValue(process.env.TERM_PROGRAM, process.env.TERM))\n    lines.push('platform: ' + process.platform)\n    lines.push('')\n    lines.push('## Log of coc.nvim')\n    lines.push('')\n    let file = getLoggerFile()\n    const stripAnsi = require('strip-ansi')\n    if (fs.existsSync(file)) {\n      let content = fs.readFileSync(file, { encoding: 'utf8' })\n      lines.push(...content.split(/\\r?\\n/).map(line => stripAnsi(line)))\n    }\n    await this.nvim.command('vnew +setl\\\\ buftype=nofile\\\\ bufhidden=wipe\\\\ nobuflisted')\n    let buf = await this.nvim.buffer\n    await buf.setLines(lines, { start: 0, end: -1, strictIndexing: false })\n  }\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "'use strict'\nimport {\n  AnnotatedTextEdit, ApplyKind, ChangeAnnotation, ChangeAnnotationIdentifier, CodeAction, CodeActionContext, CodeActionKind,\n  CodeActionTriggerKind, CodeDescription, CodeLens, Color,\n  ColorInformation,\n  ColorPresentation, Command, CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionItemTag,\n  CompletionList, CreateFile, DeleteFile, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, DocumentHighlight, DocumentHighlightKind, DocumentLink, DocumentSymbol, DocumentUri, FoldingRange, FoldingRangeKind, FormattingOptions, Hover, InlayHint, InlayHintKind,\n  InlayHintLabelPart, InlineCompletionList, InlineCompletionTriggerKind,\n  InlineValueContext, InlineValueEvaluatableExpression, InlineValueText,\n  InlineValueVariableLookup, InsertReplaceEdit, InsertTextFormat, InsertTextMode, integer,\n  Location,\n  LocationLink, MarkedString, MarkupContent, MarkupKind, OptionalVersionedTextDocumentIdentifier, ParameterInformation, Position,\n  Range, RenameFile,\n  SelectedCompletionInfo,\n  SelectionRange, SemanticTokenModifiers,\n  SemanticTokens, SemanticTokenTypes, SignatureInformation,\n  SnippetTextEdit,\n  StringValue,\n  SymbolInformation, SymbolKind, SymbolTag, TextDocumentEdit, TextDocumentIdentifier, TextDocumentItem, TextEdit, uinteger, VersionedTextDocumentIdentifier, WorkspaceChange, WorkspaceEdit, WorkspaceFolder, WorkspaceSymbol\n} from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport commands from './commands'\nimport sources from './completion/sources'\nimport diagnosticManager from './diagnostic/manager'\nimport events from './events'\nimport extensions from './extension'\nimport languages, { ProviderName } from './languages'\nimport BasicList from './list/basic'\nimport listManager from './list/manager'\nimport download from './model/download'\nimport fetch from './model/fetch'\nimport FloatFactory from './model/floatFactory'\nimport Highlighter from './model/highlighter'\nimport Mru from './model/mru'\nimport RelativePattern from './model/relativePattern'\nimport services, { ServiceStat } from './services'\nimport snippetManager from './snippets/manager'\nimport { SnippetString } from './snippets/string'\nimport { ansiparse } from './util/ansiparse'\nimport { CancellationError } from './util/errors'\nimport { Mutex } from './util/mutex'\nimport {\n  CancellationToken,\n  CancellationTokenSource, CompletionTriggerKind, Disposable, DocumentDiagnosticReportKind, Emitter, ErrorCodes, Event, FileChangeType,\n  InlineCompletionContext, InlineCompletionItem,\n  MonikerKind, NotificationType,\n  NotificationType0, ProgressType, ProtocolNotificationType,\n  ProtocolNotificationType0, ProtocolRequestType,\n  ProtocolRequestType0, RequestType,\n  RequestType0, ResponseError, SignatureHelpTriggerKind, Trace, UniquenessLevel,\n} from './util/protocol'\nimport window from './window'\nimport workspace from './workspace'\n\nimport {\n  ClientState,\n  CloseAction,\n  DiagnosticPullMode,\n  ErrorAction, LanguageClient,\n  MessageTransports, NullLogger, RevealOutputChannelOn, SettingMonitor, State, TransportKind\n} from './language-client'\n\nimport { SourceType } from './completion/types'\nimport { ConfigurationUpdateTarget } from './configuration/types'\nimport { PatternType } from './core/workspaceFolder'\nimport LineBuilder from './model/line'\nimport { SemanticTokensBuilder } from './model/semanticTokensBuilder'\nimport { TreeItem, TreeItemCollapsibleState } from './tree/index'\nimport { concurrent, disposeAll, wait } from './util'\nimport { FileType, watchFile } from './util/fs'\nimport { executable, isRunning, runCommand, terminate } from './util/processes'\n\nmodule.exports = {\n  get nvim() {\n    return workspace.nvim\n  },\n  Uri: URI,\n  LineBuilder,\n  NullLogger,\n  SettingMonitor,\n  LanguageClient,\n  CancellationTokenSource,\n  ProgressType,\n  RequestType,\n  RequestType0,\n  NotificationType,\n  NotificationType0,\n  ProtocolRequestType,\n  ProtocolRequestType0,\n  ProtocolNotificationType,\n  ProtocolNotificationType0,\n  Highlighter,\n  Mru,\n  Emitter,\n  SnippetString,\n  BasicList,\n  Mutex,\n  TreeItem,\n  SemanticTokensBuilder,\n  FloatFactory,\n  RelativePattern,\n  CancellationError,\n  WorkspaceChange,\n  ResponseError,\n  StringValue,\n  SnippetTextEdit,\n  Trace,\n  DocumentUri,\n  WorkspaceFolder,\n  SelectedCompletionInfo,\n  InlineCompletionContext,\n  InlineCompletionItem,\n  InlineCompletionList,\n  InlineCompletionTriggerKind,\n  InlineValueText,\n  InlineValueVariableLookup,\n  InlineValueEvaluatableExpression,\n  InlineValueContext,\n  InlayHintKind,\n  InlayHintLabelPart,\n  InlayHint,\n  DiagnosticRelatedInformation,\n  SemanticTokens,\n  SemanticTokenTypes,\n  SemanticTokenModifiers,\n  AnnotatedTextEdit,\n  ChangeAnnotation,\n  SymbolTag,\n  Command,\n  Color,\n  CodeDescription,\n  ColorInformation,\n  ColorPresentation,\n  TextDocumentEdit,\n  TextDocumentIdentifier,\n  VersionedTextDocumentIdentifier,\n  TextDocumentItem,\n  DocumentHighlight,\n  SelectionRange,\n  DocumentLink,\n  CodeLens,\n  FormattingOptions,\n  CodeAction,\n  CodeActionContext,\n  DocumentSymbol,\n  WorkspaceSymbol,\n  CreateFile,\n  RenameFile,\n  WorkspaceEdit,\n  InsertReplaceEdit,\n  InsertTextMode,\n  CompletionItem,\n  CompletionList,\n  Hover,\n  ParameterInformation,\n  SignatureInformation,\n  SymbolInformation,\n  MarkupContent,\n  ErrorCodes,\n  CompletionItemTag,\n  integer,\n  uinteger,\n  FoldingRangeKind,\n  FoldingRange,\n  ChangeAnnotationIdentifier,\n  DeleteFile,\n  OptionalVersionedTextDocumentIdentifier,\n  CompletionItemLabelDetails,\n  MarkedString,\n  ProviderName,\n  DocumentDiagnosticReportKind,\n  UniquenessLevel,\n  MonikerKind,\n  PatternType,\n  SourceType,\n  ConfigurationTarget: ConfigurationUpdateTarget,\n  ServiceStat,\n  FileType,\n  State,\n  ClientState,\n  CloseAction,\n  ErrorAction,\n  TransportKind,\n  MessageTransports,\n  RevealOutputChannelOn,\n  MarkupKind,\n  DiagnosticTag,\n  DocumentHighlightKind,\n  SymbolKind,\n  SignatureHelpTriggerKind,\n  FileChangeType,\n  CodeActionKind,\n  Diagnostic,\n  DiagnosticSeverity,\n  CompletionItemKind,\n  InsertTextFormat,\n  Location,\n  LocationLink,\n  CancellationToken,\n  Position,\n  Range,\n  TextEdit,\n  Disposable,\n  Event,\n  workspace,\n  window,\n  CodeActionTriggerKind,\n  CompletionTriggerKind,\n  snippetManager,\n  events,\n  services,\n  commands,\n  sources,\n  languages,\n  diagnosticManager,\n  extensions,\n  listManager,\n  TreeItemCollapsibleState,\n  DiagnosticPullMode,\n  ApplyKind,\n  terminate,\n  fetch,\n  download,\n  ansiparse,\n  disposeAll,\n  concurrent,\n  watchFile,\n  wait,\n  runCommand,\n  isRunning,\n  executable,\n}\n"
  },
  {
    "path": "src/language-client/LICENSE.txt",
    "content": "MIT License\r\n\r\nCopyright (c) 2015 - present Microsoft Corporation\r\n\r\nAll rights reserved.\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\nSOFTWARE.\r\n"
  },
  {
    "path": "src/language-client/callHierarchy.ts",
    "content": "'use strict'\nimport type {\n  CallHierarchyClientCapabilities, CallHierarchyIncomingCall, CallHierarchyItem, CallHierarchyOptions, CallHierarchyOutgoingCall, CallHierarchyRegistrationOptions, CancellationToken, ClientCapabilities, Disposable, DocumentSelector, Position, ServerCapabilities\n} from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport languages from '../languages'\nimport { CallHierarchyProvider, ProviderResult } from '../provider'\nimport { CallHierarchyIncomingCallsRequest, CallHierarchyOutgoingCallsRequest, CallHierarchyPrepareRequest } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\n\nexport interface PrepareCallHierarchySignature {\n  (this: void, document: TextDocument, position: Position, token: CancellationToken): ProviderResult<CallHierarchyItem | CallHierarchyItem[]>\n}\n\nexport interface CallHierarchyIncomingCallsSignature {\n  (this: void, item: CallHierarchyItem, token: CancellationToken): ProviderResult<CallHierarchyIncomingCall[]>\n}\n\nexport interface CallHierarchyOutgoingCallsSignature {\n  (this: void, item: CallHierarchyItem, token: CancellationToken): ProviderResult<CallHierarchyOutgoingCall[]>\n}\n\n/**\n * Call hierarchy middleware\n * @since 3.16.0\n */\nexport interface CallHierarchyMiddleware {\n  prepareCallHierarchy?: (this: void, document: TextDocument, positions: Position, token: CancellationToken, next: PrepareCallHierarchySignature) => ProviderResult<CallHierarchyItem | CallHierarchyItem[]>\n  provideCallHierarchyIncomingCalls?: (this: void, item: CallHierarchyItem, token: CancellationToken, next: CallHierarchyIncomingCallsSignature) => ProviderResult<CallHierarchyIncomingCall[]>\n  provideCallHierarchyOutgoingCalls?: (this: void, item: CallHierarchyItem, token: CancellationToken, next: CallHierarchyOutgoingCallsSignature) => ProviderResult<CallHierarchyOutgoingCall[]>\n}\n\nexport class CallHierarchyFeature extends TextDocumentLanguageFeature<boolean | CallHierarchyOptions, CallHierarchyRegistrationOptions, CallHierarchyProvider, CallHierarchyMiddleware> {\n  constructor(client: FeatureClient<CallHierarchyMiddleware>) {\n    super(client, CallHierarchyPrepareRequest.type)\n  }\n\n  public fillClientCapabilities(cap: ClientCapabilities): void {\n    const capabilities: ClientCapabilities & CallHierarchyClientCapabilities = cap as ClientCapabilities & CallHierarchyClientCapabilities\n    const capability = ensure(ensure(capabilities, 'textDocument')!, 'callHierarchy')!\n    capability.dynamicRegistration = true\n  }\n\n  public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {\n    const [id, options] = this.getRegistration(documentSelector, capabilities.callHierarchyProvider)\n    if (!id || !options) {\n      return\n    }\n    this.register({ id, registerOptions: options })\n  }\n\n  protected registerLanguageProvider(options: CallHierarchyRegistrationOptions): [Disposable, CallHierarchyProvider] {\n    const provider: CallHierarchyProvider = {\n      prepareCallHierarchy: (document: TextDocument, position: Position, token: CancellationToken) => {\n        const client = this._client\n        const prepareCallHierarchy: PrepareCallHierarchySignature = (document, position, token) => {\n          const params = client.code2ProtocolConverter.asTextDocumentPositionParams(document, position)\n          return this.sendRequest(CallHierarchyPrepareRequest.type, params, token)\n        }\n\n        const middleware = client.middleware\n        return middleware.prepareCallHierarchy\n          ? middleware.prepareCallHierarchy(document, position, token, prepareCallHierarchy)\n          : prepareCallHierarchy(document, position, token)\n      },\n\n      provideCallHierarchyIncomingCalls: (item: CallHierarchyItem, token: CancellationToken) => {\n        const client = this._client\n        const provideCallHierarchyIncomingCalls: CallHierarchyIncomingCallsSignature = (item, token) => {\n          return this.sendRequest(CallHierarchyIncomingCallsRequest.type, { item }, token)\n        }\n\n        const middleware = client.middleware\n        return middleware.provideCallHierarchyIncomingCalls\n          ? middleware.provideCallHierarchyIncomingCalls(item, token, provideCallHierarchyIncomingCalls)\n          : provideCallHierarchyIncomingCalls(item, token)\n      },\n\n      provideCallHierarchyOutgoingCalls: (item: CallHierarchyItem, token: CancellationToken) => {\n        const client = this._client\n        const provideCallHierarchyOutgoingCalls: CallHierarchyOutgoingCallsSignature = (item, token) => {\n          return this.sendRequest(CallHierarchyOutgoingCallsRequest.type, { item }, token)\n        }\n        const middleware = client.middleware\n        return middleware.provideCallHierarchyOutgoingCalls\n          ? middleware.provideCallHierarchyOutgoingCalls(item, token, provideCallHierarchyOutgoingCalls)\n          : provideCallHierarchyOutgoingCalls(item, token)\n      }\n    }\n\n    this._client.attachExtensionName(provider)\n    return [languages.registerCallHierarchyProvider(options.documentSelector, provider), provider]\n  }\n}\n"
  },
  {
    "path": "src/language-client/client.ts",
    "content": "'use strict'\nimport { ApplyWorkspaceEditParams, ApplyWorkspaceEditResult, CallHierarchyPrepareRequest, CancellationStrategy, CancellationToken, ClientCapabilities, CodeActionRequest, CodeLensRequest, CompletionRequest, ConfigurationRequest, ConnectionStrategy, DeclarationRequest, DefinitionRequest, DidChangeConfigurationNotification, DidChangeConfigurationRegistrationOptions, DidChangeTextDocumentNotification, DidChangeWatchedFilesNotification, DidChangeWatchedFilesRegistrationOptions, DidChangeWorkspaceFoldersNotification, DidCloseTextDocumentNotification, DidCloseTextDocumentParams, DidCreateFilesNotification, DidDeleteFilesNotification, DidOpenTextDocumentNotification, DidRenameFilesNotification, DidSaveTextDocumentNotification, Disposable, DocumentColorRequest, DocumentDiagnosticRequest, DocumentFormattingRequest, DocumentHighlightRequest, DocumentLinkRequest, DocumentOnTypeFormattingRequest, DocumentRangeFormattingRequest, DocumentSelector, DocumentSymbolRequest, ExecuteCommandRegistrationOptions, ExecuteCommandRequest, FileEvent, FileOperationRegistrationOptions, FoldingRangeRequest, GenericNotificationHandler, GenericRequestHandler, HandlerResult, HoverRequest, ImplementationRequest, InitializeParams, InitializeResult, InlineCompletionRequest, InlineValueRequest, LinkedEditingRangeRequest, Message, MessageActionItem, MessageSignature, NotificationHandler, NotificationHandler0, NotificationType, NotificationType0, ProgressToken, ProgressType, ProtocolNotificationType, ProtocolNotificationType0, ProtocolRequestType, ProtocolRequestType0, PublishDiagnosticsParams, ReferencesRequest, RegistrationParams, RenameRequest, RequestHandler, RequestHandler0, RequestParam, RequestType, RequestType0, SelectionRangeRequest, SemanticTokensRegistrationType, ServerCapabilities, ShowDocumentParams, ShowDocumentResult, ShowMessageRequestParams, SignatureHelpRequest, TextDocumentContentRequest, TextDocumentRegistrationOptions, TextDocumentSyncOptions, TextEdit, TraceOptions, Tracer, TypeDefinitionRequest, TypeHierarchyPrepareRequest, UnregistrationParams, WillCreateFilesRequest, WillDeleteFilesRequest, WillRenameFilesRequest, WillSaveTextDocumentNotification, WillSaveTextDocumentWaitUntilRequest, WorkDoneProgressBegin, WorkDoneProgressCreateRequest, WorkDoneProgressEnd, WorkDoneProgressReport, WorkspaceSymbolRequest } from 'vscode-languageserver-protocol'\nimport { TextDocument } from \"vscode-languageserver-textdocument\"\nimport { Diagnostic, DiagnosticTag, MarkupKind } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport { FileCreateEvent, FileDeleteEvent, FileRenameEvent, FileWillCreateEvent, FileWillDeleteEvent, FileWillRenameEvent, TextDocumentWillSaveEvent } from '../core/files'\nimport DiagnosticCollection from '../diagnostic/collection'\nimport languages from '../languages'\nimport { createLogger } from '../logger'\nimport type { MessageItem } from '../model/notification'\nimport { CallHierarchyProvider, CodeActionProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentLinkProvider, DocumentRangeFormattingEditProvider, DocumentSymbolProvider, HoverProvider, ImplementationProvider, InlineCompletionItemProvider, LinkedEditingRangeProvider, OnTypeFormattingEditProvider, ProviderResult, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, TypeHierarchyProvider, WorkspaceSymbolProvider } from '../provider'\nimport { OutputChannel, Thenable } from '../types'\nimport { defaultValue, disposeAll, getConditionValue } from '../util'\nimport { isFalsyOrEmpty, toArray } from '../util/array'\nimport { CancellationError, onUnexpectedError } from '../util/errors'\nimport { parseExtensionName } from '../util/extensionRegistry'\nimport { sameFile } from '../util/fs'\nimport * as Is from '../util/is'\nimport { os } from '../util/node'\nimport { comparePosition } from '../util/position'\nimport {\n  ApplyWorkspaceEditRequest, createProtocolConnection, Emitter, ErrorCodes, Event, ExitNotification, FailureHandlingKind, InitializedNotification, InitializeRequest, InlayHintRequest, LogMessageNotification, LSPErrorCodes, MessageReader, MessageType, MessageWriter, PositionEncodingKind, PublishDiagnosticsNotification, RegistrationRequest, ResourceOperationKind, ResponseError, SemanticTokensDeltaRequest, SemanticTokensRangeRequest, SemanticTokensRequest, ShowDocumentRequest, ShowMessageNotification, ShowMessageRequest, ShutdownRequest, TextDocumentSyncKind, Trace, TraceFormat, UnregistrationRequest, WorkDoneProgress\n} from '../util/protocol'\nimport { toText } from '../util/string'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { CallHierarchyFeature, CallHierarchyMiddleware } from './callHierarchy'\nimport { CodeActionFeature, CodeActionMiddleware } from './codeAction'\nimport { CodeLensFeature, CodeLensMiddleware, CodeLensProviderShape } from './codeLens'\nimport { ColorProviderFeature, ColorProviderMiddleware } from './colorProvider'\nimport { $CompletionOptions, CompletionItemFeature, CompletionMiddleware } from './completion'\nimport { $ConfigurationOptions, ConfigurationMiddleware, DidChangeConfigurationMiddleware, PullConfigurationFeature, SyncConfigurationFeature } from './configuration'\nimport { DeclarationFeature, DeclarationMiddleware } from './declaration'\nimport { DefinitionFeature, DefinitionMiddleware } from './definition'\nimport { $DiagnosticPullOptions, DiagnosticFeature, DiagnosticFeatureShape, DiagnosticProviderMiddleware, DiagnosticProviderShape, DiagnosticPullMode } from './diagnostic'\nimport { DocumentHighlightFeature, DocumentHighlightMiddleware } from './documentHighlight'\nimport { DocumentLinkFeature, DocumentLinkMiddleware } from './documentLink'\nimport { DocumentSymbolFeature, DocumentSymbolMiddleware } from './documentSymbol'\nimport { ExecuteCommandFeature, ExecuteCommandMiddleware } from './executeCommand'\nimport { Connection, DynamicFeature, ensure, FeatureClient, LSPCancellationError, RegistrationData, StaticFeature, TextDocumentProviderFeature, TextDocumentSendFeature } from './features'\nimport { DidCreateFilesFeature, DidDeleteFilesFeature, DidRenameFilesFeature, FileOperationsMiddleware, WillCreateFilesFeature, WillDeleteFilesFeature, WillRenameFilesFeature } from './fileOperations'\nimport { DidChangeWatchedFileSignature, FileSystemWatcherFeature } from './fileSystemWatcher'\nimport { FoldingRangeFeature, FoldingRangeProviderMiddleware, FoldingRangeProviderShape } from './foldingRange'\nimport { $FormattingOptions, DocumentFormattingFeature, DocumentOnTypeFormattingFeature, DocumentRangeFormattingFeature, FormattingMiddleware } from './formatting'\nimport { HoverFeature, HoverMiddleware } from './hover'\nimport { ImplementationFeature, ImplementationMiddleware } from './implementation'\nimport { InlayHintsFeature, InlayHintsMiddleware, InlayHintsProviderShape } from './inlayHint'\nimport { InlineCompletionItemFeature, InlineCompletionMiddleware } from './inlineCompletion'\nimport { InlineValueFeature, InlineValueMiddleware, InlineValueProviderShape } from './inlineValue'\nimport { LinkedEditingFeature, LinkedEditingRangeMiddleware } from './linkedEditingRange'\nimport { ProgressFeature } from './progress'\nimport { ProgressPart } from './progressPart'\nimport { ReferencesFeature, ReferencesMiddleware } from './reference'\nimport { RenameFeature, RenameMiddleware } from './rename'\nimport { SelectionRangeFeature, SelectionRangeProviderMiddleware } from './selectionRange'\nimport { SemanticTokensFeature, SemanticTokensMiddleware, SemanticTokensProviderShape } from './semanticTokens'\nimport { SignatureHelpFeature, SignatureHelpMiddleware } from './signatureHelp'\nimport { TextDocumentContentFeature, TextDocumentContentMiddleware, TextDocumentContentProviderShape } from './textDocumentContent'\nimport { DidChangeTextDocumentFeature, DidChangeTextDocumentFeatureShape, DidCloseTextDocumentFeature, DidCloseTextDocumentFeatureShape, DidOpenTextDocumentFeature, DidOpenTextDocumentFeatureShape, DidSaveTextDocumentFeature, DidSaveTextDocumentFeatureShape, ResolvedTextDocumentSyncCapabilities, TextDocumentSynchronizationMiddleware, WillSaveFeature, WillSaveWaitUntilFeature } from './textSynchronization'\nimport { TypeDefinitionFeature, TypeDefinitionMiddleware } from './typeDefinition'\nimport { TypeHierarchyFeature, TypeHierarchyMiddleware } from './typeHierarchy'\nimport { currentTimeStamp, data2String, fixNotificationType, fixRequestType, getLocale, getTracePrefix, toMethod } from './utils'\nimport { Delayer } from './utils/async'\nimport * as c2p from './utils/codeConverter'\nimport { CloseAction, CloseHandlerResult, DefaultErrorHandler, ErrorAction, ErrorHandler, ErrorHandlerResult, InitializationFailedHandler, toCloseHandlerResult } from './utils/errorHandler'\nimport { ConsoleLogger, NullLogger } from './utils/logger'\nimport * as UUID from './utils/uuid'\nimport { $WorkspaceOptions, WorkspaceFolderMiddleware, WorkspaceFoldersFeature } from './workspaceFolders'\nimport { WorkspaceProviderFeature, WorkspaceSymbolFeature, WorkspaceSymbolMiddleware } from './workspaceSymbol'\n\nconst logger = createLogger('language-client-client')\n\nexport { CloseAction, DiagnosticPullMode, ErrorAction, NullLogger }\n\ninterface ConnectionErrorHandler {\n  (error: Error, message: Message | undefined, count: number | undefined): void\n}\n\ninterface ConnectionCloseHandler {\n  (): void\n}\n\ninterface ConnectionOptions {\n  cancellationStrategy?: CancellationStrategy\n  connectionStrategy?: ConnectionStrategy\n  maxRestartCount?: number\n}\n\nconst redOpen = '\\x1B[31m'\nconst redClose = '\\x1B[39m'\n\nfunction createConnection(input: MessageReader, output: MessageWriter, errorHandler: ConnectionErrorHandler, closeHandler: ConnectionCloseHandler, options?: ConnectionOptions): Connection {\n  let logger = new ConsoleLogger()\n  let connection = createProtocolConnection(input, output, logger, options)\n\n  connection.onError(data => { errorHandler(data[0], data[1], data[2]) })\n  connection.onClose(closeHandler)\n  let result: Connection = {\n    id: '',\n    listen: (): void => connection.listen(),\n\n    hasPendingResponse: connection.hasPendingResponse,\n\n    sendRequest: connection.sendRequest,\n    onRequest: connection.onRequest,\n\n    sendNotification: connection.sendNotification,\n    onNotification: connection.onNotification,\n\n    onProgress: connection.onProgress,\n    sendProgress: connection.sendProgress,\n\n    trace: (value: Trace, tracer: Tracer, traceOptions: TraceOptions): Promise<void> => {\n      return connection.trace(value, tracer, traceOptions)\n    },\n\n    initialize: (params: InitializeParams) => {\n      return connection.sendRequest(InitializeRequest.type, params)\n    },\n    shutdown: () => {\n      return connection.sendRequest(ShutdownRequest.type, undefined)\n    },\n    exit: () => {\n      return connection.sendNotification(ExitNotification.type)\n    },\n    end: () => connection.end(),\n    dispose: () => connection.dispose()\n  }\n  return result\n}\n\nexport enum RevealOutputChannelOn {\n  Debug = 0,\n  Info = 1,\n  Warn = 2,\n  Error = 3,\n  Never = 4\n}\n\nexport interface HandleWorkDoneProgressSignature {\n  (this: void, token: ProgressToken, params: WorkDoneProgressBegin | WorkDoneProgressReport | WorkDoneProgressEnd): void\n}\n\nexport interface HandleDiagnosticsSignature {\n  (this: void, uri: string, diagnostics: Diagnostic[]): void\n}\n\ninterface _WorkspaceMiddleware {\n  didChangeWatchedFile?: (this: void, event: FileEvent, next: DidChangeWatchedFileSignature) => Promise<void>\n  handleApplyEdit?: (this: void, params: ApplyWorkspaceEditParams, next: ApplyWorkspaceEditRequest.HandlerSignature) => HandlerResult<ApplyWorkspaceEditResult, void>\n}\n\nexport type WorkspaceMiddleware = _WorkspaceMiddleware & ConfigurationMiddleware & DidChangeConfigurationMiddleware & WorkspaceFolderMiddleware & FileOperationsMiddleware\n\nexport interface _WindowMiddleware {\n  showDocument?: ShowDocumentRequest.MiddlewareSignature\n}\n\nexport type WindowMiddleware = _WindowMiddleware\n\n/**\n * The Middleware lets extensions intercept the request and notifications send and received\n * from the server\n */\nexport interface _Middleware {\n  handleDiagnostics?: (this: void, uri: string, diagnostics: Diagnostic[], next: HandleDiagnosticsSignature) => void\n  handleWorkDoneProgress?: (this: void, token: ProgressToken, params: WorkDoneProgressBegin | WorkDoneProgressReport | WorkDoneProgressEnd, next: HandleWorkDoneProgressSignature) => void\n  handleRegisterCapability?: (this: void, params: RegistrationParams, next: RegistrationRequest.HandlerSignature) => Promise<void>\n  handleUnregisterCapability?: (this: void, params: UnregistrationParams, next: UnregistrationRequest.HandlerSignature) => Promise<void>\n  workspace?: WorkspaceMiddleware\n  window?: WindowMiddleware\n}\n\n// A general middleware is applied to both requests and notifications\ninterface GeneralMiddleware {\n  sendRequest?<P, R>(\n    this: void,\n    type: string | MessageSignature,\n    param: P | undefined,\n    token: CancellationToken | undefined,\n    next: (type: string | MessageSignature, param?: P, token?: CancellationToken) => Promise<R>,\n  ): Promise<R>\n\n  sendNotification?<R>(\n    this: void,\n    type: string | MessageSignature,\n    next: (type: string | MessageSignature, params?: R) => Promise<void>,\n    params: R\n  ): Promise<void>\n}\n\nexport type Middleware = _Middleware & TextDocumentSynchronizationMiddleware & SignatureHelpMiddleware & ReferencesMiddleware &\n  DefinitionMiddleware & DocumentHighlightMiddleware & DocumentSymbolMiddleware & DocumentLinkMiddleware &\n  CodeActionMiddleware & FormattingMiddleware & RenameMiddleware & CodeLensMiddleware &\n  HoverMiddleware & CompletionMiddleware & ExecuteCommandMiddleware & TypeDefinitionMiddleware &\n  ImplementationMiddleware & ColorProviderMiddleware & DeclarationMiddleware &\n  FoldingRangeProviderMiddleware & CallHierarchyMiddleware & SemanticTokensMiddleware &\n  InlayHintsMiddleware & InlineCompletionMiddleware & InlineValueMiddleware & TypeHierarchyMiddleware &\n  WorkspaceSymbolMiddleware & DiagnosticProviderMiddleware & LinkedEditingRangeMiddleware &\n  SelectionRangeProviderMiddleware & GeneralMiddleware & TextDocumentContentMiddleware\n\nexport type LanguageClientOptions = {\n  rootPatterns?: string[]\n  requireRootPattern?: boolean\n  documentSelector?: DocumentSelector\n  disableMarkdown?: boolean\n  disableDiagnostics?: boolean\n  diagnosticCollectionName?: string\n  disableDynamicRegister?: boolean\n  disabledFeatures?: string[]\n  outputChannelName?: string\n  traceOutputChannel?: OutputChannel\n  outputChannel?: OutputChannel\n  revealOutputChannelOn?: RevealOutputChannelOn\n  /**\n   * The encoding use to read stdout and stderr. Defaults\n   * to 'utf8' if omitted.\n   */\n  stdioEncoding?: string\n  initializationOptions?: any | (() => any)\n  initializationFailedHandler?: InitializationFailedHandler\n  progressOnInitialization?: boolean\n  errorHandler?: ErrorHandler\n  middleware?: Middleware\n  uriConverter?: {\n    code2Protocol: c2p.URIConverter\n  }\n  connectionOptions?: ConnectionOptions\n  markdown?: {\n    isTrusted?: boolean\n    supportHtml?: boolean\n  }\n  textSynchronization?: {\n    /**\n     * Delays sending the open notification until one of the following\n     * conditions becomes `true`:\n     * - document is visible in the editor.\n     * - any of the other notifications or requests is sent to the server, except\n     * a closed notification for the pending document.\n     */\n    delayOpenNotifications?: boolean\n  }\n} & $ConfigurationOptions & $CompletionOptions & $FormattingOptions & $DiagnosticPullOptions & $WorkspaceOptions\n\ntype ResolvedClientOptions = {\n  disabledFeatures: string[]\n  disableMarkdown: boolean\n  disableDynamicRegister: boolean\n  rootPatterns?: string[]\n  requireRootPattern?: boolean\n  documentSelector: DocumentSelector\n  diagnosticCollectionName?: string\n  outputChannelName: string\n  revealOutputChannelOn: RevealOutputChannelOn\n  stdioEncoding: string\n  initializationOptions?: any | (() => any)\n  initializationFailedHandler?: InitializationFailedHandler\n  progressOnInitialization: boolean\n  errorHandler: ErrorHandler\n  middleware: Middleware\n  uriConverter?: {\n    code2Protocol: c2p.URIConverter\n  }\n  connectionOptions?: ConnectionOptions\n  markdown: {\n    isTrusted: boolean\n    supportHtml?: boolean\n  }\n  textSynchronization: {\n    delayOpenNotifications?: boolean\n  }\n} & $ConfigurationOptions & Required<$CompletionOptions> & Required<$FormattingOptions> & Required<$DiagnosticPullOptions> & Required<$WorkspaceOptions>\n\nexport enum State {\n  Stopped = 1,\n  Running = 2,\n  Starting = 3,\n  StartFailed = 4,\n}\n\nexport interface StateChangeEvent {\n  oldState: State\n  newState: State\n}\n\nexport enum ClientState {\n  Initial,\n  Starting,\n  StartFailed,\n  Running,\n  Stopping,\n  Stopped\n}\n\nexport interface MessageTransports {\n  reader: MessageReader\n  writer: MessageWriter\n  detached?: boolean\n}\n\n// eslint-disable-next-line no-redeclare\nexport namespace MessageTransports {\n  export function is(value: any): value is MessageTransports {\n    let candidate: MessageTransports = value\n    return (\n      candidate &&\n      MessageReader.is(value.reader) &&\n      MessageWriter.is(value.writer)\n    )\n  }\n}\n\nexport enum ShutdownMode {\n  Restart = 'restart',\n  Stop = 'stop'\n}\n\nconst delayTime = getConditionValue(250, 10)\n\nexport abstract class BaseLanguageClient implements FeatureClient<Middleware, LanguageClientOptions> {\n  private _rootPath: string | false\n  private _consoleDebug = false\n  private __extensionName: string\n  private _id: string\n  private _name: string\n  private _clientOptions: ResolvedClientOptions\n\n  protected _state: ClientState\n  private _onStart: Promise<void> | undefined\n  private _onStop: Promise<void> | undefined\n  private _connection: Connection | undefined\n  private _initializeResult: InitializeResult | undefined\n  private _outputChannel: OutputChannel | undefined\n  private _traceOutputChannel: OutputChannel | undefined\n  private _capabilities: ServerCapabilities & ResolvedTextDocumentSyncCapabilities\n  private _disposed: 'disposing' | 'disposed' | undefined\n  private readonly _ignoredRegistrations: Set<string>\n  private readonly _listeners: Disposable[]\n\n  private readonly _notificationHandlers: Map<string, GenericNotificationHandler>\n  private readonly _notificationDisposables: Map<string, Disposable>\n  private readonly _pendingNotificationHandlers: Map<string, GenericNotificationHandler>\n  private readonly _requestHandlers: Map<string, GenericRequestHandler<unknown, unknown>>\n  private readonly _requestDisposables: Map<string, Disposable>\n  private readonly _pendingRequestHandlers: Map<string, GenericRequestHandler<unknown, unknown>>\n  private readonly _progressHandlers: Map<string | number, { type: ProgressType<any>; handler: NotificationHandler<any> }>\n  private readonly _pendingProgressHandlers: Map<string | number, { type: ProgressType<any>; handler: NotificationHandler<any> }>\n  private readonly _progressDisposables: Map<string | number, Disposable>\n\n  private _fileEvents: FileEvent[]\n  private _fileEventDelayer: Delayer<void>\n\n  private _diagnostics: DiagnosticCollection | undefined\n  private _syncedDocuments: Map<string, TextDocument>\n\n  private _traceFormat: TraceFormat\n  private _trace: Trace\n  private _tracer: Tracer\n  private _stateChangeEmitter: Emitter<StateChangeEvent>\n\n  private readonly _c2p: c2p.Converter\n  private _didOpenTextDocumentFeature: DidOpenTextDocumentFeature | undefined\n\n  public constructor(\n    id: string,\n    name: string,\n    clientOptions: LanguageClientOptions\n  ) {\n    this._id = id\n    this._name = name\n    if (clientOptions.outputChannel) {\n      this._outputChannel = clientOptions.outputChannel\n    } else {\n      this._outputChannel = undefined\n    }\n    this._traceOutputChannel = clientOptions.traceOutputChannel\n    this._clientOptions = this.resolveClientOptions(clientOptions)\n    this.$state = ClientState.Initial\n    this._connection = undefined\n    this._initializeResult = undefined\n    this._listeners = []\n    this._diagnostics = undefined\n\n    this._notificationHandlers = new Map()\n    this._pendingNotificationHandlers = new Map()\n    this._notificationDisposables = new Map()\n    this._requestHandlers = new Map()\n    this._pendingRequestHandlers = new Map()\n    this._requestDisposables = new Map()\n    this._progressHandlers = new Map()\n    this._pendingProgressHandlers = new Map()\n    this._progressDisposables = new Map()\n\n    this._fileEvents = []\n    this._fileEventDelayer = new Delayer<void>(delayTime)\n    this._ignoredRegistrations = new Set()\n    this._onStop = undefined\n    this._stateChangeEmitter = new Emitter<StateChangeEvent>()\n    this._trace = Trace.Off\n    this._tracer = {\n      log: (messageOrDataObject: string | any, data?: string) => {\n        if (Is.string(messageOrDataObject)) {\n          this.traceMessage(messageOrDataObject, data)\n        } else {\n          this.traceObject(messageOrDataObject)\n        }\n      }\n    }\n    this._c2p = c2p.createConverter(clientOptions.uriConverter ? clientOptions.uriConverter.code2Protocol : undefined)\n    this._syncedDocuments = new Map<string, TextDocument>()\n    this.registerBuiltinFeatures()\n    Error.captureStackTrace(this)\n  }\n\n  public switchConsole(): void {\n    this._consoleDebug = !this._consoleDebug\n    this.changeTrace(Trace.Verbose, TraceFormat.Text)\n  }\n\n  private resolveClientOptions(clientOptions: LanguageClientOptions): ResolvedClientOptions {\n    const markdown = { isTrusted: false, supportHtml: false }\n    if (clientOptions.markdown != null) {\n      markdown.isTrusted = clientOptions.markdown.isTrusted === true\n      markdown.supportHtml = clientOptions.markdown.supportHtml === true\n    }\n    let disableSnippetCompletion = clientOptions.disableSnippetCompletion\n    let disableMarkdown = clientOptions.disableMarkdown\n    if (disableMarkdown === undefined) {\n      disableMarkdown = workspace.initialConfiguration.get<boolean>('coc.preferences.enableMarkdown') === false\n    }\n    const pullConfig = workspace.getConfiguration('pullDiagnostic', clientOptions.workspaceFolder)\n    let pullOption = clientOptions.diagnosticPullOptions ?? {}\n    if (pullOption.onChange === undefined) pullOption.onChange = pullConfig.get<boolean>('onChange')\n    if (pullOption.onSave === undefined) pullOption.onSave = pullConfig.get<boolean>('onSave')\n    if (pullOption.workspace === undefined) pullOption.workspace = pullConfig.get<boolean>('workspace')\n    pullOption.ignored = pullConfig.get<string[]>('ignored', []).concat(pullOption.ignored ?? [])\n\n    let disabledFeatures = clientOptions.disabledFeatures ?? []\n    for (let key of ['disableCompletion', 'disableWorkspaceFolders', 'disableDiagnostics']) {\n      if (typeof clientOptions[key] === 'boolean') {\n        let stack = '\\n' + Error().stack.split('\\n').slice(2, 4).join('\\n')\n        logger.warn(`${key} in the client options is deprecated. use disabledFeatures instead.`, stack)\n        if (clientOptions[key] === true) {\n          let s = key.slice(7)\n          disabledFeatures.push(s[0].toLowerCase() + s.slice(1))\n        }\n      }\n    }\n    return {\n      disabledFeatures,\n      disableMarkdown,\n      disableSnippetCompletion,\n      diagnosticPullOptions: pullOption,\n      rootPatterns: defaultValue(clientOptions.rootPatterns, []),\n      requireRootPattern: clientOptions.requireRootPattern,\n      disableDynamicRegister: clientOptions.disableDynamicRegister,\n      formatterPriority: defaultValue(clientOptions.formatterPriority, 0),\n      ignoredRootPaths: defaultValue(clientOptions.ignoredRootPaths, []),\n      documentSelector: defaultValue(clientOptions.documentSelector, []),\n      synchronize: defaultValue(clientOptions.synchronize, {}),\n      diagnosticCollectionName: clientOptions.diagnosticCollectionName,\n      outputChannelName: defaultValue(clientOptions.outputChannelName, this._id),\n      revealOutputChannelOn: defaultValue(clientOptions.revealOutputChannelOn, RevealOutputChannelOn.Never),\n      stdioEncoding: defaultValue(clientOptions.stdioEncoding, 'utf8'),\n      initializationOptions: clientOptions.initializationOptions,\n      initializationFailedHandler: clientOptions.initializationFailedHandler,\n      progressOnInitialization: clientOptions.progressOnInitialization === true,\n      errorHandler: clientOptions.errorHandler ?? this.createDefaultErrorHandler(clientOptions.connectionOptions?.maxRestartCount),\n      middleware: defaultValue(clientOptions.middleware, {}),\n      workspaceFolder: clientOptions.workspaceFolder,\n      connectionOptions: clientOptions.connectionOptions,\n      uriConverter: clientOptions.uriConverter,\n      textSynchronization: this.createTextSynchronizationOptions(clientOptions.textSynchronization),\n      markdown\n    }\n  }\n\n  private createTextSynchronizationOptions(options: LanguageClientOptions['textSynchronization']): ResolvedClientOptions['textSynchronization'] {\n    if (options && typeof options.delayOpenNotifications === 'boolean') {\n      return { delayOpenNotifications: options.delayOpenNotifications }\n    }\n    return { delayOpenNotifications: false }\n  }\n\n  public get supportedMarkupKind(): MarkupKind[] {\n    if (!this.clientOptions.disableMarkdown) return [MarkupKind.Markdown, MarkupKind.PlainText]\n    return [MarkupKind.PlainText]\n  }\n\n  public get state(): State {\n    return this.getPublicState()\n  }\n\n  private get $state(): ClientState {\n    return this._state\n  }\n\n  private set $state(value: ClientState) {\n    let oldState = this.getPublicState()\n    this._state = value\n    let newState = this.getPublicState()\n    if (newState !== oldState) {\n      this._stateChangeEmitter.fire({ oldState, newState })\n    }\n  }\n\n  public get id(): string {\n    return this._id\n  }\n\n  public get name(): string {\n    return this._name\n  }\n\n  public get middleware(): Middleware {\n    return this._clientOptions.middleware\n  }\n\n  public get code2ProtocolConverter(): c2p.Converter {\n    return this._c2p\n  }\n\n  public getPublicState(): State {\n    switch (this.$state) {\n      case ClientState.Starting:\n        return State.Starting\n      case ClientState.Running:\n        return State.Running\n      case ClientState.StartFailed:\n        return State.StartFailed\n      default:\n        return State.Stopped\n    }\n  }\n\n  public get initializeResult(): InitializeResult | undefined {\n    return this._initializeResult\n  }\n\n  public sendRequest<R, PR, E, RO>(type: ProtocolRequestType0<R, PR, E, RO>, token?: CancellationToken): Promise<R>\n  public sendRequest<P, R, PR, E, RO>(type: ProtocolRequestType<P, R, PR, E, RO>, params: NoInfer<RequestParam<P>>, token?: CancellationToken): Promise<R>\n  public sendRequest<R, E>(type: RequestType0<R, E>, token?: CancellationToken): Promise<R>\n  public sendRequest<P, R, E>(type: RequestType<P, R, E>, params: NoInfer<RequestParam<P>>, token?: CancellationToken): Promise<R>\n  public sendRequest<R>(method: string, token?: CancellationToken): Promise<R>\n  public sendRequest<R>(method: string, param: any, token?: CancellationToken): Promise<R>\n  public async sendRequest<R>(type: string | MessageSignature, ...params: any[]): Promise<R> {\n    if (this.$state === ClientState.StartFailed || this.$state === ClientState.Stopping || this.$state === ClientState.Stopped) {\n      return Promise.reject(new ResponseError(ErrorCodes.ConnectionInactive, `Client is not running`))\n    }\n    const connection = await this.$start()\n    // Send only depending open notifications\n    await this._didOpenTextDocumentFeature!.sendPendingOpenNotifications()\n\n    let param: any | undefined\n    let token: CancellationToken | undefined\n    // Separate cancellation tokens from other parameters for a better client interface\n    if (params.length === 1) {\n      // CancellationToken is an interface, so we need to check if the first param complies to it\n      if (CancellationToken.is(params[0])) {\n        token = params[0]\n      } else {\n        param = params[0]\n      }\n    } else if (params.length === 2) {\n      param = params[0]\n      token = params[1]\n    }\n    if (token !== undefined && token.isCancellationRequested) {\n      return Promise.reject(new ResponseError(LSPErrorCodes.RequestCancelled, 'Request got cancelled'))\n    }\n    type = fixRequestType(type, params)\n    const _sendRequest = this._clientOptions.middleware.sendRequest\n    if (_sendRequest !== undefined) {\n      // Return the general middleware invocation defining `next` as a utility function that reorganizes parameters to\n      // pass them to the original sendRequest function.\n      return _sendRequest(type, param, token, (type, param, token) => {\n        const params: any[] = []\n\n        // Add the parameters if there are any\n        if (param !== undefined) {\n          params.push(param)\n        }\n\n        // Add the cancellation token if there is one\n        if (token !== undefined) {\n          params.push(token)\n        }\n\n        return connection.sendRequest<R>(type, ...params)\n      })\n    } else {\n      return connection.sendRequest<R>(type, ...params)\n    }\n  }\n\n  public onRequest<R, PR, E, RO>(type: ProtocolRequestType0<R, PR, E, RO>, handler: NoInfer<RequestHandler0<R, E>>): Disposable\n  public onRequest<P, R, PR, E, RO>(type: ProtocolRequestType<P, R, PR, E, RO>, handler: NoInfer<RequestHandler<P, R, E>>): Disposable\n  public onRequest<R, E>(type: RequestType0<R, E>, handler: NoInfer<RequestHandler0<R, E>>): Disposable\n  public onRequest<P, R, E>(type: RequestType<P, R, E>, handler: NoInfer<RequestHandler<P, R, E>>): Disposable\n  public onRequest<R, E>(method: string, handler: GenericRequestHandler<R, E>): Disposable\n  public onRequest<R, E>(type: string | MessageSignature, handler: GenericRequestHandler<R, E>): Disposable {\n    const method = toMethod(type)\n    this._requestHandlers.set(method, handler)\n    const connection = this.activeConnection()\n    let disposable: Disposable\n    if (connection !== undefined) {\n      this._requestDisposables.set(method, connection.onRequest(type, handler))\n      disposable = {\n        dispose: () => {\n          const disposable = this._requestDisposables.get(method)\n          if (disposable !== undefined) {\n            disposable.dispose()\n            this._requestDisposables.delete(method)\n          }\n        }\n      }\n    } else {\n      this._pendingRequestHandlers.set(method, handler)\n      disposable = {\n        dispose: () => {\n          this._pendingRequestHandlers.delete(method)\n          const disposable = this._requestDisposables.get(method)\n          if (disposable !== undefined) {\n            disposable.dispose()\n            this._requestDisposables.delete(method)\n          }\n        }\n      }\n    }\n    return {\n      dispose: () => {\n        this._requestHandlers.delete(method)\n        disposable.dispose()\n      }\n    }\n  }\n\n  public sendNotification<RO>(type: ProtocolNotificationType0<RO>): Promise<void>\n  public sendNotification<P, RO>(type: ProtocolNotificationType<P, RO>, params?: NoInfer<RequestParam<P>>): Promise<void>\n  public sendNotification(type: NotificationType0): Promise<void>\n  public sendNotification<P>(type: NotificationType<P>, params?: NoInfer<RequestParam<P>>): Promise<void>\n  public sendNotification(method: string, params?: any): Promise<void>\n  public async sendNotification<P>(type: string | MessageSignature, params?: P): Promise<void> {\n    if (this.$state === ClientState.StartFailed || this.$state === ClientState.Stopping || this.$state === ClientState.Stopped) {\n      // not throw for notification\n      this.error(`Client is not running when send notification`, type)\n      return\n    }\n    try {\n      let documentToClose: string | undefined\n      if (typeof type !== 'string' && type.method === DidCloseTextDocumentNotification.method) {\n        documentToClose = (params as DidCloseTextDocumentParams).textDocument.uri\n      }\n      const connection = await this.$start()\n      // Send any depending open notifications\n      const didDropOpenNotification = await this._didOpenTextDocumentFeature!.sendPendingOpenNotifications(documentToClose)\n      if (didDropOpenNotification) {\n        // Don't forward this close notification if we dropped the\n        // corresponding open notification.\n        return\n      }\n\n      type = fixNotificationType(type, params == null ? [] : [params])\n      const _sendNotification = this._clientOptions.middleware.sendNotification\n      return await Promise.resolve(_sendNotification\n        ? _sendNotification(type, connection.sendNotification.bind(connection), params)\n        : connection.sendNotification(type, params))\n    } catch (error) {\n      this.error(`Sending notification ${toMethod(type)} failed.`, error)\n      if ([ClientState.Stopping, ClientState.Stopped].includes(this._state)) return\n      throw error\n    }\n  }\n\n  public onNotification<RO>(type: ProtocolNotificationType0<RO>, handler: NotificationHandler0): Disposable\n  public onNotification<P, RO>(type: ProtocolNotificationType<P, RO>, handler: NoInfer<NotificationHandler<P>>): Disposable\n  public onNotification(type: NotificationType0, handler: NotificationHandler0): Disposable\n  public onNotification<P>(type: NotificationType<P>, handler: NoInfer<NotificationHandler<P>>): Disposable\n  public onNotification(method: string, handler: GenericNotificationHandler): Disposable\n  public onNotification(type: string | MessageSignature, handler: GenericNotificationHandler): Disposable {\n    const method = toMethod(type)\n    this._notificationHandlers.set(method, handler)\n    const connection = this.activeConnection()\n    let disposable: Disposable\n    if (connection !== undefined) {\n      this._notificationDisposables.set(method, connection.onNotification(type, handler))\n      disposable = {\n        dispose: () => {\n          const disposable = this._notificationDisposables.get(method)\n          if (disposable !== undefined) {\n            disposable.dispose()\n            this._notificationDisposables.delete(method)\n          }\n        }\n      }\n    } else {\n      this._pendingNotificationHandlers.set(method, handler)\n      disposable = {\n        dispose: () => {\n          this._pendingNotificationHandlers.delete(method)\n          const disposable = this._notificationDisposables.get(method)\n          if (disposable !== undefined) {\n            disposable.dispose()\n            this._notificationDisposables.delete(method)\n          }\n        }\n      }\n    }\n    return {\n      dispose: () => {\n        this._notificationHandlers.delete(method)\n        disposable.dispose()\n      }\n    }\n  }\n\n  public onProgress<P>(type: ProgressType<any>, token: string | number, handler: NoInfer<NotificationHandler<P>>): Disposable {\n    this._progressHandlers.set(token, { type, handler })\n    const connection = this.activeConnection()\n    let disposable: Disposable\n    const handleWorkDoneProgress = this._clientOptions.middleware.handleWorkDoneProgress\n    const realHandler = WorkDoneProgress.is(type) && handleWorkDoneProgress !== undefined\n      ? (params: P) => {\n        handleWorkDoneProgress(token, params as any, () => handler(params as unknown as P))\n      }\n      : handler\n    if (connection !== undefined) {\n      this._progressDisposables.set(token, connection.onProgress(type, token, realHandler))\n      disposable = {\n        dispose: () => {\n          const disposable = this._progressDisposables.get(token)\n          if (disposable !== undefined) {\n            disposable.dispose()\n            this._progressDisposables.delete(token)\n          }\n        }\n      }\n    } else {\n      this._pendingProgressHandlers.set(token, { type, handler })\n      disposable = {\n        dispose: () => {\n          this._pendingProgressHandlers.delete(token)\n          const disposable = this._progressDisposables.get(token)\n          if (disposable !== undefined) {\n            disposable.dispose()\n            this._progressDisposables.delete(token)\n          }\n        }\n      }\n    }\n    return {\n      dispose: (): void => {\n        this._progressHandlers.delete(token)\n        disposable.dispose()\n      }\n    }\n  }\n\n  public async sendProgress<P>(type: ProgressType<P>, token: string | number, value: NoInfer<RequestParam<P>>): Promise<void> {\n    if (this.$state === ClientState.StartFailed || this.$state === ClientState.Stopping || this.$state === ClientState.Stopped) {\n      return Promise.reject(new ResponseError(ErrorCodes.ConnectionInactive, `Client is not running`))\n    }\n    try {\n      const connection = await this.$start()\n      await connection.sendProgress(type, token, value)\n    } catch (error) {\n      this.error(`Sending progress for token ${token} failed.`, error)\n      throw error\n    }\n  }\n\n  /**\n   * languageserver.xxx.settings or undefined\n   */\n  public get configuredSection(): string | undefined {\n    let section = defaultValue(this._clientOptions.synchronize, {}).configurationSection\n    return typeof section === 'string' && section.startsWith('languageserver.') ? section : undefined\n  }\n\n  public get clientOptions(): ResolvedClientOptions {\n    return this._clientOptions\n  }\n\n  public get onDidChangeState(): Event<StateChangeEvent> {\n    return this._stateChangeEmitter.event\n  }\n\n  public get outputChannel(): OutputChannel {\n    if (!this._outputChannel) {\n      let { outputChannelName } = this._clientOptions\n      this._outputChannel = window.createOutputChannel(defaultValue(outputChannelName, this._name))\n    }\n    return this._outputChannel\n  }\n\n  public get traceOutputChannel(): OutputChannel {\n    return this._traceOutputChannel ? this._traceOutputChannel : this.outputChannel\n  }\n\n  public get diagnostics(): DiagnosticCollection | undefined {\n    return this._diagnostics\n  }\n\n  public createDefaultErrorHandler(maxRestartCount?: number): ErrorHandler {\n    return new DefaultErrorHandler(this._id, maxRestartCount ?? 4, this._outputChannel)\n  }\n\n  public set trace(value: Trace) {\n    this.changeTrace(value, this._traceFormat)\n  }\n\n  private consoleMessage(message: string, error = false): void {\n    if (this._consoleDebug) {\n      // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n      error ? console.error(redOpen + message + redClose) : console.log(message)\n    }\n  }\n\n  public debug(message: string, data?: any, showNotification = true): void {\n    this.logOutputMessage(MessageType.Debug, RevealOutputChannelOn.Debug, 'Debug', message, data, showNotification)\n  }\n\n  public info(message: string, data?: any, showNotification = true): void {\n    this.logOutputMessage(MessageType.Info, RevealOutputChannelOn.Info, 'Info', message, data, showNotification)\n  }\n\n  public warn(message: string, data?: any, showNotification = true): void {\n    this.logOutputMessage(MessageType.Warning, RevealOutputChannelOn.Warn, 'Warn', message, data, showNotification)\n  }\n\n  public error(message: string, data?: any, showNotification: boolean | 'force' = true): void {\n    this.logOutputMessage(MessageType.Error, RevealOutputChannelOn.Error, 'Error', message, data, showNotification)\n  }\n\n  private logOutputMessage(type: MessageType, reveal: RevealOutputChannelOn, name: string, message: string, data: any | undefined, showNotification: boolean | 'force'): void {\n    const msg = `[${name.padEnd(5)} - ${currentTimeStamp()}] ${this.getLogMessage(message, data)}`\n    this.outputChannel.appendLine(msg)\n    this.consoleMessage(msg, type === MessageType.Error)\n    if (showNotification === 'force' || (showNotification && this._clientOptions.revealOutputChannelOn <= reveal)) {\n      this.showNotificationMessage(type, message, data)\n    }\n  }\n\n  private traceObject(data: any): void {\n    this.traceOutputChannel.appendLine(`${getTracePrefix(data)}${data2String(data)}`)\n  }\n\n  public traceMessage(message: string, data?: any): void {\n    const msg = `[Trace - ${currentTimeStamp()}] ${this.getLogMessage(message, data)}`\n    this.traceOutputChannel.appendLine(msg)\n    this.consoleMessage(msg)\n  }\n\n  private getLogMessage(message: string, data?: any): string {\n    return data != null ? `${message}\\n${data2String(data)}` : message\n  }\n\n  private showNotificationMessage(type: MessageType, message?: string, data?: any) {\n    message = message ?? 'A request has failed. See the output for more information.'\n    if (data) {\n      message += '\\n' + data2String(data)\n    }\n\n    const messageFunc = type === MessageType.Error\n      ? window.showErrorMessage.bind(window)\n      : type === MessageType.Warning\n        ? window.showWarningMessage.bind(window)\n        : window.showInformationMessage.bind(window)\n    let fn = getConditionValue(messageFunc, (_, _obj) => Promise.resolve(global.__showOutput))\n    fn(message, { title: 'Go to output' }).then(selection => {\n      if (selection !== undefined) {\n        this.outputChannel.show(true)\n      }\n    }, onUnexpectedError)\n  }\n\n  public needsStart(): boolean {\n    return (\n      this.$state === ClientState.Initial ||\n      this.$state === ClientState.Stopping ||\n      this.$state === ClientState.Stopped\n    )\n  }\n\n  public needsStop(): boolean {\n    return (\n      this.$state === ClientState.Starting || this.$state === ClientState.Running\n    )\n  }\n\n  private activeConnection(): Connection | undefined {\n    return this.$state === ClientState.Running && this._connection !== undefined ? this._connection : undefined\n  }\n\n  public get hasPendingResponse(): boolean {\n    return this._connection?.hasPendingResponse()\n  }\n\n  public onReady(): Promise<void> {\n    if (this._onStart) return this._onStart\n    return new Promise(resolve => {\n      let disposable = this.onDidChangeState(e => {\n        if (e.newState === State.Running) {\n          disposable.dispose()\n          resolve()\n        }\n      })\n    })\n  }\n\n  public get started(): boolean {\n    return this.$state != ClientState.Initial\n  }\n\n  public isRunning(): boolean {\n    return this.$state === ClientState.Running\n  }\n\n  public async _start(): Promise<void> {\n    if (this._disposed === 'disposing' || this._disposed === 'disposed') {\n      throw new Error(`Client got disposed and can't be restarted.`)\n    }\n    if (this.$state === ClientState.Stopping) {\n      throw new Error(`Client is currently stopping. Can only restart a full stopped client`)\n    }\n    // We are already running or are in the process of getting up\n    // to speed.\n    if (this._onStart !== undefined) {\n      return this._onStart\n    }\n    this._rootPath = this.resolveRootPath()\n\n    const [promise, resolve, reject] = this.createOnStartPromise()\n    this._onStart = promise\n\n    // If we restart then the diagnostics collection is reused.\n    if (this._diagnostics === undefined) {\n      let opts = this._clientOptions\n      let name = opts.diagnosticCollectionName ? opts.diagnosticCollectionName : this._id\n      if (!opts.disabledFeatures.includes('diagnostics')) {\n        this._diagnostics = languages.createDiagnosticCollection(name)\n      }\n    }\n\n    // When we start make all buffer handlers pending so that they\n    // get added.\n    for (const [method, handler] of this._notificationHandlers) {\n      if (!this._pendingNotificationHandlers.has(method)) {\n        this._pendingNotificationHandlers.set(method, handler)\n      }\n    }\n    for (const [method, handler] of this._requestHandlers) {\n      if (!this._pendingRequestHandlers.has(method)) {\n        this._pendingRequestHandlers.set(method, handler)\n      }\n    }\n    for (const [token, data] of this._progressHandlers) {\n      if (!this._pendingProgressHandlers.has(token)) {\n        this._pendingProgressHandlers.set(token, data)\n      }\n    }\n\n    this.$state = ClientState.Starting\n    try {\n      const connection = await this.createConnection()\n      this.handleConnectionEvents(connection)\n      connection.listen()\n      await this.initialize(connection)\n      resolve()\n    } catch (error) {\n      this.$state = ClientState.StartFailed\n      this.error(`${this._name} client: couldn't create connection to server.`, error, 'force')\n      reject(error)\n    }\n    return this._onStart\n  }\n\n  public start(): Promise<void> & Disposable {\n    let p: any = this._start()\n    p.dispose = () => {\n      if (this.needsStop()) {\n        void this.stop()\n      }\n    }\n    return p\n  }\n\n  private async $start(): Promise<Connection> {\n    if (this.$state === ClientState.StartFailed) {\n      throw new Error(`Previous start failed. Can't restart server.`)\n    }\n    await this._start()\n    const connection = this.activeConnection()\n    if (connection === undefined) {\n      throw new Error(`Starting server failed`)\n    }\n    return connection\n  }\n\n  private handleConnectionEvents(connection: Connection) {\n    connection.onNotification(LogMessageNotification.type, message => {\n      switch (message.type) {\n        case MessageType.Error:\n          this.error(message.message)\n          break\n        case MessageType.Warning:\n          this.warn(message.message)\n          break\n        case MessageType.Info:\n          this.info(message.message)\n          break\n        case MessageType.Debug:\n          this.debug(message.message)\n          break\n        default:\n          this.outputChannel.appendLine(message.message)\n      }\n    })\n    connection.onNotification(ShowMessageNotification.type, message => {\n      switch (message.type) {\n        case MessageType.Error:\n          void window.showErrorMessage(message.message)\n          break\n        case MessageType.Warning:\n          void window.showWarningMessage(message.message)\n          break\n        case MessageType.Info:\n          void window.showInformationMessage(message.message)\n          break\n        default:\n          void window.showInformationMessage(message.message)\n      }\n    })\n    // connection.onNotification(TelemetryEventNotification.type, data => {\n    //   // Not supported.\n    //   // this._telemetryEmitter.fire(data);\n    // })\n    connection.onRequest(ShowMessageRequest.type, (params: ShowMessageRequestParams) => {\n      let messageFunc: <T extends MessageItem>(message: string, ...items: T[]) => Thenable<T>\n      switch (params.type) {\n        case MessageType.Error:\n          messageFunc = window.showErrorMessage.bind(window)\n          break\n        case MessageType.Warning:\n          messageFunc = window.showWarningMessage.bind(window)\n          break\n        case MessageType.Info:\n          messageFunc = window.showInformationMessage.bind(window)\n          break\n        default:\n          messageFunc = window.showInformationMessage.bind(window)\n      }\n      let actions: MessageActionItem[] = toArray(params.actions)\n      return messageFunc(params.message, ...actions)\n    })\n    connection.onRequest(ShowDocumentRequest.type, async (params, token) => {\n      const showDocument = async (params: ShowDocumentParams): Promise<ShowDocumentResult> => {\n        try {\n          if (params.external === true || /^https?:\\/\\//.test(params.uri)) {\n            await workspace.openResource(params.uri)\n            return { success: true }\n          } else {\n            let { selection, takeFocus } = params\n            if (takeFocus === false) {\n              await workspace.loadFile(params.uri)\n            } else {\n              await workspace.jumpTo(params.uri, selection?.start)\n              if (selection && comparePosition(selection.start, selection.end) != 0) {\n                await window.selectRange(selection)\n              }\n            }\n            return { success: true }\n          }\n        } catch (error) {\n          return { success: false }\n        }\n      }\n      const middleware = this._clientOptions.middleware.window?.showDocument\n      if (middleware !== undefined) {\n        return middleware(params, token, showDocument)\n      } else {\n        return showDocument(params)\n      }\n    })\n  }\n\n  private createOnStartPromise(): [Promise<void>, () => void, (error: any) => void] {\n    let resolve!: () => void\n    let reject!: (error: any) => void\n    const promise: Promise<void> = new Promise((_resolve, _reject) => {\n      resolve = _resolve\n      reject = _reject\n    })\n    return [promise, resolve, reject]\n  }\n\n  private resolveRootPath(): string | null {\n    if (this._clientOptions.workspaceFolder) {\n      return URI.parse(this._clientOptions.workspaceFolder.uri).fsPath\n    }\n    let { ignoredRootPaths, rootPatterns, requireRootPattern } = this._clientOptions\n    let resolved: string | undefined\n    if (!isFalsyOrEmpty(rootPatterns)) {\n      resolved = workspace.documentsManager.resolveRoot(rootPatterns, requireRootPattern)\n    }\n    let rootPath = resolved || workspace.rootPath\n    if (sameFile(rootPath, os.homedir()) || ignoredRootPaths.some(p => sameFile(rootPath, p))) {\n      this.warn(`Ignored rootPath ${rootPath} of client \"${this._id}\"`)\n      return null\n    }\n    return rootPath\n  }\n\n  private initialize(connection: Connection): Promise<InitializeResult> {\n    let { initializationOptions, workspaceFolder, progressOnInitialization } = this._clientOptions\n    this.refreshTrace(false)\n    let rootPath = this._rootPath\n    let initParams: InitializeParams = {\n      processId: process.pid,\n      rootPath: rootPath ? rootPath : null,\n      rootUri: rootPath ? this.code2ProtocolConverter.asUri(URI.file(rootPath)) : null,\n      capabilities: this.computeClientCapabilities(),\n      initializationOptions: Is.func(initializationOptions) ? initializationOptions() : initializationOptions,\n      trace: Trace.toString(this._trace),\n      workspaceFolders: workspaceFolder ? [workspaceFolder] : null,\n      locale: getLocale(),\n      clientInfo: {\n        name: 'coc.nvim',\n        version: workspace.version\n      }\n    }\n    this.fillInitializeParams(initParams)\n    if (progressOnInitialization) {\n      const token: ProgressToken = UUID.generateUuid()\n      initParams.workDoneToken = token\n      connection.id = this._id\n      const part = new ProgressPart(connection, token)\n      part.begin({ title: `Initializing ${this.id}`, kind: 'begin' })\n      return this.doInitialize(connection, initParams).then(result => {\n        part.done()\n        return result\n      }, (error: Error) => {\n        part.done()\n        return Promise.reject(error)\n      })\n    } else {\n      return this.doInitialize(connection, initParams)\n    }\n  }\n\n  private async doInitialize(connection: Connection, initParams: InitializeParams): Promise<InitializeResult> {\n    try {\n      const result = await connection.initialize(initParams)\n      if (result.capabilities.positionEncoding !== undefined && result.capabilities.positionEncoding !== PositionEncodingKind.UTF16) {\n        throw new Error(`Unsupported position encoding (${result.capabilities.positionEncoding}) received from server ${this.name}`)\n      }\n\n      this._initializeResult = result\n      this.$state = ClientState.Running\n      let textDocumentSyncOptions: TextDocumentSyncOptions | undefined\n      if (Is.number(result.capabilities.textDocumentSync)) {\n        if (result.capabilities.textDocumentSync === TextDocumentSyncKind.None) {\n          textDocumentSyncOptions = {\n            openClose: false,\n            change: TextDocumentSyncKind.None,\n            save: undefined\n          }\n        } else {\n          textDocumentSyncOptions = {\n            openClose: true,\n            change: result.capabilities.textDocumentSync,\n            save: {\n              includeText: false\n            }\n          }\n        }\n      } else if (result.capabilities.textDocumentSync !== undefined && result.capabilities.textDocumentSync !== null) {\n        textDocumentSyncOptions = result.capabilities.textDocumentSync as TextDocumentSyncOptions\n      }\n      this._capabilities = Object.assign({}, result.capabilities, { resolvedTextDocumentSync: textDocumentSyncOptions })\n      connection.onNotification(PublishDiagnosticsNotification.type, params => this.handleDiagnostics(params))\n      for (let requestType of [RegistrationRequest.type, 'client/registerFeature']) {\n        connection.onRequest(requestType, params => this.handleRegistrationRequest(params))\n      }\n      for (let requestType of [UnregistrationRequest.type, 'client/unregisterFeature']) {\n        connection.onRequest(requestType, params => this.handleUnregistrationRequest(params))\n      }\n      connection.onRequest(ApplyWorkspaceEditRequest.type, params => this.handleApplyWorkspaceEdit(params))\n\n      // Add pending notification, request and progress handlers.\n      for (const [method, handler] of this._pendingNotificationHandlers) {\n        this._notificationDisposables.set(method, connection.onNotification(method, handler))\n      }\n      this._pendingNotificationHandlers.clear()\n      for (const [method, handler] of this._pendingRequestHandlers) {\n        this._requestDisposables.set(method, connection.onRequest(method, handler))\n      }\n      this._pendingRequestHandlers.clear()\n      for (const [token, data] of this._pendingProgressHandlers) {\n        this._progressDisposables.set(token, connection.onProgress(data.type, token, data.handler))\n      }\n      this._pendingProgressHandlers.clear()\n      await connection.sendNotification(InitializedNotification.type, {})\n      this.hookConfigurationChanged()\n      this.initializeFeatures(connection)\n      return result\n    } catch (error: any) {\n      this.error('Server initialization failed.', error)\n      logger.error(`Server \"${this.id}\" initialization failed.`, error)\n      let cb = (retry: boolean) => {\n        process.nextTick(() => {\n          new Promise((resolve, reject) => {\n            if (retry) {\n              this.initialize(connection).then(resolve, reject)\n            } else {\n              this.stop().then(resolve, reject)\n            }\n          }).catch(err => {\n            this.error(`Unexpected error`, err, false)\n          })\n        })\n      }\n      if (this._clientOptions.initializationFailedHandler) {\n        cb(this._clientOptions.initializationFailedHandler(error))\n      } else if (error instanceof ResponseError && error.data && error.data.retry) {\n        void window.showErrorMessage(error.message, { title: 'Retry', id: 'retry' }).then(item => {\n          cb(item && item.id === 'retry')\n        })\n      } else {\n        if (error && error.message) {\n          void window.showErrorMessage(toText(error.message))\n        }\n        cb(false)\n      }\n      throw error\n    }\n  }\n\n  public stop(timeout = 2000): Promise<void> {\n    // Wait 2 seconds on stop\n    return this.shutdown(ShutdownMode.Stop, timeout)\n  }\n\n  protected async shutdown(mode: ShutdownMode, timeout: number): Promise<void> {\n    // If the client is stopped or in its initial state return.\n    if (this.$state === ClientState.Stopped || this.$state === ClientState.Initial) {\n      return\n    }\n    if (this.$state === ClientState.Starting && this._onStart) {\n      await this._onStart\n    }\n    // If we are stopping the client and have a stop promise return it.\n    if (this.$state === ClientState.Stopping) {\n      return this._onStop\n    }\n\n    const connection = this._connection\n    // We can't stop a client that is not running (e.g. has no connection). Especially not\n    // on that us starting since it can't be correctly synchronized.\n    if (connection === undefined || (this.$state !== ClientState.Running && this.$state !== ClientState.StartFailed)) {\n      throw new Error(`Client is not running and can't be stopped. Its current state is: ${this.$state}`)\n    }\n    this._initializeResult = undefined\n    this.$state = ClientState.Stopping\n    this.cleanUp(mode)\n\n    let tm: NodeJS.Timeout\n    const tp = new Promise<any>(c => { tm = setTimeout(c, timeout) })\n    const shutdown = (async connection => {\n      await connection.shutdown()\n      await connection.exit()\n      return connection\n    })(connection)\n\n    return this._onStop = Promise.race([tp, shutdown]).then(connection => {\n      if (tm) clearTimeout(tm)\n      // The connection won the race with the timeout.\n      if (connection !== undefined) {\n        connection.end()\n        connection.dispose()\n      } else {\n        this.error(`Stopping server timed out`, undefined)\n        throw new Error(`Stopping the server timed out`)\n      }\n    }, error => {\n      this.error(`Stopping server failed`, error)\n      throw error\n    }).finally(() => {\n      this.$state = ClientState.Stopped\n      if (mode === 'stop') {\n        this.cleanUpChannel()\n      }\n      this._onStart = undefined\n      this._onStop = undefined\n      this._connection = undefined\n      this._ignoredRegistrations.clear()\n    })\n  }\n\n  public dispose(timeout = 2000): Promise<void> {\n    if (this._disposed) return\n    try {\n      this._disposed = 'disposing'\n      if (!this.needsStop()) return\n      return this.stop(timeout)\n    } finally {\n      this._disposed = 'disposed'\n    }\n  }\n\n  private cleanUp(mode: ShutdownMode): void {\n    this._fileEvents = []\n    this._fileEventDelayer.cancel()\n\n    if (this._listeners) {\n      disposeAll(this._listeners)\n    }\n\n    if (this._syncedDocuments) {\n      this._syncedDocuments.clear()\n    }\n    // Clear features in reverse order;\n    for (const feature of Array.from(this._features.entries()).map(entry => entry[1]).reverse()) {\n      if (typeof feature.dispose === 'function') {\n        feature.dispose()\n      }\n    }\n    if ((mode === ShutdownMode.Stop || mode === ShutdownMode.Restart) && this._diagnostics !== undefined) {\n      this._diagnostics.dispose()\n      this._diagnostics = undefined\n    }\n  }\n\n  private cleanUpChannel(): void {\n    if (this._outputChannel) {\n      this._outputChannel.dispose()\n      this._outputChannel = undefined\n    }\n  }\n\n  public notifyFileEvent(event: FileEvent | undefined): void {\n    const didChangeWatchedFile = async (event: FileEvent | undefined): Promise<void> => {\n      if (event) this._fileEvents.push(event)\n      return this._fileEventDelayer.trigger(async (): Promise<void> => {\n        const fileEvents = this._fileEvents\n        if (fileEvents.length === 0) return\n        this._fileEvents = []\n        try {\n          await this.sendNotification(DidChangeWatchedFilesNotification.type, { changes: fileEvents })\n        } catch (error) {\n          // Restore the file events.\n          this._fileEvents = fileEvents\n          throw error\n        }\n      })\n    }\n    const workSpaceMiddleware = this.clientOptions.middleware.workspace;\n    (workSpaceMiddleware?.didChangeWatchedFile ? workSpaceMiddleware.didChangeWatchedFile(event, didChangeWatchedFile) : didChangeWatchedFile(event)).catch(error => {\n      this.error(`Notifying ${DidChangeWatchedFilesNotification.method} failed.`, error)\n    })\n  }\n\n  /**\n   * @deprecated\n   */\n  public async forceDocumentSync(): Promise<void> {\n  }\n\n  public isSynced(uri: string): boolean {\n    return this._syncedDocuments ? this._syncedDocuments.has(uri) : false\n  }\n\n  protected abstract createMessageTransports(encoding: string): Promise<MessageTransports | null>\n\n  private async createConnection(): Promise<Connection> {\n    let onError = error => {\n      this.error(`Unexpected connection error: `, error)\n    }\n    let errorHandler = (error: Error, message: Message | undefined, count: number | undefined) => {\n      this.handleConnectionError(error, message, count).catch(onError)\n    }\n    let closeHandler = () => {\n      this.handleConnectionClosed().catch(onError)\n    }\n    const transports = await this.createMessageTransports(defaultValue(this._clientOptions.stdioEncoding, 'utf8'))\n    this._connection = createConnection(transports.reader, transports.writer, errorHandler, closeHandler, this._clientOptions.connectionOptions)\n    return this._connection\n  }\n\n  protected async handleConnectionClosed(): Promise<void> {\n    // Check whether this is a normal shutdown in progress or the client stopped normally.\n    if (this.$state === ClientState.Stopped) {\n      logger.info(`client ${this._id} normal closed`)\n      return\n    }\n    try {\n      if (this._connection !== undefined) {\n        this._connection.dispose()\n      }\n    } catch (error) {\n      // Disposing a connection could fail if error cases.\n    }\n    let handlerResult: CloseHandlerResult = { action: CloseAction.DoNotRestart }\n    let err\n    if (this.$state !== ClientState.Stopping) {\n      try {\n        let result = await this._clientOptions.errorHandler.closed()\n        handlerResult = toCloseHandlerResult(result)\n      } catch (error) {\n        err = error\n      }\n    }\n    this._connection = undefined\n    if (handlerResult.action === CloseAction.DoNotRestart) {\n      this.error(handlerResult.message ?? 'Connection to server got closed. Server will not be restarted.', undefined, handlerResult.handled === true ? false : 'force')\n      this.cleanUp(ShutdownMode.Stop)\n      if (this.$state === ClientState.Starting) {\n        this.$state = ClientState.StartFailed\n      } else {\n        this.$state = ClientState.Stopped\n      }\n      this._onStop = Promise.resolve()\n      this._onStart = undefined\n    } else if (handlerResult.action === CloseAction.Restart) {\n      this.info(handlerResult.message ?? 'Connection to server got closed. Server will restart.', undefined, !handlerResult.handled)\n      this.cleanUp(ShutdownMode.Restart)\n      this.$state = ClientState.Initial\n      this._onStop = Promise.resolve()\n      this._onStart = undefined\n      this.start().catch(error => {\n        this.error(`Restarting server failed`, error, 'force')\n      })\n    }\n    if (err) throw err\n  }\n\n  public async handleConnectionError(error: Error, message: Message | undefined, count: number): Promise<void> {\n    let res = await this._clientOptions.errorHandler!.error(error, message, count)\n    let result: ErrorHandlerResult = typeof res === 'number' ? { action: res } : defaultValue(res, { action: ErrorAction.Shutdown })\n    const showNotification = result.handled === true ? false : 'force'\n    if (result.action === ErrorAction.Shutdown) {\n      const msg = result.message ?? `Client ${this._name}: connection to server is erroring.\\n${error.message}\\nShutting down server.`\n      this.error(msg, error, showNotification)\n      return this.stop()\n    } else {\n      const msg = result.message ?? `Client ${this._name}: connection to server is erroring.\\n${error.message}`\n      this.error(msg, error, showNotification)\n    }\n  }\n\n  private hookConfigurationChanged(): void {\n    workspace.onDidChangeConfiguration(e => {\n      if (e.affectsConfiguration(this._id)) {\n        this.refreshTrace(true)\n      }\n    }, null, this._listeners)\n  }\n\n  private refreshTrace(sendNotification: boolean): void {\n    let config = workspace.getConfiguration(this._id, null)\n    let trace: Trace = Trace.Off\n    let traceFormat: TraceFormat = TraceFormat.Text\n    if (config) {\n      const traceConfig = config.get('trace.server', 'off')\n      if (typeof traceConfig === 'string') {\n        trace = Trace.fromString(traceConfig)\n      } else {\n        trace = Trace.fromString(config.get('trace.server.verbosity', 'off'))\n        traceFormat = TraceFormat.fromString(config.get('trace.server.format', 'text'))\n      }\n    }\n    if (sendNotification && this._trace == trace && this._traceFormat == traceFormat) {\n      return\n    }\n    this.changeTrace(trace, traceFormat, sendNotification)\n  }\n\n  private changeTrace(trace: Trace, traceFormat: TraceFormat, sendNotification = true): void {\n    this._trace = trace\n    this._traceFormat = traceFormat\n    if (this._connection && (this.$state === ClientState.Running || this.$state === ClientState.Starting)) {\n      this._connection.trace(this._trace, this._tracer, {\n        sendNotification,\n        traceFormat: this._traceFormat\n      }).catch(error => {\n        this.error(`Updating trace failed with error`, error, false)\n      })\n    }\n  }\n\n  private readonly _features: (StaticFeature | DynamicFeature<any>)[] = []\n  private readonly _dynamicFeatures: Map<string, DynamicFeature<any>> = new Map<\n    string,\n    DynamicFeature<any>\n  >()\n\n  public registerFeatures(\n    features: (StaticFeature | DynamicFeature<any>)[]\n  ): void {\n    for (let feature of features) {\n      this.registerFeature(feature, '')\n    }\n  }\n\n  public registerFeature(feature: StaticFeature | DynamicFeature<any>, name: string): void {\n    let { disabledFeatures } = this._clientOptions\n    if (disabledFeatures.length > 0 && disabledFeatures.includes(name)) return\n    this._features.push(feature)\n    if (DynamicFeature.is(feature)) {\n      const registrationType = feature.registrationType\n      this._dynamicFeatures.set(registrationType.method, feature)\n    }\n  }\n\n  public getStaticFeature(method: typeof ConfigurationRequest.method): PullConfigurationFeature\n  public getStaticFeature(method: typeof WorkDoneProgressCreateRequest.method): ProgressFeature\n  public getStaticFeature(method: string): StaticFeature | undefined {\n    return this._features.find(o => StaticFeature.is(o) && o.method == method) as StaticFeature\n  }\n\n  public getFeature(request: typeof ExecuteCommandRequest.method): DynamicFeature<ExecuteCommandRegistrationOptions>\n  public getFeature(request: typeof DidChangeWorkspaceFoldersNotification.method): DynamicFeature<void>\n  public getFeature(request: typeof DidChangeWatchedFilesNotification.method): DynamicFeature<DidChangeWatchedFilesRegistrationOptions>\n  public getFeature(request: typeof DidChangeConfigurationNotification.method): DynamicFeature<DidChangeConfigurationRegistrationOptions>\n  public getFeature(request: typeof DidOpenTextDocumentNotification.method): DidOpenTextDocumentFeatureShape\n  public getFeature(request: typeof DidChangeTextDocumentNotification.method): DidChangeTextDocumentFeatureShape\n  public getFeature(request: typeof WillSaveTextDocumentNotification.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentSendFeature<(textDocument: TextDocumentWillSaveEvent) => Promise<void>>\n  public getFeature(request: typeof WillSaveTextDocumentWaitUntilRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentSendFeature<(textDocument: TextDocument) => ProviderResult<TextEdit[]>>\n  public getFeature(request: typeof DidSaveTextDocumentNotification.method): DidSaveTextDocumentFeatureShape\n  public getFeature(request: typeof DidCloseTextDocumentNotification.method): DidCloseTextDocumentFeatureShape\n  public getFeature(request: typeof DidCreateFilesNotification.method): DynamicFeature<FileOperationRegistrationOptions> & { send: (event: FileCreateEvent) => Promise<void> }\n  public getFeature(request: typeof DidRenameFilesNotification.method): DynamicFeature<FileOperationRegistrationOptions> & { send: (event: FileRenameEvent) => Promise<void> }\n  public getFeature(request: typeof DidDeleteFilesNotification.method): DynamicFeature<FileOperationRegistrationOptions> & { send: (event: FileDeleteEvent) => Promise<void> }\n  public getFeature(request: typeof WillCreateFilesRequest.method): DynamicFeature<FileOperationRegistrationOptions> & { send: (event: FileWillCreateEvent) => Promise<void> }\n  public getFeature(request: typeof WillRenameFilesRequest.method): DynamicFeature<FileOperationRegistrationOptions> & { send: (event: FileWillRenameEvent) => Promise<void> }\n  public getFeature(request: typeof WillDeleteFilesRequest.method): DynamicFeature<FileOperationRegistrationOptions> & { send: (event: FileWillDeleteEvent) => Promise<void> }\n  public getFeature(request: typeof CompletionRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<CompletionItemProvider>\n  public getFeature(request: typeof HoverRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<HoverProvider>\n  public getFeature(request: typeof SignatureHelpRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<SignatureHelpProvider>\n  public getFeature(request: typeof DefinitionRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DefinitionProvider>\n  public getFeature(request: typeof ReferencesRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<ReferenceProvider>\n  public getFeature(request: typeof DocumentHighlightRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DocumentHighlightProvider>\n  public getFeature(request: typeof CodeActionRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<CodeActionProvider>\n  public getFeature(request: typeof CodeLensRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<CodeLensProviderShape>\n  public getFeature(request: typeof DocumentFormattingRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DocumentFormattingEditProvider>\n  public getFeature(request: typeof DocumentRangeFormattingRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DocumentRangeFormattingEditProvider>\n  public getFeature(request: typeof DocumentOnTypeFormattingRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<OnTypeFormattingEditProvider>\n  public getFeature(request: typeof RenameRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<RenameProvider>\n  public getFeature(request: typeof DocumentSymbolRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DocumentSymbolProvider>\n  public getFeature(request: typeof DocumentLinkRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DocumentLinkProvider>\n  public getFeature(request: typeof DocumentColorRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DocumentColorProvider>\n  public getFeature(request: typeof DeclarationRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DeclarationProvider>\n  public getFeature(request: typeof FoldingRangeRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<FoldingRangeProviderShape>\n  public getFeature(request: typeof ImplementationRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<ImplementationProvider>\n  public getFeature(request: typeof SelectionRangeRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<SelectionRangeProvider>\n  public getFeature(request: typeof TypeDefinitionRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<TypeDefinitionProvider>\n  public getFeature(request: typeof CallHierarchyPrepareRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<CallHierarchyProvider>\n  public getFeature(request: typeof SemanticTokensRegistrationType.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<SemanticTokensProviderShape>\n  public getFeature(request: typeof LinkedEditingRangeRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<LinkedEditingRangeProvider>\n  public getFeature(request: typeof TypeHierarchyPrepareRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<TypeHierarchyProvider>\n  public getFeature(request: typeof InlineCompletionRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<InlineCompletionItemProvider>\n  public getFeature(request: typeof InlineValueRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<InlineValueProviderShape>\n  public getFeature(request: typeof InlayHintRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<InlayHintsProviderShape>\n  public getFeature(request: typeof TextDocumentContentRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & WorkspaceProviderFeature<TextDocumentContentProviderShape>\n  public getFeature(request: typeof WorkspaceSymbolRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & WorkspaceProviderFeature<WorkspaceSymbolProvider>\n  public getFeature(request: typeof DocumentDiagnosticRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DiagnosticProviderShape> & DiagnosticFeatureShape\n  public getFeature(request: string): DynamicFeature<any> | undefined {\n    return this._dynamicFeatures.get(request)\n  }\n\n  protected registerBuiltinFeatures() {\n    this.registerFeature(new SyncConfigurationFeature(this), 'configuration')\n    this._didOpenTextDocumentFeature = new DidOpenTextDocumentFeature(this, this._syncedDocuments)\n    this.registerFeature(this._didOpenTextDocumentFeature, 'document')\n    this.registerFeature(new DidChangeTextDocumentFeature(this), 'document')\n    this.registerFeature(new DidCloseTextDocumentFeature(this, this._syncedDocuments), 'document')\n    this.registerFeature(new WillSaveFeature(this), 'willSave')\n    this.registerFeature(new WillSaveWaitUntilFeature(this), 'willSaveWaitUntil')\n    this.registerFeature(new DidSaveTextDocumentFeature(this), 'didSave')\n    this.registerFeature(new FileSystemWatcherFeature(this, this.notifyFileEvent.bind(this)), 'fileSystemWatcher')\n    this.registerFeature(new CompletionItemFeature(this), 'completion')\n    this.registerFeature(new HoverFeature(this), 'hover')\n    this.registerFeature(new SignatureHelpFeature(this), 'signatureHelp')\n    this.registerFeature(new ReferencesFeature(this), 'references')\n    this.registerFeature(new DefinitionFeature(this), 'definition')\n    this.registerFeature(new DocumentHighlightFeature(this), 'documentHighlight')\n    this.registerFeature(new DocumentSymbolFeature(this), 'documentSymbol')\n    this.registerFeature(new CodeActionFeature(this), 'codeAction')\n    this.registerFeature(new CodeLensFeature(this), 'codeLens')\n    this.registerFeature(new DocumentFormattingFeature(this), 'documentFormatting')\n    this.registerFeature(new DocumentRangeFormattingFeature(this), 'documentRangeFormatting')\n    this.registerFeature(new DocumentOnTypeFormattingFeature(this), 'documentOnTypeFormatting')\n    this.registerFeature(new RenameFeature(this), 'rename')\n    this.registerFeature(new DocumentLinkFeature(this), 'documentLink')\n    this.registerFeature(new ExecuteCommandFeature(this), 'executeCommand')\n    this.registerFeature(new PullConfigurationFeature(this), 'pullConfiguration')\n    this.registerFeature(new TypeDefinitionFeature(this), 'typeDefinition')\n    this.registerFeature(new ImplementationFeature(this), 'implementation')\n    this.registerFeature(new DeclarationFeature(this), 'declaration')\n    this.registerFeature(new ColorProviderFeature(this), 'colorProvider')\n    this.registerFeature(new FoldingRangeFeature(this), 'foldingRange')\n    this.registerFeature(new SelectionRangeFeature(this), 'selectionRange')\n    this.registerFeature(new CallHierarchyFeature(this), 'callHierarchy')\n    this.registerFeature(new ProgressFeature(this), 'progress')\n    this.registerFeature(new LinkedEditingFeature(this), 'linkedEditing')\n    this.registerFeature(new DidCreateFilesFeature(this), 'fileEvents')\n    this.registerFeature(new DidRenameFilesFeature(this), 'fileEvents')\n    this.registerFeature(new DidDeleteFilesFeature(this), 'fileEvents')\n    this.registerFeature(new WillCreateFilesFeature(this), 'fileEvents')\n    this.registerFeature(new WillRenameFilesFeature(this), 'fileEvents')\n    this.registerFeature(new WillDeleteFilesFeature(this), 'fileEvents')\n    this.registerFeature(new SemanticTokensFeature(this), 'semanticTokens')\n    this.registerFeature(new InlayHintsFeature(this), 'inlayHint')\n    this.registerFeature(new InlineCompletionItemFeature(this), 'inlineCompletion')\n    this.registerFeature(new TextDocumentContentFeature(this), 'textDocumentContent')\n    this.registerFeature(new InlineValueFeature(this), 'inlineValue')\n    this.registerFeature(new DiagnosticFeature(this), 'pullDiagnostic')\n    this.registerFeature(new TypeHierarchyFeature(this), 'typeHierarchy')\n    this.registerFeature(new WorkspaceSymbolFeature(this), 'workspaceSymbol')\n    // We only register the workspace folder feature if the client is not locked\n    // to a specific workspace folder.\n    if (this.clientOptions.workspaceFolder === undefined) {\n      this.registerFeature(new WorkspaceFoldersFeature(this), 'workspaceFolders')\n    }\n  }\n\n  public registerProposedFeatures() {\n    this.registerFeatures(ProposedFeatures.createAll(this))\n  }\n\n  private fillInitializeParams(params: InitializeParams): void {\n    for (let feature of this._features) {\n      if (Is.func(feature.fillInitializeParams)) {\n        feature.fillInitializeParams(params)\n      }\n    }\n  }\n\n  private computeClientCapabilities(): ClientCapabilities {\n    const result: ClientCapabilities = {}\n    ensure(result, 'workspace')!.applyEdit = true\n    const workspaceEdit = ensure(ensure(result, 'workspace')!, 'workspaceEdit')!\n    workspaceEdit.documentChanges = true\n    workspaceEdit.resourceOperations = [ResourceOperationKind.Create, ResourceOperationKind.Rename, ResourceOperationKind.Delete]\n    workspaceEdit.failureHandling = FailureHandlingKind.Undo\n    workspaceEdit.normalizesLineEndings = true\n    workspaceEdit.changeAnnotationSupport = {\n      groupsOnLabel: false\n    }\n    workspaceEdit.metadataSupport = true\n    workspaceEdit.snippetEditSupport = true\n\n    const diagnostics = ensure(ensure(result, 'textDocument')!, 'publishDiagnostics')!\n    diagnostics.relatedInformation = true\n    diagnostics.versionSupport = true\n    diagnostics.tagSupport = { valueSet: [DiagnosticTag.Unnecessary, DiagnosticTag.Deprecated] }\n    diagnostics.codeDescriptionSupport = true\n    diagnostics.dataSupport = true\n\n    const textDocumentFilter = ensure(ensure(result, 'textDocument')!, 'filters')!\n    textDocumentFilter.relativePatternSupport = true\n\n    const windowCapabilities = ensure(result, 'window')!\n    const showMessage = ensure(windowCapabilities, 'showMessage')!\n    showMessage.messageActionItem = { additionalPropertiesSupport: true }\n    const showDocument = ensure(windowCapabilities, 'showDocument')!\n    showDocument.support = true\n\n    const generalCapabilities = ensure(result, 'general')!\n    generalCapabilities.staleRequestSupport = {\n      cancel: true,\n      retryOnContentModified: Array.from(BaseLanguageClient.RequestsToCancelOnContentModified)\n    }\n    generalCapabilities.regularExpressions = { engine: 'ECMAScript', version: 'ES2020' }\n    generalCapabilities.markdown = { parser: 'marked', version: '7.0.5' }\n    generalCapabilities.positionEncodings = ['utf-16']\n    // Added in 3.17.0\n    if (this._clientOptions.markdown.supportHtml) {\n      generalCapabilities.markdown.allowedTags = ['ul', 'li', 'p', 'code', 'blockquote', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'em', 'pre', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'div', 'del', 'a', 'strong', 'br', 'span']\n    }\n    for (let feature of this._features) {\n      feature.fillClientCapabilities(result)\n    }\n    return result\n  }\n\n  private initializeFeatures(_connection: Connection): void {\n    let documentSelector = this._clientOptions.documentSelector\n    for (let feature of this._features) {\n      if (Is.func(feature.preInitialize)) {\n        feature.preInitialize(this._capabilities, documentSelector)\n      }\n    }\n    for (let feature of this._features) {\n      feature.initialize(this._capabilities, documentSelector)\n    }\n  }\n\n  private handleRegistrationRequest(params: RegistrationParams): Promise<void> {\n    if (this.clientOptions.disableDynamicRegister) return\n    const middleware = this.clientOptions.middleware.handleRegisterCapability\n    if (middleware) {\n      return middleware(params, nextParams => this.doRegisterCapability(nextParams))\n    } else {\n      return this.doRegisterCapability(params)\n    }\n  }\n\n  private async doRegisterCapability(params: RegistrationParams): Promise<void> {\n    // We will not receive a registration call before a client is running\n    // from a server. However if we stop or shutdown we might which might\n    // try to restart the server. So ignore registrations if we are not running\n    if (!this.isRunning()) {\n      for (const registration of params.registrations) {\n        this._ignoredRegistrations.add(registration.id)\n      }\n      return\n    }\n    for (const registration of params.registrations) {\n      const feature = this._dynamicFeatures.get(registration.method)\n      if (!feature) {\n        this.error(`No feature implementation for \"${registration.method}\" found. Registration failed.`, undefined, false)\n        return\n      }\n      const options = defaultValue(registration.registerOptions, {})\n      options.documentSelector = options.documentSelector ?? this._clientOptions.documentSelector\n      const data: RegistrationData<any> = {\n        id: registration.id,\n        registerOptions: options\n      }\n      feature.register(data)\n    }\n  }\n\n  private handleUnregistrationRequest(params: UnregistrationParams): Promise<void> {\n    const middleware = this._clientOptions.middleware.handleUnregisterCapability\n    if (middleware) {\n      return middleware(params, nextParams => this.doUnregisterCapability(nextParams))\n    } else {\n      return this.doUnregisterCapability(params)\n    }\n  }\n\n  private async doUnregisterCapability(params: UnregistrationParams): Promise<void> {\n    for (const unregistration of params.unregisterations) {\n      if (this._ignoredRegistrations.has(unregistration.id)) {\n        continue\n      }\n      const feature = this._dynamicFeatures.get(unregistration.method)\n      if (feature) feature.unregister(unregistration.id)\n    }\n  }\n\n  private handleDiagnostics(params: PublishDiagnosticsParams) {\n    let { uri, diagnostics, version } = params\n    if (Is.number(version) && !workspace.hasDocument(uri, version)) return\n    let middleware = this.clientOptions.middleware!.handleDiagnostics\n    if (middleware) {\n      middleware(uri, diagnostics, (uri, diagnostics) =>\n        this.setDiagnostics(uri, diagnostics)\n      )\n    } else {\n      this.setDiagnostics(uri, diagnostics)\n    }\n  }\n\n  private setDiagnostics(uri: string, diagnostics: Diagnostic[] | undefined) {\n    if (!this._diagnostics) return\n    this._diagnostics.set(uri, diagnostics)\n  }\n\n  private doHandleApplyWorkspaceEdit(params: ApplyWorkspaceEditParams): Promise<ApplyWorkspaceEditResult> {\n    return workspace.applyEdit(params.edit).then(applied => {\n      return { applied }\n    })\n  }\n\n  private async handleApplyWorkspaceEdit(params: ApplyWorkspaceEditParams): Promise<ApplyWorkspaceEditResult> {\n    const middleware = this.clientOptions.middleware.workspace?.handleApplyEdit\n    if (middleware) {\n      try {\n        let resultOrError = await Promise.resolve(middleware(params, nextParams => this.doHandleApplyWorkspaceEdit(nextParams)))\n        if (resultOrError instanceof ResponseError) {\n          throw resultOrError\n        }\n      } catch (error) {\n        this.error(`Error on apply workspace edit`, error, false)\n        return { applied: false }\n      }\n    } else {\n      return this.doHandleApplyWorkspaceEdit(params)\n    }\n  }\n\n  private static RequestsToCancelOnContentModified: Set<string> = new Set([\n    InlayHintRequest.method,\n    SemanticTokensRequest.method,\n    SemanticTokensRangeRequest.method,\n    SemanticTokensDeltaRequest.method\n  ])\n\n  public handleFailedRequest<T, P extends { method: string }>(type: P, token: CancellationToken | undefined, error: any, defaultValue: T, showNotification = true): T {\n    if (token && token.isCancellationRequested) return defaultValue\n    // If we get a request cancel or a content modified don't log anything.\n    if (error instanceof ResponseError) {\n      // The connection got disposed while we were waiting for a response.\n      // Simply return the default value. Is the best we can do.\n      if (error.code === ErrorCodes.PendingResponseRejected || error.code === ErrorCodes.ConnectionInactive) {\n        return defaultValue\n      }\n      if (error.code === LSPErrorCodes.RequestCancelled || error.code === LSPErrorCodes.ServerCancelled) {\n        if (error.data != null) {\n          throw new LSPCancellationError(error.data)\n        } else {\n          throw new CancellationError()\n        }\n      } else if (error.code === LSPErrorCodes.ContentModified) {\n        if (BaseLanguageClient.RequestsToCancelOnContentModified.has(type.method)) {\n          throw new CancellationError()\n        } else {\n          return defaultValue\n        }\n      }\n    }\n    this.error(`Request ${type.method} failed.`, error, showNotification)\n    throw error\n  }\n\n  // Should be keeped\n  public logFailedRequest(type: any, error: any): void {\n    // If we get a request cancel don't log anything.\n    if (\n      error instanceof ResponseError &&\n      error.code === LSPErrorCodes.RequestCancelled\n    ) {\n      return\n    }\n    this.error(`Request ${type.method} failed.`, error)\n  }\n\n  /**\n   * Return extension name or id.\n   */\n  public getExtensionName(): string {\n    if (this.__extensionName) return this.__extensionName\n    let name = parseExtensionName(toText(this['stack']))\n    if (name && name !== 'coc.nvim') {\n      this.__extensionName = name\n      return name\n    }\n    return this._id\n  }\n\n  /**\n   * Add __extensionName property to provider\n   */\n  public attachExtensionName<T extends object>(provider: T): void {\n    if (!provider.hasOwnProperty('__extensionName')) {\n      Object.defineProperty(provider, '__extensionName', {\n        get: () => this.getExtensionName(),\n        enumerable: true\n      })\n    }\n  }\n}\n\nconst ProposedFeatures = {\n  createAll: (_client: BaseLanguageClient): (StaticFeature | DynamicFeature<any>)[] => {\n    let result: (StaticFeature | DynamicFeature<any>)[] = []\n    return result\n  }\n}\n"
  },
  {
    "path": "src/language-client/codeAction.ts",
    "content": "'use strict'\nimport type { CancellationToken, ClientCapabilities, CodeAction, CodeActionContext, CodeActionOptions, CodeActionParams, CodeActionRegistrationOptions, Disposable, DocumentSelector, ExecuteCommandParams, Range, ServerCapabilities } from 'vscode-languageserver-protocol'\nimport { TextDocument } from \"vscode-languageserver-textdocument\"\nimport { CodeActionKind, Command } from 'vscode-languageserver-types'\nimport commands from '../commands'\nimport languages from '../languages'\nimport { CodeActionProvider, ProviderResult } from '../provider'\nimport { CodeActionRequest, CodeActionResolveRequest, ExecuteCommandRequest } from '../util/protocol'\nimport { ExecuteCommandMiddleware, ExecuteCommandSignature } from './executeCommand'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\nimport * as UUID from './utils/uuid'\n\nexport interface ProvideCodeActionsSignature {\n  (\n    this: void,\n    document: TextDocument,\n    range: Range,\n    context: CodeActionContext,\n    token: CancellationToken\n  ): ProviderResult<(Command | CodeAction)[]>\n}\n\nexport interface ResolveCodeActionSignature {\n  (this: void, item: CodeAction, token: CancellationToken): ProviderResult<CodeAction>\n}\n\nexport interface CodeActionMiddleware {\n  provideCodeActions?: (\n    this: void,\n    document: TextDocument,\n    range: Range,\n    context: CodeActionContext,\n    token: CancellationToken,\n    next: ProvideCodeActionsSignature\n  ) => ProviderResult<(Command | CodeAction)[]>\n  resolveCodeAction?: (\n    this: void,\n    item: CodeAction,\n    token: CancellationToken,\n    next: ResolveCodeActionSignature\n  ) => ProviderResult<CodeAction>\n}\n\nexport class CodeActionFeature extends TextDocumentLanguageFeature<boolean | CodeActionOptions, CodeActionRegistrationOptions, CodeActionProvider, CodeActionMiddleware & ExecuteCommandMiddleware> {\n  private disposables: Disposable[] = []\n  constructor(client: FeatureClient<CodeActionMiddleware>) {\n    super(client, CodeActionRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    const cap = ensure(ensure(capabilities, 'textDocument')!, 'codeAction')!\n    cap.dynamicRegistration = true\n    cap.isPreferredSupport = true\n    cap.disabledSupport = true\n    cap.dataSupport = true\n    cap.honorsChangeAnnotations = false\n    cap.resolveSupport = {\n      properties: ['edit']\n    }\n    cap.codeActionLiteralSupport = {\n      codeActionKind: {\n        valueSet: [\n          CodeActionKind.Empty,\n          CodeActionKind.QuickFix,\n          CodeActionKind.Refactor,\n          CodeActionKind.RefactorExtract,\n          CodeActionKind.RefactorInline,\n          CodeActionKind.RefactorRewrite,\n          CodeActionKind.Source,\n          CodeActionKind.SourceOrganizeImports\n        ]\n      }\n    }\n  }\n\n  public initialize(\n    capabilities: ServerCapabilities,\n    documentSelector: DocumentSelector\n  ): void {\n    const options = this.getRegistrationOptions(documentSelector, capabilities.codeActionProvider)\n    if (!options) {\n      return\n    }\n\n    this.register({\n      id: UUID.generateUuid(),\n      registerOptions: options\n    })\n  }\n\n  protected registerLanguageProvider(\n    options: CodeActionRegistrationOptions\n  ): [Disposable, CodeActionProvider] {\n    const registerCommand = (id: string) => {\n      const client = this._client\n      const executeCommand: ExecuteCommandSignature = (command: string, args: any[]): any => {\n        const params: ExecuteCommandParams = {\n          command,\n          arguments: args\n        }\n        return client.sendRequest(ExecuteCommandRequest.type, params)\n      }\n      this.disposables.push(commands.registerCommand(id, (...args: any[]) => {\n        return this.sendWithMiddleware(executeCommand, 'executeCommand', id, args)\n      }, null, true))\n    }\n    const provider: CodeActionProvider = {\n      provideCodeActions: (document, range, context, token) => {\n        const client = this._client\n        const _provideCodeActions: ProvideCodeActionsSignature = (document, range, context, token) => {\n          const params: CodeActionParams = {\n            textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),\n            range,\n            context,\n          }\n          return this.sendRequest(CodeActionRequest.type, params, token).then(\n            values => {\n              if (!values) return undefined\n              // some server may not registered commands to client.\n              values.forEach(val => {\n                let cmd = Command.is(val) ? val.command : val.command?.command\n                if (cmd && !commands.has(cmd)) registerCommand(cmd)\n              })\n              return values\n            }\n          )\n        }\n        const middleware = client.middleware!\n        return middleware.provideCodeActions\n          ? middleware.provideCodeActions(document, range, context, token, _provideCodeActions)\n          : _provideCodeActions(document, range, context, token)\n      },\n      resolveCodeAction: options.resolveProvider\n        ? (item: CodeAction, token: CancellationToken) => {\n          const middleware = this._client.middleware!\n          const resolveCodeAction: ResolveCodeActionSignature = (item, token) => {\n            return this.sendRequest(CodeActionResolveRequest.type, item, token, item)\n          }\n          return middleware.resolveCodeAction\n            ? middleware.resolveCodeAction(item, token, resolveCodeAction)\n            : resolveCodeAction(item, token)\n        }\n        : undefined\n    }\n    this._client.attachExtensionName(provider)\n    return [languages.registerCodeActionProvider(options.documentSelector, provider, this._client.id, options.codeActionKinds), provider]\n  }\n\n  public dispose(): void {\n    this.disposables.forEach(o => {\n      o.dispose()\n    })\n    this.disposables = []\n    super.dispose()\n  }\n}\n"
  },
  {
    "path": "src/language-client/codeLens.ts",
    "content": "'use strict'\nimport type { CancellationToken, ClientCapabilities, CodeLens, CodeLensOptions, CodeLensRegistrationOptions, Disposable, DocumentSelector, ServerCapabilities } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport languages from '../languages'\nimport { CodeLensProvider, ProviderResult } from '../provider'\nimport { CodeLensRefreshRequest, CodeLensRequest, CodeLensResolveRequest, Emitter } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\nimport * as UUID from './utils/uuid'\n\nexport interface ProvideCodeLensesSignature {\n  (this: void, document: TextDocument, token: CancellationToken): ProviderResult<CodeLens[]>\n}\n\nexport interface ResolveCodeLensSignature {\n  (this: void, codeLens: CodeLens, token: CancellationToken): ProviderResult<CodeLens>\n}\n\nexport interface CodeLensMiddleware {\n  provideCodeLenses?: (this: void, document: TextDocument, token: CancellationToken, next: ProvideCodeLensesSignature) => ProviderResult<CodeLens[]>\n  resolveCodeLens?: (this: void, codeLens: CodeLens, token: CancellationToken, next: ResolveCodeLensSignature) => ProviderResult<CodeLens>\n}\n\nexport interface CodeLensProviderShape {\n  provider?: CodeLensProvider\n  onDidChangeCodeLensEmitter: Emitter<void>\n}\n\nexport class CodeLensFeature extends TextDocumentLanguageFeature<CodeLensOptions, CodeLensRegistrationOptions, CodeLensProviderShape, CodeLensMiddleware> {\n\n  constructor(client: FeatureClient<CodeLensMiddleware>) {\n    super(client, CodeLensRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    ensure(ensure(capabilities, 'textDocument')!, 'codeLens')!.dynamicRegistration = true\n    ensure(ensure(capabilities, 'workspace')!, 'codeLens')!.refreshSupport = true\n  }\n\n  public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {\n    const client = this._client\n    client.onRequest(CodeLensRefreshRequest.type, async () => {\n      for (const provider of this.getAllProviders()) {\n        provider.onDidChangeCodeLensEmitter.fire()\n      }\n    })\n    const options = this.getRegistrationOptions(documentSelector, capabilities.codeLensProvider)\n    if (!options) return\n    this.register({ id: UUID.generateUuid(), registerOptions: options })\n  }\n\n  protected registerLanguageProvider(options: CodeLensRegistrationOptions): [Disposable, CodeLensProviderShape] {\n    const emitter: Emitter<void> = new Emitter<void>()\n    const provider: CodeLensProvider = {\n      onDidChangeCodeLenses: emitter.event,\n      provideCodeLenses: (document, token) => {\n        const client = this._client\n        const provideCodeLenses: ProvideCodeLensesSignature = (document, token) => {\n          return this.sendRequest(\n            CodeLensRequest.type,\n            client.code2ProtocolConverter.asCodeLensParams(document),\n            token\n          )\n        }\n        const middleware = client.middleware!\n        return middleware.provideCodeLenses\n          ? middleware.provideCodeLenses(document, token, provideCodeLenses)\n          : provideCodeLenses(document, token)\n      },\n      resolveCodeLens: (options.resolveProvider)\n        ? (codeLens: CodeLens, token: CancellationToken): ProviderResult<CodeLens> => {\n          const client = this._client\n          const resolveCodeLens: ResolveCodeLensSignature = (codeLens, token) => {\n            return this.sendRequest(\n              CodeLensResolveRequest.type,\n              codeLens,\n              token,\n              codeLens\n            )\n          }\n          const middleware = client.middleware!\n          return middleware.resolveCodeLens\n            ? middleware.resolveCodeLens(codeLens, token, resolveCodeLens)\n            : resolveCodeLens(codeLens, token)\n        }\n        : undefined\n    }\n    this._client.attachExtensionName(provider)\n\n    return [languages.registerCodeLensProvider(options.documentSelector, provider), { provider, onDidChangeCodeLensEmitter: emitter }]\n  }\n}\n"
  },
  {
    "path": "src/language-client/colorProvider.ts",
    "content": "'use strict'\nimport type { CancellationToken, ClientCapabilities, Color, ColorInformation, ColorPresentation, ColorPresentationParams, Disposable, DocumentColorOptions, DocumentColorParams, DocumentColorRegistrationOptions, DocumentSelector, Range, ServerCapabilities } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport languages from '../languages'\nimport { DocumentColorProvider, ProviderResult } from '../provider'\nimport { ColorPresentationRequest, DocumentColorRequest } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\n\nexport type ProvideDocumentColorsSignature = (document: TextDocument, token: CancellationToken) => ProviderResult<ColorInformation[]>\n\nexport type ProvideColorPresentationSignature = (\n  color: Color,\n  context: { document: TextDocument; range: Range },\n  token: CancellationToken\n) => ProviderResult<ColorPresentation[]>\n\nexport interface ColorProviderMiddleware {\n  provideDocumentColors?: (\n    this: void,\n    document: TextDocument,\n    token: CancellationToken,\n    next: ProvideDocumentColorsSignature\n  ) => ProviderResult<ColorInformation[]>\n  provideColorPresentations?: (\n    this: void,\n    color: Color,\n    context: { document: TextDocument; range: Range },\n    token: CancellationToken,\n    next: ProvideColorPresentationSignature\n  ) => ProviderResult<ColorPresentation[]>\n}\n\nexport class ColorProviderFeature extends TextDocumentLanguageFeature<\n  boolean | DocumentColorOptions, DocumentColorRegistrationOptions, DocumentColorProvider, ColorProviderMiddleware\n> {\n  constructor(client: FeatureClient<ColorProviderMiddleware>) {\n    super(client, DocumentColorRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    ensure(ensure(capabilities, 'textDocument')!, 'colorProvider')!.dynamicRegistration = true\n  }\n\n  public initialize(\n    capabilities: ServerCapabilities,\n    documentSelector: DocumentSelector\n  ): void {\n    let [id, options] = this.getRegistration(documentSelector, capabilities.colorProvider)\n    if (!id || !options) {\n      return\n    }\n\n    this.register({ id, registerOptions: options })\n  }\n\n  protected registerLanguageProvider(\n    options: DocumentColorRegistrationOptions\n  ): [Disposable, DocumentColorProvider] {\n    const provider: DocumentColorProvider = {\n      provideColorPresentations: (color, context, token) => {\n        const client = this._client\n        const provideColorPresentations: ProvideColorPresentationSignature = (color, context, token) => {\n          const requestParams: ColorPresentationParams = {\n            color,\n            textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(context.document),\n            range: context.range\n          }\n          return this.sendRequest(ColorPresentationRequest.type, requestParams, token)\n        }\n        const middleware = client.middleware\n        return middleware.provideColorPresentations\n          ? middleware.provideColorPresentations(color, context, token, provideColorPresentations)\n          : provideColorPresentations(color, context, token)\n      },\n      provideDocumentColors: (document, token) => {\n        const client = this._client\n        const provideDocumentColors: ProvideDocumentColorsSignature = (document, token) => {\n          const requestParams: DocumentColorParams = {\n            textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document)\n          }\n          return this.sendRequest(DocumentColorRequest.type, requestParams, token)\n        }\n        const middleware = client.middleware\n        return middleware.provideDocumentColors\n          ? middleware.provideDocumentColors(document, token, provideDocumentColors)\n          : provideDocumentColors(document, token)\n      }\n    }\n\n    this._client.attachExtensionName(provider)\n    return [languages.registerDocumentColorProvider(options.documentSelector, provider), provider]\n  }\n}\n"
  },
  {
    "path": "src/language-client/completion.ts",
    "content": "'use strict'\nimport type { CancellationToken, ClientCapabilities, CompletionContext, CompletionOptions, CompletionRegistrationOptions, DocumentSelector, ServerCapabilities } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { CompletionItem, CompletionItemKind, CompletionItemTag, CompletionList, InsertTextMode, Position } from 'vscode-languageserver-types'\nimport languages from '../languages'\nimport { CompletionItemProvider, ProviderResult } from '../provider'\nimport { CompletionRequest, CompletionResolveRequest, Disposable } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\nimport * as UUID from './utils/uuid'\n\nconst SupportedCompletionItemKinds: CompletionItemKind[] = [\n  CompletionItemKind.Text,\n  CompletionItemKind.Method,\n  CompletionItemKind.Function,\n  CompletionItemKind.Constructor,\n  CompletionItemKind.Field,\n  CompletionItemKind.Variable,\n  CompletionItemKind.Class,\n  CompletionItemKind.Interface,\n  CompletionItemKind.Module,\n  CompletionItemKind.Property,\n  CompletionItemKind.Unit,\n  CompletionItemKind.Value,\n  CompletionItemKind.Enum,\n  CompletionItemKind.Keyword,\n  CompletionItemKind.Snippet,\n  CompletionItemKind.Color,\n  CompletionItemKind.File,\n  CompletionItemKind.Reference,\n  CompletionItemKind.Folder,\n  CompletionItemKind.EnumMember,\n  CompletionItemKind.Constant,\n  CompletionItemKind.Struct,\n  CompletionItemKind.Event,\n  CompletionItemKind.Operator,\n  CompletionItemKind.TypeParameter\n]\n\nexport interface ProvideCompletionItemsSignature {\n  (\n    this: void,\n    document: TextDocument,\n    position: Position,\n    context: CompletionContext,\n    token: CancellationToken,\n  ): ProviderResult<CompletionItem[] | CompletionList>\n}\n\nexport interface ResolveCompletionItemSignature {\n  (this: void, item: CompletionItem, token: CancellationToken): ProviderResult<CompletionItem>\n}\n\nexport interface CompletionMiddleware {\n  provideCompletionItem?: (\n    this: void,\n    document: TextDocument,\n    position: Position,\n    context: CompletionContext,\n    token: CancellationToken,\n    next: ProvideCompletionItemsSignature\n  ) => ProviderResult<CompletionItem[] | CompletionList>\n  resolveCompletionItem?: (\n    this: void,\n    item: CompletionItem,\n    token: CancellationToken,\n    next: ResolveCompletionItemSignature\n  ) => ProviderResult<CompletionItem>\n}\n\nexport interface $CompletionOptions {\n  disableSnippetCompletion?: boolean\n}\n\nexport class CompletionItemFeature extends TextDocumentLanguageFeature<CompletionOptions, CompletionRegistrationOptions, CompletionItemProvider, CompletionMiddleware, $CompletionOptions> {\n  constructor(client: FeatureClient<CompletionMiddleware, $CompletionOptions>) {\n    super(client, CompletionRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    let snippetSupport = this._client.clientOptions.disableSnippetCompletion !== true\n    let completion = ensure(ensure(capabilities, 'textDocument')!, 'completion')!\n    completion.dynamicRegistration = true\n    completion.contextSupport = true\n    completion.completionItem = {\n      snippetSupport,\n      commitCharactersSupport: true,\n      documentationFormat: this._client.supportedMarkupKind,\n      deprecatedSupport: true,\n      preselectSupport: true,\n      insertReplaceSupport: true,\n      tagSupport: { valueSet: [CompletionItemTag.Deprecated] },\n      resolveSupport: { properties: ['documentation', 'detail', 'additionalTextEdits'] },\n      labelDetailsSupport: true,\n      insertTextModeSupport: { valueSet: [InsertTextMode.asIs, InsertTextMode.adjustIndentation] }\n    }\n    completion.completionItemKind = { valueSet: SupportedCompletionItemKinds }\n    completion.insertTextMode = InsertTextMode.adjustIndentation\n    completion.completionList = {\n      itemDefaults: [\n        'commitCharacters', 'editRange', 'insertTextFormat', 'insertTextMode'\n      ]\n    }\n  }\n\n  public initialize(\n    capabilities: ServerCapabilities,\n    documentSelector: DocumentSelector\n  ): void {\n    const options = this.getRegistrationOptions(documentSelector, capabilities.completionProvider)\n    if (!options) return\n    this.register({\n      id: UUID.generateUuid(),\n      registerOptions: options\n    })\n  }\n\n  protected registerLanguageProvider(options: CompletionRegistrationOptions & { priority?: number }, id: string): [Disposable, CompletionItemProvider] {\n    let triggerCharacters = options.triggerCharacters || []\n    let allCommitCharacters = options.allCommitCharacters || []\n    const provider: CompletionItemProvider = {\n      provideCompletionItems: (document: TextDocument, position: Position, token: CancellationToken, context: CompletionContext): ProviderResult<CompletionList | CompletionItem[]> => {\n        const middleware = this._client.middleware\n        const provideCompletionItems: ProvideCompletionItemsSignature = (document, position, context, token) => {\n          return this.sendRequest(\n            CompletionRequest.type,\n            this._client.code2ProtocolConverter.asCompletionParams(document, position, context),\n            token,\n            []\n          )\n        }\n        return middleware.provideCompletionItem\n          ? middleware.provideCompletionItem(document, position, context, token, provideCompletionItems)\n          : provideCompletionItems(document, position, context, token)\n      },\n      resolveCompletionItem: options.resolveProvider\n        ? (item: CompletionItem, token: CancellationToken): ProviderResult<CompletionItem> => {\n          const middleware = this._client.middleware!\n          const resolveCompletionItem: ResolveCompletionItemSignature = (item, token) => {\n            return this.sendRequest(\n              CompletionResolveRequest.type,\n              item,\n              token,\n              item\n            )\n          }\n\n          return middleware.resolveCompletionItem\n            ? middleware.resolveCompletionItem(item, token, resolveCompletionItem)\n            : resolveCompletionItem(item, token)\n        } : undefined\n    }\n    this._client.attachExtensionName(provider)\n    // index is needed since one language server could create many sources.\n    let name = this._client.id + (this.registrationLength == 0 ? '' : '-' + id)\n    const disposable = languages.registerCompletionItemProvider(\n      name,\n      'LS',\n      options.documentSelector,\n      provider,\n      triggerCharacters,\n      options.priority,\n      allCommitCharacters)\n    return [disposable, provider]\n  }\n}\n"
  },
  {
    "path": "src/language-client/configuration.ts",
    "content": "'use strict'\nimport type { ClientCapabilities, DidChangeConfigurationRegistrationOptions, Disposable, RegistrationType, WorkspaceFolder } from 'vscode-languageserver-protocol'\nimport { IConfigurationChangeEvent, WorkspaceConfiguration } from '../configuration/types'\nimport { mergeConfigProperties, toJSONObject } from '../configuration/util'\nimport { IFileSystemWatcher } from '../types'\nimport { defaultValue } from '../util'\nimport * as Is from '../util/is'\nimport {\n  ConfigurationRequest,\n  DidChangeConfigurationNotification\n} from '../util/protocol'\nimport workspace from '../workspace'\nimport { DynamicFeature, ensure, FeatureClient, FeatureState, RegistrationData, StaticFeature } from './features'\nimport * as UUID from './utils/uuid'\n\nexport interface ConfigurationMiddleware {\n  configuration?: ConfigurationRequest.MiddlewareSignature\n}\n\ninterface ConfigurationWorkspaceMiddleware {\n  workspace?: ConfigurationMiddleware\n}\n\nexport interface SynchronizeOptions {\n  /**\n   * The configuration sections to synchronize. Pushing settings from the\n   * client to the server is deprecated in favour of the new pull model\n   * that allows servers to query settings scoped on resources. In this\n   * model the client can only deliver an empty change event since the\n   * actually setting value can vary on the provided resource scope.\n   * @deprecated Use the new pull model (`workspace/configuration` request)\n   */\n  configurationSection?: string | string[]\n\n  /**\n   * Asks the client to send file change events to the server. Watchers\n   * operate on workspace folders. The LSP client doesn't support watching\n   * files outside a workspace folder.\n   */\n  fileEvents?: IFileSystemWatcher | IFileSystemWatcher[]\n}\n\nexport interface $ConfigurationOptions {\n  synchronize?: SynchronizeOptions\n  workspaceFolder?: WorkspaceFolder\n}\n\nexport class PullConfigurationFeature implements StaticFeature {\n  constructor(private _client: FeatureClient<ConfigurationWorkspaceMiddleware, $ConfigurationOptions>) {\n  }\n\n  public get method(): string {\n    return ConfigurationRequest.method\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    ensure(capabilities, 'workspace').configuration = true\n  }\n\n  public getState(): FeatureState {\n    return { kind: 'static' }\n  }\n\n  public initialize(): void {\n    let client = this._client\n    let { configuredSection } = client\n    client.onRequest(ConfigurationRequest.type, (params, token) => {\n      let configuration: ConfigurationRequest.HandlerSignature = params => {\n        let result: any[] = []\n        for (let item of params.items) {\n          let section = configuredSection ? configuredSection + (item.section ? `.${item.section}` : '') : item.section\n          result.push(this.getConfiguration(item.scopeUri, section))\n        }\n        return result\n      }\n      let middleware = client.middleware.workspace\n      return middleware?.configuration\n        ? middleware.configuration(params, token, configuration)\n        : configuration(params, token)\n    })\n  }\n\n  private getConfiguration(resource: string | undefined, section: string | undefined): any {\n    let result: any = null\n    if (section) {\n      let index = section.lastIndexOf('.')\n      if (index === -1) {\n        result = toJSONObject(workspace.getConfiguration(undefined, resource).get(section))\n      } else {\n        let config = workspace.getConfiguration(section.substr(0, index), resource)\n        result = toJSONObject(mergeConfigProperties(config))[section.substr(index + 1)]\n      }\n    } else {\n      let config = workspace.getConfiguration(section, resource)\n      result = {}\n      for (let key of Object.keys(config)) {\n        if (config.has(key)) {\n          result[key] = toJSONObject(config.get(key))\n        }\n      }\n    }\n    return result ?? null\n  }\n\n  public dispose(): void {\n  }\n}\n\nexport interface DidChangeConfigurationSignature {\n  (this: void, sections: string[] | undefined): Promise<void>\n}\n\nexport interface DidChangeConfigurationMiddleware {\n  didChangeConfiguration?: (this: void, sections: string[] | undefined, next: DidChangeConfigurationSignature) => Promise<void>\n}\n\ninterface DidChangeConfigurationWorkspaceMiddleware {\n  workspace?: DidChangeConfigurationMiddleware\n}\n\nexport class SyncConfigurationFeature implements DynamicFeature<DidChangeConfigurationRegistrationOptions> {\n  private _listeners: Map<string, Disposable> = new Map<string, Disposable>()\n  private configuredUID: string | undefined\n\n  constructor(private _client: FeatureClient<DidChangeConfigurationWorkspaceMiddleware, $ConfigurationOptions>) {}\n\n  public getState(): FeatureState {\n    return { kind: 'workspace', id: this.registrationType.method, registrations: this._listeners.size > 0 }\n  }\n\n  public get registrationType(): RegistrationType<DidChangeConfigurationRegistrationOptions> {\n    return DidChangeConfigurationNotification.type\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    ensure(ensure(capabilities, 'workspace')!, 'didChangeConfiguration')!.dynamicRegistration = true\n  }\n\n  public initialize(): void {\n    let section = defaultValue(this._client.clientOptions.synchronize, {}).configurationSection\n    if (section !== undefined) {\n      let id = this.configuredUID = UUID.generateUuid()\n      this.register({\n        id,\n        registerOptions: {\n          section\n        }\n      })\n    }\n  }\n\n  public register(\n    data: RegistrationData<DidChangeConfigurationRegistrationOptions>\n  ): void {\n    if (this._client.configuredSection && data.id !== this.configuredUID) return\n    let { section } = data.registerOptions\n    let disposable = workspace.onDidChangeConfiguration(event => {\n      this.onDidChangeConfiguration(section, event)\n    })\n    this._listeners.set(data.id, disposable)\n    if (section !== undefined) {\n      this.onDidChangeConfiguration(section, undefined)\n    }\n  }\n\n  public unregister(id: string): void {\n    let disposable = this._listeners.get(id)\n    if (disposable) {\n      this._listeners.delete(id)\n      disposable.dispose()\n    }\n  }\n\n  public dispose(): void {\n    for (let disposable of this._listeners.values()) {\n      disposable.dispose()\n    }\n    this._listeners.clear()\n  }\n\n  private onDidChangeConfiguration(configurationSection: string | string[] | undefined, event: IConfigurationChangeEvent | undefined): void {\n    let { configuredSection } = this._client\n    let sections: string[] | undefined\n    if (Is.string(configurationSection)) {\n      sections = [configurationSection]\n    } else {\n      sections = configurationSection\n    }\n    if (sections != null && event != null) {\n      let keys = sections.map(s => s.startsWith('languageserver.') ? 'languageserver' : s)\n      let affected = keys.some(section => event.affectsConfiguration(section))\n      if (!affected) return\n    }\n    let didChangeConfiguration = (sections: string[] | undefined): Promise<void> => {\n      if (sections == null) {\n        return this._client.sendNotification(DidChangeConfigurationNotification.type, { settings: null })\n      }\n      let workspaceFolder = this._client.clientOptions.workspaceFolder\n      let settings = configuredSection ? SyncConfigurationFeature.getConfiguredSettings(configuredSection, workspaceFolder) : SyncConfigurationFeature.extractSettingsInformation(sections, workspaceFolder)\n      return this._client.sendNotification(DidChangeConfigurationNotification.type, { settings })\n    }\n    let middleware = this._client.middleware.workspace?.didChangeConfiguration\n    let promise = middleware ? Promise.resolve(middleware(sections, didChangeConfiguration)) : didChangeConfiguration(sections)\n    promise.catch(error => {\n      this._client.error(`Sending notification ${DidChangeConfigurationNotification.type.method} failed`, error)\n    })\n  }\n\n  public static getConfiguredSettings(key: string, workspaceFolder: WorkspaceFolder | undefined): any {\n    let len = '.settings'.length\n    let config = workspace.getConfiguration(key.slice(0, - len), workspaceFolder)\n    return mergeConfigProperties(toJSONObject(config.get<any>('settings', {})))\n  }\n\n  public static extractSettingsInformation(keys: string[], workspaceFolder?: WorkspaceFolder): any {\n    function ensurePath(config: any, path: string[]): any {\n      let current = config\n      for (let i = 0; i < path.length - 1; i++) {\n        let obj = current[path[i]]\n        if (!obj) {\n          obj = Object.create(null)\n          current[path[i]] = obj\n        }\n        current = obj\n      }\n      return current\n    }\n    let result = Object.create(null)\n    for (let i = 0; i < keys.length; i++) {\n      let key = keys[i]\n      let index: number = key.indexOf('.')\n      let config: WorkspaceConfiguration\n      if (index >= 0) {\n        config = workspace.getConfiguration(key.substr(0, index), workspaceFolder).get(key.substr(index + 1))\n      } else {\n        config = workspace.getConfiguration(key, workspaceFolder)\n      }\n      let path = keys[i].split('.')\n      ensurePath(result, path)[path[path.length - 1]] = config\n    }\n    return result\n  }\n}\n"
  },
  {
    "path": "src/language-client/declaration.ts",
    "content": "'use strict'\nimport type { CancellationToken, ClientCapabilities, Declaration, DeclarationLink, DeclarationOptions, DeclarationRegistrationOptions, Disposable, DocumentSelector, Position, ServerCapabilities } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport languages from '../languages'\nimport { DeclarationProvider, ProviderResult } from '../provider'\nimport { DeclarationRequest } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\n\nexport interface ProvideDeclarationSignature {\n  (this: void, document: TextDocument, position: Position, token: CancellationToken): ProviderResult<Declaration | DeclarationLink[]>\n}\n\nexport interface DeclarationMiddleware {\n  provideDeclaration?: (this: void, document: TextDocument, position: Position, token: CancellationToken, next: ProvideDeclarationSignature) => ProviderResult<Declaration | DeclarationLink[]>\n}\n\nexport class DeclarationFeature extends TextDocumentLanguageFeature<boolean | DeclarationOptions, DeclarationRegistrationOptions, DeclarationProvider, DeclarationMiddleware> {\n\n  constructor(client: FeatureClient<DeclarationMiddleware>) {\n    super(client, DeclarationRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    let declarationSupport = ensure(ensure(capabilities, 'textDocument')!, 'declaration')!\n    declarationSupport.dynamicRegistration = true\n    declarationSupport.linkSupport = true\n  }\n\n  public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {\n    const [id, options] = this.getRegistration(documentSelector, capabilities.declarationProvider)\n    if (!id || !options) {\n      return\n    }\n    this.register({ id, registerOptions: options })\n  }\n\n  protected registerLanguageProvider(options: DeclarationRegistrationOptions): [Disposable, DeclarationProvider] {\n    const provider: DeclarationProvider = {\n      provideDeclaration: (document: TextDocument, position: Position, token: CancellationToken) => {\n        const client = this._client\n        const provideDeclaration: ProvideDeclarationSignature = (document, position, token) =>\n          this.sendRequest(DeclarationRequest.type, client.code2ProtocolConverter.asTextDocumentPositionParams(document, position), token)\n        const middleware = client.middleware\n        return middleware.provideDeclaration ? middleware.provideDeclaration(document, position, token, provideDeclaration)\n          : provideDeclaration(document, position, token)\n      }\n    }\n\n    this._client.attachExtensionName(provider)\n    return [languages.registerDeclarationProvider(options.documentSelector, provider), provider]\n  }\n}\n"
  },
  {
    "path": "src/language-client/definition.ts",
    "content": "'use strict'\nimport type { CancellationToken, ClientCapabilities, Definition, DefinitionLink, DefinitionOptions, DefinitionRegistrationOptions, Disposable, DocumentSelector, Position, ServerCapabilities } from 'vscode-languageserver-protocol'\nimport { TextDocument } from \"vscode-languageserver-textdocument\"\nimport languages from '../languages'\nimport { DefinitionProvider, ProviderResult } from '../provider'\nimport { DefinitionRequest } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\nimport * as UUID from './utils/uuid'\n\nexport interface ProvideDefinitionSignature {\n  (\n    this: void,\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken\n  ): ProviderResult<Definition | DefinitionLink[]>\n}\n\nexport interface DefinitionMiddleware {\n  provideDefinition?: (\n    this: void,\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken,\n    next: ProvideDefinitionSignature\n  ) => ProviderResult<Definition | DefinitionLink[]>\n}\n\nexport class DefinitionFeature extends TextDocumentLanguageFeature<\n  boolean | DefinitionOptions, DefinitionRegistrationOptions, DefinitionProvider, DefinitionMiddleware\n> {\n  constructor(client: FeatureClient<DefinitionMiddleware>) {\n    super(client, DefinitionRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    let definitionSupport = ensure(ensure(capabilities, 'textDocument')!, 'definition')!\n    definitionSupport.dynamicRegistration = true\n    definitionSupport.linkSupport = true\n  }\n\n  public initialize(\n    capabilities: ServerCapabilities,\n    documentSelector: DocumentSelector\n  ): void {\n    const options = this.getRegistrationOptions(documentSelector, capabilities.definitionProvider)\n    if (!options) {\n      return\n    }\n    this.register({\n      id: UUID.generateUuid(),\n      registerOptions: options\n    })\n  }\n\n  protected registerLanguageProvider(\n    options: DefinitionRegistrationOptions\n  ): [Disposable, DefinitionProvider] {\n    const provider: DefinitionProvider = {\n      provideDefinition: (document, position, token) => {\n        const client = this._client\n        const provideDefinition: ProvideDefinitionSignature = (document, position, token) => {\n          return this.sendRequest(\n            DefinitionRequest.type,\n            client.code2ProtocolConverter.asTextDocumentPositionParams(document, position),\n            token\n          )\n        }\n        const middleware = client.middleware!\n        return middleware.provideDefinition\n          ? middleware.provideDefinition(document, position, token, provideDefinition)\n          : provideDefinition(document, position, token)\n      }\n    }\n\n    this._client.attachExtensionName(provider)\n    return [languages.registerDefinitionProvider(options.documentSelector!, provider), provider]\n  }\n}\n"
  },
  {
    "path": "src/language-client/diagnostic.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport type {\n  CancellationToken, ClientCapabilities, Diagnostic, DiagnosticOptions, DiagnosticRegistrationOptions, DocumentDiagnosticParams, DocumentDiagnosticReport, DocumentSelector, PreviousResultId, ServerCapabilities, WorkspaceDiagnosticParams, WorkspaceDiagnosticReport, WorkspaceDiagnosticReportPartialResult\n} from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { DiagnosticTag } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport DiagnosticCollection from '../diagnostic/collection'\nimport languages from '../languages'\nimport { DiagnosticProvider, ProviderResult, ResultReporter } from '../provider'\nimport { TextDocumentMatch } from '../types'\nimport { defaultValue, getConditionValue } from '../util'\nimport { CancellationError } from '../util/errors'\nimport { LinkedMap, Touch } from '../util/map'\nimport { minimatch } from '../util/node'\nimport { CancellationTokenSource, DiagnosticRefreshRequest, DiagnosticServerCancellationData, DidChangeTextDocumentNotification, DidCloseTextDocumentNotification, DidOpenTextDocumentNotification, DidSaveTextDocumentNotification, Disposable, DocumentDiagnosticReportKind, DocumentDiagnosticRequest, Emitter, RAL, WorkspaceDiagnosticRequest } from '../util/protocol'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { BaseFeature, ensure, FeatureClient, LSPCancellationError, TextDocumentLanguageFeature } from './features'\n\ninterface HandleDiagnosticsSignature {\n  (this: void, uri: string, diagnostics: Diagnostic[]): void\n}\nexport type ProvideDiagnosticSignature = (this: void, document: TextDocument | URI, previousResultId: string | undefined, token: CancellationToken) => ProviderResult<DocumentDiagnosticReport>\n\nexport type ProvideWorkspaceDiagnosticSignature = (this: void, resultIds: PreviousResultId[], token: CancellationToken, resultReporter: ResultReporter) => ProviderResult<WorkspaceDiagnosticReport>\n\nexport interface DiagnosticProviderMiddleware {\n  handleDiagnostics?: (this: void, uri: string, diagnostics: Diagnostic[], next: HandleDiagnosticsSignature) => void\n  provideDiagnostics?: (this: void, document: TextDocument | URI, previousResultId: string | undefined, token: CancellationToken, next: ProvideDiagnosticSignature) => ProviderResult<DocumentDiagnosticReport>\n  provideWorkspaceDiagnostics?: (this: void, resultIds: PreviousResultId[], token: CancellationToken, resultReporter: ResultReporter, next: ProvideWorkspaceDiagnosticSignature) => ProviderResult<WorkspaceDiagnosticReport>\n}\n\nexport interface DiagnosticProviderShape {\n  /**\n   * An event that signals that the diagnostics should be refreshed for\n   * all documents.\n   */\n  onDidChangeDiagnosticsEmitter: Emitter<void>\n  /**\n   * The provider of diagnostics.\n   */\n  diagnostics: DiagnosticProvider\n  /**\n   * Forget the given document and remove all diagnostics.\n   * @param document The document to forget.\n   */\n  forget(document: TextDocument): void\n  knows: (kind: PullState, textDocument: TextDocument) => boolean\n}\n\nexport enum DiagnosticPullMode {\n  onType = 'onType',\n  onSave = 'onSave',\n  onFocus = 'onFocus'\n}\n\nexport interface DiagnosticPullOptions {\n\n  /**\n   * Whether to pull for diagnostics on document change.\n   */\n  onChange?: boolean\n\n  /**\n   * Whether to pull for diagnostics on document save.\n   */\n  onSave?: boolean\n\n  /**\n   * Whether to pull for diagnostics on editor focus.\n   */\n  onFocus?: boolean\n\n  /**\n   * Whether to pull workspace diagnostics.\n   */\n  workspace?: boolean\n  /**\n   * Minimatch patterns to match full filepath that should be ignored for pullDiagnostic.\n   */\n  ignored?: string[]\n\n  /**\n   * An optional filter method that is consulted when triggering a diagnostic pull during document change or document\n   * save or editor focus.\n   * The document gets filtered if the method returns `true`.\n   * @param document the document that changes or got save\n   * @param mode the mode\n   */\n  filter?(document: TextDocumentMatch, mode: DiagnosticPullMode): boolean\n  /**\n   * An optional match method that is consulted when pulling for diagnostics\n   * when only a URI is known (e.g. for not instantiated tabs)\n   *\n   * The method should return `true` if the document selector matches the\n   * given resource. See also the `vscode.languages.match` function.\n   * @param documentSelector The document selector.\n   * @param resource The resource.\n   * @returns whether the resource is matched by the given document selector.\n   */\n  match?(documentSelector: DocumentSelector, resource: URI): boolean\n}\n\nexport interface $DiagnosticPullOptions {\n  diagnosticPullOptions?: DiagnosticPullOptions\n}\n\nenum RequestStateKind {\n  active = 'open',\n  reschedule = 'reschedule',\n  outDated = 'drop'\n}\n\ntype RequestState = {\n  state: RequestStateKind.active\n  document: TextDocument | URI\n  version: number | undefined\n  tokenSource: CancellationTokenSource\n} | {\n  state: RequestStateKind.reschedule\n  document: TextDocument | URI\n} | {\n  state: RequestStateKind.outDated\n  document: TextDocument | URI\n}\n\ninterface DocumentPullState {\n  document: URI\n  pulledVersion: number | undefined\n  resultId: string | undefined\n}\n\nexport enum PullState {\n  document = 1,\n  workspace = 2\n}\n\nnamespace DocumentOrUri {\n  export function asKey(document: TextDocument | URI): string {\n    return document instanceof URI ? document.toString() : document.uri\n  }\n}\n\nconst workspacePullDebounce = getConditionValue(3000, 10)\n\nexport class DocumentPullStateTracker {\n  private readonly documentPullStates: Map<string, DocumentPullState>\n  private readonly workspacePullStates: Map<string, DocumentPullState>\n\n  constructor() {\n    this.documentPullStates = new Map()\n    this.workspacePullStates = new Map()\n  }\n\n  public track(kind: PullState, textDocument: TextDocument): DocumentPullState\n  public track(kind: PullState, uri: URI, version: number | undefined): DocumentPullState\n  public track(kind: PullState, document: TextDocument | URI, arg1?: number): DocumentPullState {\n    const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates\n    const [key, uri, version] = document instanceof URI\n      ? [document.toString(), document, arg1 as number | undefined]\n      : [document.uri.toString(), URI.parse(document.uri), document.version]\n    let state = states.get(key)\n    if (state === undefined) {\n      state = { document: uri, pulledVersion: version, resultId: undefined }\n      states.set(key, state)\n    }\n    return state\n  }\n\n  public update(kind: PullState, textDocument: TextDocument, resultId: string | undefined): void\n  public update(kind: PullState, uri: URI, version: number | undefined, resultId: string | undefined): void\n  public update(kind: PullState, document: TextDocument | URI, arg1: string | number | undefined, arg2?: string): void {\n    const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates\n    const [key, uri, version, resultId] = document instanceof URI\n      ? [document.toString(), document, arg1 as number | undefined, arg2]\n      : [document.uri, URI.parse(document.uri), document.version, arg1 as string | undefined]\n    let state = states.get(key)\n    if (state === undefined) {\n      state = { document: uri, pulledVersion: version, resultId }\n      states.set(key, state)\n    } else {\n      state.pulledVersion = version\n      state.resultId = resultId\n    }\n  }\n\n  public unTrack(kind: PullState, document: TextDocument | URI): void {\n    const key = DocumentOrUri.asKey(document)\n    const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates\n    states.delete(key)\n  }\n\n  public tracks(kind: PullState, document: TextDocument | URI): boolean {\n    const key = DocumentOrUri.asKey(document)\n    const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates\n    return states.has(key)\n  }\n\n  public trackingDocuments(): string[] {\n    return Array.from(this.documentPullStates.keys())\n  }\n\n  public tracksSameVersion(kind: PullState, document: TextDocument): boolean {\n    const key = document.uri.toString()\n    const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates\n    const state = states.get(key)\n    return state !== undefined && state.pulledVersion === document.version\n  }\n\n  public getResultId(kind: PullState, document: TextDocument | URI): string | undefined {\n    const key = DocumentOrUri.asKey(document)\n    const states = kind === PullState.document ? this.documentPullStates : this.workspacePullStates\n    return states.get(key)?.resultId\n  }\n\n  public getAllResultIds(): PreviousResultId[] {\n    const result: PreviousResultId[] = []\n    for (let [uri, value] of this.workspacePullStates) {\n      if (this.documentPullStates.has(uri)) {\n        value = this.documentPullStates.get(uri)!\n      }\n      if (value.resultId !== undefined) {\n        result.push({ uri, value: value.resultId })\n      }\n    }\n    return result\n  }\n}\n\nexport class DiagnosticRequestor extends BaseFeature<DiagnosticProviderMiddleware, $DiagnosticPullOptions> implements Disposable {\n  private isDisposed: boolean\n  private enableWorkspace: boolean\n  private readonly client: FeatureClient<DiagnosticProviderMiddleware, $DiagnosticPullOptions>\n  private readonly options: DiagnosticRegistrationOptions\n\n  public readonly onDidChangeDiagnosticsEmitter: Emitter<void>\n  public readonly provider: DiagnosticProvider\n  private readonly diagnostics: DiagnosticCollection\n  private readonly openRequests: Map<string, RequestState>\n  private readonly documentStates: DocumentPullStateTracker\n\n  private workspaceErrorCounter: number\n  private workspaceCancellation: CancellationTokenSource | undefined\n  private workspaceTimeout: Disposable | undefined\n\n  public constructor(client: FeatureClient<DiagnosticProviderMiddleware, $DiagnosticPullOptions>, options: DiagnosticRegistrationOptions) {\n    super(client)\n    this.client = client\n    this.options = options\n    this.enableWorkspace = options.workspaceDiagnostics && this.client.clientOptions.diagnosticPullOptions?.workspace !== false\n\n    this.isDisposed = false\n    this.onDidChangeDiagnosticsEmitter = new Emitter<void>()\n    this.provider = this.createProvider()\n\n    this.diagnostics = languages.createDiagnosticCollection(defaultValue(options.identifier, client.id))\n    this.openRequests = new Map()\n    this.documentStates = new DocumentPullStateTracker()\n    this.workspaceErrorCounter = 0\n  }\n\n  public knows(kind: PullState, document: TextDocument | URI): boolean {\n    return this.documentStates.tracks(kind, document) || this.openRequests.has(DocumentOrUri.asKey(document))\n  }\n\n  public knowsSameVersion(kind: PullState, document: TextDocument): boolean {\n    const requestState = this.openRequests.get(document.uri.toString())\n    if (requestState === undefined) {\n      return this.documentStates.tracksSameVersion(kind, document)\n    }\n    // A reschedule request is independent of a version so it will trigger\n    // on the latest version no matter what.\n    if (requestState.state === RequestStateKind.reschedule) {\n      return true\n    }\n    if (requestState.state === RequestStateKind.outDated) {\n      return false\n    }\n    return requestState.version === document.version\n  }\n\n  public forget(kind: PullState, document: TextDocument | URI): void {\n    this.documentStates.unTrack(kind, document)\n  }\n\n  public pull(document: TextDocument | URI, cb?: () => void): void {\n    if (this.isDisposed) {\n      return\n    }\n    const uri = DocumentOrUri.asKey(document)\n    this.pullAsync(document).then(() => {\n      if (cb) {\n        cb()\n      }\n    }, error => {\n      this.client.error(`Document pull failed for text document ${uri.toString()}`, error, false)\n    })\n  }\n\n  public async pullAsync(document: TextDocument | URI, version?: number): Promise<void> {\n    if (this.isDisposed) {\n      return\n    }\n    const isUri = document instanceof URI\n    const key = DocumentOrUri.asKey(document)\n    version = isUri ? version : document.version\n    const currentRequestState = this.openRequests.get(key)\n    const documentState = isUri\n      ? this.documentStates.track(PullState.document, document, version)\n      : this.documentStates.track(PullState.document, document)\n    if (currentRequestState === undefined) {\n      const tokenSource = new CancellationTokenSource()\n      this.openRequests.set(key, { state: RequestStateKind.active, document, version, tokenSource })\n      let report: DocumentDiagnosticReport | undefined\n      let afterState: RequestState | undefined\n      try {\n        report = await this.provider.provideDiagnostics(document, documentState.resultId, tokenSource.token) ?? { kind: DocumentDiagnosticReportKind.Full, items: [] }\n      } catch (error) {\n        if (error instanceof LSPCancellationError && DiagnosticServerCancellationData.is(error.data) && error.data.retriggerRequest === false) {\n          afterState = { state: RequestStateKind.outDated, document }\n        }\n        if (afterState === undefined && error instanceof CancellationError) {\n          afterState = { state: RequestStateKind.reschedule, document }\n        } else {\n          throw error\n        }\n      }\n      afterState = afterState ?? this.openRequests.get(key)\n      if (afterState === undefined) {\n        // This shouldn't happen. Log it\n        this.client.error(`Lost request state in diagnostic pull model. Clearing diagnostics for ${key}`)\n        this.diagnostics.delete(key)\n        return\n      }\n      this.openRequests.delete(key)\n      if (!workspace.tabs.isVisible(document)) {\n        this.documentStates.unTrack(PullState.document, document)\n        return\n      }\n      if (afterState.state === RequestStateKind.outDated) {\n        return\n      }\n      // report is only undefined if the request has thrown.\n      if (report !== undefined) {\n        if (report.kind === DocumentDiagnosticReportKind.Full) {\n          this.diagnostics.set(key, report.items)\n        }\n        documentState.pulledVersion = version\n        documentState.resultId = report.resultId\n      }\n      if (afterState.state === RequestStateKind.reschedule) {\n        this.pull(document)\n      }\n    } else {\n      if (currentRequestState.state === RequestStateKind.active) {\n        // Cancel the current request and reschedule a new one when the old one returned.\n        currentRequestState.tokenSource.cancel()\n        this.openRequests.set(key, { state: RequestStateKind.reschedule, document: currentRequestState.document })\n      } else if (currentRequestState.state === RequestStateKind.outDated) {\n        this.openRequests.set(key, { state: RequestStateKind.reschedule, document: currentRequestState.document })\n      }\n    }\n  }\n\n  public forgetDocument(document: TextDocument | URI): void {\n    const key = DocumentOrUri.asKey(document)\n    const request = this.openRequests.get(key)\n    if (this.options.workspaceDiagnostics) {\n      // If we run workspace diagnostic pull a last time for the diagnostics\n      // and the rely on getting them from the workspace result.\n      if (request !== undefined) {\n        this.openRequests.set(key, { state: RequestStateKind.reschedule, document })\n      } else {\n        this.pull(document, () => {\n          this.forget(PullState.document, document)\n        })\n      }\n\n      // The previous resultId from the workspace pull state can map to diagnostics we no longer have\n      // (e.g. they came from a workspace report but were overwritten by a later document pull request).\n      // Clear the workspace pull state for this document as well to ensure we get fresh diagnostics.\n      this.forget(PullState.workspace, document)\n    } else {\n      // We have normal pull or inter file dependencies. In this case we\n      // clear the diagnostics (to have the same start as after startup).\n      // We also cancel outstanding requests.\n      if (request !== undefined) {\n        if (request.state === RequestStateKind.active) {\n          request.tokenSource.cancel()\n        }\n        this.openRequests.set(key, { state: RequestStateKind.outDated, document })\n      }\n      this.diagnostics.delete(key)\n      this.forget(PullState.document, document)\n    }\n  }\n\n  public pullWorkspace(): void {\n    if (!this.enableWorkspace) return\n    this.pullWorkspaceAsync().then(() => {\n      this.workspaceTimeout = RAL().timer.setTimeout(() => {\n        this.pullWorkspace()\n      }, workspacePullDebounce)\n    }, error => {\n      if (!(error instanceof LSPCancellationError) && !DiagnosticServerCancellationData.is(error.data)) {\n        this.client.error(`Workspace diagnostic pull failed.`, error)\n        this.workspaceErrorCounter++\n      }\n      if (this.workspaceErrorCounter <= 5) {\n        this.workspaceTimeout = RAL().timer.setTimeout(() => {\n          this.pullWorkspace()\n        }, workspacePullDebounce)\n      }\n    })\n  }\n\n  private async pullWorkspaceAsync(): Promise<void> {\n    if (!this.provider.provideWorkspaceDiagnostics) {\n      return\n    }\n    if (this.workspaceCancellation !== undefined) {\n      this.workspaceCancellation.cancel()\n      this.workspaceCancellation = undefined\n    }\n    this.workspaceCancellation = new CancellationTokenSource()\n    const previousResultIds: PreviousResultId[] = this.documentStates.getAllResultIds()\n    await this.provider.provideWorkspaceDiagnostics(previousResultIds, this.workspaceCancellation.token, chunk => {\n      if (!chunk || this.isDisposed) {\n        return\n      }\n      for (const item of chunk.items) {\n        if (item.kind === DocumentDiagnosticReportKind.Full) {\n          // Favour document pull result over workspace results. So skip if it is tracked\n          // as a document result.\n          if (!this.documentStates.tracks(PullState.document, URI.parse(item.uri))) {\n            this.diagnostics.set(item.uri.toString(), item.items)\n          }\n        }\n        this.documentStates.update(PullState.workspace, URI.parse(item.uri), item.version ?? undefined, item.resultId)\n      }\n    })\n  }\n\n  private createProvider(): DiagnosticProvider {\n    const provider: DiagnosticProvider = {\n      onDidChangeDiagnostics: this.onDidChangeDiagnosticsEmitter.event,\n      provideDiagnostics: (document, previousResultId, token) => {\n        const middleware = this.client.middleware!\n        const client = this._client\n        const provideDiagnostics: ProvideDiagnosticSignature = (document, previousResultId, token) => {\n          const uri = client.code2ProtocolConverter.asUri(document instanceof URI ? document : URI.parse(document.uri))\n          const params: DocumentDiagnosticParams = {\n            identifier: this.options.identifier,\n            textDocument: { uri },\n            previousResultId\n          }\n          return this.sendRequest(DocumentDiagnosticRequest.type, params, token, { kind: DocumentDiagnosticReportKind.Full, items: [] }).then(async result => {\n            if (result === undefined || result === null || this.isDisposed) {\n              return { kind: DocumentDiagnosticReportKind.Full, items: [] }\n            }\n            // make handleDiagnostics middleware works\n            if (middleware.handleDiagnostics && result.kind == DocumentDiagnosticReportKind.Full) {\n              middleware.handleDiagnostics(uri, result.items, (_, diagnostics) => {\n                result.items = diagnostics\n              })\n            }\n            return result\n          })\n        }\n        return middleware.provideDiagnostics\n          ? middleware.provideDiagnostics(document, previousResultId, token, provideDiagnostics)\n          : provideDiagnostics(document, previousResultId, token)\n      }\n    }\n    if (this.options.workspaceDiagnostics) {\n      provider.provideWorkspaceDiagnostics = (resultIds, token, resultReporter): ProviderResult<WorkspaceDiagnosticReport> => {\n        const provideWorkspaceDiagnostics: ProvideWorkspaceDiagnosticSignature = (resultIds, token, resultReporter): ProviderResult<WorkspaceDiagnosticReport> => {\n          const partialResultToken = uuid()\n          const disposable = this.client.onProgress(WorkspaceDiagnosticRequest.partialResult, partialResultToken, partialResult => {\n            if (partialResult == undefined) {\n              resultReporter(null)\n              return\n            }\n            resultReporter(partialResult as WorkspaceDiagnosticReportPartialResult)\n          })\n          const params: WorkspaceDiagnosticParams & { __token?: string } = {\n            identifier: this.options.identifier,\n            previousResultIds: resultIds,\n            partialResultToken\n          }\n          return this.sendRequest(WorkspaceDiagnosticRequest.type, params, token, { items: [] }).then(async (result): Promise<WorkspaceDiagnosticReport> => {\n            resultReporter(result)\n            return { items: [] }\n          }).finally(() => {\n            disposable.dispose()\n          })\n        }\n        const middleware: DiagnosticProviderMiddleware = this.client.middleware!\n        return middleware.provideWorkspaceDiagnostics\n          ? middleware.provideWorkspaceDiagnostics(resultIds, token, resultReporter, provideWorkspaceDiagnostics)\n          : provideWorkspaceDiagnostics(resultIds, token, resultReporter)\n      }\n    }\n    return provider\n  }\n\n  public dispose(): void {\n    this.isDisposed = true\n    // Cancel and clear workspace pull if present.\n    this.workspaceCancellation?.cancel()\n    this.workspaceTimeout?.dispose()\n    // Cancel all request and mark open requests as outdated.\n    for (const request of this.openRequests.values()) {\n      if (request.state === RequestStateKind.active) {\n        request.tokenSource.cancel()\n      }\n    }\n    this.openRequests.clear()\n  }\n}\n\nconst timeoutDebounce = getConditionValue(500, 10)\n\nexport class BackgroundScheduler implements Disposable {\n\n  private readonly client: FeatureClient<DiagnosticProviderMiddleware, $DiagnosticPullOptions>\n  private readonly diagnosticRequestor: DiagnosticRequestor\n  private lastDocumentToPull: TextDocument | URI | undefined\n  private readonly documents: LinkedMap<string, TextDocument>\n  private timeoutHandle: Disposable | undefined\n  // The problem is that there could be outstanding diagnostic requests\n  // when we shutdown which when we receive the result will trigger a\n  // reschedule. So we remember if the background scheduler got disposed\n  // and ignore those re-schedules\n  private isDisposed: boolean\n\n  public constructor(client: FeatureClient<DiagnosticProviderMiddleware, $DiagnosticPullOptions>, diagnosticRequestor: DiagnosticRequestor) {\n    this.client = client\n    this.diagnosticRequestor = diagnosticRequestor\n    this.documents = new LinkedMap()\n    this.isDisposed = false\n  }\n\n  public add(document: TextDocument): void {\n    if (this.isDisposed === true) {\n      return\n    }\n    const key = document.uri\n    if (this.documents.has(key)) {\n      return\n    }\n    this.documents.set(key, document, Touch.AsNew)\n    // Make sure we run up to that document. We could\n    // consider inserting it after the current last\n    // document for performance reasons but it might not catch\n    // all interfile dependencies.\n    this.lastDocumentToPull = document\n  }\n\n  public remove(document: TextDocument | URI): void {\n    const key = DocumentOrUri.asKey(document)\n    this.documents.delete(key)\n    // No more documents. Stop background activity.\n    if (this.documents.size === 0) {\n      this.stop()\n      return\n    }\n    if (key === this.lastDocumentToPullKey()) {\n      // The remove document was the one we would run up to. So\n      // take the one before it.\n      const before = this.documents.before(key)\n      if (before === undefined) {\n        this.stop()\n      } else {\n        this.lastDocumentToPull = before\n      }\n    }\n  }\n\n  public trigger(): void {\n    this.lastDocumentToPull = this.documents.last\n    this.runLoop()\n  }\n\n  private runLoop(): void {\n    if (this.isDisposed === true) {\n      return\n    }\n\n    // We have an empty background list. Make sure we stop\n    // background activity.\n    if (this.documents.size === 0) {\n      this.stop()\n      return\n    }\n\n    // We have no last document anymore so stop the loop\n    if (this.lastDocumentToPull === undefined) {\n      return\n    }\n\n    // We have a timeout in the loop. So we should not schedule\n    // another run.\n    if (this.timeoutHandle !== undefined) {\n      return\n    }\n    this.timeoutHandle = RAL().timer.setTimeout(() => {\n      const document = this.documents.first\n      if (document === undefined) {\n        return\n      }\n      const key = DocumentOrUri.asKey(document)\n      this.diagnosticRequestor.pullAsync(document).catch(error => {\n        this.client.error(`Document pull failed for text document ${key}`, error, false)\n      }).finally(() => {\n        this.timeoutHandle = undefined\n        this.documents.set(key, document, Touch.Last)\n        if (key !== this.lastDocumentToPullKey()) {\n          this.runLoop()\n        }\n      })\n    }, timeoutDebounce)\n  }\n\n  public dispose(): void {\n    this.stop()\n    this.documents.clear()\n    this.lastDocumentToPull = undefined\n  }\n\n  private stop(): void {\n    this.timeoutHandle?.dispose()\n    this.timeoutHandle = undefined\n    this.lastDocumentToPull = undefined\n  }\n\n  private lastDocumentToPullKey(): string | undefined {\n    return this.lastDocumentToPull !== undefined ? DocumentOrUri.asKey(this.lastDocumentToPull) : undefined\n  }\n}\n\nclass DiagnosticFeatureProviderImpl implements DiagnosticProviderShape {\n  public readonly disposable: Disposable\n  private readonly diagnosticRequestor: DiagnosticRequestor\n  private activeTextDocument: TextDocument | undefined\n  private readonly backgroundScheduler: BackgroundScheduler\n\n  constructor(client: FeatureClient<DiagnosticProviderMiddleware, $DiagnosticPullOptions>, options: DiagnosticRegistrationOptions) {\n    const diagnosticPullOptions = Object.assign({ onChange: false, onSave: false, onFocus: false }, client.clientOptions.diagnosticPullOptions)\n\n    const selector = options.documentSelector ?? []\n    const disposables: Disposable[] = []\n    const ignored = diagnosticPullOptions.ignored ?? []\n\n    const matches = (document: TextDocument): boolean => {\n      if (diagnosticPullOptions.match !== undefined) {\n        return diagnosticPullOptions.match(selector, URI.parse(document.uri))\n      }\n      if (workspace.match(selector, document) <= 0 || !workspace.tabs.isVisible(document)) return false\n      if (ignored.length > 0 && ignored.some(p => minimatch(URI.parse(document.uri).fsPath, p, { dot: true }))) return false\n      return true\n    }\n\n    const isActiveDocument = (document: TextDocument): boolean => {\n      return document.uri === this.activeTextDocument?.uri\n    }\n\n    const considerDocument = (textDocument: TextDocument, mode: DiagnosticPullMode): boolean => {\n      return (diagnosticPullOptions.filter === undefined || !diagnosticPullOptions.filter(textDocument, mode))\n        && this.diagnosticRequestor.knows(PullState.document, textDocument)\n    }\n\n    this.diagnosticRequestor = new DiagnosticRequestor(client, options)\n    this.backgroundScheduler = new BackgroundScheduler(client, this.diagnosticRequestor)\n    const addToBackgroundIfNeeded = (document: TextDocument): void => {\n      if (!matches(document) || !options.interFileDependencies || isActiveDocument(document) || diagnosticPullOptions.onChange === false) return\n      this.backgroundScheduler.add(document)\n    }\n\n    this.activeTextDocument = window.activeTextEditor?.document.textDocument\n    disposables.push(window.onDidChangeActiveTextEditor(editor => {\n      const oldActive = this.activeTextDocument\n      this.activeTextDocument = editor?.document.textDocument\n      if (oldActive !== undefined) {\n        addToBackgroundIfNeeded(oldActive)\n      }\n      if (this.activeTextDocument !== undefined) {\n        this.backgroundScheduler.remove(this.activeTextDocument)\n        if (diagnosticPullOptions.onFocus === true && matches(this.activeTextDocument) && considerDocument(this.activeTextDocument, DiagnosticPullMode.onFocus)) {\n          this.diagnosticRequestor.pull(this.activeTextDocument)\n        }\n      }\n    }))\n\n    // We always pull on open.\n    const openFeature = client.getFeature(DidOpenTextDocumentNotification.method)\n    disposables.push(openFeature.onNotificationSent(event => {\n      const textDocument = event.original\n      if (this.diagnosticRequestor.knowsSameVersion(PullState.document, textDocument)) {\n        return\n      }\n      if (matches(textDocument)) {\n        this.diagnosticRequestor.pull(textDocument, () => { addToBackgroundIfNeeded(textDocument) })\n      }\n    }))\n\n    disposables.push(workspace.tabs.onOpen(opened => {\n      for (const resource of opened) {\n        // We already know about this document. This can happen via a document open.\n        if (this.diagnosticRequestor.knows(PullState.document, resource)) {\n          continue\n        }\n        const uriStr = resource.toString()\n        let textDocument = workspace.getDocument(uriStr)!.textDocument\n        if (textDocument !== undefined && matches(textDocument)) {\n          this.diagnosticRequestor.pull(textDocument, () => { addToBackgroundIfNeeded(textDocument!) })\n        }\n      }\n    }))\n\n    // Pull all diagnostics for documents that are already open\n    for (const textDocument of workspace.textDocuments) {\n      if (matches(textDocument)) {\n        this.diagnosticRequestor.pull(textDocument, () => { addToBackgroundIfNeeded(textDocument) })\n      }\n    }\n\n    if (diagnosticPullOptions.onChange === true) {\n      const changeFeature = client.getFeature(DidChangeTextDocumentNotification.method)\n      disposables.push(changeFeature.onNotificationSent(async event => {\n        const textDocument = workspace.getDocument(event.original.bufnr).textDocument\n        if (considerDocument(textDocument, DiagnosticPullMode.onType)) {\n          this.diagnosticRequestor.pull(textDocument, () => { this.backgroundScheduler.trigger() })\n        }\n      }))\n    }\n\n    if (diagnosticPullOptions.onSave === true) {\n      const saveFeature = client.getFeature(DidSaveTextDocumentNotification.method)\n      disposables.push(saveFeature.onNotificationSent(event => {\n        const textDocument = event.original\n        if (considerDocument(textDocument, DiagnosticPullMode.onSave)) {\n          this.diagnosticRequestor.pull(event.original)\n        }\n      }))\n    }\n\n    const closeFeature = client.getFeature(DidCloseTextDocumentNotification.method)\n    disposables.push(closeFeature.onNotificationSent(event => {\n      this.cleanUpDocument(event.original)\n    }))\n\n    // Same when a tabs closes.\n    disposables.push(workspace.tabs.onClose(closed => {\n      for (const document of closed) {\n        this.cleanUpDocument(document)\n      }\n    }))\n\n    // We received a did change from the server.\n    this.diagnosticRequestor.onDidChangeDiagnosticsEmitter.event(() => {\n      for (const textDocument of workspace.textDocuments) {\n        if (matches(textDocument)) {\n          this.diagnosticRequestor.pull(textDocument)\n        }\n      }\n    })\n\n    // da348dc5-c30a-4515-9d98-31ff3be38d14 is the test UUID to test the middle ware. So don't auto trigger pulls.\n    if (options.workspaceDiagnostics === true && options.identifier !== 'da348dc5-c30a-4515-9d98-31ff3be38d14') {\n      this.diagnosticRequestor.pullWorkspace()\n    }\n\n    this.disposable = Disposable.create(() => [...disposables, this.backgroundScheduler, this.diagnosticRequestor].forEach(d => d.dispose()))\n  }\n\n  public get onDidChangeDiagnosticsEmitter(): Emitter<void> {\n    return this.diagnosticRequestor.onDidChangeDiagnosticsEmitter\n  }\n\n  public get diagnostics(): DiagnosticProvider {\n    return this.diagnosticRequestor.provider\n  }\n\n  public knows(kind: PullState, textDocument: TextDocument): boolean {\n    return this.diagnosticRequestor.knows(kind, textDocument)\n  }\n\n  public forget(document: TextDocument): void {\n    this.cleanUpDocument(document)\n  }\n\n  private cleanUpDocument(document: TextDocument | URI): void {\n    this.backgroundScheduler.remove(document)\n    if (this.diagnosticRequestor.knows(PullState.document, document)) {\n      this.diagnosticRequestor.forgetDocument(document)\n    }\n  }\n}\n\nexport interface DiagnosticFeatureShape {\n  refresh(): void\n}\n\nexport class DiagnosticFeature extends TextDocumentLanguageFeature<DiagnosticOptions, DiagnosticRegistrationOptions, DiagnosticProviderShape, DiagnosticProviderMiddleware> implements DiagnosticFeatureShape {\n\n  constructor(client: FeatureClient<DiagnosticProviderMiddleware, $DiagnosticPullOptions>) {\n    super(client, DocumentDiagnosticRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    let capability = ensure(ensure(capabilities, 'textDocument')!, 'diagnostic')!\n    capability.relatedInformation = true\n    capability.tagSupport = { valueSet: [DiagnosticTag.Unnecessary, DiagnosticTag.Deprecated] }\n    capability.codeDescriptionSupport = true\n    capability.dataSupport = true\n    capability.dynamicRegistration = true\n    capability.relatedDocumentSupport = true\n\n    ensure(ensure(capabilities, 'workspace')!, 'diagnostics')!.refreshSupport = true\n  }\n\n  public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {\n    const client = this._client\n    client.onRequest(DiagnosticRefreshRequest.type, async () => {\n      for (const provider of this.getAllProviders()) {\n        provider.onDidChangeDiagnosticsEmitter.fire()\n      }\n    })\n    let [id, options] = this.getRegistration(documentSelector, capabilities.diagnosticProvider)\n    if (!id || !options) return\n    this.register({ id, registerOptions: options })\n  }\n\n  protected registerLanguageProvider(options: DiagnosticRegistrationOptions): [Disposable, DiagnosticProviderShape] {\n    const provider = new DiagnosticFeatureProviderImpl(this._client, options)\n    return [provider.disposable, provider]\n  }\n\n  public refresh(): void {\n    for (const provider of this.getAllProviders()) {\n      provider.onDidChangeDiagnosticsEmitter.fire()\n    }\n  }\n}\n"
  },
  {
    "path": "src/language-client/documentHighlight.ts",
    "content": "'use strict'\nimport type { CancellationToken, ClientCapabilities, Disposable, DocumentHighlight, DocumentHighlightOptions, DocumentHighlightRegistrationOptions, DocumentSelector, Position, ServerCapabilities, TextDocumentRegistrationOptions } from 'vscode-languageserver-protocol'\nimport { TextDocument } from \"vscode-languageserver-textdocument\"\nimport languages from '../languages'\nimport { DocumentHighlightProvider, ProviderResult } from '../provider'\nimport { DocumentHighlightRequest } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\nimport * as UUID from './utils/uuid'\n\nexport interface ProvideDocumentHighlightsSignature {\n  (\n    this: void,\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken\n  ): ProviderResult<DocumentHighlight[]>\n}\n\nexport interface DocumentHighlightMiddleware {\n  provideDocumentHighlights?: (\n    this: void,\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken,\n    next: ProvideDocumentHighlightsSignature\n  ) => ProviderResult<DocumentHighlight[]>\n}\n\nexport class DocumentHighlightFeature extends TextDocumentLanguageFeature<\n  boolean | DocumentHighlightOptions, DocumentHighlightRegistrationOptions, DocumentHighlightProvider, DocumentHighlightMiddleware\n> {\n  constructor(client: FeatureClient<DocumentHighlightMiddleware>) {\n    super(client, DocumentHighlightRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    ensure(\n      ensure(capabilities, 'textDocument')!,\n      'documentHighlight'\n    )!.dynamicRegistration = true\n  }\n\n  public initialize(\n    capabilities: ServerCapabilities,\n    documentSelector: DocumentSelector\n  ): void {\n    const options = this.getRegistrationOptions(documentSelector, capabilities.documentHighlightProvider)\n    if (!options) {\n      return\n    }\n    this.register({\n      id: UUID.generateUuid(),\n      registerOptions: options\n    })\n  }\n\n  protected registerLanguageProvider(\n    options: TextDocumentRegistrationOptions\n  ): [Disposable, DocumentHighlightProvider] {\n    const provider: DocumentHighlightProvider = {\n      provideDocumentHighlights: (document, position, token) => {\n        const client = this._client\n        const _provideDocumentHighlights: ProvideDocumentHighlightsSignature = (document, position, token) => {\n          return this.sendRequest(\n            DocumentHighlightRequest.type,\n            client.code2ProtocolConverter.asTextDocumentPositionParams(document, position),\n            token\n          )\n        }\n        const middleware = client.middleware!\n        return middleware.provideDocumentHighlights\n          ? middleware.provideDocumentHighlights(document, position, token, _provideDocumentHighlights)\n          : _provideDocumentHighlights(document, position, token)\n      }\n    }\n    this._client.attachExtensionName(provider)\n    return [languages.registerDocumentHighlightProvider(options.documentSelector!, provider), provider]\n  }\n}\n"
  },
  {
    "path": "src/language-client/documentLink.ts",
    "content": "'use strict'\nimport type { CancellationToken, ClientCapabilities, Disposable, DocumentLink, DocumentLinkOptions, DocumentLinkRegistrationOptions, DocumentSelector, ServerCapabilities } from 'vscode-languageserver-protocol'\nimport { TextDocument } from \"vscode-languageserver-textdocument\"\nimport languages from '../languages'\nimport { DocumentLinkProvider, ProviderResult } from '../provider'\nimport { DocumentLinkRequest, DocumentLinkResolveRequest } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\nimport * as UUID from './utils/uuid'\n\nexport interface ProvideDocumentLinksSignature {\n  (this: void, document: TextDocument, token: CancellationToken): ProviderResult<DocumentLink[]>\n}\n\nexport interface ResolveDocumentLinkSignature {\n  (this: void, link: DocumentLink, token: CancellationToken): ProviderResult<DocumentLink>\n}\n\nexport interface DocumentLinkMiddleware {\n  provideDocumentLinks?: (\n    this: void,\n    document: TextDocument,\n    token: CancellationToken,\n    next: ProvideDocumentLinksSignature\n  ) => ProviderResult<DocumentLink[]>\n  resolveDocumentLink?: (\n    this: void,\n    link: DocumentLink,\n    token: CancellationToken,\n    next: ResolveDocumentLinkSignature\n  ) => ProviderResult<DocumentLink>\n}\n\nexport class DocumentLinkFeature extends TextDocumentLanguageFeature<DocumentLinkOptions, DocumentLinkRegistrationOptions, DocumentLinkProvider, DocumentLinkMiddleware> {\n  constructor(client: FeatureClient<DocumentLinkMiddleware>) {\n    super(client, DocumentLinkRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    const documentLinkCapabilities = ensure(ensure(capabilities, 'textDocument')!, 'documentLink')!\n    documentLinkCapabilities.dynamicRegistration = true\n    documentLinkCapabilities.tooltipSupport = true\n  }\n\n  public initialize(\n    capabilities: ServerCapabilities,\n    documentSelector: DocumentSelector\n  ): void {\n    const options = this.getRegistrationOptions(documentSelector, capabilities.documentLinkProvider)\n    if (!options) {\n      return\n    }\n    this.register({\n      id: UUID.generateUuid(),\n      registerOptions: options\n    })\n  }\n\n  protected registerLanguageProvider(\n    options: DocumentLinkRegistrationOptions\n  ): [Disposable, DocumentLinkProvider] {\n    const provider: DocumentLinkProvider = {\n      provideDocumentLinks: (document: TextDocument, token: CancellationToken): ProviderResult<DocumentLink[]> => {\n        const client = this._client\n        const provideDocumentLinks: ProvideDocumentLinksSignature = (document, token) => {\n          return this.sendRequest(\n            DocumentLinkRequest.type,\n            client.code2ProtocolConverter.asDocumentLinkParams(document),\n            token\n          )\n        }\n        const middleware = client.middleware!\n        return middleware.provideDocumentLinks\n          ? middleware.provideDocumentLinks(document, token, provideDocumentLinks)\n          : provideDocumentLinks(document, token)\n      },\n      resolveDocumentLink: options.resolveProvider\n        ? (link, token) => {\n          const client = this._client\n          let resolveDocumentLink: ResolveDocumentLinkSignature = (link, token) => {\n            return this.sendRequest(DocumentLinkResolveRequest.type, link, token, link)\n          }\n          const middleware = client.middleware!\n          return middleware.resolveDocumentLink\n            ? middleware.resolveDocumentLink(link, token, resolveDocumentLink)\n            : resolveDocumentLink(link, token)\n        }\n        : undefined\n    }\n    this._client.attachExtensionName(provider)\n    return [languages.registerDocumentLinkProvider(options.documentSelector, provider), provider]\n  }\n}\n"
  },
  {
    "path": "src/language-client/documentSymbol.ts",
    "content": "'use strict'\nimport type { ClientCapabilities, DocumentSelector, DocumentSymbolOptions, DocumentSymbolRegistrationOptions, ServerCapabilities } from 'vscode-languageserver-protocol'\nimport { TextDocument } from \"vscode-languageserver-textdocument\"\nimport { DocumentSymbol, SymbolInformation, SymbolKind, SymbolTag } from 'vscode-languageserver-types'\nimport languages from '../languages'\nimport { DocumentSymbolProvider, ProviderResult } from '../provider'\nimport { CancellationToken, Disposable, DocumentSymbolRequest } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\nimport * as UUID from './utils/uuid'\n\nexport const SupportedSymbolKinds: SymbolKind[] = [\n  SymbolKind.File,\n  SymbolKind.Module,\n  SymbolKind.Namespace,\n  SymbolKind.Package,\n  SymbolKind.Class,\n  SymbolKind.Method,\n  SymbolKind.Property,\n  SymbolKind.Field,\n  SymbolKind.Constructor,\n  SymbolKind.Enum,\n  SymbolKind.Interface,\n  SymbolKind.Function,\n  SymbolKind.Variable,\n  SymbolKind.Constant,\n  SymbolKind.String,\n  SymbolKind.Number,\n  SymbolKind.Boolean,\n  SymbolKind.Array,\n  SymbolKind.Object,\n  SymbolKind.Key,\n  SymbolKind.Null,\n  SymbolKind.EnumMember,\n  SymbolKind.Struct,\n  SymbolKind.Event,\n  SymbolKind.Operator,\n  SymbolKind.TypeParameter\n]\n\nexport const SupportedSymbolTags: SymbolTag[] = [\n  SymbolTag.Deprecated\n]\n\nexport interface ProvideDocumentSymbolsSignature {\n  (this: void, document: TextDocument, token: CancellationToken): ProviderResult<SymbolInformation[] | DocumentSymbol[]>\n}\n\nexport interface DocumentSymbolMiddleware {\n  provideDocumentSymbols?: (\n    this: void,\n    document: TextDocument,\n    token: CancellationToken,\n    next: ProvideDocumentSymbolsSignature\n  ) => ProviderResult<SymbolInformation[] | DocumentSymbol[]>\n}\n\nexport class DocumentSymbolFeature extends TextDocumentLanguageFeature<\n  boolean | DocumentSymbolOptions, DocumentSymbolRegistrationOptions, DocumentSymbolProvider, DocumentSymbolMiddleware\n> {\n  constructor(client: FeatureClient<DocumentSymbolMiddleware>) {\n    super(client, DocumentSymbolRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    let symbolCapabilities = ensure(ensure(capabilities, 'textDocument')!, 'documentSymbol')! as any\n    symbolCapabilities.dynamicRegistration = true\n    symbolCapabilities.symbolKind = {\n      valueSet: SupportedSymbolKinds\n    }\n    symbolCapabilities.hierarchicalDocumentSymbolSupport = true\n    symbolCapabilities.tagSupport = {\n      valueSet: SupportedSymbolTags\n    }\n    symbolCapabilities.labelSupport = true\n  }\n\n  public initialize(\n    capabilities: ServerCapabilities,\n    documentSelector: DocumentSelector\n  ): void {\n    const options = this.getRegistrationOptions(documentSelector, capabilities.documentSymbolProvider)\n    if (!options) {\n      return\n    }\n    this.register({\n      id: UUID.generateUuid(),\n      registerOptions: options\n    })\n  }\n\n  protected registerLanguageProvider(\n    options: DocumentSymbolRegistrationOptions\n  ): [Disposable, DocumentSymbolProvider] {\n    const provider: DocumentSymbolProvider = {\n      meta: options.label ? { label: options.label } : undefined,\n      provideDocumentSymbols: (document, token) => {\n        const client = this._client\n        const _provideDocumentSymbols: ProvideDocumentSymbolsSignature = (document, token) => {\n          return this.sendRequest(\n            DocumentSymbolRequest.type,\n            client.code2ProtocolConverter.asDocumentSymbolParams(document),\n            token\n          )\n        }\n        const middleware = client.middleware!\n        return middleware.provideDocumentSymbols\n          ? middleware.provideDocumentSymbols(document, token, _provideDocumentSymbols)\n          : _provideDocumentSymbols(document, token)\n      }\n    }\n    this._client.attachExtensionName(provider)\n    return [languages.registerDocumentSymbolProvider(options.documentSelector!, provider), provider]\n  }\n}\n"
  },
  {
    "path": "src/language-client/executeCommand.ts",
    "content": "'use strict'\nimport type { ClientCapabilities, Disposable, ExecuteCommandParams, ExecuteCommandRegistrationOptions, RegistrationType, ServerCapabilities } from 'vscode-languageserver-protocol'\nimport commands from '../commands'\nimport { ProviderResult } from '../provider'\nimport { CancellationToken, ExecuteCommandRequest } from '../util/protocol'\nimport { BaseFeature, DynamicFeature, ensure, FeatureClient, FeatureState, RegistrationData } from './features'\nimport * as UUID from './utils/uuid'\n\nexport interface ExecuteCommandSignature {\n  (this: void, command: string, args: any[]): ProviderResult<any>\n}\n\nexport interface ExecuteCommandMiddleware {\n  executeCommand?: (this: void, command: string, args: any[], next: ExecuteCommandSignature) => ProviderResult<any>\n}\n\nexport class ExecuteCommandFeature extends BaseFeature<ExecuteCommandMiddleware> implements DynamicFeature<ExecuteCommandRegistrationOptions> {\n  private _commands: Map<string, Disposable[]> = new Map<string, Disposable[]>()\n  constructor(client: FeatureClient<ExecuteCommandMiddleware>) {\n    super(client)\n  }\n\n  public getState(): FeatureState {\n    return { kind: 'workspace', id: this.registrationType.method, registrations: this._commands.size > 0 }\n  }\n\n  public get registrationType(): RegistrationType<ExecuteCommandRegistrationOptions> {\n    return ExecuteCommandRequest.type\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    ensure(ensure(capabilities, 'workspace')!, 'executeCommand')!.dynamicRegistration = true\n  }\n\n  public initialize(capabilities: ServerCapabilities): void {\n    if (!capabilities.executeCommandProvider) {\n      return\n    }\n    this.register({\n      id: UUID.generateUuid(),\n      registerOptions: Object.assign({}, capabilities.executeCommandProvider)\n    })\n  }\n\n  public register(\n    data: RegistrationData<ExecuteCommandRegistrationOptions>\n  ): void {\n    const client = this._client\n    const middleware = client.middleware!\n    const executeCommand: ExecuteCommandSignature = (command: string, args: any[]): any => {\n      const params: ExecuteCommandParams = {\n        command,\n        arguments: args\n      }\n      return client.sendRequest(ExecuteCommandRequest.type, params, CancellationToken.None).then(\n        undefined,\n        error => {\n          client.handleFailedRequest(ExecuteCommandRequest.type, undefined, error, undefined)\n        }\n      )\n    }\n    if (data.registerOptions.commands) {\n      let disposables: Disposable[] = []\n      for (const command of data.registerOptions.commands) {\n        disposables.push(commands.registerCommand(command, (...args: any[]) => {\n          return middleware.executeCommand\n            ? middleware.executeCommand(command, args, executeCommand)\n            : executeCommand(command, args)\n        }, null, true))\n      }\n      this._commands.set(data.id, disposables)\n    }\n  }\n\n  public unregister(id: string): void {\n    let disposables = this._commands.get(id)\n    if (disposables) {\n      this._commands.delete(id)\n      disposables.forEach(disposable => disposable.dispose())\n    }\n  }\n\n  public dispose(): void {\n    this._commands.forEach(value => {\n      value.forEach(disposable => disposable.dispose())\n    })\n    this._commands.clear()\n  }\n}\n"
  },
  {
    "path": "src/language-client/features.ts",
    "content": "'use strict'\nimport type {\n  CallHierarchyPrepareRequest, CancellationToken, ClientCapabilities, CodeActionRequest, CodeLensRequest, CompletionRequest, DeclarationRequest, DefinitionRequest,\n  DidChangeTextDocumentNotification, DidChangeWatchedFilesNotification, DidChangeWatchedFilesRegistrationOptions, DidChangeWorkspaceFoldersNotification, DidCloseTextDocumentNotification, DidCreateFilesNotification, DidDeleteFilesNotification, DidOpenTextDocumentNotification,\n  DidRenameFilesNotification, DidSaveTextDocumentNotification, Disposable, DocumentColorRequest, DocumentDiagnosticRequest, DocumentFormattingRequest, DocumentHighlightRequest,\n  DocumentLinkRequest, DocumentOnTypeFormattingRequest, DocumentRangeFormattingRequest, DocumentSelector, DocumentSymbolRequest, ExecuteCommandRegistrationOptions, ExecuteCommandRequest, FileOperationRegistrationOptions,\n  FoldingRangeRequest, GenericNotificationHandler, GenericRequestHandler, HoverRequest, ImplementationRequest, InitializeParams, InitializeResult, InlayHintRequest, InlineCompletionRequest, InlineValueRequest,\n  LinkedEditingRangeRequest, MarkupKind, MessageSignature, NotificationHandler, NotificationHandler0,\n  NotificationType, NotificationType0, ProgressType, ProtocolNotificationType, ProtocolNotificationType0, ProtocolRequestType, ProtocolRequestType0, ReferencesRequest,\n  RegistrationType, RenameRequest, RequestHandler, RequestHandler0, RequestParam, RequestType, RequestType0, SelectionRangeRequest, SemanticTokensRegistrationType, ServerCapabilities,\n  SignatureHelpRequest, TextEdit, Trace, TraceOptions, Tracer, TypeDefinitionRequest, TypeHierarchyPrepareRequest, WillCreateFilesRequest,\n  WillDeleteFilesRequest, WillRenameFilesRequest, WillSaveTextDocumentNotification, WillSaveTextDocumentWaitUntilRequest, WorkspaceSymbolRequest\n} from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { FileCreateEvent, FileDeleteEvent, FileRenameEvent, FileWillCreateEvent, FileWillDeleteEvent, FileWillRenameEvent, TextDocumentWillSaveEvent } from '../core/files'\nimport { CallHierarchyProvider, CodeActionProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentLinkProvider, DocumentRangeFormattingEditProvider, DocumentSymbolProvider, HoverProvider, ImplementationProvider, InlineCompletionItemProvider, LinkedEditingRangeProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, TypeHierarchyProvider, WorkspaceSymbolProvider } from '../provider'\nimport { CancellationError } from '../util/errors'\nimport * as Is from '../util/is'\nimport { Emitter, Event, StaticRegistrationOptions, TextDocumentRegistrationOptions, WorkDoneProgressOptions } from '../util/protocol'\nimport workspace from '../workspace'\nimport * as c2p from './utils/codeConverter'\nimport * as UUID from './utils/uuid'\n\nexport class LSPCancellationError extends CancellationError {\n  public readonly data: object\n  constructor(data: object) {\n    super()\n    this.data = data\n  }\n}\n\nexport interface Connection {\n  id: string\n  listen(): void\n\n  hasPendingResponse(): boolean\n  sendRequest<R, PR, E, RO>(type: ProtocolRequestType0<R, PR, E, RO>, token?: CancellationToken): Promise<R>\n  sendRequest<P, R, PR, E, RO>(type: ProtocolRequestType<P, R, PR, E, RO>, params: NoInfer<RequestParam<P>>, token?: CancellationToken): Promise<R>\n  sendRequest<R, E>(type: RequestType0<R, E>, token?: CancellationToken): Promise<R>\n  sendRequest<P, R, E>(type: RequestType<P, R, E>, params: NoInfer<RequestParam<P>>, token?: CancellationToken): Promise<R>\n  sendRequest<R>(method: string, token?: CancellationToken): Promise<R>\n  sendRequest<R>(method: string, param: any, token?: CancellationToken): Promise<R>\n  sendRequest<R>(type: string | MessageSignature, ...params: any[]): Promise<R>\n\n  onRequest<R, PR, E, RO>(type: ProtocolRequestType0<R, PR, E, RO>, handler: NoInfer<RequestHandler0<R, E>>): Disposable\n  onRequest<P, R, PR, E, RO>(type: ProtocolRequestType<P, R, PR, E, RO>, handler: NoInfer<RequestHandler<P, R, E>>): Disposable\n  onRequest<R, E>(type: RequestType0<R, E>, handler: NoInfer<RequestHandler0<R, E>>): Disposable\n  onRequest<P, R, E>(type: RequestType<P, R, E>, handler: NoInfer<RequestHandler<P, R, E>>): Disposable\n  onRequest<R, E>(method: string | MessageSignature, handler: GenericRequestHandler<R, E>): Disposable\n\n  sendNotification<RO>(type: ProtocolNotificationType0<RO>): Promise<void>\n  sendNotification<P, RO>(type: ProtocolNotificationType<P, RO>, params?: NoInfer<RequestParam<P>>): Promise<void>\n  sendNotification(type: NotificationType0): Promise<void>\n  sendNotification<P>(type: NotificationType<P>, params?: NoInfer<RequestParam<P>>): Promise<void>\n  sendNotification(method: string | MessageSignature, params?: any): Promise<void>\n\n  onNotification<RO>(type: ProtocolNotificationType0<RO>, handler: NotificationHandler0): Disposable\n  onNotification<P, RO>(type: ProtocolNotificationType<P, RO>, handler: NoInfer<NotificationHandler<P>>): Disposable\n  onNotification(type: NotificationType0, handler: NotificationHandler0): Disposable\n  onNotification<P>(type: NotificationType<P>, handler: NoInfer<NotificationHandler<P>>): Disposable\n  onNotification(method: string | MessageSignature, handler: GenericNotificationHandler): Disposable\n\n  onProgress<P>(type: ProgressType<P>, token: string | number, handler: NoInfer<NotificationHandler<P>>): Disposable\n  sendProgress<P>(type: ProgressType<P>, token: string | number, value: P): Promise<void>\n\n  trace(value: Trace, tracer: Tracer, sendNotification?: boolean | TraceOptions): Promise<void>\n  initialize(params: InitializeParams): Promise<InitializeResult>\n  shutdown(): Promise<void>\n  exit(): Promise<void>\n  end(): void\n  dispose(): void\n}\n\nexport class BaseFeature<MW, CO = object> {\n  protected readonly _client: FeatureClient<MW, CO>\n  constructor(client: FeatureClient<MW, CO>) {\n    this._client = client\n  }\n\n  protected sendRequest<P, R, E>(type: RequestType<P, R, E>, params: RequestParam<P>, token: CancellationToken, defaultValue?: R): Promise<R> {\n    return this._client.sendRequest(type, params, token).then((res => {\n      return token.isCancellationRequested || res == null ? defaultValue ?? null : res\n    }), error => {\n      return this._client.handleFailedRequest(type, token, error, defaultValue ?? null)\n    })\n  }\n}\n\nexport interface RegistrationData<T> {\n  id: string\n  registerOptions: T\n}\n\nexport interface NextSignature<P, R> {\n  (this: void, data: P, next: (data: P) => R): R\n}\n\nexport function ensure<T, K extends keyof T>(target: T, key: K): T[K] {\n  if (target[key] === undefined) {\n    target[key] = {} as any\n  }\n  return target[key]\n}\n\nexport interface TextDocumentProviderFeature<T> {\n  readonly registrationLength: number\n  /**\n   * Triggers the corresponding RPC method.\n   */\n  getProvider(textDocument: TextDocument): T | undefined\n}\n\nexport type FeatureStateKind = 'document' | 'workspace' | 'static' | 'window'\n\nexport type FeatureState = {\n  kind: 'document'\n\n  /**\n   * The features's id. This is usually the method names used during\n   * registration.\n   */\n  id: string\n\n  /**\n   * Has active registrations.\n   */\n  registrations: boolean\n\n  /**\n   * A registration matches an open document.\n   */\n  matches: boolean\n\n} | {\n  kind: 'workspace'\n\n  /**\n   * The features's id. This is usually the method names used during\n   * registration.\n   */\n  id: string\n\n  /**\n   * Has active registrations.\n   */\n  registrations: boolean\n} | {\n  kind: 'window'\n\n  /**\n   * The features's id. This is usually the method names used during\n   * registration.\n   */\n  id: string\n\n  /**\n   * Has active registrations.\n   */\n  registrations: boolean\n} | {\n  kind: 'static'\n}\n\ninterface TextDocumentFeatureRegistration<RO, PR> {\n  disposable: Disposable\n  data: RegistrationData<RO>\n  provider: PR\n}\n\n/**\n * A static feature. A static feature can't be dynamically activated via the\n * server. It is wired during the initialize sequence.\n */\nexport interface StaticFeature {\n  readonly method: string\n  /**\n   * Called to fill the initialize params.\n   * @params the initialize params.\n   */\n  fillInitializeParams?: (params: InitializeParams) => void\n\n  /**\n   * Called to fill in the client capabilities this feature implements.\n   * @param capabilities The client capabilities to fill.\n   */\n  fillClientCapabilities(capabilities: ClientCapabilities): void\n\n  /**\n   * A preflight where the server capabilities are shown to all features\n   * before a feature is actually initialized. This allows feature to\n   * capture some state if they are a pre-requisite for other features.\n   * @param capabilities the server capabilities\n   * @param documentSelector the document selector pass to the client's constructor.\n   * May be `undefined` if the client was created without a selector.\n   */\n  preInitialize?: (capabilities: ServerCapabilities, documentSelector: DocumentSelector | undefined) => void\n\n  /**\n   * Initialize the feature. This method is called on a feature instance\n   * when the client has successfully received the initialize request from\n   * the server and before the client sends the initialized notification\n   * to the server.\n   * @param capabilities the server capabilities\n   * @param documentSelector the document selector pass to the client's constructor.\n   * May be `undefined` if the client was created without a selector.\n   */\n  initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector | undefined): void\n\n  /**\n   * Returns the state the feature is in.\n   */\n  getState?(): FeatureState\n\n  /**\n   * Called when the client is stopped to dispose this feature. Usually a feature\n   * un-registers listeners registered hooked up with the VS Code extension host.\n   */\n  dispose(): void\n}\n\n// eslint-disable-next-line no-redeclare\nexport namespace StaticFeature {\n  export function is(value: any): value is StaticFeature {\n    return value !== undefined && value !== null &&\n      Is.func(value.fillClientCapabilities) && Is.func(value.initialize) && Is.func(value.dispose) &&\n      (value.fillInitializeParams === undefined || Is.func(value.fillInitializeParams)) && value.registrationType === undefined\n  }\n}\n\n/**\n * A dynamic feature can be activated via the server.\n */\nexport interface DynamicFeature<RO> {\n\n  /**\n   * Called to fill the initialize params.\n   * @params the initialize params.\n   */\n  fillInitializeParams?: (params: InitializeParams) => void\n\n  /**\n   * Called to fill in the client capabilities this feature implements.\n   * @param capabilities The client capabilities to fill.\n   */\n  fillClientCapabilities(capabilities: ClientCapabilities): void\n\n  /**\n   * A preflight where the server capabilities are shown to all features\n   * before a feature is actually initialized. This allows feature to\n   * capture some state if they are a pre-requisite for other features.\n   * @param capabilities the server capabilities\n   * @param documentSelector the document selector pass to the client's constructor.\n   * May be `undefined` if the client was created without a selector.\n   */\n  preInitialize?: (capabilities: ServerCapabilities, documentSelector: DocumentSelector | undefined) => void\n\n  /**\n   * Initialize the feature. This method is called on a feature instance\n   * when the client has successfully received the initialize request from\n   * the server and before the client sends the initialized notification\n   * to the server.\n   * @param capabilities the server capabilities.\n   * @param documentSelector the document selector pass to the client's constructor.\n   * May be `undefined` if the client was created without a selector.\n   */\n  initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector | undefined): void\n\n  /**\n   * Returns the state the feature is in.\n   */\n  getState(): FeatureState\n\n  /**\n   * The signature (e.g. method) for which this features support dynamic activation / registration.\n   */\n  registrationType: RegistrationType<RO>\n\n  /**\n   * Is called when the server send a register request for the given message.\n   * @param data additional registration data as defined in the protocol.\n   */\n  register(data: RegistrationData<RO>): void\n\n  /**\n   * Is called when the server wants to unregister a feature.\n   * @param id the id used when registering the feature.\n   */\n  unregister(id: string): void\n\n  /**\n   * Called when the client is stopped to dispose this feature. Usually a feature\n   * un-registers listeners registered hooked up with the VS Code extension host.\n   */\n  dispose(): void\n}\n\n// eslint-disable-next-line no-redeclare\nexport namespace DynamicFeature {\n  export function is<T>(value: any): value is DynamicFeature<T> {\n    const candidate: DynamicFeature<T> = value\n    return candidate !== undefined && candidate !== null &&\n      Is.func(candidate.fillClientCapabilities) && Is.func(candidate.initialize) && Is.func(candidate.dispose) &&\n      (candidate.fillInitializeParams === undefined || Is.func(candidate.fillInitializeParams)) && Is.func(candidate.register) &&\n      Is.func(candidate.unregister) && candidate.registrationType !== undefined\n  }\n}\n\ninterface CreateParamsSignature<E, P> {\n  (data: E): RequestParam<P>\n}\n\n/**\n * An abstract dynamic feature implementation that operates on documents (e.g. text\n * documents or notebooks).\n */\nexport abstract class DynamicDocumentFeature<RO, MW, CO = object> extends BaseFeature<MW, CO> implements DynamicFeature<RO> {\n  constructor(client: FeatureClient<MW, CO>) {\n    super(client)\n  }\n\n  // Repeat from interface.\n  public abstract fillClientCapabilities(capabilities: ClientCapabilities): void\n  public abstract initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector | undefined): void\n  public abstract registrationType: RegistrationType<RO>\n  public abstract register(data: RegistrationData<RO>): void\n  public abstract unregister(id: string): void\n  public abstract dispose(): void\n\n  /**\n   * Returns the state the feature is in.\n   */\n  public getState(): FeatureState {\n    const selectors = this.getDocumentSelectors()\n    let count = 0\n    for (const selector of selectors) {\n      count++\n      for (const document of workspace.textDocuments) {\n        if (workspace.match(selector, document) > 0) {\n          return { kind: 'document', id: this.registrationType.method, registrations: true, matches: true }\n        }\n      }\n    }\n    const registrations = count > 0\n    return { kind: 'document', id: this.registrationType.method, registrations, matches: false }\n\n  }\n\n  protected abstract getDocumentSelectors(): IterableIterator<DocumentSelector>\n\n  public sendWithMiddleware<T>(fn: (...args: any[]) => ProviderResult<T>, key: string, ...params: any[]): ProviderResult<T> {\n    const middleware = this._client.middleware!\n    return middleware[key] ? middleware[key](...params, fn) : fn(...params)\n  }\n}\n\n/**\n * A mixin type that allows to send notification or requests using a registered\n * provider.\n */\n// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\nexport interface TextDocumentSendFeature<T extends Function> {\n  /**\n   * Returns a provider for the given text document.\n   */\n  getProvider(document: TextDocument): { send: T } | undefined\n}\n\nexport interface NotificationSendEvent<E, P> {\n  original: E\n  type: ProtocolNotificationType<P, TextDocumentRegistrationOptions>\n  params: RequestParam<P>\n}\n\nexport interface NotifyingFeature<E, P> {\n  onNotificationSent: Event<NotificationSendEvent<E, P>>\n}\n\nexport abstract class TextDocumentEventFeature<P, E, M> extends DynamicDocumentFeature<TextDocumentRegistrationOptions, M> implements TextDocumentSendFeature<(data: E) => Promise<void>>, NotifyingFeature<E, P> {\n\n  private readonly _event: Event<E>\n  protected readonly _type: ProtocolNotificationType<P, TextDocumentRegistrationOptions>\n  protected readonly _middleware: string\n  protected readonly _createParams: CreateParamsSignature<E, P>\n  protected readonly _selectorFilter?: (selectors: IterableIterator<DocumentSelector>, data: E) => boolean\n\n  private _listener: Disposable | undefined\n  protected readonly _selectors: Map<string, DocumentSelector>\n  private _onNotificationSent: Emitter<NotificationSendEvent<E, P>>\n\n  public static textDocumentFilter(\n    selectors: IterableIterator<DocumentSelector>,\n    textDocument: TextDocument\n  ): boolean {\n    for (const selector of selectors) {\n      if (workspace.match(selector, textDocument) > 0) {\n        return true\n      }\n    }\n    return false\n  }\n\n  constructor(client: FeatureClient<M>, event: Event<E>, type: ProtocolNotificationType<P, TextDocumentRegistrationOptions>,\n    middleware: string, createParams: NoInfer<CreateParamsSignature<E, P>>,\n    selectorFilter?: (selectors: IterableIterator<DocumentSelector>, data: E) => boolean\n  ) {\n    super(client)\n    this._event = event\n    this._type = type\n    this._middleware = middleware\n    this._createParams = createParams\n    this._selectorFilter = selectorFilter\n\n    this._selectors = new Map<string, DocumentSelector>()\n    this._onNotificationSent = new Emitter<NotificationSendEvent<E, P>>()\n  }\n\n  protected getDocumentSelectors(): IterableIterator<DocumentSelector> {\n    return this._selectors.values()\n  }\n\n  public register(data: RegistrationData<TextDocumentRegistrationOptions>): void {\n\n    if (!data.registerOptions.documentSelector) {\n      return\n    }\n    if (!this._listener) {\n      this._listener = this._event(data => {\n        this.callback(data).catch(error => {\n          this._client.error(`Sending document notification ${this._type.method} failed.`, error)\n        })\n      })\n    }\n    this._selectors.set(data.id, data.registerOptions.documentSelector)\n  }\n\n  protected async callback(data: E): Promise<void> {\n    if (!this.matches(data)) return\n    return await this.sendNotification(data)\n  }\n\n  protected async sendNotification(data: E): Promise<void> {\n    const doSend = async (data: E): Promise<void> => {\n      const params = this._createParams(data)\n      await this._client.sendNotification(this._type, params)\n      this.notificationSent(data, this._type, params)\n    }\n    return this.sendWithMiddleware(doSend, this._middleware, data)\n  }\n\n  protected matches(data: E): boolean {\n    return !this._selectorFilter || this._selectorFilter(this._selectors.values(), data)\n  }\n\n  public get onNotificationSent(): Event<NotificationSendEvent<E, P>> {\n    return this._onNotificationSent.event\n  }\n\n  protected notificationSent(data: E, type: ProtocolNotificationType<P, TextDocumentRegistrationOptions>, params: RequestParam<P>): void {\n    this._onNotificationSent.fire({ original: data, type, params })\n  }\n\n  public unregister(id: string): void {\n    this._selectors.delete(id)\n  }\n\n  public dispose(): void {\n    this._selectors.clear()\n    this._onNotificationSent.dispose()\n    this._onNotificationSent = new Emitter<NotificationSendEvent<E, P>>()\n    if (this._listener) {\n      this._listener.dispose()\n      this._listener = undefined\n    }\n  }\n\n  public getProvider(document: TextDocument): { send: (data: E) => Promise<void> } | undefined {\n    for (const selector of this.getDocumentSelectors()) {\n      if (workspace.match(selector, document) > 0) {\n        return {\n          send: (data: E) => {\n            return this.callback(data)\n          }\n        }\n      }\n    }\n    return undefined\n  }\n}\n/**\n * A abstract feature implementation that registers language providers\n * for text documents using a given document selector.\n */\nexport abstract class TextDocumentLanguageFeature<PO, RO extends TextDocumentRegistrationOptions & PO, PR, MW, CO = object> extends DynamicDocumentFeature<RO, MW, CO> {\n\n  private readonly _registrationType: RegistrationType<RO>\n  private readonly _registrations: Map<string, TextDocumentFeatureRegistration<RO, PR>>\n\n  constructor(client: FeatureClient<MW, CO>, registrationType: RegistrationType<RO>) {\n    super(client)\n    this._registrationType = registrationType\n    this._registrations = new Map()\n  }\n\n  protected *getDocumentSelectors(): IterableIterator<DocumentSelector> {\n    for (const registration of this._registrations.values()) {\n      const selector = registration.data.registerOptions.documentSelector\n      if (selector != null) {\n        yield selector\n      }\n    }\n  }\n\n  public get registrationType(): RegistrationType<RO> {\n    return this._registrationType\n  }\n\n  public get registrationLength(): number {\n    return this._registrations.size\n  }\n\n  public abstract fillClientCapabilities(capabilities: ClientCapabilities): void\n\n  public abstract initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void\n\n  public register(data: RegistrationData<RO>): void {\n    if (!data.registerOptions.documentSelector) {\n      return\n    }\n    let registration = this.registerLanguageProvider(data.registerOptions, data.id)\n    this._registrations.set(data.id, { disposable: registration[0], data, provider: registration[1] })\n  }\n\n  protected abstract registerLanguageProvider(options: RO, id: string): [Disposable, PR]\n\n  public unregister(id: string): void {\n    let registration = this._registrations.get(id)\n    if (registration !== undefined) {\n      this._registrations.delete(id)\n      registration.disposable.dispose()\n    }\n  }\n\n  public dispose(): void {\n    this._registrations.forEach(value => {\n      value.disposable.dispose()\n    })\n    this._registrations.clear()\n  }\n\n  public getRegistration(documentSelector: DocumentSelector, capability: undefined | PO & { id?: string } | (RO & StaticRegistrationOptions)): [string | undefined, (RO & { documentSelector: DocumentSelector }) | undefined] {\n    if (!capability) {\n      return [undefined, undefined]\n    } else if (TextDocumentRegistrationOptions.is(capability)) {\n      const id = StaticRegistrationOptions.hasId(capability) ? capability.id : UUID.generateUuid()\n      const selector = defaultValue(capability.documentSelector, documentSelector)\n      return [id, Object.assign({}, capability, { documentSelector: selector })]\n    } else if ((Is.boolean(capability) && capability === true) || WorkDoneProgressOptions.is(capability)) {\n      const options = capability === true ? { documentSelector } : Object.assign({}, capability, { documentSelector })\n      return [UUID.generateUuid(), options as RO & { documentSelector: DocumentSelector }]\n    }\n    return [undefined, undefined]\n  }\n\n  protected getRegistrationOptions(documentSelector: DocumentSelector | undefined, capability: undefined | PO): (RO & { documentSelector: DocumentSelector }) | undefined {\n    if (!documentSelector || !capability) {\n      return undefined\n    }\n    return (Is.boolean(capability) && capability === true ? { documentSelector } : Object.assign({}, capability, { documentSelector })) as RO & { documentSelector: DocumentSelector }\n  }\n\n  public getProvider(textDocument: TextDocument): PR | undefined {\n    for (const registration of this._registrations.values()) {\n      let selector = registration.data.registerOptions.documentSelector\n      if (selector !== null && workspace.match(selector, textDocument) > 0) {\n        return registration.provider\n      }\n    }\n    return undefined\n  }\n\n  protected getAllProviders(): Iterable<PR> {\n    const result: PR[] = []\n    for (const item of this._registrations.values()) {\n      result.push(item.provider)\n    }\n    return result\n  }\n}\n\nimport { ProviderResult } from '../provider'\nimport { defaultValue } from '../util'\nimport { CodeLensProviderShape } from './codeLens'\nimport { DiagnosticProviderShape } from './diagnostic'\nimport { InlayHintsProviderShape } from './inlayHint'\nimport { InlineValueProviderShape } from './inlineValue'\nimport { SemanticTokensProviderShape } from './semanticTokens'\nimport { DidChangeTextDocumentFeatureShape, DidCloseTextDocumentFeatureShape, DidOpenTextDocumentFeatureShape, DidSaveTextDocumentFeatureShape } from './textSynchronization'\nimport { WorkspaceProviderFeature } from './workspaceSymbol'\nimport { FoldingRangeProviderShape } from './foldingRange'\n\nexport interface FeatureClient<M, CO = object> {\n  clientOptions: CO\n  middleware: M\n  readonly id: string\n  readonly configuredSection: string | undefined\n  supportedMarkupKind: MarkupKind[]\n\n  code2ProtocolConverter: c2p.Converter\n\n  start(): Promise<void>\n  isRunning(): boolean\n  stop(): Promise<void>\n  attachExtensionName<T extends object>(provider: T): void\n\n  sendRequest<R, PR, E, RO>(type: ProtocolRequestType0<R, PR, E, RO>, token?: CancellationToken): Promise<R>\n  sendRequest<P, R, PR, E, RO>(type: ProtocolRequestType<P, R, PR, E, RO>, params: NoInfer<RequestParam<P>>, token?: CancellationToken): Promise<R>\n  sendRequest<R, E>(type: RequestType0<R, E>, token?: CancellationToken): Promise<R>\n  sendRequest<P, R, E>(type: RequestType<P, R, E>, params: NoInfer<RequestParam<P>>, token?: CancellationToken): Promise<R>\n  sendRequest<R>(method: string, token?: CancellationToken): Promise<R>\n  sendRequest<R>(method: string, param: any, token?: CancellationToken): Promise<R>\n\n  onRequest<R, PR, E, RO>(type: ProtocolRequestType0<R, PR, E, RO>, handler: NoInfer<RequestHandler0<R, E>>): Disposable\n  onRequest<P, R, PR, E, RO>(type: ProtocolRequestType<P, R, PR, E, RO>, handler: NoInfer<RequestHandler<P, R, E>>): Disposable\n  onRequest<R, E>(type: RequestType0<R, E>, handler: NoInfer<RequestHandler0<R, E>>): Disposable\n  onRequest<P, R, E>(type: RequestType<P, R, E>, handler: NoInfer<RequestHandler<P, R, E>>): Disposable\n  onRequest<R, E>(method: string, handler: GenericRequestHandler<R, E>): Disposable\n\n  sendNotification<RO>(type: ProtocolNotificationType0<RO>): Promise<void>\n  sendNotification<P, RO>(type: ProtocolNotificationType<P, RO>, params?: NoInfer<RequestParam<P>>): Promise<void>\n  sendNotification(type: NotificationType0): Promise<void>\n  sendNotification<P>(type: NotificationType<P>, params?: NoInfer<RequestParam<P>>): Promise<void>\n  sendNotification(method: string, params?: any): Promise<void>\n\n  onNotification<RO>(type: ProtocolNotificationType0<RO>, handler: NotificationHandler0): Disposable\n  onNotification<P, RO>(type: ProtocolNotificationType<P, RO>, handler: NoInfer<NotificationHandler<P>>): Disposable\n  onNotification(type: NotificationType0, handler: NotificationHandler0): Disposable\n  onNotification<P>(type: NotificationType<P>, handler: NoInfer<NotificationHandler<P>>): Disposable\n  onNotification(method: string, handler: GenericNotificationHandler): Disposable\n\n  onProgress<P>(type: ProgressType<P>, token: string | number, handler: NoInfer<NotificationHandler<P>>): Disposable\n\n  info(message: string, data?: any, showNotification?: boolean): void\n  warn(message: string, data?: any, showNotification?: boolean): void\n  error(message: string, data?: any, showNotification?: boolean | 'force'): void\n\n  handleFailedRequest<T>(type: MessageSignature, token: CancellationToken | undefined, error: any, defaultValue: T, showNotification?: boolean): T\n\n  getFeature(request: typeof DidChangeWorkspaceFoldersNotification.method): DynamicFeature<void>\n  getFeature(request: typeof ExecuteCommandRequest.method): DynamicFeature<ExecuteCommandRegistrationOptions>\n  getFeature(request: typeof DidChangeWatchedFilesNotification.method): DynamicFeature<DidChangeWatchedFilesRegistrationOptions>\n  getFeature(request: typeof DidOpenTextDocumentNotification.method): DidOpenTextDocumentFeatureShape\n  getFeature(request: typeof DidChangeTextDocumentNotification.method): DidChangeTextDocumentFeatureShape\n  getFeature(request: typeof WillSaveTextDocumentNotification.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentSendFeature<(textDocument: TextDocumentWillSaveEvent) => Promise<void>>\n  getFeature(request: typeof WillSaveTextDocumentWaitUntilRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentSendFeature<(textDocument: TextDocument) => ProviderResult<TextEdit[]>>\n  getFeature(request: typeof DidSaveTextDocumentNotification.method): DidSaveTextDocumentFeatureShape\n  getFeature(request: typeof DidCloseTextDocumentNotification.method): DidCloseTextDocumentFeatureShape\n  getFeature(request: typeof DidCreateFilesNotification.method): DynamicFeature<FileOperationRegistrationOptions> & { send: (event: FileCreateEvent) => Promise<void> }\n  getFeature(request: typeof DidRenameFilesNotification.method): DynamicFeature<FileOperationRegistrationOptions> & { send: (event: FileRenameEvent) => Promise<void> }\n  getFeature(request: typeof DidDeleteFilesNotification.method): DynamicFeature<FileOperationRegistrationOptions> & { send: (event: FileDeleteEvent) => Promise<void> }\n  getFeature(request: typeof WillCreateFilesRequest.method): DynamicFeature<FileOperationRegistrationOptions> & { send: (event: FileWillCreateEvent) => Promise<void> }\n  getFeature(request: typeof WillRenameFilesRequest.method): DynamicFeature<FileOperationRegistrationOptions> & { send: (event: FileWillRenameEvent) => Promise<void> }\n  getFeature(request: typeof WillDeleteFilesRequest.method): DynamicFeature<FileOperationRegistrationOptions> & { send: (event: FileWillDeleteEvent) => Promise<void> }\n  getFeature(request: typeof CompletionRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<CompletionItemProvider>\n  getFeature(request: typeof HoverRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<HoverProvider>\n  getFeature(request: typeof SignatureHelpRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<SignatureHelpProvider>\n  getFeature(request: typeof DefinitionRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DefinitionProvider>\n  getFeature(request: typeof ReferencesRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<ReferenceProvider>\n  getFeature(request: typeof DocumentHighlightRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DocumentHighlightProvider>\n  getFeature(request: typeof CodeActionRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<CodeActionProvider>\n  getFeature(request: typeof CodeLensRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<CodeLensProviderShape>\n  getFeature(request: typeof DocumentFormattingRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DocumentFormattingEditProvider>\n  getFeature(request: typeof DocumentRangeFormattingRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DocumentRangeFormattingEditProvider>\n  getFeature(request: typeof DocumentOnTypeFormattingRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<OnTypeFormattingEditProvider>\n  getFeature(request: typeof RenameRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<RenameProvider>\n  getFeature(request: typeof DocumentSymbolRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DocumentSymbolProvider>\n  getFeature(request: typeof DocumentLinkRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DocumentLinkProvider>\n  getFeature(request: typeof DocumentColorRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DocumentColorProvider>\n  getFeature(request: typeof DeclarationRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DeclarationProvider>\n  getFeature(request: typeof FoldingRangeRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<FoldingRangeProviderShape>\n  getFeature(request: typeof ImplementationRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<ImplementationProvider>\n  getFeature(request: typeof SelectionRangeRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<SelectionRangeProvider>\n  getFeature(request: typeof TypeDefinitionRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<TypeDefinitionProvider>\n  getFeature(request: typeof CallHierarchyPrepareRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<CallHierarchyProvider>\n  getFeature(request: typeof SemanticTokensRegistrationType.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<SemanticTokensProviderShape>\n  getFeature(request: typeof LinkedEditingRangeRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<LinkedEditingRangeProvider>\n  getFeature(request: typeof TypeHierarchyPrepareRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<TypeHierarchyProvider>\n  getFeature(request: typeof InlineCompletionRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<InlineCompletionItemProvider>\n  getFeature(request: typeof InlineValueRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<InlineValueProviderShape>\n  getFeature(request: typeof InlayHintRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<InlayHintsProviderShape>\n  getFeature(request: typeof WorkspaceSymbolRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & WorkspaceProviderFeature<WorkspaceSymbolProvider>\n  getFeature(request: typeof DocumentDiagnosticRequest.method): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DiagnosticProviderShape> | undefined\n  // getFeature(request: typeof NotebookDocumentSyncRegistrationType.method): DynamicFeature<NotebookDocumentSyncRegistrationOptions> & NotebookDocumentProviderShape | undefined;\n}\n"
  },
  {
    "path": "src/language-client/fileOperations.ts",
    "content": "'use strict'\nimport { Minimatch, MinimatchOptions } from 'minimatch'\nimport type { ClientCapabilities, CreateFilesParams, DeleteFilesParams, Disposable, Event, FileOperationClientCapabilities, FileOperationOptions, FileOperationPatternOptions, FileOperationRegistrationOptions, ProtocolNotificationType, ProtocolRequestType, RegistrationType, RenameFilesParams, RequestParam, ServerCapabilities, WorkspaceEdit } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport { FileCreateEvent, FileDeleteEvent, FileRenameEvent, FileWillCreateEvent, FileWillDeleteEvent, FileWillRenameEvent } from '../core/files'\nimport { defaultValue } from '../util'\nimport { FileType, getFileType } from '../util/fs'\nimport { minimatch } from '../util/node'\nimport {\n  CancellationToken, DidCreateFilesNotification, DidDeleteFilesNotification, DidRenameFilesNotification, FileOperationPatternKind, WillCreateFilesRequest, WillDeleteFilesRequest, WillRenameFilesRequest\n} from '../util/protocol'\nimport workspace from '../workspace'\nimport { BaseFeature, DynamicFeature, ensure, FeatureClient, FeatureState, NextSignature, RegistrationData } from './features'\nimport * as UUID from './utils/uuid'\n\nfunction access<T, K extends keyof T>(target: T, key: K): T[K] {\n  return target[key]\n}\n\nfunction assign<T, K extends keyof T>(target: T, key: K, value: T[K]): void {\n  target[key] = value\n}\n\n/**\n * File operation middleware\n * @since 3.16.0\n */\nexport interface FileOperationsMiddleware {\n  didCreateFiles?: NextSignature<FileCreateEvent, void>\n  willCreateFiles?: NextSignature<FileWillCreateEvent, Thenable<WorkspaceEdit | null | undefined>>\n  didRenameFiles?: NextSignature<FileRenameEvent, void>\n  willRenameFiles?: NextSignature<FileWillRenameEvent, Thenable<WorkspaceEdit | null | undefined>>\n  didDeleteFiles?: NextSignature<FileDeleteEvent, void>\n  willDeleteFiles?: NextSignature<FileWillDeleteEvent, Thenable<WorkspaceEdit | null | undefined>>\n}\n\ninterface FileOperationsWorkspaceMiddleware {\n  workspace?: FileOperationsMiddleware\n}\n\ninterface EventWithFiles<I> {\n  readonly files: ReadonlyArray<I>\n}\n\nabstract class FileOperationFeature<I, E extends EventWithFiles<I>>\n  extends BaseFeature<FileOperationsWorkspaceMiddleware, object> implements DynamicFeature<FileOperationRegistrationOptions> {\n  private _event: Event<E>\n  private _registrationType: RegistrationType<FileOperationRegistrationOptions>\n  private _clientCapability: keyof FileOperationClientCapabilities\n  private _serverCapability: keyof FileOperationOptions\n  private _listener: Disposable | undefined\n  private _filters = new Map<\n    string,\n    Array<{\n      scheme?: string\n      matcher: Minimatch\n      kind?: FileOperationPatternKind\n    }>\n  >()\n\n  constructor(\n    client: FeatureClient<FileOperationsWorkspaceMiddleware>,\n    event: Event<E>,\n    registrationType: RegistrationType<FileOperationRegistrationOptions>,\n    clientCapability: keyof FileOperationClientCapabilities,\n    serverCapability: keyof FileOperationOptions\n  ) {\n    super(client)\n    this._event = event\n    this._registrationType = registrationType\n    this._clientCapability = clientCapability\n    this._serverCapability = serverCapability\n  }\n\n  public getState(): FeatureState {\n    return { kind: 'workspace', id: this._registrationType.method, registrations: this._filters.size > 0 }\n  }\n\n  public get registrationType(): RegistrationType<FileOperationRegistrationOptions> {\n    return this._registrationType\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    const value = ensure(ensure(capabilities, 'workspace')!, 'fileOperations')!\n    // this happens n times but it is the same value so we tolerate this.\n    assign(value, 'dynamicRegistration', true)\n    assign(value, this._clientCapability, true)\n  }\n\n  public initialize(capabilities: ServerCapabilities): void {\n    const options = capabilities.workspace?.fileOperations\n    const capability = options !== undefined ? access(options, this._serverCapability) : undefined\n    if (capability?.filters !== undefined) {\n      try {\n        this.register({\n          id: UUID.generateUuid(),\n          registerOptions: { filters: capability.filters },\n        })\n      } catch (e) {\n        this._client.warn(\n          `Ignoring invalid glob pattern for ${this._serverCapability} registration: ${e}`\n        )\n      }\n    }\n  }\n\n  public register(data: RegistrationData<FileOperationRegistrationOptions>): void {\n    if (!this._listener) {\n      this._listener = this._event(this.send, this)\n    }\n    const minimatchFilter = data.registerOptions.filters.map(filter => {\n      const matcher = new minimatch.Minimatch(\n        filter.pattern.glob,\n        FileOperationFeature.asMinimatchOptions(filter.pattern.options)\n      )\n      if (!matcher.makeRe()) {\n        throw new Error(`Invalid pattern ${filter.pattern.glob}!`)\n      }\n      return { scheme: filter.scheme, matcher, kind: filter.pattern.matches }\n    })\n    this._filters.set(data.id, minimatchFilter)\n  }\n\n  public sendWithMiddleware<T>(fn: (...args: any[]) => Promise<T> | T, key: string, ...params: any[]): Promise<T> | T {\n    const middleware = defaultValue(defaultValue(this._client.middleware, {}).workspace, {})\n    return middleware[key] ? middleware[key](...params, fn) : fn(...params)\n  }\n\n  public abstract send(data: E): Promise<void>\n\n  public unregister(id: string): void {\n    this._filters.delete(id)\n  }\n\n  public dispose(): void {\n    this._filters.clear()\n    if (this._listener) {\n      this._listener.dispose()\n      this._listener = undefined\n    }\n  }\n\n  public async filter(event: E, prop: (i: I) => URI): Promise<E> {\n    // (Asynchronously) map each file onto a boolean of whether it matches\n    // any of the globs.\n    const fileMatches = await Promise.all(\n      event.files.map(async item => {\n        const uri = prop(item)\n        // Use fsPath to make this consistent with file system watchers but help\n        // minimatch to use '/' instead of `\\\\` if present.\n        const path = uri.fsPath.replace(/\\\\/g, '/')\n        for (const filters of this._filters.values()) {\n          for (const filter of filters) {\n            if (filter.scheme !== undefined && filter.scheme !== uri.scheme) {\n              continue\n            }\n            if (filter.matcher.match(path)) {\n              // The pattern matches. If kind is undefined then everything is ok\n              if (filter.kind === undefined) {\n                return true\n              }\n              const fileType = await getFileType(uri.fsPath)\n              // If we can't determine the file type than we treat it as a match.\n              // Dropping it would be another alternative.\n              if (fileType === undefined) {\n                this._client.error(`Failed to determine file type for ${uri.toString()}.`)\n                return true\n              }\n              if (\n                (fileType === FileType.File && filter.kind === FileOperationPatternKind.file) ||\n                (fileType === FileType.Directory && filter.kind === FileOperationPatternKind.folder)\n              ) {\n                return true\n              }\n            } else if (filter.kind === FileOperationPatternKind.folder) {\n              const fileType = await getFileType(uri.fsPath)\n              if (fileType === FileType.Directory && filter.matcher.match(`${path}/`)) {\n                return true\n              }\n            }\n          }\n        }\n        return false\n      })\n    )\n\n    // Filter the files to those that matched.\n    const files = event.files.filter((_, index) => fileMatches[index])\n\n    return { ...event, files }\n  }\n\n  public static asMinimatchOptions(options: FileOperationPatternOptions | undefined): MinimatchOptions | undefined {\n    if (options === undefined) {\n      return undefined\n    }\n    if (options.ignoreCase === true) {\n      return { nocase: true }\n    }\n    return undefined\n  }\n}\n\nabstract class NotificationFileOperationFeature<I, E extends { readonly files: ReadonlyArray<I> }, P> extends FileOperationFeature<I, E> {\n\n  private _notificationType: ProtocolNotificationType<P, FileOperationRegistrationOptions>\n  private _accessUri: (i: I) => URI\n  private _createParams: (e: E) => RequestParam<P>\n\n  constructor(\n    client: FeatureClient<FileOperationsWorkspaceMiddleware>,\n    event: Event<E>,\n    notificationType: ProtocolNotificationType<P, FileOperationRegistrationOptions>,\n    clientCapability: keyof FileOperationClientCapabilities,\n    serverCapability: keyof FileOperationOptions,\n    accessUri: (i: I) => URI,\n    createParams: (e: E) => RequestParam<P>\n  ) {\n    super(client, event, notificationType, clientCapability, serverCapability)\n    this._notificationType = notificationType\n    this._accessUri = accessUri\n    this._createParams = createParams\n  }\n\n  public async send(originalEvent: E): Promise<void> {\n    // Create a copy of the event that has the files filtered to match what the\n    // server wants.\n    const filteredEvent = await this.filter(originalEvent, this._accessUri)\n    if (filteredEvent.files.length) {\n      const next = async (event: E): Promise<void> => {\n        return this._client.sendNotification(\n          this._notificationType,\n          this._createParams(event)\n        )\n      }\n      let promise = this.doSend(filteredEvent, next)\n      if (promise) {\n        await promise.catch(e => {\n          this._client.error(`Sending notification ${this.registrationType.method} failed`, e)\n        })\n      }\n    }\n  }\n\n  protected abstract doSend(event: E, next: (event: E) => void): void | Promise<void>\n}\n\nexport class DidCreateFilesFeature extends NotificationFileOperationFeature<URI, FileCreateEvent, CreateFilesParams> {\n  constructor(client: FeatureClient<FileOperationsWorkspaceMiddleware>) {\n    super(\n      client,\n      workspace.onDidCreateFiles,\n      DidCreateFilesNotification.type,\n      'didCreate',\n      'didCreate',\n      (i: URI) => i,\n      client.code2ProtocolConverter.asDidCreateFilesParams\n    )\n  }\n\n  protected doSend(event: FileCreateEvent, next: (event: FileCreateEvent) => void): void | Promise<void> {\n    return this.sendWithMiddleware(next, 'didCreateFiles', event)\n  }\n}\n\nexport class DidRenameFilesFeature extends NotificationFileOperationFeature<{ oldUri: URI; newUri: URI }, FileRenameEvent, RenameFilesParams> {\n  constructor(client: FeatureClient<FileOperationsWorkspaceMiddleware>) {\n    super(\n      client,\n      workspace.onDidRenameFiles,\n      DidRenameFilesNotification.type,\n      'didRename',\n      'didRename',\n      (i: { oldUri: URI; newUri: URI }) => i.oldUri,\n      client.code2ProtocolConverter.asDidRenameFilesParams\n    )\n  }\n\n  protected doSend(event: FileRenameEvent, next: (event: FileRenameEvent) => void): void | Promise<void> {\n    return this.sendWithMiddleware(next, 'didRenameFiles', event)\n  }\n}\n\nexport class DidDeleteFilesFeature extends NotificationFileOperationFeature<URI, FileDeleteEvent, DeleteFilesParams> {\n  constructor(client: FeatureClient<FileOperationsWorkspaceMiddleware>) {\n    super(\n      client,\n      workspace.onDidDeleteFiles,\n      DidDeleteFilesNotification.type,\n      'didDelete',\n      'didDelete',\n      (i: URI) => i,\n      client.code2ProtocolConverter.asDidDeleteFilesParams\n    )\n  }\n\n  protected doSend(event: FileCreateEvent, next: (event: FileCreateEvent) => void): void | Promise<void> {\n    return this.sendWithMiddleware(next, 'didDeleteFiles', event)\n  }\n}\n\ninterface RequestEvent<I> {\n  readonly files: ReadonlyArray<I>\n  waitUntil(thenable: Thenable<WorkspaceEdit | any>): void\n}\n\nabstract class RequestFileOperationFeature<I, E extends RequestEvent<I>, P> extends FileOperationFeature<I, E> {\n  private _requestType: ProtocolRequestType<P, WorkspaceEdit | null, never, void, FileOperationRegistrationOptions>\n  private _accessUri: (i: I) => URI\n  private _createParams: (e: EventWithFiles<I>) => RequestParam<P>\n\n  constructor(\n    client: FeatureClient<FileOperationsWorkspaceMiddleware>,\n    event: Event<E>,\n    requestType: ProtocolRequestType<P, WorkspaceEdit | null, never, void, FileOperationRegistrationOptions>,\n    clientCapability: keyof FileOperationClientCapabilities,\n    serverCapability: keyof FileOperationOptions,\n    accessUri: (i: I) => URI,\n    createParams: (e: EventWithFiles<I>) => RequestParam<P>\n  ) {\n    super(client, event, requestType, clientCapability, serverCapability)\n    this._requestType = requestType\n    this._accessUri = accessUri\n    this._createParams = createParams\n  }\n\n  public async send(originalEvent: E & RequestEvent<I>): Promise<void> {\n    const waitUntil = this.waitUntil(originalEvent)\n    originalEvent.waitUntil(waitUntil)\n  }\n\n  private async waitUntil(originalEvent: E): Promise<WorkspaceEdit | null | undefined> {\n    // Create a copy of the event that has the files filtered to match what the\n    // server wants.\n    const filteredEvent = await this.filter(originalEvent, this._accessUri)\n\n    if (filteredEvent.files.length) {\n      const next = (event: EventWithFiles<I>): Promise<WorkspaceEdit | any> => {\n        return this.sendRequest(this._requestType, this._createParams(event), CancellationToken.None)\n      }\n      return this.doSend(filteredEvent, next)\n    } else {\n      return undefined\n    }\n  }\n\n  protected abstract doSend(event: E, next: (event: EventWithFiles<I>) => Thenable<WorkspaceEdit> | Thenable<any>): Thenable<WorkspaceEdit> | Thenable<any>\n}\n\nexport class WillCreateFilesFeature extends RequestFileOperationFeature<URI, FileWillCreateEvent, CreateFilesParams> {\n  constructor(client: FeatureClient<FileOperationsWorkspaceMiddleware>) {\n    super(\n      client,\n      workspace.onWillCreateFiles,\n      WillCreateFilesRequest.type,\n      'willCreate',\n      'willCreate',\n      (i: URI) => i,\n      client.code2ProtocolConverter.asWillCreateFilesParams\n    )\n  }\n\n  protected doSend(event: FileWillCreateEvent, next: (event: FileWillCreateEvent) => Thenable<WorkspaceEdit> | Thenable<any>): Thenable<WorkspaceEdit> | Thenable<any> {\n    return this.sendWithMiddleware(next, 'willCreateFiles', event)\n  }\n}\n\nexport class WillRenameFilesFeature extends RequestFileOperationFeature<{ oldUri: URI; newUri: URI }, FileWillRenameEvent, RenameFilesParams> {\n  constructor(client: FeatureClient<FileOperationsWorkspaceMiddleware>) {\n    super(\n      client,\n      workspace.onWillRenameFiles,\n      WillRenameFilesRequest.type,\n      'willRename',\n      'willRename',\n      (i: { oldUri: URI; newUri: URI }) => i.oldUri,\n      client.code2ProtocolConverter.asWillRenameFilesParams\n    )\n  }\n\n  protected doSend(event: FileWillRenameEvent, next: (event: FileWillRenameEvent) => Thenable<WorkspaceEdit> | Thenable<any>): Thenable<WorkspaceEdit> | Thenable<any> {\n    return this.sendWithMiddleware(next, 'willRenameFiles', event)\n  }\n}\n\nexport class WillDeleteFilesFeature extends RequestFileOperationFeature<URI, FileWillDeleteEvent, DeleteFilesParams> {\n  constructor(client: FeatureClient<FileOperationsWorkspaceMiddleware>) {\n    super(\n      client,\n      workspace.onWillDeleteFiles,\n      WillDeleteFilesRequest.type,\n      'willDelete',\n      'willDelete',\n      (i: URI) => i,\n      client.code2ProtocolConverter.asWillDeleteFilesParams\n    )\n  }\n\n  protected doSend(event: FileWillDeleteEvent, next: (event: FileWillDeleteEvent) => Thenable<WorkspaceEdit> | Thenable<any>): Thenable<WorkspaceEdit> | Thenable<any> {\n    return this.sendWithMiddleware(next, 'willDeleteFiles', event)\n  }\n}\n"
  },
  {
    "path": "src/language-client/fileSystemWatcher.ts",
    "content": "'use strict'\nimport type {\n  ClientCapabilities, DidChangeWatchedFilesRegistrationOptions, Disposable, DocumentSelector, FileEvent, RegistrationType,\n  ServerCapabilities\n} from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport RelativePatternImpl from '../model/relativePattern'\nimport { GlobPattern, IFileSystemWatcher } from '../types'\nimport { defaultValue, disposeAll } from '../util'\nimport * as Is from '../util/is'\nimport { DidChangeWatchedFilesNotification, FileChangeType, RelativePattern, WatchKind } from '../util/protocol'\nimport workspace from '../workspace'\nimport { DynamicFeature, ensure, FeatureClient, FeatureState, RegistrationData } from './features'\nimport * as UUID from './utils/uuid'\n\nexport interface DidChangeWatchedFileSignature {\n  (this: void, event: FileEvent): void\n}\n\ninterface $FileEventOptions {\n  synchronize?: {\n    fileEvents?: IFileSystemWatcher | IFileSystemWatcher[]\n  }\n}\n\nexport function asRelativePattern(rp: RelativePattern): RelativePatternImpl {\n  let { baseUri, pattern } = rp\n  if (typeof baseUri === 'string') {\n    return new RelativePatternImpl(URI.parse(baseUri), pattern)\n  }\n  return new RelativePatternImpl(baseUri, pattern)\n}\n\nexport class FileSystemWatcherFeature implements DynamicFeature<DidChangeWatchedFilesRegistrationOptions> {\n  private _watchers: Map<string, Disposable[]> = new Map<string, Disposable[]>()\n  private _fileEventsMap: Map<string, FileEvent> = new Map()\n  private readonly _client: FeatureClient<object, $FileEventOptions>\n  private readonly _notifyFileEvent: (event: FileEvent) => void\n\n  constructor(client: FeatureClient<object, $FileEventOptions>, notifyFileEvent: (event: FileEvent) => void) {\n    this._client = client\n    this._notifyFileEvent = notifyFileEvent\n    this._watchers = new Map<string, Disposable[]>()\n  }\n\n  public getState(): FeatureState {\n    return { kind: 'workspace', id: this.registrationType.method, registrations: this._watchers.size > 0 }\n  }\n\n  public get registrationType(): RegistrationType<DidChangeWatchedFilesRegistrationOptions> {\n    return DidChangeWatchedFilesNotification.type\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    ensure(ensure(capabilities, 'workspace')!, 'didChangeWatchedFiles')!.dynamicRegistration = true\n    ensure(ensure(capabilities, 'workspace')!, 'didChangeWatchedFiles')!.relativePatternSupport = true\n  }\n\n  public initialize(_capabilities: ServerCapabilities, _documentSelector: DocumentSelector): void {\n    let fileEvents = defaultValue(this._client.clientOptions.synchronize, {}).fileEvents\n    if (!fileEvents) return\n    let watchers: IFileSystemWatcher[] = Array.isArray(fileEvents) ? fileEvents : [fileEvents]\n    let disposables: Disposable[] = []\n    for (let fileSystemWatcher of watchers) {\n      disposables.push(fileSystemWatcher)\n      this.hookListeners(\n        fileSystemWatcher,\n        !fileSystemWatcher.ignoreCreateEvents,\n        !fileSystemWatcher.ignoreChangeEvents,\n        !fileSystemWatcher.ignoreDeleteEvents,\n        disposables)\n    }\n    this._watchers.set(UUID.generateUuid(), disposables)\n  }\n\n  public register(data: RegistrationData<DidChangeWatchedFilesRegistrationOptions>): void {\n    if (!Array.isArray(data.registerOptions.watchers)) {\n      return\n    }\n    let disposables: Disposable[] = []\n    for (let watcher of data.registerOptions.watchers) {\n      let globPattern: GlobPattern\n      if (Is.string(watcher.globPattern)) {\n        globPattern = watcher.globPattern\n      } else if (RelativePattern.is(watcher.globPattern)) {\n        globPattern = asRelativePattern(watcher.globPattern)\n      } else {\n        continue\n      }\n      let watchCreate = true\n      let watchChange = true\n      let watchDelete = true\n      if (watcher.kind != null) {\n        watchCreate = (watcher.kind & WatchKind.Create) !== 0\n        watchChange = (watcher.kind & WatchKind.Change) !== 0\n        watchDelete = (watcher.kind & WatchKind.Delete) !== 0\n      }\n      let fileSystemWatcher = workspace.createFileSystemWatcher(\n        globPattern,\n        !watchCreate,\n        !watchChange,\n        !watchDelete\n      )\n      this.hookListeners(\n        fileSystemWatcher,\n        watchCreate,\n        watchChange,\n        watchDelete,\n        disposables\n      )\n      disposables.push(fileSystemWatcher)\n    }\n    this._watchers.set(data.id, disposables)\n  }\n\n  private hookListeners(\n    fileSystemWatcher: IFileSystemWatcher,\n    watchCreate: boolean,\n    watchChange: boolean,\n    watchDelete: boolean,\n    listeners: Disposable[]\n  ): void {\n    const client = this._client\n    // TODO rename support\n    if (watchCreate) {\n      fileSystemWatcher.onDidCreate(\n        resource =>\n          this._notifyFileEvent({\n            uri: client.code2ProtocolConverter.asUri(resource),\n            type: FileChangeType.Created\n          }),\n        null,\n        listeners\n      )\n    }\n    if (watchChange) {\n      fileSystemWatcher.onDidChange(\n        resource =>\n          this._notifyFileEvent({\n            uri: client.code2ProtocolConverter.asUri(resource),\n            type: FileChangeType.Changed\n          }),\n        null,\n        listeners\n      )\n    }\n    if (watchDelete) {\n      fileSystemWatcher.onDidDelete(\n        resource =>\n          this._notifyFileEvent({\n            uri: client.code2ProtocolConverter.asUri(resource),\n            type: FileChangeType.Deleted\n          }),\n        null,\n        listeners\n      )\n    }\n  }\n\n  public unregister(id: string): void {\n    let disposables = this._watchers.get(id)\n    if (disposables) {\n      this._watchers.delete(id)\n      disposeAll(disposables)\n    }\n  }\n\n  public dispose(): void {\n    this._fileEventsMap.clear()\n    this._watchers.forEach(disposables => {\n      disposeAll(disposables)\n    })\n    this._watchers.clear()\n  }\n}\n"
  },
  {
    "path": "src/language-client/foldingRange.ts",
    "content": "'use strict'\nimport { Emitter, FoldingRangeRefreshRequest, type CancellationToken, type ClientCapabilities, type Disposable, type DocumentSelector, type FoldingRange, type FoldingRangeOptions, type FoldingRangeParams, type FoldingRangeRegistrationOptions, type ServerCapabilities } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport languages from '../languages'\nimport { FoldingContext, FoldingRangeProvider, ProviderResult } from '../provider'\nimport { FoldingRangeKind, FoldingRangeRequest } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\n\nexport type ProvideFoldingRangeSignature = (\n  this: void,\n  document: TextDocument,\n  context: FoldingContext,\n  token: CancellationToken\n) => ProviderResult<FoldingRange[]>\n\nexport interface FoldingRangeProviderMiddleware {\n  provideFoldingRanges?: (\n    this: void,\n    document: TextDocument,\n    context: FoldingContext,\n    token: CancellationToken,\n    next: ProvideFoldingRangeSignature\n  ) => ProviderResult<FoldingRange[]>\n}\n\nexport interface FoldingRangeProviderShape {\n  provider: FoldingRangeProvider;\n  onDidChangeFoldingRange: Emitter<void>;\n}\n\nexport class FoldingRangeFeature extends TextDocumentLanguageFeature<\n  boolean | FoldingRangeOptions, FoldingRangeRegistrationOptions, FoldingRangeProviderShape, FoldingRangeProviderMiddleware\n> {\n  constructor(client: FeatureClient<FoldingRangeProviderMiddleware>) {\n    super(client, FoldingRangeRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    let capability = ensure(ensure(capabilities, 'textDocument')!, 'foldingRange')!\n    capability.dynamicRegistration = true\n    capability.rangeLimit = 5000\n    capability.lineFoldingOnly = true\n    capability.foldingRangeKind = { valueSet: [FoldingRangeKind.Comment, FoldingRangeKind.Imports, FoldingRangeKind.Region] }\n    capability.foldingRange = { collapsedText: false }\n    ensure(ensure(capabilities, 'workspace')!, 'foldingRange')!.refreshSupport = true\n  }\n\n  public initialize(\n    capabilities: ServerCapabilities,\n    documentSelector: DocumentSelector\n  ): void {\n    this._client.onRequest(FoldingRangeRefreshRequest.type, async () => {\n      for (const provider of this.getAllProviders()) {\n        provider.onDidChangeFoldingRange.fire()\n      }\n    })\n\n    const [id, options] = this.getRegistration(documentSelector, capabilities.foldingRangeProvider)\n    if (!id || !options) {\n      return\n    }\n    this.register({ id, registerOptions: options })\n  }\n\n  protected registerLanguageProvider(\n    options: FoldingRangeRegistrationOptions\n  ): [Disposable, FoldingRangeProviderShape] {\n    const eventEmitter: Emitter<void> = new Emitter<void>()\n    const provider: FoldingRangeProvider = {\n      onDidChangeFoldingRanges: eventEmitter.event,\n      provideFoldingRanges: (document, context, token) => {\n        const client = this._client\n        const provideFoldingRanges: ProvideFoldingRangeSignature = (document, _, token) => {\n          const requestParams: FoldingRangeParams = {\n            textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document)\n          }\n          return this.sendRequest(FoldingRangeRequest.type, requestParams, token)\n        }\n        const middleware = client.middleware\n        return middleware.provideFoldingRanges\n          ? middleware.provideFoldingRanges(document, context, token, provideFoldingRanges)\n          : provideFoldingRanges(document, context, token)\n      }\n    }\n\n    this._client.attachExtensionName(provider)\n    return [languages.registerFoldingRangeProvider(options.documentSelector, provider), { provider, onDidChangeFoldingRange: eventEmitter }]\n  }\n}\n"
  },
  {
    "path": "src/language-client/formatting.ts",
    "content": "'use strict'\nimport type { CancellationToken, ClientCapabilities, Disposable, DocumentFormattingOptions, DocumentFormattingParams, DocumentFormattingRegistrationOptions, DocumentOnTypeFormattingOptions, DocumentOnTypeFormattingParams, DocumentOnTypeFormattingRegistrationOptions, DocumentRangeFormattingOptions, DocumentRangeFormattingParams, DocumentRangeFormattingRegistrationOptions, DocumentSelector, FormattingOptions, Position, Range, ServerCapabilities, TextDocumentRegistrationOptions, TextEdit } from 'vscode-languageserver-protocol'\nimport { TextDocument } from \"vscode-languageserver-textdocument\"\nimport languages from '../languages'\nimport { DocumentFormattingEditProvider, DocumentRangeFormattingEditProvider, OnTypeFormattingEditProvider, ProviderResult } from '../provider'\nimport {\n  DocumentFormattingRequest, DocumentOnTypeFormattingRequest, DocumentRangeFormattingRequest\n} from '../util/protocol'\nimport { FeatureClient, TextDocumentLanguageFeature, ensure } from './features'\nimport * as UUID from './utils/uuid'\n\nexport interface ProvideDocumentFormattingEditsSignature {\n  (\n    this: void,\n    document: TextDocument,\n    options: FormattingOptions,\n    token: CancellationToken\n  ): ProviderResult<TextEdit[]>\n}\n\nexport interface ProvideDocumentRangeFormattingEditsSignature {\n  (\n    this: void,\n    document: TextDocument,\n    range: Range,\n    options: FormattingOptions,\n    token: CancellationToken\n  ): ProviderResult<TextEdit[]>\n}\n\nexport interface ProvideOnTypeFormattingEditsSignature {\n  (\n    this: void,\n    document: TextDocument,\n    position: Position,\n    ch: string,\n    options: FormattingOptions,\n    token: CancellationToken\n  ): ProviderResult<TextEdit[]>\n}\n\nexport interface $FormattingOptions {\n  formatterPriority?: number\n}\n\nexport interface FormattingMiddleware {\n  provideDocumentFormattingEdits?: (\n    this: void,\n    document: TextDocument,\n    options: FormattingOptions,\n    token: CancellationToken,\n    next: ProvideDocumentFormattingEditsSignature\n  ) => ProviderResult<TextEdit[]>\n  provideDocumentRangeFormattingEdits?: (\n    this: void,\n    document: TextDocument,\n    range: Range,\n    options: FormattingOptions,\n    token: CancellationToken,\n    next: ProvideDocumentRangeFormattingEditsSignature\n  ) => ProviderResult<TextEdit[]>\n  provideOnTypeFormattingEdits?: (\n    this: void,\n    document: TextDocument,\n    position: Position,\n    ch: string,\n    options: FormattingOptions,\n    token: CancellationToken,\n    next: ProvideOnTypeFormattingEditsSignature\n  ) => ProviderResult<TextEdit[]>\n}\n\nexport class DocumentFormattingFeature extends TextDocumentLanguageFeature<\n  boolean | DocumentFormattingOptions, DocumentFormattingRegistrationOptions, DocumentFormattingEditProvider, FormattingMiddleware, $FormattingOptions\n> {\n\n  constructor(client: FeatureClient<FormattingMiddleware>) {\n    super(client, DocumentFormattingRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    ensure(\n      ensure(capabilities, 'textDocument')!,\n      'formatting'\n    )!.dynamicRegistration = true\n  }\n\n  public initialize(\n    capabilities: ServerCapabilities,\n    documentSelector: DocumentSelector\n  ): void {\n    const options = this.getRegistrationOptions(documentSelector, capabilities.documentFormattingProvider)\n    if (!options) {\n      return\n    }\n    this.register({\n      id: UUID.generateUuid(),\n      registerOptions: options\n    })\n  }\n\n  protected registerLanguageProvider(\n    options: TextDocumentRegistrationOptions\n  ): [Disposable, DocumentFormattingEditProvider] {\n    const provider: DocumentFormattingEditProvider = {\n      provideDocumentFormattingEdits: (document, options, token) => {\n        const client = this._client\n        const provideDocumentFormattingEdits: ProvideDocumentFormattingEditsSignature = (document, options, token) => {\n          const params: DocumentFormattingParams = {\n            textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),\n            options\n          }\n          return this.sendRequest(DocumentFormattingRequest.type, params, token)\n        }\n        const middleware = client.middleware!\n        return middleware.provideDocumentFormattingEdits\n          ? middleware.provideDocumentFormattingEdits(document, options, token, provideDocumentFormattingEdits)\n          : provideDocumentFormattingEdits(document, options, token)\n      }\n    }\n    this._client.attachExtensionName(provider)\n    return [\n      languages.registerDocumentFormatProvider(options.documentSelector!, provider, this._client.clientOptions.formatterPriority),\n      provider\n    ]\n  }\n}\n\nexport class DocumentRangeFormattingFeature extends TextDocumentLanguageFeature<\n  boolean | DocumentRangeFormattingOptions, DocumentRangeFormattingRegistrationOptions, DocumentRangeFormattingEditProvider, FormattingMiddleware, $FormattingOptions\n> {\n  constructor(client: FeatureClient<FormattingMiddleware>) {\n    super(client, DocumentRangeFormattingRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    ensure(\n      ensure(capabilities, 'textDocument')!,\n      'rangeFormatting'\n    )!.dynamicRegistration = true\n  }\n\n  public initialize(\n    capabilities: ServerCapabilities,\n    documentSelector: DocumentSelector\n  ): void {\n    const options = this.getRegistrationOptions(documentSelector, capabilities.documentRangeFormattingProvider)\n    if (!options) {\n      return\n    }\n    this.register({\n      id: UUID.generateUuid(),\n      registerOptions: options\n    })\n  }\n\n  protected registerLanguageProvider(\n    options: TextDocumentRegistrationOptions\n  ): [Disposable, DocumentRangeFormattingEditProvider] {\n    const provider: DocumentRangeFormattingEditProvider = {\n      provideDocumentRangeFormattingEdits: (document, range, options, token) => {\n        const client = this._client\n        const provideDocumentRangeFormattingEdits: ProvideDocumentRangeFormattingEditsSignature = (document, range, options, token) => {\n          const params: DocumentRangeFormattingParams = {\n            textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),\n            range,\n            options,\n          }\n          return this.sendRequest(DocumentRangeFormattingRequest.type, params, token)\n        }\n        const middleware = client.middleware!\n        return middleware.provideDocumentRangeFormattingEdits\n          ? middleware.provideDocumentRangeFormattingEdits(document, range, options, token, provideDocumentRangeFormattingEdits)\n          : provideDocumentRangeFormattingEdits(document, range, options, token)\n      }\n    }\n    this._client.attachExtensionName(provider)\n    return [\n      languages.registerDocumentRangeFormatProvider(options.documentSelector, provider, this._client.clientOptions.formatterPriority),\n      provider\n    ]\n  }\n}\n\nexport class DocumentOnTypeFormattingFeature extends TextDocumentLanguageFeature<\n  DocumentOnTypeFormattingOptions, DocumentOnTypeFormattingRegistrationOptions, OnTypeFormattingEditProvider, FormattingMiddleware\n> {\n\n  constructor(client: FeatureClient<FormattingMiddleware>) {\n    super(client, DocumentOnTypeFormattingRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    ensure(ensure(capabilities, 'textDocument')!, 'onTypeFormatting')!.dynamicRegistration = true\n  }\n\n  public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {\n    const options = this.getRegistrationOptions(documentSelector, capabilities.documentOnTypeFormattingProvider)\n    if (!options) {\n      return\n    }\n    this.register({\n      id: UUID.generateUuid(),\n      registerOptions: options\n    })\n  }\n\n  protected registerLanguageProvider(options: DocumentOnTypeFormattingRegistrationOptions): [Disposable, OnTypeFormattingEditProvider] {\n    const provider: OnTypeFormattingEditProvider = {\n      provideOnTypeFormattingEdits: (document, position, ch, options, token) => {\n        const client = this._client\n        const provideOnTypeFormattingEdits: ProvideOnTypeFormattingEditsSignature = (document, position, ch, options, token) => {\n          const params: DocumentOnTypeFormattingParams = {\n            textDocument: client.code2ProtocolConverter.asVersionedTextDocumentIdentifier(document),\n            position,\n            ch,\n            options\n          }\n          return this.sendRequest(DocumentOnTypeFormattingRequest.type, params, token)\n        }\n        const middleware = client.middleware!\n        return middleware.provideOnTypeFormattingEdits\n          ? middleware.provideOnTypeFormattingEdits(document, position, ch, options, token, provideOnTypeFormattingEdits)\n          : provideOnTypeFormattingEdits(document, position, ch, options, token)\n      }\n    }\n\n    this._client.attachExtensionName(provider)\n    const moreTriggerCharacter = options.moreTriggerCharacter || []\n    const characters = [options.firstTriggerCharacter, ...moreTriggerCharacter]\n    return [languages.registerOnTypeFormattingEditProvider(options.documentSelector!, provider, characters), provider]\n  }\n}\n"
  },
  {
    "path": "src/language-client/hover.ts",
    "content": "'use strict'\nimport type { CancellationToken, ClientCapabilities, Disposable, DocumentSelector, Hover, HoverOptions, HoverRegistrationOptions, Position, ServerCapabilities } from 'vscode-languageserver-protocol'\nimport { TextDocument } from \"vscode-languageserver-textdocument\"\nimport languages from '../languages'\nimport { HoverProvider, ProviderResult } from '../provider'\nimport { HoverRequest } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\nimport * as UUID from './utils/uuid'\n\nexport interface ProvideHoverSignature {\n  (\n    this: void,\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken\n  ): ProviderResult<Hover>\n}\n\nexport interface HoverMiddleware {\n  provideHover?: (\n    this: void,\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken,\n    next: ProvideHoverSignature\n  ) => ProviderResult<Hover>\n}\n\nexport class HoverFeature extends TextDocumentLanguageFeature<\n  boolean | HoverOptions, HoverRegistrationOptions, HoverProvider, HoverMiddleware\n> {\n  constructor(client: FeatureClient<HoverMiddleware>) {\n    super(client, HoverRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    const hoverCapability = ensure(\n      ensure(capabilities, 'textDocument')!,\n      'hover'\n    )!\n    hoverCapability.dynamicRegistration = true\n    hoverCapability.contentFormat = this._client.supportedMarkupKind\n  }\n\n  public initialize(\n    capabilities: ServerCapabilities,\n    documentSelector: DocumentSelector\n  ): void {\n    const options = this.getRegistrationOptions(documentSelector, capabilities.hoverProvider)\n    if (!options) {\n      return\n    }\n    this.register({\n      id: UUID.generateUuid(),\n      registerOptions: options\n    })\n  }\n\n  protected registerLanguageProvider(\n    options: HoverRegistrationOptions\n  ): [Disposable, HoverProvider] {\n    const provider: HoverProvider = {\n      provideHover: (document, position, token) => {\n        const client = this._client\n        const provideHover: ProvideHoverSignature = (document, position, token) => {\n          return this.sendRequest(\n            HoverRequest.type,\n            client.code2ProtocolConverter.asTextDocumentPositionParams(document, position),\n            token\n          )\n        }\n\n        const middleware = client.middleware!\n        return middleware.provideHover\n          ? middleware.provideHover(document, position, token, provideHover)\n          : provideHover(document, position, token)\n      }\n    }\n    this._client.attachExtensionName(provider)\n    return [languages.registerHoverProvider(options.documentSelector!, provider), provider]\n  }\n}\n"
  },
  {
    "path": "src/language-client/implementation.ts",
    "content": "'use strict'\nimport type { CancellationToken, ClientCapabilities, Definition, DefinitionLink, Disposable, DocumentSelector, ImplementationOptions, ImplementationRegistrationOptions, Position, ServerCapabilities } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport languages from '../languages'\nimport { ImplementationProvider, ProviderResult } from '../provider'\nimport { ImplementationRequest } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\n\nexport interface ProvideImplementationSignature {\n  (this: void, document: TextDocument, position: Position, token: CancellationToken): ProviderResult<Definition | DefinitionLink[]>\n}\n\nexport interface ImplementationMiddleware {\n  provideImplementation?: (this: void, document: TextDocument, position: Position, token: CancellationToken, next: ProvideImplementationSignature) => ProviderResult<Definition | DefinitionLink[]>\n}\n\nexport class ImplementationFeature extends TextDocumentLanguageFeature<boolean | ImplementationOptions, ImplementationRegistrationOptions, ImplementationProvider, ImplementationMiddleware> {\n\n  constructor(client: FeatureClient<ImplementationMiddleware>) {\n    super(client, ImplementationRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    const implementationSupport = ensure(ensure(capabilities, 'textDocument')!, 'implementation')!\n    implementationSupport.dynamicRegistration = true\n    implementationSupport.linkSupport = true\n  }\n\n  public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {\n    const [id, options] = this.getRegistration(documentSelector, capabilities.implementationProvider)\n    if (!id || !options) {\n      return\n    }\n    this.register({ id, registerOptions: options })\n  }\n\n  protected registerLanguageProvider(options: ImplementationRegistrationOptions): [Disposable, ImplementationProvider] {\n    const provider: ImplementationProvider = {\n      provideImplementation: (document, position, token) => {\n        const client = this._client\n        const provideImplementation: ProvideImplementationSignature = (document, position, token) =>\n          this.sendRequest(ImplementationRequest.type, client.code2ProtocolConverter.asTextDocumentPositionParams(document, position), token)\n        const middleware = client.middleware\n        return middleware.provideImplementation\n          ? middleware.provideImplementation(document, position, token, provideImplementation)\n          : provideImplementation(document, position, token)\n      }\n    }\n    this._client.attachExtensionName(provider)\n    return [languages.registerImplementationProvider(options.documentSelector, provider), provider]\n  }\n}\n"
  },
  {
    "path": "src/language-client/index.ts",
    "content": "/* eslint-disable @typescript-eslint/prefer-promise-reject-errors */\n'use strict'\n/* eslint-disable no-redeclare */\nimport { ForkOptions as CForkOptions, ChildProcess, ChildProcessWithoutNullStreams, SpawnOptions } from 'child_process'\nimport * as stream from 'stream'\nimport { MessageReader, MessageWriter } from 'vscode-languageserver-protocol/node'\nimport { URI } from 'vscode-uri'\nimport { createLogger } from '../logger'\nimport { OutputChannel } from '../types'\nimport { disposeAll, getConditionValue } from '../util'\nimport * as Is from '../util/is'\nimport { child_process, fs, path, readline } from '../util/node'\nimport { terminate } from '../util/processes'\nimport { Disposable, generateRandomPipeName, IPCMessageReader, IPCMessageWriter, StreamMessageReader, StreamMessageWriter } from '../util/protocol'\nimport workspace from '../workspace'\nimport { BaseLanguageClient, LanguageClientOptions, MessageTransports, ShutdownMode } from './client'\nimport { createClientPipeTransport, createClientSocketTransport, currentTimeStamp } from './utils'\n\nconst logger = createLogger('language-client-index')\nconst debugStartWith: string[] = ['--debug=', '--debug-brk=', '--inspect=', '--inspect-brk=']\nconst debugEquals: string[] = ['--debug', '--debug-brk', '--inspect', '--inspect-brk']\nconst STOP_TIMEOUT = getConditionValue(2000, 10)\nconst RESTART_TIMEOUT = getConditionValue(1000, 10)\n\nexport * from './client'\n\ndeclare let v8debug: any\n\nexport enum TransportKind {\n  stdio,\n  ipc,\n  pipe,\n  socket\n}\n\nexport interface SocketTransport {\n  kind: TransportKind.socket\n  port: number\n}\n\nexport interface DisposableTransport {\n  onConnected(): Promise<[MessageReader, MessageWriter]>\n  dispose(): void\n}\n\nnamespace Transport {\n  export function isSocket(value: Transport): value is SocketTransport {\n    let candidate = value as SocketTransport\n    return (\n      candidate &&\n      candidate.kind === TransportKind.socket &&\n      Is.number(candidate.port)\n    )\n  }\n}\n\n/**\n * To avoid any timing, pipe name or port number issues the pipe (TransportKind.pipe)\n * and the sockets (TransportKind.socket and SocketTransport) are owned by the\n * VS Code processes. The server process simply connects to the pipe / socket.\n * In node term the VS Code process calls `createServer`, then starts the server\n * process, waits until the server process has connected to the pipe / socket\n * and then signals that the connection has been established and messages can\n * be send back and forth. If the language server is implemented in a different\n * program language the server simply needs to create a connection to the\n * passed pipe name or port number.\n */\nexport type Transport = TransportKind | SocketTransport\n\nexport interface ExecutableOptions {\n  cwd?: string\n  env?: any\n  detached?: boolean\n  shell?: boolean\n}\n\nexport interface Executable {\n  command: string\n  transport?: Transport\n  args?: string[]\n  options?: ExecutableOptions\n}\n\nnamespace Executable {\n  export function is(value: any): value is Executable {\n    return Is.string(value.command)\n  }\n}\n\nexport interface ForkOptions {\n  cwd?: string\n  env?: any\n  execPath?: string\n  encoding?: string\n  execArgv?: string[]\n}\n\nexport interface NodeModule {\n  module: string\n  transport?: Transport\n  args?: string[]\n  runtime?: string\n  options?: ForkOptions\n}\n\nnamespace NodeModule {\n  export function is(value: any): value is NodeModule {\n    return Is.string(value.module)\n  }\n}\n\nexport interface StreamInfo {\n  writer: NodeJS.WritableStream\n  reader: NodeJS.ReadableStream\n  detached?: boolean\n}\n\nnamespace StreamInfo {\n  export function is(value: any): value is StreamInfo {\n    let candidate = value as StreamInfo\n    return (\n      candidate && candidate.writer !== void 0 && candidate.reader !== void 0\n    )\n  }\n}\n\nexport interface ChildProcessInfo {\n  process: ChildProcess\n  detached: boolean\n}\n\nnamespace ChildProcessInfo {\n  export function is(value: any): value is ChildProcessInfo {\n    let candidate = value as ChildProcessInfo\n    return (\n      candidate &&\n      candidate.process !== void 0 &&\n      typeof candidate.detached === 'boolean'\n    )\n  }\n}\n\nexport type ServerOptions =\n  | Executable\n  | { run: Executable; debug: Executable }\n  | { run: NodeModule; debug: NodeModule }\n  | NodeModule\n  | (() => Promise<ChildProcess | StreamInfo | MessageTransports | ChildProcessInfo>)\n\nexport class LanguageClient extends BaseLanguageClient {\n  private _forceDebug: boolean\n  private _isInDebugMode: boolean\n\n  private _serverProcess: ChildProcess | undefined\n  private _isDetached: boolean | undefined\n  private _serverOptions: ServerOptions\n\n  public constructor(\n    name: string,\n    serverOptions: ServerOptions,\n    clientOptions: LanguageClientOptions,\n    forceDebug?: boolean\n  )\n  public constructor(\n    id: string,\n    name: string,\n    serverOptions: ServerOptions,\n    clientOptions: LanguageClientOptions,\n    forceDebug?: boolean\n  )\n  public constructor(\n    arg1: string,\n    arg2: string | ServerOptions,\n    arg3: LanguageClientOptions | ServerOptions,\n    arg4?: boolean | LanguageClientOptions,\n    arg5?: boolean\n  ) {\n    let id: string\n    let name: string\n    let serverOptions: ServerOptions\n    let clientOptions: LanguageClientOptions\n    let forceDebug: boolean\n    if (Is.string(arg2)) {\n      id = arg1\n      name = arg2\n      serverOptions = arg3 as ServerOptions\n      clientOptions = arg4 as LanguageClientOptions\n      forceDebug = !!arg5\n    } else {\n      // first signature\n      id = arg1.toLowerCase()\n      name = arg1\n      serverOptions = arg2\n      clientOptions = arg3 as LanguageClientOptions\n      forceDebug = arg4 as boolean\n    }\n    super(id, name, clientOptions)\n    this._serverOptions = serverOptions\n    this._forceDebug = !!forceDebug\n    this._isInDebugMode = !!forceDebug\n  }\n\n  protected shutdown(mode: ShutdownMode, timeout: number): Promise<void> {\n    return super.shutdown(mode, timeout).then(() => {\n      if (this._serverProcess) {\n        let toCheck = this._serverProcess\n        this._serverProcess = undefined\n        if (this._isDetached === void 0 || !this._isDetached) {\n          checkProcessDied(toCheck)\n        }\n        this._isDetached = undefined\n      }\n    }, err => {\n      if (this._serverProcess && err.message.includes('timed out')) {\n        this._serverProcess.kill('SIGKILL')\n        this._serverProcess = undefined\n        return\n      }\n      throw err\n    })\n  }\n\n  public get serviceState() {\n    return this._state\n  }\n\n  protected handleConnectionClosed(): Promise<void> {\n    this._serverProcess = undefined\n    return super.handleConnectionClosed()\n  }\n\n  public get isInDebugMode(): boolean {\n    return this._isInDebugMode\n  }\n\n  public async restart(): Promise<void> {\n    await this.stop()\n    // We are in debug mode. Wait a little before we restart\n    // so that the debug port can be freed. We can safely ignore\n    // the disposable returned from start since it will call\n    // stop on the same client instance.\n    if (this.isInDebugMode) {\n      await new Promise(resolve => setTimeout(resolve, RESTART_TIMEOUT))\n      await this._start()\n    } else {\n      await this._start()\n    }\n  }\n\n  protected createMessageTransports(encoding: string): Promise<MessageTransports> {\n\n    function getEnvironment(env: any, fork: boolean): any {\n      if (!env && !fork) {\n        return undefined\n      }\n      let result: any = Object.create(null)\n      Object.keys(process.env).forEach(key => result[key] = process.env[key])\n      if (env) {\n        Object.keys(env).forEach(key => result[key] = env[key])\n      }\n      return result\n    }\n\n    function assertStdio(process: ChildProcess): asserts process is ChildProcessWithoutNullStreams {\n      if (process.stdin === null || process.stdout === null || process.stderr === null) {\n        process.kill('SIGKILL')\n        throw new Error('Process created without stdio streams')\n      }\n    }\n\n    function logMessage(kind: string, data: string, outputChannel: OutputChannel): void {\n      let msg = `[${kind} - ${currentTimeStamp()}] ${data}`\n      outputChannel.appendLine(msg)\n    }\n\n    function pipeStdoutToLogOutputChannel(input: stream.Readable, outputChannel: OutputChannel) {\n      readline.createInterface({\n        input,\n        crlfDelay: Infinity,\n        terminal: false,\n        historySize: 0,\n      }).on('line', data => logMessage('Stdout', data, outputChannel))\n    }\n\n    function pipeStderrToLogOutputChannel(input: stream.Readable, outputChannel: OutputChannel) {\n      readline.createInterface({\n        input,\n        crlfDelay: Infinity,\n        terminal: false,\n        historySize: 0,\n      }).on('line', data => logMessage('Stderr', data, outputChannel))\n    }\n\n    let server = this._serverOptions\n    // We got a function.\n    if (Is.func(server)) {\n      return server().then(result => {\n        if (MessageTransports.is(result)) {\n          this._isDetached = !!result.detached\n          return result\n        } else if (StreamInfo.is(result)) {\n          this._isDetached = !!result.detached\n          return {\n            reader: new StreamMessageReader(result.reader),\n            writer: new StreamMessageWriter(result.writer)\n          }\n        } else {\n          let cp: ChildProcess\n          if (ChildProcessInfo.is(result)) {\n            cp = result.process\n            this._isDetached = result.detached\n          } else {\n            cp = result\n            this._isDetached = false\n          }\n          pipeStderrToLogOutputChannel(cp.stderr, this.outputChannel)\n          return {\n            reader: new StreamMessageReader(cp.stdout!),\n            writer: new StreamMessageWriter(cp.stdin!)\n          }\n        }\n      })\n    }\n    let json: NodeModule | Executable\n    let runDebug = server as { run: any; debug: any }\n    if (runDebug.run || runDebug.debug) {\n      if (typeof v8debug === 'object' || this._forceDebug || startedInDebugMode(process.execArgv)) {\n        json = runDebug.debug\n        this._isInDebugMode = true\n      } else {\n        json = runDebug.run\n        this._isInDebugMode = false\n      }\n    } else {\n      json = server as NodeModule | Executable\n    }\n    return getServerWorkingDir(json.options).then(serverWorkingDir => {\n      if (NodeModule.is(json) && json.module) {\n        let node = json\n        let transport = node.transport || TransportKind.stdio\n        let pipeName: string | undefined\n        let runtime = node.runtime ? getRuntimePath(node.runtime, serverWorkingDir) : undefined\n        return new Promise<MessageTransports>((resolve, _reject) => {\n          let args = node.args && node.args.slice() || []\n          if (transport === TransportKind.ipc) {\n            args.push('--node-ipc')\n          } else if (transport === TransportKind.stdio) {\n            args.push('--stdio')\n          } else if (transport === TransportKind.pipe) {\n            pipeName = generateRandomPipeName()\n            args.push(`--pipe=${pipeName}`)\n          } else if (Transport.isSocket(transport)) {\n            args.push(`--socket=${transport.port}`)\n          }\n          args.push(`--clientProcessId=${process.pid}`)\n          let options: CForkOptions = node.options || Object.create(null)\n          options.env = getEnvironment(options.env, true)\n          options.execArgv = options.execArgv || []\n          options.cwd = serverWorkingDir\n          options.silent = true\n\n          if (runtime) options.execPath = runtime\n          if (transport === TransportKind.ipc || transport === TransportKind.stdio) {\n            // options.stdio = 'ignore'\n            let sp = child_process.fork(node.module, args, options)\n            assertStdio(sp)\n            this._serverProcess = sp\n            logger.info(`Language server \"${this.id}\" started with ${sp.pid}`)\n            pipeStderrToLogOutputChannel(sp.stderr, this.outputChannel)\n            if (transport === TransportKind.ipc) {\n              pipeStdoutToLogOutputChannel(sp.stdout, this.outputChannel)\n              resolve({ reader: new IPCMessageReader(this._serverProcess), writer: new IPCMessageWriter(this._serverProcess) })\n            } else {\n              resolve({ reader: new StreamMessageReader(sp.stdout), writer: new StreamMessageWriter(sp.stdin) })\n            }\n          } else if (transport === TransportKind.pipe) {\n            return createClientPipeTransport(pipeName!).then(transport => {\n              let sp = child_process.fork(node.module, args, options)\n              assertStdio(sp)\n              logger.info(`Language server \"${this.id}\" started with ${sp.pid}`)\n              this._serverProcess = sp\n              pipeStderrToLogOutputChannel(sp.stderr, this.outputChannel)\n              pipeStdoutToLogOutputChannel(sp.stdout, this.outputChannel)\n              void transport.onConnected().then(protocol => {\n                resolve({ reader: protocol[0], writer: protocol[1] })\n              })\n            })\n          } else if (Transport.isSocket(transport)) {\n            return createClientSocketTransport(transport.port).then(transport => {\n              let sp = child_process.fork(node.module, args, options)\n              assertStdio(sp)\n              this._serverProcess = sp\n              logger.info(`Language server \"${this.id}\" started with ${sp.pid}`)\n              pipeStderrToLogOutputChannel(sp.stderr, this.outputChannel)\n              pipeStdoutToLogOutputChannel(sp.stdout, this.outputChannel)\n              void transport.onConnected().then(protocol => {\n                resolve({ reader: protocol[0], writer: protocol[1] })\n              })\n            })\n          }\n        })\n      } else if (Executable.is(json) && json.command) {\n        let command: Executable = json\n        let args = Array.isArray(command.args) ? command.args.slice(0) : []\n        let pipeName: string | undefined\n        const transport = json.transport\n        if (transport === TransportKind.stdio) {\n          args.push('--stdio')\n        } else if (transport === TransportKind.pipe) {\n          pipeName = generateRandomPipeName()\n          args.push(`--pipe=${pipeName}`)\n        } else if (Transport.isSocket(transport)) {\n          args.push(`--socket=${transport.port}`)\n        } else if (transport === TransportKind.ipc) {\n          throw new Error(`Transport kind ipc is not supported for command executable`)\n        }\n        let options = Object.assign({ shell: process.platform === 'win32' }, command.options) as SpawnOptions\n        options.env = getEnvironment(options.env, false)\n        options.cwd = options.cwd ?? serverWorkingDir\n        options.windowsHide = true\n        const attachProcess = (serverProcess: ChildProcess, pipiStdout = true) => {\n          this._serverProcess = serverProcess\n          this._isDetached = !!options.detached\n          logger.info(`Language server \"${this.id}\" started with ${serverProcess.pid}`)\n          if (pipiStdout) pipeStdoutToLogOutputChannel(serverProcess.stdout, this.outputChannel)\n          pipeStderrToLogOutputChannel(serverProcess.stderr, this.outputChannel)\n        }\n        let cmd = workspace.expand(json.command)\n        if (transport === undefined || transport === TransportKind.stdio) {\n          const serverProcess = child_process.spawn(cmd, args, options)\n          if (!serverProcess || !serverProcess.pid) {\n            return handleChildProcessStartError(serverProcess, `Launching server using command ${cmd} failed.`)\n          }\n          attachProcess(serverProcess, false)\n          return Promise.resolve({ reader: new StreamMessageReader(serverProcess.stdout), writer: new StreamMessageWriter(serverProcess.stdin) })\n        } else if (transport === TransportKind.pipe || Transport.isSocket(transport)) {\n          let promise: Promise<DisposableTransport>\n          if (transport === TransportKind.pipe) {\n            promise = createClientPipeTransport(pipeName!)\n          } else {\n            promise = createClientSocketTransport(transport.port)\n          }\n          return promise.then(transport => {\n            const serverProcess = child_process.spawn(cmd, args, options)\n            if (!serverProcess || !serverProcess.pid) {\n              transport.dispose()\n              return handleChildProcessStartError(serverProcess, `Launching server using command ${cmd} failed.`)\n            }\n            attachProcess(serverProcess)\n            return transport.onConnected().then(protocol => {\n              return { reader: protocol[0], writer: protocol[1] }\n            })\n          })\n        }\n      }\n      return Promise.reject<MessageTransports>(new Error(`Unsupported server configuration ${JSON.stringify(server, null, 2)}`))\n    }).finally(() => {\n      if (this._serverProcess !== undefined) {\n        this._serverProcess.on('exit', (code, signal) => {\n          if (code === 0) {\n            this.info('Server process exited successfully', undefined, false)\n          } else if (code !== null) {\n            this.error(`Server process exited with code ${code}.`, undefined, false)\n          }\n          if (signal !== null) {\n            this.error(`Server process exited with signal ${signal}.`, undefined, false)\n          }\n        })\n      }\n    })\n  }\n}\n\nexport class SettingMonitor {\n  private _listeners: Disposable[]\n\n  constructor(private _client: LanguageClient, private _setting: string) {\n    this._listeners = []\n  }\n\n  public start(): Disposable {\n    workspace.onDidChangeConfiguration(e => {\n      if (e.affectsConfiguration(this._setting)) {\n        this.onDidChangeConfiguration()\n      }\n    }, null, this._listeners)\n    this.onDidChangeConfiguration()\n    return {\n      dispose: () => {\n        disposeAll(this._listeners)\n        void this._client.dispose()\n      }\n    }\n  }\n\n  private onDidChangeConfiguration(): void {\n    let index = this._setting.indexOf('.')\n    let primary = index >= 0 ? this._setting.substr(0, index) : this._setting\n    let rest = index >= 0 ? this._setting.substr(index + 1) : undefined\n    let enabled = rest\n      ? workspace.getConfiguration(primary).get(rest, true)\n      : workspace.getConfiguration().get(primary, true)\n    if (enabled && this._client.needsStart()) {\n      this._client.start().catch(error => this._client.error('Start failed after configuration change', error, 'force'))\n    } else if (!enabled && this._client.needsStop()) {\n      this._client.stop().catch(error => this._client.error('Stop failed after configuration change', error, 'force'))\n    }\n  }\n}\n\nexport function getRuntimePath(runtime: string, serverWorkingDirectory: string | undefined): string {\n  if (path.isAbsolute(runtime)) {\n    return runtime\n  }\n  const mainRootPath = mainGetRootPath()\n  if (mainRootPath !== undefined) {\n    const result = path.join(mainRootPath, runtime)\n    if (fs.existsSync(result)) {\n      return result\n    }\n  }\n  if (serverWorkingDirectory !== undefined) {\n    const result = path.join(serverWorkingDirectory, runtime)\n    if (fs.existsSync(result)) {\n      return result\n    }\n  }\n  return runtime\n}\n\nexport function mainGetRootPath(): string | undefined {\n  let folders = workspace.workspaceFolders\n  if (!folders || folders.length === 0) {\n    return undefined\n  }\n  return URI.parse(folders[0].uri).fsPath\n}\n\nexport function getServerWorkingDir(options?: { cwd?: string }): Promise<string | undefined> {\n  let cwd = options && options.cwd\n  if (cwd && !path.isAbsolute(cwd)) cwd = path.join(workspace.cwd, cwd)\n  if (!cwd) cwd = workspace.cwd\n  // make sure the folder exists otherwise creating the process will fail\n  return new Promise(s => {\n    fs.lstat(cwd, (err, stats) => {\n      s(!err && stats.isDirectory() ? cwd : undefined)\n    })\n  })\n}\n\nexport function startedInDebugMode(args: string[] | undefined): boolean {\n  if (args) {\n    return args.some(arg => {\n      return debugStartWith.some(value => arg.startsWith(value)) ||\n        debugEquals.some(value => arg === value)\n    })\n  }\n  return false\n}\n\nexport function handleChildProcessStartError(childProcess: ChildProcess, message: string) {\n  if (childProcess === null) {\n    return Promise.reject<MessageTransports>(message)\n  }\n  childProcess.unref()\n  return new Promise<MessageTransports>((_, reject) => {\n    childProcess.on('error', err => {\n      reject(`${message} ${err}`)\n    })\n    // the error event should always be run immediately,\n    // but race on it just in case\n    setImmediate(() => reject(message))\n  })\n}\n\nexport function checkProcessDied(childProcess: ChildProcess | undefined): void {\n  if (!childProcess || childProcess.pid === undefined) return\n  setTimeout(() => {\n    // Test if the process is still alive. Throws an exception if not\n    try {\n      process.kill(childProcess.pid, 0)\n      terminate(childProcess)\n    } catch (error) {\n      // All is fine.\n    }\n  }, STOP_TIMEOUT)\n}\n\n"
  },
  {
    "path": "src/language-client/inlayHint.ts",
    "content": "'use strict'\nimport type {\n  CancellationToken, ClientCapabilities, Disposable, DocumentSelector, InlayHint, InlayHintOptions, InlayHintParams, InlayHintRegistrationOptions, Range, ServerCapabilities\n} from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport languages from '../languages'\nimport { InlayHintsProvider, ProviderResult } from '../provider'\nimport { Emitter, InlayHintRefreshRequest, InlayHintRequest, InlayHintResolveRequest } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\n\nexport type ProvideInlayHintsSignature = (this: void, document: TextDocument, viewPort: Range, token: CancellationToken) => ProviderResult<InlayHint[]>\nexport type ResolveInlayHintSignature = (this: void, item: InlayHint, token: CancellationToken) => ProviderResult<InlayHint>\n\nexport interface InlayHintsMiddleware {\n  provideInlayHints?: (this: void, document: TextDocument, viewPort: Range, token: CancellationToken, next: ProvideInlayHintsSignature) => ProviderResult<InlayHint[]>\n  resolveInlayHint?: (this: void, item: InlayHint, token: CancellationToken, next: ResolveInlayHintSignature) => ProviderResult<InlayHint>\n}\n\nexport interface InlayHintsProviderShape {\n  provider: InlayHintsProvider\n  onDidChangeInlayHints: Emitter<void>\n}\n\nexport class InlayHintsFeature extends TextDocumentLanguageFeature<\n  boolean | InlayHintOptions, InlayHintRegistrationOptions, InlayHintsProviderShape, InlayHintsMiddleware\n> {\n  constructor(client: FeatureClient<InlayHintsMiddleware>) {\n    super(client, InlayHintRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    const inlayHint = ensure(ensure(capabilities, 'textDocument')!, 'inlayHint')!\n    inlayHint.dynamicRegistration = true\n    inlayHint.resolveSupport = {\n      properties: ['tooltip', 'textEdits', 'label.tooltip', 'label.location', 'label.command']\n    }\n    ensure(ensure(capabilities, 'workspace')!, 'inlayHint')!.refreshSupport = true\n  }\n\n  public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {\n    this._client.onRequest(InlayHintRefreshRequest.type, async () => {\n      for (const provider of this.getAllProviders()) {\n        provider.onDidChangeInlayHints.fire()\n      }\n    })\n\n    const [id, options] = this.getRegistration(documentSelector, capabilities.inlayHintProvider)\n    if (!id || !options) {\n      return\n    }\n    this.register({ id, registerOptions: options })\n  }\n\n  protected registerLanguageProvider(options: InlayHintRegistrationOptions): [Disposable, InlayHintsProviderShape] {\n    const eventEmitter: Emitter<void> = new Emitter<void>()\n    const provider: InlayHintsProvider = {\n      onDidChangeInlayHints: eventEmitter.event,\n      provideInlayHints: (document, range, token) => {\n        const client = this._client\n        const provideInlayHints: ProvideInlayHintsSignature = (document, range, token) => {\n          const requestParams: InlayHintParams = {\n            textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),\n            range\n          }\n          return this.sendRequest(InlayHintRequest.type, requestParams, token, null)\n        }\n        const middleware = client.middleware!\n        return middleware.provideInlayHints\n          ? middleware.provideInlayHints(document, range, token, provideInlayHints)\n          : provideInlayHints(document, range, token)\n      }\n    }\n    provider.resolveInlayHint = options.resolveProvider === true\n      ? (hint, token) => {\n        const resolveInlayHint: ResolveInlayHintSignature = (item, token) => {\n          return this.sendRequest(InlayHintResolveRequest.type, item, token)\n        }\n        return this.sendWithMiddleware(resolveInlayHint, 'resolveInlayHint', hint, token)\n      }\n      : undefined\n    const selector = options.documentSelector!\n    this._client.attachExtensionName(provider)\n    return [languages.registerInlayHintsProvider(selector, provider), { provider, onDidChangeInlayHints: eventEmitter }]\n  }\n}\n"
  },
  {
    "path": "src/language-client/inlineCompletion.ts",
    "content": "import {\n  CancellationToken,\n  ClientCapabilities,\n  Disposable,\n  DocumentSelector,\n  InlineCompletionContext,\n  InlineCompletionItem,\n  InlineCompletionList,\n  InlineCompletionOptions,\n  InlineCompletionParams,\n  InlineCompletionRegistrationOptions,\n  InlineCompletionRequest,\n  Position,\n  ServerCapabilities\n} from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport languages from '../languages'\nimport { InlineCompletionItemProvider, ProviderResult } from '../provider'\nimport {\n  ensure,\n  FeatureClient,\n  TextDocumentLanguageFeature\n} from './features'\nimport * as UUID from './utils/uuid'\n\nexport interface ProvideInlineCompletionItemsSignature {\n  (this: void, document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult<InlineCompletionItem[] | InlineCompletionList>\n}\n\nexport interface InlineCompletionMiddleware {\n  provideInlineCompletionItems?: (this: void, document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken, next: ProvideInlineCompletionItemsSignature) => ProviderResult<InlineCompletionItem[] | InlineCompletionList>\n}\n\nexport interface InlineCompletionProviderShape {\n  provider: InlineCompletionItemProvider\n}\n\nexport class InlineCompletionItemFeature extends TextDocumentLanguageFeature<boolean | InlineCompletionOptions, InlineCompletionRegistrationOptions, InlineCompletionItemProvider, InlineCompletionMiddleware> {\n\n  constructor(client: FeatureClient<InlineCompletionMiddleware>) {\n    super(client, InlineCompletionRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    const inlineCompletion = ensure(ensure(capabilities, 'textDocument')!, 'inlineCompletion')!\n    inlineCompletion.dynamicRegistration = true\n  }\n\n  public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {\n    const options = this.getRegistrationOptions(documentSelector, capabilities.inlineCompletionProvider)\n    if (!options) {\n      return\n    }\n\n    this.register({\n      id: UUID.generateUuid(),\n      registerOptions: options\n    })\n  }\n\n  protected registerLanguageProvider(options: InlineCompletionRegistrationOptions): [Disposable, InlineCompletionItemProvider] {\n    const provider: InlineCompletionItemProvider = {\n      provideInlineCompletionItems: (document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult<InlineCompletionList | InlineCompletionItem[]> => {\n        const provideInlineCompletionItems: ProvideInlineCompletionItemsSignature = (document, position, context, token) => {\n          const params: InlineCompletionParams = {\n            textDocument: { uri: document.uri },\n            position,\n            context\n          }\n          return this.sendRequest(InlineCompletionRequest.type, params, token, null)\n        }\n\n        const middleware = this._client.middleware\n        return middleware.provideInlineCompletionItems\n          ? middleware.provideInlineCompletionItems(document, position, context, token, provideInlineCompletionItems)\n          : provideInlineCompletionItems(document, position, context, token)\n      }\n    }\n    this._client.attachExtensionName(provider)\n    return [languages.registerInlineCompletionItemProvider(options.documentSelector, provider), provider]\n  }\n}\n"
  },
  {
    "path": "src/language-client/inlineValue.ts",
    "content": "'use strict'\nimport type { CancellationToken, ClientCapabilities, Disposable, DocumentSelector, InlineValue, InlineValueContext, InlineValueOptions, InlineValueParams, InlineValueRegistrationOptions, Range, ServerCapabilities } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport languages from '../languages'\nimport { InlineValuesProvider, ProviderResult } from '../provider'\nimport { Emitter, InlineValueRefreshRequest, InlineValueRequest } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\n\nexport type ProvideInlineValuesSignature = (this: void, document: TextDocument, viewPort: Range, context: InlineValueContext, token: CancellationToken) => ProviderResult<InlineValue[]>\n\nexport interface InlineValueMiddleware {\n  provideInlineValues?: (this: void, document: TextDocument, viewPort: Range, context: InlineValueContext, token: CancellationToken, next: ProvideInlineValuesSignature) => ProviderResult<InlineValue[]>\n}\n\nexport interface InlineValueProviderShape {\n  provider: InlineValuesProvider\n  onDidChangeInlineValues: Emitter<void>\n}\n\nexport class InlineValueFeature extends TextDocumentLanguageFeature<\n  boolean | InlineValueOptions, InlineValueRegistrationOptions, InlineValueProviderShape, InlineValueMiddleware\n> {\n  constructor(client: FeatureClient<InlineValueMiddleware>) {\n    super(client, InlineValueRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    ensure(ensure(capabilities, 'textDocument')!, 'inlineValue')!.dynamicRegistration = true\n    ensure(ensure(capabilities, 'workspace')!, 'inlineValue')!.refreshSupport = true\n  }\n\n  public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {\n    this._client.onRequest(InlineValueRefreshRequest.type, async () => {\n      for (const provider of this.getAllProviders()) {\n        provider.onDidChangeInlineValues.fire()\n      }\n    })\n\n    const [id, options] = this.getRegistration(documentSelector, capabilities.inlineValueProvider)\n    if (!id || !options) {\n      return\n    }\n    this.register({ id, registerOptions: options })\n  }\n\n  protected registerLanguageProvider(options: InlineValueRegistrationOptions): [Disposable, InlineValueProviderShape] {\n    const eventEmitter: Emitter<void> = new Emitter<void>()\n    const provider: InlineValuesProvider = {\n      onDidChangeInlineValues: eventEmitter.event,\n      provideInlineValues: (document, viewPort, context, token) => {\n        const client = this._client\n        const provideInlineValues: ProvideInlineValuesSignature = (document, range, context, token) => {\n          const requestParams: InlineValueParams = {\n            textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),\n            range,\n            context\n          }\n          return this.sendRequest(InlineValueRequest.type, requestParams, token)\n        }\n        const middleware = client.middleware!\n        return middleware.provideInlineValues\n          ? middleware.provideInlineValues(document, viewPort, context, token, provideInlineValues)\n          : provideInlineValues(document, viewPort, context, token)\n\n      }\n    }\n    this._client.attachExtensionName(provider)\n    const selector = options.documentSelector!\n    return [languages.registerInlineValuesProvider(selector, provider), { provider, onDidChangeInlineValues: eventEmitter }]\n  }\n}\n"
  },
  {
    "path": "src/language-client/linkedEditingRange.ts",
    "content": "'use strict'\nimport type { CancellationToken, ClientCapabilities, Disposable, DocumentSelector, LinkedEditingRangeOptions, LinkedEditingRangeRegistrationOptions, LinkedEditingRanges, Position, ServerCapabilities } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport languages from '../languages'\nimport { LinkedEditingRangeProvider, ProviderResult } from '../provider'\nimport { LinkedEditingRangeRequest } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\n\nexport interface ProvideLinkedEditingRangeSignature {\n  (this: void, document: TextDocument, position: Position, token: CancellationToken): ProviderResult<LinkedEditingRanges>\n}\n\n/**\n * Linked editing middleware\n * @since 3.16.0\n */\nexport interface LinkedEditingRangeMiddleware {\n  provideLinkedEditingRange?: (this: void, document: TextDocument, position: Position, token: CancellationToken, next: ProvideLinkedEditingRangeSignature) => ProviderResult<LinkedEditingRanges>\n}\n\nexport class LinkedEditingFeature extends TextDocumentLanguageFeature<boolean | LinkedEditingRangeOptions, LinkedEditingRangeRegistrationOptions, LinkedEditingRangeProvider, LinkedEditingRangeMiddleware> {\n\n  constructor(client: FeatureClient<LinkedEditingRangeMiddleware>) {\n    super(client, LinkedEditingRangeRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    const linkedEditingSupport = ensure(ensure(capabilities, 'textDocument')!, 'linkedEditingRange')!\n    linkedEditingSupport.dynamicRegistration = true\n  }\n\n  public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {\n    let [id, options] = this.getRegistration(documentSelector, capabilities.linkedEditingRangeProvider)\n    if (!id || !options) {\n      return\n    }\n    this.register({ id, registerOptions: options })\n  }\n\n  protected registerLanguageProvider(options: LinkedEditingRangeRegistrationOptions): [Disposable, LinkedEditingRangeProvider] {\n    const provider: LinkedEditingRangeProvider = {\n      provideLinkedEditingRanges: (document, position, token) => {\n        const client = this._client\n        const provideLinkedEditing: ProvideLinkedEditingRangeSignature = (document, position, token) => {\n          const params = client.code2ProtocolConverter.asTextDocumentPositionParams(document, position)\n          return this.sendRequest(LinkedEditingRangeRequest.type, params, token)\n        }\n        const middleware = client.middleware!\n        return middleware.provideLinkedEditingRange\n          ? middleware.provideLinkedEditingRange(document, position, token, provideLinkedEditing)\n          : provideLinkedEditing(document, position, token)\n      }\n    }\n    this._client.attachExtensionName(provider)\n    return [languages.registerLinkedEditingRangeProvider(options.documentSelector!, provider), provider]\n  }\n}\n"
  },
  {
    "path": "src/language-client/progress.ts",
    "content": "'use strict'\nimport type { ClientCapabilities, InitializeParams, WorkDoneProgressCreateParams } from 'vscode-languageserver-protocol'\nimport { WorkDoneProgressCreateRequest } from '../util/protocol'\nimport { ensure, FeatureClient, FeatureState, StaticFeature } from './features'\nimport { ProgressPart } from './progressPart'\n\nexport class ProgressFeature implements StaticFeature {\n  private activeParts: Set<ProgressPart> = new Set()\n  constructor(private _client: FeatureClient<object>) {\n  }\n\n  public get method(): string {\n    return WorkDoneProgressCreateRequest.method\n  }\n\n  public fillInitializeParams(_params: InitializeParams): void {\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    ensure(capabilities, 'window')!.workDoneProgress = true\n  }\n\n  public getState(): FeatureState {\n    return { kind: 'window', id: WorkDoneProgressCreateRequest.method, registrations: this.activeParts.size > 0 }\n  }\n\n  public initialize(): void {\n    let client = this._client\n    const deleteHandler = (part: ProgressPart) => {\n      this.activeParts.delete(part)\n    }\n    const createHandler = (params: WorkDoneProgressCreateParams) => {\n      this.activeParts.add(new ProgressPart(this._client, params.token, deleteHandler))\n    }\n    client.onRequest(WorkDoneProgressCreateRequest.type, createHandler)\n  }\n\n  public dispose(): void {\n    for (const part of this.activeParts) {\n      part.done()\n    }\n    this.activeParts.clear()\n  }\n}\n"
  },
  {
    "path": "src/language-client/progressPart.ts",
    "content": "'use strict'\nimport type { Disposable, NotificationHandler, ProgressToken, ProgressType, ProtocolNotificationType, WorkDoneProgressBegin, WorkDoneProgressReport } from 'vscode-languageserver-protocol'\nimport { disposeAll } from '../util'\nimport { WorkDoneProgress, WorkDoneProgressCancelNotification } from '../util/protocol'\nimport window from '../window'\n\nexport interface Progress {\n  report(value: { message?: string; increment?: number }): void\n}\n\nexport interface ProgressContext {\n  readonly id: string\n  onProgress<P>(type: ProgressType<P>, token: string | number, handler: NotificationHandler<P>): Disposable\n  sendNotification<P, RO>(type: ProtocolNotificationType<P, RO>, params?: P): void\n}\n\nexport class ProgressPart {\n  private disposables: Disposable[] = []\n  private _cancelled = false\n  private _percent = 0\n  private _started = false\n  private progress: Progress\n  private _resolve: () => void\n  private _reject: ((reason?: any) => void) | undefined\n\n  public constructor(private client: ProgressContext, private token: ProgressToken, done?: (part: ProgressPart) => void) {\n    this.disposables.push(client.onProgress(WorkDoneProgress.type, this.token, value => {\n      switch (value.kind) {\n        case 'begin':\n          this.begin(value)\n          break\n        case 'report':\n          this.report(value)\n          break\n        case 'end':\n          this.done(value.message)\n          if (done) {\n            done(this)\n          }\n          break\n      }\n    }))\n  }\n\n  public begin(params: WorkDoneProgressBegin): boolean {\n    if (this._started || this._cancelled) return false\n    this._started = true\n    void window.withProgress<void>({\n      source: `language-client-${this.client.id}`,\n      cancellable: params.cancellable,\n      title: params.title,\n    }, (progress, token) => {\n      this.progress = progress\n      this.report(params)\n      if (this._cancelled) return Promise.resolve()\n      this.disposables.push(token.onCancellationRequested(() => {\n        this.client.sendNotification(WorkDoneProgressCancelNotification.type, { token: this.token })\n        this.cancel()\n      }))\n      return new Promise((resolve, reject) => {\n        this._resolve = resolve\n        this._reject = reject\n      })\n    })\n    return true\n  }\n\n  public report(params: WorkDoneProgressReport | WorkDoneProgressBegin): void {\n    if (this.progress) {\n      let msg: { message?: string, increment?: number } = {}\n      if (params.message) msg.message = params.message\n      if (validPercent(params.percentage)) {\n        msg.increment = Math.round(params.percentage) - this._percent\n        this._percent = Math.round(params.percentage)\n      }\n      if (Object.keys(msg).length > 0) {\n        this.progress.report(msg)\n      }\n    }\n  }\n\n  public cancel(): void {\n    if (this._cancelled) return\n    this.cleanUp()\n    if (this._reject !== undefined) {\n      this._reject()\n      this._resolve = undefined\n      this._reject = undefined\n    }\n  }\n\n  public done(message?: string): void {\n    if (this.progress) {\n      let msg: { message?: string, increment?: number } = {}\n      if (message) msg.message = message\n      if (typeof this._percent === 'number' && this._percent > 0) msg.increment = 100 - this._percent\n      this.progress.report(msg)\n    }\n    this.cleanUp()\n    if (this._resolve) {\n      this._resolve()\n      this._resolve = undefined\n      this._reject = undefined\n    }\n  }\n\n  private cleanUp(): void {\n    this._cancelled = true\n    this.progress = undefined\n    disposeAll(this.disposables)\n  }\n}\n\nfunction validPercent(n: unknown): boolean {\n  if (typeof n !== 'number') return false\n  return n >= 0 && n <= 100\n}\n"
  },
  {
    "path": "src/language-client/reference.ts",
    "content": "'use strict'\nimport type { CancellationToken, ClientCapabilities, Disposable, DocumentSelector, Location, Position, ReferenceOptions, ReferenceRegistrationOptions, ServerCapabilities, TextDocumentRegistrationOptions } from 'vscode-languageserver-protocol'\nimport { TextDocument } from \"vscode-languageserver-textdocument\"\nimport languages from '../languages'\nimport { ProviderResult, ReferenceProvider } from '../provider'\nimport { ReferencesRequest } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\nimport * as UUID from './utils/uuid'\n\nexport interface ProvideReferencesSignature {\n  (\n    this: void,\n    document: TextDocument,\n    position: Position,\n    options: { includeDeclaration: boolean },\n    token: CancellationToken\n  ): ProviderResult<Location[]>\n}\n\nexport interface ReferencesMiddleware {\n  provideReferences?: (\n    this: void,\n    document: TextDocument,\n    position: Position,\n    options: { includeDeclaration: boolean },\n    token: CancellationToken,\n    next: ProvideReferencesSignature\n  ) => ProviderResult<Location[]>\n}\n\nexport class ReferencesFeature extends TextDocumentLanguageFeature<\n  boolean | ReferenceOptions, ReferenceRegistrationOptions, ReferenceProvider, ReferencesMiddleware\n> {\n  constructor(client: FeatureClient<ReferencesMiddleware>) {\n    super(client, ReferencesRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    ensure(\n      ensure(capabilities, 'textDocument')!,\n      'references'\n    )!.dynamicRegistration = true\n  }\n\n  public initialize(\n    capabilities: ServerCapabilities,\n    documentSelector: DocumentSelector\n  ): void {\n    const options = this.getRegistrationOptions(documentSelector, capabilities.referencesProvider)\n    if (!options) {\n      return\n    }\n    this.register({\n      id: UUID.generateUuid(),\n      registerOptions: options\n    })\n  }\n\n  protected registerLanguageProvider(\n    options: TextDocumentRegistrationOptions\n  ): [Disposable, ReferenceProvider] {\n    const provider: ReferenceProvider = {\n      provideReferences: (document, position, options, token) => {\n        const client = this._client\n        const _providerReferences: ProvideReferencesSignature = (document, position, options, token) => {\n          return this.sendRequest(\n            ReferencesRequest.type,\n            client.code2ProtocolConverter.asReferenceParams(document, position, options),\n            token\n          )\n        }\n        const middleware = client.middleware!\n        return middleware.provideReferences\n          ? middleware.provideReferences(document, position, options, token, _providerReferences)\n          : _providerReferences(document, position, options, token)\n      }\n    }\n    this._client.attachExtensionName(provider)\n    return [languages.registerReferencesProvider(options.documentSelector!, provider), provider]\n  }\n}\n"
  },
  {
    "path": "src/language-client/rename.ts",
    "content": "'use strict'\nimport type { CancellationToken, ClientCapabilities, Disposable, DocumentSelector, RenameOptions, RenameParams, RenameRegistrationOptions, ServerCapabilities, TextDocumentPositionParams, WorkspaceEdit } from 'vscode-languageserver-protocol'\nimport { TextDocument } from \"vscode-languageserver-textdocument\"\nimport { Position, Range } from 'vscode-languageserver-types'\nimport languages from '../languages'\nimport { ProviderResult, RenameProvider } from '../provider'\nimport * as Is from '../util/is'\nimport { PrepareRenameRequest, PrepareSupportDefaultBehavior, RenameRequest } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\nimport * as UUID from './utils/uuid'\n\ninterface DefaultBehavior {\n  defaultBehavior: boolean\n}\n\nexport interface PrepareRenameSignature {\n  (this: void, document: TextDocument, position: Position, token: CancellationToken): ProviderResult<Range | { range: Range, placeholder: string }>\n}\n\nexport interface ProvideRenameEditsSignature {\n  (\n    this: void,\n    document: TextDocument,\n    position: Position,\n    newName: string,\n    token: CancellationToken\n  ): ProviderResult<WorkspaceEdit>\n}\n\nexport interface RenameMiddleware {\n  prepareRename?: (\n    this: void, document: TextDocument,\n    position: Position,\n    token: CancellationToken,\n    next: PrepareRenameSignature\n  ) => ProviderResult<Range | { range: Range, placeholder: string }>\n  provideRenameEdits?: (\n    this: void,\n    document: TextDocument,\n    position: Position,\n    newName: string,\n    token: CancellationToken,\n    next: ProvideRenameEditsSignature\n  ) => ProviderResult<WorkspaceEdit>\n}\n\nexport class RenameFeature extends TextDocumentLanguageFeature<boolean | RenameOptions, RenameRegistrationOptions, RenameProvider, RenameMiddleware> {\n  constructor(client: FeatureClient<RenameMiddleware>) {\n    super(client, RenameRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    let rename = ensure(ensure(capabilities, 'textDocument')!, 'rename')!\n    rename.dynamicRegistration = true\n    rename.prepareSupport = true\n    rename.honorsChangeAnnotations = true\n    rename.prepareSupportDefaultBehavior = PrepareSupportDefaultBehavior.Identifier\n  }\n\n  public initialize(\n    capabilities: ServerCapabilities,\n    documentSelector: DocumentSelector\n  ): void {\n    const options = this.getRegistrationOptions(documentSelector, capabilities.renameProvider)\n    if (!options) {\n      return\n    }\n    if (Is.boolean(capabilities.renameProvider)) {\n      options.prepareProvider = false\n    }\n    this.register({\n      id: UUID.generateUuid(),\n      registerOptions: options\n    })\n  }\n\n  protected registerLanguageProvider(options: RenameRegistrationOptions): [Disposable, RenameProvider] {\n    const provider: RenameProvider = {\n      provideRenameEdits: (document, position, newName, token) => {\n        const client = this._client\n        const provideRenameEdits: ProvideRenameEditsSignature = (document, position, newName, token) => {\n          const params: RenameParams = {\n            textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),\n            position,\n            newName\n          }\n          return this.sendRequest(RenameRequest.type, params, token)\n        }\n        const middleware = client.middleware!\n        return middleware.provideRenameEdits\n          ? middleware.provideRenameEdits(document, position, newName, token, provideRenameEdits)\n          : provideRenameEdits(document, position, newName, token)\n      },\n      prepareRename: options.prepareProvider\n        ? (document, position, token) => {\n          const client = this._client\n          const prepareRename: PrepareRenameSignature = (document, position, token) => {\n            const params: TextDocumentPositionParams = {\n              textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),\n              position\n            }\n            return this.sendRequest(PrepareRenameRequest.type, params, token).then(result => {\n              if (!result) return null\n\n              if (Range.is(result)) {\n                return result\n              } else if (this.isDefaultBehavior(result)) {\n                return result.defaultBehavior === true ? null : Promise.reject(new Error(`The element can't be renamed.`))\n              } else if (result && Range.is(result.range)) {\n                return {\n                  range: result.range,\n                  placeholder: result.placeholder\n                }\n              }\n            })\n          }\n          const middleware = client.middleware!\n          return middleware.prepareRename\n            ? middleware.prepareRename(document, position, token, prepareRename)\n            : prepareRename(document, position, token)\n        }\n        : undefined\n    }\n\n    this._client.attachExtensionName(provider)\n    return [languages.registerRenameProvider(options.documentSelector, provider), provider]\n  }\n\n  private isDefaultBehavior(value: any): value is DefaultBehavior {\n    const candidate: DefaultBehavior = value\n    return candidate && Is.boolean(candidate.defaultBehavior)\n  }\n}\n"
  },
  {
    "path": "src/language-client/selectionRange.ts",
    "content": "'use strict'\nimport type { CancellationToken, ClientCapabilities, Disposable, DocumentSelector, Position, SelectionRange, SelectionRangeClientCapabilities, SelectionRangeOptions, SelectionRangeParams, SelectionRangeRegistrationOptions, ServerCapabilities } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport languages from '../languages'\nimport { ProviderResult, SelectionRangeProvider } from '../provider'\nimport { SelectionRangeRequest } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\n\nexport interface ProvideSelectionRangeSignature {\n  (this: void, document: TextDocument, positions: Position[], token: CancellationToken): ProviderResult<SelectionRange[]>\n}\n\nexport interface SelectionRangeProviderMiddleware {\n  provideSelectionRanges?: (this: void, document: TextDocument, positions: Position[], token: CancellationToken, next: ProvideSelectionRangeSignature) => ProviderResult<SelectionRange[]>\n}\n\nexport class SelectionRangeFeature extends TextDocumentLanguageFeature<boolean | SelectionRangeOptions, SelectionRangeRegistrationOptions, SelectionRangeProvider, SelectionRangeProviderMiddleware> {\n  constructor(client: FeatureClient<SelectionRangeProviderMiddleware>) {\n    super(client, SelectionRangeRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities & SelectionRangeClientCapabilities): void {\n    let capability = ensure(ensure(capabilities, 'textDocument')!, 'selectionRange')!\n    capability.dynamicRegistration = true\n  }\n\n  public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {\n    let [id, options] = this.getRegistration(documentSelector, capabilities.selectionRangeProvider)\n    if (!id || !options) {\n      return\n    }\n    this.register({ id, registerOptions: options })\n  }\n\n  protected registerLanguageProvider(options: SelectionRangeRegistrationOptions): [Disposable, SelectionRangeProvider] {\n    const provider: SelectionRangeProvider = {\n      provideSelectionRanges: (document, positions, token) => {\n        const client = this._client\n        const provideSelectionRanges: ProvideSelectionRangeSignature = (document, positions, token) => {\n          const requestParams: SelectionRangeParams = {\n            textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),\n            positions\n          }\n          return this.sendRequest(SelectionRangeRequest.type, requestParams, token)\n        }\n        const middleware = client.middleware\n        return middleware.provideSelectionRanges\n          ? middleware.provideSelectionRanges(document, positions, token, provideSelectionRanges)\n          : provideSelectionRanges(document, positions, token)\n      }\n    }\n    this._client.attachExtensionName(provider)\n    return [languages.registerSelectionRangeProvider(options.documentSelector, provider), provider]\n  }\n}\n"
  },
  {
    "path": "src/language-client/semanticTokens.ts",
    "content": "'use strict'\nimport type {\n  CancellationToken, ClientCapabilities, DocumentSelector, SemanticTokensDelta, SemanticTokensDeltaParams, SemanticTokensOptions, SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRegistrationOptions, ServerCapabilities\n} from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { Range, SemanticTokenModifiers, SemanticTokens, SemanticTokenTypes } from 'vscode-languageserver-types'\nimport languages from '../languages'\nimport { DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, ProviderResult } from '../provider'\nimport * as Is from '../util/is'\nimport { Disposable, Emitter, SemanticTokensDeltaRequest, SemanticTokensRangeRequest, SemanticTokensRefreshRequest, SemanticTokensRegistrationType, SemanticTokensRequest, TokenFormat } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\n\nexport interface DocumentSemanticsTokensSignature {\n  (this: void, document: TextDocument, token: CancellationToken): ProviderResult<SemanticTokens>\n}\n\nexport interface DocumentSemanticsTokensEditsSignature {\n  (this: void, document: TextDocument, previousResultId: string, token: CancellationToken): ProviderResult<SemanticTokens | SemanticTokensDelta>\n}\n\nexport interface DocumentRangeSemanticTokensSignature {\n  (this: void, document: TextDocument, range: Range, token: CancellationToken): ProviderResult<SemanticTokens>\n}\n\n/**\n * The semantic token middleware\n * @since 3.16.0\n */\nexport interface SemanticTokensMiddleware {\n  provideDocumentSemanticTokens?: (this: void, document: TextDocument, token: CancellationToken, next: DocumentSemanticsTokensSignature) => ProviderResult<SemanticTokens>\n  provideDocumentSemanticTokensEdits?: (this: void, document: TextDocument, previousResultId: string, token: CancellationToken, next: DocumentSemanticsTokensEditsSignature) => ProviderResult<SemanticTokens | SemanticTokensDelta>\n  provideDocumentRangeSemanticTokens?: (this: void, document: TextDocument, range: Range, token: CancellationToken, next: DocumentRangeSemanticTokensSignature) => ProviderResult<SemanticTokens>\n}\n\nexport interface SemanticTokensProviderShape {\n  range?: DocumentRangeSemanticTokensProvider\n  full?: DocumentSemanticTokensProvider\n  onDidChangeSemanticTokensEmitter: Emitter<void>\n}\n\nexport class SemanticTokensFeature extends TextDocumentLanguageFeature<boolean | SemanticTokensOptions, SemanticTokensRegistrationOptions, SemanticTokensProviderShape, SemanticTokensMiddleware> {\n\n  constructor(client: FeatureClient<SemanticTokensMiddleware>) {\n    super(client, SemanticTokensRegistrationType.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    const capability = ensure(ensure(capabilities, 'textDocument')!, 'semanticTokens')!\n    capability.dynamicRegistration = true\n    capability.tokenTypes = [\n      SemanticTokenTypes.namespace,\n      SemanticTokenTypes.type,\n      SemanticTokenTypes.class,\n      SemanticTokenTypes.enum,\n      SemanticTokenTypes.interface,\n      SemanticTokenTypes.struct,\n      SemanticTokenTypes.typeParameter,\n      SemanticTokenTypes.parameter,\n      SemanticTokenTypes.variable,\n      SemanticTokenTypes.property,\n      SemanticTokenTypes.enumMember,\n      SemanticTokenTypes.event,\n      SemanticTokenTypes.function,\n      SemanticTokenTypes.method,\n      SemanticTokenTypes.macro,\n      SemanticTokenTypes.keyword,\n      SemanticTokenTypes.modifier,\n      SemanticTokenTypes.comment,\n      SemanticTokenTypes.string,\n      SemanticTokenTypes.number,\n      SemanticTokenTypes.regexp,\n      SemanticTokenTypes.decorator,\n      SemanticTokenTypes.label,\n      SemanticTokenTypes.operator\n    ]\n    capability.tokenModifiers = [\n      SemanticTokenModifiers.declaration,\n      SemanticTokenModifiers.definition,\n      SemanticTokenModifiers.readonly,\n      SemanticTokenModifiers.static,\n      SemanticTokenModifiers.deprecated,\n      SemanticTokenModifiers.abstract,\n      SemanticTokenModifiers.async,\n      SemanticTokenModifiers.modification,\n      SemanticTokenModifiers.documentation,\n      SemanticTokenModifiers.defaultLibrary\n    ]\n    capability.formats = [TokenFormat.Relative]\n    capability.requests = {\n      range: true,\n      full: {\n        delta: true\n      }\n    }\n    capability.multilineTokenSupport = false\n    capability.overlappingTokenSupport = false\n    capability.serverCancelSupport = true\n    capability.augmentsSyntaxTokens = true\n    ensure(ensure(capabilities, 'workspace')!, 'semanticTokens')!.refreshSupport = true\n  }\n\n  public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {\n    const client = this._client\n    client.onRequest(SemanticTokensRefreshRequest.type, async () => {\n      for (const provider of this.getAllProviders()) {\n        provider.onDidChangeSemanticTokensEmitter.fire()\n      }\n    })\n    const [id, options] = this.getRegistration(documentSelector, capabilities.semanticTokensProvider)\n    if (!id || !options) {\n      return\n    }\n    this.register({ id, registerOptions: options })\n  }\n\n  protected registerLanguageProvider(options: SemanticTokensRegistrationOptions): [Disposable, SemanticTokensProviderShape] {\n    const fullProvider = Is.boolean(options.full) ? options.full : options.full !== undefined\n    const hasEditProvider = options.full !== undefined && typeof options.full !== 'boolean' && options.full.delta === true\n    const eventEmitter: Emitter<void> = new Emitter<void>()\n    const documentProvider: DocumentSemanticTokensProvider | undefined = fullProvider\n      ? {\n        onDidChangeSemanticTokens: eventEmitter.event,\n        provideDocumentSemanticTokens: (document, token) => {\n          const client = this._client\n          const middleware = client.middleware!\n          const provideDocumentSemanticTokens: DocumentSemanticsTokensSignature = (document, token) => {\n            const params: SemanticTokensParams = {\n              textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document)\n            }\n            return this.sendRequest(SemanticTokensRequest.type, params, token)\n          }\n          return middleware.provideDocumentSemanticTokens\n            ? middleware.provideDocumentSemanticTokens(document, token, provideDocumentSemanticTokens)\n            : provideDocumentSemanticTokens(document, token)\n        },\n        provideDocumentSemanticTokensEdits: hasEditProvider\n          ? (document, previousResultId, token) => {\n            const client = this._client\n            const middleware = client.middleware!\n            const provideDocumentSemanticTokensEdits: DocumentSemanticsTokensEditsSignature = (document, previousResultId, token) => {\n              const params: SemanticTokensDeltaParams = {\n                textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),\n                previousResultId\n              }\n              return this.sendRequest(SemanticTokensDeltaRequest.type, params, token)\n            }\n            return middleware.provideDocumentSemanticTokensEdits\n              ? middleware.provideDocumentSemanticTokensEdits(document, previousResultId, token, provideDocumentSemanticTokensEdits)\n              : provideDocumentSemanticTokensEdits(document, previousResultId, token)\n          }\n          : undefined\n      }\n      : undefined\n\n    const hasRangeProvider: boolean = options.range === true\n    const rangeProvider: DocumentRangeSemanticTokensProvider | undefined = hasRangeProvider\n      ? {\n        provideDocumentRangeSemanticTokens: (document: TextDocument, range: Range, token: CancellationToken) => {\n          const client = this._client\n          const middleware = client.middleware!\n          const provideDocumentRangeSemanticTokens: DocumentRangeSemanticTokensSignature = (document, range, token) => {\n            const params: SemanticTokensRangeParams = {\n              textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),\n              range\n            }\n            return this.sendRequest(SemanticTokensRangeRequest.type, params, token)\n          }\n          return middleware.provideDocumentRangeSemanticTokens\n            ? middleware.provideDocumentRangeSemanticTokens(document, range, token, provideDocumentRangeSemanticTokens)\n            : provideDocumentRangeSemanticTokens(document, range, token)\n        }\n      }\n      : undefined\n\n    const disposables: Disposable[] = []\n    if (documentProvider !== undefined) {\n      this._client.attachExtensionName(documentProvider)\n      disposables.push(languages.registerDocumentSemanticTokensProvider(options.documentSelector!, documentProvider, options.legend))\n    }\n    if (rangeProvider !== undefined) {\n      this._client.attachExtensionName(rangeProvider)\n      disposables.push(languages.registerDocumentRangeSemanticTokensProvider(options.documentSelector!, rangeProvider, options.legend))\n    }\n\n    return [Disposable.create(() => disposables.forEach(item => item.dispose())), { range: rangeProvider, full: documentProvider, onDidChangeSemanticTokensEmitter: eventEmitter }]\n  }\n}\n"
  },
  {
    "path": "src/language-client/signatureHelp.ts",
    "content": "'use strict'\nimport type { CancellationToken, ClientCapabilities, Disposable, DocumentSelector, Position, ServerCapabilities, SignatureHelp, SignatureHelpContext, SignatureHelpOptions, SignatureHelpRegistrationOptions } from 'vscode-languageserver-protocol'\nimport { TextDocument } from \"vscode-languageserver-textdocument\"\nimport languages from '../languages'\nimport { ProviderResult, SignatureHelpProvider } from '../provider'\nimport { SignatureHelpRequest } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\nimport * as UUID from './utils/uuid'\n\nexport interface ProvideSignatureHelpSignature {\n  (\n    this: void,\n    document: TextDocument,\n    position: Position,\n    context: SignatureHelpContext,\n    token: CancellationToken\n  ): ProviderResult<SignatureHelp>\n}\n\nexport interface SignatureHelpMiddleware {\n  provideSignatureHelp?: (\n    this: void,\n    document: TextDocument,\n    position: Position,\n    context: SignatureHelpContext,\n    token: CancellationToken,\n    next: ProvideSignatureHelpSignature\n  ) => ProviderResult<SignatureHelp>\n}\n\nexport class SignatureHelpFeature extends TextDocumentLanguageFeature<SignatureHelpOptions, SignatureHelpRegistrationOptions, SignatureHelpProvider, SignatureHelpMiddleware> {\n  constructor(client: FeatureClient<SignatureHelpMiddleware>) {\n    super(client, SignatureHelpRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    let config = ensure(ensure(capabilities, 'textDocument')!, 'signatureHelp')!\n    config.dynamicRegistration = true\n    config.contextSupport = true\n    config.signatureInformation = {\n      documentationFormat: this._client.supportedMarkupKind,\n      activeParameterSupport: true,\n      parameterInformation: {\n        labelOffsetSupport: true\n      }\n    }\n  }\n\n  public initialize(\n    capabilities: ServerCapabilities,\n    documentSelector: DocumentSelector\n  ): void {\n    const options = this.getRegistrationOptions(documentSelector, capabilities.signatureHelpProvider)\n    if (!options) return\n\n    this.register({\n      id: UUID.generateUuid(),\n      registerOptions: options\n    })\n  }\n\n  protected registerLanguageProvider(\n    options: SignatureHelpRegistrationOptions\n  ): [Disposable, SignatureHelpProvider] {\n    const provider: SignatureHelpProvider = {\n      provideSignatureHelp: (document, position, token, context) => {\n        const client = this._client\n        const providerSignatureHelp: ProvideSignatureHelpSignature = (document, position, context, token) => {\n          return this.sendRequest(\n            SignatureHelpRequest.type,\n            client.code2ProtocolConverter.asSignatureHelpParams(document, position, context),\n            token\n          )\n        }\n        const middleware = client.middleware!\n        return middleware.provideSignatureHelp\n          ? middleware.provideSignatureHelp(document, position, context, token, providerSignatureHelp)\n          : providerSignatureHelp(document, position, context, token)\n      }\n    }\n\n    this._client.attachExtensionName(provider)\n    const disposable = languages.registerSignatureHelpProvider(options.documentSelector!, provider, options.triggerCharacters)\n    return [disposable, provider]\n  }\n}\n"
  },
  {
    "path": "src/language-client/textDocumentContent.ts",
    "content": "import { CancellationToken, Disposable, Emitter, StaticRegistrationOptions, TextDocumentContentRefreshRequest, TextDocumentContentRequest, type ClientCapabilities, type RegistrationType, type ServerCapabilities, type TextDocumentContentParams, type TextDocumentContentRegistrationOptions } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport { ProviderResult, TextDocumentContentProvider } from '../provider'\nimport { defaultValue, disposeAll } from '../util'\nimport { toArray } from '../util/array'\nimport workspace from '../workspace'\nimport { ensure, type DynamicFeature, type FeatureClient, type FeatureState, type RegistrationData } from './features'\nimport * as UUID from './utils/uuid'\n\nexport interface ProvideTextDocumentContentSignature {\n  (this: void, uri: URI, token: CancellationToken): ProviderResult<string>\n}\n\nexport interface TextDocumentContentMiddleware {\n  provideTextDocumentContent?: (this: void, uri: URI, token: CancellationToken, next: ProvideTextDocumentContentSignature) => ProviderResult<string>\n}\n\nexport interface TextDocumentContentProviderShape {\n  scheme: string\n  onDidChangeEmitter: Emitter<URI>\n  provider: TextDocumentContentProvider\n}\n\nexport class TextDocumentContentFeature implements DynamicFeature<TextDocumentContentRegistrationOptions> {\n\n  private readonly _client: FeatureClient<TextDocumentContentMiddleware>\n  private readonly _registrations: Map<string, { disposable: Disposable; providers: TextDocumentContentProviderShape[] }> = new Map()\n\n  constructor(client: FeatureClient<TextDocumentContentMiddleware>) {\n    this._client = client\n  }\n\n  public getState(): FeatureState {\n    const registrations = this._registrations.size > 0\n    return { kind: 'workspace', id: TextDocumentContentRequest.method, registrations }\n  }\n\n  public get registrationType(): RegistrationType<TextDocumentContentRegistrationOptions> {\n    return TextDocumentContentRequest.type\n  }\n\n  public getProviders(): TextDocumentContentProviderShape[] {\n    const result: TextDocumentContentProviderShape[] = []\n    for (const registration of this._registrations.values()) {\n      result.push(...registration.providers)\n    }\n    return result\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    const textDocumentContent = ensure(ensure(capabilities, 'workspace')!, 'textDocumentContent')!\n    textDocumentContent.dynamicRegistration = true\n  }\n\n  public initialize(capabilities: ServerCapabilities): void {\n    const client = this._client\n    client.onRequest(TextDocumentContentRefreshRequest.type, async params => {\n      const uri = URI.parse(params.uri)\n      for (const registrations of this._registrations.values()) {\n        for (const provider of registrations.providers) {\n          if (provider.scheme === uri.scheme) {\n            provider.onDidChangeEmitter.fire(uri)\n          }\n        }\n      }\n    })\n\n    const capability = defaultValue(defaultValue(capabilities, {}).workspace, {}).textDocumentContent\n    if (capability) {\n      const id = StaticRegistrationOptions.hasId(capability) ? capability.id : UUID.generateUuid()\n      this.register({\n        id,\n        registerOptions: capability\n      })\n    }\n  }\n\n  public register(data: RegistrationData<TextDocumentContentRegistrationOptions>): void {\n    const registrations: TextDocumentContentProviderShape[] = []\n    const disposables: Disposable[] = []\n    for (const scheme of toArray(data.registerOptions.schemes)) {\n      const [disposable, registration] = this.registerTextDocumentContentProvider(scheme)\n      disposables.push(disposable)\n      registrations.push(registration)\n    }\n    this._registrations.set(data.id, {\n      disposable: Disposable.create(() => {\n        disposeAll(disposables)\n      }), providers: registrations\n    })\n  }\n\n  private registerTextDocumentContentProvider(scheme: string): [Disposable, TextDocumentContentProviderShape] {\n    const eventEmitter: Emitter<URI> = new Emitter<URI>()\n    const provider: TextDocumentContentProvider = {\n      onDidChange: eventEmitter.event,\n      provideTextDocumentContent: (uri, token) => {\n        const client = this._client\n        const provideTextDocumentContent: ProvideTextDocumentContentSignature = (uri, token) => {\n          const params: TextDocumentContentParams = {\n            uri: uri.toString()\n          }\n          return client.sendRequest(TextDocumentContentRequest.type, params, token).then(result => {\n            return result?.text\n          }, error => {\n            return client.handleFailedRequest(TextDocumentContentRequest.type, token, error, null)\n          })\n        }\n        const middleware = client.middleware\n        return middleware.provideTextDocumentContent\n          ? middleware.provideTextDocumentContent(uri, token, provideTextDocumentContent)\n          : provideTextDocumentContent(uri, token)\n      }\n    }\n    return [workspace.registerTextDocumentContentProvider(scheme, provider), { scheme, onDidChangeEmitter: eventEmitter, provider }]\n  }\n\n  public unregister(id: string): void {\n    const registration = this._registrations.get(id)\n    if (registration !== undefined) {\n      this._registrations.delete(id)\n      registration.disposable.dispose()\n    }\n  }\n\n  public dispose(): void {\n    this._registrations.forEach(value => {\n      value.disposable.dispose()\n    })\n    this._registrations.clear()\n  }\n}\n"
  },
  {
    "path": "src/language-client/textSynchronization.ts",
    "content": "'use strict'\nimport type { ClientCapabilities, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentSelector, ProtocolNotificationType, RegistrationType, SaveOptions, ServerCapabilities, TextDocumentChangeRegistrationOptions, TextDocumentRegistrationOptions, TextDocumentSaveRegistrationOptions, TextDocumentSyncOptions, TextEdit, WillSaveTextDocumentParams } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { TextDocumentWillSaveEvent } from '../core/files'\nimport { DidChangeTextDocumentParams as TextDocumentChangeEvent } from '../types'\nimport { defaultValue, disposeAll } from '../util'\nimport { CancellationToken, DidChangeTextDocumentNotification, DidCloseTextDocumentNotification, DidOpenTextDocumentNotification, DidSaveTextDocumentNotification, Disposable, Emitter, Event, TextDocumentSyncKind, WillSaveTextDocumentNotification, WillSaveTextDocumentWaitUntilRequest } from '../util/protocol'\nimport workspace from '../workspace'\nimport { DynamicDocumentFeature, DynamicFeature, ensure, FeatureClient, NextSignature, NotificationSendEvent, NotifyingFeature, RegistrationData, TextDocumentEventFeature, TextDocumentSendFeature } from './features'\nimport * as UUID from './utils/uuid'\n\nexport interface TextDocumentSynchronizationMiddleware {\n  didOpen?: NextSignature<TextDocument, Promise<void>>\n  didChange?: NextSignature<TextDocumentChangeEvent, Promise<void>>\n  willSave?: NextSignature<TextDocumentWillSaveEvent, Promise<void>>\n  willSaveWaitUntil?: NextSignature<TextDocumentWillSaveEvent, Thenable<TextEdit[]>>\n  didSave?: NextSignature<TextDocument, Promise<void>>\n  didClose?: NextSignature<TextDocument, Promise<void>>\n}\n\nexport interface ResolvedTextDocumentSyncCapabilities {\n  resolvedTextDocumentSync?: TextDocumentSyncOptions\n}\n\nexport interface DidOpenTextDocumentFeatureShape extends DynamicFeature<TextDocumentRegistrationOptions>, TextDocumentSendFeature<(textDocument: TextDocument) => Promise<void>>, NotifyingFeature<TextDocument, DidOpenTextDocumentParams> {\n  openDocuments: Iterable<TextDocument>\n}\n\nexport interface DidChangeTextDocumentFeatureShape extends DynamicFeature<TextDocumentChangeRegistrationOptions>, TextDocumentSendFeature<(event: TextDocumentChangeEvent) => Promise<void>>, NotifyingFeature<TextDocumentChangeEvent, DidChangeTextDocumentParams> {\n}\n\nexport interface DidSaveTextDocumentFeatureShape extends DynamicFeature<TextDocumentRegistrationOptions>, TextDocumentSendFeature<(textDocument: TextDocument) => Promise<void>>, NotifyingFeature<TextDocument, DidSaveTextDocumentParams> {\n}\n\nexport interface DidCloseTextDocumentFeatureShape extends DynamicFeature<TextDocumentRegistrationOptions>, TextDocumentSendFeature<(textDocument: TextDocument) => Promise<void>>, NotifyingFeature<TextDocument, DidCloseTextDocumentParams> {\n}\n\ninterface $ConfigurationOptions {\n  textSynchronization?: {\n    delayOpenNotifications?: boolean\n  }\n}\n\nexport class DidOpenTextDocumentFeature extends TextDocumentEventFeature<DidOpenTextDocumentParams, TextDocument, TextDocumentSynchronizationMiddleware> {\n  private readonly _syncedDocuments: Map<string, TextDocument>\n  private readonly _pendingOpenNotifications: Map<string, TextDocument>\n  private readonly _delayOpen: boolean\n  private _pendingOpenListeners: Disposable[] | undefined\n\n  constructor(client: FeatureClient<TextDocumentSynchronizationMiddleware, $ConfigurationOptions>, syncedDocuments: Map<string, TextDocument>) {\n    super(\n      client,\n      workspace.onDidOpenTextDocument,\n      DidOpenTextDocumentNotification.type,\n      'didOpen',\n      textDocument => client.code2ProtocolConverter.asOpenTextDocumentParams(textDocument),\n      TextDocumentEventFeature.textDocumentFilter\n    )\n    this._syncedDocuments = syncedDocuments\n    this._pendingOpenNotifications = new Map<string, TextDocument>()\n    this._delayOpen = defaultValue(defaultValue(client.clientOptions.textSynchronization, {}).delayOpenNotifications, false)\n  }\n\n  public async callback(document: TextDocument): Promise<void> {\n    if (!this._delayOpen) {\n      return super.callback(document)\n    } else {\n      if (!this.matches(document)) {\n        return\n      }\n      const tabsModel = workspace.tabs\n      if (tabsModel.isVisible(document)) {\n        return super.callback(document)\n      } else {\n        this._pendingOpenNotifications.set(document.uri.toString(), document)\n      }\n    }\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    ensure(ensure(capabilities, 'textDocument')!, 'synchronization')!.dynamicRegistration = true\n  }\n\n  public get openDocuments(): IterableIterator<TextDocument> {\n    return this._syncedDocuments.values()\n  }\n\n  public initialize(\n    capabilities: ServerCapabilities,\n    documentSelector: DocumentSelector\n  ): void {\n    let textDocumentSyncOptions = (capabilities as ResolvedTextDocumentSyncCapabilities).resolvedTextDocumentSync\n    if (documentSelector && textDocumentSyncOptions && textDocumentSyncOptions.openClose) {\n      this.register({\n        id: UUID.generateUuid(),\n        registerOptions: { documentSelector }\n      })\n    }\n  }\n\n  public get registrationType(): RegistrationType<TextDocumentRegistrationOptions> {\n    return DidOpenTextDocumentNotification.type\n  }\n\n  public register(data: RegistrationData<TextDocumentRegistrationOptions>): void {\n    super.register(data)\n    if (!data.registerOptions.documentSelector) return\n    const onError = error => {\n      this._client.error(`Sending document notification ${this._type.method} failed`, error)\n    }\n    workspace.textDocuments.forEach(textDocument => {\n      let uri = textDocument.uri\n      if (!this._syncedDocuments.has(uri)) {\n        this.callback(textDocument).catch(onError)\n      }\n    })\n    if (this._delayOpen && this._pendingOpenListeners === undefined) {\n      this._pendingOpenListeners = []\n      const tabsModel = workspace.tabs\n      this._pendingOpenListeners.push(tabsModel.onClose(closed => {\n        for (const uri of closed) {\n          this._pendingOpenNotifications.delete(uri.toString())\n        }\n      }))\n      this._pendingOpenListeners.push(tabsModel.onOpen(opened => {\n        for (const uri of opened) {\n          const document = this._pendingOpenNotifications.get(uri.toString())\n          if (document !== undefined) {\n            super.callback(document).catch(onError)\n            this._pendingOpenNotifications.delete(uri.toString())\n          }\n        }\n      }))\n      this._pendingOpenListeners.push(workspace.onDidCloseTextDocument(document => {\n        this._pendingOpenNotifications.delete(document.uri)\n      }))\n    }\n  }\n\n  /**\n   * Sends any pending open notifications unless they are for the document\n   * being closed.\n   * @param closingDocument The document being closed.\n   * @returns Whether a pending open notification was dropped because it was for the closing document.\n   */\n  public async sendPendingOpenNotifications(closingDocument?: string): Promise<boolean> {\n    if (!this._delayOpen) return\n    const notifications = Array.from(this._pendingOpenNotifications.values())\n    this._pendingOpenNotifications.clear()\n    let didDropOpenNotification = false\n    for (const notification of notifications) {\n      if (closingDocument !== undefined && notification.uri.toString() === closingDocument) {\n        didDropOpenNotification = true\n        continue\n      }\n      await super.callback(notification)\n    }\n    return didDropOpenNotification\n  }\n\n  protected notificationSent(textDocument: TextDocument, type: ProtocolNotificationType<DidOpenTextDocumentParams, TextDocumentRegistrationOptions>, params: DidOpenTextDocumentParams): void {\n    super.notificationSent(textDocument, type, params)\n    this._syncedDocuments.set(textDocument.uri.toString(), textDocument)\n  }\n\n  public dispose(): void {\n    this._pendingOpenNotifications.clear()\n    disposeAll(this._pendingOpenListeners ?? [])\n    this._pendingOpenListeners = undefined\n    super.dispose()\n  }\n}\n\nexport class DidCloseTextDocumentFeature extends TextDocumentEventFeature<DidCloseTextDocumentParams, TextDocument, TextDocumentSynchronizationMiddleware> implements DidCloseTextDocumentFeatureShape {\n  constructor(\n    client: FeatureClient<TextDocumentSynchronizationMiddleware>,\n    private _syncedDocuments: Map<string, TextDocument>\n  ) {\n    super(\n      client,\n      workspace.onDidCloseTextDocument,\n      DidCloseTextDocumentNotification.type,\n      'didClose',\n      textDocument => client.code2ProtocolConverter.asCloseTextDocumentParams(textDocument),\n      TextDocumentEventFeature.textDocumentFilter\n    )\n  }\n\n  public get registrationType(): RegistrationType<TextDocumentRegistrationOptions> {\n    return DidCloseTextDocumentNotification.type\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    ensure(\n      ensure(capabilities, 'textDocument')!,\n      'synchronization'\n    )!.dynamicRegistration = true\n  }\n\n  public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {\n    let textDocumentSyncOptions = (capabilities as ResolvedTextDocumentSyncCapabilities).resolvedTextDocumentSync\n    if (\n      documentSelector &&\n      textDocumentSyncOptions &&\n      textDocumentSyncOptions.openClose\n    ) {\n      this.register({\n        id: UUID.generateUuid(),\n        registerOptions: { documentSelector }\n      })\n    }\n  }\n\n  protected notificationSent(textDocument: TextDocument, type: ProtocolNotificationType<DidCloseTextDocumentParams, TextDocumentRegistrationOptions>, params: DidCloseTextDocumentParams): void {\n    super.notificationSent(textDocument, type, params)\n    this._syncedDocuments.delete(textDocument.uri.toString())\n  }\n\n  public unregister(id: string): void {\n    let selector = this._selectors.get(id)\n    if (!selector) return\n    // The super call removed the selector from the map\n    // of selectors.\n    super.unregister(id)\n    let selectors = this._selectors.values()\n    this._syncedDocuments.forEach(textDocument => {\n      if (\n        workspace.match(selector, textDocument) > 0 &&\n        !this._selectorFilter!(selectors, textDocument)\n      ) {\n        this.sendNotification(textDocument).catch(error => {\n          this._client.error(`Sending document notification ${this._type.method} failed`, error)\n        })\n      }\n    })\n  }\n}\n\ninterface DidChangeTextDocumentData {\n  syncKind: 0 | 1 | 2\n  documentSelector: DocumentSelector\n}\n\nexport class DidChangeTextDocumentFeature extends DynamicDocumentFeature<TextDocumentChangeRegistrationOptions, TextDocumentSynchronizationMiddleware> implements DidChangeTextDocumentFeatureShape {\n  private _listener: Disposable | undefined\n  private readonly _changeData: Map<string, DidChangeTextDocumentData>\n  private _onNotificationSent: Emitter<NotificationSendEvent<TextDocumentChangeEvent, DidChangeTextDocumentParams>>\n\n  constructor(client: FeatureClient<TextDocumentSynchronizationMiddleware>) {\n    super(client)\n    this._changeData = new Map<string, DidChangeTextDocumentData>()\n    this._onNotificationSent = new Emitter()\n  }\n\n  public *getDocumentSelectors(): IterableIterator<DocumentSelector> {\n    for (const data of this._changeData.values()) {\n      yield data.documentSelector\n    }\n  }\n\n  public get registrationType(): RegistrationType<TextDocumentChangeRegistrationOptions> {\n    return DidChangeTextDocumentNotification.type\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    ensure(ensure(capabilities, 'textDocument')!, 'synchronization')!.dynamicRegistration = true\n  }\n\n  public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {\n    let textDocumentSyncOptions = (capabilities as ResolvedTextDocumentSyncCapabilities).resolvedTextDocumentSync\n    if (\n      documentSelector &&\n      textDocumentSyncOptions &&\n      textDocumentSyncOptions.change !== undefined &&\n      textDocumentSyncOptions.change !== TextDocumentSyncKind.None\n    ) {\n      this.register({\n        id: UUID.generateUuid(),\n        registerOptions: Object.assign(\n          {},\n          { documentSelector },\n          { syncKind: textDocumentSyncOptions.change }\n        )\n      })\n    }\n  }\n\n  public register(\n    data: RegistrationData<TextDocumentChangeRegistrationOptions>\n  ): void {\n    if (!data.registerOptions.documentSelector) return\n    if (!this._listener) {\n      this._listener = workspace.onDidChangeTextDocument(this.callback, this)\n    }\n    this._changeData.set(data.id, {\n      documentSelector: data.registerOptions.documentSelector,\n      syncKind: data.registerOptions.syncKind\n    })\n  }\n\n  private callback(event: TextDocumentChangeEvent): Promise<void> {\n    // Text document changes are send for dirty changes as well. We don't\n    // have dirty / undirty events in the LSP so we ignore content changes\n    // with length zero.\n    if (event.contentChanges.length === 0) {\n      return\n    }\n    const promises: Promise<void>[] = []\n    for (const changeData of this._changeData.values()) {\n      if (workspace.match(changeData.documentSelector, event.document) > 0) {\n        let middleware = this._client.middleware!\n        let didChange: (event: TextDocumentChangeEvent) => Promise<void>\n        const client = this._client\n        if (changeData.syncKind === TextDocumentSyncKind.Incremental) {\n          didChange = async (event: TextDocumentChangeEvent): Promise<void> => {\n            const params = client.code2ProtocolConverter.asChangeTextDocumentParams(event)\n            await this._client.sendNotification(DidChangeTextDocumentNotification.type, params)\n            this.notificationSent(event, DidChangeTextDocumentNotification.type, params)\n          }\n        } else if (changeData.syncKind === TextDocumentSyncKind.Full) {\n          didChange = async (event: TextDocumentChangeEvent): Promise<void> => {\n            const params = client.code2ProtocolConverter.asFullChangeTextDocumentParams(event.document)\n            await this._client.sendNotification(DidChangeTextDocumentNotification.type, params)\n            this.notificationSent(event, DidChangeTextDocumentNotification.type, params)\n          }\n        }\n        if (didChange) {\n          promises.push(middleware.didChange ? middleware.didChange(event, didChange) : didChange(event))\n        }\n      }\n    }\n    return Promise.all(promises).then(undefined, error => {\n      this._client.error(`Sending document notification ${DidChangeTextDocumentNotification.type.method} failed`, error)\n    })\n  }\n\n  public get onNotificationSent(): Event<NotificationSendEvent<TextDocumentChangeEvent, DidChangeTextDocumentParams>> {\n    return this._onNotificationSent.event\n  }\n\n  private notificationSent(changeEvent: TextDocumentChangeEvent, type: ProtocolNotificationType<DidChangeTextDocumentParams, TextDocumentRegistrationOptions>, params: DidChangeTextDocumentParams): void {\n    this._onNotificationSent.fire({ original: changeEvent, type, params })\n  }\n\n  public unregister(id: string): void {\n    this._changeData.delete(id)\n  }\n\n  public dispose(): void {\n    this._changeData.clear()\n    if (this._listener) {\n      this._listener.dispose()\n      this._listener = undefined\n    }\n  }\n\n  public getProvider(document: TextDocument): { send: (event: TextDocumentChangeEvent) => Promise<void> } | undefined {\n    for (const changeData of this._changeData.values()) {\n      if (workspace.match(changeData.documentSelector, document) > 0) {\n        return {\n          send: (event: TextDocumentChangeEvent): Promise<void> => {\n            return this.callback(event)\n          }\n        }\n      }\n    }\n    return undefined\n  }\n}\n\nexport class WillSaveFeature extends TextDocumentEventFeature<WillSaveTextDocumentParams, TextDocumentWillSaveEvent, TextDocumentSynchronizationMiddleware> {\n  constructor(client: FeatureClient<TextDocumentSynchronizationMiddleware>) {\n    super(\n      client,\n      workspace.onWillSaveTextDocument,\n      WillSaveTextDocumentNotification.type,\n      'willSave',\n      willSaveEvent => client.code2ProtocolConverter.asWillSaveTextDocumentParams(willSaveEvent),\n      (selectors, willSaveEvent) => TextDocumentEventFeature.textDocumentFilter(selectors, willSaveEvent.document)\n    )\n  }\n\n  public get registrationType(): RegistrationType<TextDocumentRegistrationOptions> {\n    return WillSaveTextDocumentNotification.type\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    let value = ensure(ensure(capabilities, 'textDocument')!, 'synchronization')!\n    value.willSave = true\n  }\n\n  public initialize(\n    capabilities: ServerCapabilities,\n    documentSelector: DocumentSelector\n  ): void {\n    let textDocumentSyncOptions = (capabilities as ResolvedTextDocumentSyncCapabilities).resolvedTextDocumentSync\n    if (\n      documentSelector &&\n      textDocumentSyncOptions &&\n      textDocumentSyncOptions.willSave\n    ) {\n      this.register({\n        id: UUID.generateUuid(),\n        registerOptions: { documentSelector }\n      })\n    }\n  }\n}\n\nexport class WillSaveWaitUntilFeature extends DynamicDocumentFeature<TextDocumentRegistrationOptions, TextDocumentSynchronizationMiddleware> {\n  private _listener: Disposable | undefined\n  private _selectors: Map<string, DocumentSelector>\n\n  constructor(client: FeatureClient<TextDocumentSynchronizationMiddleware>) {\n    super(client)\n    this._selectors = new Map<string, DocumentSelector>()\n  }\n\n  protected getDocumentSelectors(): IterableIterator<DocumentSelector> {\n    return this._selectors.values()\n  }\n\n  public get registrationType(): RegistrationType<TextDocumentRegistrationOptions> {\n    return WillSaveTextDocumentWaitUntilRequest.type\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    let value = ensure(ensure(capabilities, 'textDocument')!, 'synchronization')!\n    value.willSaveWaitUntil = true\n  }\n\n  public initialize(\n    capabilities: ServerCapabilities,\n    documentSelector: DocumentSelector\n  ): void {\n    let textDocumentSyncOptions = (capabilities as ResolvedTextDocumentSyncCapabilities).resolvedTextDocumentSync\n    if (\n      documentSelector &&\n      documentSelector.length > 0 &&\n      textDocumentSyncOptions &&\n      textDocumentSyncOptions.willSaveWaitUntil\n    ) {\n      this.register({\n        id: UUID.generateUuid(),\n        registerOptions: { documentSelector }\n      })\n    }\n  }\n\n  public register(\n    data: RegistrationData<TextDocumentRegistrationOptions>\n  ): void {\n    if (data.registerOptions.documentSelector) {\n      if (!this._listener) {\n        this._listener = workspace.onWillSaveTextDocument(this.callback, this)\n      }\n      this._selectors.set(data.id, data.registerOptions.documentSelector)\n    }\n  }\n\n  private callback(event: TextDocumentWillSaveEvent): void {\n    if (TextDocumentEventFeature.textDocumentFilter(\n      this._selectors.values(),\n      event.document)) {\n      const client = this._client\n      let middleware = this._client.middleware\n      let willSaveWaitUntil = (event: TextDocumentWillSaveEvent): Thenable<TextEdit[]> => {\n        return this.sendRequest(\n          WillSaveTextDocumentWaitUntilRequest.type,\n          client.code2ProtocolConverter.asWillSaveTextDocumentParams(event),\n          CancellationToken.None\n        )\n      }\n      event.waitUntil(\n        middleware.willSaveWaitUntil\n          ? middleware.willSaveWaitUntil(event, willSaveWaitUntil)\n          : willSaveWaitUntil(event)\n      )\n    }\n  }\n\n  public unregister(id: string): void {\n    this._selectors.delete(id)\n    if (this._selectors.size === 0 && this._listener) {\n      this._listener.dispose()\n      this._listener = undefined\n    }\n  }\n\n  public dispose(): void {\n    this._selectors.clear()\n    if (this._listener) {\n      this._listener.dispose()\n      this._listener = undefined\n    }\n  }\n}\n\nexport class DidSaveTextDocumentFeature extends TextDocumentEventFeature<DidSaveTextDocumentParams, TextDocument, TextDocumentSynchronizationMiddleware> implements DidSaveTextDocumentFeatureShape {\n  private _includeText: boolean\n\n  constructor(client: FeatureClient<TextDocumentSynchronizationMiddleware>) {\n    super(\n      client, workspace.onDidSaveTextDocument, DidSaveTextDocumentNotification.type,\n      'didSave',\n      textDocument => client.code2ProtocolConverter.asSaveTextDocumentParams(textDocument, this._includeText),\n      TextDocumentEventFeature.textDocumentFilter\n    )\n    this._includeText = false\n  }\n\n  public get registrationType(): RegistrationType<TextDocumentSaveRegistrationOptions> {\n    return DidSaveTextDocumentNotification.type\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    ensure(ensure(capabilities, 'textDocument')!, 'synchronization')!.didSave = true\n  }\n\n  public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {\n    const textDocumentSyncOptions = (capabilities as ResolvedTextDocumentSyncCapabilities).resolvedTextDocumentSync\n    if (documentSelector && textDocumentSyncOptions && textDocumentSyncOptions.save) {\n      const saveOptions: SaveOptions = typeof textDocumentSyncOptions.save === 'boolean'\n        ? { includeText: false }\n        : { includeText: !!textDocumentSyncOptions.save.includeText }\n      this.register({\n        id: UUID.generateUuid(),\n        registerOptions: Object.assign({}, { documentSelector }, saveOptions)\n      })\n    }\n  }\n\n  public register(data: RegistrationData<TextDocumentSaveRegistrationOptions>): void {\n    this._includeText = !!data.registerOptions.includeText\n    super.register(data)\n  }\n}\n"
  },
  {
    "path": "src/language-client/typeDefinition.ts",
    "content": "'use strict'\nimport type { ClientCapabilities, Definition, DefinitionLink, Disposable, DocumentSelector, Position, ServerCapabilities, TypeDefinitionOptions, TypeDefinitionRegistrationOptions } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport languages from '../languages'\nimport { ProviderResult, TypeDefinitionProvider } from '../provider'\nimport { CancellationToken, TypeDefinitionRequest } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\n\nexport interface ProvideTypeDefinitionSignature {\n  (\n    this: void,\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken\n  ): ProviderResult<Definition | DefinitionLink[]>\n}\n\nexport interface TypeDefinitionMiddleware {\n  provideTypeDefinition?: (\n    this: void,\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken,\n    next: ProvideTypeDefinitionSignature\n  ) => ProviderResult<Definition | DefinitionLink[]>\n}\n\nexport class TypeDefinitionFeature extends TextDocumentLanguageFeature<boolean | TypeDefinitionOptions, TypeDefinitionRegistrationOptions, TypeDefinitionProvider, TypeDefinitionMiddleware> {\n  constructor(client: FeatureClient<TypeDefinitionMiddleware>) {\n    super(client, TypeDefinitionRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    const typeDefinitionSupport = ensure(ensure(capabilities, 'textDocument')!, 'typeDefinition')!\n    typeDefinitionSupport.dynamicRegistration = true\n    typeDefinitionSupport.linkSupport = true\n  }\n\n  public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {\n    const [id, options] = this.getRegistration(documentSelector, capabilities.typeDefinitionProvider)\n    if (!id || !options) {\n      return\n    }\n    this.register({ id, registerOptions: options })\n  }\n\n  protected registerLanguageProvider(options: TypeDefinitionRegistrationOptions): [Disposable, TypeDefinitionProvider] {\n    const provider: TypeDefinitionProvider = {\n      provideTypeDefinition: (document, position, token) => {\n        const client = this._client\n        const provideTypeDefinition: ProvideTypeDefinitionSignature = (document, position, token) =>\n          this.sendRequest(TypeDefinitionRequest.type, client.code2ProtocolConverter.asTextDocumentPositionParams(document, position), token)\n        const middleware = client.middleware\n        return middleware.provideTypeDefinition\n          ? middleware.provideTypeDefinition(document, position, token, provideTypeDefinition)\n          : provideTypeDefinition(document, position, token)\n      }\n    }\n    this._client.attachExtensionName(provider)\n    return [languages.registerTypeDefinitionProvider(options.documentSelector, provider), provider]\n  }\n}\n"
  },
  {
    "path": "src/language-client/typeHierarchy.ts",
    "content": "'use strict'\nimport type { CancellationToken, ClientCapabilities, Disposable, DocumentSelector, Position, ServerCapabilities, TypeHierarchyItem, TypeHierarchyOptions, TypeHierarchyRegistrationOptions } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport languages from '../languages'\nimport { ProviderResult, TypeHierarchyProvider } from '../provider'\nimport { TypeHierarchyPrepareRequest, TypeHierarchySubtypesRequest, TypeHierarchySupertypesRequest } from '../util/protocol'\nimport { ensure, FeatureClient, TextDocumentLanguageFeature } from './features'\n\nexport type PrepareTypeHierarchySignature = (this: void, document: TextDocument, position: Position, token: CancellationToken) => ProviderResult<TypeHierarchyItem[]>\nexport type TypeHierarchySupertypesSignature = (this: void, item: TypeHierarchyItem, token: CancellationToken) => ProviderResult<TypeHierarchyItem[]>\nexport type TypeHierarchySubtypesSignature = (this: void, item: TypeHierarchyItem, token: CancellationToken) => ProviderResult<TypeHierarchyItem[]>\n\n/**\n * Type hierarchy middleware\n * @since 3.17.0\n */\nexport interface TypeHierarchyMiddleware {\n  prepareTypeHierarchy?: (this: void, document: TextDocument, positions: Position, token: CancellationToken, next: PrepareTypeHierarchySignature) => ProviderResult<TypeHierarchyItem[]>\n  provideTypeHierarchySupertypes?: (this: void, item: TypeHierarchyItem, token: CancellationToken, next: TypeHierarchySupertypesSignature) => ProviderResult<TypeHierarchyItem[]>\n  provideTypeHierarchySubtypes?: (this: void, item: TypeHierarchyItem, token: CancellationToken, next: TypeHierarchySubtypesSignature) => ProviderResult<TypeHierarchyItem[]>\n}\n\nexport class TypeHierarchyFeature extends TextDocumentLanguageFeature<boolean | TypeHierarchyOptions, TypeHierarchyRegistrationOptions, TypeHierarchyProvider, TypeHierarchyMiddleware> {\n  constructor(client: FeatureClient<TypeHierarchyMiddleware>) {\n    super(client, TypeHierarchyPrepareRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    const capability = ensure(ensure(capabilities, 'textDocument')!, 'typeHierarchy')!\n    capability.dynamicRegistration = true\n  }\n\n  public initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector): void {\n    const [id, options] = this.getRegistration(documentSelector, capabilities.typeHierarchyProvider)\n    if (!id || !options) {\n      return\n    }\n    this.register({ id, registerOptions: options })\n  }\n\n  protected registerLanguageProvider(options: TypeHierarchyRegistrationOptions): [Disposable, TypeHierarchyProvider] {\n    const client = this._client\n    const selector = options.documentSelector!\n    const provider = {\n      prepareTypeHierarchy: (document: TextDocument, position: Position, token: CancellationToken): ProviderResult<TypeHierarchyItem[]> => {\n        const prepareTypeHierarchy: PrepareTypeHierarchySignature = (document, position, token) => {\n          const params = client.code2ProtocolConverter.asTextDocumentPositionParams(document, position)\n          return this.sendRequest(TypeHierarchyPrepareRequest.type, params, token)\n        }\n        const middleware = client.middleware!\n        return middleware.prepareTypeHierarchy\n          ? middleware.prepareTypeHierarchy(document, position, token, prepareTypeHierarchy)\n          : prepareTypeHierarchy(document, position, token)\n      },\n      provideTypeHierarchySupertypes: (item: TypeHierarchyItem, token: CancellationToken): ProviderResult<TypeHierarchyItem[]> => {\n        const provideTypeHierarchySupertypes: TypeHierarchySupertypesSignature = (item, token) => {\n          return this.sendRequest(TypeHierarchySupertypesRequest.type, { item }, token)\n        }\n        const middleware = client.middleware!\n        return middleware.provideTypeHierarchySupertypes\n          ? middleware.provideTypeHierarchySupertypes(item, token, provideTypeHierarchySupertypes)\n          : provideTypeHierarchySupertypes(item, token)\n      },\n      provideTypeHierarchySubtypes: (item: TypeHierarchyItem, token: CancellationToken): ProviderResult<TypeHierarchyItem[]> => {\n        const provideTypeHierarchySubtypes: TypeHierarchySubtypesSignature = (item, token) => {\n          return this.sendRequest(TypeHierarchySubtypesRequest.type, { item }, token)\n        }\n        const middleware = client.middleware!\n        return middleware.provideTypeHierarchySubtypes\n          ? middleware.provideTypeHierarchySubtypes(item, token, provideTypeHierarchySubtypes)\n          : provideTypeHierarchySubtypes(item, token)\n      }\n    }\n    this._client.attachExtensionName(provider)\n    return [languages.registerTypeHierarchyProvider(selector, provider), provider]\n  }\n}\n"
  },
  {
    "path": "src/language-client/utils/async.ts",
    "content": "'use strict'\nimport { Disposable, RAL } from '../../util/protocol'\n\nexport interface ITask<T> {\n  (): T\n}\n\nexport class Delayer<T> {\n\n  public defaultDelay: number\n  private timeout: Disposable | undefined\n  private completionPromise: Promise<T> | undefined\n  private onSuccess: ((value: T | Promise<T> | undefined) => void) | undefined\n  private task: ITask<T> | undefined\n\n  constructor(defaultDelay: number) {\n    this.defaultDelay = defaultDelay\n    this.timeout = undefined\n    this.completionPromise = undefined\n    this.onSuccess = undefined\n    this.task = undefined\n  }\n\n  public trigger(task: ITask<T>, delay: number = this.defaultDelay): Promise<T> {\n    this.task = task\n    if (delay >= 0) {\n      this.cancelTimeout()\n    }\n\n    if (!this.completionPromise) {\n      this.completionPromise = new Promise<T | undefined>(resolve => {\n        this.onSuccess = resolve\n      }).then(() => {\n        this.completionPromise = undefined\n        this.onSuccess = undefined\n        let result = this.task!()\n        this.task = undefined\n        return result\n      })\n    }\n\n    if (delay >= 0 || this.timeout === void 0) {\n      this.timeout = RAL().timer.setTimeout(() => {\n        this.timeout = undefined\n        this.onSuccess!(undefined)\n      }, delay >= 0 ? delay : this.defaultDelay)\n    }\n\n    return this.completionPromise\n  }\n\n  public forceDelivery(): T | undefined {\n    if (!this.completionPromise) {\n      return undefined\n    }\n    this.cancelTimeout()\n    let result: T = this.task!()\n    this.completionPromise = undefined\n    this.onSuccess = undefined\n    this.task = undefined\n    return result\n  }\n\n  public isTriggered(): boolean {\n    return this.timeout !== undefined\n  }\n\n  public cancel(): void {\n    this.cancelTimeout()\n    this.completionPromise = undefined\n  }\n\n  public dispose(): void {\n    this.cancelTimeout()\n  }\n\n  private cancelTimeout(): void {\n    if (this.timeout !== undefined) {\n      this.timeout.dispose()\n      this.timeout = undefined\n    }\n  }\n}\n"
  },
  {
    "path": "src/language-client/utils/codeConverter.ts",
    "content": "import type * as protocol from 'vscode-languageserver-protocol'\nimport { DocumentUri, TextDocument } from 'vscode-languageserver-textdocument'\nimport { Position } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport { FileCreateEvent, FileDeleteEvent, FileRenameEvent, TextDocumentWillSaveEvent } from '../../core/files'\nimport { DidChangeTextDocumentParams as TextDocumentChangeEvent } from '../../types'\nimport { omit } from '../../util/lodash'\n\nexport interface Converter {\n  asUri(value: URI): string\n\n  asTextDocumentItem(textDocument: TextDocument): protocol.TextDocumentItem\n\n  asTextDocumentIdentifier(textDocument: TextDocument): protocol.TextDocumentIdentifier\n\n  asVersionedTextDocumentIdentifier(textDocument: TextDocument): protocol.VersionedTextDocumentIdentifier\n\n  asOpenTextDocumentParams(textDocument: TextDocument): protocol.DidOpenTextDocumentParams\n\n  asChangeTextDocumentParams(event: TextDocumentChangeEvent): protocol.DidChangeTextDocumentParams\n  asFullChangeTextDocumentParams(textDocument: TextDocument): protocol.DidChangeTextDocumentParams\n\n  asCloseTextDocumentParams(textDocument: TextDocument): protocol.DidCloseTextDocumentParams\n\n  asSaveTextDocumentParams(textDocument: TextDocument, includeText?: boolean): protocol.DidSaveTextDocumentParams\n  asWillSaveTextDocumentParams(event: TextDocumentWillSaveEvent): protocol.WillSaveTextDocumentParams\n\n  asDidCreateFilesParams(event: FileCreateEvent): protocol.CreateFilesParams\n  asDidRenameFilesParams(event: FileRenameEvent): protocol.RenameFilesParams\n  asDidDeleteFilesParams(event: FileDeleteEvent): protocol.DeleteFilesParams\n  asWillCreateFilesParams(event: FileCreateEvent): protocol.CreateFilesParams\n  asWillRenameFilesParams(event: FileRenameEvent): protocol.RenameFilesParams\n  asWillDeleteFilesParams(event: FileDeleteEvent): protocol.DeleteFilesParams\n\n  asTextDocumentPositionParams(textDocument: TextDocument, position: Position): protocol.TextDocumentPositionParams\n\n  asCompletionParams(textDocument: TextDocument, position: Position, context: protocol.CompletionContext): protocol.CompletionParams\n\n  asSignatureHelpParams(textDocument: TextDocument, position: Position, context: protocol.SignatureHelpContext): protocol.SignatureHelpParams\n\n  asReferenceParams(textDocument: TextDocument, position: Position, options: { includeDeclaration: boolean }): protocol.ReferenceParams\n\n  asDocumentSymbolParams(textDocument: TextDocument): protocol.DocumentSymbolParams\n\n  asCodeLensParams(textDocument: TextDocument): protocol.CodeLensParams\n\n  asDocumentLinkParams(textDocument: TextDocument): protocol.DocumentLinkParams\n}\n\nexport interface URIConverter {\n  (value: URI): string\n}\n\nexport function createConverter(uriConverter?: URIConverter): Converter {\n  uriConverter = uriConverter || ((value: URI) => value.toString())\n\n  function asUri(value: URI | DocumentUri): string {\n    if (URI.isUri(value)) {\n      return uriConverter(value)\n    } else {\n      return uriConverter(URI.parse(value))\n    }\n  }\n\n  function asTextDocumentItem(textDocument: TextDocument): protocol.TextDocumentItem {\n    return {\n      uri: asUri(textDocument.uri),\n      languageId: textDocument.languageId,\n      version: textDocument.version,\n      text: textDocument.getText()\n    }\n  }\n\n  function asTextDocumentIdentifier(textDocument: TextDocument): protocol.TextDocumentIdentifier {\n    return {\n      uri: asUri(textDocument.uri)\n    }\n  }\n\n  function asVersionedTextDocumentIdentifier(textDocument: TextDocument): protocol.VersionedTextDocumentIdentifier {\n    return {\n      uri: asUri(textDocument.uri),\n      version: textDocument.version\n    }\n  }\n\n  function asOpenTextDocumentParams(textDocument: TextDocument): protocol.DidOpenTextDocumentParams {\n    return {\n      textDocument: asTextDocumentItem(textDocument)\n    }\n  }\n\n  function asChangeTextDocumentParams(event: TextDocumentChangeEvent): protocol.DidChangeTextDocumentParams {\n    let { textDocument, contentChanges } = event\n    let result: protocol.DidChangeTextDocumentParams = {\n      textDocument: {\n        uri: asUri(textDocument.uri),\n        version: textDocument.version\n      },\n      contentChanges: contentChanges.slice()\n    }\n    return result\n  }\n\n  function asFullChangeTextDocumentParams(textDocument: TextDocument): protocol.DidChangeTextDocumentParams {\n    return {\n      textDocument: asVersionedTextDocumentIdentifier(textDocument),\n      contentChanges: [{ text: textDocument.getText() }]\n    }\n  }\n\n  function asCloseTextDocumentParams(textDocument: TextDocument): protocol.DidCloseTextDocumentParams {\n    return {\n      textDocument: asTextDocumentIdentifier(textDocument)\n    }\n  }\n\n  function asSaveTextDocumentParams(textDocument: TextDocument, includeText = false): protocol.DidSaveTextDocumentParams {\n    let result: protocol.DidSaveTextDocumentParams = {\n      textDocument: asVersionedTextDocumentIdentifier(textDocument)\n    }\n    if (includeText) {\n      result.text = textDocument.getText()\n    }\n    return result\n  }\n\n  function asWillSaveTextDocumentParams(event: TextDocumentWillSaveEvent): protocol.WillSaveTextDocumentParams {\n    return {\n      textDocument: asTextDocumentIdentifier(event.document),\n      reason: event.reason\n    }\n  }\n\n  function asDidCreateFilesParams(event: FileCreateEvent): protocol.CreateFilesParams {\n    return {\n      files: event.files.map(file => ({ uri: asUri(file) }))\n    }\n  }\n\n  function asDidRenameFilesParams(event: FileRenameEvent): protocol.RenameFilesParams {\n    return {\n      files: event.files.map(file => ({ oldUri: asUri(file.oldUri), newUri: asUri(file.newUri) })\n      )\n    }\n  }\n\n  function asDidDeleteFilesParams(event: FileDeleteEvent): protocol.DeleteFilesParams {\n    return {\n      files: event.files.map(file => ({ uri: asUri(file) }))\n    }\n  }\n\n  function asWillCreateFilesParams(event: FileCreateEvent): protocol.CreateFilesParams {\n    return {\n      files: event.files.map(file => ({ uri: asUri(file) }))\n    }\n  }\n\n  function asWillRenameFilesParams(event: FileRenameEvent): protocol.RenameFilesParams {\n    return {\n      files: event.files.map(file => ({ oldUri: asUri(file.oldUri), newUri: asUri(file.newUri) }))\n    }\n  }\n\n  function asWillDeleteFilesParams(event: FileDeleteEvent): protocol.DeleteFilesParams {\n    return {\n      files: event.files.map(file => ({ uri: asUri(file) }))\n    }\n  }\n\n  function asTextDocumentPositionParams(textDocument: TextDocument, position: Position): protocol.TextDocumentPositionParams {\n    return {\n      textDocument: asTextDocumentIdentifier(textDocument),\n      position\n    }\n  }\n\n  function asCompletionParams(textDocument: TextDocument, position: Position, context: protocol.CompletionContext): protocol.CompletionParams {\n    return {\n      textDocument: asTextDocumentIdentifier(textDocument),\n      position,\n      context: omit(context, ['option'])\n    }\n  }\n\n  function asSignatureHelpParams(textDocument: TextDocument, position: Position, context: protocol.SignatureHelpContext): protocol.SignatureHelpParams {\n    return {\n      textDocument: asTextDocumentIdentifier(textDocument),\n      position,\n      context\n    }\n  }\n\n  function asReferenceParams(textDocument: TextDocument, position: Position, options: { includeDeclaration: boolean }): protocol.ReferenceParams {\n    return {\n      textDocument: asTextDocumentIdentifier(textDocument),\n      position,\n      context: { includeDeclaration: options.includeDeclaration }\n    }\n  }\n\n  function asDocumentSymbolParams(textDocument: TextDocument): protocol.DocumentSymbolParams {\n    return {\n      textDocument: asTextDocumentIdentifier(textDocument)\n    }\n  }\n\n  function asCodeLensParams(textDocument: TextDocument): protocol.CodeLensParams {\n    return {\n      textDocument: asTextDocumentIdentifier(textDocument)\n    }\n  }\n\n  function asDocumentLinkParams(textDocument: TextDocument): protocol.DocumentLinkParams {\n    return {\n      textDocument: asTextDocumentIdentifier(textDocument)\n    }\n  }\n\n  return {\n    asUri,\n    asTextDocumentItem,\n    asTextDocumentIdentifier,\n    asVersionedTextDocumentIdentifier,\n    asOpenTextDocumentParams,\n    asChangeTextDocumentParams,\n    asFullChangeTextDocumentParams,\n    asCloseTextDocumentParams,\n    asSaveTextDocumentParams,\n    asWillSaveTextDocumentParams,\n    asDidCreateFilesParams,\n    asDidRenameFilesParams,\n    asDidDeleteFilesParams,\n    asWillCreateFilesParams,\n    asWillRenameFilesParams,\n    asWillDeleteFilesParams,\n    asTextDocumentPositionParams,\n    asCompletionParams,\n    asSignatureHelpParams,\n    asReferenceParams,\n    asDocumentSymbolParams,\n    asCodeLensParams,\n    asDocumentLinkParams,\n  }\n}\n"
  },
  {
    "path": "src/language-client/utils/errorHandler.ts",
    "content": "'use strict'\nimport type { InitializeError, Message, ResponseError } from 'vscode-languageserver-protocol'\nimport { OutputChannel } from '../../types'\n\n/**\n * An action to be performed when the connection to a server got closed.\n */\nexport enum CloseAction {\n  /**\n   * Don't restart the server. The connection stays closed.\n   */\n  DoNotRestart = 1,\n  /**\n   * Restart the server.\n   */\n  Restart = 2\n}\n\nexport interface CloseHandlerResult {\n  /**\n   * The action to take.\n   */\n  action: CloseAction\n\n  /**\n   * An optional message to be presented to the user.\n   */\n  message?: string\n\n  /**\n   * If set to true the client assumes that the corresponding\n   * close handler has presented an appropriate message to the\n   * user and the message will only be log to the client's\n   * output channel.\n   */\n  handled?: boolean\n}\n\n/**\n * An action to be performed when the connection is producing errors.\n */\nexport enum ErrorAction {\n  /**\n   * Continue running the server.\n   */\n  Continue = 1,\n  /**\n   * Shutdown the server.\n   */\n  Shutdown = 2\n}\n\nexport interface ErrorHandlerResult {\n  /**\n   * The action to take.\n   */\n  action: ErrorAction\n\n  /**\n   * An optional message to be presented to the user.\n   */\n  message?: string\n\n  /**\n   * If set to true the client assumes that the corresponding\n   * error handler has presented an appropriate message to the\n   * user and the message will only be log to the client's\n   * output channel.\n   */\n  handled?: boolean\n}\n\n/**\n * A pluggable error handler that is invoked when the connection is either\n * producing errors or got closed.\n */\nexport interface ErrorHandler {\n  /**\n   * An error has occurred while writing or reading from the connection.\n   * @param error - the error received\n   * @param message - the message to be delivered to the server if know.\n   * @param count - a count indicating how often an error is received. Will\n   * be reset if a message got successfully send or received.\n   */\n  error(error: Error, message: Message | undefined, count: number | undefined): ErrorAction | ErrorHandlerResult | Promise<ErrorHandlerResult>\n\n  /**\n   * The connection to the server got closed.\n   */\n  closed(): CloseHandlerResult | Promise<CloseHandlerResult> | CloseAction\n}\n\nexport function toCloseHandlerResult(result: CloseHandlerResult | CloseAction): CloseHandlerResult {\n  if (typeof result === 'number') return { action: result }\n  return result\n}\n\nexport interface InitializationFailedHandler {\n  (error: ResponseError<InitializeError> | Error | any): boolean\n}\n\nexport class DefaultErrorHandler implements ErrorHandler {\n  private readonly restarts: number[]\n  public milliseconds = 3 * 60 * 1000\n\n  constructor(private name: string, private maxRestartCount: number, private outputChannel?: OutputChannel) {\n    this.restarts = []\n  }\n\n  public error(_error: Error, _message: Message, count: number): ErrorHandlerResult {\n    if (count && count <= 3) {\n      return { action: ErrorAction.Continue }\n    }\n    return { action: ErrorAction.Shutdown }\n  }\n\n  public closed(): CloseHandlerResult {\n    this.restarts.push(Date.now())\n    if (this.restarts.length < this.maxRestartCount) {\n      return { action: CloseAction.Restart }\n    } else {\n      let diff = this.restarts[this.restarts.length - 1] - this.restarts[0]\n      if (diff <= this.milliseconds) {\n        if (this.outputChannel) this.outputChannel.appendLine(`The server crashed ${this.maxRestartCount + 1} times in the last 3 minutes. The server will not be restarted.`)\n        return {\n          action: CloseAction.DoNotRestart,\n          message: `The \"${this.name}\" server crashed ${this.maxRestartCount + 1} times in the last 3 minutes. The server will not be restarted.`\n        }\n      } else {\n        this.restarts.shift()\n        return { action: CloseAction.Restart }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/language-client/utils/index.ts",
    "content": "import type { Disposable, MessageReader, MessageSignature, MessageWriter } from 'vscode-languageserver-protocol'\nimport { NotificationType, NotificationType0, NotificationType1, NotificationType2, NotificationType3, NotificationType4, NotificationType5, NotificationType6, NotificationType7, NotificationType8, NotificationType9, ParameterStructures, PipeTransport, RequestType, RequestType0, RequestType1, RequestType2, RequestType3, RequestType4, RequestType5, RequestType6, RequestType7, RequestType8, RequestType9, SocketMessageReader, SocketMessageWriter, SocketTransport } from 'vscode-languageserver-protocol/node'\nimport * as Is from '../../util/is'\nimport { inspect, net } from '../../util/node'\nimport { ResponseError } from '../../util/protocol'\n\nconst requestTypes = [\n  RequestType,\n  RequestType0,\n]\n\nconst notificationTypes = [\n  NotificationType,\n  NotificationType0,\n]\n\nexport function isValidRequestType(type: any): type is string | MessageSignature {\n  if (typeof type == 'string') return true\n  for (let clz of requestTypes) {\n    if (type instanceof clz) {\n      return true\n    }\n  }\n  return false\n}\n\nexport function isValidNotificationType(type: any): type is string | MessageSignature {\n  if (typeof type == 'string') return true\n  for (let clz of notificationTypes) {\n    if (type instanceof clz) {\n      return true\n    }\n  }\n  return false\n}\n\nexport function getLocale(): string {\n  const lang = process.env.LANG\n  if (!lang) return 'en'\n  return lang.split('.')[0]\n}\n\nexport function toMethod(type: string | MessageSignature): string {\n  return Is.string(type) ? type : type.method\n}\n\nexport function currentTimeStamp(): string {\n  return new Date().toLocaleTimeString()\n}\n\nexport function getTracePrefix(data: any): string {\n  if (data.isLSPMessage && data.type) {\n    return `[LSP - ${currentTimeStamp()}] `\n  }\n  return `[Trace - ${currentTimeStamp()}] `\n}\n\nexport function getParameterStructures(kind: string): ParameterStructures {\n  switch (kind) {\n    case 'auto':\n      return ParameterStructures.auto\n    case 'byPosition':\n      return ParameterStructures.byPosition\n    case 'byName':\n      return ParameterStructures.byName\n    default:\n      return ParameterStructures.auto\n  }\n}\n\n// The extension may use old version vscode-languageserver-protocol, and vscode-json-rpc checks the instanceof\nexport function fixRequestType(type: { method: string, numberOfParams?: number } | string, params: any[]): MessageSignature | string {\n  if (isValidRequestType(type)) return type\n  let n = typeof type.numberOfParams === 'number' ? type.numberOfParams : params.length\n  switch (n) {\n    case 0:\n      return new RequestType0(type.method)\n    case 1:\n      if (type['parameterStructures'] != null) {\n        return new RequestType1(type.method, getParameterStructures(type['parameterStructures'].toString()))\n      }\n      return new RequestType1(type.method)\n    case 2:\n      return new RequestType2(type.method)\n    case 3:\n      return new RequestType3(type.method)\n    case 4:\n      return new RequestType4(type.method)\n    case 5:\n      return new RequestType5(type.method)\n    case 6:\n      return new RequestType6(type.method)\n    case 7:\n      return new RequestType7(type.method)\n    case 8:\n      return new RequestType8(type.method)\n    case 9:\n      return new RequestType9(type.method)\n    default:\n      return new RequestType(type.method)\n  }\n}\n\n// The extension may use old version vscode-languageserver-protocol, and vscode-json-rpc checks the instanceof\nexport function fixNotificationType(type: { method: string, numberOfParams?: number } | string, params: any[]): MessageSignature | string {\n  if (isValidNotificationType(type)) return type\n  let n = typeof type.numberOfParams === 'number' ? type.numberOfParams : params.length\n  switch (n) {\n    case 0:\n      return new NotificationType0(type.method)\n    case 1:\n      if (type['parameterStructures'] != null) {\n        return new NotificationType1(type.method, getParameterStructures(type['parameterStructures'].toString()))\n      }\n      return new NotificationType1(type.method)\n    case 2:\n      return new NotificationType2(type.method)\n    case 3:\n      return new NotificationType3(type.method)\n    case 4:\n      return new NotificationType4(type.method)\n    case 5:\n      return new NotificationType5(type.method)\n    case 6:\n      return new NotificationType6(type.method)\n    case 7:\n      return new NotificationType7(type.method)\n    case 8:\n      return new NotificationType8(type.method)\n    case 9:\n      return new NotificationType9(type.method)\n    default:\n      return new NotificationType(type.method)\n  }\n}\n\nexport function data2String(data: any, color = false): string {\n  if (data instanceof ResponseError) {\n    const responseError = data as ResponseError<any>\n    return `  Message: ${responseError.message}\\n  Code: ${responseError.code\n      } ${responseError.data ? '\\n' + responseError.data.toString() : ''}`\n  }\n  if (data instanceof Error) {\n    if (Is.string(data.stack)) {\n      return data.stack\n    }\n    return (data as Error).message\n  }\n  if (Is.string(data)) {\n    return data\n  }\n  return inspect(data, false, null, color)\n}\n\nexport function parseTraceData(data: any): string {\n  if (typeof data !== 'string') return data2String(data)\n  let prefixes = ['Params: ', 'Result: ']\n  for (let prefix of prefixes) {\n    if (data.startsWith(prefix)) {\n      try {\n        let obj = JSON.parse(data.slice(prefix.length))\n        return prefix + data2String(obj, true)\n      } catch (_e) {\n        // ignore\n        return data\n      }\n    }\n  }\n  return data\n}\n\ntype MessageBufferEncoding = 'ascii' | 'utf-8'\n\nexport function createClientPipeTransport(pipeName: string, encoding: MessageBufferEncoding = 'utf-8'): Promise<PipeTransport & Disposable> {\n  let connectResolve: (value: [MessageReader, MessageWriter]) => void\n  const connected = new Promise<[MessageReader, MessageWriter]>((resolve, _reject) => {\n    connectResolve = resolve\n  })\n  return new Promise<PipeTransport & Disposable>((resolve, reject) => {\n    const server = net.createServer(socket => {\n      server.close()\n      connectResolve([\n        new SocketMessageReader(socket, encoding),\n        new SocketMessageWriter(socket, encoding)\n      ])\n    })\n    server.on('error', reject)\n    server.listen(pipeName, () => {\n      server.removeListener('error', reject)\n      resolve({\n        onConnected: () => { return connected },\n        dispose: () => {\n          server.close()\n        }\n      })\n    })\n  })\n}\n\nexport function createClientSocketTransport(port: number, encoding: MessageBufferEncoding = 'utf-8'): Promise<SocketTransport & Disposable> {\n  let connectResolve: (value: [MessageReader, MessageWriter]) => void\n  const connected = new Promise<[MessageReader, MessageWriter]>((resolve, _reject) => {\n    connectResolve = resolve\n  })\n  return new Promise<SocketTransport & Disposable>((resolve, reject) => {\n    const server = net.createServer(socket => {\n      server.close()\n      connectResolve([\n        new SocketMessageReader(socket, encoding),\n        new SocketMessageWriter(socket, encoding)\n      ])\n    })\n    server.on('error', reject)\n    server.listen(port, '127.0.0.1', () => {\n      server.removeListener('error', reject)\n      resolve({\n        onConnected: () => { return connected },\n        dispose: () => {\n          server.close()\n        }\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "src/language-client/utils/logger.ts",
    "content": "'use strict'\nimport type { Logger } from 'vscode-languageserver-protocol'\nimport { createLogger } from '../../logger'\nconst logger = createLogger('language-client')\n\nexport class ConsoleLogger implements Logger {\n  public error(message: string): void {\n    logger.error(message)\n  }\n  public warn(message: string): void {\n    logger.warn(message)\n  }\n  public info(message: string): void {\n    logger.info(message)\n  }\n  public log(message: string): void {\n    logger.log(message)\n  }\n}\n\nexport class NullLogger implements Logger {\n  public error(_message: string): void {\n  }\n  public warn(_message: string): void {\n  }\n  public info(_message: string): void {\n  }\n  public log(_message: string): void {\n  }\n}\n"
  },
  {
    "path": "src/language-client/utils/uuid.ts",
    "content": "'use strict'\nimport { v4 as uuidv4 } from 'uuid'\n\nexport function generateUuid(): string {\n  return uuidv4()\n}\n"
  },
  {
    "path": "src/language-client/workspaceFolders.ts",
    "content": "'use strict'\nimport type { CancellationToken, ClientCapabilities, DidChangeWorkspaceFoldersParams, Disposable, InitializeParams, RegistrationType, ServerCapabilities, WorkspaceFolder, WorkspaceFoldersChangeEvent } from 'vscode-languageserver-protocol'\nimport { URI } from 'vscode-uri'\nimport { defaultValue } from '../util'\nimport { isFalsyOrEmpty } from '../util/array'\nimport { sameFile } from '../util/fs'\nimport { DidChangeWorkspaceFoldersNotification, WorkspaceFoldersRequest } from '../util/protocol'\nimport workspace from '../workspace'\nimport { DynamicFeature, FeatureClient, FeatureState, NextSignature, RegistrationData } from './features'\nimport * as UUID from './utils/uuid'\n\nfunction access<T, K extends keyof T>(target: T | undefined, key: K): T[K] | undefined {\n  if (target === void 0) {\n    return undefined\n  }\n  return target[key]\n}\n\nfunction arrayDiff<T>(left: ReadonlyArray<T>, right: ReadonlyArray<T>): T[] {\n  return left.filter(element => !right.includes(element))\n}\n\nexport interface WorkspaceFolderMiddleware {\n  workspaceFolders?: WorkspaceFoldersRequest.MiddlewareSignature\n  didChangeWorkspaceFolders?: NextSignature<WorkspaceFoldersChangeEvent, Promise<void>>\n}\n\ninterface WorkspaceFolderWorkspaceMiddleware {\n  workspace?: WorkspaceFolderMiddleware\n}\n\nexport interface $WorkspaceOptions {\n  ignoredRootPaths?: string[]\n}\n\nexport class WorkspaceFoldersFeature implements DynamicFeature<void> {\n\n  private _listeners: Map<string, Disposable> = new Map<string, Disposable>()\n  private _initialFolders: ReadonlyArray<WorkspaceFolder> | undefined\n\n  constructor(private _client: FeatureClient<WorkspaceFolderWorkspaceMiddleware, $WorkspaceOptions>) {\n  }\n\n  public getState(): FeatureState {\n    return { kind: 'workspace', id: this.registrationType.method, registrations: this._listeners.size > 0 }\n  }\n\n  public get registrationType(): RegistrationType<void> {\n    return DidChangeWorkspaceFoldersNotification.type\n  }\n\n  public getValidWorkspaceFolders(): WorkspaceFolder[] | undefined {\n    let { workspaceFolders } = workspace\n    let ignoredRootPaths = this._client.clientOptions.ignoredRootPaths\n    let arr = isFalsyOrEmpty(ignoredRootPaths) ? workspaceFolders.slice(0) : workspaceFolders.filter(o => {\n      let fsPath = URI.parse(o.uri).fsPath\n      return ignoredRootPaths.every(p => !sameFile(p, fsPath))\n    })\n    return arr.length > 0 ? arr : undefined\n  }\n\n  public fillInitializeParams(params: InitializeParams): void {\n    const folders = this.getValidWorkspaceFolders()\n    this.initializeWithFolders(folders)\n    if (folders == null) {\n      params.workspaceFolders = null\n    } else {\n      params.workspaceFolders = folders.map(folder => this.asProtocol(folder))\n    }\n  }\n\n  protected initializeWithFolders(currentWorkspaceFolders: ReadonlyArray<WorkspaceFolder> | undefined) {\n    this._initialFolders = currentWorkspaceFolders\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    capabilities.workspace = defaultValue(capabilities.workspace, {})\n    capabilities.workspace.workspaceFolders = true\n  }\n\n  public initialize(capabilities: ServerCapabilities): void {\n    let client = this._client\n    client.onRequest(WorkspaceFoldersRequest.type, (token: CancellationToken) => {\n      let workspaceFolders: WorkspaceFoldersRequest.HandlerSignature = () => {\n        let folders = this.getValidWorkspaceFolders()\n        if (folders == null) {\n          return null\n        }\n        return folders.map(folder => this.asProtocol(folder))\n      }\n      const middleware = client.middleware.workspace\n      return middleware && middleware.workspaceFolders\n        ? middleware.workspaceFolders(token, workspaceFolders)\n        : workspaceFolders(token)\n    })\n    const value = access(access(access(capabilities, 'workspace'), 'workspaceFolders'), 'changeNotifications')\n    let id: string | undefined\n    if (typeof value === 'string') {\n      id = value\n    } else if (value) {\n      id = UUID.generateUuid()\n    }\n    if (id) {\n      this.register({ id, registerOptions: undefined })\n    }\n  }\n\n  protected sendInitialEvent(currentWorkspaceFolders: ReadonlyArray<WorkspaceFolder> | undefined): void {\n    let promise: Promise<void> | undefined\n    if (this._initialFolders && currentWorkspaceFolders) {\n      const removed: WorkspaceFolder[] = arrayDiff(this._initialFolders, currentWorkspaceFolders)\n      const added: WorkspaceFolder[] = arrayDiff(currentWorkspaceFolders, this._initialFolders)\n      if (added.length > 0 || removed.length > 0) {\n        promise = this.doSendEvent(added, removed)\n      }\n    } else if (this._initialFolders) {\n      promise = this.doSendEvent([], this._initialFolders)\n    } else if (currentWorkspaceFolders) {\n      promise = this.doSendEvent(currentWorkspaceFolders, [])\n    }\n    if (promise) {\n      promise.catch(this.onNotificationError.bind(this))\n    }\n  }\n\n  private onNotificationError(error: any): void {\n    this._client.error(`Sending notification ${DidChangeWorkspaceFoldersNotification.type.method} failed`, error)\n  }\n\n  private doSendEvent(addedFolders: ReadonlyArray<WorkspaceFolder>, removedFolders: ReadonlyArray<WorkspaceFolder>): Promise<void> {\n    let params: DidChangeWorkspaceFoldersParams = {\n      event: {\n        added: addedFolders.map(folder => this.asProtocol(folder)),\n        removed: removedFolders.map(folder => this.asProtocol(folder))\n      }\n    }\n    return this._client.sendNotification(DidChangeWorkspaceFoldersNotification.type, params)\n  }\n\n  public register(data: RegistrationData<undefined>): void {\n    let id = data.id\n    let client = this._client\n    if (this._listeners.size == 0) {\n      let disposable = workspace.onDidChangeWorkspaceFolders(event => {\n        let didChangeWorkspaceFolders = (e: WorkspaceFoldersChangeEvent): Promise<void> => {\n          return this.doSendEvent(e.added, e.removed)\n        }\n        let middleware = client.middleware.workspace\n        const promise = middleware && middleware.didChangeWorkspaceFolders\n          ? middleware.didChangeWorkspaceFolders(event, didChangeWorkspaceFolders)\n          : didChangeWorkspaceFolders(event)\n        if (promise) {\n          promise.catch(this.onNotificationError.bind(this))\n        }\n      })\n      this._listeners.set(id, disposable)\n      let workspaceFolders = this.getValidWorkspaceFolders()\n      this.sendInitialEvent(workspaceFolders)\n    }\n  }\n\n  public unregister(id: string): void {\n    const disposable = this._listeners.get(id)\n    if (disposable === void 0) {\n      return\n    }\n    this._listeners.delete(id)\n    disposable.dispose()\n  }\n\n  public dispose(): void {\n    for (let disposable of this._listeners.values()) {\n      disposable.dispose()\n    }\n    this._listeners.clear()\n  }\n\n  private asProtocol(workspaceFolder: WorkspaceFolder): WorkspaceFolder {\n    return { uri: this._client.code2ProtocolConverter.asUri(URI.parse(workspaceFolder.uri)), name: workspaceFolder.name }\n  }\n}\n"
  },
  {
    "path": "src/language-client/workspaceSymbol.ts",
    "content": "'use strict'\nimport type { CancellationToken, ClientCapabilities, Disposable, DocumentSelector, RegistrationType, ServerCapabilities, SymbolInformation, WorkspaceSymbol, WorkspaceSymbolRegistrationOptions } from \"vscode-languageserver-protocol\"\nimport languages from \"../languages\"\nimport { ProviderResult, WorkspaceSymbolProvider } from \"../provider\"\nimport { WorkspaceSymbolRequest, WorkspaceSymbolResolveRequest } from '../util/protocol'\nimport { SupportedSymbolKinds, SupportedSymbolTags } from './documentSymbol'\nimport { BaseFeature, DynamicFeature, ensure, FeatureClient, FeatureState, RegistrationData } from './features'\nimport * as UUID from './utils/uuid'\n\nexport interface ProvideWorkspaceSymbolsSignature {\n  (this: void, query: string, token: CancellationToken): ProviderResult<SymbolInformation[]>\n}\n\nexport interface ResolveWorkspaceSymbolSignature {\n  (this: void, item: WorkspaceSymbol, token: CancellationToken): ProviderResult<SymbolInformation>\n}\n\nexport interface WorkspaceSymbolMiddleware {\n  provideWorkspaceSymbols?: (this: void, query: string, token: CancellationToken, next: ProvideWorkspaceSymbolsSignature) => ProviderResult<SymbolInformation[]>\n  resolveWorkspaceSymbol?: (this: void, item: WorkspaceSymbol, token: CancellationToken, next: ResolveWorkspaceSymbolSignature) => ProviderResult<WorkspaceSymbol>\n}\n\ninterface WorkspaceFeatureRegistration<PR> {\n  disposable: Disposable\n  provider: PR\n}\n\nexport interface WorkspaceProviderFeature<PR> {\n  getProviders(): PR[] | undefined\n}\n\nabstract class WorkspaceFeature<RO, PR, M> extends BaseFeature<M, object> implements DynamicFeature<RO> {\n  protected _registrations: Map<string, WorkspaceFeatureRegistration<PR>> = new Map()\n\n  constructor(_client: FeatureClient<M>, private _registrationType: RegistrationType<RO>) {\n    super(_client)\n  }\n\n  public getState(): FeatureState {\n    const registrations = this._registrations.size > 0\n    return { kind: 'workspace', id: this._registrationType.method, registrations }\n  }\n\n  public get registrationType(): RegistrationType<RO> {\n    return this._registrationType\n  }\n\n  public abstract fillClientCapabilities(capabilities: ClientCapabilities): void\n\n  public abstract initialize(capabilities: ServerCapabilities, documentSelector: DocumentSelector | undefined): void\n\n  public register(data: RegistrationData<RO>): void {\n    const registration = this.registerLanguageProvider(data.registerOptions)\n    this._registrations.set(data.id, { disposable: registration[0], provider: registration[1] })\n  }\n\n  protected abstract registerLanguageProvider(options: RO): [Disposable, PR]\n\n  public unregister(id: string): void {\n    const registration = this._registrations.get(id)\n    if (registration) {\n      this._registrations.delete(id)\n      registration.disposable.dispose()\n    }\n  }\n\n  public dispose(): void {\n    this._registrations.forEach(value => {\n      value.disposable.dispose()\n    })\n    this._registrations.clear()\n  }\n\n  public getProviders(): PR[] {\n    const result: PR[] = []\n    for (const registration of this._registrations.values()) {\n      result.push(registration.provider)\n    }\n    return result\n  }\n}\n\nexport class WorkspaceSymbolFeature extends WorkspaceFeature<WorkspaceSymbolRegistrationOptions, WorkspaceSymbolProvider, WorkspaceSymbolMiddleware> {\n  constructor(client: FeatureClient<WorkspaceSymbolMiddleware>) {\n    super(client, WorkspaceSymbolRequest.type)\n  }\n\n  public fillClientCapabilities(capabilities: ClientCapabilities): void {\n    let symbolCapabilities = ensure(ensure(capabilities, 'workspace')!, 'symbol')!\n    symbolCapabilities.dynamicRegistration = true\n    symbolCapabilities.symbolKind = {\n      valueSet: SupportedSymbolKinds\n    }\n    symbolCapabilities.tagSupport = {\n      valueSet: SupportedSymbolTags\n    }\n    symbolCapabilities.resolveSupport = { properties: ['location.range'] }\n  }\n\n  public initialize(capabilities: ServerCapabilities): void {\n    if (!capabilities.workspaceSymbolProvider) {\n      return\n    }\n    this.register({\n      id: UUID.generateUuid(),\n      registerOptions: capabilities.workspaceSymbolProvider === true ? { workDoneProgress: false } : capabilities.workspaceSymbolProvider\n    })\n  }\n\n  protected registerLanguageProvider(options: WorkspaceSymbolRegistrationOptions): [Disposable, WorkspaceSymbolProvider] {\n    const provider: WorkspaceSymbolProvider = {\n      provideWorkspaceSymbols: (query, token) => {\n        const client = this._client\n        const provideWorkspaceSymbols: ProvideWorkspaceSymbolsSignature = (query, token) => {\n          return this.sendRequest(WorkspaceSymbolRequest.type, { query }, token) as any\n        }\n        const middleware = client.middleware!\n        return middleware.provideWorkspaceSymbols\n          ? middleware.provideWorkspaceSymbols(query, token, provideWorkspaceSymbols)\n          : provideWorkspaceSymbols(query, token)\n      },\n      resolveWorkspaceSymbol: options.resolveProvider === true\n        ? (item, token) => {\n          const client = this._client\n          const resolveWorkspaceSymbol: ResolveWorkspaceSymbolSignature = (item, token) => {\n            return this.sendRequest(WorkspaceSymbolResolveRequest.type, item, token) as any\n          }\n          const middleware = client.middleware!\n          return middleware.resolveWorkspaceSymbol\n            ? middleware.resolveWorkspaceSymbol(item, token, resolveWorkspaceSymbol)\n            : resolveWorkspaceSymbol(item, token)\n        }\n        : undefined\n    }\n    this._client.attachExtensionName(provider)\n    return [languages.registerWorkspaceSymbolProvider(provider), provider]\n  }\n}\n"
  },
  {
    "path": "src/languages.ts",
    "content": "'use strict'\nimport type { LinkedEditingRanges, SignatureHelpContext } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { CallHierarchyIncomingCall, CallHierarchyItem, CallHierarchyOutgoingCall, CodeAction, CodeActionContext, CodeActionKind, CodeLens, ColorInformation, ColorPresentation, DefinitionLink, DocumentHighlight, DocumentLink, DocumentSymbol, FoldingRange, FormattingOptions, Hover, InlineValue, InlineValueContext, Position, Range, SelectionRange, SemanticTokens, SemanticTokensDelta, SemanticTokensLegend, SignatureHelp, TextEdit, TypeHierarchyItem, WorkspaceEdit, WorkspaceSymbol } from 'vscode-languageserver-types'\nimport type { Sources } from './completion/sources'\nimport DiagnosticCollection from './diagnostic/collection'\nimport diagnosticManager from './diagnostic/manager'\nimport { CallHierarchyProvider, CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentLinkProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSelector, DocumentSemanticTokensProvider, DocumentSymbolProvider, DocumentSymbolProviderMetadata, FoldingContext, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionItemProvider, InlineValuesProvider, LinkedEditingRangeProvider, OnTypeFormattingEditProvider, ReferenceContext, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider, TypeHierarchyProvider, WorkspaceSymbolProvider } from './provider'\nimport CallHierarchyManager from './provider/callHierarchyManager'\nimport CodeActionManager from './provider/codeActionManager'\nimport CodeLensManager from './provider/codeLensManager'\nimport DeclarationManager from './provider/declarationManager'\nimport DefinitionManager from './provider/definitionManager'\nimport DocumentColorManager from './provider/documentColorManager'\nimport DocumentHighlightManager from './provider/documentHighlightManager'\nimport DocumentLinkManager from './provider/documentLinkManager'\nimport DocumentSymbolManager from './provider/documentSymbolManager'\nimport FoldingRangeManager from './provider/foldingRangeManager'\nimport FormatManager from './provider/formatManager'\nimport FormatRangeManager from './provider/formatRangeManager'\nimport HoverManager from './provider/hoverManager'\nimport ImplementationManager from './provider/implementationManager'\nimport InlayHintManger, { InlayHintWithProvider } from './provider/inlayHintManager'\nimport InlineCompletionItemManager, { ExtendedInlineContext } from './provider/inlineCompletionItemManager'\nimport InlineValueManager from './provider/inlineValueManager'\nimport LinkedEditingRangeManager from './provider/linkedEditingRangeManager'\nimport OnTypeFormatManager from './provider/onTypeFormatManager'\nimport ReferenceManager from './provider/referenceManager'\nimport RenameManager from './provider/renameManager'\nimport SelectionRangeManager from './provider/selectionRangeManager'\nimport SemanticTokensManager from './provider/semanticTokensManager'\nimport SemanticTokensRangeManager from './provider/semanticTokensRangeManager'\nimport SignatureManager from './provider/signatureManager'\nimport TypeDefinitionManager from './provider/typeDefinitionManager'\nimport TypeHierarchyManager, { TypeHierarchyItemWithSource } from './provider/typeHierarchyManager'\nimport WorkspaceSymbolManager from './provider/workspaceSymbolsManager'\nimport { LocationWithTarget, TextDocumentMatch } from './types'\nimport { disposeAll, getConditionValue } from './util'\nimport * as Is from './util/is'\nimport { CancellationToken, Disposable, Emitter, Event, InlineCompletionItem } from './util/protocol'\nimport { toText } from './util/string'\n\nconst eventDebounce = getConditionValue(100, 1)\n\ntype withKey<K extends string> = {\n  [k in K]?: Event<void>\n}\n\ninterface Mannger<P, A> {\n  register: (selector: DocumentSelector, provider: P, extra?: A) => Disposable\n}\n\nexport enum ProviderName {\n  FormatOnType = 'formatOnType',\n  Rename = 'rename',\n  OnTypeEdit = 'onTypeEdit',\n  DocumentLink = 'documentLink',\n  DocumentColor = 'documentColor',\n  FoldingRange = 'foldingRange',\n  Format = 'format',\n  CodeAction = 'codeAction',\n  FormatRange = 'formatRange',\n  Hover = 'hover',\n  Signature = 'signature',\n  WorkspaceSymbols = 'workspaceSymbols',\n  DocumentSymbol = 'documentSymbol',\n  DocumentHighlight = 'documentHighlight',\n  Definition = 'definition',\n  Declaration = 'declaration',\n  TypeDefinition = 'typeDefinition',\n  Reference = 'reference',\n  Implementation = 'implementation',\n  CodeLens = 'codeLens',\n  SelectionRange = 'selectionRange',\n  CallHierarchy = 'callHierarchy',\n  SemanticTokens = 'semanticTokens',\n  SemanticTokensRange = 'semanticTokensRange',\n  LinkedEditing = 'linkedEditing',\n  InlayHint = 'inlayHint',\n  InlineValue = 'inlineValue',\n  InlineCompletion = 'inlineCompletion',\n  TypeHierarchy = 'typeHierarchy'\n}\n\nclass Languages {\n  private readonly _onDidSemanticTokensRefresh = new Emitter<DocumentSelector>()\n  private readonly _onDidFoldingRangeRefresh = new Emitter<DocumentSelector>()\n  private readonly _onDidInlayHintRefresh = new Emitter<DocumentSelector>()\n  private readonly _onDidCodeLensRefresh = new Emitter<DocumentSelector>()\n  private readonly _onDidColorsRefresh = new Emitter<DocumentSelector>()\n  private readonly _onDidLinksRefresh = new Emitter<DocumentSelector>()\n  public readonly onDidSemanticTokensRefresh: Event<DocumentSelector> = this._onDidSemanticTokensRefresh.event\n  public readonly onDidFoldingRangeRefresh: Event<DocumentSelector> = this._onDidFoldingRangeRefresh.event\n  public readonly onDidInlayHintRefresh: Event<DocumentSelector> = this._onDidInlayHintRefresh.event\n  public readonly onDidCodeLensRefresh: Event<DocumentSelector> = this._onDidCodeLensRefresh.event\n  public readonly onDidColorsRefresh: Event<DocumentSelector> = this._onDidColorsRefresh.event\n  public readonly onDidLinksRefresh: Event<DocumentSelector> = this._onDidLinksRefresh.event\n  private onTypeFormatManager = new OnTypeFormatManager()\n  private documentLinkManager = new DocumentLinkManager()\n  private documentColorManager = new DocumentColorManager()\n  private foldingRangeManager = new FoldingRangeManager()\n  private renameManager = new RenameManager()\n  private formatManager = new FormatManager()\n  private codeActionManager = new CodeActionManager()\n  private workspaceSymbolsManager = new WorkspaceSymbolManager()\n  private formatRangeManager = new FormatRangeManager()\n  private hoverManager = new HoverManager()\n  private signatureManager = new SignatureManager()\n  private documentSymbolManager = new DocumentSymbolManager()\n  private documentHighlightManager = new DocumentHighlightManager()\n  private definitionManager = new DefinitionManager()\n  private declarationManager = new DeclarationManager()\n  private typeDefinitionManager = new TypeDefinitionManager()\n  private typeHierarchyManager = new TypeHierarchyManager()\n  private referenceManager = new ReferenceManager()\n  private implementationManager = new ImplementationManager()\n  private codeLensManager = new CodeLensManager()\n  private selectionRangeManager = new SelectionRangeManager()\n  private callHierarchyManager = new CallHierarchyManager()\n  private semanticTokensManager = new SemanticTokensManager()\n  private semanticTokensRangeManager = new SemanticTokensRangeManager()\n  private linkedEditingManager = new LinkedEditingRangeManager()\n  private inlayHintManager = new InlayHintManger()\n  public inlineCompletionItemManager = new InlineCompletionItemManager()\n  private inlineValueManager = new InlineValueManager()\n  public readonly registerDocumentRangeFormattingEditProvider: any\n  public readonly registerDocumentFormattingEditProvider: any\n\n  public registerReferenceProvider: (selector: DocumentSelector, provider: ReferenceProvider) => Disposable\n\n  constructor() {\n    this.registerReferenceProvider = this.registerReferencesProvider\n    // same name as VSCode\n    this.registerDocumentRangeFormattingEditProvider = this.registerDocumentRangeFormatProvider\n    this.registerDocumentFormattingEditProvider = this.registerDocumentFormatProvider\n  }\n\n  public hasFormatProvider(doc: TextDocumentMatch): boolean {\n    if (this.formatManager.hasProvider(doc)) {\n      return true\n    }\n    if (this.formatRangeManager.hasProvider(doc)) {\n      return true\n    }\n    return false\n  }\n\n  public registerOnTypeFormattingEditProvider(\n    selector: DocumentSelector,\n    provider: OnTypeFormattingEditProvider,\n    triggerCharacters?: string[]\n  ): Disposable {\n    return this.onTypeFormatManager.register(selector, provider, triggerCharacters)\n  }\n\n  public registerCompletionItemProvider(\n    name: string,\n    shortcut: string,\n    selector: DocumentSelector | string | null,\n    provider: CompletionItemProvider,\n    triggerCharacters: string[] = [],\n    priority?: number,\n    allCommitCharacters?: string[]\n  ): Disposable {\n    selector = Is.string(selector) ? [{ language: selector }] : selector\n    let sources = require('./completion/sources').default as Sources\n    sources.removeSource(name)\n    return sources.createLanguageSource(name, shortcut, selector, provider, triggerCharacters, priority, allCommitCharacters)\n  }\n\n  public registerInlineCompletionItemProvider(selector: DocumentSelector, provider: InlineCompletionItemProvider): Disposable {\n    return this.inlineCompletionItemManager.register(selector, provider)\n  }\n\n  public registerCodeActionProvider(selector: DocumentSelector, provider: CodeActionProvider, clientId: string | undefined, codeActionKinds?: CodeActionKind[]): Disposable {\n    return this.codeActionManager.register(selector, provider, clientId, codeActionKinds)\n  }\n\n  public registerHoverProvider(selector: DocumentSelector, provider: HoverProvider): Disposable {\n    return this.hoverManager.register(selector, provider)\n  }\n\n  public registerSelectionRangeProvider(selector: DocumentSelector, provider: SelectionRangeProvider): Disposable {\n    return this.selectionRangeManager.register(selector, provider)\n  }\n\n  public registerSignatureHelpProvider(\n    selector: DocumentSelector,\n    provider: SignatureHelpProvider,\n    triggerCharacters?: string[]): Disposable {\n    return this.signatureManager.register(selector, provider, triggerCharacters)\n  }\n\n  public registerDocumentSymbolProvider(selector: DocumentSelector, provider: DocumentSymbolProvider, metadata?: DocumentSymbolProviderMetadata): Disposable {\n    if (metadata) provider.meta = metadata\n    return this.documentSymbolManager.register(selector, provider)\n  }\n\n  public registerFoldingRangeProvider(selector: DocumentSelector, provider: FoldingRangeProvider): Disposable {\n    return this.registerProviderWithEvent(selector, provider, 'onDidChangeFoldingRanges', this.foldingRangeManager, this._onDidFoldingRangeRefresh)\n  }\n\n  public registerDocumentHighlightProvider(selector: DocumentSelector, provider: DocumentHighlightProvider): Disposable {\n    return this.documentHighlightManager.register(selector, provider)\n  }\n\n  public registerDocumentLinkProvider(selector: DocumentSelector, provider: DocumentLinkProvider): Disposable {\n    this._onDidLinksRefresh.fire(selector)\n    let disposable = this.documentLinkManager.register(selector, provider)\n    return Disposable.create(() => {\n      disposable.dispose()\n      this._onDidLinksRefresh.fire(selector)\n    })\n  }\n\n  public registerDocumentColorProvider(selector: DocumentSelector, provider: DocumentColorProvider): Disposable {\n    this._onDidColorsRefresh.fire(selector)\n    let disposable = this.documentColorManager.register(selector, provider)\n    return Disposable.create(() => {\n      disposable.dispose()\n      this._onDidColorsRefresh.fire(selector)\n    })\n  }\n\n  public registerDefinitionProvider(selector: DocumentSelector, provider: DefinitionProvider): Disposable {\n    return this.definitionManager.register(selector, provider)\n  }\n\n  public registerDeclarationProvider(selector: DocumentSelector, provider: DeclarationProvider): Disposable {\n    return this.declarationManager.register(selector, provider)\n  }\n\n  public registerTypeDefinitionProvider(selector: DocumentSelector, provider: TypeDefinitionProvider): Disposable {\n    return this.typeDefinitionManager.register(selector, provider)\n  }\n\n  public registerTypeHierarchyProvider(selector: DocumentSelector, provider: TypeHierarchyProvider): Disposable {\n    return this.typeHierarchyManager.register(selector, provider)\n  }\n\n  public registerImplementationProvider(selector: DocumentSelector, provider: ImplementationProvider): Disposable {\n    return this.implementationManager.register(selector, provider)\n  }\n\n  public registerReferencesProvider(selector: DocumentSelector, provider: ReferenceProvider): Disposable {\n    return this.referenceManager.register(selector, provider)\n  }\n\n  public registerRenameProvider(selector: DocumentSelector, provider: RenameProvider): Disposable {\n    return this.renameManager.register(selector, provider)\n  }\n\n  public registerWorkspaceSymbolProvider(provider: WorkspaceSymbolProvider): Disposable {\n    if (arguments.length > 1 && Is.func(arguments[1].provideWorkspaceSymbols)) {\n      provider = arguments[1]\n    }\n    return this.workspaceSymbolsManager.register(provider)\n  }\n\n  public registerDocumentFormatProvider(selector: DocumentSelector, provider: DocumentFormattingEditProvider, priority = 0): Disposable {\n    return this.formatManager.register(selector, provider, priority)\n  }\n\n  public registerDocumentRangeFormatProvider(selector: DocumentSelector, provider: DocumentRangeFormattingEditProvider, priority = 0): Disposable {\n    return this.formatRangeManager.register(selector, provider, priority)\n  }\n\n  public registerCallHierarchyProvider(selector: DocumentSelector, provider: CallHierarchyProvider): Disposable {\n    return this.callHierarchyManager.register(selector, provider)\n  }\n\n  public registerCodeLensProvider(selector: DocumentSelector, provider: CodeLensProvider): Disposable {\n    return this.registerProviderWithEvent(selector, provider, 'onDidChangeCodeLenses', this.codeLensManager, this._onDidCodeLensRefresh)\n  }\n\n  public registerDocumentSemanticTokensProvider(selector: DocumentSelector, provider: DocumentSemanticTokensProvider, legend: SemanticTokensLegend): Disposable {\n    return this.registerProviderWithEvent(selector, provider, 'onDidChangeSemanticTokens', this.semanticTokensManager, this._onDidSemanticTokensRefresh, legend)\n  }\n\n  public registerDocumentRangeSemanticTokensProvider(selector: DocumentSelector, provider: DocumentRangeSemanticTokensProvider, legend: SemanticTokensLegend): Disposable {\n    let disposable: Disposable | undefined\n    let timer = setTimeout(() => {\n      disposable = this.semanticTokensRangeManager.register(selector, provider, legend)\n      this._onDidSemanticTokensRefresh.fire(selector)\n    }, eventDebounce)\n    return Disposable.create(() => {\n      clearTimeout(timer)\n      if (disposable) {\n        disposable.dispose()\n        this._onDidSemanticTokensRefresh.fire(selector)\n      }\n    })\n  }\n\n  public registerInlayHintsProvider(selector: DocumentSelector, provider: InlayHintsProvider): Disposable {\n    return this.registerProviderWithEvent(selector, provider, 'onDidChangeInlayHints', this.inlayHintManager, this._onDidInlayHintRefresh)\n  }\n\n  public registerInlineValuesProvider(selector: DocumentSelector, provider: InlineValuesProvider): Disposable {\n    // TODO onDidChangeInlineValues\n    return this.inlineValueManager.register(selector, provider)\n  }\n\n  public registerLinkedEditingRangeProvider(selector: DocumentSelector, provider: LinkedEditingRangeProvider): Disposable {\n    return this.linkedEditingManager.register(selector, provider)\n  }\n\n  public shouldTriggerSignatureHelp(document: TextDocument, triggerCharacter: string): boolean {\n    return this.signatureManager.shouldTrigger(document, triggerCharacter)\n  }\n\n  public async getHover(document: TextDocument, position: Position, token: CancellationToken): Promise<Hover[]> {\n    return await this.hoverManager.provideHover(document, position, token)\n  }\n\n  public async getSignatureHelp(document: TextDocument, position: Position, token: CancellationToken, context: SignatureHelpContext): Promise<SignatureHelp> {\n    return await this.signatureManager.provideSignatureHelp(document, position, token, context)\n  }\n\n  public async getDefinition(document: TextDocument, position: Position, token: CancellationToken): Promise<LocationWithTarget[]> {\n    return await this.definitionManager.provideDefinition(document, position, token)\n  }\n\n  public async getDefinitionLinks(document: TextDocument, position: Position, token: CancellationToken): Promise<DefinitionLink[]> {\n    return await this.definitionManager.provideDefinitionLinks(document, position, token)\n  }\n\n  public async getDeclaration(document: TextDocument, position: Position, token: CancellationToken): Promise<LocationWithTarget[]> {\n    return await this.declarationManager.provideDeclaration(document, position, token)\n  }\n\n  public async getTypeDefinition(document: TextDocument, position: Position, token: CancellationToken): Promise<LocationWithTarget[]> {\n    return await this.typeDefinitionManager.provideTypeDefinition(document, position, token)\n  }\n\n  public async getImplementation(document: TextDocument, position: Position, token: CancellationToken): Promise<LocationWithTarget[]> {\n    return await this.implementationManager.provideImplementations(document, position, token)\n  }\n\n  public async getReferences(document: TextDocument, context: ReferenceContext, position: Position, token: CancellationToken): Promise<LocationWithTarget[]> {\n    return await this.referenceManager.provideReferences(document, position, context, token)\n  }\n\n  public async getDocumentSymbol(document: TextDocument, token: CancellationToken): Promise<DocumentSymbol[] | null> {\n    return await this.documentSymbolManager.provideDocumentSymbols(document, token)\n  }\n\n  public getDocumentSymbolMetadata(document: TextDocument): DocumentSymbolProviderMetadata | null {\n    return this.documentSymbolManager.getMetaData(document)\n  }\n\n  public async getSelectionRanges(document: TextDocument, positions: Position[], token): Promise<SelectionRange[] | null> {\n    return await this.selectionRangeManager.provideSelectionRanges(document, positions, token)\n  }\n\n  public async getWorkspaceSymbols(query: string, token: CancellationToken): Promise<WorkspaceSymbol[]> {\n    return await this.workspaceSymbolsManager.provideWorkspaceSymbols(toText(query), token)\n  }\n\n  public async resolveWorkspaceSymbol(symbol: WorkspaceSymbol, token: CancellationToken): Promise<WorkspaceSymbol> {\n    return await this.workspaceSymbolsManager.resolveWorkspaceSymbol(symbol, token)\n  }\n\n  public async prepareRename(document: TextDocument, position: Position, token: CancellationToken): Promise<Range | { range: Range; placeholder: string } | false> {\n    return await this.renameManager.prepareRename(document, position, token)\n  }\n\n  public async provideRenameEdits(document: TextDocument, position: Position, newName: string, token: CancellationToken): Promise<WorkspaceEdit> {\n    return await this.renameManager.provideRenameEdits(document, position, newName, token)\n  }\n\n  public async provideDocumentFormattingEdits(document: TextDocument, options: FormattingOptions, token: CancellationToken): Promise<TextEdit[]> {\n    let hasDocumentFormatter = this.formatManager.hasFormatProvider(document)\n    if (!hasDocumentFormatter) {\n      let hasRangeFormatter = this.formatRangeManager.hasProvider(document)\n      if (!hasRangeFormatter) return null\n      let end = document.positionAt(document.getText().length)\n      let range = Range.create(Position.create(0, 0), end)\n      return await this.provideDocumentRangeFormattingEdits(document, range, options, token)\n    }\n    return await this.formatManager.provideDocumentFormattingEdits(document, options, token)\n  }\n\n  public async provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): Promise<TextEdit[]> {\n    return await this.formatRangeManager.provideDocumentRangeFormattingEdits(document, range, options, token)\n  }\n\n  public async getCodeActions(document: TextDocument, range: Range, context: CodeActionContext, token: CancellationToken): Promise<CodeAction[]> {\n    return await this.codeActionManager.provideCodeActions(document, range, context, token)\n  }\n\n  public async getDocumentHighLight(document: TextDocument, position: Position, token: CancellationToken): Promise<DocumentHighlight[]> {\n    return await this.documentHighlightManager.provideDocumentHighlights(document, position, token)\n  }\n\n  public async getDocumentLinks(document: TextDocument, token: CancellationToken): Promise<DocumentLink[] | null> {\n    return await this.documentLinkManager.provideDocumentLinks(document, token)\n  }\n\n  public async resolveDocumentLink(link: DocumentLink, token: CancellationToken): Promise<DocumentLink> {\n    return await this.documentLinkManager.resolveDocumentLink(link, token)\n  }\n\n  public async provideDocumentColors(document: TextDocument, token: CancellationToken): Promise<ColorInformation[]> {\n    return await this.documentColorManager.provideDocumentColors(document, token)\n  }\n\n  public async provideFoldingRanges(document: TextDocument, context: FoldingContext, token: CancellationToken): Promise<FoldingRange[]> {\n    return await this.foldingRangeManager.provideFoldingRanges(document, context, token)\n  }\n\n  public async provideColorPresentations(color: ColorInformation, document: TextDocument, token: CancellationToken): Promise<ColorPresentation[] | null> {\n    return await this.documentColorManager.provideColorPresentations(color, document, token)\n  }\n\n  public async provideInlineCompletionItems(document: TextDocument, position: Position, context: ExtendedInlineContext, token: CancellationToken): Promise<InlineCompletionItem[]> {\n    return this.inlineCompletionItemManager.provideInlineCompletionItems(document, position, context, token)\n  }\n\n  public async getCodeLens(document: TextDocument, token: CancellationToken): Promise<(CodeLens | null)[]> {\n    return await this.codeLensManager.provideCodeLenses(document, token)\n  }\n\n  public async resolveCodeLens(codeLens: CodeLens, token: CancellationToken): Promise<CodeLens> {\n    return await this.codeLensManager.resolveCodeLens(codeLens, token)\n  }\n\n  public async resolveCodeAction(codeAction: CodeAction, token: CancellationToken): Promise<CodeAction> {\n    return await this.codeActionManager.resolveCodeAction(codeAction, token)\n  }\n\n  public async provideDocumentOnTypeEdits(\n    character: string,\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken\n  ): Promise<TextEdit[] | null> {\n    return this.onTypeFormatManager.onCharacterType(character, document, position, token)\n  }\n\n  public canFormatOnType(character: string, document: TextDocument): boolean {\n    return this.onTypeFormatManager.couldTrigger(document, character) != null\n  }\n\n  public async prepareCallHierarchy(document: TextDocument, position: Position, token: CancellationToken): Promise<CallHierarchyItem | CallHierarchyItem[]> {\n    return this.callHierarchyManager.prepareCallHierarchy(document, position, token)\n  }\n\n  public async provideIncomingCalls(document: TextDocument, item: CallHierarchyItem, token: CancellationToken): Promise<CallHierarchyIncomingCall[]> {\n    return this.callHierarchyManager.provideCallHierarchyIncomingCalls(document, item, token)\n  }\n\n  public async provideOutgoingCalls(document: TextDocument, item: CallHierarchyItem, token: CancellationToken): Promise<CallHierarchyOutgoingCall[]> {\n    return this.callHierarchyManager.provideCallHierarchyOutgoingCalls(document, item, token)\n  }\n\n  public getLegend(document: TextDocument, range?: boolean): SemanticTokensLegend | undefined {\n    if (range) return this.semanticTokensRangeManager.getLegend(document)\n    return this.semanticTokensManager.getLegend(document)\n  }\n\n  public hasSemanticTokensEdits(document: TextDocument): boolean {\n    return this.semanticTokensManager.hasSemanticTokensEdits(document)\n  }\n\n  public async provideDocumentSemanticTokens(document: TextDocument, token: CancellationToken): Promise<SemanticTokens | null> {\n    return this.semanticTokensManager.provideDocumentSemanticTokens(document, token)\n  }\n\n  public async provideDocumentSemanticTokensEdits(document: TextDocument, previousResultId: string, token: CancellationToken): Promise<SemanticTokens | SemanticTokensDelta | null> {\n    return this.semanticTokensManager.provideDocumentSemanticTokensEdits(document, previousResultId, token)\n  }\n\n  public async provideDocumentRangeSemanticTokens(document: TextDocument, range: Range, token: CancellationToken): Promise<SemanticTokens> {\n    return this.semanticTokensRangeManager.provideDocumentRangeSemanticTokens(document, range, token)\n  }\n\n  public async provideInlayHints(document: TextDocument, range: Range, token: CancellationToken): Promise<InlayHintWithProvider[] | null> {\n    return this.inlayHintManager.provideInlayHints(document, range, token)\n  }\n\n  public async resolveInlayHint(hint: InlayHintWithProvider, token: CancellationToken): Promise<InlayHintWithProvider> {\n    return this.inlayHintManager.resolveInlayHint(hint, token)\n  }\n\n  public async provideLinkedEdits(document: TextDocument, position: Position, token: CancellationToken): Promise<LinkedEditingRanges> {\n    return this.linkedEditingManager.provideLinkedEditingRanges(document, position, token)\n  }\n\n  public async provideInlineValues(document: TextDocument, viewPort: Range, context: InlineValueContext, token: CancellationToken): Promise<InlineValue[]> {\n    return this.inlineValueManager.provideInlineValues(document, viewPort, context, token)\n  }\n\n  public async prepareTypeHierarchy(document: TextDocument, position: Position, token: CancellationToken): Promise<TypeHierarchyItem[]> {\n    return this.typeHierarchyManager.prepareTypeHierarchy(document, position, token)\n  }\n\n  public async provideTypeHierarchySupertypes(item: TypeHierarchyItemWithSource, token: CancellationToken): Promise<TypeHierarchyItem[]> {\n    return this.typeHierarchyManager.provideTypeHierarchySupertypes(item, token)\n  }\n\n  public async provideTypeHierarchySubtypes(item: TypeHierarchyItemWithSource, token: CancellationToken): Promise<TypeHierarchyItem[]> {\n    return this.typeHierarchyManager.provideTypeHierarchySubtypes(item, token)\n  }\n\n  public createDiagnosticCollection(owner: string): DiagnosticCollection {\n    return diagnosticManager.create(owner)\n  }\n\n  public registerProviderWithEvent<K extends string, P extends withKey<K>, A>(\n    selector: DocumentSelector,\n    provider: P,\n    key: K,\n    manager: Mannger<P, A>,\n    emitter: Emitter<DocumentSelector>,\n    extra?: A): Disposable {\n    let disposables: Disposable[] = []\n    // Wait the server finish initialize\n    let timer = setTimeout(() => {\n      disposables.push(manager.register(selector, provider, extra))\n      emitter.fire(selector)\n      if (Is.func(provider[key])) {\n        disposables.push(provider[key](() => {\n          emitter.fire(selector)\n        }))\n      }\n    }, eventDebounce)\n    return Disposable.create(() => {\n      clearTimeout(timer)\n      let registered = disposables.length > 0\n      disposeAll(disposables)\n      if (registered) emitter.fire(selector)\n    })\n  }\n\n  public hasProvider(id: ProviderName, document: TextDocumentMatch): boolean {\n    switch (id) {\n      case ProviderName.OnTypeEdit:\n      case ProviderName.FormatOnType:\n        return this.onTypeFormatManager.hasProvider(document)\n      case ProviderName.Rename:\n        return this.renameManager.hasProvider(document)\n      case ProviderName.DocumentLink:\n        return this.documentLinkManager.hasProvider(document)\n      case ProviderName.DocumentColor:\n        return this.documentColorManager.hasProvider(document)\n      case ProviderName.FoldingRange:\n        return this.foldingRangeManager.hasProvider(document)\n      case ProviderName.Format:\n        return this.formatManager.hasProvider(document) || this.formatRangeManager.hasProvider(document)\n      case ProviderName.CodeAction:\n        return this.codeActionManager.hasProvider(document)\n      case ProviderName.WorkspaceSymbols:\n        return this.workspaceSymbolsManager.hasProvider()\n      case ProviderName.FormatRange:\n        return this.formatRangeManager.hasProvider(document)\n      case ProviderName.Hover:\n        return this.hoverManager.hasProvider(document)\n      case ProviderName.Signature:\n        return this.signatureManager.hasProvider(document)\n      case ProviderName.DocumentSymbol:\n        return this.documentSymbolManager.hasProvider(document)\n      case ProviderName.DocumentHighlight:\n        return this.documentHighlightManager.hasProvider(document)\n      case ProviderName.Definition:\n        return this.definitionManager.hasProvider(document)\n      case ProviderName.Declaration:\n        return this.declarationManager.hasProvider(document)\n      case ProviderName.TypeDefinition:\n        return this.typeDefinitionManager.hasProvider(document)\n      case ProviderName.Reference:\n        return this.referenceManager.hasProvider(document)\n      case ProviderName.Implementation:\n        return this.implementationManager.hasProvider(document)\n      case ProviderName.CodeLens:\n        return this.codeLensManager.hasProvider(document)\n      case ProviderName.SelectionRange:\n        return this.selectionRangeManager.hasProvider(document)\n      case ProviderName.CallHierarchy:\n        return this.callHierarchyManager.hasProvider(document)\n      case ProviderName.SemanticTokens:\n        return this.semanticTokensManager.hasProvider(document)\n      case ProviderName.SemanticTokensRange:\n        return this.semanticTokensRangeManager.hasProvider(document)\n      case ProviderName.LinkedEditing:\n        return this.linkedEditingManager.hasProvider(document)\n      case ProviderName.InlayHint:\n        return this.inlayHintManager.hasProvider(document)\n      case ProviderName.InlineCompletion:\n        return this.inlineCompletionItemManager.hasProvider(document)\n      case ProviderName.InlineValue:\n        return this.inlineValueManager.hasProvider(document)\n      case ProviderName.TypeHierarchy:\n        return this.typeHierarchyManager.hasProvider(document)\n      default:\n        return false\n    }\n  }\n}\n\nexport default new Languages()\n"
  },
  {
    "path": "src/list/basic.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { Location, Range } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport { WorkspaceConfiguration } from '../configuration/types'\nimport { ProviderResult } from '../provider'\nimport { LocationWithTarget } from '../types'\nimport { disposeAll } from '../util'\nimport { lineToLocation } from '../util/fs'\nimport { comparePosition, emptyRange } from '../util/position'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { toText } from '../util/string'\nimport workspace from '../workspace'\nimport CommandTask, { CommandTaskOption } from './commandTask'\nimport listConfiguration, { ListConfiguration } from './configuration'\nimport { IList, ListAction, ListArgument, ListContext, ListItem, ListTask, LocationWithLine, MultipleListAction, SingleListAction } from './types'\n\ninterface ActionOptions {\n  persist?: boolean\n  reload?: boolean\n  parallel?: boolean\n  tabPersist?: boolean\n}\n\ninterface ArgumentItem {\n  hasValue: boolean\n  name: string\n}\n\ninterface PreviewConfig {\n  bufnr?: number\n  winid: number\n  position: string\n  hlGroup: string\n  maxHeight: number\n  name?: string\n  splitRight: boolean\n  lnum: number\n  filetype?: string\n  range?: Range\n  scheme?: string\n  targetRange?: Range\n  toplineStyle: string\n  toplineOffset: number\n}\n\nexport interface PreviewOptions {\n  bufname?: string\n  filetype?: string\n  lines: string[]\n  lnum?: number\n  range?: Range\n  sketch?: boolean\n}\n\nexport default abstract class BasicList implements IList, Disposable {\n  public name: string\n  public defaultAction = 'open'\n  public readonly actions: ListAction[] = []\n  public options: ListArgument[] = []\n  protected disposables: Disposable[] = []\n  protected nvim: Neovim\n  private optionMap: Map<string, ArgumentItem>\n  public config: ListConfiguration\n\n  constructor() {\n    this.nvim = workspace.nvim\n    this.config = listConfiguration\n  }\n\n  public get alignColumns(): boolean {\n    return listConfiguration.get('alignColumns', false)\n  }\n\n  protected get floatPreview(): boolean {\n    return listConfiguration.get('floatPreview', false)\n  }\n\n  protected get hlGroup(): string {\n    return listConfiguration.get('previewHighlightGroup', 'Search')\n  }\n\n  protected get previewHeight(): number {\n    return listConfiguration.get('maxPreviewHeight', 12)\n  }\n\n  protected get splitRight(): boolean {\n    return listConfiguration.get('previewSplitRight', false)\n  }\n\n  protected get toplineStyle(): string {\n    return listConfiguration.get('previewToplineStyle', 'offset')\n  }\n\n  protected get toplineOffset(): number {\n    return listConfiguration.get('previewToplineOffset', 3)\n  }\n\n  public parseArguments(args: string[]): { [key: string]: string | boolean } {\n    if (!this.optionMap) {\n      this.optionMap = new Map()\n      for (let opt of this.options) {\n        let parts = opt.name.split(/,\\s*/g).map(s => s.replace(/\\s+.*/g, ''))\n        let name = opt.key ? opt.key : parts[parts.length - 1].replace(/^-+/, '')\n        for (let p of parts) {\n          this.optionMap.set(p, { name, hasValue: opt.hasValue })\n        }\n      }\n    }\n    let res: { [key: string]: string | boolean } = {}\n    for (let i = 0; i < args.length; i++) {\n      let arg = args[i]\n      let def = this.optionMap.get(arg)\n      if (!def) continue\n      let value: string | boolean = true\n      if (def.hasValue) {\n        value = toText(args[i + 1])\n        i = i + 1\n      }\n      res[def.name] = value\n    }\n    return res\n  }\n\n  /**\n   * Get configuration of current list\n   */\n  protected getConfig(): WorkspaceConfiguration {\n    return workspace.getConfiguration(`list.source.${this.name}`)\n  }\n\n  protected addAction(name: string, fn: (item: ListItem, context: ListContext) => ProviderResult<void>, options?: ActionOptions): void {\n    this.createAction(Object.assign({\n      name,\n      execute: fn\n    } as any, options || {}))\n  }\n\n  protected addMultipleAction(name: string, fn: (item: ListItem[], context: ListContext) => ProviderResult<void>, options?: ActionOptions): void {\n    this.createAction(Object.assign({\n      name,\n      multiple: true,\n      execute: fn\n    }, options || {}))\n  }\n\n  protected createCommandTask(opt: CommandTaskOption): CommandTask {\n    return new CommandTask(opt)\n  }\n\n  public addLocationActions(): void {\n    this.createAction({\n      name: 'preview',\n      execute: async (item: ListItem, context: ListContext) => {\n        let loc = await this.convertLocation(item.location)\n        await this.previewLocation(loc, context)\n      }\n    })\n    let { nvim } = this\n    this.createAction({\n      name: 'quickfix',\n      multiple: true,\n      execute: async (items: ListItem[]) => {\n        let quickfixItems = await Promise.all(items.map(item => this.convertLocation(item.location).then(loc => workspace.getQuickfixItem(loc))))\n        await nvim.call('setqflist', [quickfixItems])\n        let openCommand = await nvim.getVar('coc_quickfix_open_command') as string\n        nvim.command(typeof openCommand === 'string' ? openCommand : 'copen', true)\n      }\n    })\n    for (let name of ['open', 'tabe', 'drop', 'vsplit', 'split']) {\n      this.createAction({\n        name,\n        execute: async (item: ListItem, context: ListContext) => {\n          await this.jumpTo(item.location, name == 'open' ? null : name, context)\n        },\n        tabPersist: name === 'open'\n      })\n    }\n  }\n\n  public async convertLocation(location: LocationWithTarget | LocationWithLine | string): Promise<LocationWithTarget> {\n    if (typeof location == 'string') return Location.create(location, Range.create(0, 0, 0, 0))\n    if (Location.is(location)) return location\n    let u = URI.parse(location.uri)\n    if (u.scheme != 'file') return Location.create(location.uri, Range.create(0, 0, 0, 0))\n    return await lineToLocation(u.fsPath, location.line, location.text)\n  }\n\n  public async jumpTo(location: Location | LocationWithLine | string, command?: string, context?: ListContext): Promise<void> {\n    if (command == null && context && context.options.position === 'tab') {\n      command = 'tabe'\n    }\n    if (typeof location == 'string') {\n      await workspace.jumpTo(location, null, command)\n      return\n    }\n    let { range, uri } = await this.convertLocation(location)\n    let position = range.start\n    if (position.line == 0 && position.character == 0 && comparePosition(position, range.end) == 0) {\n      // allow plugin that remember position.\n      position = null\n    }\n    await workspace.jumpTo(uri, position, command)\n  }\n\n  public createAction(action: SingleListAction | MultipleListAction): void {\n    let { name } = action\n    let idx = this.actions.findIndex(o => o.name == name)\n    // allow override\n    if (idx !== -1) this.actions.splice(idx, 1)\n    this.actions.push(action)\n  }\n\n  protected async previewLocation(location: LocationWithTarget, context: ListContext): Promise<void> {\n    let { uri, range } = location\n    let doc = workspace.getDocument(location.uri)\n    let u = URI.parse(uri)\n    let lines = await workspace.documentsManager.getLines(uri)\n    let config: PreviewConfig = {\n      bufnr: doc ? doc.bufnr : undefined,\n      winid: context.window.id,\n      range: emptyRange(range) ? null : range,\n      lnum: range.start.line + 1,\n      name: u.scheme == 'file' ? u.fsPath : uri,\n      filetype: toVimFiletype(doc ? doc.languageId : workspace.documentsManager.getLanguageId(u.fsPath)),\n      position: context.options.position,\n      maxHeight: this.previewHeight,\n      splitRight: this.splitRight,\n      hlGroup: this.hlGroup,\n      scheme: u.scheme,\n      toplineStyle: this.toplineStyle,\n      toplineOffset: this.toplineOffset,\n      targetRange: location.targetRange\n    }\n    await this.openPreview(lines, config)\n  }\n\n  public async preview(options: PreviewOptions, context: ListContext): Promise<void> {\n    let { bufname, filetype, range, lines, lnum } = options\n    let config: PreviewConfig = {\n      winid: context.window.id,\n      lnum: range ? range.start.line + 1 : lnum || 1,\n      filetype,\n      position: context.options.position,\n      maxHeight: this.previewHeight,\n      splitRight: this.splitRight,\n      hlGroup: this.hlGroup,\n      toplineStyle: this.toplineStyle,\n      toplineOffset: this.toplineOffset,\n    }\n    if (bufname) config.name = bufname\n    if (range) config.range = range\n    await this.openPreview(lines, config)\n  }\n\n  private async openPreview(lines: ReadonlyArray<string>, config: PreviewConfig): Promise<void> {\n    let { nvim } = this\n    if (this.floatPreview && config.position !== 'tab') {\n      await nvim.call('coc#list#float_preview', [lines, config])\n    } else {\n      await nvim.call('coc#list#preview', [lines, config])\n    }\n    nvim.command('redraw', true)\n  }\n\n  public abstract loadItems(context: ListContext, token?: CancellationToken): Promise<ListItem[] | ListTask | null | undefined>\n\n  public doHighlight(): void {\n    // noop\n  }\n\n  public dispose(): void {\n    disposeAll(this.disposables)\n  }\n}\n\nexport function toVimFiletype(filetype: string): string {\n  switch (filetype) {\n    case 'latex':\n      // LaTeX (LSP language ID 'latex') has Vim filetype 'tex'\n      return 'tex'\n    default:\n      return filetype\n  }\n}\n"
  },
  {
    "path": "src/list/commandTask.ts",
    "content": "'use strict'\nimport { EventEmitter } from 'events'\nimport { createLogger } from '../logger'\nimport { ListItem, ListTask } from './types'\nimport { disposeAll } from '../util'\nimport { child_process, readline } from '../util/node'\nimport type { Disposable } from '../util/protocol'\nimport workspace from '../workspace'\nconst spawn = child_process.spawn\nconst logger = createLogger('list-commandTask')\n\nexport interface CommandTaskOption {\n  /**\n   * Command to run.\n   */\n  cmd: string\n  /**\n   * Arguments of command.\n   */\n  args: string[]\n  cwd?: string\n  env?: NodeJS.ProcessEnv\n  /**\n   * Runs for each line, return undefined for invalid item.\n   */\n  onLine: (line: string) => ListItem | undefined\n}\n\nexport default class CommandTask extends EventEmitter implements ListTask {\n  private disposables: Disposable[] = []\n  constructor(private opt: CommandTaskOption) {\n    super()\n    this.start()\n  }\n\n  private start(): void {\n    let { cmd, args, cwd, onLine } = this.opt\n    let proc = spawn(cmd, args, { cwd: cwd || workspace.cwd, windowsHide: true, shell: process.platform === 'win32' })\n    this.disposables.push({\n      dispose: () => {\n        proc.kill()\n      }\n    })\n    proc.on('error', e => {\n      this.emit('error', e.message)\n    })\n    proc.stderr.on('data', chunk => {\n      logger.error(`[${cmd} Error]`, chunk.toString('utf8'))\n    })\n    const rl = readline.createInterface(proc.stdout)\n    rl.on('line', line => {\n      let res = onLine(line)\n      if (res) this.emit('data', res)\n    })\n    rl.on('close', () => {\n      this.emit('end')\n    })\n  }\n\n  public dispose(): void {\n    disposeAll(this.disposables)\n  }\n}\n"
  },
  {
    "path": "src/list/configuration.ts",
    "content": "'use strict'\nimport window from '../window'\nimport workspace from '../workspace'\n\nexport const validKeys = [\n  '<esc>',\n  '<space>',\n  '<tab>',\n  '<s-tab>',\n  '<bs>',\n  '<right>',\n  '<left>',\n  '<up>',\n  '<down>',\n  '<home>',\n  '<end>',\n  '<cr>',\n  '<FocusGained>',\n  '<FocusLost>',\n  '<ScrollWheelUp>',\n  '<ScrollWheelDown>',\n  '<LeftMouse>',\n  '<LeftDrag>',\n  '<LeftRelease>',\n  '<2-LeftMouse>',\n  '<C-space>',\n  '<C-_>',\n  '<C-a>',\n  '<C-b>',\n  '<C-c>',\n  '<C-d>',\n  '<C-e>',\n  '<C-f>',\n  '<C-g>',\n  '<C-h>',\n  '<C-i>',\n  '<C-j>',\n  '<C-k>',\n  '<C-l>',\n  '<C-m>',\n  '<C-n>',\n  '<C-o>',\n  '<C-p>',\n  '<C-q>',\n  '<C-r>',\n  '<C-s>',\n  '<C-t>',\n  '<C-u>',\n  '<C-v>',\n  '<C-w>',\n  '<C-x>',\n  '<C-y>',\n  '<C-z>',\n  '<A-a>',\n  '<A-b>',\n  '<A-c>',\n  '<A-d>',\n  '<A-e>',\n  '<A-f>',\n  '<A-g>',\n  '<A-h>',\n  '<A-i>',\n  '<A-j>',\n  '<A-k>',\n  '<A-l>',\n  '<A-m>',\n  '<A-n>',\n  '<A-o>',\n  '<A-p>',\n  '<A-q>',\n  '<A-r>',\n  '<A-s>',\n  '<A-t>',\n  '<A-u>',\n  '<A-v>',\n  '<A-w>',\n  '<A-x>',\n  '<A-y>',\n  '<A-z>',\n  '<A-bs>'\n]\n\nexport class ListConfiguration {\n  public get debounceTime(): number {\n    return this.get<number>('interactiveDebounceTime', 100)\n  }\n\n  public get extendedSearchMode(): boolean {\n    return this.get<boolean>('extendedSearchMode', true)\n  }\n\n  public get smartcase(): boolean {\n    return this.get<boolean>('smartCase', false)\n  }\n\n  public get signOffset(): number {\n    return this.get<number>('signOffset', 900)\n  }\n\n  public get<T>(key: string, defaultValue?: T): T {\n    let configuration = workspace.initialConfiguration\n    return configuration.get<T>('list.' + key, defaultValue)\n  }\n\n  public get previousKey(): string {\n    return this.fixKey(this.get<string>('previousKeymap', '<C-j>'))\n  }\n\n  public get nextKey(): string {\n    return this.fixKey(this.get<string>('nextKeymap', '<C-k>'))\n  }\n\n  public fixKey(key: string): string {\n    if (validKeys.includes(key)) return key\n    let find = validKeys.find(s => s.toLowerCase() == key.toLowerCase())\n    if (find) return find\n    void window.showErrorMessage(`Configured key \"${key}\" not supported.`)\n    return null\n  }\n}\n\nexport default new ListConfiguration()\n"
  },
  {
    "path": "src/list/db.ts",
    "content": "/**\n * First byte tables length,\n * 4 * table_length each table byte length.\n */\nimport { fs, path } from '../util/node'\nimport { createLogger } from '../logger'\nimport { byteLength, byteSlice } from '../util/string'\nimport { dataHome } from '../util/constants'\nconst logger = createLogger('list-db')\n\nconst DB_PATH = path.join(dataHome, 'list_history.dat')\n\n// text, name index, folder index\ntype HistoryItem = [string, number, number]\n\nexport class DataBase {\n  private folders: string[] = []\n  private names: string[] = []\n  private items: HistoryItem[] = []\n  private _changed = false\n  constructor() {\n    try {\n      this.load()\n    } catch (e) {\n      logger.error(`Error on load db`, e)\n    }\n  }\n\n  public get currItems(): ReadonlyArray<HistoryItem> {\n    return this.items\n  }\n\n  public getHistory(name: string, folder: string): string[] {\n    let nameIndex = this.names.indexOf(name)\n    let folderIndex = this.folders.indexOf(folder)\n    if (nameIndex == -1 || folderIndex == -1) return []\n    return this.items.reduce((p, c) => {\n      if (c[1] == nameIndex && c[2] == folderIndex) {\n        p.push(c[0])\n      }\n      return p\n    }, [] as string[])\n  }\n\n  public addItem(name: string, text: string, folder: string): void {\n    let { folders, names } = this\n    if (byteLength(text) > 255) {\n      text = byteSlice(text, 0, 255)\n    }\n    if (!folders.includes(folder)) {\n      folders.push(folder)\n    }\n    if (!names.includes(name)) {\n      names.push(name)\n    }\n    let nameIndex = names.indexOf(name)\n    let folderIndex = folders.indexOf(folder)\n    let idx = this.items.findIndex(o => o[0] == text && o[1] == nameIndex && o[2] == folderIndex)\n    if (idx != -1) this.items.splice(idx, 1)\n    this.items.push([text, nameIndex, folderIndex])\n    this._changed = true\n  }\n\n  public save(): void {\n    let { folders, items, names } = this\n    if (!this._changed) return\n    let bufs = folders.reduce((p, folder) => {\n      p.push(Buffer.from(folder, 'utf8'), Buffer.alloc(1))\n      return p\n    }, [] as Buffer[])\n    let folderBuf = Buffer.concat(bufs)\n    bufs = names.reduce((p, name) => {\n      p.push(Buffer.from(name, 'utf8'), Buffer.alloc(1))\n      return p\n    }, [] as Buffer[])\n    let nameBuf = Buffer.concat(bufs)\n    let buf = Buffer.allocUnsafe(9)\n    buf.writeUInt8(2, 0)\n    buf.writeUInt32BE(folderBuf.byteLength, 1)\n    buf.writeUInt32BE(nameBuf.byteLength, 5)\n    bufs = items.reduce((p, item) => {\n      let b = Buffer.from(item[0], 'utf8')\n      p.push(Buffer.from([b.byteLength]), b, Buffer.from([item[1], item[2]]))\n      return p\n    }, [] as Buffer[])\n    let resultBuf = Buffer.concat([buf, folderBuf, nameBuf, ...bufs])\n    fs.writeFileSync(DB_PATH, resultBuf)\n    this._changed = false\n  }\n\n  public load(): void {\n    if (!fs.existsSync(DB_PATH)) return\n    let buffer = fs.readFileSync(DB_PATH)\n    let folder_length = buffer.readUInt32BE(1)\n    let name_length = buffer.readUInt32BE(5)\n    let folderBuf = buffer.slice(9, 9 + folder_length)\n    let start = 0\n    let folders: string[] = []\n    let names: string[] = []\n    for (let i = 0; i < folderBuf.byteLength; i++) {\n      if (folderBuf[i] === 0) {\n        let text = folderBuf.slice(start, i).toString('utf8')\n        folders.push(text)\n        start = i + 1\n      }\n    }\n    let offset = 9 + folder_length\n    let nameBuf = buffer.slice(offset, offset + name_length)\n    start = 0\n    for (let i = 0; i < nameBuf.byteLength; i++) {\n      if (nameBuf[i] === 0) {\n        let text = nameBuf.slice(start, i).toString('utf8')\n        names.push(text)\n        start = i + 1\n      }\n    }\n    let itemsBuf = buffer.slice(offset + name_length)\n    start = 0\n    let total = itemsBuf.byteLength\n    while (start < total) {\n      let len = itemsBuf.readUInt8(start)\n      let end = start + 1 + len\n      let text = itemsBuf.slice(start + 1, end).toString('utf8')\n      this.items.push([text, itemsBuf.readUInt8(end), itemsBuf.readUInt8(end + 1)])\n      start = end + 2\n    }\n    this.names = names\n    this.folders = folders\n  }\n}\n\nexport default new DataBase()\n"
  },
  {
    "path": "src/list/formatting.ts",
    "content": "'use strict'\nimport { ListItem } from './types'\nimport { path } from '../util/node'\nimport { URI } from 'vscode-uri'\nimport { isParentFolder } from '../util/fs'\nimport { toText } from '../util/string'\n\nexport type PathFormatting = \"full\" | \"short\" | \"filename\" | \"hidden\"\n\nexport interface UnformattedListItem extends Omit<ListItem, 'label'> {\n  label: string[]\n}\n\nexport function fixWidth(str: string, width: number): string {\n  if (str.length > width) {\n    return str.slice(0, width - 1) + '.'\n  }\n  return str + ' '.repeat(width - str.length)\n}\n\nexport function formatUri(uri: string, cwd: string): string {\n  if (!uri.startsWith('file:')) return uri\n  let filepath = URI.parse(uri).fsPath\n  return isParentFolder(cwd, filepath) ? path.relative(cwd, filepath) : filepath\n}\n\nexport function formatListItems(align: boolean, list: UnformattedListItem[]): ListItem[] {\n  if (list.length === 0) {\n    return []\n  }\n\n  let processedList: ListItem[] = []\n  if (align) {\n    const maxWidths = Array(Math.max(...list.map(item => item.label.length))).fill(0)\n    for (let item of list) {\n      for (let i = 0; i < item.label.length; i++) {\n        maxWidths[i] = Math.max(maxWidths[i], (item.label[i] ?? '').length)\n      }\n    }\n    processedList = list\n      .map(item => ({\n        ...item,\n        label: item.label\n          .map((element, idx) => (element ?? '').padEnd(maxWidths[idx]))\n          .join(\"\\t\")\n      }))\n  } else {\n    processedList = list.map(item => ({ ...item, label: item.label.join(\"\\t\") }))\n  }\n  return processedList\n}\n\nexport function formatPath(format: PathFormatting, pathToFormat: string): string {\n  if (format === \"hidden\") {\n    return \"\"\n  } else if (format === \"full\") {\n    return pathToFormat\n  } else if (format === \"short\") {\n    const segments = pathToFormat.split(path.sep)\n    if (segments.length < 2) {\n      return pathToFormat\n    }\n    const shortenedInit = segments\n      .slice(0, segments.length - 2)\n      .filter(seg => seg.length > 0)\n      .map(seg => seg[0])\n    return [...shortenedInit, segments[segments.length - 1]].join(path.sep)\n  } else {\n    const segments = pathToFormat.split(path.sep)\n    return toText(segments[segments.length - 1])\n  }\n}\n"
  },
  {
    "path": "src/list/history.ts",
    "content": "'use strict'\nimport { fs, path } from '../util/node'\nimport { createLogger } from '../logger'\nimport { isFalsyOrEmpty } from '../util/array'\nimport { fuzzyMatch, getCharCodes } from '../util/fuzzy'\nimport { DataBase } from './db'\nimport { toText } from '../util/string'\nconst logger = createLogger('list-history')\n\nexport default class InputHistory {\n  private _index = -1\n  private _filtered: string[] = []\n  private historyInput: string\n\n  constructor(\n    private prompt: { input: string },\n    private name: string,\n    private db: DataBase,\n    private cwd: string\n  ) {\n  }\n\n  private get loaded(): string[] {\n    return this.db.getHistory(this.name, this.cwd)\n  }\n\n  public get filtered(): ReadonlyArray<string> {\n    return this._filtered\n  }\n\n  public get index(): number {\n    return this._index\n  }\n\n  public static migrate(folder: string): void {\n    try {\n      let files = fs.readdirSync(folder)\n      files = files.filter(f => f.startsWith('list-') && f.endsWith('-history.json') && fs.statSync(path.join(folder, f)).isFile())\n      if (files.length === 0) return\n      let db = new DataBase()\n      for (let file of files) {\n        let name = file.match(/^list-(.*)-history.json$/)[1]\n        let content = fs.readFileSync(path.join(folder, file), 'utf8')\n        let obj = JSON.parse(content) as { [key: string]: string[] }\n        for (let [key, texts] of Object.entries(obj)) {\n          let folder = Buffer.from(key, 'base64').toString('utf8')\n          if (Array.isArray(texts)) {\n            texts.forEach(text => {\n              db.addItem(name, text, folder)\n            })\n          }\n        }\n      }\n      files.forEach(f => {\n        fs.unlinkSync(path.join(folder, f))\n      })\n      db.save()\n    } catch (e) {\n      logger.error(`Error on migrate history:`, e)\n    }\n  }\n\n  public get curr(): string | null {\n    return this._index == -1 || this._filtered == null ? null : this._filtered[this._index]\n  }\n\n  public filter(): void {\n    let { input } = this.prompt\n    if (input === this.curr) return\n    this.historyInput = ''\n    if (input.length > 0) {\n      let codes = getCharCodes(input)\n      this._filtered = this.loaded.filter(s => fuzzyMatch(codes, s))\n    } else {\n      this._filtered = this.loaded\n    }\n    this._index = -1\n  }\n\n  public add(): void {\n    let { db, prompt, cwd } = this\n    let { input } = prompt\n    if (!input || input.length < 2 || input == this.historyInput) return\n    db.addItem(this.name, input, cwd)\n  }\n\n  public previous(): void {\n    let { _filtered, _index } = this\n    if (isFalsyOrEmpty(_filtered)) return\n    if (_index <= 0) {\n      this._index = _filtered.length - 1\n    } else {\n      this._index = _index - 1\n    }\n    this.historyInput = this.prompt.input = toText(_filtered[this._index])\n  }\n\n  public next(): void {\n    let { _filtered, _index } = this\n    if (isFalsyOrEmpty(_filtered)) return\n    if (_index == _filtered.length - 1) {\n      this._index = 0\n    } else {\n      this._index = _index + 1\n    }\n    this.historyInput = this.prompt.input = toText(_filtered[this._index])\n  }\n}\n"
  },
  {
    "path": "src/list/manager.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { Extensions, IConfigurationNode, IConfigurationRegistry } from '../configuration/registry'\nimport { ConfigurationScope, ConfigurationTarget } from '../configuration/types'\nimport events from '../events'\nimport extensions from '../extension/index'\nimport { createLogger } from '../logger'\nimport { defaultValue, disposeAll, getConditionValue } from '../util'\nimport { dataHome, isVim } from '../util/constants'\nimport { isCancellationError } from '../util/errors'\nimport { parseExtensionName } from '../util/extensionRegistry'\nimport { stripAnsi } from '../util/node'\nimport { CancellationTokenSource, Disposable } from '../util/protocol'\nimport { Registry } from '../util/registry'\nimport { toErrorText, toInteger } from '../util/string'\nimport window from '../window'\nimport workspace from '../workspace'\nimport listConfiguration from './configuration'\nimport History from './history'\nimport Mappings from './mappings'\nimport Prompt from './prompt'\nimport ListSession from './session'\nimport CommandsList from './source/commands'\nimport DiagnosticsList from './source/diagnostics'\nimport ExtensionList from './source/extensions'\nimport FolderList from './source/folders'\nimport LinksList from './source/links'\nimport ListsList from './source/lists'\nimport LocationList from './source/location'\nimport NotificationsList from './source/notifications'\nimport OutlineList from './source/outline'\nimport ServicesList from './source/services'\nimport SourcesList from './source/sources'\nimport SymbolsList from './source/symbols'\nimport { IList, ListItem, ListOptions, ListTask, Matcher } from './types'\nconst logger = createLogger('list-manager')\n\nconst mouseKeys = ['<LeftMouse>', '<LeftDrag>', '<LeftRelease>', '<2-LeftMouse>']\nconst winleaveDalay = isVim ? 50 : 0\n\nexport class ListManager implements Disposable {\n  public prompt: Prompt\n  public mappings: Mappings\n  private plugTs = 0\n  private sessionsMap: Map<string, ListSession> = new Map()\n  private lastSession: ListSession | undefined\n  private disposables: Disposable[] = []\n  private listMap: Map<string, IList> = new Map()\n\n  constructor() {\n    History.migrate(dataHome)\n  }\n\n  private get nvim(): Neovim {\n    return workspace.nvim\n  }\n\n  public init(nvim: Neovim): void {\n    this.prompt = new Prompt(nvim)\n    this.mappings = new Mappings(this, nvim)\n    let signText = listConfiguration.get<string>('selectedSignText', '*')\n    nvim.command(`sign define CocSelected text=${signText} texthl=CocSelectedText linehl=CocSelectedLine`, true)\n    events.on('InputChar', this.onInputChar, this, this.disposables)\n    events.on('FocusGained', async () => {\n      let session = await this.getCurrentSession()\n      if (session) this.prompt.drawPrompt()\n    }, null, this.disposables)\n    events.on('WinEnter', winid => {\n      let session = this.getSessionByWinid(winid)\n      if (session) this.prompt.start(session.listOptions)\n    }, null, this.disposables)\n    let timer: NodeJS.Timeout\n    events.on('WinLeave', winid => {\n      clearTimeout(timer)\n      let session = this.getSessionByWinid(winid)\n      if (session) {\n        timer = setTimeout(() => {\n          this.prompt.cancel()\n        }, winleaveDalay)\n      }\n    }, null, this.disposables)\n    workspace.onDidChangeConfiguration(e => {\n      if (e.source !== ConfigurationTarget.Default && e.affectsConfiguration('list')) {\n        this.mappings.createMappings()\n      }\n    }, null, this.disposables)\n    this.prompt.onDidChangeInput(() => {\n      this.session?.onInputChange()\n    })\n  }\n\n  public registerLists(): void {\n    this.registerList(new LinksList(), true)\n    this.registerList(new LocationList(), true)\n    this.registerList(new SymbolsList(), true)\n    this.registerList(new OutlineList(), true)\n    this.registerList(new CommandsList(), true)\n    this.registerList(new ExtensionList(extensions.manager), true)\n    this.registerList(new DiagnosticsList(this), true)\n    this.registerList(new NotificationsList(), true)\n    this.registerList(new SourcesList(), true)\n    this.registerList(new ServicesList(), true)\n    this.registerList(new ListsList(this.listMap), true)\n    this.registerList(new FolderList(), true)\n  }\n\n  public async start(args: string[]): Promise<void> {\n    let res = this.parseArgs(args)\n    if (!res) return\n    let { name } = res.list\n    let curr = this.sessionsMap.get(name)\n    if (curr) curr.dispose()\n    this.prompt.start(res.options)\n    let session = new ListSession(this.nvim, this.prompt, res.list, res.options, res.listArgs)\n    this.sessionsMap.set(name, session)\n    this.lastSession = session\n    try {\n      await session.start(args)\n    } catch (e) {\n      this.nvim.call('coc#prompt#stop_prompt', ['list'], true)\n      this.nvim.command(`echo \"\"`, true)\n      if (isCancellationError(e)) return\n      void window.showErrorMessage(`Error on \"CocList ${name}\": ${toErrorText(e)}`)\n      this.nvim.redrawVim()\n      logger.error(`Error on load ${name} list:`, e)\n    }\n  }\n\n  private getSessionByWinid(winid: number): ListSession | null {\n    for (let session of this.sessionsMap.values()) {\n      if (session && session.winid == winid) {\n        this.lastSession = session\n        return session\n      }\n    }\n    return null\n  }\n\n  public async getCurrentSession(): Promise<ListSession | null> {\n    let { id } = await this.nvim.window\n    for (let session of this.sessionsMap.values()) {\n      if (session && session.winid == id) {\n        this.lastSession = session\n        return session\n      }\n    }\n    return null\n  }\n\n  public async resume(name?: string): Promise<void> {\n    if (!name) {\n      await this.session?.resume()\n    } else {\n      let session = this.sessionsMap.get(name)\n      if (!session) {\n        void window.showWarningMessage(`Can't find exists ${name} list`)\n        return\n      }\n      this.lastSession = session\n      await session.resume()\n    }\n  }\n\n  public async doAction(name?: string): Promise<void> {\n    let lastSession = this.lastSession\n    if (!lastSession) return\n    await lastSession.doAction(name)\n  }\n\n  public async first(name?: string): Promise<void> {\n    let s = this.getSession(name)\n    if (s) await s.first()\n  }\n\n  public async last(name?: string): Promise<void> {\n    let s = this.getSession(name)\n    if (s) await s.last()\n  }\n\n  public async previous(name?: string): Promise<void> {\n    let s = this.getSession(name)\n    if (s) await s.previous()\n  }\n\n  public async next(name?: string): Promise<void> {\n    let s = this.getSession(name)\n    if (s) await s.next()\n  }\n\n  public getSession(name?: string): ListSession {\n    if (!name) return this.session\n    return this.sessionsMap.get(name)\n  }\n\n  public async cancel(close = true): Promise<void> {\n    this.prompt.cancel()\n    if (!close) return\n    if (this.session) await this.session.hide()\n  }\n\n  /**\n   * Clear all list sessions\n   */\n  public reset(): void {\n    this.prompt.cancel()\n    this.lastSession = undefined\n    for (let session of this.sessionsMap.values()) {\n      session.dispose()\n    }\n    this.sessionsMap.clear()\n    this.nvim.call('coc#prompt#stop_prompt', ['list'], true)\n  }\n\n  public async switchMatcher(): Promise<void> {\n    await this.session?.switchMatcher()\n  }\n\n  public async togglePreview(): Promise<void> {\n    let { nvim } = this\n    let winid = await nvim.call('coc#list#get_preview', [0])\n    if (winid != -1) {\n      await nvim.call('coc#list#close_preview', [])\n      await nvim.command('redraw')\n    } else {\n      await this.doAction('preview')\n    }\n  }\n\n  public async chooseAction(): Promise<void> {\n    let { lastSession } = this\n    if (lastSession) await lastSession.chooseAction()\n  }\n\n  public parseArgs(args: string[]): { list: IList; options: ListOptions; listArgs: string[] } | null {\n    let options: string[] = []\n    let interactive = false\n    let autoPreview = false\n    let numberSelect = false\n    let noQuit = false\n    let first = false\n    let reverse = false\n    let name: string\n    let input = ''\n    let matcher: Matcher = 'fuzzy'\n    let position = 'bottom'\n    let listArgs: string[] = []\n    let listOptions: string[] = []\n    let height: number | undefined\n    for (let arg of args) {\n      if (!name && arg.startsWith('-')) {\n        listOptions.push(arg)\n      } else if (!name) {\n        if (!/^\\w+$/.test(arg)) {\n          void window.showErrorMessage(`Invalid list option: \"${arg}\"`)\n          return null\n        }\n        name = arg\n      } else {\n        listArgs.push(arg)\n      }\n    }\n    name = name || 'lists'\n    let config = workspace.initialConfiguration.get<any | undefined>(`list.source.${name}`)\n    if (!listOptions.length && !listArgs.length) listOptions = defaultValue(config?.defaultOptions, [])\n    if (!listArgs.length) listArgs = defaultValue(config?.defaultArgs, [])\n    for (let opt of listOptions) {\n      if (opt.startsWith('--input=')) {\n        input = opt.slice(8)\n      } else if (opt.startsWith('--height=')) {\n        height = toInteger(opt.slice(9))\n      } else if (opt == '--number-select' || opt == '-N') {\n        numberSelect = true\n      } else if (opt == '--auto-preview' || opt == '-A') {\n        autoPreview = true\n      } else if (opt == '--regex' || opt == '-R') {\n        matcher = 'regex'\n      } else if (opt == '--strict' || opt == '-S') {\n        matcher = 'strict'\n      } else if (opt == '--interactive' || opt == '-I') {\n        interactive = true\n      } else if (opt == '--top') {\n        position = 'top'\n      } else if (opt == '--tab') {\n        position = 'tab'\n      } else if (opt == '--ignore-case' || opt == '--normal' || opt == '--no-sort') {\n        options.push(opt.slice(2))\n      } else if (opt == '--first') {\n        first = true\n      } else if (opt == '--reverse') {\n        reverse = true\n      } else if (opt == '--no-quit') {\n        noQuit = true\n      } else {\n        void window.showErrorMessage(`Invalid option \"${opt}\" of list`)\n        return null\n      }\n    }\n    let list = this.listMap.get(name)\n    if (!list) {\n      void window.showErrorMessage(`List ${name} not found`)\n      return null\n    }\n    if (interactive && !list.interactive) {\n      void window.showErrorMessage(`Interactive mode of \"${name}\" list not supported`)\n      return null\n    }\n    return {\n      list,\n      listArgs,\n      options: {\n        numberSelect,\n        autoPreview,\n        height,\n        reverse,\n        noQuit,\n        first,\n        input,\n        interactive,\n        matcher,\n        position,\n        ignorecase: options.includes('ignore-case') ? true : false,\n        mode: !options.includes('normal') ? 'insert' : 'normal',\n        sort: !options.includes('no-sort') ? true : false\n      },\n    }\n  }\n\n  private async onInputChar(session: string, ch: string, charmod: number): Promise<void> {\n    if (!ch || session != 'list') return\n    let { mode } = this.prompt\n    let now = Date.now()\n    if (ch == '<plug>' || (this.plugTs && now - this.plugTs < 20)) {\n      this.plugTs = now\n      return\n    }\n    if (ch == '<esc>') {\n      await this.cancel()\n      return\n    }\n    if (mode == 'insert') {\n      await this.onInsertInput(ch, charmod)\n    } else {\n      await this.onNormalInput(ch, charmod)\n    }\n  }\n\n  public async onInsertInput(ch: string, charmod?: number): Promise<void> {\n    let { session } = this\n    if (mouseKeys.includes(ch)) {\n      await this.onMouseEvent(ch)\n      return\n    }\n    if (!session) return\n    let n = await session.doNumberSelect(ch)\n    if (n) return\n    let done = await this.mappings.doInsertKeymap(ch)\n    if (done || charmod) return\n    if (ch.startsWith('<') && ch.endsWith('>')) {\n      await this.feedkeys(ch, false)\n      return\n    }\n    for (let s of ch) {\n      let code = s.codePointAt(0)\n      if (code == 65533) return\n      // exclude control character\n      if (code < 32 || code >= 127 && code <= 159) return\n      await this.prompt.acceptCharacter(s)\n    }\n  }\n\n  public async onNormalInput(ch: string, _charmod?: number): Promise<void> {\n    if (mouseKeys.includes(ch)) {\n      await this.onMouseEvent(ch)\n      return\n    }\n    let used = await this.mappings.doNormalKeymap(ch)\n    if (!used) await this.feedkeys(ch)\n  }\n\n  private onMouseEvent(key): Promise<void> {\n    return this.session?.onMouseEvent(key)\n  }\n\n  public async feedkeys(key: string, remap = true): Promise<void> {\n    let { nvim } = this\n    key = key.startsWith('<') && key.endsWith('>') ? `\\\\${key}` : key\n    await nvim.call('coc#prompt#stop_prompt', ['list'])\n    await nvim.call('eval', [`feedkeys(\"${key}\", \"${remap ? 'i' : 'in'}\")`])\n    this.triggerCursorMoved()\n    this.prompt.start()\n  }\n\n  public async command(command: string): Promise<void> {\n    let { nvim } = this\n    await nvim.call('coc#prompt#stop_prompt', ['list'])\n    await nvim.command(command)\n    this.triggerCursorMoved()\n    this.prompt.start()\n  }\n\n  public async normal(command: string, bang: boolean): Promise<void> {\n    let { nvim } = this\n    await nvim.call('coc#prompt#stop_prompt', ['list'])\n    await nvim.command(`normal${bang ? '!' : ''} ${command}`)\n    this.triggerCursorMoved()\n    this.prompt.start()\n  }\n\n  public triggerCursorMoved(): void {\n    if (this.nvim.isVim) this.nvim.command('doautocmd <nomodeline> CursorMoved', true)\n    this.nvim.call('coc#util#do_autocmd', ['CocListMoved'], true)\n  }\n\n  public async call(fname: string): Promise<any> {\n    if (this.session) return await this.session.call(fname)\n  }\n\n  public get session(): ListSession | undefined {\n    return this.lastSession\n  }\n\n  public registerList(list: IList, internal = false): Disposable {\n    let { name, interactive } = list\n    let id: string | undefined\n    if (!internal) id = getConditionValue(parseExtensionName(Error().stack), undefined)\n    let removed = this.deregisterList(name)\n    this.listMap.set(name, list)\n    const configNode = createConfigurationNode(name, interactive, id)\n    if (!removed) workspace.configurations.updateConfigurations([configNode])\n    return Disposable.create(() => {\n      this.deregisterList(name)\n      const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration)\n      configurationRegistry.deregisterConfigurations([configNode])\n    })\n  }\n\n  private deregisterList(name: string): boolean {\n    let exists = this.listMap.get(name)\n    if (exists) {\n      if (typeof exists.dispose == 'function') {\n        exists.dispose()\n      }\n      this.listMap.delete(name)\n      return true\n    }\n    return false\n  }\n\n  public get names(): string[] {\n    return Array.from(this.listMap.keys())\n  }\n\n  public get descriptions(): { [name: string]: string } {\n    let d = {}\n    for (let name of this.listMap.keys()) {\n      let list = this.listMap.get(name)\n      d[name] = list.description\n    }\n    return d\n  }\n\n  /**\n   * Get items of {name} list\n   * @param {string} name\n   * @returns {Promise<any>}\n   */\n  public async loadItems(name: string): Promise<ListItem[] | undefined> {\n    let args = [name]\n    let res = this.parseArgs(args)\n    if (!res || !name) return\n    let { list, options, listArgs } = res\n    let source = new CancellationTokenSource()\n    let token = source.token\n    let arr = await this.nvim.eval('[win_getid(),bufnr(\"%\")]')\n    let items = await list.loadItems({\n      options,\n      args: listArgs,\n      input: '',\n      cwd: workspace.cwd,\n      window: this.nvim.createWindow(arr[0]),\n      buffer: this.nvim.createBuffer(arr[1]),\n      listWindow: null\n    }, token)\n    if (!items || Array.isArray(items)) {\n      return items as ListItem[]\n    }\n    let task = items as ListTask\n    let newItems = await new Promise<ListItem[]>((resolve, reject) => {\n      let items = []\n      task.on('data', item => {\n        item.label = stripAnsi(item.label)\n        items.push(item)\n      })\n      task.on('end', () => {\n        resolve(items)\n      })\n      task.on('error', msg => {\n        reject(msg instanceof Error ? msg : new Error(msg))\n        task.dispose()\n      })\n    })\n    return newItems\n  }\n\n  public toggleMode(): void {\n    let lastSession = this.lastSession\n    if (lastSession) lastSession.toggleMode()\n  }\n\n  public get isActivated(): boolean {\n    return this.session?.winid != null\n  }\n\n  public stop(): void {\n    let lastSession = this.lastSession\n    if (lastSession) lastSession.stop()\n  }\n\n  public dispose(): void {\n    for (let session of this.sessionsMap.values()) {\n      session.dispose()\n    }\n    this.sessionsMap.clear()\n    this.lastSession = undefined\n    disposeAll(this.disposables)\n  }\n}\n\nexport default new ListManager()\n\nexport function createConfigurationNode(name: string, interactive: boolean, id?: string): IConfigurationNode {\n  let properties = {}\n  properties[`list.source.${name}.defaultAction`] = {\n    type: 'string',\n    default: null,\n    description: `Default action of \"${name}\" list.`\n  }\n  properties[`list.source.${name}.defaultOptions`] = {\n    type: 'array',\n    default: interactive ? ['--interactive'] : [],\n    description: `Default list options of \"${name}\" list, only used when both list option and argument are empty.`,\n    uniqueItems: true,\n    items: {\n      type: 'string',\n      enum: ['--top', '--normal', '--no-sort', '--input', '--height', '--tab',\n        '--strict', '--regex', '--ignore-case', '--number-select',\n        '--reverse', '--interactive', '--auto-preview', '--first', '--no-quit']\n    }\n  }\n  properties[`list.source.${name}.defaultArgs`] = {\n    type: 'array',\n    default: [],\n    description: `Default argument list of \"${name}\" list, only used when list argument is empty.`,\n    uniqueItems: true,\n    items: { type: 'string' }\n  }\n  let node: IConfigurationNode = {\n    scope: ConfigurationScope.APPLICATION,\n    properties,\n  }\n  if (id) node.extensionInfo = { id }\n  return node\n}\n"
  },
  {
    "path": "src/list/mappings.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { ListMode } from './types'\nimport window from '../window'\nimport listConfiguration, { validKeys } from './configuration'\nimport { ListManager } from './manager'\n\nexport default class Mappings {\n  private insertMappings: Map<string, () => void | Promise<void>> = new Map()\n  private normalMappings: Map<string, () => void | Promise<void>> = new Map()\n  private userInsertMappings: Map<string, string> = new Map()\n  private userNormalMappings: Map<string, string> = new Map()\n  private actions: Map<string, (expr?: string) => void | Promise<void>> = new Map()\n\n  constructor(private manager: ListManager,\n    private nvim: Neovim) {\n    let { prompt } = manager\n    this.addAction('do:switch', async () => {\n      await manager.switchMatcher()\n    })\n    this.addAction('do:selectall', async () => {\n      await manager.session?.ui.selectAll()\n    })\n    this.addAction('do:help', async () => {\n      await manager.session?.showHelp()\n    })\n    this.addAction('do:refresh', async () => {\n      await manager.session?.reloadItems()\n    })\n    this.addAction('do:exit', async () => {\n      await manager.cancel()\n    })\n    this.addAction('do:stop', () => {\n      manager.stop()\n    })\n    this.addAction('do:cancel', async () => {\n      await manager.cancel(false)\n    })\n    this.addAction('do:toggle', async () => {\n      await manager.session?.ui.toggleSelection()\n    })\n    this.addAction('do:jumpback', () => {\n      manager.session?.jumpBack()\n    })\n    this.addAction('do:previous', async () => {\n      await this.navigate(true)\n    })\n    this.addAction('do:next', async () => {\n      await this.navigate(false)\n    })\n    this.addAction('do:defaultaction', async () => {\n      await manager.doAction()\n    })\n    this.addAction('do:chooseaction', async () => {\n      await manager.chooseAction()\n    })\n    this.addAction('do:togglemode', () => {\n      manager.toggleMode()\n    })\n    this.addAction('do:previewtoggle', async () => {\n      await manager.togglePreview()\n    })\n    this.addAction('do:previewup', () => {\n      this.scrollPreview('up')\n    })\n    this.addAction('do:previewdown', () => {\n      this.scrollPreview('down')\n    })\n    this.addAction('do:command', async () => {\n      await manager.cancel(false)\n      await nvim.eval('feedkeys(\":\")')\n    })\n    this.addAction('prompt:previous', () => {\n      manager.session?.history.previous()\n    })\n    this.addAction('prompt:next', () => {\n      manager.session?.history.next()\n    })\n    this.addAction('prompt:start', () => {\n      prompt.moveToStart()\n    })\n    this.addAction('prompt:end', () => {\n      prompt.moveToEnd()\n    })\n    this.addAction('prompt:left', () => {\n      prompt.moveLeft()\n    })\n    this.addAction('prompt:right', () => {\n      prompt.moveRight()\n    })\n    this.addAction('prompt:leftword', () => {\n      prompt.moveLeftWord()\n    })\n    this.addAction('prompt:rightword', () => {\n      prompt.moveRightWord()\n    })\n    this.addAction('prompt:deleteforward', () => {\n      prompt.onBackspace()\n    })\n    this.addAction('prompt:deletebackward', () => {\n      prompt.removeNext()\n    })\n    this.addAction('prompt:removetail', () => {\n      prompt.removeTail()\n    })\n    this.addAction('prompt:removeahead', () => {\n      prompt.removeAhead()\n    })\n    this.addAction('prompt:removeword', () => {\n      prompt.removeWord()\n    })\n    this.addAction('prompt:insertregister', () => {\n      prompt.insertRegister()\n    })\n    this.addAction('prompt:paste', async () => {\n      await prompt.paste()\n    })\n    this.addAction('eval', async expr => {\n      await prompt.eval(expr)\n    })\n    this.addAction('command', async expr => {\n      await manager.command(expr)\n    })\n    this.addAction('action', async expr => {\n      await manager.doAction(expr)\n    })\n    this.addAction('feedkeys', async expr => {\n      await manager.feedkeys(expr)\n    })\n    this.addAction('feedkeys!', async expr => {\n      await manager.feedkeys(expr, false)\n    })\n    this.addAction('normal', async expr => {\n      await manager.normal(expr, false)\n    })\n    this.addAction('normal!', async expr => {\n      await manager.normal(expr, true)\n    })\n    this.addAction('call', async expr => {\n      await manager.call(expr)\n    })\n    this.addAction('expr', async expr => {\n      let name = await manager.call(expr)\n      await manager.doAction(name)\n    })\n\n    this.addKeyMapping('insert', '<C-s>', 'do:switch')\n    this.addKeyMapping('insert', '<C-n>', 'prompt:next')\n    this.addKeyMapping('insert', '<C-p>', 'prompt:previous')\n    this.addKeyMapping('insert', '<C-v>', 'prompt:paste')\n    this.addKeyMapping('insert', ['<C-m>', '<cr>'], 'do:defaultaction')\n    this.addKeyMapping('insert', ['<tab>', '<C-i>', '\\t'], 'do:chooseaction')\n    this.addKeyMapping('insert', '<C-o>', 'do:togglemode')\n    this.addKeyMapping('insert', '<C-c>', 'do:stop')\n    this.addKeyMapping('insert', '<C-l>', 'do:refresh')\n    this.addKeyMapping('insert', '<left>', 'prompt:left')\n    this.addKeyMapping('insert', '<right>', 'prompt:right')\n    this.addKeyMapping('insert', ['<end>', '<C-e>'], 'prompt:end')\n    this.addKeyMapping('insert', ['<home>', '<C-a>'], 'prompt:start')\n    this.addKeyMapping('insert', ['<C-h>', '<bs>', '<backspace>'], 'prompt:deleteforward')\n    this.addKeyMapping('insert', '<C-w>', 'prompt:removeword')\n    this.addKeyMapping('insert', '<C-u>', 'prompt:removeahead')\n    this.addKeyMapping('insert', '<C-r>', 'prompt:insertregister')\n    // normal\n    this.addKeyMapping('normal', 't', 'action:tabe')\n    this.addKeyMapping('normal', 's', 'action:split')\n    this.addKeyMapping('normal', 'r', 'action:refactor')\n    this.addKeyMapping('normal', 'd', 'action:drop')\n    this.addKeyMapping('normal', ['<cr>', '<C-m>', '\\r'], 'do:defaultaction')\n    this.addKeyMapping('normal', '<C-a>', 'do:selectall')\n    this.addKeyMapping('normal', ' ', 'do:toggle')\n    this.addKeyMapping('normal', 'p', 'do:previewtoggle')\n    this.addKeyMapping('normal', ['<tab>', '\\t', '<C-i>'], 'do:chooseaction')\n    this.addKeyMapping('normal', '<C-c>', 'do:stop')\n    this.addKeyMapping('normal', '<C-l>', 'do:refresh')\n    this.addKeyMapping('normal', '<C-o>', 'do:jumpback')\n    this.addKeyMapping('normal', '<C-e>', 'do:previewdown')\n    this.addKeyMapping('normal', '<C-y>', 'do:previewup')\n    this.addKeyMapping('normal', ['i', 'I', 'o', 'O', 'a', 'A'], 'do:togglemode')\n    this.addKeyMapping('normal', '?', 'do:help')\n    this.addKeyMapping('normal', ':', 'do:command')\n    this.createMappings()\n  }\n\n  public createMappings(): void {\n    let insertMappings = listConfiguration.get<any>('insertMappings', {})\n    this.userInsertMappings = this.fixUserMappings(insertMappings, 'list.insertMappings')\n    let normalMappings = listConfiguration.get<any>('normalMappings', {})\n    this.userNormalMappings = this.fixUserMappings(normalMappings, 'list.normalMappings')\n  }\n\n  public hasUserMapping(mode: ListMode, key: string): boolean {\n    let map = mode == 'insert' ? this.userInsertMappings : this.userNormalMappings\n    return map.has(key)\n  }\n\n  public isValidAction(action: string): boolean {\n    if (this.actions.has(action)) return true\n    let [key, expr] = action.split(':', 2)\n    if (!expr || !this.actions.has(key)) return false\n    return true\n  }\n\n  private fixUserMappings(mappings: { [key: string]: string }, entry: string): Map<string, string> {\n    let res: Map<string, string> = new Map()\n    for (let [key, value] of Object.entries(mappings)) {\n      if (!this.isValidAction(value)) {\n        void window.showWarningMessage(`Invalid configuration - unable to support action \"${value}\" in \"${entry}\"`)\n        continue\n      }\n      if (key.length == 1) {\n        res.set(key, value)\n      } else if (key.startsWith('<') && key.endsWith('>')) {\n        if (key.toLowerCase() == '<space>') {\n          res.set(' ', value)\n        } else if (key.toLowerCase() == '<backspace>') {\n          res.set('<bs>', value)\n        } else if (validKeys.includes(key)) {\n          res.set(key, value)\n        } else {\n          let find = false\n          for (let i = 0; i < validKeys.length; i++) {\n            if (validKeys[i].toLowerCase() == key.toLowerCase()) {\n              find = true\n              res.set(validKeys[i], value)\n              break\n            }\n          }\n          if (!find) void window.showWarningMessage(`Invalid configuration - unable to recognize \"${key}\" in \"${entry}\"`)\n        }\n      } else {\n        void window.showWarningMessage(`Invalid configuration - unable to recognize key \"${key}\" in \"${entry}\"`)\n      }\n    }\n    return res\n  }\n\n  public async navigate(up: boolean): Promise<boolean> {\n    let ui = this.manager.session?.ui\n    if (!ui) return false\n    await ui.moveCursor(up ? -1 : 1)\n    return true\n  }\n\n  public async doInsertKeymap(key: string): Promise<boolean> {\n    if (key === listConfiguration.nextKey) return await this.navigate(false)\n    if (key === listConfiguration.previousKey) return await this.navigate(true)\n    let expr = this.userInsertMappings.get(key)\n    if (expr) {\n      let fn = this.getAction(expr)\n      await Promise.resolve(fn())\n      return true\n    }\n    if (this.insertMappings.has(key)) {\n      let fn = this.insertMappings.get(key)\n      await Promise.resolve(fn())\n      return true\n    }\n    return false\n  }\n\n  public async doNormalKeymap(key: string): Promise<boolean> {\n    let expr = this.userNormalMappings.get(key)\n    if (expr) {\n      let fn = this.getAction(expr)\n      await Promise.resolve(fn())\n      return true\n    }\n    if (this.normalMappings.has(key)) {\n      let fn = this.normalMappings.get(key)\n      await Promise.resolve(fn())\n      return true\n    }\n    return false\n  }\n\n  private addKeyMapping(mode: ListMode, key: string | string[], action: string): void {\n    let mappings = mode == 'insert' ? this.insertMappings : this.normalMappings\n    let fn = this.getAction(action)\n    if (Array.isArray(key)) {\n      for (let k of key) {\n        mappings.set(k, fn)\n      }\n    } else {\n      mappings.set(key, fn)\n    }\n  }\n\n  private addAction(key: string, fn: (expr?: string) => void | Promise<void>): void {\n    this.actions.set(key, fn)\n  }\n\n  public getAction(action: string): () => void | Promise<void> {\n    if (this.actions.has(action)) return () => {\n      return this.doAction(action)\n    }\n    let [key, expr] = action.split(':', 2)\n    if (!expr || !this.actions.has(key)) throw new Error(`Invalid action ${action}`)\n    return () => {\n      return this.doAction(key, expr)\n    }\n  }\n\n  public async doAction(key: string, expr?: string): Promise<void> {\n    let fn = this.actions.get(key)\n    if (!fn) throw new Error(`Action ${key} doesn't exist`)\n    await Promise.resolve(fn(expr))\n  }\n\n  private scrollPreview(dir: 'up' | 'down'): void {\n    const floatPreview = listConfiguration.get<boolean>('floatPreview', false)\n    let { nvim } = this\n    nvim.pauseNotification()\n    nvim.call('coc#list#scroll_preview', [dir, floatPreview], true)\n    nvim.command('redraw', true)\n    nvim.resumeNotification(false, true)\n  }\n}\n"
  },
  {
    "path": "src/list/prompt.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { Emitter, Event } from '../util/protocol'\nimport { getUnicodeClass } from '../util/string'\nimport listConfiguration from './configuration'\nimport { ListMode, ListOptions, Matcher } from './types'\n\nexport default class Prompt {\n  private cursorIndex = 0\n  private _input = ''\n  private _matcher: Matcher | ''\n  private _mode: ListMode = 'insert'\n  private interactive = false\n  private requestInput = false\n  private _onDidChangeInput = new Emitter<string>()\n  public readonly onDidChangeInput: Event<string> = this._onDidChangeInput.event\n\n  constructor(private nvim: Neovim) {\n  }\n\n  public get input(): string {\n    return this._input\n  }\n\n  public set input(str: string) {\n    if (this._input == str) return\n    this.cursorIndex = str.length\n    this._input = str\n    this.drawPrompt()\n    this._onDidChangeInput.fire(this._input)\n  }\n\n  public get mode(): ListMode {\n    return this._mode\n  }\n\n  public set mode(val: ListMode) {\n    if (val == this._mode) return\n    this._mode = val\n    this.drawPrompt()\n  }\n\n  public set matcher(val: Matcher) {\n    this._matcher = val\n    this.drawPrompt()\n  }\n\n  public start(opts?: ListOptions): void {\n    if (opts) {\n      this.interactive = opts.interactive\n      this.cursorIndex = opts.input.length\n      this._input = opts.input\n      this._mode = opts.mode\n      this._matcher = opts.interactive ? '' : opts.matcher\n    }\n    this.nvim.call('coc#prompt#start_prompt', ['list'], true)\n    this.drawPrompt()\n  }\n\n  public cancel(): void {\n    let { nvim } = this\n    nvim.call('coc#prompt#stop_prompt', ['list'], true)\n  }\n\n  public reset(): void {\n    this._input = ''\n    this.cursorIndex = 0\n  }\n\n  public drawPrompt(): void {\n    let indicator = listConfiguration.get<string>('indicator', '>')\n    let { cursorIndex, interactive, input, _matcher } = this\n    let cmds = ['echo \"\"']\n    if (this.mode == 'insert') {\n      if (interactive) {\n        cmds.push(`echohl MoreMsg | echon 'INTERACTIVE ' | echohl None`)\n      } else if (_matcher) {\n        cmds.push(`echohl MoreMsg | echon '${_matcher.toUpperCase()} ' | echohl None`)\n      }\n      cmds.push(`echohl Special | echon '${indicator} ' | echohl None`)\n      if (cursorIndex == input.length) {\n        cmds.push(`echon '${input.replace(/'/g, \"''\")}'`)\n        cmds.push(`echohl Cursor | echon ' ' | echohl None`)\n      } else {\n        let pre = input.slice(0, cursorIndex)\n        if (pre) cmds.push(`echon '${pre.replace(/'/g, \"''\")}'`)\n        cmds.push(`echohl Cursor | echon '${input[cursorIndex].replace(/'/, \"''\")}' | echohl None`)\n        let post = input.slice(cursorIndex + 1)\n        cmds.push(`echon '${post.replace(/'/g, \"''\")}'`)\n      }\n    } else {\n      cmds.push(`echohl MoreMsg | echo \"\" | echohl None`)\n    }\n    cmds.push('redraw')\n    let cmd = cmds.join('|')\n    this.nvim.command(cmd, true)\n  }\n\n  public moveLeft(): void {\n    if (this.cursorIndex == 0) return\n    this.cursorIndex = this.cursorIndex - 1\n    this.drawPrompt()\n  }\n\n  public moveRight(): void {\n    if (this.cursorIndex == this._input.length) return\n    this.cursorIndex = this.cursorIndex + 1\n    this.drawPrompt()\n  }\n\n  public moveLeftWord(): void {\n    // Reuses logic from removeWord(), except that we only update the\n    // cursorIndex and don't actually remove the word.\n    let { cursorIndex, input } = this\n    if (cursorIndex == 0) return\n    let pre = input.slice(0, cursorIndex)\n    let remain = getLastWordRemovedText(pre)\n    this.cursorIndex = cursorIndex - (pre.length - remain.length)\n    this.drawPrompt()\n    this._onDidChangeInput.fire(this._input)\n  }\n\n  public moveRightWord(): void {\n    let { cursorIndex, input } = this\n    if (cursorIndex == input.length) return\n    let post = input.slice(cursorIndex)\n    let nextWord = post.match(/[\\w$]+ */).at(0) ?? post\n    this.cursorIndex = cursorIndex + nextWord.length\n    this.drawPrompt()\n    this._onDidChangeInput.fire(this._input)\n  }\n\n  public moveToEnd(): void {\n    if (this.cursorIndex == this._input.length) return\n    this.cursorIndex = this._input.length\n    this.drawPrompt()\n  }\n\n  public moveToStart(): void {\n    if (this.cursorIndex == 0) return\n    this.cursorIndex = 0\n    this.drawPrompt()\n  }\n\n  public onBackspace(): void {\n    let { cursorIndex, input } = this\n    if (cursorIndex == 0) return\n    let pre = input.slice(0, cursorIndex)\n    let post = input.slice(cursorIndex)\n    this.cursorIndex = cursorIndex - 1\n    this._input = `${pre.slice(0, pre.length - 1)}${post}`\n    this.drawPrompt()\n    this._onDidChangeInput.fire(this._input)\n  }\n\n  public removeNext(): void {\n    let { cursorIndex, input } = this\n    if (cursorIndex == input.length) return\n    let pre = input.slice(0, cursorIndex)\n    let post = input.slice(cursorIndex + 1)\n    this._input = `${pre}${post}`\n    this.drawPrompt()\n    this._onDidChangeInput.fire(this._input)\n  }\n\n  public removeWord(): void {\n    let { cursorIndex, input } = this\n    if (cursorIndex == 0) return\n    let pre = input.slice(0, cursorIndex)\n    let post = input.slice(cursorIndex)\n    let remain = getLastWordRemovedText(pre)\n    this.cursorIndex = cursorIndex - (pre.length - remain.length)\n    this._input = `${remain}${post}`\n    this.drawPrompt()\n    this._onDidChangeInput.fire(this._input)\n  }\n\n  public removeTail(): void {\n    let { cursorIndex, input } = this\n    if (cursorIndex == input.length) return\n    let pre = input.slice(0, cursorIndex)\n    this._input = pre\n    this.drawPrompt()\n    this._onDidChangeInput.fire(this._input)\n  }\n\n  public removeAhead(): void {\n    let { cursorIndex, input } = this\n    if (cursorIndex == 0) return\n    let post = input.slice(cursorIndex)\n    this.cursorIndex = 0\n    this._input = post\n    this.drawPrompt()\n    this._onDidChangeInput.fire(this._input)\n  }\n\n  public async acceptCharacter(ch: string): Promise<void> {\n    if (this.requestInput) {\n      this.requestInput = false\n      if (/^[0-9a-z\"%#*+/:\\-.]$/.test(ch)) {\n        let text = await this.nvim.call('getreg', ch) as string\n        text = text.replace(/\\n/g, ' ')\n        this.addText(text)\n      }\n    } else {\n      this.addText(ch)\n    }\n  }\n\n  public insertRegister(): void {\n    this.requestInput = true\n  }\n\n  public async paste(): Promise<void> {\n    let text = await this.nvim.eval('@*') as string\n    text = text.replace(/\\n/g, '')\n    if (!text) return\n    this.addText(text)\n  }\n\n  public async eval(expression: string): Promise<void> {\n    let text = await this.nvim.call('eval', [expression]) as string\n    text = text.replace(/\\n/g, '')\n    this.addText(text)\n  }\n\n  private addText(text: string): void {\n    let { cursorIndex, input } = this\n    this.cursorIndex = cursorIndex + text.length\n    let pre = input.slice(0, cursorIndex)\n    let post = input.slice(cursorIndex)\n    this._input = `${pre}${text}${post}`\n    this.drawPrompt()\n    this._onDidChangeInput.fire(this._input)\n  }\n}\n\nfunction getLastWordRemovedText(text: string): string {\n  let res = text\n\n  // Remove last whitespaces\n  res = res.trimEnd()\n  if (res === \"\") return res\n\n  // Remove last contiguous characters of the same unicode class.\n  const last = getUnicodeClass(res[res.length - 1])\n  while (res !== \"\" && getUnicodeClass(res[res.length - 1]) === last) {\n    res = res.slice(0, res.length - 1)\n  }\n\n  return res\n}\n"
  },
  {
    "path": "src/list/session.ts",
    "content": "'use strict'\nimport type { Buffer, Neovim, Window } from '@chemzqm/neovim'\nimport Highlighter from '../model/highlighter'\nimport { defaultValue, disposeAll, getConditionValue, wait } from '../util'\nimport { debounce } from '../util/node'\nimport { Disposable } from '../util/protocol'\nimport window from '../window'\nimport workspace from '../workspace'\nimport listConfiguration from './configuration'\nimport db from './db'\nimport InputHistory from './history'\nimport Prompt from './prompt'\nimport { IList, ListAction, ListContext, ListItem, ListMode, ListOptions, Matcher } from './types'\nimport UI from './ui'\nimport Worker from './worker'\nconst frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']\nconst debounceTime = getConditionValue(50, 1)\n\n/**\n * Activated list session with UI and worker\n */\nexport default class ListSession {\n  public readonly history: InputHistory\n  public readonly ui: UI\n  public readonly worker: Worker\n  private cwd: string\n  private loadingFrame = ''\n  private timer: NodeJS.Timeout\n  private hidden = false\n  private disposables: Disposable[] = []\n  private savedHeight: number\n  private targetWinid: number | undefined\n  private targetBufnr: number | undefined\n  /**\n   * Original list arguments.\n   */\n  private args: string[] = []\n  constructor(\n    private nvim: Neovim,\n    private prompt: Prompt,\n    private list: IList,\n    public readonly listOptions: ListOptions,\n    private listArgs: string[]\n  ) {\n    this.ui = new UI(nvim, list.name, listOptions)\n    this.history = new InputHistory(prompt, list.name, db, workspace.cwd)\n    this.worker = new Worker(list, prompt, listOptions)\n    let debouncedChangeLine = debounce(async () => {\n      let [previewing, currwin, lnum] = await nvim.eval('[coc#list#has_preview(),win_getid(),line(\".\")]') as [number, number, number]\n      if (previewing && currwin == this.winid) {\n        let idx = this.ui.lnumToIndex(lnum)\n        await this.doPreview(idx)\n      }\n    }, debounceTime)\n    this.disposables.push({\n      dispose: () => {\n        debouncedChangeLine.clear()\n      }\n    })\n    this.ui.onDidChangeLine(debouncedChangeLine, null, this.disposables)\n    this.ui.onDidChangeLine(this.resolveItem, this, this.disposables)\n    this.ui.onDidLineChange(this.resolveItem, this, this.disposables)\n    let debounced = debounce(async () => {\n      this.updateStatus()\n      let { autoPreview } = this.listOptions\n      if (!autoPreview) {\n        let [previewing, mode] = await nvim.eval('[coc#list#has_preview(),mode()]') as [number, string]\n        if (mode != 'n' || !previewing) return\n      }\n      await this.doAction('preview')\n    }, 50)\n    this.disposables.push({\n      dispose: () => {\n        debounced.clear()\n      }\n    })\n    this.ui.onDidLineChange(debounced, null, this.disposables)\n    this.ui.onDidOpen(async () => {\n      if (typeof this.list.doHighlight == 'function') {\n        this.list.doHighlight()\n      }\n      if (this.listOptions.first) {\n        await this.doAction()\n      }\n    }, null, this.disposables)\n    this.ui.onDidClose(this.hide as any, this, this.disposables)\n    this.ui.onDidDoubleClick(this.doAction as any, this, this.disposables)\n    this.worker.onDidChangeItems(ev => {\n      if (this.hidden) return\n      this.ui.onDidChangeItems(ev)\n    }, null, this.disposables)\n    let start = 0\n    let timer: NodeJS.Timeout\n    let interval: NodeJS.Timeout\n    this.disposables.push(Disposable.create(() => {\n      clearTimeout(timer)\n      clearInterval(interval)\n    }))\n    this.worker.onDidChangeLoading(loading => {\n      if (this.hidden) return\n      if (timer) clearTimeout(timer)\n      if (loading) {\n        start = Date.now()\n        if (interval) clearInterval(interval)\n        interval = setInterval(() => {\n          let idx = Math.floor((Date.now() - start) % 1000 / 100)\n          this.loadingFrame = frames[idx]\n          this.updateStatus()\n        }, 100)\n      } else {\n        timer = setTimeout(() => {\n          this.loadingFrame = ''\n          if (interval) clearInterval(interval)\n          interval = null\n          this.updateStatus()\n        }, Math.max(0, 200 - (Date.now() - start)))\n      }\n    }, null, this.disposables)\n  }\n\n  public async start(args: string[]): Promise<void> {\n    this.args = args\n    this.cwd = workspace.cwd\n    this.hidden = false\n    let { listArgs } = this\n    let res = await this.nvim.eval(`[win_getid(),bufnr(\"%\"),${workspace.isVim ? 'winheight(\"%\")' : 'nvim_win_get_height(0)'}]`)\n    this.listArgs = listArgs\n    this.history.filter()\n    this.targetWinid = res[0]\n    this.targetBufnr = res[1]\n    this.savedHeight = res[2]\n    await this.worker.loadItems(this.context)\n  }\n\n  public async reloadItems(): Promise<void> {\n    if (!this.ui.winid) return\n    await this.worker.loadItems(this.context, true)\n  }\n\n  public async call(fname: string): Promise<any> {\n    await this.nvim.call('coc#prompt#stop_prompt', ['list'])\n    let targets = await this.ui.getItems()\n    let context = {\n      name: this.name,\n      args: this.listArgs,\n      input: this.prompt.input,\n      winid: this.targetWinid,\n      bufnr: this.targetBufnr,\n      targets\n    }\n    let res = await this.nvim.call(fname, [context])\n    this.prompt.start()\n    return res\n  }\n\n  public async chooseAction(): Promise<void> {\n    let { nvim, defaultAction } = this\n    let { actions } = this.list\n    let names: string[] = actions.map(o => o.name)\n    let idx = names.indexOf(defaultAction.name)\n    if (idx != -1) {\n      names.splice(idx, 1)\n      names.unshift(defaultAction.name)\n    }\n    let shortcuts: Set<string> = new Set()\n    let choices: string[] = []\n    let invalids: string[] = []\n    let menuAction = workspace.env.dialog && listConfiguration.get('menuAction', false)\n    for (let name of names) {\n      let i = 0\n      for (let ch of name) {\n        if (!shortcuts.has(ch)) {\n          shortcuts.add(ch)\n          choices.push(`${name.slice(0, i)}&${name.slice(i)}`)\n          break\n        }\n        i++\n      }\n      if (i == name.length) {\n        invalids.push(name)\n      }\n    }\n    if (invalids.length && !menuAction) {\n      names = names.filter(s => !invalids.includes(s))\n    }\n    let n: number\n    if (menuAction) {\n      nvim.call('coc#prompt#stop_prompt', ['list'], true)\n      n = await window.showMenuPicker(names, { title: 'Choose action', shortcuts: true })\n      n = n + 1\n      this.prompt.start()\n    } else {\n      await nvim.call('coc#prompt#stop_prompt', ['list'])\n      n = await nvim.call('confirm', ['Choose action:', choices.join('\\n')]) as number\n      await wait(10)\n      this.prompt.start()\n    }\n    if (n) await this.doAction(names[n - 1])\n  }\n\n  public async doAction(name?: string): Promise<void> {\n    let { list } = this\n    let action: ListAction\n    if (name != null) {\n      action = list.actions.find(o => o.name == name)\n      if (!action) {\n        void window.showErrorMessage(`Action ${name} not found`)\n        return\n      }\n    } else {\n      action = this.defaultAction\n    }\n    let items: ListItem[]\n    if (name == 'preview') {\n      let item = await this.ui.item\n      items = item ? [item] : []\n    } else {\n      items = await this.ui.getItems()\n    }\n    if (items.length) await this.doItemAction(items, action)\n  }\n\n  public async doPreview(index: number): Promise<void> {\n    let item = this.ui.getItem(index)\n    let action = this.list.actions.find(o => o.name == 'preview')\n    if (!item || !action) return\n    await this.doItemAction([item], action)\n  }\n\n  public async first(): Promise<void> {\n    await this.doDefaultAction(0)\n  }\n\n  public async last(): Promise<void> {\n    await this.doDefaultAction(this.ui.length - 1)\n  }\n\n  public async previous(): Promise<void> {\n    await this.doDefaultAction(this.ui.index - 1)\n  }\n\n  public async next(): Promise<void> {\n    await this.doDefaultAction(this.ui.index + 1)\n  }\n\n  private async doDefaultAction(index: number): Promise<void> {\n    let { ui } = this\n    let item = ui.getItem(index)\n    if (!item) return\n    await this.ui.setIndex(index)\n    await this.doItemAction([item], this.defaultAction)\n    await ui.echoMessage(item)\n  }\n\n  /**\n   * list name\n   */\n  public get name(): string {\n    return this.list.name\n  }\n\n  /**\n   * Window id used by list.\n   * @returns {number | undefined}\n   */\n  public get winid(): number | undefined {\n    return this.ui.winid\n  }\n\n  public get length(): number {\n    return this.ui.length\n  }\n\n  public get defaultAction(): ListAction {\n    let { defaultAction, actions, name } = this.list\n    let config = workspace.getConfiguration(`list.source.${name}`)\n    let action: ListAction\n    if (config.defaultAction) action = actions.find(o => o.name == config.defaultAction)\n    if (!action) action = actions.find(o => o.name == defaultAction)\n    if (!action) action = actions[0]\n    if (!action) throw new Error(`default action \"${defaultAction}\" not found`)\n    return action\n  }\n\n  public async hide(notify = false, isVim = workspace.isVim): Promise<void> {\n    if (this.hidden) return\n    let { nvim, timer, targetWinid, context } = this\n    let { winid } = this.ui\n    if (timer) clearTimeout(timer)\n    this.worker.stop()\n    this.history.add()\n    this.ui.reset()\n    db.save()\n    this.hidden = true\n    nvim.pauseNotification()\n    if (!isVim) nvim.call('coc#prompt#stop_prompt', ['list'], true)\n    if (winid) nvim.call('coc#list#close', [winid, context.options.position, targetWinid, this.savedHeight], true)\n    if (notify) return nvim.resumeNotification(true, true)\n    await nvim.resumeNotification(false)\n    if (isVim) {\n      // required on vim\n      await wait(10)\n      nvim.call('coc#prompt#stop_prompt', ['list'], true)\n      nvim.redrawVim()\n    }\n  }\n\n  public toggleMode(): void {\n    let mode: ListMode = this.prompt.mode == 'normal' ? 'insert' : 'normal'\n    this.prompt.mode = mode\n    this.listOptions.mode = mode\n    this.updateStatus()\n  }\n\n  public stop(): void {\n    this.worker.stop()\n  }\n\n  public async resolveItem(): Promise<void> {\n    let index = this.ui.index\n    let item = this.ui.getItem(index)\n    if (!item || item.resolved) return\n    let { list } = this\n    if (typeof list.resolveItem === 'function') {\n      let label = item.label\n      let resolved = await Promise.resolve(list.resolveItem(item))\n      if (resolved && index == this.ui.index) {\n        Object.assign(item, resolved, { resolved: true })\n        if (label == resolved.label) return\n        this.ui.updateItem(item, index)\n      }\n    }\n  }\n\n  public async showHelp(): Promise<void> {\n    await this.hide()\n    let { list, nvim } = this\n    nvim.pauseNotification()\n    nvim.command(`tabe +setl\\\\ previewwindow [LIST HELP]`, true)\n    nvim.command('setl nobuflisted noswapfile buftype=nofile bufhidden=wipe', true)\n    await nvim.resumeNotification()\n    let hasOptions = list.options && list.options.length\n    let buf = await nvim.buffer\n    let highlighter = new Highlighter()\n    highlighter.addLine('NAME', 'Label')\n    highlighter.addLine(`  ${list.name} - ${list.description || ''}\\n`)\n    highlighter.addLine('SYNOPSIS', 'Label')\n    highlighter.addLine(`  :CocList [LIST OPTIONS] ${list.name}${hasOptions ? ' [ARGUMENTS]' : ''}\\n`)\n    if (list.detail) {\n      highlighter.addLine('DESCRIPTION', 'Label')\n      let lines = list.detail.split('\\n').map(s => '  ' + s)\n      highlighter.addLine(lines.join('\\n') + '\\n')\n    }\n    if (hasOptions) {\n      highlighter.addLine('ARGUMENTS', 'Label')\n      highlighter.addLine('')\n      for (let opt of list.options) {\n        highlighter.addLine(opt.name, 'Special')\n        highlighter.addLine(`  ${opt.description}`)\n        highlighter.addLine('')\n      }\n      highlighter.addLine('')\n    }\n    let config = workspace.getConfiguration(`list.source.${list.name}`)\n    if (Object.keys(config).length) {\n      highlighter.addLine('CONFIGURATIONS', 'Label')\n      highlighter.addLine('')\n      for (let key of Object.keys(config)) {\n        let val = config[key]\n        let name = `list.source.${list.name}.${key}`\n        let description = defaultValue(workspace.configurations.getDescription(name), key)\n        highlighter.addLine(`  \"${name}\"`, 'MoreMsg')\n        highlighter.addText(` - ${description} current value: `)\n        highlighter.addText(JSON.stringify(val), 'Special')\n      }\n      highlighter.addLine('')\n    }\n    highlighter.addLine('ACTIONS', 'Label')\n    highlighter.addLine(`  ${list.actions.map(o => o.name).join(', ')}`)\n    highlighter.addLine('')\n    highlighter.addLine(`see ':h coc-list-options' for available list options.`, 'Comment')\n    nvim.pauseNotification()\n    highlighter.render(buf, 0, -1)\n    nvim.command('setl nomod', true)\n    nvim.command('setl nomodifiable', true)\n    nvim.command('normal! gg', true)\n    nvim.command('nnoremap <buffer> q :bd!<CR>', true)\n    await nvim.resumeNotification()\n  }\n\n  public async switchMatcher(): Promise<void> {\n    let { matcher, interactive } = this.listOptions\n    if (interactive) return\n    const list: Matcher[] = ['fuzzy', 'strict', 'regex']\n    let idx = list.indexOf(matcher) + 1\n    if (idx >= list.length) idx = 0\n    this.listOptions.matcher = list[idx]\n    this.prompt.matcher = list[idx]\n    await this.worker.drawItems()\n  }\n\n  private updateStatus(): void {\n    let { ui, list, nvim } = this\n    if (!ui.bufnr) return\n    let buf = nvim.createBuffer(ui.bufnr)\n    let status = {\n      mode: this.prompt.mode.toUpperCase(),\n      args: this.args.join(' '),\n      name: list.name,\n      cwd: this.cwd,\n      loading: this.loadingFrame,\n      total: this.worker.length\n    }\n    buf.setVar('list_status', status, true)\n    nvim.command('redraws', true)\n  }\n\n  public get context(): ListContext {\n    let { winid } = this.ui\n    return {\n      options: this.listOptions,\n      args: this.listArgs,\n      input: this.prompt.input,\n      cwd: workspace.cwd,\n      window: this.window,\n      buffer: this.buffer,\n      listWindow: winid ? this.nvim.createWindow(winid) : undefined\n    }\n  }\n\n  private get window(): Window | undefined {\n    return this.targetWinid ? this.nvim.createWindow(this.targetWinid) : undefined\n  }\n\n  private get buffer(): Buffer | undefined {\n    return this.targetBufnr ? this.nvim.createBuffer(this.targetBufnr) : undefined\n  }\n\n  public onMouseEvent(key): Promise<void> {\n    switch (key) {\n      case '<LeftMouse>':\n        return this.ui.onMouse('mouseDown')\n      case '<LeftDrag>':\n        return this.ui.onMouse('mouseDrag')\n      case '<LeftRelease>':\n        return this.ui.onMouse('mouseUp')\n      case '<2-LeftMouse>':\n        return this.ui.onMouse('doubleClick')\n    }\n  }\n\n  public async doNumberSelect(ch: string): Promise<boolean> {\n    if (!this.listOptions.numberSelect) return false\n    let code = ch.charCodeAt(0)\n    if (code >= 48 && code <= 57) {\n      let n = Number(ch)\n      if (n == 0) n = 10\n      if (this.ui.length >= n) {\n        this.nvim.pauseNotification()\n        this.ui.setCursor(n)\n        await this.nvim.resumeNotification()\n        await this.doAction()\n        return true\n      }\n    }\n    return false\n  }\n\n  public jumpBack(): void {\n    let { targetWinid, nvim } = this\n    if (targetWinid) {\n      nvim.pauseNotification()\n      nvim.call('coc#prompt#stop_prompt', ['list'], true)\n      this.nvim.call('win_gotoid', [targetWinid], true)\n      nvim.resumeNotification(false, true)\n    }\n  }\n\n  public async resume(): Promise<void> {\n    if (this.winid) await this.hide()\n    let res = await this.nvim.eval(`[win_getid(),bufnr(\"%\"),${workspace.isVim ? 'winheight(\"%\")' : 'nvim_win_get_height(0)'}]`)\n    this.hidden = false\n    this.targetWinid = res[0]\n    this.targetBufnr = res[1]\n    this.savedHeight = res[2]\n    this.prompt.start()\n    await this.ui.resume()\n    if (this.listOptions.autoPreview) {\n      await this.doAction('preview')\n    }\n  }\n\n  private async doItemAction(items: ListItem[], action: ListAction): Promise<void> {\n    let { noQuit, position } = this.listOptions\n    let { nvim } = this\n    let persistAction = action.persist === true || action.name == 'preview'\n    if (position === 'tab' && action.tabPersist) persistAction = true\n    let persist = this.winid && (persistAction || noQuit)\n    if (persist) {\n      if (!persistAction) {\n        nvim.pauseNotification()\n        nvim.call('coc#prompt#stop_prompt', ['list'], true)\n        nvim.call('win_gotoid', [this.context.window.id], true)\n        await nvim.resumeNotification()\n      }\n    } else {\n      await this.hide()\n    }\n    if (action.multiple) {\n      await Promise.resolve(action.execute(items, this.context))\n    } else if (action.parallel) {\n      await Promise.all(items.map(item => Promise.resolve(action.execute(item, this.context))))\n    } else {\n      for (let item of items) {\n        await Promise.resolve(action.execute(item, this.context))\n      }\n    }\n    if (persist) this.ui.restoreWindow()\n    if (action.reload && persist) {\n      await this.reloadItems()\n    } else if (persist) {\n      this.nvim.command('redraw', true)\n    }\n  }\n\n  public onInputChange(): void {\n    if (this.timer) clearTimeout(this.timer)\n    this.ui.cancel()\n    this.history.filter()\n    this.listOptions.input = this.prompt.input\n    // reload or filter items\n    if (this.listOptions.interactive) {\n      this.worker.stop()\n      this.timer = setTimeout(async () => {\n        await this.worker.loadItems(this.context)\n      }, listConfiguration.debounceTime)\n    } else {\n      void this.worker.drawItems()\n    }\n  }\n\n  public dispose(): void {\n    void this.hide(true)\n    disposeAll(this.disposables)\n    this.worker.dispose()\n    this.ui.dispose()\n  }\n}\n"
  },
  {
    "path": "src/list/source/commands.ts",
    "content": "'use strict'\nimport commandManager from '../../commands'\nimport Mru from '../../model/mru'\nimport { ListContext, ListItem } from '../types'\nimport { Extensions as ExtensionsInfo, IExtensionRegistry } from '../../util/extensionRegistry'\nimport { Registry } from '../../util/registry'\nimport workspace from '../../workspace'\nimport BasicList from '../basic'\nimport { formatListItems, UnformattedListItem } from '../formatting'\nimport { toText } from '../../util/string'\n\nconst extensionRegistry = Registry.as<IExtensionRegistry>(ExtensionsInfo.ExtensionContribution)\n\nexport default class CommandsList extends BasicList {\n  public defaultAction = 'run'\n  public description = 'registered commands of coc.nvim'\n  public readonly name = 'commands'\n  private mru: Mru\n\n  constructor() {\n    super()\n    this.mru = workspace.createMru('commands')\n    this.addAction('run', async item => {\n      await commandManager.fireCommand(item.data.cmd)\n    })\n    this.addAction('append', async item => {\n      let { cmd } = item.data\n      await workspace.nvim.feedKeys(`:CocCommand ${cmd} `, 'n', false)\n    })\n  }\n\n  public async loadItems(_context: ListContext): Promise<ListItem[]> {\n    let items: UnformattedListItem[] = []\n    let mruList = await this.mru.load()\n    let ids: Set<string> = new Set()\n    for (const obj of extensionRegistry.onCommands.concat(commandManager.commandList)) {\n      let { id, title } = obj\n      if (ids.has(id)) continue\n      ids.add(id)\n      let desc = toText(title)\n      items.push({\n        label: [id, desc],\n        filterText: id + ' ' + desc,\n        data: { cmd: id, score: score(mruList, id) }\n      })\n    }\n    items.sort((a, b) => b.data.score - a.data.score)\n    return formatListItems(this.alignColumns, items)\n  }\n\n  public doHighlight(): void {\n    let { nvim } = this\n    nvim.pauseNotification()\n    nvim.command('syntax match CocCommandsTitle /\\\\t.*$/ contained containedin=CocCommandsLine', true)\n    nvim.command('highlight default link CocCommandsTitle Comment', true)\n    nvim.resumeNotification(false, true)\n  }\n}\n\nfunction score(list: string[], key: string): number {\n  let idx = list.indexOf(key)\n  return idx == -1 ? -1 : list.length - idx\n}\n"
  },
  {
    "path": "src/list/source/diagnostics.ts",
    "content": "'use strict'\nimport { URI } from 'vscode-uri'\nimport diagnosticManager, { DiagnosticItem } from '../../diagnostic/manager'\nimport { severityLevel } from '../../diagnostic/util'\nimport { defaultValue } from '../../util'\nimport { isParentFolder } from '../../util/fs'\nimport { path } from '../../util/node'\nimport workspace from '../../workspace'\nimport { formatListItems, formatPath, PathFormatting, UnformattedListItem } from '../formatting'\nimport { ListManager } from '../manager'\nimport { ListArgument, ListContext, ListItem } from '../types'\nimport LocationList from './location'\n\nexport function convertToLabel(item: DiagnosticItem, cwd: string, includeCode: boolean, pathFormat: PathFormatting = 'full'): string[] {\n  const file = isParentFolder(cwd, item.file) ? path.relative(cwd, item.file) : item.file\n  const formattedPath = formatPath(pathFormat, file)\n  const formattedPosition = pathFormat !== \"hidden\" ? [`${formattedPath}:${item.lnum}`] : []\n  const source = includeCode ? `[${item.source} ${defaultValue(item.code, '')}]` : item.source\n  return [...formattedPosition, source, item.severity, item.message]\n}\n\nexport default class DiagnosticsList extends LocationList {\n  public readonly defaultAction = 'open'\n  public readonly description = 'diagnostics of current workspace'\n  public name = 'diagnostics'\n  public options: ListArgument[] = [{\n    name: '--buffer',\n    hasValue: false,\n    description: 'list diagnostics of current buffer only',\n  }, {\n    name: '--workspace-folder',\n    hasValue: false,\n    description: 'list diagnostics of current workspace folder only',\n  }, {\n    name: '-l, -level LEVEL',\n    hasValue: true,\n    description: 'filter diagnostics by diagnostic level, could be \"error\", \"warning\" and \"information\"'\n  }]\n  public constructor(manager: ListManager, event = true) {\n    super()\n    if (event) {\n      diagnosticManager.onDidRefresh(async () => {\n        let session = manager.getSession('diagnostics')\n        if (session) await session.reloadItems()\n      }, null, this.disposables)\n    }\n  }\n\n  public async filterDiagnostics(parsedArgs: { [key: string]: string | boolean }): Promise<DiagnosticItem[]> {\n    let list = await diagnosticManager.getDiagnosticList()\n    if (parsedArgs['workspace-folder']) {\n      const folder = workspace.getWorkspaceFolder(workspace.root)\n      if (folder) {\n        const normalized = URI.parse(folder.uri)\n        list = list.filter(item => isParentFolder(normalized.fsPath, item.file))\n      }\n    } else if (parsedArgs.buffer) {\n      const doc = await workspace.document\n      const normalized = URI.parse(doc.uri)\n      list = list.filter(item => item.file === normalized.fsPath)\n    }\n    if (typeof parsedArgs.level === 'string') {\n      let level = severityLevel(parsedArgs.level)\n      list = list.filter(item => item.level <= level)\n    }\n    return list\n  }\n\n  public async loadItems(context: ListContext): Promise<ListItem[]> {\n    let { cwd, args } = context\n    const parsedArgs = this.parseArguments(args)\n    let list = await this.filterDiagnostics(parsedArgs)\n    const config = this.getConfig()\n    const includeCode = config.get<boolean>('includeCode', true)\n    const pathFormat = config.get<PathFormatting>('pathFormat', \"full\")\n    const unformatted: UnformattedListItem[] = list.map(item => {\n      return {\n        label: convertToLabel(item, cwd, includeCode, pathFormat),\n        location: item.location,\n      }\n    })\n    return formatListItems(this.alignColumns, unformatted)\n  }\n\n  public doHighlight(): void {\n    let { nvim } = this\n    nvim.pauseNotification()\n    nvim.command('syntax match CocDiagnosticsFile /\\\\v^\\\\s*\\\\S+/ contained containedin=CocDiagnosticsLine', true)\n    nvim.command('syntax match CocDiagnosticsError /\\\\tError\\\\s*\\\\t/ contained containedin=CocDiagnosticsLine', true)\n    nvim.command('syntax match CocDiagnosticsWarning /\\\\tWarning\\\\s*\\\\t/ contained containedin=CocDiagnosticsLine', true)\n    nvim.command('syntax match CocDiagnosticsInfo /\\\\tInformation\\\\s*\\\\t/ contained containedin=CocDiagnosticsLine', true)\n    nvim.command('syntax match CocDiagnosticsHint /\\\\tHint\\\\s*\\\\t/ contained containedin=CocDiagnosticsLine', true)\n    nvim.command('highlight default link CocDiagnosticsFile Comment', true)\n    nvim.command('highlight default link CocDiagnosticsError CocErrorSign', true)\n    nvim.command('highlight default link CocDiagnosticsWarning CocWarningSign', true)\n    nvim.command('highlight default link CocDiagnosticsInfo CocInfoSign', true)\n    nvim.command('highlight default link CocDiagnosticsHint CocHintSign', true)\n    nvim.resumeNotification(false, true)\n  }\n}\n"
  },
  {
    "path": "src/list/source/extensions.ts",
    "content": "'use strict'\nimport { URI } from 'vscode-uri'\nimport { ExtensionManager } from '../../extension/manager'\nimport extensions from '../../extension'\nimport { getConditionValue, wait } from '../../util'\nimport { fs, os, path } from '../../util/node'\nimport workspace from '../../workspace'\nimport BasicList from '../basic'\nimport { formatListItems, UnformattedListItem } from '../formatting'\nimport { ListItem } from '../types'\nconst delay = getConditionValue(50, 0)\n\ninterface ItemToSort {\n  data: {\n    priority?: number,\n    id?: string\n  }\n}\n\nexport default class ExtensionList extends BasicList {\n  public defaultAction = 'toggle'\n  public description = 'manage coc extensions'\n  public name = 'extensions'\n\n  constructor(private manager: ExtensionManager) {\n    super()\n    this.addAction('toggle', async item => {\n      let { id, state } = item.data\n      if (state == 'disabled') return\n      if (state == 'activated') {\n        await this.manager.deactivate(id)\n      } else {\n        await this.manager.activate(id)\n      }\n      await wait(delay)\n    }, { persist: true, reload: true, parallel: true })\n\n    this.addAction('configuration', async item => {\n      let { root } = item.data\n      let jsonFile = path.join(root, 'package.json')\n      if (fs.existsSync(jsonFile)) {\n        let lines = fs.readFileSync(jsonFile, 'utf8').split(/\\r?\\n/)\n        let idx = lines.findIndex(s => s.includes('\"contributes\"'))\n        await workspace.jumpTo(URI.file(jsonFile), { line: idx == -1 ? 0 : idx, character: 0 })\n      }\n    })\n\n    this.addAction('open', async item => {\n      let { root } = item.data\n      workspace.nvim.call('coc#ui#open_url', [root], true)\n    })\n\n    this.addAction('disable', async item => {\n      let { id, state } = item.data\n      if (state !== 'disabled') await this.manager.toggleExtension(id)\n    }, { persist: true, reload: true, parallel: true })\n\n    this.addAction('enable', async item => {\n      let { id, state } = item.data\n      if (state == 'disabled') await this.manager.toggleExtension(id)\n    }, { persist: true, reload: true, parallel: true })\n\n    this.addAction('lock', async item => {\n      let { id } = item.data\n      this.manager.states.setLocked(id, true)\n    }, { persist: true, reload: true })\n\n    this.addAction('help', async item => {\n      let { root } = item.data\n      let files = fs.readdirSync(root, { encoding: 'utf8' })\n      let file = files.find(f => /^readme/i.test(f))\n      if (file) await workspace.jumpTo(URI.file(path.join(root, file)))\n    })\n\n    this.addAction('reload', async item => {\n      let { id } = item.data\n      await this.manager.reloadExtension(id)\n    }, { persist: true, reload: true })\n\n    this.addMultipleAction('uninstall', async items => {\n      let ids = []\n      for (let item of items) {\n        if (item.data.isLocal) continue\n        ids.push(item.data.id)\n      }\n      await this.manager.uninstallExtensions(ids)\n    })\n  }\n\n  public async loadItems(): Promise<ListItem[]> {\n    let items: (UnformattedListItem & ItemToSort)[] = []\n    let list = await extensions.getExtensionStates()\n    for (let stat of list) {\n      let prefix = getExtensionPrefix(stat.state)\n      let root = fs.realpathSync(stat.root)\n      let locked = stat.isLocked\n      items.push({\n        label: [`${prefix} ${stat.id}${locked ? ' ' : ''}`, ...(stat.isLocal ? ['[RTP]'] : []), stat.version, root.replace(os.homedir(), '~')],\n        filterText: stat.id,\n        data: {\n          id: stat.id,\n          root,\n          state: stat.state,\n          isLocal: stat.isLocal,\n          priority: getExtensionPriority(stat.state)\n        }\n      })\n    }\n    items.sort(sortExtensionItem)\n    return formatListItems(this.alignColumns, items)\n  }\n\n  public doHighlight(): void {\n    let { nvim } = this\n    nvim.pauseNotification()\n    nvim.command('syntax match CocExtensionsActivated /\\\\v^\\\\*/ contained containedin=CocExtensionsLine', true)\n    nvim.command('syntax match CocExtensionsLoaded /\\\\v^\\\\+/ contained containedin=CocExtensionsLine', true)\n    nvim.command('syntax match CocExtensionsDisabled /\\\\v^-/ contained containedin=CocExtensionsLine', true)\n    nvim.command('syntax match CocExtensionsName /\\\\v%3c\\\\S+/ contained containedin=CocExtensionsLine', true)\n    nvim.command('syntax match CocExtensionsRoot /\\\\v\\\\t[^\\\\t]*$/ contained containedin=CocExtensionsLine', true)\n    nvim.command('syntax match CocExtensionsLocal /\\\\v\\\\[RTP\\\\]/ contained containedin=CocExtensionsLine', true)\n    nvim.command('highlight default link CocExtensionsActivated Special', true)\n    nvim.command('highlight default link CocExtensionsLoaded Normal', true)\n    nvim.command('highlight default link CocExtensionsDisabled Comment', true)\n    nvim.command('highlight default link CocExtensionsName String', true)\n    nvim.command('highlight default link CocExtensionsLocal MoreMsg', true)\n    nvim.command('highlight default link CocExtensionsRoot Comment', true)\n    nvim.resumeNotification(false, true)\n  }\n}\n\nexport function sortExtensionItem(a: ItemToSort, b: ItemToSort): number {\n  if (a.data.priority != b.data.priority) {\n    return b.data.priority - a.data.priority\n  }\n  return b.data.id > a.data.id ? 1 : -1\n}\n\nexport function getExtensionPrefix(state: string): string {\n  let prefix = '+'\n  if (state == 'disabled') {\n    prefix = '-'\n  } else if (state == 'activated') {\n    prefix = '*'\n  } else if (state == 'unknown') {\n    prefix = '?'\n  }\n  return prefix\n}\n\nexport function getExtensionPriority(stat: string): number {\n  switch (stat) {\n    case 'unknown':\n      return 2\n    case 'activated':\n      return 1\n    case 'disabled':\n      return -1\n    default:\n      return 0\n  }\n}\n"
  },
  {
    "path": "src/list/source/folders.ts",
    "content": "'use strict'\nimport { URI } from 'vscode-uri'\nimport { isDirectory } from '../../util/fs'\nimport window from '../../window'\nimport workspace from '../../workspace'\nimport BasicList from '../basic'\nimport { ListItem } from '../types'\n\nexport default class FoldList extends BasicList {\n  public defaultAction = 'edit'\n  public description = 'list of current workspace folders'\n  public name = 'folders'\n\n  constructor() {\n    super()\n\n    this.addAction('edit', async item => {\n      let newPath = await this.nvim.call('input', ['Folder: ', item.label, 'dir']) as string\n      if (!isDirectory(newPath)) {\n        void window.showWarningMessage(`invalid path: ${newPath}`)\n        return\n      }\n      workspace.workspaceFolderControl.renameWorkspaceFolder(item.label, newPath)\n    })\n\n    this.addAction('delete', async item => {\n      workspace.workspaceFolderControl.removeWorkspaceFolder(item.label)\n    }, { reload: true, persist: true })\n\n    this.addAction('newfile', async (item, context) => {\n      let file = await window.requestInput('File name', item.label + '/')\n      if (!file) return\n      await workspace.createFile(file, { overwrite: false, ignoreIfExists: true })\n      await this.jumpTo(URI.file(file).toString(), null, context)\n    })\n  }\n\n  public async loadItems(): Promise<ListItem[]> {\n    return workspace.folderPaths.map(p => ({ label: p }))\n  }\n}\n"
  },
  {
    "path": "src/list/source/links.ts",
    "content": "'use strict'\nimport { Location } from 'vscode-languageserver-types'\nimport languages from '../../languages'\nimport type { CancellationToken } from '../../util/protocol'\nimport workspace from '../../workspace'\nimport BasicList from '../basic'\nimport { formatUri } from '../formatting'\nimport { ListContext, ListItem } from '../types'\n\nexport default class LinksList extends BasicList {\n  public defaultAction = 'open'\n  public description = 'links of current buffer'\n  public name = 'links'\n\n  constructor() {\n    super()\n\n    this.addAction('open', async item => {\n      let { target } = item.data\n      await workspace.openResource(target)\n    })\n\n    this.addAction('jump', async item => {\n      let { location } = item.data\n      await workspace.jumpTo(location.uri, location.range.start)\n    })\n  }\n\n  public async loadItems(context: ListContext, token: CancellationToken): Promise<ListItem[]> {\n    let buf = await context.window.buffer\n    let doc = workspace.getAttachedDocument(buf.id)\n    let items: ListItem[] = []\n    let links = await languages.getDocumentLinks(doc.textDocument, token)\n    if (links == null) throw new Error('Links provider not found.')\n    for (let link of links) {\n      link = link.target ? link : await languages.resolveDocumentLink(link, token)\n      if (link.target) {\n        items.push({\n          label: formatUri(link.target, workspace.cwd),\n          data: {\n            target: link.target,\n            location: Location.create(doc.uri, link.range)\n          }\n        })\n      }\n    }\n    return items\n  }\n}\n"
  },
  {
    "path": "src/list/source/lists.ts",
    "content": "'use strict'\nimport Mru from '../../model/mru'\nimport { toText } from '../../util/string'\nimport BasicList from '../basic'\nimport { formatListItems, UnformattedListItem } from '../formatting'\nimport { IList, ListContext, ListItem } from '../types'\n\nexport default class ListsList extends BasicList {\n  public readonly name = 'lists'\n  public readonly defaultAction = 'open'\n  public readonly description = 'registered lists of coc.nvim'\n  private mru: Mru = new Mru('lists')\n\n  constructor(private readonly listMap: Map<string, IList>) {\n    super()\n\n    this.addAction('open', async item => {\n      let { name } = item.data\n      await this.mru.add(name)\n      setTimeout(() => {\n        this.nvim.command(`CocList ${name}`, true)\n      }, 50)\n    })\n  }\n\n  public async loadItems(_context: ListContext): Promise<ListItem[]> {\n    let items: UnformattedListItem[] = []\n    let mruList = await this.mru.load()\n    for (let list of this.listMap.values()) {\n      if (list.name == 'lists') continue\n      items.push({\n        label: [list.name, toText(list.description)],\n        data: {\n          name: list.name,\n          interactive: list.interactive,\n          score: mruScore(mruList, list.name)\n        }\n      })\n    }\n    items.sort((a, b) => b.data.score - a.data.score)\n    return formatListItems(this.alignColumns, items)\n  }\n\n  public doHighlight(): void {\n    let { nvim } = this\n    nvim.pauseNotification()\n    nvim.command('syntax match CocListsDesc /\\\\t.*$/ contained containedin=CocListsLine', true)\n    nvim.command('highlight default link CocListsDesc Comment', true)\n    nvim.resumeNotification(false, true)\n  }\n}\n\nexport function mruScore(list: string[], key: string): number {\n  let idx = list.indexOf(key)\n  return idx == -1 ? -1 : list.length - idx\n}\n"
  },
  {
    "path": "src/list/source/location.ts",
    "content": "'use strict'\nimport { CancellationToken } from 'vscode-languageserver-protocol'\nimport { Location, Position, Range } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport commands from '../../commands'\nimport { AnsiHighlight, LocationWithTarget, QuickfixItem } from '../../types'\nimport { toArray } from '../../util/array'\nimport { isParentFolder } from '../../util/fs'\nimport { path } from '../../util/node'\nimport { byteLength } from '../../util/string'\nimport BasicList from '../basic'\nimport { ListContext, ListItem } from '../types'\n\nexport default class LocationList extends BasicList {\n  public defaultAction = 'open'\n  public description = 'show locations saved by g:coc_jump_locations variable'\n  public name = 'location'\n\n  constructor() {\n    super()\n    this.createAction({\n      name: 'refactor',\n      multiple: true,\n      execute: async (items: ListItem[]) => {\n        let locations = items.map(o => o.location)\n        await commands.executeCommand('editor.action.showRefactor', locations)\n      }\n    })\n    this.addLocationActions()\n  }\n\n  public formatFilepath(file: string): string {\n    if (typeof global.formatFilepath === 'function') {\n      return global.formatFilepath(file) + ''\n    }\n    return file\n  }\n\n  public async loadItems(context: ListContext, _token: CancellationToken): Promise<ListItem[]> {\n    // filename, lnum, col, text, type\n    let locs = await this.nvim.getVar('coc_jump_locations') as QuickfixItem[]\n    locs = toArray(locs)\n    let bufnr = context.buffer.id\n    let ignoreFilepath = locs.every(o => o.bufnr == bufnr)\n    let items: ListItem[] = locs.map(loc => {\n      let filename = ignoreFilepath ? '' : loc.filename\n      if (filename.length > 0 && path.isAbsolute(filename)) {\n        filename = isParentFolder(context.cwd, filename) ? path.relative(context.cwd, filename) : filename\n      }\n      return this.createItem(filename, loc)\n    })\n    return items\n  }\n\n  private createItem(filename: string, loc: QuickfixItem): ListItem {\n    let uri = loc.uri ?? URI.file(loc.filename).toString()\n    let label = ''\n    const ansiHighlights: AnsiHighlight[] = []\n    let start = 0\n    filename = this.formatFilepath(filename)\n    if (filename.length > 0) {\n      label = filename + ' '\n      ansiHighlights.push({ span: [start, start + byteLength(filename)], hlGroup: 'Directory' })\n    }\n    start = byteLength(label)\n    let lnum = loc.lnum ?? loc.range.start.line + 1\n    let col = loc.col ?? byteLength(loc.text.slice(0, loc.range.start.character)) + 1\n    let position = `|${loc.type ? loc.type + ' ' : ''}${lnum} Col ${col}|`\n    label += position\n    ansiHighlights.push({ span: [start, start + byteLength(position)], hlGroup: 'LineNr' })\n    if (loc.type) {\n      let hl = loc.type.toLowerCase() === 'error' ? 'Error' : 'WarningMsg'\n      ansiHighlights.push({ span: [start + 1, start + byteLength(loc.type)], hlGroup: hl })\n    }\n    if (loc.range && loc.range.start.line == loc.range.end.line) {\n      let len = byteLength(label) + 1\n      let start = len + byteLength(loc.text.slice(0, loc.range.start.character))\n      let end = len + byteLength(loc.text.slice(0, loc.range.end.character))\n      ansiHighlights.push({ span: [start, end], hlGroup: 'Search' })\n    }\n    label += ' ' + loc.text\n    let filterText = `${filename}${loc.text.trim()}`\n    let location: LocationWithTarget\n    if (loc.range) {\n      location = Location.create(uri, loc.range)\n    } else {\n      let start = Position.create(loc.lnum - 1, loc.col - 1)\n      let end = Position.create((loc.end_lnum ?? loc.lnum) - 1, (loc.end_col ?? loc.col) - 1)\n      location = Location.create(uri, Range.create(start, end))\n    }\n    location.targetRange = loc.targetRange ? loc.targetRange : Range.create(lnum - 1, 0, lnum - 1, 99)\n    return {\n      label,\n      location,\n      filterText,\n      ansiHighlights,\n    }\n  }\n}\n"
  },
  {
    "path": "src/list/source/notifications.ts",
    "content": "import window from '../../window'\nimport BasicList from '../basic'\nimport { ListItem } from '../types'\n\nexport default class NotificationsList extends BasicList {\n  public readonly defaultAction = 'clear'\n  public readonly description = 'notifications history'\n  public readonly name = 'notifications'\n\n  constructor() {\n    super()\n    this.addAction('clear', async () => {\n      window.notifications.clearHistory()\n    })\n  }\n\n  public async loadItems(): Promise<ListItem[]> {\n    return window.notifications.history.map(item => {\n      return {\n        label: `${item.time} ${item.kind.toUpperCase().padEnd(7)} ${item.message}`,\n        filterText: item.message\n      }\n    })\n  }\n\n  public doHighlight(): void {\n    let { nvim } = this\n    nvim.pauseNotification()\n    nvim.command('syntax match CocNotificationTime /\\\\v^\\\\s*\\\\S+/ contained containedin=CocNotificationsLine', true)\n    nvim.command('syntax match CocNotificationInfo /\\\\<INFO\\\\>/ contained containedin=CocNotificationsLine', true)\n    nvim.command('syntax match CocNotificationError /\\\\<ERROR\\\\>/ contained containedin=CocNotificationsLine', true)\n    nvim.command('syntax match CocNotificationWarning /\\\\<WARNING\\\\>/ contained containedin=CocNotificationsLine', true)\n    nvim.command('highlight default link CocNotificationTime Comment', true)\n    nvim.command('highlight default link CocNotificationInfo CocInfoSign', true)\n    nvim.command('highlight default link CocNotificationError CocErrorSign', true)\n    nvim.command('highlight default link CocNotificationWarning CocWarningSign', true)\n    nvim.resumeNotification(false, true)\n  }\n}\n"
  },
  {
    "path": "src/list/source/outline.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { DocumentSymbol, Location, Range, SymbolInformation } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport languages from '../../languages'\nimport Document from '../../model/document'\nimport { isFalsyOrEmpty } from '../../util/array'\nimport { getSymbolKind } from '../../util/convert'\nimport { writeFile } from '../../util/fs'\nimport { path, which } from '../../util/node'\nimport { compareRangesUsingStarts } from '../../util/position'\nimport { runCommand } from '../../util/processes'\nimport type { CancellationToken } from '../../util/protocol'\nimport workspace from '../../workspace'\nimport { formatListItems, UnformattedListItem } from '../formatting'\nimport { ListArgument, ListContext, ListItem } from '../types'\nimport LocationList from './location'\n\nexport default class Outline extends LocationList {\n  public readonly description = 'symbols of current document'\n  public name = 'outline'\n  public options: ListArgument[] = [{\n    name: '-k, -kind KIND',\n    hasValue: true,\n    description: 'filter symbol by kind',\n  }]\n\n  public async loadItems(context: ListContext, token: CancellationToken): Promise<ListItem[]> {\n    let document = workspace.getAttachedDocument(context.buffer.id)\n    let config = this.getConfig()\n    let ctagsFiletypes = config.get<string[]>('ctagsFiletypes', [])\n    let symbols: DocumentSymbol[] | null\n    let args = this.parseArguments(context.args)\n    let filterKind = args.kind ? args.kind.toString().toLowerCase() : null\n    if (!ctagsFiletypes.includes(document.filetype)) {\n      symbols = await languages.getDocumentSymbol(document.textDocument, token)\n    }\n    if (token.isCancellationRequested) return []\n    if (!symbols) return await loadCtagsSymbols(document, this.nvim, token)\n    if (isFalsyOrEmpty(symbols)) return []\n    let items = symbolsToListItems(symbols, document.uri, filterKind)\n    return formatListItems(this.alignColumns, items)\n  }\n\n  public doHighlight(): void {\n    let { nvim } = this\n    nvim.pauseNotification()\n    nvim.command('syntax match CocOutlineName /\\\\v\\\\s?[^\\\\t]+\\\\s/ contained containedin=CocOutlineLine', true)\n    nvim.command('syntax match CocOutlineIndentLine /\\\\v\\\\|/ contained containedin=CocOutlineLine,CocOutlineName', true)\n    nvim.command('syntax match CocOutlineKind /\\\\[\\\\w\\\\+\\\\]/ contained containedin=CocOutlineLine', true)\n    nvim.command('syntax match CocOutlineLine /\\\\d\\\\+$/ contained containedin=CocOutlineLine', true)\n    nvim.command('highlight default link CocOutlineName Normal', true)\n    nvim.command('highlight default link CocOutlineIndentLine Comment', true)\n    nvim.command('highlight default link CocOutlineKind Typedef', true)\n    nvim.command('highlight default link CocOutlineLine Comment', true)\n    nvim.resumeNotification(false, true)\n  }\n}\n\nexport function symbolsToListItems(symbols: DocumentSymbol[], uri: string, filterKind: string | null): UnformattedListItem[] {\n  let items: UnformattedListItem[] = []\n  const addSymbols = (symbols: DocumentSymbol[], level = 0) => {\n    symbols.sort((a, b) => {\n      return compareRangesUsingStarts(a.selectionRange, b.selectionRange)\n    })\n    for (let s of symbols) {\n      let kind = getSymbolKind(s.kind)\n      let location = Location.create(uri, s.selectionRange)\n      items.push({\n        label: [`${'| '.repeat(level)}${s.name}`, `[${kind}]`, `${s.range.start.line + 1}`],\n        filterText: getFilterText(s, filterKind),\n        location,\n        data: { kind }\n      })\n      if (!isFalsyOrEmpty(s.children)) {\n        addSymbols(s.children, level + 1)\n      }\n    }\n  }\n  addSymbols(symbols as DocumentSymbol[])\n  if (filterKind) {\n    items = items.filter(o => o.data.kind.toLowerCase().indexOf(filterKind) == 0)\n  }\n  return items\n}\n\nexport function getFilterText(s: DocumentSymbol | SymbolInformation, kind: string | null): string {\n  if (typeof kind === 'string' && kind.length > 0) return s.name\n  return `${s.name}${getSymbolKind(s.kind)}`\n}\n\nexport async function loadCtagsSymbols(document: Document, nvim: Neovim, token: CancellationToken): Promise<ListItem[]> {\n  if (!which.sync('ctags', { nothrow: true })) {\n    return []\n  }\n  let uri = URI.parse(document.uri)\n  let extname = path.extname(uri.fsPath)\n  let content = ''\n  let tempname = await nvim.call('tempname') as string\n  let filepath = `${tempname}.${extname}`\n  let cwd = path.dirname(tempname)\n  let escaped = await nvim.call('fnameescape', filepath) as string\n  await writeFile(escaped, document.getDocumentContent())\n  try {\n    content = await runCommand(`ctags -f - --excmd=number --language-force=${document.filetype} ${escaped}`, { cwd }, token)\n  } catch (e) {\n    // noop\n  }\n  if (!content.trim().length) {\n    content = await runCommand(`ctags -f - --excmd=number ${escaped}`, { cwd }, token)\n  }\n  content = content.trim()\n  if (!content) return []\n  return contentToItems(content, document)\n}\n\nexport function contentToItems(content: string, document: Document): ListItem[] {\n  let lines = content.split(/\\r?\\n/)\n  let items: ListItem[] = []\n  for (let line of lines) {\n    let parts = line.split('\\t')\n    if (parts.length < 4) continue\n    let lnum = Number(parts[2].replace(/;\"$/, ''))\n    let text = document.getline(lnum - 1)\n    let idx = text.indexOf(parts[0])\n    let start = idx == -1 ? 0 : idx\n    let range: Range = Range.create(lnum - 1, start, lnum - 1, start + parts[0].length)\n    items.push({\n      label: `${parts[0]} [${parts[3]}] ${lnum}`,\n      filterText: parts[0],\n      location: Location.create(document.uri, range),\n      data: { line: lnum }\n    })\n  }\n  items.sort((a, b) => a.data.line - b.data.line)\n  return items\n}\n"
  },
  {
    "path": "src/list/source/services.ts",
    "content": "'use strict'\nimport services from '../../services'\nimport { ListContext, ListItem } from '../types'\nimport { wait } from '../../util'\nimport BasicList from '../basic'\nimport { formatListItems } from '../formatting'\n\nexport default class ServicesList extends BasicList {\n  public defaultAction = 'toggle'\n  public description = 'registered services of coc.nvim'\n  public name = 'services'\n\n  constructor() {\n    super()\n\n    this.addAction('toggle', async item => {\n      let { id } = item.data\n      await services.toggle(id)\n      await wait(50)\n    }, { persist: true, reload: true })\n  }\n\n  public async loadItems(_context: ListContext): Promise<ListItem[]> {\n    let stats = services.getServiceStats()\n    return formatListItems(this.alignColumns, stats.map(stat => {\n      let prefix = stat.state == 'running' ? '*' : ' '\n      return {\n        label: [prefix, stat.id, `[${stat.state}]`, stat.languageIds.join(', ')],\n        data: { id: stat.id }\n      }\n    }))\n  }\n\n  public doHighlight(): void {\n    let { nvim } = this\n    nvim.pauseNotification()\n    nvim.command('syntax match CocServicesPrefix /\\\\v^./ contained containedin=CocServicesLine', true)\n    nvim.command('syntax match CocServicesName /\\\\v%3c\\\\S+/ contained containedin=CocServicesLine', true)\n    nvim.command('syntax match CocServicesStat /\\\\v\\\\t\\\\[\\\\w+\\\\]/ contained containedin=CocServicesLine', true)\n    nvim.command('syntax match CocServicesLanguages /\\\\v(\\\\])@<=.*$/ contained containedin=CocServicesLine', true)\n    nvim.command('highlight default link CocServicesPrefix Special', true)\n    nvim.command('highlight default link CocServicesName Type', true)\n    nvim.command('highlight default link CocServicesStat Statement', true)\n    nvim.command('highlight default link CocServicesLanguages Comment', true)\n    nvim.resumeNotification(false, true)\n  }\n}\n"
  },
  {
    "path": "src/list/source/sources.ts",
    "content": "'use strict'\nimport { Location, Range } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport sources from '../../completion/sources'\nimport { ListItem } from '../types'\nimport BasicList from '../basic'\nimport { fixWidth } from '../formatting'\n\nexport default class SourcesList extends BasicList {\n  public readonly defaultAction = 'toggle'\n  public readonly description = 'registered completion sources'\n  public readonly name = 'sources'\n\n  constructor() {\n    super()\n    this.addAction('toggle', async item => {\n      let { name } = item.data\n      sources.toggleSource(name)\n    }, { persist: true, reload: true })\n\n    this.addAction('refresh', async item => {\n      let { name } = item.data\n      await sources.refresh(name)\n    }, { persist: true, reload: true })\n\n    this.addAction('open', async (item, context) => {\n      let { location } = item\n      if (location) await this.jumpTo(location, null, context)\n    })\n  }\n\n  public async loadItems(): Promise<ListItem[]> {\n    let stats = sources.sourceStats()\n    return stats.map(stat => {\n      let prefix = stat.disabled ? ' ' : '*'\n      let location: Location\n      if (stat.filepath) {\n        location = Location.create(URI.file(stat.filepath).toString(), Range.create(0, 0, 0, 0))\n      }\n      return {\n        label: `${prefix} ${fixWidth(stat.name, 22)} ${fixWidth('[' + stat.shortcut + ']', 10)} ${fixWidth(stat.triggerCharacters.join(''), 10)} ${fixWidth(stat.priority.toString(), 3)} ${stat.filetypes.join(',')}`,\n        location,\n        data: { name: stat.name }\n      }\n    })\n  }\n\n  public doHighlight(): void {\n    let { nvim } = this\n    nvim.pauseNotification()\n    nvim.command('syntax match CocSourcesPrefix /\\\\v^./ contained containedin=CocSourcesLine', true)\n    nvim.command('syntax match CocSourcesName /\\\\v%3c\\\\S+/ contained containedin=CocSourcesLine', true)\n    nvim.command('syntax match CocSourcesType /\\\\v%25v.*%36v/ contained containedin=CocSourcesLine', true)\n    nvim.command('syntax match CocSourcesPriority /\\\\v%46v.*%52v/ contained containedin=CocSourcesLine', true)\n    nvim.command('syntax match CocSourcesFileTypes /\\\\v\\\\S+$/ contained containedin=CocSourcesLine', true)\n    nvim.command('highlight default link CocSourcesPrefix Special', true)\n    nvim.command('highlight default link CocSourcesName Type', true)\n    nvim.command('highlight default link CocSourcesPriority Number', true)\n    nvim.command('highlight default link CocSourcesFileTypes Comment', true)\n    nvim.command('highlight default link CocSourcesType Statement', true)\n    nvim.resumeNotification(false, true)\n  }\n}\n"
  },
  {
    "path": "src/list/source/symbols.ts",
    "content": "'use strict'\nimport { Location, Range, SymbolTag, WorkspaceSymbol } from 'vscode-languageserver-types'\nimport languages, { ProviderName } from '../../languages'\nimport { AnsiHighlight, LocationWithTarget } from '../../types'\nimport { defaultValue } from '../../util'\nimport { toArray } from '../../util/array'\nimport { getSymbolKind } from '../../util/convert'\nimport { minimatch } from '../../util/node'\nimport { CancellationToken, CancellationTokenSource } from '../../util/protocol'\nimport { byteLength } from '../../util/string'\nimport workspace from '../../workspace'\nimport { formatUri } from '../formatting'\nimport { ListContext, ListItem } from '../types'\nimport LocationList from './location'\n\ninterface ItemToSort {\n  data: {\n    score?: number\n    kind?: number\n    file?: string\n  }\n}\n\nexport default class Symbols extends LocationList {\n  public readonly interactive = true\n  public readonly description = 'search workspace symbols'\n  public readonly detail = 'Symbols list is provided by server, it works on interactive mode only.'\n  public fuzzyMatch = workspace.createFuzzyMatch()\n  public name = 'symbols'\n  public options = [{\n    name: '-k, -kind KIND',\n    description: 'Filter symbols by kind.',\n    hasValue: true\n  }]\n\n  public async loadItems(context: ListContext, token: CancellationToken): Promise<ListItem[]> {\n    let { input } = context\n    let args = this.parseArguments(context.args)\n    let filterKind = args.kind ? args.kind.toString().toLowerCase() : ''\n    if (!languages.hasProvider(ProviderName.WorkspaceSymbols, { uri: 'file:///1', languageId: '' })) {\n      throw new Error('No workspace symbols provider registered')\n    }\n    let symbols = await languages.getWorkspaceSymbols(input, token)\n    if (token.isCancellationRequested) return []\n    let config = this.getConfig()\n    let excludes = config.get<string[]>('excludes', [])\n    let items: (ListItem & ItemToSort)[] = []\n    this.fuzzyMatch.setPattern(input, true)\n    for (let s of symbols) {\n      let kind = getSymbolKind(s.kind)\n      if (filterKind && kind.toLowerCase() != filterKind) {\n        continue\n      }\n      let file = formatUri(s.location.uri, workspace.cwd)\n      if (excludes.some(p => minimatch(file, p))) {\n        continue\n      }\n      let item = this.createListItem(input, s, kind, file)\n      items.push(item)\n    }\n    this.fuzzyMatch.free()\n    items.sort(sortSymbolItems)\n    return items\n  }\n\n  public async resolveItem(item: ListItem): Promise<ListItem | null> {\n    let symbolItem = item.data.original as WorkspaceSymbol\n    // no need to resolve\n    if (!symbolItem || Location.is(symbolItem.location)) return null\n    let tokenSource = new CancellationTokenSource()\n    let resolved = await languages.resolveWorkspaceSymbol(symbolItem, tokenSource.token)\n    resolved = defaultValue(resolved, symbolItem)\n    if (Location.is(resolved.location)) {\n      symbolItem.location = resolved.location\n      item.location = toTargetLocation(resolved.location)\n    }\n    return item\n  }\n\n  public createListItem(input: string, item: WorkspaceSymbol, kind: string, file: string): ListItem & ItemToSort {\n    let { name } = item\n    let label = ''\n    let ansiHighlights: AnsiHighlight[] = []\n    // Normal Typedef Comment\n    let parts = [name, `[${kind}]`, this.formatFilepath(file)]\n    let highlights = ['Normal', 'Typedef', 'Comment']\n    for (let index = 0; index < parts.length; index++) {\n      const text = parts[index]\n      let start = byteLength(label)\n      label += text\n      let end = byteLength(label)\n      if (index != parts.length - 1) {\n        label += ' '\n      }\n      ansiHighlights.push({ span: [start, end], hlGroup: highlights[index] })\n      if (index === 0 && ((toArray(item.tags)).includes(SymbolTag.Deprecated)) || item['deprecated']) {\n        ansiHighlights.push({ span: [start, end], hlGroup: 'CocDeprecatedHighlight' })\n      }\n    }\n    let score = 0\n    if (input.length > 0) {\n      let result = this.fuzzyMatch.matchHighlights(name, 'CocListSearch')\n      if (result) {\n        score = result.score\n        ansiHighlights.push(...result.highlights)\n      }\n    }\n    return {\n      label,\n      filterText: '',\n      ansiHighlights,\n      location: toTargetLocation(item.location),\n      data: {\n        original: item,\n        input,\n        kind: item.kind,\n        file,\n        score,\n      }\n    }\n  }\n}\n\nexport function toTargetLocation(location: Location | { uri: string }): LocationWithTarget | Location {\n  if (!Location.is(location)) {\n    return Location.create(location.uri, Range.create(0, 0, 0, 0))\n  }\n  let loc: LocationWithTarget = Location.create(location.uri, Range.create(location.range.start, location.range.start))\n  loc.targetRange = location.range\n  return loc\n}\n\nexport function sortSymbolItems(a: ItemToSort, b: ItemToSort): number {\n  if (a.data.score != b.data.score) {\n    return b.data.score - a.data.score\n  }\n  if (a.data.kind != b.data.kind) {\n    return a.data.kind - b.data.kind\n  }\n  return a.data.file.length - b.data.file.length\n}\n"
  },
  {
    "path": "src/list/types.ts",
    "content": "import type { Buffer, Window } from '@chemzqm/neovim'\nimport type { ProviderResult } from '../provider/index'\nimport type { LocationWithTarget } from '../types'\nimport type { CancellationToken } from '../util/protocol'\n\nexport interface LocationWithLine {\n  uri: string\n  line: string\n  text?: string\n}\n\nexport interface ListItem {\n  label: string\n  filterText?: string\n  preselect?: boolean\n  location?: LocationWithTarget | LocationWithLine | string\n  data?: any\n  ansiHighlights?: AnsiHighlight[]\n  resolved?: boolean\n  /**\n   * A string that should be used when comparing this item\n   * with other items, only used for fuzzy filter.\n   */\n  sortText?: string\n  converted?: boolean\n}\n\nexport interface ListItemWithScore extends ListItem {\n  score?: number\n}\n\nexport interface AnsiHighlight {\n  span: [number, number]\n  hlGroup: string\n}\n\nexport interface ListItemsEvent {\n  items: ListItemWithScore[]\n  finished: boolean\n  sorted: boolean\n  append?: boolean\n  reload?: boolean\n}\n\nexport type ListMode = 'normal' | 'insert'\n\nexport type Matcher = 'strict' | 'fuzzy' | 'regex'\n\nexport interface ListOptions {\n  position: string\n  reverse: boolean\n  input: string\n  ignorecase: boolean\n  interactive: boolean\n  sort: boolean\n  mode: ListMode\n  matcher: Matcher\n  autoPreview: boolean\n  numberSelect: boolean\n  noQuit: boolean\n  first: boolean\n  height?: number\n}\n\nexport interface ListContext {\n  args: string[]\n  input: string\n  cwd: string\n  options: ListOptions\n  window: Window\n  buffer: Buffer\n  listWindow: Window\n}\n\nexport interface ListAction {\n  name: string\n  persist?: boolean\n  reload?: boolean\n  parallel?: boolean\n  multiple?: boolean\n  tabPersist?: boolean\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n  execute: Function\n}\n\nexport interface SingleListAction extends ListAction {\n  multiple?: false\n  execute: (item: ListItem, context: ListContext) => ProviderResult<void>\n}\n\nexport interface MultipleListAction extends ListAction {\n  multiple: boolean\n  execute: (item: ListItem[], context: ListContext) => ProviderResult<void>\n}\n\nexport interface ListTask {\n  on(event: 'data', callback: (item: ListItem) => void): void\n  on(event: 'end', callback: () => void): void\n  on(event: 'error', callback: (msg: string | Error) => void): void\n  dispose(): void\n}\n\nexport interface ListArgument {\n  key?: string\n  hasValue?: boolean\n  name: string\n  description: string\n}\n\nexport interface IList {\n  /**\n   * Unique name of list.\n   */\n  name: string\n  /**\n   * Action list.\n   */\n  actions: ListAction[]\n  /**\n   * Default action name.\n   */\n  defaultAction: string\n  /**\n   * Load list items.\n   */\n  loadItems(context: ListContext, token: CancellationToken): Promise<ListItem[] | ListTask | null | undefined>\n  /**\n   * Resolve list item.\n   */\n  resolveItem?(item: ListItem): Promise<ListItem | null>\n  /**\n   * Should be true when interactive is supported.\n   */\n  interactive?: boolean\n  /**\n   * Description of list.\n   */\n  description?: string\n  /**\n   * Detail description, shown in help.\n   */\n  detail?: string\n  /**\n   * Options supported by list.\n   */\n  options?: ListArgument[]\n  /**\n   * Highlight buffer by vim's syntax commands.\n   */\n  doHighlight?(): void\n  dispose?(): void\n}\n"
  },
  {
    "path": "src/list/ui.ts",
    "content": "'use strict'\nimport { Buffer, Neovim, Window } from '@chemzqm/neovim'\nimport events from '../events'\nimport { HighlightItem } from '../types'\nimport { defaultValue, disposeAll, getConditionValue } from '../util'\nimport { toArray } from '../util/array'\nimport { debounce } from '../util/node'\nimport { Disposable, Emitter, Event } from '../util/protocol'\nimport { Sequence } from '../util/sequence'\nimport { toText } from '../util/string'\nimport workspace from '../workspace'\nimport listConfiguration from './configuration'\nimport { ListItem, ListItemsEvent, ListOptions } from './types'\n\nexport type MouseEvent = 'mouseDown' | 'mouseDrag' | 'mouseUp' | 'doubleClick'\n\nexport interface MousePosition {\n  winid: number\n  lnum: number\n  col: number\n  current: boolean\n}\n\nexport interface HighlightGroup {\n  hlGroup: string\n  priority: number\n  pos: [number, number, number]\n}\n\nconst debounceTime = getConditionValue(100, 20)\n\nexport default class ListUI {\n  private window: Window\n  private height: number\n  public tabnr: number\n  private newTab = false\n  private reversed = false\n  private buffer: Buffer\n  private currIndex = 0\n  private items: ListItem[] = []\n  private disposables: Disposable[] = []\n  private selected: Set<number> = new Set()\n  private mouseDown: MousePosition\n  private sequence = new Sequence()\n  private _onDidChangeLine = new Emitter<number>()\n  private _onDidOpen = new Emitter<number>()\n  private _onDidClose = new Emitter<number>()\n  private _onDidLineChange = new Emitter<void>()\n  private _onDoubleClick = new Emitter<void>()\n  public readonly onDidChangeLine: Event<number> = this._onDidChangeLine.event\n  public readonly onDidLineChange: Event<void> = this._onDidLineChange.event\n  public readonly onDidOpen: Event<number> = this._onDidOpen.event\n  public readonly onDidClose: Event<number> = this._onDidClose.event\n  public readonly onDidDoubleClick: Event<void> = this._onDoubleClick.event\n\n  constructor(\n    private nvim: Neovim,\n    private name: string,\n    private listOptions: ListOptions\n  ) {\n    this.newTab = listOptions.position == 'tab'\n    this.reversed = listOptions.reverse === true\n    events.on('BufWinLeave', async bufnr => {\n      if (bufnr != this.bufnr || this.window == null) return\n      this.window = null\n      this._onDidClose.fire(bufnr)\n    }, null, this.disposables)\n    events.on('WinClosed', winid => {\n      if (this.winid == winid) {\n        let { bufnr } = this\n        this.window = null\n        this.buffer = null\n        this._onDidClose.fire(bufnr)\n      }\n    }, null, this.disposables)\n    events.on('CursorMoved', async (bufnr, cursor) => {\n      if (bufnr != this.bufnr) return\n      let idx = this.lnumToIndex(cursor[0])\n      this.onLineChange(idx)\n    }, null, this.disposables)\n    let debounced = debounce(async bufnr => {\n      if (bufnr != this.bufnr) return\n      let [winid, start, end] = await nvim.eval('[win_getid(),line(\"w0\"),line(\"w$\")]') as number[]\n      if (end < 300 || winid != this.winid) return\n      let h = end - start + 1\n      let s = this.lnumToIndex(start)\n      let e = this.lnumToIndex(start + h * 2)\n      nvim.pauseNotification()\n      this.doHighlight(s, e)\n      nvim.command('redraw', true)\n      nvim.resumeNotification(false, true)\n    }, debounceTime)\n    this.disposables.push({\n      dispose: () => {\n        debounced.clear()\n      }\n    })\n    events.on('CursorMoved', debounced, null, this.disposables)\n  }\n\n  public onDidChangeItems(ev: ListItemsEvent): void {\n    if (!ev.append) this.clearSelection()\n    this.sequence.run(async () => {\n      let { items, reload, append, finished, sorted } = ev\n      if (this.shouldSort && !sorted) {\n        // do sort\n        items = append ? this.items.concat(items) : items\n        reload = append == true\n        append = false\n        items.sort((a, b) => {\n          if (a.score != b.score) return b.score - a.score\n          if (a.sortText > b.sortText) return 1\n          return -1\n        })\n      }\n      if (append) {\n        await this.appendItems(items)\n      } else {\n        await this.drawItems(items, finished, reload)\n      }\n    })\n  }\n\n  public lnumToIndex(lnum: number): number {\n    let { reversed, length } = this\n    if (!reversed) return lnum - 1\n    return Math.max(0, length - lnum)\n  }\n\n  public indexToLnum(index: number): number {\n    let { reversed, length } = this\n    if (!reversed) return Math.min(index + 1, length)\n    return Math.max(Math.min(length, length - index), 1)\n  }\n\n  public get bufnr(): number | undefined {\n    return this.buffer?.id\n  }\n\n  public get winid(): number | undefined {\n    return this.window?.id\n  }\n  private get limitLines(): number {\n    return listConfiguration.get<number>('limitLines', Infinity)\n  }\n\n  private onLineChange(index: number): void {\n    if (this.currIndex == index) return\n    this.currIndex = index\n    this._onDidChangeLine.fire(index)\n  }\n\n  public get index(): number {\n    return this.currIndex\n  }\n\n  public getItem(index: number): ListItem | undefined {\n    return this.items[index]\n  }\n\n  public get item(): Promise<ListItem | null> {\n    let { window } = this\n    if (!window) return Promise.resolve(null)\n    return window.cursor.then(cursor => {\n      this.currIndex = this.lnumToIndex(cursor[0])\n      return this.items[this.currIndex]\n    })\n  }\n\n  public async echoMessage(item: ListItem): Promise<void> {\n    let { items } = this\n    let idx = items.indexOf(item)\n    let msg = `[${idx + 1}/${items.length}] ${toText(item.label)}`\n    this.nvim.callTimer('coc#ui#echo_lines', [[msg]], true)\n  }\n\n  public updateItem(item: ListItem, index: number): void {\n    if (!this.buffer || index >= this.length) return\n    let { nvim } = this\n    let lnum = this.indexToLnum(index)\n    nvim.pauseNotification()\n    this.buffer.setOption('modifiable', true, true)\n    nvim.call('setbufline', [this.bufnr, lnum, item.label], true)\n    this.doHighlight(index, index + 1)\n    this.buffer.setOption('modifiable', false, true)\n    nvim.resumeNotification(true, true)\n  }\n\n  public async getItems(): Promise<ListItem[]> {\n    if (this.length == 0 || !this.window) return []\n    let mode = await this.nvim.call('mode')\n    if (mode == 'v' || mode == 'V') {\n      let [start, end] = await this.getSelectedRange()\n      let res: ListItem[] = []\n      for (let i = start; i <= end; i++) {\n        let idx = this.lnumToIndex(i)\n        let item = this.items[idx]\n        if (item) res.push(item)\n      }\n      return res\n    }\n    let { selectedItems } = this\n    if (selectedItems.length) return selectedItems\n    let item = await this.item\n    return toArray(item)\n  }\n\n  public async onMouse(event: MouseEvent): Promise<void> {\n    let { nvim, window } = this\n    if (!window) return\n    let [winid, lnum, col] = await nvim.eval(`[v:mouse_winid,v:mouse_lnum,v:mouse_col]`) as [number, number, number]\n    if (event == 'mouseDown') {\n      this.mouseDown = { winid, lnum, col, current: winid == window.id }\n      return\n    }\n    let current = winid == window.id\n    if (current && event == 'doubleClick') {\n      this.setCursor(lnum)\n      this._onDoubleClick.fire()\n    }\n    if (current && event == 'mouseDrag') {\n      if (!this.mouseDown) return\n      await this.selectLines(this.mouseDown.lnum, lnum)\n    } else if (current && event == 'mouseUp') {\n      if (!this.mouseDown) return\n      if (this.mouseDown.lnum == lnum) {\n        this.setCursor(lnum)\n        nvim.command('redraw', true)\n      } else {\n        await this.selectLines(this.mouseDown.lnum, lnum)\n      }\n    } else if (!current && event == 'mouseUp') {\n      nvim.pauseNotification()\n      nvim.call('win_gotoid', winid, true)\n      nvim.call('cursor', [lnum, col], true)\n      nvim.command('redraw', true)\n      nvim.resumeNotification(false, true)\n    }\n  }\n\n  public async resume(): Promise<void> {\n    let { items, selected, nvim } = this\n    await this.drawItems(items, true, true)\n    if (!selected.size || !this.buffer) return\n    nvim.pauseNotification()\n    for (let lnum of selected) {\n      this.buffer.placeSign({ lnum, id: listConfiguration.signOffset + lnum, name: 'CocSelected', group: 'coc-list' })\n    }\n    nvim.command('redraw', true)\n    nvim.resumeNotification(false, true)\n  }\n\n  public async toggleSelection(): Promise<void> {\n    let { nvim, reversed } = this\n    await nvim.call('win_gotoid', [this.winid])\n    let lnum = await nvim.call('line', '.') as number\n    let mode = await nvim.call('mode') as string\n    if (mode == 'v' || mode == 'V') {\n      let [start, end] = await this.getSelectedRange()\n      let reverse = start > end\n      if (reverse) [start, end] = [end, start]\n      for (let i = start; i <= end; i++) {\n        this.toggleLine(i)\n      }\n      this.setCursor(end)\n      nvim.command('redraw', true)\n      await nvim.resumeNotification()\n      return\n    }\n    nvim.pauseNotification()\n    this.toggleLine(lnum)\n    this.setCursor(reversed ? lnum - 1 : lnum + 1)\n    nvim.command('redraw', true)\n    await nvim.resumeNotification()\n  }\n\n  private toggleLine(lnum: number): void {\n    let { selected, buffer } = this\n    let exists = selected.has(lnum)\n    const signOffset = listConfiguration.signOffset\n    if (!exists) {\n      selected.add(lnum)\n      buffer.placeSign({ lnum, id: signOffset + lnum, name: 'CocSelected', group: 'coc-list' })\n    } else {\n      selected.delete(lnum)\n      buffer.unplaceSign({ id: signOffset + lnum, group: 'coc-list' })\n    }\n  }\n\n  public async selectLines(start: number, end: number): Promise<void> {\n    let { nvim, buffer, length } = this\n    const signOffset = listConfiguration.signOffset\n    this.clearSelection()\n    let { selected } = this\n    nvim.pauseNotification()\n    let reverse = start > end\n    if (reverse) [start, end] = [end, start]\n    for (let i = start; i <= end; i++) {\n      if (i > length) break\n      selected.add(i)\n      buffer.placeSign({ lnum: i, id: signOffset + i, name: 'CocSelected', group: 'coc-list' })\n    }\n    this.setCursor(end)\n    nvim.command('redraw', true)\n    await nvim.resumeNotification()\n  }\n\n  public async selectAll(): Promise<void> {\n    let { length } = this\n    if (length > 0) await this.selectLines(1, length)\n  }\n\n  public clearSelection(): void {\n    let { selected, buffer } = this\n    if (buffer && selected.size > 0) {\n      buffer.unplaceSign({ group: 'coc-list' })\n      this.selected.clear()\n    }\n  }\n\n  public get ready(): Promise<void> {\n    if (this.window) return Promise.resolve()\n    return new Promise<void>(resolve => {\n      let disposable = this.onDidLineChange(() => {\n        disposable.dispose()\n        resolve()\n      })\n    })\n  }\n\n  public getHeight(len: number, finished: boolean): number {\n    let { listOptions } = this\n    if (typeof listOptions.height === 'number') return listOptions.height\n    let height = listConfiguration.get<number>('height', 10)\n    if (finished && !listOptions.interactive && listOptions.input.length == 0) {\n      height = Math.min(len, height)\n    }\n    return Math.max(1, height)\n  }\n\n  public async drawItems(items: ListItem[], finished: boolean, reload = false): Promise<void> {\n    const { nvim, name, listOptions } = this\n    this.items = items.length > this.limitLines ? items.slice(0, this.limitLines) : items\n    if (!this.window) {\n      let height = this.getHeight(items.length, finished)\n      let { position, numberSelect } = listOptions\n      let [bufnr, winid, tabnr] = await nvim.call('coc#list#create', [position, height, name, numberSelect]) as [number, number, number]\n      this.tabnr = tabnr\n      this.height = height\n      this.buffer = nvim.createBuffer(bufnr)\n      let win = this.window = nvim.createWindow(winid)\n      let statusSegments = listConfiguration.get<string[]>('statusLineSegments')\n      if (statusSegments) win.setOption('statusline', statusSegments.join(\" \"), true)\n      this._onDidOpen.fire(this.bufnr)\n    }\n    const lines: string[] = []\n    let selectIndex = 0\n    this.items.forEach((item, idx) => {\n      lines.push(item.label)\n      if (!reload && selectIndex == 0 && item.preselect) selectIndex = idx\n    })\n    let newIndex = reload ? this.currIndex : selectIndex\n    this.setLines(lines, 0, newIndex)\n    this._onDidLineChange.fire()\n  }\n\n  public async appendItems(items: ListItem[]): Promise<void> {\n    if (!this.window || items.length === 0) return\n    let curr = this.items.length\n    let remain = this.limitLines - curr\n    if (remain > 0) {\n      let append = remain < items.length ? items.slice(0, remain) : items\n      this.items = this.items.concat(append)\n      this.setLines(append.map(item => item.label), append.length, this.currIndex)\n    }\n  }\n\n  public get shouldSort(): boolean {\n    let { matcher, interactive } = this.listOptions\n    if (interactive || matcher !== 'fuzzy') return false\n    return true\n  }\n\n  public setLines(lines: string[], append: number, index: number): void {\n    let { nvim, buffer, window, reversed, newTab } = this\n    if (!buffer || !window) return\n    nvim.pauseNotification()\n    if (!append) {\n      nvim.call('coc#compat#clear_matches', [window.id], true)\n      if (!lines.length) {\n        lines = ['No results, press ? on normal mode to get help.']\n        nvim.call('coc#compat#matchaddpos', ['Comment', [[1]], 99, window.id], true)\n      }\n    }\n    buffer.setOption('modifiable', true, true)\n    if (reversed) {\n      let replacement = lines.reverse()\n      if (append) {\n        nvim.call('appendbufline', [buffer.id, 0, replacement], true)\n      } else {\n        buffer.setLines(replacement, { start: 0, end: -1, strictIndexing: false }, true)\n      }\n    } else {\n      buffer.setLines(lines, { start: append ? -1 : 0, end: -1, strictIndexing: false }, true)\n    }\n    buffer.setOption('modifiable', false, true)\n    if (reversed && !newTab) {\n      let maxHeight = listConfiguration.get<number>('height', 10)\n      nvim.call('coc#window#set_height', [window.id, Math.max(Math.min(maxHeight, this.length), 1)], true)\n    }\n    if (index > this.items.length - 1) index = 0\n    if (index == 0) {\n      if (append == 0) {\n        this.doHighlight(0, 299)\n      } else {\n        let s = this.length - append - 1\n        if (s < 300) this.doHighlight(s, Math.min(299, this.length - 1))\n      }\n    } else {\n      let height = newTab ? workspace.env.lines : this.height\n      this.doHighlight(Math.max(0, index - height), Math.min(index + height + 1, this.length - 1))\n    }\n    if (!append) {\n      this.currIndex = index\n      let lnum = this.indexToLnum(index)\n      window.setCursor([lnum, 0], true)\n      nvim.call('coc#list#select', [buffer.id, lnum], true)\n    }\n    if (reversed) nvim.command('normal! zb', true)\n    nvim.command('redraws', true)\n    nvim.resumeNotification(true, true)\n  }\n\n  public restoreWindow(): void {\n    if (this.newTab) return\n    let { winid, height } = this\n    if (winid && height) {\n      this.nvim.call('coc#window#set_height', [winid, height], true)\n    }\n  }\n\n  public get length(): number {\n    return this.items.length\n  }\n\n  public get selectedItems(): ListItem[] {\n    let { selected, items } = this\n    let res: ListItem[] = []\n    for (let i of selected) {\n      let idx = this.lnumToIndex(i)\n      if (items[i - 1]) res.push(items[idx])\n    }\n    return res\n  }\n\n  private doHighlight(start: number, end: number): void {\n    let { items, reversed, length, buffer } = this\n    const highlightItems: HighlightItem[] = []\n    const iterate = (i: number): void => {\n      let lnum = this.indexToLnum(i) - 1\n      let { ansiHighlights } = items[i]\n      if (ansiHighlights) {\n        for (let hi of ansiHighlights) {\n          let { span, hlGroup } = hi\n          highlightItems.push({ hlGroup, lnum, colStart: span[0], colEnd: span[1] })\n        }\n      }\n    }\n    if (reversed) {\n      for (let i = Math.min(end, length - 1); i >= start; i--) {\n        iterate(i)\n      }\n    } else {\n      for (let i = start; i <= Math.min(end, length - 1); i++) {\n        iterate(i)\n      }\n    }\n    start = this.indexToLnum(start) - 1\n    end = this.indexToLnum(end) - 1\n    if (start > end) {\n      [start, end] = [end, start]\n    }\n    if (!buffer || highlightItems.length == 0) return\n    buffer.updateHighlights('list', highlightItems, { start, end: end + 1, priority: 99 })\n  }\n\n  public setCursor(lnum: number, col = 0, index?: number): void {\n    let { items } = this\n    let max = items.length == 0 ? 1 : items.length\n    if (lnum > max) return\n    // change index since CursorMoved event not fired (seems bug of neovim)!\n    index = index == null ? this.lnumToIndex(lnum) : index\n    this.onLineChange(index)\n    this.window?.setCursor([lnum, col], true)\n    this.nvim.call('coc#list#select', [this.bufnr, lnum], true)\n  }\n\n  public async setIndex(index: number): Promise<void> {\n    if (index < 0 || index >= this.items.length) return\n    let { nvim } = this\n    let lnum = this.indexToLnum(index)\n    nvim.pauseNotification()\n    this.setCursor(lnum, 0, index)\n    nvim.command('redraw', true)\n    await nvim.resumeNotification(false)\n  }\n\n  public async moveCursor(delta: number): Promise<void> {\n    let { index, reversed } = this\n    await this.setIndex(reversed ? index - delta : index + delta)\n  }\n\n  private async getSelectedRange(): Promise<[number, number]> {\n    let { nvim } = this\n    await nvim.call('coc#prompt#stop_prompt', ['list'])\n    await nvim.eval('feedkeys(\"\\\\<esc>\", \"in\")')\n    let [, start] = await nvim.call('getpos', \"'<\") as [number, number]\n    let [, end] = await nvim.call('getpos', \"'>\") as [number, number]\n    this.nvim.call('coc#prompt#start_prompt', ['list'], true)\n    return [start, end]\n  }\n\n  public cancel(): void {\n    this.sequence.cancel()\n  }\n\n  public reset(): void {\n    this.cancel()\n    if (this.window) {\n      this.window = null\n      this.buffer = null\n      this.tabnr = undefined\n    }\n  }\n\n  public dispose(): void {\n    disposeAll(this.disposables)\n    this.nvim.call('coc#window#close', [defaultValue(this.winid, -1)], true)\n    this.reset()\n    this.items = []\n    this._onDidChangeLine.dispose()\n    this._onDidOpen.dispose()\n    this._onDidClose.dispose()\n    this._onDidLineChange.dispose()\n    this._onDoubleClick.dispose()\n  }\n}\n"
  },
  {
    "path": "src/list/worker.ts",
    "content": "'use strict'\nimport { createLogger } from '../logger'\nimport { FuzzyMatch } from '../model/fuzzyMatch'\nimport { defaultValue } from '../util'\nimport { parseAnsiHighlights } from '../util/ansiparse'\nimport { toArray } from '../util/array'\nimport { filter } from '../util/async'\nimport { patchLine } from '../util/diff'\nimport { fuzzyMatch, getCharCodes } from '../util/fuzzy'\nimport { Mutex } from '../util/mutex'\nimport { CancellationToken, CancellationTokenSource, Emitter, Event } from '../util/protocol'\nimport { bytes, smartcaseIndex, toText } from '../util/string'\nimport workspace from '../workspace'\nimport listConfiguration from './configuration'\nimport Prompt from './prompt'\nimport { IList, ListContext, ListItem, ListItemsEvent, ListItemWithScore, ListOptions, ListTask } from './types'\nconst logger = createLogger('list-worker')\nconst controlCode = '\\x1b'\nconst WHITE_SPACE_CHARS = [32, 9]\nconst SEARCH_HL_GROUP = 'CocListSearch'\n\nexport interface FilterOption {\n  append?: boolean\n  reload?: boolean\n}\n\nexport type OnFilter = (arr: ListItem[], finished: boolean, sort?: boolean) => void\n\n// perform loading task\nexport default class Worker {\n  private _loading = false\n  private _finished = false\n  private mutex: Mutex = new Mutex()\n  private filteredCount: number\n  private totalItems: ListItem[] = []\n  private tokenSource: CancellationTokenSource\n  private filterTokenSource: CancellationTokenSource\n  private _onDidChangeItems = new Emitter<ListItemsEvent>()\n  private _onDidChangeLoading = new Emitter<boolean>()\n  private fuzzyMatch: FuzzyMatch\n  public readonly onDidChangeItems: Event<ListItemsEvent> = this._onDidChangeItems.event\n  public readonly onDidChangeLoading: Event<boolean> = this._onDidChangeLoading.event\n\n  constructor(\n    private list: IList,\n    private prompt: Prompt,\n    private listOptions: ListOptions\n  ) {\n    this.fuzzyMatch = workspace.createFuzzyMatch()\n  }\n\n  private set loading(loading: boolean) {\n    if (this._loading == loading) return\n    this._loading = loading\n    this._onDidChangeLoading.fire(loading)\n  }\n\n  public get isLoading(): boolean {\n    return this._loading\n  }\n\n  public async loadItems(context: ListContext, reload = false): Promise<void> {\n    this.cancelFilter()\n    this.filteredCount = 0\n    this._finished = false\n    let { list, listOptions } = this\n    this.loading = true\n    let { interactive } = listOptions\n    this.tokenSource = new CancellationTokenSource()\n    let token = this.tokenSource.token\n    let items = await list.loadItems(context, token)\n    if (token.isCancellationRequested) return\n    items = items ?? []\n    if (Array.isArray(items)) {\n      this.tokenSource = null\n      this.totalItems = items\n      this.loading = false\n      this._finished = true\n      let filtered: ListItem[]\n      if (!interactive) {\n        this.filterTokenSource = new CancellationTokenSource()\n        await this.mutex.use(async () => {\n          await this.filterItems(items as ListItem[], { reload }, token)\n        })\n      } else {\n        filtered = this.convertToHighlightItems(items)\n        this._onDidChangeItems.fire({\n          sorted: true,\n          items: filtered,\n          reload,\n          finished: true\n        })\n      }\n    } else {\n      let task = items as ListTask\n      let totalItems = this.totalItems = []\n      let taken = 0\n      let currInput = context.input\n      this.filterTokenSource = new CancellationTokenSource()\n      let _onData = async (finished?: boolean) => {\n        await this.mutex.use(async () => {\n          let inputChanged = this.input != currInput\n          if (inputChanged) {\n            currInput = this.input\n            taken = defaultValue(this.filteredCount, 0)\n          }\n          if (taken >= totalItems.length) return\n          let append = taken > 0\n          let remain = totalItems.slice(taken)\n          taken = totalItems.length\n          if (!interactive) {\n            let tokenSource = this.filterTokenSource\n            await this.filterItems(remain, { append, reload }, tokenSource.token)\n          } else {\n            let items = this.convertToHighlightItems(remain)\n            this._onDidChangeItems.fire({ items, append, reload, sorted: true, finished })\n          }\n        })\n      }\n      let interval = setInterval(async () => {\n        await _onData()\n      }, 50)\n      task.on('data', item => {\n        totalItems.push(item)\n      })\n      let onEnd = async () => {\n        if (task == null) return\n        clearInterval(interval)\n        this.tokenSource = null\n        task = null\n        this.loading = false\n        this._finished = true\n        disposable.dispose()\n        if (token.isCancellationRequested) return\n        if (totalItems.length == 0) {\n          this._onDidChangeItems.fire({ items: [], append: false, sorted: true, reload, finished: true })\n          return\n        }\n        await _onData(true)\n      }\n      let disposable = token.onCancellationRequested(() => {\n        this.mutex.reset()\n        task?.dispose()\n        void onEnd()\n      })\n      let toDispose = task\n      task.on('error', async (error: Error | string) => {\n        if (task == null) return\n        task = null\n        toDispose.dispose()\n        this.tokenSource = null\n        this.loading = false\n        disposable.dispose()\n        clearInterval(interval)\n        workspace.nvim.call('coc#prompt#stop_prompt', ['list'], true)\n        workspace.nvim.echoError(`Task error: ${error.toString()}`)\n        logger.error('List task error:', error)\n      })\n      task.on('end', onEnd)\n    }\n  }\n\n  /*\n   * Draw all items with filter if necessary\n   */\n  public async drawItems(): Promise<void> {\n    let { totalItems } = this\n    if (totalItems.length === 0) return\n    this.cancelFilter()\n    let tokenSource = this.filterTokenSource = new CancellationTokenSource()\n    let token = tokenSource.token\n    await this.mutex.use(async () => {\n      if (token.isCancellationRequested) return\n      let { totalItems } = this\n      this.filteredCount = totalItems.length\n      await this.filterItems(totalItems, {}, tokenSource.token)\n    })\n  }\n\n  public cancelFilter(): void {\n    if (this.filterTokenSource) {\n      this.filterTokenSource.cancel()\n      this.filterTokenSource = null\n    }\n  }\n\n  public stop(): void {\n    this.cancelFilter()\n    if (this.tokenSource) {\n      this.tokenSource.cancel()\n      this.tokenSource = null\n    }\n    this.loading = false\n  }\n\n  public get length(): number {\n    return this.totalItems.length\n  }\n\n  private get input(): string {\n    return this.prompt.input\n  }\n\n  /**\n   * Add highlights for interactive list\n   */\n  private convertToHighlightItems(items: ListItem[]): ListItem[] {\n    let input = toText(this.input)\n    if (input.length > 0) this.fuzzyMatch.setPattern(input)\n    let res = items.map(item => {\n      convertItemLabel(item)\n      let search = input.length > 0 && item.filterText !== ''\n      if (search) {\n        let filterLabel = getFilterLabel(item)\n        let results = this.fuzzyMatch.matchHighlights(filterLabel, SEARCH_HL_GROUP)\n        item.ansiHighlights = Array.isArray(item.ansiHighlights) ? item.ansiHighlights.filter(o => o.hlGroup !== SEARCH_HL_GROUP) : []\n        if (results) item.ansiHighlights.push(...results.highlights)\n      }\n      return item\n    })\n    this.fuzzyMatch.free()\n    return res\n  }\n\n  private async filterItemsByInclude(input: string, items: ListItem[], token: CancellationToken, onFilter: OnFilter): Promise<void> {\n    let { ignorecase } = this.listOptions\n    const smartcase = listConfiguration.smartcase\n    let inputs = toInputs(input, listConfiguration.extendedSearchMode)\n    if (ignorecase) inputs = inputs.map(s => s.toLowerCase())\n    await filter(items, item => {\n      convertItemLabel(item)\n      let spans: [number, number][] = []\n      let filterLabel = getFilterLabel(item)\n      let byteIndex = bytes(filterLabel)\n      let curr = 0\n      item.ansiHighlights = toArray(item.ansiHighlights).filter(o => o.hlGroup !== SEARCH_HL_GROUP)\n      for (let input of inputs) {\n        let label = filterLabel.slice(curr)\n        let idx = indexOf(label, input, smartcase, ignorecase)\n        if (idx === -1) break\n        let end = idx + curr + input.length\n        spans.push([byteIndex(idx + curr), byteIndex(end)])\n        curr = end\n      }\n      if (spans.length !== inputs.length) return false\n      item.ansiHighlights.push(...spans.map(s => {\n        return { span: s, hlGroup: SEARCH_HL_GROUP }\n      }))\n      return true\n    }, onFilter, token)\n  }\n\n  private async filterItemsByRegex(input: string, items: ListItem[], token: CancellationToken, onFilter: OnFilter): Promise<void> {\n    let { ignorecase } = this.listOptions\n    let flags = ignorecase ? 'iu' : 'u'\n    let inputs = toInputs(input, listConfiguration.extendedSearchMode)\n    let regexes = inputs.reduce((p, c) => {\n      try { p.push(new RegExp(c, flags)) } catch (e) {}\n      return p\n    }, [])\n    await filter(items, item => {\n      convertItemLabel(item)\n      item.ansiHighlights = toArray(item.ansiHighlights).filter(o => o.hlGroup !== SEARCH_HL_GROUP)\n      let spans: [number, number][] = []\n      let filterLabel = getFilterLabel(item)\n      let byteIndex = bytes(filterLabel)\n      let curr = 0\n      for (let regex of regexes) {\n        let ms = filterLabel.slice(curr).match(regex)\n        if (ms == null) break\n        let end = ms.index + curr + ms[0].length\n        spans.push([byteIndex(ms.index + curr), byteIndex(end)])\n        curr = end\n      }\n      if (spans.length !== inputs.length) return false\n      item.ansiHighlights.push(...spans.map(s => {\n        return { span: s, hlGroup: SEARCH_HL_GROUP }\n      }))\n      return true\n    }, onFilter, token)\n  }\n\n  private async filterItemsByFuzzyMatch(input: string, items: ListItem[], token: CancellationToken, onFilter: OnFilter): Promise<void> {\n    let { extendedSearchMode, smartcase } = listConfiguration\n    let { sort } = this.listOptions\n    let idx = 0\n    this.fuzzyMatch.setPattern(input, !extendedSearchMode)\n    let codes = getCharCodes(input)\n    if (extendedSearchMode) codes = codes.filter(c => !WHITE_SPACE_CHARS.includes(c))\n    await filter(items, item => {\n      convertItemLabel(item)\n      let filterLabel = getFilterLabel(item)\n      let match = this.fuzzyMatch.matchHighlights(filterLabel, SEARCH_HL_GROUP)\n      if (!match || (smartcase && !fuzzyMatch(codes, filterLabel))) return false\n      let ansiHighlights = Array.isArray(item.ansiHighlights) ? item.ansiHighlights.filter(o => o.hlGroup != SEARCH_HL_GROUP) : []\n      ansiHighlights.push(...match.highlights)\n      return {\n        sortText: typeof item.sortText === 'string' ? item.sortText : String.fromCharCode(idx),\n        score: match.score,\n        ansiHighlights\n      }\n    }, (items, done) => {\n      onFilter(items, done, sort)\n    }, token)\n  }\n\n  private async filterItems(arr: ListItem[], opts: FilterOption, token: CancellationToken): Promise<void> {\n    let { input } = this\n    if (input.length === 0) {\n      let items = arr.map(item => {\n        return convertItemLabel(item)\n      })\n      this._onDidChangeItems.fire({ items, sorted: true, finished: this._finished, ...opts })\n      return\n    }\n    let called = false\n    let itemsToSort: ListItemWithScore[] = []\n    const onFilter = (items: ListItemWithScore[], done: boolean, sort?: boolean) => {\n      let finished = done && this._finished\n      if (token.isCancellationRequested || (!finished && items.length == 0)) return\n      if (sort) {\n        itemsToSort.push(...items)\n        if (done) this._onDidChangeItems.fire({ items: itemsToSort, append: false, sorted: false, reload: opts.reload, finished })\n      } else {\n        let append = opts.append === true || called\n        called = true\n        this._onDidChangeItems.fire({ items, append, sorted: true, reload: opts.reload, finished })\n      }\n    }\n    switch (this.listOptions.matcher) {\n      case 'strict':\n        await this.filterItemsByInclude(input, arr, token, onFilter)\n        break\n      case 'regex':\n        await this.filterItemsByRegex(input, arr, token, onFilter)\n        break\n      default:\n        await this.filterItemsByFuzzyMatch(input, arr, token, onFilter)\n    }\n  }\n\n  public dispose(): void {\n    this.stop()\n  }\n}\n\nfunction getFilterLabel(item: ListItem): string {\n  return item.filterText != null ? patchLine(item.filterText, item.label) : item.label\n}\n\nexport function toInputs(input: string, extendedSearchMode: boolean): string[] {\n  return extendedSearchMode ? parseInput(input) : [input]\n}\n\nexport function convertItemLabel(item: ListItem): ListItem {\n  let { label, converted } = item\n  if (converted) return item\n  if (label.includes('\\n')) {\n    label = item.label = label.replace(/\\r?\\n.*/gm, '')\n  }\n  if (label.includes(controlCode)) {\n    let { line, highlights } = parseAnsiHighlights(label)\n    item.label = line\n    if (!Array.isArray(item.ansiHighlights)) item.ansiHighlights = highlights\n  }\n  item.converted = true\n  return item\n}\n\nexport function indexOf(label: string, input: string, smartcase: boolean, ignorecase: boolean): number {\n  if (smartcase) return smartcaseIndex(input, label)\n  return ignorecase ? label.toLowerCase().indexOf(input.toLowerCase()) : label.indexOf(input)\n}\n\n/**\n * `a\\ b` => [`a b`]\n * `a b` =>  ['a', 'b']\n */\nexport function parseInput(input: string): string[] {\n  let res: string[] = []\n  let startIdx = 0\n  let currIdx = 0\n  let prev = ''\n  for (; currIdx < input.length; currIdx++) {\n    let ch = input[currIdx]\n    if (WHITE_SPACE_CHARS.includes(ch.charCodeAt(0))) {\n      // find space\n      if (prev && prev != '\\\\' && startIdx != currIdx) {\n        res.push(input.slice(startIdx, currIdx))\n        startIdx = currIdx + 1\n      }\n    } else {\n    }\n    prev = ch\n  }\n  if (startIdx != input.length) {\n    res.push(input.slice(startIdx, input.length))\n  }\n  return res.map(s => s.replace(/\\\\\\s/g, ' ').trim()).filter(s => s.length > 0)\n}\n"
  },
  {
    "path": "src/logger/index.ts",
    "content": "'use strict'\nimport { FileLogger, textToLogLevel, ILogger } from './log'\nimport { fs, path, os } from '../util/node'\nimport { getConditionValue } from '../util'\n\nexport { getTimestamp } from './log'\n\nexport function resolveLogFilepath(): string {\n  let file = process.env.NVIM_COC_LOG_FILE\n  if (file) return file\n  let dir = process.env.XDG_RUNTIME_DIR\n  if (dir) {\n    try {\n      fs.accessSync(dir, fs.constants.R_OK | fs.constants.W_OK)\n      return path.join(dir, `coc-nvim-${process.pid}.log`)\n    } catch (err) {\n      // noop\n    }\n  }\n  let tmpdir = os.tmpdir()\n  dir = path.join(tmpdir, `coc.nvim-${process.pid}`)\n  fs.mkdirSync(dir, { recursive: true })\n  return path.join(dir, `coc-nvim.log`)\n}\n\nexport function emptyFile(filepath: string): void {\n  if (fs.existsSync(filepath)) {\n    // cleanup if exists\n    try {\n      fs.writeFileSync(filepath, '', { encoding: 'utf8', mode: 0o666 })\n    } catch (e) {\n      // noop\n    }\n  }\n}\n\nconst logfile = resolveLogFilepath()\nemptyFile(logfile)\n\nconst level = getConditionValue(process.env.NVIM_COC_LOG_LEVEL || 'info', 'off')\n\nexport const logger = new FileLogger(logfile, textToLogLevel(level), {\n  color: !global.REVISION && process.platform !== 'win32',\n  userFormatters: true\n})\n\nexport function getLoggerFile(): string {\n  return logfile\n}\n\nexport function createLogger(category = 'coc.nvim'): ILogger {\n  return logger.createLogger(category)\n}\n"
  },
  {
    "path": "src/logger/log.ts",
    "content": "'use strict'\nimport { fs, inspect, path, promisify } from '../util/node'\nconst MAX_FILE_SIZE = 5 * 1024 * 1024 // 5MB\n\nexport enum LogLevel {\n  Trace,\n  Debug,\n  Info,\n  Warning,\n  Error,\n  Off\n}\n\nconst yellowOpen = '\\x1B[33m'\nconst yellowClose = '\\x1B[39m'\n\nexport const DEFAULT_LOG_LEVEL: LogLevel = LogLevel.Info\n\nexport const toTwoDigits = (v: number) => v < 10 ? `0${v}` : v.toString()\nexport const toThreeDigits = (v: number) => v < 10 ? `00${v}` : v < 100 ? `0${v}` : v.toString()\n\nexport interface ILogger {\n  readonly category: string\n  getLevel(): LogLevel\n  log(...args: any[]): void\n  trace(...args: any[]): void\n  debug(...args: any[]): void\n  info(...args: any[]): void\n  warn(...args: any[]): void\n  error(...args: any[]): void\n  fatal(...args: any[]): void\n  mark(...args: any[]): void\n  /**\n   * An operation to flush the contents. Can be synchronous.\n   */\n  flush(): Promise<void>\n}\n\nexport function textToLogLevel(level: string): LogLevel {\n  let str = level.toLowerCase()\n  switch (str) {\n    case 'trace':\n      return LogLevel.Trace\n    case 'debug':\n      return LogLevel.Debug\n    case 'info':\n      return LogLevel.Info\n    case 'error':\n      return LogLevel.Error\n    case 'warn':\n    case 'warning':\n      return LogLevel.Warning\n    case 'off':\n      return LogLevel.Off\n    default:\n      return LogLevel.Info\n  }\n}\n\nexport function format(args: any, depth = 2, color = false, hidden = false): string {\n  let result = ''\n\n  for (let i = 0; i < args.length; i++) {\n    let a = args[i]\n\n    if (typeof a === 'object') {\n      try {\n        a = inspect(a, hidden, depth, color)\n      } catch (e) {}\n    }\n    if (color && (typeof a === 'boolean' || typeof a === 'number')) {\n      a = `${yellowOpen}${a}${yellowClose}`\n    }\n    result += (i > 0 ? ' ' : '') + a\n  }\n\n  return result\n}\n\nabstract class AbstractLogger {\n  protected level: LogLevel = DEFAULT_LOG_LEVEL\n\n  public setLevel(level: LogLevel): void {\n    if (this.level !== level) {\n      this.level = level\n    }\n  }\n\n  public getLevel(): LogLevel {\n    return this.level\n  }\n}\n\nexport interface LoggerConfiguration {\n  userFormatters: boolean\n  color: boolean\n  depth: number // 2\n  showHidden: boolean\n}\n\nexport class FileLogger extends AbstractLogger {\n\n  private promise: Promise<void>\n  private backupIndex = 1\n  private config: LoggerConfiguration\n  private useConsole = false\n  private loggers: Map<string, ILogger> = new Map()\n\n  constructor(\n    private readonly fsPath: string,\n    level: LogLevel,\n    config: Partial<LoggerConfiguration>\n  ) {\n    super()\n    this.config = Object.assign({\n      userFormatters: true,\n      color: false,\n      depth: 2,\n      showHidden: false\n    }, config)\n    this.setLevel(level)\n    this.promise = this.initialize()\n  }\n\n  public switchConsole(): void {\n    this.useConsole = !this.useConsole\n  }\n\n  private format(args: any[]): string {\n    let { color, showHidden, depth } = this.config\n    return format(args, depth, color, showHidden)\n  }\n\n  public createLogger(scope: string): ILogger {\n    let logger = this.loggers.has(scope) ? this.loggers.get(scope) : {\n      category: scope,\n      mark: () => {\n        // not used\n      },\n      getLevel: () => {\n        return this.getLevel()\n      },\n      trace: (...args: any[]) => {\n        if (this.level <= LogLevel.Trace) {\n          this._log(LogLevel.Trace, scope, args, this.getCurrentTimestamp())\n        }\n      },\n      debug: (...args: any[]) => {\n        if (this.level <= LogLevel.Debug) {\n          this._log(LogLevel.Debug, scope, args, this.getCurrentTimestamp())\n        }\n      },\n      log: (...args: any[]) => {\n        if (this.level <= LogLevel.Info) {\n          this._log(LogLevel.Info, scope, args, this.getCurrentTimestamp())\n        }\n      },\n      info: (...args: any[]) => {\n        if (this.level <= LogLevel.Info) {\n          this._log(LogLevel.Info, scope, args, this.getCurrentTimestamp())\n        }\n      },\n      warn: (...args: any[]) => {\n        if (this.level <= LogLevel.Warning) {\n          this._log(LogLevel.Warning, scope, args, this.getCurrentTimestamp())\n        }\n      },\n      error: (...args: any[]) => {\n        if (this.level <= LogLevel.Error) {\n          this._log(LogLevel.Error, scope, args, this.getCurrentTimestamp())\n        }\n      },\n      fatal: (...args: any[]) => {\n        if (this.level <= LogLevel.Error) {\n          this._log(LogLevel.Error, scope, args, this.getCurrentTimestamp())\n        }\n      },\n      /**\n       * An operation to flush the contents. Can be synchronous.\n       */\n      flush: () => {\n        return this.promise\n      }\n    }\n    this.loggers.set(scope, logger)\n    return logger\n  }\n\n  private async initialize(): Promise<void> {\n    return Promise.resolve()\n  }\n\n  public shouldBackup(size: number): boolean {\n    return size > MAX_FILE_SIZE\n  }\n\n  private _log(level: LogLevel, scope: string, args: any[], time: string): void {\n    if (this.useConsole) {\n      let method = level === LogLevel.Error ? 'error' : 'log'\n      console[method](`${stringifyLogLevel(level)} [${scope}]`, format(args, null, true))\n    } else {\n      let message = this.format(args)\n      this.promise = this.promise.then(() => {\n        let fn = async () => {\n          let text: string\n          if (this.config.userFormatters !== false) {\n            let parts = [time, stringifyLogLevel(level), `(pid:${process.pid})`, `[${scope}]`]\n            text = `${parts.join(' ')} - ${message}\\n`\n          } else {\n            text = message\n          }\n          await promisify(fs.appendFile)(this.fsPath, text, { encoding: 'utf8', flag: 'a+' })\n          let stat = await promisify(fs.stat)(this.fsPath)\n          if (this.shouldBackup(stat.size)) {\n            let newFile = this.getBackupResource()\n            await promisify(fs.rename)(this.fsPath, newFile)\n          }\n        }\n        return fn()\n      }).catch(err => {\n        if (!global.REVISION) {\n          console.error(err)\n        }\n      })\n    }\n  }\n\n  private getCurrentTimestamp(): string {\n    const currentTime = new Date()\n    return `${currentTime.getFullYear()}-${toTwoDigits(currentTime.getMonth() + 1)}-${toTwoDigits(currentTime.getDate())}T${getTimestamp(currentTime)}`\n  }\n\n  private getBackupResource(): string {\n    this.backupIndex = this.backupIndex > 5 ? 1 : this.backupIndex\n    return path.join(path.dirname(this.fsPath), `${path.basename(this.fsPath)}_${this.backupIndex++}`)\n  }\n}\n\nexport function stringifyLogLevel(level: LogLevel): string {\n  switch (level) {\n    case LogLevel.Debug: return 'DEBUG'\n    case LogLevel.Error: return 'ERROR'\n    case LogLevel.Info: return 'INFO'\n    case LogLevel.Trace: return 'TRACE'\n    case LogLevel.Warning: return 'WARN'\n  }\n  return ''\n}\n\nexport function getTimestamp(date: Date): string {\n  return `${toTwoDigits(date.getHours())}:${toTwoDigits(date.getMinutes())}:${toTwoDigits(date.getSeconds())}.${toThreeDigits(date.getMilliseconds())}`\n}\n"
  },
  {
    "path": "src/markdown/index.ts",
    "content": "'use strict'\nimport { marked } from 'marked'\nimport { Documentation, HighlightItem } from '../types'\nimport { parseAnsiHighlights } from '../util/ansiparse'\nimport * as Is from '../util/is'\nimport { stripAnsi } from '../util/node'\nimport { byteIndex, byteLength } from '../util/string'\nimport Renderer from './renderer'\n\nexport interface MarkdownParseOptions {\n  breaks?: boolean\n  excludeImages?: boolean\n}\n\nexport interface CodeBlock {\n  /**\n   * Must have filetype or hlgroup\n   */\n  filetype?: string\n  hlGroup?: string\n  startLine: number // 0 based\n  endLine: number\n}\n\nexport interface DocumentInfo {\n  lines: string[]\n  highlights: HighlightItem[]\n  codes: CodeBlock[]\n}\n\nenum FiletypeHighlights {\n  Error = 'CocErrorFloat',\n  Warning = 'CocWarningFloat',\n  Info = 'CocInfoFloat',\n  Hint = 'CocHintFloat',\n}\n\nconst filetyepsMap = {\n  js: 'javascript',\n  ts: 'typescript',\n  bash: 'sh'\n}\nconst ACTIVE_HL_GROUP = 'CocFloatActive'\nconst HEADER_PREFIX = '\\x1b[35m'\nconst DIVIDING_LINE_HI_GROUP = 'CocFloatDividingLine'\nconst MARKDOWN = 'markdown'\nconst DOTS = '```'\nconst TXT = 'txt'\nconst DIVIDE_CHARACTER = '─'\nconst DIVIDE_LINE = '───'\n\nexport function toFiletype(match: null | undefined | string): string {\n  if (!match) return TXT\n  let mapped = filetyepsMap[match]\n  return Is.string(mapped) ? mapped : match\n}\n\nexport function parseDocuments(docs: Documentation[], opts: MarkdownParseOptions = {}): DocumentInfo {\n  let lines: string[] = []\n  let highlights: HighlightItem[] = []\n  let codes: CodeBlock[] = []\n  let idx = 0\n  for (let doc of docs) {\n    let currline = lines.length\n    let { content, filetype } = doc\n    let hls = doc.highlights\n    if (filetype == MARKDOWN) {\n      let info = parseMarkdown(content, opts)\n      codes.push(...info.codes.map(o => {\n        o.startLine = o.startLine + currline\n        o.endLine = o.endLine + currline\n        return o\n      }))\n      highlights.push(...info.highlights.map(o => {\n        o.lnum = o.lnum + currline\n        return o\n      }))\n      lines.push(...info.lines)\n    } else {\n      let parts = content.trim().split(/\\r?\\n/)\n      let hlGroup = FiletypeHighlights[doc.filetype]\n      if (Is.string(hlGroup)) {\n        codes.push({ hlGroup, startLine: currline, endLine: currline + parts.length })\n      } else {\n        codes.push({ filetype: doc.filetype, startLine: currline, endLine: currline + parts.length })\n      }\n      lines.push(...parts)\n    }\n    if (Array.isArray(hls)) {\n      highlights.push(...hls.map(o => {\n        return Object.assign({}, o, { lnum: o.lnum + currline })\n      }))\n    }\n    if (Array.isArray(doc.active)) {\n      let arr = getHighlightItems(content, currline, doc.active)\n      if (arr.length) highlights.push(...arr)\n    }\n    if (idx != docs.length - 1) {\n      highlights.push({\n        lnum: lines.length,\n        hlGroup: DIVIDING_LINE_HI_GROUP,\n        colStart: 0,\n        colEnd: -1\n      })\n      lines.push(DIVIDE_CHARACTER) // dividing line\n    }\n    idx = idx + 1\n  }\n  return { lines, highlights, codes }\n}\n\n/**\n * Get 'CocSearch' highlights from offset range\n */\nexport function getHighlightItems(content: string, currline: number, active: [number, number]): HighlightItem[] {\n  let res: HighlightItem[] = []\n  let [start, end] = active\n  let lines = content.split(/\\r?\\n/)\n  let used = 0\n  let inRange = false\n  for (let i = 0; i < lines.length; i++) {\n    let line = lines[i]\n    if (!inRange) {\n      if (used + line.length > start) {\n        inRange = true\n        let colStart = byteIndex(line, start - used)\n        if (used + line.length > end) {\n          let colEnd = byteIndex(line, end - used)\n          inRange = false\n          res.push({ colStart, colEnd, lnum: i + currline, hlGroup: ACTIVE_HL_GROUP })\n          break\n        } else {\n          let colEnd = byteLength(line)\n          res.push({ colStart, colEnd, lnum: i + currline, hlGroup: ACTIVE_HL_GROUP })\n        }\n      }\n    } else {\n      if (used + line.length > end) {\n        let colEnd = byteIndex(line, end - used)\n        res.push({ colStart: 0, colEnd, lnum: i + currline, hlGroup: ACTIVE_HL_GROUP })\n        inRange = false\n        break\n      } else {\n        let colEnd = byteLength(line)\n        res.push({ colStart: 0, colEnd, lnum: i + currline, hlGroup: ACTIVE_HL_GROUP })\n      }\n    }\n    used = used + line.length + 1\n  }\n  return res\n}\n\n/**\n * Parse markdown for lines, highlights & codes\n */\nexport function parseMarkdown(content: string, opts: MarkdownParseOptions): DocumentInfo {\n  marked.setOptions({\n    renderer: new Renderer(),\n    gfm: true,\n    breaks: Is.boolean(opts.breaks) ? opts.breaks : true,\n    hooks: Renderer.hooks,\n  })\n  let lines: string[] = []\n  let highlights: HighlightItem[] = []\n  let codes: CodeBlock[] = []\n  let currline = 0\n  let inCodeBlock = false\n  let filetype: string\n  let startLnum = 0\n  let parsed = marked(content)\n  let links = Renderer.getLinks()\n  parsed = parsed.replace(/\\s*$/, '')\n  if (links.length) {\n    parsed = parsed + '\\n\\n' + links.join('\\n')\n  }\n  let parsedLines = parsed.split(/\\n/)\n  for (let i = 0; i < parsedLines.length; i++) {\n    let line = parsedLines[i]\n    if (!line.length) {\n      let pre = lines[lines.length - 1]\n      // Skip current line when previous line is empty\n      if (!pre) continue\n      let next = parsedLines[i + 1]\n      // Skip empty line when next is code block or hr or header\n      if (!next || next.startsWith(DOTS) || next.startsWith(DIVIDE_CHARACTER)) continue\n      lines.push(line)\n      currline++\n      continue\n    }\n    if (opts.excludeImages && line.indexOf('![') !== -1) {\n      line = line.replace(/\\s*!\\[.*?\\]\\(.*?\\)/g, '')\n      if (!stripAnsi(line).trim().length) continue\n    }\n    let ms = line.match(/^\\s*```\\s*(\\S+)?/)\n    if (ms) {\n      if (!inCodeBlock) {\n        let pre = parsedLines[i - 1]\n        if (pre && /^\\s*```\\s*/.test(pre)) {\n          lines.push('')\n          currline++\n        }\n        inCodeBlock = true\n        filetype = toFiletype(ms[1])\n        startLnum = currline\n      } else {\n        inCodeBlock = false\n        codes.push({\n          filetype,\n          startLine: startLnum,\n          endLine: currline\n        })\n      }\n      continue\n    }\n    if (inCodeBlock) {\n      // no parse\n      lines.push(line)\n      currline++\n      continue\n    }\n    let res = parseAnsiHighlights(line, true)\n    if (line === DIVIDE_LINE) {\n      highlights.push({\n        hlGroup: DIVIDING_LINE_HI_GROUP,\n        lnum: currline,\n        colStart: 0,\n        colEnd: -1\n      })\n    } else if (res.highlights) {\n      for (let hi of res.highlights) {\n        let { hlGroup, span } = hi\n        highlights.push({\n          hlGroup,\n          lnum: currline,\n          colStart: span[0],\n          colEnd: span[1]\n        })\n      }\n    }\n    lines.push(res.line)\n    currline++\n  }\n  return { lines, highlights, codes }\n}\n"
  },
  {
    "path": "src/markdown/renderer.ts",
    "content": "'use strict'\nimport { toObject } from '../util/object'\nimport { MarkedOptions } from 'marked'\n/**\n * Renderer for convert markdown to terminal string\n */\nimport * as styles from './styles'\nlet TABLE_CELL_SPLIT = '^*||*^'\nlet TABLE_ROW_WRAP = '*|*|*|*'\nlet TABLE_ROW_WRAP_REGEXP = new RegExp(escapeRegExp(TABLE_ROW_WRAP), 'g')\nlet COLON_REPLACER = '*#COLON|*'\nlet COLON_REPLACER_REGEXP = new RegExp(escapeRegExp(COLON_REPLACER), 'g')\n\n// HARD_RETURN holds a character sequence used to indicate text has a\n// hard (no-reflowing) line break.  Previously \\r and \\r\\n were turned\n// into \\n in marked's lexer- preprocessing step. So \\r is safe to use\n// to indicate a hard (non-reflowed) return.\nlet HARD_RETURN = /\\r/g\n\nfunction identity(str: string): string {\n  return str\n}\n\nfunction cleanUpHtml(input: string): string {\n  return styles.gray(input.replace(/(<([^>]+)>)/ig, ''))\n}\n\nlet defaultOptions = {\n  code: identity,\n  blockquote: identity,\n  html: cleanUpHtml,\n  heading: styles.magenta,\n  firstHeading: styles.magenta,\n  hr: identity,\n  listitem: identity,\n  list,\n  table: identity,\n  paragraph: identity,\n  strong: styles.bold,\n  em: styles.italic,\n  codespan: styles.yellow,\n  del: styles.strikethrough,\n  link: styles.underline,\n  href: styles.underline,\n  text: identity,\n  unescape: true,\n  emoji: false,\n  width: 80,\n  showSectionPrefix: false,\n  tab: 2,\n  tableOptions: {}\n}\n\nexport function fixHardReturn(text, reflow) {\n  return reflow ? text.replace(HARD_RETURN, '\\n') : text\n}\n\nfunction indentLines(indent, text) {\n  return text.replace(/(^|\\n)(.+)/g, '$1' + indent + '$2')\n}\n\nexport function identify(indent, text) {\n  if (!text) return text\n  return indent + text.split('\\n').join('\\n' + indent)\n}\n\nlet BULLET_POINT_REGEX = '\\\\*'\nlet NUMBERED_POINT_REGEX = '\\\\d+\\\\.'\nlet POINT_REGEX = '(?:' + [BULLET_POINT_REGEX, NUMBERED_POINT_REGEX].join('|') + ')'\n\n// Prevents nested lists from joining their parent list's last line\nfunction fixNestedLists(body, indent) {\n  let regex = new RegExp(\n    '' +\n    '(\\\\S(?: |  )?)' + // Last char of current point, plus one or two spaces\n    // to allow trailing spaces\n    '((?:' +\n    indent +\n    ')+)' + // Indentation of sub point\n    '(' +\n    POINT_REGEX +\n    '(?:.*)+)$',\n    'gm'\n  ) // Body of subpoint\n  return body.replace(regex, '$1\\n' + indent + '$2$3')\n}\n\nlet isPointedLine = function(line, indent) {\n  return line.match('^(?:' + indent + ')*' + POINT_REGEX) != null\n}\n\nexport function toSpaces(str) {\n  return ' '.repeat(str.length)\n}\n\nconst SPECIAL_SPACE = '\\0\\0\\0'\nconst SPACE = ' '\nexport function toSpecialSpaces(str) {\n  return SPECIAL_SPACE.repeat(str.length)\n}\n\nlet BULLET_POINT = '* '\nexport function bulletPointLine(indent, line) {\n  if (isPointedLine(line, indent)) {\n    return line\n  }\n  if (!line.includes(SPECIAL_SPACE)) {\n    return toSpecialSpaces(BULLET_POINT) + line\n  }\n  return line\n}\n\nfunction bulletPointLines(lines, indent) {\n  let transform = bulletPointLine.bind(null, indent)\n  return lines\n    .split('\\n')\n    .filter(identity)\n    .map(transform)\n    .join('\\n')\n}\n\nlet numberedPoint = function(n) {\n  return n + '. '\n}\n\nexport function numberedLine(indent, line, num) {\n  return isPointedLine(line, indent)\n    ? {\n      num: num + 1,\n      line: line.replace(BULLET_POINT, numberedPoint(num + 1))\n    }\n    : {\n      num,\n      line: toSpaces(numberedPoint(num)) + line\n    }\n}\n\nfunction numberedLines(lines, indent) {\n  let transform = numberedLine.bind(null, indent)\n  let num = 0\n  return lines\n    .split('\\n')\n    .filter(identity)\n    .map(line => {\n      const numbered = transform(line, num)\n      num = numbered.num\n\n      return numbered.line\n    })\n    .join('\\n')\n}\n\nfunction list(body, ordered, indent) {\n  body = body.trim()\n  body = ordered ? numberedLines(body, indent) : bulletPointLines(body, indent)\n  return body\n}\n\nfunction section(text) {\n  return text + '\\n\\n'\n}\n\nfunction undoColon(str) {\n  return str.replace(COLON_REPLACER_REGEXP, ':')\n}\n\nexport function generateTableRow(text, escape = null) {\n  if (!text) return []\n  escape = escape || identity\n  let lines = escape(text).split('\\n')\n\n  let data = []\n  lines.forEach(function(line) {\n    if (!line) return\n    let parsed = line\n      .replace(TABLE_ROW_WRAP_REGEXP, '')\n      .split(TABLE_CELL_SPLIT)\n\n    data.push(parsed.splice(0, parsed.length - 1))\n  })\n  return data\n}\n\nfunction escapeRegExp(str) {\n  // eslint-disable-next-line no-useless-escape\n  return str.replace(/[\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\\\^\\$\\|]/g, '\\\\$&')\n}\n\nfunction unescapeEntities(html) {\n  return html\n    .replace(/&amp;/g, '&')\n    .replace(/&lt;/g, '<')\n    .replace(/&gt;/g, '>')\n    .replace(/&quot;/g, '\"')\n    .replace(/&#39;/g, \"'\")\n}\n\nconst links: Map<string, string> = new Map()\n\nexport interface RendererOptions {\n  sanitize?: boolean\n}\n\nclass Renderer {\n  private o: any\n  private tab: any\n  private tableSettings: any\n  // private emoji: any\n  private unescape: any\n  private transform: any\n  constructor(public options: RendererOptions = {}, public highlightOptions: any = {}) {\n    this.o = Object.assign({}, defaultOptions, options)\n    this.tab = '  '\n    this.tableSettings = this.o.tableOptions\n    // this.emoji = identity\n    this.unescape = unescapeEntities\n    this.highlightOptions = toObject(highlightOptions)\n    this.transform = this.compose(undoColon, this.unescape)\n  }\n  public static hooks: MarkedOptions['hooks'] = {\n    preprocess: str => str,\n    postprocess: str => {\n      return str.replace(new RegExp(SPECIAL_SPACE, 'g'), SPACE)\n    }\n  }\n\n  public text(t: string): string {\n    return this.o.text(t)\n  }\n\n  public code(code: string, lang: string, _escaped: boolean): string {\n    return '``` ' + lang + '\\n' + code + '\\n```\\n'\n  }\n\n  public blockquote(quote: string): string {\n    return section(this.o.blockquote(identify(this.tab, quote.trim())))\n  }\n\n  public html(html: string): string {\n    return this.o.html(html)\n  }\n\n  public heading(text: string, level: number, _raw: any): string {\n    text = this.transform(text)\n    return section(\n      level === 1 ? this.o.firstHeading(text) : this.o.heading(text)\n    )\n  }\n\n  public hr(): string {\n    // NOTE: the '─' character is conveniently translated into a window-wide\n    // horizontal rule by coc.nvim/autoload/coc/float.vim. Using this character\n    // causes the horizontal rule to appear like a proper hr separator. In case\n    // the user isn't benefiting from a floating window, we provide three\n    // characters so that the hr doesn't deviate too significantly from\n    // Markdown's normal '-'.\n    return `───\\n`\n  }\n\n  public list(body, ordered): string {\n    body = this.o.list(body, ordered, this.tab)\n    return section(fixNestedLists(indentLines(this.tab, body), this.tab))\n  }\n\n  public listitem(text: string): string {\n    let transform = this.compose(this.o.listitem, this.transform)\n    let isNested = text.indexOf('\\n') !== -1\n    if (isNested) text = text.trim()\n    // Use BULLET_POINT as a marker for ordered or unordered list item\n    return '\\n' + BULLET_POINT + transform(text)\n  }\n\n  public checkbox(checked): string {\n    return '[' + (checked ? 'X' : ' ') + '] '\n  }\n\n  public paragraph(text: string): string {\n    let transform = this.compose(this.o.paragraph, this.transform)\n    text = transform(text)\n    return section(text)\n  }\n\n  public table(header, body): string {\n    const Table = require('cli-table')\n    let table = new Table(\n      Object.assign(\n        {},\n        {\n          head: generateTableRow(header)[0]\n        },\n        this.tableSettings\n      )\n    )\n    generateTableRow(body, this.transform).forEach(function(row) {\n      table.push(row)\n    })\n    return section(this.o.table(table.toString()))\n  }\n\n  public tablerow(content: string): string {\n    return TABLE_ROW_WRAP + content + TABLE_ROW_WRAP + '\\n'\n  }\n\n  public tablecell(content, _flags): string {\n    return content + TABLE_CELL_SPLIT\n  }\n\n  public strong(text: string): string {\n    return this.o.strong(text)\n  }\n\n  public em(text: string): string {\n    text = fixHardReturn(text, this.o.reflowText)\n    return this.o.em(text)\n  }\n\n  public codespan(text: string): string {\n    text = fixHardReturn(text, this.o.reflowText)\n    return this.o.codespan(text.replace(/:/g, COLON_REPLACER))\n  }\n\n  public br(): string {\n    return '\\n'\n  }\n\n  public del(text: string): string {\n    return this.o.del(text)\n  }\n\n  public link(href, title, text): string {\n    let prot: string\n    try {\n      prot = decodeURIComponent(unescape(href))\n        .replace(/[^\\w:]/g, '')\n        .toLowerCase()\n    } catch (e) {\n      return ''\n    }\n    if (prot.startsWith('javascript:')) {\n      return ''\n    }\n    if (text && href && text != href) {\n      links.set(text, href)\n    }\n    if (text && text != href) return styles.blue(text)\n    let out = this.o.href(href)\n    return this.o.link(out)\n  }\n\n  public image(href, title, text): string {\n    let out = '![' + text\n    return out + '](' + href + ')'\n  }\n\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n  public compose(...funcs: Function[]): any {\n    return (...args: any[]) => {\n      for (let i = funcs.length; i-- > 0;) {\n        args = [funcs[i].apply(this, args)]\n      }\n      return args[0]\n    }\n  }\n\n  public static getLinks(): string[] {\n    let res = []\n    for (let [text, href] of links.entries()) {\n      res.push(`${styles.blue(text)}: ${href}`)\n    }\n    links.clear()\n    return res\n  }\n}\n\nexport default Renderer\n"
  },
  {
    "path": "src/markdown/styles.ts",
    "content": "'use strict'\nimport { styles } from '../util/node'\n\nexport function gray(str: string): string {\n  return `${styles.gray.open}${str}${styles.gray.close}`\n}\n\nexport function magenta(str: string): string {\n  return `${styles.magenta.open}${str}${styles.magenta.close}`\n}\n\nexport function bold(str: string): string {\n  return `${styles.bold.open}${str}${styles.bold.close}`\n}\n\nexport function underline(str: string): string {\n  return `${styles.underline.open}${str}${styles.underline.close}`\n}\n\nexport function strikethrough(str: string): string {\n  return `${styles.strikethrough.open}${str}${styles.strikethrough.close}`\n}\n\nexport function italic(str: string): string {\n  return `${styles.italic.open}${str}${styles.italic.close}`\n}\n\nexport function yellow(str: string): string {\n  return `${styles.yellow.open}${str}${styles.yellow.close}`\n}\n\nexport function green(str: string): string {\n  return `${styles.green.open}${str}${styles.green.close}`\n}\n\nexport function blue(str: string): string {\n  return `${styles.blue.open}${str}${styles.blue.close}`\n}\n"
  },
  {
    "path": "src/model/bufferSync.ts",
    "content": "'use strict'\nimport type Documents from '../core/documents'\nimport events, { VisibleEvent } from '../events'\nimport { DidChangeTextDocumentParams } from '../types'\nimport { disposeAll } from '../util'\nimport * as Is from '../util/is'\nimport { Disposable } from '../util/protocol'\nimport type Document from './document'\n\nexport interface SyncItem extends Disposable {\n  onChange?(e: DidChangeTextDocumentParams): void\n  onTextChange?(): void\n  onVisible?(winid: number, region: Readonly<[number, number]>)\n}\n\n/**\n * Buffer sync support, document is always attached and not command line buffer.\n */\nexport default class BufferSync<T extends SyncItem> {\n  private disposables: Disposable[] = []\n  private itemsMap: Map<number, { uri: string, item: T }> = new Map()\n  constructor(private _create: (doc: Document) => T | undefined, documents: Documents) {\n    let { disposables } = this\n    for (let doc of documents.attached()) {\n      this.create(doc)\n    }\n    documents.onDidOpenTextDocument(e => {\n      this.create(documents.getDocument(e.bufnr))\n    }, null, disposables)\n    documents.onDidChangeDocument(e => {\n      this.onChange(e)\n    }, null, disposables)\n    documents.onDidCloseDocument(e => {\n      this.delete(e.bufnr)\n    }, null, disposables)\n    events.on('LinesChanged', this.onTextChange, this, disposables)\n    events.on('WindowVisible', this.onVisible, this, disposables)\n  }\n\n  private onTextChange(bufnr: number): void {\n    let o = this.itemsMap.get(bufnr)\n    if (o && Is.func(o.item.onTextChange)) {\n      o.item.onTextChange()\n    }\n  }\n\n  private onVisible(ev: VisibleEvent): void {\n    let o = this.itemsMap.get(ev.bufnr)\n    if (o && typeof o.item.onVisible === 'function') {\n      o.item.onVisible(ev.winid, ev.region)\n    }\n  }\n\n  public get items(): ReadonlyArray<T> {\n    return Array.from(this.itemsMap.values()).map(x => x.item)\n  }\n\n  public getItem(bufnr: number | string | undefined): T | undefined {\n    if (bufnr == null) return undefined\n    if (typeof bufnr === 'number') {\n      return this.itemsMap.get(bufnr)?.item\n    }\n    let o = Array.from(this.itemsMap.values()).find(v => {\n      return v.uri == bufnr\n    })\n    return o ? o.item : undefined\n  }\n\n  public create(doc: Document): void {\n    let o = this.itemsMap.get(doc.bufnr)\n    if (o) o.item.dispose()\n    let item = this._create(doc)\n    if (item) this.itemsMap.set(doc.bufnr, { uri: doc.uri, item })\n  }\n\n  private onChange(e: DidChangeTextDocumentParams): void {\n    let o = this.itemsMap.get(e.bufnr)\n    if (o && typeof o.item.onChange == 'function') {\n      o.item.onChange(e)\n    }\n  }\n\n  private delete(bufnr: number): void {\n    let o = this.itemsMap.get(bufnr)\n    if (o) {\n      o.item.dispose()\n      this.itemsMap.delete(bufnr)\n    }\n  }\n\n  public reset(): void {\n    for (let o of this.itemsMap.values()) {\n      o.item.dispose()\n    }\n    this.itemsMap.clear()\n  }\n\n  public dispose(): void {\n    disposeAll(this.disposables)\n    for (let o of this.itemsMap.values()) {\n      o.item.dispose()\n    }\n    this._create = undefined\n    this.itemsMap.clear()\n  }\n}\n"
  },
  {
    "path": "src/model/chars.ts",
    "content": "'use strict'\nimport { Range } from 'vscode-languageserver-types'\nimport { waitImmediate } from '../util'\nimport { intable } from '../util/array'\nimport { hasOwnProperty } from '../util/object'\nimport { CancellationToken } from '../util/protocol'\nimport { isHighSurrogate } from '../util/string'\n\n// Word ranges from vim, tested by '\\k' option when '@' in iskeyword option.\nconst WORD_RANGES: [number, number][] = [[257, 893], [895, 902], [904, 1369], [1376, 1416], [1418, 1469], [1471, 1471], [1473, 1474], [1476, 1522], [1525, 1547], [1549, 1562], [1564, 1566], [1568, 1641], [1646, 1747], [1749, 1791], [1806, 2403], [2406, 2415], [2417, 3571], [3573, 3662], [3664, 3673], [3676, 3843], [3859, 3897], [3902, 3972], [3974, 4169], [4176, 4346], [4348, 4960], [4969, 5740], [5743, 5759], [5761, 5786], [5789, 5866], [5870, 5940], [5943, 6099], [6109, 6143], [6155, 8191], [10240, 10495], [10649, 10711], [10716, 10747], [10750, 11775], [11904, 12287], [12321, 12335], [12337, 12348], [12350, 64829], [64832, 65071], [65132, 65279], [65296, 65305], [65313, 65338], [65345, 65370], [65382, 65535]]\n\nconst MAX_CODE_UNIT = 65535\n\nconst boundary = 19968\n\nexport function getCharCode(str: string): number | undefined {\n  if (/^\\d+$/.test(str)) return parseInt(str, 10)\n  if (str.length > 0) return str.charCodeAt(0)\n  return undefined\n}\n\nexport function sameScope(a: number, b: number): boolean {\n  if (a < boundary) return b < boundary\n  return b >= boundary\n}\n\nexport function detectLanguage(code: number): string {\n  // 中文范围\n  if (code >= 0x4E00 && code <= 0x9FFF) return 'cn'\n  // 日语平假名、片假名\n  if ((code >= 0x3040 && code <= 0x309F) || (code >= 0x30A0 && code <= 0x30FF)) return 'ja'\n  // 韩语\n  if (code >= 0xAC00 && code <= 0xD7AF) return 'ko'\n  return ''\n}\n\nexport function* parseSegments(text: string, segmenterLocales: string): Iterable<string> {\n  if (Intl === undefined || typeof Intl['Segmenter'] !== 'function') {\n    yield text\n    return\n  }\n  let res: string[] = []\n  let items = new Intl['Segmenter'](segmenterLocales === '' ? undefined : segmenterLocales, { granularity: 'word' }).segment(text)\n  for (let item of items) {\n    if (item.isWordLike) {\n      yield item.segment\n    }\n  }\n  return res\n}\n\nexport function splitKeywordOption(iskeyword: string): string[] {\n  let res: string[] = []\n  let i = 0\n  let s = 0\n  let len = iskeyword.length\n  for (; i < len; i++) {\n    let c = iskeyword[i]\n    if (i + 1 == len && s != len) {\n      res.push(iskeyword.slice(s, len))\n      continue\n    }\n    if (c == ',') {\n      let d = i - s\n      if (d == 0) continue\n      if (d == 1) {\n        let p = iskeyword[i - 1]\n        if (p == '^' || p == ',') {\n          res.push(p == ',' ? ',' : '^,')\n          s = i + 1\n          if (p == '^' && iskeyword[i + 1] == ',') {\n            i++\n            s++\n          }\n          continue\n        }\n      }\n      res.push(iskeyword.slice(s, i))\n      s = i + 1\n    }\n  }\n  return res\n}\n\nexport class IntegerRanges {\n  /**\n   * Sorted ranges without overlap\n   */\n  constructor(private ranges: [number, number][] = [], public wordChars = false) {\n  }\n\n  public clone(): IntegerRanges {\n    return new IntegerRanges(this.ranges.slice(), this.wordChars)\n  }\n  /**\n   * Add new range\n   */\n  public add(start: number, end?: number): void {\n    // find newIndex, replace count, new start, new end\n    let index = 0\n    let removeCount = 0\n    if (end != null && end < start) {\n      let t = end\n      end = start\n      start = t\n    }\n    end = end == null ? start : end\n    for (let r of this.ranges) {\n      let [s, e] = r\n      if (e < start) {\n        index++\n        continue\n      }\n      if (s > end) break\n      // overlap\n      removeCount++\n      if (s < start) start = s\n      if (e > end) {\n        end = e\n        break\n      }\n    }\n    this.ranges.splice(index, removeCount, [start, end])\n  }\n\n  public exclude(start: number, end?: number): void {\n    if (end != null && end < start) {\n      let t = end\n      end = start\n      start = t\n    }\n    end = end == null ? start : end\n    let index = 0\n    let removeCount = 0\n    let created: [number, number][] = []\n    for (let r of this.ranges) {\n      let [s, e] = r\n      if (e < start) {\n        index++\n        continue\n      }\n      if (s > end) break\n      removeCount++\n      if (s < start) {\n        created.push([s, start - 1])\n      }\n      if (e > end) {\n        created.push([end + 1, e])\n        break\n      }\n    }\n    if (removeCount == 0 && created.length == 0) return\n    this.ranges.splice(index, removeCount, ...created)\n  }\n\n  public flatten(): number[] {\n    return this.ranges.reduce((p, c) => p.concat(c), [])\n  }\n\n  public includes(n: number): boolean {\n    if (n > 256 && this.wordChars) return intable(n, WORD_RANGES)\n    return intable(n, this.ranges)\n  }\n\n  public static fromKeywordOption(iskeyword: string): IntegerRanges {\n    let range = new IntegerRanges()\n    for (let part of splitKeywordOption(iskeyword)) {\n      let exclude = part.length > 1 && part.startsWith('^')\n      let method = exclude ? 'exclude' : 'add'\n      if (exclude) part = part.slice(1)\n      if (part === '@' && !exclude) { // all word class\n        range.wordChars = true\n        range[method](65, 90)\n        range[method](97, 122)\n        range[method](192, 255)\n      } else if (part == '@-@') {\n        range[method]('@'.charCodeAt(0))\n      } else if (part.length == 1 || /^\\d+$/.test(part)) {\n        range[method](getCharCode(part))\n      } else if (part.includes('-')) {\n        let items = part.split('-', 2)\n        let start = getCharCode(items[0])\n        let end = getCharCode(items[1])\n        if (start === undefined || end === undefined) continue\n        range[method](start, end)\n      }\n    }\n    return range\n  }\n}\n\nexport class Chars {\n  public ranges: IntegerRanges\n  constructor(keywordOption: string) {\n    this.ranges = IntegerRanges.fromKeywordOption(keywordOption)\n  }\n\n  public addKeyword(ch: string): void {\n    this.ranges.add(ch.codePointAt(0))\n  }\n\n  public clone(): Chars {\n    let chars = new Chars('')\n    chars.ranges = this.ranges.clone()\n    return chars\n  }\n\n  public isKeywordCode(code: number): boolean {\n    if (code === 32 || code > MAX_CODE_UNIT) return false\n    if (isHighSurrogate(code)) return false\n    return this.ranges.includes(code)\n  }\n\n  public isKeywordChar(ch: string): boolean {\n    let code = ch.charCodeAt(0)\n    return this.isKeywordCode(code)\n  }\n\n  public isKeyword(word: string): boolean {\n    for (let i = 0, l = word.length; i < l; i++) {\n      if (!this.isKeywordChar(word[i])) return false\n    }\n    return true\n  }\n\n  public *iterateWords(text: string): Iterable<[number, number]> {\n    let start = -1\n    let prevCode: number | undefined\n    for (let i = 0, l = text.length; i < l; i++) {\n      let code = text.charCodeAt(i)\n      if (this.isKeywordCode(code)) {\n        if (start == -1) {\n          start = i\n        } else if (prevCode !== undefined && !sameScope(prevCode, code)) {\n          yield [start, i]\n          start = i\n        }\n      } else {\n        if (start != -1) {\n          yield [start, i]\n          start = -1\n        }\n      }\n      if (i === l - 1 && start != -1) {\n        yield [start, i + 1]\n      }\n      prevCode = code\n    }\n  }\n\n  public matchLine(line: string, segmenterLocales = undefined, min = 2, max = 1024): string[] {\n    let res: Set<string> = new Set()\n    let l = line.length\n    if (l > max) {\n      line = line.slice(0, max)\n      l = max\n    }\n    for (let [start, end] of this.iterateWords(line)) {\n      if (end - start < min) continue\n      let word = line.slice(start, end)\n      let code = word.charCodeAt(0)\n      if (segmenterLocales != null && code > 255) {\n        if (segmenterLocales == '') {\n          segmenterLocales = detectLanguage(code)\n        }\n        for (let text of parseSegments(word, segmenterLocales)) {\n          res.add(text)\n        }\n      } else {\n        res.add(word)\n      }\n    }\n    return Array.from(res)\n  }\n\n  public async computeWordRanges(lines: ReadonlyArray<string>, range: Range, token?: CancellationToken): Promise<{ [word: string]: Range[] }> {\n    let s = range.start.line\n    let e = range.end.line\n    let res: { [word: string]: Range[] } = {}\n    let ts = Date.now()\n    for (let i = s; i <= e; i++) {\n      let text = lines[i]\n      if (text === undefined) break\n      let sc = i === s ? range.start.character : 0\n      if (i === s) text = text.slice(sc)\n      if (i === e) text = text.slice(0, range.end.character - sc)\n      if (Date.now() - ts > 15) {\n        if (token && token.isCancellationRequested) break\n        await waitImmediate()\n        ts = Date.now()\n      }\n      for (let [start, end] of this.iterateWords(text)) {\n        let word = text.slice(start, end)\n        let arr = hasOwnProperty(res, word) ? res[word] : []\n        arr.push(Range.create(i, start + sc, i, end + sc))\n        res[word] = arr\n      }\n    }\n    return res\n  }\n}\n"
  },
  {
    "path": "src/model/db.ts",
    "content": "'use strict'\nimport { fs, path } from '../util/node'\nimport { toObject } from '../util/object'\n\nexport default class DB {\n  constructor(public readonly filepath: string) {\n  }\n\n  /**\n   * Get data by key.\n   * @param {string} key unique key allows dot notation.\n   * @returns {any}\n   */\n  public fetch(key: string | undefined): any {\n    let obj = this.load()\n    if (!key) return obj\n    let parts = key.split('.')\n    for (let part of parts) {\n      if (typeof obj[part] === 'undefined') {\n        return undefined\n      }\n      obj = obj[part]\n    }\n    return obj\n  }\n\n  /**\n   * Check if key exists\n   * @param {string} key unique key allows dot notation.\n   */\n  public exists(key: string): boolean {\n    let obj = this.load()\n    let parts = key.split('.')\n    for (let part of parts) {\n      if (typeof obj[part] === 'undefined') {\n        return false\n      }\n      obj = obj[part]\n    }\n    return true\n  }\n\n  /**\n   * Delete data by key\n   * @param {string} key unique key allows dot notation.\n   */\n  public delete(key: string): void {\n    let obj = this.load()\n    let origin = obj\n    let parts = key.split('.')\n    let len = parts.length\n    for (let i = 0; i < len; i++) {\n      if (typeof obj[parts[i]] === 'undefined') {\n        break\n      }\n      if (i == len - 1) {\n        delete obj[parts[i]]\n        fs.writeFileSync(this.filepath, JSON.stringify(origin, null, 2), 'utf8')\n        break\n      }\n      obj = obj[parts[i]]\n    }\n  }\n\n  /**\n   * Save data with key\n   * @param {string} key unique string that allows dot notation.\n   * @param {number|null|boolean|string|{[index} data saved data.\n   */\n  public push(key: string, data: number | null | boolean | string | { [index: string]: any }): void {\n    let origin = toObject(this.load())\n    let obj = origin\n    let parts = key.split('.')\n    let len = parts.length\n    for (let i = 0; i < len; i++) {\n      let key = parts[i]\n      if (i == len - 1) {\n        obj[key] = data\n        let dir = path.dirname(this.filepath)\n        fs.mkdirSync(dir, { recursive: true })\n        fs.writeFileSync(this.filepath, JSON.stringify(origin, null, 2))\n        break\n      }\n      if (typeof obj[key] == 'undefined') {\n        obj[key] = {}\n        obj = obj[key]\n      } else {\n        obj = obj[key]\n      }\n    }\n  }\n\n  private load(): any {\n    let dir = path.dirname(this.filepath)\n    let exists = fs.existsSync(dir)\n    if (!exists) {\n      fs.mkdirSync(dir, { recursive: true })\n      fs.writeFileSync(this.filepath, '{}', 'utf8')\n      return {}\n    }\n    try {\n      let content = fs.readFileSync(this.filepath, 'utf8')\n      return JSON.parse(content.trim())\n    } catch (e) {\n      fs.writeFileSync(this.filepath, '{}', 'utf8')\n      return {}\n    }\n  }\n\n  /**\n   * Empty db file.\n   */\n  public clear(): void {\n    let exists = fs.existsSync(this.filepath)\n    if (!exists) return\n    fs.writeFileSync(this.filepath, '{}', 'utf8')\n  }\n\n  /**\n   * Remove db file.\n   */\n  public destroy(): void {\n    if (fs.existsSync(this.filepath)) {\n      fs.unlinkSync(this.filepath)\n    }\n  }\n}\n"
  },
  {
    "path": "src/model/dialog.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { Disposable, Emitter, Event } from '../util/protocol'\nimport events from '../events'\nimport { HighlightItem } from '../types'\nimport { disposeAll } from '../util'\nimport { toArray } from '../util/array'\n\nexport interface DialogButton {\n  /**\n   * Used by callback, should >= 0\n   */\n  index: number\n  text: string\n  /**\n   * Not shown when true\n   */\n  disabled?: boolean\n}\n\nexport interface DialogPreferences {\n  rounded?: boolean\n  maxWidth?: number\n  maxHeight?: number\n  floatHighlight?: string\n  floatBorderHighlight?: string\n  pickerButtons?: boolean\n  pickerButtonShortcut?: boolean\n  confirmKey?: string\n  shortcutHighlight?: string\n}\n\nexport interface DialogConfig {\n  content: string\n  /**\n   * Optional title text.\n   */\n  title?: string\n  /**\n   * show close button, default to true when not specified.\n   */\n  close?: boolean\n  /**\n   * highlight group for dialog window, default to `\"dialog.floatHighlight\"` or 'CocFloating'\n   */\n  highlight?: string\n  /**\n   * highlight items of content.\n   */\n  highlights?: ReadonlyArray<HighlightItem>\n  /**\n   * highlight groups for border, default to `\"dialog.borderhighlight\"` or 'CocFloatBorder'\n   */\n  borderhighlight?: string\n  /**\n   * Buttons as bottom of dialog.\n   */\n  buttons?: DialogButton[]\n  /**\n   * index is -1 for window close without button click\n   */\n  callback?: (index: number) => void\n}\n\nexport class Dialog {\n  private disposables: Disposable[] = []\n  private bufnr: number\n  private readonly _onDidClose = new Emitter<void>()\n  public readonly onDidClose: Event<void> = this._onDidClose.event\n  constructor(private nvim: Neovim, private config: DialogConfig) {\n    events.on('BufWinLeave', bufnr => {\n      if (bufnr == this.bufnr) {\n        this.dispose()\n        if (config.callback) config.callback(-1)\n      }\n    }, null, this.disposables)\n    let btns = toArray(config.buttons).filter(o => o.disabled != true)\n    events.on('FloatBtnClick', (bufnr, idx) => {\n      if (bufnr == this.bufnr) {\n        this.dispose()\n        if (config.callback) config.callback(btns[idx].index)\n      }\n    }, null, this.disposables)\n  }\n\n  private get lines(): string[] {\n    return [...this.config.content.split(/\\r?\\n/)]\n  }\n\n  public async show(preferences: DialogPreferences): Promise<void> {\n    let { nvim } = this\n    let { title, close, highlights, buttons } = this.config\n    let borderhighlight = this.config.borderhighlight || preferences.floatBorderHighlight\n    let highlight = this.config.highlight || preferences.floatHighlight\n    let opts: any = { maxwidth: preferences.maxWidth || 80, }\n    if (title) opts.title = title\n    opts.close = +(close ?? 1)\n    if (preferences.maxHeight) opts.maxHeight = preferences.maxHeight\n    if (preferences.maxWidth) opts.maxWidth = preferences.maxWidth\n    if (highlight) opts.highlight = highlight\n    if (highlights) opts.highlights = highlights\n    if (borderhighlight) opts.borderhighlight = [borderhighlight]\n    if (buttons) opts.buttons = buttons.filter(o => !o.disabled).map(o => o.text)\n    if (preferences.rounded) opts.rounded = 1\n    if (Array.isArray(opts.buttons)) opts.getchar = 1\n    let [_winid, bufnr] = await nvim.call('coc#dialog#create_dialog', [this.lines, opts]) as [number, number]\n    this.bufnr = bufnr\n    nvim.command('redraw', true)\n  }\n\n  public get winid(): Promise<number | null> {\n    if (!this.bufnr) return Promise.resolve(null)\n    return this.nvim.call('bufwinid', [this.bufnr]) as Promise<number>\n  }\n\n  public dispose(): void {\n    this._onDidClose.fire()\n    this.bufnr = undefined\n    disposeAll(this.disposables)\n    this.disposables = []\n  }\n}\n"
  },
  {
    "path": "src/model/document.ts",
    "content": "'use strict'\nimport { Buffer, Neovim, VimValue } from '@chemzqm/neovim'\nimport { Buffer as NodeBuffer } from 'buffer'\nimport { Position, Range, TextEdit } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport events from '../events'\nimport { createLogger } from '../logger'\nimport { BufferOption, DidChangeTextDocumentParams, HighlightItem, HighlightItemOption, TextDocumentContentChange } from '../types'\nimport { toArray } from '../util/array'\nimport { isVim } from '../util/constants'\nimport { diffLines, getTextEdit } from '../util/diff'\nimport { disposeAll, getConditionValue, sha256, wait, waitNextTick } from '../util/index'\nimport { isUrl } from '../util/is'\nimport { debounce, path } from '../util/node'\nimport { equals, toObject } from '../util/object'\nimport { emptyRange } from '../util/position'\nimport { Disposable, Emitter, Event } from '../util/protocol'\nimport { byteIndex, byteLength, byteSlice, characterIndex, toText } from '../util/string'\nimport { applyEdits, filterSortEdits, getPositionFromEdits, getStartLine, mergeTextEdits, TextChangeItem, toTextChanges } from '../util/textedit'\nimport { Chars } from './chars'\nimport { LinesTextDocument } from './textdocument'\nconst logger = createLogger('document')\nconst MAX_EDITS = getConditionValue(200, 400)\n\nexport type LastChangeType = 'insert' | 'change' | 'delete'\nexport type VimBufferChange = [number, number, string[]]\n\nexport interface Env {\n  readonly filetypeMap: { [index: string]: string }\n  readonly isCygwin: boolean\n}\n\nexport interface ChangeInfo {\n  lnum: number\n  line: string\n  changedtick: number\n}\n\nexport interface CursorAndCol {\n  cursor?: [number, number]\n  col?: number\n}\n\nconst debounceTime = getConditionValue(150, 15)\n\n// getText, positionAt, offsetAt\nexport default class Document {\n  public buftype: string\n  public isIgnored = false\n  public chars: Chars\n  private eol = true\n  private _disposed = false\n  private _attached = false\n  private _notAttachReason = ''\n  private _previewwindow = false\n  private _winid = -1\n  private _winids: number[] = []\n  private _filetype: string\n  private _bufname: string\n  private _commandLine = false\n  private _applying = false\n  private _uri: string\n  private _changedtick: number\n  private variables: { [key: string]: VimValue }\n  private disposables: Disposable[] = []\n  private _textDocument: LinesTextDocument\n  // real current lines\n  private lines: ReadonlyArray<string> = []\n  private _applyLines: ReadonlyArray<string>\n  public fireContentChanges: (() => void) & { clear(): void } & { flush(): void }\n  public fetchContent: (() => void) & { clear(): void } & { flush(): void }\n  private _onDocumentChange = new Emitter<DidChangeTextDocumentParams>()\n  public readonly onDocumentChange: Event<DidChangeTextDocumentParams> = this._onDocumentChange.event\n  constructor(\n    public readonly buffer: Buffer,\n    private nvim: Neovim,\n    filetype: string,\n    opts: BufferOption\n  ) {\n    this.fireContentChanges = debounce(() => {\n      this._fireContentChanges()\n    }, debounceTime)\n    this.init(filetype, opts)\n  }\n\n  /**\n   * Synchronize content\n   */\n  public get content(): string {\n    return this.syncLines.join('\\n') + (this.eol ? '\\n' : '')\n  }\n\n  public get attached(): boolean {\n    return this._attached\n  }\n\n  /**\n   * Synchronized textDocument.\n   */\n  public get textDocument(): LinesTextDocument {\n    return this._textDocument\n  }\n\n  private get syncLines(): ReadonlyArray<string> {\n    return this._textDocument.lines\n  }\n\n  public get version(): number {\n    return this._textDocument.version\n  }\n  /**\n   * Buffer number\n   */\n  public get bufnr(): number {\n    return this.buffer.id\n  }\n\n  public get bufname(): string {\n    return this._bufname\n  }\n\n  public get filetype(): string {\n    return this._filetype\n  }\n\n  public get uri(): string {\n    return this._uri\n  }\n\n  public get isCommandLine(): boolean {\n    return this._commandLine\n  }\n\n  /**\n   * LanguageId of TextDocument, main filetype are used for combined filetypes\n   * with '.'\n   */\n  public get languageId(): string {\n    let { _filetype } = this\n    return _filetype.includes('.') ? _filetype.match(/(.*?)\\./)[1] : _filetype\n  }\n\n  /**\n   * Get current buffer changedtick.\n   */\n  public get changedtick(): number {\n    return this._changedtick\n  }\n\n  /**\n   * Scheme of document.\n   */\n  public get schema(): string {\n    return URI.parse(this.uri).scheme\n  }\n\n  /**\n   * Line count of current buffer.\n   */\n  public get lineCount(): number {\n    return this.lines.length\n  }\n\n  /**\n   * Window ID when buffer create, could be -1 when no window associated.\n   */\n  public get winid(): number {\n    return this._winid\n  }\n\n  public get winids(): ReadonlyArray<number> {\n    return this._winids\n  }\n\n  /**\n   * Returns if current document is opened with previewwindow\n   * @deprecated\n   */\n  public get previewwindow(): boolean {\n    return this._previewwindow\n  }\n\n  /**\n   * Initialize document model.\n   */\n  private init(filetype: string, opts: BufferOption): void {\n    let buftype = this.buftype = opts.buftype\n    this._bufname = opts.bufname\n    this._commandLine = opts.commandline === 1\n    this._previewwindow = !!opts.previewwindow\n    this._winid = opts.winid\n    this._winids = toArray(opts.winids)\n    this.variables = toObject(opts.variables)\n    this._changedtick = opts.changedtick\n    this.eol = opts.eol == 1\n    this._uri = getUri(opts.fullpath, this.bufnr, buftype)\n    if (Array.isArray(opts.lines)) {\n      this.lines = opts.lines.map(line => toText(line))\n      this._attached = true\n      this.attach()\n    } else {\n      this.lines = []\n      this._notAttachReason = getNotAttachReason(buftype, this.variables[`coc_enabled`] as number, opts.size)\n    }\n    this._filetype = filetype\n    this.setIskeyword(opts.iskeyword, opts.lisp)\n    this.createTextDocument(1, this.lines)\n  }\n\n  public get notAttachReason(): string {\n    return this._notAttachReason\n  }\n\n  public attach(): void {\n    let lines = this.lines\n    const { bufnr } = this\n    this.buffer.attach(true).then(res => {\n      if (!res) fireDetach(this.bufnr)\n    }, _e => {\n      fireDetach(this.bufnr)\n    })\n    const onLinesChange = (_buf: number | Buffer, tick: number | null, firstline: number, lastline: number, linedata: string[]) => {\n      if (tick && tick > this._changedtick) {\n        this._changedtick = tick\n        lines = [...lines.slice(0, firstline), ...linedata, ...(lastline < 0 ? [] : lines.slice(lastline))]\n        if (lines.length == 0) lines = ['']\n        if (this._applying) {\n          this._applyLines = lines\n          return\n        }\n        this.lines = lines\n        fireLinesChanged(bufnr)\n        if (events.completing) return\n        this.fireContentChanges()\n      }\n    }\n    if (isVim) {\n      this.buffer.listen('vim_lines', onLinesChange, this.disposables)\n    } else {\n      this.buffer.listen('lines', onLinesChange, this.disposables)\n      this.buffer.listen('detach', () => {\n        fireDetach(this.bufnr)\n      }, this.disposables)\n    }\n  }\n\n  /**\n   * Check if document changed after last synchronize\n   */\n  public get dirty(): boolean {\n    // if (this.lines === this.syncLines) return false\n    // return !equals(this.lines, this.syncLines)\n    return this.lines !== this.syncLines\n  }\n\n  public get hasChanged(): boolean {\n    if (!this.dirty) return false\n    return !equals(this.lines, this.syncLines)\n  }\n\n  /**\n   * Cursor position if document is current document\n   */\n  public get cursor(): Position | undefined {\n    let { cursor } = events\n    if (cursor.bufnr !== this.bufnr) return undefined\n    let content = toText(this.lines[cursor.lnum - 1])\n    return Position.create(cursor.lnum - 1, characterIndex(content, cursor.col - 1))\n  }\n\n  private _fireContentChanges(edit?: TextEdit): void {\n    if (this.lines === this.syncLines) return\n    let textDocument = this._textDocument\n    let changes: TextDocumentContentChange[] = []\n    if (!edit) edit = getTextEdit(textDocument.lines, this.lines, this.cursor, events.cursor.insert)\n    let original: string\n    if (edit) {\n      original = textDocument.getText(edit.range)\n      changes.push({ range: edit.range, text: edit.newText, rangeLength: original.length })\n    } else {\n      original = ''\n    }\n    let created = this.createTextDocument(this.version + (edit ? 1 : 0), this.lines)\n    this._onDocumentChange.fire(Object.freeze({\n      bufnr: this.bufnr,\n      original,\n      originalLines: textDocument.lines,\n      textDocument: { version: created.version, uri: this.uri },\n      document: created,\n      contentChanges: changes\n    }))\n  }\n\n  public async applyEdits(edits: TextEdit[], joinUndo = false, move: boolean | Position = false): Promise<TextEdit | undefined> {\n    if (Array.isArray(arguments[1])) edits = arguments[1]\n    if (!this._attached || edits.length === 0) return\n    const { bufnr } = this\n    this._forceSync()\n    let textDocument = this.textDocument\n    edits = filterSortEdits(textDocument, edits)\n    // apply edits to current textDocument\n    let newLines = applyEdits(textDocument, edits)\n    if (!newLines) return\n    let lines = textDocument.lines\n    let changed = diffLines(lines, newLines, getStartLine(edits[0]))\n    // append new lines\n    let isAppend = changed.start === changed.end && changed.start === lines.length\n    let original = lines.slice(changed.start, changed.end)\n    let changes: TextChangeItem[] = []\n    // Avoid too many buf_set_text cause nvim slow.\n    // Not used when insert or delete lines.\n    if (edits.length <= MAX_EDITS && changed.start !== changed.end && changed.replacement.length > 0) {\n      changes = toTextChanges(lines, edits)\n    }\n    const { cursor, col } = this.getCursorAndCol(move, edits, newLines)\n    this.nvim.pauseNotification()\n    if (joinUndo) this.nvim.command(`if bufnr('%') == ${bufnr} | undojoin | endif`, true)\n    if (isAppend) {\n      this.buffer.setLines(changed.replacement, { start: -1, end: -1 }, true)\n    } else {\n      this.nvim.call('coc#ui#set_lines', [\n        this.bufnr,\n        this._changedtick,\n        original,\n        changed.replacement,\n        changed.start,\n        changed.end,\n        changes,\n        cursor,\n        col,\n        lines.length\n      ], true)\n    }\n    this._applying = true\n    void this.nvim.resumeNotification(true, true)\n    this.lines = newLines\n    await waitNextTick()\n    fireLinesChanged(bufnr)\n    let textEdit = edits.length == 1 ? edits[0] : mergeTextEdits(edits, lines, newLines)\n    this.fireContentChanges.clear()\n    this._fireContentChanges(textEdit)\n    let range = Range.create(changed.start, 0, changed.start + changed.replacement.length, 0)\n    return TextEdit.replace(range, original.join('\\n') + (original.length > 0 ? '\\n' : ''))\n  }\n\n  public onTextChange(): void {\n    let { bufnr } = this\n    if (this._applying) {\n      this._applying = false\n      if (this._applyLines != null && !equals(this._applyLines, this.textDocument.lines)) {\n        this.lines = this._applyLines\n        this._applyLines = undefined\n        fireLinesChanged(bufnr)\n        this.fireContentChanges()\n      }\n    }\n  }\n\n  private getCursorAndCol(move: boolean | Position, edits: TextEdit[], newLines: ReadonlyArray<string>): CursorAndCol {\n    if (!move) return {}\n    let pos = Position.is(move) ? move : this.cursor\n    if (pos) {\n      let position = getPositionFromEdits(pos, edits)\n      if (!equals(pos, position)) {\n        let content = toText(newLines[position.line])\n        let column = byteIndex(content, position.character) + 1\n        return {\n          cursor: [position.line + 1, column],\n          col: byteIndex(this.lines[pos.line], pos.character) + 1\n        }\n      }\n    }\n    return {}\n  }\n\n  public async changeLines(lines: [number, string][]): Promise<void> {\n    let filtered: [number, string][] = []\n    let newLines = this.lines.slice()\n    for (let [lnum, text] of lines) {\n      if (newLines[lnum] != text) {\n        filtered.push([lnum, text])\n        newLines[lnum] = text\n      }\n    }\n    if (!filtered.length) return\n    this.nvim.call('coc#ui#change_lines', [this.bufnr, filtered], true)\n    this.nvim.redrawVim()\n    this.lines = newLines\n    await waitNextTick()\n    fireLinesChanged(this.bufnr)\n    this._forceSync()\n  }\n\n  public _forceSync(): void {\n    if (!this._attached) return\n    this.fireContentChanges.clear()\n    this._fireContentChanges()\n  }\n\n  public forceSync(): void {\n    // may cause bugs, prevent extensions use it.\n    if (global.__TEST__) {\n      this._forceSync()\n    }\n  }\n\n  /**\n   * Get offset from lnum & col\n   */\n  public getOffset(lnum: number, col: number): number {\n    return this.textDocument.offsetAt({\n      line: lnum - 1,\n      character: col\n    })\n  }\n\n  /**\n   * Check string is word.\n   */\n  public isWord(word: string): boolean {\n    return this.chars.isKeyword(word)\n  }\n\n  public getStartWord(text: string): string {\n    let i = 0\n    for (; i < text.length; i++) {\n      if (!this.chars.isKeywordChar(text[i])) break\n    }\n    return text.slice(0, i)\n  }\n\n  /**\n   * Current word for replacement\n   */\n  public getWordRangeAtPosition(position: Position, extraChars?: string, current = true): Range | null {\n    let chars = this.chars\n    if (extraChars && extraChars.length) {\n      chars = this.chars.clone()\n      for (let ch of extraChars) {\n        chars.addKeyword(ch)\n      }\n    }\n    let line = this.getline(position.line, current)\n    let ch = line[position.character]\n    if (ch == null || !chars.isKeywordChar(ch)) return null\n    let start = position.character\n    let end = position.character + 1\n    while (start >= 0) {\n      let ch = line[start - 1]\n      if (!ch || !chars.isKeywordChar(ch)) break\n      start = start - 1\n    }\n    while (end <= line.length) {\n      let ch = line[end]\n      if (!ch || !chars.isKeywordChar(ch)) break\n      end = end + 1\n    }\n    return Range.create(position.line, start, position.line, end)\n  }\n\n  private createTextDocument(version: number, lines: ReadonlyArray<string>): LinesTextDocument {\n    let { uri, languageId, eol } = this\n    let textDocument = this._textDocument = new LinesTextDocument(uri, languageId, version, lines, this.bufnr, eol)\n    return textDocument\n  }\n\n  /**\n   * Get ranges of word in textDocument.\n   */\n  public getSymbolRanges(word: string): Range[] {\n    let { version, languageId, uri } = this\n    let textDocument = new LinesTextDocument(uri, languageId, version, this.lines, this.bufnr, this.eol)\n    let res: Range[] = []\n    let content = textDocument.getText()\n    let str = ''\n    for (let i = 0, l = content.length; i < l; i++) {\n      let ch = content[i]\n      if ('-' == ch && str.length == 0) {\n        continue\n      }\n      let isKeyword = this.chars.isKeywordChar(ch)\n      if (isKeyword) {\n        str = str + ch\n      }\n      if (str.length > 0 && !isKeyword && str == word) {\n        res.push(Range.create(textDocument.positionAt(i - str.length), textDocument.positionAt(i)))\n      }\n      if (!isKeyword) {\n        str = ''\n      }\n    }\n    return res\n  }\n\n  /**\n   * Adjust col with new valid character before position.\n   */\n  public fixStartcol(position: Position, valids: string[]): number {\n    let line = this.getline(position.line)\n    if (!line) return 0\n    let { character } = position\n    let start = line.slice(0, character)\n    let col = byteLength(start)\n    let { chars } = this\n    for (let i = start.length - 1; i >= 0; i--) {\n      let c = start[i]\n      if (!chars.isKeywordChar(c) && !valids.includes(c)) {\n        break\n      }\n      col = col - byteLength(c)\n    }\n    return col\n  }\n\n  /**\n   * Add vim highlight items from highlight group and range.\n   * Synchronized lines are used for calculate cols.\n   */\n  public addHighlights(items: HighlightItem[], hlGroup: string, range: Range, opts: HighlightItemOption = {}): void {\n    let { start, end } = range\n    if (emptyRange(range)) return\n    for (let line = start.line; line <= end.line; line++) {\n      const text = this.getline(line, false)\n      let colStart = line == start.line ? byteIndex(text, start.character) : 0\n      let colEnd = line == end.line ? byteIndex(text, end.character) : NodeBuffer.byteLength(text)\n      if (colStart >= colEnd) continue\n      items.push(Object.assign({ hlGroup, lnum: line, colStart, colEnd }, opts))\n    }\n  }\n\n  /**\n   * Line content 0 based line\n   */\n  public getline(line: number, current = true): string {\n    if (current) return this.lines[line] || ''\n    return this.syncLines[line] || ''\n  }\n\n  /**\n   * Get lines, zero indexed, end exclude.\n   */\n  public getLines(start?: number, end?: number): string[] {\n    return this.lines.slice(start ?? 0, end ?? this.lines.length)\n  }\n\n  /**\n   * Get current content text.\n   */\n  public getDocumentContent(): string {\n    let content = this.lines.join('\\n')\n    return this.eol ? content + '\\n' : content\n  }\n\n  /**\n   * Get variable value by key, defined by `b:coc_{key}`\n   */\n  public getVar<T extends VimValue>(key: string, defaultValue?: T): T {\n    let val = this.variables[`coc_${key}`] as T\n    return val === undefined ? defaultValue : val\n  }\n\n  /**\n   * Get position from lnum & col\n   */\n  public getPosition(lnum: number, col: number): Position {\n    let line = this.getline(lnum - 1)\n    if (!line || col == 0) return { line: lnum - 1, character: 0 }\n    let pre = byteSlice(line, 0, col - 1)\n    return { line: lnum - 1, character: pre.length }\n  }\n\n  /**\n   * Recreate document with new filetype.\n   */\n  public setFiletype(filetype: string): void {\n    this._filetype = filetype\n    let lines = this.lines\n    this._textDocument = new LinesTextDocument(this.uri, this.languageId, 1, lines, this.bufnr, this.eol)\n  }\n\n  /**\n   * Change iskeyword option of document\n   */\n  public setIskeyword(iskeyword: string, lisp?: number): void {\n    let chars = this.chars = new Chars(iskeyword)\n    let additional = this.getVar<string[]>('additional_keywords', [])\n    if (lisp) chars.addKeyword('-')\n    if (additional && Array.isArray(additional)) {\n      for (let ch of additional) {\n        chars.addKeyword(ch)\n      }\n    }\n  }\n\n  /**\n   * Detach document.\n   */\n  public detach(): void {\n    disposeAll(this.disposables)\n    if (this._disposed) return\n    this._disposed = true\n    this._attached = false\n    this.lines = []\n    this.fireContentChanges.clear()\n    this._onDocumentChange.dispose()\n  }\n\n  /**\n   * Synchronize latest document content\n   */\n  public async synchronize(): Promise<void> {\n    if (!this.attached) return\n    let { changedtick } = this\n    await this.patchChange()\n    if (changedtick != this.changedtick) {\n      await wait(30)\n    }\n  }\n\n  /**\n   * Synchronize buffer change\n   */\n  public async patchChange(): Promise<void> {\n    if (!this._attached) return\n    // changedtick from buffer events could be not latest. #3003\n    this._changedtick = await this.nvim.call('coc#util#get_changedtick', [this.bufnr]) as number\n    this._forceSync()\n  }\n\n  public getSha256(): string {\n    return sha256(this.lines.join('\\n'))\n  }\n\n  public async fetchLines(): Promise<void> {\n    let lines = await this.nvim.call('getbufline', [this.bufnr, 1, '$']) as ReadonlyArray<string>\n    this.lines = lines\n    fireLinesChanged(this.bufnr)\n    this.fireContentChanges()\n    logger.error(`Buffer ${this.bufnr} not synchronized on vim9, consider send bug report!`)\n  }\n}\n\nfunction fireDetach(bufnr: number): void {\n  void events.fire('BufDetach', [bufnr])\n}\n\nfunction fireLinesChanged(bufnr: number): void {\n  void events.fire('LinesChanged', [bufnr])\n}\n\nexport function getUri(fullpath: string, id: number, buftype: string): string {\n  if (!fullpath) return `untitled:${id}`\n  if (path.isAbsolute(fullpath)) return URI.file(path.normalize(fullpath)).toString()\n  if (isUrl(fullpath)) return URI.parse(fullpath).toString()\n  if (buftype != '') return `${buftype}:${id}`\n  return `unknown:${id}`\n}\n\nexport function getNotAttachReason(buftype: string, enabled: number | undefined, size: number): string {\n  if (!['', 'acwrite'].includes(buftype)) {\n    return `not a normal buffer, buftype \"${buftype}\"`\n  }\n  if (enabled === 0) {\n    return `b:coc_enabled = 0`\n  }\n  return `buffer size ${size} exceed coc.preferences.maxFileSize`\n}\n"
  },
  {
    "path": "src/model/download.ts",
    "content": "'use strict'\nimport http, { IncomingHttpHeaders, IncomingMessage } from 'http'\nimport { URL } from 'url'\nimport { v1 as uuidv1 } from 'uuid'\nimport { createLogger } from '../logger'\nimport { crypto, fs, path } from '../util/node'\nimport { CancellationToken } from '../util/protocol'\nimport { FetchOptions, getRequestModule, resolveRequestOptions, timeout, toURL } from './fetch'\nconst logger = createLogger('model-download')\n\nexport interface DownloadOptions extends Omit<FetchOptions, 'buffer'> {\n  /**\n   * Folder that contains downloaded file or extracted files by untar or unzip\n   */\n  dest: string\n  /**\n   * algorithm for check etag.\n   */\n  etagAlgorithm?: string\n  /**\n   * Remove the specified number of leading path elements for *untar* only, default to `1`.\n   */\n  strip?: number\n  /**\n   * If true, use untar for `.tar.gz` filename\n   */\n  extract?: boolean | 'untar' | 'unzip'\n  onProgress?: (percent: string) => void\n  agent?: http.Agent\n}\n\nexport function getEtag(headers: IncomingHttpHeaders): string | undefined {\n  let header = headers['etag']\n  if (typeof header !== 'string') return undefined\n  header = header.replace(/^W\\//, '')\n  if (!header.startsWith('\"') || !header.endsWith('\"')) return undefined\n  return header.slice(1, -1)\n}\n\nexport function getExtname(dispositionHeader: string): string | undefined {\n  const contentDisposition = require('content-disposition')\n  let disposition = contentDisposition.parse(dispositionHeader)\n  let filename = disposition.parameters.filename\n  if (filename) return path.extname(filename)\n  return undefined\n}\n\n/**\n * Download file from url, with optional untar/unzip support.\n * @param {string} url\n * @param {DownloadOptions} options contains dest folder and optional onProgress callback\n */\nexport default function download(urlInput: string | URL, options: DownloadOptions, token?: CancellationToken, obj: any = {}): Promise<string> {\n  let url = toURL(urlInput)\n  let { etagAlgorithm } = options\n  let { dest, onProgress, extract } = options\n  if (!dest || !path.isAbsolute(dest)) {\n    throw new Error(`Invalid dest path: ${dest}`)\n  }\n  if (!fs.existsSync(dest)) {\n    fs.mkdirSync(dest, { recursive: true })\n  } else {\n    let stat = fs.statSync(dest)\n    if (stat && !stat.isDirectory()) {\n      throw new Error(`${dest} exists, but not directory!`)\n    }\n  }\n  let mod = getRequestModule(url)\n  let opts = resolveRequestOptions(url, options)\n  if (!opts.agent && options.agent) opts.agent = options.agent\n  let extname = path.extname(url.pathname)\n  return new Promise<string>((resolve, reject) => {\n    if (token) {\n      let disposable = token.onCancellationRequested(() => {\n        disposable.dispose()\n        req.destroy(new Error('request aborted'))\n      })\n    }\n    let timer: NodeJS.Timeout\n    const req = mod.request(opts, (res: IncomingMessage) => {\n      if ((res.statusCode >= 200 && res.statusCode < 300) || res.statusCode === 1223) {\n        let headers = res.headers\n        let dispositionHeader = headers['content-disposition']\n        let etag = getEtag(headers)\n        let checkEtag = etag && typeof etagAlgorithm === 'string'\n        if (!extname && dispositionHeader) {\n          extname = getExtname(dispositionHeader)\n        }\n        if (extract === true) {\n          if (extname === '.zip' || headers['content-type'] == 'application/zip') {\n            extract = 'unzip'\n          } else if (extname == '.tgz') {\n            extract = 'untar'\n          } else {\n            reject(new Error(`Unable to detect extract method for ${url}`))\n            return\n          }\n        }\n        let total = Number(headers['content-length'])\n        let hasTotal = !isNaN(total)\n        let cur = 0\n        res.on('error', err => {\n          reject(new Error(`Unable to connect ${url}: ${err.message}`))\n        })\n        let hash = checkEtag ? crypto.createHash(etagAlgorithm) : undefined\n        res.on('data', chunk => {\n          cur += chunk.length\n          if (hash) hash.update(chunk)\n          if (hasTotal) {\n            let percent = (cur / total * 100).toFixed(1)\n            if (typeof onProgress === 'function') {\n              onProgress(percent)\n            } else {\n              logger.info(`Download ${url} progress ${percent}%`)\n            }\n          }\n        })\n        res.on('end', () => {\n          clearTimeout(timer)\n          timer = undefined\n          logger.info('Download completed:', url)\n        })\n        let stream: any\n        if (extract === 'untar') {\n          const tar = require('tar')\n          stream = res.pipe(tar.x({ strip: options.strip ?? 1, C: dest }))\n        } else if (extract === 'unzip') {\n          const unzip = require('unzip-stream')\n          stream = res.pipe(unzip.Extract({ path: dest }))\n        } else {\n          dest = path.join(dest, `${uuidv1()}${extname}`)\n          stream = res.pipe(fs.createWriteStream(dest))\n        }\n        stream.on('finish', () => {\n          if (hash) {\n            if (hash.digest('hex') !== etag) {\n              reject(new Error(`Etag check failed by ${etagAlgorithm}, content not match.`))\n              return\n            }\n          }\n          logger.info(`Downloaded ${url} => ${dest}`)\n          setTimeout(() => {\n            resolve(dest)\n          }, 100)\n        })\n        stream.on('error', reject)\n      } else {\n        reject(new Error(`Invalid response from ${url}: ${res.statusCode}`))\n      }\n    })\n    obj.req = req\n    req.on('error', e => {\n      // Possible succeed proxy request with ECONNRESET error on node > 14\n      if (e['code'] == 'ECONNRESET') {\n        timer = setTimeout(() => {\n          reject(e)\n        }, timeout)\n      } else {\n        clearTimeout(timer)\n        if (opts.agent && opts.agent.proxy) {\n          reject(new Error(`Request failed using proxy ${opts.agent.proxy.host}: ${e.message}`))\n          return\n        }\n        reject(e)\n      }\n    })\n    req.on('timeout', () => {\n      req.destroy(new Error(`request timeout after ${options.timeout}ms`))\n    })\n    if (typeof options.timeout === 'number' && options.timeout) {\n      req.setTimeout(options.timeout)\n    }\n    req.end()\n  })\n}\n"
  },
  {
    "path": "src/model/editInspect.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { ChangeAnnotation, CreateFile, DeleteFile, Position, RenameFile, SnippetTextEdit, TextDocumentEdit, TextEdit, WorkspaceEdit } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport type { LinesChange } from '../core/files'\nimport type Keymaps from '../core/keymaps'\nimport events from '../events'\nimport { DocumentChange } from '../types'\nimport { disposeAll } from '../util'\nimport { toArray } from '../util/array'\nimport { isParentFolder } from '../util/fs'\nimport { fastDiff, path } from '../util/node'\nimport { Disposable } from '../util/protocol'\nimport { getAnnotationKey, getPositionFromEdits, mergeSortEdits } from '../util/textedit'\nimport Highlighter from './highlighter'\n\nexport type RecoverFunc = () => Promise<any> | void\n\nexport interface EditState {\n  edit: WorkspaceEdit\n  changes: {\n    [uri: string]: LinesChange\n  }\n  recovers: RecoverFunc[]\n  applied: boolean\n}\n\nexport interface ChangedFileItem {\n  index: number\n  filepath: string\n  lnum?: number\n}\n\nlet global_id = 0\n\nexport default class EditInspect {\n  private disposables: Disposable[] = []\n  private bufnr: number\n  private items: ChangedFileItem[] = []\n  private renameMap: Map<string, string> = new Map()\n  constructor(private nvim: Neovim, private keymaps: Keymaps) {\n    events.on('BufUnload', bufnr => {\n      if (bufnr == this.bufnr) this.dispose()\n    }, null, this.disposables)\n  }\n\n  private addFile(filepath: string, highlighter: Highlighter, lnum?: number): void {\n    this.items.push({\n      index: highlighter.length,\n      filepath,\n      lnum\n    })\n  }\n\n  public async show(state: EditState): Promise<void> {\n    let { nvim } = this\n    let id = global_id++\n    nvim.pauseNotification()\n    nvim.command(`tabe +setl\\\\ buftype=nofile CocWorkspaceEdit${id}`, true)\n    nvim.command(`setl bufhidden=wipe nolist`, true)\n    nvim.command('setl nobuflisted wrap undolevels=-1 filetype=cocedits noswapfile', true)\n    await nvim.resumeNotification(true)\n    let buffer = await nvim.buffer\n    let cwd = await nvim.call('getcwd') as string\n    this.bufnr = buffer.id\n    const relpath = (uri: string): string => {\n      let fsPath = URI.parse(uri).fsPath\n      return isParentFolder(cwd, fsPath, true) ? path.relative(cwd, fsPath) : fsPath\n    }\n    const absPath = filepath => {\n      return path.isAbsolute(filepath) ? filepath : path.join(cwd, filepath)\n    }\n    let highlighter = new Highlighter()\n    let changes = toArray(state.edit.documentChanges)\n    let map = grouByAnnotation(changes, state.edit.changeAnnotations ?? {})\n    for (let [label, changes] of map.entries()) {\n      if (label) {\n        highlighter.addLine(label, 'MoreMsg')\n        highlighter.addLine('')\n      }\n      for (let change of changes) {\n        if (TextDocumentEdit.is(change)) {\n          let linesChange = state.changes[change.textDocument.uri]\n          let fsPath = relpath(change.textDocument.uri)\n          highlighter.addTexts([\n            { text: 'Change', hlGroup: 'Title' },\n            { text: ' ' },\n            { text: fsPath, hlGroup: 'Directory' },\n            { text: `:${linesChange.lnum}`, hlGroup: 'LineNr' },\n          ])\n          this.addFile(fsPath, highlighter, linesChange.lnum)\n          highlighter.addLine('')\n          this.addChangedLines(highlighter, linesChange, fsPath, linesChange.lnum)\n          highlighter.addLine('')\n        } else if (CreateFile.is(change) || DeleteFile.is(change)) {\n          let title = DeleteFile.is(change) ? 'Delete' : 'Create'\n          let fsPath = relpath(change.uri)\n          highlighter.addTexts([\n            { text: title, hlGroup: 'Title' },\n            { text: ' ' },\n            { text: fsPath, hlGroup: 'Directory' }\n          ])\n          this.addFile(fsPath, highlighter)\n          highlighter.addLine('')\n        } else if (RenameFile.is(change)) {\n          let oldPath = relpath(change.oldUri)\n          let newPath = relpath(change.newUri)\n          highlighter.addTexts([\n            { text: 'Rename', hlGroup: 'Title' },\n            { text: ' ' },\n            { text: oldPath, hlGroup: 'Directory' },\n            { text: '->', hlGroup: 'Comment' },\n            { text: newPath, hlGroup: 'Directory' }\n          ])\n          this.renameMap.set(oldPath, newPath)\n          this.addFile(newPath, highlighter)\n          highlighter.addLine('')\n        }\n      }\n    }\n    nvim.pauseNotification()\n    highlighter.render(buffer)\n    buffer.setOption('modifiable', false, true)\n    await nvim.resumeNotification(true)\n    this.disposables.push(this.keymaps.registerLocalKeymap(buffer.id, 'n', '<CR>', async () => {\n      let lnum = await nvim.call('line', '.') as number\n      let col = await nvim.call('col', '.') as number\n      let find: ChangedFileItem\n      for (let i = this.items.length - 1; i >= 0; i--) {\n        let item = this.items[i]\n        if (lnum >= item.index) {\n          find = item\n          break\n        }\n      }\n      if (!find) return\n      let uri = URI.file(absPath(find.filepath)).toString()\n      let filepath = this.renameMap.has(find.filepath) ? this.renameMap.get(find.filepath) : find.filepath\n      await nvim.call('coc#util#open_file', ['tab drop', absPath(filepath)])\n      let documentChanges = toArray(state.edit.documentChanges)\n      let change = documentChanges.find(o => TextDocumentEdit.is(o) && o.textDocument.uri == uri) as TextDocumentEdit\n      let originLine = getOriginalLine(find, change)\n      if (originLine !== undefined) await nvim.call('cursor', [originLine, col])\n      nvim.redrawVim()\n    }, true))\n    this.disposables.push(this.keymaps.registerLocalKeymap(buffer.id, 'n', '<esc>', async () => {\n      nvim.command('bwipeout!', true)\n    }, true))\n  }\n\n  public addChangedLines(highlighter: Highlighter, linesChange: LinesChange, fsPath: string, lnum: number): void {\n    let diffs = fastDiff(linesChange.oldLines.join('\\n'), linesChange.newLines.join('\\n'))\n    for (let i = 0; i < diffs.length; i++) {\n      let diff = diffs[i]\n      if (diff[0] == fastDiff.EQUAL) {\n        let text = diff[1]\n        if (!text.includes('\\n')) {\n          highlighter.addText(text)\n        } else {\n          let parts = text.split('\\n')\n          highlighter.addText(parts[0])\n          let curr = lnum + parts.length - 1\n          highlighter.addLine('')\n          highlighter.addTexts([\n            { text: 'Change', hlGroup: 'Title' },\n            { text: ' ' },\n            { text: fsPath, hlGroup: 'Directory' },\n            { text: `:${curr}`, hlGroup: 'LineNr' },\n          ])\n          this.addFile(fsPath, highlighter, curr)\n          highlighter.addLine('')\n          let last = parts[parts.length - 1]\n          highlighter.addText(last)\n        }\n        lnum += text.split('\\n').length - 1\n      } else if (diff[0] == fastDiff.DELETE) {\n        lnum += diff[1].split('\\n').length - 1\n        highlighter.addText(diff[1], 'DiffDelete')\n      } else {\n        highlighter.addText(diff[1], 'DiffAdd')\n      }\n    }\n  }\n\n  public dispose(): void {\n    disposeAll(this.disposables)\n  }\n}\n\nexport function getOriginalLine(item: ChangedFileItem, change: TextDocumentEdit | undefined): number | undefined {\n  if (typeof item.lnum !== 'number') return undefined\n  let lnum = item.lnum\n  if (change) {\n    // Use snippet value as text should be fine to get the line number.\n    let edits: TextEdit[] = change.edits.map(o => SnippetTextEdit.is(o) ? { range: o.range, newText: o.snippet.value } : o)\n    edits = mergeSortEdits(edits)\n    let pos = getPositionFromEdits(Position.create(lnum - 1, 0), edits)\n    lnum = pos.line + 1\n  }\n  return lnum\n}\n\nfunction grouByAnnotation(changes: DocumentChange[], annotations: { [id: string]: ChangeAnnotation }): Map<string | null, DocumentChange[]> {\n  let map: Map<string | null, DocumentChange[]> = new Map()\n  for (let change of changes) {\n    let id = getAnnotationKey(change) ?? null\n    let key = id ? annotations[id]?.label : null\n    let arr = map.get(key)\n    if (arr) {\n      arr.push(change)\n    } else {\n      map.set(key, [change])\n    }\n  }\n  return map\n}\n"
  },
  {
    "path": "src/model/fetch.ts",
    "content": "'use strict'\nimport decompressResponse from 'decompress-response'\nimport { http, https } from 'follow-redirects'\nimport { HttpProxyAgent } from 'http-proxy-agent'\nimport { HttpsProxyAgent } from 'https-proxy-agent'\nimport { ParsedUrlQueryInput, stringify } from 'querystring'\nimport { Readable } from 'stream'\nimport { URL } from 'url'\nimport { createLogger } from '../logger'\nimport { getConditionValue } from '../util'\nimport { CancellationError } from '../util/errors'\nimport { objectLiteral } from '../util/is'\nimport { fs } from '../util/node'\nimport { CancellationToken } from '../util/protocol'\nimport { toText } from '../util/string'\nimport workspace from '../workspace'\nimport { Agent } from 'node:http'\nconst logger = createLogger('model-fetch')\nexport const timeout = getConditionValue(500, 50)\n\nexport type ResponseResult = string | Buffer | { [name: string]: any }\n\nexport interface ProxyOptions {\n  proxy: string\n  proxyStrictSSL?: boolean\n  proxyAuthorization?: string | null\n  proxyCA?: string | null\n}\n\nexport interface FetchOptions {\n  /**\n   * Default to 'GET'\n   */\n  method?: string\n  /**\n   * Default no timeout\n   */\n  timeout?: number\n  /**\n   * Always return buffer instead of parsed response.\n   */\n  buffer?: boolean\n  /**\n   * - 'string' for text response content\n   * - 'object' for json response content\n   * - 'buffer' for response not text or json\n   */\n  data?: string | { [key: string]: any } | Buffer\n  /**\n   * Plain object added as query of url\n   */\n  query?: ParsedUrlQueryInput\n  headers?: any\n  /**\n   * User for http basic auth, should use with password\n   */\n  user?: string\n  /**\n   * Password for http basic auth, should use with user\n   */\n  password?: string\n}\n\nexport function getRequestModule(url: URL): typeof http | typeof https {\n  return url.protocol === 'https:' ? https : http\n}\n\nexport function getText(data: any): string | Buffer {\n  if (typeof data === 'string' || Buffer.isBuffer(data)) return data\n  return JSON.stringify(data)\n}\n\nexport function toURL(urlInput: string | URL): URL {\n  if (urlInput instanceof URL) return urlInput\n  let url = new URL(urlInput)\n  if (!['https:', 'http:'].includes(url.protocol)) throw new Error(`Not valid protocol with ${urlInput}, should be http: or https:`)\n  return url\n}\n\nexport function toPort(port: number | string | undefined, protocol: string): number {\n  if (port) {\n    port = typeof port === 'number' ? port : parseInt(port, 10)\n    if (!isNaN(port)) return port\n  }\n  return protocol.startsWith('https') ? 443 : 80\n}\n\nexport function getDataType(data: any): string {\n  if (data === null) return 'null'\n  if (data === undefined) return 'undefined'\n  if (typeof data == 'string') return 'string'\n  if (Buffer.isBuffer(data)) return 'buffer'\n  if (Array.isArray(data) || objectLiteral(data)) return 'object'\n  return 'unknown'\n}\n\nexport function getSystemProxyURI(endpoint: URL, env = process.env): string | null {\n  let noProxy = env.NO_PROXY ?? env.no_proxy\n  if (noProxy === '*') {\n    return null\n  }\n  if (noProxy) {\n    // canonicalize the hostname, so that 'oogle.com' won't match 'google.com'\n    const hostname = endpoint.hostname.replace(/^\\.*/, '.').toLowerCase()\n    const port = toPort(endpoint.port, endpoint.protocol).toString()\n    const noProxyList = noProxy.split(',')\n    for (let i = 0, len = noProxyList.length; i < len; i++) {\n      let noProxyItem = noProxyList[i].trim().toLowerCase()\n      // no_proxy can be granular at the port level, which complicates things a bit.\n      if (noProxyItem.includes(':')) {\n        let noProxyItemParts = noProxyItem.split(':', 2)\n        let noProxyHost = noProxyItemParts[0].replace(/^\\.*/, '.')\n        let noProxyPort = noProxyItemParts[1]\n        if (port == noProxyPort && hostname.endsWith(noProxyHost)) {\n          return null\n        }\n      } else {\n        noProxyItem = noProxyItem.replace(/^\\.*/, '.')\n        if (hostname.endsWith(noProxyItem)) {\n          return null\n        }\n      }\n    }\n  }\n  let proxyUri: string | null\n  if (endpoint.protocol === 'http:') {\n    proxyUri = env.HTTP_PROXY || env.http_proxy || null\n  } else {\n    proxyUri = env.HTTPS_PROXY || env.https_proxy || env.HTTP_PROXY || env.http_proxy || null\n  }\n  return proxyUri\n}\n\nexport function getAgent(endpoint: URL, options: ProxyOptions): Agent {\n  let proxy = options.proxy || getSystemProxyURI(endpoint)\n  if (proxy) {\n    let proxyURL: URL\n    try {\n      proxyURL = new URL(proxy)\n      if (!/^https?:$/.test(proxyURL.protocol)) return null\n    } catch (e) {\n      return null\n    }\n    let rejectUnauthorized = typeof options.proxyStrictSSL === 'boolean' ? options.proxyStrictSSL : true\n    logger.info(`Using proxy ${proxy} from ${options.proxy ? 'configuration' : 'system environment'} for ${endpoint.hostname}:`)\n    const agentOptions = { rejectUnauthorized }\n    return endpoint.protocol === 'http:' ? new HttpProxyAgent(proxyURL, agentOptions) : new HttpsProxyAgent(proxyURL, agentOptions)\n  }\n  return null\n}\n\nexport function resolveRequestOptions(url: URL, options: FetchOptions): any {\n  let config = workspace.getConfiguration('http', null)\n  let dataType = getDataType(options.data)\n  let proxyOptions: ProxyOptions = {\n    proxy: config.get<string>('proxy', ''),\n    proxyStrictSSL: config.get<boolean>('proxyStrictSSL', true),\n    proxyAuthorization: config.get<string | null>('proxyAuthorization', null),\n    proxyCA: config.get<string | null>('proxyCA', null)\n  }\n  if (options.query && !url.search) {\n    url.search = `?${stringify(options.query)}`\n  }\n  let agent = getAgent(url, proxyOptions)\n  let opts: any = {\n    method: options.method ?? 'GET',\n    hostname: url.hostname,\n    port: toPort(url.port, url.protocol),\n    path: url.pathname + url.search,\n    agent,\n    rejectUnauthorized: proxyOptions.proxyStrictSSL,\n    maxRedirects: 3,\n    headers: {\n      'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64)',\n      'Accept-Encoding': 'gzip, deflate',\n      ...(options.headers ?? {})\n    }\n  }\n  if (dataType == 'object') {\n    opts.headers['Content-Type'] = 'application/json'\n  } else if (dataType == 'string') {\n    opts.headers['Content-Type'] = 'text/plain'\n  }\n  if (proxyOptions.proxyAuthorization) opts.headers['Proxy-Authorization'] = proxyOptions.proxyAuthorization\n  if (proxyOptions.proxyCA) opts.ca = fs.readFileSync(proxyOptions.proxyCA)\n  if (options.user) opts.auth = options.user + ':' + (toText(options.password))\n  if (url.username) opts.auth = url.username + ':' + (toText(url.password))\n  if (options.timeout) opts.timeout = options.timeout\n  if (options.buffer) opts.buffer = true\n  return opts\n}\n\nexport function request(url: URL, data: any, opts: any, token?: CancellationToken, obj: any = {}): Promise<ResponseResult> {\n  let mod = getRequestModule(url)\n  return new Promise<ResponseResult>((resolve, reject) => {\n    if (token) {\n      let disposable = token.onCancellationRequested(() => {\n        disposable.dispose()\n        req.destroy(new CancellationError())\n      })\n    }\n    let timer: NodeJS.Timeout\n    const req = mod.request(opts, res => {\n      let readable: Readable = res\n      if ((res.statusCode >= 200 && res.statusCode < 300) || res.statusCode === 1223) {\n        let headers = res.headers\n        let chunks: Buffer[] = []\n        let contentType: string = toText(headers['content-type'])\n        readable = decompressResponse(res)\n        readable.on('data', chunk => {\n          chunks.push(chunk)\n        })\n        readable.on('end', () => {\n          clearTimeout(timer)\n          let buf = Buffer.concat(chunks)\n          if (!opts.buffer && (contentType.startsWith('application/json') || contentType.startsWith('text/'))) {\n            let ms = contentType.match(/charset=(\\S+)/)\n            let encoding = ms ? ms[1] : 'utf8'\n            let rawData = buf.toString(encoding as BufferEncoding)\n            if (!contentType.includes('application/json')) {\n              resolve(rawData)\n            } else {\n              try {\n                const parsedData = JSON.parse(rawData)\n                resolve(parsedData)\n              } catch (e) {\n                reject(new Error(`Parse response error: ${e}`))\n              }\n            }\n          } else {\n            resolve(buf)\n          }\n        })\n        readable.on('error', err => {\n          reject(new Error(`Connection error to ${url}: ${err.message}`))\n        })\n      } else {\n        reject(new Error(`Bad response from ${url}: ${res.statusCode}`))\n      }\n    })\n    obj.req = req\n    req.on('error', e => {\n      // Possible succeed proxy request with ECONNRESET error on node > 14\n      if (e['code'] == 'ECONNRESET') {\n        timer = setTimeout(() => {\n          reject(e)\n        }, timeout)\n      } else {\n        reject(e)\n      }\n    })\n    req.on('timeout', () => {\n      req.destroy(new Error(`Request timeout after ${opts.timeout}ms`))\n    })\n    if (data) req.write(getText(data))\n    if (opts.timeout) req.setTimeout(opts.timeout)\n    req.end()\n  })\n}\n\n/**\n * Send request to server for response, supports:\n *\n * - Send json data and parse json response.\n * - Throw error for failed response statusCode.\n * - Timeout support (no timeout by default).\n * - Send buffer (as data) and receive data (as response).\n * - Proxy support from user configuration & environment.\n * - Redirect support, limited to 3.\n * - Support of gzip & deflate response content.\n */\nexport default function fetch(urlInput: string | URL, options: FetchOptions = {}, token?: CancellationToken): Promise<ResponseResult> {\n  let url = toURL(urlInput)\n  let opts = resolveRequestOptions(url, options)\n  return request(url, options.data, opts, token).catch(err => {\n    logger.error(`Fetch error for ${url}:`, opts, err)\n    if (opts.agent && opts.agent.proxy) {\n      let { proxy } = opts.agent\n      throw new Error(`Request failed using proxy ${proxy.host}: ${err.message}`)\n    } else {\n      throw err\n    }\n  })\n}\n"
  },
  {
    "path": "src/model/floatFactory.ts",
    "content": "'use strict'\nimport { Buffer, Neovim, Window } from '@chemzqm/neovim'\nimport events, { BufEvents } from '../events'\nimport { parseDocuments } from '../markdown'\nimport { Documentation, FloatConfig } from '../types'\nimport { disposeAll, getConditionValue } from '../util'\nimport { isFalsyOrEmpty } from '../util/array'\nimport { isVim } from '../util/constants'\nimport { Mutex } from '../util/mutex'\nimport { debounce } from '../util/node'\nimport { equals } from '../util/object'\nimport { Disposable } from '../util/protocol'\nconst debounceTime = getConditionValue(100, 10)\n\nexport interface WindowConfig {\n  width: number\n  height: number\n  col: number\n  row: number\n  relative: 'cursor' | 'win' | 'editor'\n  style?: string\n  cursorline?: number\n  title?: string\n  border?: number[]\n  autohide?: number\n  close?: number\n}\n\nexport interface FloatWinConfig extends FloatConfig {\n  breaks?: boolean\n  preferTop?: boolean\n  autoHide?: boolean\n  offsetX?: number\n  cursorline?: boolean\n  modes?: string[]\n  excludeImages?: boolean\n  position?: \"fixed\" | \"auto\"\n  top?: number\n  bottom?: number\n  left?: number\n  right?: number\n}\n\n/**\n * Float window/popup factory for create float/popup around current cursor.\n */\nexport default class FloatFactoryImpl implements Disposable {\n  private winid = 0\n  private _bufnr = 0\n  private closeTs: number\n  private targetBufnr: number\n  private mutex: Mutex = new Mutex()\n  private disposables: Disposable[] = []\n  private cursor: [number, number]\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n  private onCursorMoved: Function & { clear(): void }\n  constructor(private nvim: Neovim) {\n    this.onCursorMoved = debounce(this._onCursorMoved.bind(this), debounceTime)\n  }\n\n  private bindEvents(autoHide: boolean, alignTop: boolean): void {\n    let eventNames: BufEvents[] = ['InsertLeave', 'InsertEnter', 'BufEnter']\n    for (let ev of eventNames) {\n      events.on(ev, bufnr => {\n        if (bufnr == this._bufnr) return\n        this.close()\n      }, null, this.disposables)\n    }\n    events.on('MenuPopupChanged', () => {\n      // avoid intersect with pum\n      if (events.pumAlignTop == alignTop) {\n        this.close()\n      }\n    }, null, this.disposables)\n    this.disposables.push(Disposable.create(() => {\n      this.onCursorMoved.clear()\n    }))\n    events.on('CursorMoved', this.onCursorMoved.bind(this, autoHide), this, this.disposables)\n    events.on('CursorMovedI', this.onCursorMoved.bind(this, autoHide), this, this.disposables)\n  }\n\n  public unbind(): void {\n    if (this.disposables.length) {\n      disposeAll(this.disposables)\n      this.disposables = []\n    }\n  }\n\n  public _onCursorMoved(autoHide: boolean, bufnr: number, cursor: [number, number]): void {\n    if (bufnr == this._bufnr) return\n    if (bufnr == this.targetBufnr && equals(cursor, this.cursor)) {\n      // cursor not moved\n      return\n    }\n    if (bufnr != this.targetBufnr || !events.insertMode || autoHide) {\n      this.close()\n      return\n    }\n  }\n\n  /**\n   * Create float window/popup at cursor position.\n   * @deprecated use show method instead\n   */\n  public async create(docs: Documentation[], _allowSelection = false, offsetX = 0): Promise<void> {\n    await this.show(docs, {\n      offsetX\n    })\n  }\n\n  /**\n   * Show documentations in float window/popup around cursor.\n   * Window and buffer are reused when possible.\n   * Window is closed automatically on change buffer, InsertEnter, CursorMoved and CursorMovedI.\n   * @param docs List of documentations.\n   * @param config Configuration for floating window/popup.\n   */\n  public async show(docs: Documentation[], config: FloatWinConfig = {}): Promise<void> {\n    if (docs.length == 0 || docs.every(doc => doc.content.length == 0)) {\n      this.close()\n      return\n    }\n    let curr = Date.now()\n    let release = await this.mutex.acquire()\n    try {\n      await this.createPopup(docs, config, curr)\n      release()\n    } catch (e) {\n      this.nvim.echoError(e)\n      release()\n    }\n  }\n\n  private async createPopup(docs: Documentation[], opts: FloatWinConfig, timestamp: number): Promise<void> {\n    docs = docs.filter(o => o.content.trim().length > 0)\n    let { lines, codes, highlights } = parseDocuments(docs, { excludeImages: opts.excludeImages, breaks: opts.breaks })\n    let config: any = {\n      codes,\n      highlights,\n      pumAlignTop: events.pumAlignTop,\n      preferTop: typeof opts.preferTop === 'boolean' ? opts.preferTop : false,\n      offsetX: opts.offsetX || 0,\n      title: opts.title || '',\n      close: opts.close ? 1 : 0,\n      rounded: opts.rounded ? 1 : 0,\n      modes: opts.modes || ['n', 'i', 'ic', 's'],\n      relative: opts.position === 'fixed' ? 'editor' : 'cursor'\n    }\n    if (!isVim) {\n      if (typeof opts.winblend === 'number') config.winblend = opts.winblend\n      if (opts.focusable != null) config.focusable = opts.focusable ? 1 : 0\n      if (opts.shadow) config.shadow = 1\n    }\n    if (opts.maxHeight) config.maxHeight = opts.maxHeight\n    if (opts.maxWidth) config.maxWidth = opts.maxWidth\n    if (opts.border === true) {\n      config.border = [1, 1, 1, 1]\n    } else if (Array.isArray(opts.border) && !opts.border.every(o => o == 0)) {\n      config.border = opts.border.slice(0, 4)\n      config.rounded = opts.rounded ? 1 : 0\n    }\n    if (opts.highlight) config.highlight = opts.highlight\n    if (opts.borderhighlight) config.borderhighlight = opts.borderhighlight\n    if (opts.cursorline) config.cursorline = 1\n    for (let key of ['top', 'left', 'bottom', 'right']) {\n      if (typeof opts[key] === 'number' && opts[key] >= 0) {\n        config[key] = opts[key]\n      }\n    }\n    let autoHide = opts.autoHide === false ? false : true\n    if (autoHide) config.autohide = 1\n    this.unbind()\n    let arr = await this.nvim.call('coc#dialog#create_cursor_float', [this.winid, this._bufnr, lines, config]) as [number, [number, number], number, number, number]\n    if (isFalsyOrEmpty(arr) || this.closeTs > timestamp) {\n      let winid = arr && arr.length > 0 ? arr[2] : this.winid\n      if (winid) {\n        this.winid = 0\n        this.nvim.call('coc#float#close', [winid], true)\n        this.nvim.redrawVim()\n      }\n      return\n    }\n    let [targetBufnr, cursor, winid, bufnr, alignTop] = arr\n    this.winid = winid\n    this._bufnr = bufnr\n    this.targetBufnr = targetBufnr\n    this.cursor = cursor\n    this.bindEvents(autoHide, alignTop == 1)\n  }\n\n  /**\n   * Close float window\n   */\n  public close(): void {\n    let { winid, nvim } = this\n    this.closeTs = Date.now()\n    this.unbind()\n    if (winid) {\n      this.winid = 0\n      nvim.call('coc#float#close', [winid], true)\n      nvim.redrawVim()\n    }\n  }\n\n  public checkRetrigger(bufnr: number): boolean {\n    if (this.winid && this.targetBufnr == bufnr) return true\n    return false\n  }\n\n  public get bufnr(): number {\n    return this._bufnr\n  }\n\n  public get buffer(): Buffer | null {\n    return this.bufnr ? this.nvim.createBuffer(this.bufnr) : null\n  }\n\n  public get window(): Window | null {\n    return this.winid ? this.nvim.createWindow(this.winid) : null\n  }\n\n  public async activated(): Promise<boolean> {\n    if (!this.winid) return false\n    return await this.nvim.call('coc#float#valid', [this.winid]) != 0\n  }\n\n  public dispose(): void {\n    this.cursor = undefined\n    this.onCursorMoved.clear()\n    this.close()\n  }\n}\n"
  },
  {
    "path": "src/model/fuzzyMatch.ts",
    "content": "import { AnsiHighlight } from '../types'\nimport { pluginRoot } from '../util/constants'\nimport { anyScore, fuzzyScore, FuzzyScore, fuzzyScoreGracefulAggressive, FuzzyScoreOptions, FuzzyScorer } from '../util/filter'\nimport { fs, path, promisify } from '../util/node'\nimport { bytes } from '../util/string'\n\nexport interface FuzzyWasi {\n  fuzzyMatch: (textPtr: number, patternPtr: number, resultPtr: number, matchSeq: 0 | 1) => number\n  malloc: (size: number) => number\n  free: (ptr: number) => void\n  memory: {\n    buffer: ArrayBuffer\n  }\n}\n\nexport type FuzzyKind = 'normal' | 'aggressive' | 'any'\n\nexport type ScoreFunction = (word: string, wordPos?: number) => FuzzyScore | undefined\n\nexport interface MatchResult {\n  score: number\n  positions: Uint32Array\n}\n\nexport interface MatchHighlights {\n  score: number\n  highlights: AnsiHighlight[]\n}\n\nconst wasmFile = path.join(pluginRoot, 'bin/fuzzy.wasm')\n\nexport async function initFuzzyWasm(): Promise<FuzzyWasi> {\n  const buffer = await promisify(fs.readFile)(wasmFile)\n  const res = await global.WebAssembly.instantiate(buffer, { env: {} })\n  return res.instance.exports as FuzzyWasi\n}\n\n/**\n * Convert FuzzyScore to highlight byte spans.\n */\nexport function toSpans(label: string, score: FuzzyScore): [number, number][] {\n  let res: [number, number][] = []\n  for (let span of matchSpansReverse(label, score, 2)) {\n    res.push(span)\n  }\n  return res\n}\n\n/**\n * Convert to spans from reversed list of utf16 code unit numbers\n * Positions should be sorted numbers from big to small\n */\nexport function* matchSpansReverse(text: string, positions: ArrayLike<number>, endIndex = 0, max = Number.MAX_SAFE_INTEGER): Iterable<[number, number]> {\n  let len = positions.length\n  if (len <= endIndex) return\n  let byteIndex = bytes(text, Math.min(positions[endIndex] + 1, max))\n  let start: number | undefined\n  let prev: number | undefined\n  for (let i = len - 1; i >= endIndex; i--) {\n    let curr = positions[i]\n    if (curr >= max) {\n      if (start != null) yield [byteIndex(start), byteIndex(prev + 1)]\n      break\n    }\n    if (prev != undefined) {\n      let d = curr - prev\n      if (d == 1) {\n        prev = curr\n      } else if (d > 1) {\n        yield [byteIndex(start), byteIndex(prev + 1)]\n        start = curr\n      } else {\n        // invalid number\n        yield [byteIndex(start), byteIndex(prev + 1)]\n        break\n      }\n    } else {\n      start = curr\n    }\n    prev = curr\n    if (i == endIndex) {\n      yield [byteIndex(start), byteIndex(prev + 1)]\n    }\n  }\n}\n\nfunction* matchSpans(text: string, positions: ArrayLike<number>, max?: number): Iterable<[number, number]> {\n  max = max ? Math.min(max, text.length) : text.length\n  let byteIndex = bytes(text, Math.min(text.length, 4096))\n  let start: number | undefined\n  let prev: number | undefined\n  let len = positions.length\n  for (let i = 0; i < len; i++) {\n    let curr = positions[i]\n    if (curr >= max) {\n      if (start != null) yield [byteIndex(start), byteIndex(prev + 1)]\n      break\n    }\n    if (prev != undefined) {\n      let d = curr - prev\n      if (d == 1) {\n        prev = curr\n      } else if (d > 1) {\n        yield [byteIndex(start), byteIndex(prev + 1)]\n        start = curr\n      } else {\n        // invalid number\n        yield [byteIndex(start), byteIndex(prev + 1)]\n        break\n      }\n    } else {\n      start = curr\n    }\n    prev = curr\n    if (i == len - 1) {\n      yield [byteIndex(start), byteIndex(prev + 1)]\n    }\n  }\n}\n\nexport class FuzzyMatch {\n  private contentPtr: number | undefined\n  private patternPtr: number | undefined\n  private resultPtr: number | undefined\n  private patternLength = 0\n  private matchSeq = false\n  private sizes: number[] = [2048, 1024, 1024]\n\n  constructor(private exports: FuzzyWasi) {\n  }\n\n  /**\n   * Match character positions to column spans.\n   */\n  public matchSpans(text: string, positions: ArrayLike<number>, max?: number): Iterable<[number, number]> {\n    return matchSpans(text, positions, max)\n  }\n\n  /**\n   * Create 0 index byte spans from matched text and FuzzyScore for highlights\n   */\n  public matchScoreSpans(text: string, score: FuzzyScore): Iterable<[number, number]> {\n    return matchSpansReverse(text, score, 2)\n  }\n\n  /**\n   * Create a score function\n   */\n  public createScoreFunction(pattern: string, patternPos: number, options?: FuzzyScoreOptions, kind?: FuzzyKind): ScoreFunction {\n    let lowPattern = pattern.toLowerCase()\n    let fn: FuzzyScorer\n    if (kind === 'any') {\n      fn = anyScore\n    } else if (kind === 'aggressive') {\n      fn = fuzzyScoreGracefulAggressive\n    } else {\n      fn = fuzzyScore\n    }\n    return (word: string, wordPos = 0): FuzzyScore | undefined => {\n      return fn(pattern, lowPattern, patternPos, word, word.toLowerCase(), wordPos, options)\n    }\n  }\n\n  public getSizes(): number[] {\n    return this.sizes\n  }\n\n  public setPattern(pattern: string, matchSeq = false): void {\n    // Can't handle length > 256\n    if (pattern.length > 256) pattern = pattern.slice(0, 256)\n    this.matchSeq = matchSeq\n    this.patternLength = matchSeq ? pattern.length : pattern.replace(/(\\s|\\t)/g, '').length\n    if (this.patternPtr == null) {\n      let { malloc } = this.exports\n      let { sizes } = this\n      this.contentPtr = malloc(sizes[0])\n      this.patternPtr = malloc(sizes[1])\n      this.resultPtr = malloc(sizes[2])\n    }\n    let buf = Buffer.from(pattern, 'utf8')\n    let len = buf.length\n    let bytes = new Uint8Array(this.exports.memory.buffer, this.patternPtr, len + 1)\n    bytes.set(buf)\n    bytes[len] = 0\n  }\n\n  private changeContent(text: string): void {\n    let { sizes } = this\n    if (text.length > 4096) text = text.slice(0, 4096)\n    let buf = Buffer.from(text, 'utf8')\n    let len = buf.length\n    if (len > sizes[0]) {\n      let { malloc, free } = this.exports\n      free(this.contentPtr)\n      let byteLength = len + 1\n      this.contentPtr = malloc(byteLength)\n      sizes[0] = byteLength\n    }\n    let bytes = new Uint8Array(this.exports.memory.buffer, this.contentPtr, len + 1)\n    bytes.set(buf)\n    bytes[len] = 0\n  }\n\n  public match(text: string): MatchResult | undefined {\n    if (this.patternPtr == null) throw new Error('setPattern not called before match')\n    if (this.patternLength === 0) return { score: 100, positions: new Uint32Array() }\n    this.changeContent(text)\n    let { fuzzyMatch, memory } = this.exports\n    let { resultPtr } = this\n    let score = fuzzyMatch(this.contentPtr, this.patternPtr, resultPtr, this.matchSeq ? 1 : 0)\n    if (!score) return undefined\n    const u32 = new Uint32Array(memory.buffer, resultPtr, this.patternLength)\n    return { score, positions: u32.slice() }\n  }\n\n  public matchHighlights(text: string, hlGroup: string): MatchHighlights | undefined {\n    let res = this.match(text)\n    if (!res) return undefined\n    let highlights: AnsiHighlight[] = []\n    for (let span of this.matchSpans(text, res.positions)) {\n      highlights.push({ span, hlGroup })\n    }\n    return { score: res.score, highlights }\n  }\n\n  public free(): void {\n    let ptrs = [this.contentPtr, this.patternPtr, this.resultPtr]\n    let { free } = this.exports\n    ptrs.forEach(p => {\n      if (p != null) free(p)\n    })\n    this.contentPtr = this.patternPtr = this.resultPtr = undefined\n  }\n}\n"
  },
  {
    "path": "src/model/highlighter.ts",
    "content": "'use strict'\nimport { Buffer } from '@chemzqm/neovim'\nimport { parseAnsiHighlights } from '../util/ansiparse'\nimport { byteLength } from '../util/string'\nimport { HighlightItem } from '../types'\n\nexport interface TextItem {\n  text: string\n  hlGroup?: string\n}\n\n/**\n * Build highlights, with lines and highlights\n */\nexport default class Highlighter {\n  private lines: string[] = []\n  private _highlights: HighlightItem[] = []\n\n  public addLine(line: string, hlGroup?: string): void {\n    if (line.includes('\\n')) {\n      for (let content of line.split(/\\r?\\n/)) {\n        this.addLine(content, hlGroup)\n      }\n      return\n    }\n    if (hlGroup) {\n      this._highlights.push({\n        lnum: this.lines.length,\n        colStart: line.match(/^\\s*/)[0].length,\n        colEnd: byteLength(line),\n        hlGroup\n      })\n    } // '\\x1b'\n    if (line.includes('\\x1b')) {\n      let res = parseAnsiHighlights(line)\n      for (let hl of res.highlights) {\n        let { span, hlGroup } = hl\n        this._highlights.push({\n          lnum: this.lines.length,\n          colStart: span[0],\n          colEnd: span[1],\n          hlGroup\n        })\n      }\n      this.lines.push(res.line)\n    } else {\n      this.lines.push(line)\n    }\n  }\n\n  public addLines(lines: string[]): void {\n    this.lines.push(...lines)\n  }\n\n  /**\n   * Add texts to new Lines\n   */\n  public addTexts(items: TextItem[]): void {\n    let len = this.lines.length\n    let text = ''\n    for (let item of items) {\n      let colStart = byteLength(text)\n      if (item.hlGroup) {\n        this._highlights.push({\n          lnum: len,\n          colStart,\n          colEnd: colStart + byteLength(item.text),\n          hlGroup: item.hlGroup\n        })\n      }\n      text += item.text\n    }\n    this.lines.push(text)\n  }\n\n  public addText(text: string, hlGroup?: string): void {\n    if (!text) return\n    let { lines } = this\n    let pre = lines[lines.length - 1] || ''\n    if (text.includes('\\n')) {\n      let parts = text.split('\\n')\n      this.addText(parts[0], hlGroup)\n      for (let line of parts.slice(1)) {\n        this.addLine(line, hlGroup)\n      }\n      return\n    }\n    if (hlGroup) {\n      let colStart = byteLength(pre)\n      this._highlights.push({\n        lnum: lines.length ? lines.length - 1 : 0,\n        colStart,\n        colEnd: colStart + byteLength(text),\n        hlGroup\n      })\n    }\n    if (lines.length) {\n      lines[lines.length - 1] = `${pre}${text}`\n    } else {\n      lines.push(text)\n    }\n  }\n\n  public get length(): number {\n    return this.lines.length\n  }\n\n  public getline(line: number): string {\n    return this.lines[line] || ''\n  }\n\n  public get highlights(): ReadonlyArray<HighlightItem> {\n    return this._highlights\n  }\n\n  public get content(): string {\n    return this.lines.join('\\n')\n  }\n\n  // default to replace\n  public render(buffer: Buffer, start = 0, end = -1): void {\n    buffer.setLines(this.lines, { start, end, strictIndexing: false }, true)\n    for (let item of this._highlights) {\n      // eslint-disable-next-line @typescript-eslint/no-floating-promises\n      buffer.addHighlight({\n        hlGroup: item.hlGroup,\n        colStart: item.colStart,\n        colEnd: item.colEnd,\n        line: start + item.lnum,\n        srcId: -1\n      })\n    }\n  }\n}\n"
  },
  {
    "path": "src/model/input.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport events from '../events'\nimport { disposeAll } from '../util'\nimport { isVim } from '../util/constants'\nimport { omitUndefined } from '../util/object'\nimport { Disposable, Emitter, Event } from '../util/protocol'\nimport { toText } from '../util/string'\n\nexport interface InputPreference {\n  placeHolder?: string\n  position?: 'cursor' | 'center'\n  marginTop?: number\n  border?: [0 | 1, 0 | 1, 0 | 1, 0 | 1]\n  rounded?: boolean\n  minWidth?: number\n  maxWidth?: number\n  highlight?: string\n  borderhighlight?: string\n  quickpick?: string[]\n  /**\n   * map list key-mappings\n   */\n  list?: boolean\n}\n\nexport interface Dimension {\n  width: number\n  height: number\n  row: number\n  col: number\n}\n\ntype RequestResult = [number, number, [number, number, number, number]]\n\nexport default class InputBox implements Disposable {\n  private disposables: Disposable[] = []\n  private _winid: number | undefined\n  private _bufnr: number | undefined\n  private _input: string\n  private accepted = false\n  private _disposed = false\n  public title: string\n  public loading: boolean\n  public value: string\n  public borderhighlight: string\n  // width, height, row, col\n  private _dimension: [number, number, number, number] = [0, 0, 0, 0]\n  private readonly _onDidFinish = new Emitter<string>()\n  private readonly _onDidChange = new Emitter<string>()\n  private clear = false\n  public readonly onDidFinish: Event<string | null> = this._onDidFinish.event\n  public readonly onDidChange: Event<string> = this._onDidChange.event\n  constructor(private nvim: Neovim, defaultValue: string) {\n    this._input = defaultValue\n    this.disposables.push(this._onDidFinish)\n    this.disposables.push(this._onDidChange)\n    let _title: string | undefined\n    Object.defineProperty(this, 'title', {\n      set: (newTitle: string) => {\n        _title = newTitle\n        if (this._winid) nvim.call('coc#dialog#change_title', [this._winid, newTitle], true)\n      },\n      get: () => {\n        return _title\n      }\n    })\n    let _loading = false\n    Object.defineProperty(this, 'loading', {\n      set: (loading: boolean) => {\n        _loading = loading\n        if (this._winid) nvim.call('coc#dialog#change_loading', [this._winid, loading], true)\n      },\n      get: () => {\n        return _loading\n      }\n    })\n    let _borderhighlight: string\n    Object.defineProperty(this, 'borderhighlight', {\n      set: (borderhighlight: string) => {\n        _borderhighlight = borderhighlight\n        if (this._winid) nvim.call('coc#dialog#change_border_hl', [this._winid, borderhighlight], true)\n      },\n      get: () => {\n        return _borderhighlight\n      }\n    })\n    Object.defineProperty(this, 'value', {\n      set: (value: string) => {\n        value = toText(value)\n        if (value !== this._input) {\n          this.clearVirtualText()\n          this._input = value\n          this.nvim.call('coc#dialog#change_input_value', [this.winid, this.bufnr, value], true)\n          this._onDidChange.fire(value)\n        }\n      },\n      get: () => {\n        return this._input\n      }\n    })\n    events.on('BufWinLeave', bufnr => {\n      if (bufnr == this._bufnr) {\n        this.dispose()\n      }\n    }, null, this.disposables)\n    events.on('PromptInsert', (value, bufnr) => {\n      if (bufnr == this._bufnr) {\n        this._input = value\n        this.accepted = true\n        this.dispose()\n      }\n    }, null, this.disposables)\n    events.on('PromptExit', bufnr => {\n      if (bufnr == this._bufnr) {\n        this.dispose()\n      }\n    }, null, this.disposables)\n    events.on('TextChangedI', (bufnr, info) => {\n      if (bufnr == this._bufnr && this._input !== info.line) {\n        this.clearVirtualText()\n        this._input = info.line\n        this._onDidChange.fire(info.line)\n      }\n    }, null, this.disposables)\n  }\n\n  private clearVirtualText(): void {\n    if (this.clear && this.bufnr) {\n      this.clear = false\n      let buf = this.nvim.createBuffer(this.bufnr)\n      buf.clearNamespace('input-box')\n    }\n  }\n\n  public get dimension(): Dimension | undefined {\n    let { _dimension } = this\n    return { width: _dimension[0], height: _dimension[1], row: _dimension[2], col: _dimension[3] }\n  }\n\n  public get bufnr(): number | undefined {\n    return this._bufnr\n  }\n\n  public get winid(): number | undefined {\n    return this._winid\n  }\n\n  public async show(title: string, preferences: InputPreference): Promise<boolean> {\n    this.title = title\n    this.borderhighlight = preferences.borderhighlight ?? 'CocFloatBorder'\n    this.loading = false\n    if (preferences.placeHolder && !this._input && !isVim) {\n      this.clear = true\n    }\n    let res = await this.nvim.call('coc#dialog#create_prompt_win', [title, this._input, omitUndefined(preferences)]) as RequestResult\n    if (!res) throw new Error('Unable to open input window')\n    this._bufnr = res[0]\n    this._winid = res[1]\n    this._dimension = res[2]\n    return true\n  }\n\n  public dispose(): void {\n    if (this._disposed) return\n    this._disposed = true\n    this.nvim.call('coc#float#close', [this._winid ?? -1], true)\n    if (isVim) this.nvim.command(`silent! bd! ${this._bufnr}`, true)\n    this._onDidFinish.fire(this.accepted ? this._input : null)\n    this._winid = undefined\n    this._bufnr = undefined\n    disposeAll(this.disposables)\n  }\n}\n"
  },
  {
    "path": "src/model/line.ts",
    "content": "import { AnsiHighlight } from '../types'\nimport { byteIndex, byteLength } from '../util/string'\n\ninterface NestedHighlight {\n  offset: number\n  length: number\n  hlGroup: string\n}\n\n/**\n * Build line with content and highlights.\n */\nexport default class LineBuilder {\n  private _label = ''\n  private _len = 0\n  private _highlights: AnsiHighlight[] = []\n  constructor(private addSpace = false) {\n  }\n\n  public append(text: string, hlGroup?: string, nested?: NestedHighlight[]): void {\n    if (text.length == 0) return\n    let space = this._len > 0 && this.addSpace ? ' ' : ''\n    let start = this._len + space.length\n    this._label = this._label + space + text\n    this._len = this._len + byteLength(text) + space.length\n    if (hlGroup) {\n      this._highlights.push({\n        hlGroup,\n        span: [start, start + byteLength(text)]\n      })\n    }\n    if (nested) {\n      for (let item of nested) {\n        let s = start + byteIndex(text, item.offset)\n        let e = start + byteIndex(text, item.offset + item.length)\n        this._highlights.push({\n          hlGroup: item.hlGroup,\n          span: [s, e]\n        })\n      }\n    }\n  }\n\n  public appendBuilder(builder: LineBuilder): void {\n    let space = this._len > 0 && this.addSpace ? ' ' : ''\n    let curr = this._len + space.length\n    this._label = this._label + space + builder.label\n    this._len = this._len + byteLength(builder.label) + space.length\n    this._highlights.push(...builder.highlights.map(item => {\n      return {\n        hlGroup: item.hlGroup,\n        span: item.span.map(v => {\n          return curr + v\n        }) as [number, number]\n      }\n    }))\n  }\n\n  public get label(): string {\n    return this._label\n  }\n\n  public get highlights(): AnsiHighlight[] {\n    return this._highlights\n  }\n}\n"
  },
  {
    "path": "src/model/memos.ts",
    "content": "'use strict'\nimport { loadJson, writeJson } from '../util/fs'\nimport { fs } from '../util/node'\nimport { deepClone } from '../util/object'\n\n/**\n * A memento represents a storage utility. It can store and retrieve\n * values.\n */\nexport interface Memento {\n  get<T>(key: string): T | undefined\n  get<T>(key: string, defaultValue: T): T\n  update(key: string, value: any): Promise<void>\n}\n\nexport default class Memos {\n  constructor(private filepath: string) {\n    if (!fs.existsSync(filepath)) {\n      fs.writeFileSync(filepath, '{}', 'utf8')\n    }\n  }\n\n  public merge(filepath: string): void {\n    if (!fs.existsSync(filepath)) return\n    let obj = loadJson(filepath)\n    let current = loadJson(this.filepath)\n    Object.assign(current, obj)\n    writeJson(this.filepath, current)\n    fs.unlinkSync(filepath)\n  }\n\n  private fetchContent(id: string, key: string): any {\n    let res = loadJson(this.filepath)\n    let obj = res[id]\n    if (!obj) return undefined\n    return obj[key]\n  }\n\n  private async update(id: string, key: string, value: any): Promise<void> {\n    let { filepath } = this\n    let current = loadJson(filepath)\n    current[id] = current[id] || {}\n    if (value !== undefined) {\n      current[id][key] = deepClone(value)\n    } else {\n      delete current[id][key]\n    }\n    writeJson(filepath, current)\n  }\n\n  public createMemento(id: string): Memento {\n    return {\n      get: <T>(key: string, defaultValue?: T): T | undefined => {\n        let res = this.fetchContent(id, key)\n        return res === undefined ? defaultValue : res\n      },\n      update: async (key: string, value: any): Promise<void> => {\n        await this.update(id, key, value)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/model/menu.ts",
    "content": "'use strict'\nimport { Buffer, Neovim } from '@chemzqm/neovim'\nimport { CancellationToken, Disposable, Emitter, Event } from '../util/protocol'\nimport events from '../events'\nimport { HighlightItem } from '../types'\nimport { disposeAll } from '../util'\nimport { byteLength, isAlphabet } from '../util/string'\nimport { DialogPreferences } from './dialog'\nimport Popup from './popup'\n\nexport interface MenuItem {\n  text: string\n  disabled?: boolean | { reason: string }\n}\n\nexport interface MenuConfig {\n  items: string[] | MenuItem[]\n  title?: string\n  content?: string\n  shortcuts?: boolean\n  position?: 'cursor' | 'center'\n  borderhighlight?: string\n}\n\nexport function isMenuItem(item: any): item is MenuItem {\n  if (!item) return false\n  return typeof item.text === 'string'\n}\n\nexport function toIndexText(n: number): string {\n  return n < 99 ? `${n + 1}. ` : '  '\n}\n\n/**\n * Select single item from menu at cursor position.\n */\nexport default class Menu {\n  private bufnr: number\n  private win: Popup\n  private currIndex = 0\n  private contentHeight = 0\n  private total: number\n  private disposables: Disposable[] = []\n  private keyMappings: Map<string, (character: string) => void> = new Map()\n  private shortcutIndexes: Set<number> = new Set()\n  private _disposed = false\n  private readonly _onDidClose = new Emitter<number>()\n  public readonly onDidClose: Event<number> = this._onDidClose.event\n  constructor(private nvim: Neovim, private config: MenuConfig, token?: CancellationToken) {\n    this.total = config.items.length\n    if (token) {\n      token.onCancellationRequested(() => {\n        this._onDidClose.fire(-1)\n        this.dispose()\n      })\n    }\n    this.disposables.push(this._onDidClose)\n    this.addKeymappings()\n  }\n\n  private attachEvents(): void {\n    events.on('InputChar', this.onInputChar.bind(this), null, this.disposables)\n    events.on('BufWinLeave', bufnr => {\n      if (bufnr == this.bufnr) {\n        this._onDidClose.fire(-1)\n        this.dispose()\n      }\n    }, null, this.disposables)\n  }\n\n  private addKeymappings(): void {\n    let { nvim } = this\n    this.addKeys(['<esc>', '<C-c>'], () => {\n      this._onDidClose.fire(-1)\n      this.dispose()\n    })\n    this.addKeys(['\\r', '<cr>'], () => {\n      this.selectCurrent()\n    })\n    let setCursorIndex = idx => {\n      nvim.pauseNotification()\n      this.setCursor(idx + this.contentHeight)\n      this.win.refreshScrollbar()\n      nvim.command('redraw', true)\n      nvim.resumeNotification(false, true)\n    }\n    this.addKeys('<C-f>', async () => {\n      await this.win.scrollForward()\n    })\n    this.addKeys('<C-b>', async () => {\n      await this.win.scrollBackward()\n    })\n    this.addKeys(['j', '<C-j>', '<down>', '<tab>', '<C-n>'], () => {\n      // next\n      let idx = this.currIndex == this.total - 1 ? 0 : this.currIndex + 1\n      setCursorIndex(idx)\n    })\n    this.addKeys(['k', '<C-k>', '<up>', '<s-tab>', '<C-p>'], () => {\n      // previous\n      let idx = this.currIndex == 0 ? this.total - 1 : this.currIndex - 1\n      setCursorIndex(idx)\n    })\n    this.addKeys(['g'], () => {\n      setCursorIndex(0)\n    })\n    this.addKeys(['G'], () => {\n      setCursorIndex(this.total - 1)\n    })\n    let timer: NodeJS.Timeout\n    let firstNumber: number\n    const choose = (n: number) => {\n      let disabled = this.isDisabled(n)\n      if (disabled) return\n      this._onDidClose.fire(n)\n      this.dispose()\n    }\n    this.addKeys(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], character => {\n      if (timer) clearTimeout(timer)\n      let n = parseInt(character, 10)\n      if (isNaN(n) || n > this.total) return\n      if (firstNumber == null && n == 0) return\n      if (firstNumber) {\n        let count = firstNumber * 10 + n\n        firstNumber = undefined\n        choose(count - 1)\n        return\n      }\n      if (this.total < 10 || n * 10 > this.total) {\n        choose(n - 1)\n        return\n      }\n      timer = setTimeout(async () => {\n        choose(n - 1)\n      }, 200)\n      firstNumber = n\n    })\n    if (this.config.shortcuts) {\n      this.addShortcuts(choose)\n    }\n  }\n\n  private addShortcuts(choose: (idx: number) => void): void {\n    let { items } = this.config\n    let texts: string[] = items.map(o => {\n      return isMenuItem(o) ? o.text : o\n    })\n    texts.forEach((text, idx) => {\n      if (text.length) {\n        let s = text[0]\n        if (isAlphabet(s.charCodeAt(0)) && !this.keyMappings.has(s)) {\n          this.shortcutIndexes.add(idx)\n          this.addKeys(s, () => {\n            choose(idx)\n          })\n        }\n      }\n    })\n  }\n\n  private isDisabled(idx: number): boolean {\n    let { items } = this.config\n    let item = items[idx]\n    if (isMenuItem(item) && item.disabled) {\n      return true\n    }\n    return false\n  }\n\n  public async show(preferences: DialogPreferences = {}): Promise<void> {\n    let { nvim, shortcutIndexes } = this\n    let { title, items, borderhighlight, position, content } = this.config\n    let opts: any = {}\n    if (title) opts.title = title\n    if (position === 'center') opts.relative = 'editor'\n    if (preferences.maxHeight) opts.maxHeight = preferences.maxHeight\n    if (preferences.maxWidth) opts.maxWidth = preferences.maxWidth\n    if (preferences.floatHighlight) opts.highlight = preferences.floatHighlight\n    if (borderhighlight) {\n      opts.borderhighlight = borderhighlight\n    } else if (preferences.floatBorderHighlight) {\n      opts.borderhighlight = preferences.floatBorderHighlight\n    }\n    if (preferences.rounded) opts.rounded = 1\n    if (typeof content === 'string') opts.content = content\n    if (preferences.confirmKey) {\n      this.addKeys(preferences.confirmKey, () => {\n        this.selectCurrent()\n      })\n    }\n    let highlights: HighlightItem[] = []\n    let lines = items.map((v, i) => {\n      let text: string = isMenuItem(v) ? v.text : v\n      let pre = toIndexText(i)\n      if (shortcutIndexes.has(i)) {\n        highlights.push({\n          lnum: i,\n          hlGroup: preferences.shortcutHighlight || 'MoreMsg',\n          colStart: byteLength(pre),\n          colEnd: byteLength(pre) + 1\n        })\n      }\n      return pre + text.trim()\n    })\n    lines.forEach((line, i) => {\n      let item = items[i]\n      if (isMenuItem(item) && item.disabled) {\n        highlights.push({\n          hlGroup: 'CocDisabled',\n          lnum: i,\n          colStart: 0,\n          colEnd: byteLength(line)\n        })\n      }\n    })\n    if (highlights.length) opts.highlights = highlights\n    let [winid, bufnr, contentHeight] = await nvim.call('coc#dialog#create_menu', [lines, opts]) as [number, number, number]\n    nvim.command('redraw', true)\n    if (this._disposed) return\n    this.win = new Popup(nvim, winid, bufnr, lines.length + contentHeight, contentHeight)\n    this.bufnr = bufnr\n    this.contentHeight = contentHeight\n    this.attachEvents()\n    nvim.call('coc#prompt#start_prompt', ['menu'], true)\n  }\n\n  private selectCurrent(): void {\n    if (this.isDisabled(this.currIndex)) {\n      let item = this.config.items[this.currIndex] as MenuItem\n      if (item.disabled['reason']) {\n        this.nvim.outWriteLine(`Item disabled: ${item.disabled['reason']}`)\n      }\n      return\n    }\n    this._onDidClose.fire(this.currIndex)\n    this.dispose()\n  }\n\n  public get buffer(): Buffer {\n    return this.bufnr ? this.nvim.createBuffer(this.bufnr) : undefined\n  }\n\n  public dispose(): void {\n    if (this._disposed) return\n    this._disposed = true\n    disposeAll(this.disposables)\n    this.shortcutIndexes.clear()\n    this.keyMappings.clear()\n    this.nvim.call('coc#prompt#stop_prompt', ['menu'], true)\n    this.win?.close()\n    this.bufnr = undefined\n    this.win = undefined\n  }\n\n  public async onInputChar(session: string, character: string): Promise<void> {\n    if (session != 'menu' || !this.win) return\n    let fn = this.keyMappings.get(character)\n    if (fn) await Promise.resolve(fn(character))\n  }\n\n  private setCursor(index: number): void {\n    this.currIndex = index - this.contentHeight\n    this.win.setCursor(index)\n  }\n\n  private addKeys(keys: string | string[], fn: (character: string) => void): void {\n    if (Array.isArray(keys)) {\n      for (let key of keys) {\n        this.keyMappings.set(key, fn)\n      }\n    } else {\n      this.keyMappings.set(keys, fn)\n    }\n  }\n}\n"
  },
  {
    "path": "src/model/mru.ts",
    "content": "'use strict'\nimport { distinct } from '../util/array'\nimport { dataHome } from '../util/constants'\nimport { readFileLines, writeFile } from '../util/fs'\nimport { fs, path, promisify } from '../util/node'\n\n/**\n * Mru - manage string items as lines in mru file.\n */\nexport default class Mru {\n  private file: string\n\n  /**\n   * @param {string} name unique name\n   * @param {string} base? optional directory name, default to data root of coc.nvim\n   */\n  constructor(\n    name: string,\n    base?: string,\n    private maximum = 5000) {\n    this.file = path.join(base || dataHome, name)\n    let dir = path.dirname(this.file)\n    fs.mkdirSync(dir, { recursive: true })\n  }\n\n  /**\n   * Load lines from mru file\n   */\n  public async load(): Promise<string[]> {\n    try {\n      let lines = await readFileLines(this.file, 0, this.maximum)\n      if (lines.length > this.maximum) {\n        let newLines = lines.slice(0, this.maximum)\n        await writeFile(this.file, newLines.join('\\n'))\n        return distinct(newLines)\n      }\n      return distinct(lines)\n    } catch (e) {\n      return []\n    }\n  }\n\n  public loadSync(): string[] {\n    try {\n      let content = fs.readFileSync(this.file, 'utf8')\n      content = content.trim()\n      return content.length ? content.trim().split('\\n') : []\n    } catch (e) {\n      return []\n    }\n  }\n\n  /**\n   * Add item to mru file.\n   */\n  public async add(item: string): Promise<void> {\n    let buf: Buffer\n    try {\n      buf = fs.readFileSync(this.file)\n      if (buf[0] === 239 && buf[1] === 187 && buf[2] === 191) {\n        buf = buf.slice(3)\n      }\n      buf = Buffer.concat([Buffer.from(item, 'utf8'), new Uint8Array([10]), buf])\n    } catch (e) {\n      buf = Buffer.concat([Buffer.from(item, 'utf8'), new Uint8Array([10])])\n    }\n    await promisify(fs.writeFile)(this.file, buf)\n  }\n\n  /**\n   * Remove item from mru file.\n   */\n  public async remove(item: string): Promise<void> {\n    let items = await this.load()\n    let len = items.length\n    items = items.filter(s => s != item)\n    if (items.length != len) {\n      await writeFile(this.file, items.join('\\n'))\n    }\n  }\n\n  /**\n   * Remove the data file.\n   */\n  public async clean(): Promise<void> {\n    try {\n      await promisify(fs.unlink)(this.file)\n    } catch (e) {\n      // noop\n    }\n  }\n}\n"
  },
  {
    "path": "src/model/notification.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport events from '../events'\nimport { defaultValue, disposeAll } from '../util'\nimport { toArray } from '../util/array'\nimport { Disposable } from '../util/protocol'\nimport { toText } from '../util/string'\nimport { DialogButton } from './dialog'\n\n/**\n * Represents an action that is shown with an information, warning, or\n * error message.\n * @see [showInformationMessage](#window.showInformationMessage)\n * @see [showWarningMessage](#window.showWarningMessage)\n * @see [showErrorMessage](#window.showErrorMessage)\n */\nexport interface MessageItem {\n\n  /**\n   * A short title like 'Retry', 'Open Log' etc.\n   */\n  title: string\n\n  /**\n   * A hint for modal dialogs that the item should be triggered\n   * when the user cancels the dialog (e.g. by pressing the ESC\n   * key).\n   *\n   * Note: this option is ignored for non-modal messages.\n   * Note: not used by coc.nvim for now.\n   */\n  isCloseAffordance?: boolean\n}\n\nexport interface NotificationPreferences {\n  disabled: boolean\n  maxWidth: number\n  maxHeight: number\n  highlight: string\n  winblend: number\n  border: boolean\n  timeout: number\n  marginRight: number\n  focusable: boolean\n  minWidth?: number\n  source?: string\n}\n\nexport type NotificationKind = 'error' | 'info' | 'warning' | 'progress'\n\nexport interface NotificationConfig {\n  kind?: NotificationKind\n\n  content?: string\n  /**\n   * Optional title text.\n   */\n  title?: string\n  /**\n   * Buttons as bottom of dialog.\n   */\n  buttons?: DialogButton[]\n  /**\n   * index is -1 for window close without button click\n   */\n  callback?: (index: number) => void\n  closable?: boolean\n}\n\nexport function toButtons(texts: string[]): DialogButton[] {\n  return texts.map((s, index) => {\n    return { text: s, index }\n  })\n}\n\nexport function toTitles(items: (string | MessageItem)[]): string[] {\n  return items.map(item => typeof item === 'string' ? item : item.title)\n}\n\nexport default class Notification {\n  protected disposables: Disposable[] = []\n  public bufnr: number\n  protected _winid: number\n  constructor(protected nvim: Neovim, protected config: NotificationConfig, attachEvents = true) {\n    if (attachEvents) {\n      events.on('BufWinLeave', bufnr => {\n        if (bufnr == this.bufnr) {\n          this.dispose()\n          if (config.callback) config.callback(-1)\n        }\n      }, null, this.disposables)\n      let btns = toArray(config.buttons).filter(o => o.disabled != true)\n      events.on('FloatBtnClick', (bufnr, idx) => {\n        if (bufnr == this.bufnr) {\n          this.dispose()\n          if (config.callback) config.callback(defaultValue(btns[idx]?.index, -1))\n        }\n      }, null, this.disposables)\n    }\n  }\n\n  protected get lines(): string[] {\n    return this.config.content ? this.config.content.split(/\\r?\\n/) : []\n  }\n\n  public async show(preferences: Partial<NotificationPreferences>): Promise<void> {\n    let { nvim } = this\n    let { buttons, kind, title } = this.config\n    let opts: any = Object.assign({}, preferences)\n    opts.kind = toText(kind)\n    opts.close = this.config.closable === true ? 1 : 0\n    if (title) opts.title = title\n    if (preferences.border) {\n      opts.borderhighlight = kind ? `CocNotification${kind[0].toUpperCase()}${kind.slice(1)}` : preferences.highlight\n    }\n    if (Array.isArray(buttons)) {\n      let actions: string[] = buttons.filter(o => !o.disabled).map(o => o.text)\n      opts.actions = actions\n    }\n    let res = await nvim.call('coc#notify#create', [this.lines, opts]) as [number, number]\n    this._winid = res[0]\n    this.bufnr = res[1]\n  }\n\n  public get winid(): number | undefined {\n    return this._winid\n  }\n\n  public dispose(): void {\n    let { winid } = this\n    if (winid) {\n      this.nvim.call('coc#notify#close', [winid], true)\n      this.nvim.redrawVim()\n    }\n    this.bufnr = undefined\n    this._winid = undefined\n    disposeAll(this.disposables)\n  }\n}\n"
  },
  {
    "path": "src/model/outputChannel.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { OutputChannel } from '../types'\n\nfunction escapeQuote(input: string): string {\n  return input.replace(/'/g, \"''\")\n}\n\nexport default class BufferChannel implements OutputChannel {\n  private lines: string[] = ['']\n  private _disposed = false\n  public created = false\n  constructor(public name: string, private nvim?: Neovim, private onDispose?: () => void) {\n  }\n\n  public get content(): string {\n    return this.lines.join('\\n')\n  }\n\n  private _append(value: string): void {\n    let { nvim } = this\n    if (!nvim) return\n    let idx = this.lines.length - 1\n    let newlines = value.split(/\\r?\\n/)\n    let lastline = this.lines[idx] + newlines[0]\n    this.lines[idx] = lastline\n    let append = newlines.slice(1)\n    this.lines = this.lines.concat(append)\n    if (!this.created) return\n    nvim.pauseNotification()\n    nvim.call('setbufline', [this.bufname, '$', lastline], true)\n    if (append.length) {\n      nvim.call('appendbufline', [this.bufname, '$', append], true)\n    }\n    nvim.resumeNotification(false, true)\n  }\n\n  public append(value: string): void {\n    if (!this.validate()) return\n    this._append(value)\n  }\n\n  public appendLine(value: string): void {\n    if (!this.validate()) return\n    this._append(value + '\\n')\n  }\n\n  public clear(keep?: number): void {\n    let { nvim } = this\n    if (!this.validate() || !nvim) return\n    this.lines = keep ? this.lines.slice(-keep) : []\n    if (!this.created) return\n    nvim.pauseNotification()\n    nvim.call('deletebufline', [this.bufname, 1, '$'], true)\n    if (this.lines.length) {\n      nvim.call('appendbufline', [this.bufname, '$', this.lines], true)\n    }\n    nvim.resumeNotification(true, true)\n  }\n\n  public hide(): void {\n    this.created = false\n    let name = escapeQuote(this.bufname)\n    if (this.nvim) this.nvim.command(`exe 'silent! bwipeout! '.fnameescape('${name}')`, true)\n  }\n\n  private get bufname(): string {\n    return `output:///${encodeURI(this.name)}`\n  }\n\n  public show(preserveFocus?: boolean, cmd = 'vs'): void {\n    let { nvim } = this\n    if (!nvim) return\n    let name = escapeQuote(this.bufname)\n    nvim.pauseNotification()\n    nvim.command(`exe '${cmd} '.fnameescape('${name}')`, true)\n    if (preserveFocus) {\n      nvim.command('wincmd p', true)\n    }\n    nvim.resumeNotification(true, true)\n    this.created = true\n  }\n\n  private validate(): boolean {\n    return !this._disposed\n  }\n\n  public dispose(): void {\n    if (this.onDispose) this.onDispose()\n    this._disposed = true\n    this.hide()\n    this.lines = []\n  }\n}\n"
  },
  {
    "path": "src/model/picker.ts",
    "content": "'use strict'\nimport { Buffer, Neovim } from '@chemzqm/neovim'\nimport events from '../events'\nimport { HighlightItem, QuickPickItem } from '../types'\nimport { disposeAll } from '../util'\nimport { CancellationToken, Disposable, Emitter, Event } from '../util/protocol'\nimport { byteLength } from '../util/string'\nimport { DialogPreferences } from './dialog'\nimport Popup from './popup'\n\ninterface PickerConfig {\n  title: string\n  items: QuickPickItem[]\n}\n\nexport function toPickerItems(items: (QuickPickItem | string)[]): QuickPickItem[] {\n  return items.map(item => typeof item === 'string' ? { label: item } : item)\n}\n\n/**\n * Pick multiple items from dialog\n */\nexport default class Picker {\n  public bufnr: number\n  private win: Popup | undefined\n  private picked: Set<number> = new Set()\n  private total: number\n  private disposables: Disposable[] = []\n  private keyMappings: Map<string, (character: string) => void> = new Map()\n  private readonly _onDidClose = new Emitter<number[] | undefined>()\n  public readonly onDidClose: Event<number[] | undefined> = this._onDidClose.event\n  constructor(private nvim: Neovim, private config: PickerConfig, token?: CancellationToken) {\n    for (let i = 0; i < config.items.length; i++) {\n      let item = config.items[i]\n      if (item.picked) this.picked.add(i)\n    }\n    this.total = config.items.length\n    if (token) {\n      token.onCancellationRequested(() => {\n        this.win?.close()\n      })\n    }\n    this.disposables.push(this._onDidClose)\n  }\n\n  public get currIndex(): number {\n    return this.win ? this.win.currIndex : 0\n  }\n\n  private attachEvents(): void {\n    events.on('InputChar', this.onInputChar.bind(this), null, this.disposables)\n    events.on('BufWinLeave', bufnr => {\n      if (bufnr == this.bufnr) {\n        this._onDidClose.fire(undefined)\n        this.bufnr = undefined\n        this.win = undefined\n        this.dispose()\n      }\n    }, null, this.disposables)\n    events.on('FloatBtnClick', (bufnr, idx) => {\n      if (bufnr != this.bufnr) return\n      if (idx == 0) {\n        let selected = Array.from(this.picked)\n        this._onDidClose.fire(selected.length > 0 ? selected : undefined)\n      } else {\n        this._onDidClose.fire(undefined)\n      }\n      this.dispose()\n    }, null, this.disposables)\n    this.addKeymappings()\n  }\n\n  private addKeymappings(): void {\n    let { nvim } = this\n    const toggleSelect = idx => {\n      if (this.picked.has(idx)) {\n        this.picked.delete(idx)\n      } else {\n        this.picked.add(idx)\n      }\n    }\n    this.addKeys('<LeftRelease>', async () => {\n      // not work on vim\n      // if (isVim) return\n      let [winid, lnum, col] = await nvim.call('coc#ui#get_mouse') as [number, number, number]\n      nvim.pauseNotification()\n      if (winid == this.win.winid) {\n        if (col <= 3) {\n          toggleSelect(lnum - 1)\n          this.changeLine(lnum - 1)\n        } else {\n          this.win.setCursor(lnum - 1)\n        }\n      }\n      nvim.call('win_gotoid', [winid], true)\n      nvim.call('cursor', [lnum, col], true)\n      nvim.call('coc#float#nvim_float_click', [], true)\n      nvim.command('redraw', true)\n      await nvim.resumeNotification()\n    })\n    this.addKeys(['<esc>', '<C-c>'], () => {\n      this._onDidClose.fire(undefined)\n      this.dispose()\n    })\n    this.addKeys('<cr>', () => {\n      if (this.picked.size == 0) {\n        this._onDidClose.fire(undefined)\n      } else {\n        let selected = Array.from(this.picked)\n        this._onDidClose.fire(selected)\n      }\n      this.dispose()\n    })\n    this.addKeys(['j', '<down>', '<tab>', '<C-n>'], () => {\n      this.win.setCursor(this.currIndex + 1, true)\n    })\n    this.addKeys(['k', '<up>', '<s-tab>', '<C-p>'], () => {\n      this.win.setCursor(this.currIndex - 1, true)\n    })\n    this.addKeys(['g'], () => {\n      this.win.setCursor(0, true)\n    })\n    this.addKeys(['G'], () => {\n      this.win.setCursor(this.total - 1, true)\n    })\n    this.addKeys(' ', async () => {\n      let idx = this.currIndex\n      toggleSelect(idx)\n      nvim.pauseNotification()\n      this.changeLine(idx)\n      this.win.setCursor(this.currIndex + 1)\n      nvim.command('redraw', true)\n      await nvim.resumeNotification()\n    })\n    this.addKeys('<C-f>', async () => {\n      await this.win.scrollForward()\n    })\n    this.addKeys('<C-b>', async () => {\n      await this.win.scrollBackward()\n    })\n  }\n\n  public async show(preferences: DialogPreferences = {}): Promise<number> {\n    let { nvim } = this\n    let { title, items } = this.config\n    let opts: any = { close: 1, cursorline: 1 }\n    if (preferences.maxHeight) opts.maxHeight = preferences.maxHeight\n    if (preferences.maxWidth) opts.maxWidth = preferences.maxWidth\n    if (title) opts.title = title\n    if (preferences.floatHighlight) opts.highlight = preferences.floatHighlight\n    if (preferences.floatBorderHighlight) opts.borderhighlight = [preferences.floatBorderHighlight]\n    if (preferences.pickerButtons) {\n      let shortcut = preferences.pickerButtonShortcut\n      opts.buttons = ['Submit' + (shortcut ? ' <cr>' : ''), 'Cancel' + (shortcut ? ' <esc>' : '')]\n    }\n    if (preferences.rounded) opts.rounded = 1\n    if (preferences.confirmKey && preferences.confirmKey != '<cr>') {\n      this.addKeys(preferences.confirmKey, () => {\n        this._onDidClose.fire(undefined)\n        this.dispose()\n      })\n    }\n    let lines = []\n    let highlights: HighlightItem[] = []\n    for (let i = 0; i < items.length; i++) {\n      let item = items[i]\n      let line = `[${item.picked ? 'x' : ' '}] ${item.label}`\n      if (item.description) {\n        let start = byteLength(line)\n        line = line + ` ${item.description}`\n        highlights.push({ hlGroup: 'Comment', lnum: i, colStart: start, colEnd: byteLength(line) })\n      }\n      lines.push(line)\n    }\n    if (highlights.length) opts.highlights = highlights\n    let res = await nvim.call('coc#dialog#create_dialog', [lines, opts]) as [number, number]\n    this.win = new Popup(nvim, res[0], res[1], lines.length)\n    this.bufnr = res[1]\n    nvim.call('coc#prompt#start_prompt', ['picker'], true)\n    this.attachEvents()\n    this.win.setCursor(0, true)\n    return res[0]\n  }\n\n  public get buffer(): Buffer {\n    return this.bufnr ? this.nvim.createBuffer(this.bufnr) : undefined\n  }\n\n  public dispose(): void {\n    this.picked.clear()\n    this.keyMappings.clear()\n    disposeAll(this.disposables)\n    this.nvim.call('coc#prompt#stop_prompt', ['picker'], true)\n    this.win?.close()\n    this.win = undefined\n  }\n\n  public async onInputChar(session: string, character: string): Promise<void> {\n    if (session != 'picker' || !this.win) return\n    let fn = this.keyMappings.get(character)\n    if (fn) await Promise.resolve(fn(character))\n  }\n\n  public changeLine(index: number): void {\n    let { nvim } = this\n    let item = this.config.items[index]\n    if (!item) return\n    let line = `[${this.picked.has(index) ? 'x' : ' '}] ${item.label}`\n    let col = byteLength(line)\n    if (item.description) line = line + ` ${item.description}`\n    nvim.call('setbufline', [this.bufnr, index + 1, line], true)\n    let buf = nvim.createBuffer(this.bufnr)\n    // eslint-disable-next-line @typescript-eslint/no-floating-promises\n    buf.addHighlight({ hlGroup: 'Comment', line: index, srcId: 1, colStart: col, colEnd: -1 })\n  }\n\n  private addKeys(keys: string | string[], fn: (character: string) => void): void {\n    if (Array.isArray(keys)) {\n      for (let key of keys) {\n        this.keyMappings.set(key, fn)\n      }\n    } else {\n      this.keyMappings.set(keys, fn)\n    }\n  }\n}\n"
  },
  {
    "path": "src/model/popup.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { isVim } from '../util/constants'\n\ninterface WindowInfo {\n  topline: number,\n  botline: number\n}\n\n/**\n * More methods for float window/popup\n */\nexport default class Popup {\n  constructor(\n    private nvim: Neovim,\n    public readonly winid,\n    public readonly bufnr,\n    public linecount: number,\n    private _currIndex = 0\n  ) {\n  }\n\n  public get currIndex(): number {\n    return this._currIndex\n  }\n\n  public close(): void {\n    this.nvim.call('coc#float#close', [this.winid], true)\n  }\n\n  public refreshScrollbar(): void {\n    if (!isVim) this.nvim.call('coc#float#nvim_scrollbar', [this.winid], true)\n  }\n\n  public execute(cmd: string): void {\n    this.nvim.call('win_execute', [this.winid, cmd], true)\n  }\n\n  private async getWininfo(): Promise<WindowInfo> {\n    return await this.nvim.call('coc#float#get_wininfo', [this.winid]) as WindowInfo\n  }\n\n  /**\n   * Simple scroll method, not consider wrapped lines.\n   */\n  public async scrollForward(): Promise<void> {\n    let { nvim, bufnr } = this\n    let buf = nvim.createBuffer(bufnr)\n    let total = await buf.length\n    let { botline } = await this.getWininfo()\n    if (botline >= total || botline == 0) return\n    nvim.pauseNotification()\n    this.setCursor(botline - 1)\n    this.execute(`silent! noa setl scrolloff=0`)\n    this.execute(`normal! ${botline}Gzt`)\n    this.refreshScrollbar()\n    nvim.command('redraw', true)\n    nvim.resumeNotification(false, true)\n  }\n\n  /**\n   * Simple scroll method, not consider wrapped lines.\n   */\n  public async scrollBackward(): Promise<void> {\n    let { nvim } = this\n    let { topline } = await this.getWininfo()\n    if (topline == 1) return\n    nvim.pauseNotification()\n    this.setCursor(topline - 1)\n    this.execute(`normal! ${topline}Gzb`)\n    this.refreshScrollbar()\n    nvim.command('redraw', true)\n    nvim.resumeNotification(false, true)\n  }\n\n  /**\n   * Move cursor and highlight.\n   */\n  public setCursor(index: number, redraw = false): void {\n    let { nvim, bufnr, winid, linecount } = this\n    if (index < 0) {\n      index = 0\n    } else if (index > linecount - 1) {\n      index = linecount - 1\n    }\n    this._currIndex = index\n    nvim.call('coc#dialog#set_cursor', [winid, bufnr, index + 1], true)\n    if (redraw) {\n      this.refreshScrollbar()\n      nvim.command('redraw', true)\n    }\n  }\n}\n"
  },
  {
    "path": "src/model/progress.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport events from '../events'\nimport { createLogger } from '../logger'\nimport { CancellationToken, CancellationTokenSource, Emitter, Event } from '../util/protocol'\nimport Notification, { NotificationPreferences } from './notification'\nconst logger = createLogger('model-progress')\n\nexport interface Progress {\n  report(value: { message?: string; increment?: number }): void\n}\n\nexport interface ProgressOptions<R> {\n  title?: string\n  cancellable?: boolean\n  task: (progress: Progress, token: CancellationToken) => Thenable<R>\n}\n\nexport function formatMessage(title: string | undefined, message: string | undefined, total: number) {\n  let parts = []\n  if (title) parts.push(title)\n  if (message) parts.push(message)\n  if (total) parts.push(total + '%')\n  return parts.join(' ')\n}\n\nexport default class ProgressNotification<R> extends Notification {\n  private tokenSource: CancellationTokenSource\n  private readonly _onDidFinish = new Emitter<R>()\n  public readonly onDidFinish: Event<R> = this._onDidFinish.event\n  constructor(nvim: Neovim, private option: ProgressOptions<R>) {\n    super(nvim, {\n      kind: 'progress',\n      title: option.title,\n      closable: option.cancellable\n    }, false)\n    this.disposables.push(this._onDidFinish)\n    events.on('BufWinLeave', this.cancelProgress, null, this.disposables)\n  }\n\n  private cancelProgress = (bufnr: any) => {\n    if (bufnr == this.bufnr && this.tokenSource) {\n      this.tokenSource.cancel()\n    }\n  }\n\n  public async show(preferences: Partial<NotificationPreferences>): Promise<void> {\n    let { task } = this.option\n    let tokenSource = this.tokenSource = new CancellationTokenSource()\n    this.disposables.push(tokenSource)\n    let total = 0\n    if (!preferences.disabled) {\n      await super.show(preferences)\n    } else {\n      logger.warn(`progress window disabled by configuration \"notification.disabledProgressSources\"`)\n    }\n    task({\n      report: p => {\n        if (!this.winid) return\n        let { nvim } = this\n        if (p.increment) {\n          total += p.increment\n          nvim.call('coc#window#set_var', [this.winid, 'percent', `${total}%`], true)\n        }\n        if (p.message) nvim.call('coc#window#set_var', [this.winid, 'message', p.message], true)\n      }\n    }, tokenSource.token).then(res => {\n      this._onDidFinish.fire(res)\n      this.dispose()\n    }, err => {\n      if (err) this.nvim.echoError(err)\n      this._onDidFinish.fire(undefined)\n      this.dispose()\n    })\n  }\n}\n"
  },
  {
    "path": "src/model/quickpick.ts",
    "content": "'use strict'\nimport { Buffer, Neovim } from '@chemzqm/neovim'\nimport events from '../events'\nimport { createLogger } from '../logger'\nimport { HighlightItem, QuickPickItem } from '../types'\nimport { defaultValue, disposeAll } from '../util'\nimport { toArray } from '../util/array'\nimport { FuzzyScorer, anyScore, fuzzyScoreGracefulAggressive } from '../util/filter'\nimport { Disposable, Emitter, Event } from '../util/protocol'\nimport { byteLength, toText } from '../util/string'\nimport { DialogPreferences } from './dialog'\nimport { toSpans } from './fuzzyMatch'\nimport InputBox from './input'\nimport Popup from './popup'\nimport { StrWidth } from './strwidth'\nconst logger = createLogger('quickpick')\n\ninterface FilteredLine {\n  line: string\n  score: number\n  index: number\n  spans: [number, number][]\n  descriptionSpan?: [number, number]\n}\n\n/**\n * Pick single/multiple items from prompt list.\n */\nexport default class QuickPick<T extends QuickPickItem> {\n  public title: string\n  public loading: boolean\n  public items: readonly T[]\n  public activeItems: readonly T[]\n  public selectedItems: T[]\n  public value: string\n  public canSelectMany = false\n  public matchOnDescription = false\n  public maxHeight = 30\n  public width: number | undefined\n  public placeholder: string | undefined\n  private bufnr: number\n  private win: Popup\n  private filteredItems: readonly T[] = []\n  private disposables: Disposable[] = []\n  private input: InputBox | undefined\n  private _changed = false\n  // emitted with selected items or undefined when cancelled.\n  private readonly _onDidFinish = new Emitter<T[] | null>()\n  private readonly _onDidChangeSelection = new Emitter<ReadonlyArray<T>>()\n  private readonly _onDidChangeValue = new Emitter<string>()\n  public readonly onDidFinish: Event<T[] | null> = this._onDidFinish.event\n  public readonly onDidChangeSelection: Event<ReadonlyArray<T>> = this._onDidChangeSelection.event\n  public readonly onDidChangeValue: Event<string> = this._onDidChangeValue.event\n  constructor(private nvim: Neovim, private preferences: DialogPreferences = {}) {\n    let items = []\n    let input = this.input = new InputBox(this.nvim, '')\n    if (preferences.maxHeight) this.maxHeight = preferences.maxHeight\n    Object.defineProperty(this, 'items', {\n      set: (list: T[]) => {\n        items = toArray(list)\n        this.selectedItems = items.filter(o => o.picked)\n        this.filterItems('')\n      },\n      get: () => items\n    })\n    Object.defineProperty(this, 'activeItems', {\n      set: (list: T[]) => {\n        items = toArray(list)\n        this.filteredItems = items\n        this.showFilteredItems()\n      },\n      get: () => this.filteredItems\n    })\n    Object.defineProperty(this, 'value', {\n      set: (value: string) => {\n        this.input.value = value\n      },\n      get: () => this.input.value\n    })\n    Object.defineProperty(this, 'title', {\n      set: (newTitle: string) => {\n        input.title = toText(newTitle)\n      },\n      get: () => input.title ?? ''\n    })\n    Object.defineProperty(this, 'loading', {\n      set: (loading: boolean) => {\n        input.loading = loading\n      },\n      get: () => input.loading\n    })\n    input.onDidChange(value => {\n      this._changed = false\n      this._onDidChangeValue.fire(value)\n      // List already update by change items or activeItems\n      if (this._changed) {\n        this._changed = false\n        return\n      }\n      this.filterItems(value)\n    }, this)\n    input.onDidFinish(this.onFinish, this)\n  }\n\n  public get maxWidth(): number {\n    return this.preferences.maxWidth ?? 80\n  }\n\n  public get currIndex(): number {\n    return this.win ? this.win.currIndex : 0\n  }\n\n  public get buffer(): Buffer {\n    return this.bufnr ? this.nvim.createBuffer(this.bufnr) : undefined\n  }\n\n  public get winid(): number | undefined {\n    return this.win?.winid\n  }\n\n  public get inputBox(): InputBox | undefined {\n    return this.input\n  }\n\n  public setCursor(index: number): void {\n    this.win?.setCursor(index, true)\n  }\n\n  private attachEvents(inputBufnr: number): void {\n    events.on('BufWinLeave', bufnr => {\n      if (bufnr == this.bufnr) {\n        this.bufnr = undefined\n        this.win = undefined\n      }\n    }, null, this.disposables)\n    events.on('InputListSelect', index => {\n      if (index >= 0) {\n        this.setCursor(index)\n      }\n      this.onFinish(index < 0 ? undefined : '')\n    }, null, this.disposables)\n    events.on('PromptKeyPress', async (bufnr, key) => {\n      if (bufnr == inputBufnr) {\n        if (key == '<C-f>') {\n          await this.win.scrollForward()\n        } else if (key == '<C-b>') {\n          await this.win.scrollBackward()\n        } else if (['<C-j>', '<C-n>', '<down>'].includes(key)) {\n          this.setCursor(this.currIndex + 1)\n        } else if (['<C-k>', '<C-p>', '<up>'].includes(key)) {\n          this.setCursor(this.currIndex - 1)\n        } else if (this.canSelectMany && ['<C-@>', '<C-t>'].includes(key)) {\n          this.toggePicked(this.currIndex)\n        }\n      }\n    }, null, this.disposables)\n  }\n\n  public async show(): Promise<void> {\n    let { nvim, items, input, width, preferences, maxHeight } = this\n    let { lines, highlights } = this.buildList(items, input.value)\n    let minWidth: number | undefined\n    let lincount = 0\n    const sw = await StrWidth.create()\n    if (typeof width === 'number') minWidth = Math.min(width, this.maxWidth)\n    let max = 40\n    lines.forEach(line => {\n      let w = sw.getWidth(line)\n      if (typeof minWidth === 'number') {\n        lincount += Math.ceil(w / minWidth)\n      } else {\n        if (w >= 80) {\n          minWidth = 80\n          lincount += Math.ceil(w / minWidth)\n        } else {\n          max = Math.max(max, w)\n          lincount += 1\n        }\n      }\n    })\n    if (minWidth === undefined) minWidth = max\n    let rounded = !!preferences.rounded\n    await input.show(this.title, {\n      quickpick: lines,\n      position: 'center',\n      placeHolder: this.placeholder,\n      marginTop: 10,\n      border: [1, 1, 0, 1],\n      list: true,\n      rounded,\n      minWidth,\n      maxWidth: this.maxWidth,\n      highlight: preferences.floatHighlight,\n      borderhighlight: preferences.floatBorderHighlight\n    })\n    let opts: any = { lines, rounded, maxHeight, highlights, linecount: Math.max(1, lincount) }\n    opts.highlight = defaultValue(preferences.floatHighlight, undefined)\n    opts.borderhighlight = defaultValue(preferences.floatBorderHighlight, undefined)\n    let res = await nvim.call('coc#dialog#create_list', [input.winid, input.dimension, opts])\n    if (!res) throw new Error('Unable to open list window.')\n    // let height\n    this.win = new Popup(nvim, res[0], res[1], lines.length)\n    this.win.refreshScrollbar()\n    this.bufnr = res[1]\n    this.setCursor(0)\n    this.attachEvents(input.bufnr)\n  }\n\n  private buildList(items: ReadonlyArray<T>, input: string, loose = false): { lines: string[], highlights: HighlightItem[] } {\n    let { selectedItems, canSelectMany } = this\n    let filteredItems: T[] = []\n    let filtered: FilteredLine[] = []\n    let emptyInput = input.length === 0\n    let lowInput = input.toLowerCase()\n    const scoreFn: FuzzyScorer = loose ? anyScore : fuzzyScoreGracefulAggressive\n    const wordPos = canSelectMany ? 4 : 0\n    for (let index = 0; index < items.length; index++) {\n      const item = items[index]\n      let filterText = this.toFilterText(item)\n      let spans: [number, number][] = []\n      let score = 0\n      let descriptionSpan: [number, number] | undefined\n      if (!emptyInput) {\n        let res = scoreFn(input, lowInput, 0, filterText, filterText.toLowerCase(), wordPos, { boostFullMatch: false, firstMatchCanBeWeak: true })\n        if (!res) continue\n        // keep the order for loose match\n        score = loose ? 0 : res[0]\n        spans = toSpans(filterText, res)\n      }\n      let picked = selectedItems.includes(item)\n      let line = canSelectMany ? `[${picked ? 'x' : ' '}] ${item.label}` : item.label\n      if (item.description) {\n        let start = byteLength(line)\n        line = line + ` ${item.description}`\n        descriptionSpan = [start, start + 1 + byteLength(item.description)]\n      }\n      let lineItem: FilteredLine = { line, descriptionSpan, index, score, spans }\n      filtered.push(lineItem)\n    }\n    let lines: string[] = []\n    let highlights: HighlightItem[] = []\n    filtered.sort((a, b) => {\n      if (a.score != b.score) return b.score - a.score\n      return a.index - b.index\n    })\n    const toHighlight = (lnum: number, span: [number, number], hlGroup: string, pre: number) => {\n      return { lnum, colStart: span[0] + pre, colEnd: span[1] + pre, hlGroup }\n    }\n    filtered.forEach((item, index) => {\n      lines.push(item.line)\n      item.spans.forEach(span => {\n        highlights.push(toHighlight(index, span, 'CocSearch', wordPos))\n      })\n      if (item.descriptionSpan) {\n        highlights.push(toHighlight(index, item.descriptionSpan, 'Comment', 0))\n      }\n      filteredItems.push(items[item.index])\n    })\n    this.filteredItems = filteredItems\n    return { lines, highlights }\n  }\n\n  /**\n   * Filter items, does highlight only when loose is true\n   */\n  private _filter(items: ReadonlyArray<T>, input: string, loose = false): void {\n    if (!this.win) return\n    this._changed = true\n    let { lines, highlights } = this.buildList(items, input, loose)\n    this.nvim.call('coc#dialog#update_list', [this.win.winid, this.win.bufnr, lines, highlights], true)\n    this.win.linecount = lines.length\n    this.setCursor(0)\n  }\n\n  /**\n   * Filter items with input\n   */\n  public filterItems(input: string): void {\n    this._filter(this.items, input)\n  }\n\n  public showFilteredItems(): void {\n    let { input, filteredItems } = this\n    this._filter(filteredItems, input.value, true)\n  }\n\n  private onFinish(input: string | undefined): void {\n    let items = input == null ? null : this.getSelectedItems()\n    if (!this.canSelectMany && input !== undefined && Array.isArray(items)) {\n      this._onDidChangeSelection.fire(items)\n    }\n    this.nvim.call('coc#float#close', [this.winid], true)\n    // needed to make sure window closed\n    setTimeout(() => {\n      this._onDidFinish.fire(items)\n      this.dispose()\n    }, 30)\n  }\n\n  private getSelectedItems(): T[] {\n    let { canSelectMany } = this\n    if (canSelectMany) return this.selectedItems\n    return toArray(this.filteredItems[this.currIndex])\n  }\n\n  public toggePicked(index: number): void {\n    let { nvim, filteredItems, selectedItems } = this\n    let item = filteredItems[index]\n    if (!item) return\n    let idx = selectedItems.indexOf(item)\n    if (idx != -1) {\n      selectedItems.splice(idx, 1)\n    } else {\n      selectedItems.push(item)\n    }\n    let text = idx == -1 ? 'x' : ' '\n    nvim.pauseNotification()\n    this.win.execute(`normal! ^1lr${text}`)\n    this.win.setCursor(this.win.currIndex + 1)\n    nvim.resumeNotification(true, true)\n    this._onDidChangeSelection.fire(selectedItems)\n  }\n\n  private toFilterText(item: T): string {\n    let { label, description } = item\n    let { canSelectMany } = this\n    let line = `${canSelectMany ? '    ' : ''}${label.replace(/\\r?\\n/, '')}`\n    return this.matchOnDescription ? line + ' ' + (description ?? '') : line\n  }\n\n  public dispose(): void {\n    this.bufnr = undefined\n    this.input.dispose()\n    this.win?.close()\n    this._onDidFinish.dispose()\n    this._onDidChangeSelection.dispose()\n    disposeAll(this.disposables)\n  }\n}\n"
  },
  {
    "path": "src/model/regions.ts",
    "content": "'use strict'\n/**\n * Remember used regions\n */\nexport default class Regions {\n  /**\n   * ranges that never overlaps.\n   */\n  private ranges: [number, number][] = []\n\n  public get current(): ReadonlyArray<number> {\n    let res: number[] = []\n    this.ranges.sort((a, b) => a[0] - b[0])\n    this.ranges.forEach(o => {\n      res.push(o[0], o[1])\n    })\n    return res\n  }\n\n  public get isEmpty(): boolean {\n    return this.ranges.length === 0\n  }\n\n  public clear(): void {\n    this.ranges = []\n  }\n\n  public getRange(line: number): [number, number] | undefined {\n    for (const [start, end] of this.ranges) {\n      if (line >= start && line <= end) return [start, end]\n    }\n    return undefined\n  }\n\n  /**\n   * Get the span that not covered yet, all 0 based\n   */\n  public toUncoveredSpan(span: [number, number], delta: number, max: number): [number, number] | undefined {\n    let [start, end] = span\n    start = Math.max(0, start - delta)\n    end = Math.min(max, end + delta)\n    let r = this.getRange(start)\n    start = r ? r[1] : start\n    let s = this.getRange(end)\n    // start, end in the same range\n    if (s && r && s[0] === r[0] && s[1] === r[1]) return undefined\n    end = s ? s[0] : end\n    return [start, end]\n  }\n\n  /**\n   * start, end 0 based, both inclusive\n   */\n  public add(start: number, end: number): void {\n    if (start > end) {\n      [start, end] = [end, start]\n    }\n    let { ranges } = this\n    if (ranges.length == 0) {\n      ranges.push([start, end])\n    } else {\n      // 1, 2, 3\n      ranges.sort((a, b) => a[0] - b[0])\n      let s: number\n      let e: number\n      let removedIndexes: number[] = []\n      for (let i = 0; i < ranges.length; i++) {\n        let r = ranges[i]\n        if (r[1] < start - 1) continue\n        if (r[0] > end + 1) break\n        removedIndexes.push(i)\n        if (s == null) s = Math.min(start, r[0])\n        e = Math.max(end, r[1])\n      }\n      let newRanges = removedIndexes.length ? ranges.filter((_, i) => !removedIndexes.includes(i)) : ranges\n      this.ranges = newRanges\n      if (s != null && e != null) {\n        this.ranges.push([s, e])\n      } else {\n        this.ranges.push([start, end])\n      }\n    }\n  }\n\n  public has(start: number, end: number): boolean {\n    let idx = this.ranges.findIndex(o => o[0] <= start && o[1] >= end)\n    return idx !== -1\n  }\n\n  public static mergeSpans(ranges: [number, number][]): [number, number][] {\n    let res: [number, number][] = []\n    for (let r of ranges) {\n      let idx = res.findIndex(o => !(r[1] < o[0] || r[0] > o[1]))\n      if (idx == -1) {\n        res.push(r)\n      } else {\n        let o = res[idx]\n        res[idx] = [Math.min(r[0], o[0]), Math.max(r[1], o[1])]\n      }\n    }\n    return res\n  }\n}\n"
  },
  {
    "path": "src/model/relativePattern.ts",
    "content": "'use strict'\nimport { URI } from 'vscode-uri'\nimport { illegalArgument } from '../util/errors'\nimport { WorkspaceFolder } from 'vscode-languageserver-types'\n\nexport default class RelativePattern {\n  public pattern: string\n  public baseUri: URI\n\n  constructor(base: WorkspaceFolder | URI | string, pattern: string) {\n    if (typeof base !== 'string') {\n      if (!base || !URI.isUri(base) && typeof base.uri !== 'string') {\n        throw illegalArgument('base')\n      }\n    }\n    if (typeof pattern !== 'string') {\n      throw illegalArgument('pattern')\n    }\n    if (typeof base === 'string') {\n      this.baseUri = URI.file(base)\n    } else if (URI.isUri(base)) {\n      this.baseUri = base\n    } else {\n      this.baseUri = URI.parse(base.uri)\n    }\n    this.pattern = pattern\n  }\n\n  public toJSON() {\n    return {\n      pattern: this.pattern,\n      baseUri: this.baseUri.toJSON()\n    }\n  }\n}\n"
  },
  {
    "path": "src/model/resolver.ts",
    "content": "'use strict'\nimport { fs, path } from '../util/node'\nimport { statAsync } from '../util/fs'\nimport { executable, runCommand } from '../util/processes'\nimport stripAnsi from 'strip-ansi'\n\nexport default class Resolver {\n  private _npmFolder: string\n  private _yarnFolder: string\n\n  public get nodeFolder(): Promise<string> {\n    if (!executable('npm')) return Promise.resolve('')\n    if (this._npmFolder) return Promise.resolve(this._npmFolder)\n    return runCommand('npm --loglevel silent root -g', {}, 3000).then(root => {\n      this._npmFolder = stripAnsi(root).trim()\n      return this._npmFolder\n    })\n  }\n\n  public get yarnFolder(): Promise<string> {\n    if (!executable('yarnpkg')) return Promise.resolve('')\n    if (this._yarnFolder) return Promise.resolve(this._yarnFolder)\n    return runCommand('yarnpkg global dir', {}, 3000).then(root => {\n      let folder = path.join(stripAnsi(root).trim(), 'node_modules')\n      let exists = fs.existsSync(folder)\n      if (exists) this._yarnFolder = folder\n      return exists ? folder : ''\n    })\n  }\n\n  public async resolveModule(mod: string): Promise<string> {\n    let nodeFolder = await this.nodeFolder\n    let yarnFolder = await this.yarnFolder\n    if (yarnFolder) {\n      let s = await statAsync(path.join(yarnFolder, mod, 'package.json'))\n      if (s && s.isFile()) return path.join(yarnFolder, mod)\n    }\n    if (nodeFolder) {\n      let s = await statAsync(path.join(nodeFolder, mod, 'package.json'))\n      if (s && s.isFile()) return path.join(nodeFolder, mod)\n    }\n    return null\n  }\n}\n"
  },
  {
    "path": "src/model/semanticTokensBuilder.ts",
    "content": "'use strict'\nimport { Range, SemanticTokens, SemanticTokensLegend } from \"vscode-languageserver-types\"\n\nfunction isStringArray(value: any): value is string[] {\n  return Array.isArray(value) && (value as any[]).every(elem => typeof elem === 'string')\n}\n\nfunction isStrArrayOrUndefined(arg: any): arg is string[] | undefined {\n  return ((typeof arg === 'undefined') || isStringArray(arg))\n}\n\n/**\n * A semantic tokens builder can help with creating a `SemanticTokens` instance\n * which contains delta encoded semantic tokens.\n */\nexport class SemanticTokensBuilder {\n  private _prevLine: number\n  private _prevChar: number\n  private _dataIsSortedAndDeltaEncoded: boolean\n  private _data: number[]\n  private _dataLen: number\n  private _tokenTypeStrToInt: Map<string, number>\n  private _tokenModifierStrToInt: Map<string, number>\n  private _hasLegend: boolean\n\n  constructor(legend?: SemanticTokensLegend) {\n    this._prevLine = 0\n    this._prevChar = 0\n    this._dataIsSortedAndDeltaEncoded = true\n    this._data = []\n    this._dataLen = 0\n    this._tokenTypeStrToInt = new Map<string, number>()\n    this._tokenModifierStrToInt = new Map<string, number>()\n    this._hasLegend = false\n    if (legend) {\n      this._hasLegend = true\n      for (let i = 0, len = legend.tokenTypes.length; i < len; i++) {\n        this._tokenTypeStrToInt.set(legend.tokenTypes[i], i)\n      }\n      for (let i = 0, len = legend.tokenModifiers.length; i < len; i++) {\n        this._tokenModifierStrToInt.set(legend.tokenModifiers[i], i)\n      }\n    }\n  }\n\n  /**\n   * Add another token.\n   * @param line The token start line number (absolute value).\n   * @param char The token start character (absolute value).\n   * @param length The token length in characters.\n   * @param tokenType The encoded token type.\n   * @param tokenModifiers The encoded token modifiers.\n   */\n  public push(line: number, char: number, length: number, tokenType: number, tokenModifiers?: number): void\n  /**\n   * Add another token. Use only when providing a legend.\n   * @param range The range of the token. Must be single-line.\n   * @param tokenType The token type.\n   * @param tokenModifiers The token modifiers.\n   */\n  public push(range: Range, tokenType: string, tokenModifiers?: string[]): void\n  public push(arg0: any, arg1: any, arg2: any, arg3?: any, arg4?: any): void {\n    if (typeof arg0 === 'number' && typeof arg1 === 'number' && typeof arg2 === 'number' && typeof arg3 === 'number' && (typeof arg4 === 'number' || typeof arg4 === 'undefined')) {\n      if (typeof arg4 === 'undefined') {\n        arg4 = 0\n      }\n      // 1st overload\n      return this._pushEncoded(arg0, arg1, arg2, arg3, arg4)\n    }\n    if (Range.is(arg0) && typeof arg1 === 'string' && isStrArrayOrUndefined(arg2)) {\n      // 2nd overload\n      return this._push(arg0, arg1, arg2)\n    }\n    throw new Error('Illegal argument')\n  }\n\n  private _push(range: Range, tokenType: string, tokenModifiers?: string[]): void {\n    if (!this._hasLegend) {\n      throw new Error('Legend must be provided in constructor')\n    }\n    if (range.start.line !== range.end.line) {\n      throw new Error('`range` cannot span multiple lines')\n    }\n    if (!this._tokenTypeStrToInt.has(tokenType)) {\n      throw new Error('`tokenType` is not in the provided legend')\n    }\n    const line = range.start.line\n    const char = range.start.character\n    const length = range.end.character - range.start.character\n    const nTokenType = this._tokenTypeStrToInt.get(tokenType)!\n    let nTokenModifiers = 0\n    if (tokenModifiers) {\n      for (const tokenModifier of tokenModifiers) {\n        if (!this._tokenModifierStrToInt.has(tokenModifier)) {\n          throw new Error('`tokenModifier` is not in the provided legend')\n        }\n        const nTokenModifier = this._tokenModifierStrToInt.get(tokenModifier)!\n        nTokenModifiers |= (1 << nTokenModifier) >>> 0\n      }\n    }\n    this._pushEncoded(line, char, length, nTokenType, nTokenModifiers)\n  }\n\n  private _pushEncoded(line: number, char: number, length: number, tokenType: number, tokenModifiers: number): void {\n    if (this._dataIsSortedAndDeltaEncoded && (line < this._prevLine || (line === this._prevLine && char < this._prevChar))) {\n      // push calls were ordered and are no longer ordered\n      this._dataIsSortedAndDeltaEncoded = false\n\n      // Remove delta encoding from data\n      const tokenCount = (this._data.length / 5) | 0\n      let prevLine = 0\n      let prevChar = 0\n      for (let i = 0; i < tokenCount; i++) {\n        let line = this._data[5 * i]\n        let char = this._data[5 * i + 1]\n\n        if (line === 0) {\n          // on the same line as previous token\n          line = prevLine\n          char += prevChar\n        } else {\n          // on a different line than previous token\n          line += prevLine\n        }\n\n        this._data[5 * i] = line\n        this._data[5 * i + 1] = char\n\n        prevLine = line\n        prevChar = char\n      }\n    }\n\n    let pushLine = line\n    let pushChar = char\n    if (this._dataIsSortedAndDeltaEncoded && this._dataLen > 0) {\n      pushLine -= this._prevLine\n      if (pushLine === 0) {\n        pushChar -= this._prevChar\n      }\n    }\n\n    this._data[this._dataLen++] = pushLine\n    this._data[this._dataLen++] = pushChar\n    this._data[this._dataLen++] = length\n    this._data[this._dataLen++] = tokenType\n    this._data[this._dataLen++] = tokenModifiers\n\n    this._prevLine = line\n    this._prevChar = char\n  }\n\n  private static _sortAndDeltaEncode(data: number[]): number[] {\n    let pos: number[] = []\n    const tokenCount = (data.length / 5) | 0\n    for (let i = 0; i < tokenCount; i++) {\n      pos[i] = i\n    }\n    pos.sort((a, b) => {\n      const aLine = data[5 * a]\n      const bLine = data[5 * b]\n      if (aLine === bLine) {\n        const aChar = data[5 * a + 1]\n        const bChar = data[5 * b + 1]\n        return aChar - bChar\n      }\n      return aLine - bLine\n    })\n    const result = new Array<number>(data.length)\n    let prevLine = 0\n    let prevChar = 0\n    for (let i = 0; i < tokenCount; i++) {\n      const srcOffset = 5 * pos[i]\n      const line = data[srcOffset + 0]\n      const char = data[srcOffset + 1]\n      const length = data[srcOffset + 2]\n      const tokenType = data[srcOffset + 3]\n      const tokenModifiers = data[srcOffset + 4]\n\n      const pushLine = line - prevLine\n      const pushChar = (pushLine === 0 ? char - prevChar : char)\n\n      const dstOffset = 5 * i\n      result[dstOffset + 0] = pushLine\n      result[dstOffset + 1] = pushChar\n      result[dstOffset + 2] = length\n      result[dstOffset + 3] = tokenType\n      result[dstOffset + 4] = tokenModifiers\n\n      prevLine = line\n      prevChar = char\n    }\n\n    return result\n  }\n\n  /**\n   * Finish and create a `SemanticTokens` instance.\n   */\n  public build(resultId?: string): SemanticTokens {\n    if (!this._dataIsSortedAndDeltaEncoded) {\n      return { data: SemanticTokensBuilder._sortAndDeltaEncode(this._data), resultId }\n    }\n    return { data: this._data, resultId }\n  }\n}\n"
  },
  {
    "path": "src/model/status.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { v1 as uuidv1 } from 'uuid'\nimport type { Disposable } from '../util/protocol'\n\nexport const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']\n\nexport interface StatusBarItem {\n  /**\n   * The priority of this item. Higher value means the item should\n   * be shown more to the left.\n   */\n  readonly priority: number\n  isProgress: boolean\n  text: string\n  show(): void\n  hide(): void\n  dispose(): void\n}\n\nexport default class StatusLine implements Disposable {\n  private items: Map<string, StatusBarItem> = new Map()\n  private shownIds: Set<string> = new Set()\n  private _text = ''\n  private interval: NodeJS.Timeout\n  public nvim: Neovim\n  constructor() {\n    this.interval = setInterval(() => {\n      this.setStatusText()\n    }, 100).unref()\n  }\n\n  public dispose(): void {\n    this.items.clear()\n    this.shownIds.clear()\n    clearInterval(this.interval)\n  }\n\n  public reset(): void {\n    this.items.clear()\n    this.shownIds.clear()\n  }\n\n  public createStatusBarItem(priority: number, isProgress = false): StatusBarItem {\n    let uid = uuidv1()\n\n    let item: StatusBarItem = {\n      text: '',\n      priority,\n      isProgress,\n      show: () => {\n        this.shownIds.add(uid)\n        this.setStatusText()\n      },\n      hide: () => {\n        this.shownIds.delete(uid)\n        this.setStatusText()\n      },\n      dispose: () => {\n        this.shownIds.delete(uid)\n        this.items.delete(uid)\n        this.setStatusText()\n      }\n    }\n    this.items.set(uid, item)\n    return item\n  }\n\n  private getText(): string {\n    if (this.shownIds.size == 0) return ''\n    let d = new Date()\n    let idx = Math.floor(d.getMilliseconds() / 100)\n    let text = ''\n    let items: StatusBarItem[] = []\n    for (let [id, item] of this.items) {\n      if (this.shownIds.has(id)) {\n        items.push(item)\n      }\n    }\n    items.sort((a, b) => a.priority - b.priority)\n    for (let item of items) {\n      if (!item.isProgress) {\n        text = `${text} ${item.text}`\n      } else {\n        text = `${text} ${frames[idx]} ${item.text}`\n      }\n    }\n    return text\n  }\n\n  private setStatusText(): void {\n    let text = this.getText()\n    let { nvim } = this\n    if (text != this._text && nvim) {\n      this._text = text\n      nvim.pauseNotification()\n      this.nvim.setVar('coc_status', text, true)\n      this.nvim.callTimer('coc#util#do_autocmd', ['CocStatusChange'], true)\n      nvim.resumeNotification(false, true)\n    }\n  }\n}\n"
  },
  {
    "path": "src/model/strwidth.ts",
    "content": "import { pluginRoot } from '../util/constants'\nimport { fs, path, promisify } from '../util/node'\n\nexport interface StrWidthWasi {\n  strWidth: (textPtr: number) => number\n  setAmbw: (ambiguousAsDouble: number) => void\n  malloc: (size: number) => number\n  free: (ptr: number) => void\n  memory: {\n    buffer: ArrayBuffer\n  }\n}\n\nconst wasmPath = path.join(pluginRoot, 'bin/strwidth.wasm')\n\nexport async function initStrWidthWasm(): Promise<StrWidthWasi> {\n  const buffer = await promisify(fs.readFile)(wasmPath)\n  const res = await global.WebAssembly.instantiate(buffer, { env: {} })\n  return res.instance.exports as StrWidthWasi\n}\nlet instance: StrWidth\n\nexport class StrWidth {\n  private contentPtr: number | undefined\n  private bytes: Uint8Array\n  private cache: Map<string, number> = new Map()\n  constructor(private exports: StrWidthWasi) {\n    this.bytes = new Uint8Array(exports.memory.buffer)\n    this.contentPtr = exports.malloc(4096)\n  }\n\n  public setAmbw(ambiguousAsDouble: boolean): void {\n    this.exports.setAmbw(ambiguousAsDouble ? 1 : 0)\n    this.cache.clear()\n  }\n\n  public getWidth(content: string, cache = false): number {\n    let l = content.length\n    if (l === 0) return 0\n    if (l > 4095) {\n      content = content.slice(0, 4095)\n    }\n    if (cache && this.cache.has(content)) {\n      return this.cache.get(content)\n    }\n    let { contentPtr } = this\n    let buf = Buffer.from(content, 'utf8')\n    let len = buf.length\n    this.bytes.set(buf, contentPtr)\n    this.bytes[contentPtr + len] = 0\n    let res = this.exports.strWidth(contentPtr)\n    if (cache) this.cache.set(content, res)\n    return res\n  }\n\n  public static async create(): Promise<StrWidth> {\n    if (instance) return instance\n    let api = await initStrWidthWasm()\n    instance = new StrWidth(api)\n    return instance\n  }\n}\n"
  },
  {
    "path": "src/model/tabs.ts",
    "content": "import { TextDocument } from 'vscode-languageserver-textdocument'\nimport { URI } from 'vscode-uri'\nimport type Editors from '../core/editors'\nimport { Emitter, Event } from '../util/protocol'\n\nexport interface TabsModel {\n  onClose: Event<Set<URI>>\n  onOpen: Event<Set<URI>>\n  isActive(document: TextDocument | URI): boolean\n  isVisible(document: TextDocument | URI): boolean\n  getTabResources(): Set<URI>\n}\n\n/**\n * Track visible documents\n */\nexport default class Tabs implements TabsModel {\n  private open: Set<string> = new Set()\n  private readonly _onOpen: Emitter<Set<URI>>\n  private readonly _onClose: Emitter<Set<URI>>\n\n  constructor(\n    private editors: Editors\n  ) {\n    this._onOpen = new Emitter()\n    this._onClose = new Emitter()\n    this.editors.onDidChangeVisibleTextEditors(editors => {\n      let uris = Array.from(this.open)\n      let seen: Set<string> = new Set()\n      let opened: Set<URI> = new Set()\n      let closed: Set<URI> = new Set()\n      for (let editor of editors) {\n        if (!seen.has(editor.uri) && !uris.includes(editor.uri)) {\n          this.open.add(editor.uri)\n          opened.add(URI.parse(editor.uri))\n        }\n        seen.add(editor.uri)\n      }\n      for (let uri of uris) {\n        if (!seen.has(uri)) {\n          this.open.delete(uri)\n          closed.add(URI.parse(uri))\n        }\n      }\n      if (opened.size > 0) {\n        this._onOpen.fire(opened)\n      }\n      if (closed.size > 0) {\n        this._onClose.fire(closed)\n      }\n    })\n  }\n\n  public attach(): void {\n    for (let editor of this.editors.visibleTextEditors) {\n      this.open.add(editor.uri)\n    }\n  }\n\n  public get onClose(): Event<Set<URI>> {\n    return this._onClose.event\n  }\n\n  public get onOpen(): Event<Set<URI>> {\n    return this._onOpen.event\n  }\n\n  public isActive(document: TextDocument | URI): boolean {\n    const uri = document instanceof URI ? document : document.uri\n    return this.editors.activeTextEditor?.document.uri === uri.toString()\n  }\n\n  public isVisible(document: TextDocument | URI): boolean {\n    const uri = document instanceof URI ? document : document.uri\n    return this.open.has(uri.toString())\n  }\n\n  public getTabResources(): Set<URI> {\n    const result: Set<URI> = new Set()\n    let seen: Set<string> = new Set()\n    for (let editor of this.editors.visibleTextEditors) {\n      if (!seen.has(editor.uri)) {\n        result.add(URI.parse(editor.uri))\n        seen.add(editor.uri)\n      }\n    }\n    return result\n  }\n}\n"
  },
  {
    "path": "src/model/task.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport events from '../events'\nimport { disposeAll } from '../util'\nimport { Disposable, Emitter, Event } from '../util/protocol'\n\nexport interface TaskOptions {\n  cmd: string\n  args?: string[]\n  cwd?: string\n  pty?: boolean\n  env?: { [key: string]: string }\n  detach?: boolean\n}\n\n/**\n * Controls long running task started by vim.\n * Useful to keep the task running after CocRestart.\n * @public\n */\nexport default class Task implements Disposable {\n  private disposables: Disposable[] = []\n  private readonly _onExit = new Emitter<number>()\n  private readonly _onStderr = new Emitter<string[]>()\n  private readonly _onStdout = new Emitter<string[]>()\n  public readonly onExit: Event<number> = this._onExit.event\n  public readonly onStdout: Event<string[]> = this._onStdout.event\n  public readonly onStderr: Event<string[]> = this._onStderr.event\n\n  /**\n   * @param {Neovim} nvim\n   * @param {string} id unique id\n   */\n  constructor(private nvim: Neovim, private id: string) {\n    events.on('TaskExit', (id, code) => {\n      if (id == this.id) {\n        this._onExit.fire(code)\n      }\n    }, null, this.disposables)\n    events.on('TaskStderr', (id, lines) => {\n      if (id == this.id) {\n        this._onStderr.fire(lines)\n      }\n    }, null, this.disposables)\n    events.on('TaskStdout', (id, lines) => {\n      if (id == this.id) {\n        this._onStdout.fire(lines)\n      }\n    }, null, this.disposables)\n  }\n\n  /**\n   * Start task, task will be restarted when already running.\n   * @param {TaskOptions} opts\n   * @returns {Promise<boolean>}\n   */\n  public async start(opts: TaskOptions): Promise<boolean> {\n    let { nvim } = this\n    return await nvim.call('coc#task#start', [this.id, opts]) as boolean\n  }\n\n  /**\n   * Stop task by SIGTERM or SIGKILL\n   */\n  public async stop(): Promise<void> {\n    let { nvim } = this\n    await nvim.call('coc#task#stop', [this.id])\n  }\n\n  /**\n   * Check if the task is running.\n   */\n  public get running(): Promise<boolean> {\n    let { nvim } = this\n    return nvim.call('coc#task#running', [this.id]) as Promise<boolean>\n  }\n\n  /**\n   * Stop task and dispose all events.\n   */\n  public dispose(): void {\n    let { nvim } = this\n    nvim.call('coc#task#stop', [this.id], true)\n    this._onStdout.dispose()\n    this._onStderr.dispose()\n    this._onExit.dispose()\n    disposeAll(this.disposables)\n  }\n}\n"
  },
  {
    "path": "src/model/terminal.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\n\nexport interface TerminalOptions {\n  /**\n   * A human-readable string which will be used to represent the terminal in the UI.\n   */\n  name?: string\n\n  /**\n   * A path to a custom shell executable to be used in the terminal.\n   */\n  shellPath?: string\n\n  /**\n   * Args for the custom shell executable, this does not work on Windows (see #8429)\n   */\n  shellArgs?: string[]\n\n  /**\n   * A path or URI for the current working directory to be used for the terminal.\n   */\n  cwd?: string\n\n  /**\n   * Object with environment variables that will be added to the VS Code process.\n   */\n  env?: { [key: string]: string | null }\n\n  /**\n   * Whether the terminal process environment should be exactly as provided in\n   * `TerminalOptions.env`. When this is false (default), the environment will be based on the\n   * window's environment and also apply configured platform settings like\n   * `terminal.integrated.windows.env` on top. When this is true, the complete environment\n   * must be provided as nothing will be inherited from the process or any configuration.\n   */\n  strictEnv?: boolean\n}\n\nexport interface TerminalExitStatus {\n  code: number | undefined\n}\n\nexport class TerminalModel {\n  public bufnr: number\n  private pid = 0\n  public exitStatus: TerminalExitStatus | undefined\n\n  constructor(private cmd: string,\n    private args: string[],\n    private nvim: Neovim,\n    private _name?: string,\n    private strictEnv?: boolean\n  ) {\n  }\n\n  public async start(cwd?: string, env?: { [key: string]: string | null }): Promise<void> {\n    let { nvim } = this\n    let cmd = [this.cmd, ...this.args]\n    let [bufnr, pid] = await nvim.call('coc#terminal#start', [cmd, cwd, env || {}, !!this.strictEnv]) as [number, number]\n    this.bufnr = bufnr\n    this.pid = pid\n  }\n\n  public onExit(code: number | undefined): void {\n    this.exitStatus = { code: code === -1 ? undefined : code }\n  }\n\n  public get name(): string {\n    return this._name || this.cmd\n  }\n\n  public get processId(): Promise<number> {\n    return Promise.resolve(this.pid)\n  }\n\n  public sendText(text: string, addNewLine = true): void {\n    if (!this.bufnr) return\n    this.nvim.call('coc#terminal#send', [this.bufnr, text, addNewLine], true)\n  }\n\n  public async show(preserveFocus?: boolean): Promise<boolean> {\n    let { bufnr, nvim } = this\n    if (!bufnr) return false\n    return await nvim.call('coc#terminal#show', [bufnr, { preserveFocus }]) as boolean\n  }\n\n  public async hide(): Promise<void> {\n    let { bufnr, nvim } = this\n    if (!bufnr) return\n    await nvim.eval(`coc#window#close(bufwinid(${bufnr}))`)\n  }\n\n  public dispose(): void {\n    if (!this.exitStatus) {\n      this.exitStatus = { code: undefined }\n    }\n    let { bufnr, nvim } = this\n    if (!bufnr) return\n    this.bufnr = undefined\n    nvim.call('coc#terminal#close', [bufnr], true)\n  }\n}\n"
  },
  {
    "path": "src/model/textdocument.ts",
    "content": "'use strict'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { Position, Range } from 'vscode-languageserver-types'\nimport { getRangeText } from '../util/textedit'\nimport { TextLine } from './textline'\n\nexport function computeLinesOffsets(lines: ReadonlyArray<string>, eol: boolean): number[] {\n  const result: number[] = []\n  let textOffset = 0\n  for (let line of lines) {\n    result.push(textOffset)\n    textOffset += line.length + 1\n  }\n  if (eol) result.push(textOffset)\n  return result\n}\n\n/**\n * Get the first different line\n */\nexport function firstDiffLine(oldLines: ReadonlyArray<string>, newLines: ReadonlyArray<string>): [number, string, string] | undefined {\n  let m = Math.max(oldLines.length, newLines.length)\n  for (let i = 0; i < m; i++) {\n    let oldLine = oldLines[i]\n    let newLine = newLines[i]\n    if (oldLine !== newLine) {\n      return [i + 1, oldLine ?? '', newLine ?? '']\n    }\n  }\n  return undefined\n}\n\n/**\n * Text document that created with readonly lines.\n *\n * Created for save memory since we could reuse readonly lines.\n */\nexport class LinesTextDocument implements TextDocument {\n  private _lineOffsets: number[] | undefined\n  private _content: string\n  constructor(\n    public readonly uri: string,\n    public readonly languageId: string,\n    public readonly version: number,\n    public lines: ReadonlyArray<string>,\n    public readonly bufnr: number,\n    public readonly eol: boolean\n  ) {\n  }\n\n  private get content(): string {\n    if (!this._content) {\n      this._content = this.lines.join('\\n') + (this.eol ? '\\n' : '')\n    }\n    return this._content\n  }\n\n  public get length(): number {\n    if (!this._content) {\n      let n = this.lines.reduce((p, c) => {\n        return p + c.length + 1\n      }, 0)\n      return this.eol ? n : n - 1\n    }\n    return this._content.length\n  }\n\n  public get end(): Position {\n    let len = this.lines.length\n    if (this.eol) return Position.create(len, 0)\n    return Position.create(len - 1, this.lines[len - 1].length)\n  }\n\n  public get lineCount(): number {\n    return this.lines.length + (this.eol ? 1 : 0)\n  }\n\n  public intersectWith(range: Range): Range {\n    let start: Position = Position.create(0, 0)\n    if (start.line < range.start.line) {\n      start = range.start\n    } else if (range.start.line === start.line) {\n      start = Position.create(start.line, Math.max(start.character, range.start.character))\n    }\n\n    let end: Position = this.end\n    if (range.end.line < end.line) {\n      end = range.end\n    } else if (range.end.line === end.line) {\n      end = Position.create(end.line, Math.min(end.character, range.end.character))\n    }\n\n    return Range.create(start, end)\n  }\n\n  public getText(range?: Range): string {\n    if (range) return getRangeText(this.lines, range)\n    return this.content\n  }\n\n  public lineAt(lineOrPos: number | Position): TextLine {\n    const line = Position.is(lineOrPos) ? lineOrPos.line : lineOrPos\n    if (typeof line !== 'number' ||\n      line < 0 ||\n      line >= this.lineCount ||\n      Math.floor(line) !== line) {\n      throw new Error('Illegal value for `line`')\n    }\n\n    return new TextLine(line, this.lines[line] ?? '', line === this.lineCount - 1)\n  }\n\n  public positionAt(offset: number): Position {\n    offset = Math.max(Math.min(offset, this.content.length), 0)\n    let lineOffsets = this.getLineOffsets()\n    let low = 0\n    let high = lineOffsets.length\n    if (high === 0) {\n      return { line: 0, character: offset }\n    }\n    while (low < high) {\n      let mid = Math.floor((low + high) / 2)\n      if (lineOffsets[mid] > offset) {\n        high = mid\n      } else {\n        low = mid + 1\n      }\n    }\n    // low is the least x for which the line offset is larger than the current offset\n    // or array.length if no line offset is larger than the current offset\n    let line = low - 1\n    return { line, character: offset - lineOffsets[line] }\n  }\n\n  public offsetAt(position: Position) {\n    let lineOffsets = this.getLineOffsets()\n    if (position.line >= lineOffsets.length) {\n      return this.content.length\n    } else if (position.line < 0) {\n      return 0\n    }\n    let lineOffset = lineOffsets[position.line]\n    let nextLineOffset = (position.line + 1 < lineOffsets.length) ? lineOffsets[position.line + 1] : this.content.length\n    return Math.max(Math.min(lineOffset + position.character, nextLineOffset), lineOffset)\n  }\n\n  private getLineOffsets(): number[] {\n    if (this._lineOffsets === undefined) {\n      this._lineOffsets = computeLinesOffsets(this.lines, this.eol)\n    }\n    return this._lineOffsets\n  }\n}\n"
  },
  {
    "path": "src/model/textline.ts",
    "content": "'use strict'\nimport { Range } from 'vscode-languageserver-types'\n\n/**\n * Represents a line of text, such as a line of source code.\n *\n * TextLine objects are __immutable__. When a {@link TextDocument document} changes,\n * previously retrieved lines will not represent the latest state.\n */\nexport class TextLine {\n\n  private readonly _line: number\n  private readonly _text: string\n  private readonly _isLastLine: boolean\n\n  constructor(line: number, text: string, isLastLine: boolean) {\n    this._line = line\n    this._text = text\n    this._isLastLine = isLastLine\n  }\n\n  /**\n   * The zero-based line number.\n   */\n  public get lineNumber(): number {\n    return this._line\n  }\n\n  /**\n   * The text of this line without the line separator characters.\n   */\n  public get text(): string {\n    return this._text\n  }\n\n  /**\n   * The range this line covers without the line separator characters.\n   */\n  public get range(): Range {\n    return Range.create(this._line, 0, this._line, this._text.length)\n  }\n\n  /**\n   * The range this line covers with the line separator characters.\n   */\n  public get rangeIncludingLineBreak(): Range {\n    return this._isLastLine ? this.range : Range.create(this._line, 0, this._line + 1, 0)\n  }\n\n  /**\n   * The offset of the first character which is not a whitespace character as defined\n   * by `/\\s/`. **Note** that if a line is all whitespace the length of the line is returned.\n   */\n  public get firstNonWhitespaceCharacterIndex(): number {\n    return /^(\\s*)/.exec(this._text)![1].length\n  }\n\n  /**\n   * Whether this line is whitespace only, shorthand\n   * for {@link TextLine.firstNonWhitespaceCharacterIndex} === {@link TextLine.text TextLine.text.length}.\n   */\n  public get isEmptyOrWhitespace(): boolean {\n    return this.firstNonWhitespaceCharacterIndex === this._text.length\n  }\n}\n"
  },
  {
    "path": "src/plugin.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { CallHierarchyItem, CodeAction, CodeActionKind, InsertTextMode, Range, WorkspaceSymbol } from 'vscode-languageserver-types'\nimport commandManager from './commands'\nimport completion, { Completion } from './completion'\nimport sources from './completion/sources'\nimport type { CompleteFinishKind } from './completion/types'\nimport Cursors from './cursors'\nimport diagnosticManager from './diagnostic/manager'\nimport events from './events'\nimport extensions from './extension'\nimport Handler from './handler'\nimport { AcceptKind, InlineSuggestOption } from './handler/inline'\nimport listManager from './list/manager'\nimport { createLogger } from './logger'\nimport services from './services'\nimport snippetManager from './snippets/manager'\nimport { HoverTarget, UltiSnippetOption } from './types'\nimport { Disposable, disposeAll, getConditionValue } from './util'\nimport window, { Window } from './window'\nimport workspace, { Workspace } from './workspace'\nconst logger = createLogger('plugin')\n\nexport type Callback = (...args: any[]) => unknown\n\nexport default class Plugin {\n  private ready = false\n  private initialized = false\n  public handler: Handler | undefined\n  private cursors: Cursors\n  private actions: Map<string, Callback> = new Map()\n  private disposables: Disposable[] = []\n\n  constructor(public nvim: Neovim) {\n    Object.defineProperty(window, 'workspace', {\n      get: () => workspace\n    })\n    Object.defineProperty(workspace, 'nvim', {\n      get: () => this.nvim\n    })\n    Object.defineProperty(window, 'nvim', {\n      get: () => this.nvim\n    })\n    Object.defineProperty(window, 'cursors', {\n      get: () => this.cursors\n    })\n    Object.defineProperty(commandManager, 'nvim', {\n      get: () => this.nvim\n    })\n    this.cursors = new Cursors(nvim)\n    listManager.init(nvim)\n    this.addAction('checkJsonExtension', () => {\n      if (extensions.has('coc-json')) return\n      void window.showInformationMessage(`Run :CocInstall coc-json for json intellisense`)\n    })\n    this.addAction('rootPatterns', (bufnr: number) => this.handler.workspace.getRootPatterns(bufnr))\n    this.addAction('ensureDocument', (bufnr?: number) => this.handler.workspace.ensureDocument(bufnr))\n    this.addAction('addWorkspaceFolder', (folder: string) => this.handler.workspace.addWorkspaceFolder(folder))\n    this.addAction('removeWorkspaceFolder', (folder: string) => this.handler.workspace.removeWorkspaceFolder(folder))\n    this.addAction('getConfig', (key: string) => this.handler.workspace.getConfiguration(key))\n    this.addAction('doAutocmd', (id: number, ...args: []) => this.handler.workspace.doAutocmd(id, args))\n    this.addAction('openLog', () => this.handler.workspace.openLog())\n    this.addAction('attach', () => workspace.attach())\n    this.addAction('detach', () => workspace.detach())\n    this.addAction('doKeymap', (key: string, defaultReturn: string) => this.handler.workspace.doKeymap(key, defaultReturn))\n    this.addAction('registerExtensions', (...folders: string[]) => extensions.manager.loadExtension(folders), 'registExtensions')\n    this.addAction('snippetCheck', (checkExpand: boolean, checkJump: boolean) => this.handler.workspace.snippetCheck(checkExpand, checkJump))\n    this.addAction('snippetInsert', (range: Range, newText: string, mode?: InsertTextMode, ultisnip?: UltiSnippetOption) => snippetManager.insertSnippet(newText, true, range, mode, ultisnip))\n    this.addAction('snippetNext', () => snippetManager.nextPlaceholder())\n    this.addAction('snippetPrev', () => snippetManager.previousPlaceholder())\n    this.addAction('snippetCancel', () => snippetManager.cancel())\n    this.addAction('openLocalConfig', () => this.handler.workspace.openLocalConfig())\n    this.addAction('bufferCheck', () => this.handler.workspace.bufferCheck())\n    this.addAction('showInfo', () => this.handler.workspace.showInfo())\n    this.addAction('hasProvider', (id: string, bufnr?: number) => this.handler.hasProvider(id, bufnr))\n    this.addAction('cursorsSelect', (bufnr: number, kind: string, mode: string) => this.cursors.select(bufnr, kind, mode))\n    this.addAction('commandList', () => this.handler.commands.getCommandList())\n    this.addAction('selectSymbolRange', (inner: boolean, visualmode: string, supportedSymbols: string[]) => this.handler.symbols.selectSymbolRange(inner, visualmode, supportedSymbols))\n    this.addAction('openList', (...args: string[]) => listManager.start(args))\n    this.addAction('listNames', () => listManager.names)\n    this.addAction('listDescriptions', () => listManager.descriptions)\n    this.addAction('listLoadItems', (name: string) => listManager.loadItems(name))\n    this.addAction('listResume', (name?: string) => listManager.resume(name))\n    this.addAction('listCancel', () => listManager.cancel(true))\n    this.addAction('listPrev', (name?: string) => listManager.previous(name))\n    this.addAction('listNext', (name?: string) => listManager.next(name))\n    this.addAction('listFirst', (name?: string) => listManager.first(name))\n    this.addAction('listLast', (name?: string) => listManager.last(name))\n    this.addAction('sendRequest', (id: string, method: string, params?: any) => services.sendRequest(id, method, params))\n    this.addAction('sendNotification', (id: string, method: string, params?: any) => services.sendNotification(id, method, params))\n    this.addAction('registerNotification', (id: string, method: string) => services.registerNotification(id, method), 'registNotification')\n    this.addAction('updateConfig', (section: string, val: any) => workspace.configurations.updateMemoryConfig({ [section]: val }))\n    this.addAction('links', () => this.handler.links.getLinks())\n    this.addAction('openLink', () => this.handler.links.openCurrentLink())\n    this.addAction('pickColor', () => this.handler.colors.pickColor())\n    this.addAction('colorPresentation', () => this.handler.colors.pickPresentation())\n    this.addAction('highlight', () => this.handler.documentHighlighter.highlight())\n    this.addAction('fold', (kind?: string) => this.handler.fold.fold(kind))\n    this.addAction('startCompletion', (option: { source?: string, col?: number }) => completion.startCompletion(option))\n    this.addAction('stopCompletion', (kind: CompleteFinishKind) => completion.stop(kind))\n    this.addAction('sourceStat', () => sources.sourceStats())\n    this.addAction('refreshSource', (name: string) => sources.refresh(name))\n    this.addAction('toggleSource', (name: string) => sources.toggleSource(name))\n    this.addAction('fillDiagnostics', (bufnr: number) => diagnosticManager.setLocationlist(bufnr))\n    this.addAction('diagnosticRefresh', (bufnr?: number) => diagnosticManager.refresh(bufnr))\n    this.addAction('diagnosticInfo', (target?: string) => diagnosticManager.echoCurrentMessage(target))\n    this.addAction('diagnosticToggle', (enable?: number) => diagnosticManager.toggleDiagnostic(enable))\n    this.addAction('diagnosticToggleBuffer', (bufnr?: number, enable?: number) => diagnosticManager.toggleDiagnosticBuffer(bufnr, enable))\n    this.addAction('diagnosticNext', (severity?: string) => diagnosticManager.jumpNext(severity))\n    this.addAction('diagnosticPrevious', (severity?: string) => diagnosticManager.jumpPrevious(severity))\n    this.addAction('diagnosticPreview', () => diagnosticManager.preview())\n    this.addAction('diagnosticList', () => diagnosticManager.getDiagnosticList())\n    this.addAction('diagnosticRelatedInformation', () => diagnosticManager.relatedInformation())\n    this.addAction('findLocations', (id: string, method: string, params: any, openCommand: string) => this.handler.locations.findLocations(id, method, params, openCommand))\n    this.addAction('getTagList', () => this.handler.locations.getTagList())\n    this.addAction('definitions', () => this.handler.locations.definitions())\n    this.addAction('declarations', () => this.handler.locations.declarations())\n    this.addAction('implementations', () => this.handler.locations.implementations())\n    this.addAction('typeDefinitions', () => this.handler.locations.typeDefinitions())\n    this.addAction('references', (excludeDeclaration?: boolean) => this.handler.locations.references(excludeDeclaration))\n    this.addAction('jumpUsed', (openCommand?: string) => this.handler.locations.gotoReferences(openCommand, false))\n    this.addAction('jumpDefinition', (openCommand?: string | false) => this.handler.locations.gotoDefinition(openCommand))\n    this.addAction('jumpReferences', (openCommand?: string | false) => this.handler.locations.gotoReferences(openCommand))\n    this.addAction('jumpTypeDefinition', (openCommand?: string | false) => this.handler.locations.gotoTypeDefinition(openCommand))\n    this.addAction('jumpDeclaration', (openCommand?: string | false) => this.handler.locations.gotoDeclaration(openCommand))\n    this.addAction('jumpImplementation', (openCommand?: string | false) => this.handler.locations.gotoImplementation(openCommand))\n    this.addAction('doHover', (hoverTarget: HoverTarget) => this.handler.hover.onHover(hoverTarget))\n    this.addAction('definitionHover', (hoverTarget: HoverTarget) => this.handler.hover.definitionHover(hoverTarget))\n    this.addAction('getHover', (loc?: { bufnr?: number, line: number, col: number }) => this.handler.hover.getHover(loc))\n    this.addAction('showSignatureHelp', () => this.handler.signature.triggerSignatureHelp())\n    this.addAction('documentSymbols', (bufnr?: number) => this.handler.symbols.getDocumentSymbols(bufnr))\n    this.addAction('symbolRanges', () => this.handler.documentHighlighter.getSymbolsRanges())\n    this.addAction('selectionRanges', () => this.handler.selectionRange.getSelectionRanges())\n    this.addAction('rangeSelect', (visualmode: string, forward: boolean) => this.handler.selectionRange.selectRange(visualmode, forward))\n    this.addAction('rename', (newName?: string) => this.handler.rename.rename(newName))\n    this.addAction('getWorkspaceSymbols', (input: string) => this.handler.symbols.getWorkspaceSymbols(input))\n    this.addAction('resolveWorkspaceSymbol', (symbolInfo: WorkspaceSymbol) => this.handler.symbols.resolveWorkspaceSymbol(symbolInfo))\n    this.addAction('formatSelected', (mode: string) => this.handler.format.formatCurrentRange(mode))\n    this.addAction('format', () => this.handler.format.formatCurrentBuffer())\n    this.addAction('commands', () => commandManager.commandList)\n    this.addAction('services', () => services.getServiceStats())\n    this.addAction('toggleService', (name: string) => services.toggle(name))\n    this.addAction('codeAction', (mode: string | null, only: CodeActionKind[] | string, noExclude: boolean) => this.handler.codeActions.doCodeAction(mode, only, noExclude))\n    this.addAction('organizeImport', () => this.handler.codeActions.organizeImport())\n    this.addAction('fixAll', () => this.handler.codeActions.doCodeAction(null, [CodeActionKind.SourceFixAll]))\n    this.addAction('doCodeAction', (codeAction: CodeAction) => this.handler.codeActions.applyCodeAction(codeAction))\n    this.addAction('codeActions', (mode?: string, only?: CodeActionKind[]) => this.handler.codeActions.getCurrentCodeActions(mode, only))\n    this.addAction('quickfixes', (mode?: string) => this.handler.codeActions.getCurrentCodeActions(mode, [CodeActionKind.QuickFix]))\n    this.addAction('codeLensAction', () => this.handler.codeLens.doAction())\n    this.addAction('doQuickfix', () => this.handler.codeActions.doQuickfix())\n    this.addAction('search', (...args: string[]) => this.handler.refactor.search(args))\n    this.addAction('saveRefactor', (bufnr: number) => this.handler.refactor.save(bufnr))\n    this.addAction('refactor', () => this.handler.refactor.doRefactor())\n    this.addAction('runCommand', (...args: any[]) => this.handler.commands.runCommand(...args))\n    this.addAction('repeatCommand', () => this.handler.commands.repeat())\n    this.addAction('installExtensions', (...list: string[]) => extensions.installExtensions(list))\n    this.addAction('updateExtensions', (silent: boolean) => extensions.updateExtensions(silent, extensions.getUpdateSettings().updateUIInTab))\n    this.addAction('extensionStats', () => extensions.getExtensionStates())\n    this.addAction('loadedExtensions', () => extensions.manager.loadedExtensions)\n    this.addAction('watchExtension', (id: string) => extensions.manager.watchExtension(id))\n    this.addAction('activeExtension', (name: string) => extensions.manager.activate(name))\n    this.addAction('deactivateExtension', (name: string) => extensions.manager.deactivate(name))\n    this.addAction('reloadExtension', (name: string) => extensions.manager.reloadExtension(name))\n    this.addAction('toggleExtension', (name: string) => extensions.manager.toggleExtension(name))\n    this.addAction('uninstallExtension', (...args: string[]) => extensions.manager.uninstallExtensions(args))\n    this.addAction('getCurrentFunctionSymbol', () => this.handler.symbols.getCurrentFunctionSymbol())\n    this.addAction('showOutline', (keep?: number) => this.handler.symbols.showOutline(keep))\n    this.addAction('hideOutline', () => this.handler.symbols.hideOutline())\n    this.addAction('getWordEdit', () => this.handler.rename.getWordEdit())\n    this.addAction('addCommand', (cmd: { id: string, cmd: string, title?: string }) => this.handler.commands.addVimCommand(cmd))\n    this.addAction('addRanges', (ranges: Range[]) => this.cursors.addRanges(ranges))\n    this.addAction('currentWorkspacePath', () => workspace.rootPath)\n    this.addAction('selectCurrentPlaceholder', (triggerAutocmd: boolean) => snippetManager.selectCurrentPlaceholder(!!triggerAutocmd))\n    this.addAction('codeActionRange', (start: number, end: number, only?: string) => this.handler.codeActions.codeActionRange(start, end, only))\n    this.addAction('incomingCalls', (item?: CallHierarchyItem) => this.handler.callHierarchy.getIncoming(item))\n    this.addAction('outgoingCalls', (item?: CallHierarchyItem) => this.handler.callHierarchy.getOutgoing(item))\n    this.addAction('showIncomingCalls', () => this.handler.callHierarchy.showCallHierarchyTree('incoming'))\n    this.addAction('showOutgoingCalls', () => this.handler.callHierarchy.showCallHierarchyTree('outgoing'))\n    this.addAction('showSuperTypes', () => this.handler.typeHierarchy.showTypeHierarchyTree('supertypes'))\n    this.addAction('showSubTypes', () => this.handler.typeHierarchy.showTypeHierarchyTree('subtypes'))\n    this.addAction('inspectSemanticToken', () => this.handler.semanticHighlighter.inspectSemanticToken())\n    this.addAction('semanticHighlight', () => this.handler.semanticHighlighter.highlightCurrent())\n    this.addAction('showSemanticHighlightInfo', () => this.handler.semanticHighlighter.showHighlightInfo())\n    this.addAction('inlineTrigger', (bufnr: number, option?: InlineSuggestOption) => this.handler.inlineCompletion.trigger(bufnr, option))\n    this.addAction('inlineCancel', () => this.handler.inlineCompletion.cancel())\n    this.addAction('inlineAccept', (bufnr: number, kind?: AcceptKind) => this.handler.inlineCompletion.accept(bufnr, kind))\n    this.addAction('inlineNext', (bufnr: number) => this.handler.inlineCompletion.next(bufnr))\n    this.addAction('inlinePrev', (bufnr: number) => this.handler.inlineCompletion.prev(bufnr))\n    this.addAction('notificationHistory', () => window.notifications.history)\n  }\n\n  public get workspace(): Workspace {\n    return workspace\n  }\n\n  public get window(): Window {\n    return window\n  }\n\n  public get completion(): Completion {\n    return completion\n  }\n\n  public addAction(key: string, fn: Callback, alias?: string): void {\n    if (this.actions.has(key)) {\n      throw new Error(`Action ${key} already exists`)\n    }\n    this.actions.set(key, fn)\n    if (alias) this.actions.set(alias, fn)\n  }\n\n  public async init(rtp: string): Promise<void> {\n    if (this.initialized) return\n    this.initialized = true\n    let { nvim } = this\n    await extensions.init(rtp)\n    await workspace.init(window)\n    nvim.setVar('coc_workspace_initialized', true, true)\n    snippetManager.init()\n    services.init()\n    sources.init()\n    completion.init()\n    diagnosticManager.init()\n    this.handler = new Handler(nvim)\n    this.disposables.push(this.handler)\n    listManager.registerLists()\n    await extensions.activateExtensions()\n    workspace.configurations.flushConfigurations()\n    nvim.pauseNotification()\n    nvim.setVar('coc_service_initialized', 1, true)\n    nvim.call('coc#util#do_autocmd', ['CocNvimInit'], true)\n    nvim.resumeNotification(false, true)\n    logger.info(`coc.nvim initialized with node: ${process.version} after`, Date.now() - getConditionValue(global.__starttime, Date.now()))\n    this.ready = true\n    await events.fire('ready', [])\n  }\n\n  public get isReady(): boolean {\n    return this.ready\n  }\n\n  public hasAction(method: string): boolean {\n    return this.actions.has(method)\n  }\n\n  public async cocAction(method: string, ...args: any[]): Promise<any> {\n    let fn = this.actions.get(method)\n    if (!fn) throw new Error(`Action \"${method}\" does not exist`)\n    return await Promise.resolve(fn.apply(null, args))\n  }\n\n  public getHandler(): Handler {\n    return this.handler\n  }\n\n  public dispose(): void {\n    disposeAll(this.disposables)\n    extensions.dispose()\n    listManager.dispose()\n    workspace.dispose()\n    window.dispose()\n    sources.dispose()\n    services.dispose()\n    snippetManager.dispose()\n    commandManager.dispose()\n    completion.dispose()\n    diagnosticManager.dispose()\n  }\n}\n"
  },
  {
    "path": "src/provider/callHierarchyManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { CallHierarchyIncomingCall, CallHierarchyItem, CallHierarchyOutgoingCall, Position } from 'vscode-languageserver-types'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { CallHierarchyProvider, DocumentSelector } from './index'\nimport Manager from './manager'\n\nexport default class CallHierarchyManager extends Manager<CallHierarchyProvider> {\n\n  public register(selector: DocumentSelector, provider: CallHierarchyProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider\n    })\n  }\n\n  public async prepareCallHierarchy(document: TextDocument, position: Position, token: CancellationToken): Promise<CallHierarchyItem | CallHierarchyItem[]> {\n    let item = this.getProvider(document)\n    if (!item) return null\n    let { provider } = item\n    return await Promise.resolve(provider.prepareCallHierarchy(document, position, token))\n  }\n\n  public async provideCallHierarchyOutgoingCalls(document: TextDocument, item: CallHierarchyItem, token: CancellationToken): Promise<CallHierarchyOutgoingCall[]> {\n    let providerItem = this.getProvider(document)\n    if (!providerItem) return null\n    let { provider } = providerItem\n    return await Promise.resolve(provider.provideCallHierarchyOutgoingCalls(item, token))\n  }\n\n  public async provideCallHierarchyIncomingCalls(document: TextDocument, item: CallHierarchyItem, token: CancellationToken): Promise<CallHierarchyIncomingCall[]> {\n    let providerItem = this.getProvider(document)\n    if (!providerItem) return null\n    let { provider } = providerItem\n    return await Promise.resolve(provider.provideCallHierarchyIncomingCalls(item, token))\n  }\n}\n"
  },
  {
    "path": "src/provider/codeActionManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { CodeAction, CodeActionContext, CodeActionKind, Command, Range } from 'vscode-languageserver-types'\nimport { isFalsyOrEmpty } from '../util/array'\nimport * as Is from '../util/is'\nimport { omit } from '../util/lodash'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { CodeActionProvider, DocumentSelector } from './index'\nimport Manager from './manager'\n\ninterface ProviderMeta {\n  kinds: CodeActionKind[] | undefined\n  clientId: string\n}\n\n/*\n * With providerId so it can be resolved.\n */\nexport interface ExtendedCodeAction extends CodeAction {\n  providerId?: string\n  extensionName?: string\n}\n\nfunction codeActionContains(kinds: CodeActionKind[], kind: CodeActionKind): boolean {\n  return kinds.some(k => kind === k || kind.startsWith(k + '.'))\n}\n\nexport function checkAction(only: CodeActionKind[] | undefined, action: CodeAction | Command): boolean {\n  if (isFalsyOrEmpty(only)) return true\n  if (Command.is(action)) return false\n  return codeActionContains(only, action.kind)\n}\n\nexport default class CodeActionManager extends Manager<CodeActionProvider, ProviderMeta> {\n  public register(selector: DocumentSelector, provider: CodeActionProvider, clientId: string | undefined, codeActionKinds?: CodeActionKind[]): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider,\n      kinds: codeActionKinds,\n      clientId\n    })\n  }\n\n  public async provideCodeActions(\n    document: TextDocument,\n    range: Range,\n    context: CodeActionContext,\n    token: CancellationToken\n  ): Promise<ExtendedCodeAction[]> {\n    let providers = this.getProviders(document)\n    const only = isFalsyOrEmpty(context.only) ? undefined : context.only\n    if (only) {\n      providers = providers.filter(p => {\n        if (Array.isArray(p.kinds) && !p.kinds.some(kind => codeActionContains(only, kind))) {\n          return false\n        }\n        return true\n      })\n    }\n    let res: ExtendedCodeAction[] = []\n    const titles: string[] = []\n    let results = await Promise.allSettled(providers.map(item => {\n      let { provider, id } = item\n      let fn = async () => {\n        let actions = await Promise.resolve(provider.provideCodeActions(document, range, context, token))\n        let extensionName = provider['__extensionName']\n        if (isFalsyOrEmpty(actions)) return\n        for (let action of actions) {\n          if (titles.includes(action.title) || !checkAction(only, action)) continue\n          if (Command.is(action)) {\n            let codeAction: ExtendedCodeAction = {\n              title: action.title,\n              command: action,\n              providerId: id,\n              extensionName,\n            }\n            res.push(codeAction)\n          } else {\n            res.push(Object.assign({ providerId: id }, action))\n          }\n          titles.push(action.title)\n        }\n      }\n      return fn()\n    }))\n    this.handleResults(results, 'provideCodeActions')\n    return res\n  }\n\n  public async resolveCodeAction(codeAction: ExtendedCodeAction, token: CancellationToken): Promise<CodeAction> {\n    // no need to resolve\n    if (codeAction.edit != null || codeAction.providerId == null) return codeAction\n    let provider = this.getProviderById(codeAction.providerId)\n    if (!provider || !Is.func(provider.resolveCodeAction)) return codeAction\n    let resolved = await Promise.resolve(provider.resolveCodeAction(omit(codeAction, ['providerId']), token))\n    return resolved ?? codeAction\n  }\n}\n"
  },
  {
    "path": "src/provider/codeLensManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport type { CodeLens } from 'vscode-languageserver-types'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { omit } from '../util/lodash'\nimport { CodeLensProvider, DocumentSelector } from './index'\nimport Manager from './manager'\nimport { isCommand } from '../util/is'\n\ninterface CodeLensWithSource extends CodeLens {\n  source?: string\n}\n\nexport default class CodeLensManager extends Manager<CodeLensProvider> {\n\n  public register(selector: DocumentSelector, provider: CodeLensProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider\n    })\n  }\n\n  public async provideCodeLenses(\n    document: TextDocument,\n    token: CancellationToken\n  ): Promise<CodeLensWithSource[] | null> {\n    let providers = this.getProviders(document)\n    let codeLens: CodeLens[] = []\n    let results = await Promise.allSettled(providers.map(item => {\n      let { provider, id } = item\n      return Promise.resolve(provider.provideCodeLenses(document, token)).then(res => {\n        if (Array.isArray(res)) {\n          for (let item of res) {\n            codeLens.push(Object.assign({ source: id }, item))\n          }\n        }\n      })\n    }))\n    this.handleResults(results, 'provideCodeLenses')\n    return codeLens\n  }\n\n  public async resolveCodeLens(\n    codeLens: CodeLensWithSource,\n    token: CancellationToken\n  ): Promise<CodeLens> {\n    // no need to resolve\n    if (isCommand(codeLens.command)) return codeLens\n    let provider = this.getProviderById(codeLens.source)\n    if (!provider || typeof provider.resolveCodeLens != 'function') {\n      return codeLens\n    }\n    let res = await Promise.resolve(provider.resolveCodeLens(omit(codeLens, ['source']), token))\n    Object.assign(codeLens, res)\n    return codeLens\n  }\n}\n"
  },
  {
    "path": "src/provider/declarationManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { Position } from 'vscode-languageserver-types'\nimport { LocationWithTarget } from '../types'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { DeclarationProvider, DocumentSelector } from './index'\nimport Manager from './manager'\n\nexport default class DeclarationManager extends Manager<DeclarationProvider> {\n\n  public register(selector: DocumentSelector, provider: DeclarationProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider\n    })\n  }\n\n  public async provideDeclaration(\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken\n  ): Promise<LocationWithTarget[]> {\n    const providers = this.getProviders(document)\n    let locations: LocationWithTarget[] = []\n    const results = await Promise.allSettled(providers.map(item => {\n      return Promise.resolve(item.provider.provideDeclaration(document, position, token)).then(location => {\n        this.addLocation(locations, location)\n      })\n    }))\n    this.handleResults(results, 'provideDeclaration')\n    return locations\n  }\n}\n"
  },
  {
    "path": "src/provider/definitionManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { DefinitionLink, LocationLink, Position } from 'vscode-languageserver-types'\nimport { LocationWithTarget } from '../types'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { DefinitionProvider, DocumentSelector } from './index'\nimport Manager from './manager'\n\nexport default class DefinitionManager extends Manager<DefinitionProvider> {\n\n  public register(selector: DocumentSelector, provider: DefinitionProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider\n    })\n  }\n\n  public async provideDefinition(\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken\n  ): Promise<LocationWithTarget[]> {\n    const providers = this.getProviders(document)\n    let locations: LocationWithTarget[] = []\n    const results = await Promise.allSettled(providers.map(item => {\n      return Promise.resolve(item.provider.provideDefinition(document, position, token)).then(location => {\n        this.addLocation(locations, location)\n      })\n    }))\n    this.handleResults(results, 'provideDefinition')\n    return locations\n  }\n\n  public async provideDefinitionLinks(\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken\n  ): Promise<DefinitionLink[]> {\n    const providers = this.getProviders(document)\n    let locations: DefinitionLink[] = []\n    const results = await Promise.allSettled(providers.map(item => {\n      return Promise.resolve(item.provider.provideDefinition(document, position, token)).then(location => {\n        if (Array.isArray(location)) {\n          location.forEach(loc => {\n            if (LocationLink.is(loc)) {\n              locations.push(loc)\n            }\n          })\n        }\n      })\n    }))\n    this.handleResults(results, 'provideDefinition')\n    return locations\n  }\n}\n"
  },
  {
    "path": "src/provider/documentColorManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { ColorInformation, ColorPresentation } from 'vscode-languageserver-types'\nimport { equals } from '../util/object'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { DocumentColorProvider, DocumentSelector } from './index'\nimport Manager from './manager'\n\ninterface ColorWithSource extends ColorInformation {\n  source?: string\n}\n\nexport default class DocumentColorManager extends Manager<DocumentColorProvider> {\n\n  public register(selector: DocumentSelector, provider: DocumentColorProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider\n    })\n  }\n\n  public async provideDocumentColors(document: TextDocument, token: CancellationToken): Promise<ColorInformation[]> {\n    let items = this.getProviders(document)\n    let colors: ColorWithSource[] = []\n    const results = await Promise.allSettled(items.map(item => {\n      let { id } = item\n      return Promise.resolve(item.provider.provideDocumentColors(document, token)).then(arr => {\n        let noCheck = colors.length == 0\n        if (Array.isArray(arr)) {\n          for (let color of arr) {\n            if (noCheck || !colors.some(o => equals(o.range, color.range))) {\n              colors.push(Object.assign({ source: id }, color))\n            }\n          }\n        }\n      })\n    }))\n    this.handleResults(results, 'provideDocumentColors')\n    return colors\n  }\n\n  public async provideColorPresentations(colorInformation: ColorWithSource, document: TextDocument, token: CancellationToken): Promise<ColorPresentation[] | null> {\n    let providers = this.getProviders(document)\n    let { range, color } = colorInformation\n    for (let item of providers) {\n      let res = await Promise.resolve(item.provider.provideColorPresentations(color, { document, range }, token))\n      if (res) return res\n    }\n    return null\n  }\n}\n"
  },
  {
    "path": "src/provider/documentHighlightManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { DocumentHighlight, Position } from 'vscode-languageserver-types'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { DocumentHighlightProvider, DocumentSelector } from './index'\nimport Manager from './manager'\n\nexport default class DocumentHighlightManager extends Manager<DocumentHighlightProvider> {\n\n  public register(selector: DocumentSelector, provider: DocumentHighlightProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider\n    })\n  }\n\n  public async provideDocumentHighlights(\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken\n  ): Promise<DocumentHighlight[]> {\n    let items = this.getProviders(document)\n    let res: DocumentHighlight[] = null\n    for (const item of items) {\n      try {\n        res = await Promise.resolve(item.provider.provideDocumentHighlights(document, position, token))\n        if (res != null) break\n      } catch (e) {\n        this.handleResults([{ status: 'rejected', reason: e }], 'provideDocumentHighlights')\n      }\n    }\n    return res\n  }\n}\n"
  },
  {
    "path": "src/provider/documentLinkManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { DocumentLink, Range } from 'vscode-languageserver-types'\nimport { omit } from '../util/lodash'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { DocumentLinkProvider, DocumentSelector } from './index'\nimport Manager from './manager'\n\ninterface DocumentLinkWithSource extends DocumentLink {\n  source?: string\n}\n\nfunction rangeToString(range: Range): string {\n  return `${range.start.line},${range.start.character},${range.end.line},${range.end.character}`\n}\n\nexport default class DocumentLinkManager extends Manager<DocumentLinkProvider> {\n\n  public register(selector: DocumentSelector, provider: DocumentLinkProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider\n    })\n  }\n\n  public async provideDocumentLinks(document: TextDocument, token: CancellationToken): Promise<DocumentLinkWithSource[] | null> {\n    let items = this.getProviders(document)\n    if (items.length == 0) return null\n    const links: DocumentLinkWithSource[] = []\n    const seenRanges: Set<string> = new Set()\n\n    const results = await Promise.allSettled(items.map(async item => {\n      let { id, provider } = item\n      const arr = await provider.provideDocumentLinks(document, token)\n      if (Array.isArray(arr)) {\n        let check = links.length > 0\n        arr.forEach(link => {\n          if (check) {\n            const rangeString = rangeToString(link.range)\n            if (!seenRanges.has(rangeString)) {\n              seenRanges.add(rangeString)\n              links.push(Object.assign({ source: id }, link))\n            }\n          } else {\n            if (items.length > 1) seenRanges.add(rangeToString(link.range))\n            links.push(Object.assign({ source: id }, link))\n          }\n        })\n      }\n    }))\n    this.handleResults(results, 'provideDocumentLinks')\n    return links\n  }\n\n  public async resolveDocumentLink(link: DocumentLinkWithSource, token: CancellationToken): Promise<DocumentLink> {\n    let provider = this.getProviderById(link.source)\n    if (typeof provider.resolveDocumentLink === 'function') {\n      let resolved = await Promise.resolve(provider.resolveDocumentLink(omit(link, ['source']), token))\n      if (resolved) Object.assign(link, resolved)\n    }\n    return link\n  }\n}\n"
  },
  {
    "path": "src/provider/documentSymbolManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { DocumentSymbol, SymbolInformation, SymbolTag } from 'vscode-languageserver-types'\nimport { isFalsyOrEmpty, toArray } from '../util/array'\nimport { compareRangesUsingStarts, equalsRange, rangeInRange } from '../util/position'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { toText } from '../util/string'\nimport { DocumentSelector, DocumentSymbolProvider, DocumentSymbolProviderMetadata } from './index'\nimport Manager from './manager'\n\nexport default class DocumentSymbolManager extends Manager<DocumentSymbolProvider> {\n  public register(selector: DocumentSelector, provider: DocumentSymbolProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider\n    })\n  }\n\n  public getMetaData(document: TextDocument): DocumentSymbolProviderMetadata | null {\n    let item = this.getProvider(document)\n    if (!item) return null\n    return item.provider.meta ?? {}\n  }\n\n  public async provideDocumentSymbols(\n    document: TextDocument,\n    token: CancellationToken\n  ): Promise<DocumentSymbol[] | null> {\n    let item = this.getProvider(document)\n    if (!item) return null\n    let symbols: DocumentSymbol[] | null = []\n    let results = await Promise.allSettled([item].map(item => {\n      return Promise.resolve(item.provider.provideDocumentSymbols(document, token)).then(result => {\n        if (!token.isCancellationRequested && !isFalsyOrEmpty(result)) {\n          if (DocumentSymbol.is(result[0])) {\n            symbols = result as DocumentSymbol[]\n          } else {\n            symbols = asDocumentSymbolTree(result as SymbolInformation[])\n          }\n        }\n      })\n    }))\n    this.handleResults(results, 'provideDocumentSymbols')\n    return symbols\n  }\n}\n\nexport function asDocumentSymbolTree(infos: SymbolInformation[]): DocumentSymbol[] {\n  infos = infos.slice().sort((a, b) => {\n    return compareRangesUsingStarts(a.location.range, b.location.range)\n  })\n  const res: DocumentSymbol[] = []\n  const parentStack: DocumentSymbol[] = []\n  for (const info of infos) {\n    const element: DocumentSymbol = {\n      name: toText(info.name),\n      kind: info.kind,\n      tags: toArray(info.tags),\n      detail: '',\n      range: info.location.range,\n      selectionRange: info.location.range,\n    }\n    if (info.deprecated) {\n      element.tags.push(SymbolTag.Deprecated)\n    }\n\n    while (true) {\n      if (parentStack.length === 0) {\n        parentStack.push(element)\n        res.push(element)\n        break\n      }\n      const parent = parentStack[parentStack.length - 1]\n      if (rangeInRange(element.range, parent.range) && !equalsRange(parent.range, element.range)) {\n        parent.children = toArray(parent.children)\n        parent.children.push(element)\n        parentStack.push(element)\n        break\n      }\n      parentStack.pop()\n    }\n  }\n  return res\n}\n"
  },
  {
    "path": "src/provider/foldingRangeManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { FoldingRange } from 'vscode-languageserver-types'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { DocumentSelector, FoldingContext, FoldingRangeProvider } from './index'\nimport Manager from './manager'\n\nexport default class FoldingRangeManager extends Manager<FoldingRangeProvider>  {\n\n  public register(selector: DocumentSelector, provider: FoldingRangeProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider\n    })\n  }\n\n  /**\n   * Multiple providers can be registered for a language. In that case providers are asked in\n   * parallel and the results are merged.\n   * If multiple folding ranges start at the same position, only the range of the first registered provider is used.\n   * If a folding range overlaps with an other range that has a smaller position, it is also ignored.\n   */\n  public async provideFoldingRanges(document: TextDocument, context: FoldingContext, token: CancellationToken): Promise<FoldingRange[] | null> {\n    let items = this.getProviders(document)\n    let ranges: FoldingRange[] = []\n    let results = await Promise.allSettled(items.map(item => {\n      return Promise.resolve(item.provider.provideFoldingRanges(document, context, token)).then(res => {\n        if (Array.isArray(res) && res.length > 0) {\n          if (ranges.length == 0) {\n            ranges.push(...res)\n          } else {\n            for (let r of res) {\n              let sp = getParent(r.startLine, ranges)\n              if (sp?.startLine === r.startLine) continue\n              let ep = getParent(r.endLine, ranges)\n              if (sp === ep) {\n                ranges.push(r)\n              }\n            }\n          }\n          ranges.sort((a, b) => a.startLine - b.startLine)\n        }\n      })\n    }))\n    this.handleResults(results, 'provideFoldingRanges')\n    return ranges\n  }\n}\n\nfunction getParent(line: number, sortedRanges: FoldingRange[]): FoldingRange | undefined {\n  for (let r of sortedRanges) {\n    if (line >= r.startLine) {\n      if (line <= r.endLine) {\n        return r\n      } else {\n        continue\n      }\n    } else {\n      break\n    }\n  }\n  return undefined\n}\n"
  },
  {
    "path": "src/provider/formatManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { FormattingOptions, TextEdit } from 'vscode-languageserver-types'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { TextDocumentMatch } from '../types'\nimport { DocumentFormattingEditProvider, DocumentSelector } from './index'\nimport Manager from './manager'\n\nexport default class FormatManager extends Manager<DocumentFormattingEditProvider> {\n\n  public register(selector: DocumentSelector, provider: DocumentFormattingEditProvider, priority: number): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      priority,\n      provider\n    })\n  }\n\n  public hasFormatProvider(document: TextDocumentMatch): boolean {\n    return this.getFormatProvider(document) != null\n  }\n\n  public async provideDocumentFormattingEdits(\n    document: TextDocument,\n    options: FormattingOptions,\n    token: CancellationToken\n  ): Promise<TextEdit[]> {\n    let item = this.getFormatProvider(document)\n    if (!item) return null\n    let { provider } = item\n    let res = await Promise.resolve(provider.provideDocumentFormattingEdits(document, options, token))\n    if (Array.isArray(res)) {\n      Object.defineProperty(res, '__extensionName', {\n        get: () => item.provider['__extensionName']\n      })\n    }\n    return res\n  }\n}\n"
  },
  {
    "path": "src/provider/formatRangeManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { FormattingOptions, Range, TextEdit } from 'vscode-languageserver-types'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { DocumentRangeFormattingEditProvider, DocumentSelector } from './index'\nimport Manager from './manager'\n\nexport default class FormatRangeManager extends Manager<DocumentRangeFormattingEditProvider> {\n\n  public register(selector: DocumentSelector,\n    provider: DocumentRangeFormattingEditProvider,\n    priority: number): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider,\n      priority\n    })\n  }\n\n  /**\n   * Multiple providers can be registered for a language. In that case providers are sorted\n   * by their {@link languages.match score} and the best-matching provider is used. Failure\n   * of the selected provider will cause a failure of the whole operation.\n   */\n  public async provideDocumentRangeFormattingEdits(\n    document: TextDocument,\n    range: Range,\n    options: FormattingOptions,\n    token: CancellationToken\n  ): Promise<TextEdit[]> {\n    let item = this.getFormatProvider(document)\n    if (!item) return null\n    let { provider } = item\n    let res = await Promise.resolve(provider.provideDocumentRangeFormattingEdits(document, range, options, token))\n    if (Array.isArray(res)) {\n      Object.defineProperty(res, '__extensionName', {\n        get: () => item.provider['__extensionName']\n      })\n    }\n    return res\n  }\n}\n"
  },
  {
    "path": "src/provider/hoverManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { Hover, Position } from 'vscode-languageserver-types'\nimport { isHover } from '../util/is'\nimport { equals } from '../util/object'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { DocumentSelector, HoverProvider } from './index'\nimport Manager from './manager'\n\nexport default class HoverManager extends Manager<HoverProvider> {\n\n  public register(selector: DocumentSelector, provider: HoverProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider\n    })\n  }\n\n  public async provideHover(\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken\n  ): Promise<Hover[]> {\n    let items = this.getProviders(document)\n    let hovers: Hover[] = []\n    let results = await Promise.allSettled(items.map(item => {\n      return Promise.resolve(item.provider.provideHover(document, position, token)).then(hover => {\n        if (!isHover(hover)) return\n        if (hovers.findIndex(o => equals(o.contents, hover.contents)) == -1) {\n          hovers.push(hover)\n        }\n      })\n    }))\n    this.handleResults(results, 'provideHover')\n    return hovers\n  }\n}\n"
  },
  {
    "path": "src/provider/implementationManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { Position } from 'vscode-languageserver-types'\nimport { LocationWithTarget } from '../types'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { ImplementationProvider, DocumentSelector } from './index'\nimport Manager from './manager'\n\nexport default class ImplementationManager extends Manager<ImplementationProvider> {\n\n  public register(selector: DocumentSelector, provider: ImplementationProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider\n    })\n  }\n\n  public async provideImplementations(\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken\n  ): Promise<LocationWithTarget[]> {\n    const providers = this.getProviders(document)\n    let locations: LocationWithTarget[] = []\n    const results = await Promise.allSettled(providers.map(item => {\n      return Promise.resolve(item.provider.provideImplementation(document, position, token)).then(location => {\n        this.addLocation(locations, location)\n      })\n    }))\n    this.handleResults(results, 'provideImplementations')\n    return locations\n  }\n}\n"
  },
  {
    "path": "src/provider/index.ts",
    "content": "'use strict'\nimport type { CallHierarchyIncomingCall, DocumentFilter, CallHierarchyItem, CallHierarchyOutgoingCall, CancellationToken, CodeAction, CodeActionContext, CodeActionKind, CodeLens, Color, ColorInformation, ColorPresentation, Command, CompletionContext, CompletionItem, CompletionList, Definition, DefinitionLink, DocumentDiagnosticReport, DocumentHighlight, DocumentLink, DocumentSymbol, Event, FoldingRange, FormattingOptions, Hover, InlayHint, InlineValue, InlineValueContext, LinkedEditingRanges, Location, Position, PreviousResultId, Range, SelectionRange, SemanticTokens, SemanticTokensDelta, SignatureHelp, SignatureHelpContext, SymbolInformation, TextEdit, TypeHierarchyItem, WorkspaceDiagnosticReport, WorkspaceDiagnosticReportPartialResult, WorkspaceEdit, WorkspaceSymbol, InlineCompletionContext, InlineCompletionItem, InlineCompletionList } from 'vscode-languageserver-protocol'\nimport type { TextDocument } from 'vscode-languageserver-textdocument'\nimport type { URI } from 'vscode-uri'\n\nexport type DocumentSelector = (string | DocumentFilter)[]\n\n/**\n * A provider result represents the values a provider, like the [`HoverProvider`](#HoverProvider),\n * may return. For once this is the actual result type `T`, like `Hover`, or a thenable that resolves\n * to that type `T`. In addition, `null` and `undefined` can be returned - either directly or from a\n * thenable.\n *\n * The snippets below are all valid implementations of the [`HoverProvider`](#HoverProvider):\n *\n * ```ts\n * let a: HoverProvider = {\n *   provideHover(doc, pos, token): ProviderResult<Hover> {\n *     return new Hover('Hello World')\n *   }\n * }\n *\n * let b: HoverProvider = {\n *   provideHover(doc, pos, token): ProviderResult<Hover> {\n *     return new Promise(resolve => {\n *       resolve(new Hover('Hello World'))\n *      })\n *   }\n * }\n *\n * let c: HoverProvider = {\n *   provideHover(doc, pos, token): ProviderResult<Hover> {\n *     return; // undefined\n *   }\n * }\n * ```\n */\nexport type ProviderResult<T> =\n  | T\n  | undefined\n  | null\n  | Thenable<T | undefined | null>\n\n/**\n * The completion item provider interface defines the contract between extensions and\n * [IntelliSense](https://code.visualstudio.com/docs/editor/intellisense).\n *\n * Providers can delay the computation of the [`detail`](#CompletionItem.detail)\n * and [`documentation`](#CompletionItem.documentation) properties by implementing the\n * [`resolveCompletionItem`](#CompletionItemProvider.resolveCompletionItem)-function. However, properties that\n * are needed for the initial sorting and filtering, like `sortText`, `filterText`, `insertText`, and `range`, must\n * not be changed during resolve.\n *\n * Providers are asked for completions either explicitly by a user gesture or -depending on the configuration-\n * implicitly when typing words or trigger characters.\n */\nexport interface CompletionItemProvider {\n  /**\n   * Provide completion items for the given position and document.\n   * @param document The document in which the command was invoked.\n   * @param position The position at which the command was invoked.\n   * @param token A cancellation token.\n   * @param context How the completion was triggered.\n   * @return An array of completions, a [completion list](#CompletionList), or a thenable that resolves to either.\n   * The lack of a result can be signaled by returning `undefined`, `null`, or an empty array.\n   */\n  provideCompletionItems(\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken,\n    context?: CompletionContext\n  ): ProviderResult<CompletionItem[] | CompletionList>\n\n  /**\n   * Given a completion item fill in more data, like [doc-comment](#CompletionItem.documentation)\n   * or [details](#CompletionItem.detail).\n   *\n   * The editor will only resolve a completion item once.\n   * @param item A completion item currently active in the UI.\n   * @param token A cancellation token.\n   * @return The resolved completion item or a thenable that resolves to of such. It is OK to return the given\n   * `item`. When no result is returned, the given `item` will be used.\n   */\n  resolveCompletionItem?(\n    item: CompletionItem,\n    token: CancellationToken\n  ): ProviderResult<CompletionItem>\n}\n\n/**\n * The inline completion item provider interface defines the contract between extensions and\n * the inline completion feature.\n *\n * Providers are asked for completions either explicitly by a user gesture or implicitly when typing.\n */\nexport interface InlineCompletionItemProvider {\n\n  /**\n   * Provides inline completion items for the given position and document.\n   * If inline completions are enabled, this method will be called whenever the user stopped typing.\n   * It will also be called when the user explicitly triggers inline completions or explicitly asks for the next or previous inline completion.\n   * In that case, all available inline completions should be returned.\n   * `context.triggerKind` can be used to distinguish between these scenarios.\n   * @param document The document inline completions are requested for.\n   * @param position The position inline completions are requested for.\n   * @param context A context object with additional information.\n   * @param token A cancellation token.\n   * @returns An array of completion items or a thenable that resolves to an array of completion items.\n   */\n  provideInlineCompletionItems(\n    document: TextDocument,\n    position: Position,\n    context: InlineCompletionContext,\n    token: CancellationToken\n  ): ProviderResult<InlineCompletionItem[] | InlineCompletionList>\n}\n\n/**\n * The hover provider interface defines the contract between extensions and\n * the [hover](https://code.visualstudio.com/docs/editor/intellisense)-feature.\n */\nexport interface HoverProvider {\n  /**\n   * Provide a hover for the given position and document. Multiple hovers at the same\n   * position will be merged by the editor. A hover can have a range which defaults\n   * to the word range at the position when omitted.\n   * @param document The document in which the command was invoked.\n   * @param position The position at which the command was invoked.\n   * @param token A cancellation token.\n   * @return A hover or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined` or `null`.\n   */\n  provideHover(\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken\n  ): ProviderResult<Hover>\n}\n\n/**\n * The definition provider interface defines the contract between extensions and\n * the [go to definition](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-definition)\n * and peek definition features.\n */\nexport interface DefinitionProvider {\n  /**\n   * Provide the definition of the symbol at the given position and document.\n   * @param document The document in which the command was invoked.\n   * @param position The position at which the command was invoked.\n   * @param token A cancellation token.\n   * @return A definition or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined` or `null`.\n   */\n  provideDefinition(\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken\n  ): ProviderResult<Definition | DefinitionLink[]>\n}\n\n/**\n * The definition provider interface defines the contract between extensions and\n * the [go to definition](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-definition)\n * and peek definition features.\n */\nexport interface DeclarationProvider {\n  /**\n   * Provide the declaration of the symbol at the given position and document.\n   */\n  provideDeclaration(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<Definition | DefinitionLink[]>\n}\n\n/**\n * The signature help provider interface defines the contract between extensions and\n * the [parameter hints](https://code.visualstudio.com/docs/editor/intellisense)-feature.\n */\nexport interface SignatureHelpProvider {\n  /**\n   * Provide help for the signature at the given position and document.\n   * @param document The document in which the command was invoked.\n   * @param position The position at which the command was invoked.\n   * @param token A cancellation token.\n   * @return Signature help or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined` or `null`.\n   */\n  provideSignatureHelp(\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken,\n    context: SignatureHelpContext\n  ): ProviderResult<SignatureHelp>\n}\n\n/**\n * The type definition provider defines the contract between extensions and\n * the go to type definition feature.\n */\nexport interface TypeDefinitionProvider {\n  /**\n   * Provide the type definition of the symbol at the given position and document.\n   * @param document The document in which the command was invoked.\n   * @param position The position at which the command was invoked.\n   * @param token A cancellation token.\n   * @return A definition or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined` or `null`.\n   */\n  provideTypeDefinition(\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken\n  ): ProviderResult<Definition | DefinitionLink[]>\n}\n\n/**\n * Value-object that contains additional information when\n * requesting references.\n */\nexport interface ReferenceContext {\n  /**\n   * Include the declaration of the current symbol.\n   */\n  includeDeclaration: boolean\n}\n\n/**\n * The reference provider interface defines the contract between extensions and\n * the [find references](https://code.visualstudio.com/docs/editor/editingevolved#_peek)-feature.\n */\nexport interface ReferenceProvider {\n  /**\n   * Provide a set of project-wide references for the given position and document.\n   * @param document The document in which the command was invoked.\n   * @param position The position at which the command was invoked.\n   * @param context\n   * @param token A cancellation token.\n   * @return An array of locations or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined`, `null`, or an empty array.\n   */\n  provideReferences(\n    document: TextDocument,\n    position: Position,\n    context: ReferenceContext,\n    token: CancellationToken\n  ): ProviderResult<Location[]>\n}\n\n/**\n * Folding context (for future use)\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface FoldingContext {}\n\n/**\n * The folding range provider interface defines the contract between extensions and\n * [Folding](https://code.visualstudio.com/docs/editor/codebasics#_folding) in the editor.\n */\nexport interface FoldingRangeProvider {\n\n  /**\n   * An optional event to signal that the folding ranges from this provider have changed.\n   */\n  onDidChangeFoldingRanges?: Event<void>\n\n  /**\n   * Returns a list of folding ranges or null and undefined if the provider\n   * does not want to participate or was cancelled.\n   * @param document The document in which the command was invoked.\n   * @param context Additional context information (for future use)\n   * @param token A cancellation token.\n   */\n  provideFoldingRanges(\n    document: TextDocument,\n    context: FoldingContext,\n    token: CancellationToken\n  ): ProviderResult<FoldingRange[]>\n}\n\nexport interface DocumentSymbolProviderMetadata {\n  /**\n   * A human-readable string that is shown when multiple outlines trees show for one document.\n   */\n  label?: string\n}\n\n/**\n * The document symbol provider interface defines the contract between extensions and\n * the [go to symbol](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-symbol)-feature.\n */\nexport interface DocumentSymbolProvider {\n\n  meta?: DocumentSymbolProviderMetadata\n\n  /**\n   * Provide symbol information for the given document.\n   * @param document The document in which the command was invoked.\n   * @param token A cancellation token.\n   * @return An array of document highlights or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined`, `null`, or an empty array.\n   */\n  provideDocumentSymbols(\n    document: TextDocument,\n    token: CancellationToken\n  ): ProviderResult<SymbolInformation[] | DocumentSymbol[]>\n}\n\n/**\n * The implementation provider interface defines the contract between extensions and\n * the go to implementation feature.\n */\nexport interface ImplementationProvider {\n  /**\n   * Provide the implementations of the symbol at the given position and document.\n   * @param document The document in which the command was invoked.\n   * @param position The position at which the command was invoked.\n   * @param token A cancellation token.\n   * @return A definition or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined` or `null`.\n   */\n  provideImplementation(\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken\n  ): ProviderResult<Definition | DefinitionLink[]>\n}\n\n/**\n * The workspace symbol provider interface defines the contract between extensions and\n * the [symbol search](https://code.visualstudio.com/docs/editor/editingevolved#_open-symbol-by-name)-feature.\n */\nexport interface WorkspaceSymbolProvider {\n  /**\n   * Project-wide search for a symbol matching the given query string. It is up to the provider\n   * how to search given the query string, like substring, indexOf etc. To improve performance implementors can\n   * skip the [location](#SymbolInformation.location) of symbols and implement `resolveWorkspaceSymbol` to do that\n   * later.\n   *\n   * The `query`-parameter should be interpreted in a *relaxed way* as the editor will apply its own highlighting\n   * and scoring on the results. A good rule of thumb is to match case-insensitive and to simply check that the\n   * characters of *query* appear in their order in a candidate symbol. Don't use prefix, substring, or similar\n   * strict matching.\n   * @param query A non-empty query string.\n   * @param token A cancellation token.\n   * @return An array of document highlights or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined`, `null`, or an empty array.\n   */\n  provideWorkspaceSymbols(\n    query: string,\n    token: CancellationToken\n  ): ProviderResult<(WorkspaceSymbol & { deprecated?: boolean })[]>\n\n  /**\n   * Given a symbol fill in its [location](#SymbolInformation.location). This method is called whenever a symbol\n   * is selected in the UI. Providers can implement this method and return incomplete symbols from\n   * [`provideWorkspaceSymbols`](#WorkspaceSymbolProvider.provideWorkspaceSymbols) which often helps to improve\n   * performance.\n   * @param symbol The symbol that is to be resolved. Guaranteed to be an instance of an object returned from an\n   * earlier call to `provideWorkspaceSymbols`.\n   * @param token A cancellation token.\n   * @return The resolved symbol or a thenable that resolves to that. When no result is returned,\n   * the given `symbol` is used.\n   */\n  resolveWorkspaceSymbol?(\n    symbol: WorkspaceSymbol,\n    token: CancellationToken\n  ): ProviderResult<WorkspaceSymbol>\n}\n\n/**\n * The rename provider interface defines the contract between extensions and\n * the [rename](https://code.visualstudio.com/docs/editor/editingevolved#_rename-symbol)-feature.\n */\nexport interface RenameProvider {\n  /**\n   * Provide an edit that describes changes that have to be made to one\n   * or many resources to rename a symbol to a different name.\n   * @param document The document in which the command was invoked.\n   * @param position The position at which the command was invoked.\n   * @param newName The new name of the symbol. If the given name is not valid, the provider must return a rejected promise.\n   * @param token A cancellation token.\n   * @return A workspace edit or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined` or `null`.\n   */\n  provideRenameEdits(\n    document: TextDocument,\n    position: Position,\n    newName: string,\n    token: CancellationToken\n  ): ProviderResult<WorkspaceEdit>\n\n  /**\n   * Optional function for resolving and validating a position *before* running rename. The result can\n   * be a range or a range and a placeholder text. The placeholder text should be the identifier of the symbol\n   * which is being renamed - when omitted the text in the returned range is used.\n   * @param document The document in which rename will be invoked.\n   * @param position The position at which rename will be invoked.\n   * @param token A cancellation token.\n   * @return The range or range and placeholder text of the identifier that is to be renamed. The lack of a result can signaled by returning `undefined` or `null`.\n   */\n  prepareRename?(\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken\n  ): ProviderResult<Range | { range: Range; placeholder: string }>\n}\n\n/**\n * The document formatting provider interface defines the contract between extensions and\n * the formatting-feature.\n */\nexport interface DocumentFormattingEditProvider {\n  /**\n   * Provide formatting edits for a whole document.\n   * @param document The document in which the command was invoked.\n   * @param options Options controlling formatting.\n   * @param token A cancellation token.\n   * @return A set of text edits or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined`, `null`, or an empty array.\n   */\n  provideDocumentFormattingEdits(\n    document: TextDocument,\n    options: FormattingOptions,\n    token: CancellationToken\n  ): ProviderResult<TextEdit[]>\n}\n\n/**\n * The document formatting provider interface defines the contract between extensions and\n * the formatting-feature.\n */\nexport interface DocumentRangeFormattingEditProvider {\n  /**\n   * Provide formatting edits for a range in a document.\n   *\n   * The given range is a hint and providers can decide to format a smaller\n   * or larger range. Often this is done by adjusting the start and end\n   * of the range to full syntax nodes.\n   * @param document The document in which the command was invoked.\n   * @param range The range which should be formatted.\n   * @param options Options controlling formatting.\n   * @param token A cancellation token.\n   * @return A set of text edits or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined`, `null`, or an empty array.\n   */\n  provideDocumentRangeFormattingEdits(\n    document: TextDocument,\n    range: Range,\n    options: FormattingOptions,\n    token: CancellationToken\n  ): ProviderResult<TextEdit[]>\n}\n\n/**\n * The code action interface defines the contract between extensions and\n * the [light bulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) feature.\n *\n * A code action can be any command that is [known](#commands.getCommands) to the system.\n */\nexport interface CodeActionProvider<T extends CodeAction = CodeAction> {\n  /**\n   * Provide commands for the given document and range.\n   * @param document The document in which the command was invoked.\n   * @param range The selector or range for which the command was invoked. This will always be a selection if\n   * there is a currently active editor.\n   * @param context Context carrying additional information.\n   * @param token A cancellation token.\n   * @return An array of commands, quick fixes, or refactorings or a thenable of such. The lack of a result can be\n   * signaled by returning `undefined`, `null`, or an empty array.\n   */\n  provideCodeActions(\n    document: TextDocument,\n    range: Range,\n    context: CodeActionContext,\n    token: CancellationToken\n  ): ProviderResult<(Command | CodeAction)[]>\n\n  /**\n   * Given a code action fill in its [`edit`](#CodeAction.edit)-property. Changes to\n   * all other properties, like title, are ignored. A code action that has an edit\n   * will not be resolved.\n   * @param codeAction A code action.\n   * @param token A cancellation token.\n   * @return The resolved code action or a thenable that resolves to such. It is OK to return the given\n   * `item`. When no result is returned, the given `item` will be used.\n   */\n  resolveCodeAction?(codeAction: T, token: CancellationToken): ProviderResult<T>\n}\n\n/**\n * Metadata about the type of code actions that a [CodeActionProvider](#CodeActionProvider) providers\n */\nexport interface CodeActionProviderMetadata {\n  /**\n   * [CodeActionKinds](#CodeActionKind) that this provider may return.\n   *\n   * The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the provider\n   * may list our every specific kind they provide, such as `CodeActionKind.Refactor.Extract.append('function`)`\n   */\n  readonly providedCodeActionKinds?: ReadonlyArray<CodeActionKind>\n}\n\n/**\n * The document highlight provider interface defines the contract between extensions and\n * the word-highlight-feature.\n */\nexport interface DocumentHighlightProvider {\n\n  /**\n   * Provide a set of document highlights, like all occurrences of a variable or\n   * all exit-points of a function.\n   * @param document The document in which the command was invoked.\n   * @param position The position at which the command was invoked.\n   * @param token A cancellation token.\n   * @return An array of document highlights or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined`, `null`, or an empty array.\n   */\n  provideDocumentHighlights(\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken\n  ): ProviderResult<DocumentHighlight[]>\n}\n\n/**\n * The document link provider defines the contract between extensions and feature of showing\n * links in the editor.\n */\nexport interface DocumentLinkProvider {\n\n  /**\n   * Provide links for the given document. Note that the editor ships with a default provider that detects\n   * `http(s)` and `file` links.\n   * @param document The document in which the command was invoked.\n   * @param token A cancellation token.\n   * @return An array of [document links](#DocumentLink) or a thenable that resolves to such. The lack of a result\n   * can be signaled by returning `undefined`, `null`, or an empty array.\n   */\n  provideDocumentLinks(document: TextDocument, token: CancellationToken): ProviderResult<DocumentLink[]>\n\n  /**\n   * Given a link fill in its [target](#DocumentLink.target). This method is called when an incomplete\n   * link is selected in the UI. Providers can implement this method and return incomple links\n   * (without target) from the [`provideDocumentLinks`](#DocumentLinkProvider.provideDocumentLinks) method which\n   * often helps to improve performance.\n   * @param link The link that is to be resolved.\n   * @param token A cancellation token.\n   */\n  resolveDocumentLink?(link: DocumentLink, token: CancellationToken): ProviderResult<DocumentLink>\n}\n\n/**\n * A code lens provider adds [commands](#Command) to source text. The commands will be shown\n * as dedicated horizontal lines in between the source text.\n */\nexport interface CodeLensProvider {\n\n  /**\n   * An optional event to signal that the code lenses from this provider have changed.\n   */\n  onDidChangeCodeLenses?: Event<void>\n\n  /**\n   * Compute a list of [lenses](#CodeLens). This call should return as fast as possible and if\n   * computing the commands is expensive implementors should only return code lens objects with the\n   * range set and implement [resolve](#CodeLensProvider.resolveCodeLens).\n   * @param document The document in which the command was invoked.\n   * @param token A cancellation token.\n   * @return An array of code lenses or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined`, `null`, or an empty array.\n   */\n  provideCodeLenses(document: TextDocument, token: CancellationToken): ProviderResult<CodeLens[]>\n\n  /**\n   * This function will be called for each visible code lens, usually when scrolling and after\n   * calls to [compute](#CodeLensProvider.provideCodeLenses)-lenses.\n   * @param codeLens code lens that must be resolved.\n   * @param token A cancellation token.\n   * @return The given, resolved code lens or thenable that resolves to such.\n   */\n  resolveCodeLens?(codeLens: CodeLens, token: CancellationToken): ProviderResult<CodeLens>\n}\n\n/**\n * The document formatting provider interface defines the contract between extensions and\n * the formatting-feature.\n */\nexport interface OnTypeFormattingEditProvider {\n\n  /**\n   * Provide formatting edits after a character has been typed.\n   *\n   * The given position and character should hint to the provider\n   * what range the position to expand to, like find the matching `{`\n   * when `}` has been entered.\n   * @param document The document in which the command was invoked.\n   * @param position The position at which the command was invoked.\n   * @param ch The character that has been typed.\n   * @param options Options controlling formatting.\n   * @param token A cancellation token.\n   * @return A set of text edits or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined`, `null`, or an empty array.\n   */\n  provideOnTypeFormattingEdits(document: TextDocument, position: Position, ch: string, options: FormattingOptions, token: CancellationToken): ProviderResult<TextEdit[]>\n}\n\n/**\n * The document color provider defines the contract between extensions and feature of\n * picking and modifying colors in the editor.\n */\nexport interface DocumentColorProvider {\n\n  /**\n   * Provide colors for the given document.\n   * @param document The document in which the command was invoked.\n   * @param token A cancellation token.\n   * @return An array of [color information](#ColorInformation) or a thenable that resolves to such. The lack of a result\n   * can be signaled by returning `undefined`, `null`, or an empty array.\n   */\n  provideDocumentColors(document: TextDocument, token: CancellationToken): ProviderResult<ColorInformation[]>\n\n  /**\n   * Provide [representations](#ColorPresentation) for a color.\n   * @param color The color to show and insert.\n   * @param context A context object with additional information\n   * @param token A cancellation token.\n   * @return An array of color presentations or a thenable that resolves to such. The lack of a result\n   * can be signaled by returning `undefined`, `null`, or an empty array.\n   */\n  provideColorPresentations(color: Color, context: { document: TextDocument; range: Range }, token: CancellationToken): ProviderResult<ColorPresentation[]>\n}\n\nexport interface TextDocumentContentProvider {\n\n  /**\n   * An event to signal a resource has changed.\n   */\n  onDidChange?: Event<URI>\n\n  /**\n   * Provide textual content for a given uri.\n   *\n   * The editor will use the returned string-content to create a readonly\n   * [document](#TextDocument). Resources allocated should be released when\n   * the corresponding document has been [closed](#workspace.onDidCloseTextDocument).\n   * @param uri An uri which scheme matches the scheme this provider was [registered](#workspace.registerTextDocumentContentProvider) for.\n   * @param token A cancellation token.\n   * @return A string or a thenable that resolves to such.\n   */\n  provideTextDocumentContent(uri: URI, token: CancellationToken): ProviderResult<string>\n}\n\nexport interface SelectionRangeProvider {\n  /**\n   * Provide selection ranges starting at a given position. The first range must [contain](#Range.contains)\n   * position and subsequent ranges must contain the previous range.\n   */\n  provideSelectionRanges(document: TextDocument, positions: Position[], token: CancellationToken): ProviderResult<SelectionRange[]>\n}\n\n/**\n * The call hierarchy provider interface describes the contract between extensions\n * and the call hierarchy feature which allows to browse calls and caller of function,\n * methods, constructor etc.\n */\nexport interface CallHierarchyProvider {\n\n  /**\n   * Bootstraps call hierarchy by returning the item that is denoted by the given document\n   * and position. This item will be used as entry into the call graph. Providers should\n   * return `undefined` or `null` when there is no item at the given location.\n   * @param document The document in which the command was invoked.\n   * @param position The position at which the command was invoked.\n   * @param token A cancellation token.\n   * @returns A call hierarchy item or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined` or `null`.\n   */\n  prepareCallHierarchy(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<CallHierarchyItem | CallHierarchyItem[]>\n\n  /**\n   * Provide all incoming calls for an item, e.g all callers for a method. In graph terms this describes directed\n   * and annotated edges inside the call graph, e.g the given item is the starting node and the result is the nodes\n   * that can be reached.\n   * @param item The hierarchy item for which incoming calls should be computed.\n   * @param token A cancellation token.\n   * @returns A set of incoming calls or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined` or `null`.\n   */\n  provideCallHierarchyIncomingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult<CallHierarchyIncomingCall[]>\n\n  /**\n   * Provide all outgoing calls for an item, e.g call calls to functions, methods, or constructors from the given item. In\n   * graph terms this describes directed and annotated edges inside the call graph, e.g the given item is the starting\n   * node and the result is the nodes that can be reached.\n   * @param item The hierarchy item for which outgoing calls should be computed.\n   * @param token A cancellation token.\n   * @returns A set of outgoing calls or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined` or `null`.\n   */\n  provideCallHierarchyOutgoingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult<CallHierarchyOutgoingCall[]>\n}\n\n/**\n * The document semantic tokens provider interface defines the contract between extensions and\n * semantic tokens.\n */\nexport interface DocumentSemanticTokensProvider {\n  /**\n   * An optional event to signal that the semantic tokens from this provider have changed.\n   */\n  onDidChangeSemanticTokens?: Event<void>\n\n  /**\n   * Tokens in a file are represented as an array of integers. The position of each token is expressed relative to\n   * the token before it, because most tokens remain stable relative to each other when edits are made in a file.\n   *\n   * ---\n   * In short, each token takes 5 integers to represent, so a specific token `i` in the file consists of the following array indices:\n   *\n   * - at index `5*i`   - `deltaLine`: token line number, relative to the previous token\n   * - at index `5*i+1` - `deltaStart`: token start character, relative to the previous token (relative to 0 or the previous token's start if they are on the same line)\n   * - at index `5*i+2` - `length`: the length of the token. A token cannot be multiline.\n   * - at index `5*i+3` - `tokenType`: will be looked up in `SemanticTokensLegend.tokenTypes`. We currently ask that `tokenType` < 65536.\n   * - at index `5*i+4` - `tokenModifiers`: each set bit will be looked up in `SemanticTokensLegend.tokenModifiers`\n   *\n   * ---\n   * ### How to encode tokens\n   *\n   * Here is an example for encoding a file with 3 tokens in a uint32 array:\n   * ```\n   * { line: 2, startChar:  5, length: 3, tokenType: \"property\",  tokenModifiers: [\"private\", \"static\"] },\n   * { line: 2, startChar: 10, length: 4, tokenType: \"type\",      tokenModifiers: [] },\n   * { line: 5, startChar:  2, length: 7, tokenType: \"class\",     tokenModifiers: [] }\n   * ```\n   *\n   * 1. First of all, a legend must be devised. This legend must be provided up-front and capture all possible token types.\n   * For this example, we will choose the following legend which must be passed in when registering the provider:\n   * ```\n   * tokenTypes: ['property', 'type', 'class'],\n   * tokenModifiers: ['private', 'static']\n   * ```\n   *\n   * 2. The first transformation step is to encode `tokenType` and `tokenModifiers` as integers using the legend. Token types are looked\n   * up by index, so a `tokenType` value of `1` means `tokenTypes[1]`. Multiple token modifiers can be set by using bit flags,\n   * so a `tokenModifier` value of `3` is first viewed as binary `0b00000011`, which means `[tokenModifiers[0], tokenModifiers[1]]` because\n   * bits 0 and 1 are set. Using this legend, the tokens now are:\n   * ```\n   * { line: 2, startChar:  5, length: 3, tokenType: 0, tokenModifiers: 3 },\n   * { line: 2, startChar: 10, length: 4, tokenType: 1, tokenModifiers: 0 },\n   * { line: 5, startChar:  2, length: 7, tokenType: 2, tokenModifiers: 0 }\n   * ```\n   *\n   * 3. The next step is to represent each token relative to the previous token in the file. In this case, the second token\n   * is on the same line as the first token, so the `startChar` of the second token is made relative to the `startChar`\n   * of the first token, so it will be `10 - 5`. The third token is on a different line than the second token, so the\n   * `startChar` of the third token will not be altered:\n   * ```\n   * { deltaLine: 2, deltaStartChar: 5, length: 3, tokenType: 0, tokenModifiers: 3 },\n   * { deltaLine: 0, deltaStartChar: 5, length: 4, tokenType: 1, tokenModifiers: 0 },\n   * { deltaLine: 3, deltaStartChar: 2, length: 7, tokenType: 2, tokenModifiers: 0 }\n   * ```\n   *\n   * 4. Finally, the last step is to inline each of the 5 fields for a token in a single array, which is a memory friendly representation:\n   * ```\n   * // 1st token,  2nd token,  3rd token\n   * [  2,5,3,0,3,  0,5,4,1,0,  3,2,7,2,0 ]\n   * ```\n   * @see [SemanticTokensBuilder](#SemanticTokensBuilder) for a helper to encode tokens as integers.\n   * *NOTE*: When doing edits, it is possible that multiple edits occur until VS Code decides to invoke the semantic tokens provider.\n   * *NOTE*: If the provider cannot temporarily compute semantic tokens, it can indicate this by throwing an error with the message 'Busy'.\n   */\n  provideDocumentSemanticTokens(document: TextDocument, token: CancellationToken): ProviderResult<SemanticTokens>\n\n  /**\n   * Instead of always returning all the tokens in a file, it is possible for a `DocumentSemanticTokensProvider` to implement\n   * this method (`provideDocumentSemanticTokensEdits`) and then return incremental updates to the previously provided semantic tokens.\n   *\n   * ---\n   * ### How tokens change when the document changes\n   *\n   * Suppose that `provideDocumentSemanticTokens` has previously returned the following semantic tokens:\n   * ```\n   *    // 1st token,  2nd token,  3rd token\n   *    [  2,5,3,0,3,  0,5,4,1,0,  3,2,7,2,0 ]\n   * ```\n   *\n   * Also suppose that after some edits, the new semantic tokens in a file are:\n   * ```\n   *    // 1st token,  2nd token,  3rd token\n   *    [  3,5,3,0,3,  0,5,4,1,0,  3,2,7,2,0 ]\n   * ```\n   * It is possible to express these new tokens in terms of an edit applied to the previous tokens:\n   * ```\n   *    [  2,5,3,0,3,  0,5,4,1,0,  3,2,7,2,0 ] // old tokens\n   *    [  3,5,3,0,3,  0,5,4,1,0,  3,2,7,2,0 ] // new tokens\n   *\n   *    edit: { start:  0, deleteCount: 1, data: [3] } // replace integer at offset 0 with 3\n   * ```\n   *\n   * *NOTE*: If the provider cannot compute `SemanticTokensEdits`, it can \"give up\" and return all the tokens in the document again.\n   * *NOTE*: All edits in `SemanticTokensEdits` contain indices in the old integers array, so they all refer to the previous result state.\n   */\n  provideDocumentSemanticTokensEdits?(document: TextDocument, previousResultId: string, token: CancellationToken): ProviderResult<SemanticTokens | SemanticTokensDelta>\n}\n\n/**\n * The document range semantic tokens provider interface defines the contract between extensions and\n * semantic tokens.\n */\nexport interface DocumentRangeSemanticTokensProvider {\n  /**\n   * @see [provideDocumentSemanticTokens](#DocumentSemanticTokensProvider.provideDocumentSemanticTokens).\n   */\n  provideDocumentRangeSemanticTokens(document: TextDocument, range: Range, token: CancellationToken): ProviderResult<SemanticTokens>\n}\n\nexport interface LinkedEditingRangeProvider {\n  /**\n   * For a given position in a document, returns the range of the symbol at the position and all ranges\n   * that have the same content. A change to one of the ranges can be applied to all other ranges if the new content\n   * is valid. An optional word pattern can be returned with the result to describe valid contents.\n   * If no result-specific word pattern is provided, the word pattern from the language configuration is used.\n   * @param document The document in which the provider was invoked.\n   * @param position The position at which the provider was invoked.\n   * @param token A cancellation token.\n   * @return A list of ranges that can be edited together\n   */\n  provideLinkedEditingRanges(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<LinkedEditingRanges>\n}\n\n/**\n * The inlay hints provider interface defines the contract between extensions and\n * the inlay hints feature.\n */\nexport interface InlayHintsProvider<T extends InlayHint = InlayHint> {\n\n  /**\n   * An optional event to signal that inlay hints from this provider have changed.\n   */\n  onDidChangeInlayHints?: Event<void>\n\n  /**\n   * Provide inlay hints for the given range and document.\n   *\n   * *Note* that inlay hints that are not {@link Range.contains contained} by the given range are ignored.\n   * @param document The document in which the command was invoked.\n   * @param range The range for which inlay hints should be computed.\n   * @param token A cancellation token.\n   * @return An array of inlay hints or a thenable that resolves to such.\n   */\n  provideInlayHints(document: TextDocument, range: Range, token: CancellationToken): ProviderResult<T[]>\n\n  /**\n   * Given an inlay hint fill in {@link InlayHint.tooltip tooltip}, {@link InlayHint.textEdits text edits},\n   * or complete label {@link InlayHintLabelPart parts}.\n   *\n   * *Note* that the editor will resolve an inlay hint at most once.\n   * @param hint An inlay hint.\n   * @param token A cancellation token.\n   * @return The resolved inlay hint or a thenable that resolves to such. It is OK to return the given `item`. When no result is returned, the given `item` will be used.\n   */\n  resolveInlayHint?(hint: T, token: CancellationToken): ProviderResult<T>\n}\n\n/**\n * The type hierarchy provider interface describes the contract between extensions\n * and the type hierarchy feature.\n */\nexport interface TypeHierarchyProvider {\n\n  /**\n   * Bootstraps type hierarchy by returning the item that is denoted by the given document\n   * and position. This item will be used as entry into the type graph. Providers should\n   * return `undefined` or `null` when there is no item at the given location.\n   * @param document The document in which the command was invoked.\n   * @param position The position at which the command was invoked.\n   * @param token A cancellation token.\n   * @returns One or multiple type hierarchy items or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined`, `null`, or an empty array.\n   */\n  prepareTypeHierarchy(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<TypeHierarchyItem[]>\n\n  /**\n   * Provide all supertypes for an item, e.g all types from which a type is derived/inherited. In graph terms this describes directed\n   * and annotated edges inside the type graph, e.g the given item is the starting node and the result is the nodes\n   * that can be reached.\n   * @param item The hierarchy item for which super types should be computed.\n   * @param token A cancellation token.\n   * @returns A set of direct supertypes or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined` or `null`.\n   */\n  provideTypeHierarchySupertypes(item: TypeHierarchyItem, token: CancellationToken): ProviderResult<TypeHierarchyItem[]>\n\n  /**\n   * Provide all subtypes for an item, e.g all types which are derived/inherited from the given item. In\n   * graph terms this describes directed and annotated edges inside the type graph, e.g the given item is the starting\n   * node and the result is the nodes that can be reached.\n   * @param item The hierarchy item for which subtypes should be computed.\n   * @param token A cancellation token.\n   * @returns A set of direct subtypes or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined` or `null`.\n   */\n  provideTypeHierarchySubtypes(item: TypeHierarchyItem, token: CancellationToken): ProviderResult<TypeHierarchyItem[]>\n}\n\n/**\n * The inline values provider interface defines the contract between extensions and the editor's debugger inline values feature.\n * In this contract the provider returns inline value information for a given document range\n * and the editor shows this information in the editor at the end of lines.\n */\nexport interface InlineValuesProvider {\n\n  /**\n   * An optional event to signal that inline values have changed.\n   * @see {@link EventEmitter}\n   */\n  onDidChangeInlineValues?: Event<void> | undefined\n\n  /**\n   * Provide \"inline value\" information for a given document and range.\n   * The editor calls this method whenever debugging stops in the given document.\n   * The returned inline values information is rendered in the editor at the end of lines.\n   * @param document The document for which the inline values information is needed.\n   * @param viewPort The visible document range for which inline values should be computed.\n   * @param context A bag containing contextual information like the current location.\n   * @param token A cancellation token.\n   * @return An array of InlineValueDescriptors or a thenable that resolves to such. The lack of a result can be\n   * signaled by returning `undefined` or `null`.\n   */\n  provideInlineValues(document: TextDocument, viewPort: Range, context: InlineValueContext, token: CancellationToken): ProviderResult<InlineValue[]>\n}\n\nexport interface ResultReporter {\n  (chunk: WorkspaceDiagnosticReportPartialResult | null): void\n}\n\nexport interface DiagnosticProvider {\n  onDidChangeDiagnostics: Event<void> | undefined\n  provideDiagnostics(document: TextDocument | URI, previousResultId: string | undefined, token: CancellationToken): ProviderResult<DocumentDiagnosticReport>\n  provideWorkspaceDiagnostics?(resultIds: PreviousResultId[], token: CancellationToken, resultReporter: ResultReporter): ProviderResult<WorkspaceDiagnosticReport>\n}\n"
  },
  {
    "path": "src/provider/inlayHintManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { InlayHint, Position, Range } from 'vscode-languageserver-types'\nimport { createLogger } from '../logger'\nimport { comparePosition, positionInRange } from '../util/position'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { DocumentSelector, InlayHintsProvider } from './index'\nimport Manager from './manager'\nconst logger = createLogger('inlayHintManger')\n\nexport interface InlayHintWithProvider extends InlayHint {\n  providerId: string\n  resolved?: boolean\n}\n\nexport default class InlayHintManger extends Manager<InlayHintsProvider> {\n\n  public register(selector: DocumentSelector, provider: InlayHintsProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider\n    })\n  }\n\n  /**\n   * Multiple providers can be registered for a language. In that case providers are asked in\n   * parallel and the results are merged. A failing provider (rejected promise or exception) will\n   * not cause a failure of the whole operation.\n   */\n  public async provideInlayHints(\n    document: TextDocument,\n    range: Range,\n    token: CancellationToken\n  ): Promise<InlayHintWithProvider[]> {\n    let items = this.getProviders(document)\n    let inlayHints: InlayHintWithProvider[] = []\n    let results = await Promise.allSettled(items.map(async item => {\n      let { id, provider } = item\n      let hints = await Promise.resolve(provider.provideInlayHints(document, range, token))\n      if (!Array.isArray(hints) || token.isCancellationRequested) return\n      let noCheck = inlayHints.length == 0\n      for (let hint of hints) {\n        if (!isValidInlayHint(hint, range)) continue\n        if (!noCheck && inlayHints.findIndex(o => sameHint(o, hint)) != -1) continue\n        inlayHints.push({ providerId: id, ...hint })\n      }\n    }))\n    this.handleResults(results, 'provideInlayHints', token)\n    return inlayHints\n  }\n\n  public async resolveInlayHint(hint: InlayHintWithProvider, token: CancellationToken): Promise<InlayHintWithProvider> {\n    let provider = this.getProviderById(hint.providerId)\n    if (!provider || typeof provider.resolveInlayHint !== 'function' || hint.resolved === true) return hint\n    let res = await Promise.resolve(provider.resolveInlayHint(hint, token))\n    if (token.isCancellationRequested) return hint\n    return Object.assign(hint, res, { resolved: true })\n  }\n}\n\nexport function sameHint(one: InlayHint, other: InlayHint): boolean {\n  if (comparePosition(one.position, other.position) !== 0) return false\n  return getLabel(one) === getLabel(other)\n}\n\nexport function isInlayHint(obj: any): obj is InlayHint {\n  if (!obj || !Position.is(obj.position) || obj.label == null) return false\n  if (typeof obj.label !== 'string') return Array.isArray(obj.label) && obj.label.every(p => typeof p.value === 'string')\n  return true\n}\n\nexport function isValidInlayHint(hint: InlayHint, range: Range): boolean {\n  if (hint.label.length === 0 || (Array.isArray(hint.label) && hint.label.every(part => part.value.length === 0))) {\n    logger.warn('INVALID inlay hint, empty label', hint)\n    return false\n  }\n  if (!isInlayHint(hint)) {\n    logger.warn('INVALID inlay hint', hint)\n    return false\n  }\n  if (range && positionInRange(hint.position, range) !== 0) {\n    // console.log('INVALID inlay hint, position outside range', range, hint);\n    return false\n  }\n  return true\n}\n\nexport function getLabel(hint: InlayHint): string {\n  if (typeof hint.label === 'string') return hint.label\n  return hint.label.map(o => o.value).join('')\n}\n"
  },
  {
    "path": "src/provider/inlineCompletionItemManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { InlineCompletionContext, InlineCompletionItem, Position } from 'vscode-languageserver-types'\nimport { toArray } from '../util/array'\nimport { onUnexpectedError } from '../util/errors'\nimport { omit } from '../util/lodash'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { DocumentSelector, InlineCompletionItemProvider } from './index'\nimport Manager, { ProviderItem } from './manager'\n\nexport interface ExtendedInlineContext extends InlineCompletionContext {\n  provider?: string\n}\n\nexport default class InlineCompletionItemManager extends Manager<InlineCompletionItemProvider> {\n  public register(selector: DocumentSelector, provider: InlineCompletionItemProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider\n    })\n  }\n\n  public get isEmpty(): boolean {\n    return this.providers.size === 0\n  }\n\n  /**\n   * Multiple providers can be registered for a language. In that case providers are asked in\n   * parallel and the results are merged. A failing provider (rejected promise or exception) will\n   * not cause a failure of the whole operation.\n   */\n  public async provideInlineCompletionItems(\n    document: TextDocument,\n    position: Position,\n    context: ExtendedInlineContext,\n    token: CancellationToken\n  ): Promise<InlineCompletionItem[]> {\n    let providers: ProviderItem<InlineCompletionItemProvider>[] = []\n    if (context.provider) {\n      let item = this.getProvideByExtension(document, context.provider)\n      if (item) providers = [item]\n    } else {\n      providers = this.getProviders(document)\n    }\n    const items: InlineCompletionItem[] = []\n    const promise = Promise.allSettled(providers.map(item => {\n      let provider = item.provider\n      return Promise.resolve(provider.provideInlineCompletionItems(document, position, omit(context, ['provider']), token)).then(result => {\n        let list = Array.isArray(result) ? result : toArray(result?.items)\n        for (let item of list) {\n          Object.defineProperty(item, 'provider', {\n            get: () => provider['__extensionName'],\n            enumerable: false\n          })\n          items.push(item)\n        }\n      })\n    }))\n    let disposable: Disposable\n    await Promise.race([new Promise(resolve => {\n      disposable = token.onCancellationRequested(() => {\n        resolve(undefined)\n      })\n    }), promise.then(results => {\n      if (!token.isCancellationRequested) this.handleResults(results, 'provideInlineCompletionItems')\n    })]).catch(onUnexpectedError)\n    if (disposable) disposable.dispose()\n    return items\n  }\n}\n"
  },
  {
    "path": "src/provider/inlineValueManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { InlineValue, InlineValueContext, Range } from 'vscode-languageserver-types'\nimport { equals } from '../util/object'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { DocumentSelector, InlineValuesProvider } from './index'\nimport Manager from './manager'\n\nexport default class InlineValueManager extends Manager<InlineValuesProvider> {\n\n  public register(selector: DocumentSelector, provider: InlineValuesProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider\n    })\n  }\n\n  /**\n   * Multiple providers can be registered for a language. In that case providers are asked in\n   * parallel and the results are merged. A failing provider (rejected promise or exception) will\n   * not cause a failure of the whole operation.\n   */\n  public async provideInlineValues(document: TextDocument, viewPort: Range, context: InlineValueContext, token: CancellationToken): Promise<InlineValue[]> {\n    const items = this.getProviders(document)\n    const values: InlineValue[] = []\n    const results = await Promise.allSettled(items.map(item => {\n      return Promise.resolve(item.provider.provideInlineValues(document, viewPort, context, token)).then(arr => {\n        if (!Array.isArray(arr)) return\n        let noCheck = values.length === 0\n        for (let value of arr) {\n          if (noCheck || values.every(o => !equals(o, value))) {\n            values.push(value)\n          }\n        }\n      })\n    }))\n    this.handleResults(results, 'provideInlineValues')\n    return values\n  }\n}\n"
  },
  {
    "path": "src/provider/linkedEditingRangeManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport type { CancellationToken, Disposable, DocumentSelector, LinkedEditingRanges } from 'vscode-languageserver-protocol'\nimport type { TextDocument } from 'vscode-languageserver-textdocument'\nimport type { Position } from 'vscode-languageserver-types'\nimport { createLogger } from '../logger'\nimport { LinkedEditingRangeProvider } from './index'\nimport Manager from './manager'\nconst logger = createLogger('linkedEditingManager')\n\nexport default class LinkedEditingRangeManager extends Manager<LinkedEditingRangeProvider> {\n  public register(selector: DocumentSelector, provider: LinkedEditingRangeProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider\n    })\n  }\n\n  /**\n   * Multiple providers can be registered for a language. In that case providers are sorted\n   * by their {@link workspace.match score} and the best-matching provider that has a result is used. Failure\n   * of the selected provider will cause a failure of the whole operation.\n   */\n  public async provideLinkedEditingRanges(document: TextDocument, position: Position, token: CancellationToken): Promise<LinkedEditingRanges> {\n    let items = this.getProviders(document)\n    for (let item of items) {\n      let res = await Promise.resolve(item.provider.provideLinkedEditingRanges(document, position, token))\n      if (res != null) return res\n    }\n    return null\n  }\n}\n"
  },
  {
    "path": "src/provider/manager.ts",
    "content": "'use strict'\nimport { Location, LocationLink } from 'vscode-languageserver-types'\nimport { createLogger } from '../logger'\nimport { LocationWithTarget, TextDocumentMatch } from '../types'\nimport { isCancellationError, shouldIgnore } from '../util/errors'\nimport { parseExtensionName } from '../util/extensionRegistry'\nimport { equals } from '../util/object'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { toText } from '../util/string'\nimport workspace from '../workspace'\nimport { DocumentSelector } from './index'\nconst logger = createLogger('provider-manager')\n\nexport type ProviderItem<T extends object, P = object> = {\n  id: string\n  selector: DocumentSelector\n  provider: T\n  priority?: number\n} & P\n\nexport default class Manager<T extends object, P = object> {\n  protected providers: Set<ProviderItem<T, P>> = new Set()\n\n  public hasProvider(document: TextDocumentMatch): boolean {\n    return this.getProvider(document) != null\n  }\n\n  protected addProvider(item: ProviderItem<T, P>): Disposable {\n    if (!item.provider.hasOwnProperty('__extensionName')) {\n      Error.captureStackTrace(item)\n      let name: string\n      Object.defineProperty(item.provider, '__extensionName', {\n        get: () => {\n          if (name) return name\n          name = parseExtensionName(toText(item['stack']))\n          return name\n        },\n        enumerable: true\n      })\n    }\n    this.providers.add(item)\n    return Disposable.create(() => {\n      this.providers.delete(item)\n    })\n  }\n\n  protected handleResults(results: PromiseSettledResult<void>[], name: string, token?: CancellationToken): void {\n    let serverCancelError: Error\n    results.forEach(res => {\n      if (res.status === 'rejected') {\n        if (!shouldIgnore(res.reason)) logger.error(`Provider error on ${name}:`, res.reason)\n        if (token && !token.isCancellationRequested && isCancellationError(res.reason)) {\n          serverCancelError = res.reason\n        }\n      }\n    })\n    if (serverCancelError) throw serverCancelError\n  }\n\n  protected getProvider(document: TextDocumentMatch): ProviderItem<T, P> {\n    let currScore = 0\n    let providerItem: ProviderItem<T, P>\n    for (let item of this.providers) {\n      let { selector, priority } = item\n      let score = workspace.match(selector, document)\n      if (score == 0) continue\n      if (typeof priority == 'number' && priority > 0) {\n        score = score + priority\n      }\n      if (score < currScore) continue\n      currScore = score\n      providerItem = item\n    }\n    return providerItem\n  }\n\n  public getProvideByExtension(document: TextDocumentMatch, extension: string): ProviderItem<T, P> {\n    for (let item of this.providers) {\n      if (item.provider['__extensionName'] === extension) {\n        return item\n      }\n    }\n    logger.warn(`User-specified formatter not found for ${document.languageId}:`, extension)\n    return undefined\n  }\n\n  protected getFormatProvider(document: TextDocumentMatch): ProviderItem<T, P> {\n    // Prefer user choice\n    const userChoice = workspace.getConfiguration('coc.preferences', document).get<string>('formatterExtension')\n    if (userChoice) {\n      let provider = this.getProvideByExtension(document, userChoice)\n      if (provider) return provider\n    }\n    return this.getProvider(document)\n  }\n\n  protected getProviderById(id: string): T {\n    let item = Array.from(this.providers).find(o => o.id == id)\n    return item ? item.provider : null\n  }\n\n  public getProviders(document: TextDocumentMatch): ProviderItem<T, P>[] {\n    let items = Array.from(this.providers)\n    items = items.filter(item => workspace.match(item.selector, document) > 0)\n    return items.sort((a, b) => workspace.match(b.selector, document) - workspace.match(a.selector, document))\n  }\n\n  public addLocation(locations: LocationWithTarget[], location: Location | Location[] | LocationLink[] | null | undefined): void {\n    if (Array.isArray(location)) {\n      for (let loc of location) {\n        if (Location.is(loc)) {\n          addLocation(locations, loc)\n        } else if (loc && typeof loc.targetUri === 'string') {\n          addLocation(locations, loc)\n        }\n      }\n    } else if (Location.is(location)) {\n      addLocation(locations, location)\n    }\n  }\n}\n\n/**\n * Add unique location\n */\nfunction addLocation(arr: LocationWithTarget[], location: Location | LocationLink): void {\n  if (Location.is(location)) {\n    let { range, uri } = location\n    if (arr.find(o => o.uri == uri && equals(o.range, range)) != null) return\n    arr.push(location)\n  } else if (location && typeof location.targetUri === 'string') {\n    let { targetUri, targetSelectionRange, targetRange } = location\n    if (arr.find(o => o.uri == targetUri && equals(o.range, targetSelectionRange)) != null) return\n    arr.push({\n      uri: targetUri,\n      range: targetSelectionRange,\n      targetRange\n    })\n  }\n}\n"
  },
  {
    "path": "src/provider/onTypeFormatManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { Position, TextEdit } from 'vscode-languageserver-types'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport workspace from '../workspace'\nimport type { OnTypeFormattingEditProvider, DocumentSelector } from './index'\nimport Manager from './manager'\n\nexport interface ProviderMeta {\n  triggerCharacters: string[]\n}\n\nexport default class OnTypeFormatManager extends Manager<OnTypeFormattingEditProvider, ProviderMeta> {\n\n  public register(selector: DocumentSelector, provider: OnTypeFormattingEditProvider, triggerCharacters: string[] | undefined): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider,\n      triggerCharacters: triggerCharacters ?? []\n    })\n  }\n\n  public couldTrigger(document: TextDocument, triggerCharacter: string): OnTypeFormattingEditProvider | null {\n    for (let o of this.providers) {\n      let { triggerCharacters, selector } = o\n      if (workspace.match(selector, document) > 0 && triggerCharacters.includes(triggerCharacter)) {\n        return o.provider\n      }\n    }\n    return null\n  }\n\n  public async onCharacterType(character: string, document: TextDocument, position: Position, token: CancellationToken): Promise<TextEdit[] | null> {\n    let items = this.getProviders(document)\n    let item = items.find(o => o.triggerCharacters.includes(character))\n    if (!item) return null\n    let formatOpts = await workspace.getFormatOptions(document.uri)\n    return await Promise.resolve(item.provider.provideOnTypeFormattingEdits(document, position, character, formatOpts, token))\n  }\n}\n"
  },
  {
    "path": "src/provider/referenceManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport type { CancellationToken, Disposable, DocumentSelector, Position, ReferenceContext } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { LocationWithTarget } from '../types'\nimport type { ReferenceProvider } from './index'\nimport Manager from './manager'\n\nexport default class ReferenceManager extends Manager<ReferenceProvider>  {\n\n  public register(selector: DocumentSelector, provider: ReferenceProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider\n    })\n  }\n\n  public async provideReferences(\n    document: TextDocument,\n    position: Position,\n    context: ReferenceContext,\n    token: CancellationToken\n  ): Promise<LocationWithTarget[]> {\n    const providers = this.getProviders(document)\n    let locations: LocationWithTarget[] = []\n    const results = await Promise.allSettled(providers.map(item => {\n      return Promise.resolve(item.provider.provideReferences(document, position, context, token)).then(location => {\n        this.addLocation(locations, location)\n      })\n    }))\n    this.handleResults(results, 'provideReferences')\n    return locations\n  }\n}\n"
  },
  {
    "path": "src/provider/renameManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { Position, Range, WorkspaceEdit } from 'vscode-languageserver-types'\nimport type { CancellationToken, Disposable } from '../util/protocol'\nimport { RenameProvider, DocumentSelector } from './index'\nimport Manager from './manager'\n\nexport default class RenameManager extends Manager<RenameProvider> {\n\n  public register(selector: DocumentSelector, provider: RenameProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider\n    })\n  }\n\n  /**\n   * Multiple providers can be registered for a language. In that case providers are sorted\n   * by their {@link workspace.match score} and asked in sequence. The first provider producing a result\n   * defines the result of the whole operation.\n   */\n  public async provideRenameEdits(\n    document: TextDocument,\n    position: Position,\n    newName: string,\n    token: CancellationToken\n  ): Promise<WorkspaceEdit | null> {\n    let items = this.getProviders(document)\n    let edit: WorkspaceEdit = null\n    for (const item of items) {\n      try {\n        edit = await Promise.resolve(item.provider.provideRenameEdits(document, position, newName, token))\n      } catch (e) {\n        this.handleResults([{ status: 'rejected', reason: e }], 'provideRenameEdits')\n      }\n      if (edit != null) break\n    }\n    return edit\n  }\n\n  public async prepareRename(\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken\n  ): Promise<Range | { range: Range; placeholder: string } | false> {\n    let items = this.getProviders(document)\n    items = items.filter(o => typeof o.provider.prepareRename === 'function')\n    if (items.length === 0) return null\n    for (const item of items) {\n      let res = await Promise.resolve(item.provider.prepareRename(document, position, token))\n      // can rename\n      if (res != null) return res\n    }\n    return false\n  }\n}\n"
  },
  {
    "path": "src/provider/selectionRangeManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { Position, SelectionRange } from 'vscode-languageserver-types'\nimport { equals } from '../util/object'\nimport { rangeInRange } from '../util/position'\nimport type { CancellationToken, Disposable } from '../util/protocol'\nimport { DocumentSelector, SelectionRangeProvider } from './index'\nimport Manager from './manager'\n\nexport default class SelectionRangeManager extends Manager<SelectionRangeProvider>  {\n\n  public register(selector: DocumentSelector, provider: SelectionRangeProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider\n    })\n  }\n\n  /**\n   * Multiple providers can be registered for a language. In that case providers are asked in\n   * parallel and the results are merged. A failing provider (rejected promise or exception) will\n   * not cause a failure of the whole operation.\n   */\n  public async provideSelectionRanges(\n    document: TextDocument,\n    positions: Position[],\n    token: CancellationToken\n  ): Promise<SelectionRange[] | null> {\n    let items = this.getProviders(document)\n    if (items.length === 0) return null\n    let selectionRangeResult: SelectionRange[][] = []\n    let results = await Promise.allSettled(items.map(item => {\n      return Promise.resolve(item.provider.provideSelectionRanges(document, positions, token)).then(ranges => {\n        if (Array.isArray(ranges) && ranges.length > 0) {\n          selectionRangeResult.push(ranges)\n        }\n      })\n    }))\n    this.handleResults(results, 'provideSelectionRanges')\n    if (selectionRangeResult.length === 0) return null\n    let selectionRanges = selectionRangeResult[0]\n    // concat ranges when possible\n    if (selectionRangeResult.length > 1) {\n      for (let i = 1; i <= selectionRangeResult.length - 1; i++) {\n        let start = selectionRanges[0].range\n        let end = selectionRanges[selectionRanges.length - 1].range\n        let ranges = selectionRangeResult[i]\n        let len = ranges.length\n        if (rangeInRange(end, ranges[0].range) && !equals(end, ranges[0].range)) {\n          selectionRanges.push(...ranges)\n        } else if (rangeInRange(ranges[len - 1].range, start) && !equals(ranges[len - 1].range, start)) {\n          selectionRanges.unshift(...ranges)\n        }\n      }\n    }\n    for (let i = 0; i < selectionRanges.length - 1; i++) {\n      let r = selectionRanges[i]\n      r.parent = selectionRanges[i + 1]\n    }\n    return selectionRanges\n  }\n}\n"
  },
  {
    "path": "src/provider/semanticTokensManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { SemanticTokens, SemanticTokensDelta, SemanticTokensLegend } from 'vscode-languageserver-types'\nimport type { CancellationToken, Disposable } from '../util/protocol'\nimport { DocumentSemanticTokensProvider, DocumentSelector } from './index'\nimport Manager from './manager'\n\ninterface ProviderMeta {\n  legend: SemanticTokensLegend\n}\n\nexport default class SemanticTokensManager extends Manager<DocumentSemanticTokensProvider, ProviderMeta> {\n\n  public register(selector: DocumentSelector, provider: DocumentSemanticTokensProvider, legend: SemanticTokensLegend): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider,\n      legend,\n    })\n  }\n\n  public getLegend(document: TextDocument): SemanticTokensLegend {\n    const item = this.getProvider(document)\n    if (!item) return\n    return item.legend\n  }\n\n  public hasSemanticTokensEdits(document: TextDocument): boolean {\n    let provider = this.getProvider(document)?.provider\n    if (!provider) return false\n    return (typeof provider.provideDocumentSemanticTokensEdits === 'function')\n  }\n\n  public async provideDocumentSemanticTokens(document: TextDocument, token: CancellationToken): Promise<SemanticTokens | null> {\n    let provider = this.getProvider(document)?.provider\n    if (!provider || typeof provider.provideDocumentSemanticTokens !== 'function') return null\n    return await Promise.resolve(provider.provideDocumentSemanticTokens(document, token))\n  }\n\n  public async provideDocumentSemanticTokensEdits(document: TextDocument, previousResultId: string, token: CancellationToken): Promise<SemanticTokens | SemanticTokensDelta | null> {\n    let item = this.getProvider(document)\n    if (!item || typeof item.provider.provideDocumentSemanticTokensEdits !== 'function') return null\n    return await Promise.resolve(item.provider.provideDocumentSemanticTokensEdits(document, previousResultId, token))\n  }\n}\n"
  },
  {
    "path": "src/provider/semanticTokensRangeManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { Range, SemanticTokens, SemanticTokensLegend } from 'vscode-languageserver-types'\nimport type { CancellationToken, Disposable } from '../util/protocol'\nimport { DocumentRangeSemanticTokensProvider, DocumentSelector } from './index'\nimport Manager from './manager'\n\ninterface ProviderMeta {\n  legend: SemanticTokensLegend\n}\n\nexport default class SemanticTokensRangeManager extends Manager<DocumentRangeSemanticTokensProvider, ProviderMeta> {\n  public register(selector: DocumentSelector, provider: DocumentRangeSemanticTokensProvider, legend: SemanticTokensLegend): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      legend,\n      provider\n    })\n  }\n\n  public getLegend(document: TextDocument): SemanticTokensLegend {\n    const item = this.getProvider(document)\n    if (!item) return\n    return item.legend\n  }\n\n  public async provideDocumentRangeSemanticTokens(document: TextDocument, range: Range, token: CancellationToken): Promise<SemanticTokens> {\n    let item = this.getProvider(document)\n    if (!item) return null\n    let { provider } = item\n    return await Promise.resolve(provider.provideDocumentRangeSemanticTokens(document, range, token))\n  }\n}\n"
  },
  {
    "path": "src/provider/signatureManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport type { SignatureHelpContext } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { Position, SignatureHelp } from 'vscode-languageserver-types'\nimport { isFalsyOrEmpty } from '../util/array'\nimport type { CancellationToken, Disposable } from '../util/protocol'\nimport { DocumentSelector, SignatureHelpProvider } from './index'\nimport Manager from './manager'\n\ninterface ProviderMeta {\n  triggerCharacters: string[] | undefined\n}\n\nexport default class SignatureManager extends Manager<SignatureHelpProvider, ProviderMeta> {\n\n  public register(selector: DocumentSelector, provider: SignatureHelpProvider, triggerCharacters: string[] | undefined): Disposable {\n    triggerCharacters = isFalsyOrEmpty(triggerCharacters) ? [] : triggerCharacters\n    let characters = triggerCharacters.reduce((p, c) => p.concat(c.length == 1 ? [c] : c.split(/\\s*/g)), [] as string[])\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider,\n      triggerCharacters: characters\n    })\n  }\n\n  public shouldTrigger(document: TextDocument, triggerCharacter: string): boolean {\n    let items = this.getProviders(document)\n    if (items.length === 0) return false\n    for (let item of items) {\n      if (item.triggerCharacters.includes(triggerCharacter)) {\n        return true\n      }\n    }\n    return false\n  }\n\n  /**\n   * Multiple providers can be registered for a language. In that case providers are sorted\n   * by their {@link languages.match score} and called sequentially until a provider returns a\n   * valid result.\n   */\n  public async provideSignatureHelp(\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken,\n    context: SignatureHelpContext\n  ): Promise<SignatureHelp | null> {\n    let items = this.getProviders(document)\n    for (const item of items) {\n      let res = await Promise.resolve(item.provider.provideSignatureHelp(document, position, token, context))\n      if (res && res.signatures && res.signatures.length > 0) return res\n    }\n    return null\n  }\n}\n"
  },
  {
    "path": "src/provider/typeDefinitionManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { Position } from 'vscode-languageserver-types'\nimport { LocationWithTarget } from '../types'\nimport type { CancellationToken, Disposable } from '../util/protocol'\nimport { TypeDefinitionProvider, DocumentSelector } from './index'\nimport Manager from './manager'\n\nexport default class TypeDefinitionManager extends Manager<TypeDefinitionProvider> {\n\n  public register(selector: DocumentSelector, provider: TypeDefinitionProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider\n    })\n  }\n\n  public async provideTypeDefinition(\n    document: TextDocument,\n    position: Position,\n    token: CancellationToken\n  ): Promise<LocationWithTarget[]> {\n    const providers = this.getProviders(document)\n    let locations: LocationWithTarget[] = []\n    const results = await Promise.allSettled(providers.map(item => {\n      return Promise.resolve(item.provider.provideTypeDefinition(document, position, token)).then(location => {\n        this.addLocation(locations, location)\n      })\n    }))\n    this.handleResults(results, 'provideTypeDefinition')\n    return locations\n  }\n}\n"
  },
  {
    "path": "src/provider/typeHierarchyManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { Position, TypeHierarchyItem } from 'vscode-languageserver-types'\nimport { omit } from '../util/lodash'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { TypeHierarchyProvider, DocumentSelector } from './index'\nimport Manager from './manager'\n\nexport interface TypeHierarchyItemWithSource extends TypeHierarchyItem {\n  source?: string\n}\n\nconst excludeKeys = ['source']\n\nexport default class TypeHierarchyManager extends Manager<TypeHierarchyProvider> {\n\n  public register(selector: DocumentSelector, provider: TypeHierarchyProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector,\n      provider\n    })\n  }\n\n  /**\n   * Multiple providers can be registered for a language. In that case providers are asked in\n   * parallel and the results are merged. A failing provider (rejected promise or exception) will\n   * not cause a failure of the whole operation.\n   */\n  public async prepareTypeHierarchy(document: TextDocument, position: Position, token: CancellationToken): Promise<TypeHierarchyItem[]> {\n    const items = this.getProviders(document)\n    let hierarchyItems: TypeHierarchyItemWithSource[] = []\n    let results = await Promise.allSettled(items.map(item => {\n      let { provider, id } = item\n      return (async () => {\n        let arr = await Promise.resolve(provider.prepareTypeHierarchy(document, position, token))\n        if (Array.isArray(arr)) {\n          let noCheck = hierarchyItems.length === 0\n          arr.forEach(item => {\n            if (noCheck || hierarchyItems.every(o => o.name !== item.name)) {\n              hierarchyItems.push(Object.assign({ source: id }, item))\n            }\n          })\n        }\n      })()\n    }))\n    this.handleResults(results, 'prepareTypeHierarchy')\n    return hierarchyItems\n  }\n\n  public async provideTypeHierarchySupertypes(item: TypeHierarchyItemWithSource, token: CancellationToken): Promise<TypeHierarchyItem[]> {\n    let { source } = item\n    const provider = this.getProviderById(source)\n    if (!provider) return []\n    return await Promise.resolve(provider.provideTypeHierarchySupertypes(omit(item, excludeKeys), token)).then(arr => {\n      if (Array.isArray(arr)) {\n        return arr.map(item => {\n          return Object.assign({ source }, item)\n        })\n      }\n      return []\n    })\n  }\n\n  public async provideTypeHierarchySubtypes(item: TypeHierarchyItemWithSource, token: CancellationToken): Promise<TypeHierarchyItem[]> {\n    let { source } = item\n    const provider = this.getProviderById(source)\n    if (!provider) return []\n    return await Promise.resolve(provider.provideTypeHierarchySubtypes(omit(item, excludeKeys), token)).then(arr => {\n      if (Array.isArray(arr)) {\n        return arr.map(item => {\n          return Object.assign({ source }, item)\n        })\n      }\n      return []\n    })\n  }\n}\n"
  },
  {
    "path": "src/provider/workspaceSymbolsManager.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { WorkspaceSymbol } from 'vscode-languageserver-types'\nimport type { CancellationToken, Disposable } from '../util/protocol'\nimport { WorkspaceSymbolProvider } from './index'\nimport Manager from './manager'\n\ninterface WorkspaceSymbolWithSource extends WorkspaceSymbol {\n  source?: string\n}\n\nexport default class WorkspaceSymbolManager extends Manager<WorkspaceSymbolProvider> {\n  public register(provider: WorkspaceSymbolProvider): Disposable {\n    return this.addProvider({\n      id: uuid(),\n      selector: [{ language: '*' }],\n      provider\n    })\n  }\n\n  public async provideWorkspaceSymbols(\n    query: string,\n    token: CancellationToken\n  ): Promise<WorkspaceSymbol[]> {\n    let entries = Array.from(this.providers)\n    let infos: WorkspaceSymbol[] = []\n    let results = await Promise.allSettled(entries.map(o => {\n      let { id, provider } = o\n      return Promise.resolve(provider.provideWorkspaceSymbols(query, token)).then(list => {\n        if (Array.isArray(list)) {\n          infos.push(...list.map(item => Object.assign({ source: id }, item)))\n        }\n      })\n    }))\n    this.handleResults(results, 'provideWorkspaceSymbols')\n    return infos\n  }\n\n  public async resolveWorkspaceSymbol(\n    symbolInfo: WorkspaceSymbolWithSource,\n    token: CancellationToken\n  ): Promise<WorkspaceSymbol> {\n    let provider = this.getProviderById(symbolInfo.source)\n    if (!provider || typeof provider.resolveWorkspaceSymbol !== 'function') return symbolInfo\n    return provider.resolveWorkspaceSymbol(symbolInfo, token)\n  }\n\n  public hasProvider(): boolean {\n    return this.providers.size > 0\n  }\n}\n"
  },
  {
    "path": "src/services.ts",
    "content": "'use strict'\nimport { SpawnOptions } from 'child_process'\nimport type { DocumentSelector } from 'vscode-languageserver-protocol'\nimport type { TextDocument } from 'vscode-languageserver-textdocument'\nimport type { WorkspaceFolder } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport events from './events'\nimport { Executable, ForkOptions, LanguageClient, LanguageClientOptions, RevealOutputChannelOn, ServerOptions, State, Transport, TransportKind } from './language-client'\nimport { createLogger } from './logger'\nimport { disposeAll, wait } from './util'\nimport { toArray } from './util/array'\nimport { fs, net, path } from './util/node'\nimport { toObject } from './util/object'\nimport { CancellationToken, Disposable, Emitter, Event } from './util/protocol'\nimport window from './window'\nimport workspace from './workspace'\nconst logger = createLogger('services')\n\nexport enum ServiceStat {\n  Initial,\n  Starting,\n  StartFailed,\n  Running,\n  Stopping,\n  Stopped,\n}\n\ninterface ServiceInfo {\n  id: string\n  state: string\n  languageIds: string[]\n}\n\nexport interface LanguageServerConfig {\n  module?: string\n  command?: string\n  transport?: string\n  transportPort?: number\n  maxRestartCount?: number\n  disableSnippetCompletion?: boolean\n  disableDynamicRegister?: boolean\n  disabledFeatures?: string[]\n  formatterPriority?: number\n  filetypes: string[]\n  additionalSchemes?: string[]\n  enable?: boolean\n  args?: string[]\n  cwd?: string\n  env?: any\n  // socket port\n  port?: number\n  host?: string\n  detached?: boolean\n  shell?: boolean\n  execArgv?: string[]\n  rootPatterns?: string[]\n  requireRootPattern?: boolean\n  ignoredRootPaths?: string[]\n  initializationOptions?: any\n  progressOnInitialization?: boolean\n  revealOutputChannelOn?: string\n  configSection?: string\n  stdioEncoding?: string\n  runtime?: string\n}\n\nexport interface IServiceProvider {\n  // unique service id\n  id: string\n  name: string\n  client?: LanguageClient\n  selector: DocumentSelector\n  // current state\n  state: ServiceStat\n  start(): Promise<void> | void\n  dispose(): void\n  stop(): Promise<void> | void\n  restart(): Promise<void> | void\n  onServiceReady: Event<void>\n}\n\nexport interface NotificationItem {\n  id: string\n  method: string\n}\n\nclass ServiceManager implements Disposable {\n  private readonly registered: Map<string, IServiceProvider> = new Map()\n  private disposables: Disposable[] = []\n  private pendingNotifications: Map<string, NotificationItem[]> = new Map()\n  /**\n   * @deprecated\n   */\n  public regist\n  /**\n   * @deprecated\n   */\n  public registLanguageClient\n\n  constructor() {\n    this.registLanguageClient = this.registerLanguageClient.bind(this)\n    this.regist = this.register.bind(this)\n  }\n\n  public init(): void {\n    workspace.onDidOpenTextDocument(document => {\n      void this.start(document)\n    }, null, this.disposables)\n    const iterate = (folders: Iterable<WorkspaceFolder>) => {\n      for (let folder of folders) {\n        this.registerClientsFromFolder(folder)\n      }\n    }\n    workspace.onDidChangeWorkspaceFolders(e => {\n      iterate(e.added)\n    }, null, this.disposables)\n    // `languageserver.${name}`\n    // Global configured languageserver\n    let lspConfig = workspace.initialConfiguration.get<{ key: LanguageServerConfig }>('languageserver', {} as any)\n    this.registerClientsByConfig(lspConfig)\n    iterate(workspace.workspaceFolders)\n    workspace.onDidChangeConfiguration(e => {\n      if (e.affectsConfiguration('languageserver')) {\n        let lspConfig = workspace.getConfiguration('languageserver', null)\n        this.registerClientsByConfig(lspConfig)\n      }\n    }, null, this.disposables)\n  }\n\n  private registerClientsFromFolder(workspaceFolder: WorkspaceFolder): void {\n    let uri = URI.parse(workspaceFolder.uri)\n    let lspConfig = workspace.getConfiguration(undefined, uri)\n    let config = lspConfig.inspect('languageserver').workspaceFolderValue\n    this.registerClientsByConfig(config as { [key: string]: LanguageServerConfig }, uri)\n  }\n\n  public register(service: IServiceProvider): Disposable {\n    let { id } = service\n    if (this.registered.get(id)) return\n    this.registered.set(id, service)\n    this.tryStartService(service)\n    service.onServiceReady(() => {\n      logger.info(`service ${id} started`)\n    }, null, this.disposables)\n    return Disposable.create(() => {\n      if (!this.registered.has(id)) return\n      service.dispose()\n      this.registered.delete(id)\n    })\n  }\n\n  public tryStartService(service: IServiceProvider): void {\n    if (!events.ready) {\n      let disposable = events.on('ready', () => {\n        disposable.dispose()\n        if (this.shouldStart(service)) {\n          void service.start()\n        }\n      })\n    } else if (this.shouldStart(service)) {\n      void service.start()\n    }\n  }\n\n  public getService(id: string): IServiceProvider {\n    let service = this.registered.get(id)\n    if (!service) service = this.registered.get(`languageserver.${id}`)\n    return service\n  }\n\n  private shouldStart(service: IServiceProvider): boolean {\n    if (service.state != ServiceStat.Initial) return false\n    let selector = service.selector\n    for (let doc of workspace.documents) {\n      if (workspace.match(selector, doc.textDocument)) {\n        return true\n      }\n    }\n    return false\n  }\n\n  public async start(document: TextDocument): Promise<void> {\n    let services: IServiceProvider[] = []\n    for (let service of this.registered.values()) {\n      if (service.state == ServiceStat.Initial && workspace.match(service.selector, document) > 0) {\n        services.push(service)\n      }\n    }\n    // eslint-disable-next-line @typescript-eslint/await-thenable\n    await Promise.allSettled(services.map(service => {\n      return service.start()\n    }))\n  }\n\n  public stop(id: string): Promise<void> {\n    let service = this.registered.get(id)\n    if (service) return Promise.resolve(service.stop())\n  }\n\n  public async toggle(id: string): Promise<void> {\n    let service = this.registered.get(id)\n    if (!service) throw new Error(`Service ${id} not found`)\n    let { state } = service\n    if (state == ServiceStat.Running) {\n      await Promise.resolve(service.stop())\n    } else if (state == ServiceStat.Initial || state == ServiceStat.StartFailed) {\n      await service.start()\n    } else if (state == ServiceStat.Stopped) {\n      await service.restart()\n    }\n  }\n\n  public getServiceStats(): ServiceInfo[] {\n    let res: ServiceInfo[] = []\n    for (let [id, service] of this.registered) {\n      res.push({\n        id,\n        languageIds: documentSelectorToLanguageIds(service.selector),\n        state: getStateName(service.state)\n      })\n    }\n    return res\n  }\n\n  private registerClientsByConfig(lspConfig: { [key: string]: LanguageServerConfig }, folder?: URI): void {\n    for (let key of Object.keys(toObject(lspConfig))) {\n      let config: LanguageServerConfig = lspConfig[key]\n      if (!isValidServerConfig(key, config)) {\n        continue\n      }\n      this.registerLanguageClient(key, config, folder)\n    }\n  }\n\n  private async getLanguageClient(id: string): Promise<LanguageClient> {\n    let service = this.getService(id)\n    // wait for extension activate\n    if (!service) await wait(100)\n    service = this.getService(id)\n    if (!service || !service.client) {\n      throw new Error(`Language server ${id} not found`)\n    }\n    return service.client\n  }\n\n  public async sendNotification(id: string, method: string, params?: any): Promise<void> {\n    let client = await this.getLanguageClient(id)\n    await Promise.resolve(client.sendNotification(method, params))\n  }\n\n  public async sendRequest(id: string, method: string, params?: any, token?: CancellationToken): Promise<any> {\n    let client = await this.getLanguageClient(id)\n    token = token ?? CancellationToken.None\n    return await Promise.resolve(client.sendRequest(method, params, token))\n  }\n\n  public registerNotification(id: string, method: string): void {\n    let service = this.getService(id)\n    if (service && service.client) {\n      service.client.onNotification(method, async result => {\n        this.sendNotificationVim(id, method, result)\n      })\n    }\n    let arr = this.pendingNotifications.get(id) ?? []\n    arr.push({ id, method })\n    this.pendingNotifications.set(id, arr)\n  }\n\n  private getRegisteredNotifications(id: string): NotificationItem[] {\n    id = id.startsWith('languageserver') ? id.slice('languageserver.'.length) : id\n    return this.pendingNotifications.get(id) ?? []\n  }\n\n  private sendNotificationVim(id: string, method: string, result: any): void {\n    workspace.nvim.call('coc#do_notify', [id, method, result], true)\n  }\n\n  public registerLanguageClient(client: LanguageClient): Disposable\n  public registerLanguageClient(name: string, config: LanguageServerConfig, folder?: URI): Disposable\n  public registerLanguageClient(name: string | LanguageClient, config?: LanguageServerConfig, folder?: URI): Disposable {\n    let id = typeof name === 'string' ? `languageserver.${name}` : name.id\n    let disposables: Disposable[] = []\n    let onDidServiceReady = new Emitter<void>()\n    let client: LanguageClient | null = typeof name === 'string' ? null : name\n    if (this.registered.has(id)) return Disposable.create(() => {})\n    if (client && typeof client.dispose === 'function') disposables.push(client)\n    let created = false\n    let service: IServiceProvider = {\n      id,\n      client,\n      name: typeof name === 'string' ? name : name.name,\n      selector: typeof name === 'string' ? getDocumentSelector(config.filetypes, config.additionalSchemes) : name.clientOptions.documentSelector,\n      state: client && client.state === State.Running ? ServiceStat.Running : ServiceStat.Initial,\n      onServiceReady: onDidServiceReady.event,\n      start: async (): Promise<void> => {\n        if (!created) {\n          if (typeof name == 'string' && !client) {\n            let config: LanguageServerConfig = workspace.getConfiguration(undefined, folder).get(`languageserver.${name}`, {} as any)\n            let opts = getLanguageServerOptions(id, name, config, folder)\n            if (!opts || config.enable === false) return\n            client = new LanguageClient(id, name, opts[1], opts[0])\n            service.selector = opts[0].documentSelector\n            service.client = client\n            disposables.push(client)\n          }\n          created = true\n          for (let item of this.getRegisteredNotifications(id)) {\n            service.client.onNotification(item.method, async result => {\n              this.sendNotificationVim(item.id, item.method, result)\n            })\n          }\n          client.onDidChangeState(changeEvent => {\n            let { oldState, newState } = changeEvent\n            service.state = convertState(newState)\n            let oldStr = stateString(oldState)\n            let newStr = stateString(newState)\n            logger.info(`LanguageClient ${client.name} state change: ${oldStr} => ${newStr}`)\n          }, null, disposables)\n        }\n        try {\n          if (!client.needsStart()) {\n            service.state = convertState(client.state)\n          } else {\n            service.state = ServiceStat.Starting\n            logger.debug(`starting service: ${id}`)\n            await client.start()\n            onDidServiceReady.fire(void 0)\n          }\n        } catch (e) {\n          void window.showErrorMessage(`Server ${id} failed to start: ${e}`)\n          logger.error(`Server ${id} failed to start:`, e)\n          service.state = ServiceStat.StartFailed\n        }\n      },\n      dispose: async () => {\n        onDidServiceReady.dispose()\n        disposeAll(disposables)\n      },\n      stop: async (): Promise<void> => {\n        if (!client || !client.needsStop()) return\n        await Promise.resolve(client.stop())\n      },\n      restart: async (): Promise<void> => {\n        if (client) {\n          service.state = ServiceStat.Starting\n          await client.restart()\n        } else {\n          await service.start()\n        }\n      },\n    }\n    return this.register(service)\n  }\n\n  public dispose(): void {\n    disposeAll(this.disposables)\n    for (let service of this.registered.values()) {\n      service.dispose()\n    }\n    this.registered.clear()\n  }\n}\n\nexport function documentSelectorToLanguageIds(documentSelector: DocumentSelector): string[] {\n  let res = documentSelector.map(filter => {\n    if (typeof filter == 'string') {\n      return filter\n    }\n    return filter.language\n  })\n  res = res.filter(s => typeof s == 'string')\n  return Array.from(new Set(res))\n}\n\n// convert config to options\nexport function getLanguageServerOptions(id: string, name: string, config: Readonly<LanguageServerConfig>, folder?: URI): [LanguageClientOptions, ServerOptions] {\n  let { command, module, port, args, filetypes } = config\n  args = args || []\n  if (!filetypes) {\n    void window.showErrorMessage(`Wrong configuration of LS \"${name}\", filetypes not found`)\n    return null\n  }\n  if (!command && !module && !port) {\n    void window.showErrorMessage(`Wrong configuration of LS \"${name}\", no command or module specified.`)\n    return null\n  }\n  let serverOptions: ServerOptions\n  if (module) {\n    module = workspace.expand(module)\n    if (!fs.existsSync(module)) {\n      void window.showErrorMessage(`Module file \"${module}\" not found for LS \"${name}\"`)\n      return null\n    }\n    serverOptions = {\n      module,\n      runtime: config.runtime ?? process.execPath,\n      args,\n      transport: getTransportKind(config),\n      options: getForkOptions(config)\n    }\n  } else if (command) {\n    serverOptions = {\n      command,\n      args,\n      options: getSpawnOptions(config)\n    } as Executable\n  } else {\n    serverOptions = () => new Promise((resolve, reject) => {\n      let client = new net.Socket()\n      let host = config.host ?? '127.0.0.1'\n      logger.info(`languageserver \"${id}\" connecting to ${host}:${port}`)\n      client.connect(port, host, () => {\n        resolve({\n          reader: client,\n          writer: client\n        })\n      })\n      client.on('error', e => {\n        reject(new Error(`Connection error for ${id}: ${e.message}`))\n      })\n    })\n  }\n  // compatible\n  let disabledFeatures: string[] = Array.from(config.disabledFeatures || [])\n  for (let key of ['disableWorkspaceFolders', 'disableCompletion', 'disableDiagnostics']) {\n    if (config[key] === true) {\n      logger.warn(`Language server config \"${key}\" is deprecated, use \"disabledFeatures\" instead.`)\n      let s = key.slice(7)\n      disabledFeatures.push(s[0].toLowerCase() + s.slice(1))\n    }\n  }\n  let disableSnippetCompletion = !!config.disableSnippetCompletion\n  let ignoredRootPaths = toArray(config.ignoredRootPaths)\n  let clientOptions: LanguageClientOptions = {\n    workspaceFolder: folder == null ? undefined : { name: path.basename(folder.fsPath), uri: folder.toString() },\n    rootPatterns: config.rootPatterns,\n    requireRootPattern: config.requireRootPattern,\n    ignoredRootPaths: ignoredRootPaths.map(s => workspace.expand(s)),\n    disableSnippetCompletion,\n    disableDynamicRegister: !!config.disableDynamicRegister,\n    disabledFeatures,\n    formatterPriority: config.formatterPriority,\n    documentSelector: getDocumentSelector(config.filetypes, config.additionalSchemes),\n    revealOutputChannelOn: getRevealOutputChannelOn(config.revealOutputChannelOn),\n    synchronize: {\n      configurationSection: `${id}.settings`\n    },\n    diagnosticCollectionName: name,\n    outputChannelName: id,\n    stdioEncoding: config.stdioEncoding,\n    progressOnInitialization: config.progressOnInitialization === true,\n    initializationOptions: config.initializationOptions ?? {}\n  }\n  if (config.maxRestartCount) {\n    clientOptions.connectionOptions = { maxRestartCount: config.maxRestartCount }\n  }\n  return [clientOptions, serverOptions]\n}\n\nexport function isValidServerConfig(key: string, config: Partial<LanguageServerConfig>): boolean {\n  let errors: string[] = []\n  let fields = ['module', 'command', 'transport']\n  for (let field of fields) {\n    let val = config[field]\n    if (val && typeof val !== 'string') {\n      errors.push(`\"${field}\" field of languageserver ${key} should be string`)\n    }\n  }\n  if (config.transportPort != null && typeof config.transportPort !== 'number') {\n    errors.push(`\"transportPort\" field of languageserver ${key} should be number`)\n  }\n  if (!Array.isArray(config.filetypes) || !config.filetypes.every(s => typeof s === 'string')) {\n    errors.push(`\"filetypes\" field of languageserver ${key} should be array of string`)\n  }\n  if (config.additionalSchemes && (!Array.isArray(config.additionalSchemes) || config.additionalSchemes.some(s => typeof s !== 'string'))) {\n    errors.push(`\"additionalSchemes\" field of languageserver ${key} should be array of string`)\n  }\n  if (errors.length) {\n    logger.error(`Invalid language server configuration for ${key}`, errors.join('\\n'))\n    return false\n  }\n  return true\n}\n\nexport function getRevealOutputChannelOn(revealOn: string | undefined): RevealOutputChannelOn {\n  switch (revealOn) {\n    case 'info':\n      return RevealOutputChannelOn.Info\n    case 'warn':\n      return RevealOutputChannelOn.Warn\n    case 'error':\n      return RevealOutputChannelOn.Error\n    case 'never':\n      return RevealOutputChannelOn.Never\n    default:\n      return RevealOutputChannelOn.Never\n  }\n}\n\nexport function getDocumentSelector(filetypes: string[] | undefined, additionalSchemes?: string[]): DocumentSelector {\n  let documentSelector: DocumentSelector = []\n  let schemes = ['file', 'untitled'].concat(additionalSchemes || [])\n  if (!filetypes) return schemes.map(s => ({ scheme: s }))\n  filetypes.forEach(filetype => {\n    documentSelector.push(...schemes.map(scheme => ({ language: filetype, scheme })))\n  })\n  return documentSelector\n}\n\nexport function getTransportKind(config: LanguageServerConfig): Transport {\n  let { transport, transportPort } = config\n  if (!transport || transport == 'ipc') return TransportKind.ipc\n  if (transport == 'stdio') return TransportKind.stdio\n  if (transport == 'pipe') return TransportKind.pipe\n  return { kind: TransportKind.socket, port: transportPort }\n}\n\nexport function getForkOptions(config: LanguageServerConfig): ForkOptions {\n  return {\n    cwd: config.cwd && workspace.expand(config.cwd),\n    execArgv: config.execArgv ?? [],\n    env: config.env ?? undefined\n  }\n}\n\nexport function getSpawnOptions(config: LanguageServerConfig): SpawnOptions {\n  return {\n    cwd: config.cwd && workspace.expand(config.cwd),\n    detached: !!config.detached,\n    shell: process.platform === 'win32' || !!config.shell,\n    env: config.env ?? undefined\n  }\n}\n\nexport function convertState(state: State): ServiceStat {\n  switch (state) {\n    case State.Running:\n      return ServiceStat.Running\n    case State.Starting:\n      return ServiceStat.Starting\n    case State.Stopped:\n      return ServiceStat.Stopped\n    default:\n      return undefined\n  }\n}\n\nexport function stateString(state: State): string {\n  switch (state) {\n    case State.Running:\n      return 'running'\n    case State.Starting:\n      return 'starting'\n    case State.Stopped:\n      return 'stopped'\n    case State.StartFailed:\n      return 'startFailed'\n    default:\n      return 'unknown'\n  }\n}\n\nexport function getStateName(state: ServiceStat): string {\n  switch (state) {\n    case ServiceStat.Initial:\n      return 'init'\n    case ServiceStat.Running:\n      return 'running'\n    case ServiceStat.Starting:\n      return 'starting'\n    case ServiceStat.StartFailed:\n      return 'startFailed'\n    case ServiceStat.Stopping:\n      return 'stopping'\n    case ServiceStat.Stopped:\n      return 'stopped'\n    default:\n      return 'unknown'\n  }\n}\n\nexport default new ServiceManager()\n"
  },
  {
    "path": "src/snippets/eval.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { Range } from 'vscode-languageserver-types'\nimport events from '../events'\nimport { UltiSnippetOption } from '../types'\nimport { isVim } from '../util/constants'\nimport { UltiSnippetContext } from './util'\nexport type EvalKind = 'vim' | 'python' | 'shell'\n\nconst contexts_var = '__coc_ultisnip_contexts'\n\nlet context_id = 1\n\nexport function generateContextId(bufnr: number): string {\n  return `${bufnr}-${context_id++}`\n}\n\nexport function hasPython(snip?: UltiSnippetContext | UltiSnippetOption): boolean {\n  if (!snip) return false\n  if (snip.context) return true\n  if (snip.actions && Object.keys(snip.actions).length > 0) return true\n  return false\n}\n\nexport function getResetPythonCode(context: UltiSnippetContext): string[] {\n  const pyCodes: string[] = []\n  pyCodes.push(`${contexts_var} = ${contexts_var} if '${contexts_var}' in locals() else {}`)\n  pyCodes.push(`context = ${contexts_var}.get('${context.id}', {}).get('context', None)`)\n  pyCodes.push(`match = ${contexts_var}.get('${context.id}', {}).get('match', None)`)\n  return pyCodes\n}\n\nexport function getPyBlockCode(snip: UltiSnippetContext): string[] {\n  let { range, line } = snip\n  let pyCodes: string[] = [\n    'import re, os, vim, string, random',\n    `path = vim.eval('coc#util#get_fullpath()') or \"\"`,\n    `fn = os.path.basename(path)`,\n  ]\n  let start = `(${range.start.line},${range.start.character})`\n  let end = `(${range.start.line},${range.end.character})`\n  let indent = line.match(/^\\s*/)[0]\n  pyCodes.push(...getResetPythonCode(snip))\n  pyCodes.push(`snip = SnippetUtil(\"${escapeString(indent)}\", ${start}, ${end}, context)`)\n  return pyCodes\n}\n\nexport function getInitialPythonCode(context: UltiSnippetContext): string[] {\n  let pyCodes: string[] = [\n    'import re, os, vim, string, random',\n    `path = vim.eval('coc#util#get_fullpath()') or \"\"`,\n    `fn = os.path.basename(path)`,\n  ]\n  let { range, regex, line, id } = context\n  if (context.context) {\n    pyCodes.push(`snip = ContextSnippet()`)\n    pyCodes.push(`context = ${context.context}`)\n  } else {\n    pyCodes.push(`context = None`)\n  }\n  if (regex && Range.is(range)) {\n    let trigger = line.slice(range.start.character, range.end.character)\n    pyCodes.push(`pattern = re.compile(\"${escapeString(regex)}\")`)\n    pyCodes.push(`match = pattern.search(\"${escapeString(trigger)}\")`)\n  } else {\n    pyCodes.push(`match = None`)\n  }\n  // save 'context and 'match' for synchronize and actions.\n  pyCodes.push(`${contexts_var} = ${contexts_var} if '${contexts_var}' in locals() else {}`)\n  let prefix = id.match(/^\\w+-/)[0]\n  // keep context of current buffer only.\n  pyCodes.push(`${contexts_var} = {k: v for k, v in ${contexts_var}.items() if k.startswith('${prefix}')}`)\n  pyCodes.push(`${contexts_var}['${context.id}'] = {'context': context, 'match': match}`)\n  return pyCodes\n}\n\nexport async function executePythonCode(nvim: Neovim, codes: string[]) {\n  if (codes.length == 0) return\n  let lines = [...codes]\n  lines.unshift(`__requesting = ${events.requesting ? 'True' : 'False'}`)\n  try {\n    await nvim.command(`pyx ${addPythonTryCatch(lines.join('\\n'))}`)\n  } catch (e: any) {\n    let err = new Error(e.message)\n    err.stack = `Error on execute python code:\\n${codes.join('\\n')}\\n` + e.stack\n    throw err\n  }\n}\n\nexport function getVariablesCode(values: { [index: number]: string }): string {\n  let keys = Object.keys(values)\n  if (keys.length == 0) return `t = ()`\n  let maxIndex = Math.max.apply(null, keys.map(v => Number(v)))\n  let vals = (new Array(maxIndex)).fill('\"\"')\n  for (let [idx, val] of Object.entries(values)) {\n    vals[idx] = `\"${escapeString(val)}\"`\n  }\n  return `t = (${vals.join(',')},)`\n}\n\n/**\n * vim8 doesn't throw any python error with :py command\n * we have to use g:errmsg since v:errmsg can't be changed in python script.\n */\nexport function addPythonTryCatch(code: string, force = false): string {\n  if (!isVim && force === false) return code\n  let lines = [\n    'import traceback, vim',\n    `vim.vars['errmsg'] = ''`,\n    'try:',\n  ]\n  lines.push(...code.split('\\n').map(line => '    ' + line))\n  lines.push('except Exception as e:')\n  lines.push(`    vim.vars['errmsg'] = traceback.format_exc()`)\n  return lines.join('\\n')\n}\n\nexport function escapeString(input: string): string {\n  return input\n    .replace(/\\\\/g, '\\\\\\\\')\n    .replace(/\"/g, '\\\\\"')\n    .replace(/\\t/g, '\\\\t')\n    .replace(/\\n/g, '\\\\n')\n}\n"
  },
  {
    "path": "src/snippets/manager.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { InsertTextMode, Position, Range, TextEdit } from 'vscode-languageserver-types'\nimport commands from '../commands'\nimport events from '../events'\nimport BufferSync from '../model/bufferSync'\nimport { StatusBarItem } from '../model/status'\nimport { UltiSnippetOption } from '../types'\nimport { defaultValue, disposeAll } from '../util'\nimport { deepClone } from '../util/object'\nimport { emptyRange, toValidRange } from '../util/position'\nimport { Disposable } from '../util/protocol'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { executePythonCode, generateContextId, getInitialPythonCode, hasPython } from './eval'\nimport { SnippetConfig, SnippetEdit, SnippetSession } from './session'\nimport { SnippetString } from './string'\nimport { getAction, normalizeSnippetString, shouldFormat, SnippetFormatOptions, toSnippetString, UltiSnippetContext } from './util'\n\nexport class SnippetManager {\n  private disposables: Disposable[] = []\n  private _statusItem: StatusBarItem\n  private bufferSync: BufferSync<SnippetSession>\n  private config: SnippetConfig\n\n  public init() {\n    this.synchronizeConfig()\n    workspace.onDidChangeConfiguration(e => {\n      if (e.affectsConfiguration('snippet') || e.affectsConfiguration('suggest')) {\n        this.synchronizeConfig()\n      }\n    }, null, this.disposables)\n    events.on(['InsertCharPre', 'Enter'], () => {\n      let session = this.session\n      if (session) session.cancel()\n    }, null, this.disposables)\n    events.on('CompleteDone', async (_item, _line, bufnr) => {\n      let session = this.bufferSync.getItem(bufnr)\n      if (session) await session.onCompleteDone()\n    }, null, this.disposables)\n    events.on('CompleteStart', async opt => {\n      let session = this.bufferSync.getItem(opt.bufnr)\n      if (session) session.cancel(true)\n    }, null, this.disposables)\n    events.on('InsertEnter', async bufnr => {\n      let session = this.bufferSync.getItem(bufnr)\n      if (session) await session.checkPosition()\n    }, null, this.disposables)\n\n    this.bufferSync = workspace.registerBufferSync(doc => {\n      let session = new SnippetSession(this.nvim, doc, this.config)\n      session.onActiveChange(isActive => {\n        if (events.bufnr !== session.bufnr) return\n        this.statusItem[isActive ? 'show' : 'hide']()\n      })\n      return session\n    })\n    this.disposables.push(this.bufferSync)\n\n    window.onDidChangeActiveTextEditor(async e => {\n      let session = this.bufferSync.getItem(e.bufnr)\n      if (session && session.isActive) {\n        this.statusItem.show()\n        if (!session.selected) {\n          await session.selectCurrentPlaceholder()\n        }\n      } else {\n        this.statusItem.hide()\n      }\n    }, null, this.disposables)\n\n    commands.register({\n      id: 'editor.action.insertSnippet',\n      execute: async (edit: TextEdit, ultisnip?: UltiSnippetOption | true) => {\n        const opts = ultisnip === true ? {} : ultisnip\n        return await this.insertSnippet(edit.newText, true, edit.range, InsertTextMode.adjustIndentation, opts ? opts : undefined)\n      }\n    }, true)\n    commands.register({\n      id: 'editor.action.insertBufferSnippets',\n      execute: async (bufnr: number, edits: SnippetEdit[], select: boolean) => {\n        return await this.insertBufferSnippets(bufnr, edits, select)\n      }\n    }, true)\n  }\n\n  private get nvim(): Neovim {\n    return workspace.nvim\n  }\n\n  private get statusItem(): StatusBarItem {\n    if (this._statusItem) return this._statusItem\n    const snippetConfig = workspace.initialConfiguration.get('snippet') as any\n    const statusItem = this._statusItem = window.createStatusBarItem(0)\n    statusItem.text = defaultValue(snippetConfig.statusText, '')\n    return this._statusItem\n  }\n\n  private synchronizeConfig(): void {\n    const snippetConfig = workspace.getConfiguration('snippet', null)\n    const suggest = workspace.getConfiguration('suggest', null)\n    let obj = {\n      highlight: defaultValue(snippetConfig.inspect('highlight').globalValue, false) as boolean,\n      nextOnDelete: defaultValue(snippetConfig.inspect('nextPlaceholderOnDelete').globalValue, false) as boolean,\n      preferComplete: suggest.get<boolean>('preferCompleteThanJumpPlaceholder', false)\n    }\n    if (this.config) {\n      Object.assign(this.config, obj)\n    } else {\n      this.config = obj\n    }\n  }\n\n  private async toRange(range: Range | undefined): Promise<Range> {\n    if (range) return toValidRange(range)\n    let pos = await window.getCursorPosition()\n    return Range.create(pos, pos)\n  }\n\n  public async insertBufferSnippets(bufnr: number, edits: SnippetEdit[], select = false): Promise<boolean> {\n    let document = workspace.getAttachedDocument(bufnr)\n    const session = this.bufferSync.getItem(bufnr)\n    session.cancel(true)\n    let snippetEdits: SnippetEdit[] = []\n    for (const edit of edits) {\n      let currentLine = document.getline(edit.range.start.line)\n      let inserted = await this.normalizeInsertText(bufnr, toSnippetString(edit.snippet), currentLine, InsertTextMode.asIs)\n      snippetEdits.push({ range: edit.range, snippet: inserted })\n    }\n    await session.synchronize()\n    let isActive = await session.insertSnippetEdits(snippetEdits)\n    if (isActive && select && workspace.bufnr === bufnr) {\n      await session.selectCurrentPlaceholder()\n    }\n    return isActive\n  }\n\n  /**\n   * Insert snippet to specific buffer, ultisnips not supported, and the placeholder is not selected\n   */\n  public async insertBufferSnippet(bufnr: number, snippet: string | SnippetString, range: Range, insertTextMode?: InsertTextMode): Promise<boolean> {\n    let document = workspace.getAttachedDocument(bufnr)\n    const session = this.bufferSync.getItem(bufnr)\n    session.cancel(true)\n    range = toValidRange(range)\n    const line = document.getline(range.start.line)\n    const snippetStr = toSnippetString(snippet)\n    const inserted = await this.normalizeInsertText(document.bufnr, snippetStr, line, insertTextMode)\n    await session.synchronize()\n    return await session.start(inserted, range, false)\n  }\n\n  /**\n   * Insert snippet at current cursor position\n   */\n  public async insertSnippet(snippet: string | SnippetString, select = true, range?: Range, insertTextMode?: InsertTextMode, ultisnip?: UltiSnippetOption): Promise<boolean> {\n    let { nvim } = workspace\n    let document = workspace.getAttachedDocument(workspace.bufnr)\n    const session = this.bufferSync.getItem(document.bufnr)\n    let context: UltiSnippetContext\n    session.cancel(true)\n    range = await this.toRange(range)\n    const currentLine = document.getline(range.start.line)\n    const snippetStr = toSnippetString(snippet)\n    const inserted = await this.normalizeInsertText(document.bufnr, snippetStr, currentLine, insertTextMode, ultisnip)\n    if (ultisnip != null) {\n      const usePy = hasPython(ultisnip) || inserted.includes('`!p')\n      const bufnr = document.bufnr\n      context = Object.assign({ range: deepClone(range), line: currentLine }, ultisnip, { id: generateContextId(bufnr) })\n      if (usePy) {\n        if (session.placeholder) {\n          let { start, end } = session.placeholder.range\n          let last = {\n            current_text: session.placeholder.value,\n            start: { line: start.line, col: start.character },\n            end: { line: end.line, col: end.character }\n          }\n          this.nvim.setVar('coc_last_placeholder', last, true)\n        } else {\n          this.nvim.call('coc#compat#del_var', ['coc_last_placeholder'], true)\n        }\n        const codes = getInitialPythonCode(context)\n        let preExpand = getAction(ultisnip, 'preExpand')\n        if (preExpand) {\n          nvim.call('coc#cursor#move_to', [range.end.line, range.end.character], true)\n          await executePythonCode(nvim, codes.concat(['snip = coc_ultisnips_dict[\"PreExpandContext\"]()', preExpand]))\n          const [valid, pos] = await nvim.call('pyxeval', 'snip.getResult()') as [boolean, [number, number]]\n          // need remove the trigger\n          if (valid) {\n            let count = range.end.character - range.start.character\n            range = Range.create(pos[0], Math.max(0, pos[1] - count), pos[0], pos[1])\n          } else {\n            // trigger removed already\n            range = Range.create(pos[0], pos[1], pos[0], pos[1])\n          }\n        } else {\n          await executePythonCode(nvim, codes)\n        }\n      }\n    }\n    // same behavior as Ultisnips\n    const noMove = ultisnip == null && !session.isActive\n    if (!noMove) {\n      const { start } = range\n      nvim.call('coc#cursor#move_to', [start.line, start.character], true)\n      // range could outside snippet range when session synchronize is canceled\n      if (!emptyRange(range)) {\n        await document.applyEdits([TextEdit.del(range)])\n      }\n      if (session.isActive) {\n        await session.synchronize()\n        // the cursor position could be changed on session synchronize.\n        let pos = await window.getCursorPosition()\n        range = Range.create(pos, pos)\n      } else {\n        range.end = Position.create(start.line, start.character)\n      }\n    }\n    await session.start(inserted, range, select, context)\n    return session.isActive\n  }\n\n  public async selectCurrentPlaceholder(triggerAutocmd = true): Promise<void> {\n    let { session } = this\n    if (session) return await session.selectCurrentPlaceholder(triggerAutocmd)\n  }\n\n  public async nextPlaceholder(): Promise<string> {\n    let { session } = this\n    if (session) {\n      await session.nextPlaceholder()\n    } else {\n      this.nvim.call('coc#snippet#disable', [], true)\n    }\n    return ''\n  }\n\n  public async previousPlaceholder(): Promise<string> {\n    let { session } = this\n    if (session) {\n      await session.previousPlaceholder()\n    } else {\n      this.nvim.call('coc#snippet#disable', [], true)\n    }\n    return ''\n  }\n\n  public cancel(): void {\n    let session = this.bufferSync.getItem(workspace.bufnr)\n    if (session) return session.deactivate()\n    this.nvim.call('coc#snippet#disable', [], true)\n    this.statusItem.hide()\n  }\n\n  public get session(): SnippetSession | undefined {\n    return this.bufferSync.getItem(workspace.bufnr)\n  }\n\n  /**\n   * exported method\n   */\n  public getSession(bufnr: number): SnippetSession | undefined {\n    let session = this.bufferSync.getItem(bufnr)\n    return session && session.isActive ? session : undefined\n  }\n\n  public isActivated(bufnr: number): boolean {\n    let session = this.bufferSync.getItem(bufnr)\n    return session && session.isActive\n  }\n\n  public jumpable(): boolean {\n    let { session } = this\n    if (!session) return false\n    return session.placeholder != null && session.placeholder.index != 0\n  }\n\n  /**\n   * Exposed for snippet preview\n   */\n  public async resolveSnippet(snippetString: string, ultisnip?: UltiSnippetOption): Promise<string | undefined> {\n    let session = this.bufferSync.getItem(workspace.bufnr)\n    if (!session) return\n    return await session.resolveSnippet(this.nvim, snippetString, ultisnip)\n  }\n\n  public async normalizeInsertText(bufnr: number, snippetString: string, currentLine: string, insertTextMode: InsertTextMode, ultisnip?: Partial<UltiSnippetOption>): Promise<string> {\n    let inserted = ''\n    if (insertTextMode === InsertTextMode.asIs || !shouldFormat(snippetString)) {\n      inserted = snippetString\n    } else {\n      const currentIndent = currentLine.match(/^\\s*/)[0]\n      let formatOptions = await workspace.getFormatOptions(bufnr) as SnippetFormatOptions\n      let opts: Partial<UltiSnippetOption> = ultisnip ?? {}\n      // trim when option not exists\n      formatOptions.trimTrailingWhitespace = opts.trimTrailingWhitespace !== false\n      if (opts.noExpand) formatOptions.noExpand = true\n      inserted = normalizeSnippetString(snippetString, currentIndent, formatOptions)\n    }\n    return inserted\n  }\n\n  public dispose(): void {\n    this.cancel()\n    disposeAll(this.disposables)\n  }\n}\n\nexport default new SnippetManager()\n"
  },
  {
    "path": "src/snippets/parser.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { exec, ExecOptions } from 'child_process'\nimport { CancellationToken } from 'vscode-languageserver-protocol'\nimport { createLogger } from '../logger'\nimport { groupBy } from '../util/array'\nimport { runSequence } from '../util/async'\nimport { CharCode } from '../util/charCode'\nimport { onUnexpectedError } from '../util/errors'\nimport { promisify, unidecode } from '../util/node'\nimport { iterateCharacter, toText } from '../util/string'\nimport { escapeString, EvalKind, executePythonCode, getVariablesCode } from './eval'\nimport { convertRegex, UltiSnippetContext } from './util'\nconst logger = createLogger('snippets-parser')\nconst ULTISNIP_VARIABLES = ['VISUAL', 'YANK', 'UUID']\nlet id = 0\nlet snippet_id = 0\n\nconst knownRegexOptions = ['d', 'g', 'i', 'm', 's', 'u', 'y']\nconst ultisnipSpecialEscape = ['u', 'l', 'U', 'L', 'E', 'n', 't']\nexport const enum TokenType {\n  Dollar,\n  Colon,\n  Comma,\n  CurlyOpen,\n  CurlyClose,\n  Backslash,\n  Forwardslash,\n  Pipe,\n  Int,\n  VariableName,\n  Format,\n  Plus,\n  Dash,\n  QuestionMark,\n  EOF,\n  OpenParen,\n  CloseParen,\n  BackTick,\n  ExclamationMark,\n}\n\nexport interface Token {\n  type: TokenType\n  pos: number\n  len: number\n}\n\nexport class Scanner {\n\n  private static _table: { [ch: number]: TokenType } = {\n    [CharCode.DollarSign]: TokenType.Dollar,\n    [CharCode.Colon]: TokenType.Colon,\n    [CharCode.Comma]: TokenType.Comma,\n    [CharCode.OpenCurlyBrace]: TokenType.CurlyOpen,\n    [CharCode.CloseCurlyBrace]: TokenType.CurlyClose,\n    [CharCode.Backslash]: TokenType.Backslash,\n    [CharCode.Slash]: TokenType.Forwardslash,\n    [CharCode.Pipe]: TokenType.Pipe,\n    [CharCode.Plus]: TokenType.Plus,\n    [CharCode.Dash]: TokenType.Dash,\n    [CharCode.QuestionMark]: TokenType.QuestionMark,\n    [CharCode.OpenParen]: TokenType.OpenParen,\n    [CharCode.CloseParen]: TokenType.CloseParen,\n    [CharCode.BackTick]: TokenType.BackTick,\n    [CharCode.ExclamationMark]: TokenType.ExclamationMark,\n  }\n\n  public static isDigitCharacter(ch: number): boolean {\n    return ch >= CharCode.Digit0 && ch <= CharCode.Digit9\n  }\n\n  public static isVariableCharacter(ch: number): boolean {\n    return ch === CharCode.Underline\n      || (ch >= CharCode.a && ch <= CharCode.z)\n      || (ch >= CharCode.A && ch <= CharCode.Z)\n  }\n\n  public value: string\n  public pos: number\n\n  constructor() {\n    this.text('')\n  }\n\n  public text(value: string): void {\n    this.value = value\n    this.pos = 0\n  }\n\n  public tokenText(token: Token): string {\n    return this.value.substr(token.pos, token.len)\n  }\n\n  public isEnd(): boolean {\n    return this.pos >= this.value.length\n  }\n\n  public next(): Token {\n\n    if (this.pos >= this.value.length) {\n      return { type: TokenType.EOF, pos: this.pos, len: 0 }\n    }\n\n    let pos = this.pos\n    let len = 0\n    let ch = this.value.charCodeAt(pos)\n    let type: TokenType\n\n    // static types\n    type = Scanner._table[ch]\n    if (typeof type === 'number') {\n      this.pos += 1\n      return { type, pos, len: 1 }\n    }\n\n    // number\n    if (Scanner.isDigitCharacter(ch)) {\n      type = TokenType.Int\n      do {\n        len += 1\n        ch = this.value.charCodeAt(pos + len)\n      } while (Scanner.isDigitCharacter(ch))\n\n      this.pos += len\n      return { type, pos, len }\n    }\n\n    // variable name\n    if (Scanner.isVariableCharacter(ch)) {\n      type = TokenType.VariableName\n      do {\n        ch = this.value.charCodeAt(pos + (++len))\n      } while (Scanner.isVariableCharacter(ch) || Scanner.isDigitCharacter(ch))\n\n      this.pos += len\n      return { type, pos, len }\n    }\n\n    // format\n    type = TokenType.Format\n    do {\n      len += 1\n      ch = this.value.charCodeAt(pos + len)\n    } while (\n      !isNaN(ch)\n      && typeof Scanner._table[ch] === 'undefined' // not static token\n      && !Scanner.isDigitCharacter(ch) // not number\n      && !Scanner.isVariableCharacter(ch) // not variable\n    )\n\n    this.pos += len\n    return { type, pos, len }\n  }\n}\n\nexport abstract class Marker {\n  public parent: Marker\n  protected _children: Marker[] = []\n\n  public appendChild(child: Marker): this {\n    if (child instanceof Text && this._children[this._children.length - 1] instanceof Text) {\n      // this and previous child are text -> merge them\n      (this._children[this._children.length - 1] as Text).value += child.value\n    } else {\n      // normal adoption of child\n      child.parent = this\n      this._children.push(child)\n    }\n    return this\n  }\n\n  public setOnlyChild(child: Marker): void {\n    child.parent = this\n    this._children = [child]\n  }\n\n  public replaceChildren(children: Marker[]): void {\n    for (const child of children) {\n      child.parent = this\n    }\n    this._children = children\n  }\n\n  public replaceWith(newMarker: Marker): boolean {\n    if (!this.parent) return false\n    let p = this.parent\n    let idx = p.children.indexOf(this)\n    if (idx == -1) return false\n    newMarker.parent = p\n    p.children.splice(idx, 1, newMarker)\n    return true\n  }\n\n  public insertBefore(text: string): void {\n    if (!this.parent) return\n    let p = this.parent\n    let idx = p.children.indexOf(this)\n    if (idx == -1) return\n    let prev = p.children[idx - 1]\n    if (prev instanceof Text) {\n      let v = prev.value\n      prev.replaceWith(new Text(v + text))\n    } else {\n      let marker = new Text(text)\n      marker.parent = p\n      p.children.splice(idx, 0, marker)\n    }\n  }\n\n  public get children(): Marker[] {\n    return this._children\n  }\n\n  public get snippet(): TextmateSnippet | undefined {\n    // eslint-disable-next-line @typescript-eslint/no-this-alias\n    let candidate: Marker = this\n    while (true) {\n      if (!candidate) {\n        return undefined\n      }\n      if (candidate instanceof TextmateSnippet) {\n        return candidate\n      }\n      candidate = candidate.parent\n    }\n  }\n\n  public toString(): string {\n    return this.children.reduce((prev, cur) => prev + cur.toString(), '')\n  }\n\n  public abstract toTextmateString(): string\n\n  public len(): number {\n    return 0\n  }\n\n  public abstract clone(): Marker\n}\n\nexport class Text extends Marker {\n\n  public static escape(value: string): string {\n    return value.replace(/\\$|}|\\\\/g, '\\\\$&')\n  }\n\n  constructor(public value: string) {\n    super()\n  }\n\n  public toString(): string {\n    return this.value\n  }\n\n  public toTextmateString(): string {\n    return Text.escape(this.value)\n  }\n\n  public len(): number {\n    return this.value.length\n  }\n\n  public clone(): Text {\n    return new Text(this.value)\n  }\n}\n\nexport class CodeBlock extends Marker {\n\n  private _value = ''\n  private _related: number[] = []\n\n  constructor(public code: string, public readonly kind: EvalKind, value?: string, related?: number[]) {\n    super()\n    if (Array.isArray(related)) {\n      this._related = related\n    } else if (kind === 'python') {\n      this._related = CodeBlock.parseRelated(code)\n    }\n    if (typeof value === 'string') this._value = value\n  }\n\n  public static parseRelated(code: string): number[] {\n    let list: number[] = []\n    let arr\n    let re = /\\bt\\[(\\d+)\\]/g\n    while (true) {\n      arr = re.exec(code)\n      if (arr == null) break\n      let n = parseInt(arr[1], 10)\n      if (!list.includes(n)) list.push(n)\n    }\n    return list\n  }\n\n  public get related(): number[] {\n    return this._related\n  }\n\n  public get index(): number | undefined {\n    if (this.parent instanceof Placeholder) {\n      return this.parent.index\n    }\n    return undefined\n  }\n\n  public async resolve(nvim: Neovim, token?: CancellationToken): Promise<void> {\n    if (!this.code.length) return\n    if (token?.isCancellationRequested) return\n    let res: string\n    if (this.kind == 'python') {\n      res = await this.evalPython(nvim, token)\n    } else if (this.kind == 'vim') {\n      res = await this.evalVim(nvim)\n    } else if (this.kind == 'shell') {\n      res = await this.evalShell()\n    }\n    if (token?.isCancellationRequested) return\n    if (res != null) this._value = res\n  }\n\n  public async evalShell(): Promise<string> {\n    let opts: ExecOptions = { windowsHide: true }\n    Object.assign(opts, { shell: process.env.SHELL })\n    let res = await promisify(exec)(this.code, opts)\n    return res.stdout.replace(/\\s*$/, '')\n  }\n\n  public async evalVim(nvim: Neovim): Promise<string> {\n    let res = await nvim.eval(this.code)\n    return res == null ? '' : res.toString()\n  }\n\n  public async evalPython(nvim: Neovim, token?: CancellationToken): Promise<string> {\n    let curr = toText(this._value)\n    let lines = [`snip._reset(\"${escapeString(curr)}\")`]\n    lines.push(...this.code.split(/\\r?\\n/).map(line => line.replace(/\\t/g, '    ')))\n    await executePythonCode(nvim, lines)\n    if (token?.isCancellationRequested) return\n    return await nvim.call(`pyxeval`, 'str(snip.rv)') as string\n  }\n\n  public len(): number {\n    return this._value.length\n  }\n\n  public toString(): string {\n    return this._value\n  }\n\n  public get value(): string {\n    return this._value\n  }\n\n  public toTextmateString(): string {\n    let t = ''\n    if (this.kind == 'python') {\n      t = '!p '\n    } else if (this.kind == 'shell') {\n      t = ''\n    } else if (this.kind == 'vim') {\n      t = '!v '\n    }\n    return '`' + t + (this.code) + '`'\n  }\n\n  public clone(): CodeBlock {\n    return new CodeBlock(this.code, this.kind, this.value, this._related.slice())\n  }\n}\n\nabstract class TransformableMarker extends Marker {\n  public transform: Transform\n}\n\nexport class Placeholder extends TransformableMarker {\n  public primary = false\n  public id: number\n\n  constructor(public index: number) {\n    super()\n  }\n\n  public get isFinalTabstop(): boolean {\n    return this.index === 0\n  }\n\n  public get choice(): Choice | undefined {\n    return this._children.length === 1 && this._children[0] instanceof Choice\n      ? this._children[0] as Choice\n      : undefined\n  }\n\n  public toTextmateString(): string {\n    let transformString = ''\n    if (this.transform) {\n      transformString = this.transform.toTextmateString()\n    }\n    if (this.children.length === 0 && !this.transform) {\n      return `$${this.index}`\n    } else if (this.children.length === 0 || (this.children.length == 1 && this.children[0].toTextmateString() == '')) {\n      return `\\${${this.index}${transformString}}`\n    } else if (this.choice) {\n      return `\\${${this.index}|${this.choice.toTextmateString()}|${transformString}}`\n    } else {\n      return `\\${${this.index}:${this.children.map(child => child.toTextmateString()).join('')}${transformString}}`\n    }\n  }\n\n  public clone(): Placeholder {\n    let ret = new Placeholder(this.index)\n    if (this.transform) {\n      ret.transform = this.transform.clone()\n    }\n    ret.id = this.id\n    ret.primary = this.primary\n    ret._children = this.children.map(child => {\n      let m = child.clone()\n      m.parent = ret\n      return m\n    })\n    return ret\n  }\n\n  public checkParentPlaceHolders(): void {\n    let idx = this.index\n    let p = this.parent\n    while (p != null && !(p instanceof TextmateSnippet)) {\n      if (p instanceof Placeholder && p.index == idx) {\n        throw new Error(`Parent placeholder has same index: ${idx}`)\n      }\n      p = p.parent\n    }\n  }\n}\n\nexport class Choice extends Marker {\n  private _index\n  constructor(index = 0) {\n    super()\n    this._index = index\n  }\n  public readonly options: Text[] = []\n\n  public appendChild(marker: Marker): this {\n    if (marker instanceof Text) {\n      marker.parent = this\n      this.options.push(marker)\n    }\n    return this\n  }\n\n  public toString(): string {\n    return this.options[this._index].value\n  }\n\n  public toTextmateString(): string {\n    return this.options\n      .map(option => option.value.replace(/\\||,/g, '\\\\$&'))\n      .join(',')\n  }\n\n  public len(): number {\n    return this.options[this._index].len()\n  }\n\n  public clone(): Choice {\n    let ret = new Choice(this._index)\n    for (let opt of this.options) {\n      ret.appendChild(opt)\n    }\n    return ret\n  }\n}\n\nexport class Transform extends Marker {\n\n  public regexp: RegExp\n  public ascii = false\n  public ultisnip = false\n\n  public resolve(value: string): string {\n    let didMatch = false\n    let ret = value.replace(this.regexp, (...args) => {\n      didMatch = true\n      return this._replace(args.slice(0, -2))\n    })\n    // when the regex didn't match and when the transform has\n    // else branches, then run those\n    if (!didMatch && this._children.some(child => child instanceof FormatString && Boolean(child.elseValue))) {\n      ret = this._replace([])\n    }\n    return ret\n  }\n\n  private _replace(groups: string[]): string {\n    let ret = ''\n    let backslashIndexes: number[] = []\n    for (const marker of this._children) {\n      let val = ''\n      let len = ret.length\n      if (marker instanceof FormatString) {\n        val = marker.resolve(groups[marker.index] ?? '')\n        if (this.ultisnip && val.indexOf('\\\\') !== -1) {\n          for (let idx of iterateCharacter(val, '\\\\')) {\n            backslashIndexes.push(len + idx)\n          }\n        }\n      } else if (marker instanceof ConditionString) {\n        val = marker.resolve(groups[marker.index])\n        if (this.ultisnip) {\n          val = val.replace(/(?<!\\\\)\\$(\\d+)/g, (...args) => {\n            return toText(groups[Number(args[1])])\n          })\n        }\n      } else {\n        val = marker.toString()\n      }\n\n      ret += val\n    }\n    if (this.ascii) ret = unidecode(ret)\n    return this.ultisnip ? transformEscapes(ret, backslashIndexes) : ret\n  }\n\n  public toString(): string {\n    return ''\n  }\n\n  public toTextmateString(): string {\n    let format = this.children.map(c => c.toTextmateString()).join('')\n    if (this.ultisnip) {\n      // avoid bad escape of Text for ultisnip format\n      format = format.replace(/\\\\\\\\(\\w)/g, (match, ch) => {\n        if (ultisnipSpecialEscape.includes(ch)) {\n          return '\\\\' + ch\n        }\n        return match\n      })\n    }\n    return `/${this.regexp.source}/${format}/${(this.regexp.ignoreCase ? 'i' : '') + (this.regexp.global ? 'g' : '')}`\n  }\n\n  public clone(): Transform {\n    let ret = new Transform()\n    ret.regexp = new RegExp(this.regexp.source, '' + (this.regexp.ignoreCase ? 'i' : '') + (this.regexp.global ? 'g' : ''))\n    ret._children = this.children.map(child => {\n      let m = child.clone()\n      m.parent = ret\n      return m\n    })\n    return ret\n  }\n\n}\n\nexport class ConditionString extends Marker {\n  constructor(\n    public readonly index: number,\n    public readonly ifValue: string,\n    public readonly elseValue: string,\n  ) {\n    super()\n  }\n\n  public resolve(value: string): string {\n    if (value) return this.ifValue\n    return this.elseValue\n  }\n\n  public toTextmateString(): string {\n    return '(?' + this.index + ':' + this.ifValue + (this.elseValue ? ':' + this.elseValue : '') + ')'\n  }\n\n  public clone(): ConditionString {\n    return new ConditionString(this.index, this.ifValue, this.elseValue)\n  }\n}\n\n// TODO ultisnip only, not used yet\nexport class ConditionMarker extends Marker {\n  constructor(\n    public readonly index: number,\n    protected ifMarkers: Marker[],\n    protected elseMarkers: Marker[],\n  ) {\n    super()\n  }\n\n  public resolve(value: string, groups: string[]): string {\n    let fn = (p: string, c: Marker): string => {\n      return p + (c instanceof FormatString ? c.resolve(groups[c.index]) : c.toString())\n    }\n    if (value) return this.ifMarkers.reduce(fn, '')\n    return this.elseMarkers.reduce(fn, '')\n  }\n\n  public addIfMarker(marker: Marker) {\n    this.ifMarkers.push(marker)\n  }\n\n  public addElseMarker(marker: Marker) {\n    this.elseMarkers.push(marker)\n  }\n\n  public toTextmateString(): string {\n    let ifValue = this.ifMarkers.reduce((p, c) => p + c.toTextmateString(), '')\n    let elseValue = this.elseMarkers.reduce((p, c) => p + c.toTextmateString(), '')\n    return '(?' + this.index + ':' + ifValue + (elseValue.length > 0 ? ':' + elseValue : '') + ')'\n  }\n\n  public clone(): ConditionMarker {\n    return new ConditionMarker(this.index, this.ifMarkers.map(m => m.clone()), this.elseMarkers.map(m => m.clone()))\n  }\n}\n\nexport class FormatString extends Marker {\n\n  constructor(\n    public readonly index: number,\n    public readonly shorthandName?: string,\n    public readonly ifValue?: string,\n    public readonly elseValue?: string,\n  ) {\n    super()\n  }\n\n  public resolve(value: string): string {\n    if (this.shorthandName === 'upcase') {\n      return !value ? '' : value.toLocaleUpperCase()\n    } else if (this.shorthandName === 'downcase') {\n      return !value ? '' : value.toLocaleLowerCase()\n    } else if (this.shorthandName === 'capitalize') {\n      return !value ? '' : (value[0].toLocaleUpperCase() + value.substr(1))\n    } else if (this.shorthandName === 'pascalcase') {\n      return !value ? '' : this._toPascalCase(value)\n    } else if (Boolean(value) && typeof this.ifValue === 'string') {\n      return this.ifValue\n    } else if (!value && typeof this.elseValue === 'string') {\n      return this.elseValue\n    } else {\n      return value || ''\n    }\n  }\n\n  private _toPascalCase(value: string): string {\n    const match = value.match(/[a-z]+/gi)\n    if (!match) {\n      return value\n    }\n    return match.map(word => word.charAt(0).toUpperCase()\n      + word.substr(1).toLowerCase())\n      .join('')\n  }\n\n  public toTextmateString(): string {\n    let value = '${'\n    value += this.index\n    if (this.shorthandName) {\n      value += `:/${this.shorthandName}`\n\n    } else if (this.ifValue && this.elseValue) {\n      value += `:?${this.ifValue}:${this.elseValue}`\n    } else if (this.ifValue) {\n      value += `:+${this.ifValue}`\n    } else if (this.elseValue) {\n      value += `:-${this.elseValue}`\n    }\n    value += '}'\n    return value\n  }\n\n  public clone(): FormatString {\n    let ret = new FormatString(this.index, this.shorthandName, this.ifValue, this.elseValue)\n    return ret\n  }\n}\n\nexport class Variable extends TransformableMarker {\n  private _resolved: boolean\n\n  constructor(public name: string, resolved = false) {\n    super()\n    this._resolved = resolved\n  }\n\n  public get resolved(): boolean {\n    return this._resolved\n  }\n\n  public async resolve(resolver: VariableResolver): Promise<boolean> {\n    let value = await resolver.resolve(this)\n    this._resolved = true\n    if (value && value.includes('\\n')) {\n      // get indent from previous texts\n      let indent = ''\n      this.snippet.walk(m => {\n        if (m == this) {\n          return false\n        }\n        if (m instanceof Text) {\n          let lines = m.toString().split(/\\r?\\n/)\n          indent = lines[lines.length - 1].match(/^\\s*/)[0]\n        }\n        return true\n      }, true)\n      let lines = value.split('\\n')\n      let indents = lines.filter(s => s.length > 0).map(s => s.match(/^\\s*/)[0])\n      let minIndent = indents.reduce((p, c) => p < c.length ? p : c.length, 0)\n      let newLines = lines.map((s, i) => i == 0 || s.length == 0 || !s.startsWith(' '.repeat(minIndent)) ? s :\n        indent + s.slice(minIndent))\n      value = newLines.join('\\n')\n    }\n    if (typeof value !== 'string') return false\n    if (this.transform) {\n      value = this.transform.resolve(toText(value))\n    }\n    this._children = [new Text(value.toString())]\n    return true\n  }\n\n  public toTextmateString(): string {\n    let transformString = ''\n    if (this.transform) {\n      transformString = this.transform.toTextmateString()\n    }\n    if (this.children.length === 0) {\n      return `\\${${this.name}${transformString}}`\n    } else {\n      return `\\${${this.name}:${this.children.map(child => child.toTextmateString()).join('')}${transformString}}`\n    }\n  }\n\n  public clone(): Variable {\n    const ret = new Variable(this.name, this.resolved)\n    if (this.transform) {\n      ret.transform = this.transform.clone()\n    }\n    ret._children = this.children.map(child => {\n      let m = child.clone()\n      m.parent = ret\n      return m\n    })\n    return ret\n  }\n}\n\nexport interface VariableResolver {\n  resolve(variable: Variable): Promise<string | undefined>\n}\n\nexport interface PlaceholderInfo {\n  placeholders: Placeholder[]\n  pyBlocks: CodeBlock[]\n  otherBlocks: CodeBlock[]\n}\n\nfunction walk(marker: Marker[], visitor: (marker: Marker) => boolean, ignoreChild = false): void {\n  const stack = [...marker]\n  while (stack.length > 0) {\n    const marker = stack.shift()\n    if (ignoreChild && marker instanceof TextmateSnippet) continue\n    const recurse = visitor(marker)\n    if (!recurse) {\n      break\n    }\n    stack.unshift(...marker.children)\n  }\n}\n\nexport class TextmateSnippet extends Marker {\n\n  public readonly ultisnip: boolean\n  public readonly id: number\n  public readonly related: { codes?: string[], context?: UltiSnippetContext } = {}\n  constructor(ultisnip?: boolean, id?: number) {\n    super()\n    this.ultisnip = ultisnip === true\n    this.id = id ?? snippet_id++\n  }\n\n  public get hasPythonBlock(): boolean {\n    if (!this.ultisnip) return false\n    return this.pyBlocks.length > 0\n  }\n\n  public get hasCodeBlock(): boolean {\n    if (!this.ultisnip) return false\n    let { pyBlocks, otherBlocks } = this\n    return pyBlocks.length > 0 || otherBlocks.length > 0\n  }\n\n  /**\n   * Values for each placeholder index\n   */\n  public get values(): { [index: number]: string } {\n    let values: { [index: number]: string } = {}\n    let maxIndexNumber = 0\n    this.placeholders.forEach(c => {\n      if (!Number.isInteger(c.index)) return\n      maxIndexNumber = Math.max(c.index, maxIndexNumber)\n      if (c.transform != null) return\n      if (c.primary || values[c.index] === undefined) values[c.index] = c.toString()\n    })\n    for (let i = 0; i <= maxIndexNumber; i++) {\n      if (values[i] === undefined) values[i] = ''\n    }\n    return values\n  }\n\n  public get orderedPyIndexBlocks(): CodeBlock[] {\n    let res: CodeBlock[] = []\n    let filtered = this.pyBlocks.filter(o => typeof o.index === 'number')\n    if (filtered.length === 0) return res\n    let allIndexes = filtered.map(o => o.index)\n    let usedIndexes: number[] = []\n    const checkBlock = (b: CodeBlock): boolean => {\n      let { related } = b\n      if (related.length == 0\n        || related.every(idx => !allIndexes.includes(idx) || usedIndexes.includes(idx))) {\n        usedIndexes.push(b.index)\n        res.push(b)\n        return true\n      }\n      return false\n    }\n    while (filtered.length > 0) {\n      let c = false\n      for (let b of filtered) {\n        if (checkBlock(b)) {\n          c = true\n        }\n      }\n      if (!c) {\n        // recursive dependencies detected\n        break\n      }\n      filtered = filtered.filter(o => !usedIndexes.includes(o.index))\n    }\n    return res\n  }\n\n  public async evalCodeBlocks(nvim: Neovim, pyCodes: string[]): Promise<void> {\n    const { pyBlocks, otherBlocks } = this.placeholderInfo\n    // update none python blocks\n    await Promise.all(otherBlocks.map(block => {\n      let pre = block.value\n      return block.resolve(nvim).then(() => {\n        if (block.parent instanceof Placeholder && pre !== block.value) {\n          // update placeholder with same index\n          this.onPlaceholderUpdate(block.parent)\n        }\n      })\n    }))\n    if (pyCodes.length === 0) return\n    // update normal python block with related.\n    let relatedBlocks = pyBlocks.filter(o => o.index === undefined && o.related.length > 0)\n    // run all python code by sequence\n    const variableCode = getVariablesCode(this.values)\n    await executePythonCode(nvim, [...pyCodes, variableCode])\n    for (let block of pyBlocks) {\n      let pre = block.value\n      if (relatedBlocks.includes(block)) continue\n      await block.resolve(nvim)\n      if (pre === block.value) continue\n      if (block.parent instanceof Placeholder) {\n        // update placeholder with same index\n        this.onPlaceholderUpdate(block.parent)\n        await executePythonCode(nvim, [getVariablesCode(this.values)])\n      }\n    }\n    for (let block of this.orderedPyIndexBlocks) {\n      await this.updatePyIndexBlock(nvim, block)\n    }\n    for (let block of relatedBlocks) {\n      await block.resolve(nvim)\n    }\n  }\n\n  /**\n   * Update python blocks after user change Placeholder with index\n   */\n  public async updatePythonCodes(nvim: Neovim, marker: Placeholder, codes: string[], token: CancellationToken): Promise<void> {\n    let index = marker.index\n    // update related placeholders\n    let blocks = this.getDependentPyIndexBlocks(index)\n    await runSequence([async () => {\n      await executePythonCode(nvim, [...codes, getVariablesCode(this.values)])\n    }, async () => {\n      for (let block of blocks) {\n        await this.updatePyIndexBlock(nvim, block, token)\n      }\n    }, async () => {\n      // update normal pyBlocks.\n      let filtered = this.pyBlocks.filter(o => o.index === undefined && o.related.length > 0)\n      for (let block of filtered) {\n        await block.resolve(nvim, token)\n      }\n    }], token)\n  }\n\n  private getDependentPyIndexBlocks(index: number): CodeBlock[] {\n    const res: CodeBlock[] = []\n    const taken: number[] = []\n    let filtered = this.pyBlocks.filter(o => typeof o.index === 'number')\n    const search = (idx: number) => {\n      let blocks = filtered.filter(o => !taken.includes(o.index) && o.related.includes(idx))\n      if (blocks.length > 0) {\n        res.push(...blocks)\n        blocks.forEach(b => {\n          search(b.index)\n        })\n      }\n    }\n    search(index)\n    return res\n  }\n\n  /**\n   * Update single index block\n   */\n  private async updatePyIndexBlock(nvim: Neovim, block: CodeBlock, token?: CancellationToken): Promise<void> {\n    let pre = block.value\n    await block.resolve(nvim, token)\n    if (pre === block.value || token?.isCancellationRequested) return\n    if (block.parent instanceof Placeholder) {\n      this.onPlaceholderUpdate(block.parent)\n    }\n    await executePythonCode(nvim, [getVariablesCode(this.values)])\n  }\n\n  public get placeholderInfo(): PlaceholderInfo {\n    const pyBlocks: CodeBlock[] = []\n    const otherBlocks: CodeBlock[] = []\n    // fill in placeholders\n    let placeholders: Placeholder[] = []\n    this.walk(candidate => {\n      if (candidate instanceof Placeholder) {\n        placeholders.push(candidate)\n      } else if (candidate instanceof CodeBlock) {\n        if (candidate.kind === 'python') {\n          pyBlocks.push(candidate)\n        } else {\n          otherBlocks.push(candidate)\n        }\n      }\n      return true\n    }, true)\n    return { placeholders, pyBlocks, otherBlocks }\n  }\n\n  public get variables(): Variable[] {\n    const variables = []\n    this.walk(candidate => {\n      if (candidate instanceof Variable) {\n        variables.push(candidate)\n      }\n      return true\n    }, true)\n    return variables\n  }\n\n  public get placeholders(): Placeholder[] {\n    let placeholders: Placeholder[] = []\n    this.walk(candidate => {\n      if (candidate instanceof Placeholder) {\n        placeholders.push(candidate)\n      }\n      return true\n    }, true)\n    return placeholders\n  }\n\n  public get pyBlocks(): CodeBlock[] {\n    return this.placeholderInfo.pyBlocks\n  }\n\n  public get otherBlocks(): CodeBlock[] {\n    return this.placeholderInfo.otherBlocks\n  }\n\n  public get first(): Placeholder {\n    let { placeholders } = this\n    let [normals, finals] = groupBy(placeholders.filter(p => !p.transform), v => v.index !== 0)\n    if (normals.length) {\n      let minIndex = Math.min.apply(null, normals.map(o => o.index))\n      let arr = normals.filter(v => v.index == minIndex)\n      return arr.find(p => p.primary) ?? arr[0]\n    }\n    return finals.find(o => o.primary) ?? finals[0]\n  }\n\n  public async update(nvim: Neovim, marker: Placeholder, token: CancellationToken): Promise<void> {\n    this.onPlaceholderUpdate(marker)\n    let codes = this.related.codes ?? []\n    if (codes.length === 0 || !this.hasPythonBlock) return\n    await this.updatePythonCodes(nvim, marker, codes, token)\n  }\n\n  /**\n   * Reflact changes for related markers.\n   */\n  public onPlaceholderUpdate(marker: Placeholder): void {\n    let val = marker.toString()\n    let markers = this.placeholders.filter(o => o.index == marker.index)\n    for (let p of markers) {\n      p.checkParentPlaceHolders()\n      if (p === marker) continue\n      let newText = p.transform ? p.transform.resolve(val) : val\n      p.setOnlyChild(new Text(toText(newText)))\n    }\n    this.synchronizeParents(markers)\n  }\n\n  public synchronizeParents(markers: Marker[]): void {\n    let parents: Set<Placeholder> = new Set()\n    markers.forEach(m => {\n      let p = m.parent\n      if (p instanceof Placeholder) parents.add(p)\n    })\n    for (let p of parents) {\n      this.onPlaceholderUpdate(p)\n    }\n  }\n\n  public offset(marker: Marker): number {\n    let pos = 0\n    let found = false\n    this.walk(candidate => {\n      if (candidate === marker) {\n        found = true\n        return false\n      }\n      pos += candidate.len()\n      return true\n    }, true)\n\n    if (!found) {\n      return -1\n    }\n    return pos\n  }\n\n  public fullLen(marker: Marker): number {\n    let ret = 0\n    walk([marker], marker => {\n      ret += marker.len()\n      return true\n    })\n    return ret\n  }\n\n  public getTextBefore(marker: Marker, parent: Placeholder): string {\n    let res = ''\n    const calc = (m: Marker): void => {\n      let p = m.parent\n      if (!p) return\n      let s = ''\n      for (let b of p.children) {\n        if (b === m) break\n        s = s + b.toString()\n      }\n      res = s + res\n      if (p == parent) return\n      calc(p)\n    }\n    calc(marker)\n    return res\n  }\n\n  public enclosingPlaceholders(placeholder: Placeholder | Variable): Placeholder[] {\n    let ret: Placeholder[] = []\n    let { parent } = placeholder\n    while (parent) {\n      if (parent instanceof Placeholder) {\n        ret.push(parent)\n      }\n      parent = parent.parent\n    }\n    return ret\n  }\n\n  public async resolveVariables(resolver: VariableResolver): Promise<void> {\n    let variables = this.variables\n    if (variables.length === 0) return\n    let failed: Variable[] = []\n    let succeed: Variable[] = []\n    let promises: Promise<void>[] = []\n    const changedParents: Set<Marker> = new Set()\n    for (let item of variables) {\n      promises.push(item.resolve(resolver).then(res => {\n        changedParents.add(item.parent)\n        let arr = res ? succeed : failed\n        arr.push(item)\n      }, onUnexpectedError))\n    }\n    await Promise.allSettled(promises)\n    // convert resolved variables to text\n    for (const variable of succeed) {\n      let text = new Text(variable.toString())\n      variable.replaceWith(text)\n    }\n    if (failed.length > 0) {\n      // convert to placeholders\n      let indexMap: Map<string, number> = new Map()\n      const primarySet: Set<number> = new Set()\n      // create index for variables\n      let max = this.getMaxPlaceholderIndex()\n      for (let i = 0; i < failed.length; i++) {\n        const v = failed[i]\n        let idx = indexMap.get(v.name)\n        if (idx == null) {\n          idx = ++max\n          indexMap.set(v.name, idx)\n        }\n        let p = new Placeholder(idx)\n        p.transform = v.transform\n        if (!p.transform && !primarySet.has(idx)) {\n          primarySet.add(idx)\n          p.primary = true\n        }\n        let newText = p.transform ? p.transform.resolve(v.name) : v.name\n        p.setOnlyChild(new Text(toText(newText)))\n        v.replaceWith(p)\n      }\n    }\n    changedParents.forEach(marker => {\n      mergeTexts(marker)\n      if (marker instanceof Placeholder) this.onPlaceholderUpdate(marker)\n    })\n  }\n\n  public getMaxPlaceholderIndex(): number {\n    let res = 0\n    this.walk(candidate => {\n      if (candidate instanceof Placeholder) {\n        res = Math.max(res, candidate.index)\n      }\n      return true\n    }, true)\n    return res\n  }\n\n  public replace(marker: Marker, children: Marker[]): void {\n    marker.replaceChildren(children)\n    if (marker instanceof Placeholder) {\n      this.onPlaceholderUpdate(marker)\n    }\n  }\n\n  public toTextmateString(): string {\n    return this.children.reduce((prev, cur) => prev + cur.toTextmateString(), '')\n  }\n\n  public clone(): TextmateSnippet {\n    let ret = new TextmateSnippet(this.ultisnip, this.id)\n    ret.related.codes = this.related.codes\n    ret.related.context = this.related.context\n    ret._children = this.children.map(child => {\n      let m = child.clone()\n      m.parent = ret\n      return m\n    })\n    return ret\n  }\n\n  public walk(visitor: (marker: Marker) => boolean, ignoreChild = false): void {\n    walk(this.children, visitor, ignoreChild)\n  }\n}\n\nexport class SnippetParser {\n  constructor(private ultisnip?: boolean) {\n  }\n\n  public static escape(value: string): string {\n    return value.replace(/\\$|}|\\\\/g, '\\\\$&')\n  }\n\n  public static isPlainText(value: string): boolean {\n    let s = new SnippetParser().parse(value.replace(/\\$0$/, ''), false)\n    return s.children.length == 1 && s.children[0] instanceof Text\n  }\n\n  private _scanner = new Scanner()\n  private _token: Token\n\n  public text(value: string): string {\n    return this.parse(value, false).toString()\n  }\n\n  public parse(value: string, insertFinalTabstop?: boolean): TextmateSnippet {\n\n    this._scanner.text(value)\n    this._token = this._scanner.next()\n\n    const snippet = new TextmateSnippet(this.ultisnip)\n    while (this._parse(snippet)) {\n      // nothing\n    }\n\n    // fill in values for placeholders. the first placeholder of an index\n    // that has a value defines the value for all placeholders with that index\n    const defaultValues = new Map<number, string>()\n    const incompletePlaceholders: Placeholder[] = []\n    let complexPlaceholders: Placeholder[] = []\n    let hasFinal = false\n    snippet.walk(marker => {\n      if (marker instanceof Placeholder) {\n        if (marker.index == 0) hasFinal = true\n        if (marker.children.some(o => o instanceof Placeholder)) {\n          marker.primary = true\n          complexPlaceholders.push(marker)\n        } else if (!defaultValues.has(marker.index) && marker.children.length > 0) {\n          marker.primary = true\n          defaultValues.set(marker.index, marker.toString())\n        } else {\n          incompletePlaceholders.push(marker)\n        }\n      }\n      return true\n    })\n\n    const complexIndexes = complexPlaceholders.map(p => p.index)\n    for (const placeholder of incompletePlaceholders) {\n      // avoid transform and replace since no value exists.\n      if (defaultValues.has(placeholder.index)) {\n        let val = defaultValues.get(placeholder.index)\n        let text = new Text(placeholder.transform ? placeholder.transform.resolve(val) : val)\n        placeholder.setOnlyChild(text)\n      } else if (!complexIndexes.includes(placeholder.index)) {\n        if (placeholder.transform) {\n          let text = new Text(placeholder.transform.resolve(''))\n          placeholder.setOnlyChild(text)\n        } else {\n          placeholder.primary = true\n          defaultValues.set(placeholder.index, '')\n        }\n      }\n    }\n    const resolveComplex = () => {\n      let resolved: Set<number> = new Set()\n      for (let p of complexPlaceholders) {\n        if (p.children.every(o => !(o instanceof Placeholder) || defaultValues.has(o.index))) {\n          let val = p.toString()\n          defaultValues.set(p.index, val)\n          for (let placeholder of incompletePlaceholders.filter(o => o.index == p.index)) {\n            let text = new Text(placeholder.transform ? placeholder.transform.resolve(val) : val)\n            placeholder.setOnlyChild(text)\n          }\n          resolved.add(p.index)\n        }\n      }\n      complexPlaceholders = complexPlaceholders.filter(p => !resolved.has(p.index))\n      if (complexPlaceholders.length == 0 || !resolved.size) return\n      resolveComplex()\n    }\n    resolveComplex()\n\n    if (!hasFinal && insertFinalTabstop) {\n      // the snippet uses placeholders but has no\n      // final tabstop defined -> insert at the end\n      snippet.appendChild(new Placeholder(0))\n    }\n\n    return snippet\n  }\n\n  private _accept(type?: TokenType): boolean\n  private _accept(type: TokenType | undefined, value: true): string\n  private _accept(type: TokenType, value?: boolean): boolean | string {\n    if (type === undefined || this._token.type === type) {\n      let ret = !value ? true : this._scanner.tokenText(this._token)\n      this._token = this._scanner.next()\n      return ret\n    }\n    return false\n  }\n\n  private _backTo(token: Token): false {\n    this._scanner.pos = token.pos + token.len\n    this._token = token\n    return false\n  }\n\n  private _until(type: TokenType, checkBackSlash = false): false | string {\n    if (this._token.type === TokenType.EOF) {\n      return false\n    }\n    let start = this._token\n    let pre: Token\n    while (this._token.type !== type || (checkBackSlash && pre && pre.type === TokenType.Backslash)) {\n      if (checkBackSlash) pre = this._token\n      this._token = this._scanner.next()\n      if (this._token.type === TokenType.EOF) {\n        return false\n      }\n    }\n    let value = this._scanner.value.substring(start.pos, this._token.pos)\n    this._token = this._scanner.next()\n    return value\n  }\n\n  private _parse(marker: Marker): boolean {\n    return this._parseEscaped(marker)\n      || this._parseCodeBlock(marker)\n      || this._parseTabstopOrVariableName(marker)\n      || this._parseComplexPlaceholder(marker)\n      || this._parseComplexVariable(marker)\n      || this._parseAnything(marker)\n  }\n\n  // \\$, \\\\, \\} -> just text\n  private _parseEscaped(marker: Marker): boolean {\n    let value: string\n    // eslint-disable-next-line no-cond-assign\n    if (value = this._accept(TokenType.Backslash, true)) {\n      // saw a backslash, append escaped token or that backslash\n      value = this._accept(TokenType.Dollar, true)\n        || this._accept(TokenType.CurlyClose, true)\n        || this._accept(TokenType.Backslash, true)\n        || (this.ultisnip && this._accept(TokenType.CurlyOpen, true))\n        || (this.ultisnip && this._accept(TokenType.BackTick, true))\n        || value\n\n      marker.appendChild(new Text(value))\n      return true\n    }\n    return false\n  }\n\n  // $foo -> variable, $1 -> tabstop\n  private _parseTabstopOrVariableName(parent: Marker): boolean {\n    let value: string\n    const token = this._token\n    const match = this._accept(TokenType.Dollar)\n      && (value = this._accept(TokenType.VariableName, true) || this._accept(TokenType.Int, true))\n\n    if (!match) {\n      return this._backTo(token)\n    }\n    if (/^\\d+$/.test(value)) {\n      parent.appendChild(new Placeholder(Number(value)))\n    } else {\n      if (this.ultisnip && !ULTISNIP_VARIABLES.includes(value)) {\n        parent.appendChild(new Text('$' + value))\n      } else {\n        parent.appendChild(new Variable(value))\n      }\n    }\n\n    return true\n  }\n\n  private _checkCulybrace(marker: Marker): boolean {\n    let count = 0\n    for (marker of marker.children) {\n      if (marker instanceof Text) {\n        let text = marker.value\n        for (let index = 0; index < text.length; index++) {\n          const ch = text[index]\n          if (ch === '{') {\n            count++\n          } else if (ch === '}') {\n            count--\n          }\n        }\n      }\n    }\n    return count <= 0\n  }\n\n  // ${1:<children>}, ${1} -> placeholder\n  private _parseComplexPlaceholder(parent: Marker): boolean {\n    let index: string\n    const token = this._token\n    const match = this._accept(TokenType.Dollar)\n      && this._accept(TokenType.CurlyOpen)\n      && (index = this._accept(TokenType.Int, true))\n    if (!match) {\n      return this._backTo(token)\n    }\n    const placeholder = new Placeholder(Number(index))\n    if (this._accept(TokenType.Colon)) {\n      // ${1:<children>}\n      while (true) {\n        const lastChar = this._scanner.isEnd()\n        // ...} -> done\n        if (this._accept(TokenType.CurlyClose)) {\n          // we should consider ${1:{}} with text as {}, like ultisnip.\n          // check if missed paried }\n          if (!this._checkCulybrace(placeholder) && !lastChar) {\n            placeholder.appendChild(new Text('}'))\n            continue\n          }\n          parent.appendChild(placeholder)\n          return true\n        }\n\n        if (this._parse(placeholder)) {\n          continue\n        }\n\n        // fallback\n        parent.appendChild(new Text('${' + index + ':'))\n        placeholder.children.forEach(parent.appendChild, parent)\n        return true\n      }\n    } else if (placeholder.index > 0 && this._accept(TokenType.Pipe)) {\n      // ${1|one,two,three|}\n      const choice = new Choice()\n\n      while (true) {\n        if (this._parseChoiceElement(choice)) {\n\n          if (this._accept(TokenType.Comma)) {\n            // opt, -> more\n            continue\n          }\n\n          if (this._accept(TokenType.Pipe)) {\n            placeholder.appendChild(choice)\n            if (this._accept(TokenType.CurlyClose)) {\n              // ..|} -> done\n              parent.appendChild(placeholder)\n              return true\n            }\n          }\n        }\n\n        this._backTo(token)\n        return false\n      }\n\n    } else if (this._accept(TokenType.Forwardslash)) {\n      // ${1/<regex>/<format>/<options>}\n      if (this._parseTransform(placeholder)) {\n        parent.appendChild(placeholder)\n        return true\n      }\n\n      this._backTo(token)\n      return false\n\n    } else if (this._accept(TokenType.CurlyClose)) {\n      // ${1}\n      parent.appendChild(placeholder)\n      return true\n\n    } else {\n      // ${1 <- missing curly or colon\n      return this._backTo(token)\n    }\n  }\n\n  private _parseChoiceElement(parent: Choice): boolean {\n    const token = this._token\n    const values: string[] = []\n\n    while (true) {\n      if (this._token.type === TokenType.Comma || this._token.type === TokenType.Pipe) {\n        break\n      }\n      let value: string\n      // eslint-disable-next-line no-cond-assign\n      if (value = this._accept(TokenType.Backslash, true)) {\n        // \\, \\|, or \\\\\n        value = this._accept(TokenType.Comma, true)\n          || this._accept(TokenType.Pipe, true)\n          || this._accept(TokenType.Backslash, true)\n          || value\n      } else {\n        value = this._accept(undefined, true)\n      }\n      if (!value) {\n        // EOF\n        this._backTo(token)\n        return false\n      }\n      values.push(value)\n    }\n\n    if (values.length === 0) {\n      this._backTo(token)\n      return false\n    }\n\n    parent.appendChild(new Text(values.join('')))\n    return true\n  }\n\n  // ${foo:<children>}, ${foo} -> variable\n  private _parseComplexVariable(parent: Marker): boolean {\n    let name: string\n    const token = this._token\n    const match = this._accept(TokenType.Dollar)\n      && this._accept(TokenType.CurlyOpen)\n      && (name = this._accept(TokenType.VariableName, true))\n\n    if (!match) {\n      return this._backTo(token)\n    }\n    if (this.ultisnip && !ULTISNIP_VARIABLES.includes(name)) {\n      return this._backTo(token)\n    }\n\n    const variable = new Variable(name)\n    if (this._accept(TokenType.Colon)) {\n      // ${foo:<children>}\n      while (true) {\n\n        // ...} -> done\n        if (this._accept(TokenType.CurlyClose)) {\n          parent.appendChild(variable)\n          return true\n        }\n\n        if (this._parse(variable)) {\n          continue\n        }\n\n        // fallback\n        parent.appendChild(new Text('${' + name + ':'))\n        variable.children.forEach(parent.appendChild, parent)\n        return true\n      }\n\n    } else if (this._accept(TokenType.Forwardslash)) {\n      // ${foo/<regex>/<format>/<options>}\n      if (this._parseTransform(variable)) {\n        parent.appendChild(variable)\n        return true\n      }\n\n      this._backTo(token)\n      return false\n\n    } else if (this._accept(TokenType.CurlyClose)) {\n      // ${foo}\n      parent.appendChild(variable)\n      return true\n\n    } else {\n      // ${foo <- missing curly or colon\n      return this._backTo(token)\n    }\n  }\n\n  private _parseTransform(parent: TransformableMarker): boolean {\n    // ...<regex>/<format>/<options>}\n\n    let transform = new Transform()\n    transform.ultisnip = this.ultisnip === true\n    let regexValue = ''\n    let regexOptions = ''\n\n    // (1) /regex\n    while (true) {\n      if (this._accept(TokenType.Forwardslash)) {\n        break\n      }\n\n      let escaped: string\n      // eslint-disable-next-line no-cond-assign\n      if (escaped = this._accept(TokenType.Backslash, true)) {\n        escaped = this._accept(TokenType.Forwardslash, true) || escaped\n        regexValue += escaped\n        continue\n      }\n\n      if (this._token.type !== TokenType.EOF) {\n        regexValue += this._accept(undefined, true)\n        continue\n      }\n      return false\n    }\n\n    // (2) /format\n    while (true) {\n      if (this._accept(TokenType.Forwardslash)) {\n        break\n      }\n\n      let escaped: string\n      // eslint-disable-next-line no-cond-assign\n      if (escaped = this._accept(TokenType.Backslash, true)) {\n        escaped = this._accept(TokenType.Backslash, true) || this._accept(TokenType.Forwardslash, true) || escaped\n        transform.appendChild(new Text(escaped))\n        continue\n      }\n      if (this._parseFormatString(transform) || this._parseConditionString(transform) || this._parseAnything(transform)) {\n        continue\n      }\n      return false\n    }\n\n    let ascii = false\n    // (3) /option\n    while (true) {\n      if (this._accept(TokenType.CurlyClose)) {\n        break\n      }\n      if (this._token.type !== TokenType.EOF) {\n        let c = this._accept(undefined, true)\n        if (c == 'a') {\n          ascii = true\n        } else {\n          if (!knownRegexOptions.includes(c)) {\n            logger.error(`Unknown regex option: ${c}`)\n          }\n          regexOptions += c\n        }\n        continue\n      }\n      return false\n    }\n\n    try {\n      if (ascii) transform.ascii = true\n      if (this.ultisnip) regexValue = convertRegex(regexValue)\n      transform.regexp = new RegExp(regexValue, regexOptions)\n    } catch (e) {\n      return false\n    }\n\n    parent.transform = transform\n    return true\n  }\n\n  private _parseConditionString(parent: Transform): boolean {\n    if (!this.ultisnip) return false\n    const token = this._token\n    // (?1:foo:bar)\n    if (!this._accept(TokenType.OpenParen)) {\n      return false\n    }\n    if (!this._accept(TokenType.QuestionMark)) {\n      this._backTo(token)\n      return false\n    }\n    let index = this._accept(TokenType.Int, true)\n    if (!index) {\n      this._backTo(token)\n      return false\n    }\n    if (!this._accept(TokenType.Colon)) {\n      this._backTo(token)\n      return false\n    }\n    let text = this._until(TokenType.CloseParen, true)\n    // TODO parse ConditionMarker for ultisnip\n    if (text) {\n      let i = 0\n      while (i < text.length) {\n        let t = text[i]\n        if (t == ':' && text[i - 1] != '\\\\') {\n          break\n        }\n        i++\n      }\n      let ifValue = text.slice(0, i)\n      let elseValue = text.slice(i + 1)\n      parent.appendChild(new ConditionString(Number(index), ifValue, elseValue))\n      return true\n    }\n    this._backTo(token)\n    return false\n  }\n\n  private _parseFormatString(parent: Transform): boolean {\n\n    const token = this._token\n    if (!this._accept(TokenType.Dollar)) {\n      return false\n    }\n\n    let complex = false\n    if (this._accept(TokenType.CurlyOpen)) {\n      complex = true\n    }\n\n    let index = this._accept(TokenType.Int, true)\n\n    if (!index) {\n      this._backTo(token)\n      return false\n\n    } else if (!complex) {\n      // $1\n      parent.appendChild(new FormatString(Number(index)))\n      return true\n\n    } else if (this._accept(TokenType.CurlyClose)) {\n      // ${1}\n      parent.appendChild(new FormatString(Number(index)))\n      return true\n\n    } else if (!this._accept(TokenType.Colon)) {\n      this._backTo(token)\n      return false\n    }\n    if (this.ultisnip) {\n      this._backTo(token)\n      return false\n    }\n\n    if (this._accept(TokenType.Forwardslash)) {\n      // ${1:/upcase}\n      let shorthand = this._accept(TokenType.VariableName, true)\n      if (!shorthand || !this._accept(TokenType.CurlyClose)) {\n        this._backTo(token)\n        return false\n      } else {\n        parent.appendChild(new FormatString(Number(index), shorthand))\n        return true\n      }\n\n    } else if (this._accept(TokenType.Plus)) {\n      // ${1:+<if>}\n      let ifValue = this._until(TokenType.CurlyClose)\n      if (ifValue) {\n        parent.appendChild(new FormatString(Number(index), undefined, ifValue, undefined))\n        return true\n      }\n\n    } else if (this._accept(TokenType.Dash)) {\n      // ${2:-<else>}\n      let elseValue = this._until(TokenType.CurlyClose)\n      if (elseValue) {\n        parent.appendChild(new FormatString(Number(index), undefined, undefined, elseValue))\n        return true\n      }\n\n    } else if (this._accept(TokenType.QuestionMark)) {\n      // ${2:?<if>:<else>}\n      let ifValue = this._until(TokenType.Colon)\n      if (ifValue) {\n        let elseValue = this._until(TokenType.CurlyClose)\n        if (elseValue) {\n          parent.appendChild(new FormatString(Number(index), undefined, ifValue, elseValue))\n          return true\n        }\n      }\n\n    } else {\n      let elseValue = this._until(TokenType.CurlyClose)\n      if (elseValue) {\n        parent.appendChild(new FormatString(Number(index), undefined, undefined, elseValue))\n        return true\n      }\n    }\n\n    this._backTo(token)\n    return false\n  }\n\n  private _parseCodeBlock(parent: Marker): boolean {\n    if (!this.ultisnip) return false\n    const token = this._token\n    if (!this._accept(TokenType.BackTick)) {\n      return false\n    }\n    let text = this._until(TokenType.BackTick, true)\n    // `shell code` `!v` `!p`\n    if (text) {\n      if (!text.startsWith('!')) {\n        let marker = new CodeBlock(text.trim(), 'shell')\n        parent.appendChild(marker)\n        return true\n      }\n      if (text.startsWith('!v')) {\n        let marker = new CodeBlock(text.slice(2).trim(), 'vim')\n        parent.appendChild(marker)\n        return true\n      }\n      if (text.startsWith('!p')) {\n        let code = text.slice(2)\n        if (code.indexOf('\\n') == -1) {\n          let marker = new CodeBlock(code.trim(), 'python')\n          parent.appendChild(marker)\n        } else {\n          let codes = code.split(/\\r?\\n/)\n          codes = codes.filter(s => !/^\\s*$/.test(s))\n          if (!codes.length) return true\n          // format multi line code\n          let ind = codes[0].match(/^\\s*/)[0]\n          if (ind.length && codes.every(s => s.startsWith(ind))) {\n            codes = codes.map(s => s.slice(ind.length))\n          }\n          if (ind == ' ' && codes[0].startsWith(ind)) codes[0] = codes[0].slice(1)\n          let marker = new CodeBlock(codes.join('\\n'), 'python')\n          parent.appendChild(marker)\n        }\n        return true\n      }\n    }\n    this._backTo(token)\n    return false\n  }\n\n  private _parseAnything(marker: Marker): boolean {\n    if (this._token.type !== TokenType.EOF) {\n      let text = this._scanner.tokenText(this._token)\n      marker.appendChild(new Text(text))\n      this._accept(undefined)\n      return true\n    }\n    return false\n  }\n}\n\nconst escapedCharacters = [':', '(', ')', '{', '}']\n// \\u \\l \\U \\L \\E \\n \\t\nexport function transformEscapes(input: string, backslashIndexes = []): string {\n  let res = ''\n  let len = input.length\n  let i = 0\n  let toUpper = false\n  let toLower = false\n  while (i < len) {\n    let ch = input[i]\n    if (ch.charCodeAt(0) === CharCode.Backslash && !backslashIndexes.includes(i)) {\n      let next = input[i + 1]\n      if (escapedCharacters.includes(next)) {\n        i++\n        continue\n      }\n      if (next == 'u' || next == 'l') {\n        // Uppercase/Lowercase next letter\n        let follow = input[i + 2]\n        if (follow) res = res + (next == 'u' ? follow.toUpperCase() : follow.toLowerCase())\n        i = i + 3\n        continue\n      }\n      if (next == 'U' || next == 'L') {\n        // Uppercase/Lowercase to \\E\n        if (next == 'U') {\n          toUpper = true\n        } else {\n          toLower = true\n        }\n        i = i + 2\n        continue\n      }\n      if (next == 'E') {\n        toUpper = false\n        toLower = false\n        i = i + 2\n        continue\n      }\n      if (next == 'n') {\n        res += '\\n'\n        i = i + 2\n        continue\n      }\n      if (next == 't') {\n        res += '\\t'\n        i = i + 2\n        continue\n      }\n    }\n    if (toUpper) {\n      ch = ch.toUpperCase()\n    } else if (toLower) {\n      ch = ch.toLowerCase()\n    }\n    res += ch\n    i++\n  }\n  return res\n}\n\n// merge adjacent Texts of marker's children\nexport function mergeTexts(marker: Marker, begin = 0): void {\n  let { children } = marker\n  let end: number | undefined\n  let start: number\n  for (let i = begin; i < children.length; i++) {\n    let m = children[i]\n    if (m instanceof Text) {\n      if (start !== undefined) {\n        end = i\n      } else {\n        start = i\n      }\n    } else {\n      if (end !== undefined) {\n        break\n      }\n      start = undefined\n    }\n  }\n  if (end === undefined) return\n  let newText = ''\n  for (let i = start; i <= end; i++) {\n    newText += children[i].toString()\n  }\n  let m = new Text(newText)\n  children.splice(start, end - start + 1, m)\n  m.parent = marker\n  return mergeTexts(marker, start + 1)\n}\n\nexport function getPlaceholderId(p: Placeholder): number {\n  if (typeof p.id === 'number') return p.id\n  p.id = id++\n  return p.id\n}\n"
  },
  {
    "path": "src/snippets/session.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { Position, Range, StringValue, TextEdit } from 'vscode-languageserver-types'\nimport events from '../events'\nimport { createLogger } from '../logger'\nimport Document from '../model/document'\nimport { LinesTextDocument } from '../model/textdocument'\nimport { DidChangeTextDocumentParams, JumpInfo, TextDocumentContentChange, UltiSnippetOption } from '../types'\nimport { defaultValue, waitNextTick } from '../util'\nimport { getTextEdit } from '../util/diff'\nimport { onUnexpectedError } from '../util/errors'\nimport { omit } from '../util/lodash'\nimport { Mutex } from '../util/mutex'\nimport { equals } from '../util/object'\nimport { comparePosition, emptyRange, getEnd, positionInRange, rangeInRange } from '../util/position'\nimport { CancellationTokenSource, Emitter, Event } from '../util/protocol'\nimport { byteIndex } from '../util/string'\nimport { filterSortEdits, reduceTextEdit } from '../util/textedit'\nimport window from '../window'\nimport workspace from '../workspace'\nimport { executePythonCode, generateContextId, getInitialPythonCode } from './eval'\nimport { getPlaceholderId, Placeholder, Text, TextmateSnippet } from './parser'\nimport { CocSnippet, CocSnippetPlaceholder, getNextPlaceholder, getUltiSnipActionCodes } from \"./snippet\"\nimport { SnippetString } from './string'\nimport { toSnippetString, UltiSnippetContext, wordsSource } from './util'\nimport { SnippetVariableResolver } from \"./variableResolve\"\nconst logger = createLogger('snippets-session')\nconst NAME_SPACE = 'snippets'\n\ninterface DocumentChange {\n  version: number\n  change: TextDocumentContentChange\n}\n\nexport interface SnippetEdit {\n  range: Range\n  snippet: string | SnippetString | StringValue\n}\n\nexport interface SnippetConfig {\n  readonly highlight: boolean\n  readonly nextOnDelete: boolean\n  readonly preferComplete: boolean\n}\n\nexport class SnippetSession {\n  public mutex = new Mutex()\n  private current: Placeholder\n  private textDocument: LinesTextDocument\n  private tokenSource: CancellationTokenSource\n  private _applying = false\n  private _paused = false\n  public snippet: CocSnippet = null\n  private _onActiveChange = new Emitter<boolean>()\n  private _selected = false\n  public readonly onActiveChange: Event<boolean> = this._onActiveChange.event\n\n  constructor(\n    private nvim: Neovim,\n    public readonly document: Document,\n    private readonly config: SnippetConfig\n  ) {\n  }\n\n  public get selected(): boolean {\n    return this._selected\n  }\n\n  public async insertSnippetEdits(edits: SnippetEdit[]): Promise<boolean> {\n    if (edits.length === 0) return this.isActive\n    if (edits.length === 1) return await this.start(toSnippetString(edits[0].snippet), edits[0].range, false)\n    const textDocument = this.document.textDocument\n    const textEdits = filterSortEdits(textDocument, edits.map(e => TextEdit.replace(e.range, toSnippetString(e.snippet))))\n    const len = textEdits.length\n    const snip = new TextmateSnippet()\n    for (let i = 0; i < len; i++) {\n      let range = textEdits[i].range\n      let placeholder = new Placeholder(i + 1)\n      placeholder.appendChild(new Text(textDocument.getText(range)))\n      snip.appendChild(placeholder)\n      if (i != len - 1) {\n        let r = Range.create(range.end, textEdits[i + 1].range.start)\n        snip.appendChild(new Text(textDocument.getText(r)))\n      }\n    }\n    this.deactivate()\n    const resolver = new SnippetVariableResolver(this.nvim, workspace.workspaceFolderControl)\n    let snippet = new CocSnippet(snip, textEdits[0].range.start, this.nvim, resolver)\n    await snippet.init()\n    this.activate(snippet)\n    // reverse insert needed\n    for (let i = len - 1; i >= 0; i--) {\n      let idx = i + 1\n      this.current = snip.placeholders.find(o => o.index === idx)\n      let edit = textEdits[i]\n      await this.start(edit.newText, edit.range, false)\n    }\n    return this.isActive\n  }\n\n  public async start(inserted: string, range: Range, select = true, context?: UltiSnippetContext): Promise<boolean> {\n    let { document, snippet } = this\n    this._paused = false\n    const edits: TextEdit[] = []\n    let textmateSnippet: TextmateSnippet\n    if (inserted.length === 0) return this.isActive\n    if (snippet && rangeInRange(range, snippet.range)) {\n      // update all snippet.\n      let oldRange = snippet.range\n      let previous = snippet.text\n      textmateSnippet = await this.snippet.replaceWithSnippet(range, inserted, this.current, context)\n      let edit = reduceTextEdit({\n        range: oldRange,\n        newText: this.snippet.text\n      }, previous)\n      edits.push(edit)\n    } else {\n      this.deactivate()\n      const resolver = new SnippetVariableResolver(this.nvim, workspace.workspaceFolderControl)\n      snippet = new CocSnippet(inserted, range.start, this.nvim, resolver)\n      await snippet.init(context)\n      textmateSnippet = snippet.tmSnippet\n      edits.push(TextEdit.replace(range, snippet.text))\n      // try fix indent of text after snippet when insert new line\n      if (inserted.replace(/\\$0$/, '').endsWith('\\n')) {\n        const currentLine = document.getline(range.start.line)\n        const remain = currentLine.slice(range.end.character)\n        if (remain.length) {\n          let s = range.end.character\n          let l = remain.match(/^\\s*/)[0].length\n          let r = Range.create(range.end.line, s, range.end.line, s + l)\n          edits.push(TextEdit.replace(r, currentLine.match(/^\\s*/)[0]))\n        }\n      }\n    }\n    this.current = textmateSnippet.first\n    this.nvim.call('coc#compat#del_var', ['coc_selected_text'], true)\n    await this.applyEdits(edits)\n    this.activate(snippet)\n    // Not delay, avoid unexpected character insert\n    if (context) await this.tryPostExpand(textmateSnippet)\n    let { placeholder } = this\n    if (select && placeholder) await this.selectPlaceholder(placeholder, true)\n    return this.isActive\n  }\n\n  private async tryPostExpand(textmateSnippet: TextmateSnippet): Promise<void> {\n    let result = getUltiSnipActionCodes(textmateSnippet, 'postExpand')\n    if (!result) return\n    const { start, end } = this.snippet.range\n    const [code, resetCodes] = result\n    let pos = `[${start.line},${start.character},${end.line},${end.character}]`\n    let codes = [...resetCodes, `snip = coc_ultisnips_dict[\"PostExpandContext\"](${pos})`, code]\n    this.cancel()\n    await executePythonCode(this.nvim, codes)\n    await this.forceSynchronize()\n  }\n\n  private async tryPostJump(code: string, resetCodes: string[], info: JumpInfo, bufnr: number): Promise<void> {\n    // make events.requesting = false\n    await waitNextTick()\n    this.nvim.setVar('coc_ultisnips_tabstops', info.tabstops, true)\n    const { snippet_start, snippet_end } = info\n    let pos = `[${snippet_start.line},${snippet_start.character},${snippet_end.line},${snippet_end.character}]`\n    let codes = [...resetCodes, `snip = coc_ultisnips_dict[\"PostJumpContext\"](${pos},${info.index},${info.forward ? 1 : 0})`, code]\n    this.cancel()\n    await executePythonCode(this.nvim, codes)\n    await this.forceSynchronize()\n    void events.fire('PlaceholderJump', [bufnr, info])\n  }\n\n  public async removeWhiteSpaceBefore(placeholder: CocSnippetPlaceholder): Promise<void> {\n    if (!emptyRange(placeholder.range)) return\n    let pos = placeholder.range.start\n    let line = this.document.getline(pos.line)\n    let ms = line.match(/\\s+$/)\n    if (ms && line.length === pos.character) {\n      let startCharacter = pos.character - ms[0].length\n      let textEdit = TextEdit.del(Range.create(pos.line, startCharacter, pos.line, pos.character))\n      await this.document.applyEdits([textEdit])\n      await this.forceSynchronize()\n    }\n  }\n\n  private async applyEdits(edits: TextEdit[], joinundo = false): Promise<void> {\n    let { document } = this\n    this._applying = true\n    await document.applyEdits(edits, joinundo)\n    this._applying = false\n    this.textDocument = document.textDocument\n  }\n\n  public async nextPlaceholder(): Promise<void> {\n    await this.forceSynchronize()\n    if (!this.current) return\n    let marker = this.current\n    if (this.snippet.getUltiSnipOption(marker, 'removeWhiteSpace')) {\n      let { placeholder } = this\n      if (placeholder) await this.removeWhiteSpaceBefore(placeholder)\n    }\n    const p = this.snippet.getPlaceholderOnJump(marker, true)\n    await this.selectPlaceholder(p, true)\n  }\n\n  public async previousPlaceholder(): Promise<void> {\n    await this.forceSynchronize()\n    if (!this.current) return\n    const p = this.snippet.getPlaceholderOnJump(this.current, false)\n    await this.selectPlaceholder(p, true, false)\n  }\n\n  public async selectCurrentPlaceholder(triggerAutocmd = true): Promise<void> {\n    await this.forceSynchronize()\n    let { placeholder } = this\n    if (placeholder) await this.selectPlaceholder(placeholder, triggerAutocmd)\n  }\n\n  public async selectPlaceholder(placeholder: CocSnippetPlaceholder | undefined, triggerAutocmd = true, forward = true): Promise<void> {\n    let { nvim, document } = this\n    if (!document || !placeholder) return\n    this._selected = true\n    let { start, end } = placeholder.range\n    const line = document.getline(start.line)\n    const marker = this.current = placeholder.marker\n    const range = this.snippet.getSnippetRange(marker)\n    const tabstops = this.snippet.getSnippetTabstops(marker)\n    if (marker instanceof Placeholder && marker.choice && marker.choice.options.length) {\n      const col = byteIndex(line, start.character) + 1\n      wordsSource.words = marker.choice.options.map(o => o.value)\n      wordsSource.startcol = col - 1\n      // pum not work when use request during request.\n      nvim.call('coc#snippet#show_choices', [start.line + 1, col, end, placeholder.value], true)\n    } else {\n      await this.select(placeholder)\n      this.highlights()\n    }\n    if (triggerAutocmd) nvim.call('coc#util#do_autocmd', ['CocJumpPlaceholder'], true)\n    let info: JumpInfo = {\n      forward,\n      tabstops,\n      snippet_start: range.start,\n      snippet_end: range.end,\n      index: placeholder.index,\n      range: placeholder.range,\n      charbefore: start.character == 0 ? '' : line.slice(start.character - 1, start.character)\n    }\n    let result = getUltiSnipActionCodes(marker, 'postJump')\n    if (result) {\n      this.tryPostJump(result[0], result[1], info, document.bufnr).catch(onUnexpectedError)\n    } else {\n      void events.fire('PlaceholderJump', [document.bufnr, info])\n    }\n    this.checkFinalPlaceholder()\n  }\n\n  public checkFinalPlaceholder(): void {\n    let current = this.current\n    if (current && current.index === 0) {\n      const { snippet } = current\n      if (snippet === this.snippet.tmSnippet) {\n        logger.info('Jump to final placeholder, cancelling snippet session')\n        this.deactivate()\n      } else {\n        let marker = snippet.parent\n        this.snippet.deactivateSnippet(snippet)\n        if (marker instanceof Placeholder) {\n          this.current = marker\n        }\n      }\n    }\n  }\n\n  private highlights(): void {\n    let { current, config } = this\n    if (!current || !config.highlight || events.bufnr !== this.bufnr) return\n    let buf = this.document.buffer\n    this.nvim.pauseNotification()\n    buf.clearNamespace(NAME_SPACE)\n    let ranges = this.snippet.getRanges(current)\n    buf.highlightRanges(NAME_SPACE, 'CocSnippetVisual', ranges)\n    this.nvim.resumeNotification(true, true)\n  }\n\n  private async select(placeholder: CocSnippetPlaceholder): Promise<void> {\n    let { range, value } = placeholder\n    let { nvim } = this\n    if (value.length > 0) {\n      await nvim.call('coc#snippet#select', [range.start, range.end, value])\n    } else {\n      await nvim.call('coc#snippet#move', [range.start])\n    }\n    nvim.redrawVim()\n  }\n\n  public async checkPosition(): Promise<void> {\n    if (!this.isActive) return\n    let position = await window.getCursorPosition()\n    if (this.snippet && positionInRange(position, this.snippet.range) != 0) {\n      logger.info('Cursor insert out of range, cancelling snippet session')\n      this.deactivate()\n    }\n  }\n\n  public onTextChange(): void {\n    this.cancel()\n  }\n\n  public onChange(e: DidChangeTextDocumentParams): void {\n    if (this._applying || !this.isActive || this._paused) return\n    let changes = e.contentChanges\n    // if not cancel, applyEdits would change latest document lines, which could be wrong.\n    this.cancel()\n    this.synchronize({ version: e.textDocument.version, change: changes[0] }).catch(onUnexpectedError)\n  }\n\n  public async synchronize(change?: DocumentChange): Promise<void> {\n    const { document, isActive } = this\n    this._paused = false\n    if (!isActive) return\n    await this.mutex.use(() => {\n      if (!document.attached\n        || document.dirty\n        || !this.snippet\n        || !this.textDocument\n        || document.version === this.version) return Promise.resolve()\n      if (change && (change.version - this.version !== 1 || document.version != change.version)) {\n        // can't be used any more\n        change = undefined\n      }\n      return this._synchronize(change)\n    })\n  }\n\n  public async _synchronize(documentChange?: DocumentChange): Promise<void> {\n    let { document, textDocument, current, snippet } = this\n    const newDocument = document.textDocument\n    if (equals(textDocument.lines, newDocument.lines)) {\n      this.textDocument = newDocument\n      return\n    }\n    const startTs = Date.now()\n    let tokenSource = this.tokenSource = new CancellationTokenSource()\n    const cursor = events.bufnr == document.bufnr ? await window.getCursorPosition() : undefined\n    if (tokenSource.token.isCancellationRequested) return\n    let change = documentChange?.change\n    if (!change) {\n      let edit = getTextEdit(textDocument.lines, newDocument.lines, cursor, events.insertMode)\n      change = { range: edit.range, text: edit.newText }\n    }\n    const { range, start } = snippet\n    let c = comparePosition(change.range.start, range.end)\n    // consider insert at the end\n    let insertEnd = emptyRange(change.range) && snippet.hasEndPlaceholder\n    // change after snippet, do nothing\n    if (c > 0 || (c === 0 && !insertEnd)) {\n      logger.info('Content change after snippet')\n      this.textDocument = newDocument\n      return\n    }\n    // consider insert at the beginning, exclude new lines before.\n    c = comparePosition(change.range.end, range.start)\n    let insertBeginning = emptyRange(change.range)\n      && !change.text.endsWith('\\n')\n      && snippet.hasBeginningPlaceholder\n    if (c < 0 || (c === 0 && !insertBeginning)) {\n      // change before beginning, reset position\n      let changeEnd = change.range.end\n      let checkCharacter = range.start.line === changeEnd.line\n      let newLines = change.text.split(/\\n/)\n      let lc = newLines.length - (changeEnd.line - change.range.start.line + 1)\n      let cc = 0\n      if (checkCharacter) {\n        if (newLines.length > 1) {\n          cc = newLines[newLines.length - 1].length - changeEnd.character\n        } else {\n          cc = change.range.start.character + change.text.length - changeEnd.character\n        }\n      }\n      this.snippet.resetStartPosition(Position.create(start.line + lc, start.character + cc))\n      this.textDocument = newDocument\n      logger.info('Content change before snippet, reset snippet position')\n      return\n    }\n    if (!rangeInRange(change.range, range)) {\n      logger.info('Before and snippet body changed, cancel snippet session')\n      this.deactivate()\n      return\n    }\n    const nextPlaceholder = getNextPlaceholder(current, true)\n    const id = getPlaceholderId(current)\n    const res = await this.snippet.replaceWithText(change.range, change.text, tokenSource.token, current, cursor)\n    this.tokenSource = undefined\n    if (!res) {\n      if (this.snippet) {\n        // find out the cloned placeholder\n        let marker = this.snippet.getPlaceholderById(id, current.index)\n        // the current could be invalid, so not able to find a cloned placeholder.\n        this.current = defaultValue(marker, this.snippet.tmSnippet.first)\n      }\n      return\n    }\n    this.textDocument = newDocument\n    let { snippetText, delta } = res\n    let changedRange = Range.create(start, getEnd(start, snippetText))\n    // check if snippet not changed as expected\n    const expected = newDocument.getText(changedRange)\n    if (expected !== snippetText) {\n      logger.error(`Something went wrong with the snippet implementation`, change, snippetText, expected)\n      this.deactivate()\n      return\n    }\n    let newText = this.snippet.text\n    // further update caused by related placeholders or python CodeBlock change\n    if (newText !== snippetText) {\n      let edit = reduceTextEdit({ range: changedRange, newText }, snippetText)\n      await this.applyEdits([edit], true)\n      if (delta) {\n        this.nvim.call(`coc#cursor#move_to`, [cursor.line + delta.line, cursor.character + delta.character], true)\n      }\n    }\n    this.highlights()\n    logger.debug('update cost:', Date.now() - startTs, res.delta)\n    this.trySelectNextOnDelete(current, nextPlaceholder).catch(onUnexpectedError)\n    return\n  }\n\n  public async trySelectNextOnDelete(curr: Placeholder, next: Placeholder | undefined): Promise<void> {\n    if (!this.config.nextOnDelete\n      || !this.snippet\n      || !curr\n      || (curr.snippet != null && curr.toString() != '')\n      || !next\n    ) return\n    let p = this.snippet.getPlaceholderByMarker(next)\n    // the placeholder could be removed\n    if (p) await this.selectPlaceholder(p, true)\n  }\n\n  public async forceSynchronize(): Promise<void> {\n    if (this.isActive) {\n      this._paused = false\n      await this.document.patchChange()\n      await this.synchronize()\n    } else {\n      await this.document.patchChange()\n    }\n  }\n\n  public async onCompleteDone(): Promise<void> {\n    if (this.isActive) {\n      this._paused = false\n      this.document._forceSync()\n      await this.synchronize()\n    }\n  }\n\n  public get version(): number {\n    return this.textDocument ? this.textDocument.version : -1\n  }\n\n  public get isActive(): boolean {\n    return this.snippet != null\n  }\n\n  public get bufnr(): number {\n    return this.document.bufnr\n  }\n\n  private activate(snippet: CocSnippet): void {\n    if (this.isActive) return\n    this.snippet = snippet\n    this.nvim.call('coc#snippet#enable', [this.bufnr, this.config.preferComplete ? 1 : 0], true)\n    this._onActiveChange.fire(true)\n  }\n\n  public deactivate(): void {\n    this.cancel()\n    if (!this.isActive) return\n    this.snippet = null\n    this.current = null\n    this.nvim.call('coc#snippet#disable', [this.bufnr], true)\n    if (this.config.highlight) this.nvim.call('coc#highlight#clear_highlight', [this.bufnr, NAME_SPACE, 0, -1], true)\n    this._onActiveChange.fire(false)\n    logger.debug(`session ${this.bufnr} deactivate`)\n  }\n\n  public get placeholder(): CocSnippetPlaceholder | undefined {\n    if (!this.snippet || !this.current) return undefined\n    return this.snippet.getPlaceholderByMarker(this.current)\n  }\n\n  public cancel(pause = false): void {\n    if (!this.isActive) return\n    if (this.tokenSource) {\n      this.tokenSource.cancel()\n      this.tokenSource.dispose()\n      this.tokenSource = null\n    }\n    if (pause) this._paused = true\n  }\n\n  public dispose(): void {\n    this.cancel()\n    this._onActiveChange.dispose()\n    this.snippet = null\n    this.current = null\n    this.textDocument = undefined\n  }\n\n  public async resolveSnippet(nvim: Neovim, snippetString: string, ultisnip?: UltiSnippetOption): Promise<string> {\n    let context: UltiSnippetContext\n    if (ultisnip) {\n      // avoid all actions\n      ultisnip = omit(ultisnip, ['actions'])\n      context = Object.assign({\n        range: Range.create(0, 0, 0, 0),\n        line: ''\n      }, ultisnip, { id: generateContextId(events.bufnr) })\n      if (ultisnip.noPython !== true && snippetString.includes('`!p')) {\n        await executePythonCode(nvim, getInitialPythonCode(context))\n      }\n    }\n    const resolver = new SnippetVariableResolver(nvim, workspace.workspaceFolderControl)\n    const snippet = new CocSnippet(snippetString, Position.create(0, 0), nvim, resolver)\n    await snippet.init(context)\n    return snippet.text\n  }\n}\n"
  },
  {
    "path": "src/snippets/snippet.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { Position, Range } from 'vscode-languageserver-types'\nimport { LinesTextDocument } from '../model/textdocument'\nimport { TabStopInfo } from '../types'\nimport { defaultValue, waitWithToken } from '../util'\nimport { adjacentPosition, comparePosition, emptyRange, getEnd, positionInRange, rangeInRange, samePosition } from '../util/position'\nimport { CancellationToken } from '../util/protocol'\nimport { getPyBlockCode, getResetPythonCode, hasPython } from './eval'\nimport { Marker, mergeTexts, Placeholder, SnippetParser, Text, TextmateSnippet, VariableResolver } from \"./parser\"\nimport { getAction, getNewRange, getTextAfter, getTextBefore, UltiSnippetContext, UltiSnipsAction, UltiSnipsOption } from './util'\n\nexport interface ParentInfo {\n  marker: TextmateSnippet | Placeholder\n  range: Range\n}\n\nexport interface CocSnippetPlaceholder {\n  index: number\n  marker: Placeholder\n  value: string\n  primary: boolean\n  // range in current buffer\n  range: Range\n}\n\nexport interface CocSnippetInfo {\n  marker: TextmateSnippet\n  value: string\n  range: Range\n}\n\nexport interface ChangedInfo {\n  // The changed marker\n  marker: Marker\n  // snippet text with only changed marker changed\n  snippetText: string\n  delta?: Position\n}\n\nexport interface CursorDelta {\n  // the line change cursor should move\n  line: number\n  // the character count cursor should move\n  character: number\n}\n\nexport class CocSnippet {\n  // placeholders and snippets from top to bottom\n  private _markerSequence: (Placeholder | TextmateSnippet)[] = []\n  private _placeholders: CocSnippetPlaceholder[] = []\n  // from upper to lower\n  private _snippets: CocSnippetInfo[] = []\n  private _text: string\n  private _tmSnippet: TextmateSnippet\n\n  constructor(\n    private snippet: string | TextmateSnippet,\n    private position: Position,\n    private nvim: Neovim,\n    private resolver?: VariableResolver,\n  ) {\n  }\n\n  public get tmSnippet(): TextmateSnippet {\n    return this._tmSnippet\n  }\n\n  public get snippets(): TextmateSnippet[] {\n    return this._snippets.map(o => o.marker)\n  }\n\n  private getSnippet(marker: Marker): TextmateSnippet | undefined {\n    return marker instanceof TextmateSnippet ? marker : marker.snippet\n  }\n\n  public deactivateSnippet(snip: TextmateSnippet | undefined): void {\n    if (!snip) return\n    let marker = snip.parent\n    if (marker) {\n      let text = new Text(snip.toString())\n      snip.replaceWith(text)\n      this.synchronize()\n    }\n  }\n\n  public getUltiSnipOption(marker: Marker, key: UltiSnipsOption): boolean | undefined {\n    let snip = this.getSnippet(marker)\n    if (!snip) return undefined\n    let context = snip.related.context\n    if (!context) return undefined\n    return context[key]\n  }\n\n  public async init(ultisnip?: UltiSnippetContext): Promise<void> {\n    if (typeof this.snippet === 'string') {\n      const parser = new SnippetParser(!!ultisnip)\n      const snippet = parser.parse(this.snippet, true)\n      this._tmSnippet = snippet\n    } else {\n      this._tmSnippet = this.snippet\n    }\n    await this.resolve(this._tmSnippet, ultisnip)\n    this.synchronize()\n  }\n\n  private async resolve(snippet: TextmateSnippet, ultisnip?: UltiSnippetContext): Promise<void> {\n    let { resolver, nvim } = this\n    if (resolver) await snippet.resolveVariables(resolver)\n    if (ultisnip) {\n      let pyCodes: string[] = []\n      snippet.related.context = ultisnip\n      if (ultisnip.noPython !== true) {\n        if (snippet.hasPythonBlock) {\n          pyCodes = getPyBlockCode(ultisnip)\n        } else if (hasPython(ultisnip)) {\n          pyCodes = getResetPythonCode(ultisnip)\n        }\n        if (pyCodes.length > 0) {\n          snippet.related.codes = pyCodes\n        }\n      }\n      // Code from getSnippetPythonCode already executed before snippet insert\n      await snippet.evalCodeBlocks(nvim, pyCodes)\n    }\n  }\n\n  public getPlaceholderOnJump(current: Placeholder | undefined, forward: boolean): CocSnippetPlaceholder | undefined {\n    const p = getNextPlaceholder(current, forward)\n    return p ? this.getPlaceholderByMarker(p) : undefined\n  }\n\n  /**\n   * Same index and in same snippet only\n   */\n  public getRanges(marker: Placeholder): Range[] {\n    if (marker.toString().length === 0 || !marker.snippet) return []\n    let tmSnippet = marker.snippet\n    let placeholders = this._placeholders.filter(o => o.index == marker.index && o.marker.snippet === tmSnippet)\n    return placeholders.map(o => o.range).filter(r => !emptyRange(r))\n  }\n\n  /**\n   * Find the most possible marker contains range, throw error when not found\n   */\n  public findParent(range: Range, current?: Placeholder): ParentInfo {\n    const isInsert = emptyRange(range)\n    let marker: TextmateSnippet | Placeholder\n    let markerRange: Range\n    const { _snippets, _placeholders, _markerSequence } = this\n    const seq = _markerSequence.filter(o => o !== current)\n    if (current && _markerSequence.includes(current)) seq.push(current)\n    const list = seq.map(m => {\n      return m instanceof TextmateSnippet ? _snippets.find(o => o.marker === m) : _placeholders.find(o => o.marker === m)\n    })\n    for (let index = list.length - 1; index >= 0; index--) {\n      const o = list[index]\n      if (rangeInRange(range, o.range)) {\n        // Gives choice and final placeholder lower priority for text insert\n        if (isInsert\n          && o.marker instanceof Placeholder\n          && (o.marker.choice || o.marker.index === 0)\n          && o.marker !== current\n          && adjacentPosition(range.start, o.range)\n        ) {\n          continue\n        }\n        marker = o.marker\n        markerRange = o.range\n        break\n      }\n    }\n    if (!marker) throw new Error(`Unable to find parent marker in range ${JSON.stringify(range, null, 2)}`)\n    return { marker, range: markerRange }\n  }\n\n  /**\n   * The change must happens with same marker parents, return the changed marker\n   */\n  public replaceWithMarker(range: Range, marker: Marker, current?: Placeholder): Marker {\n    // the range should already inside this.range\n    const isInsert = emptyRange(range)\n    const result = this.findParent(range, current)\n    let parentMarker = result.marker\n    let parentRange = result.range\n    // search children need to be replaced\n    const children = parentMarker.children\n    let pos = parentRange.start\n    let startIdx = 0\n    let deleteCount = 0\n    const { start, end } = range\n    let startMarker: Marker | undefined\n    let endMarker: Marker | undefined\n    let preText = ''\n    let afterText = ''\n    let len = children.length\n    for (let i = 0; i < len; i++) {\n      let child = children[i]\n      let value = child.toString()\n      let s = Position.create(pos.line, pos.character)\n      let e = getEnd(s, value)\n      let r = Range.create(s, e)\n      // Not include start position at the end of marker\n      if (startMarker === undefined && positionInRange(start, r) === 0 && !samePosition(start, e)) {\n        startMarker = child\n        startIdx = i\n        preText = getTextBefore(Range.create(s, e), value, start)\n        // avoid delete when insert at the beginning\n        if (isInsert && samePosition(end, s)) {\n          endMarker = child\n          break\n        }\n      }\n      if (startMarker != null) {\n        let val = positionInRange(end, r)\n        if (val === 0) {\n          endMarker = child\n          afterText = getTextAfter(Range.create(s, e), value, end)\n        }\n        deleteCount += 1\n      } else if (i == len - 1 && samePosition(start, e)) {\n        // insert at the end\n        startIdx = len\n      }\n      if (endMarker != null) break\n      pos = e\n    }\n    if (marker instanceof Text) {\n      let newText = new Text(preText + marker.value + afterText)\n      // Placeholder have to contain empty Text\n      parentMarker.children.splice(startIdx, deleteCount, newText)\n      newText.parent = parentMarker\n      mergeTexts(parentMarker, 0)\n      // Placeholder should not have line break at the beginning\n      if (parentMarker instanceof Placeholder && parentMarker.children[0] instanceof Text) {\n        let text = parentMarker.children[0]\n        if (text.value.startsWith('\\n')) {\n          text.replaceWith(new Text(text.value.slice(1)))\n          parentMarker.insertBefore('\\n')\n        }\n      }\n    } else {\n      let markers: Marker[] = []\n      if (preText) markers.push(new Text(preText))\n      if (parentMarker instanceof TextmateSnippet) {\n        // create a new Placeholder to make it selectable by jump\n        let p = new Placeholder((current ? current.index : 0) + Math.random())\n        p.appendChild(marker)\n        p.primary = true\n        markers.push(p)\n      } else {\n        markers.push(marker)\n      }\n      if (afterText) markers.push(new Text(afterText))\n      children.splice(startIdx, deleteCount, ...markers)\n      markers.forEach(m => m.parent = parentMarker)\n      if (preText.length > 0 || afterText.length > 0) {\n        mergeTexts(parentMarker, 0)\n      }\n    }\n    if (parentMarker instanceof Placeholder && !parentMarker.primary) {\n      let first = parentMarker.children[0]\n      // Replace with Text\n      if (parentMarker.children.length === 1 && first instanceof Text) {\n        parentMarker.replaceWith(first)\n        return first\n      }\n      // increase index to not synchronize\n      if (Number.isInteger(parentMarker.index)) {\n        parentMarker.index += 0.1\n      }\n    }\n    return parentMarker\n  }\n\n  /**\n   * Replace range with text, return new Cursor position when cursor provided\n   *\n   * Get new Cursor position for synchronize update only.\n   * The cursor position should already adjusted before call this function.\n   */\n  public async replaceWithText(range: Range, text: string, token: CancellationToken, current?: Placeholder, cursor?: Position): Promise<ChangedInfo | undefined> {\n    let cloned = this._tmSnippet.clone()\n    let marker = this.replaceWithMarker(range, new Text(text), current)\n    let snippetText = this._tmSnippet.toString()\n    // No need further action when only affect the top snippet.\n    if (marker === this._tmSnippet) {\n      this.synchronize()\n      return { snippetText, marker }\n    }\n    // Try keep relative position with marker, since no more change for marker.\n    let sp = this.getMarkerPosition(marker)\n    let changeCharacter = sp && cursor && sp.line === cursor.line\n    const reset = () => {\n      this._tmSnippet = cloned\n      this.synchronize()\n    }\n    token.onCancellationRequested(reset)\n    await this.onMarkerUpdate(marker, token)\n    if (token.isCancellationRequested) return undefined\n    let ep = this.getMarkerPosition(marker)\n    let delta: Position | undefined\n    if (cursor && sp && ep) {\n      let lc = ep.line - sp.line\n      let cc = (changeCharacter ? ep.character - sp.character : 0)\n      if (lc != 0 || cc != 0) delta = Position.create(lc, cc)\n    }\n    return { snippetText, marker, delta }\n  }\n\n  public async replaceWithSnippet(range: Range, text: string, current?: Placeholder, ultisnip?: UltiSnippetContext): Promise<TextmateSnippet> {\n    let snippet = new SnippetParser(!!ultisnip).parse(text, true)\n    // no need to move cursor, there should be placeholder selection afterwards.\n    let marker = this.replaceWithMarker(range, snippet, current)\n    await this.resolve(snippet, ultisnip)\n    await this.onMarkerUpdate(marker, CancellationToken.None)\n    return snippet\n  }\n\n  /**\n   * Get placeholder or snippet start position in current document\n   */\n  public getMarkerPosition(marker: Marker): Position | undefined {\n    if (marker instanceof Placeholder) {\n      let p = this._placeholders.find(o => o.marker === marker)\n      return p ? p.range.start : undefined\n    }\n    let o = this._snippets.find(o => o.marker === marker)\n    return o ? o.range.start : undefined\n  }\n\n  public getSnippetRange(marker: Marker): Range | undefined {\n    let snip = marker.snippet\n    if (!snip) return undefined\n    let info = this._snippets.find(o => o.marker === snip)\n    return info ? info.range : undefined\n  }\n\n  /**\n   * Get TabStops of same snippet.\n   */\n  public getSnippetTabstops(marker: Marker): TabStopInfo[] {\n    let snip = marker.snippet\n    if (!snip) return []\n    let res: TabStopInfo[] = []\n    this._placeholders.forEach(p => {\n      const { start, end } = p.range\n      if (p.marker.snippet === snip && (p.primary || p.index === 0)) {\n        res.push({\n          index: p.index,\n          range: [start.line, start.character, end.line, end.character],\n          text: p.value\n        })\n      }\n    })\n    return res\n  }\n\n  public async onMarkerUpdate(marker: Marker, token: CancellationToken): Promise<void> {\n    let ts = Date.now()\n    while (marker != null) {\n      if (marker instanceof Placeholder) {\n        let snip = marker.snippet\n        if (!snip) break\n        await snip.update(this.nvim, marker, token)\n        if (token.isCancellationRequested) return\n        marker = snip.parent\n      } else {\n        marker = marker.parent\n      }\n    }\n    // Avoid document change fired during document change event, which may cause unexpected behavior.\n    await waitWithToken(Math.max(0, 16 - Date.now() + ts), token)\n    if (token.isCancellationRequested) return\n    this.synchronize()\n  }\n\n  private usePython(snip: TextmateSnippet): boolean {\n    return snip.hasCodeBlock || hasPython(snip.related.context)\n  }\n\n  public get hasPython(): boolean {\n    for (const info of this._snippets) {\n      let snip = info.marker\n      if (this.usePython(snip)) return true\n    }\n    return false\n  }\n\n  public resetStartPosition(pos: Position): void {\n    this.position = pos\n    this.synchronize()\n  }\n\n  public get start(): Position {\n    return Position.create(this.position.line, this.position.character)\n  }\n\n  public get range(): Range {\n    let end = getEnd(this.position, this._text)\n    return Range.create(this.position, end)\n  }\n\n  public get text(): string {\n    return this._text\n  }\n\n  public get hasBeginningPlaceholder(): boolean {\n    let { position } = this\n    return this._placeholders.find(o => o.index !== 0 && comparePosition(o.range.start, position) === 0) != null\n  }\n\n  public get hasEndPlaceholder(): boolean {\n    let position = this._snippets[0].range.end\n    return this._placeholders.find(o => o.index !== 0 && comparePosition(o.range.end, position) === 0) != null\n  }\n\n  public getPlaceholderByMarker(marker: Marker): CocSnippetPlaceholder | undefined {\n    return this._placeholders.find(o => o.marker === marker)\n  }\n\n  public getPlaceholderByIndex(index: number): CocSnippetPlaceholder {\n    let filtered = this._placeholders.filter(o => o.index == index && !o.marker.transform)\n    let find = filtered.find(o => o.primary)\n    return defaultValue(find, filtered[0])\n  }\n\n  public getPlaceholderById(id: number, index: number): Placeholder | undefined {\n    let p = this._tmSnippet.placeholders.find(o => o.id === id)\n    if (p) return p\n    let placeholder = this.getPlaceholderByIndex(index)\n    return placeholder ? placeholder.marker : undefined\n  }\n\n  /**\n   * Should be used after snippet resolved.\n   */\n  public synchronize(): void {\n    const snippet = this._tmSnippet\n    const snippetStr = snippet.toString()\n    const document = new LinesTextDocument('/', '', 0, snippetStr.split(/\\n/), 0, false)\n    const placeholders: CocSnippetPlaceholder[] = []\n    const snippets: CocSnippetInfo[] = []\n    const markerSequence = []\n    const { start } = this\n    snippets.push({ range: Range.create(start, getEnd(start, snippetStr)), marker: snippet, value: snippetStr })\n    markerSequence.push(snippet)\n    // all placeholders, including nested placeholder from snippet\n    let offset = 0\n    snippet.walk(marker => {\n      if (marker instanceof Placeholder && marker.transform == null) {\n        markerSequence.push(marker)\n        const position = document.positionAt(offset)\n        const value = marker.toString()\n        placeholders.push({\n          index: marker.index,\n          value,\n          marker,\n          range: getNewRange(start, position, value),\n          primary: marker.primary === true\n        })\n      } else if (marker instanceof TextmateSnippet) {\n        markerSequence.push(marker)\n        const position = document.positionAt(offset)\n        const value = marker.toString()\n        snippets.push({\n          range: getNewRange(start, position, value),\n          marker,\n          value\n        })\n      }\n      offset += marker.len()\n      return true\n    }, false)\n    this._snippets = snippets\n    this._text = snippetStr\n    this._placeholders = placeholders\n    this._markerSequence = markerSequence\n  }\n}\n\n/**\n * Next or previous placeholder\n */\nexport function getNextPlaceholder(marker: Placeholder | undefined, forward: boolean, nested = false): Placeholder | undefined {\n  if (!marker) return undefined\n  let { snippet } = marker\n  let idx = marker.index\n  if (idx < 0 || !snippet) return undefined\n  let arr: Placeholder[] = []\n  let min_index: number\n  let max_index: number\n  if (idx > 0) {\n    snippet.walk(m => {\n      if (m instanceof Placeholder && !m.transform) {\n        if (\n          (forward && (m.index > idx || m.isFinalTabstop)) ||\n          (!forward && (m.index < idx && !m.isFinalTabstop))\n        ) {\n          arr.push(m)\n          if (!m.isFinalTabstop) {\n            min_index = min_index === undefined ? m.index : Math.min(min_index, m.index)\n          }\n          max_index = max_index === undefined ? m.index : Math.max(max_index, m.index)\n        }\n      }\n      return true\n    }, true)\n    if (arr.length > 0) {\n      arr.sort((a, b) => {\n        if (b.primary && !a.primary) return 1\n        if (a.primary && !b.primary) return -1\n        return 0\n      })\n      if (forward) return min_index === undefined ? arr[0] : arr.find(o => o.index === min_index)\n      return arr.find(o => o.index === max_index)\n    }\n  }\n  if (snippet.parent instanceof Placeholder) {\n    return getNextPlaceholder(snippet.parent, forward, true)\n  }\n  if (nested) return marker\n  return undefined\n}\n\n/**\n * Return action code and reset code of snippet.\n */\nexport function getUltiSnipActionCodes(marker: Marker | undefined, action: UltiSnipsAction): [string, string[]] | undefined {\n  if (!marker) return undefined\n  const snip = marker instanceof TextmateSnippet ? marker : marker.snippet\n  if (!snip) return undefined\n  let context = snip.related.context\n  let code = getAction(context, action)\n  if (!code) return undefined\n  return [code, getResetPythonCode(context)]\n}\n"
  },
  {
    "path": "src/snippets/string.ts",
    "content": "'use strict'\nexport class SnippetString {\n\n  public static isSnippetString(thing: any): thing is SnippetString {\n    if (thing instanceof SnippetString) {\n      return true\n    }\n    if (!thing) {\n      return false\n    }\n    return typeof (thing as SnippetString).value === 'string'\n  }\n\n  private static _escape(value: string): string {\n    return value.replace(/\\$|}|\\\\/g, '\\\\$&')\n  }\n\n  private _tabstop = 1\n\n  public value: string\n\n  constructor(value?: string) {\n    this.value = value || ''\n  }\n\n  public appendText(str: string): SnippetString {\n    this.value += SnippetString._escape(str)\n    return this\n  }\n\n  public appendTabstop(num: number = this._tabstop++): SnippetString {\n    this.value += '$'\n    this.value += num\n    return this\n  }\n\n  public appendPlaceholder(value: string | ((snippet: SnippetString) => any), num: number = this._tabstop++): SnippetString {\n\n    if (typeof value === 'function') {\n      const nested = new SnippetString()\n      nested._tabstop = this._tabstop\n      value(nested)\n      this._tabstop = nested._tabstop\n      value = nested.value\n    } else {\n      value = SnippetString._escape(value)\n    }\n\n    this.value += '${'\n    this.value += num\n    this.value += ':'\n    this.value += value\n    this.value += '}'\n\n    return this\n  }\n\n  public appendChoice(values: string[], num: number = this._tabstop++): SnippetString {\n    const value = values.map(s => s.replaceAll(/[|\\\\,]/g, '\\\\$&')).join(',')\n\n    this.value += '${'\n    this.value += num\n    this.value += '|'\n    this.value += value\n    this.value += '|}'\n\n    return this\n  }\n\n  public appendVariable(name: string, defaultValue?: string | ((snippet: SnippetString) => any)): SnippetString {\n\n    if (typeof defaultValue === 'function') {\n      const nested = new SnippetString()\n      nested._tabstop = this._tabstop\n      defaultValue(nested)\n      this._tabstop = nested._tabstop\n      defaultValue = nested.value\n\n    } else if (typeof defaultValue === 'string') {\n      defaultValue = defaultValue.replace(/\\$|}/g, '\\\\$&') // CodeQL [SM02383] I do not want to escape backslashes here\n    }\n\n    this.value += '${'\n    this.value += name\n    if (defaultValue) {\n      this.value += ':'\n      this.value += defaultValue\n    }\n    this.value += '}'\n\n    return this\n  }\n}\n"
  },
  {
    "path": "src/snippets/util.ts",
    "content": "import { Position, Range, StringValue } from 'vscode-languageserver-types'\nimport type { CompleteOption, ExtendedCompleteItem, ISource } from '../completion/types'\nimport { UltiSnipsActions } from '../types'\nimport { defaultValue } from '../util'\nimport { getEnd } from '../util/position'\nimport { SnippetString } from './string'\n\nexport type UltiSnipsAction = 'preExpand' | 'postExpand' | 'postJump'\n\nexport type UltiSnipsOption = 'trimTrailingWhitespace' | 'removeWhiteSpace' | 'noExpand'\n\nexport interface UltiSnippetContext {\n  id: string\n  /**\n   * line on insert\n   */\n  line: string\n  /**\n   * Range to replace, start.line should equal end.line\n   */\n  range: Range\n  /**\n   * Context python code.\n   */\n  context?: string\n  /**\n   * Regex trigger (python code)\n   */\n  regex?: string\n  /**\n   * Avoid python code eval when is true.\n   */\n  noPython?: boolean\n  /**\n   * Do not expand tabs\n   */\n  noExpand?: boolean\n  /**\n   * Trim all whitespaces from right side of snippet lines.\n   */\n  trimTrailingWhitespace?: boolean\n  /**\n   * Remove whitespace immediately before the cursor at the end of a line before jumping to the next tabstop\n   */\n  removeWhiteSpace?: boolean\n\n  actions?: UltiSnipsActions\n}\n\nexport interface SnippetFormatOptions {\n  tabSize: number\n  insertSpaces: boolean\n  trimTrailingWhitespace?: boolean\n  // options from ultisnips context\n  noExpand?: boolean\n  [key: string]: boolean | number | string | undefined\n}\n\nconst stringStartRe = /\\\\A/\nconst conditionRe = /\\(\\?\\(\\w+\\).+\\|/\nconst commentRe = /\\(\\?#.*?\\)/\nconst namedCaptureRe = /\\(\\?P<\\w+>.*?\\)/\nconst namedReferenceRe = /\\(\\?P=(\\w+)\\)/\nconst regex = new RegExp(`${commentRe.source}|${stringStartRe.source}|${namedCaptureRe.source}|${namedReferenceRe.source}`, 'g')\n\n/**\n * Convert python regex to javascript regex,\n * throw error when unsupported pattern found\n */\nexport function convertRegex(str: string): string {\n  if (str.indexOf('\\\\z') !== -1) {\n    throw new Error('pattern \\\\z not supported')\n  }\n  if (str.indexOf('(?s)') !== -1) {\n    throw new Error('pattern (?s) not supported')\n  }\n  if (str.indexOf('(?x)') !== -1) {\n    throw new Error('pattern (?x) not supported')\n  }\n  if (str.indexOf('\\n') !== -1) {\n    throw new Error('pattern \\\\n not supported')\n  }\n  if (conditionRe.test(str)) {\n    throw new Error('pattern (?id/name)yes-pattern|no-pattern not supported')\n  }\n  return str.replace(regex, (match, p1) => {\n    if (match.startsWith('(?#')) return ''\n    if (match.startsWith('(?P<')) return '(?' + match.slice(3)\n    if (match.startsWith('(?P=')) return `\\\\k<${p1}>`\n    // if (match == '\\\\A') return '^'\n    return '^'\n  })\n}\n\n/**\n * Action code from context or option\n */\nexport function getAction(opt: { actions?: { [key: string]: any } } | undefined, action: UltiSnipsAction): string | undefined {\n  if (!opt || !opt.actions) return undefined\n  return opt.actions[action]\n}\n\nexport function shouldFormat(snippet: string): boolean {\n  if (/^\\s/.test(snippet)) return true\n  if (snippet.indexOf('\\n') !== -1) return true\n  return false\n}\n\nexport function normalizeSnippetString(snippet: string, indent: string, opts: SnippetFormatOptions): string {\n  let lines = snippet.split(/\\r?\\n/)\n  let ind = opts.insertSpaces ? ' '.repeat(opts.tabSize) : '\\t'\n  let tabSize = defaultValue(opts.tabSize, 2)\n  let noExpand = opts.noExpand\n  let trimTrailingWhitespace = opts.trimTrailingWhitespace\n  lines = lines.map((line, idx) => {\n    let space = line.match(/^\\s*/)[0]\n    let pre = space\n    let isTab = space.startsWith('\\t')\n    if (isTab && opts.insertSpaces && !noExpand) {\n      pre = ind.repeat(space.length)\n    } else if (!isTab && !opts.insertSpaces) {\n      pre = ind.repeat(space.length / tabSize)\n    }\n    return (idx == 0 || (trimTrailingWhitespace && line.length == 0) ? '' : indent) + pre + line.slice(space.length)\n  })\n  return lines.join('\\n')\n}\n\n/**\n * For static words, must be triggered by source option.\n * Used for completion of snippet choices.\n */\nexport class WordsSource implements ISource<ExtendedCompleteItem> {\n  public readonly name = '$words'\n  public readonly shortcut = ''\n  public readonly triggerOnly = true\n  public words: string[] = []\n  public startcol: number | undefined\n\n  public doComplete(opt: CompleteOption) {\n    return {\n      startcol: this.startcol,\n      items: this.words.map(s => {\n        return { word: s, filterText: opt.input }\n      })\n    }\n  }\n}\n\nexport const wordsSource = new WordsSource()\n\n/**\n * Get range from base position and position, text\n */\nexport function getNewRange(base: Position, pos: Position, value: string): Range {\n  const { line, character } = base\n  const start: Position = {\n    line: line + pos.line,\n    character: pos.line == 0 ? character + pos.character : pos.character\n  }\n  return Range.create(start, getEnd(start, value))\n}\n\nexport function getTextBefore(range: Range, text: string, pos: Position): string {\n  let newLines = []\n  let { line, character } = range.start\n  let n = pos.line - line\n  const lines = text.split('\\n')\n  for (let i = 0; i <= n; i++) {\n    let line = lines[i]\n    if (i == n) {\n      newLines.push(line.slice(0, i == 0 ? pos.character - character : pos.character))\n    } else {\n      newLines.push(line)\n    }\n  }\n  return newLines.join('\\n')\n}\n\nexport function getTextAfter(range: Range, text: string, pos: Position): string {\n  let newLines = []\n  let n = range.end.line - pos.line\n  const lines = text.split('\\n')\n  let len = lines.length\n  for (let i = 0; i <= n; i++) {\n    let idx = len - i - 1\n    let line = lines[idx]\n    if (i == n) {\n      let sc = range.start.character\n      let from = idx == 0 ? pos.character - sc : pos.character\n      newLines.unshift(line.slice(from))\n    } else {\n      newLines.unshift(line)\n    }\n  }\n  return newLines.join('\\n')\n}\n\nexport function toSnippetString(snippet: string | SnippetString | StringValue): string {\n  if (typeof snippet === 'string') {\n    return snippet\n  }\n  if (typeof snippet.value === 'string') {\n    return snippet.value\n  }\n  throw new TypeError(`Snippet should be string or has value as string`)\n}\n"
  },
  {
    "path": "src/snippets/variableResolve.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { v4 as uuid } from 'uuid'\nimport { URI } from 'vscode-uri'\nimport WorkspaceFolderController from '../core/workspaceFolder'\nimport { path } from '../util/node'\nimport { hasOwnProperty } from '../util/object'\nimport { Variable, VariableResolver } from \"./parser\"\n\nexport function padZero(n: number): string {\n  return n < 10 ? '0' + n : n.toString()\n}\n\nexport function parseComments(comments: string): { start?: string, end?: string, single?: string } {\n  let start: string\n  let end: string\n  let single: string\n  let parts = comments.split(',')\n  for (let s of parts) {\n    if (start && end && single) break\n    if (!s.includes(':')) continue\n    let [flag, str] = s.split(':')\n    if (flag.includes('s')) {\n      start = str\n    } else if (flag.includes('e')) {\n      end = str\n    } else if (!single && flag == '') {\n      single = str\n    }\n  }\n  return { start, end, single }\n}\n\n/*\n * Get single line comment text\n */\nexport function parseCommentstring(commentstring: string): string | undefined {\n  if (commentstring.endsWith('%s')) return commentstring.slice(0, -2).trim()\n  return undefined\n}\n\nexport class SnippetVariableResolver implements VariableResolver {\n  private _variableToValue: { [key: string]: string } = {}\n\n  constructor(private nvim: Neovim, private workspaceFolder: WorkspaceFolderController) {\n    const currentDate = new Date()\n    const fullyear = currentDate.getFullYear().toString()\n    Object.assign(this._variableToValue, {\n      CURRENT_YEAR: fullyear,\n      CURRENT_YEAR_SHORT: fullyear.slice(-2),\n      CURRENT_MONTH: padZero(currentDate.getMonth() + 1),\n      CURRENT_DATE: padZero(currentDate.getDate()),\n      CURRENT_HOUR: padZero(currentDate.getHours()),\n      CURRENT_MINUTE: padZero(currentDate.getMinutes()),\n      CURRENT_SECOND: padZero(currentDate.getSeconds()),\n      CURRENT_DAY_NAME: currentDate.toLocaleString(\"en-US\", { weekday: \"long\" }),\n      CURRENT_DAY_NAME_SHORT: currentDate.toLocaleString(\"en-US\", { weekday: \"short\" }),\n      CURRENT_MONTH_NAME: currentDate.toLocaleString(\"en-US\", { month: \"long\" }),\n      CURRENT_MONTH_NAME_SHORT: currentDate.toLocaleString(\"en-US\", { month: \"short\" }),\n      TM_FILENAME: null,\n      TM_FILENAME_BASE: null,\n      TM_DIRECTORY: null,\n      TM_FILEPATH: null,\n      YANK: null,\n      TM_LINE_INDEX: null,\n      TM_LINE_NUMBER: null,\n      TM_CURRENT_LINE: null,\n      TM_CURRENT_WORD: null,\n      TM_SELECTED_TEXT: null,\n      VISUAL: null,\n      CLIPBOARD: null,\n      RELATIVE_FILEPATH: null,\n      RANDOM: null,\n      RANDOM_HEX: null,\n      UUID: null,\n      BLOCK_COMMENT_START: null,\n      BLOCK_COMMENT_END: null,\n      LINE_COMMENT: null,\n      WORKSPACE_NAME: null,\n      WORKSPACE_FOLDER: null\n    })\n  }\n\n  private async resolveValue(name: string): Promise<string | undefined> {\n    let { nvim } = this\n    if (['TM_FILENAME', 'TM_FILENAME_BASE', 'TM_DIRECTORY', 'TM_FILEPATH'].includes(name)) {\n      let filepath = await nvim.call('coc#util#get_fullpath') as string\n      if (name === 'TM_FILENAME') return path.basename(filepath)\n      if (name === 'TM_FILENAME_BASE') return path.basename(filepath, path.extname(filepath))\n      if (name === 'TM_DIRECTORY') return path.dirname(filepath)\n      if (name === 'TM_FILEPATH') return filepath\n    }\n    if (name === 'YANK') {\n      return await nvim.call('getreg', ['\"\"']) as string\n    }\n    if (name === 'TM_LINE_INDEX') {\n      let lnum = await nvim.call('line', ['.']) as number\n      return (lnum - 1).toString()\n    }\n    if (name === 'TM_LINE_NUMBER') {\n      let lnum = await nvim.call('line', ['.']) as number\n      return lnum.toString()\n    }\n    if (name === 'TM_CURRENT_LINE') {\n      return await nvim.call('getline', ['.']) as string\n    }\n    if (name === 'TM_CURRENT_WORD') {\n      return await nvim.eval(`expand('<cword>')`) as string\n    }\n    if (name === 'TM_SELECTED_TEXT' || name == 'VISUAL') {\n      return await nvim.eval(`get(g:,'coc_selected_text', v:null)`) as string\n    }\n    if (name === 'CLIPBOARD') {\n      return await nvim.eval('@*') as string\n    }\n    if (name === 'RANDOM') {\n      return Math.random().toString().slice(-6)\n    }\n    if (name === 'RANDOM_HEX') {\n      return Math.random().toString(16).slice(-6)\n    }\n    if (name === 'UUID') {\n      return uuid()\n    }\n    if (['RELATIVE_FILEPATH', 'WORKSPACE_NAME', 'WORKSPACE_FOLDER'].includes(name)) {\n      let filepath = await nvim.call('coc#util#get_fullpath') as string\n      let folder = this.workspaceFolder.getWorkspaceFolder(URI.file(filepath))\n      if (name === 'RELATIVE_FILEPATH') return this.workspaceFolder.getRelativePath(filepath)\n      if (name === 'WORKSPACE_NAME') return folder.name\n      if (name === 'WORKSPACE_FOLDER') return URI.parse(folder.uri).fsPath\n    }\n    if (name === 'LINE_COMMENT') {\n      let commentstring = await nvim.eval('&commentstring') as string\n      let s = parseCommentstring(commentstring)\n      if (s) return s\n      let comments = await nvim.eval('&comments') as string\n      let { single } = parseComments(comments)\n      return single\n    }\n    if (['BLOCK_COMMENT_START', 'BLOCK_COMMENT_END'].includes(name)) {\n      let comments = await nvim.eval('&comments') as string\n      let { start, end } = parseComments(comments)\n      if (name === 'BLOCK_COMMENT_START') return start\n      if (name === 'BLOCK_COMMENT_END') return end\n    }\n  }\n\n  public async resolve(variable: Variable): Promise<string | undefined> {\n    const name = variable.name\n    let resolved = this._variableToValue[name]\n    if (resolved != null) return resolved.toString()\n    // resolve known value\n    if (hasOwnProperty(this._variableToValue, name)) {\n      let value = await this.resolveValue(name)\n      if (!value && variable.children.length) {\n        return variable.toString()\n      }\n      return value == null ? '' : value.toString()\n    }\n    if (variable.children.length) return variable.toString()\n    return undefined\n  }\n}\n"
  },
  {
    "path": "src/tree/BasicDataProvider.ts",
    "content": "'use strict'\nimport { v4 as uuid } from 'uuid'\nimport { CancellationToken, Disposable, Emitter, Event } from '../util/protocol'\nimport { MarkupContent } from 'vscode-languageserver-types'\nimport commandsManager from '../commands'\nimport { ProviderResult } from '../provider'\nimport { disposeAll } from '../util'\nimport { TreeDataProvider, TreeItemAction } from './index'\nimport { TreeItem, TreeItemCollapsibleState, TreeItemIcon, TreeItemLabel } from './TreeItem'\nimport { toArray } from '../util/array'\n\nexport interface TreeNode {\n  label: string\n  key?: string\n  tooltip?: string | MarkupContent\n  description?: string\n  deprecated?: boolean\n  icon?: TreeItemIcon\n  children?: this[]\n}\n\nexport interface ProviderOptions<T> {\n  provideData: () => ProviderResult<T[]>\n  expandLevel?: number\n  onDispose?: () => void\n  handleClick?: (item: T) => ProviderResult<void>\n  resolveIcon?: (item: T) => TreeItemIcon | undefined\n  resolveItem?: (item: TreeItem, element: T, token: CancellationToken) => ProviderResult<TreeItem>\n  resolveActions?(item: TreeItem, element: T): ProviderResult<TreeItemAction<T>[]>\n}\n\nfunction isIcon(obj: any): obj is TreeItemIcon {\n  if (!obj) return false\n  return typeof obj.text === 'string' && typeof obj.hlGroup === 'string'\n}\n\n/**\n * Check label and key, children not checked.\n */\nfunction sameTreeNode<T extends TreeNode>(one: T, two: T): boolean {\n  if (one.label === two.label\n    && one.deprecated === two.deprecated\n    && one.key === two.key) {\n    return true\n  }\n  return false\n}\n\n/**\n * Check changes of nodes array, children not checked.\n */\nfunction sameTreeNodes<T extends TreeNode>(one: T[], two: T[]): boolean {\n  if (one.length !== two.length) return false\n  return one.every((v, idx) => sameTreeNode(v, two[idx]))\n}\n\n/**\n * Tree data provider for resolved tree with children.\n * Use update() to update data.\n */\nexport default class BasicDataProvider<T extends TreeNode> implements TreeDataProvider<T> {\n  private disposables: Disposable[] = []\n  private invokeCommand: string\n  private data: T[] | undefined\n  // only fired for change of exists TreeNode\n  private _onDidChangeTreeData = new Emitter<void | T>()\n  public onDidChangeTreeData: Event<void | T> = this._onDidChangeTreeData.event\n  public resolveActions: (item: TreeItem, element: T) => ProviderResult<TreeItemAction<T>[]>\n  // data is shared with TreeView\n  constructor(private opts: ProviderOptions<T>) {\n    this.invokeCommand = `_invoke_${uuid()}`\n    this.disposables.push(commandsManager.registerCommand(this.invokeCommand, async (node: T) => {\n      await opts.handleClick(node)\n    }, null, true))\n    if (typeof opts.resolveActions === 'function') {\n      this.resolveActions = opts.resolveActions.bind(this)\n    }\n  }\n\n  public iterate(node: T, parentNode: T | undefined, level: number, fn: (node: T, parentNode: T | undefined, level: number) => void | boolean): void | boolean {\n    let res = fn(node, parentNode, level)\n    if (res === false) return false\n    if (Array.isArray(node.children)) {\n      for (let element of node.children) {\n        let res = this.iterate(element, node, level + 1, fn)\n        if (res === false) return false\n      }\n    }\n    return res\n  }\n\n  /**\n   * Change old array to new nodes in place, keep old reference when possible.\n   */\n  private updateNodes(old: T[], data: T[], parentNode: T | undefined, fireEvent = true): void {\n    let sameNodes = sameTreeNodes(old, data)\n    const applyNode = (previous: T, curr: T, fireEvent: boolean): void => {\n      let changed = false\n      for (let key of Object.keys(curr)) {\n        if (['children', 'key'].includes(key)) continue\n        previous[key] = curr[key]\n      }\n      if (previous.children?.length && !curr.children?.length) {\n        // removed children\n        delete previous.children\n        changed = true\n      }\n      if (!previous.children?.length && curr.children?.length) {\n        // new children\n        previous.children = curr.children\n        changed = true\n      }\n      if (changed) {\n        if (fireEvent) this._onDidChangeTreeData.fire(previous)\n        return\n      }\n      if (toArray(previous.children).length > 0 && toArray(curr.children).length > 0) {\n        this.updateNodes(previous.children, curr.children, previous, fireEvent)\n      }\n    }\n    if (sameNodes) {\n      for (let i = 0; i < old.length; i++) {\n        applyNode(old[i], data[i], fireEvent)\n      }\n    } else {\n      let oldNodes = old.splice(0, old.length)\n      let used: Set<number> = new Set()\n      for (let i = 0; i < data.length; i++) {\n        let curr = data[i]\n        let findIndex: number\n        if (curr.key) {\n          findIndex = oldNodes.findIndex((o, i) => !used.has(i) && o.key == curr.key)\n        } else {\n          findIndex = oldNodes.findIndex((o, i) => !used.has(i) && o.label == curr.label)\n        }\n        if (findIndex === -1) {\n          old[i] = curr\n        } else {\n          used.add(findIndex)\n          let previous = oldNodes[findIndex]\n          applyNode(previous, curr, false)\n          old[i] = previous\n        }\n      }\n      if (fireEvent) {\n        this._onDidChangeTreeData.fire(parentNode)\n      }\n    }\n  }\n\n  /**\n   * Update with new data, fires change event when necessary.\n   */\n  public update(data: T[], reset?: boolean): ReadonlyArray<T> {\n    if (!this.data) return\n    if (reset) {\n      this.data = toArray(data)\n      this._onDidChangeTreeData.fire(undefined)\n    } else {\n      this.updateNodes(this.data, toArray(data), undefined)\n    }\n    return this.data\n  }\n\n  public getTreeItem(node: T): TreeItem {\n    let label: string | TreeItemLabel = node.label\n    let { expandLevel } = this.opts\n    let item: TreeItem\n    if (!node.children?.length) {\n      item = new TreeItem(label)\n    } else {\n      if (expandLevel && expandLevel > 0) {\n        let level = this.getLevel(node)\n        let state = level && level <= expandLevel ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.Collapsed\n        item = new TreeItem(label, state)\n      } else {\n        item = new TreeItem(label, TreeItemCollapsibleState.Collapsed)\n      }\n    }\n    item.description = node.description\n    if (node.deprecated) item.deprecated = true\n    if (node.tooltip) item.tooltip = node.tooltip\n    if (isIcon(node.icon)) {\n      item.icon = node.icon\n    } else if (typeof this.opts.resolveIcon === 'function') {\n      let res = this.opts.resolveIcon(node)\n      if (res && isIcon(res)) item.icon = res\n    }\n    return item\n  }\n\n  public async getChildren(element?: T): Promise<T[]> {\n    if (element) return element.children ?? []\n    if (this.data) return this.data\n    let data = await Promise.resolve(this.opts.provideData())\n    if (!Array.isArray(data)) throw new Error(`Unable to fetch data`)\n    this.data = data\n    return data\n  }\n\n  /**\n   * Use reference check\n   */\n  public getParent(element: T): T | undefined {\n    if (!this.data) return undefined\n    let find: T\n    for (let item of this.data) {\n      let res = this.iterate(item, null, 0, (node, parentNode) => {\n        if (node === element) {\n          find = parentNode\n          return false\n        }\n      })\n      if (res === false) break\n    }\n    return find\n  }\n\n  public getLevel(element: T): number {\n    if (!this.data) return 0\n    let level = 0\n    for (let item of toArray(this.data)) {\n      let res = this.iterate(item, null, 1, (node, _parentNode, l) => {\n        if (node === element) {\n          level = l\n          return false\n        }\n      })\n      if (res === false) break\n    }\n    return level\n  }\n\n  /**\n   * Resolve command and tooltip\n   */\n  public async resolveTreeItem(item: TreeItem, element: T, token: CancellationToken): Promise<TreeItem> {\n    if (typeof this.opts.resolveItem === 'function') {\n      let res = await Promise.resolve(this.opts.resolveItem(item, element, token))\n      if (res) Object.assign(item, res)\n    }\n    if (!item.command) {\n      item.command = {\n        title: `invoke ${element.label}`,\n        command: this.invokeCommand,\n        arguments: [element]\n      }\n    }\n    return item\n  }\n\n  public dispose(): void {\n    this.data = []\n    this._onDidChangeTreeData.dispose()\n    if (typeof this.opts.onDispose === 'function') {\n      this.opts.onDispose()\n    }\n    disposeAll(this.disposables)\n  }\n}\n"
  },
  {
    "path": "src/tree/LocationsDataProvider.ts",
    "content": "import { Range, SymbolKind, SymbolTag } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport commands from '../commands'\nimport { path } from '../util/node'\nimport { CancellationToken, CancellationTokenSource, Emitter, Event } from '../util/protocol'\nimport workspace from '../workspace'\nimport { TreeDataProvider, TreeItemAction } from './index'\nimport { TreeItem, TreeItemCollapsibleState } from './TreeItem'\n\nexport interface LocationDataItem<T> {\n  name: string\n  kind: SymbolKind\n  tags?: SymbolTag[]\n  detail?: string\n  uri: string\n  range?: Range\n  selectionRange?: Range\n  parent?: T\n  children?: T[]\n}\n\ninterface ProviderConfig {\n  readonly openCommand: string\n  readonly enableTooltip: boolean\n}\n\nexport default class LocationsDataProvider<T extends LocationDataItem<T>, P> implements TreeDataProvider<T>{\n  private readonly _onDidChangeTreeData = new Emitter<T | undefined>()\n  public readonly onDidChangeTreeData: Event<T | undefined> = this._onDidChangeTreeData.event\n  private tokenSource: CancellationTokenSource\n  private actions: TreeItemAction<T>[] = []\n  public static rangesHighlight = 'CocSelectedRange'\n  constructor(\n    public meta: P,\n    private winid: number,\n    private config: ProviderConfig,\n    private commandId: string,\n    private rootItems: ReadonlyArray<T>,\n    private getIcon: (kind: SymbolKind) => { text: string, hlGroup: string },\n    private resolveChildren: (el: T, meta: P, token: CancellationToken) => Promise<T[]>\n  ) {\n    this.addAction('Open in new tab', async element => {\n      await commands.executeCommand(this.commandId, winid, element, 'tabe')\n    })\n    this.addAction('Dismiss', async element => {\n      if (element.parent == null) {\n        let els = this.rootItems.filter(o => o !== element)\n        this.reset(els)\n      } else {\n        let parentElement = element.parent\n        let idx = parentElement.children.findIndex(o => o === element)\n        parentElement.children.splice(idx, 1)\n        this._onDidChangeTreeData.fire(parentElement)\n      }\n    })\n  }\n\n  protected cancel() {\n    if (this.tokenSource) {\n      this.tokenSource.cancel()\n      this.tokenSource = undefined\n    }\n  }\n\n  public reset(rootItems: T[]): void {\n    this.rootItems = rootItems\n    this._onDidChangeTreeData.fire(undefined)\n  }\n\n  public addAction(title: string, handler: (element: T) => void): void {\n    this.actions.push({ title, handler })\n  }\n\n  public async getChildren(element?: T): Promise<ReadonlyArray<T>> {\n    this.cancel()\n    this.tokenSource = new CancellationTokenSource()\n    let { token } = this.tokenSource\n    if (!element) {\n      for (let o of this.rootItems) {\n        let children = await this.resolveChildren(o, this.meta, token)\n        addChildren(o, children, token)\n      }\n      return this.rootItems\n    }\n    if (element.children) return element.children\n    let items = await this.resolveChildren(element, this.meta, token)\n    this.tokenSource = undefined\n    addChildren(element, items, token)\n    return items\n  }\n\n  public getTreeItem(element: T): TreeItem {\n    let item = new TreeItem(element.name, element.children ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.Collapsed)\n    if (this.config.enableTooltip) {\n      item.tooltip = path.relative(workspace.cwd, URI.parse(element.uri).fsPath)\n    }\n    item.description = element.detail\n    item.deprecated = element.tags?.includes(SymbolTag.Deprecated)\n    item.icon = this.getIcon(element.kind)\n    item.command = {\n      command: this.commandId,\n      title: 'open location',\n      arguments: [this.winid, element, this.config.openCommand]\n    }\n    return item\n  }\n\n  public resolveActions(): TreeItemAction<T>[] {\n    return this.actions\n  }\n\n  public dispose(): void {\n    this.cancel()\n    let win = workspace.nvim.createWindow(this.winid)\n    win.clearMatchGroup(LocationsDataProvider.rangesHighlight)\n  }\n}\n\nexport function addChildren<T extends LocationDataItem<T>>(el: T, children: T[] | undefined, token?: CancellationToken): void {\n  if (!Array.isArray(children) || (token && token.isCancellationRequested)) return\n  children.forEach(item => item.parent = el)\n  el.children = children\n}\n"
  },
  {
    "path": "src/tree/TreeItem.ts",
    "content": "'use strict'\nimport { Command, MarkupContent } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport { path } from '../util/node'\n\nexport interface TreeItemLabel {\n  label: string\n  highlights?: [number, number][]\n}\n\n// eslint-disable-next-line no-redeclare\nexport namespace TreeItemLabel {\n  export function is(obj: any): obj is TreeItemLabel {\n    return typeof obj.label == 'string'\n  }\n}\n\nexport interface TreeItemIcon {\n  text: string\n  hlGroup: string\n}\n\n/**\n * Collapsible state of the tree item\n */\nexport enum TreeItemCollapsibleState {\n  /**\n   * Determines an item can be neither collapsed nor expanded. Implies it has no children.\n   */\n  None = 0,\n  /**\n   * Determines an item is collapsed\n   */\n  Collapsed = 1,\n  /**\n   * Determines an item is expanded\n   */\n  Expanded = 2\n}\n\nexport class TreeItem {\n  public label: string | TreeItemLabel\n  public id?: string\n  public description?: string\n  public icon?: TreeItemIcon\n  public resourceUri?: URI\n  public command?: Command\n  public tooltip?: string | MarkupContent\n  public deprecated?: boolean\n\n  constructor(label: string | TreeItemLabel, collapsibleState?: TreeItemCollapsibleState)\n  // eslint-disable-next-line @typescript-eslint/unified-signatures\n  constructor(resourceUri: URI, collapsibleState?: TreeItemCollapsibleState)\n  constructor(label: string | TreeItemLabel | URI, public collapsibleState: TreeItemCollapsibleState = TreeItemCollapsibleState.None) {\n    if (URI.isUri(label)) {\n      this.resourceUri = label\n      this.label = path.basename(label.path)\n      this.id = label.toString()\n    } else {\n      this.label = label\n    }\n  }\n}\n\nexport function getItemLabel(item: TreeItem): string {\n  return TreeItemLabel.is(item.label) ? item.label.label : item.label\n}\n"
  },
  {
    "path": "src/tree/TreeView.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport { MarkupContent, MarkupKind, Range } from 'vscode-languageserver-types'\nimport commandManager from '../commands'\nimport type { LocalMode } from '../core/keymaps'\nimport events from '../events'\nimport { createLogger } from '../logger'\nimport { toSpans } from '../model/fuzzyMatch'\nimport { Documentation, FloatFactory, HighlightItem, IConfigurationChangeEvent } from '../types'\nimport { defaultValue, disposeAll, getConditionValue } from '../util'\nimport { isFalsyOrEmpty, toArray } from '../util/array'\nimport { fuzzyScoreGracefulAggressive } from '../util/filter'\nimport { Mutex } from '../util/mutex'\nimport { debounce } from '../util/node'\nimport { equals } from '../util/object'\nimport { CancellationTokenSource, Disposable, Emitter, Event } from '../util/protocol'\nimport { byteLength, byteSlice, toText } from '../util/string'\nimport window from '../window'\nimport workspace from '../workspace'\nimport Filter, { sessionKey } from './filter'\nimport { LineState, TreeDataProvider, TreeItemData, TreeView, TreeViewExpansionEvent, TreeViewKeys, TreeViewOptions, TreeViewSelectionChangeEvent, TreeViewVisibilityChangeEvent } from './index'\nimport { getItemLabel, TreeItem, TreeItemCollapsibleState, TreeItemLabel } from './TreeItem'\nconst logger = createLogger('BasicTreeView')\nconst retryTimeout = getConditionValue(500, 10)\nconst maxRetry = getConditionValue(5, 1)\nconst highlightNamespace = 'tree'\nconst signOffset = 3000\nlet globalId = 1\n\ninterface TreeViewConfig {\n  openedIcon: string\n  closedIcon: string\n}\n\ninterface RenderedItem<T> {\n  line: string\n  level: number\n  node: T\n}\n\ninterface ExtendedItem<T> extends RenderedItem<T> {\n  index: number\n  score: number\n  highlights: HighlightItem[]\n}\n\ninterface LocalKeymapDef<T> {\n  mode: LocalMode\n  key: string\n  notify: boolean\n  fn: (element: T | undefined) => Promise<void> | void\n}\n\n/**\n * Basic TreeView implementation\n */\nexport default class BasicTreeView<T> implements TreeView<T> {\n  private bufnr: number | undefined\n  private bufname: string\n  private winid: number | undefined\n  private config: TreeViewConfig\n  private keys: TreeViewKeys\n  private _targetBufnr: number\n  private _targetWinId: number\n  private _targetTabId: number | undefined\n  private _selection: T[] = []\n  private _keymapDefs: LocalKeymapDef<T>[] = []\n  private _onDispose = new Emitter<void>()\n  private _onDidRefrash = new Emitter<void>()\n  private _onDidExpandElement = new Emitter<TreeViewExpansionEvent<T>>()\n  private _onDidCollapseElement = new Emitter<TreeViewExpansionEvent<T>>()\n  private _onDidChangeSelection = new Emitter<TreeViewSelectionChangeEvent<T>>()\n  private _onDidChangeVisibility = new Emitter<TreeViewVisibilityChangeEvent>()\n  private _onDidFilterStateChange = new Emitter<boolean>()\n  private readonly _onDidCursorMoved = new Emitter<T | undefined>()\n  public readonly onDidRefrash: Event<void> = this._onDidRefrash.event\n  public readonly onDispose: Event<void> = this._onDispose.event\n  public readonly onDidExpandElement: Event<TreeViewExpansionEvent<T>> = this._onDidExpandElement.event\n  public readonly onDidCollapseElement: Event<TreeViewExpansionEvent<T>> = this._onDidCollapseElement.event\n  public readonly onDidChangeSelection: Event<TreeViewSelectionChangeEvent<T>> = this._onDidChangeSelection.event\n  public readonly onDidChangeVisibility: Event<TreeViewVisibilityChangeEvent> = this._onDidChangeVisibility.event\n  public readonly onDidFilterStateChange: Event<boolean> = this._onDidFilterStateChange.event\n  public readonly onDidCursorMoved: Event<T | undefined> = this._onDidCursorMoved.event\n  public message: string | undefined\n  public title: string\n  public description: string | undefined\n  private retryTimers = 0\n  private renderedItems: RenderedItem<T>[] = []\n  public provider: TreeDataProvider<T>\n  private nodesMap: Map<T, TreeItemData> = new Map()\n  private mutex: Mutex = new Mutex()\n  private timer: NodeJS.Timeout\n  private disposables: Disposable[] = []\n  private tooltipFactory: FloatFactory\n  private resolveTokenSource: CancellationTokenSource | undefined\n  private lineState: LineState = { titleCount: 0, messageCount: 0 }\n  private filter: Filter<T> | undefined\n  private filterText: string | undefined\n  private itemsToFilter: T[] | undefined\n  private readonly leafIndent: boolean\n  private readonly winfixwidth: boolean\n  private readonly autoWidth: boolean\n  constructor(private viewId: string, private opts: TreeViewOptions<T>) {\n    this.loadConfiguration()\n    workspace.onDidChangeConfiguration(this.loadConfiguration, this, this.disposables)\n    if (opts.enableFilter) {\n      this.filter = new Filter(this.nvim, [this.keys.selectNext, this.keys.selectPrevious, this.keys.invoke])\n    }\n    let id = globalId\n    globalId = globalId + 1\n    this.bufname = `CocTree${id}`\n    this.tooltipFactory = window.createFloatFactory({ modes: ['n'] })\n    this.provider = opts.treeDataProvider\n    this.leafIndent = opts.disableLeafIndent !== true\n    this.winfixwidth = opts.winfixwidth !== false\n    this.autoWidth = opts.autoWidth === true\n    let message: string | undefined\n    Object.defineProperty(this, 'message', {\n      set: (msg: string | undefined) => {\n        message = msg ? msg.replace(/\\r?\\n/g, ' ') : undefined\n        this.updateHeadLines()\n      },\n      get: () => {\n        return message\n      }\n    })\n    let title = viewId.replace(/\\r?\\n/g, ' ')\n    Object.defineProperty(this, 'title', {\n      set: (newTitle: string) => {\n        title = newTitle ? newTitle.replace(/\\r?\\n/g, ' ') : undefined\n        this.updateHeadLines()\n      },\n      get: () => {\n        return title\n      }\n    })\n    let description: string | undefined\n    Object.defineProperty(this, 'description', {\n      set: (desc: string | undefined) => {\n        description = desc ? desc.replace(/\\r?\\n/g, ' ') : undefined\n        this.updateHeadLines()\n      },\n      get: () => {\n        return description\n      }\n    })\n    let filterText: string | undefined\n    Object.defineProperty(this, 'filterText', {\n      set: (text: string | undefined) => {\n        let { titleCount, messageCount } = this.lineState\n        let start = titleCount + messageCount\n        if (text != null) {\n          let highlights: HighlightItem[] = [{\n            lnum: start,\n            colStart: byteLength(text),\n            colEnd: byteLength(text) + 1,\n            hlGroup: 'Cursor'\n          }]\n          this.renderedItems = []\n          this.updateUI([text + ' '], highlights, start, -1, true)\n          void this.doFilter(text)\n        } else if (filterText != null) {\n          this.updateUI([], [], start, start + 1)\n        }\n        filterText = text\n      },\n      get: () => {\n        return filterText\n      }\n    })\n    if (this.provider.onDidChangeTreeData) {\n      this.provider.onDidChangeTreeData(this.onDataChange, this, this.disposables)\n    }\n    events.on('BufUnload', bufnr => {\n      if (bufnr != this.bufnr) return\n      let isVisible = this.winid != null\n      this.winid = undefined\n      this.bufnr = undefined\n      if (isVisible) this._onDidChangeVisibility.fire({ visible: false })\n      this.dispose()\n    }, null, this.disposables)\n    events.on('WinClosed', winid => {\n      if (this.winid === winid) {\n        this.winid = undefined\n        this._onDidChangeVisibility.fire({ visible: false })\n      }\n    }, null, this.disposables)\n    // switched to another buffer\n    events.on('BufWinLeave', (bufnr: number, winid: number) => {\n      if (bufnr == this.bufnr && winid == this.winid) {\n        this.winid = undefined\n        this._onDidChangeVisibility.fire({ visible: false })\n      }\n    }, null, this.disposables)\n    window.onDidTabClose(id => {\n      if (this._targetTabId === id) {\n        this.dispose()\n      }\n    }, null, this.disposables)\n    events.on('CursorHold', async (bufnr: number, cursor: [number, number]) => {\n      if (bufnr != this.bufnr) return\n      await this.onHover(cursor[0])\n    }, null, this.disposables)\n    events.on(['CursorMoved', 'BufEnter'], () => {\n      this.cancelResolve()\n    }, null, this.disposables)\n    // vim may send unexpected CursorMoved when switch tab\n    let debounced = debounce((bufnr: number, cursor: [number, number]) => {\n      if (bufnr !== this.bufnr) return\n      let element = this.getElementByLnum(cursor[0] - 1)\n      this._onDidCursorMoved.fire(element)\n    }, 30)\n    this.disposables.push(Disposable.create(() => {\n      debounced.clear()\n    }))\n    events.on('CursorMoved', debounced, null, this.disposables)\n    events.on('WinEnter', winid => {\n      if (winid != this.windowId || !this.filtering) return\n      let buf = this.nvim.createBuffer(this.bufnr)\n      let line = this.startLnum - 1\n      let len = toText(this.filterText).length\n      let range = Range.create(line, len, line, len + 1)\n      buf.highlightRanges(highlightNamespace, 'Cursor', [range])\n      this.nvim.call('coc#prompt#start_prompt', [sessionKey], true)\n      this.redraw()\n    }, null, this.disposables)\n    events.on('WinLeave', winid => {\n      if (winid != this.windowId || !this.filtering) return\n      let buf = this.nvim.createBuffer(this.bufnr)\n      this.nvim.call('coc#prompt#stop_prompt', [sessionKey], true)\n      buf.clearNamespace(highlightNamespace, this.startLnum - 1, this.startLnum)\n    }, null, this.disposables)\n    this.disposables.push(this._onDidChangeVisibility, this._onDidCursorMoved, this._onDidChangeSelection, this._onDidCollapseElement, this._onDidExpandElement)\n    if (this.filter) {\n      this.filter.onDidExit(node => {\n        this.nodesMap.clear()\n        this.filterText = undefined\n        this.itemsToFilter = undefined\n        if (node && typeof this.provider.getParent === 'function') {\n          this.renderedItems = []\n          void this.reveal(node, { focus: true })\n        } else {\n          this.clearSelection()\n          void this.render()\n        }\n        this._onDidFilterStateChange.fire(false)\n      })\n      this.filter.onDidUpdate(text => {\n        this.filterText = text\n      })\n      this.filter.onDidKeyPress(async character => {\n        let items = toArray(this.renderedItems)\n        let curr = this.selection[0]\n        if (character == '<up>' || character == this.keys.selectPrevious) {\n          let idx = items.findIndex(o => o.node == curr)\n          let index = idx == -1 || idx == 0 ? items.length - 1 : idx - 1\n          let node = items[index]?.node\n          if (node) this.selectItem(node, true)\n        }\n        if (character == '<down>' || character == this.keys.selectNext) {\n          let idx = items.findIndex(o => o.node == curr)\n          let index = idx == -1 || idx == items.length - 1 ? 0 : idx + 1\n          let node = items[index]?.node\n          if (node) this.selectItem(node, true)\n        }\n        if (character == '<cr>' || character == this.keys.invoke) {\n          if (!curr) return\n          await this.invokeCommand(curr)\n          this.filter.deactivate(curr)\n        }\n      })\n    }\n  }\n\n  public get windowId(): number | undefined {\n    return this.winid\n  }\n\n  public get targetTabId(): number | undefined {\n    return this._targetTabId\n  }\n\n  public get targetWinId(): number | undefined {\n    return this._targetWinId\n  }\n\n  public get targetBufnr(): number | undefined {\n    return this._targetBufnr\n  }\n\n  private get startLnum(): number {\n    let filterCount = this.filterText == null ? 0 : 1\n    return this.lineState.messageCount + this.lineState.titleCount + filterCount\n  }\n\n  private get nvim(): Neovim {\n    return workspace.nvim\n  }\n\n  public get filtering(): boolean {\n    return this.filter != null && this.filter.activated\n  }\n\n  private loadConfiguration(e?: IConfigurationChangeEvent): void {\n    if (!e || e.affectsConfiguration('tree')) {\n      let config = workspace.getConfiguration('tree', null)\n      this.config = {\n        openedIcon: config.get('openedIcon', ' '),\n        closedIcon: config.get('closedIcon', ' ')\n      }\n      this.keys = {\n        close: config.get<string>('key.close'),\n        invoke: config.get<string>('key.invoke'),\n        toggle: config.get<string>('key.toggle'),\n        actions: config.get<string>('key.actions'),\n        collapseAll: config.get<string>('key.collapseAll'),\n        toggleSelection: config.get<string>('key.toggleSelection'),\n        activeFilter: config.get<string>('key.activeFilter'),\n        selectNext: config.get<string>('key.selectNext'),\n        selectPrevious: config.get<string>('key.selectPrevious')\n      }\n      if (e && this.visible) {\n        void this.render()\n      }\n    }\n  }\n\n  private async doFilter(text: string): Promise<void> {\n    let items: ExtendedItem<T>[] = []\n    let index = 0\n    let release = await this.mutex.acquire()\n    try {\n      if (!this.itemsToFilter) {\n        let itemsToFilter: T[] = []\n        const addNodes = async (nodes: ReadonlyArray<T>): Promise<void> => {\n          for (let n of nodes) {\n            itemsToFilter.push(n)\n            let arr = await Promise.resolve(this.provider.getChildren(n))\n            if (!isFalsyOrEmpty(arr)) await addNodes(arr)\n          }\n        }\n        let nodes = await Promise.resolve(this.provider.getChildren())\n        await addNodes(nodes)\n        this.itemsToFilter = itemsToFilter\n      }\n      let lowInput = text.toLowerCase()\n      let emptyInput = text.length === 0\n      for (let n of this.itemsToFilter) {\n        let item = await this.getTreeItem(n)\n        let label = getItemLabel(item)\n        let score = 0\n        if (!emptyInput) {\n          let res = fuzzyScoreGracefulAggressive(text, lowInput, 0, label, label.toLowerCase(), 0, { boostFullMatch: true, firstMatchCanBeWeak: true })\n          if (!res) continue\n          score = res[0]\n          item.label = { label, highlights: toSpans(label, res) }\n        } else {\n          item.label = { label, highlights: [] }\n        }\n        item.collapsibleState = TreeItemCollapsibleState.None\n        let { line, highlights } = this.getRenderedLine(item, index, 0)\n        items.push({\n          level: 0,\n          node: n,\n          line,\n          index,\n          score,\n          highlights\n        })\n        index += 1\n      }\n      items.sort((a, b) => {\n        if (a.score != b.score) return b.score - a.score\n        return a.index - b.index\n      })\n      let lnum = this.startLnum\n      let highlights: HighlightItem[] = []\n      let renderedItems = this.renderedItems = items.map((o, idx) => {\n        highlights.push(...o.highlights.map(h => {\n          h.lnum = lnum + idx\n          return h\n        }))\n        delete o.index\n        delete o.score\n        delete o.highlights\n        return o\n      })\n      this.updateUI(renderedItems.map(o => o.line), highlights, lnum, -1, true)\n      if (renderedItems.length) {\n        this.selectItem(renderedItems[0].node, true)\n      } else {\n        this.clearSelection()\n      }\n      this.redraw()\n      release()\n    } catch (e) {\n      release()\n      logger.error(`Error on tree filter:`, e)\n    }\n  }\n\n  public async onHover(lnum: number): Promise<void> {\n    let element = this.getElementByLnum(lnum - 1)\n    if (!element || !this.nodesMap.has(element)) return\n    let obj = this.nodesMap.get(element)\n    let item = obj.item\n    if (!item.tooltip && !obj.resolved) item = await this.resolveItem(element, item)\n    if (!item.tooltip) return\n    let isMarkdown = MarkupContent.is(item.tooltip) && item.tooltip.kind == MarkupKind.Markdown\n    let doc: Documentation = {\n      filetype: isMarkdown ? 'markdown' : 'txt',\n      content: MarkupContent.is(item.tooltip) ? item.tooltip.value : item.tooltip\n    }\n    await this.tooltipFactory.show([doc])\n  }\n\n  private async onClick(element: T): Promise<void> {\n    let { nvim } = this\n    let [line, col] = await nvim.eval(`[getline('.'),col('.')]`) as [string, number]\n    let pre = byteSlice(line, 0, col - 1)\n    let character = line[pre.length]\n    let { openedIcon, closedIcon } = this.config\n    if (/^\\s*$/.test(pre) && [openedIcon, closedIcon].includes(character)) {\n      await this.toggleExpand(element)\n    } else {\n      await this.invokeCommand(element)\n    }\n  }\n\n  public async invokeCommand(element: T): Promise<void> {\n    let obj = this.nodesMap.get(element)\n    if (!obj) return\n    this.selectItem(element)\n    let item = obj.item\n    if (!item.command) item = await this.resolveItem(element, item)\n    if (!item || !item.command) throw new Error(`Failed to resolve command from TreeItem.`)\n    await commandManager.execute(item.command)\n  }\n\n  public async invokeActions(element: T | undefined): Promise<void> {\n    if (!element) return\n    this.selectItem(element)\n    if (typeof this.provider.resolveActions !== 'function') {\n      await window.showWarningMessage('No actions')\n      return\n    }\n    let obj = this.nodesMap.get(element)\n    let actions = await Promise.resolve(this.provider.resolveActions(obj.item, element))\n    if (!actions || actions.length == 0) {\n      await window.showWarningMessage('No actions available')\n      return\n    }\n    let keys = actions.map(o => o.title)\n    let res = await window.showMenuPicker(keys, 'Choose action')\n    if (res == -1) return\n    await Promise.resolve(actions[res].handler(element))\n  }\n\n  private async onDataChange(node: T | undefined | null | void): Promise<void> {\n    if (this.filtering) {\n      this.itemsToFilter = undefined\n      await this.doFilter(toText(this.filterText))\n      return\n    }\n    this.clearSelection()\n    if (!node) {\n      await this.render()\n      return\n    }\n    let release = await this.mutex.acquire()\n    try {\n      let items = this.renderedItems\n      let idx = items.findIndex(o => o.node === node)\n      if (idx != -1 && this.bufnr) {\n        let obj = items[idx]\n        let level = obj.level\n        let removeCount = 0\n        for (let i = idx; i < items.length; i++) {\n          let o = items[i]\n          if (i == idx || o && o.level > level) {\n            removeCount += 1\n          }\n        }\n        let appendItems: RenderedItem<T>[] = []\n        let highlights: HighlightItem[] = []\n        let start = idx + this.startLnum\n        await this.appendTreeNode(node, level, start, appendItems, highlights)\n        items.splice(idx, removeCount, ...appendItems)\n        this.updateUI(appendItems.map(o => o.line), highlights, start, start + removeCount)\n      }\n      release()\n    } catch (e) {\n      let errMsg = `Error on tree refresh: ${e}`\n      logger.error(errMsg, e)\n      this.nvim.errWriteLine('[coc.nvim] ' + errMsg)\n      release()\n    }\n  }\n\n  private async resolveItem(element: T, item: TreeItem): Promise<TreeItem | undefined> {\n    if (typeof this.provider.resolveTreeItem === 'function') {\n      let tokenSource = this.resolveTokenSource = new CancellationTokenSource()\n      let token = tokenSource.token\n      item = await Promise.resolve(this.provider.resolveTreeItem(item, element, token))\n      tokenSource.dispose()\n      this.resolveTokenSource = undefined\n      if (token.isCancellationRequested) return undefined\n    }\n    this.nodesMap.set(element, { item, resolved: true })\n    return item\n  }\n\n  public get visible(): boolean {\n    if (!this.bufnr) return false\n    return this.winid != null\n  }\n\n  public get valid(): boolean {\n    return typeof this.bufnr === 'number'\n  }\n\n  public get selection(): T[] {\n    return this._selection.slice()\n  }\n\n  public async checkLines(): Promise<boolean> {\n    if (!this.bufnr) return false\n    let buf = this.nvim.createBuffer(this.bufnr)\n    let curr = await buf.lines\n    let { titleCount, messageCount } = this.lineState\n    curr = curr.slice(titleCount + messageCount)\n    let lines = this.renderedItems.map(o => o.line)\n    return equals(curr, lines)\n  }\n\n  /**\n   * Expand/collapse TreeItem.\n   */\n  private async toggleExpand(element: T | undefined): Promise<void> {\n    let o = this.nodesMap.get(element)\n    if (!o) return\n    let treeItem = o.item\n    let lnum = this.getItemLnum(element)\n    let nodeIdx = lnum - this.startLnum\n    let obj = this.renderedItems[nodeIdx]\n    if (!obj || treeItem.collapsibleState == TreeItemCollapsibleState.None) {\n      if (typeof this.provider.getParent === 'function') {\n        let node = await Promise.resolve(this.provider.getParent(element))\n        if (node) {\n          await this.toggleExpand(node)\n          this.focusItem(node)\n        }\n      }\n      return\n    }\n    // remove lines\n    let removeCount = 0\n    if (treeItem.collapsibleState == TreeItemCollapsibleState.Expanded) {\n      let level = obj.level\n      for (let i = nodeIdx + 1; i < this.renderedItems.length; i++) {\n        let o = this.renderedItems[i]\n        if (!o || o.level <= level) break\n        removeCount += 1\n      }\n      treeItem.collapsibleState = TreeItemCollapsibleState.Collapsed\n    } else if (treeItem.collapsibleState == TreeItemCollapsibleState.Collapsed) {\n      treeItem.collapsibleState = TreeItemCollapsibleState.Expanded\n    }\n    let newItems: RenderedItem<T>[] = []\n    let newHighlights: HighlightItem[] = []\n    await this.appendTreeNode(obj.node, obj.level, lnum, newItems, newHighlights)\n    this.renderedItems.splice(nodeIdx, removeCount + 1, ...newItems)\n    this.updateUI(newItems.map(o => o.line), newHighlights, lnum, lnum + removeCount + 1)\n    this.refreshSigns()\n    if (treeItem.collapsibleState == TreeItemCollapsibleState.Collapsed) {\n      this._onDidCollapseElement.fire({ element })\n    } else {\n      this._onDidExpandElement.fire({ element })\n    }\n  }\n\n  private toggleSelection(element: T | undefined): void {\n    if (!element) return\n    let idx = this._selection.findIndex(o => o === element)\n    if (idx !== -1) {\n      this.unselectItem(idx)\n    } else {\n      this.selectItem(element)\n    }\n  }\n\n  private clearSelection(): void {\n    if (!this.bufnr) return\n    this._selection = []\n    let buf = this.nvim.createBuffer(this.bufnr)\n    buf.unplaceSign({ group: 'CocTree' })\n    this._onDidChangeSelection.fire({ selection: [] })\n  }\n\n  public selectItem(item: T, forceSingle?: boolean, noRedraw?: boolean): void {\n    let { nvim } = this\n    let row = this.getItemLnum(item)\n    if (row == null || !this.bufnr) return\n    let buf = nvim.createBuffer(this.bufnr)\n    let exists = this._selection.includes(item)\n    if (!this.opts.canSelectMany || forceSingle) {\n      this._selection = [item]\n    } else if (!exists) {\n      this._selection.push(item)\n    }\n    nvim.pauseNotification()\n    if (!this.opts.canSelectMany || forceSingle) {\n      buf.unplaceSign({ group: 'CocTree' })\n    }\n    nvim.call('win_execute', [this.winid, `normal! ${row + 1}G`], true)\n    buf.placeSign({ id: signOffset + row, lnum: row + 1, name: 'CocTreeSelected', group: 'CocTree' })\n    if (!noRedraw) this.redraw()\n    nvim.resumeNotification(false, true)\n    if (!exists) this._onDidChangeSelection.fire({ selection: this._selection })\n  }\n\n  public unselectItem(idx: number): void {\n    let item = this._selection[idx]\n    let row = this.getItemLnum(item)\n    if (row == null || !this.bufnr) return\n    this._selection.splice(idx, 1)\n    let buf = this.nvim.createBuffer(this.bufnr)\n    buf.unplaceSign({ group: 'CocTree', id: signOffset + row })\n    this._onDidChangeSelection.fire({ selection: this._selection })\n  }\n\n  public focusItem(element: T): void {\n    if (!this.winid) return\n    let lnum = this.getItemLnum(element)\n    if (lnum == null) return\n    this.nvim.call('win_execute', [this.winid, `exe ${lnum + 1}`], true)\n  }\n\n  private getElementByLnum(lnum: number): T | undefined {\n    let item = this.renderedItems[lnum - this.startLnum]\n    return item ? item.node : undefined\n  }\n\n  private getItemLnum(item: T): number | undefined {\n    let idx = this.renderedItems.findIndex(o => o.node === item)\n    if (idx == -1) return undefined\n    return this.startLnum + idx\n  }\n\n  private async getTreeItem(element: T): Promise<TreeItem | undefined> {\n    let exists: TreeItem\n    let resolved = false\n    let obj = this.nodesMap.get(element)\n    if (obj != null) {\n      exists = obj.item\n      resolved = obj.resolved\n    }\n    let item = await Promise.resolve(this.provider.getTreeItem(element))\n    if (exists\n      && item\n      && exists.collapsibleState != TreeItemCollapsibleState.None\n      && item.collapsibleState != TreeItemCollapsibleState.None) {\n      item.collapsibleState = exists.collapsibleState\n    }\n    this.nodesMap.set(element, { item, resolved })\n    return item\n  }\n\n  private getRenderedLine(treeItem: TreeItem, lnum: number, level: number): { line: string, highlights: HighlightItem[] } {\n    let { openedIcon, closedIcon } = this.config\n    const highlights: HighlightItem[] = []\n    const { label, deprecated, description } = treeItem\n    let prefix = '  '.repeat(level)\n    const addHighlight = (text: string, hlGroup: string) => {\n      let colStart = byteLength(prefix)\n      highlights.push({\n        lnum,\n        hlGroup,\n        colStart,\n        colEnd: colStart + byteLength(text),\n      })\n    }\n    switch (treeItem.collapsibleState) {\n      case TreeItemCollapsibleState.Expanded: {\n        addHighlight(openedIcon, 'CocTreeOpenClose')\n        prefix += openedIcon + ' '\n        break\n      }\n      case TreeItemCollapsibleState.Collapsed: {\n        addHighlight(closedIcon, 'CocTreeOpenClose')\n        prefix += closedIcon + ' '\n        break\n      }\n      default:\n        prefix += this.leafIndent ? '  ' : ''\n    }\n    if (treeItem.icon) {\n      let { text, hlGroup } = treeItem.icon\n      addHighlight(text, hlGroup)\n      prefix += text + ' '\n    }\n    if (TreeItemLabel.is(label) && Array.isArray(label.highlights)) {\n      let colStart = byteLength(prefix)\n      for (let o of label.highlights) {\n        highlights.push({\n          lnum,\n          hlGroup: 'CocSearch',\n          colStart: colStart + o[0],\n          colEnd: colStart + o[1]\n        })\n      }\n    }\n    let labelText = getItemLabel(treeItem)\n    if (deprecated) {\n      addHighlight(labelText, 'CocDeprecatedHighlight')\n    }\n    prefix += labelText\n    if (description && description.indexOf('\\n') == -1) {\n      prefix += ' '\n      addHighlight(description, 'CocTreeDescription')\n      prefix += description\n    }\n    return { line: prefix, highlights }\n  }\n\n  private async appendTreeNode(element: T, level: number, lnum: number, items: RenderedItem<T>[], highlights: HighlightItem[]): Promise<number> {\n    let treeItem = await this.getTreeItem(element)\n    if (!treeItem) return 0\n    let takes = 1\n    let res = this.getRenderedLine(treeItem, lnum, level)\n    highlights.push(...res.highlights)\n    items.push({ level, line: res.line, node: element })\n    if (treeItem.collapsibleState == TreeItemCollapsibleState.Expanded) {\n      let l = level + 1\n      let children = await Promise.resolve(this.provider.getChildren(element))\n      for (let el of toArray(children as any)) {\n        let n = await this.appendTreeNode(el, l, lnum + takes, items, highlights)\n        takes = takes + n\n      }\n    }\n    return takes\n  }\n\n  private updateUI(lines: string[], highlights: HighlightItem[], start = 0, end = -1, noRedraw = false): void {\n    if (!this.bufnr) return\n    let { nvim, winid } = this\n    let buf = nvim.createBuffer(this.bufnr)\n    nvim.pauseNotification()\n    buf.setOption('modifiable', true, true)\n    void buf.setLines(lines, { start, end, strictIndexing: false }, true)\n    if (this.autoWidth) this.nvim.call('coc#window#adjust_width', [winid], true)\n    if (highlights.length) {\n      let highlightEnd = end == -1 ? -1 : start + lines.length\n      buf.updateHighlights(highlightNamespace, highlights, { start, end: highlightEnd })\n    }\n    buf.setOption('modifiable', false, true)\n    if (!noRedraw) this.redraw()\n    nvim.resumeNotification(false, true)\n  }\n\n  public async reveal(element: T, options: { select?: boolean; focus?: boolean; expand?: number | boolean } = {}): Promise<void> {\n    if (this.filtering) return\n    let isShown = this.getItemLnum(element) != null\n    let { select, focus, expand } = options\n    let curr = element\n    if (typeof this.provider.getParent !== 'function') {\n      throw new Error('missing getParent function from provider for reveal.')\n    }\n    if (!isShown) {\n      while (curr) {\n        let parentNode = await Promise.resolve(this.provider.getParent(curr))\n        if (parentNode) {\n          let item = await this.getTreeItem(parentNode)\n          item.collapsibleState = TreeItemCollapsibleState.Expanded\n          curr = parentNode\n        } else {\n          break\n        }\n      }\n    }\n    if (expand) {\n      let item = await this.getTreeItem(element)\n      // Unable to expand\n      if (item.collapsibleState != TreeItemCollapsibleState.None) {\n        item.collapsibleState = TreeItemCollapsibleState.Expanded\n        if (typeof expand === 'boolean') expand = 1\n        if (expand > 1) {\n          let curr = Math.min(expand, 2)\n          let nodes = await Promise.resolve(this.provider.getChildren(element))\n          while (!isFalsyOrEmpty(nodes)) {\n            let arr: T[] = []\n            for (let n of nodes) {\n              let item = await this.getTreeItem(n)\n              if (item.collapsibleState == TreeItemCollapsibleState.None) continue\n              item.collapsibleState = TreeItemCollapsibleState.Expanded\n              if (curr > 1) {\n                let res = await Promise.resolve(this.provider.getChildren(n))\n                arr.push(...res)\n              }\n            }\n            nodes = arr\n            curr = curr - 1\n          }\n        }\n      }\n    }\n    if (!isShown || expand) {\n      await this.render()\n    }\n    if (select !== false) this.selectItem(element)\n    if (focus) this.focusItem(element)\n  }\n\n  private updateHeadLines(initialize = false): void {\n    let { titleCount, messageCount } = this.lineState\n    let end = initialize ? -1 : titleCount + messageCount\n    let lines: string[] = []\n    let highlights: HighlightItem[] = []\n    if (this.message) {\n      highlights.push({ hlGroup: 'MoreMsg', colStart: 0, colEnd: byteLength(this.message), lnum: 0 })\n      lines.push(this.message)\n      lines.push('')\n    }\n    if (this.title) {\n      highlights.push({ hlGroup: 'CocTreeTitle', colStart: 0, colEnd: byteLength(this.title), lnum: lines.length })\n      if (this.description) {\n        let colStart = byteLength(this.title) + 1\n        highlights.push({ hlGroup: 'Comment', colStart, colEnd: colStart + byteLength(this.description), lnum: lines.length })\n      }\n      lines.push(this.title + (this.description ? ' ' + this.description : ''))\n    }\n    this.lineState.messageCount = this.message ? 2 : 0\n    this.lineState.titleCount = this.title ? 1 : 0\n    this.updateUI(lines, highlights, 0, end)\n    if (!initialize) {\n      this.refreshSigns()\n    }\n  }\n\n  /**\n   * Update signs after collapse/expand or head change\n   */\n  private refreshSigns(): void {\n    let { selection, nvim, bufnr } = this\n    if (!selection.length || !bufnr) return\n    let buf = nvim.createBuffer(bufnr)\n    nvim.pauseNotification()\n    buf.unplaceSign({ group: 'CocTree' })\n    for (let n of selection) {\n      let row = this.getItemLnum(n)\n      if (row == null) continue\n      buf.placeSign({ id: signOffset + row, lnum: row + 1, name: 'CocTreeSelected', group: 'CocTree' })\n    }\n    nvim.resumeNotification(false, true)\n  }\n\n  // Render all tree items\n  public async render(): Promise<void> {\n    if (!this.bufnr) return\n    let release = await this.mutex.acquire()\n    try {\n      let lines: string[] = []\n      let highlights: HighlightItem[] = []\n      let { startLnum } = this\n      let nodes = await Promise.resolve(this.provider.getChildren())\n      let level = 0\n      let lnum = startLnum\n      let renderedItems: RenderedItem<T>[] = []\n      if (isFalsyOrEmpty(nodes)) {\n        this.message = 'No results'\n      } else {\n        if (this.message == 'No results') this.message = ''\n        for (let node of nodes) {\n          let n = await this.appendTreeNode(node, level, lnum, renderedItems, highlights)\n          lnum += n\n        }\n      }\n      lines.push(...renderedItems.map(o => o.line))\n      this.renderedItems = renderedItems\n      let delta = this.startLnum - startLnum\n      highlights.forEach(o => o.lnum = o.lnum + delta)\n      this.updateUI(lines, highlights, this.startLnum, -1)\n      this._onDidRefrash.fire()\n      this.retryTimers = 0\n      release()\n    } catch (err) {\n      logger.error('Error on render', err)\n      this.renderedItems = []\n      this.nodesMap.clear()\n      this.lineState = { titleCount: 0, messageCount: 1 }\n      release()\n      let errMsg = `${err}`.replace(/\\r?\\n/g, ' ')\n      this.updateUI([errMsg], [{ hlGroup: 'WarningMsg', colStart: 0, colEnd: byteLength(errMsg), lnum: 0 }])\n      if (this.retryTimers == maxRetry) return\n      this.timer = setTimeout(() => {\n        this.retryTimers = this.retryTimers + 1\n        void this.render()\n      }, retryTimeout)\n    }\n  }\n\n  public async show(splitCommand = 'belowright 30vs', waitRender = true): Promise<boolean> {\n    let { nvim } = this\n    let [targetBufnr, windowId] = await nvim.eval(`[bufnr(\"%\"),win_getid()]`) as [number, number]\n    this._targetBufnr = targetBufnr\n    this._targetWinId = windowId\n    let opts = {\n      command: splitCommand,\n      bufname: this.bufname,\n      viewId: this.viewId.replace(/\"/g, '\\\\\"'),\n      bufnr: defaultValue(this.bufnr, -1),\n      winid: defaultValue(this.winid, -1),\n      bufhidden: defaultValue(this.opts.bufhidden, 'wipe'),\n      canSelectMany: this.opts.canSelectMany === true,\n      winfixwidth: this.winfixwidth === true\n    }\n    let [bufnr, winid, tabId] = await nvim.call('coc#ui#create_tree', [opts]) as [number, number, number]\n    this.bufnr = bufnr\n    this.winid = winid\n    this._targetTabId = tabId\n    if (winid != opts.winid) this._onDidChangeVisibility.fire({ visible: true })\n    if (bufnr == opts.bufnr) return true\n    this.registerKeymaps()\n    this.updateHeadLines(true)\n    let promise = this.render()\n    if (waitRender) await promise\n    return true\n  }\n\n  public registerLocalKeymap(mode: LocalMode, key: string, fn: (element: T | undefined) => Promise<void> | void, notify = false): void {\n    if (!this.bufnr) {\n      this._keymapDefs.push({ mode, key, fn, notify })\n    } else {\n      this.addLocalKeymap(mode, key, fn, notify)\n    }\n  }\n\n  private addLocalKeymap(mode: LocalMode, key: string | undefined, fn: (element: T | undefined) => Promise<void> | void, notify = true): void {\n    if (!key) return\n    workspace.registerLocalKeymap(this.bufnr, mode, key, async () => {\n      let lnum = await this.nvim.call('line', ['.']) as number\n      let element = this.getElementByLnum(lnum - 1)\n      await Promise.resolve(fn(element))\n    }, notify)\n  }\n\n  private registerKeymaps(): void {\n    let { toggleSelection, actions, close, invoke, toggle, collapseAll, activeFilter } = this.keys\n    let { nvim, _keymapDefs } = this\n    this.disposables.push(workspace.registerLocalKeymap(this.bufnr, 'n', '<C-o>', () => {\n      nvim.call('win_gotoid', [this._targetWinId], true)\n    }, true))\n    this.addLocalKeymap('n', '<LeftRelease>', async element => {\n      if (element) await this.onClick(element)\n    })\n    if (this.filter != null) {\n      this.addLocalKeymap('n', activeFilter, async () => {\n        this.nvim.command(`exe ${this.startLnum}`, true)\n        this.filter.active()\n        this.filterText = ''\n        this._onDidFilterStateChange.fire(true)\n      })\n    }\n    this.addLocalKeymap('n', toggleSelection, element => this.toggleSelection(element))\n    this.addLocalKeymap('n', invoke, element => this.invokeCommand(element))\n    this.addLocalKeymap('n', actions, element => this.invokeActions(element))\n    this.addLocalKeymap('n', toggle, element => this.toggleExpand(element))\n    this.addLocalKeymap('n', collapseAll, () => this.collapseAll())\n    this.addLocalKeymap('n', close, () => this.hide())\n    while (_keymapDefs.length) {\n      const def = _keymapDefs.pop()\n      this.addLocalKeymap(def.mode, def.key, def.fn, def.notify)\n    }\n  }\n\n  public hide(): void {\n    this.nvim.call('coc#window#close', [this.winid], true)\n    this.redraw()\n    this.winid = undefined\n    this._onDidChangeVisibility.fire({ visible: false })\n  }\n\n  private redraw(): void {\n    if (workspace.isVim || this.filter?.activated) {\n      this.nvim.command('redraw', true)\n    }\n  }\n\n  private async collapseAll(): Promise<void> {\n    for (let obj of this.nodesMap.values()) {\n      let item = obj.item\n      if (item.collapsibleState == TreeItemCollapsibleState.Expanded) {\n        item.collapsibleState = TreeItemCollapsibleState.Collapsed\n      }\n    }\n    await this.render()\n  }\n\n  private cancelResolve(): void {\n    if (this.resolveTokenSource) {\n      this.resolveTokenSource.cancel()\n      this.resolveTokenSource = undefined\n    }\n  }\n\n  public dispose(): void {\n    if (!this.provider) return\n    if (this.timer) clearTimeout(this.timer)\n    this.cancelResolve()\n    let { bufnr } = this\n    if (this.winid) this._onDidChangeVisibility.fire({ visible: false })\n    if (bufnr) this.nvim.command(`silent! bwipeout! ${bufnr}`, true)\n    this._keymapDefs = []\n    this.winid = undefined\n    this.bufnr = undefined\n    this.filter?.dispose()\n    this._selection = []\n    this.itemsToFilter = []\n    this.tooltipFactory.dispose()\n    this.renderedItems = []\n    this.nodesMap.clear()\n    this.provider = undefined\n    this._onDispose.fire()\n    this._onDispose.dispose()\n    disposeAll(this.disposables)\n  }\n}\n"
  },
  {
    "path": "src/tree/filter.ts",
    "content": "'use strict'\nimport events from '../events'\nimport { Neovim } from '@chemzqm/neovim'\nimport { Disposable, Emitter, Event } from '../util/protocol'\nimport { disposeAll } from '../util'\nexport const sessionKey = 'filter'\n\nexport class HistoryInput {\n  private history: string[] = []\n\n  public next(input: string): string | undefined {\n    let idx = this.history.indexOf(input)\n    return this.history[idx + 1] ?? this.history[0]\n  }\n\n  public previous(input: string): string | undefined {\n    let idx = this.history.indexOf(input)\n    return this.history[idx - 1] ?? this.history[this.history.length - 1]\n  }\n\n  public add(input: string): void {\n    let idx = this.history.indexOf(input)\n    if (idx !== -1) {\n      this.history.splice(idx, 1)\n    }\n    this.history.unshift(input)\n  }\n\n  public toJSON(): string {\n    return `[${this.history.join(',')}]`\n  }\n}\n\nexport default class Filter<T> {\n  private _activated = false\n  private text: string\n  private history = new HistoryInput()\n  private disposables: Disposable[] = []\n  private readonly _onDidUpdate = new Emitter<string>()\n  private readonly _onDidExit = new Emitter<T | undefined>()\n  private readonly _onDidKeyPress = new Emitter<string>()\n  public readonly onDidKeyPress: Event<string> = this._onDidKeyPress.event\n  public readonly onDidUpdate: Event<string> = this._onDidUpdate.event\n  public readonly onDidExit: Event<T | undefined> = this._onDidExit.event\n  constructor(private nvim: Neovim, keys: string[]) {\n    this.text = ''\n    events.on('InputChar', (session, character) => {\n      if (session !== sessionKey || !this._activated) return\n      if (!keys.includes(character)) {\n        if (character.length == 1) {\n          this.text = this.text + character\n          this._onDidUpdate.fire(this.text)\n          return\n        }\n        if (character == '<bs>' || character == '<C-h>') {\n          this.text = this.text.slice(0, -1)\n          this._onDidUpdate.fire(this.text)\n          return\n        }\n        if (character == '<C-u>') {\n          this.text = ''\n          this._onDidUpdate.fire(this.text)\n          return\n        }\n        if (character == '<C-n>') {\n          let text = this.history.next(this.text)\n          if (text) {\n            this.text = text\n            this._onDidUpdate.fire(this.text)\n          }\n          return\n        }\n        if (character == '<C-p>') {\n          let text = this.history.previous(this.text)\n          if (text) {\n            this.text = text\n            this._onDidUpdate.fire(this.text)\n          }\n        }\n        if (character == '<esc>' || character == '<C-o>') {\n          this.deactivate()\n          return\n        }\n      }\n      this._onDidKeyPress.fire(character)\n    }, null, this.disposables)\n  }\n\n  public active(): void {\n    this._activated = true\n    this.text = ''\n    this.nvim.call('coc#prompt#start_prompt', [sessionKey], true)\n  }\n\n  public deactivate(node?: T): void {\n    if (!this._activated) return\n    this.nvim.call('coc#prompt#stop_prompt', [sessionKey], true)\n    this._activated = false\n    let { text } = this\n    this.text = ''\n    this._onDidExit.fire(node)\n    this.history.add(text)\n  }\n\n  public get activated(): boolean {\n    return this._activated\n  }\n\n  public dispose(): void {\n    this.deactivate()\n    this._onDidKeyPress.dispose()\n    this._onDidUpdate.dispose()\n    this._onDidExit.dispose()\n    disposeAll(this.disposables)\n  }\n}\n"
  },
  {
    "path": "src/tree/index.ts",
    "content": "'use strict'\nimport type { Disposable, Event, CancellationToken } from '../util/protocol'\nimport type { ProviderResult } from '../provider'\nimport { TreeItem, TreeItemCollapsibleState } from './TreeItem'\n\nexport { TreeItem, TreeItemCollapsibleState }\n\nexport interface TreeItemAction<T> {\n  /**\n   * Label text in menu.\n   */\n  title: string\n  handler: (item: T) => ProviderResult<void>\n}\n\nexport interface LineState {\n  /**\n   * Line count used by message\n   */\n  messageCount: number\n  /**\n   * Line count used by title\n   */\n  titleCount: number\n}\n\nexport interface TreeViewKeys {\n  invoke: string\n  toggle: string\n  actions: string\n  collapseAll: string\n  toggleSelection: string\n  close: string\n  activeFilter: string\n  selectNext: string\n  selectPrevious: string\n}\n\nexport interface TreeItemData {\n  item: TreeItem\n  resolved: boolean\n}\n\n/**\n * Options for creating a {@link TreeView}\n */\nexport interface TreeViewOptions<T> {\n  /**\n   * bufhidden option for TreeView, default to 'wipe'\n   */\n  bufhidden?: 'hide' | 'unload' | 'delete' | 'wipe'\n  /**\n   * Increase width to avoid wrapped lines.\n   */\n  autoWidth?: boolean\n  /**\n   * Fixed width for window, default to true\n   */\n  winfixwidth?: boolean\n  /**\n   * Enable filter feature, default to false\n   */\n  enableFilter?: boolean\n  /**\n   * Disable indent of leaves without children, default to false\n   */\n  disableLeafIndent?: boolean\n  /**\n   * A data provider that provides tree data.\n   */\n  treeDataProvider: TreeDataProvider<T>\n  /**\n   * Whether the tree supports multi-select. When the tree supports multi-select and a command is executed from the tree,\n   * the first argument to the command is the tree item that the command was executed on and the second argument is an\n   * array containing all selected tree items.\n   */\n  canSelectMany?: boolean\n}\n\n/**\n * The event that is fired when an element in the {@link TreeView} is expanded or collapsed\n */\nexport interface TreeViewExpansionEvent<T> {\n  /**\n   * Element that is expanded or collapsed.\n   */\n  readonly element: T\n}\n\n/**\n * The event that is fired when there is a change in {@link TreeView.selection tree view's selection}\n */\nexport interface TreeViewSelectionChangeEvent<T> {\n  readonly selection: T[]\n}\n\n/**\n * The event that is fired when there is a change in {@link TreeView.visible tree view's visibility}\n */\nexport interface TreeViewVisibilityChangeEvent {\n  readonly visible: boolean\n}\n\n/**\n * Represents a Tree view\n */\nexport interface TreeView<T> extends Disposable {\n\n  /**\n   * Event that is fired when an element is expanded\n   */\n  readonly onDidExpandElement: Event<TreeViewExpansionEvent<T>>\n\n  /**\n   * Event that is fired when an element is collapsed\n   */\n  readonly onDidCollapseElement: Event<TreeViewExpansionEvent<T>>\n\n  /**\n   * Currently selected elements.\n   */\n  readonly selection: T[]\n\n  /**\n   * Event that is fired when the {@link TreeView.selection selection} has changed\n   */\n  readonly onDidChangeSelection: Event<TreeViewSelectionChangeEvent<T>>\n\n  /**\n   * `true` if the {@link TreeView tree view} is visible otherwise `false`.\n   *\n   * **NOTE:** is `true` when TreeView visible on other tab.\n   */\n  readonly visible: boolean\n\n  /**\n   * Window id used by TreeView.\n   */\n  readonly windowId: number | undefined\n\n  /**\n   * Event that is fired when {@link TreeView.visible visibility} has changed\n   */\n  readonly onDidChangeVisibility: Event<TreeViewVisibilityChangeEvent>\n\n  /**\n   * An optional human-readable message that will be rendered in the view.\n   * Setting the message to null, undefined, or empty string will remove the message from the view.\n   */\n  message?: string\n\n  /**\n   * The tree view title is initially taken from viewId of TreeView\n   * Changes to the title property will be properly reflected in the UI in the title of the view.\n   */\n  title?: string\n\n  /**\n   * An optional human-readable description which is rendered less prominently in the title of the view.\n   * Setting the title description to null, undefined, or empty string will remove the description from the view.\n   */\n  description?: string\n\n  /**\n   * Reveals the given element in the tree view.\n   * If the tree view is not visible then the tree view is shown and element is revealed.\n   *\n   * By default revealed element is selected.\n   * In order to not to select, set the option `select` to `false`.\n   * In order to focus, set the option `focus` to `true`.\n   * In order to expand the revealed element, set the option `expand` to `true`. To expand recursively set `expand` to the number of levels to expand.\n   * **NOTE:** You can expand only to 3 levels maximum.\n   *\n   * **NOTE:** The {@link TreeDataProvider} that the `TreeView` {@link window.createTreeView is registered with} with must implement {@link TreeDataProvider.getParent getParent} method to access this API.\n   */\n  reveal(element: T, options?: { select?: boolean, focus?: boolean, expand?: boolean | number }): Thenable<void>\n\n  /**\n   * Create tree view in new window, does nothing when it's visible.\n   *\n   * **NOTE:**\n   * **NOTE:** TreeView with same viewId in current tab would be disposed.\n   * @param splitCommand The command to open TreeView window, default to 'belowright 30vs'\n   */\n  show(splitCommand?: string): Promise<boolean>\n}\n\n/**\n * A data provider that provides tree data\n */\nexport interface TreeDataProvider<T> {\n  /**\n   * An optional event to signal that an element or root has changed.\n   * This will trigger the view to update the changed element/root and its children recursively (if shown).\n   * To signal that root has changed, do not pass any argument or pass `undefined` or `null`.\n   */\n  onDidChangeTreeData?: Event<T | undefined | null | void>\n\n  /**\n   * Get {@link TreeItem} representation of the `element`\n   * @param element The element for which {@link TreeItem} representation is asked for.\n   * @return {@link TreeItem} representation of the element\n   */\n  getTreeItem(element: T): TreeItem | Thenable<TreeItem>\n\n  /**\n   * Get the children of `element` or root if no element is passed.\n   * @param element The element from which the provider gets children. Can be `undefined`.\n   * @return Children of `element` or root if no element is passed.\n   */\n  getChildren(element?: T): ProviderResult<ReadonlyArray<T>>\n\n  /**\n   * Optional method to return the parent of `element`.\n   * Return `null` or `undefined` if `element` is a child of root.\n   *\n   * **NOTE:** This method should be implemented in order to access {@link TreeView.reveal reveal} API.\n   * @param element The element for which the parent has to be returned.\n   * @return Parent of `element`.\n   */\n  getParent?(element: T): ProviderResult<T>\n\n  /**\n   * Called on hover to resolve the {@link TreeItem.tooltip TreeItem} property if it is undefined.\n   * Called on tree item click/open to resolve the {@link TreeItem.command TreeItem} property if it is undefined.\n   * Only properties that were undefined can be resolved in `resolveTreeItem`.\n   * Functionality may be expanded later to include being called to resolve other missing\n   * properties on selection and/or on open.\n   *\n   * Will only ever be called once per TreeItem.\n   *\n   * onDidChangeTreeData should not be triggered from within resolveTreeItem.\n   *\n   * *Note* that this function is called when tree items are already showing in the UI.\n   * Because of that, no property that changes the presentation (label, description, etc.)\n   * can be changed.\n   * @param item Undefined properties of `item` should be set then `item` should be returned.\n   * @param element The object associated with the TreeItem.\n   * @param token A cancellation token.\n   * @return The resolved tree item or a thenable that resolves to such. It is OK to return the given\n   * `item`. When no result is returned, the given `item` will be used.\n   */\n  resolveTreeItem?(item: TreeItem, element: T, token: CancellationToken): ProviderResult<TreeItem>\n\n  /**\n   * Called with current element to resolve actions.\n   * Called when user press 'actions' key.\n   * @param item Resolved item.\n   * @param element The object under cursor.\n   */\n  resolveActions?(item: TreeItem, element: T): ProviderResult<ReadonlyArray<TreeItemAction<T>>>\n}\n"
  },
  {
    "path": "src/types.ts",
    "content": "'use strict'\nimport type { Window } from '@chemzqm/neovim'\nimport type { Disposable, Event } from 'vscode-languageserver-protocol'\nimport type { CreateFile, DeleteFile, Diagnostic, Location, Position, Range, RenameFile, TextDocumentEdit } from 'vscode-languageserver-types'\nimport type { URI } from 'vscode-uri'\nimport type RelativePattern from './model/relativePattern'\nimport type { LinesTextDocument } from './model/textdocument'\n\nexport type { IConfigurationChangeEvent } from './configuration/types'\n\nexport type GlobPattern = string | RelativePattern\n\ndeclare global {\n  namespace NodeJS {\n    interface Global {\n      __isMain?: boolean\n      __TEST__?: boolean\n      __starttime?: number\n      REVISION?: string\n      WebAssembly: any\n    }\n  }\n}\n\nexport type HoverTarget = 'float' | 'preview' | 'echo'\n\nexport type Optional<T extends object, K extends keyof T = keyof T> = Omit<\n  T,\n  K\n> &\n  Partial<Pick<T, K>>\n\nexport interface Thenable<T> {\n  then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Thenable<TResult>\n  // eslint-disable-next-line @typescript-eslint/unified-signatures\n  then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => void): Thenable<TResult>\n}\n\nexport interface AnsiHighlight {\n  span: [number, number]\n  hlGroup: string\n}\n\nexport interface FileWatchConfig {\n  readonly watchmanPath: string | null | undefined\n  readonly enable: boolean\n  readonly ignoredFolders: string[]\n}\n\nexport interface LocationWithTarget extends Location {\n  /**\n   * The full target range of this link. If the target for example is a symbol then target range is the\n   * range enclosing this symbol not including leading/trailing whitespace but everything else like comments. This information is typically used to highlight the range in the editor.\n   */\n  targetRange?: Range\n}\n\nexport interface BufferOption {\n  readonly bufnr: number\n  readonly eol: number\n  readonly size: number\n  readonly winid: number\n  readonly winids: number[]\n  readonly lines: null | string[]\n  readonly variables: { [key: string]: any }\n  readonly bufname: string\n  readonly commandline: number\n  readonly fullpath: string\n  readonly buftype: string\n  readonly filetype: string\n  readonly iskeyword: string\n  readonly lisp: number\n  readonly changedtick: number\n  readonly previewwindow: number\n}\n\nexport interface IFileSystemWatcher extends Disposable {\n  ignoreCreateEvents: boolean\n  ignoreChangeEvents: boolean\n  ignoreDeleteEvents: boolean\n  onDidCreate: Event<URI>\n  onDidChange: Event<URI>\n  onDidDelete: Event<URI>\n}\n\nexport interface Documentation {\n  filetype: string\n  content: string\n  highlights?: HighlightItem[]\n  active?: [number, number]\n}\n\nexport interface FloatFactory {\n  window: Window | null\n  activated: () => Promise<boolean>\n  show: (docs: Documentation[], options?: FloatOptions) => Promise<void>\n  close: () => void\n  checkRetrigger: (bufnr: number) => boolean\n  dispose: () => void\n}\n\nexport interface FloatConfig {\n  border?: boolean | [number, number, number, number]\n  rounded?: boolean\n  highlight?: string\n  title?: string\n  borderhighlight?: string\n  close?: boolean\n  maxHeight?: number\n  maxWidth?: number\n  winblend?: number\n  focusable?: boolean\n  shadow?: boolean\n}\n\nexport interface FloatOptions extends FloatConfig {\n  title?: string\n  offsetX?: number\n}\n\nexport interface HighlightItemOption {\n  /**\n   * default to true\n   */\n  combine?: boolean\n  /**\n   * default to false\n   */\n  start_incl?: boolean\n  /**\n   * default to false\n   */\n  end_incl?: boolean\n}\n\n/**\n * Represent a highlight that not cross lines\n * all zero based.\n */\nexport interface HighlightItem extends HighlightItemOption {\n  lnum: number\n  hlGroup: string\n  /**\n   * 0 based start column.\n   */\n  colStart: number\n  /**\n   * 0 based end column.\n   */\n  colEnd: number\n}\n\nexport interface Env {\n  runtimepath: string\n  readonly jumpAutocmd: boolean\n  readonly guicursor: string\n  readonly tabCount: number\n  readonly mode: string\n  readonly apiversion: number\n  readonly pumwidth: number\n  readonly unixPrefix: string\n  readonly ambiguousIsNarrow: boolean\n  readonly floating: boolean\n  readonly sign: boolean\n  readonly globalExtensions: string[]\n  readonly workspaceFolders: string[]\n  readonly config: any\n  readonly pid: number\n  readonly columns: number\n  readonly lines: number\n  readonly pumevent: boolean\n  readonly cmdheight: number\n  readonly filetypeMap: { [index: string]: string }\n  readonly isVim: boolean\n  readonly isCygwin: boolean\n  readonly isMacvim: boolean\n  readonly isiTerm: boolean\n  readonly version: string\n  readonly locationlist: boolean\n  readonly progpath: string\n  readonly dialog: boolean\n  readonly terminal: boolean\n  readonly textprop: boolean\n  readonly vimCommands: CommandConfig[]\n  readonly semanticHighlights: string[]\n}\n\nexport interface CommandConfig {\n  id: string\n  cmd: string\n  title?: string\n}\n\n/**\n * An output channel is a container for readonly textual information.\n *\n * To get an instance of an `OutputChannel` use\n * [createOutputChannel](#window.createOutputChannel).\n */\nexport interface OutputChannel {\n\n  /**\n   * The human-readable name of this output channel.\n   */\n  readonly name: string\n\n  readonly content?: string\n  /**\n   * Append the given value to the channel.\n   * @param value A string, falsy values will not be printed.\n   */\n  append(value: string): void\n\n  /**\n   * Append the given value and a line feed character\n   * to the channel.\n   * @param value A string, falsy values will be printed.\n   */\n  appendLine(value: string): void\n\n  /**\n   * Removes output from the channel. Latest `keep` lines will be remained.\n   */\n  clear(keep?: number): void\n\n  /**\n   * Reveal this channel in the UI.\n   * @param preserveFocus When `true` the channel will not take focus.\n   */\n  show(preserveFocus?: boolean): void\n\n  /**\n   * Hide this channel from the UI.\n   */\n  hide(): void\n\n  /**\n   * Dispose and free associated resources.\n   */\n  dispose(): void\n}\n\nexport interface KeymapOption {\n  /**\n   * Use `<Cmd>` prefix\n   */\n  cmd?: boolean\n  sync?: boolean\n  cancel?: boolean\n  silent?: boolean\n  repeat?: boolean\n  special?: boolean\n}\n\nexport interface TabStopInfo {\n  // tabstop index\n  index: number\n  // 0 based line character\n  range: [number, number, number, number]\n  // current text\n  text: string\n}\n\nexport interface JumpInfo {\n  readonly index: number\n  readonly forward: boolean\n  readonly tabstops: TabStopInfo[]\n  // placeholder range\n  readonly range: Range\n  // character before current placeholder.\n  readonly charbefore: string\n  readonly snippet_start: Position\n  readonly snippet_end: Position\n}\n\nexport interface Autocmd {\n  event: string | string[]\n  callback: (...args: any[]) => void | Promise<void>\n  buffer?: number\n  once?: boolean\n  nested?: boolean\n  pattern?: string | string[]\n  arglist?: string[]\n  request?: boolean\n  thisArg?: any\n}\n\nexport interface UltiSnipsActions {\n  preExpand?: string\n  postExpand?: string\n  postJump?: string\n}\n\nexport interface UltiSnippetOption {\n  regex?: string\n  context?: string\n  noPython?: boolean\n  range?: Range\n  line?: string\n  actions?: UltiSnipsActions\n  /**\n   * Do not expand tabs\n   */\n  noExpand?: boolean\n  /**\n   * Trim all whitespaces from right side of snippet lines.\n   */\n  trimTrailingWhitespace?: boolean\n  /**\n   * Remove whitespace immediately before the cursor at the end of a line before jumping to the next tabstop\n   */\n  removeWhiteSpace?: boolean\n}\n\nexport interface TextDocumentMatch {\n  readonly uri: string\n  readonly languageId: string\n}\n\nexport interface QuickfixItem {\n  uri?: string\n  module?: string\n  range?: Range\n  text?: string\n  type?: string\n  filename: string\n  bufnr?: number\n  lnum?: number\n  end_lnum?: number\n  col?: number\n  end_col?: number\n  valid?: boolean\n  nr?: number\n  targetRange?: Range\n}\n\n/**\n * Represents an item that can be selected from\n * a list of items.\n */\nexport interface QuickPickItem {\n  /**\n   * A human-readable string which is rendered prominent\n   */\n  label: string\n  /**\n   * A human-readable string which is rendered less prominent in the same line\n   */\n  description?: string\n  /**\n   * Optional flag indicating if this item is picked initially.\n   */\n  picked?: boolean\n}\n\n// TextDocument {{\nexport type DocumentChange = TextDocumentEdit | CreateFile | RenameFile | DeleteFile\n\n/**\n * An event describing a change to a text document.\n */\nexport interface TextDocumentContentChange {\n  /**\n   * The range of the document that changed.\n   */\n  range: Range\n  /**\n   * The optional length of the range that got replaced.\n   * @deprecated use range instead.\n   */\n  rangeLength?: number\n  /**\n   * The new text for the provided range.\n   */\n  text: string\n}\n\nexport interface DidChangeTextDocumentParams {\n  /**\n   * The document that did change. The version number points\n   * to the version after all provided content changes have\n   * been applied.\n   */\n  readonly textDocument: {\n    version: number\n    uri: string\n  }\n\n  readonly document: LinesTextDocument\n  /**\n   * The actual content changes. The content changes describe single state changes\n   * to the document. So if there are two content changes c1 (at array index 0) and\n   * c2 (at array index 1) for a document in state S then c1 moves the document from\n   * S to S' and c2 from S' to S''. So c1 is computed on the state S and c2 is computed\n   * on the state S'.\n   */\n  readonly contentChanges: ReadonlyArray<TextDocumentContentChange>\n  /**\n   * Buffer number of document.\n   */\n  readonly bufnr: number\n  /**\n   * Original content before change\n   */\n  readonly original: string\n  /**\n   * Changed lines\n   */\n  readonly originalLines: ReadonlyArray<string>\n}\n// }}\n\nexport interface DiagnosticWithFileType extends Diagnostic {\n  /**\n   * The `filetype` property provides the type of file associated with the diagnostic information.\n   * This information is utilized by the diagnostic buffer panel for highlighting and formatting\n   * the diagnostic messages according to the specific filetype.\n   */\n  filetype?: string\n}\n"
  },
  {
    "path": "src/util/ansiparse.ts",
    "content": "'use strict'\nimport { byteLength, toText, upperFirst } from './string'\n\nexport interface AnsiItem {\n  foreground?: string\n  background?: string\n  bold?: boolean\n  italic?: boolean\n  underline?: boolean\n  strikethrough?: boolean\n  text: string\n}\n\nconst foregroundColors = {\n  30: 'black',\n  31: 'red',\n  32: 'green',\n  33: 'yellow',\n  34: 'blue',\n  35: 'magenta',\n  36: 'cyan',\n  37: 'white',\n  90: 'grey'\n}\n\nconst backgroundColors = {\n  40: 'black',\n  41: 'red',\n  42: 'green',\n  43: 'yellow',\n  44: 'blue',\n  45: 'magenta',\n  46: 'cyan',\n  47: 'white'\n}\n\nconst styles = {\n  1: 'bold',\n  3: 'italic',\n  4: 'underline',\n  9: 'strikethrough'\n}\n\nexport interface AnsiHighlight {\n  span: [number, number]\n  hlGroup: string\n}\n\nexport interface AnsiResult {\n  line: string\n  highlights: AnsiHighlight[]\n}\n\nexport function parseAnsiHighlights(line: string, markdown = false): AnsiResult {\n  let items = ansiparse(line)\n  let highlights: AnsiHighlight[] = []\n  let newLabel = ''\n  for (let item of items) {\n    if (!item.text) continue\n    let { foreground, background } = item\n    let len = byteLength(newLabel)\n    let span: [number, number] = [len, len + byteLength(item.text)]\n    if (foreground && background) {\n      let hlGroup = `CocList${upperFirst(foreground)}${upperFirst(background)}`\n      highlights.push({ span, hlGroup })\n    } else if (foreground) {\n      let hlGroup: string\n      if (markdown) {\n        if (foreground == 'yellow') {\n          hlGroup = 'CocMarkdownCode'\n        } else if (foreground == 'blue') {\n          hlGroup = 'CocMarkdownLink'\n        } else if (foreground == 'magenta') {\n          hlGroup = 'CocMarkdownHeader'\n        } else {\n          hlGroup = `CocListFg${upperFirst(foreground)}`\n        }\n      } else {\n        hlGroup = `CocListFg${upperFirst(foreground)}`\n      }\n      highlights.push({ span, hlGroup })\n    } else if (background) {\n      let hlGroup = `CocListBg${upperFirst(background)}`\n      highlights.push({ span, hlGroup })\n    }\n    if (item.bold) {\n      highlights.push({ span, hlGroup: 'CocBold' })\n    } else if (item.italic) {\n      highlights.push({ span, hlGroup: 'CocItalic' })\n    } else if (item.underline) {\n      highlights.push({ span, hlGroup: 'CocUnderline' })\n    } else if (item.strikethrough) {\n      highlights.push({ span, hlGroup: 'CocStrikeThrough' })\n    }\n    newLabel = newLabel + item.text\n  }\n  return { line: newLabel, highlights }\n}\n\nexport function ansiparse(str: string): AnsiItem[] {\n  //\n  // I'm terrible at writing parsers.\n  //\n  let matchingControl = null\n  let matchingData = null\n  let matchingText = ''\n  let ansiState = []\n  let result = []\n  let state: Partial<AnsiItem> = {}\n  let eraseChar\n\n  //\n  // General workflow for this thing is:\n  // \\033\\[33mText\n  // |     |  |\n  // |     |  matchingText\n  // |     matchingData\n  // matchingControl\n  //\n  // \\033\\[K or \\033\\[m\n  //\n  // In further steps we hope it's all going to be fine. It usually is.\n  //\n\n  //\n  // Erases a char from the output\n  //\n  eraseChar = () => {\n    let index\n    let text\n    if (matchingText.length) {\n      matchingText = matchingText.substr(0, matchingText.length - 1)\n    }\n    else if (result.length) {\n      index = result.length - 1\n      text = result[index].text\n      if (text.length === 1) {\n        //\n        // A result bit was fully deleted, pop it out to simplify the final output\n        //\n        result.pop()\n      }\n      else {\n        result[index].text = text.substr(0, text.length - 1)\n      }\n    }\n  }\n\n  for (let i = 0; i < str.length; i++) {\n    if (matchingControl != null) {\n      if (matchingControl == '\\x1b' && str[i] == '[') {\n        //\n        // We've matched full control code. Lets start matching formatting data.\n        //\n\n        //\n        // \"emit\" matched text with correct state\n        //\n        if (matchingText) {\n          state.text = matchingText\n          result.push(state)\n          state = {}\n          matchingText = ''\n        }\n        if (matchingText == '' && (str[i + 1] == 'm' || str[i + 1] == 'K')) {\n          if (state.foreground || state.background) {\n            state.text = ''\n            result.push(state)\n          }\n          state = {}\n        }\n\n        matchingControl = null\n        matchingData = ''\n      } else {\n        //\n        // We failed to match anything - most likely a bad control code. We\n        // go back to matching regular strings.\n        //\n        matchingText += matchingControl + str[i]\n        matchingControl = null\n      }\n      continue\n    } else if (matchingData != null) {\n      if (str[i] == ';') {\n        //\n        // `;` separates many formatting codes, for example: `\\033[33;43m`\n        // means that both `33` and `43` should be applied.\n        //\n        // TODO: this can be simplified by modifying state here.\n        //\n        ansiState.push(matchingData)\n        matchingData = ''\n      } else if (str[i] == 'm' || str[i] == 'K') {\n        //\n        // `m` finished whole formatting code. We can proceed to matching\n        // formatted text.\n        //\n        ansiState.push(matchingData)\n        matchingData = null\n        matchingText = ''\n\n        //\n        // Convert matched formatting data into user-friendly state object.\n        //\n        ansiState.forEach(ansiCode => {\n          if (foregroundColors[ansiCode]) {\n            state.foreground = foregroundColors[ansiCode]\n          } else if (backgroundColors[ansiCode]) {\n            state.background = backgroundColors[ansiCode]\n          } else if (ansiCode == 39) {\n            delete state.foreground\n          } else if (ansiCode == 49) {\n            delete state.background\n          } else if (styles[ansiCode]) {\n            state[styles[ansiCode]] = true\n          } else if (ansiCode == 22) {\n            state.bold = false\n          } else if (ansiCode == 23) {\n            state.italic = false\n          } else if (ansiCode == 24) {\n            state.underline = false\n          } else if (ansiCode == 29) {\n            state.strikethrough = false\n          }\n        })\n        ansiState = []\n      }\n      else {\n        matchingData += str[i]\n      }\n      continue\n    }\n\n    if (str[i] == '\\x1b') {\n      matchingControl = str[i]\n    } else if (str[i] == '\\u0008') {\n      eraseChar()\n    } else {\n      matchingText += str[i]\n    }\n  }\n\n  if (matchingText) {\n    state.text = matchingText + toText(matchingControl)\n    result.push(state)\n  }\n  return result\n}\n\nexport function stripAnsiColoring(str?: string): string {\n  // eslint-disable-next-line\n  const ansiColorCodeRegex = /\\u001b\\[[0-9;]*m/g\n  return str.replace(ansiColorCodeRegex, '')\n}\n"
  },
  {
    "path": "src/util/array.ts",
    "content": "'use strict'\n\nexport function toArray<T>(item: T | T[] | null | undefined): T[] {\n  return Array.isArray(item) ? item : item == null ? [] : [item]\n}\n\n/**\n * @returns false if the provided object is an array and not empty.\n */\nexport function isFalsyOrEmpty(obj: any): boolean {\n  return !Array.isArray(obj) || obj.length === 0\n}\n\nfunction compareValue(n: number, r: [number, number]): number {\n  if (n < r[0]) return 1\n  if (n > r[1]) return -1\n  return 0\n}\n\n/**\n * Check if n in sorted table\n */\nexport function intable(n: number, table: ReadonlyArray<[number, number]>): boolean {\n  // do binary search\n  let low = 0\n  let high = table.length - 1\n  while (low <= high) {\n    const mid = ((low + high) / 2) | 0\n    const comp = compareValue(n, table[mid])\n    if (comp < 0) {\n      low = mid + 1\n    } else if (comp > 0) {\n      high = mid - 1\n    } else {\n      return true\n    }\n  }\n  return false\n}\n\n/**\n * Performs a binary search algorithm over a sorted array.\n * @param array The array being searched.\n * @param key The value we search for.\n * @param comparator A function that takes two array elements and returns zero\n * if they are equal, a negative number if the first element precedes the\n * second one in the sorting order, or a positive number if the second element\n * precedes the first one.\n * @return See {@link binarySearch2}\n */\nexport function binarySearch<T>(array: ReadonlyArray<T>, key: T, comparator: (op1: T, op2: T) => number): number {\n  return binarySearch2(array.length, i => comparator(array[i], key))\n}\n\n/**\n * Performs a binary search algorithm over a sorted collection. Useful for cases\n * when we need to perform a binary search over something that isn't actually an\n * array, and converting data to an array would defeat the use of binary search\n * in the first place.\n * @param length The collection length.\n * @param compareToKey A function that takes an index of an element in the\n * collection and returns zero if the value at this index is equal to the\n * search key, a negative number if the value precedes the search key in the\n * sorting order, or a positive number if the search key precedes the value.\n * @return A non-negative index of an element, if found. If not found, the\n * result is -(n+1) (or ~n, using bitwise notation), where n is the index\n * where the key should be inserted to maintain the sorting order.\n */\nexport function binarySearch2(length: number, compareToKey: (index: number) => number): number {\n  let low = 0\n  let high = length - 1\n\n  while (low <= high) {\n    const mid = ((low + high) / 2) | 0\n    const comp = compareToKey(mid)\n    if (comp < 0) {\n      low = mid + 1\n    } else if (comp > 0) {\n      high = mid - 1\n    } else {\n      return mid\n    }\n  }\n  return -(low + 1)\n}\n\nexport function intersect<T>(array: T[], other: T[]): boolean {\n  for (let item of other) {\n    if (array.includes(item)) {\n      return true\n    }\n  }\n  return false\n}\n\nexport function findIndex<T>(array: ArrayLike<T>, val: T, start = 0): number {\n  let idx = -1\n  for (let i = start; i < array.length; i++) {\n    if (array[i] === val) {\n      idx = i\n      break\n    }\n  }\n  return idx\n}\n\nexport function splitArray<T>(array: T[], fn: (item: T) => boolean): [T[], T[]] {\n  let res: [T[], T[]] = [[], []]\n  for (let item of array) {\n    if (fn(item)) {\n      res[0].push(item)\n    } else {\n      res[1].push(item)\n    }\n  }\n  return res\n}\n\nexport function tail<T>(array: T[], n = 0): T {\n  return array[array.length - (1 + n)]\n}\n\nexport function group<T>(array: T[], size: number): T[][] {\n  let len = array.length\n  let res: T[][] = []\n  for (let i = 0; i < Math.ceil(len / size); i++) {\n    res.push(array.slice(i * size, (i + 1) * size))\n  }\n  return res\n}\n\nexport function groupBy<T>(array: T[], fn: (v: T) => boolean): [T[], T[]] {\n  let res: [T[], T[]] = [[], []]\n  array.forEach(v => {\n    if (fn(v)) {\n      res[0].push(v)\n    } else {\n      res[1].push(v)\n    }\n  })\n  return res\n}\n\n/**\n * Removes duplicates from the given array. The optional keyFn allows to specify\n * how elements are checked for equalness by returning a unique string for each.\n */\nexport function distinct<T>(array: T[], keyFn?: (t: T) => string): T[] {\n  if (!keyFn) {\n    return array.filter((element, position) => array.indexOf(element) === position)\n  }\n\n  const seen: { [key: string]: boolean } = Object.create(null)\n  return array.filter(elem => {\n    const key = keyFn(elem)\n    if (seen[key]) {\n      return false\n    }\n\n    seen[key] = true\n\n    return true\n  })\n}\n\nexport function lastIndex<T>(array: T[], fn: (t: T) => boolean): number {\n  let i = array.length - 1\n  while (i >= 0) {\n    if (fn(array[i])) {\n      break\n    }\n    i--\n  }\n  return i\n}\n\nexport const flatMap = <T, U>(xs: T[], f: (item: T) => U[]): U[] =>\n  xs.reduce((x: U[], y: T) => [...x, ...f(y)], [])\n\n/**\n * Add text to sorted array\n */\nexport function addSortedArray(text: string, arr: string[]): string[] {\n  let idx: number\n  for (let i = 0; i < arr.length; i++) {\n    let s = arr[i]\n    if (text === s) return arr\n    if (s > text) {\n      idx = i\n      break\n    }\n  }\n  if (idx === undefined) {\n    arr.push(text)\n  } else {\n    arr.splice(idx, 0, text)\n  }\n  return arr\n}\n"
  },
  {
    "path": "src/util/async.ts",
    "content": "import { CancellationToken } from '../util/protocol'\n\nconst defaultYieldTimeout = 15\n\nfunction waitImmediate(): Promise<void> {\n  return new Promise(resolve => {\n    setImmediate(() => {\n      resolve(undefined)\n    })\n  })\n}\n\nclass Timer {\n\n  private readonly yieldAfter: number\n  private startTime: number\n  private counter: number\n  private total: number\n  private counterInterval: number\n\n  constructor(yieldAfter: number = defaultYieldTimeout) {\n    this.yieldAfter = Math.max(yieldAfter, defaultYieldTimeout)\n    this.startTime = Date.now()\n    this.counter = 0\n    this.total = 0\n    // start with a counter interval of 1.\n    this.counterInterval = 1\n  }\n  public start() {\n    this.startTime = Date.now()\n  }\n  public shouldYield(): boolean {\n    if (++this.counter >= this.counterInterval) {\n      const timeTaken = Date.now() - this.startTime\n      const timeLeft = Math.max(0, this.yieldAfter - timeTaken)\n      this.total += this.counter\n      this.counter = 0\n      if (timeTaken >= this.yieldAfter || timeLeft <= 1) {\n        // Yield also if time left <= 1 since we compute the counter\n        // for max < 2 ms.\n\n        // Start with interval 1 again. We could do some calculation\n        // with using 80% of the last counter however other things (GC)\n        // affect the timing heavily since we have small timings (1 - 15ms).\n        this.counterInterval = 1\n        this.total = 0\n        return true\n      } else {\n        // Only increase the counter until we have spent <= 2 ms. Increasing\n        // the counter further is very fragile since timing is influenced\n        // by other things and can increase the counter too much. This will result\n        // that we yield in average after [14 - 16]ms.\n        switch (timeTaken) {\n          case 0:\n          case 1:\n            this.counterInterval = this.total * 2\n            break\n        }\n      }\n    }\n    return false\n  }\n}\n\nexport async function runSequence(funcs: (() => Promise<void>)[], token: CancellationToken): Promise<void> {\n  for (const fn of funcs) {\n    if (token.isCancellationRequested) {\n      break\n    }\n    await fn()\n  }\n}\n\nexport interface YieldOptions {\n  /**\n   * The time in ms after which the function should yield.\n   * The minimum yield time is 15ms\n   */\n  yieldAfter?: number /* ms */\n\n  /**\n   * An optional callback that is invoke when the code yields.\n   */\n  yieldCallback?: () => void\n}\n\nexport async function map<P, C>(items: ReadonlyArray<P>, func: (item: P) => C, token?: CancellationToken, options?: YieldOptions): Promise<C[]> {\n  if (items.length === 0) {\n    return []\n  }\n  const result: C[] = new Array(items.length)\n  const timer = new Timer(options?.yieldAfter)\n  function convertBatch(start: number): number {\n    timer.start()\n    for (let i = start; i < items.length; i++) {\n      result[i] = func(items[i])\n      if (timer.shouldYield()) {\n        if (options?.yieldCallback) {\n          options.yieldCallback()\n        }\n        return i + 1\n      }\n    }\n    return -1\n  }\n  // Convert the first batch sync on the same frame.\n  let index = convertBatch(0)\n  while (index !== -1) {\n    await waitImmediate()\n    if (token !== undefined && token.isCancellationRequested) {\n      return result\n    }\n    index = convertBatch(index)\n  }\n  return result\n}\n\nexport async function forEach<P>(items: ReadonlyArray<P>, func: (item: P) => void, token?: CancellationToken, options?: YieldOptions): Promise<void> {\n  if (items.length === 0) {\n    return\n  }\n  const timer = new Timer(options?.yieldAfter)\n  function runBatch(start: number): number {\n    timer.start()\n    for (let i = start; i < items.length; i++) {\n      func(items[i])\n      if (timer.shouldYield()) {\n        if (options?.yieldCallback) {\n          options.yieldCallback()\n        }\n        return i + 1\n      }\n    }\n    return -1\n  }\n  // Convert the first batch sync on the same frame.\n  let index = runBatch(0)\n  while (index !== -1) {\n    await waitImmediate()\n    if (token !== undefined && token.isCancellationRequested) {\n      break\n    }\n    index = runBatch(index)\n  }\n}\n\nexport async function filter<P>(items: ReadonlyArray<P>, isValid: (item: P) => boolean | { [key: string]: any }, onFilter: (items: (P & { [key: string]: any })[], done: boolean) => void, token?: CancellationToken): Promise<void> {\n  if (items.length === 0) return\n  const timer = new Timer()\n  const len = items.length\n  function convertBatch(start: number): number {\n    const result: P[] = []\n    timer.start()\n    for (let i = start; i < len; i++) {\n      let item = items[i]\n      let res = isValid(item)\n      if (res === true) {\n        result.push(item)\n      } else if (res) {\n        result.push(Object.assign({}, item, res))\n      }\n      if (timer.shouldYield()) {\n        let done = i === len - 1\n        onFilter(result, done)\n        return done ? -1 : i + 1\n      }\n    }\n    onFilter(result, true)\n    return -1\n  }\n  // Convert the first batch sync on the same frame.\n  let index = convertBatch(0)\n  while (index !== -1) {\n    await waitImmediate()\n    if (token != null && token.isCancellationRequested) {\n      break\n    }\n    index = convertBatch(index)\n  }\n}\n"
  },
  {
    "path": "src/util/charCode.ts",
    "content": "'use strict'\n/* ---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\n// Names from https://blog.codinghorror.com/ascii-pronunciation-rules-for-programmers/\n\n/**\n * An inlined enum containing useful character codes (to be used with String.charCodeAt).\n * Please leave the const keyword such that it gets inlined when compiled to JavaScript!\n */\nexport const enum CharCode {\n  Null = 0,\n  /**\n   * The `\\b` character.\n   */\n  Backspace = 8,\n  /**\n   * The `\\t` character.\n   */\n  Tab = 9,\n  /**\n   * The `\\n` character.\n   */\n  LineFeed = 10,\n  /**\n   * The `\\r` character.\n   */\n  CarriageReturn = 13,\n  Space = 32,\n  /**\n   * The `!` character.\n   */\n  ExclamationMark = 33,\n  /**\n   * The `\"` character.\n   */\n  DoubleQuote = 34,\n  /**\n   * The `#` character.\n   */\n  Hash = 35,\n  /**\n   * The `$` character.\n   */\n  DollarSign = 36,\n  /**\n   * The `%` character.\n   */\n  PercentSign = 37,\n  /**\n   * The `&` character.\n   */\n  Ampersand = 38,\n  /**\n   * The `'` character.\n   */\n  SingleQuote = 39,\n  /**\n   * The `(` character.\n   */\n  OpenParen = 40,\n  /**\n   * The `)` character.\n   */\n  CloseParen = 41,\n  /**\n   * The `*` character.\n   */\n  Asterisk = 42,\n  /**\n   * The `+` character.\n   */\n  Plus = 43,\n  /**\n   * The `,` character.\n   */\n  Comma = 44,\n  /**\n   * The `-` character.\n   */\n  Dash = 45,\n  /**\n   * The `.` character.\n   */\n  Period = 46,\n  /**\n   * The `/` character.\n   */\n  Slash = 47,\n\n  Digit0 = 48,\n  Digit1 = 49,\n  Digit2 = 50,\n  Digit3 = 51,\n  Digit4 = 52,\n  Digit5 = 53,\n  Digit6 = 54,\n  Digit7 = 55,\n  Digit8 = 56,\n  Digit9 = 57,\n\n  /**\n   * The `:` character.\n   */\n  Colon = 58,\n  /**\n   * The `;` character.\n   */\n  Semicolon = 59,\n  /**\n   * The `<` character.\n   */\n  LessThan = 60,\n  /**\n   * The `=` character.\n   */\n  Equals = 61,\n  /**\n   * The `>` character.\n   */\n  GreaterThan = 62,\n  /**\n   * The `?` character.\n   */\n  QuestionMark = 63,\n  /**\n   * The `@` character.\n   */\n  AtSign = 64,\n\n  A = 65,\n  B = 66,\n  C = 67,\n  D = 68,\n  E = 69,\n  F = 70,\n  G = 71,\n  H = 72,\n  I = 73,\n  J = 74,\n  K = 75,\n  L = 76,\n  M = 77,\n  N = 78,\n  O = 79,\n  P = 80,\n  Q = 81,\n  R = 82,\n  S = 83,\n  T = 84,\n  U = 85,\n  V = 86,\n  W = 87,\n  X = 88,\n  Y = 89,\n  Z = 90,\n\n  /**\n   * The `[` character.\n   */\n  OpenSquareBracket = 91,\n  /**\n   * The `\\` character.\n   */\n  Backslash = 92,\n  /**\n   * The `]` character.\n   */\n  CloseSquareBracket = 93,\n  /**\n   * The `^` character.\n   */\n  Caret = 94,\n  /**\n   * The `_` character.\n   */\n  Underline = 95,\n  /**\n   * The ``(`)`` character.\n   */\n  BackTick = 96,\n\n  a = 97,\n  b = 98,\n  c = 99,\n  d = 100,\n  e = 101,\n  f = 102,\n  g = 103,\n  h = 104,\n  i = 105,\n  j = 106,\n  k = 107,\n  l = 108,\n  m = 109,\n  n = 110,\n  o = 111,\n  p = 112,\n  q = 113,\n  r = 114,\n  s = 115,\n  t = 116,\n  u = 117,\n  v = 118,\n  w = 119,\n  x = 120,\n  y = 121,\n  z = 122,\n\n  /**\n   * The `{` character.\n   */\n  OpenCurlyBrace = 123,\n  /**\n   * The `|` character.\n   */\n  Pipe = 124,\n  /**\n   * The `}` character.\n   */\n  CloseCurlyBrace = 125,\n  /**\n   * The `~` character.\n   */\n  Tilde = 126,\n\n  U_Combining_Grave_Accent = 0x0300,        // U+0300 Combining Grave Accent\n  U_Combining_Acute_Accent = 0x0301,        // U+0301 Combining Acute Accent\n  U_Combining_Circumflex_Accent = 0x0302,       // U+0302 Combining Circumflex Accent\n  U_Combining_Tilde = 0x0303,          // U+0303 Combining Tilde\n  U_Combining_Macron = 0x0304,         // U+0304 Combining Macron\n  U_Combining_Overline = 0x0305,         // U+0305 Combining Overline\n  U_Combining_Breve = 0x0306,          // U+0306 Combining Breve\n  U_Combining_Dot_Above = 0x0307,         // U+0307 Combining Dot Above\n  U_Combining_Diaeresis = 0x0308,         // U+0308 Combining Diaeresis\n  U_Combining_Hook_Above = 0x0309,        // U+0309 Combining Hook Above\n  U_Combining_Ring_Above = 0x030A,        // U+030A Combining Ring Above\n  U_Combining_Double_Acute_Accent = 0x030B,      // U+030B Combining Double Acute Accent\n  U_Combining_Caron = 0x030C,          // U+030C Combining Caron\n  U_Combining_Vertical_Line_Above = 0x030D,      // U+030D Combining Vertical Line Above\n  U_Combining_Double_Vertical_Line_Above = 0x030E,    // U+030E Combining Double Vertical Line Above\n  U_Combining_Double_Grave_Accent = 0x030F,      // U+030F Combining Double Grave Accent\n  U_Combining_Candrabindu = 0x0310,        // U+0310 Combining Candrabindu\n  U_Combining_Inverted_Breve = 0x0311,       // U+0311 Combining Inverted Breve\n  U_Combining_Turned_Comma_Above = 0x0312,      // U+0312 Combining Turned Comma Above\n  U_Combining_Comma_Above = 0x0313,        // U+0313 Combining Comma Above\n  U_Combining_Reversed_Comma_Above = 0x0314,      // U+0314 Combining Reversed Comma Above\n  U_Combining_Comma_Above_Right = 0x0315,       // U+0315 Combining Comma Above Right\n  U_Combining_Grave_Accent_Below = 0x0316,      // U+0316 Combining Grave Accent Below\n  U_Combining_Acute_Accent_Below = 0x0317,      // U+0317 Combining Acute Accent Below\n  U_Combining_Left_Tack_Below = 0x0318,       // U+0318 Combining Left Tack Below\n  U_Combining_Right_Tack_Below = 0x0319,       // U+0319 Combining Right Tack Below\n  U_Combining_Left_Angle_Above = 0x031A,       // U+031A Combining Left Angle Above\n  U_Combining_Horn = 0x031B,          // U+031B Combining Horn\n  U_Combining_Left_Half_Ring_Below = 0x031C,      // U+031C Combining Left Half Ring Below\n  U_Combining_Up_Tack_Below = 0x031D,        // U+031D Combining Up Tack Below\n  U_Combining_Down_Tack_Below = 0x031E,       // U+031E Combining Down Tack Below\n  U_Combining_Plus_Sign_Below = 0x031F,       // U+031F Combining Plus Sign Below\n  U_Combining_Minus_Sign_Below = 0x0320,       // U+0320 Combining Minus Sign Below\n  U_Combining_Palatalized_Hook_Below = 0x0321,     // U+0321 Combining Palatalized Hook Below\n  U_Combining_Retroflex_Hook_Below = 0x0322,      // U+0322 Combining Retroflex Hook Below\n  U_Combining_Dot_Below = 0x0323,         // U+0323 Combining Dot Below\n  U_Combining_Diaeresis_Below = 0x0324,       // U+0324 Combining Diaeresis Below\n  U_Combining_Ring_Below = 0x0325,        // U+0325 Combining Ring Below\n  U_Combining_Comma_Below = 0x0326,        // U+0326 Combining Comma Below\n  U_Combining_Cedilla = 0x0327,         // U+0327 Combining Cedilla\n  U_Combining_Ogonek = 0x0328,         // U+0328 Combining Ogonek\n  U_Combining_Vertical_Line_Below = 0x0329,      // U+0329 Combining Vertical Line Below\n  U_Combining_Bridge_Below = 0x032A,        // U+032A Combining Bridge Below\n  U_Combining_Inverted_Double_Arch_Below = 0x032B,    // U+032B Combining Inverted Double Arch Below\n  U_Combining_Caron_Below = 0x032C,        // U+032C Combining Caron Below\n  U_Combining_Circumflex_Accent_Below = 0x032D,     // U+032D Combining Circumflex Accent Below\n  U_Combining_Breve_Below = 0x032E,        // U+032E Combining Breve Below\n  U_Combining_Inverted_Breve_Below = 0x032F,      // U+032F Combining Inverted Breve Below\n  U_Combining_Tilde_Below = 0x0330,        // U+0330 Combining Tilde Below\n  U_Combining_Macron_Below = 0x0331,        // U+0331 Combining Macron Below\n  U_Combining_Low_Line = 0x0332,         // U+0332 Combining Low Line\n  U_Combining_Double_Low_Line = 0x0333,       // U+0333 Combining Double Low Line\n  U_Combining_Tilde_Overlay = 0x0334,        // U+0334 Combining Tilde Overlay\n  U_Combining_Short_Stroke_Overlay = 0x0335,      // U+0335 Combining Short Stroke Overlay\n  U_Combining_Long_Stroke_Overlay = 0x0336,      // U+0336 Combining Long Stroke Overlay\n  U_Combining_Short_Solidus_Overlay = 0x0337,      // U+0337 Combining Short Solidus Overlay\n  U_Combining_Long_Solidus_Overlay = 0x0338,      //  U+0338 Combining Long Solidus Overlay\n  U_Combining_Right_Half_Ring_Below = 0x0339,      //  U+0339 Combining Right Half Ring Below\n  U_Combining_Inverted_Bridge_Below = 0x033A,      //  U+033A Combining Inverted Bridge Below\n  U_Combining_Square_Below = 0x033B,        //  U+033B Combining Square Below\n  U_Combining_Seagull_Below = 0x033C,        //  U+033C Combining Seagull Below\n  U_Combining_X_Above = 0x033D,         //  U+033D Combining X Above\n  U_Combining_Vertical_Tilde = 0x033E,       //  U+033E Combining Vertical Tilde\n  U_Combining_Double_Overline = 0x033F,       //  U+033F Combining Double Overline\n  U_Combining_Grave_Tone_Mark = 0x0340,       //  U+0340 Combining Grave Tone Mark\n  U_Combining_Acute_Tone_Mark = 0x0341,       //  U+0341 Combining Acute Tone Mark\n  U_Combining_Greek_Perispomeni = 0x0342,       //  U+0342 Combining Greek Perispomeni\n  U_Combining_Greek_Koronis = 0x0343,        //  U+0343 Combining Greek Koronis\n  U_Combining_Greek_Dialytika_Tonos = 0x0344,      //  U+0344 Combining Greek Dialytika Tonos\n  U_Combining_Greek_Ypogegrammeni = 0x0345,      //  U+0345 Combining Greek Ypogegrammeni\n  U_Combining_Bridge_Above = 0x0346,        //  U+0346 Combining Bridge Above\n  U_Combining_Equals_Sign_Below = 0x0347,       //  U+0347 Combining Equals Sign Below\n  U_Combining_Double_Vertical_Line_Below = 0x0348,    //  U+0348 Combining Double Vertical Line Below\n  U_Combining_Left_Angle_Below = 0x0349,       //  U+0349 Combining Left Angle Below\n  U_Combining_Not_Tilde_Above = 0x034A,       //  U+034A Combining Not Tilde Above\n  U_Combining_Homothetic_Above = 0x034B,       //  U+034B Combining Homothetic Above\n  U_Combining_Almost_Equal_To_Above = 0x034C,      //  U+034C Combining Almost Equal To Above\n  U_Combining_Left_Right_Arrow_Below = 0x034D,     //  U+034D Combining Left Right Arrow Below\n  U_Combining_Upwards_Arrow_Below = 0x034E,      //  U+034E Combining Upwards Arrow Below\n  U_Combining_Grapheme_Joiner = 0x034F,       //  U+034F Combining Grapheme Joiner\n  U_Combining_Right_Arrowhead_Above = 0x0350,      //  U+0350 Combining Right Arrowhead Above\n  U_Combining_Left_Half_Ring_Above = 0x0351,      //  U+0351 Combining Left Half Ring Above\n  U_Combining_Fermata = 0x0352,         //  U+0352 Combining Fermata\n  U_Combining_X_Below = 0x0353,         //  U+0353 Combining X Below\n  U_Combining_Left_Arrowhead_Below = 0x0354,      //  U+0354 Combining Left Arrowhead Below\n  U_Combining_Right_Arrowhead_Below = 0x0355,      //  U+0355 Combining Right Arrowhead Below\n  U_Combining_Right_Arrowhead_And_Up_Arrowhead_Below = 0x0356, //  U+0356 Combining Right Arrowhead And Up Arrowhead Below\n  U_Combining_Right_Half_Ring_Above = 0x0357,      //  U+0357 Combining Right Half Ring Above\n  U_Combining_Dot_Above_Right = 0x0358,       //  U+0358 Combining Dot Above Right\n  U_Combining_Asterisk_Below = 0x0359,       //  U+0359 Combining Asterisk Below\n  U_Combining_Double_Ring_Below = 0x035A,       //  U+035A Combining Double Ring Below\n  U_Combining_Zigzag_Above = 0x035B,        //  U+035B Combining Zigzag Above\n  U_Combining_Double_Breve_Below = 0x035C,      //  U+035C Combining Double Breve Below\n  U_Combining_Double_Breve = 0x035D,        //  U+035D Combining Double Breve\n  U_Combining_Double_Macron = 0x035E,        //  U+035E Combining Double Macron\n  U_Combining_Double_Macron_Below = 0x035F,      //  U+035F Combining Double Macron Below\n  U_Combining_Double_Tilde = 0x0360,        //  U+0360 Combining Double Tilde\n  U_Combining_Double_Inverted_Breve = 0x0361,      //  U+0361 Combining Double Inverted Breve\n  U_Combining_Double_Rightwards_Arrow_Below = 0x0362,    //  U+0362 Combining Double Rightwards Arrow Below\n  U_Combining_Latin_Small_Letter_A = 0x0363,       //  U+0363 Combining Latin Small Letter A\n  U_Combining_Latin_Small_Letter_E = 0x0364,       //  U+0364 Combining Latin Small Letter E\n  U_Combining_Latin_Small_Letter_I = 0x0365,       //  U+0365 Combining Latin Small Letter I\n  U_Combining_Latin_Small_Letter_O = 0x0366,       //  U+0366 Combining Latin Small Letter O\n  U_Combining_Latin_Small_Letter_U = 0x0367,       //  U+0367 Combining Latin Small Letter U\n  U_Combining_Latin_Small_Letter_C = 0x0368,       //  U+0368 Combining Latin Small Letter C\n  U_Combining_Latin_Small_Letter_D = 0x0369,       //  U+0369 Combining Latin Small Letter D\n  U_Combining_Latin_Small_Letter_H = 0x036A,       //  U+036A Combining Latin Small Letter H\n  U_Combining_Latin_Small_Letter_M = 0x036B,       //  U+036B Combining Latin Small Letter M\n  U_Combining_Latin_Small_Letter_R = 0x036C,       //  U+036C Combining Latin Small Letter R\n  U_Combining_Latin_Small_Letter_T = 0x036D,       //  U+036D Combining Latin Small Letter T\n  U_Combining_Latin_Small_Letter_V = 0x036E,       //  U+036E Combining Latin Small Letter V\n  U_Combining_Latin_Small_Letter_X = 0x036F,       //  U+036F Combining Latin Small Letter X\n\n  /**\n   * Unicode Character 'LINE SEPARATOR' (U+2028)\n   * http://www.fileformat.info/info/unicode/char/2028/index.htm\n   */\n  LINE_SEPARATOR_2028 = 8232,\n\n  // http://www.fileformat.info/info/unicode/category/Sk/list.htm\n  // U_CIRCUMFLEX = 0x005E,         // U+005E CIRCUMFLEX\n  // U_GRAVE_ACCENT = 0x0060,        // U+0060 GRAVE ACCENT\n  U_DIAERESIS = 0x00A8,         // U+00A8 DIAERESIS\n  U_MACRON = 0x00AF,          // U+00AF MACRON\n  U_ACUTE_ACCENT = 0x00B4,        // U+00B4 ACUTE ACCENT\n  U_CEDILLA = 0x00B8,          // U+00B8 CEDILLA\n  U_MODIFIER_LETTER_LEFT_ARROWHEAD = 0x02C2,    // U+02C2 MODIFIER LETTER LEFT ARROWHEAD\n  U_MODIFIER_LETTER_RIGHT_ARROWHEAD = 0x02C3,    // U+02C3 MODIFIER LETTER RIGHT ARROWHEAD\n  U_MODIFIER_LETTER_UP_ARROWHEAD = 0x02C4,    // U+02C4 MODIFIER LETTER UP ARROWHEAD\n  U_MODIFIER_LETTER_DOWN_ARROWHEAD = 0x02C5,    // U+02C5 MODIFIER LETTER DOWN ARROWHEAD\n  U_MODIFIER_LETTER_CENTRED_RIGHT_HALF_RING = 0x02D2,  // U+02D2 MODIFIER LETTER CENTRED RIGHT HALF RING\n  U_MODIFIER_LETTER_CENTRED_LEFT_HALF_RING = 0x02D3,  // U+02D3 MODIFIER LETTER CENTRED LEFT HALF RING\n  U_MODIFIER_LETTER_UP_TACK = 0x02D4,      // U+02D4 MODIFIER LETTER UP TACK\n  U_MODIFIER_LETTER_DOWN_TACK = 0x02D5,     // U+02D5 MODIFIER LETTER DOWN TACK\n  U_MODIFIER_LETTER_PLUS_SIGN = 0x02D6,     // U+02D6 MODIFIER LETTER PLUS SIGN\n  U_MODIFIER_LETTER_MINUS_SIGN = 0x02D7,     // U+02D7 MODIFIER LETTER MINUS SIGN\n  U_BREVE = 0x02D8,          // U+02D8 BREVE\n  U_DOT_ABOVE = 0x02D9,         // U+02D9 DOT ABOVE\n  U_RING_ABOVE = 0x02DA,         // U+02DA RING ABOVE\n  U_OGONEK = 0x02DB,          // U+02DB OGONEK\n  U_SMALL_TILDE = 0x02DC,         // U+02DC SMALL TILDE\n  U_DOUBLE_ACUTE_ACCENT = 0x02DD,       // U+02DD DOUBLE ACUTE ACCENT\n  U_MODIFIER_LETTER_RHOTIC_HOOK = 0x02DE,     // U+02DE MODIFIER LETTER RHOTIC HOOK\n  U_MODIFIER_LETTER_CROSS_ACCENT = 0x02DF,    // U+02DF MODIFIER LETTER CROSS ACCENT\n  U_MODIFIER_LETTER_EXTRA_HIGH_TONE_BAR = 0x02E5,   // U+02E5 MODIFIER LETTER EXTRA-HIGH TONE BAR\n  U_MODIFIER_LETTER_HIGH_TONE_BAR = 0x02E6,    // U+02E6 MODIFIER LETTER HIGH TONE BAR\n  U_MODIFIER_LETTER_MID_TONE_BAR = 0x02E7,    // U+02E7 MODIFIER LETTER MID TONE BAR\n  U_MODIFIER_LETTER_LOW_TONE_BAR = 0x02E8,    // U+02E8 MODIFIER LETTER LOW TONE BAR\n  U_MODIFIER_LETTER_EXTRA_LOW_TONE_BAR = 0x02E9,   // U+02E9 MODIFIER LETTER EXTRA-LOW TONE BAR\n  U_MODIFIER_LETTER_YIN_DEPARTING_TONE_MARK = 0x02EA,  // U+02EA MODIFIER LETTER YIN DEPARTING TONE MARK\n  U_MODIFIER_LETTER_YANG_DEPARTING_TONE_MARK = 0x02EB, // U+02EB MODIFIER LETTER YANG DEPARTING TONE MARK\n  U_MODIFIER_LETTER_UNASPIRATED = 0x02ED,     // U+02ED MODIFIER LETTER UNASPIRATED\n  U_MODIFIER_LETTER_LOW_DOWN_ARROWHEAD = 0x02EF,   // U+02EF MODIFIER LETTER LOW DOWN ARROWHEAD\n  U_MODIFIER_LETTER_LOW_UP_ARROWHEAD = 0x02F0,   // U+02F0 MODIFIER LETTER LOW UP ARROWHEAD\n  U_MODIFIER_LETTER_LOW_LEFT_ARROWHEAD = 0x02F1,   // U+02F1 MODIFIER LETTER LOW LEFT ARROWHEAD\n  U_MODIFIER_LETTER_LOW_RIGHT_ARROWHEAD = 0x02F2,   // U+02F2 MODIFIER LETTER LOW RIGHT ARROWHEAD\n  U_MODIFIER_LETTER_LOW_RING = 0x02F3,     // U+02F3 MODIFIER LETTER LOW RING\n  U_MODIFIER_LETTER_MIDDLE_GRAVE_ACCENT = 0x02F4,   // U+02F4 MODIFIER LETTER MIDDLE GRAVE ACCENT\n  U_MODIFIER_LETTER_MIDDLE_DOUBLE_GRAVE_ACCENT = 0x02F5, // U+02F5 MODIFIER LETTER MIDDLE DOUBLE GRAVE ACCENT\n  U_MODIFIER_LETTER_MIDDLE_DOUBLE_ACUTE_ACCENT = 0x02F6, // U+02F6 MODIFIER LETTER MIDDLE DOUBLE ACUTE ACCENT\n  U_MODIFIER_LETTER_LOW_TILDE = 0x02F7,     // U+02F7 MODIFIER LETTER LOW TILDE\n  U_MODIFIER_LETTER_RAISED_COLON = 0x02F8,    // U+02F8 MODIFIER LETTER RAISED COLON\n  U_MODIFIER_LETTER_BEGIN_HIGH_TONE = 0x02F9,    // U+02F9 MODIFIER LETTER BEGIN HIGH TONE\n  U_MODIFIER_LETTER_END_HIGH_TONE = 0x02FA,    // U+02FA MODIFIER LETTER END HIGH TONE\n  U_MODIFIER_LETTER_BEGIN_LOW_TONE = 0x02FB,    // U+02FB MODIFIER LETTER BEGIN LOW TONE\n  U_MODIFIER_LETTER_END_LOW_TONE = 0x02FC,    // U+02FC MODIFIER LETTER END LOW TONE\n  U_MODIFIER_LETTER_SHELF = 0x02FD,      // U+02FD MODIFIER LETTER SHELF\n  U_MODIFIER_LETTER_OPEN_SHELF = 0x02FE,     // U+02FE MODIFIER LETTER OPEN SHELF\n  U_MODIFIER_LETTER_LOW_LEFT_ARROW = 0x02FF,    // U+02FF MODIFIER LETTER LOW LEFT ARROW\n  U_GREEK_LOWER_NUMERAL_SIGN = 0x0375,     // U+0375 GREEK LOWER NUMERAL SIGN\n  U_GREEK_TONOS = 0x0384,         // U+0384 GREEK TONOS\n  U_GREEK_DIALYTIKA_TONOS = 0x0385,      // U+0385 GREEK DIALYTIKA TONOS\n  U_GREEK_KORONIS = 0x1FBD,        // U+1FBD GREEK KORONIS\n  U_GREEK_PSILI = 0x1FBF,         // U+1FBF GREEK PSILI\n  U_GREEK_PERISPOMENI = 0x1FC0,       // U+1FC0 GREEK PERISPOMENI\n  U_GREEK_DIALYTIKA_AND_PERISPOMENI = 0x1FC1,    // U+1FC1 GREEK DIALYTIKA AND PERISPOMENI\n  U_GREEK_PSILI_AND_VARIA = 0x1FCD,      // U+1FCD GREEK PSILI AND VARIA\n  U_GREEK_PSILI_AND_OXIA = 0x1FCE,      // U+1FCE GREEK PSILI AND OXIA\n  U_GREEK_PSILI_AND_PERISPOMENI = 0x1FCF,     // U+1FCF GREEK PSILI AND PERISPOMENI\n  U_GREEK_DASIA_AND_VARIA = 0x1FDD,      // U+1FDD GREEK DASIA AND VARIA\n  U_GREEK_DASIA_AND_OXIA = 0x1FDE,      // U+1FDE GREEK DASIA AND OXIA\n  U_GREEK_DASIA_AND_PERISPOMENI = 0x1FDF,     // U+1FDF GREEK DASIA AND PERISPOMENI\n  U_GREEK_DIALYTIKA_AND_VARIA = 0x1FED,     // U+1FED GREEK DIALYTIKA AND VARIA\n  U_GREEK_DIALYTIKA_AND_OXIA = 0x1FEE,     // U+1FEE GREEK DIALYTIKA AND OXIA\n  U_GREEK_VARIA = 0x1FEF,         // U+1FEF GREEK VARIA\n  U_GREEK_OXIA = 0x1FFD,         // U+1FFD GREEK OXIA\n  U_GREEK_DASIA = 0x1FFE,         // U+1FFE GREEK DASIA\n\n  U_OVERLINE = 0x203E, // Unicode Character 'OVERLINE'\n\n  /**\n   * UTF-8 BOM\n   * Unicode Character 'ZERO WIDTH NO-BREAK SPACE' (U+FEFF)\n   * http://www.fileformat.info/info/unicode/char/feff/index.htm\n   */\n  UTF8_BOM = 65279\n}\n"
  },
  {
    "path": "src/util/color.ts",
    "content": "'use strict'\nimport { Color } from 'vscode-languageserver-types'\n\nfunction pad(str: string): string {\n  return str.length == 1 ? `0${str}` : str\n}\n\nexport function toHexString(color: Color): string {\n  let c = toHexColor(color)\n  return `${pad(c.red.toString(16))}${pad(c.green.toString(16))}${pad(c.blue.toString(16))}`\n}\n\nfunction toHexColor(color: Color): { red: number; green: number; blue: number } {\n  let { red, green, blue } = color\n  return {\n    red: Math.round(red * 255),\n    green: Math.round(green * 255),\n    blue: Math.round(blue * 255)\n  }\n}\n\nexport function isDark(color: Color): boolean {\n  // http://www.w3.org/TR/WCAG20/#relativeluminancedef\n  let rgb = [color.red, color.green, color.blue]\n  let lum = []\n  for (let i = 0; i < rgb.length; i++) {\n    let chan = rgb[i]\n    lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4)\n  }\n  let luma = 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2]\n  return luma <= 0.5\n}\n\n"
  },
  {
    "path": "src/util/constants.ts",
    "content": "import { version } from '../../package.json'\nimport { defaultValue, getConditionValue } from './index'\nimport { os, path } from './node'\n\nexport const ASCII_END = 128\nexport const VERSION = version\nexport const isVim = process.env.VIM_NODE_RPC == '1'\nexport const APIVERSION = 38\nexport const floatHighlightGroup = 'CocFloating'\nexport const CONFIG_FILE_NAME = 'coc-settings.json'\nexport const configHome = defaultValue<string>(process.env.COC_VIMCONFIG, path.join(os.homedir(), '.vim'))\nexport const dataHome = defaultValue<string>(process.env.COC_DATA_HOME, path.join(os.homedir(), '.config/coc'))\nexport const userConfigFile = path.join(path.normalize(configHome), CONFIG_FILE_NAME)\nexport const pluginRoot = getConditionValue(path.dirname(__dirname), path.resolve(__dirname, '../..'))\n"
  },
  {
    "path": "src/util/convert.ts",
    "content": "'use strict'\nimport { FormattingOptions, SymbolKind } from 'vscode-languageserver-types'\n\nexport interface VimFormatOption {\n  tabsize: number\n  expandtab: number\n  insertFinalNewline: boolean\n  trimTrailingWhitespace: boolean\n  trimFinalNewlines: boolean\n}\n\nexport function convertFormatOptions(opts: VimFormatOption): FormattingOptions {\n  let obj: FormattingOptions = { tabSize: opts.tabsize, insertSpaces: opts.expandtab == 1 }\n  if (opts.insertFinalNewline) obj.insertFinalNewline = true\n  if (opts.trimTrailingWhitespace) obj.trimTrailingWhitespace = true\n  if (opts.trimFinalNewlines) obj.trimFinalNewlines = true\n  return obj\n}\n\nexport function getSymbolKind(kind: SymbolKind): string {\n  switch (kind) {\n    case SymbolKind.File:\n      return 'File'\n    case SymbolKind.Module:\n      return 'Module'\n    case SymbolKind.Namespace:\n      return 'Namespace'\n    case SymbolKind.Package:\n      return 'Package'\n    case SymbolKind.Class:\n      return 'Class'\n    case SymbolKind.Method:\n      return 'Method'\n    case SymbolKind.Property:\n      return 'Property'\n    case SymbolKind.Field:\n      return 'Field'\n    case SymbolKind.Constructor:\n      return 'Constructor'\n    case SymbolKind.Enum:\n      return 'Enum'\n    case SymbolKind.Interface:\n      return 'Interface'\n    case SymbolKind.Function:\n      return 'Function'\n    case SymbolKind.Variable:\n      return 'Variable'\n    case SymbolKind.Constant:\n      return 'Constant'\n    case SymbolKind.String:\n      return 'String'\n    case SymbolKind.Number:\n      return 'Number'\n    case SymbolKind.Boolean:\n      return 'Boolean'\n    case SymbolKind.Array:\n      return 'Array'\n    case SymbolKind.Object:\n      return 'Object'\n    case SymbolKind.Key:\n      return 'Key'\n    case SymbolKind.Null:\n      return 'Null'\n    case SymbolKind.EnumMember:\n      return 'EnumMember'\n    case SymbolKind.Struct:\n      return 'Struct'\n    case SymbolKind.Event:\n      return 'Event'\n    case SymbolKind.Operator:\n      return 'Operator'\n    case SymbolKind.TypeParameter:\n      return 'TypeParameter'\n    default:\n      return 'Unknown'\n  }\n}\n"
  },
  {
    "path": "src/util/diff.ts",
    "content": "'use strict'\nimport { Position, Range, TextEdit } from 'vscode-languageserver-types'\nimport { fastDiff } from './node'\nimport { emptyRange, getEnd, positionInRange } from './position'\nimport { byteLength } from './string'\n\nexport interface ChangedLines {\n  start: number\n  end: number\n  replacement: string[]\n}\n\nexport function diffLines(oldLines: ReadonlyArray<string>, newLines: ReadonlyArray<string>, startLine: number): ChangedLines {\n  let endOffset = 0\n  let startOffset = 0\n  let parts = oldLines.slice(startLine + 1)\n  for (let i = 0; i < Math.min(parts.length, newLines.length); i++) {\n    if (parts[parts.length - 1 - i] == newLines[newLines.length - 1 - i]) {\n      endOffset = endOffset + 1\n    } else {\n      break\n    }\n  }\n  for (let i = 0; i <= Math.min(startLine, newLines.length - 1 - endOffset); i++) {\n    if (oldLines[i] == newLines[i]) {\n      startOffset = startOffset + 1\n    } else {\n      break\n    }\n  }\n  let replacement = newLines.slice(startOffset, newLines.length - endOffset)\n  let end = oldLines.length - endOffset\n  if (end > startOffset && replacement.length) {\n    let offset = 0\n    for (let i = 0; i < Math.min(replacement.length, end - startOffset); i++) {\n      if (replacement[i] == oldLines[startOffset + i]) {\n        offset = offset + 1\n      } else {\n        break\n      }\n    }\n    if (offset) {\n      return {\n        start: startOffset + offset,\n        end,\n        replacement: replacement.slice(offset)\n      }\n    }\n  }\n  return {\n    start: startOffset,\n    end,\n    replacement\n  }\n}\n\nexport function patchLine(from: string, to: string, fill = ' '): string {\n  if (from == to) return to\n  let idx = to.indexOf(from)\n  if (idx !== -1) return fill.repeat(byteLength(to.substring(0, idx))) + from\n  let result = fastDiff(from, to)\n  let str = ''\n  for (let item of result) {\n    if (item[0] == fastDiff.DELETE) {\n      // not allowed\n      return to\n    } else if (item[0] == fastDiff.INSERT) {\n      str = str + fill.repeat(byteLength(item[1]))\n    } else {\n      str = str + item[1]\n    }\n  }\n  return str\n}\n\nexport function getTextEdit(oldLines: ReadonlyArray<string>, newLines: ReadonlyArray<string>, cursor?: Position, insertMode?: boolean): TextEdit | undefined {\n  let ol = oldLines.length\n  let nl = newLines.length\n  let n: number\n  if (cursor) {\n    // consider new line insert\n    n = nl > ol && insertMode && cursor.line > 0 ? cursor.line - 1 : cursor.line\n  } else {\n    n = Math.min(ol, nl)\n  }\n  let used = 0\n  for (let i = 0; i < n; i++) {\n    if (newLines[i] === oldLines[i]) {\n      used += 1\n    } else {\n      break\n    }\n  }\n  if (ol == nl && used == ol) return undefined\n  let delta = nl - ol\n  let r = Math.min(ol - used, nl - used)\n  let e = 0\n  for (let i = 0; i < r; i++) {\n    if (newLines[nl - i - 1] === oldLines[ol - i - 1]) {\n      e += 1\n    } else {\n      break\n    }\n  }\n  let inserted = e == 0 ? newLines.slice(used) : newLines.slice(used, -e)\n  if (delta == 0 && cursor && inserted.length == 1) {\n    let newLine = newLines[used]\n    let oldLine = oldLines[used]\n    let nl = newLine.length\n    let ol = oldLine.length\n    if (nl === 0) return TextEdit.del(Range.create(used, 0, used, ol))\n    if (ol === 0) return TextEdit.insert(Position.create(used, 0), newLine)\n    let character = Math.min(cursor.character, nl)\n    if (!insertMode && nl >= ol && character !== nl) {\n      // insert text\n      character += 1\n    }\n    let r = 0\n    for (let i = 0; i < nl - character; i++) {\n      let idx = ol - 1 - i\n      if (idx === -1) break\n      if (newLine[nl - 1 - i] === oldLine[idx]) {\n        r += 1\n      } else {\n        break\n      }\n    }\n    let l = 0\n    for (let i = 0; i < Math.min(ol - r, nl - r); i++) {\n      if (newLine[i] === oldLine[i]) {\n        l += 1\n      } else {\n        break\n      }\n    }\n    let newText = r === 0 ? newLine.slice(l) : newLine.slice(l, -r)\n    return TextEdit.replace(Range.create(used, l, used, ol - r), newText)\n  }\n  let text = inserted.length > 0 ? inserted.join('\\n') + '\\n' : ''\n  if (text.length === 0 && used === ol - e) return undefined\n  let original = oldLines.slice(used, ol - e).join('\\n') + '\\n'\n  let edit = TextEdit.replace(Range.create(used, 0, ol - e, 0), text)\n  return reduceReplaceEdit(edit, original, cursor)\n}\n\nexport function getCommonSuffixLen(a: string, b: string, max: number): number {\n  if (max === 0) return 0\n  let al = a.length\n  let bl = b.length\n  let n = 0\n  for (let i = 0; i < max; i++) {\n    if (a[al - 1 - i] === b[bl - 1 - i]) {\n      n++\n    } else {\n      break\n    }\n  }\n  return n\n}\n\nexport function getCommonPrefixLen(a: string, b: string, max: number): number {\n  if (max === 0) return 0\n  let n = 0\n  for (let i = 0; i < max; i++) {\n    if (a[i] === b[i]) {\n      n++\n    } else {\n      break\n    }\n  }\n  return n\n}\n\nexport function reduceReplaceEdit(edit: TextEdit, original: string, cursor?: Position): TextEdit {\n  let { newText, range } = edit\n  if (emptyRange(range) || newText === '') return edit\n  // let isAdd = newText.length > original.length\n  let endOffset: number | undefined\n  if (cursor) {\n    let newEnd = getEnd(range.start, newText)\n    if (positionInRange(cursor, Range.create(range.start, newEnd)) === 0) {\n      endOffset = 0\n      let lc = newEnd.line - cursor.line + 1\n      let lines = newText.split('\\n')\n      let len = lines.length\n      for (let i = 0; i < lc; i++) {\n        let idx = len - i - 1\n        if (i == lc - 1) {\n          let s = idx === 0 ? range.start.character : 0\n          endOffset += lines[idx].slice(cursor.character - s).length\n        } else {\n          endOffset += lines[idx].length + 1\n        }\n      }\n    }\n  }\n  let sl: number\n  let pl: number\n  let min = Math.min(original.length, newText.length)\n  if (endOffset) {\n    sl = getCommonSuffixLen(original, newText, endOffset)\n    pl = getCommonPrefixLen(original, newText, min - sl)\n  } else {\n    pl = getCommonPrefixLen(original, newText, min)\n    sl = getCommonSuffixLen(original, newText, min - pl)\n  }\n  let s = pl === 0 ? range.start : getEnd(range.start, original.slice(0, pl))\n  let e = sl === 0 ? range.end : getEnd(range.start, original.slice(0, -sl))\n  let text = newText.slice(pl, sl === 0 ? undefined : -sl)\n  return TextEdit.replace(Range.create(s, e), text)\n}\n"
  },
  {
    "path": "src/util/errors.ts",
    "content": "'use strict'\n\nconst canceledName = 'Canceled'\n\n// !!!IMPORTANT!!!\n// Do NOT change this class because it is also used as an API-type.\nexport class CancellationError extends Error {\n  constructor() {\n    super(canceledName)\n    this.name = this.message\n  }\n}\n\nexport function assert(condition: boolean): void {\n  if (!condition) {\n    throw new BugIndicatingError('Assertion Failed')\n  }\n}\n\n/**\n * This error indicates a bug.\n * Do not throw this for invalid user input.\n * Only catch this error to recover gracefully from bugs.\n */\nclass BugIndicatingError extends Error {\n  constructor(message: string) {\n    super(message)\n    Object.setPrototypeOf(this, BugIndicatingError.prototype)\n\n    // Because we know for sure only buggy code throws this,\n    // we definitely want to break here and fix the bug.\n    // eslint-disable-next-line no-debugger\n    debugger\n  }\n}\n\n/**\n * Checks if the given error is a promise in canceled state\n */\nexport function isCancellationError(error: any): boolean {\n  if (error instanceof CancellationError) {\n    return true\n  }\n  return error instanceof Error && error.name === canceledName && error.message === canceledName\n}\n\nexport function shouldIgnore(err: any) {\n  if (isCancellationError(err)) return true\n  if (err instanceof Error && err.message.includes('transport disconnected')) return true\n  return false\n}\n\nexport function onUnexpectedError(e: any): void {\n  // ignore errors from cancelled promises\n  if (shouldIgnore(e)) return\n  if (e.stack) {\n    throw new Error(e.message + '\\n\\n' + e.stack)\n  } else {\n    throw e\n  }\n}\n\nexport function notLoaded(uri: string): Error {\n  return new Error(`File ${uri} not loaded`)\n}\n\nexport function illegalArgument(name?: string): Error {\n  if (name) {\n    return new Error(`Illegal argument: ${name}`)\n  } else {\n    return new Error('Illegal argument')\n  }\n}\n\nexport function directoryNotExists(dir: string): Error {\n  return new Error(`Directory ${dir} not exists`)\n}\n\nexport function fileExists(filepath: string) {\n  return new Error(`File ${filepath} already exists`)\n}\n\nexport function fileNotExists(filepath: string) {\n  return new Error(`File ${filepath} not exists`)\n}\n\nexport function shouldNotAsync(method: string) {\n  return new Error(`${method} should not be called in an asynchronize manner`)\n}\n\nexport function badScheme(uri: string) {\n  return new Error(`Change of ${uri} not supported`)\n}\n"
  },
  {
    "path": "src/util/extensionRegistry.ts",
    "content": "'use strict'\nimport { isFalsyOrEmpty, toArray } from './array'\nimport { pluginRoot } from './constants'\nimport { isParentFolder, sameFile } from './fs'\nimport * as Is from './is'\nimport type { IJSONSchema } from './jsonSchema'\nimport { fs } from './node'\nimport { toObject } from './object'\nimport { Registry } from './registry'\nimport { toText } from './string'\n\nexport type IStringDictionary<V> = Record<string, V>\n\n/**\n * Contains static extension infos\n */\nexport const Extensions = {\n  ExtensionContribution: 'base.contributions.extensions'\n}\n\nexport interface CommandContribution {\n  readonly title: string\n  readonly command: string\n}\n\nexport interface RootPatternContrib {\n  readonly filetype: string\n  readonly patterns?: string[]\n}\n\nexport interface IExtensionInfo {\n  readonly name: string,\n  readonly directory: string,\n  readonly filepath?: string\n  readonly onCommands?: string[]\n  readonly commands?: CommandContribution[]\n  readonly rootPatterns?: RootPatternContrib[]\n  readonly definitions?: IStringDictionary<IJSONSchema>\n}\n\nexport interface IExtensionContributions {\n  extensions: Iterable<IExtensionInfo>\n}\n\nexport interface IExtensionRegistry {\n\n  /**\n   * Commands for activate extensions.\n   */\n  readonly onCommands: ({ id: string, title: string })[]\n\n  /**\n   * Commands contributed from extensions.\n   */\n  readonly commands: CommandContribution[]\n\n  getCommandTitle(id: string): string | undefined\n  /**\n   * Root patterns by filetype.\n   */\n  getRootPatternsByFiletype(filetype: string): string[]\n\n  /**\n   * Register a extension to the registry.\n   */\n  registerExtension(id: string, info: IExtensionInfo): void\n\n  /**\n   * Remove a extension from registry\n   */\n  unregistExtension(id: string): void\n\n  /**\n   * Get extension info.\n   */\n  getExtension(id: string): IExtensionInfo\n  /**\n   * Get all extensions\n   */\n  getExtensions(): IExtensionContributions\n\n  resolveExtension(filepath: string): IExtensionInfo | undefined\n}\n\n/**\n * Registry for loaded extensions.\n */\nclass ExtensionRegistry implements IExtensionRegistry {\n  private extensionsById: Map<string, IExtensionInfo>\n\n  constructor() {\n    this.extensionsById = new Map()\n  }\n  public resolveExtension(filepath: string): IExtensionInfo {\n    for (let item of this.extensionsById.values()) {\n      if (item.filepath && sameFile(item.filepath, filepath)) {\n        return item\n      }\n      if (!item.name.startsWith('single-')\n        && fs.existsSync(item.directory)\n        && isParentFolder(fs.realpathSync(item.directory), filepath, false)) {\n        return item\n      }\n    }\n    return undefined\n  }\n\n  public get onCommands(): ({ id: string, title: string })[] {\n    let res: ({ id: string, title: string })[] = []\n    for (let item of this.extensionsById.values()) {\n      let { commands, onCommands } = item\n      for (let cmd of onCommands) {\n        if (typeof cmd === 'string') {\n          let find = commands.find(o => o.command === cmd)\n          let title = find == null ? '' : find.title\n          res.push({ id: cmd, title })\n        }\n      }\n    }\n    return res\n  }\n\n  public getCommandTitle(id: string): string | undefined {\n    for (let item of this.extensionsById.values()) {\n      for (let cmd of toArray(item.commands)) {\n        if (cmd.command === id) return cmd.title\n      }\n    }\n    return undefined\n  }\n\n  public get commands(): CommandContribution[] {\n    let res: CommandContribution[] = []\n    for (let item of this.extensionsById.values()) {\n      res.push(...(toArray(item.commands).filter(validCommandContribution)))\n    }\n    return res\n  }\n\n  public getRootPatternsByFiletype(filetype: string): string[] {\n    let res: string[] = []\n    for (let item of this.extensionsById.values()) {\n      for (let p of toArray(item.rootPatterns).filter(validRootPattern)) {\n        if (p.filetype === filetype) res.push(...p.patterns.filter(s => typeof s === 'string'))\n      }\n    }\n    return res\n  }\n\n  public unregistExtension(id: string): void {\n    this.extensionsById.delete(id)\n  }\n\n  public registerExtension(id: string, info: IExtensionInfo): void {\n    this.extensionsById.set(id, info)\n  }\n\n  public getExtension(id: string): IExtensionInfo {\n    return this.extensionsById.get(id)\n  }\n\n  public getExtensions(): IExtensionContributions {\n    return { extensions: this.extensionsById.values() }\n  }\n}\n\nlet extensionRegistry = new ExtensionRegistry()\nRegistry.add(Extensions.ExtensionContribution, extensionRegistry)\n\nexport function getExtensionDefinitions(): IStringDictionary<IJSONSchema> {\n  let obj = {}\n  for (let extensionInfo of extensionRegistry.getExtensions().extensions) {\n    let definitions = extensionInfo.definitions\n    Object.entries(toObject(definitions)).forEach(([key, val]) => {\n      obj[key] = val\n    })\n  }\n  return obj\n}\n\nexport function validRootPattern(rootPattern: RootPatternContrib | undefined): boolean {\n  return rootPattern && typeof rootPattern.filetype === 'string' && !isFalsyOrEmpty(rootPattern.patterns)\n}\n\nexport function validCommandContribution(cmd: CommandContribution | undefined): boolean {\n  return cmd && typeof cmd.command === 'string' && typeof cmd.title === 'string'\n}\n\nexport function getProperties(configuration: object): IStringDictionary<IJSONSchema> {\n  let obj = {}\n  if (Array.isArray(configuration)) {\n    for (let item of configuration) {\n      Object.assign(obj, toObject(item['properties']))\n    }\n  } else if (Is.objectLiteral(configuration['properties'])) {\n    obj = configuration['properties']\n  }\n  return obj\n}\n\n/**\n * Get extension name from error stack\n */\nexport function parseExtensionName(stack: string, level = 2): string | undefined {\n  let lines = toText(stack).split(/\\r?\\n/).slice(level)\n  if (lines.length === 0) return undefined\n  for (let line of lines) {\n    let filepath: string | undefined\n    line = line.replace(/^\\s*at\\s*/, '')\n    if (line.endsWith(')')) {\n      let ms = line.match(/(\\((.*?):\\d+:\\d+\\))$/)\n      if (ms) filepath = ms[2]\n    } else {\n      let ms = line.match(/(.*?):\\d+:\\d+$/)\n      if (ms) filepath = ms[1]\n    }\n    if (!filepath || isParentFolder(pluginRoot, filepath)) continue\n    let find = extensionRegistry.resolveExtension(filepath)\n    if (find) return find.name\n  }\n  return 'coc.nvim'\n}\n"
  },
  {
    "path": "src/util/factory.ts",
    "content": "'use strict'\nimport { createLogger } from '../logger'\nimport { fs, path, vm } from '../util/node'\nimport { hasOwnProperty, toObject } from './object'\n\nexport interface ExtensionExport {\n  activate: (context: unknown) => any\n  deactivate?: () => any\n  [key: string]: any\n}\n\nexport interface ILogger {\n  category?: string\n  log(...args: any[]): void\n  trace(...args: any[]): void\n  debug(...args: any[]): void\n  info(...args: any[]): void\n  warn(...args: any[]): void\n  error(...args: any[]): void\n  fatal(...args: any[]): void\n  mark(...args: any[]): void\n}\n\nexport interface IModule {\n  new(name: string, parent?: boolean): any\n  _resolveFilename: (file: string, context: any, isMain: boolean, options: any) => string\n  _extensions: object\n  _cache: { [file: string]: any }\n  _compile: (content: string, filename: string) => any\n  wrap: (content: string) => string\n  require: (file: string) => NodeModule\n  _nodeModulePaths: (filename: string) => string[]\n  createRequire: (filename: string) => (file: string) => any\n}\n\nexport const consoleLogger: ILogger = {\n  category: '',\n  log: console.log.bind(console),\n  debug: console.debug.bind(console),\n  error: console.error.bind(console),\n  warn: console.warn.bind(console),\n  info: console.info.bind(console),\n  trace: console.log.bind(console),\n  fatal: console.error.bind(console),\n  mark: console.log.bind(console),\n}\n\nconst Module: IModule = require('module')\nconst mainModule = require.main\nconst REMOVED_GLOBALS = [\n  'reallyExit',\n  'abort',\n  'umask',\n  'setuid',\n  'setgid',\n  'setgroups',\n  '_fatalException',\n  'exit',\n  'kill',\n]\n\nfunction removedGlobalStub(name: string) {\n  return () => {\n    throw new Error(`process.${name}() is not allowed in extension sandbox`)\n  }\n}\n\n// @see node/lib/internal/module.js\nfunction makeRequireFunction(this: any, cocExports: any): any {\n  const req: any = (p: string) => {\n    if (p === 'coc.nvim') {\n      return toObject(cocExports)\n    }\n    return this.require(p)\n  }\n  req.resolve = (request, options) => Module._resolveFilename(request, this, false, options)\n  // request => Module._resolveFilename(request, this)\n  req.main = mainModule\n  // Enable support to add extra extension types\n  req.extensions = Module._extensions\n  req.cache = Module._cache\n  return req\n}\n\n// @see node/lib/module.js\nexport function compileInSandbox(sandbox: ISandbox, cocExports?: any): (content: string, filename: string) => any {\n  return function(this: any, content: string, filename: string): any {\n    const require = makeRequireFunction.call(this, cocExports)\n    const dirname = path.dirname(filename)\n    const newContent = content.startsWith('#!') ? content.replace(/^#!.*/, '') : content\n    const wrapper = Module.wrap(newContent)\n    const compiledWrapper = vm.runInContext(wrapper, sandbox, { filename })\n    const args = [this.exports, require, this, filename, dirname]\n    return compiledWrapper.apply(this.exports, args)\n  }\n}\n\nexport interface ISandbox {\n  process: NodeJS.Process\n  module: NodeModule\n  require: (p: string) => any\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n  console: { [key in keyof Console]?: Function }\n  Buffer: any\n  Reflect: any\n  // eslint-disable-next-line id-blacklist\n  String: any\n  Promise: any\n}\n\n// find correct Module since jest use a fake Module object that extends Module\n// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\nexport function getProtoWithCompile(mod: Function): IModule {\n  if (hasOwnProperty(mod.prototype, '_compile')) return mod.prototype\n  if (hasOwnProperty(mod.prototype.__proto__, '_compile')) return mod.prototype.__proto__\n  throw new Error('_compile not found')\n}\n\nconst ModuleProto = getProtoWithCompile(Module)\n\nexport function copyGlobalProperties(sandbox: ISandbox, globalObj: any): ISandbox {\n  // Use Reflect.ownKeys affect instanceof of extensions, instanceof Error and instanceof TypeError won't work\n  for (const key of Object.keys(globalObj)) {\n    const value = sandbox[key]\n    if (value === undefined) {\n      sandbox[key] = globalObj[key]\n    }\n  }\n  return sandbox\n}\n\nexport function createConsole(con: object, logger: ILogger): object {\n  let result: any = {}\n  let methods = ['debug', 'log', 'info', 'error', 'warn']\n  for (let key of Object.keys(con)) {\n    if (methods.includes(key)) {\n      result[key] = (...args: any[]) => {\n        logger[key].apply(logger, args)\n      }\n    } else {\n      let fn = con[key]\n      if (key !== 'Console' && typeof fn === 'function') {\n        result[key] = () => {\n          logger.warn(`function console.${key} not supported`)\n        }\n      } else {\n        result[key] = fn\n      }\n    }\n  }\n  return result\n}\n\nexport function createSandbox(filename: string, logger: ILogger, name?: string, noExport = global.__TEST__): ISandbox {\n  const module = new Module(filename)\n  module.paths = Module._nodeModulePaths(filename)\n\n  const sandbox = vm.createContext({\n    module,\n    Buffer,\n    URL: globalThis.URL,\n    WebAssembly: globalThis.WebAssembly,\n    console: createConsole(console, logger)\n  }, { name }) as ISandbox\n\n  copyGlobalProperties(sandbox, global)\n  // sandbox.Reflect = Reflect\n  let cocExports = noExport ? undefined : require('../index')\n  sandbox.require = function sandboxRequire(p): any {\n    const oldCompile = ModuleProto._compile\n    ModuleProto._compile = compileInSandbox(sandbox, cocExports)\n    const moduleExports = sandbox.module.require(p)\n    ModuleProto._compile = oldCompile\n    return moduleExports\n  }\n\n  // patch `require` in sandbox to run loaded module in sandbox context\n  // if you need any of these, it might be worth discussing spawning separate processes\n  sandbox.process = new (process as any).constructor()\n  for (let key of Reflect.ownKeys(process)) {\n    if (typeof key === 'string' && key.startsWith('_')) {\n      continue\n    }\n    sandbox.process[key] = process[key]\n  }\n\n  REMOVED_GLOBALS.forEach(name => {\n    sandbox.process[name] = removedGlobalStub(name)\n  })\n  sandbox.process['chdir'] = () => {}\n\n  // read-only umask\n  sandbox.process.umask = (mask?: number) => {\n    if (typeof mask !== 'undefined') {\n      throw new Error('Cannot use process.umask() to change mask (read-only)')\n    }\n    return process.umask()\n  }\n\n  return sandbox\n}\n\nfunction getLogger(useConsole: boolean, id: string): ILogger {\n  return useConsole ? consoleLogger : createLogger(`extension:${id}`)\n}\n\n// inspiration drawn from Module\nexport function createExtension(id: string, filename: string, isEmpty: boolean): ExtensionExport {\n  if (isEmpty || !fs.existsSync(filename)) return {\n    activate: () => {},\n    deactivate: null\n  }\n  const logger = getLogger(!global.__isMain && !global.__TEST__, id)\n  const sandbox = createSandbox(filename, logger, id)\n\n  delete Module._cache[require.resolve(filename)]\n\n  // attempt to import plugin\n  // Require plugin to export activate & deactivate\n  const defaultImport = sandbox.require(filename)\n  const activate = (defaultImport && defaultImport.activate) || defaultImport\n  if (typeof activate !== 'function') return { activate: () => {} }\n  return typeof defaultImport === 'function' ? { activate } : Object.assign({}, defaultImport)\n}\n"
  },
  {
    "path": "src/util/filter.ts",
    "content": "import { CharCode } from './charCode'\nimport { isEmojiImprecise } from './string'\n\n/**\n * An array representing a fuzzy match.\n *\n * 0. the score\n * 1. the offset at which matching started\n * 2. `<match_pos_N>`\n * 3. `<match_pos_1>`\n * 4. `<match_pos_0>` etc\n */\nexport type FuzzyScore = [score: number, wordStart: number, ...matches: number[]]\nconst _maxLen = 128\nconst enum Arrow { Diag = 1, Left = 2, LeftLeft = 3 }\n\nexport interface FuzzyScoreOptions {\n  readonly boostFullMatch: boolean\n  readonly firstMatchCanBeWeak: boolean\n}\n\nexport interface IMatch {\n  start: number\n  end: number\n}\n\nexport interface FuzzyScorer {\n  (pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, options?: FuzzyScoreOptions): FuzzyScore | undefined\n}\n\nfunction initTable() {\n  const table: number[][] = []\n  const row: number[] = []\n  for (let i = 0; i <= _maxLen; i++) {\n    row[i] = 0\n  }\n  for (let i = 0; i <= _maxLen; i++) {\n    table.push(row.slice(0))\n  }\n  return table\n}\n\nfunction initArr(maxLen: number) {\n  const row: number[] = []\n  for (let i = 0; i <= maxLen; i++) {\n    row[i] = 0\n  }\n  return row\n}\n\nfunction isUpperCaseAtPos(pos: number, word: string, wordLow: string): boolean {\n  return word[pos] !== wordLow[pos]\n}\n\nconst _minWordMatchPos = initArr(2 * _maxLen) // min word position for a certain pattern position\nconst _maxWordMatchPos = initArr(2 * _maxLen) // max word position for a certain pattern position\nconst _diag = initTable() // the length of a contiguous diagonal match\nconst _table = initTable()\nconst _arrows = initTable() as Arrow[][]\n\nexport function fuzzyScoreGracefulAggressive(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, options?: FuzzyScoreOptions): FuzzyScore | undefined {\n  return fuzzyScoreWithPermutations(pattern, lowPattern, patternPos, word, lowWord, wordPos, true, options)\n}\n\nexport function fuzzyScoreGraceful(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, options?: FuzzyScoreOptions): FuzzyScore | undefined {\n  return fuzzyScoreWithPermutations(pattern, lowPattern, patternPos, word, lowWord, wordPos, false, options)\n}\n\nfunction fuzzyScoreWithPermutations(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, aggressive: boolean, options?: FuzzyScoreOptions): FuzzyScore | undefined {\n  let top = fuzzyScore(pattern, lowPattern, patternPos, word, lowWord, wordPos, options)\n\n  if (top && !aggressive) {\n    // when using the original pattern yield a result we`\n    // return it unless we are aggressive and try to find\n    // a better alignment, e.g. `cno` -> `^co^ns^ole` or `^c^o^nsole`.\n    return top\n  }\n\n  if (pattern.length >= 3) {\n    // When the pattern is long enough then try a few (max 7)\n    // permutations of the pattern to find a better match. The\n    // permutations only swap neighbouring characters, e.g\n    // `cnoso` becomes `conso`, `cnsoo`, `cnoos`.\n    const tries = Math.min(7, pattern.length - 1)\n    for (let movingPatternPos = patternPos + 1; movingPatternPos < tries; movingPatternPos++) {\n      const newPattern = nextTypoPermutation(pattern, movingPatternPos)\n      if (newPattern) {\n        const candidate = fuzzyScore(newPattern, newPattern.toLowerCase(), patternPos, word, lowWord, wordPos, options)\n        if (candidate) {\n          candidate[0] -= 3 // permutation penalty\n          if (!top || candidate[0] > top[0]) {\n            top = candidate\n          }\n        }\n      }\n    }\n  }\n\n  return top\n}\n\nexport function fuzzyScore(pattern: string, patternLow: string, patternStart: number, word: string, wordLow: string, wordStart: number, options: FuzzyScoreOptions = { boostFullMatch: true, firstMatchCanBeWeak: false }): FuzzyScore | undefined {\n\n  const patternLen = pattern.length > _maxLen ? _maxLen : pattern.length\n  const wordLen = word.length > _maxLen ? _maxLen : word.length\n\n  if (patternStart >= patternLen || wordStart >= wordLen || (patternLen - patternStart) > (wordLen - wordStart)) {\n    return undefined\n  }\n\n  // Run a simple check if the characters of pattern occur\n  // (in order) at all in word. If that isn't the case we\n  // stop because no match will be possible\n  if (!isPatternInWord(patternLow, patternStart, patternLen, wordLow, wordStart, wordLen, true)) {\n    return undefined\n  }\n\n  // Find the max matching word position for each pattern position\n  // NOTE: the min matching word position was filled in above, in the `isPatternInWord` call\n  _fillInMaxWordMatchPos(patternLen, wordLen, patternStart, wordStart, patternLow, wordLow)\n\n  let row = 1\n  let column = 1\n  let patternPos = patternStart\n  let wordPos = wordStart\n\n  const hasStrongFirstMatch = [false]\n\n  // There will be a match, fill in tables\n  for (row = 1, patternPos = patternStart; patternPos < patternLen; row++, patternPos++) {\n\n    // Reduce search space to possible matching word positions and to possible access from next row\n    const minWordMatchPos = _minWordMatchPos[patternPos]\n    const maxWordMatchPos = _maxWordMatchPos[patternPos]\n    const nextMaxWordMatchPos = (patternPos + 1 < patternLen ? _maxWordMatchPos[patternPos + 1] : wordLen)\n\n    for (column = minWordMatchPos - wordStart + 1, wordPos = minWordMatchPos; wordPos < nextMaxWordMatchPos; column++, wordPos++) {\n\n      let score = Number.MIN_SAFE_INTEGER\n      let canComeDiag = false\n\n      if (wordPos <= maxWordMatchPos) {\n        score = _doScore(\n          pattern, patternLow, patternPos, patternStart,\n          word, wordLow, wordPos, wordLen, wordStart,\n          _diag[row - 1][column - 1] === 0,\n          hasStrongFirstMatch\n        )\n      }\n\n      let diagScore = 0\n      canComeDiag = true\n      diagScore = score + _table[row - 1][column - 1]\n\n      const canComeLeft = wordPos > minWordMatchPos\n      const leftScore = canComeLeft ? _table[row][column - 1] + (_diag[row][column - 1] > 0 ? -5 : 0) : 0 // penalty for a gap start\n\n      const canComeLeftLeft = wordPos > minWordMatchPos + 1 && _diag[row][column - 1] > 0\n      const leftLeftScore = canComeLeftLeft ? _table[row][column - 2] + (_diag[row][column - 2] > 0 ? -5 : 0) : 0 // penalty for a gap start\n\n      if (canComeLeftLeft && (!canComeLeft || leftLeftScore >= leftScore) && (!canComeDiag || leftLeftScore >= diagScore)) {\n        // always prefer choosing left left to jump over a diagonal because that means a match is earlier in the word\n        _table[row][column] = leftLeftScore\n        _arrows[row][column] = Arrow.LeftLeft\n        _diag[row][column] = 0\n      } else if (canComeLeft && (!canComeDiag || leftScore >= diagScore)) {\n        // always prefer choosing left since that means a match is earlier in the word\n        _table[row][column] = leftScore\n        _arrows[row][column] = Arrow.Left\n        _diag[row][column] = 0\n      } else {\n        _table[row][column] = diagScore\n        _arrows[row][column] = Arrow.Diag\n        _diag[row][column] = _diag[row - 1][column - 1] + 1\n      }\n    }\n  }\n\n  // if (_debug) {\n  //   printTables(pattern, patternStart, word, wordStart)\n  // }\n\n  if (!hasStrongFirstMatch[0] && !options.firstMatchCanBeWeak) {\n    return undefined\n  }\n\n  row--\n  column--\n\n  const result: FuzzyScore = [_table[row][column], wordStart]\n\n  let backwardsDiagLength = 0\n  let maxMatchColumn = 0\n\n  while (row >= 1) {\n    // Find the column where we go diagonally up\n    let diagColumn = column\n    do {\n      const arrow = _arrows[row][diagColumn]\n      if (arrow === Arrow.LeftLeft) {\n        diagColumn = diagColumn - 2\n      } else if (arrow === Arrow.Left) {\n        diagColumn = diagColumn - 1\n      } else {\n        // found the diagonal\n        break\n      }\n    } while (diagColumn >= 1)\n\n    // Overturn the \"forwards\" decision if keeping the \"backwards\" diagonal would give a better match\n    if (\n      backwardsDiagLength > 1 // only if we would have a contiguous match of 3 characters\n      && patternLow[patternStart + row - 1] === wordLow[wordStart + column - 1] // only if we can do a contiguous match diagonally\n      && !isUpperCaseAtPos(diagColumn + wordStart - 1, word, wordLow) // only if the forwards chose diagonal is not an uppercase\n      && backwardsDiagLength + 1 > _diag[row][diagColumn] // only if our contiguous match would be longer than the \"forwards\" contiguous match\n    ) {\n      diagColumn = column\n    }\n\n    if (diagColumn === column) {\n      // this is a contiguous match\n      backwardsDiagLength++\n    } else {\n      backwardsDiagLength = 1\n    }\n\n    if (!maxMatchColumn) {\n      // remember the last matched column\n      maxMatchColumn = diagColumn\n    }\n\n    row--\n    column = diagColumn - 1\n    result.push(column)\n  }\n\n  if (wordLen === patternLen && options.boostFullMatch) {\n    // the word matches the pattern with all characters!\n    // giving the score a total match boost (to come up ahead other words)\n    result[0] += 2\n  }\n\n  // Add 1 penalty for each skipped character in the word\n  const skippedCharsCount = maxMatchColumn - patternLen\n  result[0] -= skippedCharsCount\n\n  return result\n}\n\nexport function anyScore(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number, options?: FuzzyScoreOptions): FuzzyScore {\n  const max = Math.min(13, pattern.length)\n  for (; patternPos < max; patternPos++) {\n    const result = fuzzyScore(pattern, lowPattern, patternPos, word, lowWord, wordPos, options)\n    if (result) {\n      return result\n    }\n  }\n  return [0, wordPos]\n}\n\nexport function createMatches(score: undefined | FuzzyScore): IMatch[] {\n  if (typeof score === 'undefined') {\n    return []\n  }\n  const res: IMatch[] = []\n  const wordPos = score[1]\n  for (let i = score.length - 1; i > 1; i--) {\n    const pos = score[i] + wordPos\n    const last = res[res.length - 1]\n    if (last && last.end === pos) {\n      last.end = pos + 1\n    } else {\n      res.push({ start: pos, end: pos + 1 })\n    }\n  }\n  return res\n}\n\nfunction _doScore(\n  pattern: string, patternLow: string, patternPos: number, patternStart: number,\n  word: string, wordLow: string, wordPos: number, wordLen: number, wordStart: number,\n  newMatchStart: boolean,\n  outFirstMatchStrong: boolean[],\n): number {\n  if (patternLow[patternPos] !== wordLow[wordPos]) {\n    return Number.MIN_SAFE_INTEGER\n  }\n\n  let score = 1\n  let isGapLocation = false\n  if (wordPos === (patternPos - patternStart)) {\n    // common prefix: `foobar <-> foobaz`\n    //                            ^^^^^\n    score = pattern[patternPos] === word[wordPos] ? 7 : 5\n\n  } else if (isUpperCaseAtPos(wordPos, word, wordLow) && (wordPos === 0 || !isUpperCaseAtPos(wordPos - 1, word, wordLow))) {\n    // hitting upper-case: `foo <-> forOthers`\n    //                              ^^ ^\n    score = pattern[patternPos] === word[wordPos] ? 7 : 5\n    isGapLocation = true\n\n  } else if (isSeparatorAtPos(wordLow, wordPos) && (wordPos === 0 || !isSeparatorAtPos(wordLow, wordPos - 1))) {\n    // hitting a separator: `. <-> foo.bar`\n    //                                ^\n    score = 5\n\n  } else if (isSeparatorAtPos(wordLow, wordPos - 1) || isWhitespaceAtPos(wordLow, wordPos - 1)) {\n    // post separator: `foo <-> bar_foo`\n    //                              ^^^\n    score = 5\n    isGapLocation = true\n  }\n\n  if (score > 1 && patternPos === patternStart) {\n    outFirstMatchStrong[0] = true\n  }\n\n  if (!isGapLocation) {\n    isGapLocation = isUpperCaseAtPos(wordPos, word, wordLow) || isSeparatorAtPos(wordLow, wordPos - 1) || isWhitespaceAtPos(wordLow, wordPos - 1)\n  }\n\n  //\n  if (patternPos === patternStart) { // first character in pattern\n    if (wordPos > wordStart) {\n      // the first pattern character would match a word character that is not at the word start\n      // so introduce a penalty to account for the gap preceding this match\n      score -= isGapLocation ? 3 : 5\n    }\n  } else {\n    if (newMatchStart) {\n      // this would be the beginning of a new match (i.e. there would be a gap before this location)\n      score += isGapLocation ? 2 : 0\n    } else {\n      // this is part of a contiguous match, so give it a slight bonus, but do so only if it would not be a preferred gap location\n      score += isGapLocation ? 0 : 1\n    }\n  }\n\n  if (wordPos + 1 === wordLen) {\n    // we always penalize gaps, but this gives unfair advantages to a match that would match the last character in the word\n    // so pretend there is a gap after the last character in the word to normalize things\n    score -= isGapLocation ? 3 : 5\n  }\n\n  return score\n}\n\nexport function isSeparatorAtPos(value: string, index: number): boolean {\n  const code = value.codePointAt(index)\n  switch (code) {\n    case CharCode.Underline:\n    case CharCode.Dash:\n    case CharCode.Period:\n    case CharCode.Space:\n    case CharCode.Slash:\n    case CharCode.Backslash:\n    case CharCode.SingleQuote:\n    case CharCode.DoubleQuote:\n    case CharCode.Colon:\n    case CharCode.DollarSign:\n    case CharCode.LessThan:\n    case CharCode.GreaterThan:\n    case CharCode.OpenParen:\n    case CharCode.CloseParen:\n    case CharCode.OpenSquareBracket:\n    case CharCode.CloseSquareBracket:\n    case CharCode.OpenCurlyBrace:\n    case CharCode.CloseCurlyBrace:\n      return true\n    case undefined:\n      return false\n    default:\n      if (isEmojiImprecise(code)) {\n        return true\n      }\n      return false\n  }\n}\n\nexport function isWhitespaceAtPos(value: string, index: number): boolean {\n  if (index < 0 || index >= value.length) {\n    return false\n  }\n  const code = value.charCodeAt(index)\n  switch (code) {\n    case CharCode.Space:\n    case CharCode.Tab:\n      return true\n    default:\n      return false\n  }\n}\n\nexport function isPatternInWord(patternLow: string, patternPos: number, patternLen: number, wordLow: string, wordPos: number, wordLen: number, fillMinWordPosArr = false): boolean {\n  while (patternPos < patternLen && wordPos < wordLen) {\n    if (patternLow[patternPos] === wordLow[wordPos]) {\n      if (fillMinWordPosArr) {\n        // Remember the min word position for each pattern position\n        _minWordMatchPos[patternPos] = wordPos\n      }\n      patternPos += 1\n    }\n    wordPos += 1\n  }\n  return patternPos === patternLen // pattern must be exhausted\n}\n\nfunction _fillInMaxWordMatchPos(patternLen: number, wordLen: number, patternStart: number, wordStart: number, patternLow: string, wordLow: string) {\n  let patternPos = patternLen - 1\n  let wordPos = wordLen - 1\n  while (patternPos >= patternStart && wordPos >= wordStart) {\n    if (patternLow[patternPos] === wordLow[wordPos]) {\n      _maxWordMatchPos[patternPos] = wordPos\n      patternPos--\n    }\n    wordPos--\n  }\n}\n\nexport function nextTypoPermutation(pattern: string, patternPos: number): string | undefined {\n  if (patternPos + 1 >= pattern.length) {\n    return undefined\n  }\n  const swap1 = pattern[patternPos]\n  const swap2 = pattern[patternPos + 1]\n  if (swap1 === swap2) {\n    return undefined\n  }\n  return pattern.slice(0, patternPos)\n    + swap2\n    + swap1\n    + pattern.slice(patternPos + 2)\n}\n"
  },
  {
    "path": "src/util/fs.ts",
    "content": "'use strict'\nimport type { Stats } from 'fs'\nimport { parse, ParseError } from 'jsonc-parser'\nimport { Location, Position, Range } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport { createLogger } from '../logger'\nimport { fs, path, promisify } from '../util/node'\nimport { CancellationToken, Disposable } from '../util/protocol'\nimport { isFalsyOrEmpty, toArray } from './array'\nimport { CancellationError } from './errors'\nimport { child_process, debounce, glob, minimatch, readline } from './node'\nimport { toObject } from './object'\nimport * as platform from './platform'\nimport { smartcaseIndex } from './string'\nconst logger = createLogger('util-fs')\nconst exec = child_process.exec\n\nexport enum FileType {\n  /**\n   * The file type is unknown.\n   */\n  Unknown = 0,\n  /**\n   * A regular file.\n   */\n  File = 1,\n  /**\n   * A directory.\n   */\n  Directory = 2,\n  /**\n   * A symbolic link to a file.\n   */\n  SymbolicLink = 64\n}\n\nexport type OnReadLine = (line: string) => void\n\nexport function watchFile(filepath: string, onChange: () => void, immediate = false): Disposable {\n  let callback = debounce(onChange, 100)\n  try {\n    let watcher = fs.watch(filepath, {\n      persistent: true,\n      recursive: false,\n      encoding: 'utf8'\n    }, () => {\n      callback()\n    })\n    if (immediate) {\n      setTimeout(onChange, 10)\n    }\n    return Disposable.create(() => {\n      callback.clear()\n      watcher.close()\n    })\n  } catch (e) {\n    return Disposable.create(() => {\n      callback.clear()\n    })\n  }\n}\n\nexport function loadJson(filepath: string): object {\n  try {\n    let errors: ParseError[] = []\n    let text = fs.readFileSync(filepath, 'utf8')\n    let data = parse(text, errors, { allowTrailingComma: true })\n    if (errors.length > 0) {\n      logger.error(`Error on parse json file ${filepath}`, errors)\n    }\n    return data ?? {}\n  } catch (e) {\n    return {}\n  }\n}\n\nexport function writeJson(filepath: string, obj: any): void {\n  let dir = path.dirname(filepath)\n  if (!fs.existsSync(dir)) {\n    fs.mkdirSync(dir, { recursive: true })\n    logger.info(`Creating directory ${dir}`)\n  }\n  fs.writeFileSync(filepath, JSON.stringify(toObject(obj), null, 2), 'utf8')\n}\n\nexport async function statAsync(filepath: string): Promise<Stats | null> {\n  let stat = null\n  try {\n    stat = await promisify(fs.stat)(filepath)\n  } catch (e) {}\n  return stat\n}\n\nexport function isDirectory(filepath: string | undefined): boolean {\n  if (!filepath || !path.isAbsolute(filepath) || !fs.existsSync(filepath)) return false\n  let stat = fs.statSync(filepath)\n  return stat.isDirectory()\n}\n\nexport function renameAsync(oldPath: string, newPath: string): Promise<void> {\n  return new Promise((resolve, reject) => {\n    fs.rename(oldPath, newPath, err => {\n      if (err) return reject(err)\n      resolve()\n    })\n  })\n}\n\nexport async function remove(filepath: string | undefined): Promise<void> {\n  if (!filepath) return\n  try {\n    await promisify(fs.rm)(filepath, { force: true, recursive: true })\n  } catch (e) {\n    return\n  }\n}\n\nexport async function getFileType(filepath: string): Promise<FileType | undefined> {\n  try {\n    const stat = await promisify(fs.lstat)(filepath)\n    if (stat.isFile()) {\n      return FileType.File\n    }\n    if (stat.isDirectory()) {\n      return FileType.Directory\n    }\n    if (stat.isSymbolicLink()) {\n      return FileType.SymbolicLink\n    }\n    return FileType.Unknown\n  } catch (e) {\n    return undefined\n  }\n}\n\nexport async function isGitIgnored(fullpath: string | undefined): Promise<boolean> {\n  if (!fullpath) return false\n  let stat = await statAsync(fullpath)\n  if (!stat || !stat.isFile()) return false\n  let root = null\n  try {\n    let { stdout } = await promisify(exec)('git rev-parse --show-toplevel', { cwd: path.dirname(fullpath) })\n    root = stdout.trim()\n  } catch (e) {}\n  if (!root) return false\n  let file = path.relative(root, fullpath)\n  try {\n    let { stdout } = await promisify(exec)(`git check-ignore ${file}`, { cwd: root })\n    return stdout.trim() == file\n  } catch (e) {}\n  return false\n}\n\nexport function isFolderIgnored(folder: string, ignored: string[] | undefined): boolean {\n  if (isFalsyOrEmpty(ignored)) return false\n  return ignored.some(p => sameFile(p, folder) || minimatch(folder, p, { dot: true }))\n}\n\nexport function resolveRoot(folder: string, subs: ReadonlyArray<string>, cwd?: string, bottomup = false, checkCwd = true, ignored: string[] = []): string | null {\n  let dir = normalizeFilePath(folder)\n  if (checkCwd\n    && cwd\n    && isParentFolder(cwd, dir, true)\n    && !isFolderIgnored(cwd, ignored)\n    && inDirectory(cwd, subs)) return cwd\n  let parts = dir.split(path.sep)\n  if (bottomup) {\n    while (parts.length > 0) {\n      let dir = parts.join(path.sep)\n      if (!isFolderIgnored(dir, ignored) && inDirectory(dir, subs)) {\n        return dir\n      }\n      parts.pop()\n    }\n    return null\n  } else {\n    let curr: string[] = [parts.shift()]\n    for (let part of parts) {\n      curr.push(part)\n      let dir = curr.join(path.sep)\n      if (!isFolderIgnored(dir, ignored) && inDirectory(dir, subs)) {\n        return dir\n      }\n    }\n    return null\n  }\n}\n\nexport function checkFolder(dir: string, patterns: string[], token?: CancellationToken): Promise<boolean> {\n  return new Promise(async (resolve, reject) => {\n    if (isFalsyOrEmpty(patterns)) return resolve(false)\n    let disposable: Disposable | undefined\n    const ac = new AbortController()\n    if (token) {\n      disposable = token.onCancellationRequested(() => {\n        ac.abort()\n        reject(new CancellationError())\n      })\n    }\n\n    let find = false\n    let pattern = patterns.length == 1 ? patterns[0] : `{${patterns.join(',')}}`\n    let gl = new glob.Glob(pattern, {\n      nosort: true,\n      signal: ac.signal,\n      ignore: ['node_modules/**', '.git/**'],\n      dot: true,\n      cwd: dir,\n      nodir: true,\n      absolute: false\n    })\n    try {\n      for await (const _file of gl) {\n        find = true\n        break\n      }\n    } catch (e) {\n      logger.error(`Error on glob \"${pattern}\"`, dir, e)\n    }\n    resolve(find)\n  })\n}\n\nexport function inDirectory(dir: string, subs: ReadonlyArray<string>): boolean {\n  try {\n    let files = fs.readdirSync(dir)\n    for (let pattern of subs) {\n      // note, only '*' expanded\n      let is_wildcard = (pattern.includes('*'))\n      let res = is_wildcard ?\n        (minimatch.match(files, pattern, { nobrace: true, noext: true, nocomment: true, nonegate: true, dot: true }).length !== 0) :\n        (files.includes(pattern))\n      if (res) return true\n    }\n  } catch (e) {\n    // could be failed without permission\n  }\n  return false\n}\n\n/**\n * Find a matched file inside directory.\n */\nexport function findMatch(dir: string, subs: string[]): string | undefined {\n  try {\n    let files = fs.readdirSync(dir)\n    for (let pattern of subs) {\n      // note, only '*' expanded\n      let isWildcard = (pattern.includes('*'))\n      if (isWildcard) {\n        let filtered = files.filter(minimatch.filter(pattern, { nobrace: true, noext: true, nocomment: true, nonegate: true, dot: true }))\n        if (filtered.length > 0) return filtered[0]\n      } else {\n        let file = files.find(s => s === pattern)\n        if (file) return file\n      }\n    }\n  } catch (e) {\n    // could be failed without permission\n  }\n  return undefined\n}\n\nexport function findUp(name: string | string[], cwd: string): string {\n  let root = path.parse(cwd).root\n  let subs = toArray(name)\n  while (cwd && cwd !== root) {\n    let find = findMatch(cwd, subs)\n    if (find) return path.join(cwd, find)\n    cwd = path.dirname(cwd)\n  }\n  return null\n}\n\nexport function readFile(fullpath: string, encoding: BufferEncoding): Promise<string> {\n  return new Promise((resolve, reject) => {\n    fs.readFile(fullpath, encoding, (err, content) => {\n      if (err) reject(err)\n      resolve(content)\n    })\n  })\n}\n\nexport function getFileLineCount(filepath: string): Promise<number> {\n  let i\n  let count = 0\n  return new Promise((resolve, reject) => {\n    fs.createReadStream(filepath)\n      .on('error', e => reject(e))\n      .on('data', chunk => {\n        for (i = 0; i < chunk.length; ++i) if (chunk[i] == 10) count++\n      })\n      .on('end', () => resolve(count))\n  })\n}\n\nexport function readFileLines(fullpath: string, start: number, end: number): Promise<string[]> {\n  if (!fs.existsSync(fullpath)) {\n    return Promise.reject(new Error(`file does not exist: ${fullpath}`))\n  }\n  let res: string[] = []\n  const input = fs.createReadStream(fullpath, { encoding: 'utf8' })\n  const rl = readline.createInterface({\n    input,\n    crlfDelay: Infinity,\n    terminal: false\n  } as any)\n  let n = 0\n  return new Promise((resolve, reject) => {\n    rl.on('line', line => {\n      if (n >= start && n <= end) {\n        res.push(line)\n      }\n      if (n == end) {\n        rl.close()\n      }\n      n = n + 1\n    })\n    rl.on('close', () => {\n      resolve(res)\n      input.close()\n    })\n    rl.on('error', reject)\n  })\n}\n\nexport function readFileLine(fullpath: string, count: number): Promise<string> {\n  if (!fs.existsSync(fullpath)) return Promise.reject(new Error(`file does not exist: ${fullpath}`))\n  const input = fs.createReadStream(fullpath, { encoding: 'utf8' })\n  const rl = readline.createInterface({ input, crlfDelay: Infinity, terminal: false } as any)\n  let n = 0\n  let result = ''\n  return new Promise((resolve, reject) => {\n    rl.on('line', line => {\n      if (n == count) {\n        result = line\n        rl.close()\n        input.close()\n      }\n      n = n + 1\n    })\n    rl.on('close', () => {\n      resolve(result)\n    })\n    rl.on('error', reject)\n  })\n}\n\nexport async function lineToLocation(fsPath: string, match: string, text?: string): Promise<Location> {\n  let uri = URI.file(fsPath).toString()\n  if (!fs.existsSync(fsPath)) return Location.create(uri, Range.create(0, 0, 0, 0))\n  const rl = readline.createInterface({\n    input: fs.createReadStream(fsPath, { encoding: 'utf8' }),\n  })\n  let n = 0\n  let line = await new Promise<string | undefined>(resolve => {\n    let find = false\n    rl.on('line', line => {\n      if (line.includes(match)) {\n        find = true\n        rl.removeAllListeners()\n        rl.close()\n        resolve(line)\n        return\n      }\n      n = n + 1\n    })\n    rl.on('close', () => {\n      if (!find) resolve(undefined)\n    })\n  })\n  if (line != null) {\n    let character = text == null ? -1 : smartcaseIndex(text, line)\n    if (character == -1) character = line.match(/^\\s*/)[0].length\n    let end = Position.create(n, character + (text ? text.length : 0))\n    return Location.create(uri, Range.create(Position.create(n, character), end))\n  }\n  return Location.create(uri, Range.create(0, 0, 0, 0))\n}\n\nexport function sameFile(fullpath: string | null, other: string | null, caseInsensitive?: boolean): boolean {\n  caseInsensitive = typeof caseInsensitive == 'boolean' ? caseInsensitive : platform.isWindows || platform.isMacintosh\n  if (!fullpath || !other) return false\n  fullpath = normalizeFilePath(fullpath)\n  other = normalizeFilePath(other)\n  if (caseInsensitive) return fullpath.toLowerCase() === other.toLowerCase()\n  return fullpath === other\n}\n\nexport function fileStartsWith(dir: string, pdir: string, caseInsensitive = platform.isWindows || platform.isMacintosh) {\n  if (caseInsensitive) return dir.toLowerCase().startsWith(pdir.toLowerCase())\n  return dir.startsWith(pdir)\n}\n\nexport async function writeFile(fullpath: string, content: string): Promise<void> {\n  await promisify(fs.writeFile)(fullpath, content, { encoding: 'utf8' })\n}\n\nexport function isFile(uri: string): boolean {\n  return uri.startsWith('file:')\n}\n\nexport function parentDirs(pth: string): string[] {\n  let { root, dir } = path.parse(pth)\n  if (dir === root) return [root]\n  const dirs = [root]\n  const parts = dir.slice(root.length).split(path.sep)\n  for (let i = 1; i <= parts.length; i++) {\n    dirs.push(path.join(root, parts.slice(0, i).join(path.sep)))\n  }\n  return dirs\n}\n\nexport function normalizeFilePath(filepath: string) {\n  return URI.file(path.resolve(path.normalize(filepath))).fsPath\n}\n\nexport function isParentFolder(folder: string, filepath: string, checkEqual = false): boolean {\n  let pdir = normalizeFilePath(folder)\n  let dir = normalizeFilePath(filepath)\n  if (sameFile(pdir, dir)) return checkEqual ? true : false\n  return fileStartsWith(dir, pdir) && dir[pdir.length] == path.sep\n}\n"
  },
  {
    "path": "src/util/fuzzy.ts",
    "content": "'use strict'\nimport { ASCII_END } from './constants'\n\nexport function getCharCodes(str: string): Uint16Array {\n  let len = str.length\n  let res = new Uint16Array(len)\n  for (let i = 0, l = len; i < l; i++) {\n    res[i] = str.charCodeAt(i)\n  }\n  return res\n}\n\nexport function wordChar(ch: number): boolean {\n  return (ch >= 97 && ch <= 122) || (ch >= 65 && ch <= 90)\n}\n\nexport function caseMatch(input: number, code: number, ignorecase = false): boolean {\n  if (input === code) return true\n  if (code < ASCII_END) {\n    if (input >= 97 && input <= 122 && code + 32 === input) return true\n    if (ignorecase) {\n      if (input <= 90 && input + 32 === code) return true\n      if (toLower(input) === code) return true\n    }\n  } else {\n    let lower = toLower(code)\n    if (lower === input || (ignorecase && toLower(input) === lower)) return true\n  }\n  return false\n}\n\nfunction toLower(code: number): number {\n  return String.fromCharCode(code).toLowerCase().charCodeAt(0)\n}\n\nexport function fuzzyChar(a: string, b: string, ignorecase = false): boolean {\n  let ca = a.charCodeAt(0)\n  let cb = b.charCodeAt(0)\n  return caseMatch(ca, cb, ignorecase)\n}\n\n// upper case must match, lower case ignore case\nexport function fuzzyMatch(needle: ArrayLike<number>, text: string, ignorecase = false): boolean {\n  let totalCount = needle.length\n  let tl = text.length\n  if (totalCount > tl) return false\n  let i = 0\n  let curr = needle[0]\n  for (let j = 0; j < tl; j++) {\n    let code = text.charCodeAt(j)\n    if (caseMatch(curr, code, ignorecase)) {\n      i = i + 1\n      curr = needle[i]\n      if (i === totalCount) return true\n      continue\n    }\n    if (tl - j - 1 < totalCount - i) {\n      break\n    }\n  }\n  return false\n}\n"
  },
  {
    "path": "src/util/index.ts",
    "content": "'use strict'\nimport type { CancellationToken } from 'vscode-languageserver-protocol'\nimport { crypto } from './node'\n\nexport interface Disposable {\n  dispose(): void\n}\n\nexport function sha256(data: string): string {\n  return crypto.createHash('sha256').update(data).digest('hex')\n}\n\nexport function getConditionValue<T>(value: T, testValue: T): T {\n  return global.__TEST__ ? testValue : value\n}\n\nexport const pariedCharacters: Map<string, string> = new Map([\n  ['<', '>'],\n  ['>', '<'],\n  ['{', '}'],\n  ['[', ']'],\n  ['(', ')'],\n])\n\nexport function defaultValue<T>(val: T | undefined | null, defaultValue: T): T {\n  return val == null ? defaultValue : val\n}\n\nexport function wait(ms: number): Promise<void> {\n  if (ms <= 0) return Promise.resolve(undefined)\n  return new Promise(resolve => {\n    let timer = setTimeout(() => {\n      resolve(undefined)\n    }, ms)\n    timer.unref()\n  })\n}\n\nexport function waitWithToken(ms: number, token: CancellationToken): Promise<boolean> {\n  if (token.isCancellationRequested || !ms) return Promise.resolve(true)\n  return new Promise<boolean>(resolve => {\n    let disposable = token.onCancellationRequested(() => {\n      disposable.dispose()\n      clearTimeout(timer)\n      resolve(true)\n    })\n    let timer = setTimeout(() => {\n      disposable.dispose()\n      resolve(false)\n    }, ms)\n    timer.unref()\n  })\n}\n\nexport function waitNextTick(): Promise<void> {\n  return new Promise(resolve => {\n    process.nextTick(() => {\n      resolve(undefined)\n    })\n  })\n}\n\nexport function waitImmediate(): Promise<void> {\n  return new Promise(resolve => {\n    setImmediate(() => {\n      resolve(undefined)\n    })\n  })\n}\n\nexport function delay(func: () => void, defaultDelay: number): ((ms?: number) => void) & { clear: () => void } {\n  let timer: NodeJS.Timeout\n  let fn = (ms?: number) => {\n    if (timer) clearTimeout(timer)\n    timer = setTimeout(() => {\n      func()\n    }, ms ?? defaultDelay)\n    timer.unref()\n  }\n  Object.defineProperty(fn, 'clear', {\n    get: () => {\n      return () => {\n        clearTimeout(timer)\n      }\n    }\n  })\n  return fn as any\n}\n\nexport function concurrent<T>(arr: T[], fn: (val: T) => Promise<void>, limit = 3): Promise<void> {\n  if (arr.length == 0) return Promise.resolve()\n  let finished = 0\n  let total = arr.length\n  let remain = arr.slice()\n  return new Promise(resolve => {\n    let run = (val): void => {\n      let cb = () => {\n        finished = finished + 1\n        if (finished == total) {\n          resolve()\n        } else if (remain.length) {\n          let next = remain.shift()\n          run(next)\n        }\n      }\n      fn(val).then(cb, cb)\n    }\n    for (let i = 0; i < Math.min(limit, remain.length); i++) {\n      let val = remain.shift()\n      run(val)\n    }\n  })\n}\n\nexport function disposeAll(disposables: Disposable[]): void {\n  while (disposables.length) {\n    const item = disposables.pop()\n    item?.dispose()\n  }\n}\n"
  },
  {
    "path": "src/util/is.ts",
    "content": "'use strict'\nimport { URL } from 'url'\nimport { Command, CompletionItem, CompletionList, Hover, MarkedString, MarkupContent, Range } from 'vscode-languageserver-types'\nimport { EditRange } from '../completion/types'\n\n/* eslint-disable id-blacklist */\nconst hasOwnProperty = Object.prototype.hasOwnProperty\n\nexport function isUrl(url: any): boolean {\n  try {\n    new URL(url)\n    return true\n  } catch (e) {\n    return false\n  }\n}\n\nexport function isHover(value: any): value is Hover {\n  let candidate = value as Hover\n  return !!candidate && objectLiteral(candidate) && (\n    MarkupContent.is(candidate.contents) ||\n    MarkedString.is(candidate.contents) ||\n    typedArray(candidate.contents, MarkedString.is)\n  ) && (\n      value.range == null || Range.is(value.range)\n    )\n}\n\nexport function isEditRange(value: any): value is EditRange {\n  if (!value) return false\n  if (Range.is(value)) return true\n  return Range.is(value.insert) && Range.is(value.replace)\n}\n\nexport function isCommand(obj: any): obj is Command {\n  if (!obj || !string(obj.title) || !string(obj.command) || obj.command.length == 0) return false\n  return true\n}\n\nexport function isMarkdown(content: MarkupContent | string | undefined): boolean {\n  if (content != null && content['kind'] == 'markdown') {\n    return true\n  }\n  return false\n}\n\nexport function isCompletionItem(obj: any): obj is CompletionItem {\n  return obj && typeof obj.label === 'string'\n}\n\nexport function isCompletionList(obj: any): obj is CompletionList {\n  return !Array.isArray(obj) && Array.isArray(obj.items)\n}\n\nexport function boolean(value: any): value is boolean {\n  return typeof value === 'boolean'\n}\n\nexport function string(value: any): value is string {\n  return typeof value === 'string'\n}\n\nexport function number(value: any): value is number {\n  return typeof value === 'number'\n}\n\nexport function array(array: any): array is any[] {\n  return Array.isArray(array)\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\nexport function func(value: any): value is Function {\n  return typeof value == 'function'\n}\n\nexport function objectLiteral(obj: any): obj is object {\n  return (\n    obj != null &&\n    typeof obj === 'object' &&\n    !Array.isArray(obj) &&\n    !(obj instanceof RegExp) &&\n    !(obj instanceof Date)\n  )\n}\n\nexport function emptyObject(obj: any): boolean {\n  if (!objectLiteral(obj)) {\n    return false\n  }\n\n  for (let key in obj) {\n    if (hasOwnProperty.call(obj, key)) {\n      return false\n    }\n  }\n\n  return true\n}\n\nexport function typedArray<T>(\n  value: any,\n  check: (value: any) => boolean\n): value is T[] {\n  return Array.isArray(value) && (value as any).every(check)\n}\n"
  },
  {
    "path": "src/util/jsonRegistry.ts",
    "content": "'use strict'\nimport { Emitter, Event } from '../util/protocol'\nimport type { IJSONSchema } from './jsonSchema'\nimport { Registry } from './registry'\n\nexport const Extensions = {\n  JSONContribution: 'base.contributions.json'\n}\n\nexport interface ISchemaContributions {\n  schemas: { [id: string]: IJSONSchema }\n}\n\nexport interface IJSONContributionRegistry {\n\n  readonly onDidChangeSchema: Event<string>\n\n  /**\n   * Register a schema to the registry.\n   */\n  registerSchema(uri: string, unresolvedSchemaContent: IJSONSchema): void\n\n  /**\n   * Notifies all listeners that the content of the given schema has changed.\n   * @param uri The id of the schema\n   */\n  notifySchemaChanged(uri: string): void\n\n  /**\n   * Get all schemas\n   */\n  getSchemaContributions(): ISchemaContributions\n}\n\nclass JSONContributionRegistry implements IJSONContributionRegistry {\n\n  private schemasById: { [id: string]: IJSONSchema }\n\n  private readonly _onDidChangeSchema = new Emitter<string>()\n  public readonly onDidChangeSchema: Event<string> = this._onDidChangeSchema.event\n\n  constructor() {\n    this.schemasById = {}\n  }\n\n  public registerSchema(uri: string, unresolvedSchemaContent: IJSONSchema): void {\n    this.schemasById[uri] = unresolvedSchemaContent\n    this._onDidChangeSchema.fire(uri)\n  }\n\n  public notifySchemaChanged(uri: string): void {\n    this._onDidChangeSchema.fire(uri)\n  }\n\n  public getSchemaContributions(): ISchemaContributions {\n    return {\n      schemas: this.schemasById,\n    }\n  }\n\n}\n\nconst jsonContributionRegistry = new JSONContributionRegistry()\nRegistry.add(Extensions.JSONContribution, jsonContributionRegistry)\n"
  },
  {
    "path": "src/util/jsonSchema.ts",
    "content": "'use strict'\n\nexport type JSONSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'null' | 'array' | 'object'\n\nexport interface IJSONSchema {\n  id?: string\n  $id?: string\n  $schema?: string\n  type?: JSONSchemaType | JSONSchemaType[]\n  title?: string\n  default?: any\n  definitions?: IJSONSchemaMap\n  description?: string\n  properties?: IJSONSchemaMap\n  patternProperties?: IJSONSchemaMap\n  additionalProperties?: boolean | IJSONSchema\n  minProperties?: number\n  maxProperties?: number\n  dependencies?: IJSONSchemaMap | { [prop: string]: string[] }\n  items?: IJSONSchema | IJSONSchema[]\n  minItems?: number\n  maxItems?: number\n  uniqueItems?: boolean\n  additionalItems?: boolean | IJSONSchema\n  pattern?: string\n  minLength?: number\n  maxLength?: number\n  minimum?: number\n  maximum?: number\n  exclusiveMinimum?: boolean | number\n  exclusiveMaximum?: boolean | number\n  multipleOf?: number\n  required?: string[]\n  $ref?: string\n  anyOf?: IJSONSchema[]\n  allOf?: IJSONSchema[]\n  oneOf?: IJSONSchema[]\n  not?: IJSONSchema\n  enum?: any[]\n  format?: string\n\n  // schema draft 06\n  const?: any\n  contains?: IJSONSchema\n  propertyNames?: IJSONSchema\n  examples?: any[]\n\n  // schema draft 07\n  $comment?: string\n  if?: IJSONSchema\n  then?: IJSONSchema\n  else?: IJSONSchema\n\n  // schema 2019-09\n  unevaluatedProperties?: boolean | IJSONSchema\n  unevaluatedItems?: boolean | IJSONSchema\n  minContains?: number\n  maxContains?: number\n  deprecated?: boolean\n  dependentRequired?: { [prop: string]: string[] }\n  dependentSchemas?: IJSONSchemaMap\n  $defs?: { [name: string]: IJSONSchema }\n  $anchor?: string\n  $recursiveRef?: string\n  $recursiveAnchor?: string\n  $vocabulary?: any\n\n  // schema 2020-12\n  prefixItems?: IJSONSchema[]\n  $dynamicRef?: string\n  $dynamicAnchor?: string\n\n  // VSCode extensions\n\n  defaultSnippets?: IJSONSchemaSnippet[]\n  errorMessage?: string\n  patternErrorMessage?: string\n  deprecationMessage?: string\n  markdownDeprecationMessage?: string\n  enumDescriptions?: string[]\n  markdownEnumDescriptions?: string[]\n  markdownDescription?: string\n  doNotSuggest?: boolean\n  suggestSortText?: string\n  allowComments?: boolean\n  allowTrailingCommas?: boolean\n}\n\nexport interface IJSONSchemaMap {\n  [name: string]: IJSONSchema\n}\n\nexport interface IJSONSchemaSnippet {\n  label?: string\n  description?: string\n  body?: any // a object that will be JSON stringified\n  bodyText?: string // an already stringified JSON object that can contain new lines (\\n) and tabs (\\t)\n}\n"
  },
  {
    "path": "src/util/lodash.ts",
    "content": "'use strict'\n\n/** Used for built-in method references. */\nconst objectProto = Object.prototype\n\n/** Used to check objects for own properties. */\nconst hasOwnProperty = objectProto.hasOwnProperty\n\n/**\n * Assigns own and inherited enumerable string keyed properties of source\n * objects to the destination object for all destination properties that\n * resolve to `undefined`. Source objects are applied from left to right.\n * Once a property is set, additional values of the same property are ignored.\n *\n * **Note:** This method mutates `object`.\n * @since 0.1.0\n * @category Object\n * @param {Object} object The destination object.\n * @param {...Object} [sources] The source objects.\n * @returns {Object} Returns `object`.\n * @see defaultsDeep\n * @example\n *\n * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 })\n * // => { 'a': 1, 'b': 2 }\n */\nexport function defaults(obj: any, ...sources: any[]): any {\n  obj = Object(obj)\n  sources.forEach(source => {\n    if (source != null) {\n      source = Object(source)\n      for (const key in source) {\n        const value = obj[key]\n        if (value === undefined ||\n          (value === objectProto[key] && !hasOwnProperty.call(obj, key))) {\n          obj[key] = source[key]\n        }\n      }\n    }\n  })\n  return obj\n}\n\nexport function omit<T>(obj: T, properties: string[]): T {\n  let o = {}\n  for (let key of Object.keys(obj)) {\n    if (!properties.includes(key)) {\n      o[key] = obj[key]\n    }\n  }\n  return o as T\n}\n"
  },
  {
    "path": "src/util/map.ts",
    "content": "\ninterface Item<K, V> {\n  previous: Item<K, V> | undefined\n  next: Item<K, V> | undefined\n  key: K\n  value: V\n}\n\nexport namespace Touch {\n  export const None = 0\n  export const First = 1\n  export const AsOld = First\n  export const Last = 2\n  export const AsNew: 2 = Last\n}\n\n// eslint-disable-next-line no-redeclare\nexport type Touch = 0 | 1 | 2\n\nexport class LinkedMap<K, V> implements Map<K, V> {\n\n  public readonly [Symbol.toStringTag] = 'LinkedMap'\n\n  private _map: Map<K, Item<K, V>>\n  private _head: Item<K, V> | undefined\n  private _tail: Item<K, V> | undefined\n  private _size: number\n\n  private _state: number\n\n  public constructor() {\n    this._map = new Map<K, Item<K, V>>()\n    this._head = undefined\n    this._tail = undefined\n    this._size = 0\n    this._state = 0\n  }\n\n  public clear(): void {\n    this._map.clear()\n    this._head = undefined\n    this._tail = undefined\n    this._size = 0\n    this._state++\n  }\n\n  public isEmpty(): boolean {\n    return !this._head && !this._tail\n  }\n\n  public get size(): number {\n    return this._size\n  }\n\n  public get first(): V | undefined {\n    return this._head?.value\n  }\n\n  public get last(): V | undefined {\n    return this._tail?.value\n  }\n\n  public before(key: K): V | undefined {\n    const item = this._map.get(key)\n    return item ? item.previous?.value : undefined\n  }\n\n  public after(key: K): V | undefined {\n    const item = this._map.get(key)\n    return item ? item.next?.value : undefined\n  }\n\n  public has(key: K): boolean {\n    return this._map.has(key)\n  }\n\n  public get(key: K, touch: Touch = Touch.None): V | undefined {\n    const item = this._map.get(key)\n    if (!item) {\n      return undefined\n    }\n    if (touch !== Touch.None) {\n      this.touch(item, touch)\n    }\n    return item.value\n  }\n\n  public set(key: K, value: V, touch: Touch = Touch.None): this {\n    let item = this._map.get(key)\n    if (item) {\n      item.value = value\n      if (touch !== Touch.None) {\n        this.touch(item, touch)\n      }\n    } else {\n      item = { key, value, next: undefined, previous: undefined }\n      switch (touch) {\n        case Touch.None:\n          this.addItemLast(item)\n          break\n        case Touch.First:\n          this.addItemFirst(item)\n          break\n        case Touch.Last:\n          this.addItemLast(item)\n          break\n        default:\n          this.addItemLast(item)\n          break\n      }\n      this._map.set(key, item)\n      this._size++\n    }\n    return this\n  }\n\n  public delete(key: K): boolean {\n    return !!this.remove(key)\n  }\n\n  public remove(key: K): V | undefined {\n    const item = this._map.get(key)\n    if (!item) {\n      return undefined\n    }\n    this._map.delete(key)\n    this.removeItem(item)\n    this._size--\n    return item.value\n  }\n\n  public shift(): V | undefined {\n    if (!this._head && !this._tail) {\n      return undefined\n    }\n    const item = this._head\n    this._map.delete(item.key)\n    this.removeItem(item)\n    this._size--\n    return item.value\n  }\n\n  public forEach(callbackfn: (value: V, key: K, map: LinkedMap<K, V>) => void, thisArg?: any): void {\n    const state = this._state\n    let current = this._head\n    while (current) {\n      if (thisArg) {\n        callbackfn.bind(thisArg)(current.value, current.key, this)\n      } else {\n        callbackfn(current.value, current.key, this)\n      }\n      if (this._state !== state) {\n        throw new Error(`LinkedMap got modified during iteration.`)\n      }\n      current = current.next\n    }\n  }\n\n  public keys(): IterableIterator<K> {\n    const state = this._state\n    let current = this._head\n    const iterator: IterableIterator<K> = {\n      [Symbol.iterator]: () => {\n        return iterator\n      },\n      next: (): IteratorResult<K> => {\n        if (this._state !== state) {\n          throw new Error(`LinkedMap got modified during iteration.`)\n        }\n        if (current) {\n          const result = { value: current.key, done: false }\n          current = current.next\n          return result\n        } else {\n          return { value: undefined, done: true }\n        }\n      }\n    }\n    return iterator\n  }\n\n  public values(): IterableIterator<V> {\n    const state = this._state\n    let current = this._head\n    const iterator: IterableIterator<V> = {\n      [Symbol.iterator]: () => {\n        return iterator\n      },\n      next: (): IteratorResult<V> => {\n        if (this._state !== state) {\n          throw new Error(`LinkedMap got modified during iteration.`)\n        }\n        if (current) {\n          const result = { value: current.value, done: false }\n          current = current.next\n          return result\n        } else {\n          return { value: undefined, done: true }\n        }\n      }\n    }\n    return iterator\n  }\n\n  public entries(): IterableIterator<[K, V]> {\n    const state = this._state\n    let current = this._head\n    const iterator: IterableIterator<[K, V]> = {\n      [Symbol.iterator]: () => {\n        return iterator\n      },\n      next: (): IteratorResult<[K, V]> => {\n        if (this._state !== state) {\n          throw new Error(`LinkedMap got modified during iteration.`)\n        }\n        if (current) {\n          const result: IteratorResult<[K, V]> = { value: [current.key, current.value], done: false }\n          current = current.next\n          return result\n        } else {\n          return { value: undefined, done: true }\n        }\n      }\n    }\n    return iterator\n  }\n\n  public [Symbol.iterator](): IterableIterator<[K, V]> {\n    return this.entries()\n  }\n\n  public trimOld(newSize: number): void {\n    if (newSize >= this.size) {\n      return\n    }\n    if (newSize === 0) {\n      this.clear()\n      return\n    }\n    let current = this._head\n    let currentSize = this.size\n    while (current && currentSize > newSize) {\n      this._map.delete(current.key)\n      current = current.next\n      currentSize--\n    }\n    this._head = current\n    this._size = currentSize\n    if (current) {\n      current.previous = undefined\n    }\n    this._state++\n  }\n\n  private addItemFirst(item: Item<K, V>): void {\n    // First time Insert\n    if (!this._head && !this._tail) {\n      this._tail = item\n    } else {\n      item.next = this._head\n      this._head.previous = item\n    }\n    this._head = item\n    this._state++\n  }\n\n  private addItemLast(item: Item<K, V>): void {\n    // First time Insert\n    if (!this._head && !this._tail) {\n      this._head = item\n    } else {\n      item.previous = this._tail\n      this._tail.next = item\n    }\n    this._tail = item\n    this._state++\n  }\n\n  private removeItem(item: Item<K, V>): void {\n    if (item === this._head && item === this._tail) {\n      this._head = undefined\n      this._tail = undefined\n    }\n    else if (item === this._head) {\n      item.next.previous = undefined\n      this._head = item.next\n    }\n    else if (item === this._tail) {\n      item.previous.next = undefined\n      this._tail = item.previous\n    }\n    else {\n      const next = item.next\n      const previous = item.previous\n      next.previous = previous\n      previous.next = next\n    }\n    item.next = undefined\n    item.previous = undefined\n    this._state++\n  }\n\n  private touch(item: Item<K, V>, touch: Touch): void {\n    if ((touch !== Touch.First && touch !== Touch.Last)) {\n      return\n    }\n\n    if (touch === Touch.First) {\n      if (item === this._head) {\n        return\n      }\n\n      const next = item.next\n      const previous = item.previous\n\n      // Unlink the item\n      if (item === this._tail) {\n        // previous must be defined since item was not head but is tail\n        // So there are more than on item in the map\n        previous!.next = undefined\n        this._tail = previous\n      }\n      else {\n        // Both next and previous are not undefined since item was neither head nor tail.\n        next!.previous = previous\n        previous!.next = next\n      }\n\n      // Insert the node at head\n      item.previous = undefined\n      item.next = this._head\n      this._head.previous = item\n      this._head = item\n      this._state++\n    } else if (touch === Touch.Last) {\n      if (item === this._tail) {\n        return\n      }\n\n      const next = item.next\n      const previous = item.previous\n\n      // Unlink the item.\n      if (item === this._head) {\n        // next must be defined since item was not tail but is head\n        // So there are more than on item in the map\n        next!.previous = undefined\n        this._head = next\n      } else {\n        // Both next and previous are not undefined since item was neither head nor tail.\n        next!.previous = previous\n        previous!.next = next\n      }\n      item.next = undefined\n      item.previous = this._tail\n      this._tail.next = item\n      this._tail = item\n      this._state++\n    }\n  }\n\n  public toJSON(): [K, V][] {\n    const data: [K, V][] = []\n\n    this.forEach((value, key) => {\n      data.push([key, value])\n    })\n\n    return data\n  }\n\n  public fromJSON(data: [K, V][]): void {\n    this.clear()\n\n    for (const [key, value] of data) {\n      this.set(key, value)\n    }\n  }\n}\n\nexport class LRUCache<K, V> extends LinkedMap<K, V> {\n\n  private _limit: number\n  private _ratio: number\n\n  public constructor(limit: number, ratio = 1) {\n    super()\n    this._limit = limit\n    this._ratio = Math.min(Math.max(0, ratio), 1)\n  }\n\n  public get limit(): number {\n    return this._limit\n  }\n\n  public set limit(limit: number) {\n    this._limit = limit\n    this.checkTrim()\n  }\n\n  public get ratio(): number {\n    return this._ratio\n  }\n\n  public set ratio(ratio: number) {\n    this._ratio = Math.min(Math.max(0, ratio), 1)\n    this.checkTrim()\n  }\n\n  public get(key: K, touch: Touch = Touch.AsNew): V | undefined {\n    return super.get(key, touch)\n  }\n\n  public peek(key: K): V | undefined {\n    return super.get(key, Touch.None)\n  }\n\n  public set(key: K, value: V): this {\n    super.set(key, value, Touch.Last)\n    this.checkTrim()\n    return this\n  }\n\n  private checkTrim() {\n    if (this.size > this._limit) {\n      this.trimOld(Math.round(this._limit * this._ratio))\n    }\n  }\n}\n"
  },
  {
    "path": "src/util/mutex.ts",
    "content": "'use strict'\nexport class Mutex {\n  private tasks: (() => void)[] = []\n  private count = 1\n\n  private sched(): void {\n    if (this.count > 0 && this.tasks.length > 0) {\n      this.count--\n      let next = this.tasks.shift()\n      next()\n    }\n  }\n\n  public reset(): void {\n    this.tasks = []\n    this.count = 1\n  }\n\n  public get busy(): boolean {\n    return this.count == 0\n  }\n\n  public acquire(): Promise<() => void> {\n    return new Promise<() => void>(res => {\n      let task = () => {\n        let released = false\n        res(() => {\n          if (!released) {\n            released = true\n            this.count++\n            this.sched()\n          }\n        })\n      }\n      this.tasks.push(task)\n      process.nextTick(this.sched.bind(this))\n    })\n  }\n\n  public use<T>(f: () => Promise<T>): Promise<T> {\n    return this.acquire()\n      .then(release => f()\n        .then(res => {\n          release()\n          return res\n        })\n        .catch(err => {\n          release()\n          throw err\n        }))\n  }\n}\n"
  },
  {
    "path": "src/util/node.ts",
    "content": "import type STYLES from 'ansi-styles'\nimport type CHILD_PROCESS from 'child_process'\nimport type CRYPTO from 'crypto'\nimport type DEBOUNCE from 'debounce'\nimport type FASTDIFF from 'fast-diff'\nimport type FS from 'fs'\nimport type * as GLOB from 'glob'\nimport type * as Minimatch from 'minimatch'\nimport type NET from 'net'\nimport type OS from 'os'\nimport type PATH from 'path'\nimport type READLINE from 'readline'\nimport type SEMVER from 'semver'\nimport type STRIPANSI from 'strip-ansi'\nimport type UNIDECODE from 'unidecode'\nimport { inspect, promisify } from 'util'\nimport type VM from 'vm'\nimport type WHICH from 'which'\n\nexport const fs = require('fs') as typeof FS\nexport const path = require('path') as typeof PATH\nexport const os = require('os') as typeof OS\nexport const crypto = require('crypto') as typeof CRYPTO\nexport const styles = require('ansi-styles') as typeof STYLES\nexport const debounce = require('debounce') as typeof DEBOUNCE\nexport const readline = require('readline') as typeof READLINE\nexport const child_process = require('child_process') as typeof CHILD_PROCESS\nexport const glob = require('glob') as typeof GLOB\nexport const { minimatch } = require('minimatch') as typeof Minimatch\nexport const which = require('which') as typeof WHICH\nexport const semver = require('semver') as typeof SEMVER\nexport const vm = require('vm') as typeof VM\nexport const net = require('net') as typeof NET\nexport const stripAnsi = require('strip-ansi') as typeof STRIPANSI\nexport const fastDiff = require('fast-diff') as typeof FASTDIFF\nexport const unidecode = require('unidecode') as typeof UNIDECODE\nexport { inspect, promisify }\n\n"
  },
  {
    "path": "src/util/numbers.ts",
    "content": "'use strict'\nimport * as Is from './is'\n\nexport function toNumber(n: number | undefined | null, defaultValue = 0): number {\n  return Is.number(n) ? n : defaultValue\n}\n\nexport function boolToNumber(val: boolean | undefined | null): number {\n  return val ? 1 : 0\n}\n\nexport function clamp(value: number, min: number, max: number): number {\n  return Math.min(Math.max(value, min), max)\n}\n\nexport function rot(index: number, modulo: number): number {\n  return (modulo + (index % modulo)) % modulo\n}\n"
  },
  {
    "path": "src/util/object.ts",
    "content": "'use strict'\nimport * as Is from './is'\n\nexport function isEmpty(obj: object | null | undefined): boolean {\n  if (!obj) return true\n  if (Array.isArray(obj)) return obj.length == 0\n  return Object.keys(obj).length == 0\n}\n\nexport function toObject<T>(obj: T | null | undefined): Partial<T> {\n  return obj == null ? {} : obj\n}\n\nexport function omitUndefined<T extends object>(obj: T): Partial<T> {\n  const result: any = {}\n  Object.entries(obj).forEach(([key, val]) => {\n    if (val !== undefined) result[key] = val\n  })\n  return result\n}\n\nexport function omitNullUndefined<T extends object>(obj: T): Partial<T> {\n  return Object.entries(obj).reduce((acc, [key, value]) => {\n    if (value !== null && value !== undefined) {\n      acc[key as keyof T] = value\n    }\n    return acc\n  }, {} as Partial<T>)\n}\n\nexport function deepIterate(obj: object, fn: (node: object, key: string) => void): object {\n  Object.entries(obj).forEach(([key, val]) => {\n    fn(obj, key)\n    if (Array.isArray(val)) {\n      val.forEach(node => {\n        if (Is.objectLiteral(node)) {\n          deepIterate(node, fn)\n        }\n      })\n    } else if (Is.objectLiteral(val)) {\n      deepIterate(val, fn)\n    }\n  })\n  return obj\n}\n\nexport function toReadonly<T extends object>(obj: T): T {\n  const result = {}\n  for (let key of Object.keys(obj)) {\n    Object.defineProperty(result, key, {\n      value: obj[key],\n      writable: false,\n      enumerable: true\n    })\n  }\n  return result as T\n}\n\nexport function deepClone<T>(obj: T): T {\n  if (!obj || typeof obj !== 'object') {\n    return obj\n  }\n  if (obj instanceof RegExp) {\n    // See https://github.com/Microsoft/TypeScript/issues/10990\n    return obj as any\n  }\n  const result: any = Array.isArray(obj) ? [] : {}\n  Object.keys(obj).forEach(key => {\n    if (obj[key] && typeof obj[key] === 'object') {\n      result[key] = deepClone(obj[key])\n    } else {\n      result[key] = obj[key]\n    }\n  })\n  return result\n}\n\nconst _hasOwnProperty = Object.prototype.hasOwnProperty\n\nexport function hasOwnProperty(obj: any, key: string): boolean {\n  return _hasOwnProperty.call(obj, key)\n}\n\nexport function deepFreeze<T>(obj: T): T {\n  if (!obj || typeof obj !== 'object') {\n    return obj\n  }\n  const stack: any[] = [obj]\n  while (stack.length > 0) {\n    let obj = stack.shift()\n    Object.freeze(obj)\n    for (const key of Object.keys(obj)) {\n      let prop = obj[key]\n      if (typeof prop === 'object' && !Object.isFrozen(prop)) {\n        stack.push(prop)\n      }\n    }\n  }\n  return obj\n}\n\n/**\n * Copies all properties of source into destination. The optional parameter \"overwrite\" allows to control\n * if existing properties on the destination should be overwritten or not. Defaults to true (overwrite).\n */\nexport function mixin(\n  destination: any,\n  source: any,\n  overwrite = true\n): any {\n  if (!Is.objectLiteral(destination)) {\n    return source\n  }\n\n  if (Is.objectLiteral(source)) {\n    Object.keys(source).forEach(key => {\n      if (key in destination) {\n        if (overwrite) {\n          if (Is.objectLiteral(destination[key]) && Is.objectLiteral(source[key])) {\n            mixin(destination[key], source[key], overwrite)\n          } else {\n            destination[key] = source[key]\n          }\n        }\n      } else {\n        destination[key] = source[key]\n      }\n    })\n  }\n  return destination\n}\n\nexport function equals(one: any, other: any): boolean {\n  if (one === other) {\n    return true\n  }\n  if (\n    one === null ||\n    one === undefined ||\n    other === null ||\n    other === undefined\n  ) {\n    return false\n  }\n  if (typeof one !== typeof other) {\n    return false\n  }\n  if (typeof one !== 'object') {\n    return false\n  }\n  if (Array.isArray(one) !== Array.isArray(other)) {\n    return false\n  }\n\n  let i: number\n  let key: string\n\n  if (Array.isArray(one)) {\n    if (one.length !== other.length) {\n      return false\n    }\n    for (i = 0; i < one.length; i++) {\n      if (!equals(one[i], other[i])) {\n        return false\n      }\n    }\n  } else {\n    const oneKeys: string[] = []\n\n    for (key in one) {\n      oneKeys.push(key)\n    }\n    oneKeys.sort()\n    const otherKeys: string[] = []\n    for (key in other) {\n      otherKeys.push(key)\n    }\n    otherKeys.sort()\n    if (!equals(oneKeys, otherKeys)) {\n      return false\n    }\n    for (i = 0; i < oneKeys.length; i++) {\n      if (!equals(one[oneKeys[i]], other[oneKeys[i]])) {\n        return false\n      }\n    }\n  }\n  return true\n}\n"
  },
  {
    "path": "src/util/platform.ts",
    "content": "'use strict'\n\nexport interface IProcessEnvironment {\n  [key: string]: string\n}\n\ninterface INodeProcess {\n  nextTick: () => void\n  platform: string\n  env: IProcessEnvironment\n  getuid(): number\n}\n\ndeclare let process: INodeProcess\n\nexport enum Platform {\n  Web,\n  Mac,\n  Linux,\n  Windows,\n  Unknown\n}\n\nexport function getPlatform(process: INodeProcess): Platform {\n  let { platform } = process\n  if (platform === 'win32') return Platform.Windows\n  if (platform === 'darwin') return Platform.Mac\n  if (platform === 'linux') return Platform.Linux\n  return Platform.Unknown\n}\n\nlet _platform: Platform = getPlatform(process)\n\nexport const platform = _platform\nexport const isWindows = _platform === Platform.Windows\nexport const isMacintosh = _platform === Platform.Mac\nexport const isLinux = _platform === Platform.Linux\nexport const isNative = true\nexport const isWeb = false\n"
  },
  {
    "path": "src/util/position.ts",
    "content": "'use strict'\nimport { Position, Range } from 'vscode-languageserver-types'\n\nexport function rangeInRange(r: Range, range: Range): boolean {\n  return positionInRange(r.start, range) === 0 && positionInRange(r.end, range) === 0\n}\n\nexport function equalsRange(r: Range, range: Range): boolean {\n  if (!samePosition(r.start, range.start)) return false\n  return samePosition(r.end, range.end)\n}\n\nexport function samePosition(one: Position, two: Position): boolean {\n  return one.line === two.line && one.character === two.character\n}\n\nexport function adjacentPosition(pos: Position, range: Range) {\n  return samePosition(pos, range.start) || samePosition(pos, range.end)\n}\n\n/**\n * A function that compares ranges, useful for sorting ranges\n * It will first compare ranges on the startPosition and then on the endPosition\n */\nexport function compareRangesUsingStarts(a: Range, b: Range): number {\n  const aStartLineNumber = a.start.line | 0\n  const bStartLineNumber = b.start.line | 0\n\n  if (aStartLineNumber === bStartLineNumber) {\n    const aStartColumn = a.start.character | 0\n    const bStartColumn = b.start.character | 0\n\n    if (aStartColumn === bStartColumn) {\n      const aEndLineNumber = a.end.line | 0\n      const bEndLineNumber = b.end.line | 0\n\n      if (aEndLineNumber === bEndLineNumber) {\n        const aEndColumn = a.end.character | 0\n        const bEndColumn = b.end.character | 0\n        return aEndColumn - bEndColumn\n      }\n      return aEndLineNumber - bEndLineNumber\n    }\n    return aStartColumn - bStartColumn\n  }\n  return aStartLineNumber - bStartLineNumber\n}\n\n/**\n * Convert to well formed range\n */\nexport function toValidRange(range: Range, max?: number): Range {\n  let { start, end } = range\n  if (start.line > end.line || (start.line === end.line && start.character > end.character)) {\n    let m = start\n    start = end\n    end = m\n  }\n  start = Position.create(Math.max(0, start.line), Math.max(0, start.character))\n  let endCharacter = Math.max(0, end.character)\n  if (typeof max === 'number' && endCharacter > max) endCharacter = max\n  end = Position.create(Math.max(0, end.line), endCharacter)\n  return { start, end }\n}\n\nexport function rangeAdjacent(r: Range, range: Range): boolean {\n  if (comparePosition(r.end, range.start) == 0) {\n    return true\n  }\n  if (comparePosition(range.end, r.start) == 0) {\n    return true\n  }\n  return false\n}\n\n/**\n * Check if two ranges have overlap character.\n */\nexport function rangeOverlap(r: Range, range: Range): boolean {\n  let { start, end } = r\n  if (comparePosition(end, range.start) <= 0) {\n    return false\n  }\n  if (comparePosition(start, range.end) >= 0) {\n    return false\n  }\n  return true\n}\n\n/**\n * Check if two ranges have overlap or nested\n */\nexport function rangeIntersect(r: Range, range: Range): boolean {\n  if (positionInRange(r.start, range) == 0) {\n    return true\n  }\n  if (positionInRange(r.end, range) == 0) {\n    return true\n  }\n  if (rangeInRange(range, r)) {\n    return true\n  }\n  return false\n}\n\n/**\n * Adjust from start position\n */\nexport function adjustRangePosition(range: Range, position: Position): Range {\n  let { line, character } = position\n  let { start, end } = range\n  let endCharacter = end.line == start.line ? end.character + character : end.character\n  return Range.create(start.line + line, character + start.character, end.line + line, endCharacter)\n}\n\nexport function lineInRange(line: number, range: Range): boolean {\n  let { start, end } = range\n  return line >= start.line && line <= end.line\n}\n\nexport function emptyRange(range: Range): boolean {\n  let { start, end } = range\n  return start.line == end.line && start.character == end.character\n}\n\nexport function positionInRange(position: Position, range: Range): number {\n  let { start, end } = range\n  if (comparePosition(position, start) < 0) return -1\n  if (comparePosition(position, end) > 0) return 1\n  return 0\n}\n\nexport function comparePosition(position: Position, other: Position): number {\n  if (position.line > other.line) return 1\n  if (other.line == position.line && position.character > other.character) return 1\n  if (other.line == position.line && position.character == other.character) return 0\n  return -1\n}\n\nexport function isSingleLine(range: Range): boolean {\n  return range.start.line == range.end.line\n}\n\n/*\n * Get end position by content\n */\nexport function getEnd(start: Position, content: string): Position {\n  const lines = content.split(/\\r?\\n/)\n  const len = lines.length\n  const lastLine = lines[len - 1]\n  const end = len == 1 ? start.character + content.length : lastLine.length\n  return Position.create(start.line + len - 1, end)\n}\n"
  },
  {
    "path": "src/util/processes.ts",
    "content": "'use strict'\nimport type { ChildProcess, ExecOptions } from 'child_process'\nimport { pluginRoot } from './constants'\nimport { CancellationError } from './errors'\nimport { child_process, path, which } from './node'\nimport { platform, Platform } from './platform'\nimport iconv from 'iconv-lite'\nimport { CancellationToken, Disposable } from './protocol'\nimport { omit } from './lodash'\n\nexport function isRunning(pid: number): boolean {\n  try {\n    let res: any = process.kill(pid, 0)\n    return res == true\n  }\n  catch (e) {\n    return e['code'] === 'EPERM'\n  }\n}\n\nexport function executable(command: string): boolean {\n  try {\n    which.sync(command)\n  } catch (e) {\n    return false\n  }\n  return true\n}\n\nexport function runCommand(cmd: string, opts: ExecOptions & { encoding?: string } = {}, timeout?: CancellationToken | number, isWindows = platform === Platform.Windows): Promise<string> {\n  if (!isWindows) {\n    opts.shell = opts.shell || process.env.SHELL\n  }\n  opts.maxBuffer = opts.maxBuffer ?? 500 * 1024\n  let encoding = opts.encoding || 'utf8'\n  encoding = iconv.encodingExists(encoding) ? encoding : 'utf8'\n  return new Promise<string>((resolve, reject) => {\n    let disposable: Disposable | undefined\n    let cp: ChildProcess\n    if (typeof timeout === 'number') {\n      let timer = setTimeout(() => {\n        terminate(cp)\n        reject(new CancellationError())\n      }, timeout * 1000)\n      disposable = Disposable.create(() => {\n        clearTimeout(timer)\n      })\n    } else if (CancellationToken.is(timeout)) {\n      disposable = timeout.onCancellationRequested(() => {\n        terminate(cp)\n        reject(new CancellationError())\n      })\n    }\n    cp = child_process.exec(cmd, { ...omit(opts, ['encoding']), encoding: 'buffer' }, (err, stdout, stderr) => {\n      if (disposable) disposable.dispose()\n      if (err) {\n        reject(new Error(`exited with ${err.code}\\n${err}\\n${stderr.toString('utf8')}`))\n        return\n      }\n      resolve(iconv.decode(stdout, encoding))\n    })\n  })\n}\n\nexport function terminate(process: ChildProcess, cwd?: string, pt = platform): boolean {\n  if (process.killed) return\n  if (pt === Platform.Windows) {\n    try {\n      // This we run in Atom execFileSync is available.\n      // Ignore stderr since this is otherwise piped to parent.stderr\n      // which might be already closed.\n      let options: any = {\n        stdio: ['pipe', 'pipe', 'ignore']\n      }\n      if (cwd) options.cwd = cwd\n\n      child_process.execFileSync(\n        'taskkill',\n        ['/T', '/F', '/PID', process.pid.toString()],\n        options\n      )\n      return true\n    } catch (err) {\n      return false\n    }\n  } else if (pt === Platform.Linux || pt === Platform.Mac) {\n    try {\n      let filepath = path.join(pluginRoot, 'bin/terminateProcess.sh')\n      let result = child_process.spawnSync(filepath, [process.pid.toString()])\n      return result.error ? false : true\n    } catch (err) {\n      return false\n    }\n  } else {\n    process.kill('SIGKILL')\n    return true\n  }\n}\n"
  },
  {
    "path": "src/util/protocol.ts",
    "content": "\nexport {\n  createProtocolConnection,\n  generateRandomPipeName,\n  InitializeRequest,\n  ShutdownRequest,\n  ExitNotification,\n  LogMessageNotification,\n  ShowMessageNotification,\n  ShowMessageRequest,\n  ShowDocumentRequest,\n  PublishDiagnosticsNotification,\n  RegistrationRequest,\n  UnregistrationRequest,\n  ApplyWorkspaceEditRequest,\n  InitializedNotification,\n  InlineCompletionItem,\n  InlineCompletionContext,\n  TraceFormat,\n  ResourceOperationKind,\n  FailureHandlingKind,\n  LSPErrorCodes,\n  MessageType,\n  MessageReader,\n  MessageWriter,\n  Disposable,\n  Event,\n  CancellationToken,\n  TextDocumentFilter,\n  SignatureHelpTriggerKind,\n  DocumentDiagnosticReportKind,\n  DiagnosticServerCancellationData,\n  RAL,\n  Trace,\n  FileOperationPatternKind,\n  PositionEncodingKind,\n  CompletionTriggerKind,\n  TextDocumentSaveReason,\n  UniquenessLevel,\n  ErrorCodes,\n  MonikerKind,\n  IPCMessageReader,\n  IPCMessageWriter,\n  StreamMessageReader,\n  StreamMessageWriter,\n  ProgressType,\n  ResponseError,\n  ProtocolRequestType,\n  ProtocolRequestType0,\n  ProtocolNotificationType,\n  ProtocolNotificationType0,\n  RequestType,\n  RequestType0,\n  NotificationType,\n  NotificationType0,\n  Emitter,\n  CancellationTokenSource,\n  RelativePattern,\n  WatchKind,\n  FileChangeType,\n  FoldingRangeKind,\n  PrepareSupportDefaultBehavior,\n  TokenFormat,\n  TextDocumentSyncKind,\n  TextDocumentRegistrationOptions,\n  StaticRegistrationOptions,\n  WorkDoneProgressOptions,\n  WorkspaceSymbolRequest,\n  WorkspaceSymbolResolveRequest,\n  TypeHierarchyPrepareRequest,\n  TypeHierarchySupertypesRequest,\n  TypeHierarchySubtypesRequest,\n  TypeDefinitionRequest,\n  DidChangeWorkspaceFoldersNotification,\n  WorkspaceFoldersRequest,\n  WillSaveTextDocumentNotification,\n  WillSaveTextDocumentWaitUntilRequest,\n  SignatureHelpRequest,\n  SemanticTokensRequest,\n  SemanticTokensRangeRequest,\n  SemanticTokensDeltaRequest,\n  SemanticTokensRefreshRequest,\n  SemanticTokensRegistrationType,\n  SelectionRangeRequest,\n  PrepareRenameRequest,\n  HoverRequest,\n  InlayHintRequest,\n  InlayHintResolveRequest,\n  InlayHintRefreshRequest,\n  InlineValueRefreshRequest,\n  InlineValueRequest,\n  LinkedEditingRangeRequest,\n  WorkDoneProgressCreateRequest,\n  WorkDoneProgress,\n  WorkDoneProgressCancelNotification,\n  RenameRequest,\n  ReferencesRequest,\n  ImplementationRequest,\n  DocumentFormattingRequest,\n  DocumentRangeFormattingRequest,\n  DocumentOnTypeFormattingRequest,\n  FoldingRangeRequest,\n  DidChangeWatchedFilesNotification,\n  DidCreateFilesNotification,\n  DidRenameFilesNotification,\n  WillCreateFilesRequest,\n  DidDeleteFilesNotification,\n  WillRenameFilesRequest,\n  WillDeleteFilesRequest,\n  DocumentSymbolRequest,\n  DocumentLinkRequest,\n  DocumentLinkResolveRequest,\n  DocumentDiagnosticRequest,\n  WorkspaceDiagnosticRequest,\n  DocumentHighlightRequest,\n  DidOpenTextDocumentNotification,\n  DidChangeTextDocumentNotification,\n  DidSaveTextDocumentNotification,\n  DidCloseTextDocumentNotification,\n  DiagnosticRefreshRequest,\n  CallHierarchyPrepareRequest,\n  CallHierarchyIncomingCallsRequest,\n  CallHierarchyOutgoingCallsRequest,\n  CodeActionRequest,\n  ExecuteCommandRequest,\n  CodeActionResolveRequest,\n  CodeLensResolveRequest,\n  CodeLensRequest,\n  CodeLensRefreshRequest,\n  DocumentColorRequest,\n  ColorPresentationRequest,\n  CompletionRequest,\n  CompletionResolveRequest,\n  ConfigurationRequest,\n  DidChangeConfigurationNotification,\n  DeclarationRequest,\n  DefinitionRequest,\n} from 'vscode-languageserver-protocol/node'\n"
  },
  {
    "path": "src/util/registry.ts",
    "content": "import type { IConfigurationPropertySchema } from '../configuration/registry'\nimport { ConfigurationScope, IStringDictionary } from '../configuration/types'\nimport { assert } from './errors'\nimport { objectLiteral } from './is'\nimport { deepClone, toObject } from './object'\n\nexport interface IRegistry {\n\n  /**\n   * Adds the extension functions and properties defined by data to the\n   * platform. The provided id must be unique.\n   * @param id a unique identifier\n   * @param data a contribution\n   */\n  add(id: string, data: any): void\n\n  /**\n   * Returns true iff there is an extension with the provided id.\n   * @param id an extension identifier\n   */\n  knows(id: string): boolean\n\n  /**\n   * Returns the extension functions and properties defined by the specified key or null.\n   * @param id an extension identifier\n   */\n  as<T>(id: string): T\n}\n\nclass RegistryImpl implements IRegistry {\n\n  private readonly data = new Map<string, any>()\n\n  public add(id: string, data: any): void {\n    assert(typeof id === 'string')\n    assert(objectLiteral(data))\n    assert(!this.data.has(id))\n    this.data.set(id, data)\n  }\n\n  public knows(id: string): boolean {\n    return this.data.has(id)\n  }\n\n  public as(id: string): any {\n    return this.data.get(id) || null\n  }\n}\n\nexport const Registry: IRegistry = new RegistryImpl()\n\nconst sourcePrefixes = ['coc.source.', 'list.source.']\n\nenum ScopeNames {\n  Application = 'application',\n  Window = 'window',\n  Resource = 'resource',\n  MachineOverridable = 'machine-overridable',\n  LanguageOverridable = 'language-overridable',\n}\n\nfunction convertScope(key: string, scope: string, defaultScope: ConfigurationScope): ConfigurationScope {\n  if (sourcePrefixes.some(p => key.startsWith(p))) return ConfigurationScope.APPLICATION\n  if (scope === ScopeNames.Application) return ConfigurationScope.APPLICATION\n  if (scope === ScopeNames.Window) return ConfigurationScope.WINDOW\n  if (scope === ScopeNames.Resource || scope === ScopeNames.MachineOverridable) return ConfigurationScope.RESOURCE\n  if (scope === ScopeNames.LanguageOverridable) return ConfigurationScope.LANGUAGE_OVERRIDABLE\n  return defaultScope\n}\n\n/**\n * Properties to schema\n */\nexport function convertProperties(properties: object | null | undefined, defaultScope = ConfigurationScope.WINDOW): IStringDictionary<IConfigurationPropertySchema> {\n  let obj: IStringDictionary<IConfigurationPropertySchema> = {}\n  for (let [key, def] of Object.entries(toObject(properties))) {\n    let data = deepClone(def)\n    data.scope = convertScope(key, def.scope, defaultScope)\n    obj[key] = data\n  }\n  return obj\n}\n"
  },
  {
    "path": "src/util/sequence.ts",
    "content": "\nexport class Sequence {\n  private _busy = false\n  private _fns: (() => Promise<void>)[] = []\n  private _resolves: (() => void)[] = []\n\n  public run(fn: () => Promise<void>): void {\n    if (!this._busy) {\n      this._busy = true\n      void fn().finally(() => {\n        this.next()\n      })\n    } else {\n      this._fns.push(fn)\n    }\n  }\n\n  public waitFinish(): Promise<void> {\n    if (!this._busy) return Promise.resolve()\n    return new Promise(resolve => {\n      this._resolves.push(resolve)\n    })\n  }\n\n  private next(): void {\n    let fn = this._fns.shift()\n    if (!fn) {\n      this.finish()\n    } else {\n      void fn().finally(() => {\n        this.next()\n      })\n    }\n  }\n\n  private finish(): void {\n    this._busy = false\n    let fn: () => void\n    while ((fn = this._resolves.pop()) != null) {\n      fn()\n    }\n  }\n\n  public cancel(): void {\n    this._fns = []\n    this.finish()\n  }\n}\n"
  },
  {
    "path": "src/util/string.ts",
    "content": "'use strict'\nimport type { Range } from 'vscode-languageserver-types'\nimport { intable } from './array'\nimport { CharCode } from './charCode'\n\nconst UTF8_2BYTES_START = 0x80\nconst UTF8_3BYTES_START = 0x800\nconst UTF8_4BYTES_START = 65536\nconst encoding = 'utf8'\nconst asciiTable: ReadonlyArray<[number, number]> = [\n  [48, 57],\n  [65, 90],\n  [97, 122]\n]\n\nexport function toErrorText(error: any): string {\n  return error instanceof Error ? error.message : error.toString()\n}\n\nexport function toInteger(text: string): number | undefined {\n  let n = parseInt(text, 10)\n  return isNaN(n) ? undefined : n\n}\n\nexport function toText(text: string | number | null | undefined): string {\n  if (typeof text === 'number') return text.toString()\n  return text ?? ''\n}\n\nexport function toBase64(text: string) {\n  return global.Buffer.from(text).toString('base64')\n}\n\nexport function isHighlightGroupCharCode(code: number): boolean {\n  if (intable(code, asciiTable)) return true\n  return code === CharCode.Underline || code === CharCode.Period || code === CharCode.AtSign\n}\n\n/**\n * A fast function (therefore imprecise) to check if code points are emojis.\n * Generated using https://github.com/alexdima/unicode-utils/blob/main/emoji-test.js\n */\nexport function isEmojiImprecise(x: number): boolean {\n  return (\n    (x >= 0x1F1E6 && x <= 0x1F1FF) || (x === 8986) || (x === 8987) || (x === 9200)\n    || (x === 9203) || (x >= 9728 && x <= 10175) || (x === 11088) || (x === 11093)\n    || (x >= 127744 && x <= 128591) || (x >= 128640 && x <= 128764)\n    || (x >= 128992 && x <= 129008) || (x >= 129280 && x <= 129535)\n    || (x >= 129648 && x <= 129782)\n  )\n}\n\n/**\n * Get previous and after part of range\n */\nexport function rangeParts(text: string, range: Range): [string, string] {\n  let { start, end } = range\n  let lines = text.split(/\\r?\\n/)\n  let before = ''\n  let after = ''\n  let len = lines.length\n  // get start and end parts\n  for (let i = 0; i < len; i++) {\n    let curr = lines[i]\n    if (i < start.line) {\n      before += curr + '\\n'\n      continue\n    }\n    if (i > end.line) {\n      after += curr + (i == len - 1 ? '' : '\\n')\n      continue\n    }\n    if (i == start.line) {\n      before += curr.slice(0, start.character)\n    }\n    if (i == end.line) {\n      after += curr.slice(end.character) + (i == len - 1 ? '' : '\\n')\n    }\n  }\n  return [before, after]\n}\n\n// lowerCase 1, upperCase 2\nexport function getCase(code: number): number {\n  if (code >= 97 && code <= 122) return 1\n  if (code >= 65 && code <= 90) return 2\n  return 0\n}\n\nexport function getNextWord(codes: Uint16Array, index: number): [number, number] | undefined {\n  let preCase = index == 0 ? 0 : getCase(codes[index - 1])\n  for (let i = index; i < codes.length; i++) {\n    let curr = getCase(codes[i])\n    if (curr > 0 && curr != preCase) {\n      return [i, codes[i]]\n    }\n    preCase = curr\n  }\n  return undefined\n}\n\nexport function getCharIndexes(input: string, character: string): number[] {\n  let res: number[] = []\n  for (let i = 0; i < input.length; i++) {\n    if (input[i] == character) res.push(i)\n  }\n  return res\n}\n\nexport function* iterateCharacter(input: string, character: string): Iterable<number> {\n  for (let i = 0; i < input.length; i++) {\n    if (input[i] == character) yield i\n  }\n}\n\nexport function isHighSurrogate(codePoint: number): boolean {\n  return codePoint >= 0xd800 && codePoint <= 0xdbff\n}\n\nexport function isLowSurrogate(codePoint: number): boolean {\n  return codePoint >= 0xdc00 && codePoint <= 0xdfff\n}\n\n/**\n * Get byte length from string, from code unit start index.\n */\nexport function byteLength(str: string, start = 0): number {\n  if (start === 0) return Buffer.byteLength(str, encoding)\n  let len = 0\n  let unitIndex = 0\n  for (let codePoint of str) {\n    let n = codePoint.codePointAt(0)\n    if (unitIndex >= start) {\n      len += utf8_code2len(n)\n    }\n    unitIndex += (n >= UTF8_4BYTES_START ? 2 : 1)\n  }\n  return len\n}\n\n/**\n * utf16 code unit to byte index.\n */\nexport function byteIndex(content: string, index: number): number {\n  let byteLength = 0\n  let codePoint: number | undefined\n  let prevCodePoint: number | undefined\n  let max = Math.min(index, content.length)\n  for (let i = 0; i < max; i++) {\n    codePoint = content.charCodeAt(i)\n    if (isLowSurrogate(codePoint)) {\n      if (prevCodePoint && isHighSurrogate(prevCodePoint)) {\n        byteLength += 1\n      } else {\n        byteLength += 3\n      }\n    } else {\n      byteLength += utf8_code2len(codePoint)\n    }\n    prevCodePoint = codePoint\n  }\n  return byteLength\n}\n\nexport function upperFirst(str: string): string {\n  return str?.length > 0 ? str[0].toUpperCase() + str.slice(1) : ''\n}\n\nexport function indexOf(str: string, ch: string, count = 1): number {\n  let curr = 0\n  for (let i = 0; i < str.length; i++) {\n    if (str[i] == ch) {\n      curr = curr + 1\n      if (curr == count) {\n        return i\n      }\n    }\n  }\n  return -1\n}\n\nexport function characterIndex(content: string, byteIndex: number): number {\n  if (byteIndex == 0) return 0\n  let characterIndex = 0\n  let total = 0\n  for (let codePoint of content) {\n    let code = codePoint.codePointAt(0)\n    if (code >= UTF8_4BYTES_START) {\n      characterIndex += 2\n      total += 4\n    } else {\n      characterIndex += 1\n      total += utf8_code2len(code)\n    }\n    if (total >= byteIndex) break\n  }\n  return characterIndex\n}\n\nexport function utf8_code2len(code: number): number {\n  if (code < UTF8_2BYTES_START) return 1\n  if (code < UTF8_3BYTES_START) return 2\n  if (code < UTF8_4BYTES_START) return 3\n  return 4\n}\n\n/**\n * No need to create Buffer\n */\nexport function byteSlice(content: string, start: number, end?: number): string {\n  let si = characterIndex(content, start)\n  let ei = end === undefined ? undefined : characterIndex(content, end)\n  return content.slice(si, ei)\n}\n\nexport function isAlphabet(code: number): boolean {\n  if (code >= 65 && code <= 90) return true\n  if (code >= 97 && code <= 122) return true\n  return false\n}\n\nexport function doEqualsIgnoreCase(a: string, b: string, stopAt = a.length): boolean {\n  if (typeof a !== 'string' || typeof b !== 'string') {\n    return false\n  }\n  for (let i = 0; i < stopAt; i++) {\n    const codeA = a.charCodeAt(i)\n    const codeB = b.charCodeAt(i)\n    if (codeA === codeB) {\n      continue\n    }\n    // a-z A-Z\n    if (isAlphabet(codeA) && isAlphabet(codeB)) {\n      const diff = Math.abs(codeA - codeB)\n      if (diff !== 0 && diff !== 32) {\n        return false\n      }\n    }\n    // Any other charcode\n    else {\n      if (String.fromCharCode(codeA).toLowerCase() !== String.fromCharCode(codeB).toLowerCase()) {\n        return false\n      }\n    }\n  }\n  return true\n}\n\nexport function equalsIgnoreCase(a: string, b: string): boolean {\n  const len1 = a ? a.length : 0\n  const len2 = b ? b.length : 0\n  if (len1 !== len2) return false\n  return doEqualsIgnoreCase(a, b)\n}\n\nexport function contentToLines(content: string, eol: boolean): string[] {\n  if (eol && content.endsWith('\\n')) {\n    return content.slice(0, -1).split('\\n')\n  }\n  return content.split('\\n')\n}\n\nfunction hasUpperCase(str: string): boolean {\n  for (let i = 0, l = str.length; i < l; i++) {\n    let code = str.charCodeAt(i)\n    if (code >= 65 && code <= 90) {\n      return true\n    }\n  }\n  return false\n}\n\nfunction smartMatch(a: string, b: string): boolean {\n  if (a === b) return true\n  let c = b.charCodeAt(0)\n  if (c >= 65 && c <= 90) {\n    if (c + 32 === a.charCodeAt(0)) return true\n  }\n  return false\n}\n\n// check if string smartcase include the other string\nexport function smartcaseIndex(input: string, other: string): number {\n  if (input.length > other.length) return -1\n  if (input.length === 0) return 0\n  if (!hasUpperCase(input)) {\n    return other.toLowerCase().indexOf(input)\n  }\n  let total = input.length\n  let checked = 0\n  for (let i = 0; i < other.length; i++) {\n    let ch = other[i]\n    if (smartMatch(input[checked], ch)) {\n      checked++\n      if (checked === total) {\n        return i - checked + 1\n      }\n    } else if (checked > 0) {\n      i = i - checked\n      checked = 0\n    }\n  }\n  return -1\n}\n\n/**\n * For faster convert sequence utf16 character index to byte index\n */\nexport function bytes(text: string, max?: number): (characterIndex: number) => number {\n  max = max ?? text.length\n  let lens = new Uint8Array(max)\n  let ascii = true\n  let prevCodePoint: number | undefined\n  for (let i = 0; i < max; i++) {\n    let code = text.charCodeAt(i)\n    let len: number\n    if (isLowSurrogate(code)) {\n      if (prevCodePoint && isHighSurrogate(prevCodePoint)) {\n        len = 1\n      } else {\n        len = 3\n      }\n    } else {\n      len = utf8_code2len(code)\n    }\n    if (ascii && len > 1) ascii = false\n    lens[i] = len\n    prevCodePoint = code\n  }\n  return characterIndex => {\n    if (characterIndex === 0) return 0\n    if (ascii) return Math.min(characterIndex, max)\n    let res = 0\n    for (let i = 0; i < Math.min(characterIndex, max); i++) {\n      res += lens[i]\n    }\n    return res\n  }\n}\n\n/**\n * Unicode class.\n */\nexport type UnicodeClass =\n  | \"ascii\"\n  | \"punctuation\"\n  | \"space\"\n  | \"word\"\n  | \"hiragana\"\n  | \"katakana\"\n  | \"cjkideograph\"\n  | \"hangulsyllable\"\n  | \"superscript\"\n  | \"subscript\"\n  | \"braille\"\n  | \"other\"\n\n// Unicode class ranges. This list is based on Neovim's classification.\n// reference: https://github.com/neovim/neovim/blob/052e048db676ef3e68efc497c02902e3d43e6255/src/nvim/mbyte.c#L1229-L1305\nconst nonAsciiUnicodeClassRanges = [\n  [0x037e, 0x037e, \"punctuation\"],\n  [0x0387, 0x0387, \"punctuation\"],\n  [0x055a, 0x055f, \"punctuation\"],\n  [0x0589, 0x0589, \"punctuation\"],\n  [0x05be, 0x05be, \"punctuation\"],\n  [0x05c0, 0x05c0, \"punctuation\"],\n  [0x05c3, 0x05c3, \"punctuation\"],\n  [0x05f3, 0x05f4, \"punctuation\"],\n  [0x060c, 0x060c, \"punctuation\"],\n  [0x061b, 0x061b, \"punctuation\"],\n  [0x061f, 0x061f, \"punctuation\"],\n  [0x066a, 0x066d, \"punctuation\"],\n  [0x06d4, 0x06d4, \"punctuation\"],\n  [0x0700, 0x070d, \"punctuation\"],\n  [0x0964, 0x0965, \"punctuation\"],\n  [0x0970, 0x0970, \"punctuation\"],\n  [0x0df4, 0x0df4, \"punctuation\"],\n  [0x0e4f, 0x0e4f, \"punctuation\"],\n  [0x0e5a, 0x0e5b, \"punctuation\"],\n  [0x0f04, 0x0f12, \"punctuation\"],\n  [0x0f3a, 0x0f3d, \"punctuation\"],\n  [0x0f85, 0x0f85, \"punctuation\"],\n  [0x104a, 0x104f, \"punctuation\"],\n  [0x10fb, 0x10fb, \"punctuation\"],\n  [0x1361, 0x1368, \"punctuation\"],\n  [0x166d, 0x166e, \"punctuation\"],\n  [0x1680, 0x1680, \"space\"],\n  [0x169b, 0x169c, \"punctuation\"],\n  [0x16eb, 0x16ed, \"punctuation\"],\n  [0x1735, 0x1736, \"punctuation\"],\n  [0x17d4, 0x17dc, \"punctuation\"],\n  [0x1800, 0x180a, \"punctuation\"],\n  [0x2000, 0x200b, \"space\"],\n  [0x200c, 0x2027, \"punctuation\"],\n  [0x2028, 0x2029, \"space\"],\n  [0x202a, 0x202e, \"punctuation\"],\n  [0x202f, 0x202f, \"space\"],\n  [0x2030, 0x205e, \"punctuation\"],\n  [0x205f, 0x205f, \"space\"],\n  [0x2060, 0x27ff, \"punctuation\"],\n  [0x2070, 0x207f, \"superscript\"],\n  [0x2080, 0x2094, \"subscript\"],\n  [0x20a0, 0x27ff, \"punctuation\"],\n  [0x2800, 0x28ff, \"braille\"],\n  [0x2900, 0x2998, \"punctuation\"],\n  [0x29d8, 0x29db, \"punctuation\"],\n  [0x29fc, 0x29fd, \"punctuation\"],\n  [0x2e00, 0x2e7f, \"punctuation\"],\n  [0x3000, 0x3000, \"space\"],\n  [0x3001, 0x3020, \"punctuation\"],\n  [0x3030, 0x3030, \"punctuation\"],\n  [0x303d, 0x303d, \"punctuation\"],\n  [0x3040, 0x309f, \"hiragana\"],\n  [0x30a0, 0x30ff, \"katakana\"],\n  [0x3300, 0x9fff, \"cjkideograph\"],\n  [0xac00, 0xd7a3, \"hangulsyllable\"],\n  [0xf900, 0xfaff, \"cjkideograph\"],\n  [0xfd3e, 0xfd3f, \"punctuation\"],\n  [0xfe30, 0xfe6b, \"punctuation\"],\n  [0xff00, 0xff0f, \"punctuation\"],\n  [0xff1a, 0xff20, \"punctuation\"],\n  [0xff3b, 0xff40, \"punctuation\"],\n  [0xff5b, 0xff65, \"punctuation\"],\n  [0x1d000, 0x1d24f, \"other\"],\n  [0x1d400, 0x1d7ff, \"other\"],\n  [0x1f000, 0x1f2ff, \"other\"],\n  [0x1f300, 0x1f9ff, \"other\"],\n  [0x20000, 0x2a6df, \"cjkideograph\"],\n  [0x2a700, 0x2b73f, \"cjkideograph\"],\n  [0x2b740, 0x2b81f, \"cjkideograph\"],\n  [0x2f800, 0x2fa1f, \"cjkideograph\"],\n] as const\n\n/**\n * Get class of a Unicode character.\n */\nexport function getUnicodeClass(char: string): UnicodeClass {\n  if (char == null || char.length === 0) return \"other\"\n\n  const charCode = char.charCodeAt(0)\n  // Check for ASCII character\n  if (charCode <= 0x7f) {\n    if (charCode === 0) return \"other\"\n    if (/\\s/.test(char)) return \"space\"\n    if (/\\w/.test(char)) return \"word\"\n    return \"punctuation\"\n  }\n\n  for (const [start, end, category] of nonAsciiUnicodeClassRanges) {\n    if (start <= charCode && charCode <= end) {\n      return category\n    }\n  }\n\n  return \"other\"\n}\n"
  },
  {
    "path": "src/util/textedit.ts",
    "content": "'use strict'\nimport { AnnotatedTextEdit, ChangeAnnotation, Position, Range, SnippetTextEdit, TextDocumentEdit, TextEdit, WorkspaceEdit } from 'vscode-languageserver-types'\nimport { LinesTextDocument } from '../model/textdocument'\nimport { DocumentChange } from '../types'\nimport { isFalsyOrEmpty } from './array'\nimport { diffLines } from './diff'\nimport { equals, toObject } from './object'\nimport { comparePosition, emptyRange, getEnd, samePosition, toValidRange } from './position'\nimport { byteIndex, contentToLines, toText } from './string'\n\nexport type TextChangeItem = [string[], number, number, number, number]\n\nexport function getStartLine(edit: TextEdit): number {\n  let { start, end } = edit.range\n  if (edit.newText.endsWith('\\n') && start.line == end.line && start.character == 0 && end.character == 0) {\n    return start.line - 1\n  }\n  return start.line\n}\n\nexport function lineCountChange(edit: TextEdit): number {\n  let { newText } = edit\n  let range = getWellformedRange(edit.range)\n  let n = range.end.line - range.start.line\n  return newText.split(/\\r?\\n/).length - n - 1\n}\n\nexport function getWellformedRange(range: Range): Range {\n  const start = range.start\n  const end = range.end\n  if (start.line > end.line || (start.line === end.line && start.character > end.character)) {\n    return { start: end, end: start }\n  }\n  return range\n}\n\nexport function getWellformedEdit(textEdit: TextEdit) {\n  const range = getWellformedRange(textEdit.range)\n  if (range !== textEdit.range) {\n    return { newText: textEdit.newText, range }\n  }\n  return textEdit\n}\n\nfunction mergeSort<T>(data: T[], compare: (a: T, b: T) => number): T[] {\n  if (data.length <= 1) {\n    // sorted\n    return data\n  }\n  const p = (data.length / 2) | 0\n  const left = data.slice(0, p)\n  const right = data.slice(p)\n  mergeSort(left, compare)\n  mergeSort(right, compare)\n  let leftIdx = 0\n  let rightIdx = 0\n  let i = 0\n  while (leftIdx < left.length && rightIdx < right.length) {\n    let ret = compare(left[leftIdx], right[rightIdx])\n    if (ret <= 0) {\n      // smaller_equal -> take left to preserve order\n      data[i++] = left[leftIdx++]\n    } else {\n      // greater -> take right\n      data[i++] = right[rightIdx++]\n    }\n  }\n  while (leftIdx < left.length) {\n    data[i++] = left[leftIdx++]\n  }\n  while (rightIdx < right.length) {\n    data[i++] = right[rightIdx++]\n  }\n  return data\n}\n\nexport function mergeSortEdits<T extends { range: Range }>(edits: T[]): T[] {\n  return mergeSort(edits, (a, b) => {\n    let diff = a.range.start.line - b.range.start.line\n    if (diff === 0) {\n      return a.range.start.character - b.range.start.character\n    }\n    return diff\n  })\n}\n\nexport function emptyTextEdit(edit: TextEdit): boolean {\n  return emptyRange(edit.range) && edit.newText.length === 0\n}\n\nexport function emptyWorkspaceEdit(edit: WorkspaceEdit): boolean {\n  let { changes, documentChanges } = edit\n  if (documentChanges && documentChanges.length) return false\n  if (changes && Object.keys(changes).length) return false\n  return true\n}\n\nexport function getRangesFromEdit(uri: string, edit: WorkspaceEdit): Range[] | undefined {\n  let { changes, documentChanges } = edit\n  if (changes) {\n    let edits = changes[uri]\n    return edits ? edits.map(e => e.range) : undefined\n  } else if (Array.isArray(documentChanges)) {\n    for (let c of documentChanges) {\n      if (TextDocumentEdit.is(c) && c.textDocument.uri == uri) {\n        return c.edits.map(e => e.range)\n      }\n    }\n  }\n  return undefined\n}\n\nexport function getConfirmAnnotations(changes: ReadonlyArray<DocumentChange>, changeAnnotations: { [id: string]: ChangeAnnotation }): ReadonlyArray<string> {\n  let keys: string[] = []\n  const add = (key: string | undefined) => {\n    if (key && !keys.includes(key) && changeAnnotations[key]?.needsConfirmation) keys.push(key)\n  }\n  for (let change of changes) {\n    if (TextDocumentEdit.is(change)) {\n      change.edits.forEach(edit => {\n        add(edit['annotationId'])\n      })\n    } else {\n      add(change.annotationId)\n    }\n  }\n  return keys\n}\n\nexport function isDeniedEdit(edit: TextEdit | AnnotatedTextEdit | SnippetTextEdit, denied: string[]): boolean {\n  if (AnnotatedTextEdit.is(edit) && denied.includes(edit.annotationId)) return true\n  return false\n}\n\n/**\n * Create new changes with denied filtered\n */\nexport function createFilteredChanges(documentChanges: DocumentChange[], denied: string[]): DocumentChange[] {\n  let changes: DocumentChange[] = []\n  documentChanges.forEach(change => {\n    if (TextDocumentEdit.is(change)) {\n      let edits = change.edits.filter(edit => {\n        return !isDeniedEdit(edit, denied)\n      })\n      if (edits.length > 0) {\n        changes.push({ textDocument: change.textDocument, edits })\n      }\n    } else if (!denied.includes(change.annotationId)) {\n      changes.push(change)\n    }\n  })\n  return changes\n}\n\nexport function getAnnotationKey(change: DocumentChange): string | undefined {\n  let key: string\n  if (TextDocumentEdit.is(change)) {\n    if (AnnotatedTextEdit.is(change.edits[0])) {\n      key = change.edits[0].annotationId\n    }\n  } else {\n    key = change.annotationId\n  }\n  return key\n}\n\nexport function toDocumentChanges(edit: WorkspaceEdit): DocumentChange[] {\n  if (edit.documentChanges) return edit.documentChanges\n  let changes: DocumentChange[] = []\n  for (let [uri, edits] of Object.entries(toObject(edit.changes))) {\n    changes.push({ textDocument: { uri, version: null }, edits })\n  }\n  return changes\n}\n\n/**\n * Filter unnecessary edits and fix edits.\n */\nexport function filterSortEdits(textDocument: LinesTextDocument, edits: TextEdit[]): TextEdit[] {\n  let res: TextEdit[] = []\n  let end = textDocument.end\n  let checkEnd = end.line > 0 && end.character == 0\n  let prevDelete: Position | undefined\n  for (let i = 0; i < edits.length; i++) {\n    let edit = edits[i]\n    let { newText, range } = edit\n    let max = (textDocument.lines[range.end.line] ?? '').length\n    range = toValidRange(edit.range, max)\n    if (prevDelete) {\n      // merge possible delete, insert edits.\n      if (samePosition(prevDelete, range.start) && emptyRange(range) && newText.length > 0) {\n        let last = res[res.length - 1]\n        last.newText = newText\n        prevDelete = undefined\n        continue\n      }\n      prevDelete = undefined\n    }\n    if (newText.includes('\\r')) newText = newText.replace(/\\r\\n/g, '\\n')\n    let d = comparePosition(range.end, end)\n    if (d > 0) range.end = { line: end.line, character: end.character }\n    if (textDocument.getText(range) !== newText) {\n      // Adjust textEdit to make it acceptable by nvim_buf_set_text\n      if (d === 0 && checkEnd && !emptyRange(range) && newText.endsWith('\\n')) {\n        newText = newText.slice(0, -1)\n        let text = textDocument.lines[end.line - 1]\n        range.end = Position.create(end.line - 1, text.length)\n      } else if (newText.length == 0) {\n        prevDelete = range.start\n      }\n      res.push({ range, newText })\n    }\n  }\n  return mergeSortEdits(res)\n}\n\n/**\n * Apply valid & sorted edits\n */\nexport function applyEdits(document: LinesTextDocument, edits: TextEdit[] | undefined): string[] | undefined {\n  if (isFalsyOrEmpty(edits)) return undefined\n  if (edits.length == 1) {\n    let { start, end } = edits[0].range\n    let { lines } = document\n    let sl = lines[start.line] ?? ''\n    let el = lines[end.line] ?? ''\n    let content = sl.substring(0, start.character) + edits[0].newText + el.substring(end.character)\n    if (end.line >= lines.length && document.eol) {\n      if (content == '') {\n        const result = [...lines.slice(0, start.line)]\n        return result.length === 0 ? [''] : result\n      }\n      if (content.endsWith('\\n')) content = content.slice(0, -1)\n      return [...lines.slice(0, start.line), ...content.split('\\n')]\n    }\n    return [...lines.slice(0, start.line), ...content.split('\\n'), ...lines.slice(end.line + 1)]\n  }\n  let text = document.getText()\n  let lastModifiedOffset = 0\n  const spans = []\n  for (const e of edits) {\n    let startOffset = document.offsetAt(e.range.start)\n    if (startOffset < lastModifiedOffset) {\n      throw new Error('Overlapping edit')\n    }\n    else if (startOffset > lastModifiedOffset) {\n      spans.push(text.substring(lastModifiedOffset, startOffset))\n    }\n    if (e.newText.length) {\n      spans.push(e.newText)\n    }\n    lastModifiedOffset = document.offsetAt(e.range.end)\n  }\n  spans.push(text.substring(lastModifiedOffset))\n  let result = spans.join('')\n  if (result === text) return undefined\n  return contentToLines(result, document.eol)\n}\n\nexport function getRangeText(lines: ReadonlyArray<string>, range: Range): string {\n  let result: string[] = []\n  const { start, end } = range\n  if (start.line === end.line) {\n    let line = toText(lines[start.line])\n    return line.slice(start.character, end.character)\n  }\n  for (let i = start.line; i <= end.line; i++) {\n    let line = toText(lines[i])\n    let text = line\n    if (i === start.line) {\n      text = line.slice(start.character)\n    } else if (i === end.line) {\n      text = line.slice(0, end.character)\n    }\n    result.push(text)\n  }\n  return result.join('\\n')\n}\n\nexport function validEdit(edit: TextEdit): boolean {\n  let { range, newText } = edit\n  if (!newText.endsWith('\\n')) return false\n  if (range.end.character !== 0) return false\n  return true\n}\n\nexport function toTextChanges(lines: ReadonlyArray<string>, edits: TextEdit[]): TextChangeItem[] {\n  if (edits.length === 0) return []\n  for (let edit of edits) {\n    if (edit.range.end.line > lines.length) return []\n    if (edit.range.end.line == lines.length) {\n      // should only be insert at the end\n      if (!validEdit(edit)) return []\n      let line = lines.length - 1\n      let character = lines[line].length\n      if (emptyRange(edit.range)) {\n        // convert to insert at the end of last line.\n        edit.range = Range.create(line, character, line, character)\n        edit.newText = '\\n' + edit.newText.slice(0, -1)\n      } else {\n        // convert to replace to the end of last line.\n        const start = edit.range.start\n        edit.range = Range.create(start, Position.create(line, character))\n        edit.newText = edit.newText.slice(0, -1)\n      }\n    }\n  }\n  return edits.map(o => {\n    const oldText = getRangeText(lines, o.range)\n    let edit = reduceTextEdit(o, oldText)\n    let { start, end } = edit.range\n    let sl = toText(lines[start.line])\n    let sc = byteIndex(sl, start.character)\n    let el = end.line == start.line ? sl : toText(lines[end.line])\n    let ec = byteIndex(el, end.character)\n    let { newText } = edit\n    return [newText.length > 0 ? newText.split('\\n') : [], start.line, sc, end.line, ec]\n  })\n}\n\nexport function getChangedPosition(start: Position, edit: TextEdit): { line: number; character: number } {\n  let { range, newText } = edit\n  if (comparePosition(range.end, start) <= 0) {\n    let lines = newText.split('\\n')\n    let lineCount = lines.length - (range.end.line - range.start.line) - 1\n    let character = start.character\n    if (range.end.line == start.line) {\n      let last = lines[lines.length - 1].length\n      if (lines.length > 1) {\n        character = last + character - range.end.character\n      } else {\n        character = range.start.character + last + character - range.end.character\n      }\n    }\n    return { line: lineCount, character: character - start.character }\n  }\n  return { line: 0, character: 0 }\n}\n\nexport function getPosition(start: Position, edit: TextEdit): Position {\n  let { line, character } = start\n  let { range, newText } = edit\n  let { end } = range\n  let lines = newText.split('\\n')\n  let lineCount = lines.length - (end.line - range.start.line) - 1\n  let c = range.end.line - start.line\n  if (c > 0) return { line, character }\n  if (c < 0) return { line: line + lineCount, character }\n  if (lines.length > 1) {\n    let last = lines[lines.length - 1].length\n    return { line: line + lineCount, character: last + character - end.character }\n  }\n  let d = range.start.character - range.end.character\n  return { line: line + lineCount, character: d + newText.length + character }\n}\n\n/**\n * Get new position from sorted edits\n */\nexport function getPositionFromEdits(start: Position, edits: TextEdit[]): Position {\n  let position = Position.create(start.line, start.character)\n  let before = false\n  for (let i = edits.length - 1; i >= 0; i--) {\n    let edit = edits[i]\n    if (before) {\n      position.line += lineCountChange(edit)\n      continue\n    }\n    let d = comparePosition(edit.range.end, position)\n    if (d > 0) continue\n    if (edit.range.end.line == position.line) {\n      position = getPosition(position, edit)\n    } else {\n      before = true\n      position.line += lineCountChange(edit)\n    }\n  }\n  return position\n}\n\nexport function getChangedLineCount(start: Position, edits: TextEdit[]): number {\n  let total = 0\n  for (let edit of edits) {\n    let r = getWellformedRange(edit.range)\n    if (comparePosition(r.end, start) <= 0) {\n      total += lineCountChange(edit)\n    }\n  }\n  return total\n}\n\n/**\n * Merge sorted edits to single textedit\n */\nexport function mergeTextEdits(edits: TextEdit[], oldLines: ReadonlyArray<string>, newLines: ReadonlyArray<string>): TextEdit {\n  let start = edits[0].range.start\n  let end = edits[edits.length - 1].range.end\n  let lr = oldLines.length - end.line\n  let cr = (oldLines[end.line] ?? '').length - end.character\n  let line = newLines.length - lr\n  let character = (newLines[line] ?? '').length - cr\n  let newText = getRangeText(newLines, Range.create(start, Position.create(line, character)))\n  return TextEdit.replace(Range.create(start, end), newText)\n}\n\n/*\n * Avoid change unnecessary range of text.\n */\nexport function reduceTextEdit(edit: TextEdit, oldText: string): TextEdit {\n  if (oldText.length === 0) return edit\n  let { range, newText } = edit\n  let ol = oldText.length\n  let nl = newText.length\n  if (ol === 0 || nl === 0) return edit\n  let { start, end } = range\n  let bo = 0\n  for (let i = 1; i <= Math.min(nl, ol); i++) {\n    if (newText[i - 1] === oldText[i - 1]) {\n      bo = i\n    } else {\n      break\n    }\n  }\n  let eo = 0\n  let t = Math.min(nl - bo, ol - bo)\n  if (t > 0) {\n    for (let i = 1; i <= t; i++) {\n      if (newText[nl - i] === oldText[ol - i]) {\n        eo = i\n      } else {\n        break\n      }\n    }\n  }\n  let text = eo == 0 ? newText.slice(bo) : newText.slice(bo, -eo)\n  if (bo > 0) start = getEnd(start, newText.slice(0, bo))\n  if (eo > 0) end = getEnd(range.start, oldText.slice(0, -eo))\n  return TextEdit.replace(Range.create(start, end), text)\n}\n\nexport function getRevertEdit(oldLines: ReadonlyArray<string>, newLines: ReadonlyArray<string>, startLine: number): TextEdit | undefined {\n  if (equals(oldLines, newLines)) return undefined\n  let changed = diffLines(oldLines, newLines, startLine)\n  let original = oldLines.slice(changed.start, changed.end)\n  let range = Range.create(changed.start, 0, changed.start + changed.replacement.length, 0)\n  return TextEdit.replace(range, original.join('\\n') + (original.length > 0 ? '\\n' : ''))\n}\n"
  },
  {
    "path": "src/util/timing.ts",
    "content": "'use strict'\nimport { createLogger } from '../logger'\nconst logger = createLogger('timing')\n\ninterface Timing {\n  start(label?: string): void\n  stop(): void\n}\n\n/**\n * Trace the duration and show error on timeout\n */\nexport function createTiming(name: string, timeout?: number): Timing {\n  let start: number\n  let timer: NodeJS.Timeout\n  let _label: string\n  return {\n    start(label?: string) {\n      _label = label\n      start = Date.now()\n      clearTimeout(timer)\n      if (timeout) {\n        timer = setTimeout(() => {\n          logger.error(`${name} timeout after ${timeout}ms`)\n        }, timeout)\n        timer.unref()\n      }\n    },\n    stop() {\n      clearTimeout(timer)\n      logger.trace(`${name}${_label ? ` ${_label}` : ''} cost:`, Date.now() - start)\n    }\n  }\n}\n"
  },
  {
    "path": "src/window.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport type { Position, Range } from 'vscode-languageserver-types'\nimport type { WorkspaceConfiguration } from './configuration/types'\nimport channels from './core/channels'\nimport { Dialogs, InputOptions, Item, MenuOption, QuickPickConfig, QuickPickOptions } from './core/dialogs'\nimport type { TextEditor } from './core/editors'\nimport { HighlightDiff, Highlights } from './core/highlights'\nimport { Notifications, ProgressOptions } from './core/notifications'\nimport Terminals, { OpenTerminalOption, TerminalResult } from './core/terminals'\nimport * as ui from './core/ui'\nimport type Cursors from './cursors/index'\nimport type { Dialog, DialogConfig } from './model/dialog'\nimport type { FloatWinConfig } from './model/floatFactory'\nimport InputBox, { InputPreference } from './model/input'\nimport type { MenuItem } from './model/menu'\nimport type { MessageItem, NotificationConfig } from './model/notification'\nimport type { Progress } from './model/progress'\nimport type QuickPick from './model/quickpick'\nimport type { StatusBarItem } from './model/status'\nimport type { TerminalModel, TerminalOptions } from './model/terminal'\nimport type { TreeView, TreeViewOptions } from './tree'\nimport type { FloatConfig, FloatFactory, HighlightItem, OutputChannel, QuickPickItem } from './types'\nimport { toObject } from './util/object'\nimport { CancellationToken, Event } from './util/protocol'\nimport type { Workspace } from './workspace'\n\nexport interface StatusItemOption {\n  progress?: boolean\n}\n\nexport class Window {\n  private nvim: Neovim\n  public highlights: Highlights = new Highlights()\n  private terminalManager: Terminals = new Terminals()\n  public readonly cursors: Cursors\n  public readonly dialogs = new Dialogs()\n  public readonly notifications = new Notifications(this.dialogs)\n  private workspace: Workspace\n  constructor() {\n    Object.defineProperty(this.highlights, 'nvim', {\n      get: () => this.nvim\n    })\n    Object.defineProperty(this.dialogs, 'nvim', {\n      get: () => this.nvim\n    })\n    Object.defineProperty(this.dialogs, 'configuration', {\n      get: () => this.workspace.initialConfiguration\n    })\n    Object.defineProperty(this.notifications, 'nvim', {\n      get: () => this.nvim\n    })\n    Object.defineProperty(this.notifications, 'configuration', {\n      get: () => this.workspace.initialConfiguration\n    })\n    Object.defineProperty(this.notifications, 'statusLine', {\n      get: () => this.workspace.statusLine\n    })\n  }\n\n  public get activeTextEditor(): TextEditor | undefined {\n    return this.workspace.editors.activeTextEditor\n  }\n\n  public get visibleTextEditors(): TextEditor[] {\n    return this.workspace.editors.visibleTextEditors\n  }\n\n  public get onDidTabClose(): Event<number> {\n    return this.workspace.editors.onDidTabClose\n  }\n\n  public get onDidChangeActiveTextEditor(): Event<TextEditor | undefined> {\n    return this.workspace.editors.onDidChangeActiveTextEditor\n  }\n\n  public get onDidChangeVisibleTextEditors(): Event<ReadonlyArray<TextEditor>> {\n    return this.workspace.editors.onDidChangeVisibleTextEditors\n  }\n\n  public get terminals(): ReadonlyArray<TerminalModel> {\n    return this.terminalManager.terminals\n  }\n\n  public get onDidOpenTerminal(): Event<TerminalModel> {\n    return this.terminalManager.onDidOpenTerminal\n  }\n\n  public get onDidCloseTerminal(): Event<TerminalModel> {\n    return this.terminalManager.onDidCloseTerminal\n  }\n\n  public async createTerminal(opts: TerminalOptions): Promise<TerminalModel> {\n    return await this.terminalManager.createTerminal(this.nvim, opts)\n  }\n\n  /**\n   * Run command in vim terminal for result\n   * @param cmd Command to run.\n   * @param cwd Cwd of terminal, default to result of |getcwd()|.\n   */\n  public async runTerminalCommand(cmd: string, cwd?: string, keepfocus = false): Promise<TerminalResult> {\n    return await this.terminalManager.runTerminalCommand(this.nvim, cmd, cwd, keepfocus)\n  }\n\n  /**\n   * Open terminal window.\n   * @param cmd Command to run.\n   * @param opts Terminal option.\n   * @returns number buffer number of terminal\n   */\n  public async openTerminal(cmd: string, opts?: OpenTerminalOption): Promise<number> {\n    return await this.terminalManager.openTerminal(this.nvim, cmd, opts)\n  }\n\n  /**\n   * Reveal message with message type.\n   * @param msg Message text to show.\n   * @param messageType Type of message, could be `error` `warning` and `more`, default to `more`\n   */\n  public showMessage(msg: string, messageType: ui.MsgTypes = 'more'): void {\n    this.notifications.echoMessages(msg, messageType)\n  }\n\n  /**\n   * Create a new output channel\n   * @param name Unique name of output channel.\n   * @returns A new output channel.\n   */\n  public createOutputChannel(name: string): OutputChannel {\n    return channels.create(name, this.nvim)\n  }\n\n  /**\n   * Reveal buffer of output channel.\n   * @param name Name of output channel.\n   * @param cmd command for open output channel.\n   * @param preserveFocus Preserve window focus when true.\n   */\n  public showOutputChannel(name: string, cmd?: string, preserveFocus?: boolean): void {\n    let command = cmd ? cmd : this.configuration.get<string>('workspace.openOutputCommand', 'vs')\n    channels.show(name, command, preserveFocus)\n  }\n\n  /**\n   * Echo lines at the bottom of vim.\n   * @param lines Line list.\n   * @param truncate Truncate the lines to avoid 'press enter to continue' when true\n   */\n  public async echoLines(lines: string[], truncate = false): Promise<void> {\n    await ui.echoLines(this.nvim, this.workspace.env, lines, truncate)\n  }\n\n  /**\n   * Get current cursor position (line, character both 0 based).\n   * @returns Cursor position.\n   */\n  public getCursorPosition(): Promise<Position> {\n    return ui.getCursorPosition(this.nvim)\n  }\n\n  /**\n   * Move cursor to position.\n   * @param position LSP position.\n   */\n  public async moveTo(position: Position): Promise<void> {\n    await ui.moveTo(this.nvim, position, this.workspace.env.isVim)\n  }\n\n  /**\n   * Get selected range for current document\n   */\n  public getSelectedRange(mode: string): Promise<Range | null> {\n    return ui.getSelection(this.nvim, mode)\n  }\n\n  /**\n   * Visual select range of current document\n   */\n  public async selectRange(range: Range): Promise<void> {\n    await ui.selectRange(this.nvim, range, this.nvim.isVim)\n  }\n\n  /**\n   * Get current cursor character offset in document,\n   * length of line break would always be 1.\n   * @returns Character offset.\n   */\n  public getOffset(): Promise<number> {\n    return ui.getOffset(this.nvim)\n  }\n\n  /**\n   * Get screen position of current cursor(relative to editor),\n   * both `row` and `col` are 0 based.\n   * @returns Cursor screen position.\n   */\n  public getCursorScreenPosition(): Promise<ui.ScreenPosition> {\n    return ui.getCursorScreenPosition(this.nvim)\n  }\n\n  /**\n   * Create a {@link TreeView} instance.\n   * @param viewId Id of the view, used as title of TreeView when title doesn't exist.\n   * @param options Options for creating the {@link TreeView}\n   * @returns a {@link TreeView}.\n   */\n  public createTreeView<T>(viewId: string, options: TreeViewOptions<T>): TreeView<T> {\n    const BasicTreeView = require('./tree/TreeView').default\n    return new BasicTreeView(viewId, options)\n  }\n\n  /**\n   * Create statusbar item that would be included in `g:coc_status`.\n   * @param priority Higher priority item would be shown right.\n   * @param option\n   * @return A new status bar item.\n   */\n  public createStatusBarItem(priority = 0, option: StatusItemOption = {}): StatusBarItem {\n    return this.workspace.statusLine.createStatusBarItem(priority, option.progress)\n  }\n\n  /**\n   * Get diff from highlight items and current highlights on vim.\n   * Return null when buffer not loaded\n   * @param bufnr Buffer number\n   * @param ns Highlight namespace\n   * @param items Highlight items\n   * @param region 0 based start and end line count (end inclusive)\n   * @param token CancellationToken\n   * @returns {Promise<HighlightDiff | null>}\n   */\n  public async diffHighlights(bufnr: number, ns: string, items: HighlightItem[], region?: [number, number], token?: CancellationToken): Promise<HighlightDiff | null> {\n    return this.highlights.diffHighlights(bufnr, ns, items, region, token)\n  }\n\n  /**\n   * Create a FloatFactory, user's configurations are respected.\n   * @param {FloatWinConfig} conf - Float window configuration\n   * @returns {FloatFactory}\n   */\n  public createFloatFactory(conf: FloatWinConfig): FloatFactory {\n    let configuration = this.workspace.initialConfiguration\n    let defaults = toObject(configuration.get('floatFactory.floatConfig')) as FloatConfig\n    let markdownPreference = this.workspace.configurations.markdownPreference\n    return ui.createFloatFactory(this.workspace.nvim, Object.assign({ ...markdownPreference, maxWidth: 80 }, conf), defaults)\n  }\n\n  /**\n   * Show quickpick for single item, use `window.menuPick` for menu at current current position.\n   * @deprecated Use 'window.showMenuPicker()' or `window.showQuickPick` instead.\n   * @param items Label list.\n   * @param placeholder Prompt text, default to 'choose by number'.\n   * @returns Index of selected item, or -1 when canceled.\n   */\n  public async showQuickpick(items: string[], placeholder = 'Choose by number'): Promise<number> {\n    return await this.showMenuPicker(items, { title: placeholder, position: 'center' })\n  }\n\n  /**\n   * Shows a selection list.\n   */\n  public async showQuickPick(itemsOrItemsPromise: Item[] | Promise<Item[]>, options?: QuickPickOptions, token: CancellationToken = CancellationToken.None): Promise<Item | Item[] | undefined> {\n    return await this.dialogs.showQuickPick(itemsOrItemsPromise, options, token)\n  }\n\n  /**\n   * Creates a {@link QuickPick} to let the user pick an item or items from a\n   * list of items of type T.\n   *\n   * Note that in many cases the more convenient {@link window.showQuickPick}\n   * is easier to use. {@link window.createQuickPick} should be used\n   * when {@link window.showQuickPick} does not offer the required flexibility.\n   * @return A new {@link QuickPick}.\n   */\n  public async createQuickPick<T extends QuickPickItem>(config: QuickPickConfig<T> = {}): Promise<QuickPick<T>> {\n    return await this.dialogs.createQuickPick(config)\n  }\n\n  public async requestInputList(prompt: string, items: string[]): Promise<number> {\n    if (items.length > this.workspace.env.lines) {\n      items = items.slice(0, this.workspace.env.lines - 2)\n    }\n    return await this.dialogs.requestInputList(prompt, items)\n  }\n\n  /**\n   * Show menu picker at current cursor position.\n   * @param items Array of texts.\n   * @param option Options for menu.\n   * @param token A token that can be used to signal cancellation.\n   * @returns Selected index (0 based), -1 when canceled.\n   */\n  public async showMenuPicker(items: string[] | MenuItem[], option?: MenuOption, token?: CancellationToken): Promise<number> {\n    return await this.dialogs.showMenuPicker(items, option, token)\n  }\n\n  /**\n   * Prompt user for confirm, a float/popup window would be used when possible,\n   * use vim's |confirm()| function as callback.\n   * @param title The prompt text.\n   * @returns Result of confirm.\n   */\n  public async showPrompt(title: string): Promise<boolean> {\n    return await this.dialogs.showPrompt(title)\n  }\n\n  /**\n   * Show dialog window at the center of screen.\n   * Note that the dialog would always be closed after button click.\n   * @param config Dialog configuration.\n   * @returns Dialog or null when dialog can't work.\n   */\n  public async showDialog(config: DialogConfig): Promise<Dialog | null> {\n    return await this.dialogs.showDialog(config)\n  }\n\n  /**\n   * Request input from user\n   * @param title Title text of prompt window.\n   * @param value Default value of input, empty text by default.\n   * @param {InputOptions} option for input window\n   * @returns {Promise<string>}\n   */\n  public async requestInput(title: string, value?: string, option?: InputOptions): Promise<string | undefined> {\n    return await this.dialogs.requestInput(title, this.workspace.env, value, option)\n  }\n\n  /**\n   * Creates and show a {@link InputBox} to let the user enter some text input.\n   * @return A new {@link InputBox}.\n   */\n  public async createInputBox(title: string, value: string | undefined, option?: InputPreference): Promise<InputBox> {\n    return await this.dialogs.createInputBox(title, value, option)\n  }\n\n  /**\n   * Show multiple picker at center of screen.\n   * Use `this.workspace.env.dialog` to check if dialog could work.\n   * @param items Array of QuickPickItem or string.\n   * @param title Title of picker dialog.\n   * @param token A token that can be used to signal cancellation.\n   * @return A promise that resolves to the selected items or `undefined`.\n   */\n  public async showPickerDialog(items: string[], title: string, token?: CancellationToken): Promise<string[] | undefined>\n  public async showPickerDialog<T extends QuickPickItem>(items: T[], title: string, token?: CancellationToken): Promise<T[] | undefined>\n  public async showPickerDialog(items: any, title: string, token?: CancellationToken): Promise<any | undefined> {\n    return await this.dialogs.showPickerDialog(items, title, token)\n  }\n\n  /**\n   * Show an information message to users. Optionally provide an array of items which will be presented as\n   * clickable buttons.\n   * @param message The message to show.\n   * @param items A set of items that will be rendered as actions in the message.\n   * @return Promise that resolves to the selected item or `undefined` when being dismissed.\n   */\n  public async showInformationMessage<T extends MessageItem | string>(message: string, ...items: T[]): Promise<T | undefined> {\n    return await this.notifications._showMessage('Info', message, items)\n  }\n\n  /**\n   * Show an warning message to users. Optionally provide an array of items which will be presented as\n   * clickable buttons.\n   * @param message The message to show.\n   * @param items A set of items that will be rendered as actions in the message.\n   * @return Promise that resolves to the selected item or `undefined` when being dismissed.\n   */\n  public async showWarningMessage<T extends MessageItem | string>(message: string, ...items: T[]): Promise<T | undefined> {\n    return await this.notifications._showMessage('Warning', message, items)\n  }\n\n  /**\n   * Show an error message to users. Optionally provide an array of items which will be presented as\n   * clickable buttons.\n   * @param message The message to show.\n   * @param items A set of items that will be rendered as actions in the message.\n   * @return Promise that resolves to the selected item or `undefined` when being dismissed.\n   */\n  public async showErrorMessage<T extends MessageItem | string>(message: string, ...items: T[]): Promise<T | undefined> {\n    return await this.notifications._showMessage('Error', message, items)\n  }\n\n  public async showNotification(config: NotificationConfig): Promise<void> {\n    let stack = Error().stack\n    await this.notifications.showNotification(config, stack)\n  }\n\n  /**\n   * Show progress in the editor. Progress is shown while running the given callback\n   * and while the promise it returned isn't resolved nor rejected.\n   */\n  public async withProgress<R>(options: ProgressOptions, task: (progress: Progress, token: CancellationToken) => Thenable<R>): Promise<R> {\n    return this.notifications.withProgress(options, task)\n  }\n\n  /**\n   * Apply highlight diffs, normally used with `window.diffHighlights`\n   *\n   * Timer is used to add highlights when there're too many highlight items to add,\n   * the highlight process won't be finished on that case.\n   * @param {number} bufnr - Buffer name\n   * @param {string} ns - Namespace\n   * @param {number} priority\n   * @param {HighlightDiff} diff\n   * @param {boolean} notify - Use notification, default false.\n   * @returns {Promise<void>}\n   */\n  public async applyDiffHighlights(bufnr: number, ns: string, priority: number, diff: HighlightDiff, notify = false): Promise<void> {\n    return this.highlights.applyDiffHighlights(bufnr, ns, priority, diff, notify)\n  }\n\n  /**\n   * Get visible ranges of bufnr with optional winid\n   */\n  public async getVisibleRanges(bufnr: number, winid?: number): Promise<[number, number][]> {\n    return await ui.getVisibleRanges(this.nvim, bufnr, winid)\n  }\n\n  private get configuration(): WorkspaceConfiguration {\n    return this.workspace.initialConfiguration\n  }\n\n  public dispose(): void {\n    this.terminalManager.dispose()\n  }\n}\n\nexport default new Window()\n"
  },
  {
    "path": "src/workspace.ts",
    "content": "'use strict'\nimport { Neovim } from '@chemzqm/neovim'\nimport type { DocumentFilter, DocumentSelector, WorkspaceFoldersChangeEvent } from 'vscode-languageserver-protocol'\nimport { TextDocument } from 'vscode-languageserver-textdocument'\nimport { CreateFileOptions, DeleteFileOptions, FormattingOptions, Location, LocationLink, Position, Range, RenameFileOptions, WorkspaceEdit, WorkspaceFolder } from 'vscode-languageserver-types'\nimport { URI } from 'vscode-uri'\nimport Configurations from './configuration'\nimport ConfigurationShape from './configuration/shape'\nimport type { ConfigurationResourceScope, WorkspaceConfiguration } from './configuration/types'\nimport Autocmds, { AutocmdOptionWithStack } from './core/autocmds'\nimport channels from './core/channels'\nimport ContentProvider from './core/contentProvider'\nimport Documents from './core/documents'\nimport Editors from './core/editors'\nimport { FileSystemWatcher, FileSystemWatcherManager } from './core/fileSystemWatcher'\nimport Files, { FileCreateEvent, FileDeleteEvent, FileRenameEvent, FileWillCreateEvent, FileWillDeleteEvent, FileWillRenameEvent, TextDocumentWillSaveEvent } from './core/files'\nimport { callAsync, createNameSpace, findUp, getWatchmanPath, has, resolveModule, score } from './core/funcs'\nimport Keymaps, { KeymapCallback, LocalMode, MapMode } from './core/keymaps'\nimport * as ui from './core/ui'\nimport Watchers from './core/watchers'\nimport WorkspaceFolderController from './core/workspaceFolder'\nimport events from './events'\nimport { createLogger } from './logger'\nimport BufferSync, { SyncItem } from './model/bufferSync'\nimport DB from './model/db'\nimport type Document from './model/document'\nimport { FuzzyMatch, FuzzyWasi, initFuzzyWasm } from './model/fuzzyMatch'\nimport Mru from './model/mru'\nimport StatusLine from './model/status'\nimport { StrWidth } from './model/strwidth'\nimport TabsModel from './model/tabs'\nimport Task from './model/task'\nimport { LinesTextDocument } from './model/textdocument'\nimport { TextDocumentContentProvider } from './provider'\nimport { Autocmd, DidChangeTextDocumentParams, Env, FileWatchConfig, GlobPattern, IConfigurationChangeEvent, KeymapOption, LocationWithTarget, QuickfixItem, TextDocumentMatch } from './types'\nimport { defaultValue, getConditionValue } from './util'\nimport { APIVERSION, VERSION, dataHome, pluginRoot, userConfigFile } from './util/constants'\nimport { onUnexpectedError } from './util/errors'\nimport { FileType, getFileType } from './util/fs'\nimport { IJSONSchema } from './util/jsonSchema'\nimport { path } from './util/node'\nimport { toObject } from './util/object'\nimport { runCommand } from './util/processes'\nimport { CancellationToken, Disposable, Emitter, Event } from './util/protocol'\nconst logger = createLogger('workspace')\n\nconst methods = [\n  'showMessage', 'runTerminalCommand', 'openTerminal', 'showQuickpick',\n  'menuPick', 'openLocalConfig', 'showPrompt', 'createStatusBarItem', 'createOutputChannel',\n  'showOutputChannel', 'requestInput', 'echoLines', 'getCursorPosition', 'moveTo',\n  'getOffset', 'getSelectedRange', 'selectRange', 'createTerminal',\n]\n\nexport class Workspace {\n  public readonly onDidChangeConfiguration: Event<IConfigurationChangeEvent>\n  public readonly onDidOpenTextDocument: Event<LinesTextDocument>\n  public readonly onDidCloseTextDocument: Event<LinesTextDocument>\n  public readonly onDidChangeTextDocument: Event<DidChangeTextDocumentParams>\n  public readonly onDidSaveTextDocument: Event<LinesTextDocument>\n  public readonly onWillSaveTextDocument: Event<TextDocumentWillSaveEvent>\n  public readonly onDidChangeWorkspaceFolders: Event<WorkspaceFoldersChangeEvent>\n  public readonly onDidCreateFiles: Event<FileCreateEvent>\n  public readonly onDidRenameFiles: Event<FileRenameEvent>\n  public readonly onDidDeleteFiles: Event<FileDeleteEvent>\n  public readonly onWillCreateFiles: Event<FileWillCreateEvent>\n  public readonly onWillRenameFiles: Event<FileWillRenameEvent>\n  public readonly onWillDeleteFiles: Event<FileWillDeleteEvent>\n  public readonly nvim: Neovim\n  public readonly configurations: Configurations\n  public readonly workspaceFolderControl: WorkspaceFolderController\n  public readonly documentsManager: Documents\n  public readonly contentProvider: ContentProvider\n  public readonly autocmds: Autocmds\n  public readonly watchers: Watchers\n  public readonly keymaps: Keymaps\n  public readonly files: Files\n  public readonly fileSystemWatchers: FileSystemWatcherManager\n  public readonly editors: Editors\n  public readonly tabs: TabsModel\n  public readonly isTrusted = true\n  public statusLine = new StatusLine()\n  private _onDidRuntimePathChange = new Emitter<string[]>()\n  public readonly onDidRuntimePathChange: Event<string[]> = this._onDidRuntimePathChange.event\n\n  private fuzzyExports: FuzzyWasi\n  private strWidth: StrWidth\n  private _env: Env\n\n  constructor() {\n    void initFuzzyWasm().then(api => {\n      this.fuzzyExports = api\n    }, onUnexpectedError)\n    void StrWidth.create().then(strWidth => {\n      this.strWidth = strWidth\n    }, onUnexpectedError)\n    events.on('VimResized', (columns, lines) => {\n      Object.assign(toObject(this.env), { columns, lines })\n    })\n    Object.defineProperty(this.statusLine, 'nvim', {\n      get: () => this.nvim\n    })\n    this.configurations = new Configurations(userConfigFile, new ConfigurationShape(this))\n    this.workspaceFolderControl = new WorkspaceFolderController(this.configurations)\n    let documents = this.documentsManager = new Documents(this.configurations, this.workspaceFolderControl)\n    this.contentProvider = new ContentProvider(documents)\n    this.watchers = new Watchers()\n    this.autocmds = new Autocmds()\n    this.keymaps = new Keymaps()\n    this.files = new Files(documents, this.configurations, this.workspaceFolderControl, this.keymaps)\n    this.editors = new Editors(documents)\n    this.tabs = new TabsModel(this.editors)\n    this.onDidChangeWorkspaceFolders = this.workspaceFolderControl.onDidChangeWorkspaceFolders\n    this.onDidChangeConfiguration = this.configurations.onDidChange\n    this.onDidOpenTextDocument = documents.onDidOpenTextDocument\n    this.onDidChangeTextDocument = documents.onDidChangeDocument\n    this.onDidCloseTextDocument = documents.onDidCloseDocument\n    this.onDidSaveTextDocument = documents.onDidSaveTextDocument\n    this.onWillSaveTextDocument = documents.onWillSaveTextDocument\n    this.onDidCreateFiles = this.files.onDidCreateFiles\n    this.onDidRenameFiles = this.files.onDidRenameFiles\n    this.onDidDeleteFiles = this.files.onDidDeleteFiles\n    this.onWillCreateFiles = this.files.onWillCreateFiles\n    this.onWillRenameFiles = this.files.onWillRenameFiles\n    this.onWillDeleteFiles = this.files.onWillDeleteFiles\n    // use global value only\n    const config = this.getWatchConfig()\n    this.fileSystemWatchers = new FileSystemWatcherManager(this.workspaceFolderControl, config)\n  }\n\n  public get initialConfiguration(): WorkspaceConfiguration {\n    return this.configurations.initialConfiguration\n  }\n\n  public getWatchConfig(): FileWatchConfig {\n    let { initialConfiguration } = this\n    let watchConfig = defaultValue<Partial<FileWatchConfig>>(initialConfiguration.get('fileSystemWatch'), {})\n    let watchmanPath = watchConfig.watchmanPath\n    if (!watchmanPath) watchmanPath = initialConfiguration.inspect<string>('coc.preferences.watchmanPath').globalValue\n    if (typeof watchmanPath === 'string') watchmanPath = this.expand(watchmanPath)\n    let ignoredFolders = defaultValue(watchConfig.ignoredFolders, [\"${tmpdir}\", \"/private/tmp\", \"/\"])\n    let enable = getConditionValue(watchConfig.enable == null ? true : !!watchConfig.enable, false)\n    return {\n      watchmanPath,\n      enable,\n      ignoredFolders: ignoredFolders.map(p => this.expand(p))\n    }\n  }\n\n  public async init(window: any): Promise<void> {\n    let { nvim } = this\n    for (let method of methods) {\n      Object.defineProperty(this, method, {\n        get: () => {\n          return (...args: any[]) => {\n            let stack = '\\n' + Error().stack.split('\\n').slice(2, 4).join('\\n')\n            logger.warn(`workspace.${method} is deprecated, please use window.${method} instead.`, stack)\n            return window[method].apply(window, args)\n          }\n        }\n      })\n    }\n    for (let name of ['onDidOpenTerminal', 'onDidCloseTerminal']) {\n      Object.defineProperty(this, name, {\n        get: () => {\n          let stack = '\\n' + Error().stack.split('\\n').slice(2, 4).join('\\n')\n          logger.warn(`workspace.${name} is deprecated, please use window.${name} instead.`, stack)\n          return window[name]\n        }\n      })\n    }\n    let env = this._env = await nvim.call('coc#util#vim_info') as Env\n    this.checkVersion(APIVERSION)\n    this.configurations.updateMemoryConfig(this._env.config)\n    this.workspaceFolderControl.setWorkspaceFolders(this._env.workspaceFolders)\n    this.workspaceFolderControl.onDidChangeWorkspaceFolders(() => {\n      nvim.setVar('WorkspaceFolders', this.folderPaths, true)\n    })\n    this.files.attach(nvim, env, window)\n    this.contentProvider.attach(nvim)\n    this.registerTextDocumentContentProvider('output', channels.getProvider(nvim))\n    this.keymaps.attach(nvim)\n    this.autocmds.attach(nvim)\n    this.watchers.attach(nvim, env)\n    this.watchers.watchOption('runtimepath', async (oldValue: string, newValue: string) => {\n      let oldList: string[] = oldValue.split(',')\n      let newList: string[] = newValue.split(',')\n      let paths = newList.filter(x => !oldList.includes(x))\n      if (paths.length > 0) {\n        let filepaths: string[] = []\n        await Promise.allSettled(paths.map(filepath => {\n          return new Promise((resolve, reject) => {\n            let converted = this.fixWin32unixFilepath(filepath)\n            getFileType(converted).then(t => {\n              if (t == FileType.Directory) {\n                filepaths.push(converted)\n              }\n              resolve(undefined)\n            }, reject)\n          })\n        }))\n        if (filepaths.length > 0) {\n          this._onDidRuntimePathChange.fire(filepaths)\n          this.env.runtimepath = [...oldList, ...filepaths].join(',')\n        }\n      }\n    })\n    await this.documentsManager.attach(this.nvim, this._env)\n    await this.editors.attach(nvim)\n    this.tabs.attach()\n    let channel = channels.create('watchman', nvim)\n    this.fileSystemWatchers.attach(channel)\n    if (this.strWidth) this.strWidth.setAmbw(!env.ambiguousIsNarrow)\n  }\n\n  public checkVersion(version: number) {\n    if (this._env.apiversion != version) {\n      this.nvim.echoError(`API version ${this._env.apiversion} is not ${APIVERSION}, building coc.nvim by 'npm ci'.`)\n      this.nvim.call('coc#ui#fix', [], true)\n    }\n  }\n\n  public getDisplayWidth(text: string, cache = false): number {\n    return this.strWidth.getWidth(text, cache)\n  }\n\n  public get version(): string {\n    return VERSION\n  }\n\n  public get cwd(): string {\n    return this.documentsManager.cwd\n  }\n\n  public get env(): Env {\n    return this._env\n  }\n\n  public get root(): string {\n    return this.documentsManager.root || this.cwd\n  }\n\n  public get rootPath(): string {\n    return this.root\n  }\n\n  public get bufnr(): number {\n    return this.documentsManager.bufnr\n  }\n\n  /**\n   * @deprecated\n   */\n  public get insertMode(): boolean {\n    return events.insertMode\n  }\n\n  /**\n   * @deprecated always true\n   */\n  public get floatSupported(): boolean {\n    return true\n  }\n\n  /**\n   * @deprecated\n   */\n  public get uri(): string {\n    return this.documentsManager.uri\n  }\n\n  /**\n   * @deprecated\n   */\n  public get workspaceFolder(): WorkspaceFolder {\n    return this.workspaceFolders[0]\n  }\n\n  public get textDocuments(): TextDocument[] {\n    return this.documentsManager.textDocuments\n  }\n\n  public get documents(): Document[] {\n    return this.documentsManager.documents\n  }\n\n  public get document(): Promise<Document | undefined> {\n    return this.documentsManager.document\n  }\n\n  public get workspaceFolders(): ReadonlyArray<WorkspaceFolder> {\n    return this.workspaceFolderControl.workspaceFolders\n  }\n\n  public fixWin32unixFilepath(filepath: string): string {\n    return this.documentsManager.fixUnixPrefix(filepath)\n  }\n\n  public checkPatterns(patterns: string[], folders?: WorkspaceFolder[]): Promise<boolean> {\n    return this.workspaceFolderControl.checkPatterns(folders ?? this.workspaceFolderControl.workspaceFolders, patterns)\n  }\n\n  public get folderPaths(): string[] {\n    return this.workspaceFolders.map(f => URI.parse(f.uri).fsPath)\n  }\n\n  public get channelNames(): string[] {\n    return channels.names\n  }\n\n  public get pluginRoot(): string {\n    return pluginRoot\n  }\n\n  public get isVim(): boolean {\n    return this._env.isVim\n  }\n\n  public get isNvim(): boolean {\n    return !this._env.isVim\n  }\n\n  /**\n   * Kept for backward compatible\n   */\n  public get completeOpt(): string {\n    return ''\n  }\n\n  public get filetypes(): Set<string> {\n    return this.documentsManager.filetypes\n  }\n\n  public get languageIds(): Set<string> {\n    return this.documentsManager.languageIds\n  }\n\n  /**\n   * @deprecated Use nvim.createNamespace() instead.\n   */\n  public createNameSpace(name: string): number {\n    return createNameSpace(name)\n  }\n\n  public has(feature: string): boolean {\n    return has(this.env, feature)\n  }\n\n  /**\n   * Register autocmd on vim.\n   */\n  public registerAutocmd(autocmd: Autocmd, disposables?: Disposable[]): Disposable {\n    let opts = Object.assign({}, autocmd)\n    Error.captureStackTrace(opts)\n    let disposable = this.autocmds.registerAutocmd(opts as AutocmdOptionWithStack)\n    if (disposables) disposables.push(disposable)\n    return disposable\n  }\n\n  /**\n   * Watch for option change.\n   */\n  public watchOption(key: string, callback: (oldValue: any, newValue: any) => Thenable<void> | void, disposables?: Disposable[]): Disposable {\n    return this.watchers.watchOption(key, callback, disposables)\n  }\n\n  /**\n   * Watch global variable, works on neovim only.\n   */\n  public watchGlobal(key: string, callback?: (oldValue: any, newValue: any) => Thenable<void> | void, disposables?: Disposable[]): Disposable {\n    let cb = callback ?? function() {}\n    return this.watchers.watchGlobal(key, cb, disposables)\n  }\n\n  /**\n   * Check if selector match document.\n   */\n  public match(selector: DocumentSelector | DocumentFilter | string, document: TextDocumentMatch): number {\n    return score(selector, document.uri, document.languageId)\n  }\n\n  /**\n   * Create a FileSystemWatcher instance, doesn't fail when watchman not found.\n   */\n  public createFileSystemWatcher(globPattern: GlobPattern, ignoreCreate?: boolean, ignoreChange?: boolean, ignoreDelete?: boolean): FileSystemWatcher {\n    return this.fileSystemWatchers.createFileSystemWatcher(globPattern, ignoreCreate, ignoreChange, ignoreDelete)\n  }\n\n  public createFuzzyMatch(): FuzzyMatch {\n    return new FuzzyMatch(this.fuzzyExports)\n  }\n\n  public getWatchmanPath(): string | null {\n    return getWatchmanPath(this.configurations)\n  }\n\n  /**\n   * Get configuration by section and optional resource uri.\n   */\n  public getConfiguration(section?: string, scope?: ConfigurationResourceScope): WorkspaceConfiguration {\n    return this.configurations.getConfiguration(section, scope)\n  }\n\n  public resolveJSONSchema(uri: string): IJSONSchema | undefined {\n    return this.configurations.getJSONSchema(uri)\n  }\n\n  /**\n   * Get created document by uri or bufnr.\n   */\n  public getDocument(uri: number | string): Document | null | undefined {\n    return this.documentsManager.getDocument(uri)\n  }\n\n  public hasDocument(uri: string, version?: number): boolean {\n    let doc = this.documentsManager.getDocument(uri)\n    return doc && (version != null ? doc.version == version : true)\n  }\n\n  public getUri(bufnr: number, defaultValue = ''): string {\n    let doc = this.documentsManager.getDocument(bufnr)\n    return doc ? doc.uri : defaultValue\n  }\n\n  public isAttached(bufnr: number): boolean {\n    let doc = this.documentsManager.getDocument(bufnr)\n    return doc != null && doc.attached\n  }\n\n  /**\n   * Get attached document by uri or bufnr.\n   * Throw error when document doesn't exist or isn't attached.\n   */\n  public getAttachedDocument(uri: number | string): Document {\n    let doc = this.getDocument(uri)\n    if (!doc) throw new Error(`Buffer ${uri} not exists.`)\n    if (!doc.attached) throw new Error(`Buffer ${uri} not attached, ${doc.notAttachReason}`)\n    return doc\n  }\n  /**\n   * Convert location to quickfix item.\n   */\n  public getQuickfixItem(loc: Location | LocationLink, text?: string, type = '', module?: string): Promise<QuickfixItem> {\n    return this.documentsManager.getQuickfixItem(loc, text, type, module)\n  }\n\n  /**\n   * Create persistence Mru instance.\n   */\n  public createMru(name: string): Mru {\n    return new Mru(name)\n  }\n\n  public async getQuickfixList(locations: Location[]): Promise<ReadonlyArray<QuickfixItem>> {\n    return this.documentsManager.getQuickfixList(locations)\n  }\n\n  /**\n   * Populate locations to UI.\n   */\n  public async showLocations(locations: LocationWithTarget[]): Promise<void> {\n    await this.documentsManager.showLocations(locations)\n  }\n\n  /**\n   * Get content of line by uri and line.\n   */\n  public getLine(uri: string, line: number): Promise<string> {\n    return this.documentsManager.getLine(uri, line)\n  }\n\n  /**\n   * Get WorkspaceFolder of uri\n   */\n  public getWorkspaceFolder(uri: string | URI): WorkspaceFolder | undefined {\n    return this.workspaceFolderControl.getWorkspaceFolder(typeof uri === 'string' ? URI.parse(uri) : uri)\n  }\n\n  /**\n   * Get content from buffer or file by uri.\n   */\n  public readFile(uri: string): Promise<string> {\n    return this.documentsManager.readFile(uri)\n  }\n\n  public async getCurrentState(): Promise<{ document: LinesTextDocument, position: Position }> {\n    let document = await this.document\n    let position = await ui.getCursorPosition(this.nvim)\n    return {\n      document: document.textDocument,\n      position\n    }\n  }\n\n  public async getFormatOptions(uri?: string | number): Promise<FormattingOptions> {\n    return this.documentsManager.getFormatOptions(uri)\n  }\n\n  /**\n   * Resolve module from yarn or npm.\n   */\n  public resolveModule(name: string): Promise<string> {\n    return resolveModule(name)\n  }\n\n  /**\n   * Run nodejs command\n   */\n  public async runCommand(cmd: string, cwd?: string, timeout?: number): Promise<string> {\n    return runCommand(cmd, { cwd: cwd ?? this.cwd }, timeout)\n  }\n\n  /**\n   * Expand filepath with `~` and/or environment placeholders\n   */\n  public expand(filepath: string): string {\n    return this.documentsManager.expand(filepath)\n  }\n\n  public async callAsync<T>(method: string, args: any[]): Promise<T> {\n    return await callAsync(this.nvim, method, args)\n  }\n\n  public registerTextDocumentContentProvider(scheme: string, provider: TextDocumentContentProvider): Disposable {\n    return this.contentProvider.registerTextDocumentContentProvider(scheme, provider)\n  }\n\n  public registerKeymap(modes: MapMode[], key: string, fn: KeymapCallback, opts: Partial<KeymapOption> = {}): Disposable {\n    return this.keymaps.registerKeymap(modes, key, fn, opts)\n  }\n\n  public registerExprKeymap(mode: 'i' | 'n' | 'v' | 's' | 'x', key: string, fn: KeymapCallback, buffer = false, cancel = true): Disposable {\n    return this.keymaps.registerExprKeymap(mode, key, fn, buffer, cancel)\n  }\n\n  public registerLocalKeymap(bufnr: number, mode: LocalMode, key: string, fn: KeymapCallback, notify: KeymapOption | boolean = false): Disposable {\n    if (typeof arguments[0] === 'string') {\n      bufnr = this.bufnr\n      mode = arguments[0] as LocalMode\n      key = arguments[1]\n      fn = arguments[2]\n      notify = arguments[3] ?? false\n    }\n    return this.keymaps.registerLocalKeymap(bufnr, mode, key, fn, notify)\n  }\n\n  /**\n   * Create Task instance that runs in vim.\n   */\n  public createTask(id: string): Task {\n    return new Task(this.nvim, id)\n  }\n\n  /**\n   * Create DB instance at extension root.\n   */\n  public createDatabase(name: string): DB {\n    return new DB(path.join(dataHome, name + '.json'))\n  }\n\n  public registerBufferSync<T extends SyncItem>(create: (doc: Document) => T | undefined): BufferSync<T> {\n    return new BufferSync(create, this.documentsManager)\n  }\n\n  public async attach(): Promise<void> {\n    await this.documentsManager.attach(this.nvim, this._env)\n  }\n\n  public jumpTo(uri: string | URI, position?: Position | null, openCommand?: string): Promise<void> {\n    return this.files.jumpTo(uri, position, openCommand)\n  }\n\n  /**\n   * Findup for filename or filenames from current filepath or root.\n   */\n  public findUp(filename: string | string[]): Promise<string | null> {\n    return findUp(this.nvim, this.cwd, filename)\n  }\n\n  /**\n   * Apply WorkspaceEdit.\n   */\n  public applyEdit(edit: WorkspaceEdit): Promise<boolean> {\n    return this.files.applyEdit(edit)\n  }\n\n  /**\n   * Create a file in vim and disk\n   */\n  public createFile(filepath: string, opts: CreateFileOptions = {}): Promise<void> {\n    return this.files.createFile(filepath, opts)\n  }\n\n  /**\n   * Load uri as document.\n   */\n  public loadFile(uri: string, cmd?: string): Promise<Document> {\n    return this.files.loadResource(uri, cmd)\n  }\n\n  /**\n   * Load the files that not loaded\n   */\n  public async loadFiles(uris: string[]): Promise<(Document | undefined)[]> {\n    return this.files.loadResources(uris)\n  }\n\n  /**\n   * Rename file in vim and disk\n   */\n  public async renameFile(oldPath: string, newPath: string, opts: RenameFileOptions = {}): Promise<void> {\n    await this.files.renameFile(oldPath, newPath, opts)\n  }\n\n  /**\n   * Delete file from vim and disk.\n   */\n  public async deleteFile(filepath: string, opts: DeleteFileOptions = {}): Promise<void> {\n    await this.files.deleteFile(filepath, opts)\n  }\n\n  /**\n   * Open resource by uri\n   */\n  public async openResource(uri: string): Promise<void> {\n    await this.files.openResource(uri)\n  }\n\n  public async computeWordRanges(uri: string | number, range: Range, token?: CancellationToken): Promise<{ [word: string]: Range[] } | null> {\n    let doc = this.getDocument(uri)\n    if (!doc) return null\n    return await doc.chars.computeWordRanges(doc.textDocument.lines, range, token)\n  }\n\n  public openTextDocument(uri: URI | string): Promise<Document> {\n    return this.files.openTextDocument(uri)\n  }\n\n  public getRelativePath(pathOrUri: string | URI, includeWorkspace?: boolean): string {\n    return this.workspaceFolderControl.getRelativePath(pathOrUri, includeWorkspace)\n  }\n\n  public asRelativePath(pathOrUri: string | URI, includeWorkspace?: boolean): string {\n    return this.getRelativePath(pathOrUri, includeWorkspace)\n  }\n\n  public async findFiles(include: GlobPattern, exclude?: GlobPattern | null, maxResults?: number, token?: CancellationToken): Promise<URI[]> {\n    return this.files.findFiles(include, exclude, maxResults, token)\n  }\n\n  public detach(): void {\n    this.documentsManager.detach()\n  }\n\n  public reset(): void {\n    this.statusLine.reset()\n    this.configurations.reset()\n    this.workspaceFolderControl.reset()\n    this.documentsManager.reset()\n  }\n\n  public dispose(): void {\n    channels.dispose()\n    this.autocmds.dispose()\n    this.statusLine.dispose()\n    this.watchers.dispose()\n    this.contentProvider.dispose()\n    this.documentsManager.dispose()\n    this.configurations.dispose()\n  }\n}\n\nexport default new Workspace()\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"outDir\": \"lib\",\n    \"strict\": true,\n    \"noEmit\": true,\n    \"sourceMap\": true,\n    \"importHelpers\": true,\n    \"allowUnreachableCode\": false,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false,\n    \"strictPropertyInitialization\": false,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"moduleResolution\": \"bundler\",\n    \"lib\": [\"es2017\", \"es2018\", \"es2019\", \"ES2020.Promise\", \"Es2021\"],\n    \"declaration\": false,\n    \"resolveJsonModule\": true,\n    \"esModuleInterop\": true,\n    \"strictNullChecks\": false,\n    \"strictFunctionTypes\": true,\n    \"plugins\": []\n  },\n  \"include\": [\"src\"],\n  \"exclude\": [\"typings\", \"build\", \"node_modules\", \"**/node_modules/*\"]\n}\n"
  },
  {
    "path": "typings/LICENSE",
    "content": "Copyright 2020 chemzqm@gmail.com\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the \"Software\"),\nto deal in the Software without restriction, including without limitation\nthe rights to use, copy, modify, merge, publish, distribute, sublicense,\nand/or sell copies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\nDAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE\nOR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "typings/Readme.md",
    "content": "This package contains typings of coc.nvim only.\n\nFiles were exported from https://github.com/neoclide/coc.nvim/blob/master/typings\n\n## Installation\n\n    npm install --save-dev coc.nvim\n\n## Support coc.nvim\n\nIf you like my work, consider supporting me on Patreon or PayPal:\n\n<a href=\"https://www.patreon.com/chemzqm\"><img src=\"https://c5.patreon.com/external/logo/become_a_patron_button.png\" alt=\"Patreon donate button\" /> </a>\n<a href=\"https://www.paypal.com/paypalme/chezqm\"><img src=\"https://werwolv.net/assets/paypal_banner.png\" alt=\"PayPal donate button\" /> </a>\n\n## Credits\n\n[Visual Studio Code](https://github.com/microsoft/vscode), and [Microsoft](https://github.com/microsoft)\n"
  },
  {
    "path": "typings/index.d.ts",
    "content": "/******************************************************************\nMIT License http://www.opensource.org/licenses/mit-license.php\nAuthor Qiming Zhao <chemzqm@gmail> (https://github.com/chemzqm)\n*******************************************************************/\n\n/// <reference types=\"node\" />\n\ndeclare module 'coc.nvim' {\n  import cp from 'child_process'\n  import { URL } from 'url'\n\n  // language server types {{\n  /**\n   * A tagging type for string properties that are actually document URIs.\n   */\n  export type DocumentUri = string\n  export namespace DocumentUri {\n    function is(value: any): value is DocumentUri\n  }\n\n  /**\n   * Defines an integer in the range of -2^31 to 2^31 - 1.\n   */\n  export type integer = number\n  export namespace integer {\n    const MIN_VALUE = -2147483648\n    const MAX_VALUE = 2147483647\n    function is(value: any): value is integer\n  }\n  /**\n   * Defines an unsigned integer in the range of 0 to 2^31 - 1.\n   */\n  export type uinteger = number\n  export namespace uinteger {\n    const MIN_VALUE = 0\n    const MAX_VALUE = 2147483647\n    function is(value: any): value is uinteger\n  }\n  /**\n   * Defines a decimal number. Since decimal numbers are very\n   * rare in the language server specification we denote the\n   * exact range with every decimal using the mathematics\n   * interval notations (e.g. [0, 1] denotes all decimals d with\n   * 0 <= d <= 1.\n   */\n  export type decimal = number\n  /**\n   * The LSP any type.\n   *\n   * In the current implementation we map LSPAny to any. This is due to the fact\n   * that the TypeScript compilers can't infer string access signatures for\n   * interface correctly (it can though for types). See the following issue for\n   * details: https://github.com/microsoft/TypeScript/issues/15300.\n   *\n   * When the issue is addressed LSPAny can be defined as follows:\n   *\n   * ```ts\n   * export type LSPAny = LSPObject | LSPArray | string | integer | uinteger | decimal | boolean | null | undefined;\n   * export type LSPObject = { [key: string]: LSPAny };\n   * export type LSPArray = LSPAny[];\n   * ```\n   *\n   * Please note that strictly speaking a property with the value `undefined`\n   * can't be converted into JSON preserving the property name. However for\n   * convenience it is allowed and assumed that all these properties are\n   * optional as well.\n   *\n   * @since 3.17.0\n   */\n  export type LSPAny = any\n  export type LSPObject = object\n  export type LSPArray = any[]\n  /**\n   * Position in a text document expressed as zero-based line and character\n   * offset. Prior to 3.17 the offsets were always based on a UTF-16 string\n   * representation. So a string of the form `a𐐀b` the character offset of the\n   * character `a` is 0, the character offset of `𐐀` is 1 and the character\n   * offset of b is 3 since `𐐀` is represented using two code units in UTF-16.\n   * Since 3.17 clients and servers can agree on a different string encoding\n   * representation (e.g. UTF-8). The client announces it's supported encoding\n   * via the client capability [`general.positionEncodings`](#clientCapabilities).\n   * The value is an array of position encodings the client supports, with\n   * decreasing preference (e.g. the encoding at index `0` is the most preferred\n   * one). To stay backwards compatible the only mandatory encoding is UTF-16\n   * represented via the string `utf-16`. The server can pick one of the\n   * encodings offered by the client and signals that encoding back to the\n   * client via the initialize result's property\n   * [`capabilities.positionEncoding`](#serverCapabilities). If the string value\n   * `utf-16` is missing from the client's capability `general.positionEncodings`\n   * servers can safely assume that the client supports UTF-16. If the server\n   * omits the position encoding in its initialize result the encoding defaults\n   * to the string value `utf-16`. Implementation considerations: since the\n   * conversion from one encoding into another requires the content of the\n   * file / line the conversion is best done where the file is read which is\n   * usually on the server side.\n   *\n   * Positions are line end character agnostic. So you can not specify a position\n   * that denotes `\\r|\\n` or `\\n|` where `|` represents the character offset.\n   *\n   * @since 3.17.0 - support for negotiated position encoding.\n   */\n  export interface Position {\n    /**\n     * Line position in a document (zero-based).\n     *\n     * If a line number is greater than the number of lines in a document, it defaults back to the number of lines in the document.\n     * If a line number is negative, it defaults to 0.\n     */\n    line: uinteger\n    /**\n     * Character offset on a line in a document (zero-based).\n     *\n     * The meaning of this offset is determined by the negotiated\n     * `PositionEncodingKind`.\n     *\n     * If the character value is greater than the line length it defaults back to the\n     * line length.\n     */\n    character: uinteger\n  }\n  /**\n   * The Position namespace provides helper functions to work with\n   * [Position](#Position) literals.\n   */\n  export namespace Position {\n    /**\n     * Creates a new Position literal from the given line and character.\n     * @param line The position's line.\n     * @param character The position's character.\n     */\n    function create(line: uinteger, character: uinteger): Position\n    /**\n     * Checks whether the given literal conforms to the [Position](#Position) interface.\n     */\n    function is(value: any): value is Position\n  }\n  /**\n   * A range in a text document expressed as (zero-based) start and end positions.\n   *\n   * If you want to specify a range that contains a line including the line ending\n   * character(s) then use an end position denoting the start of the next line.\n   * For example:\n   * ```ts\n   * {\n   *     start: { line: 5, character: 23 }\n   *     end : { line 6, character : 0 }\n   * }\n   * ```\n   */\n  export interface Range {\n    /**\n     * The range's start position.\n     */\n    start: Position\n    /**\n     * The range's end position.\n     */\n    end: Position\n  }\n  /**\n   * The Range namespace provides helper functions to work with\n   * [Range](#Range) literals.\n   */\n  export namespace Range {\n    /**\n     * Create a new Range literal.\n     * @param start The range's start position.\n     * @param end The range's end position.\n     */\n    function create(start: Position, end: Position): Range\n    /**\n     * Create a new Range literal.\n     * @param startLine The start line number.\n     * @param startCharacter The start character.\n     * @param endLine The end line number.\n     * @param endCharacter The end character.\n     */\n    function create(startLine: uinteger, startCharacter: uinteger, endLine: uinteger, endCharacter: uinteger): Range\n    /**\n     * Checks whether the given literal conforms to the [Range](#Range) interface.\n     */\n    function is(value: any): value is Range\n  }\n  /**\n   * Represents a location inside a resource, such as a line\n   * inside a text file.\n   */\n  export interface Location {\n    uri: DocumentUri\n    range: Range\n  }\n  /**\n   * The Location namespace provides helper functions to work with\n   * [Location](#Location) literals.\n   */\n  export namespace Location {\n    /**\n     * Creates a Location literal.\n     * @param uri The location's uri.\n     * @param range The location's range.\n     */\n    function create(uri: DocumentUri, range: Range): Location\n    /**\n     * Checks whether the given literal conforms to the [Location](#Location) interface.\n     */\n    function is(value: any): value is Location\n  }\n  /**\n       * Represents the connection of two locations. Provides additional metadata over normal [locations](#Location),\n       * including an origin range.\n   */\n  export interface LocationLink {\n    /**\n     * Span of the origin of this link.\n     *\n     * Used as the underlined span for mouse interaction. Defaults to the word range at\n     * the definition position.\n     */\n    originSelectionRange?: Range\n    /**\n     * The target resource identifier of this link.\n     */\n    targetUri: DocumentUri\n    /**\n     * The full target range of this link. If the target for example is a symbol then target range is the\n     * range enclosing this symbol not including leading/trailing whitespace but everything else\n     * like comments. This information is typically used to highlight the range in the editor.\n     */\n    targetRange: Range\n    /**\n     * The range that should be selected and revealed when this link is being followed, e.g the name of a function.\n     * Must be contained by the `targetRange`. See also `DocumentSymbol#range`\n     */\n    targetSelectionRange: Range\n  }\n  /**\n   * The LocationLink namespace provides helper functions to work with\n   * [LocationLink](#LocationLink) literals.\n   */\n  export namespace LocationLink {\n    /**\n     * Creates a LocationLink literal.\n     * @param targetUri The definition's uri.\n     * @param targetRange The full range of the definition.\n     * @param targetSelectionRange The span of the symbol definition at the target.\n     * @param originSelectionRange The span of the symbol being defined in the originating source file.\n     */\n    function create(targetUri: DocumentUri, targetRange: Range, targetSelectionRange: Range, originSelectionRange?: Range): LocationLink\n    /**\n     * Checks whether the given literal conforms to the [LocationLink](#LocationLink) interface.\n     */\n    function is(value: any): value is LocationLink\n  }\n  /**\n   * Represents a color in RGBA space.\n   */\n  export interface Color {\n    /**\n     * The red component of this color in the range [0-1].\n     */\n    readonly red: decimal\n    /**\n     * The green component of this color in the range [0-1].\n     */\n    readonly green: decimal\n    /**\n     * The blue component of this color in the range [0-1].\n     */\n    readonly blue: decimal\n    /**\n     * The alpha component of this color in the range [0-1].\n     */\n    readonly alpha: decimal\n  }\n  /**\n   * The Color namespace provides helper functions to work with\n   * [Color](#Color) literals.\n   */\n  export namespace Color {\n    /**\n     * Creates a new Color literal.\n     */\n    function create(red: decimal, green: decimal, blue: decimal, alpha: decimal): Color\n    /**\n     * Checks whether the given literal conforms to the [Color](#Color) interface.\n     */\n    function is(value: any): value is Color\n  }\n  /**\n   * Represents a color range from a document.\n   */\n  export interface ColorInformation {\n    /**\n     * The range in the document where this color appears.\n     */\n    range: Range\n    /**\n     * The actual color value for this color range.\n     */\n    color: Color\n  }\n  /**\n   * The ColorInformation namespace provides helper functions to work with\n   * [ColorInformation](#ColorInformation) literals.\n   */\n  export namespace ColorInformation {\n    /**\n     * Creates a new ColorInformation literal.\n     */\n    function create(range: Range, color: Color): ColorInformation\n    /**\n     * Checks whether the given literal conforms to the [ColorInformation](#ColorInformation) interface.\n     */\n    function is(value: any): value is ColorInformation\n  }\n  export interface ColorPresentation {\n    /**\n     * The label of this color presentation. It will be shown on the color\n     * picker header. By default this is also the text that is inserted when selecting\n     * this color presentation.\n     */\n    label: string\n    /**\n     * An [edit](#TextEdit) which is applied to a document when selecting\n     * this presentation for the color.  When `falsy` the [label](#ColorPresentation.label)\n     * is used.\n     */\n    textEdit?: TextEdit\n    /**\n     * An optional array of additional [text edits](#TextEdit) that are applied when\n     * selecting this color presentation. Edits must not overlap with the main [edit](#ColorPresentation.textEdit) nor with themselves.\n     */\n    additionalTextEdits?: TextEdit[]\n  }\n  /**\n   * The Color namespace provides helper functions to work with\n   * [ColorPresentation](#ColorPresentation) literals.\n   */\n  export namespace ColorPresentation {\n    /**\n     * Creates a new ColorInformation literal.\n     */\n    function create(label: string, textEdit?: TextEdit, additionalTextEdits?: TextEdit[]): ColorPresentation\n    /**\n     * Checks whether the given literal conforms to the [ColorInformation](#ColorInformation) interface.\n     */\n    function is(value: any): value is ColorPresentation\n  }\n  /**\n   * A set of predefined range kinds.\n   */\n  export namespace FoldingRangeKind {\n    /**\n     * Folding range for a comment\n     */\n    const Comment = \"comment\"\n    /**\n     * Folding range for an import or include\n     */\n    const Imports = \"imports\"\n    /**\n     * Folding range for a region (e.g. `#region`)\n     */\n    const Region = \"region\"\n  }\n  /**\n   * A predefined folding range kind.\n   *\n   * The type is a string since the value set is extensible\n   */\n  export type FoldingRangeKind = string\n  /**\n   * Represents a folding range. To be valid, start and end line must be bigger than zero and smaller\n   * than the number of lines in the document. Clients are free to ignore invalid ranges.\n   */\n  export interface FoldingRange {\n    /**\n     * The zero-based start line of the range to fold. The folded area starts after the line's last character.\n     * To be valid, the end must be zero or larger and smaller than the number of lines in the document.\n     */\n    startLine: uinteger\n    /**\n     * The zero-based character offset from where the folded range starts. If not defined, defaults to the length of the start line.\n     */\n    startCharacter?: uinteger\n    /**\n     * The zero-based end line of the range to fold. The folded area ends with the line's last character.\n     * To be valid, the end must be zero or larger and smaller than the number of lines in the document.\n     */\n    endLine: uinteger\n    /**\n     * The zero-based character offset before the folded range ends. If not defined, defaults to the length of the end line.\n     */\n    endCharacter?: uinteger\n    /**\n     * Describes the kind of the folding range such as `comment' or 'region'. The kind\n     * is used to categorize folding ranges and used by commands like 'Fold all comments'.\n     * See [FoldingRangeKind](#FoldingRangeKind) for an enumeration of standardized kinds.\n     */\n    kind?: FoldingRangeKind\n    /**\n     * The text that the client should show when the specified range is\n     * collapsed. If not defined or not supported by the client, a default\n     * will be chosen by the client.\n     *\n     * @since 3.17.0\n     */\n    collapsedText?: string\n  }\n  /**\n   * The folding range namespace provides helper functions to work with\n   * [FoldingRange](#FoldingRange) literals.\n   */\n  export namespace FoldingRange {\n    /**\n     * Creates a new FoldingRange literal.\n     */\n    function create(startLine: uinteger, endLine: uinteger, startCharacter?: uinteger, endCharacter?: uinteger, kind?: FoldingRangeKind, collapsedText?: string): FoldingRange\n    /**\n     * Checks whether the given literal conforms to the [FoldingRange](#FoldingRange) interface.\n     */\n    function is(value: any): value is FoldingRange\n  }\n  /**\n   * Represents a related message and source code location for a diagnostic. This should be\n   * used to point to code locations that cause or related to a diagnostics, e.g when duplicating\n   * a symbol in a scope.\n   */\n  export interface DiagnosticRelatedInformation {\n    /**\n     * The location of this related diagnostic information.\n     */\n    location: Location\n    /**\n     * The message of this related diagnostic information.\n     */\n    message: string\n  }\n  /**\n   * The DiagnosticRelatedInformation namespace provides helper functions to work with\n   * [DiagnosticRelatedInformation](#DiagnosticRelatedInformation) literals.\n   */\n  export namespace DiagnosticRelatedInformation {\n    /**\n     * Creates a new DiagnosticRelatedInformation literal.\n     */\n    function create(location: Location, message: string): DiagnosticRelatedInformation\n    /**\n     * Checks whether the given literal conforms to the [DiagnosticRelatedInformation](#DiagnosticRelatedInformation) interface.\n     */\n    function is(value: any): value is DiagnosticRelatedInformation\n  }\n  /**\n   * The diagnostic's severity.\n   */\n  export namespace DiagnosticSeverity {\n    /**\n     * Reports an error.\n     */\n    const Error: 1\n    /**\n     * Reports a warning.\n     */\n    const Warning: 2\n    /**\n     * Reports an information.\n     */\n    const Information: 3\n    /**\n     * Reports a hint.\n     */\n    const Hint: 4\n  }\n  export type DiagnosticSeverity = 1 | 2 | 3 | 4\n  /**\n   * The diagnostic tags.\n   *\n   * @since 3.15.0\n   */\n  export namespace DiagnosticTag {\n    /**\n     * Unused or unnecessary code.\n     *\n     * Clients are allowed to render diagnostics with this tag faded out instead of having\n     * an error squiggle.\n     */\n    const Unnecessary: 1\n    /**\n     * Deprecated or obsolete code.\n     *\n     * Clients are allowed to rendered diagnostics with this tag strike through.\n     */\n    const Deprecated: 2\n  }\n  export type DiagnosticTag = 1 | 2\n  /**\n   * Structure to capture a description for an error code.\n   *\n   * @since 3.16.0\n   */\n  export interface CodeDescription {\n    /**\n     * An URI to open with more information about the diagnostic error.\n     */\n    href: string\n  }\n  /**\n   * The CodeDescription namespace provides functions to deal with descriptions for diagnostic codes.\n   *\n   * @since 3.16.0\n   */\n  export namespace CodeDescription {\n    function is(value: any): value is CodeDescription\n  }\n  /**\n   * Represents a diagnostic, such as a compiler error or warning. Diagnostic objects\n   * are only valid in the scope of a resource.\n   */\n  export interface Diagnostic {\n    /**\n     * The range at which the message applies\n     */\n    range: Range\n    /**\n     * The diagnostic's severity. Can be omitted. If omitted it is up to the\n     * client to interpret diagnostics as error, warning, info or hint.\n     */\n    severity?: DiagnosticSeverity\n    /**\n     * The diagnostic's code, which usually appear in the user interface.\n     */\n    code?: integer | string\n    /**\n     * An optional property to describe the error code.\n     * Requires the code field (above) to be present/not null.\n     *\n     * @since 3.16.0\n     */\n    codeDescription?: CodeDescription\n    /**\n     * A human-readable string describing the source of this\n     * diagnostic, e.g. 'typescript' or 'super lint'. It usually\n     * appears in the user interface.\n     */\n    source?: string\n    /**\n     * The diagnostic's message. It usually appears in the user interface\n     */\n    message: string\n    /**\n     * Additional metadata about the diagnostic.\n     *\n     * @since 3.15.0\n     */\n    tags?: DiagnosticTag[]\n    /**\n     * An array of related diagnostic information, e.g. when symbol-names within\n     * a scope collide all definitions can be marked via this property.\n     */\n    relatedInformation?: DiagnosticRelatedInformation[]\n    /**\n     * A data entry field that is preserved between a `textDocument/publishDiagnostics`\n     * notification and `textDocument/codeAction` request.\n     *\n     * @since 3.16.0\n     */\n    data?: LSPAny\n  }\n  /**\n   * The Diagnostic namespace provides helper functions to work with\n   * [Diagnostic](#Diagnostic) literals.\n   */\n  export namespace Diagnostic {\n    /**\n     * Creates a new Diagnostic literal.\n     */\n    function create(range: Range, message: string, severity?: DiagnosticSeverity, code?: integer | string, source?: string, relatedInformation?: DiagnosticRelatedInformation[]): Diagnostic\n    /**\n     * Checks whether the given literal conforms to the [Diagnostic](#Diagnostic) interface.\n     */\n    function is(value: any): value is Diagnostic\n  }\n  /**\n   * Represents a reference to a command. Provides a title which\n   * will be used to represent a command in the UI and, optionally,\n   * an array of arguments which will be passed to the command handler\n   * function when invoked.\n   */\n  export interface Command {\n    /**\n     * Title of the command, like `save`.\n     */\n    title: string\n    /**\n     * The identifier of the actual command handler.\n     */\n    command: string\n    /**\n     * Arguments that the command handler should be\n     * invoked with.\n     */\n    arguments?: LSPAny[]\n  }\n  /**\n   * The Command namespace provides helper functions to work with\n   * [Command](#Command) literals.\n   */\n  export namespace Command {\n    /**\n     * Creates a new Command literal.\n     */\n    function create(title: string, command: string, ...args: any[]): Command\n    /**\n     * Checks whether the given literal conforms to the [Command](#Command) interface.\n     */\n    function is(value: any): value is Command\n  }\n  /**\n   * A text edit applicable to a text document.\n   */\n  export interface TextEdit {\n    /**\n     * The range of the text document to be manipulated. To insert\n     * text into a document create a range where start === end.\n     */\n    range: Range\n    /**\n     * The string to be inserted. For delete operations use an\n     * empty string.\n     */\n    newText: string\n  }\n  /**\n   * The TextEdit namespace provides helper function to create replace,\n   * insert and delete edits more easily.\n   */\n  export namespace TextEdit {\n    /**\n     * Creates a replace text edit.\n     * @param range The range of text to be replaced.\n     * @param newText The new text.\n     */\n    function replace(range: Range, newText: string): TextEdit\n    /**\n     * Creates an insert text edit.\n     * @param position The position to insert the text at.\n     * @param newText The text to be inserted.\n     */\n    function insert(position: Position, newText: string): TextEdit\n    /**\n     * Creates a delete text edit.\n     * @param range The range of text to be deleted.\n     */\n    function del(range: Range): TextEdit\n    function is(value: any): value is TextEdit\n  }\n  /**\n   * Additional information that describes document changes.\n   *\n   * @since 3.16.0\n   */\n  export interface ChangeAnnotation {\n    /**\n     * A human-readable string describing the actual change. The string\n     * is rendered prominent in the user interface.\n     */\n    label: string\n    /**\n     * A flag which indicates that user confirmation is needed\n     * before applying the change.\n     */\n    needsConfirmation?: boolean\n    /**\n     * A human-readable string which is rendered less prominent in\n     * the user interface.\n     */\n    description?: string\n  }\n  export namespace ChangeAnnotation {\n    function create(label: string, needsConfirmation?: boolean, description?: string): ChangeAnnotation\n    function is(value: any): value is ChangeAnnotation\n  }\n  export namespace ChangeAnnotationIdentifier {\n    function is(value: any): value is ChangeAnnotationIdentifier\n  }\n  /**\n   * An identifier to refer to a change annotation stored with a workspace edit.\n   */\n  export type ChangeAnnotationIdentifier = string\n  /**\n   * A special text edit with an additional change annotation.\n   *\n   * @since 3.16.0.\n   */\n  export interface AnnotatedTextEdit extends TextEdit {\n    /**\n     * The actual identifier of the change annotation\n     */\n    annotationId: ChangeAnnotationIdentifier\n  }\n  export namespace AnnotatedTextEdit {\n    /**\n     * Creates an annotated replace text edit.\n     *\n     * @param range The range of text to be replaced.\n     * @param newText The new text.\n     * @param annotation The annotation.\n     */\n    function replace(range: Range, newText: string, annotation: ChangeAnnotationIdentifier): AnnotatedTextEdit\n    /**\n     * Creates an annotated insert text edit.\n     *\n     * @param position The position to insert the text at.\n     * @param newText The text to be inserted.\n     * @param annotation The annotation.\n     */\n    function insert(position: Position, newText: string, annotation: ChangeAnnotationIdentifier): AnnotatedTextEdit\n    /**\n     * Creates an annotated delete text edit.\n     *\n     * @param range The range of text to be deleted.\n     * @param annotation The annotation.\n     */\n    function del(range: Range, annotation: ChangeAnnotationIdentifier): AnnotatedTextEdit\n    function is(value: any): value is AnnotatedTextEdit\n  }\n  /**\n   * Describes textual changes on a text document. A TextDocumentEdit describes all changes\n   * on a document version Si and after they are applied move the document to version Si+1.\n   * So the creator of a TextDocumentEdit doesn't need to sort the array of edits or do any\n   * kind of ordering. However the edits must be non overlapping.\n   */\n  export interface TextDocumentEdit {\n    /**\n     * The text document to change.\n     */\n    textDocument: OptionalVersionedTextDocumentIdentifier\n    /**\n     * The edits to be applied.\n     *\n     * @since 3.16.0 - support for AnnotatedTextEdit. This is guarded using a\n     * client capability.\n     *\n     * @since 3.18.0 - support for SnippetTextEdit. This is guarded using a\n     * client capability.\n     */\n    edits: (TextEdit | AnnotatedTextEdit | SnippetTextEdit)[]\n  }\n  /**\n   * The TextDocumentEdit namespace provides helper function to create\n   * an edit that manipulates a text document.\n   */\n  export namespace TextDocumentEdit {\n    /**\n     * Creates a new `TextDocumentEdit`\n     */\n    function create(textDocument: OptionalVersionedTextDocumentIdentifier, edits: (TextEdit | AnnotatedTextEdit)[]): TextDocumentEdit\n    function is(value: any): value is TextDocumentEdit\n  }\n  /**\n   * A generic resource operation.\n   */\n  interface ResourceOperation {\n    /**\n     * The resource operation kind.\n     */\n    kind: string\n    /**\n     * An optional annotation identifier describing the operation.\n     *\n     * @since 3.16.0\n     */\n    annotationId?: ChangeAnnotationIdentifier\n  }\n  /**\n   * Options to create a file.\n   */\n  export interface CreateFileOptions {\n    /**\n     * Overwrite existing file. Overwrite wins over `ignoreIfExists`\n     */\n    overwrite?: boolean\n    /**\n     * Ignore if exists.\n     */\n    ignoreIfExists?: boolean\n  }\n  /**\n   * Create file operation.\n   */\n  export interface CreateFile extends ResourceOperation {\n    /**\n     * A create\n     */\n    kind: 'create'\n    /**\n     * The resource to create.\n     */\n    uri: DocumentUri\n    /**\n     * Additional options\n     */\n    options?: CreateFileOptions\n  }\n  export namespace CreateFile {\n    function create(uri: DocumentUri, options?: CreateFileOptions, annotation?: ChangeAnnotationIdentifier): CreateFile\n    function is(value: any): value is CreateFile\n  }\n  /**\n   * Rename file options\n   */\n  export interface RenameFileOptions {\n    /**\n     * Overwrite target if existing. Overwrite wins over `ignoreIfExists`\n     */\n    overwrite?: boolean\n    /**\n     * Ignores if target exists.\n     */\n    ignoreIfExists?: boolean\n  }\n  /**\n   * Rename file operation\n   */\n  export interface RenameFile extends ResourceOperation {\n    /**\n     * A rename\n     */\n    kind: 'rename'\n    /**\n     * The old (existing) location.\n     */\n    oldUri: DocumentUri\n    /**\n     * The new location.\n     */\n    newUri: DocumentUri\n    /**\n     * Rename options.\n     */\n    options?: RenameFileOptions\n  }\n  export namespace RenameFile {\n    function create(oldUri: DocumentUri, newUri: DocumentUri, options?: RenameFileOptions, annotation?: ChangeAnnotationIdentifier): RenameFile\n    function is(value: any): value is RenameFile\n  }\n  /**\n   * Delete file options\n   */\n  export interface DeleteFileOptions {\n    /**\n     * Delete the content recursively if a folder is denoted.\n     */\n    recursive?: boolean\n    /**\n     * Ignore the operation if the file doesn't exist.\n     */\n    ignoreIfNotExists?: boolean\n  }\n  /**\n   * Delete file operation\n   */\n  export interface DeleteFile extends ResourceOperation {\n    /**\n     * A delete\n     */\n    kind: 'delete'\n    /**\n     * The file to delete.\n     */\n    uri: DocumentUri\n    /**\n     * Delete options.\n     */\n    options?: DeleteFileOptions\n  }\n  export namespace DeleteFile {\n    function create(uri: DocumentUri, options?: DeleteFileOptions, annotation?: ChangeAnnotationIdentifier): DeleteFile\n    function is(value: any): value is DeleteFile\n  }\n  /**\n   * A workspace edit represents changes to many resources managed in the workspace. The edit\n   * should either provide `changes` or `documentChanges`. If documentChanges are present\n   * they are preferred over `changes` if the client can handle versioned document edits.\n   *\n   * Since version 3.13.0 a workspace edit can contain resource operations as well. If resource\n   * operations are present clients need to execute the operations in the order in which they\n   * are provided. So a workspace edit for example can consist of the following two changes:\n   * (1) a create file a.txt and (2) a text document edit which insert text into file a.txt.\n   *\n   * An invalid sequence (e.g. (1) delete file a.txt and (2) insert text into file a.txt) will\n   * cause failure of the operation. How the client recovers from the failure is described by\n   * the client capability: `workspace.workspaceEdit.failureHandling`\n   */\n  export interface WorkspaceEdit {\n    /**\n     * Holds changes to existing resources.\n     */\n    changes?: {\n      [uri: DocumentUri]: TextEdit[]\n    }\n    /**\n     * Depending on the client capability `workspace.workspaceEdit.resourceOperations` document changes\n     * are either an array of `TextDocumentEdit`s to express changes to n different text documents\n     * where each text document edit addresses a specific version of a text document. Or it can contain\n     * above `TextDocumentEdit`s mixed with create, rename and delete file / folder operations.\n     *\n     * Whether a client supports versioned document edits is expressed via\n     * `workspace.workspaceEdit.documentChanges` client capability.\n     *\n     * If a client neither supports `documentChanges` nor `workspace.workspaceEdit.resourceOperations` then\n     * only plain `TextEdit`s using the `changes` property are supported.\n     */\n    documentChanges?: (TextDocumentEdit | CreateFile | RenameFile | DeleteFile)[]\n    /**\n     * A map of change annotations that can be referenced in `AnnotatedTextEdit`s or create, rename and\n     * delete file / folder operations.\n     *\n     * Whether clients honor this property depends on the client capability `workspace.changeAnnotationSupport`.\n     *\n     * @since 3.16.0\n     */\n    changeAnnotations?: {\n      [id: ChangeAnnotationIdentifier]: ChangeAnnotation\n    }\n  }\n  export namespace WorkspaceEdit {\n    function is(value: any): value is WorkspaceEdit\n  }\n  /**\n   * A change to capture text edits for existing resources.\n   */\n  export interface TextEditChange {\n    /**\n     * Gets all text edits for this change.\n     *\n     * @return An array of text edits.\n     *\n     * @since 3.16.0 - support for annotated text edits. This is usually\n     * guarded using a client capability.\n     */\n    all(): (TextEdit | AnnotatedTextEdit)[]\n    /**\n     * Clears the edits for this change.\n     */\n    clear(): void\n    /**\n     * Adds a text edit.\n     *\n     * @param edit the text edit to add.\n     *\n     * @since 3.16.0 - support for annotated text edits. This is usually\n     * guarded using a client capability.\n     */\n    add(edit: TextEdit | AnnotatedTextEdit): void\n    /**\n     * Insert the given text at the given position.\n     *\n     * @param position A position.\n     * @param newText A string.\n     * @param annotation An optional annotation.\n     */\n    insert(position: Position, newText: string): void\n    insert(position: Position, newText: string, annotation: ChangeAnnotation | ChangeAnnotationIdentifier): ChangeAnnotationIdentifier\n    /**\n     * Replace the given range with given text for the given resource.\n     *\n     * @param range A range.\n     * @param newText A string.\n     * @param annotation An optional annotation.\n     */\n    replace(range: Range, newText: string): void\n    replace(range: Range, newText: string, annotation?: ChangeAnnotation | ChangeAnnotationIdentifier): ChangeAnnotationIdentifier\n    /**\n     * Delete the text at the given range.\n     *\n     * @param range A range.\n     * @param annotation An optional annotation.\n     */\n    delete(range: Range): void\n    delete(range: Range, annotation?: ChangeAnnotation | ChangeAnnotationIdentifier): ChangeAnnotationIdentifier\n  }\n  /**\n   * A workspace change helps constructing changes to a workspace.\n   */\n  export class WorkspaceChange {\n    private _workspaceEdit\n    private _textEditChanges\n    private _changeAnnotations\n    constructor(workspaceEdit?: WorkspaceEdit)\n    /**\n     * Returns the underlying [WorkspaceEdit](#WorkspaceEdit) literal\n     * use to be returned from a workspace edit operation like rename.\n     */\n    get edit(): WorkspaceEdit\n    /**\n     * Returns the [TextEditChange](#TextEditChange) to manage text edits\n     * for resources.\n     */\n    getTextEditChange(textDocument: OptionalVersionedTextDocumentIdentifier): TextEditChange\n    getTextEditChange(uri: DocumentUri): TextEditChange\n    private initDocumentChanges\n    private initChanges\n    createFile(uri: DocumentUri, options?: CreateFileOptions): void\n    createFile(uri: DocumentUri, annotation: ChangeAnnotation | ChangeAnnotationIdentifier, options?: CreateFileOptions): ChangeAnnotationIdentifier\n    renameFile(oldUri: DocumentUri, newUri: DocumentUri, options?: RenameFileOptions): void\n    renameFile(oldUri: DocumentUri, newUri: DocumentUri, annotation?: ChangeAnnotation | ChangeAnnotationIdentifier, options?: RenameFileOptions): ChangeAnnotationIdentifier\n    deleteFile(uri: DocumentUri, options?: DeleteFileOptions): void\n    deleteFile(uri: DocumentUri, annotation: ChangeAnnotation | ChangeAnnotationIdentifier, options?: DeleteFileOptions): ChangeAnnotationIdentifier\n  }\n  /**\n   * A literal to identify a text document in the client.\n   */\n  export interface TextDocumentIdentifier {\n    /**\n     * The text document's uri.\n     */\n    uri: DocumentUri\n  }\n  /**\n   * The TextDocumentIdentifier namespace provides helper functions to work with\n   * [TextDocumentIdentifier](#TextDocumentIdentifier) literals.\n   */\n  export namespace TextDocumentIdentifier {\n    /**\n     * Creates a new TextDocumentIdentifier literal.\n     * @param uri The document's uri.\n     */\n    function create(uri: DocumentUri): TextDocumentIdentifier\n    /**\n     * Checks whether the given literal conforms to the [TextDocumentIdentifier](#TextDocumentIdentifier) interface.\n     */\n    function is(value: any): value is TextDocumentIdentifier\n  }\n  /**\n   * A text document identifier to denote a specific version of a text document.\n   */\n  export interface VersionedTextDocumentIdentifier extends TextDocumentIdentifier {\n    /**\n     * The version number of this document.\n     */\n    version: integer\n  }\n  /**\n   * The VersionedTextDocumentIdentifier namespace provides helper functions to work with\n   * [VersionedTextDocumentIdentifier](#VersionedTextDocumentIdentifier) literals.\n   */\n  export namespace VersionedTextDocumentIdentifier {\n    /**\n     * Creates a new VersionedTextDocumentIdentifier literal.\n     * @param uri The document's uri.\n     * @param version The document's version.\n     */\n    function create(uri: DocumentUri, version: integer): VersionedTextDocumentIdentifier\n    /**\n     * Checks whether the given literal conforms to the [VersionedTextDocumentIdentifier](#VersionedTextDocumentIdentifier) interface.\n     */\n    function is(value: any): value is VersionedTextDocumentIdentifier\n  }\n  /**\n   * A text document identifier to optionally denote a specific version of a text document.\n   */\n  export interface OptionalVersionedTextDocumentIdentifier extends TextDocumentIdentifier {\n    /**\n     * The version number of this document. If a versioned text document identifier\n     * is sent from the server to the client and the file is not open in the editor\n     * (the server has not received an open notification before) the server can send\n     * `null` to indicate that the version is unknown and the content on disk is the\n     * truth (as specified with document content ownership).\n     */\n    version: integer | null\n  }\n  /**\n   * The OptionalVersionedTextDocumentIdentifier namespace provides helper functions to work with\n   * [OptionalVersionedTextDocumentIdentifier](#OptionalVersionedTextDocumentIdentifier) literals.\n   */\n  export namespace OptionalVersionedTextDocumentIdentifier {\n    /**\n     * Creates a new OptionalVersionedTextDocumentIdentifier literal.\n     * @param uri The document's uri.\n     * @param version The document's version.\n     */\n    function create(uri: DocumentUri, version: integer | null): OptionalVersionedTextDocumentIdentifier\n    /**\n     * Checks whether the given literal conforms to the [OptionalVersionedTextDocumentIdentifier](#OptionalVersionedTextDocumentIdentifier) interface.\n     */\n    function is(value: any): value is OptionalVersionedTextDocumentIdentifier\n  }\n  /**\n   * An item to transfer a text document from the client to the\n   * server.\n   */\n  export interface TextDocumentItem {\n    /**\n     * The text document's uri.\n     */\n    uri: DocumentUri\n    /**\n     * The text document's language identifier.\n     */\n    languageId: string\n    /**\n     * The version number of this document (it will increase after each\n     * change, including undo/redo).\n     */\n    version: integer\n    /**\n     * The content of the opened text document.\n     */\n    text: string\n  }\n  /**\n   * The TextDocumentItem namespace provides helper functions to work with\n   * [TextDocumentItem](#TextDocumentItem) literals.\n   */\n  export namespace TextDocumentItem {\n    /**\n     * Creates a new TextDocumentItem literal.\n     * @param uri The document's uri.\n     * @param languageId The document's language identifier.\n     * @param version The document's version number.\n     * @param text The document's text.\n     */\n    function create(uri: DocumentUri, languageId: string, version: integer, text: string): TextDocumentItem\n    /**\n     * Checks whether the given literal conforms to the [TextDocumentItem](#TextDocumentItem) interface.\n     */\n    function is(value: any): value is TextDocumentItem\n  }\n  /**\n   * Describes the content type that a client supports in various\n   * result literals like `Hover`, `ParameterInfo` or `CompletionItem`.\n   *\n   * Please note that `MarkupKinds` must not start with a `$`. This kinds\n   * are reserved for internal usage.\n   */\n  export namespace MarkupKind {\n    /**\n     * Plain text is supported as a content format\n     */\n    const PlainText: 'plaintext'\n    /**\n     * Markdown is supported as a content format\n     */\n    const Markdown: 'markdown'\n    /**\n     * Checks whether the given value is a value of the [MarkupKind](#MarkupKind) type.\n     */\n    function is(value: any): value is MarkupKind\n  }\n  export type MarkupKind = 'plaintext' | 'markdown'\n  /**\n   * A `MarkupContent` literal represents a string value which content is interpreted base on its\n   * kind flag. Currently the protocol supports `plaintext` and `markdown` as markup kinds.\n   *\n   * If the kind is `markdown` then the value can contain fenced code blocks like in GitHub issues.\n   * See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting\n   *\n   * Here is an example how such a string can be constructed using JavaScript / TypeScript:\n   * ```ts\n   * let markdown: MarkdownContent = {\n   *  kind: MarkupKind.Markdown,\n   *  value: [\n   *    '# Header',\n   *    'Some text',\n   *    '```typescript',\n   *    'someCode();',\n   *    '```'\n   *  ].join('\\n')\n   * };\n   * ```\n   *\n   * *Please Note* that clients might sanitize the return markdown. A client could decide to\n   * remove HTML from the markdown to avoid script execution.\n   */\n  export interface MarkupContent {\n    /**\n     * The type of the Markup\n     */\n    kind: MarkupKind\n    /**\n     * The content itself\n     */\n    value: string\n  }\n  export namespace MarkupContent {\n    /**\n     * Checks whether the given value conforms to the [MarkupContent](#MarkupContent) interface.\n     */\n    function is(value: any): value is MarkupContent\n  }\n  /**\n   * The kind of a completion entry.\n   */\n  export namespace CompletionItemKind {\n    const Text: 1\n    const Method: 2\n    const Function: 3\n    const Constructor: 4\n    const Field: 5\n    const Variable: 6\n    const Class: 7\n    const Interface: 8\n    const Module: 9\n    const Property: 10\n    const Unit: 11\n    const Value: 12\n    const Enum: 13\n    const Keyword: 14\n    const Snippet: 15\n    const Color: 16\n    const File: 17\n    const Reference: 18\n    const Folder: 19\n    const EnumMember: 20\n    const Constant: 21\n    const Struct: 22\n    const Event: 23\n    const Operator: 24\n    const TypeParameter: 25\n  }\n  export type CompletionItemKind = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25\n  /**\n   * Defines whether the insert text in a completion item should be interpreted as\n   * plain text or a snippet.\n   */\n  export namespace InsertTextFormat {\n    /**\n     * The primary text to be inserted is treated as a plain string.\n     */\n    const PlainText: 1\n    /**\n     * The primary text to be inserted is treated as a snippet.\n     *\n     * A snippet can define tab stops and placeholders with `$1`, `$2`\n     * and `${3:foo}`. `$0` defines the final tab stop, it defaults to\n     * the end of the snippet. Placeholders with equal identifiers are linked,\n     * that is typing in one will update others too.\n     *\n     * See also: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#snippet_syntax\n     */\n    const Snippet: 2\n  }\n  export type InsertTextFormat = 1 | 2\n  /**\n   * Completion item tags are extra annotations that tweak the rendering of a completion\n   * item.\n   *\n   * @since 3.15.0\n   */\n  export namespace CompletionItemTag {\n    /**\n     * Render a completion as obsolete, usually using a strike-out.\n     */\n    const Deprecated = 1\n  }\n  export type CompletionItemTag = 1\n  /**\n   * A special text edit to provide an insert and a replace operation.\n   *\n   * @since 3.16.0\n   */\n  export interface InsertReplaceEdit {\n    /**\n     * The string to be inserted.\n     */\n    newText: string\n    /**\n     * The range if the insert is requested\n     */\n    insert: Range\n    /**\n     * The range if the replace is requested.\n     */\n    replace: Range\n  }\n  /**\n   * The InsertReplaceEdit namespace provides functions to deal with insert / replace edits.\n   *\n   * @since 3.16.0\n   */\n  export namespace InsertReplaceEdit {\n    /**\n     * Creates a new insert / replace edit\n     */\n    function create(newText: string, insert: Range, replace: Range): InsertReplaceEdit\n    /**\n     * Checks whether the given literal conforms to the [InsertReplaceEdit](#InsertReplaceEdit) interface.\n     */\n    function is(value: TextEdit | InsertReplaceEdit): value is InsertReplaceEdit\n  }\n  /**\n   * How whitespace and indentation is handled during completion\n   * item insertion.\n   *\n   * @since 3.16.0\n   */\n  export namespace InsertTextMode {\n    /**\n     * The insertion or replace strings is taken as it is. If the\n     * value is multi line the lines below the cursor will be\n     * inserted using the indentation defined in the string value.\n     * The client will not apply any kind of adjustments to the\n     * string.\n     */\n    const asIs: 1\n    /**\n     * The editor adjusts leading whitespace of new lines so that\n     * they match the indentation up to the cursor of the line for\n     * which the item is accepted.\n     *\n     * Consider a line like this: <2tabs><cursor><3tabs>foo. Accepting a\n     * multi line completion item is indented using 2 tabs and all\n     * following lines inserted will be indented using 2 tabs as well.\n     */\n    const adjustIndentation: 2\n  }\n  export type InsertTextMode = 1 | 2\n  /**\n   * Additional details for a completion item label.\n   *\n   * @since 3.17.0\n   */\n  export interface CompletionItemLabelDetails {\n    /**\n     * An optional string which is rendered less prominently directly after {@link CompletionItem.label label},\n     * without any spacing. Should be used for function signatures and type annotations.\n     */\n    detail?: string\n    /**\n     * An optional string which is rendered less prominently after {@link CompletionItem.detail}. Should be used\n     * for fully qualified names and file paths.\n     */\n    description?: string\n  }\n  export namespace CompletionItemLabelDetails {\n    function is(value: any): value is CompletionItemLabelDetails\n  }\n  /**\n   * A completion item represents a text snippet that is\n   * proposed to complete text that is being typed.\n   */\n  export interface CompletionItem {\n    /**\n     * The label of this completion item.\n     *\n     * The label property is also by default the text that\n     * is inserted when selecting this completion.\n     *\n     * If label details are provided the label itself should\n     * be an unqualified name of the completion item.\n     */\n    label: string\n    /**\n     * Additional details for the label\n     *\n     * @since 3.17.0\n     */\n    labelDetails?: CompletionItemLabelDetails\n    /**\n     * The kind of this completion item. Based of the kind\n     * an icon is chosen by the editor.\n     */\n    kind?: CompletionItemKind\n    /**\n     * Tags for this completion item.\n     *\n     * @since 3.15.0\n     */\n    tags?: CompletionItemTag[]\n    /**\n     * A human-readable string with additional information\n     * about this item, like type or symbol information.\n     */\n    detail?: string\n    /**\n     * A human-readable string that represents a doc-comment.\n     */\n    documentation?: string | MarkupContent\n    /**\n     * Indicates if this item is deprecated.\n     * @deprecated Use `tags` instead.\n     */\n    deprecated?: boolean\n    /**\n     * Select this item when showing.\n     *\n     * *Note* that only one completion item can be selected and that the\n     * tool / client decides which item that is. The rule is that the *first*\n     * item of those that match best is selected.\n     */\n    preselect?: boolean\n    /**\n     * A string that should be used when comparing this item\n     * with other items. When `falsy` the [label](#CompletionItem.label)\n     * is used.\n     */\n    sortText?: string\n    /**\n     * A string that should be used when filtering a set of\n     * completion items. When `falsy` the [label](#CompletionItem.label)\n     * is used.\n     */\n    filterText?: string\n    /**\n     * A string that should be inserted into a document when selecting\n     * this completion. When `falsy` the [label](#CompletionItem.label)\n     * is used.\n     *\n     * The `insertText` is subject to interpretation by the client side.\n     * Some tools might not take the string literally. For example\n     * VS Code when code complete is requested in this example\n     * `con<cursor position>` and a completion item with an `insertText` of\n     * `console` is provided it will only insert `sole`. Therefore it is\n     * recommended to use `textEdit` instead since it avoids additional client\n     * side interpretation.\n     */\n    insertText?: string\n    /**\n     * The format of the insert text. The format applies to both the\n     * `insertText` property and the `newText` property of a provided\n     * `textEdit`. If omitted defaults to `InsertTextFormat.PlainText`.\n     *\n     * Please note that the insertTextFormat doesn't apply to\n     * `additionalTextEdits`.\n     */\n    insertTextFormat?: InsertTextFormat\n    /**\n     * How whitespace and indentation is handled during completion\n     * item insertion. If not provided the clients default value depends on\n     * the `textDocument.completion.insertTextMode` client capability.\n     *\n     * @since 3.16.0\n     */\n    insertTextMode?: InsertTextMode\n    /**\n     * An [edit](#TextEdit) which is applied to a document when selecting\n     * this completion. When an edit is provided the value of\n     * [insertText](#CompletionItem.insertText) is ignored.\n     *\n     * Most editors support two different operations when accepting a completion\n     * item. One is to insert a completion text and the other is to replace an\n     * existing text with a completion text. Since this can usually not be\n     * predetermined by a server it can report both ranges. Clients need to\n     * signal support for `InsertReplaceEdits` via the\n     * `textDocument.completion.insertReplaceSupport` client capability\n     * property.\n     *\n     * *Note 1:* The text edit's range as well as both ranges from an insert\n     * replace edit must be a [single line] and they must contain the position\n     * at which completion has been requested.\n     * *Note 2:* If an `InsertReplaceEdit` is returned the edit's insert range\n     * must be a prefix of the edit's replace range, that means it must be\n     * contained and starting at the same position.\n     *\n     * @since 3.16.0 additional type `InsertReplaceEdit`\n     */\n    textEdit?: TextEdit | InsertReplaceEdit\n    /**\n     * The edit text used if the completion item is part of a CompletionList and\n     * CompletionList defines an item default for the text edit range.\n     *\n     * Clients will only honor this property if they opt into completion list\n     * item defaults using the capability `completionList.itemDefaults`.\n     *\n     * If not provided and a list's default range is provided the label\n     * property is used as a text.\n     *\n     * @since 3.17.0\n     */\n    textEditText?: string\n    /**\n     * An optional array of additional [text edits](#TextEdit) that are applied when\n     * selecting this completion. Edits must not overlap (including the same insert position)\n     * with the main [edit](#CompletionItem.textEdit) nor with themselves.\n     *\n     * Additional text edits should be used to change text unrelated to the current cursor position\n     * (for example adding an import statement at the top of the file if the completion item will\n     * insert an unqualified type).\n     */\n    additionalTextEdits?: TextEdit[]\n    /**\n     * An optional set of characters that when pressed while this completion is active will accept it first and\n     * then type that character. *Note* that all commit characters should have `length=1` and that superfluous\n     * characters will be ignored.\n     */\n    commitCharacters?: string[]\n    /**\n     * An optional [command](#Command) that is executed *after* inserting this completion. *Note* that\n     * additional modifications to the current document should be described with the\n     * [additionalTextEdits](#CompletionItem.additionalTextEdits)-property.\n     */\n    command?: Command\n    /**\n     * A data entry field that is preserved on a completion item between a\n     * [CompletionRequest](#CompletionRequest) and a [CompletionResolveRequest](#CompletionResolveRequest).\n     */\n    data?: LSPAny\n  }\n  /**\n   * The CompletionItem namespace provides functions to deal with\n   * completion items.\n   */\n  export namespace CompletionItem {\n    /**\n     * Create a completion item and seed it with a label.\n     * @param label The completion item's label\n     */\n    function create(label: string): CompletionItem\n  }\n  /**\n   * Represents a collection of [completion items](#CompletionItem) to be presented\n   * in the editor.\n   */\n  export interface CompletionList {\n    /**\n     * This list it not complete. Further typing results in recomputing this list.\n     *\n     * Recomputed lists have all their items replaced (not appended) in the\n     * incomplete completion sessions.\n     */\n    isIncomplete: boolean\n    /**\n     * In many cases the items of an actual completion result share the same\n     * value for properties like `commitCharacters` or the range of a text\n     * edit. A completion list can therefore define item defaults which will\n     * be used if a completion item itself doesn't specify the value.\n     *\n     * If a completion list specifies a default value and a completion item\n     * also specifies a corresponding value the one from the item is used.\n     *\n     * Servers are only allowed to return default values if the client\n     * signals support for this via the `completionList.itemDefaults`\n     * capability.\n     *\n     * @since 3.17.0\n     */\n    itemDefaults?: {\n      /**\n       * A default commit character set.\n       *\n       * @since 3.17.0\n       */\n      commitCharacters?: string[]\n      /**\n       * A default edit range.\n       *\n       * @since 3.17.0\n       */\n      editRange?: Range | {\n        insert: Range\n        replace: Range\n      }\n      /**\n       * A default insert text format.\n       *\n       * @since 3.17.0\n       */\n      insertTextFormat?: InsertTextFormat\n      /**\n       * A default insert text mode.\n       *\n       * @since 3.17.0\n       */\n      insertTextMode?: InsertTextMode\n      /**\n       * A default data value.\n       *\n       * @since 3.17.0\n       */\n      data?: LSPAny\n    }\n    /**\n     * The completion items.\n     */\n    items: CompletionItem[]\n  }\n  /**\n   * The CompletionList namespace provides functions to deal with\n   * completion lists.\n   */\n  export namespace CompletionList {\n    /**\n     * Creates a new completion list.\n     *\n     * @param items The completion items.\n     * @param isIncomplete The list is not complete.\n     */\n    function create(items?: CompletionItem[], isIncomplete?: boolean): CompletionList\n  }\n  /**\n   * MarkedString can be used to render human readable text. It is either a markdown string\n   * or a code-block that provides a language and a code snippet. The language identifier\n   * is semantically equal to the optional language identifier in fenced code blocks in GitHub\n   * issues. See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting\n   *\n   * The pair of a language and a value is an equivalent to markdown:\n   * ```${language}\n   * ${value}\n   * ```\n   *\n   * Note that markdown strings will be sanitized - that means html will be escaped.\n   * @deprecated use MarkupContent instead.\n   */\n  export type MarkedString = string | {\n    language: string\n    value: string\n  }\n  export namespace MarkedString {\n    /**\n     * Creates a marked string from plain text.\n     *\n     * @param plainText The plain text.\n     */\n    function fromPlainText(plainText: string): string\n    /**\n     * Checks whether the given value conforms to the [MarkedString](#MarkedString) type.\n     */\n    function is(value: any): value is MarkedString\n  }\n  /**\n   * The result of a hover request.\n   */\n  export interface Hover {\n    /**\n     * The hover's content\n     */\n    contents: MarkupContent | MarkedString | MarkedString[]\n    /**\n     * An optional range inside the text document that is used to\n     * visualize the hover, e.g. by changing the background color.\n     */\n    range?: Range\n  }\n  export namespace Hover {\n    /**\n     * Checks whether the given value conforms to the [Hover](#Hover) interface.\n     */\n    function is(value: any): value is Hover\n  }\n  /**\n   * Represents a parameter of a callable-signature. A parameter can\n   * have a label and a doc-comment.\n   */\n  export interface ParameterInformation {\n    /**\n     * The label of this parameter information.\n     *\n     * Either a string or an inclusive start and exclusive end offsets within its containing\n     * signature label. (see SignatureInformation.label). The offsets are based on a UTF-16\n     * string representation as `Position` and `Range` does.\n     *\n     * *Note*: a label of type string should be a substring of its containing signature label.\n     * Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`.\n     */\n    label: string | [uinteger, uinteger]\n    /**\n     * The human-readable doc-comment of this parameter. Will be shown\n     * in the UI but can be omitted.\n     */\n    documentation?: string | MarkupContent\n  }\n  /**\n   * The ParameterInformation namespace provides helper functions to work with\n   * [ParameterInformation](#ParameterInformation) literals.\n   */\n  export namespace ParameterInformation {\n    /**\n     * Creates a new parameter information literal.\n     *\n     * @param label A label string.\n     * @param documentation A doc string.\n     */\n    function create(label: string | [uinteger, uinteger], documentation?: string): ParameterInformation\n  }\n  /**\n   * Represents the signature of something callable. A signature\n   * can have a label, like a function-name, a doc-comment, and\n   * a set of parameters.\n   */\n  export interface SignatureInformation {\n    /**\n     * The label of this signature. Will be shown in\n     * the UI.\n     */\n    label: string\n    /**\n     * The human-readable doc-comment of this signature. Will be shown\n     * in the UI but can be omitted.\n     */\n    documentation?: string | MarkupContent\n    /**\n     * The parameters of this signature.\n     */\n    parameters?: ParameterInformation[]\n    /**\n     * The index of the active parameter.\n     *\n     * If provided, this is used in place of `SignatureHelp.activeParameter`.\n     *\n     * @since 3.16.0\n     */\n    activeParameter?: uinteger\n  }\n  /**\n   * The SignatureInformation namespace provides helper functions to work with\n   * [SignatureInformation](#SignatureInformation) literals.\n   */\n  export namespace SignatureInformation {\n    function create(label: string, documentation?: string, ...parameters: ParameterInformation[]): SignatureInformation\n  }\n  /**\n   * Signature help represents the signature of something\n   * callable. There can be multiple signature but only one\n   * active and only one active parameter.\n   */\n  export interface SignatureHelp {\n    /**\n     * One or more signatures.\n     */\n    signatures: SignatureInformation[]\n    /**\n     * The active signature. If omitted or the value lies outside the\n     * range of `signatures` the value defaults to zero or is ignored if\n     * the `SignatureHelp` has no signatures.\n     *\n     * Whenever possible implementors should make an active decision about\n     * the active signature and shouldn't rely on a default value.\n     *\n     * In future version of the protocol this property might become\n     * mandatory to better express this.\n     */\n    activeSignature?: uinteger\n    /**\n     * The active parameter of the active signature. If omitted or the value\n     * lies outside the range of `signatures[activeSignature].parameters`\n     * defaults to 0 if the active signature has parameters. If\n     * the active signature has no parameters it is ignored.\n     * In future version of the protocol this property might become\n     * mandatory to better express the active parameter if the\n     * active signature does have any.\n     */\n    activeParameter?: uinteger\n  }\n  /**\n   * The definition of a symbol represented as one or many [locations](#Location).\n   * For most programming languages there is only one location at which a symbol is\n   * defined.\n   *\n   * Servers should prefer returning `DefinitionLink` over `Definition` if supported\n   * by the client.\n   */\n  export type Definition = Location | Location[]\n  /**\n   * Information about where a symbol is defined.\n   *\n   * Provides additional metadata over normal [location](#Location) definitions, including the range of\n   * the defining symbol\n   */\n  export type DefinitionLink = LocationLink\n  /**\n   * The declaration of a symbol representation as one or many [locations](#Location).\n   */\n  export type Declaration = Location | Location[]\n  /**\n   * Information about where a symbol is declared.\n   *\n   * Provides additional metadata over normal [location](#Location) declarations, including the range of\n   * the declaring symbol.\n   *\n   * Servers should prefer returning `DeclarationLink` over `Declaration` if supported\n   * by the client.\n   */\n  export type DeclarationLink = LocationLink\n  /**\n   * Value-object that contains additional information when\n   * requesting references.\n   */\n  export interface ReferenceContext {\n    /**\n     * Include the declaration of the current symbol.\n     */\n    includeDeclaration: boolean\n  }\n  /**\n   * A document highlight kind.\n   */\n  export namespace DocumentHighlightKind {\n    /**\n     * A textual occurrence.\n     */\n    const Text: 1\n    /**\n     * Read-access of a symbol, like reading a variable.\n     */\n    const Read: 2\n    /**\n     * Write-access of a symbol, like writing to a variable.\n     */\n    const Write: 3\n  }\n  export type DocumentHighlightKind = 1 | 2 | 3\n  /**\n   * A document highlight is a range inside a text document which deserves\n   * special attention. Usually a document highlight is visualized by changing\n   * the background color of its range.\n   */\n  export interface DocumentHighlight {\n    /**\n     * The range this highlight applies to.\n     */\n    range: Range\n    /**\n     * The highlight kind, default is [text](#DocumentHighlightKind.Text).\n     */\n    kind?: DocumentHighlightKind\n  }\n  /**\n   * DocumentHighlight namespace to provide helper functions to work with\n   * [DocumentHighlight](#DocumentHighlight) literals.\n   */\n  export namespace DocumentHighlight {\n    /**\n     * Create a DocumentHighlight object.\n     * @param range The range the highlight applies to.\n     * @param kind The highlight kind\n     */\n    function create(range: Range, kind?: DocumentHighlightKind): DocumentHighlight\n  }\n  /**\n   * A symbol kind.\n   */\n  export namespace SymbolKind {\n    const File: 1\n    const Module: 2\n    const Namespace: 3\n    const Package: 4\n    const Class: 5\n    const Method: 6\n    const Property: 7\n    const Field: 8\n    const Constructor: 9\n    const Enum: 10\n    const Interface: 11\n    const Function: 12\n    const Variable: 13\n    const Constant: 14\n    const String: 15\n    const Number: 16\n    const Boolean: 17\n    const Array: 18\n    const Object: 19\n    const Key: 20\n    const Null: 21\n    const EnumMember: 22\n    const Struct: 23\n    const Event: 24\n    const Operator: 25\n    const TypeParameter: 26\n  }\n  export type SymbolKind = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26\n  /**\n   * Symbol tags are extra annotations that tweak the rendering of a symbol.\n   *\n   * @since 3.16\n   */\n  export namespace SymbolTag {\n    /**\n     * Render a symbol as obsolete, usually using a strike-out.\n     */\n    const Deprecated: 1\n  }\n  export type SymbolTag = 1\n  /**\n   * A base for all symbol information.\n   */\n  export interface BaseSymbolInformation {\n    /**\n     * The name of this symbol.\n     */\n    name: string\n    /**\n     * The kind of this symbol.\n     */\n    kind: SymbolKind\n    /**\n     * Tags for this symbol.\n     *\n     * @since 3.16.0\n     */\n    tags?: SymbolTag[]\n    /**\n     * The name of the symbol containing this symbol. This information is for\n     * user interface purposes (e.g. to render a qualifier in the user interface\n     * if necessary). It can't be used to re-infer a hierarchy for the document\n     * symbols.\n     */\n    containerName?: string\n  }\n  /**\n   * Represents information about programming constructs like variables, classes,\n   * interfaces etc.\n   */\n  export interface SymbolInformation extends BaseSymbolInformation {\n    /**\n     * Indicates if this symbol is deprecated.\n     *\n     * @deprecated Use tags instead\n     */\n    deprecated?: boolean\n    /**\n     * The location of this symbol. The location's range is used by a tool\n     * to reveal the location in the editor. If the symbol is selected in the\n     * tool the range's start information is used to position the cursor. So\n     * the range usually spans more than the actual symbol's name and does\n     * normally include things like visibility modifiers.\n     *\n     * The range doesn't have to denote a node range in the sense of an abstract\n     * syntax tree. It can therefore not be used to re-construct a hierarchy of\n     * the symbols.\n     */\n    location: Location\n  }\n  export namespace SymbolInformation {\n    /**\n     * Creates a new symbol information literal.\n     *\n     * @param name The name of the symbol.\n     * @param kind The kind of the symbol.\n     * @param range The range of the location of the symbol.\n     * @param uri The resource of the location of symbol.\n     * @param containerName The name of the symbol containing the symbol.\n     */\n    function create(name: string, kind: SymbolKind, range: Range, uri: DocumentUri, containerName?: string): SymbolInformation\n  }\n  /**\n   * A special workspace symbol that supports locations without a range.\n   *\n   * See also SymbolInformation.\n   *\n   * @since 3.17.0\n   */\n  export interface WorkspaceSymbol extends BaseSymbolInformation {\n    /**\n     * The location of the symbol. Whether a server is allowed to\n     * return a location without a range depends on the client\n     * capability `workspace.symbol.resolveSupport`.\n     *\n     * See SymbolInformation#location for more details.\n     */\n    location: Location | {\n      uri: DocumentUri\n    }\n    /**\n     * A data entry field that is preserved on a workspace symbol between a\n     * workspace symbol request and a workspace symbol resolve request.\n     */\n    data?: LSPAny\n  }\n  export namespace WorkspaceSymbol {\n    /**\n     * Create a new workspace symbol.\n     *\n     * @param name The name of the symbol.\n     * @param kind The kind of the symbol.\n     * @param uri The resource of the location of the symbol.\n     * @param range An options range of the location.\n     * @returns A WorkspaceSymbol.\n     */\n    function create(name: string, kind: SymbolKind, uri: DocumentUri, range?: Range): WorkspaceSymbol\n  }\n  /**\n   * Represents programming constructs like variables, classes, interfaces etc.\n   * that appear in a document. Document symbols can be hierarchical and they\n   * have two ranges: one that encloses its definition and one that points to\n   * its most interesting range, e.g. the range of an identifier.\n   */\n  export interface DocumentSymbol {\n    /**\n     * The name of this symbol. Will be displayed in the user interface and therefore must not be\n     * an empty string or a string only consisting of white spaces.\n     */\n    name: string\n    /**\n     * More detail for this symbol, e.g the signature of a function.\n     */\n    detail?: string\n    /**\n     * The kind of this symbol.\n     */\n    kind: SymbolKind\n    /**\n     * Tags for this document symbol.\n     *\n     * @since 3.16.0\n     */\n    tags?: SymbolTag[]\n    /**\n     * Indicates if this symbol is deprecated.\n     *\n     * @deprecated Use tags instead\n     */\n    deprecated?: boolean\n    /**\n     * The range enclosing this symbol not including leading/trailing whitespace but everything else\n     * like comments. This information is typically used to determine if the clients cursor is\n     * inside the symbol to reveal in the symbol in the UI.\n     */\n    range: Range\n    /**\n     * The range that should be selected and revealed when this symbol is being picked, e.g the name of a function.\n     * Must be contained by the `range`.\n     */\n    selectionRange: Range\n    /**\n     * Children of this symbol, e.g. properties of a class.\n     */\n    children?: DocumentSymbol[]\n  }\n  export namespace DocumentSymbol {\n    /**\n     * Creates a new symbol information literal.\n     *\n     * @param name The name of the symbol.\n     * @param detail The detail of the symbol.\n     * @param kind The kind of the symbol.\n     * @param range The range of the symbol.\n     * @param selectionRange The selectionRange of the symbol.\n     * @param children Children of the symbol.\n     */\n    function create(name: string, detail: string | undefined, kind: SymbolKind, range: Range, selectionRange: Range, children?: DocumentSymbol[]): DocumentSymbol\n    /**\n     * Checks whether the given literal conforms to the [DocumentSymbol](#DocumentSymbol) interface.\n     */\n    function is(value: any): value is DocumentSymbol\n  }\n  /**\n   * The kind of a code action.\n   *\n   * Kinds are a hierarchical list of identifiers separated by `.`, e.g. `\"refactor.extract.function\"`.\n   *\n   * The set of kinds is open and client needs to announce the kinds it supports to the server during\n   * initialization.\n   */\n  export type CodeActionKind = string\n  /**\n   * A set of predefined code action kinds\n   */\n  export namespace CodeActionKind {\n    /**\n     * Empty kind.\n     */\n    const Empty: ''\n    /**\n     * Base kind for quickfix actions: 'quickfix'\n     */\n    const QuickFix: 'quickfix'\n    /**\n     * Base kind for refactoring actions: 'refactor'\n     */\n    const Refactor: 'refactor'\n    /**\n     * Base kind for refactoring extraction actions: 'refactor.extract'\n     *\n     * Example extract actions:\n     *\n     * - Extract method\n     * - Extract function\n     * - Extract variable\n     * - Extract interface from class\n     * - ...\n     */\n    const RefactorExtract: 'refactor.extract'\n    /**\n     * Base kind for refactoring inline actions: 'refactor.inline'\n     *\n     * Example inline actions:\n     *\n     * - Inline function\n     * - Inline variable\n     * - Inline constant\n     * - ...\n     */\n    const RefactorInline: 'refactor.inline'\n    /**\n     * Base kind for refactoring rewrite actions: 'refactor.rewrite'\n     *\n     * Example rewrite actions:\n     *\n     * - Convert JavaScript function to class\n     * - Add or remove parameter\n     * - Encapsulate field\n     * - Make method static\n     * - Move method to base class\n     * - ...\n     */\n    const RefactorRewrite: 'refactor.rewrite'\n    /**\n     * Base kind for source actions: `source`\n     *\n     * Source code actions apply to the entire file.\n     */\n    const Source: 'source'\n    /**\n     * Base kind for an organize imports source action: `source.organizeImports`\n     */\n    const SourceOrganizeImports: 'source.organizeImports'\n    /**\n     * Base kind for auto-fix source actions: `source.fixAll`.\n     *\n     * Fix all actions automatically fix errors that have a clear fix that do not require user input.\n     * They should not suppress errors or perform unsafe fixes such as generating new types or classes.\n     *\n     * @since 3.15.0\n     */\n    const SourceFixAll: 'source.fixAll'\n  }\n  /**\n   * The reason why code actions were requested.\n   *\n   * @since 3.17.0\n   */\n  export namespace CodeActionTriggerKind {\n    /**\n     * Code actions were explicitly requested by the user or by an extension.\n     */\n    const Invoked: 1\n    /**\n     * Code actions were requested automatically.\n     *\n     * This typically happens when current selection in a file changes, but can\n     * also be triggered when file content changes.\n     */\n    const Automatic: 2\n  }\n  export type CodeActionTriggerKind = 1 | 2\n  /**\n   * Contains additional diagnostic information about the context in which\n   * a [code action](#CodeActionProvider.provideCodeActions) is run.\n   */\n  export interface CodeActionContext {\n    /**\n     * An array of diagnostics known on the client side overlapping the range provided to the\n     * `textDocument/codeAction` request. They are provided so that the server knows which\n     * errors are currently presented to the user for the given range. There is no guarantee\n     * that these accurately reflect the error state of the resource. The primary parameter\n     * to compute code actions is the provided range.\n     */\n    diagnostics: Diagnostic[]\n    /**\n     * Requested kind of actions to return.\n     *\n     * Actions not of this kind are filtered out by the client before being shown. So servers\n     * can omit computing them.\n     */\n    only?: CodeActionKind[]\n    /**\n     * The reason why code actions were requested.\n     *\n     * @since 3.17.0\n     */\n    triggerKind?: CodeActionTriggerKind\n  }\n  /**\n   * The CodeActionContext namespace provides helper functions to work with\n   * [CodeActionContext](#CodeActionContext) literals.\n   */\n  export namespace CodeActionContext {\n    /**\n     * Creates a new CodeActionContext literal.\n     */\n    function create(diagnostics: Diagnostic[], only?: CodeActionKind[], triggerKind?: CodeActionTriggerKind): CodeActionContext\n    /**\n     * Checks whether the given literal conforms to the [CodeActionContext](#CodeActionContext) interface.\n     */\n    function is(value: any): value is CodeActionContext\n  }\n  /**\n   * A code action represents a change that can be performed in code, e.g. to fix a problem or\n   * to refactor code.\n   *\n   * A CodeAction must set either `edit` and/or a `command`. If both are supplied, the `edit` is applied first, then the `command` is executed.\n   */\n  export interface CodeAction {\n    /**\n     * A short, human-readable, title for this code action.\n     */\n    title: string\n    /**\n     * The kind of the code action.\n     *\n     * Used to filter code actions.\n     */\n    kind?: CodeActionKind\n    /**\n     * The diagnostics that this code action resolves.\n     */\n    diagnostics?: Diagnostic[]\n    /**\n     * Marks this as a preferred action. Preferred actions are used by the `auto fix` command and can be targeted\n     * by keybindings.\n     *\n     * A quick fix should be marked preferred if it properly addresses the underlying error.\n     * A refactoring should be marked preferred if it is the most reasonable choice of actions to take.\n     *\n     * @since 3.15.0\n     */\n    isPreferred?: boolean\n    /**\n     * Marks that the code action cannot currently be applied.\n     *\n     * Clients should follow the following guidelines regarding disabled code actions:\n     *\n     *   - Disabled code actions are not shown in automatic [lightbulbs](https://code.visualstudio.com/docs/editor/editingevolved#_code-action)\n     *     code action menus.\n     *\n     *   - Disabled actions are shown as faded out in the code action menu when the user requests a more specific type\n     *     of code action, such as refactorings.\n     *\n     *   - If the user has a [keybinding](https://code.visualstudio.com/docs/editor/refactoring#_keybindings-for-code-actions)\n     *     that auto applies a code action and only disabled code actions are returned, the client should show the user an\n     *     error message with `reason` in the editor.\n     *\n     * @since 3.16.0\n     */\n    disabled?: {\n      /**\n       * Human readable description of why the code action is currently disabled.\n       *\n       * This is displayed in the code actions UI.\n       */\n      reason: string\n    }\n    /**\n     * The workspace edit this code action performs.\n     */\n    edit?: WorkspaceEdit\n    /**\n     * A command this code action executes. If a code action\n     * provides an edit and a command, first the edit is\n     * executed and then the command.\n     */\n    command?: Command\n    /**\n     * A data entry field that is preserved on a code action between\n     * a `textDocument/codeAction` and a `codeAction/resolve` request.\n     *\n     * @since 3.16.0\n     */\n    data?: LSPAny\n  }\n  export namespace CodeAction {\n    /**\n     * Creates a new code action.\n     *\n     * @param title The title of the code action.\n     * @param kind The kind of the code action.\n     */\n    function create(title: string, kind?: CodeActionKind): CodeAction\n    /**\n     * Creates a new code action.\n     *\n     * @param title The title of the code action.\n     * @param command The command to execute.\n     * @param kind The kind of the code action.\n     */\n    function create(title: string, command: Command, kind?: CodeActionKind): CodeAction\n    /**\n     * Creates a new code action.\n     *\n     * @param title The title of the code action.\n     * @param edit The edit to perform.\n     * @param kind The kind of the code action.\n     */\n    function create(title: string, edit: WorkspaceEdit, kind?: CodeActionKind): CodeAction\n    function is(value: any): value is CodeAction\n  }\n  /**\n   * A code lens represents a [command](#Command) that should be shown along with\n   * source text, like the number of references, a way to run tests, etc.\n   *\n   * A code lens is _unresolved_ when no command is associated to it. For performance\n   * reasons the creation of a code lens and resolving should be done in two stages.\n   */\n  export interface CodeLens {\n    /**\n     * The range in which this code lens is valid. Should only span a single line.\n     */\n    range: Range\n    /**\n     * The command this code lens represents.\n     */\n    command?: Command\n    /**\n     * A data entry field that is preserved on a code lens item between\n     * a [CodeLensRequest](#CodeLensRequest) and a [CodeLensResolveRequest]\n     * (#CodeLensResolveRequest)\n     */\n    data?: LSPAny\n  }\n  /**\n   * The CodeLens namespace provides helper functions to work with\n   * [CodeLens](#CodeLens) literals.\n   */\n  export namespace CodeLens {\n    /**\n     * Creates a new CodeLens literal.\n     */\n    function create(range: Range, data?: LSPAny): CodeLens\n    /**\n     * Checks whether the given literal conforms to the [CodeLens](#CodeLens) interface.\n     */\n    function is(value: any): value is CodeLens\n  }\n  /**\n   * Value-object describing what options formatting should use.\n   */\n  export interface FormattingOptions {\n    /**\n     * Size of a tab in spaces.\n     */\n    tabSize: uinteger\n    /**\n     * Prefer spaces over tabs.\n     */\n    insertSpaces: boolean\n    /**\n     * Trim trailing whitespace on a line.\n     *\n     * @since 3.15.0\n     */\n    trimTrailingWhitespace?: boolean\n    /**\n     * Insert a newline character at the end of the file if one does not exist.\n     *\n     * @since 3.15.0\n     */\n    insertFinalNewline?: boolean\n    /**\n     * Trim all newlines after the final newline at the end of the file.\n     *\n     * @since 3.15.0\n     */\n    trimFinalNewlines?: boolean\n    /**\n     * Signature for further properties.\n     */\n    [key: string]: boolean | integer | string | undefined\n  }\n  /**\n   * The FormattingOptions namespace provides helper functions to work with\n   * [FormattingOptions](#FormattingOptions) literals.\n   */\n  export namespace FormattingOptions {\n    /**\n     * Creates a new FormattingOptions literal.\n     */\n    function create(tabSize: uinteger, insertSpaces: boolean): FormattingOptions\n    /**\n     * Checks whether the given literal conforms to the [FormattingOptions](#FormattingOptions) interface.\n     */\n    function is(value: any): value is FormattingOptions\n  }\n  /**\n   * A document link is a range in a text document that links to an internal or external resource, like another\n   * text document or a web site.\n   */\n  export interface DocumentLink {\n    /**\n     * The range this link applies to.\n     */\n    range: Range\n    /**\n     * The uri this link points to. If missing a resolve request is sent later.\n     */\n    target?: string\n    /**\n     * The tooltip text when you hover over this link.\n     *\n     * If a tooltip is provided, is will be displayed in a string that includes instructions on how to\n     * trigger the link, such as `{0} (ctrl + click)`. The specific instructions vary depending on OS,\n     * user settings, and localization.\n     *\n     * @since 3.15.0\n     */\n    tooltip?: string\n    /**\n     * A data entry field that is preserved on a document link between a\n     * DocumentLinkRequest and a DocumentLinkResolveRequest.\n     */\n    data?: LSPAny\n  }\n  /**\n   * The DocumentLink namespace provides helper functions to work with\n   * [DocumentLink](#DocumentLink) literals.\n   */\n  export namespace DocumentLink {\n    /**\n     * Creates a new DocumentLink literal.\n     */\n    function create(range: Range, target?: string, data?: LSPAny): DocumentLink\n    /**\n     * Checks whether the given literal conforms to the [DocumentLink](#DocumentLink) interface.\n     */\n    function is(value: any): value is DocumentLink\n  }\n  /**\n   * A selection range represents a part of a selection hierarchy. A selection range\n   * may have a parent selection range that contains it.\n   */\n  export interface SelectionRange {\n    /**\n     * The [range](#Range) of this selection range.\n     */\n    range: Range\n    /**\n     * The parent selection range containing this range. Therefore `parent.range` must contain `this.range`.\n     */\n    parent?: SelectionRange\n  }\n  /**\n   * The SelectionRange namespace provides helper function to work with\n   * SelectionRange literals.\n   */\n  export namespace SelectionRange {\n    /**\n     * Creates a new SelectionRange\n     * @param range the range.\n     * @param parent an optional parent.\n     */\n    function create(range: Range, parent?: SelectionRange): SelectionRange\n    function is(value: any): value is SelectionRange\n  }\n  /**\n   * Represents programming constructs like functions or constructors in the context\n   * of call hierarchy.\n   *\n   * @since 3.16.0\n   */\n  export interface CallHierarchyItem {\n    /**\n     * The name of this item.\n     */\n    name: string\n    /**\n     * The kind of this item.\n     */\n    kind: SymbolKind\n    /**\n     * Tags for this item.\n     */\n    tags?: SymbolTag[]\n    /**\n     * More detail for this item, e.g. the signature of a function.\n     */\n    detail?: string\n    /**\n     * The resource identifier of this item.\n     */\n    uri: DocumentUri\n    /**\n     * The range enclosing this symbol not including leading/trailing whitespace but everything else, e.g. comments and code.\n     */\n    range: Range\n    /**\n     * The range that should be selected and revealed when this symbol is being picked, e.g. the name of a function.\n     * Must be contained by the [`range`](#CallHierarchyItem.range).\n     */\n    selectionRange: Range\n    /**\n     * A data entry field that is preserved between a call hierarchy prepare and\n     * incoming calls or outgoing calls requests.\n     */\n    data?: LSPAny\n  }\n  /**\n   * Represents an incoming call, e.g. a caller of a method or constructor.\n   *\n   * @since 3.16.0\n   */\n  export interface CallHierarchyIncomingCall {\n    /**\n     * The item that makes the call.\n     */\n    from: CallHierarchyItem\n    /**\n     * The ranges at which the calls appear. This is relative to the caller\n     * denoted by [`this.from`](#CallHierarchyIncomingCall.from).\n     */\n    fromRanges: Range[]\n  }\n  /**\n   * Represents an outgoing call, e.g. calling a getter from a method or a method from a constructor etc.\n   *\n   * @since 3.16.0\n   */\n  export interface CallHierarchyOutgoingCall {\n    /**\n     * The item that is called.\n     */\n    to: CallHierarchyItem\n    /**\n     * The range at which this item is called. This is the range relative to the caller, e.g the item\n     * passed to [`provideCallHierarchyOutgoingCalls`](#CallHierarchyItemProvider.provideCallHierarchyOutgoingCalls)\n     * and not [`this.to`](#CallHierarchyOutgoingCall.to).\n     */\n    fromRanges: Range[]\n  }\n  /**\n   * A set of predefined token types. This set is not fixed\n   * an clients can specify additional token types via the\n   * corresponding client capabilities.\n   *\n   * @since 3.16.0\n   */\n  export enum SemanticTokenTypes {\n    namespace = \"namespace\",\n    /**\n     * Represents a generic type. Acts as a fallback for types which can't be mapped to\n     * a specific type like class or enum.\n     */\n    type = \"type\",\n    class = \"class\",\n    enum = \"enum\",\n    interface = \"interface\",\n    struct = \"struct\",\n    typeParameter = \"typeParameter\",\n    parameter = \"parameter\",\n    variable = \"variable\",\n    property = \"property\",\n    enumMember = \"enumMember\",\n    event = \"event\",\n    function = \"function\",\n    method = \"method\",\n    macro = \"macro\",\n    keyword = \"keyword\",\n    modifier = \"modifier\",\n    comment = \"comment\",\n    string = \"string\",\n    number = \"number\",\n    regexp = \"regexp\",\n    operator = \"operator\",\n    /**\n     * @since 3.17.0\n     */\n    decorator = \"decorator\"\n  }\n  /**\n   * A set of predefined token modifiers. This set is not fixed\n   * an clients can specify additional token types via the\n   * corresponding client capabilities.\n   *\n   * @since 3.16.0\n   */\n  export enum SemanticTokenModifiers {\n    declaration = \"declaration\",\n    definition = \"definition\",\n    readonly = \"readonly\",\n    static = \"static\",\n    deprecated = \"deprecated\",\n    abstract = \"abstract\",\n    async = \"async\",\n    modification = \"modification\",\n    documentation = \"documentation\",\n    defaultLibrary = \"defaultLibrary\"\n  }\n  /**\n   * @since 3.16.0\n   */\n  export interface SemanticTokensLegend {\n    /**\n     * The token types a server uses.\n     */\n    tokenTypes: string[]\n    /**\n     * The token modifiers a server uses.\n     */\n    tokenModifiers: string[]\n  }\n  /**\n   * @since 3.16.0\n   */\n  export interface SemanticTokens {\n    /**\n     * An optional result id. If provided and clients support delta updating\n     * the client will include the result id in the next semantic token request.\n     * A server can then instead of computing all semantic tokens again simply\n     * send a delta.\n     */\n    resultId?: string\n    /**\n     * The actual tokens.\n     */\n    data: uinteger[]\n  }\n  /**\n   * @since 3.16.0\n   */\n  export namespace SemanticTokens {\n    function is(value: any): value is SemanticTokens\n  }\n  /**\n   * @since 3.16.0\n   */\n  export interface SemanticTokensEdit {\n    /**\n     * The start offset of the edit.\n     */\n    start: uinteger\n    /**\n     * The count of elements to remove.\n     */\n    deleteCount: uinteger\n    /**\n     * The elements to insert.\n     */\n    data?: uinteger[]\n  }\n  /**\n   * @since 3.16.0\n   */\n  export interface SemanticTokensDelta {\n    readonly resultId?: string\n    /**\n     * The semantic token edits to transform a previous result into a new result.\n     */\n    edits: SemanticTokensEdit[]\n  }\n  /**\n   * @since 3.17.0\n   */\n  export type TypeHierarchyItem = {\n    /**\n     * The name of this item.\n     */\n    name: string\n    /**\n     * The kind of this item.\n     */\n    kind: SymbolKind\n    /**\n     * Tags for this item.\n     */\n    tags?: SymbolTag[]\n    /**\n     * More detail for this item, e.g. the signature of a function.\n     */\n    detail?: string\n    /**\n     * The resource identifier of this item.\n     */\n    uri: DocumentUri\n    /**\n     * The range enclosing this symbol not including leading/trailing whitespace\n     * but everything else, e.g. comments and code.\n     */\n    range: Range\n    /**\n     * The range that should be selected and revealed when this symbol is being\n     * picked, e.g. the name of a function. Must be contained by the\n     * [`range`](#TypeHierarchyItem.range).\n     */\n    selectionRange: Range\n    /**\n     * A data entry field that is preserved between a type hierarchy prepare and\n     * supertypes or subtypes requests. It could also be used to identify the\n     * type hierarchy in the server, helping improve the performance on\n     * resolving supertypes and subtypes.\n     */\n    data?: LSPAny\n  }\n\n  /**\n   * Provide inline value as text.\n   *\n   * @since 3.17.0\n   */\n  export type InlineValueText = {\n    /**\n     * The document range for which the inline value applies.\n     */\n    range: Range\n    /**\n     * The text of the inline value.\n     */\n    text: string\n  }\n  /**\n   * The InlineValueText namespace provides functions to deal with InlineValueTexts.\n   *\n   * @since 3.17.0\n   */\n  export namespace InlineValueText {\n    /**\n     * Creates a new InlineValueText literal.\n     */\n    function create(range: Range, text: string): InlineValueText\n    function is(value: InlineValue | undefined | null): value is InlineValueText\n  }\n  /**\n   * Provide inline value through a variable lookup.\n   * If only a range is specified, the variable name will be extracted from the underlying document.\n   * An optional variable name can be used to override the extracted name.\n   *\n   * @since 3.17.0\n   */\n  export type InlineValueVariableLookup = {\n    /**\n     * The document range for which the inline value applies.\n     * The range is used to extract the variable name from the underlying document.\n     */\n    range: Range\n    /**\n     * If specified the name of the variable to look up.\n     */\n    variableName?: string\n    /**\n     * How to perform the lookup.\n     */\n    caseSensitiveLookup: boolean\n  }\n  /**\n   * The InlineValueVariableLookup namespace provides functions to deal with InlineValueVariableLookups.\n   *\n   * @since 3.17.0\n   */\n  export namespace InlineValueVariableLookup {\n    /**\n     * Creates a new InlineValueText literal.\n     */\n    function create(range: Range, variableName: string | undefined, caseSensitiveLookup: boolean): InlineValueVariableLookup\n    function is(value: InlineValue | undefined | null): value is InlineValueVariableLookup\n  }\n  /**\n   * Provide an inline value through an expression evaluation.\n   * If only a range is specified, the expression will be extracted from the underlying document.\n   * An optional expression can be used to override the extracted expression.\n   *\n   * @since 3.17.0\n   */\n  export type InlineValueEvaluatableExpression = {\n    /**\n     * The document range for which the inline value applies.\n     * The range is used to extract the evaluatable expression from the underlying document.\n     */\n    range: Range\n    /**\n     * If specified the expression overrides the extracted expression.\n     */\n    expression?: string\n  }\n  /**\n   * The InlineValueEvaluatableExpression namespace provides functions to deal with InlineValueEvaluatableExpression.\n   *\n   * @since 3.17.0\n   */\n  export namespace InlineValueEvaluatableExpression {\n    /**\n     * Creates a new InlineValueEvaluatableExpression literal.\n     */\n    function create(range: Range, expression: string | undefined): InlineValueEvaluatableExpression\n    function is(value: InlineValue | undefined | null): value is InlineValueEvaluatableExpression\n  }\n  /**\n   * Inline value information can be provided by different means:\n   * - directly as a text value (class InlineValueText).\n   * - as a name to use for a variable lookup (class InlineValueVariableLookup)\n   * - as an evaluatable expression (class InlineValueEvaluatableExpression)\n   * The InlineValue types combines all inline value types into one type.\n   *\n   * @since 3.17.0\n   */\n  export type InlineValue = InlineValueText | InlineValueVariableLookup | InlineValueEvaluatableExpression\n  /**\n   * @since 3.17.0\n   */\n  export type InlineValueContext = {\n    /**\n     * The stack frame (as a DAP Id) where the execution has stopped.\n     */\n    frameId: integer\n    /**\n     * The document range where execution has stopped.\n     * Typically the end position of the range denotes the line where the inline values are shown.\n     */\n    stoppedLocation: Range\n  }\n  /**\n   * The InlineValueContext namespace provides helper functions to work with\n   * [InlineValueContext](#InlineValueContext) literals.\n   *\n   * @since 3.17.0\n   */\n  export namespace InlineValueContext {\n    /**\n     * Creates a new InlineValueContext literal.\n     */\n    function create(frameId: integer, stoppedLocation: Range): InlineValueContext\n    /**\n     * Checks whether the given literal conforms to the [InlineValueContext](#InlineValueContext) interface.\n     */\n    function is(value: any): value is InlineValueContext\n  }\n  /**\n   * Inlay hint kinds.\n   *\n   * @since 3.17.0\n   */\n  export namespace InlayHintKind {\n    /**\n     * An inlay hint that for a type annotation.\n     */\n    const Type = 1\n    /**\n     * An inlay hint that is for a parameter.\n     */\n    const Parameter = 2\n    function is(value: number): value is InlayHintKind\n  }\n  export type InlayHintKind = 1 | 2\n  /**\n   * An inlay hint label part allows for interactive and composite labels\n   * of inlay hints.\n   *\n   * @since 3.17.0\n   */\n  export type InlayHintLabelPart = {\n    /**\n     * The value of this label part.\n     */\n    value: string\n    /**\n     * The tooltip text when you hover over this label part. Depending on\n     * the client capability `inlayHint.resolveSupport` clients might resolve\n     * this property late using the resolve request.\n     */\n    tooltip?: string | MarkupContent\n    /**\n     * An optional source code location that represents this\n     * label part.\n     *\n     * The editor will use this location for the hover and for code navigation\n     * features: This part will become a clickable link that resolves to the\n     * definition of the symbol at the given location (not necessarily the\n     * location itself), it shows the hover that shows at the given location,\n     * and it shows a context menu with further code navigation commands.\n     *\n     * Depending on the client capability `inlayHint.resolveSupport` clients\n     * might resolve this property late using the resolve request.\n     */\n    location?: Location\n    /**\n     * An optional command for this label part.\n     *\n     * Depending on the client capability `inlayHint.resolveSupport` clients\n     * might resolve this property late using the resolve request.\n     */\n    command?: Command\n  }\n  export namespace InlayHintLabelPart {\n    function create(value: string): InlayHintLabelPart\n    function is(value: any): value is InlayHintLabelPart\n  }\n  /**\n   * Inlay hint information.\n   *\n   * @since 3.17.0\n   */\n  export type InlayHint = {\n    /**\n     * The position of this hint.\n     */\n    position: Position\n    /**\n     * The label of this hint. A human readable string or an array of\n     * InlayHintLabelPart label parts.\n     *\n     * *Note* that neither the string nor the label part can be empty.\n     */\n    label: string | InlayHintLabelPart[]\n    /**\n     * The kind of this hint. Can be omitted in which case the client\n     * should fall back to a reasonable default.\n     */\n    kind?: InlayHintKind\n    /**\n     * Optional text edits that are performed when accepting this inlay hint.\n     *\n     * *Note* that edits are expected to change the document so that the inlay\n     * hint (or its nearest variant) is now part of the document and the inlay\n     * hint itself is now obsolete.\n     */\n    textEdits?: TextEdit[]\n    /**\n     * The tooltip text when you hover over this item.\n     */\n    tooltip?: string | MarkupContent\n    /**\n     * Render padding before the hint.\n     *\n     * Note: Padding should use the editor's background color, not the\n     * background color of the hint itself. That means padding can be used\n     * to visually align/separate an inlay hint.\n     */\n    paddingLeft?: boolean\n    /**\n     * Render padding after the hint.\n     *\n     * Note: Padding should use the editor's background color, not the\n     * background color of the hint itself. That means padding can be used\n     * to visually align/separate an inlay hint.\n     */\n    paddingRight?: boolean\n    /**\n     * A data entry field that is preserved on an inlay hint between\n     * a `textDocument/inlayHint` and a `inlayHint/resolve` request.\n     */\n    data?: LSPAny\n  }\n  export namespace InlayHint {\n    function create(position: Position, label: string | InlayHintLabelPart[], kind?: InlayHintKind): InlayHint\n    function is(value: any): value is InlayHint\n  }\n  /**\n   * A workspace folder inside a client.\n   */\n  export interface WorkspaceFolder {\n    /**\n     * The associated URI for this workspace folder.\n     */\n    uri: string\n    /**\n     * The name of the workspace folder. Used to refer to this\n     * workspace folder in the user interface.\n     */\n    name: string\n  }\n  export namespace WorkspaceFolder {\n    function is(value: any): value is WorkspaceFolder\n  }\n  export const EOL: string[]\n  /**\n   * A simple text document. Not to be implemented. The document keeps the content\n   * as string.\n   */\n  export interface TextDocument {\n    /**\n     * The associated URI for this document. Most documents have the __file__-scheme, indicating that they\n     * represent files on disk. However, some documents may have other schemes indicating that they are not\n     * available on disk.\n     *\n     * @readonly\n     */\n    readonly uri: DocumentUri\n    /**\n     * The identifier of the language associated with this document.\n     *\n     * @readonly\n     */\n    readonly languageId: string\n    /**\n     * The version number of this document (it will increase after each\n     * change, including undo/redo).\n     *\n     * @readonly\n     */\n    readonly version: integer\n    /**\n     * Get the text of this document. A substring can be retrieved by\n     * providing a range.\n     *\n     * @param range (optional) An range within the document to return.\n     * If no range is passed, the full content is returned.\n     * Invalid range positions are adjusted as described in [Position.line](#Position.line)\n     * and [Position.character](#Position.character).\n     * If the start range position is greater than the end range position,\n     * then the effect of getText is as if the two positions were swapped.\n\n     * @return The text of this document or a substring of the text if a\n     *         range is provided.\n     */\n    getText(range?: Range): string\n    /**\n     * Converts a zero-based offset to a position.\n     *\n     * @param offset A zero-based offset.\n     * @return A valid [position](#Position).\n     */\n    positionAt(offset: uinteger): Position\n    /**\n     * Converts the position to a zero-based offset.\n     * Invalid positions are adjusted as described in [Position.line](#Position.line)\n     * and [Position.character](#Position.character).\n     *\n     * @param position A position.\n     * @return A valid zero-based offset.\n     */\n    offsetAt(position: Position): uinteger\n    /**\n     * The number of lines in this document.\n     *\n     * @readonly\n     */\n    readonly lineCount: uinteger\n  }\n\n  /**\n   * Describes how an {@link InlineCompletionItemProvider inline completion provider} was triggered.\n   */\n  export namespace InlineCompletionTriggerKind {\n    /**\n     * Completion was triggered explicitly by a user gesture.\n     */\n    const Invoked: 1\n    /**\n     * Completion was triggered automatically while editing.\n     */\n    const Automatic: 2\n  }\n\n  export interface InlineCompletionOption {\n    /**\n     * The provider name, extension name or LanguageClient id.\n     */\n    provider?: string\n    /**\n     * Set trigger kind to InlineCompletionTriggerKind.Automatic when true.\n     */\n    autoTrigger?: boolean\n  }\n\n  export type InlineCompletionTriggerKind = 1 | 2\n\n  /**\n   * Describes the currently selected completion item.\n   */\n  export type SelectedCompletionInfo = {\n    /**\n     * The range that will be replaced if this completion item is accepted.\n     */\n    range: Range\n    /**\n     * The text the range will be replaced with if this completion is accepted.\n     */\n    text: string\n  }\n\n  export namespace SelectedCompletionInfo {\n    function create(range: Range, text: string): SelectedCompletionInfo\n  }\n\n  /**\n  * Provides information about the context in which an inline completion was requested.\n  */\n  export type InlineCompletionContext = {\n    /**\n    * Describes how the inline completion was triggered.\n    */\n    triggerKind: InlineCompletionTriggerKind\n    /**\n    * Provides information about the currently selected item in the autocomplete widget if it is visible.\n    */\n    selectedCompletionInfo?: SelectedCompletionInfo\n  }\n\n  export namespace InlineCompletionContext {\n    function create(triggerKind: InlineCompletionTriggerKind, selectedCompletionInfo?: SelectedCompletionInfo): InlineCompletionContext\n  }\n\n  /**\n   * A string value used as a snippet is a template which allows to insert text\n   * and to control the editor cursor when insertion happens.\n   *\n   * A snippet can define tab stops and placeholders with `$1`, `$2`\n   * and `${3:foo}`. `$0` defines the final tab stop, it defaults to\n   * the end of the snippet. Variables are defined with `$name` and\n   * `${name:default value}`.\n   */\n  export type StringValue = {\n    /**\n     * The kind of string value.\n     */\n    kind: 'snippet'\n    /**\n     * The snippet string.\n     */\n    value: string\n  }\n\n  export namespace StringValue {\n    function createSnippet(value: string): StringValue\n    function isSnippet(value: any): value is StringValue\n  }\n  /**\n   * An inline completion item represents a text snippet that is proposed inline to complete text that is being typed.\n   */\n  export interface InlineCompletionItem {\n    /**\n     * The text to replace the range with. Must be set.\n     */\n    insertText: string | StringValue\n    /**\n     * A text that is used to decide if this inline completion should be shown. When `falsy` the {@link InlineCompletionItem.insertText} is used.\n     */\n    filterText?: string\n    /**\n     * The range to replace. Must begin and end on the same line.\n     */\n    range?: Range\n    /**\n     * An optional {@link Command} that is executed *after* inserting this completion.\n     */\n    command?: Command\n  }\n\n  export namespace InlineCompletionItem {\n    function create(insertText: string | StringValue, filterText?: string, range?: Range, command?: Command): InlineCompletionItem\n  }\n\n  /**\n   * Represents a collection of {@link InlineCompletionItem inline completion items} to be presented in the editor.\n   */\n  export interface InlineCompletionList {\n    /**\n     * The inline completion items\n     */\n    items: InlineCompletionItem[]\n  }\n  export namespace InlineCompletionList {\n    function create(items: InlineCompletionItem[]): InlineCompletionList\n  }\n\n  /**\n  * An interactive text edit.\n  */\n  export interface SnippetTextEdit {\n    /**\n    * The range of the text document to be manipulated.\n    */\n    range: Range\n    /**\n    * The snippet to be inserted.\n    */\n    snippet: StringValue\n    /**\n    * The actual identifier of the snippet edit.\n    */\n    annotationId?: ChangeAnnotationIdentifier\n  }\n\n  export namespace SnippetTextEdit {\n    function is(value: any): value is SnippetTextEdit\n  }\n\n  /**\n   * Defines how values from a set of defaults and an individual item will be\n   * merged.\n   */\n  export namespace ApplyKind {\n    /**\n     * The value from the individual item (if provided and not `null`) will be\n     * used instead of the default.\n     */\n    const Replace: 1\n    /**\n     * The value from the item will be merged with the default.\n     *\n     * The specific rules for merging values are defined against each field\n     * that supports merging.\n     */\n    const Merge: 2\n  }\n  /**\n   * Defines how values from a set of defaults and an individual item will be\n   * merged.\n   */\n  export type ApplyKind = 1 | 2\n\n  /**\n  * Additional data about a workspace edit.\n  */\n  export type WorkspaceEditMetadata = {\n    /**\n    * Signal to the editor that this edit is a refactoring.\n    */\n    isRefactoring?: boolean\n  }\n  /**\n  * The parameters passed via an apply workspace edit request.\n  */\n  export interface ApplyWorkspaceEditParams {\n    /**\n    * An optional label of the workspace edit. This label is\n    * presented in the user interface for example on an undo\n    * stack to undo the workspace edit.\n    */\n    label?: string\n    /**\n    * The edits to apply.\n    */\n    edit: WorkspaceEdit\n    /**\n    * Additional data about the edit.\n    *\n    * @since 3.18.0\n    * @proposed\n    */\n    metadata?: WorkspaceEditMetadata\n  }\n\n  /**\n   * The result returned from the apply workspace edit request.\n   */\n  export interface ApplyWorkspaceEditResult {\n    /**\n     * Indicates whether the edit was applied or not.\n     */\n    applied: boolean\n    /**\n     * An optional textual description for why the edit was not applied.\n     * This may be used by the server for diagnostic logging or to provide\n     * a suitable error for a request that triggered the edit.\n     */\n    failureReason?: string\n    /**\n     * Depending on the client's failure handling strategy `failedChange` might\n     * contain the index of the change that failed. This property is only available\n     * if the client signals a `failureHandlingStrategy` in its client capabilities.\n     */\n    failedChange?: uinteger\n  }\n  // }}\n\n  // Language server protocol interfaces {{\n  export interface Thenable<T> {\n    then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Thenable<TResult>\n    // eslint-disable-next-line @typescript-eslint/unified-signatures\n    then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => void): Thenable<TResult>\n  }\n\n  export interface Disposable {\n    /**\n     * Dispose this object.\n     */\n    dispose(): void\n  }\n\n  export namespace Disposable {\n    function create(func: () => void): Disposable\n  }\n\n  /**\n   * A parameter literal used in requests to pass a text document and a position inside that\n   * document.\n   */\n  export interface TextDocumentPositionParams {\n    /**\n     * The text document.\n     */\n    textDocument: TextDocumentIdentifier\n    /**\n     * The position inside the text document.\n     */\n    position: Position\n  }\n\n  /**\n   * An event describing a change to a text document.\n   */\n  export interface TextDocumentContentChange {\n    /**\n     * The range of the document that changed.\n     */\n    range: Range\n    /**\n     * The new text for the provided range.\n     */\n    text: string\n  }\n\n  /**\n   * The workspace folder change event.\n   */\n  export interface WorkspaceFoldersChangeEvent {\n    /**\n     * The array of added workspace folders\n     */\n    added: WorkspaceFolder[]\n    /**\n     * The array of the removed workspace folders\n     */\n    removed: WorkspaceFolder[]\n  }\n\n  /**\n   * An event that is fired when a [document](#LinesTextDocument) will be saved.\n   *\n   * To make modifications to the document before it is being saved, call the\n   * [`waitUntil`](#TextDocumentWillSaveEvent.waitUntil)-function with a thenable\n   * that resolves to an array of [text edits](#TextEdit).\n   */\n  export interface TextDocumentWillSaveEvent {\n\n    /**\n     * The document that will be saved.\n     */\n    document: LinesTextDocument\n\n    /**\n     * The reason why save was triggered.\n     */\n    reason: 1 | 2 | 3\n  }\n\n  /**\n   * A document filter denotes a document by different properties like\n   * the [language](#LinesTextDocument.languageId), the [scheme](#Uri.scheme) of\n   * its resource, or a glob-pattern that is applied to the [path](#LinesTextDocument.fileName).\n   *\n   * Glob patterns can have the following syntax:\n   * - `*` to match one or more characters in a path segment\n   * - `?` to match on one character in a path segment\n   * - `**` to match any number of path segments, including none\n   * - `{}` to group conditions (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files)\n   * - `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)\n   * - `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`)\n   *\n   * @sample A language filter that applies to typescript files on disk: `{ language: 'typescript', scheme: 'file' }`\n   * @sample A language filter that applies to all package.json paths: `{ language: 'json', pattern: '**package.json' }`\n   */\n  export type DocumentFilter = {\n    /** A language id, like `typescript`. */\n    language: string\n    /** A Uri [scheme](#Uri.scheme), like `file` or `untitled`. */\n    scheme?: string\n    /** A glob pattern, like `*.{ts,js}`. */\n    pattern?: string\n  } | {\n    /** A language id, like `typescript`. */\n    language?: string\n    /** A Uri [scheme](#Uri.scheme), like `file` or `untitled`. */\n    scheme: string\n    /** A glob pattern, like `*.{ts,js}`. */\n    pattern?: string\n  } | {\n    /** A language id, like `typescript`. */\n    language?: string\n    /** A Uri [scheme](#Uri.scheme), like `file` or `untitled`. */\n    scheme?: string\n    /** A glob pattern, like `*.{ts,js}`. */\n    pattern: string\n  }\n\n  /**\n    * A language selector is the combination of one or many language identifiers\n    * and {@link DocumentFilter language filters}.\n    *\n    * *Note* that a document selector that is just a language identifier selects *all*\n    * documents, even those that are not saved on disk. Only use such selectors when\n    * a feature works without further context, e.g. without the need to resolve related\n    * 'files'.\n    *\n    * @example\n    * let sel:DocumentSelector = { scheme: 'file', language: 'typescript' };\n    */\n  export type DocumentSelector = DocumentFilter | string | ReadonlyArray<DocumentFilter | string>\n\n  /**\n   * How a signature help was triggered.\n   */\n  export namespace SignatureHelpTriggerKind {\n    /**\n    * Signature help was invoked manually by the user or by a command.\n    */\n    const Invoked: 1\n    /**\n    * Signature help was triggered by a trigger character.\n    */\n    const TriggerCharacter: 2\n    /**\n    * Signature help was triggered by the cursor moving or by the document content changing.\n    */\n    const ContentChange: 3\n  }\n\n  export type SignatureHelpTriggerKind = 1 | 2 | 3\n\n  /**\n   * Additional information about the context in which a signature help request was triggered.\n   *\n   * @since 3.15.0\n   */\n  export interface SignatureHelpContext {\n    /**\n     * Action that caused signature help to be triggered.\n     */\n    triggerKind: SignatureHelpTriggerKind\n    /**\n     * Character that caused signature help to be triggered.\n     *\n     * This is undefined when `triggerKind !== SignatureHelpTriggerKind.TriggerCharacter`\n     */\n    triggerCharacter?: string\n    /**\n     * `true` if signature help was already showing when it was triggered.\n     *\n     * Retriggers occur when the signature help is already active and can be caused by actions such as\n     * typing a trigger character, a cursor move, or document content changes.\n     */\n    isRetrigger: boolean\n    /**\n     * The currently active `SignatureHelp`.\n     *\n     * The `activeSignatureHelp` has its `SignatureHelp.activeSignature` field updated based on\n     * the user navigating through available signatures.\n     */\n    activeSignatureHelp?: SignatureHelp\n  }\n\n  /**\n   * How a completion was triggered\n   */\n  export namespace CompletionTriggerKind {\n    /**\n     * Completion was triggered by typing an identifier (24x7 code\n     * complete), manual invocation (e.g Ctrl+Space) or via API.\n     */\n    const Invoked: 1\n    /**\n     * Completion was triggered by a trigger character specified by\n     * the `triggerCharacters` properties of the `CompletionRegistrationOptions`.\n     */\n    const TriggerCharacter: 2\n    /**\n     * Completion was re-triggered as current completion list is incomplete\n     */\n    const TriggerForIncompleteCompletions: 3\n  }\n\n  export type CompletionTriggerKind = 1 | 2 | 3\n\n  /**\n   * Contains additional information about the context in which a completion request is triggered.\n   */\n  export interface CompletionContext {\n    /**\n     * How the completion was triggered.\n     */\n    triggerKind: CompletionTriggerKind,\n    /**\n     * The trigger character (a single character) that has trigger code complete.\n     * Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter`\n     */\n    triggerCharacter?: string\n\n    option: CompleteOption\n  }\n\n  /**\n   * Represents a typed event.\n   *\n   * A function that represents an event to which you subscribe by calling it with\n   * a listener function as argument.\n   *\n   * @example\n   * item.onDidChange(function(event) { console.log(\"Event happened: \" + event); });\n   */\n  export interface Event<T> {\n\n    /**\n     * A function that represents an event to which you subscribe by calling it with\n     * a listener function as argument.\n     *\n     * @param listener The listener function will be called when the event happens.\n     * @param thisArgs The `this`-argument which will be used when calling the event listener.\n     * @param disposables An array to which a [disposable](#Disposable) will be added.\n     * @return A disposable which unsubscribes the event listener.\n     */\n    (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]): Disposable\n  }\n\n  export namespace Event {\n    const None: Event<any>\n  }\n\n  export interface EmitterOptions {\n    onFirstListenerAdd?: Function\n    onLastListenerRemove?: Function\n  }\n\n  export class Emitter<T> {\n    constructor(_options?: EmitterOptions | undefined)\n    /**\n     * For the public to allow to subscribe\n     * to events from this Emitter\n     */\n    get event(): Event<T>\n    /**\n     * To be kept private to fire an event to\n     * subscribers\n     */\n    fire(event: T): any\n    dispose(): void\n  }\n\n  /**\n   * Defines a CancellationToken. This interface is not\n   * intended to be implemented. A CancellationToken must\n   * be created via a CancellationTokenSource.\n   */\n  export interface CancellationToken {\n    /**\n     * Is `true` when the token has been cancelled, `false` otherwise.\n     */\n    readonly isCancellationRequested: boolean\n    /**\n     * An [event](#Event) which fires upon cancellation.\n     */\n    readonly onCancellationRequested: Event<any>\n  }\n\n  export namespace CancellationToken {\n    const None: CancellationToken\n    const Cancelled: CancellationToken\n    function is(value: any): value is CancellationToken\n  }\n\n  export class CancellationTokenSource {\n    get token(): CancellationToken\n    cancel(): void\n    dispose(): void\n  }\n\n  /**\n   * Represents a line of text, such as a line of source code.\n   *\n   * TextLine objects are __immutable__. When a {@link LinesTextDocument document} changes,\n   * previously retrieved lines will not represent the latest state.\n   */\n  export interface TextLine {\n    /**\n     * The zero-based line number.\n     */\n    readonly lineNumber: number\n\n    /**\n     * The text of this line without the line separator characters.\n     */\n    readonly text: string\n\n    /**\n     * The range this line covers without the line separator characters.\n     */\n    readonly range: Range\n\n    /**\n     * The range this line covers with the line separator characters.\n     */\n    readonly rangeIncludingLineBreak: Range\n\n    /**\n     * The offset of the first character which is not a whitespace character as defined\n     * by `/\\s/`. **Note** that if a line is all whitespace the length of the line is returned.\n     */\n    readonly firstNonWhitespaceCharacterIndex: number\n\n    /**\n     * Whether this line is whitespace only, shorthand\n     * for {@link TextLine.firstNonWhitespaceCharacterIndex} === {@link TextLine.text TextLine.text.length}.\n     */\n    readonly isEmptyOrWhitespace: boolean\n  }\n\n  export interface LinesTextDocument extends TextDocument {\n    /**\n     * Total length of TextDocument.\n     */\n    readonly length: number\n    /**\n     * End position of TextDocument.\n     */\n    readonly end: Position\n    /**\n     * 'eol' option of related buffer. When enabled additional `\\n` will be\n     * added to the end of document content\n     */\n    readonly eol: boolean\n    /**\n     * Lines of TextDocument.\n     */\n    readonly lines: ReadonlyArray<string>\n    /**\n     * Returns a text line denoted by the line number. Note\n     * that the returned object is *not* live and changes to the\n     * document are not reflected.\n     *\n     * @param line or position\n     * @return A {@link TextLine line}.\n     */\n    lineAt(lineOrPos: number | Position): TextLine\n  }\n\n  /**\n  * The result of a linked editing range request.\n  *\n  * @since 3.16.0\n  */\n  export interface LinkedEditingRanges {\n    /**\n    * A list of ranges that can be edited together. The ranges must have\n    * identical length and contain identical text content. The ranges cannot overlap.\n    */\n    ranges: Range[]\n    /**\n    * An optional word pattern (regular expression) that describes valid contents for\n    * the given ranges. If no pattern is provided, the client configuration's word\n    * pattern will be used.\n    */\n    wordPattern?: string\n  }\n\n  /**\n   * Moniker uniqueness level to define scope of the moniker.\n   *\n   * @since 3.16.0\n   */\n  export namespace UniquenessLevel {\n    /**\n     * The moniker is only unique inside a document\n     */\n    export const document = 'document'\n\n    /**\n     * The moniker is unique inside a project for which a dump got created\n     */\n    export const project = 'project'\n\n    /**\n     * The moniker is unique inside the group to which a project belongs\n     */\n    export const group = 'group'\n\n    /**\n     * The moniker is unique inside the moniker scheme.\n     */\n    export const scheme = 'scheme'\n\n    /**\n     * The moniker is globally unique\n     */\n    export const global = 'global'\n  }\n\n  export type UniquenessLevel = 'document' | 'project' | 'group' | 'scheme' | 'global'\n\n  /**\n   * The moniker kind.\n   *\n   * @since 3.16.0\n   */\n  export enum MonikerKind {\n    /**\n     * The moniker represent a symbol that is imported into a project\n     */\n    import = 'import',\n\n    /**\n     * The moniker represents a symbol that is exported from a project\n     */\n    export = 'export',\n\n    /**\n     * The moniker represents a symbol that is local to a project (e.g. a local\n     * variable of a function, a class not visible outside the project, ...)\n     */\n    local = 'local'\n  }\n\n  /**\n   * Moniker definition to match LSIF 0.5 moniker definition.\n   *\n   * @since 3.16.0\n   */\n  export interface Moniker {\n    /**\n     * The scheme of the moniker. For example tsc or .Net\n     */\n    scheme: string\n\n    /**\n     * The identifier of the moniker. The value is opaque in LSIF however\n     * schema owners are allowed to define the structure if they want.\n     */\n    identifier: string\n\n    /**\n     * The scope in which the moniker is unique\n     */\n    unique: UniquenessLevel\n\n    /**\n     * The moniker kind if known.\n     */\n    kind?: MonikerKind\n  }\n\n  /**\n   * A previous result id in a workspace pull request.\n   *\n   * @since 3.17.0\n   */\n  export type PreviousResultId = {\n    /**\n     * The URI for which the client knowns a\n     * result id.\n     */\n    uri: string\n    /**\n     * The value of the previous result id.\n     */\n    value: string\n  }\n\n  export type DocumentDiagnosticReportKind = 'full' | 'unchanged'\n\n  /**\n   * The document diagnostic report kinds.\n   *\n   * @since 3.17.0\n   */\n  export namespace DocumentDiagnosticReportKind {\n    /**\n     * A diagnostic report with a full\n     * set of problems.\n     */\n    const Full = \"full\"\n    /**\n     * A report indicating that the last\n     * returned report is still accurate.\n     */\n    const Unchanged = \"unchanged\"\n  }\n  /**\n   * A diagnostic report with a full set of problems.\n   *\n   * @since 3.17.0\n   */\n  export type FullDocumentDiagnosticReport = {\n    /**\n     * A full document diagnostic report.\n     */\n    kind: typeof DocumentDiagnosticReportKind.Full\n    /**\n     * An optional result id. If provided it will\n     * be sent on the next diagnostic request for the\n     * same document.\n     */\n    resultId?: string\n    /**\n     * The actual items.\n     */\n    items: Diagnostic[]\n  }\n\n  /**\n   * A diagnostic report indicating that the last returned\n   * report is still accurate.\n   *\n   * @since 3.17.0\n   */\n  export type UnchangedDocumentDiagnosticReport = {\n    /**\n     * A document diagnostic report indicating\n     * no changes to the last result. A server can\n     * only return `unchanged` if result ids are\n     * provided.\n     */\n    kind: typeof DocumentDiagnosticReportKind.Unchanged\n    /**\n     * A result id which will be sent on the next\n     * diagnostic request for the same document.\n     */\n    resultId: string\n  }\n\n  /**\n   * An unchanged diagnostic report with a set of related documents.\n   *\n   * @since 3.17.0\n   */\n  export type RelatedUnchangedDocumentDiagnosticReport = UnchangedDocumentDiagnosticReport & {\n    /**\n     * Diagnostics of related documents. This information is useful\n     * in programming languages where code in a file A can generate\n     * diagnostics in a file B which A depends on. An example of\n     * such a language is C/C++ where marco definitions in a file\n     * a.cpp and result in errors in a header file b.hpp.\n     *\n     * @since 3.17.0\n     */\n    relatedDocuments?: {\n      [uri: string]: FullDocumentDiagnosticReport | UnchangedDocumentDiagnosticReport\n    }\n  }\n\n  export type RelatedFullDocumentDiagnosticReport = FullDocumentDiagnosticReport & {\n    /**\n     * Diagnostics of related documents. This information is useful\n     * in programming languages where code in a file A can generate\n     * diagnostics in a file B which A depends on. An example of\n     * such a language is C/C++ where marco definitions in a file\n     * a.cpp and result in errors in a header file b.hpp.\n     *\n     * @since 3.17.0\n     */\n    relatedDocuments?: {\n      [uri: string]: FullDocumentDiagnosticReport | UnchangedDocumentDiagnosticReport\n    }\n  }\n  export type DocumentDiagnosticReport = RelatedFullDocumentDiagnosticReport | RelatedUnchangedDocumentDiagnosticReport\n\n  /*\n   * A workspace diagnostic report.\n   *\n   * @since 3.17.0\n   */\n  export type WorkspaceDiagnosticReport = {\n    items: WorkspaceDocumentDiagnosticReport[]\n  }\n  /**\n   * A partial result for a workspace diagnostic report.\n   *\n   * @since 3.17.0\n   */\n  export type WorkspaceDiagnosticReportPartialResult = {\n    items: WorkspaceDocumentDiagnosticReport[]\n  }\n\n  export type WorkspaceFullDocumentDiagnosticReport = FullDocumentDiagnosticReport & {\n    /**\n    * The URI for which diagnostic information is reported.\n    */\n    uri: string\n    /**\n    * The version number for which the diagnostics are reported.\n    * If the document is not marked as open `null` can be provided.\n    */\n    version: number | null\n  }\n\n  export type WorkspaceUnchangedDocumentDiagnosticReport = UnchangedDocumentDiagnosticReport & {\n    /**\n     * The URI for which diagnostic information is reported.\n     */\n    uri: string\n    /**\n     * The version number for which the diagnostics are reported.\n     * If the document is not marked as open `null` can be provided.\n     */\n    version: number | null\n  }\n\n  export type WorkspaceDocumentDiagnosticReport = WorkspaceFullDocumentDiagnosticReport | WorkspaceUnchangedDocumentDiagnosticReport\n\n  export interface ResultReporter {\n    (chunk: WorkspaceDiagnosticReportPartialResult | null): void\n  }\n\n  export type ErrorCodes = number\n\n  /**\n   * Predefined error codes.\n   */\n  export namespace ErrorCodes {\n    const ParseError: -32700\n    const InvalidRequest: -32600\n    const MethodNotFound: -32601\n    const InvalidParams: -32602\n    const InternalError: -32603\n    /**\n     * This is the start range of JSON RPC reserved error codes.\n     * It doesn't denote a real error code. No application error codes should\n     * be defined between the start and end range. For backwards\n     * compatibility the `ServerNotInitialized` and the `UnknownErrorCode`\n     * are left in the range.\n     *\n     * @since 3.16.0\n    */\n    const jsonrpcReservedErrorRangeStart: -32099\n    /** @deprecated use  jsonrpcReservedErrorRangeStart */\n    const serverErrorStart: -32099\n    /**\n     * An error occurred when write a message to the transport layer.\n     */\n    const MessageWriteError: -32099\n    /**\n     * An error occurred when reading a message from the transport layer.\n     */\n    const MessageReadError: -32098\n    /**\n     * The connection got disposed or lost and all pending responses got\n     * rejected.\n     */\n    const PendingResponseRejected: -32097\n    /**\n     * The connection is inactive and a use of it failed.\n     */\n    const ConnectionInactive: -32096\n    /**\n     * Error code indicating that a server received a notification or\n     * request before the server has received the `initialize` request.\n     */\n    const ServerNotInitialized: -32002\n    const UnknownErrorCode: -32001\n    /**\n     * This is the end range of JSON RPC reserved error codes.\n     * It doesn't denote a real error code.\n     *\n     * @since 3.16.0\n    */\n    const jsonrpcReservedErrorRangeEnd: -32000\n    /** @deprecated use  jsonrpcReservedErrorRangeEnd */\n    const serverErrorEnd: -32000\n  }\n\n  export interface ResponseErrorLiteral<D = void> {\n    /**\n     * A number indicating the error type that occurred.\n     */\n    code: number\n    /**\n     * A string providing a short description of the error.\n     */\n    message: string\n    /**\n     * A Primitive or Structured value that contains additional\n     * information about the error. Can be omitted.\n     */\n    data?: D\n  }\n\n  /**\n   * An error object return in a response in case a request\n   * has failed.\n   */\n  export class ResponseError<D = void> extends Error {\n    readonly code: number\n    readonly data: D | undefined\n    constructor(code: number, message: string, data?: D)\n    toJson(): ResponseErrorLiteral<D>\n  }\n\n  /**\n   * A language server message\n   */\n  export interface Message {\n    jsonrpc: string\n  }\n\n  export interface AbstractCancellationTokenSource extends Disposable {\n    token: CancellationToken\n    cancel(): void\n  }\n\n  /**\n   * A response message.\n   */\n  export interface ResponseMessage extends Message {\n    /**\n     * The request id.\n     */\n    id: number | string | null\n    /**\n     * The result of a request. This member is REQUIRED on success.\n     * This member MUST NOT exist if there was an error invoking the method.\n     */\n    result?: string | number | boolean | object | any[] | null\n    /**\n     * The error object in case a request fails.\n     */\n    error?: ResponseErrorLiteral<any>\n  }\n  // }}\n\n  // nvim interfaces {{\n  type VimValue =\n    | number\n    | boolean\n    | string\n    | number[]\n    | { [key: string]: any }\n\n  // see `:h nvim_set_client_info()` for details.\n  export interface VimClientInfo {\n    name: string\n    version: {\n      major?: number\n      minor?: number\n      patch?: number\n      prerelease?: string\n      commit?: string\n    }\n    type: 'remote' | 'embedder' | 'host'\n    methods?: {\n      [index: string]: any\n    }\n    attributes?: {\n      [index: string]: any\n    }\n  }\n\n  export interface UiAttachOptions {\n    rgb?: boolean\n    ext_popupmenu?: boolean\n    ext_tabline?: boolean\n    ext_wildmenu?: boolean\n    ext_cmdline?: boolean\n    ext_linegrid?: boolean\n    ext_hlstate?: boolean\n  }\n\n  export interface ChanInfo {\n    id: number\n    stream: 'stdio' | 'stderr' | 'socket' | 'job'\n    mode: 'bytes' | 'terminal' | 'rpc'\n    pty?: number\n    buffer?: number\n    client?: VimClientInfo\n  }\n\n  /**\n   * Returned by nvim_get_commands api.\n   */\n  export interface VimCommandDescription {\n    name: string\n    bang: boolean\n    bar: boolean\n    register: boolean\n    definition: string\n    count?: number | null\n    script_id: number\n    complete?: string\n    nargs?: string\n    range?: string\n    complete_arg?: string\n  }\n\n  export interface NvimFloatOptions {\n    standalone?: boolean\n    focusable?: boolean\n    relative?: 'editor' | 'cursor' | 'win' | 'mouse'\n    anchor?: 'NW' | 'NE' | 'SW' | 'SE'\n    border?: 'none' | 'single' | 'double' | 'rounded' | 'solid' | 'shadow' | string[]\n    style?: 'minimal'\n    title?: string\n    title_pos?: 'left' | 'center' | 'right'\n    footer?: string | [string, string][]\n    footer_pos?: 'left' | 'center' | 'right'\n    noautocmd?: boolean\n    fixed?: boolean\n    hide?: boolean\n    height: number\n    width: number\n    row: number\n    col: number\n  }\n\n  export interface ExtmarkOptions {\n    id?: number\n    // 0-based inclusive.\n    end_line?: number\n    // 0-based exclusive.\n    end_col?: number\n    //  name of the highlight group used to highlight this mark.\n    hl_group?: string\n    hl_mode?: 'replace' | 'combine' | 'blend'\n    hl_eol?: boolean\n    // A list of [text, highlight] tuples\n    virt_text?: [string, string | string[]][]\n    virt_text_pos?: 'eol' | 'overlay' | 'right_align' | 'inline'\n    virt_text_win_col?: number\n    virt_text_hide?: boolean\n    virt_lines?: [string, string | string[]][][]\n    virt_lines_above?: boolean\n    virt_lines_leftcol?: boolean\n    right_gravity?: boolean\n    end_right_gravity?: boolean\n    priority?: number\n  }\n\n  export interface ExtmarkDetails {\n    end_col: number\n    end_row: number\n    priority: number\n    hl_group?: string\n    virt_text?: [string, string][]\n    virt_lines?: [string, string | string][][]\n  }\n\n  export interface NvimProc {\n    ppid: number\n    name: string\n    pid: number\n  }\n\n  export interface SignPlaceOption {\n    id?: number\n    group?: string\n    name: string\n    lnum: number\n    priority?: number\n  }\n\n  export interface SignUnplaceOption {\n    group?: string\n    id?: number\n  }\n\n  export interface SignPlacedOption {\n    /**\n     * Use '*' for all group, default to '' as unnamed group.\n     */\n    group?: string\n    id?: number\n    lnum?: number\n  }\n\n  export interface SignItem {\n    group: string\n    id: number\n    lnum: number\n    name: string\n    priority: number\n  }\n\n  export interface HighlightItem {\n    hlGroup: string\n    /**\n    * 0 based\n    */\n    lnum: number\n    /**\n    * 0 based\n    */\n    colStart: number\n    /**\n    * 0 based\n    */\n    colEnd: number\n  }\n\n  export interface ExtendedHighlightItem extends HighlightItem {\n    combine?: boolean\n    start_incl?: boolean\n    end_incl?: boolean\n  }\n\n  export interface HighlightOption {\n    /**\n     * 0 based start line, default to 0.\n     */\n    start?: number\n    /**\n     * 0 based end line, default to 0.\n     */\n    end?: number\n    /**\n     * Default to 0 on vim8, 4096 on neovim\n     */\n    priority?: number\n    /**\n     * Buffer changedtick to match.\n     */\n    changedtick?: number\n  }\n\n  /**\n   * All values default to `false`, see `:h :map-arguments`\n   */\n  export interface BufferKeymapOption {\n    desc?: string\n    noremap?: boolean\n    nowait?: boolean\n    silent?: boolean\n    script?: boolean\n    expr?: boolean\n    unique?: boolean\n    // vim9 only\n    special?: boolean\n  }\n\n  export interface BufferHighlight {\n    /**\n     * Name of the highlight group to use\n     */\n    hlGroup?: string\n    /**\n     * Namespace to use or -1 for ungrouped highlight\n     */\n    srcId?: number\n    /**\n     * Line to highlight (zero-indexed)\n     */\n    line?: number\n    /**\n     * Start of (byte-indexed) column range to highlight\n     */\n    colStart?: number\n    /**\n     * End of (byte-indexed) column range to highlight, or -1 to highlight to end of line\n     */\n    colEnd?: number\n  }\n\n  export interface BufferClearHighlight {\n    srcId?: number\n    lineStart?: number\n    lineEnd?: number\n  }\n\n  export interface VirtualTextOption {\n    /**\n     * Used on vim9 and neovim >= 0.10.0.\n     */\n    col?: number\n    /**\n     * Add line indent when text_align is below or above.\n     */\n    indent?: boolean\n    /**\n     * highlight mode, blend is neovim only (replace is used on vim when specified).\n     */\n    hl_mode?: 'combine' | 'replace' | 'blend'\n    /**\n     * neovim and vim.\n     */\n    text_align?: 'after' | 'right' | 'below' | 'above'\n    /**\n     * neovim only, right_gravity of nvim_buf_set_extmark.\n     */\n    right_gravity?: boolean\n    /**\n     * neovim only\n     */\n    virt_text_win_col?: number\n    /**\n     * vim9 only\n     */\n    text_wrap?: 'wrap' | 'truncate'\n  }\n\n  export interface AugroupOption {\n    /**\n     * Clear the all autocmds before create autocmd group, default to `true`.\n     */\n    clear?: boolean\n  }\n\n  interface AutocmdOption {\n    /**\n     * Group name or group id from `nvim.createAugroup()`, see `:h autocmd-groups`.\n     */\n    group?: string | number\n    /**\n     * Pattern to match, see `:h autocmd-pattern`.\n     */\n    pattern?: string | string[]\n    /**\n     * Buffer number for buflocal autocommand, see `:h autocmd-buflocal`.\n     */\n    buffer?: number\n    /**\n    * Description test, not used on vim9.\n    */\n    desc?: string\n    /**\n     * Vim command to run when trigger autocommand.\n     */\n    command?: string\n    /**\n     * See `:h autocmd-once`.\n     */\n    once?: boolean\n    /**\n     * See `:h autocmd-nested`.\n     */\n    nested?: boolean\n    /**\n     * Vim9 only, see `:h autocmd_add()`\n     */\n    replace?: boolean\n  }\n\n  interface BaseApi<T> {\n    /**\n     * unique identify number\n     */\n    id: number\n\n    /**\n     * Check if same by compare id.\n     */\n    equals(other: T): boolean\n\n    /**\n     * Request to vim, name need to be nvim_ prefixed and supported by vim.\n     *\n     * @param {string} name - nvim function name\n     * @param {VimValue[]} args\n     * @returns {Promise<VimValue>}\n     */\n    request(name: string, args?: VimValue[]): Promise<any>\n\n    /**\n     * Send notification to vim, name need to be nvim_ prefixed and supported\n     * by vim\n     */\n    notify(name: string, args?: VimValue[]): void\n\n    /**\n     * Retrieves scoped variable, returns null when value doesn't exist.\n     */\n    getVar(name: string): Promise<VimValue | null>\n\n    /**\n     * Set scoped variable by request.\n     *\n     * @param {string} name\n     * @param {VimValue} value\n     * @returns {Promise<void>}\n     */\n    setVar(name: string, value: VimValue): Promise<void>\n\n    /**\n     * Set scoped variable by notification.\n     */\n    setVar(name: string, value: VimValue, isNotify: true): void\n\n    /**\n     * Delete scoped variable by notification.\n     */\n    deleteVar(name: string): void\n\n    /**\n     * Retrieves a scoped option, doesn't exist for tabpage.\n     *\n     * Note: neovim returns true/false for boolean option, but it would be 0/1\n     * on vim.\n     */\n    getOption(name: string): Promise<VimValue>\n\n    /**\n     * Set scoped option by request, doesn't exist for tabpage.\n     */\n    setOption(name: string, value: VimValue): Promise<void>\n\n    /**\n     * Set scoped  variable by notification, doesn't exist for tabpage.\n     */\n    setOption(name: string, value: VimValue, isNotify: true): void\n  }\n\n  export const nvim: Neovim\n\n  export interface Neovim extends BaseApi<Neovim> {\n\n    /**\n     * Echo error message to vim and log error stack.\n     */\n    echoError(error: Error | string): void\n\n    /**\n     * Check if `nvim_` function exists.\n     *\n     * @deprecated use `workspace.has` to check vim version instead.\n     */\n    hasFunction(name: string): boolean\n\n    /**\n     * Get channelid used by coc.nvim.\n     */\n    channelId: Promise<number>\n\n    /**\n     * Create buffer instance by id.\n     */\n    createBuffer(id: number): Buffer\n\n    /**\n     * Create window instance by id.\n     */\n    createWindow(id: number): Window\n\n    /**\n     * Create tabpage instance by id.\n     */\n    createTabpage(id: number): Tabpage\n\n    /**\n     * Stop send subsequent notifications.\n     * This method **must** be paired with `nvim.resumeNotification` in a sync manner.\n     */\n    pauseNotification(): void\n\n    /**\n     * Send paused notifications by nvim_call_atomic request\n     *\n     * @param {boolean} redrawVim Redraw vim on vim8 to update the screen\n     *\n     * **Note**: avoid call async function between pauseNotification and\n     * resumeNotification.\n     */\n    resumeNotification(redrawVim?: boolean): Promise<[VimValue[], [string, number, string] | null]>\n\n    /**\n     * Send paused notifications by nvim_call_atomic notification\n     *\n     * @param {boolean} redrawVim Redraw vim to update the screen\n     * @param {true} notify\n     * @returns {void}\n     */\n    resumeNotification(redrawVim: boolean, notify: true): void\n\n    /**\n     * Send redraw command to vim, does nothing on neovim since it's not necessary on most cases.\n     */\n    redrawVim(): void\n\n    /**\n     * Get list of current buffers.\n     */\n    buffers: Promise<Buffer[]>\n\n    /**\n     * Get current buffer.\n     */\n    buffer: Promise<Buffer>\n\n    /**\n     * Set current buffer\n     */\n    setBuffer(buffer: Buffer): Promise<void>\n\n    /**\n     * Get list of current tabpages.\n     */\n    tabpages: Promise<Tabpage[]>\n\n    /**\n     * Get current tabpage.\n     */\n    tabpage: Promise<Tabpage>\n\n    /**\n     * Set current tabpage\n     */\n    setTabpage(tabpage: Tabpage): Promise<void>\n\n    /**\n     * Get list of current windows.\n     */\n    windows: Promise<Window[]>\n\n    /**\n     * Get current window.\n     */\n    window: Promise<Window>\n\n    /**\n     * Set current window.\n     */\n    setWindow(window: Window): Promise<void>\n\n    /**\n     * Get information of all channels,\n     * **Note:** works on neovim only.\n     */\n    chans: Promise<ChanInfo[]>\n\n    /**\n     * Get information of channel by id,\n     * **Note:** works on neovim only.\n     */\n    getChanInfo(id: number): Promise<ChanInfo>\n\n    /**\n     * Creates a new namespace, or gets an existing one.\n     * `:h nvim_create_namespace()`\n     */\n    createNamespace(name?: string): Promise<number>\n\n    /**\n     * Gets existing, non-anonymous namespaces.\n     * **Note:** works on neovim only.\n     *\n     * @return dict that maps from names to namespace ids.\n     */\n    namespaces: Promise<{ [name: string]: number }>\n\n    /**\n     * Gets a map of global (non-buffer-local) Ex commands.\n     *\n     * @return Map of maps describing commands.\n     */\n    getCommands(opt?: { builtin: boolean }): Promise<{ [name: string]: VimCommandDescription }>\n\n    /**\n     * Get list of all runtime paths\n     */\n    runtimePaths: Promise<string[]>\n\n    /**\n     * Set global working directory.\n     * **Note:** works on neovim only.\n     */\n    setDirectory(dir: string): Promise<void>\n\n    /**\n     * Get current line.\n     */\n    line: Promise<string>\n\n    /**\n     * Creates a new, empty, unnamed buffer.\n     */\n    createNewBuffer(listed?: boolean, scratch?: boolean): Promise<Buffer>\n\n    /**\n     * Create float window of neovim.\n     *\n     * **Note:** works on neovim only, use high level api provided by window\n     * module is recommended.\n     */\n    openFloatWindow(buffer: Buffer, enter: boolean, options: NvimFloatOptions): Promise<Window>\n\n    /**\n     * Set current line.\n     */\n    setLine(line: string): Promise<void>\n\n    /**\n     * Gets a list of global (non-buffer-local) |mapping| definitions.\n     * `:h nvim_get_keymap`\n     *\n     * **Note:** works on neovim only.\n     */\n    getKeymap(mode: string): Promise<object[]>\n\n    /**\n     * Gets the current mode. |mode()| \"blocking\" is true if Nvim is waiting for input.\n     *\n     * **Note:** blocking would always be false when used with vim.\n     */\n    mode: Promise<{ mode: string; blocking: boolean }>\n\n    /**\n     * Returns a map of color names and RGB values.\n     *\n     * **Note:** works on neovim only.\n     */\n    colorMap(): Promise<{ [name: string]: number }>\n\n    /**\n     * Returns the 24-bit RGB value of a |nvim_get_color_map()| color name or\n     * \"#rrggbb\" hexadecimal string.\n     *\n     * **Note:** works on neovim only.\n     */\n    getColorByName(name: string): Promise<number>\n\n    /**\n     * Gets a highlight definition by id. |hlID()|\n     *\n     * **Note:** works on neovim only.\n     */\n    getHighlight(nameOrId: string | number, isRgb?: boolean): Promise<object>\n\n    /**\n     * Get a highlight by name, return rgb by default.\n     *\n     * **Note:** works on neovim only.\n     */\n    getHighlightByName(name: string, isRgb?: boolean): Promise<object>\n\n    /**\n     * Get a highlight by id, return rgb by default.\n     *\n     * **Note:** works on neovim only.\n     */\n    getHighlightById(id: number, isRgb?: boolean): Promise<object>\n\n    /**\n     * Delete current line in buffer.\n     */\n    deleteCurrentLine(): Promise<void>\n\n    /**\n     * Evaluates a VimL expression (:help expression). Dictionaries\n     * and Lists are recursively expanded. On VimL error: Returns a\n     * generic error; v:errmsg is not updated.\n     *\n     */\n    eval(expr: string): Promise<VimValue>\n\n    /**\n     * Executes lua, it's possible neovim client does not support this\n     *\n     * **Note:** works on neovim only.\n     */\n    lua(code: string, args?: VimValue[]): Promise<object>\n\n    /**\n     * Calls a VimL |Dictionary-function| with the given arguments.\n     */\n    callDictFunction(dict: object | string, fname: string, args: VimValue | VimValue[]): Promise<object>\n\n    /**\n     * Call a vim function.\n     *\n     * @param {string} fname - function name\n     * @param {VimValue | VimValue[]} args\n     * @returns {Promise<any>}\n     */\n    call(fname: string, args?: VimValue | VimValue[]): Promise<unknown>\n\n    /**\n     * Call a vim function by notification.\n     */\n    call(fname: string, args: VimValue | VimValue[], isNotify: true): void\n\n    /**\n     * Use call command `:h channel-commands` to call function on vim9.\n     * Warning: NodeJS side only get the 'ERROR' text on error, to get error message,\n     * see `:h coc-api-channel`\n     */\n    callVim(fname: string, args?: VimValue | VimValue[]): Promise<unknown>\n\n    /**\n     * Use call command `:h channel-commands` to call function on vim9.\n     * Warning: errors not exists on NodeJS side, see `:h coc-api-channel`\n     */\n    callVim(fname: string, args: VimValue | VimValue[], isNotify: true): void\n\n    /**\n     * Use expr command `:h channel-commands` to eval expression on vim9.\n     * Warning: NodeJS side only get the 'ERROR' text on error, to get error message,\n     * see `:h coc-api-channel`\n     */\n    evalVim(expr: string): Promise<unknown>\n\n    /**\n     * Use ex command `:h channel-commands` to execute command on vim9.\n     * Warning: errors not exists on NodeJS side, see `:h coc-api-channel`\n     */\n    exVim(command: string): void\n\n    /**\n     * Call a vim function with timer of timeout 0.\n     *\n     * @param {string} fname - function name\n     * @param {VimValue | VimValue[]} args\n     * @returns {Promise<any>}\n     */\n    callTimer(fname: string, args?: VimValue | VimValue[]): Promise<void>\n\n    /**\n     * Call a vim function with timer of timeout 0 by notification.\n     */\n    callTimer(fname: string, args: VimValue | VimValue[], isNotify: true): void\n\n    /**\n     * Call async vim function that accept callback as argument\n     * by using notifications.\n     */\n    callAsync(fname: string, args?: VimValue | VimValue[]): Promise<unknown>\n\n    /**\n     * Calls many API methods atomically.\n     */\n    callAtomic(calls: [string, VimValue[]][]): Promise<[any[], any[] | null]>\n\n    /**\n     * Executes an ex-command by request.\n     */\n    command(arg: string): Promise<void>\n\n    /**\n     * Executes an ex-command by notification.\n     */\n    command(arg: string, isNotify: true): void\n\n    /**\n     * Runs a command and returns output.\n     *\n     * @deprecated Use exec() instead.\n     */\n    commandOutput(arg: string): Promise<string>\n\n    /**\n     * Executes Vimscript (multiline block of Ex-commands), like anonymous |:source|\n     */\n    exec(src: string, output?: boolean): Promise<string>\n\n    /**\n     * Gets a v: variable.\n     */\n    getVvar(name: string): Promise<VimValue>\n\n    /**\n     * `:h nvim_feedkeys`\n     */\n    feedKeys(keys: string, mode: string, escapeCsi: boolean): Promise<void>\n\n    /**\n     * Add global keymap by notification, `:h nvim_set_keymap`\n     */\n    setKeymap(mode: string, lhs: string, rhs: string, opts?: BufferKeymapOption): void\n\n    /**\n     * Delete global keymap, `:h nvim_del_keymap`\n     */\n    deleteKeymap(mode: string, lhs: string): void\n\n    /**\n     * Queues raw user-input. Unlike |nvim_feedkeys()|, this uses a\n     * low-level input buffer and the call is non-blocking (input is\n     * processed asynchronously by the eventloop).\n     *\n     * On execution error: does not fail, but updates v:errmsg.\n     *\n     * **Note:** works on neovim only.\n     */\n    input(keys: string): Promise<number>\n\n    /**\n     * Parse a VimL Expression.\n     */\n    parseExpression(expr: string, flags: string, highlight: boolean): Promise<object>\n\n    /**\n     * Get process info, neovim only.\n     *\n     * **Note:** works on neovim only.\n     */\n    getProc(pid: number): Promise<NvimProc>\n\n    /**\n     * Gets the immediate children of process `pid`.\n     *\n     * **Note:** works on neovim only.\n     */\n    getProcChildren(pid: number): Promise<NvimProc[]>\n\n    /**\n     * Replaces terminal codes and |keycodes| (<CR>, <Esc>, ...)\n     * in a string with the internal representation.\n     *\n     * **Note:** works on neovim only.\n     */\n    replaceTermcodes(str: string, fromPart: boolean, doIt: boolean, special: boolean): Promise<string>\n\n    /**\n     * Gets width(display cells) of string.\n     */\n    strWidth(str: string): Promise<number>\n\n    /**\n     * Create autocmd group with {name} and {option}\n     */\n    createAugroup(name: string, option?: AugroupOption): Promise<number>\n\n    /**\n     * Create autocmd group with {name} and {option}, use notification to vim.\n     */\n    createAugroup(name: string, option: AugroupOption, isNotify: true): void\n\n    /**\n     * Create autocmd with {event} and {option}\n     */\n    createAutocmd(event: string | string[], option?: AutocmdOption): Promise<number>\n\n    /**\n     * Create autocmd with {event} and {option}\n     */\n    createAutocmd(event: string | string[], option: AutocmdOption, isNotify: true): void\n\n    /**\n     * Delete autocmd with {id} returned from `nvim.createAutocmd()`\n     * Notice: vim9 can't support delete specific autocmd yet, autocmds which\n     * have the same `group` `event` `pattern` are all cleared.\n     */\n    deleteAutocmd(id: number): void\n\n    /**\n     * Gets a list of dictionaries representing attached UIs.\n     *\n     * **Note:** works on neovim only.\n     */\n    uis: Promise<any[]>\n\n    /**\n     * Subscribe to nvim event broadcasts.\n     *\n     * **Note:** works on neovim only.\n     */\n    subscribe(event: string): Promise<void>\n\n    /**\n     * Unsubscribe to nvim event broadcasts\n     *\n     * **Note:** works on neovim only.\n     */\n    unsubscribe(event: string): Promise<void>\n\n    /**\n     * Quit vim.\n     */\n    quit(): Promise<void>\n  }\n\n  export interface Buffer extends BaseApi<Buffer> {\n    id: number\n\n    /** Total number of lines in buffer */\n    length: Promise<number>\n\n    /**\n     * Get lines of buffer.\n     */\n    lines: Promise<string[]>\n\n    /**\n     * Get changedtick of buffer.\n     */\n    changedtick: Promise<number>\n\n    /**\n     * Add buffer keymap by notification, `:h nvim_buf_set_keymap`\n     */\n    setKeymap(mode: string, lhs: string, rhs: string, opts?: BufferKeymapOption): void\n\n    /**\n     * Delete buffer keymap, `:h nvim_buf_del_keymap`\n     */\n    deleteKeymap(mode: string, lhs: string): void\n\n    /**\n     * Removes an ext mark by notification. Neovim only.\n     *\n     * @public\n     * @param {number} ns_id - Namespace id\n     * @param {number} id - Extmark id\n     */\n    deleteExtMark(ns_id: number, id: number): void\n\n    /**\n     * Gets the position (0-indexed) of an extmark. Neovim only.\n     *\n     * @param {number} ns_id - Namespace id\n     * @param {number} id - Extmark id\n     * @param {Object} opts - Optional parameters.\n     * @returns {Promise<[] | [number, number] | [number, number, ExtmarkDetails]>}\n     */\n    getExtMarkById(ns_id: number, id: number, opts?: {\n      details?: boolean\n    }): Promise<[] | [number, number] | [number, number, ExtmarkDetails]>\n\n    /**\n     * Gets extmarks in \"traversal order\" from a |charwise| region defined by\n     * buffer positions (inclusive, 0-indexed |api-indexing|).\n     *\n     * Region can be given as (row,col) tuples, or valid extmark ids (whose\n     * positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1)\n     * respectively, thus the following are equivalent:\n     *\n     *     nvim_buf_get_extmarks(0, my_ns, 0, -1, {})\n     *     nvim_buf_get_extmarks(0, my_ns, [0,0], [-1,-1], {})\n     *\n     * @param {number} ns_id - Namespace id\n     * @param {[number, number] | number} start\n     * @param {[number, number] | number} end\n     * @param {Object} opts\n     * @returns {Promise<[number, number, number, ExtmarkDetails?][]>}\n     */\n    getExtMarks(ns_id: number, start: [number, number] | number, end: [number, number] | number, opts?: {\n      details?: boolean\n      limit?: number\n    }): Promise<[number, number, number, ExtmarkDetails?][]>\n\n    /**\n     * Creates or updates an extmark by notification, `:h nvim_buf_set_extmark`.\n     *\n     * @param {number} ns_id\n     * @param {number} line\n     * @param {number} col\n     * @param {ExtmarkOptions} opts\n     * @returns {void}\n     */\n    setExtMark(ns_id: number, line: number, col: number, opts?: ExtmarkOptions): void\n\n    /**\n     * Add sign to buffer by notification.\n     *\n     * @param {SignPlaceOption} sign\n     */\n    placeSign(sign: SignPlaceOption): void\n\n    /**\n     * Unplace signs by notification\n     */\n    unplaceSign(opts: SignUnplaceOption): void\n\n    /**\n     * Get signs by group name or id and lnum.\n     *\n     * @param {SignPlacedOption} opts\n     */\n    getSigns(opts: SignPlacedOption): Promise<SignItem[]>\n\n    /**\n     * Get highlight items by namespace (end inclusive).\n     *\n     * @param {string} ns Namespace key or id.\n     * @param {number} start 0 based line number, default to 0.\n     * @param {number} end 0 based line number, default to -1.\n     */\n    getHighlights(ns: string, start?: number, end?: number): Promise<HighlightItem[]>\n\n    /**\n     * Update namespaced highlights in range by notification.\n     * Priority default to 0 on vim and 4096 on neovim.\n     * Note: timer used for whole buffer highlights for better performance.\n     *\n     * @param {string} ns Namespace key.\n     * @param {HighlightItem[]} highlights Highlight items.\n     * @param {HighlightOption} opts Highlight options.\n     */\n    updateHighlights(ns: string, highlights: ExtendedHighlightItem[], opts?: HighlightOption): void\n\n    /**\n     * Gets a map of buffer-local |user-commands|.\n     *\n     * **Note:** works on neovim only.\n     */\n    getCommands(options?: {}): Promise<Object>\n\n    /**\n     * Get lines of buffer, get all lines by default.\n     */\n    getLines(opts?: { start: number, end: number, strictIndexing?: boolean }): Promise<string[]>\n\n    /**\n     * Set lines of buffer given indices use request.\n     */\n    setLines(lines: string[], opts?: { start: number, end: number, strictIndexing?: boolean }): Promise<void>\n\n    /**\n     * Set lines of buffer given indices use notification.\n     */\n    setLines(lines: string[], opts: { start: number, end: number, strictIndexing?: boolean }, isNotify: true): void\n\n    /**\n     * Set virtual text for a line use notification, works on both neovim and vim9.\n     *\n     * @public\n     * @param {number} src_id - Source group to use or 0 to use a new group, or -1\n     * @param {number} line - Line to annotate with virtual text (zero-indexed)\n     * @param {Chunk[]} chunks - List with [text, hl_group]\n     * @param {[index} opts\n     * @returns {Promise<number>}\n     */\n    setVirtualText(src_id: number, line: number, chunks: [string, string][], opts?: VirtualTextOption): void\n\n    /**\n     * Append a string or list of lines to end of buffer\n     */\n    append(lines: string[] | string): Promise<void>\n\n    /**\n     * Get buffer name.\n     */\n    name: Promise<string>\n\n    /**\n     * Set buffer name.\n     */\n    setName(name: string): Promise<void>\n\n    /**\n     * Check if buffer valid.\n     */\n    valid: Promise<boolean>\n\n    /**\n     * Get mark position given mark name\n     *\n     * **Note:** works on neovim only.\n     */\n    mark(name: string): Promise<[number, number]>\n\n    /**\n     * Gets a list of buffer-local |mapping| definitions.\n     *\n     * @return Array of maparg()-like dictionaries describing mappings.\n     * The \"buffer\" key holds the associated buffer handle.\n     */\n    getKeymap(mode: string): Promise<object[]>\n\n    /**\n     * Check if buffer loaded.\n     */\n    loaded: Promise<boolean>\n\n    /**\n     * Returns the byte offset for a line.\n     *\n     * Line 1 (index=0) has offset 0. UTF-8 bytes are counted. EOL is\n     * one byte. 'fileformat' and 'fileencoding' are ignored. The\n     * line index just after the last line gives the total byte-count\n     * of the buffer. A final EOL byte is counted if it would be\n     * written, see 'eol'.\n     *\n     * Unlike |line2byte()|, throws error for out-of-bounds indexing.\n     * Returns -1 for unloaded buffer.\n     *\n     * @return {Number} Integer byte offset, or -1 for unloaded buffer.\n     */\n    getOffset(index: number): Promise<number>\n\n    /**\n     * Adds a highlight to buffer, checkout |nvim_buf_add_highlight|.\n     *\n     * Note: when `srcId = 0`, request is made for new `srcId`, otherwire, use notification.\n     * Note: `hlGroup` as empty string is not supported.\n     *\n     * @deprecated use `highlightRanges()` instead.\n     */\n    addHighlight(opts: BufferHighlight): Promise<number | null>\n\n    /**\n     * Clear highlights of specified lines.\n     *\n     * @deprecated use clearNamespace() instead.\n     */\n    clearHighlight(args?: BufferClearHighlight)\n\n    /**\n     * Add highlight to ranges by notification, works on both vim & neovim.\n     *\n     * Works on neovim and `workspace.isVim && workspace.env.textprop` is true\n     *\n     * @param {string | number} srcId Unique key or namespace number.\n     * @param {string} hlGroup Highlight group.\n     * @param {Range[]} ranges List of highlight ranges\n     */\n    highlightRanges(srcId: string | number, hlGroup: string, ranges: Range[]): void\n\n    /**\n     * Clear namespace by id or name by notification, works on both vim & neovim.\n     *\n     * Works on neovim and `workspace.isVim && workspace.env.textprop` is true\n     *\n     * @param key Unique key or namespace number, use -1 for all namespaces\n     * @param lineStart Start of line, 0 based, default to 0.\n     * @param lineEnd End of line, 0 based, default to -1.\n     */\n    clearNamespace(key: number | string, lineStart?: number, lineEnd?: number)\n  }\n\n  export interface Window extends BaseApi<Window> {\n    /**\n     * The windowid that not change within a Vim session\n     */\n    id: number\n\n    /**\n     * Buffer in window.\n     */\n    buffer: Promise<Buffer>\n\n    /**\n     * Tabpage contains window.\n     */\n    tabpage: Promise<Tabpage>\n\n    /**\n     * Cursor position as [line, col], 1 based.\n     */\n    cursor: Promise<[number, number]>\n\n    /**\n     * Window height.\n     */\n    height: Promise<number>\n\n    /**\n     * Window width.\n     */\n    width: Promise<number>\n\n    /**\n     * Set cursor position by request.\n     */\n    setCursor(pos: [number, number]): Promise<void>\n\n    /**\n     * Set cursor position by notification.\n     */\n    setCursor(pos: [number, number], isNotify: true): void\n\n    /**\n     * Set height\n     */\n    setHeight(height: number): Promise<void>\n\n    /**\n     * Set height by notification.\n     */\n    setHeight(height: number, isNotify: true): void\n\n    /**\n     * Set width.\n     */\n    setWidth(width: number): Promise<void>\n\n    /**\n     * Set width by notification.\n     */\n    setWidth(width: number, isNotify: true): void\n\n    /**\n     * Get window position, not work with vim8's popup.\n     */\n    position: Promise<[number, number]>\n\n    /** 0-indexed, on-screen window position(row) in display cells. */\n    row: Promise<number>\n\n    /** 0-indexed, on-screen window position(col) in display cells. */\n    col: Promise<number>\n\n    /**\n     * Check if window valid.\n     */\n    valid: Promise<boolean>\n\n    /**\n     * Get window number, throws for invalid window.\n     */\n    number: Promise<number>\n\n    /**\n     * Config float window with options.\n     *\n     * **Note:** works on neovim only.\n     */\n    setConfig(options: NvimFloatOptions): Promise<void>\n\n    /**\n     * Config float window with options by send notification.\n     *\n     * **Note:** works on neovim only.\n     */\n    setConfig(options: NvimFloatOptions, isNotify: true): void\n\n    /**\n     * Gets window configuration.\n     *\n     * **Note:** works on neovim only.\n     *\n     * @returns Map defining the window configuration, see |nvim_open_win()|\n     */\n    getConfig(): Promise<NvimFloatOptions>\n\n    /**\n     * Close window by send request.\n     */\n    close(force: boolean): Promise<void>\n\n    /**\n     * Close window by send notification.\n     */\n    close(force: boolean, isNotify: true): void\n\n    /**\n     * Add highlight to ranges by request (matchaddpos is used)\n     *\n     * @return {Promise<number[]>} match ids.\n     */\n    highlightRanges(hlGroup: string, ranges: Range[], priority?: number): Promise<number[]>\n\n    /**\n     * Add highlight to ranges by notification (matchaddpos is used)\n     */\n    highlightRanges(hlGroup: string, ranges: Range[], priority: number, isNotify: true): void\n\n    /**\n     * Clear match of highlight group by send notification.\n     */\n    clearMatchGroup(hlGroup: string): void\n\n    /**\n     * Clear match of match ids by send notification.\n     */\n    clearMatches(ids: number[]): void\n  }\n\n  export interface Tabpage extends BaseApi<Tabpage> {\n    /**\n     * tabpage number.\n     */\n    number: Promise<number>\n\n    /**\n     * Is current tabpage valid.\n     */\n    valid: Promise<boolean>\n\n    /**\n     * Returns all windows of tabpage.\n     */\n    windows: Promise<Window[]>\n\n    /**\n     * Current window of tabpage.\n     */\n    window: Promise<Window>\n  }\n  // }}\n\n  // vscode-uri {{\n  export interface UriComponents {\n    scheme: string\n    authority: string\n    path: string\n    query: string\n    fragment: string\n  }\n  /**\n   * Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986.\n   * This class is a simple parser which creates the basic component parts\n   * (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation\n   * and encoding.\n   *\n   * ```txt\n   *       foo://example.com:8042/over/there?name=ferret#nose\n   *       \\_/   \\______________/\\_________/ \\_________/ \\__/\n   *        |           |            |            |        |\n   *     scheme     authority       path        query   fragment\n   *        |   _____________________|__\n   *       / \\ /                        \\\n   *       urn:example:animal:ferret:nose\n   * ```\n   */\n  export class Uri implements UriComponents {\n    static isUri(thing: any): thing is Uri\n    /**\n     * scheme is the 'http' part of 'http://www.msft.com/some/path?query#fragment'.\n     * The part before the first colon.\n     */\n    readonly scheme: string\n    /**\n     * authority is the 'www.msft.com' part of 'http://www.msft.com/some/path?query#fragment'.\n     * The part between the first double slashes and the next slash.\n     */\n    readonly authority: string\n    /**\n     * path is the '/some/path' part of 'http://www.msft.com/some/path?query#fragment'.\n     */\n    readonly path: string\n    /**\n     * query is the 'query' part of 'http://www.msft.com/some/path?query#fragment'.\n     */\n    readonly query: string\n    /**\n     * fragment is the 'fragment' part of 'http://www.msft.com/some/path?query#fragment'.\n     */\n    readonly fragment: string\n    /**\n     * @internal\n     */\n    protected constructor(scheme: string, authority?: string, path?: string, query?: string, fragment?: string, _strict?: boolean)\n    /**\n     * @internal\n     */\n    protected constructor(components: UriComponents)\n    /**\n     * Returns a string representing the corresponding file system path of this URI.\n     * Will handle UNC paths, normalizes windows drive letters to lower-case, and uses the\n     * platform specific path separator.\n     *\n     * * Will *not* validate the path for invalid characters and semantics.\n     * * Will *not* look at the scheme of this URI.\n     * * The result shall *not* be used for display purposes but for accessing a file on disk.\n     *\n     *\n     * The *difference* to `URI#path` is the use of the platform specific separator and the handling\n     * of UNC paths. See the below sample of a file-uri with an authority (UNC path).\n     *\n     * ```ts\n            const u = URI.parse('file://server/c$/folder/file.txt')\n            u.authority === 'server'\n            u.path === '/shares/c$/file.txt'\n            u.fsPath === '\\\\server\\c$\\folder\\file.txt'\n        ```\n     *\n     * Using `URI#path` to read a file (using fs-apis) would not be enough because parts of the path,\n     * namely the server name, would be missing. Therefore `URI#fsPath` exists - it's sugar to ease working\n     * with URIs that represent files on disk (`file` scheme).\n     */\n    readonly fsPath: string\n    with(change: {\n      scheme?: string\n      authority?: string | null\n      path?: string | null\n      query?: string | null\n      fragment?: string | null\n    }): Uri\n    /**\n     * Creates a new URI from a string, e.g. `http://www.msft.com/some/path`,\n     * `file:///usr/home`, or `scheme:with/path`.\n     *\n     * @param value A string which represents an URI (see `URI#toString`).\n     */\n    static parse(value: string, _strict?: boolean): Uri\n    /**\n     * Creates a new URI from a file system path, e.g. `c:\\my\\files`,\n     * `/usr/home`, or `\\\\server\\share\\some\\path`.\n     *\n     * The *difference* between `URI#parse` and `URI#file` is that the latter treats the argument\n     * as path, not as stringified-uri. E.g. `URI.file(path)` is **not the same as**\n     * `URI.parse('file://' + path)` because the path might contain characters that are\n     * interpreted (# and ?). See the following sample:\n     * ```ts\n        const good = URI.file('/coding/c#/project1');\n        good.scheme === 'file';\n        good.path === '/coding/c#/project1';\n        good.fragment === '';\n        const bad = URI.parse('file://' + '/coding/c#/project1');\n        bad.scheme === 'file';\n        bad.path === '/coding/c'; // path is now broken\n        bad.fragment === '/project1';\n        ```\n     *\n     * @param path A file system path (see `URI#fsPath`)\n     */\n    static file(path: string): Uri\n    static from(components: {\n      scheme: string\n      authority?: string\n      path?: string\n      query?: string\n      fragment?: string\n    }): Uri\n    /**\n     * Creates a string representation for this URI. It's guaranteed that calling\n     * `URI.parse` with the result of this function creates an URI which is equal\n     * to this URI.\n     *\n     * * The result shall *not* be used for display purposes but for externalization or transport.\n     * * The result will be encoded using the percentage encoding and encoding happens mostly\n     * ignore the scheme-specific encoding rules.\n     *\n     * @param skipEncoding Do not encode the result, default is `false`\n     */\n    toString(skipEncoding?: boolean): string\n    toJSON(): UriComponents\n  }\n  // }}\n\n  // vim interfaces {{\n  /**\n   * See `:h complete-items`\n   */\n  export interface VimCompleteItem {\n    word: string\n    abbr?: string\n    menu?: string\n    /**\n     * @deprecated use documentation property.\n     */\n    info?: string\n    kind?: string\n    icase?: number\n    equal?: number\n    dup?: number\n    empty?: number\n    user_data?: string\n    /**\n     * The same as deprecated tag.\n     */\n    deprecated?: boolean\n    /**\n     * Additional details for a completion item label.\n     */\n    labelDetails?: CompletionItemLabelDetails\n    /**\n     * A string that should be used when comparing this item\n     * with other items. When `falsy` the [word](#VimCompleteItem.word)\n     * is used.\n     */\n    sortText?: string\n    /**\n     * A string that should be used when filtering a set of\n     * completion items. When `falsy` the [word](#VimCompleteItem.word)\n     * is used.\n     */\n    filterText?: string\n    /**\n     * Text to insert, could be snippet text.\n     */\n    insertText?: string\n    /**\n     * When `true` and onCompleteDone handler not exists on source, the snippet\n     * would be expanded after confirm completion.\n     */\n    isSnippet?: boolean\n    /**\n     * Docs to shown in detail window.\n     */\n    documentation?: Documentation[]\n  }\n\n  export interface CompleteDoneItem {\n    readonly word: string\n    readonly abbr?: string\n    readonly source: string\n    readonly isSnippet: boolean\n    readonly kind?: string | CompletionItemKind\n    readonly menu?: string\n  }\n\n  export interface LocationListItem {\n    bufnr: number\n    lnum: number\n    end_lnum: number\n    col: number\n    end_col: number\n    text: string\n    type: string\n  }\n\n  export interface QuickfixItem {\n    uri?: string\n    module?: string\n    range?: Range\n    text?: string\n    type?: string\n    filename?: string\n    bufnr?: number\n    lnum?: number\n    end_lnum?: number\n    col?: number\n    end_col?: number\n    valid?: boolean\n    nr?: number\n  }\n  // }}\n\n  // provider interfaces {{\n  /**\n   * A provider result represents the values a provider, like the [`HoverProvider`](#HoverProvider),\n   * may return. For once this is the actual result type `T`, like `Hover`, or a thenable that resolves\n   * to that type `T`. In addition, `null` and `undefined` can be returned - either directly or from a\n   * thenable.\n   *\n   * The snippets below are all valid implementations of the [`HoverProvider`](#HoverProvider):\n   *\n   * ```ts\n   * let a: HoverProvider = {\n   *   provideHover(doc, pos, token): ProviderResult<Hover> {\n   *     return new Hover('Hello World')\n   *   }\n   * }\n   *\n   * let b: HoverProvider = {\n   *   provideHover(doc, pos, token): ProviderResult<Hover> {\n   *     return new Promise(resolve => {\n   *       resolve(new Hover('Hello World'))\n   *      })\n   *   }\n   * }\n   *\n   * let c: HoverProvider = {\n   *   provideHover(doc, pos, token): ProviderResult<Hover> {\n   *     return; // undefined\n   *   }\n   * }\n   * ```\n   */\n  export type ProviderResult<T> =\n    | T\n    | undefined\n    | null\n    | Thenable<T | undefined | null>\n\n  /**\n   * Supported provider names.\n   */\n  export enum ProviderName {\n    FormatOnType = 'formatOnType',\n    Rename = 'rename',\n    OnTypeEdit = 'onTypeEdit',\n    DocumentLink = 'documentLink',\n    DocumentColor = 'documentColor',\n    FoldingRange = 'foldingRange',\n    Format = 'format',\n    CodeAction = 'codeAction',\n    FormatRange = 'formatRange',\n    Hover = 'hover',\n    Signature = 'signature',\n    WorkspaceSymbols = 'workspaceSymbols',\n    DocumentSymbol = 'documentSymbol',\n    DocumentHighlight = 'documentHighlight',\n    Definition = 'definition',\n    Declaration = 'declaration',\n    TypeDefinition = 'typeDefinition',\n    Reference = 'reference',\n    Implementation = 'implementation',\n    CodeLens = 'codeLens',\n    SelectionRange = 'selectionRange',\n    CallHierarchy = 'callHierarchy',\n    SemanticTokens = 'semanticTokens',\n    SemanticTokensRange = 'semanticTokensRange',\n    LinkedEditing = 'linkedEditing',\n    InlayHint = 'inlayHint',\n    InlineValue = 'inlineValue',\n    InlineCompletion = 'inlineCompletion',\n    TypeHierarchy = 'typeHierarchy'\n  }\n\n  /**\n   * The completion item provider interface defines the contract between extensions and\n   * [IntelliSense](https://code.visualstudio.com/docs/editor/intellisense).\n   *\n   * Providers can delay the computation of the [`detail`](#CompletionItem.detail)\n   * and [`documentation`](#CompletionItem.documentation) properties by implementing the\n   * [`resolveCompletionItem`](#CompletionItemProvider.resolveCompletionItem)-function. However, properties that\n   * are needed for the initial sorting and filtering, like `sortText`, `filterText`, `insertText`, and `range`, must\n   * not be changed during resolve.\n   *\n   * Providers are asked for completions either explicitly by a user gesture or -depending on the configuration-\n   * implicitly when typing words or trigger characters.\n   */\n  export interface CompletionItemProvider {\n    /**\n     * Provide completion items for the given position and document.\n     *\n     * @param document The document in which the command was invoked.\n     * @param position The position at which the command was invoked.\n     * @param token A cancellation token.\n     * @param context How the completion was triggered.\n     *\n     * @return An array of completions, a [completion list](#CompletionList), or a thenable that resolves to either.\n     * The lack of a result can be signaled by returning `undefined`, `null`, or an empty array.\n     */\n    provideCompletionItems(\n      document: LinesTextDocument,\n      position: Position,\n      token: CancellationToken,\n      context?: CompletionContext\n    ): ProviderResult<CompletionItem[] | CompletionList>\n\n    /**\n     * Given a completion item fill in more data, like [doc-comment](#CompletionItem.documentation)\n     * or [details](#CompletionItem.detail).\n     *\n     * The editor will only resolve a completion item once.\n     *\n     * @param item A completion item currently active in the UI.\n     * @param token A cancellation token.\n     * @return The resolved completion item or a thenable that resolves to of such. It is OK to return the given\n     * `item`. When no result is returned, the given `item` will be used.\n     */\n    resolveCompletionItem?(\n      item: CompletionItem,\n      token: CancellationToken\n    ): ProviderResult<CompletionItem>\n  }\n\n  /**\n   * The hover provider interface defines the contract between extensions and\n   * the [hover](https://code.visualstudio.com/docs/editor/intellisense)-feature.\n   */\n  export interface HoverProvider {\n    /**\n     * Provide a hover for the given position and document. Multiple hovers at the same\n     * position will be merged by the editor. A hover can have a range which defaults\n     * to the word range at the position when omitted.\n     *\n     * @param document The document in which the command was invoked.\n     * @param position The position at which the command was invoked.\n     * @param token A cancellation token.\n     * @return A hover or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined` or `null`.\n     */\n    provideHover(\n      document: LinesTextDocument,\n      position: Position,\n      token: CancellationToken\n    ): ProviderResult<Hover>\n  }\n\n  /**\n   * The definition provider interface defines the contract between extensions and\n   * the [go to definition](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-definition)\n   * and peek definition features.\n   */\n  export interface DefinitionProvider {\n    /**\n     * Provide the definition of the symbol at the given position and document.\n     *\n     * @param document The document in which the command was invoked.\n     * @param position The position at which the command was invoked.\n     * @param token A cancellation token.\n     * @return A definition or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined` or `null`.\n     */\n    provideDefinition(\n      document: LinesTextDocument,\n      position: Position,\n      token: CancellationToken\n    ): ProviderResult<Definition | DefinitionLink[]>\n  }\n\n  /**\n   * The definition provider interface defines the contract between extensions and\n   * the [go to definition](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-definition)\n   * and peek definition features.\n   */\n  export interface DeclarationProvider {\n    /**\n     * Provide the declaration of the symbol at the given position and document.\n     */\n    provideDeclaration(\n      document: LinesTextDocument,\n      position: Position,\n      token: CancellationToken\n    ): ProviderResult<Definition | DefinitionLink[]>\n  }\n\n  /**\n   * The signature help provider interface defines the contract between extensions and\n   * the [parameter hints](https://code.visualstudio.com/docs/editor/intellisense)-feature.\n   */\n  export interface SignatureHelpProvider {\n    /**\n     * Provide help for the signature at the given position and document.\n     *\n     * @param document The document in which the command was invoked.\n     * @param position The position at which the command was invoked.\n     * @param token A cancellation token.\n     * @return Signature help or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined` or `null`.\n     */\n    provideSignatureHelp(\n      document: LinesTextDocument,\n      position: Position,\n      token: CancellationToken,\n      context: SignatureHelpContext\n    ): ProviderResult<SignatureHelp>\n  }\n\n  /**\n   * The type definition provider defines the contract between extensions and\n   * the go to type definition feature.\n   */\n  export interface TypeDefinitionProvider {\n    /**\n     * Provide the type definition of the symbol at the given position and document.\n     *\n     * @param document The document in which the command was invoked.\n     * @param position The position at which the command was invoked.\n     * @param token A cancellation token.\n     * @return A definition or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined` or `null`.\n     */\n    provideTypeDefinition(\n      document: LinesTextDocument,\n      position: Position,\n      token: CancellationToken\n    ): ProviderResult<Definition | DefinitionLink[]>\n  }\n\n  /**\n   * The reference provider interface defines the contract between extensions and\n   * the [find references](https://code.visualstudio.com/docs/editor/editingevolved#_peek)-feature.\n   */\n  export interface ReferenceProvider {\n    /**\n     * Provide a set of project-wide references for the given position and document.\n     *\n     * @param document The document in which the command was invoked.\n     * @param position The position at which the command was invoked.\n     * @param context\n     * @param token A cancellation token.\n     * @return An array of locations or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined`, `null`, or an empty array.\n     */\n    provideReferences(\n      document: LinesTextDocument,\n      position: Position,\n      context: ReferenceContext,\n      token: CancellationToken\n    ): ProviderResult<Location[]>\n  }\n\n  /**\n   * Folding context (for future use)\n   */\n  export interface FoldingContext {}\n\n  /**\n   * The folding range provider interface defines the contract between extensions and\n   * [Folding](https://code.visualstudio.com/docs/editor/codebasics#_folding) in the editor.\n   */\n  export interface FoldingRangeProvider {\n\n    /**\n     * An optional event to signal that the folding ranges from this provider have changed.\n     */\n    onDidChangeFoldingRanges?: Event<void>\n\n    /**\n     * Returns a list of folding ranges or null and undefined if the provider\n     * does not want to participate or was cancelled.\n     *\n     * @param document The document in which the command was invoked.\n     * @param context Additional context information (for future use)\n     * @param token A cancellation token.\n     */\n    provideFoldingRanges(\n      document: LinesTextDocument,\n      context: FoldingContext,\n      token: CancellationToken\n    ): ProviderResult<FoldingRange[]>\n  }\n\n  /**\n   * The document symbol provider interface defines the contract between extensions and\n   * the [go to symbol](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-symbol)-feature.\n   */\n  export interface DocumentSymbolProvider {\n    /**\n     * Provide symbol information for the given document.\n     *\n     * @param document The document in which the command was invoked.\n     * @param token A cancellation token.\n     * @return An array of document highlights or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined`, `null`, or an empty array.\n     */\n    provideDocumentSymbols(\n      document: LinesTextDocument,\n      token: CancellationToken\n    ): ProviderResult<SymbolInformation[] | DocumentSymbol[]>\n  }\n\n  /**\n   * The implementation provider interface defines the contract between extensions and\n   * the go to implementation feature.\n   */\n  export interface ImplementationProvider {\n    /**\n     * Provide the implementations of the symbol at the given position and document.\n     *\n     * @param document The document in which the command was invoked.\n     * @param position The position at which the command was invoked.\n     * @param token A cancellation token.\n     * @return A definition or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined` or `null`.\n     */\n    provideImplementation(\n      document: LinesTextDocument,\n      position: Position,\n      token: CancellationToken\n    ): ProviderResult<Definition | DefinitionLink[]>\n  }\n\n  /**\n   * The workspace symbol provider interface defines the contract between extensions and\n   * the [symbol search](https://code.visualstudio.com/docs/editor/editingevolved#_open-symbol-by-name)-feature.\n   */\n  export interface WorkspaceSymbolProvider {\n    /**\n     * Project-wide search for a symbol matching the given query string. It is up to the provider\n     * how to search given the query string, like substring, indexOf etc. To improve performance implementors can\n     * skip the [location](#WorkspaceSymbol.location) of symbols and implement `resolveWorkspaceSymbol` to do that\n     * later.\n     *\n     * The `query`-parameter should be interpreted in a *relaxed way* as the editor will apply its own highlighting\n     * and scoring on the results. A good rule of thumb is to match case-insensitive and to simply check that the\n     * characters of *query* appear in their order in a candidate symbol. Don't use prefix, substring, or similar\n     * strict matching.\n     *\n     * @param query A non-empty query string.\n     * @param token A cancellation token.\n     * @return An array of document highlights or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined`, `null`, or an empty array.\n     */\n    provideWorkspaceSymbols(\n      query: string,\n      token: CancellationToken\n    ): ProviderResult<WorkspaceSymbol[]>\n\n    /**\n     * Given a symbol fill in its [location](#WorkspaceSymbol.location). This method is called whenever a symbol\n     * is selected in the UI. Providers can implement this method and return incomplete symbols from\n     * [`provideWorkspaceSymbols`](#WorkspaceSymbolProvider.provideWorkspaceSymbols) which often helps to improve\n     * performance.\n     *\n     * @param symbol The symbol that is to be resolved. Guaranteed to be an instance of an object returned from an\n     * earlier call to `provideWorkspaceSymbols`.\n     * @param token A cancellation token.\n     * @return The resolved symbol or a thenable that resolves to that. When no result is returned,\n     * the given `symbol` is used.\n     */\n    resolveWorkspaceSymbol?(\n      symbol: WorkspaceSymbol,\n      token: CancellationToken\n    ): ProviderResult<WorkspaceSymbol>\n  }\n\n  /**\n   * The rename provider interface defines the contract between extensions and\n   * the [rename](https://code.visualstudio.com/docs/editor/editingevolved#_rename-symbol)-feature.\n   */\n  export interface RenameProvider {\n    /**\n     * Provide an edit that describes changes that have to be made to one\n     * or many resources to rename a symbol to a different name.\n     *\n     * @param document The document in which the command was invoked.\n     * @param position The position at which the command was invoked.\n     * @param newName The new name of the symbol. If the given name is not valid, the provider must return a rejected promise.\n     * @param token A cancellation token.\n     * @return A workspace edit or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined` or `null`.\n     */\n    provideRenameEdits(\n      document: LinesTextDocument,\n      position: Position,\n      newName: string,\n      token: CancellationToken\n    ): ProviderResult<WorkspaceEdit>\n\n    /**\n     * Optional function for resolving and validating a position *before* running rename. The result can\n     * be a range or a range and a placeholder text. The placeholder text should be the identifier of the symbol\n     * which is being renamed - when omitted the text in the returned range is used.\n     *\n     * @param document The document in which rename will be invoked.\n     * @param position The position at which rename will be invoked.\n     * @param token A cancellation token.\n     * @return The range or range and placeholder text of the identifier that is to be renamed. The lack of a result can signaled by returning `undefined` or `null`.\n     */\n    prepareRename?(\n      document: LinesTextDocument,\n      position: Position,\n      token: CancellationToken\n    ): ProviderResult<Range | { range: Range; placeholder: string }>\n  }\n\n  /**\n   * The document formatting provider interface defines the contract between extensions and\n   * the formatting-feature.\n   */\n  export interface DocumentFormattingEditProvider {\n    /**\n     * Provide formatting edits for a whole document.\n     *\n     * @param document The document in which the command was invoked.\n     * @param options Options controlling formatting.\n     * @param token A cancellation token.\n     * @return A set of text edits or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined`, `null`, or an empty array.\n     */\n    provideDocumentFormattingEdits(\n      document: LinesTextDocument,\n      options: FormattingOptions,\n      token: CancellationToken\n    ): ProviderResult<TextEdit[]>\n  }\n\n  /**\n   * The document formatting provider interface defines the contract between extensions and\n   * the formatting-feature.\n   */\n  export interface DocumentRangeFormattingEditProvider {\n    /**\n     * Provide formatting edits for a range in a document.\n     *\n     * The given range is a hint and providers can decide to format a smaller\n     * or larger range. Often this is done by adjusting the start and end\n     * of the range to full syntax nodes.\n     *\n     * @param document The document in which the command was invoked.\n     * @param range The range which should be formatted.\n     * @param options Options controlling formatting.\n     * @param token A cancellation token.\n     * @return A set of text edits or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined`, `null`, or an empty array.\n     */\n    provideDocumentRangeFormattingEdits(\n      document: LinesTextDocument,\n      range: Range,\n      options: FormattingOptions,\n      token: CancellationToken\n    ): ProviderResult<TextEdit[]>\n  }\n\n  /**\n   * The code action interface defines the contract between extensions and\n   * the [light bulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) feature.\n   *\n   * A code action can be any command that is [known](#commands.getCommands) to the system.\n   */\n  export interface CodeActionProvider<T extends CodeAction = CodeAction> {\n    /**\n     * Provide commands for the given document and range.\n     *\n     * @param document The document in which the command was invoked.\n     * @param range The selector or range for which the command was invoked. This will always be a selection if\n     * there is a currently active editor.\n     * @param context Context carrying additional information.\n     * @param token A cancellation token.\n     * @return An array of commands, quick fixes, or refactorings or a thenable of such. The lack of a result can be\n     * signaled by returning `undefined`, `null`, or an empty array.\n     */\n    provideCodeActions(\n      document: LinesTextDocument,\n      range: Range,\n      context: CodeActionContext,\n      token: CancellationToken\n    ): ProviderResult<(Command | CodeAction)[]>\n\n    /**\n     * Given a code action fill in its [`edit`](#CodeAction.edit)-property. Changes to\n     * all other properties, like title, are ignored. A code action that has an edit\n     * will not be resolved.\n     *\n     * @param codeAction A code action.\n     * @param token A cancellation token.\n     * @return The resolved code action or a thenable that resolves to such. It is OK to return the given\n     * `item`. When no result is returned, the given `item` will be used.\n     */\n    resolveCodeAction?(codeAction: T, token: CancellationToken): ProviderResult<T>\n  }\n\n  /**\n   * Metadata about the type of code actions that a [CodeActionProvider](#CodeActionProvider) providers\n   */\n  export interface CodeActionProviderMetadata {\n    /**\n     * [CodeActionKinds](#CodeActionKind) that this provider may return.\n     *\n     * The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the provider\n     * may list our every specific kind they provide, such as `CodeActionKind.Refactor.Extract.append('function`)`\n     */\n    readonly providedCodeActionKinds?: ReadonlyArray<string>\n  }\n\n  /**\n   * The document highlight provider interface defines the contract between extensions and\n   * the word-highlight-feature.\n   */\n  export interface DocumentHighlightProvider {\n\n    /**\n     * Provide a set of document highlights, like all occurrences of a variable or\n     * all exit-points of a function.\n     *\n     * @param document The document in which the command was invoked.\n     * @param position The position at which the command was invoked.\n     * @param token A cancellation token.\n     * @return An array of document highlights or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined`, `null`, or an empty array.\n     */\n    provideDocumentHighlights(\n      document: LinesTextDocument,\n      position: Position,\n      token: CancellationToken\n    ): ProviderResult<DocumentHighlight[]>\n  }\n\n  /**\n   * The document link provider defines the contract between extensions and feature of showing\n   * links in the editor.\n   */\n  export interface DocumentLinkProvider {\n\n    /**\n     * Provide links for the given document. Note that the editor ships with a default provider that detects\n     * `http(s)` and `file` links.\n     *\n     * @param document The document in which the command was invoked.\n     * @param token A cancellation token.\n     * @return An array of [document links](#DocumentLink) or a thenable that resolves to such. The lack of a result\n     * can be signaled by returning `undefined`, `null`, or an empty array.\n     */\n    provideDocumentLinks(document: LinesTextDocument, token: CancellationToken): ProviderResult<DocumentLink[]>\n\n    /**\n     * Given a link fill in its [target](#DocumentLink.target). This method is called when an incomplete\n     * link is selected in the UI. Providers can implement this method and return incomple links\n     * (without target) from the [`provideDocumentLinks`](#DocumentLinkProvider.provideDocumentLinks) method which\n     * often helps to improve performance.\n     *\n     * @param link The link that is to be resolved.\n     * @param token A cancellation token.\n     */\n    resolveDocumentLink?(link: DocumentLink, token: CancellationToken): ProviderResult<DocumentLink>\n  }\n\n  /**\n   * A code lens provider adds [commands](#Command) to source text. The commands will be shown\n   * as dedicated horizontal lines in between the source text.\n   */\n  export interface CodeLensProvider {\n\n    /**\n     * Compute a list of [lenses](#CodeLens). This call should return as fast as possible and if\n     * computing the commands is expensive implementors should only return code lens objects with the\n     * range set and implement [resolve](#CodeLensProvider.resolveCodeLens).\n     *\n     * @param document The document in which the command was invoked.\n     * @param token A cancellation token.\n     * @return An array of code lenses or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined`, `null`, or an empty array.\n     */\n    provideCodeLenses(document: LinesTextDocument, token: CancellationToken): ProviderResult<CodeLens[]>\n\n    /**\n     * This function will be called for each visible code lens, usually when scrolling and after\n     * calls to [compute](#CodeLensProvider.provideCodeLenses)-lenses.\n     *\n     * @param codeLens code lens that must be resolved.\n     * @param token A cancellation token.\n     * @return The given, resolved code lens or thenable that resolves to such.\n     */\n    resolveCodeLens?(codeLens: CodeLens, token: CancellationToken): ProviderResult<CodeLens>\n  }\n\n  /**\n   * The document formatting provider interface defines the contract between extensions and\n   * the formatting-feature.\n   */\n  export interface OnTypeFormattingEditProvider {\n\n    /**\n     * Provide formatting edits after a character has been typed.\n     *\n     * The given position and character should hint to the provider\n     * what range the position to expand to, like find the matching `{`\n     * when `}` has been entered.\n     *\n     * @param document The document in which the command was invoked.\n     * @param position The position at which the command was invoked.\n     * @param ch The character that has been typed.\n     * @param options Options controlling formatting.\n     * @param token A cancellation token.\n     * @return A set of text edits or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined`, `null`, or an empty array.\n     */\n    provideOnTypeFormattingEdits(document: LinesTextDocument, position: Position, ch: string, options: FormattingOptions, token: CancellationToken): ProviderResult<TextEdit[]>\n  }\n\n  /**\n   * The document color provider defines the contract between extensions and feature of\n   * picking and modifying colors in the editor.\n   */\n  export interface DocumentColorProvider {\n\n    /**\n     * Provide colors for the given document.\n     *\n     * @param document The document in which the command was invoked.\n     * @param token A cancellation token.\n     * @return An array of [color information](#ColorInformation) or a thenable that resolves to such. The lack of a result\n     * can be signaled by returning `undefined`, `null`, or an empty array.\n     */\n    provideDocumentColors(document: LinesTextDocument, token: CancellationToken): ProviderResult<ColorInformation[]>\n\n    /**\n     * Provide [representations](#ColorPresentation) for a color.\n     *\n     * @param color The color to show and insert.\n     * @param context A context object with additional information\n     * @param token A cancellation token.\n     * @return An array of color presentations or a thenable that resolves to such. The lack of a result\n     * can be signaled by returning `undefined`, `null`, or an empty array.\n     */\n    provideColorPresentations(color: Color, context: { document: LinesTextDocument; range: Range }, token: CancellationToken): ProviderResult<ColorPresentation[]>\n  }\n\n  export interface TextDocumentContentProvider {\n\n    /**\n     * An event to signal a resource has changed.\n     */\n    onDidChange?: Event<Uri>\n\n    /**\n     * Provide textual content for a given uri.\n     *\n     * The editor will use the returned string-content to create a readonly\n     * [document](#LinesTextDocument). Resources allocated should be released when\n     * the corresponding document has been [closed](#workspace.onDidCloseTextDocument).\n     *\n     * @param uri An uri which scheme matches the scheme this provider was [registered](#workspace.registerTextDocumentContentProvider) for.\n     * @param token A cancellation token.\n     * @return A string or a thenable that resolves to such.\n     */\n    provideTextDocumentContent(uri: Uri, token: CancellationToken): ProviderResult<string>\n  }\n\n  export interface SelectionRangeProvider {\n    /**\n     * Provide selection ranges starting at a given position. The first range must [contain](#Range.contains)\n     * position and subsequent ranges must contain the previous range.\n     */\n    provideSelectionRanges(document: LinesTextDocument, positions: Position[], token: CancellationToken): ProviderResult<SelectionRange[]>\n  }\n\n  /**\n   * The call hierarchy provider interface describes the contract between extensions\n   * and the call hierarchy feature which allows to browse calls and caller of function,\n   * methods, constructor etc.\n   */\n  export interface CallHierarchyProvider {\n\n    /**\n     * Bootstraps call hierarchy by returning the item that is denoted by the given document\n     * and position. This item will be used as entry into the call graph. Providers should\n     * return `undefined` or `null` when there is no item at the given location.\n     *\n     * @param document The document in which the command was invoked.\n     * @param position The position at which the command was invoked.\n     * @param token A cancellation token.\n     * @returns A call hierarchy item or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined` or `null`.\n     */\n    prepareCallHierarchy(document: LinesTextDocument, position: Position, token: CancellationToken): ProviderResult<CallHierarchyItem | CallHierarchyItem[]>\n\n    /**\n     * Provide all incoming calls for an item, e.g all callers for a method. In graph terms this describes directed\n     * and annotated edges inside the call graph, e.g the given item is the starting node and the result is the nodes\n     * that can be reached.\n     *\n     * @param item The hierarchy item for which incoming calls should be computed.\n     * @param token A cancellation token.\n     * @returns A set of incoming calls or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined` or `null`.\n     */\n    provideCallHierarchyIncomingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult<CallHierarchyIncomingCall[]>\n\n    /**\n     * Provide all outgoing calls for an item, e.g call calls to functions, methods, or constructors from the given item. In\n     * graph terms this describes directed and annotated edges inside the call graph, e.g the given item is the starting\n     * node and the result is the nodes that can be reached.\n     *\n     * @param item The hierarchy item for which outgoing calls should be computed.\n     * @param token A cancellation token.\n     * @returns A set of outgoing calls or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined` or `null`.\n     */\n    provideCallHierarchyOutgoingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult<CallHierarchyOutgoingCall[]>\n  }\n\n  /**\n   * The document semantic tokens provider interface defines the contract between extensions and\n   * semantic tokens.\n   */\n  export interface DocumentSemanticTokensProvider {\n    /**\n     * An optional event to signal that the semantic tokens from this provider have changed.\n     */\n    onDidChangeSemanticTokens?: Event<void>\n\n    /**\n     * Tokens in a file are represented as an array of integers. The position of each token is expressed relative to\n     * the token before it, because most tokens remain stable relative to each other when edits are made in a file.\n     *\n     * ---\n     * In short, each token takes 5 integers to represent, so a specific token `i` in the file consists of the following array indices:\n     *\n     * - at index `5*i`   - `deltaLine`: token line number, relative to the previous token\n     * - at index `5*i+1` - `deltaStart`: token start character, relative to the previous token (relative to 0 or the previous token's start if they are on the same line)\n     * - at index `5*i+2` - `length`: the length of the token. A token cannot be multiline.\n     * - at index `5*i+3` - `tokenType`: will be looked up in `SemanticTokensLegend.tokenTypes`. We currently ask that `tokenType` < 65536.\n     * - at index `5*i+4` - `tokenModifiers`: each set bit will be looked up in `SemanticTokensLegend.tokenModifiers`\n     *\n     * ---\n     * ### How to encode tokens\n     *\n     * Here is an example for encoding a file with 3 tokens in a uint32 array:\n     * ```\n     *    { line: 2, startChar:  5, length: 3, tokenType: \"property\",  tokenModifiers: [\"private\", \"static\"] },\n     *    { line: 2, startChar: 10, length: 4, tokenType: \"type\",      tokenModifiers: [] },\n     *    { line: 5, startChar:  2, length: 7, tokenType: \"class\",     tokenModifiers: [] }\n     * ```\n     *\n     * 1. First of all, a legend must be devised. This legend must be provided up-front and capture all possible token types.\n     * For this example, we will choose the following legend which must be passed in when registering the provider:\n     * ```\n     *    tokenTypes: ['property', 'type', 'class'],\n     *    tokenModifiers: ['private', 'static']\n     * ```\n     *\n     * 2. The first transformation step is to encode `tokenType` and `tokenModifiers` as integers using the legend. Token types are looked\n     * up by index, so a `tokenType` value of `1` means `tokenTypes[1]`. Multiple token modifiers can be set by using bit flags,\n     * so a `tokenModifier` value of `3` is first viewed as binary `0b00000011`, which means `[tokenModifiers[0], tokenModifiers[1]]` because\n     * bits 0 and 1 are set. Using this legend, the tokens now are:\n     * ```\n     *    { line: 2, startChar:  5, length: 3, tokenType: 0, tokenModifiers: 3 },\n     *    { line: 2, startChar: 10, length: 4, tokenType: 1, tokenModifiers: 0 },\n     *    { line: 5, startChar:  2, length: 7, tokenType: 2, tokenModifiers: 0 }\n     * ```\n     *\n     * 3. The next step is to represent each token relative to the previous token in the file. In this case, the second token\n     * is on the same line as the first token, so the `startChar` of the second token is made relative to the `startChar`\n     * of the first token, so it will be `10 - 5`. The third token is on a different line than the second token, so the\n     * `startChar` of the third token will not be altered:\n     * ```\n     *    { deltaLine: 2, deltaStartChar: 5, length: 3, tokenType: 0, tokenModifiers: 3 },\n     *    { deltaLine: 0, deltaStartChar: 5, length: 4, tokenType: 1, tokenModifiers: 0 },\n     *    { deltaLine: 3, deltaStartChar: 2, length: 7, tokenType: 2, tokenModifiers: 0 }\n     * ```\n     *\n     * 4. Finally, the last step is to inline each of the 5 fields for a token in a single array, which is a memory friendly representation:\n     * ```\n     *    // 1st token,  2nd token,  3rd token\n     *    [  2,5,3,0,3,  0,5,4,1,0,  3,2,7,2,0 ]\n     * ```\n     *\n     * @see [SemanticTokensBuilder](#SemanticTokensBuilder) for a helper to encode tokens as integers.\n     * *NOTE*: When doing edits, it is possible that multiple edits occur until VS Code decides to invoke the semantic tokens provider.\n     * *NOTE*: If the provider cannot temporarily compute semantic tokens, it can indicate this by throwing an error with the message 'Busy'.\n     */\n    provideDocumentSemanticTokens(document: LinesTextDocument, token: CancellationToken): ProviderResult<SemanticTokens>\n\n    /**\n     * Instead of always returning all the tokens in a file, it is possible for a `DocumentSemanticTokensProvider` to implement\n     * this method (`provideDocumentSemanticTokensEdits`) and then return incremental updates to the previously provided semantic tokens.\n     *\n     * ---\n     * ### How tokens change when the document changes\n     *\n     * Suppose that `provideDocumentSemanticTokens` has previously returned the following semantic tokens:\n     * ```\n     *    // 1st token,  2nd token,  3rd token\n     *    [  2,5,3,0,3,  0,5,4,1,0,  3,2,7,2,0 ]\n     * ```\n     *\n     * Also suppose that after some edits, the new semantic tokens in a file are:\n     * ```\n     *    // 1st token,  2nd token,  3rd token\n     *    [  3,5,3,0,3,  0,5,4,1,0,  3,2,7,2,0 ]\n     * ```\n     * It is possible to express these new tokens in terms of an edit applied to the previous tokens:\n     * ```\n     *    [  2,5,3,0,3,  0,5,4,1,0,  3,2,7,2,0 ] // old tokens\n     *    [  3,5,3,0,3,  0,5,4,1,0,  3,2,7,2,0 ] // new tokens\n     *\n     *    edit: { start:  0, deleteCount: 1, data: [3] } // replace integer at offset 0 with 3\n     * ```\n     *\n     * *NOTE*: If the provider cannot compute `SemanticTokensEdits`, it can \"give up\" and return all the tokens in the document again.\n     * *NOTE*: All edits in `SemanticTokensEdits` contain indices in the old integers array, so they all refer to the previous result state.\n     */\n    provideDocumentSemanticTokensEdits?(document: LinesTextDocument, previousResultId: string, token: CancellationToken): ProviderResult<SemanticTokens | SemanticTokensDelta>\n  }\n\n  /**\n   * The document range semantic tokens provider interface defines the contract between extensions and\n   * semantic tokens.\n   */\n  export interface DocumentRangeSemanticTokensProvider {\n    /**\n     * @see [provideDocumentSemanticTokens](#DocumentSemanticTokensProvider.provideDocumentSemanticTokens).\n     */\n    provideDocumentRangeSemanticTokens(document: LinesTextDocument, range: Range, token: CancellationToken): ProviderResult<SemanticTokens>\n  }\n\n  export interface LinkedEditingRangeProvider {\n    /**\n     * For a given position in a document, returns the range of the symbol at the position and all ranges\n     * that have the same content. A change to one of the ranges can be applied to all other ranges if the new content\n     * is valid. An optional word pattern can be returned with the result to describe valid contents.\n     * If no result-specific word pattern is provided, the word pattern from the language configuration is used.\n     *\n     * @param document The document in which the provider was invoked.\n     * @param position The position at which the provider was invoked.\n     * @param token A cancellation token.\n     * @return A list of ranges that can be edited together\n     */\n    provideLinkedEditingRanges(document: LinesTextDocument, position: Position, token: CancellationToken): ProviderResult<LinkedEditingRanges>\n  }\n\n  /**\n   * The inlay hints provider interface defines the contract between extensions and\n   * the inlay hints feature.\n   */\n  export interface InlayHintsProvider<T extends InlayHint = InlayHint> {\n\n    /**\n     * An optional event to signal that inlay hints from this provider have changed.\n     */\n    onDidChangeInlayHints?: Event<void>\n\n    /**\n     * Provide inlay hints for the given range and document.\n     *\n     * *Note* that inlay hints that are not {@link Range.contains contained} by the given range are ignored.\n     *\n     * @param document The document in which the command was invoked.\n     * @param range The range for which inlay hints should be computed.\n     * @param token A cancellation token.\n     * @return An array of inlay hints or a thenable that resolves to such.\n     */\n    provideInlayHints(document: LinesTextDocument, range: Range, token: CancellationToken): ProviderResult<T[]>\n\n    /**\n     * Given an inlay hint fill in {@link InlayHint.tooltip tooltip}, {@link InlayHint.textEdits text edits},\n     * or complete label {@link InlayHintLabelPart parts}.\n     *\n     * *Note* that the editor will resolve an inlay hint at most once.\n     *\n     * @param hint An inlay hint.\n     * @param token A cancellation token.\n     * @return The resolved inlay hint or a thenable that resolves to such. It is OK to return the given `item`. When no result is returned, the given `item` will be used.\n     */\n    resolveInlayHint?(hint: T, token: CancellationToken): ProviderResult<T>\n  }\n\n  /**\n   * The type hierarchy provider interface describes the contract between extensions\n   * and the type hierarchy feature.\n   */\n  export interface TypeHierarchyProvider {\n\n    /**\n     * Bootstraps type hierarchy by returning the item that is denoted by the given document\n     * and position. This item will be used as entry into the type graph. Providers should\n     * return `undefined` or `null` when there is no item at the given location.\n     *\n     * @param document The document in which the command was invoked.\n     * @param position The position at which the command was invoked.\n     * @param token A cancellation token.\n     * @returns One or multiple type hierarchy items or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined`, `null`, or an empty array.\n     */\n    prepareTypeHierarchy(document: LinesTextDocument, position: Position, token: CancellationToken): ProviderResult<TypeHierarchyItem[]>\n\n    /**\n     * Provide all supertypes for an item, e.g all types from which a type is derived/inherited. In graph terms this describes directed\n     * and annotated edges inside the type graph, e.g the given item is the starting node and the result is the nodes\n     * that can be reached.\n     *\n     * @param item The hierarchy item for which super types should be computed.\n     * @param token A cancellation token.\n     * @returns A set of direct supertypes or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined` or `null`.\n     */\n    provideTypeHierarchySupertypes(item: TypeHierarchyItem, token: CancellationToken): ProviderResult<TypeHierarchyItem[]>\n\n    /**\n     * Provide all subtypes for an item, e.g all types which are derived/inherited from the given item. In\n     * graph terms this describes directed and annotated edges inside the type graph, e.g the given item is the starting\n     * node and the result is the nodes that can be reached.\n     *\n     * @param item The hierarchy item for which subtypes should be computed.\n     * @param token A cancellation token.\n     * @returns A set of direct subtypes or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined` or `null`.\n     */\n    provideTypeHierarchySubtypes(item: TypeHierarchyItem, token: CancellationToken): ProviderResult<TypeHierarchyItem[]>\n  }\n\n  /**\n   * The inline values provider interface defines the contract between extensions and the editor's debugger inline values feature.\n   * In this contract the provider returns inline value information for a given document range\n   * and the editor shows this information in the editor at the end of lines.\n   */\n  export interface InlineValuesProvider {\n\n    /**\n     * An optional event to signal that inline values have changed.\n     * @see {@link EventEmitter}\n     */\n    onDidChangeInlineValues?: Event<void> | undefined\n\n    /**\n     * Provide \"inline value\" information for a given document and range.\n     * The editor calls this method whenever debugging stops in the given document.\n     * The returned inline values information is rendered in the editor at the end of lines.\n     * @param document The document for which the inline values information is needed.\n     * @param viewPort The visible document range for which inline values should be computed.\n     * @param context A bag containing contextual information like the current location.\n     * @param token A cancellation token.\n     * @return An array of InlineValueDescriptors or a thenable that resolves to such. The lack of a result can be\n     * signaled by returning `undefined` or `null`.\n     */\n    provideInlineValues(document: TextDocument, viewPort: Range, context: InlineValueContext, token: CancellationToken): ProviderResult<InlineValue[]>\n  }\n\n  export interface DiagnosticProvider {\n    onDidChangeDiagnostics: Event<void> | undefined\n    provideDiagnostics(document: TextDocument | Uri, previousResultId: string | undefined, token: CancellationToken): ProviderResult<DocumentDiagnosticReport>\n    provideWorkspaceDiagnostics?(resultIds: PreviousResultId[], token: CancellationToken, resultReporter: ResultReporter): ProviderResult<WorkspaceDiagnosticReport>\n  }\n\n  /**\n   * The inline completion item provider interface defines the contract between extensions and\n   * the inline completion feature.\n   *\n   * Providers are asked for completions either explicitly by a user gesture or implicitly when typing.\n   */\n  export interface InlineCompletionItemProvider {\n\n    /**\n     * Provides inline completion items for the given position and document.\n     * If inline completions are enabled, this method will be called whenever the user stopped typing.\n     * It will also be called when the user explicitly triggers inline completions or explicitly asks for the next or previous inline completion.\n     * In that case, all available inline completions should be returned.\n     * `context.triggerKind` can be used to distinguish between these scenarios.\n     * @param document The document inline completions are requested for.\n     * @param position The position inline completions are requested for.\n     * @param context A context object with additional information.\n     * @param token A cancellation token.\n     * @returns An array of completion items or a thenable that resolves to an array of completion items.\n     */\n    provideInlineCompletionItems(\n      document: TextDocument,\n      position: Position,\n      context: InlineCompletionContext,\n      token: CancellationToken\n    ): ProviderResult<InlineCompletionItem[] | InlineCompletionList>\n  }\n  // }}\n\n  // Classes {{\n  /**\n   * An error type that should be used to signal cancellation of an operation.\n   *\n   * This type can be used in response to a {@link CancellationToken cancellation token}\n   * being cancelled or when an operation is being cancelled by the\n   * executor of that operation.\n   */\n  export class CancellationError extends Error {\n\n    /**\n     * Creates a new cancellation error.\n     */\n    constructor()\n  }\n\n  /**\n   * A semantic tokens builder can help with creating a `SemanticTokens` instance\n   * which contains delta encoded semantic tokens.\n   */\n  export class SemanticTokensBuilder {\n    constructor(legend?: SemanticTokensLegend)\n\n    /**\n     * Add another token.\n     *\n     * @public\n     * @param line The token start line number (absolute value).\n     * @param char The token start character (absolute value).\n     * @param length The token length in characters.\n     * @param tokenType The encoded token type.\n     * @param tokenModifiers The encoded token modifiers.\n     */\n    push(line: number, char: number, length: number, tokenType: number, tokenModifiers?: number): void\n    /**\n     * Add another token. Use only when providing a legend.\n     *\n     * @public\n     * @param range The range of the token. Must be single-line.\n     * @param tokenType The token type.\n     * @param tokenModifiers The token modifiers.\n     */\n    push(range: Range, tokenType: string, tokenModifiers?: string[]): void\n\n    /**\n     * Finish and create a `SemanticTokens` instance.\n     *\n     * @public\n     */\n    build(resultId?: string): SemanticTokens\n  }\n\n  export interface Document {\n    readonly buffer: Buffer\n    /**\n     * Document is attached to vim.\n     */\n    readonly attached: boolean\n    /**\n     * Is command line document.\n     */\n    readonly isCommandLine: boolean\n    /**\n     * `buftype` option of buffer.\n     */\n    readonly buftype: string\n    /**\n     * Text document that synchronized.\n     */\n    readonly textDocument: LinesTextDocument\n    /**\n     * Fired when document change.\n     */\n    readonly onDocumentChange: Event<DidChangeTextDocumentParams>\n    /**\n     * Get current buffer changedtick.\n     */\n    readonly changedtick: number\n    /**\n     * Scheme of document.\n     */\n    readonly schema: string\n    /**\n     * Line count of current buffer.\n     */\n    readonly lineCount: number\n    /**\n     * Window ID when buffer create, could be -1 when no window associated.\n     */\n    readonly winid: number\n    /**\n     * Returns if current document is opened with previewwindow\n     */\n    readonly previewwindow: boolean\n    /**\n     * Check if document changed after last synchronize\n     */\n    readonly dirty: boolean\n    /**\n     * Buffer number\n     */\n    readonly bufnr: number\n    /**\n     * Content of textDocument.\n     */\n    readonly content: string\n    /**\n     * Converted filetype.\n     */\n    readonly filetype: string\n    /**\n     * Main filetype of buffer, first part when buffer filetype contains dots.\n     * Same as filetype most of the time.\n     */\n    readonly languageId: string\n    readonly uri: string\n    readonly version: number\n    /**\n     * Current lines of buffer\n     */\n    readonly lines: ReadonlyArray<string>\n    /**\n     * Apply text edits to document. `nvim_buf_set_text()` is used when possible\n     *\n     * @param {TextEdit[]} edits\n     * @param {boolean} joinUndo - Join further changes with the previous undo block by `:undojoin`.\n     * @param {boolean | Position} move - Move the cursor when true or from custom position.\n     * @returns {Promise<void>}\n     */\n    applyEdits(edits: TextEdit[], joinUndo?: boolean, move?: boolean | Position): Promise<void>\n\n    /**\n     * Change individual lines.\n     *\n     * @param {[number, string][]} lines\n     * @returns {void}\n     */\n    changeLines(lines: [number, string][]): Promise<void>\n\n    /**\n     * Get offset from lnum & col\n     */\n    getOffset(lnum: number, col: number): number\n\n    /**\n     * Check string is word.\n     */\n    isWord(word: string): boolean\n\n    /**\n     * Word range at position.\n     *\n     * @param {Position} position\n     * @param {string} extraChars Extra characters that should be keyword.\n     * @param {boolean} current Use current lines instead of textDocument, default to true.\n     * @returns {Range | null}\n     */\n    getWordRangeAtPosition(position: Position, extraChars?: string, current?: boolean): Range | null\n\n    /**\n     * Get ranges of word in textDocument.\n     */\n    getSymbolRanges(word: string): Range[]\n\n    /**\n     * Get line for buffer\n     *\n     * @param {number} line 0 based line index.\n     * @param {boolean} current Use textDocument lines when false, default to true.\n     * @returns {string}\n     */\n    getline(line: number, current?: boolean): string\n\n    /**\n     * Get range of current lines, zero indexed, end exclude.\n     */\n    getLines(start?: number, end?: number): string[]\n\n    /**\n     * Get variable value by key, defined by `b:coc_{key}`\n     */\n    getVar<T>(key: string, defaultValue?: T): T\n\n    /**\n     * Get position from lnum & col\n     */\n    getPosition(lnum: number, col: number): Position\n\n    /**\n     * Adjust col with new valid character before position.\n     */\n    fixStartcol(position: Position, valids: string[]): number\n\n    /**\n     * Get current content text, consider eol option.\n     */\n    getDocumentContent(): string\n  }\n\n  /**\n   * Represents a {@link TextEditor text editor}'s {@link TextEditor.options options}.\n   */\n  export interface TextEditorOptions {\n    /**\n     * The size in spaces a tab takes. This is used for two purposes:\n     *  - the rendering width of a tab character;\n     *  - the number of spaces to insert when {@link TextEditorOptions.insertSpaces insertSpaces} is true.\n     *\n     * When getting a text editor's options, this property will always be a number (resolved).\n    */\n    tabSize: number\n    /**\n     * When pressing Tab insert {@link TextEditorOptions.tabSize n} spaces.\n     * When getting a text editor's options, this property will always be a boolean (resolved).\n     */\n    insertSpaces: boolean\n    /**\n     * Trim trailing whitespace on a line.\n     */\n    trimTrailingWhitespace?: boolean\n    /**\n     * Insert a newline character at the end of the file if one does not exist.\n     */\n    insertFinalNewline?: boolean\n    /**\n     * Trim all newlines after the final newline at the end of the file.\n     */\n    trimFinalNewlines?: boolean\n  }\n\n  /**\n   * Represents an editor that is attached to a {@link Document document}.\n   */\n  export interface TextEditor {\n    /**\n     * The tabpageid of current editor.\n     */\n    readonly tabpageid: number\n    /**\n     * The window id of current editor.\n     */\n    readonly winid: number\n    /**\n     * The window number of current editor.\n     */\n    readonly winnr: number\n    /**\n     * The document associated with this text editor. The document will be the same for the entire lifetime of this text editor.\n     */\n    readonly document: Document\n    /**\n     * The current visible ranges in the editor (vertically).\n     * This accounts only for vertical scrolling, and not for horizontal scrolling.\n     */\n    readonly visibleRanges: readonly Range[]\n    /**\n     * Text editor options.\n     */\n    readonly options: TextEditorOptions\n  }\n\n  export interface Documentation {\n    /**\n     * Filetype used for highlight, markdown is supported.\n     */\n    filetype: string\n    /**\n     * Content of document.\n     */\n    content: string\n    /**\n     * Byte offset (0 based) that should be undelined.\n     */\n    active?: [number, number]\n    highlights?: HighlightItem[]\n  }\n\n  /**\n   * A file glob pattern to match file paths against. This can either be a glob pattern string\n   * (like `**​/*.{ts,js}` or `*.{ts,js}`) or a {@link RelativePattern relative pattern}.\n   *\n   * Glob patterns can have the following syntax:\n   * * `*` to match one or more characters in a path segment\n   * * `?` to match on one character in a path segment\n   * * `**` to match any number of path segments, including none\n   * * `{}` to group conditions (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files)\n   * * `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)\n   * * `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`)\n   *\n   * Note: a backslash (`\\`) is not valid within a glob pattern. If you have an existing file\n   * path to match against, consider to use the {@link RelativePattern relative pattern} support\n   * that takes care of converting any backslash into slash. Otherwise, make sure to convert\n   * any backslash to slash when creating the glob pattern.\n   */\n  export type GlobPattern = string | RelativePattern\n\n  /**\n   * A relative pattern is a helper to construct glob patterns that are matched\n   * relatively to a base file path. The base path can either be an absolute file\n   * path as string or uri or a {@link WorkspaceFolder workspace folder}, which is the\n   * preferred way of creating the relative pattern.\n   */\n  export class RelativePattern {\n\n    /**\n     * A base file path to which this pattern will be matched against relatively.\n     */\n    baseUri: Uri\n\n    /**\n     * A file glob pattern like `*.{ts,js}` that will be matched on file paths\n     * relative to the base path.\n     *\n     * Example: Given a base of `/home/work/folder` and a file path of `/home/work/folder/index.js`,\n     * the file glob pattern will match on `index.js`.\n     */\n    pattern: string\n\n    /**\n     * Creates a new relative pattern object with a base file path and pattern to match. This pattern\n     * will be matched on file paths relative to the base.\n     *\n     * Example:\n     * ```ts\n     * const folder = vscode.workspace.workspaceFolders?.[0];\n     * if (folder) {\n     *\n     *   // Match any TypeScript file in the root of this workspace folder\n     *   const pattern1 = new vscode.RelativePattern(folder, '*.ts');\n     *\n     *   // Match any TypeScript file in `someFolder` inside this workspace folder\n     *   const pattern2 = new vscode.RelativePattern(folder, 'someFolder/*.ts');\n     * }\n     * ```\n     *\n     * @param base A base to which this pattern will be matched against relatively. It is recommended\n     * to pass in a {@link WorkspaceFolder workspace folder} if the pattern should match inside the workspace.\n     * Otherwise, a uri or string should only be used if the pattern is for a file path outside the workspace.\n     * @param pattern A file glob pattern like `*.{ts,js}` that will be matched on paths relative to the base.\n     */\n    constructor(base: WorkspaceFolder | Uri | string, pattern: string)\n  }\n\n  /**\n   * Build buffer with lines and highlights\n   */\n  export class Highlighter {\n    constructor(srcId?: number)\n    /**\n     * Add a line with highlight group.\n     */\n    addLine(line: string, hlGroup?: string): void\n    /**\n     * Add lines without highlights.\n     */\n    addLines(lines: string[]): void\n    /**\n     * Add text with highlight.\n     */\n    addText(text: string, hlGroup?: string): void\n    /**\n     * Get line count\n     */\n    get length(): number\n    /**\n     * Render lines to buffer at specified range.\n     * Since notifications is used, use `nvim.pauseNotification` & `nvim.resumeNotification`\n     * when you need to wait for the request finish.\n     *\n     * @param {Buffer} buffer\n     * @param {number} start\n     * @param {number} end\n     * @returns {void}\n     */\n    render(buffer: Buffer, start?: number, end?: number): void\n  }\n\n  export interface ListConfiguration {\n    get<T>(key: string, defaultValue?: T): T\n    previousKey(): string\n    nextKey(): string\n    dispose(): void\n  }\n\n  export interface ListActionOptions {\n    /**\n     * No prompt stop and window switch when invoked.\n     */\n    persist?: boolean\n    /**\n     * Reload list after action invoked.\n     */\n    reload?: boolean\n    /**\n     * Support multiple items as execute argument.\n     */\n    parallel?: boolean\n    /**\n     * Tab positioned list should be persisted (no window switch) on action invoke.\n     */\n    tabPersist?: boolean\n  }\n\n  export interface CommandTaskOption {\n    /**\n     * Command to run.\n     */\n    cmd: string\n    /**\n     * Arguments of command.\n     */\n    args: string[]\n    /**\n     * Current working directory.\n     */\n    cwd?: string\n    env?: NodeJS.ProcessEnv\n    /**\n     * Runs for each line, return undefined for invalid item.\n     */\n    onLine: (line: string) => ListItem | undefined\n  }\n\n  export abstract class BasicList implements IList {\n    /**\n     * Unique name, must be provided by implementation class.\n     * @requires\n     */\n    name: string\n    /**\n     * Default action name invoked by <cr> by default, must be provided by implementation class.\n     * @requires\n     */\n    defaultAction: string\n    /**\n     * Registered actions.\n     */\n    readonly actions: ListAction[]\n    /**\n     * Arguments configuration of list.\n     */\n    options: ListArgument[]\n    protected nvim: Neovim\n    protected disposables: Disposable[]\n    protected config: ListConfiguration\n    constructor()\n    /**\n     * Should align columns when true.\n     */\n    get alignColumns(): boolean\n    get hlGroup(): string\n    get previewHeight(): string\n    get splitRight(): boolean\n    /**\n     * Parse argument string array for argument object from `this.options`.\n     * Could be used inside `this.loadItems()`\n     */\n    protected parseArguments(args: string[]): { [key: string]: string | boolean }\n    /**\n     * Get configurations of current list\n     */\n    protected getConfig(): WorkspaceConfiguration\n    /**\n     * Add an action\n     */\n    protected addAction(name: string, fn: (item: ListItem, context: ListContext) => ProviderResult<void>, options?: ListActionOptions): void\n    /**\n     * Add action that support multiple selection.\n     */\n    protected addMultipleAction(name: string, fn: (item: ListItem[], context: ListContext) => ProviderResult<void>, options?: ListActionOptions): void\n    /**\n     * Create task from command task option.\n     */\n    protected createCommandTask(opt: CommandTaskOption): ListTask\n    /**\n     * Add location related actions, should be called in constructor.\n     */\n    protected addLocationActions(): void\n    protected convertLocation(location: Location | LocationWithLine | string): Promise<Location>\n    /**\n     * Jump to location\n     */\n    protected jumpTo(location: Location | LocationWithLine | string, command?: string): Promise<void>\n    /**\n     * Preview location.\n     */\n    protected previewLocation(location: Location, context: ListContext): Promise<void>\n    /**\n     * Preview lines.\n     */\n    protected preview(options: PreviewOptions, context: ListContext): Promise<void>\n    /**\n     * Use for syntax highlights, invoked after buffer loaded.\n     */\n    doHighlight(): void\n    /**\n     * Invoked for listItems or listTask, could throw error when failed to load.\n     */\n    abstract loadItems(context: ListContext, token?: CancellationToken): Promise<ListItem[] | ListTask | null | undefined>\n  }\n\n  export class Mutex {\n    /**\n     * Returns true when task is running.\n     */\n    get busy(): boolean\n    /**\n     * Resolved release function that must be called after task finish.\n     */\n    acquire(): Promise<() => void>\n    /**\n     * Captrue the async task function that ensures to be executed one by one.\n     */\n    use<T>(f: () => Promise<T>): Promise<T>\n  }\n  // }}\n\n  // functions {{\n\n  export interface AnsiItem {\n    foreground?: string\n    background?: string\n    bold?: boolean\n    italic?: boolean\n    underline?: boolean\n    text: string\n  }\n\n  export interface ParsedUrlQueryInput {\n    [key: string]: unknown\n  }\n\n  export interface FetchOptions {\n    /**\n     * Default to 'GET'\n     */\n    method?: string\n    /**\n     * Default no timeout\n     */\n    timeout?: number\n    /**\n     * Always return buffer instead of parsed response.\n     */\n    buffer?: boolean\n    /**\n     * Data send to server.\n     */\n    data?: string | { [key: string]: any } | Buffer\n    /**\n     * Plain object added as query of url\n     */\n    query?: ParsedUrlQueryInput\n    headers?: any\n    /**\n     * User for http basic auth, should use with password\n     */\n    user?: string\n    /**\n     * Password for http basic auth, should use with user\n     */\n    password?: string\n  }\n\n  export interface DownloadOptions extends Omit<FetchOptions, 'buffer'> {\n    /**\n     * Folder that contains downloaded file or extracted files by untar or unzip\n     */\n    dest: string\n    /**\n     * Remove the specified number of leading path elements for *untar* only, default to `1`.\n     */\n    strip?: number\n    /**\n     * algorithm for check etag header with response data, used by `crypto.createHash()`.\n     */\n    etagAlgorithm?: string\n    /**\n     * If true, use untar for `.tar.gz` filename\n     */\n    extract?: boolean | 'untar' | 'unzip'\n    onProgress?: (percent: string) => void\n  }\n\n  export type ResponseResult = string | Buffer | {\n    [name: string]: any\n  }\n\n  /**\n   * Parse ansi result from string contains ansi characters.\n   */\n  export function ansiparse(str: string): AnsiItem[]\n\n  /**\n   * Send request to server for response, supports:\n   *\n   * - Send json data and parse json response.\n   * - Throw error for failed response statusCode.\n   * - Timeout support (no timeout by default).\n   * - Send buffer (as data) and receive data (as response).\n   * - Proxy support from user configuration & environment.\n   * - Redirect support, limited to 3.\n   * - Support of gzip & deflate response content.\n   *\n   * @return Parsed object if response content type is application/json, text if content type starts with `text/`\n   */\n  export function fetch(url: string | URL, options?: FetchOptions, token?: CancellationToken): Promise<ResponseResult>\n\n  /**\n   * Download file from url, with optional untar/unzip support.\n   *\n   * Note: you may need to set `strip` to 0 when using untar as extract method.\n   *\n   * @param {string} url\n   * @param {DownloadOptions} options contains dest folder and optional onProgress callback\n   */\n  export function download(url: string | URL, options: DownloadOptions, token?: CancellationToken): Promise<string>\n\n  interface ExecOptions {\n    cwd?: string\n    env?: NodeJS.ProcessEnv\n    shell?: string\n    timeout?: number\n    maxBuffer?: number\n    killSignal?: string\n    uid?: number\n    gid?: number\n    windowsHide?: boolean\n    encoding?: string\n  }\n\n  /**\n   * Dispose all disposables.\n   */\n  export function disposeAll(disposables: Disposable[]): void\n\n  /**\n   * Concurrent run async functions with limit support.\n   */\n  export function concurrent<T>(arr: T[], fn: (val: T) => Promise<void>, limit?: number): Promise<void>\n\n  /**\n   * Create promise resolved after ms milliseconds.\n   */\n  export function wait(ms: number): Promise<any>\n\n  /**\n   * Run command with `child_process.exec`, CancellationError is rejected when timeout or cancelled.\n   *\n   * @param {string} cmd\n   * @param {ExecOptions} opts - Execute options, encoding is used by\n   * iconv-lite for decode stdout buffer to string, default to 'utf8'\n   * @param {number | CancellationToken} timeout - Timeout in seconds or Cancellation token.\n   * @returns {Promise<string>}\n   */\n  export function runCommand(cmd: string, opts?: ExecOptions, timeout?: number | CancellationToken): Promise<string>\n\n  /**\n   * Check if process with pid is running\n   */\n  export function isRunning(pid: number): boolean\n\n  /**\n   * Check if command is executable.\n   */\n  export function executable(command: string): boolean\n\n  /**\n   * Watch single file for change, the filepath needs to be exists file.\n   *\n   * @param filepath Full path of file.\n   * @param onChange Handler on file change detected.\n   */\n  export function watchFile(filepath: string, onChange: () => void): Disposable\n  // }}\n\n  // commands module {{\n  export interface CommandItem {\n    id: string\n    internal?: boolean\n    execute(...args: any[]): any\n  }\n  /**\n   * Namespace for dealing with commands of coc.nvim\n   */\n  export namespace commands {\n    /**\n     * Registered commands.\n     */\n    export const commandList: CommandItem[]\n\n    /**\n     * Execute specified command.\n     *\n     * @deprecated use `executeCommand()` instead.\n     */\n    export function execute(command: { name: string, arguments?: any[] }): void\n\n    /**\n     * Check if command is registered.\n     *\n     * @param id Unique id of command.\n     */\n    export function has(id: string): boolean\n\n    /**\n     * Registers a command that can be invoked via a keyboard shortcut,\n     * a menu item, an action, or directly.\n     *\n     * Registering a command with an existing command identifier twice\n     * will cause an error.\n     *\n     * @param command A unique identifier for the command.\n     * @param impl A command handler function.\n     * @param thisArg The `this` context used when invoking the handler function.\n     * @return Disposable which unregisters this command on disposal.\n     */\n    export function registerCommand(id: string, impl: (...args: any[]) => void, thisArg?: any, internal?: boolean): Disposable\n\n    /**\n     * Executes the command denoted by the given command identifier.\n     *\n     * * *Note 1:* When executing an editor command not all types are allowed to\n     * be passed as arguments. Allowed are the primitive types `string`, `boolean`,\n     * `number`, `undefined`, and `null`, as well as [`Position`](#Position), [`Range`](#Range), [`URI`](#URI) and [`Location`](#Location).\n     * * *Note 2:* There are no restrictions when executing commands that have been contributed\n     * by extensions.\n     *\n     * @param command Identifier of the command to execute.\n     * @param rest Parameters passed to the command function.\n     * @return A promise that resolves to the returned value of the given command. `undefined` when\n     * the command handler function doesn't return anything.\n     */\n    export function executeCommand<T>(command: string, ...rest: any[]): Promise<T>\n\n    /**\n     * Open uri with external tool, use `open` on mac, use `xdg-open` on linux.\n     */\n    export function executeCommand(command: 'vscode.open', uri: string | Uri): Promise<void>\n\n    /**\n     * Reload current buffer by `:edit` command.\n     */\n    export function executeCommand(command: 'workbench.action.reloadWindow'): Promise<void>\n\n    /**\n     * Open user's coc-settings.json configuration file.\n     */\n    export function executeCommand(command: 'workbench.action.openSettingsJson'): Promise<void>\n\n    /**\n     * Insert snippet at range of current buffer.\n     *\n     * @param edit Contains snippet text and range to replace.\n     */\n    export function executeCommand(command: 'editor.action.insertSnippet', edit: TextEdit, ultisnip?: UltiSnippetOption): Promise<boolean>\n\n    /**\n     * Invoke specified code action.\n     */\n    export function executeCommand(command: 'editor.action.doCodeAction', action: CodeAction): Promise<void>\n\n    /**\n     * Trigger coc.nvim's completion at current cursor position.\n     */\n    export function executeCommand(command: 'editor.action.triggerSuggest', source?: string): Promise<void>\n\n    /**\n     * Trigger signature help at current cursor position.\n     */\n    export function executeCommand(command: 'editor.action.triggerParameterHints'): Promise<void>\n\n    /**\n     * Add ranges to cursors session for multiple cursors.\n     */\n    export function executeCommand(command: 'editor.action.addRanges', ranges: Range[]): Promise<void>\n\n    /**\n     * Restart coc.nvim service by `:CocRestart` command.\n     */\n    export function executeCommand(command: 'editor.action.restart'): Promise<void>\n\n    /**\n     * Show locations by location list or vim's quickfix list.\n     */\n    export function executeCommand(command: 'editor.action.showReferences', uri: string | Uri, position: Position | undefined, locations: Location[]): Promise<void>\n\n    /**\n     * Invoke rename action at position of specified uri.\n     */\n    export function executeCommand(command: 'editor.action.rename', uri: string | Uri, position: Position, newName?: string): Promise<void>\n\n    /**\n     * Run format action for current buffer.\n     */\n    export function executeCommand(command: 'editor.action.format'): Promise<void>\n\n    /**\n     * Trigger inline completion\n     */\n    export function executeCommand(command: 'editor.action.triggerInlineCompletion', option?: InlineCompletionOption): Promise<void>\n  }\n  // }}\n\n  // events module {{\n  type EventResult = void | Promise<void>\n  type MoveEvents = 'CursorMoved' | 'CursorMovedI'\n  type HoldEvents = 'CursorHold' | 'CursorHoldI'\n  type BufEvents = 'BufHidden' | 'BufEnter' | 'BufWritePost'\n    | 'InsertLeave' | 'TermOpen' | 'InsertEnter'\n    | 'BufCreate' | 'BufUnload' | 'BufWritePre' | 'Enter'\n  type EmptyEvents = 'FocusGained' | 'FocusLost' | 'InsertSnippet'\n  type InsertChangeEvents = 'TextChangedP' | 'TextChangedI'\n  type TaskEvents = 'TaskExit' | 'TaskStderr' | 'TaskStdout'\n  type WindowEvents = 'WinLeave' | 'WinEnter' | 'WinClosed'\n  type AllEvents = BufEvents | EmptyEvents | HoldEvents | MoveEvents | TaskEvents | WindowEvents | InsertChangeEvents | 'CompleteDone' | 'TextChanged' | 'MenuPopupChanged' | 'InsertCharPre' | 'FileType' | 'BufWinEnter' | 'BufWinLeave' | 'VimResized' | 'DirChanged' | 'OptionSet' | 'Command' | 'BufReadCmd' | 'GlobalChange' | 'InputChar' | 'WinLeave' | 'MenuInput' | 'PromptInsert' | 'FloatBtnClick' | 'InsertSnippet' | 'PromptKeyPress' | 'WinScrolled' | 'WindowVisible'\n  type OptionValue = string | number | boolean\n  type PromptWidowKeys = 'C-j' | 'C-k' | 'C-n' | 'C-p' | 'up' | 'down'\n\n  export interface CursorPosition {\n    readonly bufnr: number\n    readonly lnum: number\n    readonly col: number\n    readonly insert: boolean\n  }\n\n  export interface InsertChange {\n    /**\n     * 1 based line number\n     */\n    readonly lnum: number\n    /**\n     * 1 based column number\n     */\n    readonly col: number\n    /**\n     * Text before cursor.\n     */\n    readonly pre: string\n    /**\n     * Insert character that cause change of this time.\n     */\n    readonly insertChar: string | undefined\n    readonly changedtick: number\n  }\n\n  export interface PopupChangeEvent {\n    /**\n     * 0 based index of item in the list.\n     */\n    readonly index: number\n    /**\n     * Word of item.\n     */\n    readonly word: string\n    /**\n     * Height of pum.\n     */\n    readonly height: number\n    /**\n     * Width of pum.\n     */\n    readonly width: number\n    /**\n     * Screen row of pum.\n     */\n    readonly row: number\n    /**\n     * Screen col of pum.\n     */\n    readonly col: number\n    /**\n     * Total length of completion list.\n     */\n    readonly size: number\n    /**\n     * Scollbar in the pum.\n     */\n    readonly scrollbar: boolean\n    /**\n     * Word is inserted.\n     */\n    readonly inserted: boolean\n    /**\n     * Caused by selection change (not initial or completed)\n     */\n    readonly move: boolean\n  }\n\n  export interface VisibleEvent {\n    winid: number\n    bufnr: number\n    /**\n    * 1 based, end inclusive topline, botline\n    */\n    region: [number, number]\n  }\n\n  /**\n   * Used for listen to events send from vim.\n   */\n  export namespace events {\n    /**\n     * Latest cursor position.\n     */\n    export const cursor: Readonly<CursorPosition>\n    /**\n     * Latest pum position, is true when pum positioned above current line.\n     */\n    export const pumAlignTop: boolean\n    /**\n     * Insert mode detected by latest events.\n     */\n    export const insertMode: boolean\n\n    /**\n     * Popup menu is visible.\n     */\n    export const pumvisible: boolean\n\n    /**\n     * Wait for any of event in events to fire, resolve undefined when timeout or CancellationToken requested.\n     * @param events Event names to wait.\n     * @param timeoutOrToken Timeout in miniseconds or CancellationToken.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function race(events: AllEvents[], timeoutOrToken?: number | CancellationToken): Promise<{ name: AllEvents, args: unknown[] } | undefined>\n\n    /**\n     * Attach handler to buffer events.\n     */\n    export function on(event: BufEvents, handler: (bufnr: number) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    /**\n     * Attach handler to mouse move events.\n     */\n    export function on(event: MoveEvents, handler: (bufnr: number, cursor: [number, number], hasInsert: boolean) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    /**\n     * Attach handler to cursor hold events.\n     */\n    export function on(event: HoldEvents, handler: (bufnr: number, cursor: [number, number], winid: number) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    /**\n     * Attach handler to TextChangedI or TextChangedP.\n     */\n    export function on(event: InsertChangeEvents, handler: (bufnr: number, info: InsertChange) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    /**\n     * Attach handler to window event.\n     */\n    export function on(event: WindowEvents, handler: (winid: number) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    /**\n     * Attach handler to window scroll event.\n     */\n    export function on(event: WindowEvents, handler: (winid: number, bufnr: number) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    /**\n     * Attach handler to float button click.\n     */\n    export function on(event: 'FloatBtnClick', handler: (bufnr: number, index: number) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    /**\n     * Attach handler to keypress in prompt window.\n     * Key could only be 'C-j', 'C-k', 'C-n', 'C-p', 'up' and 'down'\n     */\n    export function on(event: 'PromptKeyPress', handler: (bufnr: number, key: PromptWidowKeys) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    /**\n     * Fired on vim's TextChanged event.\n     */\n    export function on(event: 'TextChanged', handler: (bufnr: number, changedtick: number) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    export function on(event: 'TaskExit', handler: (id: string, code: number) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    export function on(event: 'TaskStderr' | 'TaskStdout', handler: (id: string, lines: string[]) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    /**\n     * Fired on vim's BufReadCmd event.\n     */\n    export function on(event: 'BufReadCmd', handler: (scheme: string, fullpath: string) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    /**\n     * Fired on vim's VimResized event.\n     */\n    export function on(event: 'VimResized', handler: (columns: number, lines: number) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    export function on(event: 'MenuPopupChanged', handler: (event: PopupChangeEvent, cursorline: number) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    /**\n     * Fired on completion finish.\n     */\n    export function on(event: 'CompleteDone', handler: (item: VimCompleteItem & CompleteDoneItem | CompletionItem & CompleteDoneItem | {}) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    /**\n     * Fired on completion start.\n     */\n    export function on(event: 'CompleteStart', handler: (option: CompleteOption) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    export function on(event: 'InsertCharPre', handler: (character: string) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    export function on(event: 'FileType', handler: (filetype: string, bufnr: number) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    export function on(event: 'BufWinEnter' | 'BufWinLeave', handler: (bufnr: number, winid: number) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    export function on(event: 'DirChanged', handler: (cwd: string) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    export function on(event: 'OptionSet' | 'GlobalChange', handler: (option: string, oldVal: OptionValue, newVal: OptionValue) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    export function on(event: 'InputChar', handler: (session: string, character: string, mode: number) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    export function on(event: 'PromptInsert', handler: (value: string, bufnr: number) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    export function on(event: 'Command', handler: (name: string) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    /**\n     * Emitted on WinScrolled event of vim, with related `winid` and `bufnr`,\n     * `region` contains [topline, botline] which are 1 based, end enclusive\n     * (the same as the result from getwininfo()).\n     */\n    export function on(event: 'WinScrolled', handler: (winid: number, bufnr: number, region: Readonly<[number, number]>) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    /**\n     * Emitted with debounce time (100ms) after `BufWinEnter` and `WinScrolled`\n     * for the change of window visible regions. To track visible changes of all\n     * attached buffers, use `workspace.registerBufferSync()` is recommended.\n     */\n    export function on(event: 'WindowVisible', handler: (event: VisibleEvent) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n    /**\n     * Fired after user insert character and made change to the buffer.\n     * Fired after TextChangedI & TextChangedP event.\n     */\n    export function on(event: 'TextInsert', handler: (bufnr: number, info: InsertChange, character: string) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n\n    export function on(event: EmptyEvents, handler: () => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n\n    export function on(event: AllEvents[], handler: (...args: unknown[]) => EventResult, thisArg?: any, disposables?: Disposable[]): Disposable\n  }\n  // }}\n\n  // file events {{\n  /**\n   * An event that is fired after files are created.\n   */\n  export interface FileCreateEvent {\n\n    /**\n     * The files that got created.\n     */\n    readonly files: ReadonlyArray<Uri>\n  }\n\n  /**\n   * An event that is fired when files are going to be created.\n   *\n   * To make modifications to the workspace before the files are created,\n   * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a\n   * thenable that resolves to a [workspace edit](#WorkspaceEdit).\n   */\n  export interface FileWillCreateEvent {\n\n    /**\n    * A cancellation token.\n    */\n    readonly token: CancellationToken\n\n    /**\n     * The files that are going to be created.\n     */\n    readonly files: ReadonlyArray<Uri>\n\n    /**\n     * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit).\n     *\n     * *Note:* This function can only be called during event dispatch and not\n     * in an asynchronous manner:\n     *\n     * ```ts\n     * workspace.onWillCreateFiles(event => {\n     *     // async, will *throw* an error\n     *     setTimeout(() => event.waitUntil(promise));\n     *\n     *     // sync, OK\n     *     event.waitUntil(promise);\n     * })\n     * ```\n     *\n     * @param thenable A thenable that delays saving.\n     */\n    waitUntil(thenable: Thenable<WorkspaceEdit | any>): void\n  }\n\n  /**\n   * An event that is fired when files are going to be deleted.\n   *\n   * To make modifications to the workspace before the files are deleted,\n   * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a\n   * thenable that resolves to a [workspace edit](#WorkspaceEdit).\n   */\n  export interface FileWillDeleteEvent {\n\n    /**\n     * The files that are going to be deleted.\n     */\n    readonly files: ReadonlyArray<Uri>\n\n    /**\n     * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit).\n     *\n     * *Note:* This function can only be called during event dispatch and not\n     * in an asynchronous manner:\n     *\n     * ```ts\n     * workspace.onWillCreateFiles(event => {\n     *     // async, will *throw* an error\n     *     setTimeout(() => event.waitUntil(promise));\n     *\n     *     // sync, OK\n     *     event.waitUntil(promise);\n     * })\n     * ```\n     *\n     * @param thenable A thenable that delays saving.\n     */\n    waitUntil(thenable: Thenable<WorkspaceEdit | any>): void\n  }\n\n  /**\n   * An event that is fired after files are deleted.\n   */\n  export interface FileDeleteEvent {\n\n    /**\n     * The files that got deleted.\n     */\n    readonly files: ReadonlyArray<Uri>\n  }\n\n  /**\n   * An event that is fired after files are renamed.\n   */\n  export interface FileRenameEvent {\n\n    /**\n     * The files that got renamed.\n     */\n    readonly files: ReadonlyArray<{ oldUri: Uri, newUri: Uri }>\n  }\n\n  /**\n   * An event that is fired when files are going to be renamed.\n   *\n   * To make modifications to the workspace before the files are renamed,\n   * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a\n   * thenable that resolves to a [workspace edit](#WorkspaceEdit).\n   */\n  export interface FileWillRenameEvent {\n\n    /**\n     * The files that are going to be renamed.\n     */\n    readonly files: ReadonlyArray<{ oldUri: Uri, newUri: Uri }>\n\n    /**\n     * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit).\n     *\n     * *Note:* This function can only be called during event dispatch and not\n     * in an asynchronous manner:\n     *\n     * ```ts\n     * workspace.onWillCreateFiles(event => {\n     * \t// async, will *throw* an error\n     * \tsetTimeout(() => event.waitUntil(promise));\n     *\n     * \t// sync, OK\n     * \tevent.waitUntil(promise);\n     * })\n     * ```\n     *\n     * @param thenable A thenable that delays saving.\n     */\n    waitUntil(thenable: Thenable<WorkspaceEdit | any>): void\n  }\n  // }}\n\n  // languages module {{\n  export interface DocumentSymbolProviderMetadata {\n    /**\n    * A human-readable string that is shown when multiple outlines trees show for one document.\n    */\n    label?: string\n  }\n\n  export namespace languages {\n\n    /**\n     * Check if specific provider exists for document.\n     */\n    export function hasProvider(id: ProviderName, document: TextDocumentMatch): boolean\n    /**\n     * Create a diagnostics collection.\n     *\n     * @param name The [name](#DiagnosticCollection.name) of the collection.\n     * @return A new diagnostic collection.\n     */\n    export function createDiagnosticCollection(name?: string): DiagnosticCollection\n\n    /**\n     * Register a formatting provider that works on type. The provider is active when the user enables the setting `coc.preferences.formatOnType`.\n     *\n     * Multiple providers can be registered for a language. In that case providers are sorted\n     * by their [score](#languages.match) and the best-matching provider is used. Failure\n     * of the selected provider will cause a failure of the whole operation.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider An on type formatting edit provider.\n     * @param triggerCharacters Trigger character that should trigger format on type.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerOnTypeFormattingEditProvider(selector: DocumentSelector, provider: OnTypeFormattingEditProvider, triggerCharacters: string[]): Disposable\n\n    /**\n     * Register a completion provider.\n     *\n     * Multiple providers can be registered for a language. In that case providers are sorted\n     * by their [score](#languages.match) and groups of equal score are sequentially asked for\n     * completion items. The process stops when one or many providers of a group return a\n     * result. A failing provider (rejected promise or exception) will not fail the whole\n     * operation.\n     *\n     * A completion item provider can be associated with a set of `triggerCharacters`. When trigger\n     * characters are being typed, completions are requested but only from providers that registered\n     * the typed character. Because of that trigger characters should be different than [word characters](#LanguageConfiguration.wordPattern),\n     * a common trigger character is `.` to trigger member completions.\n     *\n     * @param name Name of completion source.\n     * @param shortcut Shortcut used in completion menu.\n     * @param selector Document selector of created completion source.\n     * @param provider A completion provider.\n     * @param triggerCharacters Trigger completion when the user types one of the characters.\n     * @param priority Higher priority would shown first.\n     * @param allCommitCharacters Commit characters of completion source.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerCompletionItemProvider(name: string, shortcut: string, selector: DocumentSelector | null, provider: CompletionItemProvider, triggerCharacters?: string[], priority?: number, allCommitCharacters?: string[]): Disposable\n\n    /**\n     * Register a code action provider.\n     *\n     * Multiple providers can be registered for a language. In that case providers are asked in\n     * parallel and the results are merged. A failing provider (rejected promise or exception) will\n     * not cause a failure of the whole operation.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A code action provider.\n     * @param clientId Optional id of language client.\n     * @param codeActionKinds Optional supported code action kinds.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerCodeActionProvider(selector: DocumentSelector, provider: CodeActionProvider, clientId: string | undefined, codeActionKinds?: ReadonlyArray<string>): Disposable\n\n    /**\n     * Register a hover provider.\n     *\n     * Multiple providers can be registered for a language. In that case providers are asked in\n     * parallel and the results are merged. A failing provider (rejected promise or exception) will\n     * not cause a failure of the whole operation.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A hover provider.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerHoverProvider(selector: DocumentSelector, provider: HoverProvider): Disposable\n\n    /**\n     * Register a selection range provider.\n     *\n     * Multiple providers can be registered for a language. In that case providers are asked in\n     * parallel and the results are merged. A failing provider (rejected promise or exception) will\n     * not cause a failure of the whole operation.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A selection range provider.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerSelectionRangeProvider(selector: DocumentSelector, provider: SelectionRangeProvider): Disposable\n\n    /**\n     * Register a signature help provider.\n     *\n     * Multiple providers can be registered for a language. In that case providers are sorted\n     * by their [score](#languages.match) and called sequentially until a provider returns a\n     * valid result.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A signature help provider.\n     * @param triggerCharacters Trigger signature help when the user types one of the characters, like `,` or `(`.\n     * @param metadata Information about the provider.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerSignatureHelpProvider(selector: DocumentSelector, provider: SignatureHelpProvider, triggerCharacters?: string[]): Disposable\n\n    /**\n     * Register a document symbol provider.\n     *\n     * Multiple providers can be registered for a language. In that case providers only first provider\n     * are asked for result.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A document symbol provider.\n     * @param metadata Optional meta data.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerDocumentSymbolProvider(selector: DocumentSelector, provider: DocumentSymbolProvider, metadata?: DocumentSymbolProviderMetadata): Disposable\n\n    /**\n     * Register a folding range provider.\n     *\n     * Multiple providers can be registered for a language. In that case providers only first provider\n     * are asked for result.\n     *\n     * A failing provider (rejected promise or exception) will\n     * not cause a failure of the whole operation.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A folding range provider.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerFoldingRangeProvider(selector: DocumentSelector, provider: FoldingRangeProvider): Disposable\n\n    /**\n     * Register a document highlight provider.\n     *\n     * Multiple providers can be registered for a language. In that case providers are sorted\n     * by their [score](#languages.match) and groups sequentially asked for document highlights.\n     * The process stops when a provider returns a `non-falsy` or `non-failure` result.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A document highlight provider.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerDocumentHighlightProvider(selector: DocumentSelector, provider: DocumentHighlightProvider): Disposable\n\n    /**\n     * Register a code lens provider.\n     *\n     * Multiple providers can be registered for a language. In that case providers are asked in\n     * parallel and the results are merged. A failing provider (rejected promise or exception) will\n     * not cause a failure of the whole operation.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A code lens provider.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerCodeLensProvider(selector: DocumentSelector, provider: CodeLensProvider): Disposable\n\n    /**\n     * Register a document link provider.\n     *\n     * Multiple providers can be registered for a language. In that case providers are asked in\n     * parallel and the results are merged. A failing provider (rejected promise or exception) will\n     * not cause a failure of the whole operation.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A document link provider.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerDocumentLinkProvider(selector: DocumentSelector, provider: DocumentLinkProvider): Disposable\n\n    /**\n     * Register a color provider.\n     *\n     * Multiple providers can be registered for a language. In that case providers are asked in\n     * parallel and the results are merged. A failing provider (rejected promise or exception) will\n     * not cause a failure of the whole operation.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A color provider.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerDocumentColorProvider(selector: DocumentSelector, provider: DocumentColorProvider): Disposable\n\n    /**\n     * Register a definition provider.\n     *\n     * Multiple providers can be registered for a language. In that case providers are asked in\n     * parallel and the results are merged. A failing provider (rejected promise or exception) will\n     * not cause a failure of the whole operation.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A definition provider.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerDefinitionProvider(selector: DocumentSelector, provider: DefinitionProvider): Disposable\n\n    /**\n     * Register a declaration provider.\n     *\n     * Multiple providers can be registered for a language. In that case providers are asked in\n     * parallel and the results are merged. A failing provider (rejected promise or exception) will\n     * not cause a failure of the whole operation.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A declaration provider.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerDeclarationProvider(selector: DocumentSelector, provider: DeclarationProvider): Disposable\n\n\n    /**\n     * Register a type definition provider.\n     *\n     * Multiple providers can be registered for a language. In that case providers are asked in\n     * parallel and the results are merged. A failing provider (rejected promise or exception) will\n     * not cause a failure of the whole operation.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A type definition provider.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerTypeDefinitionProvider(selector: DocumentSelector, provider: TypeDefinitionProvider): Disposable\n\n    /**\n     * Register an implementation provider.\n     *\n     * Multiple providers can be registered for a language. In that case providers are asked in\n     * parallel and the results are merged. A failing provider (rejected promise or exception) will\n     * not cause a failure of the whole operation.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider An implementation provider.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerImplementationProvider(selector: DocumentSelector, provider: ImplementationProvider): Disposable\n\n    /**\n     * Register a reference provider.\n     *\n     * Multiple providers can be registered for a language. In that case providers are asked in\n     * parallel and the results are merged. A failing provider (rejected promise or exception) will\n     * not cause a failure of the whole operation.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A reference provider.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerReferencesProvider(selector: DocumentSelector, provider: ReferenceProvider): Disposable\n\n    /**\n     * Register a rename provider.\n     *\n     * Multiple providers can be registered for a language. In that case providers are sorted\n     * by their [score](#workspace.match) and asked in sequence. The first provider producing a result\n     * defines the result of the whole operation.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A rename provider.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerRenameProvider(selector: DocumentSelector, provider: RenameProvider): Disposable\n\n    /**\n     * Register a workspace symbol provider.\n     *\n     * Multiple providers can be registered. In that case providers are asked in parallel and\n     * the results are merged. A failing provider (rejected promise or exception) will not cause\n     * a failure of the whole operation.\n     *\n     * @param provider A workspace symbol provider.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerWorkspaceSymbolProvider(provider: WorkspaceSymbolProvider): Disposable\n\n    /**\n     * Register a formatting provider for a document.\n     *\n     * Multiple providers can be registered for a language. In that case providers are sorted\n     * by their priority. Failure of the selected provider will cause a failure of the whole operation.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A document formatting edit provider.\n     * @param priority default to 0.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerDocumentFormatProvider(selector: DocumentSelector, provider: DocumentFormattingEditProvider, priority?: number): Disposable\n\n    /**\n     * Register a formatting provider for a document range.\n     *\n     * *Note:* A document range provider is also a [document formatter](#DocumentFormattingEditProvider)\n     * which means there is no need to [register](#languages.registerDocumentFormattingEditProvider) a document\n     * formatter when also registering a range provider.\n     *\n     * Multiple providers can be registered for a language. In that case provider with highest priority is used.\n     * Failure of the selected provider will cause a failure of the whole operation.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A document range formatting edit provider.\n     * @param priority default to 0.\n     * @return A [disposable](#Disposable) that unregisters this provider when being disposed.\n     */\n    export function registerDocumentRangeFormatProvider(selector: DocumentSelector, provider: DocumentRangeFormattingEditProvider, priority?: number): Disposable\n\n    /**\n     * Register a call hierarchy provider.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A call hierarchy provider.\n     * @return A {@link Disposable} that unregisters this provider when being disposed.\n     */\n    export function registerCallHierarchyProvider(selector: DocumentSelector, provider: CallHierarchyProvider): Disposable\n\n    /**\n     * Register a semantic tokens provider for a whole document.\n     *\n     * Multiple providers can be registered for a language. In that case providers are sorted\n     * by their {@link languages.match score} and the best-matching provider is used. Failure\n     * of the selected provider will cause a failure of the whole operation.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A document semantic tokens provider.\n     * @return A {@link Disposable} that unregisters this provider when being disposed.\n     */\n    export function registerDocumentSemanticTokensProvider(selector: DocumentSelector, provider: DocumentSemanticTokensProvider, legend: SemanticTokensLegend): Disposable\n\n    /**\n     * Register a semantic tokens provider for a document range.\n     *\n     * *Note:* If a document has both a `DocumentSemanticTokensProvider` and a `DocumentRangeSemanticTokensProvider`,\n     * the range provider will be invoked only initially, for the time in which the full document provider takes\n     * to resolve the first request. Once the full document provider resolves the first request, the semantic tokens\n     * provided via the range provider will be discarded and from that point forward, only the document provider\n     * will be used.\n     *\n     * Multiple providers can be registered for a language. In that case providers are sorted\n     * by their {@link languages.match score} and the best-matching provider is used. Failure\n     * of the selected provider will cause a failure of the whole operation.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A document range semantic tokens provider.\n     * @return A {@link Disposable} that unregisters this provider when being disposed.\n     */\n    export function registerDocumentRangeSemanticTokensProvider(selector: DocumentSelector, provider: DocumentRangeSemanticTokensProvider, legend: SemanticTokensLegend): Disposable\n\n    /**\n     * Register a linked editing range provider.\n     *\n     * Multiple providers can be registered for a language. In that case providers are sorted\n     * by their {@link languages.match score} and the best-matching provider that has a result is used. Failure\n     * of the selected provider will cause a failure of the whole operation.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A linked editing range provider.\n     * @return A {@link Disposable} that unregisters this provider when being disposed.\n     */\n    export function registerLinkedEditingRangeProvider(selector: DocumentSelector, provider: LinkedEditingRangeProvider): Disposable\n\n    /**\n     * Register a inlay hints provider.\n     *\n     * Multiple providers can be registered for a language. In that case providers are asked in\n     * parallel and the results are merged. A failing provider (rejected promise or exception) will\n     * not cause a failure of the whole operation.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider An inlay hints provider.\n     * @return A {@link Disposable} that unregisters this provider when being disposed.\n     */\n    export function registerInlayHintsProvider(selector: DocumentSelector, provider: InlayHintsProvider): Disposable\n\n    /**\n     * Register a type hierarchy provider.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A type hierarchy provider.\n     * @return A {@link Disposable} that unregisters this provider when being disposed.\n     */\n    export function registerTypeHierarchyProvider(selector: DocumentSelector, provider: TypeHierarchyProvider): Disposable\n\n    /**\n     * Register a inline completion item provider.\n     *\n     * @param selector A selector that defines the documents this provider is applicable to.\n     * @param provider A InlineCompletion provider.\n     * @return A {@link Disposable} that unregisters this provider when being disposed.\n     */\n    export function registerInlineCompletionItemProvider(selector: DocumentSelector, provider: InlineCompletionItemProvider): Disposable\n  }\n  // }}\n\n  // services module {{\n  export enum ServiceStat {\n    Initial,\n    Starting,\n    StartFailed,\n    Running,\n    Stopping,\n    Stopped,\n  }\n\n  export interface IServiceProvider {\n    // unique service id\n    id: string\n    name: string\n    client?: LanguageClient\n    selector: DocumentSelector\n    // current state\n    state: ServiceStat\n    start(): Promise<void>\n    dispose(): void\n    stop(): Promise<void> | void\n    restart(): Promise<void> | void\n    onServiceReady: Event<void>\n  }\n\n  export namespace services {\n    /**\n     * Register languageClient as service provider.\n     */\n    export function registerLanguageClient(client: LanguageClient): Disposable\n    /**\n     * @deprecated use registerLanguageClient instead.\n     */\n    export function registLanguageClient(client: LanguageClient): Disposable\n    /**\n     * Register service, nothing happens when `service.id` already exists.\n     */\n    export function register(service: IServiceProvider): Disposable\n    /**\n     * @deprecated use register instead.\n     */\n    export function regist(service: IServiceProvider): Disposable\n    /**\n     * Get service by id.\n     */\n    export function getService(id: string): IServiceProvider\n    /**\n     * Stop service by id.\n     */\n    export function stop(id: string): Promise<void>\n    /**\n     * Stop running service or start stopped service.\n     */\n    export function toggle(id: string): Promise<void>\n  }\n  // }}\n\n  // sources module {{\n  /**\n   * Source options to create source that could respect configuration from `coc.source.{name}`\n   */\n  export interface SourceConfig {\n    name: string\n    triggerOnly?: boolean\n    isSnippet?: boolean\n    sourceType?: SourceType\n    filepath?: string\n    documentSelector?: DocumentSelector\n    firstMatch?: boolean\n    refresh?(): Promise<void>\n    toggle?(): void\n    onEnter?(bufnr: number): void\n    shouldComplete?(opt: CompleteOption): ProviderResult<boolean>\n    doComplete(opt: CompleteOption, token: CancellationToken): ProviderResult<CompleteResult>\n    onCompleteResolve?(item: VimCompleteItem, opt: CompleteOption, token: CancellationToken): ProviderResult<void>\n    onCompleteDone?(item: VimCompleteItem, opt: CompleteOption, snippetsSupport?: boolean): ProviderResult<void>\n    shouldCommit?(item: VimCompleteItem, character: string): boolean\n  }\n\n  export interface SourceStat {\n    name: string\n    priority: number\n    triggerCharacters: string[]\n    type: 'native' | 'remote' | 'service'\n    shortcut: string\n    filepath: string\n    disabled: boolean\n    filetypes: string[]\n  }\n\n  export enum SourceType {\n    Native,\n    Remote,\n    Service,\n  }\n\n  export interface CompleteResult {\n    items: ReadonlyArray<VimCompleteItem>\n    isIncomplete?: boolean\n    startcol?: number\n  }\n\n  // option on complete & should_complete\n  export interface CompleteOption {\n    /**\n     * Current buffer number.\n     */\n    readonly bufnr: number\n    /**\n     * Current line.\n     */\n    readonly line: string\n    /**\n     * Column to start completion, determined by iskeyword options of buffer.\n     */\n    readonly col: number\n    /**\n     * Input text.\n     */\n    readonly input: string\n    readonly filetype: string\n    readonly filepath: string\n    /**\n     * Word under cursor.\n     */\n    readonly word: string\n    /**\n     * Trigger character, could be undefined.\n     */\n    readonly triggerCharacter?: string\n    /**\n     * Col of cursor, 1 based.\n     */\n    readonly colnr: number\n    readonly linenr: number\n    /**\n     * Position of cursor when trigger completion\n     */\n    readonly position: Position\n    readonly synname: string\n    /**\n     * Buffer changetick\n     */\n    readonly changedtick: number\n    /**\n     * Is trigger for in complete completion.\n     */\n    readonly triggerForInComplete?: boolean\n  }\n\n  export interface ISource {\n    /**\n     * Identifier name\n     */\n    name: string\n    /**\n     * @deprecated use documentSelector instead.\n     */\n    filetypes?: string[]\n    /**\n     * Filters of document.\n     */\n    documentSelector?: DocumentSelector\n    enable?: boolean\n    shortcut?: string\n    priority?: number\n    sourceType?: SourceType\n    /**\n     * Should only be used when completion is triggered, requires `triggerPatterns` or `triggerCharacters` defined.\n     */\n    triggerOnly?: boolean\n    triggerCharacters?: string[]\n    // regex to detect trigger completion, ignored when triggerCharacters exists.\n    triggerPatterns?: RegExp[]\n    disableSyntaxes?: string[]\n    filepath?: string\n    // should the first character always match\n    firstMatch?: boolean\n    refresh?(): Promise<void>\n    /**\n     * For disable/enable\n     */\n    toggle?(): void\n\n    /**\n     * Triggered on BufEnter, used for cache normally\n     */\n    onEnter?(bufnr: number): void\n\n    /**\n     * Check if this source should doComplete\n     *\n     * @public\n     * @param {CompleteOption} opt\n     * @returns {Promise<boolean> }\n     */\n    shouldComplete?(opt: CompleteOption): ProviderResult<boolean>\n\n    /**\n     * Invoke completion\n     *\n     * @public\n     * @param {CompleteOption} opt\n     * @param {CancellationToken} token\n     * @returns {Promise<CompleteResult | null>}\n     */\n    doComplete(opt: CompleteOption, token: CancellationToken): ProviderResult<CompleteResult>\n\n    /**\n     * Action for complete item on complete item selected\n     *\n     * @public\n     * @param {VimCompleteItem} item\n     * @param {CancellationToken} token\n     * @returns {Promise<void>}\n     */\n    onCompleteResolve?(item: VimCompleteItem, token: CancellationToken): ProviderResult<void>\n\n    /**\n     * Action for complete item on complete done\n     *\n     * @public\n     * @param {VimCompleteItem} item\n     * @returns {Promise<void>}\n     */\n    onCompleteDone?(item: VimCompleteItem, opt: CompleteOption): ProviderResult<void>\n\n    shouldCommit?(item: VimCompleteItem, character: string): boolean\n  }\n\n  export namespace sources {\n    /**\n     * Names of registered sources.\n     */\n    export const names: ReadonlyArray<string>\n    export const sources: ReadonlyArray<ISource>\n    /**\n     * Check if source exists by name.\n     */\n    export function has(name: string): boolean\n    /**\n     * Get source by name.\n     */\n    export function getSource(name: string): ISource | null\n\n    /**\n     * Add source to sources list.\n     *\n     * Note: Use `sources.createSource()` to register new source is recommended for\n     * user configuration support.\n     */\n    export function addSource(source: ISource): Disposable\n\n    /**\n     * Create source by source config, configurations starts with `coc.source.{name}`\n     * are automatically supported.\n     *\n     * `name` and `doComplete()` must be provided in config.\n     */\n    export function createSource(config: SourceConfig): Disposable\n\n    /**\n     * Get list of all source stats.\n     */\n    export function sourceStats(): SourceStat[]\n\n    /**\n     * Call refresh for _name_ source or all sources.\n     */\n    export function refresh(name?: string): Promise<void>\n\n    /**\n     * Toggle state of _name_ source.\n     */\n    export function toggleSource(name: string): void\n\n    /**\n     * Remove source by name.\n     */\n    export function removeSource(name: string): void\n  }\n  // }}\n\n  // TreeView related {{\n  export interface TreeItemLabel {\n    label: string\n    highlights?: [number, number][]\n  }\n\n  export interface TreeItemIcon {\n    text: string\n    hlGroup: string\n  }\n\n  /**\n   * Collapsible state of the tree item\n   */\n  export enum TreeItemCollapsibleState {\n    /**\n     * Determines an item can be neither collapsed nor expanded. Implies it has no children.\n     */\n    None = 0,\n    /**\n     * Determines an item is collapsed\n     */\n    Collapsed = 1,\n    /**\n     * Determines an item is expanded\n     */\n    Expanded = 2\n  }\n\n  export class TreeItem {\n    /**\n     * A human-readable string describing this item. When `falsy`, it is derived from {@link TreeItem.resourceUri resourceUri}.\n     */\n    label?: string | TreeItemLabel\n\n    /**\n     * Description rendered less prominently after label.\n     */\n    description?: string\n\n    /**\n     * The icon path or {@link ThemeIcon} for the tree item.\n     * When `falsy`, {@link ThemeIcon.Folder Folder Theme Icon} is assigned, if item is collapsible otherwise {@link ThemeIcon.File File Theme Icon}.\n     * When a file or folder {@link ThemeIcon} is specified, icon is derived from the current file icon theme for the specified theme icon using {@link TreeItem.resourceUri resourceUri} (if provided).\n     */\n    icon?: TreeItemIcon\n\n    /**\n     * Optional id for the tree item that has to be unique across tree. The id is used to preserve the selection and expansion state of the tree item.\n     *\n     * If not provided, an id is generated using the tree item's resourceUri when exists. **Note** that when labels change, ids will change and that selection and expansion state cannot be kept stable anymore.\n     */\n    id?: string\n\n    /**\n     * The {@link Uri} of the resource representing this item.\n     *\n     * Will be used to derive the {@link TreeItem.label label}, when it is not provided.\n     * Will be used to derive the icon from current file icon theme, when {@link TreeItem.iconPath iconPath} has {@link ThemeIcon} value.\n     */\n    resourceUri?: Uri\n\n    /**\n     * The tooltip text when you hover over this item.\n     */\n    tooltip?: string | MarkupContent\n\n    /**\n     * The {@link Command} that should be executed when the tree item is selected.\n     *\n     * Please use `vscode.open` or `vscode.diff` as command IDs when the tree item is opening\n     * something in the editor. Using these commands ensures that the resulting editor will\n     * appear consistent with how other built-in trees open editors.\n     */\n    command?: Command\n\n    /**\n     * {@link TreeItemCollapsibleState} of the tree item.\n     */\n    collapsibleState?: TreeItemCollapsibleState\n\n    /**\n     * @param label A human-readable string describing this item\n     * @param collapsibleState {@link TreeItemCollapsibleState} of the tree item. Default is {@link TreeItemCollapsibleState.None}\n     */\n    constructor(label: string | TreeItemLabel, collapsibleState?: TreeItemCollapsibleState)\n\n    /**\n     * @param resourceUri The {@link Uri} of the resource representing this item.\n     * @param collapsibleState {@link TreeItemCollapsibleState} of the tree item. Default is {@link TreeItemCollapsibleState.None}\n     */\n    constructor(resourceUri: Uri, collapsibleState?: TreeItemCollapsibleState)\n  }\n\n  /**\n   * Action resolved by {@link TreeDataProvider}\n   */\n  export interface TreeItemAction<T> {\n    /**\n     * Label text in menu.\n     */\n    title: string\n    handler: (item: T) => ProviderResult<void>\n  }\n\n  /**\n   * Options for creating a {@link TreeView}\n   */\n  export interface TreeViewOptions<T> {\n    /**\n     * bufhidden option for TreeView, default to 'wipe'\n     */\n    bufhidden?: 'hide' | 'unload' | 'delete' | 'wipe'\n    /**\n     * Fixed width for window, default to true\n     */\n    winfixwidth?: boolean\n    /**\n     * Enable filter feature, default to false\n     */\n    enableFilter?: boolean\n    /**\n     * Disable indent of leaves without children, default to false\n     */\n    disableLeafIndent?: boolean\n    /**\n     * A data provider that provides tree data.\n     */\n    treeDataProvider: TreeDataProvider<T>\n    /**\n     * Whether the tree supports multi-select. When the tree supports multi-select and a command is executed from the tree,\n     * the first argument to the command is the tree item that the command was executed on and the second argument is an\n     * array containing all selected tree items.\n     */\n    canSelectMany?: boolean\n  }\n\n  /**\n   * The event that is fired when an element in the {@link TreeView} is expanded or collapsed\n   */\n  export interface TreeViewExpansionEvent<T> {\n\n    /**\n     * Element that is expanded or collapsed.\n     */\n    readonly element: T\n\n  }\n\n  /**\n   * The event that is fired when there is a change in {@link TreeView.selection tree view's selection}\n   */\n  export interface TreeViewSelectionChangeEvent<T> {\n\n    /**\n     * Selected elements.\n     */\n    readonly selection: T[]\n\n  }\n\n  /**\n   * The event that is fired when there is a change in {@link TreeView.visible tree view's visibility}\n   */\n  export interface TreeViewVisibilityChangeEvent {\n\n    /**\n     * `true` if the {@link TreeView tree view} is visible otherwise `false`.\n     */\n    readonly visible: boolean\n\n  }\n\n  /**\n   * Represents a Tree view\n   */\n  export interface TreeView<T> extends Disposable {\n\n    /**\n     * Event that is fired when an element is expanded\n     */\n    readonly onDidExpandElement: Event<TreeViewExpansionEvent<T>>\n\n    /**\n     * Event that is fired when an element is collapsed\n     */\n    readonly onDidCollapseElement: Event<TreeViewExpansionEvent<T>>\n\n    /**\n     * Currently selected elements.\n     */\n    readonly selection: T[]\n\n    /**\n     * Event that is fired when the {@link TreeView.selection selection} has changed\n     */\n    readonly onDidChangeSelection: Event<TreeViewSelectionChangeEvent<T>>\n\n    /**\n     * Event that is fired when {@link TreeView.visible visibility} has changed\n     */\n    readonly onDidChangeVisibility: Event<TreeViewVisibilityChangeEvent>\n\n    /**\n     * `true` if the {@link TreeView tree view} is visible otherwise `false`.\n     *\n     * **NOTE:** is `true` when TreeView visible on other tab.\n     */\n    readonly visible: boolean\n\n    /**\n     * Window id used by TreeView.\n     */\n    readonly windowId: number | undefined\n\n    /**\n     * An optional human-readable message that will be rendered in the view.\n     * Setting the message to null, undefined, or empty string will remove the message from the view.\n     */\n    message?: string\n\n    /**\n     * The tree view title is initially taken from viewId of TreeView\n     * Changes to the title property will be properly reflected in the UI in the title of the view.\n     */\n    title?: string\n\n    /**\n     * An optional human-readable description which is rendered less prominently in the title of the view.\n     * Setting the title description to null, undefined, or empty string will remove the description from the view.\n     */\n    description?: string\n\n    /**\n     * Reveals the given element in the tree view.\n     * If the tree view is not visible then the tree view is shown and element is revealed.\n     *\n     * By default revealed element is selected.\n     * In order to not to select, set the option `select` to `false`.\n     * In order to focus, set the option `focus` to `true`.\n     * In order to expand the revealed element, set the option `expand` to `true`. To expand recursively set `expand` to the number of levels to expand.\n     * **NOTE:** You can expand only to 3 levels maximum.\n     *\n     * **NOTE:** The {@link TreeDataProvider} that the `TreeView` {@link window.createTreeView is registered with} with must implement {@link TreeDataProvider.getParent getParent} method to access this API.\n     */\n    reveal(element: T, options?: { select?: boolean, focus?: boolean, expand?: boolean | number }): Thenable<void>\n\n    /**\n     * Create tree view in new window.\n     *\n     * **NOTE:** TreeView with same viewId in current tab would be disposed.\n     *\n     * @param splitCommand The command to open TreeView window, default to 'belowright 30vs'\n     * @return `true` if window shown.\n     */\n    show(splitCommand?: string): Promise<boolean>\n  }\n\n  /**\n   * A data provider that provides tree data\n   */\n  export interface TreeDataProvider<T> {\n    /**\n     * An optional event to signal that an element or root has changed.\n     * This will trigger the view to update the changed element/root and its children recursively (if shown).\n     * To signal that root has changed, do not pass any argument or pass `undefined` or `null`.\n     */\n    onDidChangeTreeData?: Event<T | undefined | null | void>\n\n    /**\n     * Get {@link TreeItem} representation of the `element`\n     *\n     * @param element The element for which {@link TreeItem} representation is asked for.\n     * @return {@link TreeItem} representation of the element\n     */\n    getTreeItem(element: T): TreeItem | Thenable<TreeItem>\n\n    /**\n     * Get the children of `element` or root if no element is passed.\n     *\n     * @param element The element from which the provider gets children. Can be `undefined`.\n     * @return Children of `element` or root if no element is passed.\n     */\n    getChildren(element?: T): ProviderResult<T[]>\n\n    /**\n     * Optional method to return the parent of `element`.\n     * Return `null` or `undefined` if `element` is a child of root.\n     *\n     * **NOTE:** This method should be implemented in order to access {@link TreeView.reveal reveal} API.\n     *\n     * @param element The element for which the parent has to be returned.\n     * @return Parent of `element`.\n     */\n    getParent?(element: T): ProviderResult<T>\n\n    /**\n     * Called on hover to resolve the {@link TreeItem.tooltip TreeItem} property if it is undefined.\n     * Called on tree item click/open to resolve the {@link TreeItem.command TreeItem} property if it is undefined.\n     * Only properties that were undefined can be resolved in `resolveTreeItem`.\n     * Functionality may be expanded later to include being called to resolve other missing\n     * properties on selection and/or on open.\n     *\n     * Will only ever be called once per TreeItem.\n     *\n     * onDidChangeTreeData should not be triggered from within resolveTreeItem.\n     *\n     * *Note* that this function is called when tree items are already showing in the UI.\n     * Because of that, no property that changes the presentation (label, description, etc.)\n     * can be changed.\n     *\n     * @param item Undefined properties of `item` should be set then `item` should be returned.\n     * @param element The object associated with the TreeItem.\n     * @param token A cancellation token.\n     * @return The resolved tree item or a thenable that resolves to such. It is OK to return the given\n     * `item`. When no result is returned, the given `item` will be used.\n     */\n    resolveTreeItem?(item: TreeItem, element: T, token: CancellationToken): ProviderResult<TreeItem>\n\n    /**\n     * Called with current element to resolve actions.\n     * Called when user press 'actions' key.\n     *\n     * @param item Resolved item.\n     * @param element The object under cursor.\n     */\n    resolveActions?(item: TreeItem, element: T): ProviderResult<TreeItemAction<T>[]>\n  }\n  // }}\n\n  // workspace module {{\n  /**\n   * An event describing the change in Configuration\n   */\n  export interface ConfigurationChangeEvent {\n\n    /**\n     * Returns `true` if the given section for the given resource (if provided) is affected.\n     *\n     * @param section Configuration name, supports _dotted_ names.\n     * @param resource A resource URI.\n     * @return `true` if the given section for the given resource (if provided) is affected.\n     */\n    affectsConfiguration(section: string, scope?: ConfigurationScope): boolean\n  }\n\n  export interface WillSaveEvent extends TextDocumentWillSaveEvent {\n    /**\n     * Allows to pause the event loop and to apply [pre-save-edits](#TextEdit).\n     * Edits of subsequent calls to this function will be applied in order. The\n     * edits will be *ignored* if concurrent modifications of the document happened.\n     *\n     * *Note:* This function can only be called during event dispatch and not\n     * in an asynchronous manner:\n     *\n     * ```ts\n     * workspace.onWillSaveTextDocument(event => {\n     * \t// async, will *throw* an error\n     * \tsetTimeout(() => event.waitUntil(promise));\n     *\n     * \t// sync, OK\n     * \tevent.waitUntil(promise);\n     * })\n     * ```\n     *\n     * @param thenable A thenable that resolves to [pre-save-edits](#TextEdit).\n     */\n    waitUntil(thenable: Thenable<TextEdit[] | any>): void\n  }\n\n  export interface KeymapOption {\n    /**\n     * Use <Cmd> as rhs command prefix, ignored on insert mode (<expr> is used on insert mode), see `:h map-cmd`.\n     */\n    cmd?: boolean\n    /**\n     * When invoke the callback, send request to NodeJS instead of notification, default `true`.\n     */\n    sync?: boolean\n    /**\n     * Cancel completion before invoke callback, default `true`, insert mode only.\n     */\n    cancel?: boolean\n    /**\n     * Use <silent> for keymap, default `true`.\n     */\n    silent?: boolean\n    /**\n     * Enable repeat support for repeat.vim, default `false`.\n     */\n    repeat?: boolean\n    /**\n     * Use <special> map argument, see `:h :map-special`, vim9 only.\n     */\n    special?: boolean\n  }\n\n  export interface DidChangeTextDocumentParams {\n    /**\n     * The document that did change. The version number points\n     * to the version after all provided content changes have\n     * been applied.\n     */\n    readonly textDocument: {\n      version: number\n      uri: string\n    }\n    /**\n     * The affected document.\n     */\n    readonly document: LinesTextDocument\n    /**\n     * The actual content changes. The content changes describe single state changes\n     * to the document. So if there are two content changes c1 (at array index 0) and\n     * c2 (at array index 1) for a document in state S then c1 moves the document from\n     * S to S' and c2 from S' to S''. So c1 is computed on the state S and c2 is computed\n     * on the state S'.\n     */\n    readonly contentChanges: ReadonlyArray<TextDocumentContentChange>\n    /**\n     * Buffer number of document.\n     */\n    readonly bufnr: number\n    /**\n     * Original content before change\n     */\n    readonly original: string\n    /**\n     * Original lines before change\n     */\n    readonly originalLines: ReadonlyArray<string>\n  }\n\n  export interface EditerState {\n    document: LinesTextDocument\n    position: Position\n  }\n\n  export type MapMode = 'n' | 'i' | 'v' | 'x' | 's' | 'o' | '!' | 't' | 'c' | 'l'\n\n  export interface Autocmd {\n    /**\n     * Vim event or event set.\n     */\n    event: string | string[]\n    /**\n     * Callback functions that called with evaled arglist as arguments.\n     */\n    callback: (...args: any[]) => void | Promise<void>\n    /**\n     * Match pattern, default to `*`.\n     */\n    pattern?: string | string[]\n    /**\n     * Vim expression that eval to arguments of callback, default to `[]`\n     */\n    arglist?: string[]\n    /**\n     * buffer number for buffer-local autocommand.\n     */\n    buffer?: number\n    /**\n     * the command is executed once when `true`, see `:h autocmd-once`\n     */\n    once?: boolean\n    /**\n     * allow nested autocmd when `true`, see `:h autocmd-nested`\n     */\n    nested?: boolean\n    /**\n     * Use request when `true`, use notification by default.\n     */\n    request?: boolean\n    /**\n     * `this` of callback.\n     */\n    thisArg?: any\n  }\n\n  export interface Env {\n    /**\n     * |runtimepath| option of (neo)vim.\n     */\n    readonly runtimepath: string\n    /**\n     * |virtualText| support in (neo)vim\n     */\n    readonly virtualText: boolean\n    /**\n     * |guicursor| option of (neo)vim\n     */\n    readonly guicursor: string\n    /**\n     * Could use float window on neovim, always false on vim.\n     */\n    readonly floating: boolean\n    /**\n     * |sign_place()| and |sign_unplace()| can be used when true.\n     */\n    readonly sign: boolean\n    /**\n     * Root directory of extensions.\n     */\n    readonly extensionRoot: string\n    /**\n     * Process id of (neo)vim.\n     */\n    readonly pid: number\n    /**\n     * Total columns of screen.\n     */\n    readonly columns: number\n    /**\n     * Total lines of screen.\n     */\n    readonly lines: number\n    /**\n     * Is true when |CompleteChanged| event is supported.\n     */\n    readonly pumevent: boolean\n    /**\n     * |cmdheight| option of (neo)vim.\n     */\n    readonly cmdheight: number\n    /**\n     * Value of |g:coc_filetype_map|\n     */\n    readonly filetypeMap: { [index: string]: string }\n    /**\n     * Is true when not using neovim.\n     */\n    readonly isVim: boolean\n    /**\n     * Is cygvim when true.\n     */\n    readonly isCygwin: boolean\n    /**\n     * Is macvim when true.\n     */\n    readonly isMacvim: boolean\n    /**\n     * Is true when iTerm.app is used on mac.\n     */\n    readonly isiTerm: boolean\n    /**\n     * version of (neo)vim, on vim it's like: 8020750, on neoivm it's like\n     */\n    readonly version: string\n    /**\n     * |v:progpath| value, could be empty.\n     */\n    readonly progpath: string\n    /**\n     * Is true when dialog feature is supported\n     */\n    readonly dialog: boolean\n    /**\n     * Is true when terminal feature is supported\n     */\n    readonly terminal: boolean\n    /**\n     * Is true when vim's textprop is supported.\n     */\n    readonly textprop: boolean\n  }\n\n  /**\n   * Store & retrieve most recent used items.\n   */\n  export interface Mru {\n    /**\n     * Load iems from mru file\n     */\n\n    load(): Promise<string[]>\n    /**\n     * Add item to mru file.\n     */\n    add(item: string): Promise<void>\n\n    /**\n     * Remove item from mru file.\n     */\n\n    remove(item: string): Promise<void>\n\n    /**\n     * Remove the data file.\n     */\n    clean(): Promise<void>\n  }\n\n  /**\n   * Option to create task that runs in (neo)vim.\n   */\n  export interface TaskOptions {\n    /**\n     *  The command to run, without arguments\n     */\n    cmd: string\n    /**\n     * Arguments of command.\n     */\n    args?: string[]\n    /**\n     * Current working directory of the task, Default to current vim's cwd.\n     */\n    cwd?: string\n    /**\n     * Additional environment key-value pairs.\n     */\n    env?: { [key: string]: string }\n    /**\n     * Use pty when true.\n     */\n    pty?: boolean\n    /**\n     * Detach child process when true.\n     */\n    detach?: boolean\n  }\n\n  /**\n   * Controls long running task started by (neo)vim.\n   * Useful to keep the task running after CocRestart.\n   */\n  export interface Task extends Disposable {\n    /**\n     * Fired on task exit with exit code.\n     */\n    onExit: Event<number>\n    /**\n     * Fired with lines on stdout received.\n     */\n    onStdout: Event<string[]>\n    /**\n     * Fired with lines on stderr received.\n     */\n    onStderr: Event<string[]>\n    /**\n     * Start task, task will be restarted when already running.\n     *\n     * @param {TaskOptions} opts\n     * @returns {Promise<boolean>}\n     */\n    start(opts: TaskOptions): Promise<boolean>\n    /**\n     * Stop task by SIGTERM or SIGKILL\n     */\n    stop(): Promise<void>\n    /**\n     * Check if the task is running.\n     */\n    running: Promise<boolean>\n  }\n\n  /**\n   * A simple json database.\n   */\n  export interface JsonDB {\n    filepath: string\n    /**\n     * Get data by key.\n     *\n     * @param {string} key unique key allows dot notation.\n     * @returns {any}\n     */\n    fetch(key: string): any\n    /**\n     * Check if key exists\n     *\n     * @param {string} key unique key allows dot notation.\n     */\n    exists(key: string): boolean\n    /**\n     * Delete data by key\n     *\n     * @param {string} key unique key allows dot notation.\n     */\n    delete(key: string): void\n    /**\n     * Save data with key\n     */\n    push(key: string, data: number | null | boolean | string | { [index: string]: any }): void\n    /**\n     * Empty db file.\n     */\n    clear(): void\n    /**\n     * Remove db file.\n     */\n    destroy(): void\n  }\n\n  export interface RenameEvent {\n    oldUri: Uri\n    newUri: Uri\n  }\n\n  export interface FileSystemWatcher {\n    readonly ignoreCreateEvents: boolean\n    readonly ignoreChangeEvents: boolean\n    readonly ignoreDeleteEvents: boolean\n    readonly onDidCreate: Event<Uri>\n    readonly onDidChange: Event<Uri>\n    readonly onDidDelete: Event<Uri>\n    readonly onDidRename: Event<RenameEvent>\n    dispose(): void\n  }\n\n  export type ConfigurationScope = string | null | Uri | TextDocument | WorkspaceFolder | { uri?: string; languageId?: string }\n\n  export interface ConfigurationInspect<T> {\n    key: string\n    defaultValue?: T\n    globalValue?: T\n    workspaceValue?: T\n    workspaceFolderValue?: T\n  }\n\n  export enum ConfigurationTarget {\n    Global = 1,\n    /**\n     * Not exists with coc.nvim yet.\n     */\n    Workspace = 2,\n    WorkspaceFolder = 3\n  }\n\n  export interface WorkspaceConfiguration {\n    /**\n     * Return a value from this configuration.\n     *\n     * @param section Configuration name, supports _dotted_ names.\n     * @return The value `section` denotes or `undefined`.\n     */\n    get<T>(section: string): T | undefined\n\n    /**\n     * Return a value from this configuration.\n     *\n     * @param section Configuration name, supports _dotted_ names.\n     * @param defaultValue A value should be returned when no value could be found, is `undefined`.\n     * @return The value `section` denotes or the default.\n     */\n    get<T>(section: string, defaultValue: T): T\n\n    /**\n     * Check if this configuration has a certain value.\n     *\n     * @param section Configuration name, supports _dotted_ names.\n     * @return `true` if the section doesn't resolve to `undefined`.\n     */\n    has(section: string): boolean\n\n    /**\n     * Retrieve all information about a configuration setting. A configuration value\n     * often consists of a *default* value, a global or installation-wide value,\n     * a workspace-specific value\n     *\n     * *Note:* The configuration name must denote a leaf in the configuration tree\n     * (`editor.fontSize` vs `editor`) otherwise no result is returned.\n     *\n     * @param section Configuration name, supports _dotted_ names.\n     * @return Information about a configuration setting or `undefined`.\n     */\n    inspect<T>(section: string): ConfigurationInspect<T> | undefined\n    /**\n     * Update a configuration value.\n     * The updated configuration values are persisted to configuration file.\n     *\n     * @param section Configuration name, supports _dotted_ names.\n     * @param value The new value.\n     * @param updateTarget Target of configuration, use global user configuration when is `true`,\n     *                     when `false` or undefined use workspace folder confirmation.\n     */\n    update(section: string, value: any, updateTarget?: ConfigurationTarget | boolean): Thenable<void>\n\n    /**\n     * Readable dictionary that backs this configuration.\n     */\n    readonly [key: string]: any\n  }\n\n  export interface BufferSyncItem {\n    /**\n     * Called on buffer unload.\n     */\n    dispose: () => void\n    /**\n     * Called on buffer content change.\n     */\n    onChange?(e: DidChangeTextDocumentParams): void\n    /**\n     * Called when NodeJS client receive lines change event, could be before or\n     * after `TextChangedI` and `TextChangedP` events, but always before\n     * `TextDocumentContentChange` event.\n     */\n    onTextChange?(): void\n    /**\n     * Called on `WindowVisible` event when exists.\n     * `region` contains, 1 based, end inclusive topline, botline\n     */\n    onVisible?(winid: number, region: Readonly<[number, number]>): void\n  }\n\n  export interface BufferSync<T extends BufferSyncItem> {\n    /**\n     * Current items.\n     */\n    readonly items: Iterable<T>\n    /**\n     * Get created item by uri\n     */\n    getItem(uri: string): T | undefined\n    /**\n     * Get created item by bufnr\n     */\n    getItem(bufnr: number): T | undefined\n    dispose: () => void\n  }\n\n  export interface FuzzyMatchResult {\n    score: number,\n    positions: Uint32Array\n  }\n\n  export interface FuzzyMatchHighlights {\n    score: number\n    highlights: AnsiHighlight[]\n  }\n\n  /**\n  * An array representing a fuzzy match.\n  *\n  * 0. the score\n  * 1. the offset at which matching started\n  * 2. `<match_pos_N>`\n  * 3. `<match_pos_1>`\n  * 4. `<match_pos_0>` etc\n  */\n  export type FuzzyScore = [score: number, wordStart: number, ...matches: number[]]\n\n  export interface FuzzyScoreOptions {\n    readonly boostFullMatch: boolean\n    /**\n     * Allows first match to be a weak match\n     */\n    readonly firstMatchCanBeWeak: boolean\n  }\n\n  /**\n   * Match kinds could be:\n   *\n   * - 'aggressive' with fixed match for permutations.\n   * - 'any' fast match with first 13 characters only.\n   * - 'normal' nothing special.\n   */\n  export type FuzzyKind = 'normal' | 'aggressive' | 'any'\n\n  export type ScoreFunction = (word: string, wordPos?: number) => FuzzyScore | undefined\n\n  export interface FuzzyMatch {\n\n    /**\n     * Create 0 index byte spans from matched text and FuzzyScore.\n     * Mostly used for create {@link HighlightItem highlight items}.\n     *\n     * @param {string} text The matched text for count bytes.\n     * @param {FuzzyScore} score\n     * @returns {Iterable<[number, number]>}\n     */\n    matchScoreSpans(text: string, score: FuzzyScore): Iterable<[number, number]>\n\n    /**\n     *\n     * Create a score function\n     *\n     * @param {string} pattern The pattern to match.\n     * @param {number} patternPos Start character index of pattern.\n     * @param {FuzzyScoreOptions} options Optional option.\n     * @param {FuzzyKind} kind Use 'normal' when undefined.\n     * @returns {ScoreFunction}\n     */\n    createScoreFunction(pattern: string, patternPos: number, options?: FuzzyScoreOptions, kind?: FuzzyKind): ScoreFunction\n\n    /**\n     * Initialize match by set the match pattern and matchSeq.\n     *\n     * @param {string} pattern The match pattern Limited length to 256.\n     * @param {boolean} matchSeq Match the sequence characters instead of split pattern by white spaces.\n     * @returns {void}\n     */\n    setPattern(pattern: string, matchSeq?: boolean): void\n    /**\n     * Get the match result of text including score and character index\n     * positions, return undefined when no match found.\n     *\n     * @param {string} text Content to match\n     * @returns {FuzzyMatchResult | undefined}\n     */\n    match(text: string): FuzzyMatchResult | undefined\n    /**\n     * Match character positions to column spans.\n     * Better than matchHighlights method by reduce iteration.\n     *\n     * @param {string} text Context to match\n     * @param {ArrayLike<number>} positions Matched character index positions.\n     * @param {number} max Maximum column number to calculate\n     * @returns {Iterable<[number, number]>}\n     */\n    matchSpans(text: string, positions: ArrayLike<number>, max?: number): Iterable<[number, number]>\n    /**\n     * Get the match highlights result, including score and highlight items.\n     * Return undefined when no match found.\n     *\n     * @param {string} text Content to match\n     * @returns {FuzzyMatchHighlights | undefined}\n     */\n    matchHighlights(text: string, hlGroup: string): FuzzyMatchHighlights | undefined\n  }\n\n  export interface TextDocumentMatch {\n    readonly uri: string\n    readonly languageId: string\n  }\n\n  export namespace workspace {\n    export const nvim: Neovim\n    export const isTrusted = true\n    /**\n     * Current buffer number, could be wrong since vim could not send autocmd as expected.\n     *\n     * @deprecated will be removed in the feature.\n     */\n    export const bufnr: number\n    /**\n     * Current document.\n     */\n    export const document: Promise<Document>\n    /**\n     * Environments or current (neo)vim.\n     */\n    export const env: Env\n    /**\n     * Float window or popup can work.\n     */\n    export const floatSupported: boolean\n    /**\n     * Current working directory of vim.\n     */\n    export const cwd: string\n    /**\n     * Current workspace root.\n     */\n    export const root: string\n    /**\n     * @deprecated aliased to root.\n     */\n    export const rootPath: string\n    /**\n     * Not neovim when true.\n     */\n    export const isVim: boolean\n    /**\n     * Is neovim when true.\n     */\n    export const isNvim: boolean\n    /**\n     * All filetypes of loaded documents.\n     */\n    export const filetypes: ReadonlySet<string>\n    /**\n     * All languageIds of loaded documents.\n     */\n    export const languageIds: ReadonlySet<string>\n    /**\n     * Root directory of coc.nvim\n     */\n    export const pluginRoot: string\n    /**\n     * Exists channel names.\n     */\n    export const channelNames: ReadonlyArray<string>\n    /**\n     * Loaded documents that attached.\n     */\n    export const documents: ReadonlyArray<Document>\n    /**\n     * Current document array.\n     */\n    export const textDocuments: ReadonlyArray<LinesTextDocument>\n    /**\n     * Current workspace folders.\n     */\n    export const workspaceFolders: ReadonlyArray<WorkspaceFolder>\n    /**\n     * Directory paths of workspaceFolders.\n     */\n    export const folderPaths: ReadonlyArray<string>\n    /**\n     * Current workspace folder, could be null when vim started from user's home.\n     *\n     * @deprecated\n     */\n    export const workspaceFolder: WorkspaceFolder | null\n    export const onDidCreateFiles: Event<FileCreateEvent>\n    export const onDidRenameFiles: Event<FileRenameEvent>\n    export const onDidDeleteFiles: Event<FileDeleteEvent>\n    export const onWillCreateFiles: Event<FileWillCreateEvent>\n    export const onWillRenameFiles: Event<FileWillRenameEvent>\n    export const onWillDeleteFiles: Event<FileWillDeleteEvent>\n    /**\n     * Event fired on workspace folder change.\n     */\n    export const onDidChangeWorkspaceFolders: Event<WorkspaceFoldersChangeEvent>\n    /**\n     * Event fired after document create.\n     */\n    export const onDidOpenTextDocument: Event<LinesTextDocument & { bufnr: number }>\n    /**\n     * Event fired after document unload.\n     */\n    export const onDidCloseTextDocument: Event<LinesTextDocument & { bufnr: number }>\n    /**\n     * Event fired on document change.\n     */\n    export const onDidChangeTextDocument: Event<DidChangeTextDocumentParams>\n    /**\n     * Event fired before document save.\n     */\n    export const onWillSaveTextDocument: Event<WillSaveEvent>\n    /**\n     * Event fired after document save.\n     */\n    export const onDidSaveTextDocument: Event<LinesTextDocument>\n\n    /**\n     * Event fired on configuration change. Configuration change could by many\n     * reasons, including:\n     *\n     * - Changes detected from `coc-settings.json`.\n     * - Change to document that using another configuration file.\n     * - Configuration change by call update API of WorkspaceConfiguration.\n     */\n    export const onDidChangeConfiguration: Event<ConfigurationChangeEvent>\n\n    /**\n     * Fired when vim's runtimepath change detected.\n     */\n    export const onDidRuntimePathChange: Event<ReadonlyArray<string>>\n\n    /**\n     * Returns a path that is relative to the workspace folder or folders.\n     *\n     * When there are no {@link workspace.workspaceFolders workspace folders} or when the path\n     * is not contained in them, the input is returned.\n     *\n     * @param pathOrUri A path or uri. When a uri is given its {@link Uri.fsPath fsPath} is used.\n     * @param includeWorkspaceFolder When `true` and when the given path is contained inside a\n     * workspace folder the name of the workspace is prepended. Defaults to `true` when there are\n     * multiple workspace folders and `false` otherwise.\n     * @return A path relative to the root or the input.\n     */\n    export function asRelativePath(pathOrUri: string | Uri, includeWorkspaceFolder?: boolean): string\n\n    /**\n     * Returns converted unix path when the vim is built with win32unix enabled. Original fullpath is returned when the\n     * convert is not necessary.  Only needed when the fullpath is passed vim directly.\n     *\n     * @param fullpath The filepath to fix, only windows absolute filepath is fixed.\n     */\n    export function fixWin32unixFilepath(fullpath: string): string\n\n    /**\n     * Opens a document. Will return early if this document is already open. Otherwise\n     * the document is loaded and the {@link workspace.onDidOpenTextDocument didOpen}-event fires.\n     *\n     * The document is denoted by an {@link Uri}. Depending on the {@link Uri.scheme scheme} the\n     * following rules apply:\n     * * `file`-scheme: Open a file on disk (`openTextDocument(Uri.file(path))`). Will be rejected if the file\n     * does not exist or cannot be loaded.\n     * * `untitled`-scheme: Open a blank untitled file with associated path (`openTextDocument(Uri.file(path).with({ scheme: 'untitled' }))`).\n     * The language will be derived from the file name.\n     * * For all other schemes contributed {@link TextDocumentContentProvider text document content providers} and\n     * {@link FileSystemProvider file system providers} are consulted.\n     *\n     * *Note* that the lifecycle of the returned document is owned by the editor and not by the extension. That means an\n     * {@linkcode workspace.onDidCloseTextDocument onDidClose}-event can occur at any time after opening it.\n     *\n     * @param uri Identifies the resource to open.\n     * @return A promise that resolves to a {@link Document document}.\n     */\n    export function openTextDocument(uri: Uri): Thenable<Document>\n\n    /**\n     * A short-hand for `openTextDocument(Uri.file(fileName))`.\n     *\n     * @see {@link openTextDocument}\n     * @param fileName A name of a file on disk.\n     * @return A promise that resolves to a {@link Document document}.\n     */\n    export function openTextDocument(fileName: string): Thenable<Document>\n\n    /**\n     * Get display cell count of text on vim.\n     * Control character below 0x80 are considered as 1.\n     *\n     * @param text Text to display.\n     * @return The cells count.\n     */\n    export function getDisplayWidth(text: string, cache?: boolean): number\n\n    /**\n     * Like vim's has(), but for version check only.\n     * Check patch on neovim and check nvim on vim would return false.\n     *\n     * For example:\n     * - has('nvim-0.6.0')\n     * - has('patch-7.4.248')\n     */\n    export function has(feature: string): boolean\n\n    /**\n     * Register autocmd on vim.\n     *\n     * Note: avoid request autocmd when possible since vim could be blocked\n     * forever when request triggered during request.\n     */\n    export function registerAutocmd(autocmd: Autocmd, disposables?: Disposable[]): Disposable\n\n    /**\n     * Watch for vim's global option change.\n     */\n    export function watchOption(key: string, callback: (oldValue: any, newValue: any) => Thenable<void> | void, disposables?: Disposable[]): void\n\n    /**\n     * Watch for vim's global variable change, works on neovim only.\n     */\n    export function watchGlobal(key: string, callback?: (oldValue: any, newValue: any) => Thenable<void> | void, disposables?: Disposable[]): void\n\n    /**\n     * Check if selector match document.\n     */\n    export function match(selector: DocumentSelector, document: TextDocumentMatch): number\n\n    /**\n     * Findup from filename or filenames from current filepath or root.\n     *\n     * @return fullpath of file or null when not found.\n     */\n    export function findUp(filename: string | string[]): Promise<string | null>\n\n    /**\n     * Get possible watchman binary path.\n     */\n    export function getWatchmanPath(): string | null\n\n    /**\n     * Get configuration by section and optional resource uri.\n     */\n    export function getConfiguration(section?: string, scope?: ConfigurationScope): WorkspaceConfiguration\n\n    /**\n     * Resolve internal json schema, uri should starts with `vscode://`\n     */\n    export function resolveJSONSchema(uri: string): any\n    /**\n     * Get created document by uri or bufnr.\n     */\n    export function getDocument(uri: number | string): Document | null | undefined\n\n    /**\n     * Apply WorkspaceEdit.\n     */\n    export function applyEdit(edit: WorkspaceEdit, metadata?: WorkspaceEditMetadata): Promise<boolean>\n\n    /**\n     * Convert location to quickfix item.\n     */\n    export function getQuickfixItem(loc: Location | LocationLink, text?: string, type?: string, module?: string): Promise<QuickfixItem>\n\n    /**\n     * Convert locations to quickfix list.\n     */\n    export function getQuickfixList(locations: Location[]): Promise<ReadonlyArray<QuickfixItem>>\n\n    /**\n     * Populate locations to UI.\n     */\n    export function showLocations(locations: Location[]): Promise<void>\n\n    /**\n     * Get content of line by uri and line.\n     */\n    export function getLine(uri: string, line: number): Promise<string>\n\n    /**\n     * Get WorkspaceFolder of uri\n     */\n    export function getWorkspaceFolder(uri: string | Uri): WorkspaceFolder | undefined\n\n    /**\n     * Get content from buffer or file by uri.\n     */\n    export function readFile(uri: string): Promise<string>\n\n    /**\n     * Get current document and position.\n     */\n    export function getCurrentState(): Promise<EditerState>\n\n    /**\n     * Get format options of uri or current buffer.\n     */\n    export function getFormatOptions(uri?: string): Promise<FormattingOptions>\n\n    /**\n     * Jump to location.\n     */\n    export function jumpTo(uri: string | Uri, position?: Position | null, openCommand?: string): Promise<void>\n\n    /**\n     * Create a file in vim and disk\n     */\n    export function createFile(filepath: string, opts?: CreateFileOptions): Promise<void>\n\n    /**\n     * Load uri as document, buffer would be invisible if not loaded.\n     */\n    export function loadFile(uri: string): Promise<Document>\n\n    /**\n     * Load the files that not loaded\n     */\n    export function loadFiles(uris: string[]): Promise<void>\n\n    /**\n     * Rename file in vim and disk\n     */\n    export function renameFile(oldPath: string, newPath: string, opts?: RenameFileOptions): Promise<void>\n\n    /**\n     * Delete file from vim and disk.\n     */\n    export function deleteFile(filepath: string, opts?: DeleteFileOptions): Promise<void>\n\n    /**\n     * Open resource by uri\n     */\n    export function openResource(uri: string): Promise<void>\n\n    /**\n     * Resolve full path of module from yarn or npm global directory.\n     */\n    export function resolveModule(name: string): Promise<string>\n\n    /**\n     * Run nodejs command\n     */\n    export function runCommand(cmd: string, cwd?: string, timeout?: number): Promise<string>\n\n    /**\n     * Expand filepath with `~` and/or environment placeholders\n     */\n    export function expand(filepath: string): string\n\n    /**\n     * Call a function by use notifications, useful for functions like |input| that could block vim.\n     */\n    export function callAsync<T>(method: string, args: any[]): Promise<T>\n\n    /**\n     * Register TextDocumentContentProvider for custom scheme\n     */\n    export function registerTextDocumentContentProvider(scheme: string, provider: TextDocumentContentProvider): Disposable\n\n    /**\n     * Register unique global key-mapping with `<Plug>(coc-{key})` as lhs.\n     * 'noremap' is always used, Throw error when {key} already exists.\n     *\n     * @param {MapMode[]} modes - Array of map mode short-name.\n     * @param {string} key - Unique name, should only use alphabetical characters and '-'.\n     * @param {Function} fn - Callback function.\n     * @param {KeymapOption} opts - Optional option.\n     * @returns {Disposable}\n     */\n    export function registerKeymap(modes: MapMode[], key: string, fn: () => ProviderResult<any>, opts?: KeymapOption): Disposable\n\n    /**\n     * Register expr mapping global or local to buffer.\n     *\n     * Unlike :map, space in {lhs} is accepted as part of the {lhs}, keycodes\n     * are replaced are usual.\n     * 'noremap' and map arguments <silent>, <nowait> are always used.\n     *\n     * @param {MapMode} mode - Mode short-name.\n     * @param {string} rhs - rhs of key-mapping.\n     * @param {Function} fn - callback function.\n     * @param {number | boolean} buffer - Buffer number or current buffer by use `true` or 0, default to false.\n     * @param {boolean} cancel - Cancel pupop menu before invoke callback, insert mode only, define to true.\n     * @returns {Disposable}\n     */\n    export function registerExprKeymap(mode: MapMode, rhs: string, fn: () => ProviderResult<string>, buffer?: number | boolean, cancel?: boolean): Disposable\n\n    /**\n     * Register local keymap with callback.\n     *\n     * Unlike :map, space in {lhs} is accepted as part of the {lhs}, keycodes\n     * are replaced are usual.\n     * 'noremap' and map arguments <nowait> are always used.\n     *\n     * @param {number} bufnr - buffer number, use 0 for current buffer.\n     * @param {'n' | 'i' | 'v' | 's' | 'x'} mode - mode short-name.\n     * @param {string} lhs - lhs of key-mapping.\n     * @param {Function} fn - callback function.\n     * @param {KeymapOption | boolean} opts - Optional option, when it's boolean value, indicate use notification or not.\n     * @returns {Disposable}\n     */\n    export function registerLocalKeymap(bufnr: number, mode: 'n' | 'i' | 'v' | 's' | 'x', lhs: string, fn: () => ProviderResult<any>, opts?: KeymapOption | boolean): Disposable\n\n    /**\n     * Register for buffer sync objects, created sync object should be\n     * disposable and provide optional event handlers:\n     *\n     * - `onChange` called on `onDidChangeTextDocument` event.\n     * - `onTextChange` called on line change event from vim.\n     * - `onVisible` called on `WindowVisible` event.\n     *\n     * The document is always attached and not command line buffer.\n     *\n     * @param create Called for each attached document and on document create.\n     * @returns Disposable\n     */\n    export function registerBufferSync<T extends BufferSyncItem>(create: (doc: Document) => T | undefined): BufferSync<T>\n\n    /**\n     * Create a FuzzyMatch instance using wasm module.\n     * The FuzzyMatch does the same match algorithm as vim's `:h matchfuzzypos()`\n     */\n    export function createFuzzyMatch(): FuzzyMatch\n\n    /**\n     * Compute word ranges of opened document in specified range.\n     *\n     * @param {string | number} uri Uri of resource\n     * @param {Range} range Range of resource\n     * @param {CancellationToken} token\n     * @returns {Promise<{ [word: string]: Range[] } | null>}\n     */\n    export function computeWordRanges(uri: string | number, range: Range, token?: CancellationToken): Promise<{ [word: string]: Range[] } | null>\n    /**\n     * Create a FileSystemWatcher instance, when watchman doesn't exist, the\n     * returned FileSystemWatcher can still be used, but not work at all.\n     */\n    export function createFileSystemWatcher(globPattern: GlobPattern, ignoreCreate?: boolean, ignoreChange?: boolean, ignoreDelete?: boolean): FileSystemWatcher\n    /**\n     * Find files across all {@link workspace.workspaceFolders workspace folders} in the workspace.\n     *\n     * @example\n     * findFiles('**​/*.js', '**​/node_modules/**', 10)\n     *\n     * @param include A {@link GlobPattern glob pattern} that defines the files to search for. The glob pattern\n     * will be matched against the file paths of resulting matches relative to their workspace.\n     * Use a {@link RelativePattern relative pattern} to restrict the search results to a {@link WorkspaceFolder workspace folder}.\n     * @param exclude  A {@link GlobPattern glob pattern} that defines files and folders to exclude. The glob pattern\n     * will be matched against the file paths of resulting matches relative to their workspace. When `undefined` or`null`,\n     * no excludes will apply.\n     * @param maxResults An upper-bound for the result.\n     * @param token A token that can be used to signal cancellation to the underlying search engine.\n     * @return A thenable that resolves to an array of resource identifiers. Will return no results if no\n     * {@link workspace.workspaceFolders workspace folders} are opened.\n     */\n    export function findFiles(include: GlobPattern, exclude?: GlobPattern | null, maxResults?: number, token?: CancellationToken): Thenable<Uri[]>\n\n    /**\n     * Create persistence Mru instance.\n     */\n    export function createMru(name: string): Mru\n\n    /**\n     * Create Task instance that runs in (neo)vim, no shell.\n     *\n     * @param id Unique id string, like `TSC`\n     */\n    export function createTask(id: string): Task\n\n    /**\n     * Create DB instance at extension root.\n     */\n    export function createDatabase(name: string): JsonDB\n  }\n  // }}\n\n  // window module {{\n  /**\n  * Represents how a terminal exited.\n  */\n  export interface TerminalExitStatus {\n    /**\n      * The exit code that a terminal exited with, it can have the following values:\n      * - Zero: the terminal process or custom execution succeeded.\n      * - Non-zero: the terminal process or custom execution failed.\n      * - `undefined`: the user forcibly closed the terminal or a custom execution exited\n      *   without providing an exit code.\n      */\n    readonly code: number | undefined\n  }\n\n  export interface TerminalOptions {\n    /**\n     * A human-readable string which will be used to represent the terminal in the UI.\n     */\n    name?: string\n\n    /**\n     * A path to a custom shell executable to be used in the terminal.\n     */\n    shellPath?: string\n\n    /**\n     * Args for the custom shell executable, this does not work on Windows (see #8429)\n     */\n    shellArgs?: string[]\n\n    /**\n     * A path or URI for the current working directory to be used for the terminal.\n     */\n    cwd?: string\n\n    /**\n     * Object with environment variables that will be added to the VS Code process.\n     */\n    env?: { [key: string]: string | null }\n\n    /**\n     * Whether the terminal process environment should be exactly as provided in\n     * `TerminalOptions.env`. When this is false (default), the environment will be based on the\n     * window's environment and also apply configured platform settings like\n     * `terminal.integrated.windows.env` on top. When this is true, the complete environment\n     * must be provided as nothing will be inherited from the process or any configuration.\n     * Neovim only.\n     */\n    strictEnv?: boolean\n  }\n\n  /**\n   * An individual terminal instance within the integrated terminal.\n   */\n  export interface Terminal {\n\n    /**\n     * The bufnr of terminal buffer.\n     */\n    readonly bufnr: number\n\n    /**\n     * The name of the terminal.\n     */\n    readonly name: string\n\n    /**\n     * The process ID of the shell process.\n     */\n    readonly processId: Promise<number>\n\n    /**\n     * The exit status of the terminal, this will be undefined while the terminal is active.\n     *\n     * **Example:** Show a notification with the exit code when the terminal exists with a\n     * non-zero exit code.\n     * ```typescript\n     * window.onDidCloseTerminal(t => {\n     *   if (t.exitStatus && t.exitStatus.code) {\n     *   \tvscode.window.showInformationMessage(`Exit code: ${t.exitStatus.code}`);\n     *   }\n     * });\n     * ```\n     */\n    readonly exitStatus: TerminalExitStatus | undefined\n\n    /**\n     * Send text to the terminal. The text is written to the stdin of the underlying pty process\n     * (shell) of the terminal.\n     *\n     * @param text The text to send.\n     * @param addNewLine Whether to add a new line to the text being sent, this is normally\n     * required to run a command in the terminal. The character(s) added are \\n or \\r\\n\n     * depending on the platform. This defaults to `true`.\n     */\n    sendText(text: string, addNewLine?: boolean): void\n\n    /**\n     * Show the terminal panel and reveal this terminal in the UI, return false when failed.\n     *\n     * @param preserveFocus When `true` the terminal will not take focus.\n     */\n    show(preserveFocus?: boolean): Promise<boolean>\n\n    /**\n     * Hide the terminal panel if this terminal is currently showing.\n     */\n    hide(): void\n\n    /**\n     * Dispose and free associated resources.\n     */\n    dispose(): void\n  }\n\n  /**\n   * Option for create status item.\n   */\n  export interface StatusItemOption {\n    progress?: boolean\n  }\n\n  /**\n   * Status item that included in `g:coc_status`\n   */\n  export interface StatusBarItem {\n    /**\n     * The priority of this item. Higher value means the item should\n     * be shown more to the left.\n     */\n    readonly priority: number\n\n    isProgress: boolean\n\n    /**\n     * The text to show for the entry. You can embed icons in the text by leveraging the syntax:\n     *\n     * `My text $(icon-name) contains icons like $(icon-name) this one.`\n     *\n     * Where the icon-name is taken from the [octicon](https://octicons.github.com) icon set, e.g.\n     * `light-bulb`, `thumbsup`, `zap` etc.\n     */\n    text: string\n\n    /**\n     * Shows the entry in the status bar.\n     */\n    show(): void\n\n    /**\n     * Hide the entry in the status bar.\n     */\n    hide(): void\n\n    /**\n     * Dispose and free associated resources. Call\n     * [hide](#StatusBarItem.hide).\n     */\n    dispose(): void\n  }\n\n  /**\n   * Value-object describing where and how progress should show.\n   */\n  export interface ProgressOptions {\n\n    /**\n     * A human-readable string which will be used to describe the\n     * operation.\n     */\n    title?: string\n\n    /**\n     * Controls if a cancel button should show to allow the user to\n     * cancel the long running operation.\n     */\n    cancellable?: boolean\n  }\n\n  /**\n   * Defines a generalized way of reporting progress updates.\n   */\n  export interface Progress<T> {\n\n    /**\n     * Report a progress update.\n     *\n     * @param value A progress item, like a message and/or an\n     * report on how much work finished\n     */\n    report(value: T): void\n  }\n\n  /**\n   * Represents an action that is shown with an information, warning, or\n   * error message.\n   *\n   * @see [showInformationMessage](#window.showInformationMessage)\n   * @see [showWarningMessage](#window.showWarningMessage)\n   * @see [showErrorMessage](#window.showErrorMessage)\n   */\n  export interface MessageItem {\n\n    /**\n     * A short title like 'Retry', 'Open Log' etc.\n     */\n    title: string\n\n    /**\n     * A hint for modal dialogs that the item should be triggered\n     * when the user cancels the dialog (e.g. by pressing the ESC\n     * key).\n     *\n     * Note: this option is ignored for non-modal messages.\n     * Note: not used by coc.nvim for now.\n     */\n    isCloseAffordance?: boolean\n  }\n\n  export interface DialogButton {\n    /**\n     * Use by callback, should >= 0\n     */\n    index: number\n    text: string\n    /**\n     * Not shown when true\n     */\n    disabled?: boolean\n  }\n\n  export interface DialogConfig {\n    /**\n     * Content shown in window.\n     */\n    content: string\n    /**\n     * Optional title text.\n     */\n    title?: string\n    /**\n     * show close button, default to true when not specified.\n     */\n    close?: boolean\n    /**\n     * highlight group for dialog window, default to `\"dialog.floatHighlight\"` or 'CocFlating'\n     */\n    highlight?: string\n    /**\n     * highlight items of content.\n     */\n    highlights?: ReadonlyArray<HighlightItem>\n    /**\n     * highlight groups for border, default to `\"dialog.borderhighlight\"` or 'CocFlating'\n     */\n    borderhighlight?: string\n    /**\n     * Buttons as bottom of dialog.\n     */\n    buttons?: DialogButton[]\n    /**\n     * index is -1 for window close without button click\n     */\n    callback?: (index: number) => void\n  }\n\n  export type NotificationKind = 'error' | 'info' | 'warning' | 'progress'\n\n  export interface NotificationConfig {\n    kind?: NotificationKind\n\n    content?: string\n    /**\n     * Optional title text.\n     */\n    title?: string\n    /**\n     * Buttons as bottom of dialog.\n     */\n    buttons?: DialogButton[]\n    /**\n     * index is -1 for window close without button click\n     */\n    callback?: (index: number) => void\n  }\n\n  /**\n   * Options to configure the behavior of the quick pick UI.\n   */\n  export interface QuickPickOptions {\n    /**\n     * An optional string that represents the title of the quick pick.\n     */\n    title?: string\n    /**\n     * Placeholder text that shown when input value is empty.\n     */\n    placeHolder?: string\n    /**\n     * An optional flag to include the description when filtering the picks.\n     */\n    matchOnDescription?: boolean\n    /**\n     * An optional flag to make the picker accept multiple selections, if true the result is an array of picks.\n     */\n    canPickMany?: boolean\n    /**\n     * @deprecated use placeHolder instead.\n     */\n    placeholder?: string\n  }\n\n  /**\n   * Represents an item that can be selected from\n   * a list of items.\n   */\n  export interface QuickPickItem {\n    /**\n     * A human-readable string which is rendered prominent\n     */\n    label: string\n    /**\n     * A human-readable string which is rendered less prominent in the same line\n     */\n    description?: string\n    /**\n     * Optional flag indicating if this item is picked initially.\n     */\n    picked?: boolean\n  }\n\n  export interface QuickPickConfig<T extends QuickPickItem> {\n    /**\n     * An optional title.\n     */\n    title?: string\n    /**\n     * Placeholder text that shown when input value is empty.\n     */\n    placeholder?: string\n    /**\n     * Items to pick from.\n     */\n    items: readonly T[]\n    /**\n     * Initial value of the filter text.\n     */\n    value?: string\n    /**\n     * If multiple items can be selected at the same time. Defaults to false.\n     */\n    canSelectMany?: boolean\n    /**\n     * If the filter text should also be matched against the description of the items. Defaults to false.\n     */\n    matchOnDescription: boolean\n  }\n\n  export interface QuickPick<T extends QuickPickItem> {\n    /**\n     * Set or get current input value.\n     */\n    value: string\n    /**\n     * An optional title.\n     */\n    title: string | undefined\n    /**\n     * An optional placeholder text.\n     */\n    placeholder: string | undefined\n    /**\n     * If the UI should show a progress indicator. Defaults to false.\n     *\n     * Change this to true, e.g., while loading more data or validating\n     * user input.\n     */\n    loading: boolean\n    /**\n     * Items to pick from. This can be read and updated by the extension.\n     */\n    items: readonly T[]\n    /**\n     * Active items. This can be read and updated by the extension.\n     */\n    activeItems: readonly T[]\n    /**\n     * If the filter text should also be matched against the description of the items. Defaults to false.\n     */\n    matchOnDescription: boolean\n    /**\n      * If multiple items can be selected at the same time. Defaults to false.\n      */\n    canSelectMany: boolean\n    /**\n     * Max height of list, ``\n     */\n    maxHeight: number\n    /**\n     * Width of window, limited by `dialog.maxWidth` configuration and vim's 'columns'.\n     * Undefined by default, which means the width is dynamically calculated.\n     */\n    width: number | undefined\n    /**\n     * Represents the input prompt box field of the quickpick element\n    **/\n    readonly inputBox: InputBox | undefined\n    /**\n     * The current selection index, can be used to act on an item with onDidFinish, even\n     * if the item is not selected. The index corresponds to the .items or .activeItems\n     * arrays, and can be used to index into them\n    **/\n    readonly currIndex: number\n    /**\n     * The buffer for the popup element of the quick pick containing the .items to be selected\n    **/\n    readonly buffer: number\n    /**\n     * The window for the popup element of the quick pick containing the .items to be selected\n    **/\n    readonly winid: number | undefined\n    /**\n     * An event signaling when QuickPick closed, fired with selected items or null when canceled.\n     */\n    readonly onDidFinish: Event<T[] | null>\n    /**\n     * An event signaling when the value of the filter text has changed.\n     */\n    readonly onDidChangeValue: Event<string>\n    /**\n     * An event signaling when the selected items have changed.\n     */\n    readonly onDidChangeSelection: Event<readonly T[]>\n    /**\n     * Makes the input UI visible in its current configuration.\n     */\n    show(): Promise<void>\n  }\n\n  export interface ScreenPosition {\n    row: number\n    col: number\n  }\n\n  export type MsgTypes = 'error' | 'warning' | 'more'\n\n  export interface OpenTerminalOption {\n    /**\n     * Cwd of terminal, default to result of |getcwd()|\n     */\n    cwd?: string\n    /**\n     * Close terminal on job finish, default to true.\n     */\n    autoclose?: boolean\n    /**\n     * Keep focus current window, default to false.\n     */\n    keepfocus?: boolean\n    /**\n     * Position of terminal window, default to 'right'.\n     */\n    position?: 'bottom' | 'right'\n  }\n\n  /**\n   * An output channel is a container for readonly textual information.\n   *\n   * To get an instance of an `OutputChannel` use\n   * [createOutputChannel](#window.createOutputChannel).\n   */\n  export interface OutputChannel {\n\n    /**\n     * The human-readable name of this output channel.\n     */\n    readonly name: string\n\n    readonly content: string\n    /**\n     * Append the given value to the channel.\n     *\n     * @param value A string, falsy values will not be printed.\n     */\n    append(value: string): void\n\n    /**\n     * Append the given value and a line feed character\n     * to the channel.\n     *\n     * @param value A string, falsy values will be printed.\n     */\n    appendLine(value: string): void\n\n    /**\n     * Removes output from the channel. Latest `keep` lines will be remained.\n     */\n    clear(keep?: number): void\n\n    /**\n     * Reveal this channel in the UI.\n     *\n     * @param preserveFocus When `true` the channel will not take focus.\n     */\n    show(preserveFocus?: boolean): void\n\n    /**\n     * Hide this channel from the UI.\n     */\n    hide(): void\n\n    /**\n     * Dispose and free associated resources.\n     */\n    dispose(): void\n  }\n\n  export interface TerminalResult {\n    bufnr: number\n    success: boolean\n    content?: string\n  }\n\n  export interface Dialog {\n    /**\n     * Buffer number of dialog.\n     */\n    bufnr: number\n    /**\n     * Window id of dialog.\n     */\n    winid: Promise<number | null>\n    dispose: () => void\n  }\n\n  export type HighlightItemDef = [string, number, number, number, number?, number?, number?]\n\n  export interface HighlightDiff {\n    remove: number[]\n    removeMarkers: number[]\n    add: HighlightItemDef[]\n  }\n\n  export interface MenuItem {\n    text: string\n    disabled?: boolean | { reason: string }\n  }\n\n  export interface MenuOption {\n    /**\n     * Title in menu window.\n     */\n    title?: string,\n    /**\n     * Content in menu window as normal text.\n     */\n    content?: string\n    /**\n     * Create and highlight shortcut characters.\n     */\n    shortcuts?: boolean\n    /**\n     * Position of menu, default to 'cursor'\n     */\n    position?: 'center' | 'cursor'\n  }\n\n  export interface InputOptions {\n    /**\n     * Placeholder text that shown when input value is empty.\n     */\n    placeholder?: string\n    /**\n     * Position to show input, default to 'cursor'\n     */\n    position?: 'cursor' | 'center'\n    /**\n     * Margin top editor when position is 'center'\n     */\n    marginTop?: number\n    /**\n     * Border highlight of float window/popup, configuration `dialog.borderhighlight` used as default.\n     */\n    borderhighlight?: string\n    /**\n     * Create key-mappings for quickpick list.\n     */\n    list?: boolean\n  }\n\n  export interface InputPreference extends InputOptions {\n    /**\n     * Top, right, bottom, left border existence, default to [1,1,1,1]\n     */\n    border?: [0 | 1, 0 | 1, 0 | 1, 0 | 1]\n    /**\n     * Rounded border, default to true, configuration `dialog.rounded` used as default.\n     */\n    rounded?: boolean\n    /**\n     * Minimal window width, `g:coc_prompt_win_width` or 32 used as default.\n     */\n    minWidth?: number\n    /**\n     * Maximum window width, configuration `dialog.maxWidth` used as default.\n     */\n    maxWidth?: number\n  }\n\n  export interface InputDimension {\n    readonly width: number\n    readonly height: number\n    /**\n     * 0 based screen row\n     */\n    readonly row: number\n    /**\n     * O based screen col\n     */\n    readonly col: number\n  }\n\n  export interface InputBox {\n    /**\n     * Current input text, could be changed.\n     */\n    value: string\n    /**\n     * Change or get title of input box.\n     */\n    title: string\n    /**\n     * Change or get loading state of input box.\n     */\n    loading: boolean\n    /**\n     * Change or get borderhighlight of input box.\n     */\n    borderhighlight: string\n    /**\n     * Dimension of float window/popup\n     */\n    readonly dimension: InputDimension\n    /**\n     * Buffer number of float window/popup\n     */\n    readonly bufnr: number\n    /**\n     * An event signaling when the value has changed.\n     */\n    readonly onDidChange: Event<string>\n    /**\n     * An event signaling input finished, emit input value or null when canceled.\n     */\n    readonly onDidFinish: Event<string | null>\n  }\n\n  /**\n   * FloatWinConfig.\n   */\n  export interface FloatWinConfig {\n    border?: boolean | [number, number, number, number]\n    rounded?: boolean\n    highlight?: string\n    title?: string\n    borderhighlight?: string\n    close?: boolean\n    maxHeight?: number\n    maxWidth?: number\n    winblend?: number\n    focusable?: boolean\n    shadow?: boolean\n    preferTop?: boolean\n    autoHide?: boolean\n    offsetX?: number\n    cursorline?: boolean\n    modes?: string[]\n    excludeImages?: boolean\n    position?: \"fixed\" | \"auto\"\n    top?: number\n    bottom?: number\n    left?: number\n    right?: number\n  }\n\n  export interface FloatFactory {\n    /**\n     * Show documentations in float window/popup.\n     * Window and buffer are reused when possible.\n     *\n     * @param docs List of documentations.\n     * @param options Configuration for floating window/popup.\n     */\n    show: (docs: Documentation[], options?: FloatWinConfig) => Promise<void>\n    /**\n     * Close the float window created by this float factory.\n     */\n    close: () => void\n    /**\n     * Check if float window is shown.\n     */\n    activated: () => Promise<boolean>\n    /**\n     * Unbind events\n     */\n    dispose: () => void\n  }\n\n  export namespace window {\n    /**\n     * The currently active editor or `undefined`. The active editor is the one\n     * that currently has focus or, when none has focus, the one that has changed\n     * input most recently.\n     */\n    export const activeTextEditor: TextEditor | undefined\n\n    /**\n     * The currently visible editors or an empty array.\n     */\n    export const visibleTextEditors: readonly TextEditor[]\n\n    /**\n     * An {@link Event} which fires when the {@link window.activeTextEditor active editor}\n     * has changed. *Note* that the event also fires when the active editor changes\n     * to `undefined`.\n     */\n    export const onDidChangeActiveTextEditor: Event<TextEditor | undefined>\n\n    /**\n     * An {@link Event} which fires when the array of {@link window.visibleTextEditors visible editors}\n     * has changed.\n     */\n    export const onDidChangeVisibleTextEditors: Event<readonly TextEditor[]>\n\n    /**\n     * The currently opened terminals or an empty array.\n     * onDidChangeTerminalState doesn't exist since we can't detect window resize on vim.\n     */\n    export const terminals: readonly Terminal[]\n\n    /**\n     * Event fired after terminal created, only fired with Terminal that\n     * created by `window.createTerminal`\n     */\n    export const onDidOpenTerminal: Event<Terminal>\n\n    /**\n     * Event fired on terminal close, only fired with Terminal that created by\n     * `window.createTerminal`\n     */\n    export const onDidCloseTerminal: Event<Terminal>\n\n    /**\n     * Creates a {@link Terminal} with a backing shell process.\n     * The terminal is created by (neo)vim.\n     *\n     * @param options A TerminalOptions object describing the characteristics of the new terminal.\n     * @return A new Terminal.\n     * @throws When running in an environment where a new process cannot be started.\n     */\n    export function createTerminal(opts: TerminalOptions): Promise<Terminal>\n\n    /**\n     * Create float window factory for create float window/popup around current\n     * cursor.\n     * Configuration \"floatFactory.floatConfig\" is used as default float config.\n     * Configuration \"coc.preferences.excludeImageLinksInMarkdownDocument\" is also used.\n     *\n     * Float windows are automatic reused and hidden on specific events including:\n     *  - BufEnter\n     *  - InsertEnter\n     *  - InsertLeave\n     *  - MenuPopupChanged\n     *  - CursorMoved\n     *  - CursorMovedI\n     *\n     * @param conf Configuration of float window.\n     * @return FloatFactory\n     */\n    export function createFloatFactory(conf: FloatWinConfig): FloatFactory\n\n    /**\n     * Reveal message with message type.\n     *\n     * @deprecated Use `window.showErrorMessage`, `window.showWarningMessage` and `window.showInformationMessage` instead.\n     * @param msg Message text to show.\n     * @param messageType Type of message, could be `error` `warning` and `more`, default to `more`\n     */\n    export function showMessage(msg: string, messageType?: MsgTypes): void\n\n    /**\n     * Run command in vim terminal for result\n     *\n     * @param cmd Command to run.\n     * @param cwd Cwd of terminal, default to result of |getcwd()|.\n     */\n    export function runTerminalCommand(cmd: string, cwd?: string, keepfocus?: boolean): Promise<TerminalResult>\n\n    /**\n     * Open terminal window.\n     *\n     * @param cmd Command to run.\n     * @param opts Terminal option.\n     * @returns buffer number of terminal.\n     */\n    export function openTerminal(cmd: string, opts?: OpenTerminalOption): Promise<number>\n\n    /**\n     * Show quickpick for single item, use `window.menuPick` for menu at current current position.\n     * Use `window.showPickerDialog()` for multiple selection.\n     *\n     * @param items Label list.\n     * @param placeholder Prompt text, default to 'choose by number'.\n     * @returns Index of selected item, or -1 when canceled.\n     * @deprecated use `window.showQuickPick()` instead.\n     */\n    export function showQuickpick(items: string[], placeholder?: string): Promise<number>\n\n    /**\n     * Shows a selection list allowing multiple selections.\n     *\n     * @param items An array of strings, or a promise that resolves to an array of strings.\n     * @param options Configures the behavior of the selection list.\n     * @param token A token that can be used to signal cancellation.\n     * @return A promise that resolves to the selected items or `undefined`.\n     */\n    export function showQuickPick(items: readonly string[] | Thenable<readonly string[]>, options: QuickPickOptions & { canPickMany: true }, token?: CancellationToken): Thenable<string[] | undefined>\n\n    /**\n     * Shows a selection list.\n     *\n     * @param items An array of strings, or a promise that resolves to an array of strings.\n     * @param options Configures the behavior of the selection list.\n     * @param token A token that can be used to signal cancellation.\n     * @return A promise that resolves to the selection or `undefined`.\n     */\n    export function showQuickPick(items: readonly string[] | Thenable<readonly string[]>, options?: QuickPickOptions, token?: CancellationToken): Thenable<string | undefined>\n\n    /**\n     * Shows a selection list allowing multiple selections.\n     *\n     * @param items An array of items, or a promise that resolves to an array of items.\n     * @param options Configures the behavior of the selection list.\n     * @param token A token that can be used to signal cancellation.\n     * @return A promise that resolves to the selected items or `undefined`.\n     */\n    export function showQuickPick<T extends QuickPickItem>(items: readonly T[] | Thenable<readonly T[]>, options: QuickPickOptions & { canPickMany: true }, token?: CancellationToken): Thenable<T[] | undefined>\n\n    /**\n     * Shows a selection list.\n     *\n     * @param items An array of items, or a promise that resolves to an array of items.\n     * @param options Configures the behavior of the selection list.\n     * @param token A token that can be used to signal cancellation.\n     * @return A promise that resolves to the selected item or `undefined`.\n     */\n    export function showQuickPick<T extends QuickPickItem>(items: readonly T[] | Thenable<readonly T[]>, options?: QuickPickOptions, token?: CancellationToken): Thenable<T | undefined>\n\n    /**\n     * Show menu picker at current cursor position, |inputlist()| is used as fallback.\n     *\n     * @param items Array of texts or menu items.\n     * @param title Optional title of float/popup window.\n     * @param token A token that can be used to signal cancellation.\n     * @returns Selected index (0 based), -1 when canceled.\n     */\n    export function showMenuPicker(items: string[] | MenuItem[], option?: MenuOption | string, token?: CancellationToken): Promise<number>\n\n    /**\n     * Prompt user for confirm, a float/popup window would be used when possible,\n     * use vim's |confirm()| function as callback.\n     *\n     * @param title The prompt text.\n     * @returns Result of confirm.\n     */\n    export function showPrompt(title: string): Promise<boolean>\n\n    /**\n     * Show dialog window at the center of screen.\n     * Note that the dialog would always be closed after button click.\n     *\n     * @param config Dialog configuration.\n     * @returns Dialog or null when dialog can't work.\n     */\n    export function showDialog(config: DialogConfig): Promise<Dialog | null>\n\n    /**\n     * Request input from user, `input()` is used when `window.env.dialog` not true.\n     *\n     * @param title Title text of prompt window.\n     * @param defaultValue Default value of input, empty text by default.\n     * @param {InputOptions} option for input window, other preferences are read from user configuration.\n     */\n    export function requestInput(title: string, defaultValue?: string, option?: InputOptions): Promise<string>\n\n    /**\n     * Creates and show a {@link InputBox} to let the user enter some text input.\n     *\n     * @return A new {@link InputBox}.\n     */\n    export function createInputBox(title: string, defaultValue?: string, option?: InputPreference): Promise<InputBox>\n\n    /**\n     * Creates and show a {@link QuickPick} to let the user pick an item or items from a\n     * list of items of type T.\n     *\n     * Note that in many cases the more convenient {@link window.showQuickPick}\n     * is easier to use. {@link window.createQuickPick} should be used\n     * when {@link window.showQuickPick} does not offer the required flexibility.\n     *\n     * Note that unlike VSCode, promise is returned for wait other inputs finished.\n     *\n     * @param config @deprecated config of quickpick, use properties of QuickPick instance instead.\n     * @return A new {@link QuickPick}.\n     */\n    export function createQuickPick<T extends QuickPickItem>(config?: QuickPickConfig<T>): Promise<QuickPick<T>>\n\n    /**\n     * Create statusbar item that would be included in `g:coc_status`.\n     *\n     * @param priority Higher priority item would be shown right.\n     * @param option\n     * @return A new status bar item.\n     */\n    export function createStatusBarItem(priority?: number, option?: StatusItemOption): StatusBarItem\n\n    /**\n     * Open local config file\n     */\n    export function openLocalConfig(): Promise<void>\n\n    /**\n     * Create a new output channel\n     *\n     * @param name Unique name of output channel.\n     * @returns A new output channel.\n     */\n    export function createOutputChannel(name: string): OutputChannel\n\n    /**\n     * Create a {@link TreeView} instance, call `show()` method to render.\n     *\n     * @param viewId Id of the view, used as title of TreeView when title doesn't exist.\n     * @param options Options for creating the {@link TreeView}\n     * @returns a {@link TreeView}.\n     */\n    export function createTreeView<T>(viewId: string, options: TreeViewOptions<T>): TreeView<T>\n\n    /**\n     * Reveal buffer of output channel.\n     *\n     * @param name Name of output channel.\n     * @param preserveFocus Preserve window focus when true.\n     */\n    export function showOutputChannel(name: string, preserveFocus: boolean): void\n\n    /**\n     * Echo lines at the bottom of vim.\n     *\n     * @param lines Line list.\n     * @param truncate Truncate the lines to avoid 'press enter to continue' when true\n     */\n    export function echoLines(lines: string[], truncate?: boolean): Promise<void>\n\n    /**\n     * Get current cursor position (line, character both 0 based).\n     *\n     * @returns Cursor position.\n     */\n    export function getCursorPosition(): Promise<Position>\n\n    /**\n     * Move cursor to position (line, character both 0 based).\n     *\n     * @param position LSP position.\n     */\n    export function moveTo(position: Position): Promise<void>\n\n    /**\n     * Get current cursor character offset in document,\n     * length of line break would always be 1.\n     *\n     * @returns Character offset.\n     */\n    export function getOffset(): Promise<number>\n\n    /**\n     * Get screen position of current cursor(relative to editor),\n     * both `row` and `col` are 0 based.\n     *\n     * @returns Cursor screen position.\n     */\n    export function getCursorScreenPosition(): Promise<ScreenPosition>\n\n    /**\n     * Show multiple picker at center of screen.\n     *\n     * @param items A set of items that will be rendered as actions in the message.\n     * @param title Title of picker dialog.\n     * @param token A token that can be used to signal cancellation.\n     * @return A promise that resolves to the selected items or `undefined`.\n     */\n    export function showPickerDialog(items: string[], title: string, token?: CancellationToken): Promise<string[] | undefined>\n\n    /**\n     * Show multiple picker at center of screen.\n     *\n     * @param items A set of items that will be rendered as actions in the message.\n     * @param title Title of picker dialog.\n     * @param token A token that can be used to signal cancellation.\n     * @return A promise that resolves to the selected items or `undefined`.\n     */\n    export function showPickerDialog<T extends QuickPickItem>(items: T[], title: string, token?: CancellationToken): Promise<T[] | undefined>\n\n    /**\n     * Show an information message to users. Optionally provide an array of items which will be presented as\n     * clickable buttons.\n     *\n     * @param message The message to show.\n     * @param items A set of items that will be rendered as actions in the message.\n     * @return Promise that resolves to the selected item or `undefined` when being dismissed.\n     */\n    export function showInformationMessage(message: string, ...items: string[]): Promise<string | undefined>\n    /**\n     * Show an information message to users. Optionally provide an array of items which will be presented as\n     * clickable buttons.\n     *\n     * @param message The message to show.\n     * @param items A set of items that will be rendered as actions in the message.\n     * @return Promise that resolves to the selected item or `undefined` when being dismissed.\n     */\n    export function showInformationMessage<T extends MessageItem>(message: string, ...items: T[]): Promise<T | undefined>\n\n    /**\n     * Show an warning message to users. Optionally provide an array of items which will be presented as\n     * clickable buttons.\n     *\n     * @param message The message to show.\n     * @param items A set of items that will be rendered as actions in the message.\n     * @return Promise that resolves to the selected item or `undefined` when being dismissed.\n     */\n    export function showWarningMessage(message: string, ...items: string[]): Promise<string | undefined>\n    /**\n     * Show an warning message to users. Optionally provide an array of items which will be presented as\n     * clickable buttons.\n     *\n     * @param message The message to show.\n     * @param items A set of items that will be rendered as actions in the message.\n     * @return Promise that resolves to the selected item or `undefined` when being dismissed.\n     */\n    export function showWarningMessage<T extends MessageItem>(message: string, ...items: T[]): Promise<T | undefined>\n\n    /**\n     * Show an error message to users. Optionally provide an array of items which will be presented as\n     * clickable buttons.\n     *\n     * @param message The message to show.\n     * @param items A set of items that will be rendered as actions in the message.\n     * @return Promise that resolves to the selected item or `undefined` when being dismissed.\n     */\n    export function showErrorMessage(message: string, ...items: string[]): Promise<string | undefined>\n    /**\n     * Show an error message to users. Optionally provide an array of items which will be presented as\n     * clickable buttons.\n     *\n     * @param message The message to show.\n     * @param items A set of items that will be rendered as actions in the message.\n     * @return Promise that resolves to the selected item or `undefined` when being dismissed.\n     */\n    export function showErrorMessage<T extends MessageItem>(message: string, ...items: T[]): Promise<T | undefined>\n\n    /**\n     * Show notification window at bottom right of screen.\n     */\n    export function showNotification(config: NotificationConfig): Promise<void>\n\n    /**\n     * Show progress in the editor. Progress is shown while running the given callback\n     * and while the promise it returned isn't resolved nor rejected.\n     *\n     * @param task A callback returning a promise. Progress state can be reported with\n     * the provided [progress](#Progress)-object.\n     *\n     * To report discrete progress, use `increment` to indicate how much work has been completed. Each call with\n     * a `increment` value will be summed up and reflected as overall progress until 100% is reached (a value of\n     * e.g. `10` accounts for `10%` of work done).\n     *\n     * To monitor if the operation has been cancelled by the user, use the provided [`CancellationToken`](#CancellationToken).\n     *\n     * @return The thenable the task-callback returned.\n     */\n    export function withProgress<R>(options: ProgressOptions, task: (progress: Progress<{\n      message?: string\n      increment?: number\n    }>, token: CancellationToken) => Thenable<R>): Promise<R>\n\n    /**\n     * Get selected range for current document\n     */\n    export function getSelectedRange(visualmode: string): Promise<Range | null>\n\n    /**\n     * Visual select range of current document\n     */\n    export function selectRange(range: Range): Promise<void>\n\n    /**\n     * Get diff between new highlight items and current highlights requested from vim\n     *\n     * @param {number} bufnr - Buffer number\n     * @param {string} ns - Highlight namespace\n     * @param {HighlightItem[]} items - Highlight items\n     * @param {[number, number] | undefined} region - 0 based start line and end line (end inclusive)\n     * @param {CancellationToken} token - CancellationToken\n     * @returns {Promise<HighlightDiff>}\n     */\n    export function diffHighlights(bufnr: number, ns: string, items: ExtendedHighlightItem[], region?: [number, number] | undefined, token?: CancellationToken): Promise<HighlightDiff | null>\n\n    /**\n     * Apply highlight diffs, normally used with `window.diffHighlights`\n     *\n     * Timer is used to add highlights when there're too many highlight items to add,\n     * the highlight process won't be finished on that case.\n     *\n     * @param {number} bufnr - Buffer name\n     * @param {string} ns - Namespace\n     * @param {number} priority\n     * @param {HighlightDiff} diff\n     * @param {boolean} notify - Use notification, default false.\n     * @returns {Promise<void>}\n     */\n    export function applyDiffHighlights(bufnr: number, ns: string, priority: number, diff: HighlightDiff, notify?: boolean): Promise<void>\n\n    /**\n     * Get visible ranges of bufnr, when winid specified, only visible range of winid returned.\n     * Return empty array when buffer is hidden or window with winid not exists.\n     *\n     * @param {number} bufnr - Buffer number\n     * @param {number} winid - Window ID.\n     * @returns {Promise<[number, number][]>} List with [topline, botline], both 1 based and inclusive (returned by getwininfo()).\n     */\n    export function getVisibleRanges(bufnr: number, winid?: number): Promise<[number, number][]>\n  }\n  // }}\n\n  // extensions module {{\n  export interface Logger {\n    readonly category: string\n    log(...args: any[]): void\n    trace(message: any, ...args: any[]): void\n    debug(message: any, ...args: any[]): void\n    info(message: any, ...args: any[]): void\n    warn(message: any, ...args: any[]): void\n    error(message: any, ...args: any[]): void\n    fatal(message: any, ...args: any[]): void\n    mark(message: any, ...args: any[]): void\n  }\n\n  /**\n   * A memento represents a storage utility. It can store and retrieve\n   * values.\n   */\n  export interface Memento {\n\n    /**\n     * Return a value.\n     *\n     * @param key A string.\n     * @return The stored value or `undefined`.\n     */\n    get<T>(key: string): T | undefined\n\n    /**\n     * Return a value.\n     *\n     * @param key A string.\n     * @param defaultValue A value that should be returned when there is no\n     * value (`undefined`) with the given key.\n     * @return The stored value or the defaultValue.\n     */\n    get<T>(key: string, defaultValue: T): T\n\n    /**\n     * Store a value. The value must be JSON-stringifyable.\n     *\n     * @param key A string.\n     * @param value A value. MUST not contain cyclic references.\n     */\n    update(key: string, value: any): Promise<void>\n  }\n\n  export type ExtensionState = 'disabled' | 'loaded' | 'activated' | 'unknown'\n\n  export enum ExtensionType {\n    Global,\n    Local,\n    SingleFile,\n    Internal\n  }\n\n  export interface ExtensionJson {\n    name: string\n    main?: string\n    engines: {\n      [key: string]: string\n    }\n    version?: string\n    [key: string]: any\n  }\n\n  export interface ExtensionInfo {\n    id: string\n    version: string\n    description: string\n    root: string\n    exotic: boolean\n    uri?: string\n    state: ExtensionState\n    isLocal: boolean\n    packageJSON: Readonly<ExtensionJson>\n  }\n\n  /**\n   * Represents an extension.\n   *\n   * To get an instance of an `Extension` use [getExtension](#extensions.getExtension).\n   */\n  export interface Extension<T> {\n\n    /**\n     * The canonical extension identifier in the form of: `publisher.name`.\n     */\n    readonly id: string\n\n    /**\n     * The absolute file path of the directory containing this extension.\n     */\n    readonly extensionPath: string\n\n    /**\n     * The uri of the directory containing the extension.\n     */\n    readonly extensionUri: Uri\n\n    /**\n     * `true` if the extension has been activated.\n     */\n    readonly isActive: boolean\n\n    /**\n     * The parsed contents of the extension's package.json.\n     */\n    readonly packageJSON: any\n\n    /**\n     * The public API exported by this extension. It is an invalid action\n     * to access this field before this extension has been activated.\n     */\n    readonly exports: T\n\n    /**\n     * Activates this extension and returns its public API.\n     *\n     * @return A promise that will resolve when this extension has been activated.\n     */\n    activate(): Promise<T>\n  }\n\n  /**\n   * An extension context is a collection of utilities private to an\n   * extension.\n   *\n   * An instance of an `ExtensionContext` is provided as the first\n   * parameter to the `activate`-call of an extension.\n   */\n  export interface ExtensionContext {\n\n    /**\n     * An array to which disposables can be added. When this\n     * extension is deactivated the disposables will be disposed.\n     */\n    subscriptions: Disposable[]\n\n    /**\n     * The absolute file path of the directory containing the extension.\n     */\n    extensionPath: string\n\n    /**\n     * Get the absolute path of a resource contained in the extension.\n     *\n     * @param relativePath A relative path to a resource contained in the extension.\n     * @return The absolute path of the resource.\n     */\n    asAbsolutePath(relativePath: string): string\n\n    /**\n     * The absolute directory path for extension to download persist data.\n     * The directory might not exist.\n     */\n    storagePath: string\n\n    /**\n     * A memento object that stores state in the context\n     * of the currently opened [workspace](#workspace.workspaceFolders).\n     */\n    workspaceState: Memento\n\n    /**\n     * A memento object that stores state independent\n     * of the current opened [workspace](#workspace.workspaceFolders).\n     */\n    globalState: Memento\n\n    logger: Logger\n  }\n\n  export interface PropertyScheme {\n    type: string\n    default: any\n    description: string\n    enum?: string[]\n    items?: any\n    [key: string]: any\n  }\n\n  export namespace extensions {\n    /**\n     * Fired on extension loaded, extension not activated yet.\n     */\n    export const onDidLoadExtension: Event<Extension<any>>\n\n    /**\n     * Fired on extension activated.\n     */\n    export const onDidActiveExtension: Event<Extension<any>>\n\n    /**\n     * Fired with extension id on extension unload.\n     */\n    export const onDidUnloadExtension: Event<string>\n\n    /**\n     * Get all loaded extensions, without disabled extensions, extension may not activated.\n     */\n    export const all: ReadonlyArray<Extension<any>>\n\n    /**\n      * Get an extension by its full identifier in the form of: `publisher.name`.\n      *\n      * @param extensionId An extension identifier.\n      * @return An extension or `undefined`.\n      */\n    export function getExtensionById<T = any>(extensionId: string): Extension<T> | undefined\n\n    /**\n     * Get state of specific extension.\n     */\n    export function getExtensionState(id: string): ExtensionState\n\n    /**\n     * Get state of all extensions, including disabled extensions.\n     */\n    export function getExtensionStates(): Promise<ExtensionInfo[]>\n\n    /**\n     * Check if extension is activated.\n     */\n    export function isActivated(id: string): boolean\n  }\n  // }}\n\n  // listManager module {{\n  export interface LocationWithLine {\n    uri: string\n    /**\n     * Match text of line.\n     */\n    line: string\n    /**\n     * Highlight text in line.\n     */\n    text?: string\n  }\n\n  export interface AnsiHighlight {\n    /**\n     * Byte indexes, 0 based.\n     */\n    span: [number, number]\n    hlGroup: string\n  }\n\n  export interface ListItem {\n    label: string\n    preselect?: boolean\n    filterText?: string\n    /**\n     * A string that should be used when comparing this item\n     * with other items, only used for fuzzy filter.\n     */\n    sortText?: string\n    location?: Location | LocationWithLine | string\n    data?: any\n    ansiHighlights?: AnsiHighlight[]\n    resolved?: boolean\n  }\n\n  export type ListMode = 'normal' | 'insert'\n\n  export type ListMatcher = 'strict' | 'fuzzy' | 'regex'\n\n  export interface ListOptions {\n    position: string\n    reverse: boolean\n    input: string\n    ignorecase: boolean\n    interactive: boolean\n    sort: boolean\n    mode: ListMode\n    matcher: ListMatcher\n    autoPreview: boolean\n    numberSelect: boolean\n    noQuit: boolean\n    first: boolean\n  }\n\n  export interface ListContext {\n    /**\n     * Input on list activated.\n     */\n    input: string\n    /**\n     * Current work directory on activated.\n     */\n    cwd: string\n    /**\n     * Options of list.\n     */\n    options: ListOptions\n    /**\n     * Arguments passed to list.\n     */\n    args: string[]\n    /**\n     * Original window on list invoke.\n     */\n    window: Window\n    /**\n     * Original buffer on list invoke.\n     */\n    buffer: Buffer\n    listWindow: Window | null\n  }\n\n  export interface ListAction {\n    /**\n     * Action name\n     */\n    name: string\n    /**\n     * Should persist list window on invoke.\n     */\n    persist?: boolean\n    /**\n     * Should reload list after invoke.\n     */\n    reload?: boolean\n    /**\n     * Inovke all selected items in parallel.\n     */\n    parallel?: boolean\n    /**\n     * Support handle multiple items at once.\n     */\n    multiple?: boolean\n    /**\n     * Tab positioned list should be persisted (no window switch) on action invoke.\n     */\n    tabPersist?: boolean\n    /**\n     * Item is array of selected items when multiple is true.\n     */\n    execute: (item: ListItem | ListItem[], context: ListContext) => ProviderResult<void>\n  }\n\n  export interface MultipleListAction extends Omit<ListAction, 'execute'> {\n    multiple: true\n    execute: (item: ListItem[], context: ListContext) => ProviderResult<void>\n  }\n\n  export interface ListTask {\n    on(event: 'data', callback: (item: ListItem) => void): void\n    on(event: 'end', callback: () => void): void\n    on(event: 'error', callback: (msg: string | Error) => void): void\n    dispose(): void\n  }\n\n  export interface ListArgument {\n    key?: string\n    hasValue?: boolean\n    name: string\n    description: string\n  }\n\n  export interface IList {\n    /**\n     * Unique name of list.\n     */\n    name: string\n    /**\n     * Default action name.\n     */\n    defaultAction: string\n    /**\n     * Action list.\n     */\n    actions: ListAction[]\n    /**\n     * Load list items.\n     */\n    loadItems(context: ListContext, token: CancellationToken): Promise<ListItem[] | ListTask | null | undefined>\n    /**\n     * Should be true when interactive is supported.\n     */\n    interactive?: boolean\n    /**\n     * Description of list.\n     */\n    description?: string\n    /**\n     * Detail description, shown in help.\n     */\n    detail?: string\n    /**\n     * Options supported by list.\n     */\n    options?: ListArgument[]\n    /**\n     * Resolve list item.\n     */\n    resolveItem?(item: ListItem): Promise<ListItem | null>\n    /**\n     * Highlight buffer by vim's syntax commands.\n     */\n    doHighlight?(): void\n    /**\n     * Called on list unregistered.\n     */\n    dispose?(): void\n  }\n\n  export interface PreviewOptions {\n    bufname?: string\n    lines: string[]\n    filetype?: string\n    lnum?: number\n    range?: Range\n    /**\n     * @deprecated not used\n     */\n    sketch?: boolean\n  }\n\n  export namespace listManager {\n    /**\n     * Registered list names set.\n     */\n    export const names: ReadonlyArray<string>\n    /**\n     * Register list, list session can be created by `CocList [name]` after registered.\n     */\n    export function registerList(list: IList): Disposable\n  }\n  // }}\n\n  // snippetManager module {{\n  export interface SnippetSession {\n    isActive: boolean\n  }\n\n  export interface SnippetEdit {\n    range: Range\n    snippet: string | SnippetString | StringValue\n  }\n\n  export interface UltiSnipsActions {\n    // pre_expand action code.\n    preExpand?: string\n    // post_expand action code.\n    postExpand?: string\n    // post_jump action code.\n    postJump?: string\n  }\n\n  export interface UltiSnippetOption {\n    /**\n     * Regex text for regex snippet.\n     */\n    regex?: string\n    /**\n     * Context code to execute.\n     */\n    context?: string\n    /**\n     * Do not expand tabs.\n     */\n    noExpand?: boolean\n    /**\n     * Trim all whitespaces from right side of snippet lines.\n     */\n    trimTrailingWhitespace?: boolean\n    /**\n     * Remove whitespace immediately before the cursor at the end of a line before jumping to the next tabstop\n     */\n    removeWhiteSpace?: boolean\n    /**\n     * UltiSnips action codes of the snippet.\n     */\n    actions: UltiSnipsActions\n  }\n\n  /**\n   * A snippet string is a template which allows to insert text\n   * and to control the editor cursor when insertion happens.\n   *\n   * A snippet can define tab stops and placeholders with `$1`, `$2`\n   * and `${3:foo}`. `$0` defines the final tab stop, it defaults to\n   * the end of the snippet. Variables are defined with `$name` and\n   * `${name:default value}`. The full snippet syntax is documented\n   * [here](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_creating-your-own-snippets).\n   */\n  export class SnippetString {\n    /**\n     * The snippet string.\n     */\n    value: string\n\n    constructor(\n      /**\n       * The default snippet string.\n       */\n      value?: string\n    )\n\n    /**\n     * Builder-function that appends the given string to\n     * the {@link SnippetString.value `value`} of this snippet string.\n     *\n     * @param string A value to append 'as given'. The string will be escaped.\n     * @return This snippet string.\n     */\n    appendText(string: string): SnippetString\n\n    /**\n     * Builder-function that appends a tabstop (`$1`, `$2` etc) to\n     * the {@link SnippetString.value `value`} of this snippet string.\n     *\n     * @param number The number of this tabstop, defaults to an auto-increment\n     * value starting at 1.\n     * @return This snippet string.\n     */\n    appendTabstop(number?: number): SnippetString\n\n    /**\n     * Builder-function that appends a placeholder (`${1:value}`) to\n     * the {@link SnippetString.value `value`} of this snippet string.\n     *\n     * @param value The value of this placeholder - either a string or a function\n     * with which a nested snippet can be created.\n     * @param number The number of this tabstop, defaults to an auto-increment\n     * value starting at 1.\n     * @return This snippet string.\n     */\n    appendPlaceholder(value: string | ((snippet: SnippetString) => any), number?: number): SnippetString\n\n    /**\n     * Builder-function that appends a choice (`${1|a,b,c|}`) to\n     * the {@link SnippetString.value `value`} of this snippet string.\n     *\n     * @param values The values for choices - the array of strings\n     * @param number The number of this tabstop, defaults to an auto-increment\n     * value starting at 1.\n     * @return This snippet string.\n     */\n    appendChoice(values: string[], number?: number): SnippetString\n\n    /**\n     * Builder-function that appends a variable (`${VAR}`) to\n     * the {@link SnippetString.value `value`} of this snippet string.\n     *\n     * @param name The name of the variable - excluding the `$`.\n     * @param defaultValue The default value which is used when the variable name cannot\n     * be resolved - either a string or a function with which a nested snippet can be created.\n     * @return This snippet string.\n     */\n    appendVariable(name: string, defaultValue: string | ((snippet: SnippetString) => any)): SnippetString\n  }\n  /**\n   * Manage snippet sessions.\n   */\n  export namespace snippetManager {\n    /**\n     * Get snippet session by bufnr, only returns active session.\n     */\n    export function getSession(bufnr: number): SnippetSession | undefined\n    /**\n     * Resolve snippet string to text.\n     */\n    export function resolveSnippet(body: string, ultisnip?: UltiSnippetOption): Promise<string>\n    /**\n     * Insert snippet to specific buffer, ultisnips not supported, and the placeholder is not selected.\n     *\n     * @param bufnr Buffer number for snippet to insert.\n     * @param snippet Textmate snippet or snippet string.\n     * @param range Range to replace.\n     * @param insertTextMode The insert text mode.\n     * @returns Whether the snippet is activated.\n     */\n    export function insertBufferSnippet(bufnr: number, snippet: string | SnippetString, range: Range, insertTextMode?: InsertTextMode): Promise<boolean>\n    /**\n     * Insert snippet to current buffer.\n     *\n     * @param snippet Textmate snippet string.\n     * @param select Not select first placeholder when false, default `true`.\n     * @param range Replace range, insert to current cursor position when undefined.\n     * @param insertTextMode The insert text mode.\n     * @param ultisnip Option of UltiSnips snippet.\n     * @returns Whether the snippet is activated.\n     */\n    export function insertSnippet(snippet: string | SnippetString, select?: boolean, range?: Range, insertTextMode?: InsertTextMode, ultisnip?: UltiSnippetOption): Promise<boolean>\n\n    /**\n     * Insert multiple snippets to a specific buffer, the buffer must be\n     * attached buffer.  The buffer could be hidden, ranges of inserted snippets\n     * should not have overlap, snippets are inserted as nested snippets of a\n     * top snippet.  No ultisnip snippet support, selection is disabled by\n     * default.  When not selected, the first placeholder is selected on\n     * BufEnter event.\n     *\n     * @param {number} bufnr - Buffer number of attached buffer.\n     * @param {SnippetEdit[]} edits - snippet edits with range and snippet.\n     * @param {boolean} [select] - select the first placeholder when bufnr is\n     * current buffer.\n     * @returns {Promise<boolean>} True when snippet is activated.\n     */\n    export function insertBufferSnippets(bufnr: number, edits: SnippetEdit[], select?: boolean): Promise<boolean>\n\n    /**\n     * Jump to next placeholder, only works when snippet session activated.\n     */\n    export function nextPlaceholder(): Promise<void>\n    /**\n     * Jump to previous placeholder, only works when snippet session activated.\n     */\n    export function previousPlaceholder(): Promise<void>\n    /**\n     * Cancel snippet session of current buffer, does nothing when no session activated.\n     */\n    export function cancel(): void\n    /**\n     * Check if snippet activated for bufnr.\n     */\n    export function isActivated(bufnr: number): boolean\n  }\n  // }}\n\n  // diagnosticManager module {{\n  export interface DiagnosticItem {\n    file: string\n    lnum: number\n    col: number\n    source: string\n    code: string | number\n    message: string\n    severity: string\n    level: number\n    location: Location\n  }\n\n  export enum DiagnosticKind {\n    Syntax,\n    Semantic,\n    Suggestion,\n  }\n\n  /**\n   * A diagnostics collection is a container that manages a set of\n   * [diagnostics](#Diagnostic). Diagnostics are always scopes to a\n   * diagnostics collection and a resource.\n   *\n   * To get an instance of a `DiagnosticCollection` use\n   * [createDiagnosticCollection](#languages.createDiagnosticCollection).\n   */\n  export interface DiagnosticCollection {\n\n    /**\n     * The name of this diagnostic collection, for instance `typescript`. Every diagnostic\n     * from this collection will be associated with this name. Also, the task framework uses this\n     * name when defining [problem matchers](https://code.visualstudio.com/docs/editor/tasks#_defining-a-problem-matcher).\n     */\n    readonly name: string\n\n    /**\n     * Assign diagnostics for given resource. Will replace\n     * existing diagnostics for that resource.\n     *\n     * @param uri A resource identifier.\n     * @param diagnostics Array of diagnostics or `undefined`\n     */\n    set(uri: string, diagnostics: Diagnostic[] | null): void\n    /**\n     * Replace all entries in this collection.\n     *\n     * Diagnostics of multiple tuples of the same uri will be merged, e.g\n     * `[[file1, [d1]], [file1, [d2]]]` is equivalent to `[[file1, [d1, d2]]]`.\n     * If a diagnostics item is `undefined` as in `[file1, undefined]`\n     * all previous but not subsequent diagnostics are removed.\n     *\n     * @param entries An array of tuples, like `[[file1, [d1, d2]], [file2, [d3, d4, d5]]]`, or `undefined`.\n     */\n    set(entries: [string, Diagnostic[] | null][] | string, diagnostics?: Diagnostic[]): void\n\n    /**\n     * Remove all diagnostics from this collection that belong\n     * to the provided `uri`. The same as `#set(uri, undefined)`.\n     *\n     * @param uri A resource identifier.\n     */\n    delete(uri: string): void\n\n    /**\n     * Remove all diagnostics from this collection. The same\n     * as calling `#set(undefined)`\n     */\n    clear(): void\n\n    /**\n     * Iterate over each entry in this collection.\n     *\n     * @param callback Function to execute for each entry.\n     * @param thisArg The `this` context used when invoking the handler function.\n     */\n    forEach(callback: (uri: string, diagnostics: Diagnostic[], collection: DiagnosticCollection) => any, thisArg?: any): void\n\n    /**\n     * Get the diagnostics for a given resource. *Note* that you cannot\n     * modify the diagnostics-array returned from this call.\n     *\n     * @param uri A resource identifier.\n     * @returns An immutable array of [diagnostics](#Diagnostic) or `undefined`.\n     */\n    get(uri: string): Diagnostic[] | undefined\n\n    /**\n     * Check if this collection contains diagnostics for a\n     * given resource.\n     *\n     * @param uri A resource identifier.\n     * @returns `true` if this collection has diagnostic for the given resource.\n     */\n    has(uri: string): boolean\n\n    /**\n     * Dispose and free associated resources. Calls\n     * [clear](#DiagnosticCollection.clear).\n     */\n    dispose(): void\n  }\n\n  export interface DiagnosticEventParams {\n    bufnr: number\n    uri: string\n    diagnostics: ReadonlyArray<Diagnostic>\n  }\n\n  export namespace diagnosticManager {\n\n    export const onDidRefresh: Event<DiagnosticEventParams>\n    /**\n     * Create collection by name\n     */\n    export function create(name: string): DiagnosticCollection\n\n    /**\n     * Get readonly diagnostics for uri\n     */\n    export function getDiagnostics(uri: string): { [collection: string]: Diagnostic[] }\n\n    /**\n     * Get readonly diagnostics by document and range.\n     */\n    export function getDiagnosticsInRange(doc: TextDocumentIdentifier, range: Range): ReadonlyArray<Diagnostic>\n    /**\n     * Get all sorted diagnostics\n     */\n    export function getDiagnosticList(): Promise<ReadonlyArray<DiagnosticItem>>\n\n    /**\n     * All diagnostics at current cursor position.\n     */\n    export function getCurrentDiagnostics(): Promise<ReadonlyArray<Diagnostic>>\n\n    /**\n     * Get diagnostic collection.\n     */\n    export function getCollectionByName(name: string): DiagnosticCollection\n  }\n  // }}\n\n  // language client {{\n\n  export type ProgressToken = number | string\n\n  export interface WorkDoneProgressBegin {\n    kind: 'begin'\n    /**\n     * Mandatory title of the progress operation. Used to briefly inform about\n     * the kind of operation being performed.\n     *\n     * Examples: \"Indexing\" or \"Linking dependencies\".\n     */\n    title: string\n    /**\n     * Controls if a cancel button should show to allow the user to cancel the\n     * long running operation. Clients that don't support cancellation are allowed\n     * to ignore the setting.\n     */\n    cancellable?: boolean\n    /**\n     * Optional, more detailed associated progress message. Contains\n     * complementary information to the `title`.\n     *\n     * Examples: \"3/25 files\", \"project/src/module2\", \"node_modules/some_dep\".\n     * If unset, the previous progress message (if any) is still valid.\n     */\n    message?: string\n    /**\n     * Optional progress percentage to display (value 100 is considered 100%).\n     * If not provided infinite progress is assumed and clients are allowed\n     * to ignore the `percentage` value in subsequent in report notifications.\n     *\n     * The value should be steadily rising. Clients are free to ignore values\n     * that are not following this rule.\n     */\n    percentage?: number\n  }\n\n  export interface WorkDoneProgressReport {\n    kind: 'report'\n    /**\n     * Controls enablement state of a cancel button. This property is only valid if a cancel\n     * button got requested in the `WorkDoneProgressStart` payload.\n     *\n     * Clients that don't support cancellation or don't support control the button's\n     * enablement state are allowed to ignore the setting.\n     */\n    cancellable?: boolean\n    /**\n     * Optional, more detailed associated progress message. Contains\n     * complementary information to the `title`.\n     *\n     * Examples: \"3/25 files\", \"project/src/module2\", \"node_modules/some_dep\".\n     * If unset, the previous progress message (if any) is still valid.\n     */\n    message?: string\n    /**\n     * Optional progress percentage to display (value 100 is considered 100%).\n     * If not provided infinite progress is assumed and clients are allowed\n     * to ignore the `percentage` value in subsequent in report notifications.\n     *\n     * The value should be steadily rising. Clients are free to ignore values\n     * that are not following this rule.\n     */\n    percentage?: number\n  }\n\n  export interface WorkDoneProgressEnd {\n    kind: 'end'\n    /**\n     * Optional, a final message indicating to for example indicate the outcome\n     * of the operation.\n     */\n    message?: string\n  }\n\n  /**\n   * The file event type\n   */\n  export namespace FileChangeType {\n    /**\n     * The file got created.\n     */\n    const Created = 1\n    /**\n     * The file got changed.\n     */\n    const Changed = 2\n    /**\n     * The file got deleted.\n     */\n    const Deleted = 3\n  }\n\n  export type FileChangeType = 1 | 2 | 3\n\n  /**\n   * An event describing a file change.\n   */\n  export interface FileEvent {\n    /**\n     * The file's uri.\n     */\n    uri: string\n    /**\n     * The change type.\n     */\n    type: FileChangeType\n  }\n  /**\n   * An action to be performed when the connection is producing errors.\n   */\n  export enum ErrorAction {\n    /**\n     * Continue running the server.\n     */\n    Continue = 1,\n    /**\n     * Shutdown the server.\n     */\n    Shutdown = 2\n  }\n  /**\n   * An action to be performed when the connection to a server got closed.\n   */\n  export enum CloseAction {\n    /**\n     * Don't restart the server. The connection stays closed.\n     */\n    DoNotRestart = 1,\n    /**\n     * Restart the server.\n     */\n    Restart = 2\n  }\n\n  export interface CloseHandlerResult {\n    /**\n    * The action to take.\n    */\n    action: CloseAction\n\n    /**\n    * An optional message to be presented to the user.\n    */\n    message?: string\n\n    /**\n    * If set to true the client assumes that the corresponding\n    * close handler has presented an appropriate message to the\n    * user and the message will only be log to the client's\n    * output channel.\n    */\n    handled?: boolean\n  }\n\n  export interface ErrorHandlerResult {\n    /**\n    * The action to take.\n    */\n    action: ErrorAction\n\n    /**\n    * An optional message to be presented to the user.\n    */\n    message?: string\n\n    /**\n    * If set to true the client assumes that the corresponding\n    * error handler has presented an appropriate message to the\n    * user and the message will only be log to the client's\n    * output channel.\n    */\n    handled?: boolean\n  }\n  /**\n   * A pluggable error handler that is invoked when the connection is either\n   * producing errors or got closed.\n   */\n  export interface ErrorHandler {\n    /**\n     * An error has occurred while writing or reading from the connection.\n     *\n     * @param error - the error received\n     * @param message - the message to be delivered to the server if know.\n     * @param count - a count indicating how often an error is received. Will\n     *  be reset if a message got successfully send or received.\n     */\n    error(error: Error, message: { jsonrpc: string }, count: number): ErrorAction | ErrorHandlerResult | Promise<ErrorHandlerResult>\n    /**\n     * The connection to the server got closed.\n     * Use CloseHandlerResult should be preferred.\n     */\n    closed(): CloseAction | CloseHandlerResult | Promise<CloseHandlerResult>\n  }\n\n  export interface InitializationFailedHandler {\n    (error: Error | any): boolean\n  }\n\n  export interface SynchronizeOptions {\n    /**\n     * @deprecated Use the new pull model (`workspace/configuration` request)\n     */\n    configurationSection?: string | string[]\n    fileEvents?: FileSystemWatcher | FileSystemWatcher[]\n  }\n\n  export enum RevealOutputChannelOn {\n    Debug = 0,\n    Info = 1,\n    Warn = 2,\n    Error = 3,\n    Never = 4\n  }\n  export interface ConfigurationItem {\n    /**\n     * The scope to get the configuration section for.\n     */\n    scopeUri?: string\n    /**\n     * The configuration section asked for.\n     */\n    section?: string\n  }\n\n  export type HandlerResult<R, E> = R | ResponseError<E> | Thenable<R> | Thenable<ResponseError<E>> | Thenable<R | ResponseError<E>>\n\n  export interface RequestHandler<P, R, E> {\n    (params: P, token: CancellationToken): HandlerResult<R, E>\n  }\n\n  export interface RequestHandler0<R, E> {\n    (token: CancellationToken): HandlerResult<R, E>\n  }\n  /**\n   * The parameters of a configuration request.\n   */\n  export interface ConfigurationParams {\n    items: ConfigurationItem[]\n  }\n\n  export interface ConfigurationWorkspaceMiddleware {\n    configuration?: (params: ConfigurationParams, token: CancellationToken, next: RequestHandler<ConfigurationParams, any[], void>) => HandlerResult<any[], void>\n  }\n\n  export interface WorkspaceFolderWorkspaceMiddleware {\n    workspaceFolders?: (token: CancellationToken, next: RequestHandler0<WorkspaceFolder[] | null, void>) => HandlerResult<WorkspaceFolder[] | null, void>\n    didChangeWorkspaceFolders?: NextSignature<WorkspaceFoldersChangeEvent, Promise<void>>\n  }\n\n  export interface ProvideTypeDefinitionSignature {\n    (\n      this: void,\n      document: LinesTextDocument,\n      position: Position,\n      token: CancellationToken\n    ): ProviderResult<Definition | DefinitionLink[]>\n  }\n\n  export interface TypeDefinitionMiddleware {\n    provideTypeDefinition?: (\n      this: void,\n      document: LinesTextDocument,\n      position: Position,\n      token: CancellationToken,\n      next: ProvideTypeDefinitionSignature\n    ) => ProviderResult<Definition | DefinitionLink[]>\n  }\n\n  export interface ProvideImplementationSignature {\n    (this: void, document: LinesTextDocument, position: Position, token: CancellationToken): ProviderResult<Definition | DefinitionLink[]>\n  }\n\n  export interface ImplementationMiddleware {\n    provideImplementation?: (this: void, document: LinesTextDocument, position: Position, token: CancellationToken, next: ProvideImplementationSignature) => ProviderResult<Definition | DefinitionLink[]>\n  }\n  export type ProvideDocumentColorsSignature = (document: LinesTextDocument, token: CancellationToken) => ProviderResult<ColorInformation[]>\n\n  export type ProvideColorPresentationSignature = (\n    color: Color,\n    context: { document: LinesTextDocument; range: Range },\n    token: CancellationToken\n  ) => ProviderResult<ColorPresentation[]>\n\n  export interface ColorProviderMiddleware {\n    provideDocumentColors?: (\n      this: void,\n      document: LinesTextDocument,\n      token: CancellationToken,\n      next: ProvideDocumentColorsSignature\n    ) => ProviderResult<ColorInformation[]>\n    provideColorPresentations?: (\n      this: void,\n      color: Color,\n      context: { document: LinesTextDocument; range: Range },\n      token: CancellationToken,\n      next: ProvideColorPresentationSignature\n    ) => ProviderResult<ColorPresentation[]>\n  }\n\n  export interface ProvideDeclarationSignature {\n    (this: void, document: LinesTextDocument, position: Position, token: CancellationToken): ProviderResult<Declaration | DeclarationLink[]>\n  }\n\n  export interface DeclarationMiddleware {\n    provideDeclaration?: (this: void, document: LinesTextDocument, position: Position, token: CancellationToken, next: ProvideDeclarationSignature) => ProviderResult<Declaration | DeclarationLink[]>\n  }\n\n  export type ProvideFoldingRangeSignature = (\n    this: void,\n    document: LinesTextDocument,\n    context: FoldingContext,\n    token: CancellationToken\n  ) => ProviderResult<FoldingRange[]>\n\n  export interface FoldingRangeProviderMiddleware {\n    provideFoldingRanges?: (\n      this: void,\n      document: LinesTextDocument,\n      context: FoldingContext,\n      token: CancellationToken,\n      next: ProvideFoldingRangeSignature\n    ) => ProviderResult<FoldingRange[]>\n  }\n\n  export interface PrepareCallHierarchySignature {\n    (this: void, document: LinesTextDocument, position: Position, token: CancellationToken): ProviderResult<CallHierarchyItem | CallHierarchyItem[]>\n  }\n\n  export interface CallHierarchyIncomingCallsSignature {\n    (this: void, item: CallHierarchyItem, token: CancellationToken): ProviderResult<CallHierarchyIncomingCall[]>\n  }\n\n  export interface CallHierarchyOutgoingCallsSignature {\n    (this: void, item: CallHierarchyItem, token: CancellationToken): ProviderResult<CallHierarchyOutgoingCall[]>\n  }\n  export interface CallHierarchyMiddleware {\n    prepareCallHierarchy?: (\n      this: void,\n      document: LinesTextDocument,\n      positions: Position,\n      token: CancellationToken,\n      next: PrepareCallHierarchySignature\n    ) => ProviderResult<CallHierarchyItem | CallHierarchyItem[]>\n    provideCallHierarchyIncomingCalls?: (\n      this: void,\n      item: CallHierarchyItem,\n      token: CancellationToken,\n      next: CallHierarchyIncomingCallsSignature\n    ) => ProviderResult<CallHierarchyIncomingCall[]>\n    provideCallHierarchyOutgoingCalls?: (\n      this: void,\n      item: CallHierarchyItem,\n      token: CancellationToken,\n      next: CallHierarchyOutgoingCallsSignature\n    ) => ProviderResult<CallHierarchyOutgoingCall[]>\n  }\n\n  export interface DocumentSemanticsTokensSignature {\n    (this: void, document: LinesTextDocument, token: CancellationToken): ProviderResult<SemanticTokens>\n  }\n\n  export interface DocumentSemanticsTokensEditsSignature {\n    (this: void, document: LinesTextDocument, previousResultId: string, token: CancellationToken): ProviderResult<SemanticTokens | SemanticTokensDelta>\n  }\n\n  export interface DocumentRangeSemanticTokensSignature {\n    (this: void, document: LinesTextDocument, range: Range, token: CancellationToken): ProviderResult<SemanticTokens>\n  }\n\n  export interface SemanticTokensMiddleware {\n    provideDocumentSemanticTokens?: (\n      this: void,\n      document: LinesTextDocument,\n      token: CancellationToken,\n      next: DocumentSemanticsTokensSignature\n    ) => ProviderResult<SemanticTokens>\n    provideDocumentSemanticTokensEdits?: (\n      this: void,\n      document: LinesTextDocument,\n      previousResultId: string,\n      token: CancellationToken,\n      next: DocumentSemanticsTokensEditsSignature\n    ) => ProviderResult<SemanticTokens | SemanticTokensDelta>\n    provideDocumentRangeSemanticTokens?: (\n      this: void,\n      document: LinesTextDocument,\n      range: Range,\n      token: CancellationToken,\n      next: DocumentRangeSemanticTokensSignature\n    ) => ProviderResult<SemanticTokens>\n  }\n\n  export interface FileOperationsMiddleware {\n    didCreateFiles?: NextSignature<FileCreateEvent, Promise<void>>\n    willCreateFiles?: NextSignature<FileWillCreateEvent, Thenable<WorkspaceEdit | null | undefined>>\n    didRenameFiles?: NextSignature<FileRenameEvent, Promise<void>>\n    willRenameFiles?: NextSignature<FileWillRenameEvent, Thenable<WorkspaceEdit | null | undefined>>\n    didDeleteFiles?: NextSignature<FileDeleteEvent, Promise<void>>\n    willDeleteFiles?: NextSignature<FileWillDeleteEvent, Thenable<WorkspaceEdit | null | undefined>>\n  }\n\n  export interface ProvideLinkedEditingRangeSignature {\n    (this: void, document: LinesTextDocument, position: Position, token: CancellationToken): ProviderResult<LinkedEditingRanges>\n  }\n\n  export interface LinkedEditingRangeMiddleware {\n    provideLinkedEditingRange?: (\n      this: void,\n      document: LinesTextDocument,\n      position: Position,\n      token: CancellationToken,\n      next: ProvideLinkedEditingRangeSignature\n    ) => ProviderResult<LinkedEditingRanges>\n  }\n\n  export interface ProvideSelectionRangeSignature {\n    (this: void, document: LinesTextDocument, positions: Position[], token: CancellationToken): ProviderResult<SelectionRange[]>\n  }\n\n  export interface SelectionRangeProviderMiddleware {\n    provideSelectionRanges?: (this: void, document: LinesTextDocument, positions: Position[], token: CancellationToken, next: ProvideSelectionRangeSignature) => ProviderResult<SelectionRange[]>\n  }\n\n  export type ProvideDiagnosticSignature = (this: void, document: TextDocument, previousResultId: string | undefined, token: CancellationToken) => ProviderResult<DocumentDiagnosticReport>\n\n  export type ProvideWorkspaceDiagnosticSignature = (this: void, resultIds: PreviousResultId[], token: CancellationToken, resultReporter: ResultReporter) => ProviderResult<WorkspaceDiagnosticReport>\n\n  export interface DiagnosticProviderMiddleware {\n    provideDiagnostics?: (this: void, document: TextDocument, previousResultId: string | undefined, token: CancellationToken, next: ProvideDiagnosticSignature) => ProviderResult<DocumentDiagnosticReport>\n    provideWorkspaceDiagnostics?: (this: void, resultIds: PreviousResultId[], token: CancellationToken, resultReporter: ResultReporter, next: ProvideWorkspaceDiagnosticSignature) => ProviderResult<WorkspaceDiagnosticReport>\n  }\n\n  export interface HandleWorkDoneProgressSignature {\n    (this: void, token: ProgressToken, params: WorkDoneProgressBegin | WorkDoneProgressReport | WorkDoneProgressEnd): void\n  }\n\n  export interface HandleDiagnosticsSignature {\n    (this: void, uri: string, diagnostics: Diagnostic[]): void\n  }\n\n  export interface ProvideCompletionItemsSignature {\n    (this: void, document: LinesTextDocument, position: Position, context: CompletionContext, token: CancellationToken): ProviderResult<CompletionItem[] | CompletionList | null>\n  }\n\n  export interface ResolveCompletionItemSignature {\n    (this: void, item: CompletionItem, token: CancellationToken): ProviderResult<CompletionItem>\n  }\n\n  export interface ProvideHoverSignature {\n    (this: void, document: LinesTextDocument, position: Position, token: CancellationToken): ProviderResult<Hover>\n  }\n\n  export interface ProvideSignatureHelpSignature {\n    (this: void, document: LinesTextDocument, position: Position, context: SignatureHelpContext, token: CancellationToken): ProviderResult<SignatureHelp>\n  }\n\n  export interface ProvideDefinitionSignature {\n    (this: void, document: LinesTextDocument, position: Position, token: CancellationToken): ProviderResult<Definition | DefinitionLink[]>\n  }\n\n  export interface ProvideReferencesSignature {\n    (this: void, document: LinesTextDocument, position: Position, options: {\n      includeDeclaration: boolean\n    }, token: CancellationToken): ProviderResult<Location[]>\n  }\n\n  export interface ProvideDocumentHighlightsSignature {\n    (this: void, document: LinesTextDocument, position: Position, token: CancellationToken): ProviderResult<DocumentHighlight[]>\n  }\n\n  export interface ProvideDocumentSymbolsSignature {\n    (this: void, document: LinesTextDocument, token: CancellationToken): ProviderResult<SymbolInformation[] | DocumentSymbol[]>\n  }\n\n  export interface ProvideWorkspaceSymbolsSignature {\n    (this: void, query: string, token: CancellationToken): ProviderResult<WorkspaceSymbol[]>\n  }\n\n  export interface ProvideCodeActionsSignature {\n    (this: void, document: LinesTextDocument, range: Range, context: CodeActionContext, token: CancellationToken): ProviderResult<(Command | CodeAction)[]>\n  }\n\n  export interface ResolveCodeActionSignature {\n    (this: void, item: CodeAction, token: CancellationToken): ProviderResult<CodeAction>\n  }\n\n  export interface ProvideCodeLensesSignature {\n    (this: void, document: LinesTextDocument, token: CancellationToken): ProviderResult<CodeLens[]>\n  }\n\n  export interface ResolveCodeLensSignature {\n    (this: void, codeLens: CodeLens, token: CancellationToken): ProviderResult<CodeLens>\n  }\n\n  export interface ProvideDocumentFormattingEditsSignature {\n    (this: void, document: LinesTextDocument, options: FormattingOptions, token: CancellationToken): ProviderResult<TextEdit[]>\n  }\n\n  export interface ProvideDocumentRangeFormattingEditsSignature {\n    (this: void, document: LinesTextDocument, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult<TextEdit[]>\n  }\n\n  export interface ProvideOnTypeFormattingEditsSignature {\n    (this: void, document: LinesTextDocument, position: Position, ch: string, options: FormattingOptions, token: CancellationToken): ProviderResult<TextEdit[]>\n  }\n\n  export interface PrepareRenameSignature {\n    (this: void, document: LinesTextDocument, position: Position, token: CancellationToken): ProviderResult<Range | {\n      range: Range\n      placeholder: string\n    }>\n  }\n\n  export interface ProvideRenameEditsSignature {\n    (this: void, document: LinesTextDocument, position: Position, newName: string, token: CancellationToken): ProviderResult<WorkspaceEdit>\n  }\n\n  export interface ProvideDocumentLinksSignature {\n    (this: void, document: LinesTextDocument, token: CancellationToken): ProviderResult<DocumentLink[]>\n  }\n\n  export interface ResolveDocumentLinkSignature {\n    (this: void, link: DocumentLink, token: CancellationToken): ProviderResult<DocumentLink>\n  }\n\n  export interface ExecuteCommandSignature {\n    (this: void, command: string, args: any[]): ProviderResult<any>\n  }\n\n  export interface NextSignature<P, R> {\n    (this: void, data: P, next: (data: P) => R): R\n  }\n\n  export interface DidChangeConfigurationSignature {\n    (this: void, sections: string[] | undefined): void\n  }\n\n  export interface DidChangeWatchedFileSignature {\n    (this: void, event: FileEvent): void\n  }\n\n  export interface ProvideInlineCompletionItemsSignature {\n    (this: void, document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken): ProviderResult<InlineCompletionItem[] | InlineCompletionList>\n  }\n\n  export interface _WorkspaceMiddleware {\n    didChangeConfiguration?: (this: void, sections: string[] | undefined, next: DidChangeConfigurationSignature) => Promise<void>\n    didChangeWatchedFile?: (this: void, event: FileEvent, next: DidChangeWatchedFileSignature) => void\n    handleApplyEdit?: (this: void, params: ApplyWorkspaceEditParams, next: RequestHandler<ApplyWorkspaceEditParams, ApplyWorkspaceEditResult, void>) => HandlerResult<ApplyWorkspaceEditResult, void>\n  }\n\n  export type WorkspaceMiddleware = _WorkspaceMiddleware & ConfigurationWorkspaceMiddleware & WorkspaceFolderWorkspaceMiddleware & FileOperationsMiddleware\n\n  /**\n   * Params to show a document.\n   *\n   * @since 3.16.0\n   */\n  export interface ShowDocumentParams {\n    /**\n       * The document uri to show.\n    */\n    uri: string\n    /**\n       * Indicates to show the resource in an external program.\n       * To show for example `https://code.visualstudio.com/`\n       * in the default WEB browser set `external` to `true`.\n    */\n    external?: boolean\n    /**\n       * An optional property to indicate whether the editor\n       * showing the document should take focus or not.\n       * Clients might ignore this property if an external\n       * program in started.\n    */\n    takeFocus?: boolean\n    /**\n       * An optional selection range if the document is a text\n       * document. Clients might ignore the property if an\n       * external program is started or the file is not a text\n       * file.\n    */\n    selection?: Range\n  }\n  /**\n   * The result of an show document request.\n   *\n   * @since 3.16.0\n   */\n  export interface ShowDocumentResult {\n    /**\n     * A boolean indicating if the show was successful.\n    */\n    success: boolean\n  }\n\n  /**\n   * General parameters to register for a notification or to register a provider.\n   */\n  export interface Registration {\n    /**\n     * The id used to register the request. The id can be used to deregister\n     * the request again.\n     */\n    id: string\n    /**\n     * The method / capability to register for.\n     */\n    method: string\n    /**\n     * Options necessary for the registration.\n     */\n    registerOptions?: LSPAny\n  }\n  export interface RegistrationParams {\n    registrations: Registration[]\n  }\n\n  /**\n   * General parameters to unregister a request or notification.\n   */\n  export interface Unregistration {\n    /**\n     * The id used to unregister the request or notification. Usually an id\n     * provided during the register request.\n     */\n    id: string\n    /**\n     * The method to unregister for.\n     */\n    method: string\n  }\n  export interface UnregistrationParams {\n    unregisterations: Unregistration[]\n  }\n\n  export interface _WindowMiddleware {\n    showDocument?: (\n      params: ShowDocumentParams,\n      token: CancellationToken,\n      next: RequestHandler<ShowDocumentParams, ShowDocumentResult, void>\n    ) => Promise<ShowDocumentResult>\n  }\n  export type WindowMiddleware = _WindowMiddleware\n\n  /**\n   * The Middleware lets extensions intercept the request and notifications send and received\n   * from the server\n   */\n  interface _Middleware {\n    didOpen?: NextSignature<LinesTextDocument, Promise<void>>\n    didChange?: NextSignature<DidChangeTextDocumentParams, Promise<void>>\n    willSave?: NextSignature<TextDocumentWillSaveEvent, Promise<void>>\n    willSaveWaitUntil?: NextSignature<TextDocumentWillSaveEvent, Thenable<TextEdit[]>>\n    didSave?: NextSignature<LinesTextDocument, Promise<void>>\n    didClose?: NextSignature<LinesTextDocument, Promise<void>>\n    handleDiagnostics?: (this: void, uri: string, diagnostics: Diagnostic[], next: HandleDiagnosticsSignature) => void\n    provideCompletionItem?: (this: void, document: LinesTextDocument, position: Position, context: CompletionContext, token: CancellationToken, next: ProvideCompletionItemsSignature) => ProviderResult<CompletionItem[] | CompletionList | null>\n    resolveCompletionItem?: (this: void, item: CompletionItem, token: CancellationToken, next: ResolveCompletionItemSignature) => ProviderResult<CompletionItem>\n    provideHover?: (this: void, document: LinesTextDocument, position: Position, token: CancellationToken, next: ProvideHoverSignature) => ProviderResult<Hover>\n    provideSignatureHelp?: (this: void, document: LinesTextDocument, position: Position, context: SignatureHelpContext, token: CancellationToken, next: ProvideSignatureHelpSignature) => ProviderResult<SignatureHelp>\n    provideDefinition?: (this: void, document: LinesTextDocument, position: Position, token: CancellationToken, next: ProvideDefinitionSignature) => ProviderResult<Definition | DefinitionLink[]>\n    provideReferences?: (this: void, document: LinesTextDocument, position: Position, options: {\n      includeDeclaration: boolean\n    }, token: CancellationToken, next: ProvideReferencesSignature) => ProviderResult<Location[]>\n    provideDocumentHighlights?: (this: void, document: LinesTextDocument, position: Position, token: CancellationToken, next: ProvideDocumentHighlightsSignature) => ProviderResult<DocumentHighlight[]>\n    provideDocumentSymbols?: (this: void, document: LinesTextDocument, token: CancellationToken, next: ProvideDocumentSymbolsSignature) => ProviderResult<SymbolInformation[] | DocumentSymbol[]>\n    provideWorkspaceSymbols?: (this: void, query: string, token: CancellationToken, next: ProvideWorkspaceSymbolsSignature) => ProviderResult<WorkspaceSymbol[]>\n    provideCodeActions?: (this: void, document: LinesTextDocument, range: Range, context: CodeActionContext, token: CancellationToken, next: ProvideCodeActionsSignature) => ProviderResult<(Command | CodeAction)[]>\n    handleWorkDoneProgress?: (this: void, token: ProgressToken, params: WorkDoneProgressBegin | WorkDoneProgressReport | WorkDoneProgressEnd, next: HandleWorkDoneProgressSignature) => void\n    handleRegisterCapability?: (this: void, params: RegistrationParams, next: RequestHandler<RegistrationParams, void, void>) => Promise<void>\n    handleUnregisterCapability?: (this: void, params: UnregistrationParams, next: RequestHandler<UnregistrationParams, void, void>) => Promise<void>\n    resolveCodeAction?: (this: void, item: CodeAction, token: CancellationToken, next: ResolveCodeActionSignature) => ProviderResult<CodeAction>\n    provideCodeLenses?: (this: void, document: LinesTextDocument, token: CancellationToken, next: ProvideCodeLensesSignature) => ProviderResult<CodeLens[]>\n    resolveCodeLens?: (this: void, codeLens: CodeLens, token: CancellationToken, next: ResolveCodeLensSignature) => ProviderResult<CodeLens>\n    provideDocumentFormattingEdits?: (this: void, document: LinesTextDocument, options: FormattingOptions, token: CancellationToken, next: ProvideDocumentFormattingEditsSignature) => ProviderResult<TextEdit[]>\n    provideDocumentRangeFormattingEdits?: (this: void, document: LinesTextDocument, range: Range, options: FormattingOptions, token: CancellationToken, next: ProvideDocumentRangeFormattingEditsSignature) => ProviderResult<TextEdit[]>\n    provideOnTypeFormattingEdits?: (this: void, document: LinesTextDocument, position: Position, ch: string, options: FormattingOptions, token: CancellationToken, next: ProvideOnTypeFormattingEditsSignature) => ProviderResult<TextEdit[]>\n    prepareRename?: (this: void, document: LinesTextDocument, position: Position, token: CancellationToken, next: PrepareRenameSignature) => ProviderResult<Range | {\n      range: Range\n      placeholder: string\n    }>\n    provideRenameEdits?: (this: void, document: LinesTextDocument, position: Position, newName: string, token: CancellationToken, next: ProvideRenameEditsSignature) => ProviderResult<WorkspaceEdit>\n    provideDocumentLinks?: (this: void, document: LinesTextDocument, token: CancellationToken, next: ProvideDocumentLinksSignature) => ProviderResult<DocumentLink[]>\n    resolveDocumentLink?: (this: void, link: DocumentLink, token: CancellationToken, next: ResolveDocumentLinkSignature) => ProviderResult<DocumentLink>\n    executeCommand?: (this: void, command: string, args: any[], next: ExecuteCommandSignature) => ProviderResult<any>\n    workspace?: WorkspaceMiddleware\n    window?: WindowMiddleware\n  }\n\n  // A general middleware is applied to both requests and notifications\n  interface GeneralMiddleware {\n    sendRequest?<P, R>(\n      this: void,\n      type: string | MessageSignature,\n      param: P | undefined,\n      token: CancellationToken | undefined,\n      next: (type: string | MessageSignature, param?: P, token?: CancellationToken) => Promise<R>,\n    ): Promise<R>\n\n    sendNotification?<R>(\n      this: void,\n      type: string | MessageSignature,\n      next: (type: string | MessageSignature, params?: R) => Promise<void>,\n      params: R\n    ): Promise<void>\n  }\n\n  export interface ProvideTextDocumentContentSignature {\n    (this: void, uri: Uri, token: CancellationToken): ProviderResult<string>\n  }\n\n  export interface TextDocumentContentMiddleware {\n    provideTextDocumentContent?: (this: void, uri: Uri, token: CancellationToken, next: ProvideTextDocumentContentSignature) => ProviderResult<string>\n  }\n\n  export interface InlineCompletionMiddleware {\n    provideInlineCompletionItems?: (this: void, document: TextDocument, position: Position, context: InlineCompletionContext, token: CancellationToken, next: ProvideInlineCompletionItemsSignature) => ProviderResult<InlineCompletionItem[] | InlineCompletionList>\n  }\n\n  export type Middleware = _Middleware & TypeDefinitionMiddleware & ImplementationMiddleware & ColorProviderMiddleware & DeclarationMiddleware & FoldingRangeProviderMiddleware & CallHierarchyMiddleware & SemanticTokensMiddleware & LinkedEditingRangeMiddleware & SelectionRangeProviderMiddleware & DiagnosticProviderMiddleware & GeneralMiddleware & TextDocumentContentMiddleware & InlineCompletionMiddleware\n\n  export interface ConnectionOptions {\n    maxRestartCount?: number\n  }\n\n  export enum DiagnosticPullMode {\n    onType = 'onType',\n    onSave = 'onSave',\n    onFocus = 'onFocus'\n  }\n\n  export interface DiagnosticPullOptions {\n    /**\n     * Whether to pull for diagnostics on document change.\n     * Default to \"pullDiagnostic.onChange\" configuration.\n     */\n    onChange?: boolean\n\n    /**\n    * Whether to pull for diagnostics on editor focus.\n    */\n    onFocus?: boolean\n\n    /**\n     * Whether to pull for diagnostics on document save.\n     * Default to \"pullDiagnostic.onSave\" configuration.\n     */\n    onSave?: boolean\n\n    /**\n     * Whether to pull for workspace diagnostics when possible.\n     * Default to \"pullDiagnostic.workspace\" configuration.\n     */\n    workspace?: boolean\n    /**\n     * Minimatch patterns to match full filepath that should be ignored for pullDiagnostic.\n     * Default to \"pullDiagnostic.ignored\" configuration.\n     */\n    ignored?: string[]\n\n    /**\n     * An optional filter method that is consulted when triggering a\n     * diagnostic pull during document change or document save.\n     *\n     * The document gets filtered if the method returns `true`.\n     *\n     * @param document the document that changes or got save\n     * @param mode the mode\n     */\n    filter?(document: { uri: string, languageId: string }, mode: 'onType' | 'onSave'): boolean\n\n    /**\n     * An optional match method that is consulted when pulling for diagnostics\n     * when only a URI is known (e.g. for not instantiated tabs)\n     *\n     * The method should return `true` if the document selector matches the\n     * given resource. See also the `vscode.languages.match` function.\n     *\n     * @param documentSelector The document selector.\n     * @param resource The resource.\n     * @returns whether the resource is matched by the given document selector.\n     */\n    match?(documentSelector: DocumentSelector, resource: Uri): boolean\n  }\n\n  export interface URIConverter {\n    (value: Uri): string\n  }\n\n  export interface LanguageClientOptions {\n    ignoredRootPaths?: string[]\n    disableSnippetCompletion?: boolean\n    disableDynamicRegister?: boolean\n    disabledFeatures?: string[]\n    formatterPriority?: number\n    documentSelector?: DocumentSelector | string[]\n    synchronize?: SynchronizeOptions\n    diagnosticCollectionName?: string\n    outputChannelName?: string\n    outputChannel?: OutputChannel\n    traceOutputChannel?: OutputChannel\n    revealOutputChannelOn?: RevealOutputChannelOn\n    /**\n     * The encoding use to read stdout and stderr. Defaults\n     * to 'utf8' if omitted.\n     */\n    stdioEncoding?: string\n    // converter used to decode uri.\n    uriConverter?: {\n      code2Protocol: URIConverter\n    }\n    initializationOptions?: any | (() => any)\n    initializationFailedHandler?: InitializationFailedHandler\n    progressOnInitialization?: boolean\n    errorHandler?: ErrorHandler\n    middleware?: Middleware\n    workspaceFolder?: WorkspaceFolder\n    connectionOptions?: ConnectionOptions\n    diagnosticPullOptions?: DiagnosticPullOptions\n    textSynchronization?: {\n      /**\n      * Delays sending the open notification until one of the following\n      * conditions becomes `true`:\n      * - document is visible in the editor.\n      * - any of the other notifications or requests is sent to the server, except\n      * a closed notification for the pending document.\n      */\n      delayOpenNotifications?: boolean\n    }\n    markdown?: {\n      isTrusted?: boolean\n      supportHtml?: boolean\n    }\n  }\n  export enum State {\n    Stopped = 1,\n    Running = 2,\n    Starting = 3,\n    StartFailed = 4,\n  }\n  export interface StateChangeEvent {\n    oldState: State\n    newState: State\n  }\n  export interface RegistrationData<T> {\n    id: string\n    registerOptions: T\n  }\n\n  export type FeatureState = {\n    kind: 'document'\n\n    /**\n     * The features's id. This is usually the method names used during\n     * registration.\n     */\n    id: string\n\n    /**\n     * Has active registrations.\n     */\n    registrations: boolean\n\n    /**\n     * A registration matches an open document.\n     */\n    matches: boolean\n\n  } | {\n    kind: 'workspace'\n\n    /**\n     * The features's id. This is usually the method names used during\n     * registration.\n     */\n    id: string\n\n    /**\n     * Has active registrations.\n     */\n    registrations: boolean\n  } | {\n    kind: 'window'\n\n    /**\n     * The features's id. This is usually the method names used during\n     * registration.\n     */\n    id: string\n\n    /**\n     * Has active registrations.\n     */\n    registrations: boolean\n  } | {\n    kind: 'static'\n  }\n  /**\n   * A static feature. A static feature can't be dynamically activate via the\n   * server. It is wired during the initialize sequence.\n   */\n  export interface StaticFeature {\n    /**\n     * Called to fill the initialize params.\n     *\n     * @params the initialize params.\n     */\n    fillInitializeParams?: (params: object) => void\n    /**\n     * Called to fill in the client capabilities this feature implements.\n     *\n     * @param capabilities The client capabilities to fill.\n     */\n    fillClientCapabilities(capabilities: object): void\n    /**\n     * A preflight where the server capabilities are shown to all features\n     * before a feature is actually initialized. This allows feature to\n     * capture some state if they are a pre-requisite for other features.\n     *\n     * @param capabilities the server capabilities\n     * @param documentSelector the document selector pass to the client's constructor.\n     * May be `undefined` if the client was created without a selector.\n     */\n    preInitialize?: (capabilities: object, documentSelector: DocumentSelector | undefined) => void\n    /**\n     * Initialize the feature. This method is called on a feature instance\n     * when the client has successfully received the initialize request from\n     * the server and before the client sends the initialized notification\n     * to the server.\n     *\n     * @param capabilities the server capabilities\n     * @param documentSelector the document selector pass to the client's constructor.\n     *  May be `undefined` if the client was created without a selector.\n     */\n    initialize(capabilities: object, documentSelector: DocumentSelector | undefined): void\n    /**\n     * Returns the state the feature is in.\n     */\n    getState?(): FeatureState\n    /**\n     * Called when the client is stopped to dispose this feature. Usually a feature\n     * unregisters listeners registered hooked up with the VS Code extension host.\n     */\n    dispose(): void\n  }\n\n  /**\n   * A dynamic feature can be activated via the server.\n   */\n  export interface DynamicFeature<RO> {\n    /**\n     * Called to fill the initialize params.\n     *\n     * @params the initialize params.\n     */\n    fillInitializeParams?: (params: InitializeParams) => void\n    /**\n     * Called to fill in the client capabilities this feature implements.\n     *\n     * @param capabilities The client capabilities to fill.\n     */\n    fillClientCapabilities(capabilities: any): void\n    /**\n     * Initialize the feature. This method is called on a feature instance\n     * when the client has successfully received the initialize request from\n     * the server and before the client sends the initialized notification\n     * to the server.\n     *\n     * @param capabilities the server capabilities.\n     * @param documentSelector the document selector pass to the client's constructor.\n     *  May be `undefined` if the client was created without a selector.\n     */\n    initialize(capabilities: object, documentSelector: DocumentSelector | undefined): void\n\n    /**\n     * A preflight where the server capabilities are shown to all features\n     * before a feature is actually initialized. This allows feature to\n     * capture some state if they are a pre-requisite for other features.\n     *\n     * @param capabilities the server capabilities\n     * @param documentSelector the document selector pass to the client's constructor.\n     * May be `undefined` if the client was created without a selector.\n     */\n    preInitialize?: (capabilities: object, documentSelector: DocumentSelector | undefined) => void\n    /**\n      * The signature (e.g. method) for which this features support dynamic activation / registration.\n      */\n    registrationType: RegistrationType<RO>\n    /**\n     * Is called when the server send a register request for the given message.\n     *\n     * @param data additional registration data as defined in the protocol.\n     */\n    register(data: RegistrationData<RO>): void\n    /**\n     * Is called when the server wants to unregister a feature.\n     *\n     * @param id the id used when registering the feature.\n     */\n    unregister(id: string): void\n    /**\n     * Returns the state the feature is in.\n     */\n    getState?(): FeatureState\n    /**\n     * Called when the client is stopped to dispose this feature. Usually a feature\n     * unregisters listeners registered hooked up with the VS Code extension host.\n     */\n    dispose(): void\n  }\n\n  class ParameterStructures {\n    private readonly kind\n    /**\n     * The parameter structure is automatically inferred on the number of parameters\n     * and the parameter type in case of a single param.\n    */\n    static readonly auto: ParameterStructures\n    /**\n     * Forces `byPosition` parameter structure. This is useful if you have a single\n     * parameter which has a literal type.\n    */\n    static readonly byPosition: ParameterStructures\n    /**\n     * Forces `byName` parameter structure. This is only useful when having a single\n     * parameter. The library will report errors if used with a different number of\n     * parameters.\n    */\n    static readonly byName: ParameterStructures\n    private constructor()\n    static is(value: any): value is ParameterStructures\n    toString(): string\n  }\n  /**\n   * An interface to type messages.\n   */\n  export interface MessageSignature {\n    readonly method: string\n    readonly numberOfParams: number\n    readonly parameterStructures: ParameterStructures\n  }\n\n  /**\n   *\n   * An abstract implementation of a MessageType.\n   */\n  abstract class AbstractMessageSignature implements MessageSignature {\n    readonly method: string\n    readonly numberOfParams: number\n    constructor(method: string, numberOfParams: number)\n    get parameterStructures(): ParameterStructures\n  }\n\n  /**\n   * Classes to type request response pairs\n   */\n  export class RequestType0<R, E> extends AbstractMessageSignature {\n    /**\n     * Clients must not use this property. It is here to ensure correct typing.\n     */\n    readonly _: [R, E, _EM] | undefined\n    constructor(method: string)\n  }\n\n  export class RequestType<P, R, E> extends AbstractMessageSignature {\n    private _parameterStructures\n    /**\n     * Clients must not use this property. It is here to ensure correct typing.\n     */\n    readonly _: [P, R, E, _EM] | undefined\n    constructor(method: string, _parameterStructures?: ParameterStructures)\n    get parameterStructures(): ParameterStructures\n  }\n\n  export class NotificationType<P> extends AbstractMessageSignature {\n    /**\n     * Clients must not use this property. It is here to ensure correct typing.\n     */\n    readonly _: [P, _EM] | undefined\n    constructor(method: string)\n  }\n\n  export class NotificationType0 extends AbstractMessageSignature {\n    /**\n     * Clients must not use this property. It is here to ensure correct typing.\n     */\n    readonly _: [_EM] | undefined\n    constructor(method: string)\n  }\n\n  export interface InitializeParams {\n    /**\n     * The process Id of the parent process that started\n     * the server.\n     */\n    processId: number | null\n    /**\n     * Information about the client\n     *\n     * @since 3.15.0\n     */\n    clientInfo?: {\n      /**\n       * The name of the client as defined by the client.\n       */\n      name: string\n      /**\n       * The client's version as defined by the client.\n       */\n      version?: string\n    }\n    /**\n     * The rootPath of the workspace. Is null\n     * if no folder is open.\n     *\n     * @deprecated in favour of rootUri.\n     */\n    rootPath?: string | null\n    /**\n     * The rootUri of the workspace. Is null if no\n     * folder is open. If both `rootPath` and `rootUri` are set\n     * `rootUri` wins.\n     *\n     * @deprecated in favour of workspaceFolders.\n     */\n    rootUri: string | null\n    /**\n     * The capabilities provided by the client (editor or tool)\n     */\n    capabilities: any\n    /**\n     * User provided initialization options.\n     */\n    initializationOptions?: any\n    /**\n     * The initial trace setting. If omitted trace is disabled ('off').\n     */\n    trace?: 'off' | 'messages' | 'verbose'\n    /**\n     * An optional token that a server can use to report work done progress.\n     */\n    workDoneToken?: ProgressToken\n  }\n\n  class RegistrationType<RO> {\n    /**\n     * Clients must not use this property. It is here to ensure correct typing.\n     */\n    readonly ____: [RO, _EM] | undefined\n    readonly method: string\n    constructor(method: string)\n  }\n  /**\n   * The result returned from an initialize request.\n   */\n  export interface InitializeResult {\n    /**\n     * The capabilities the language server provides.\n     */\n    capabilities: any\n    /**\n     * Information about the server.\n     *\n     * @since 3.15.0\n     */\n    serverInfo?: {\n      /**\n       * The name of the server as defined by the server.\n       */\n      name: string\n      /**\n       * The servers's version as defined by the server.\n       */\n      version?: string\n    }\n    /**\n     * Custom initialization results.\n     */\n    [custom: string]: any\n  }\n\n  export interface NotificationFeature<T extends Function> {\n    /**\n     * Triggers the corresponding RPC method.\n     */\n    getProvider(document: { uri: string, languageId: string }): {\n      send: T\n    }\n  }\n\n  export interface ExecutableOptions {\n    cwd?: string\n    env?: any\n    detached?: boolean\n    shell?: boolean\n  }\n\n  export interface Executable {\n    command: string\n    args?: string[]\n    options?: ExecutableOptions\n  }\n\n  export interface ForkOptions {\n    cwd?: string\n    env?: any\n    execPath?: string\n    encoding?: string\n    execArgv?: string[]\n  }\n\n  export interface StreamInfo {\n    writer: NodeJS.WritableStream\n    reader: NodeJS.ReadableStream\n    detached?: boolean\n  }\n\n  export enum TransportKind {\n    stdio = 0,\n    ipc = 1,\n    pipe = 2,\n    socket = 3\n  }\n\n  export interface SocketTransport {\n    kind: TransportKind.socket\n    port: number\n  }\n\n  export interface NodeModule {\n    module: string\n    transport?: TransportKind | SocketTransport\n    args?: string[]\n    runtime?: string\n    options?: ForkOptions\n  }\n\n  export interface ChildProcessInfo {\n    process: cp.ChildProcess\n    detached: boolean\n  }\n\n  export interface PartialMessageInfo {\n    readonly messageToken: number\n    readonly waitingTime: number\n  }\n\n  export interface MessageReader {\n    readonly onError: Event<Error>\n    readonly onClose: Event<void>\n    readonly onPartialMessage: Event<PartialMessageInfo>\n    listen(callback: (data: { jsonrpc: string }) => void): void\n    dispose(): void\n  }\n\n  export interface MessageWriter {\n    readonly onError: Event<[Error, { jsonrpc: string } | undefined, number | undefined]>\n    readonly onClose: Event<void>\n    write(msg: { jsonrpc: string }): void\n    dispose(): void\n  }\n\n  export class NullLogger {\n    constructor()\n    error(message: string): void\n    warn(message: string): void\n    info(message: string): void\n    log(message: string): void\n  }\n\n  export interface MessageTransports {\n    reader: MessageReader\n    writer: MessageWriter\n    detached?: boolean\n  }\n\n  export namespace MessageTransports {\n    /**\n    * Checks whether the given value conforms to the [MessageTransports](#MessageTransports) interface.\n    */\n    function is(value: any): value is MessageTransports\n  }\n\n  export type ServerOptions = Executable | NodeModule | {\n    run: Executable\n    debug: Executable\n  } | {\n    run: NodeModule\n    debug: NodeModule\n  } | (() => Promise<cp.ChildProcess | StreamInfo | MessageTransports | ChildProcessInfo>)\n\n  export interface _EM {\n    _$endMarker$_: number\n  }\n\n  export class ProgressType<PR> {\n    /**\n     * Clients must not use these properties. They are here to ensure correct typing.\n     * in TypeScript\n     */\n    readonly __?: [PR, _EM]\n    readonly _pr?: PR\n    constructor()\n  }\n\n  export enum Trace {\n    Off = 0,\n    Messages = 1,\n    Verbose = 2\n  }\n\n  export interface RequestProtocolSignature<P, R, PR, E, RO> {\n    method: string\n    numberOfParams?: number\n    parameterStructures?: unknown\n  }\n\n  export interface RequestProtocolSignature0<R, PR, E, RO> {\n    method: string\n  }\n\n  export interface RequestSignature<P, R, E> {\n    method: string\n    numberOfParams?: number\n    parameterStructures?: unknown\n  }\n\n  export interface RequestSignature0<R, E> {\n    method: string\n  }\n\n  export interface NotificationProtocolSignature<P, RO> {\n    method: string\n    numberOfParams?: number\n    parameterStructures?: unknown\n  }\n\n  export interface NotificationProtocolSignature0<RO> {\n    readonly ____: [RO, _EM] | undefined\n    method: string\n  }\n\n  export interface NotificationSignature<P> {\n    readonly _: [P, _EM] | undefined\n    method: string\n    numberOfParams?: number\n    parameterStructures?: unknown\n  }\n\n  export interface NotificationSignature0 {\n    method: string\n  }\n\n  export class ProtocolRequestType0<R, PR, E, RO> extends RequestType0<R, E> implements ProgressType<PR>, RegistrationType<RO> {\n    /**\n     * Clients must not use these properties. They are here to ensure correct typing.\n     * in TypeScript\n     */\n    readonly ___: [PR, RO, _EM] | undefined\n    readonly ____: [RO, _EM] | undefined\n    readonly _pr: PR | undefined\n    constructor(method: string)\n  }\n\n  export class ProtocolRequestType<P, R, PR, E, RO> extends RequestType<P, R, E> implements ProgressType<PR>, RegistrationType<RO> {\n    /**\n     * Clients must not use this property. It is here to ensure correct typing.\n     */\n    readonly ___: [PR, RO, _EM] | undefined\n    readonly ____: [RO, _EM] | undefined\n    readonly _pr: PR | undefined\n    constructor(method: string)\n  }\n\n  export class ProtocolNotificationType0<RO> extends NotificationType0 implements RegistrationType<RO> {\n    /**\n     * Clients must not use this property. It is here to ensure correct typing.\n     */\n    readonly ___: [RO, _EM] | undefined\n    readonly ____: [RO, _EM] | undefined\n    constructor(method: string)\n  }\n  export class ProtocolNotificationType<P, RO> extends NotificationType<P> implements RegistrationType<RO> {\n    /**\n     * Clients must not use this property. It is here to ensure correct typing.\n     */\n    readonly ___: [RO, _EM] | undefined\n    readonly ____: [RO, _EM] | undefined\n    constructor(method: string)\n  }\n\n  export interface NotificationHandler0 {\n    (): void\n  }\n\n  export interface NotificationHandler<P> {\n    (params: P): void\n  }\n\n  /**\n   * Including the registration options from languageserver protocol package could be too complicated\n   * and the options can be changed from time to time.\n   */\n  export interface GeneralRegistrationOptions {\n    [key: string]: any\n  }\n\n  export interface DidChangeWatchedFilesRegistrationOptions {\n    /**\n     * The watchers to register.\n     */\n    watchers: FileSystemWatcher[]\n  }\n\n  export interface DidChangeConfigurationRegistrationOptions {\n    section?: string | string[]\n  }\n\n  interface TextDocumentRegistrationOptions {\n    /**\n    * A document selector to identify the scope of the registration. If set to null\n    * the document selector provided on the client side will be used.\n    */\n    documentSelector: DocumentSelector | null\n  }\n\n  interface TextDocumentChangeRegistrationOptions {\n    /**\n     * How documents are synced to the server.\n     */\n    syncKind: 0 | 1 | 2\n  }\n\n  interface TextDocumentSendFeature<T extends Function> {\n    /**\n    * Returns a provider for the given text document.\n    */\n    getProvider(document: TextDocument): { send: T } | undefined\n  }\n\n  interface NotificationSendEvent<E, P> {\n    original: E\n    type: ProtocolNotificationType<P, TextDocumentRegistrationOptions>\n    params: P\n  }\n\n  interface DidOpenTextDocumentParams {\n    /**\n     * The document that was opened.\n     */\n    textDocument: TextDocumentItem\n  }\n\n  interface NotifyingFeature<E, P> {\n    onNotificationSent: Event<NotificationSendEvent<E, P>>\n  }\n\n  export interface DidOpenTextDocumentFeatureShape extends DynamicFeature<TextDocumentRegistrationOptions>, TextDocumentSendFeature<(textDocument: TextDocument) => Promise<void>>, NotifyingFeature<TextDocument, DidOpenTextDocumentParams> {\n    openDocuments: Iterable<TextDocument>\n  }\n\n  export interface DidChangeTextDocumentFeatureShape extends DynamicFeature<TextDocumentChangeRegistrationOptions>, TextDocumentSendFeature<(event: DidChangeTextDocumentParams) => Promise<void>>, NotifyingFeature<DidChangeTextDocumentParams, Pick<DidChangeTextDocumentParams, 'textDocument' | 'contentChanges'>> {\n  }\n\n  export interface DidSaveTextDocumentFeatureShape extends DynamicFeature<TextDocumentRegistrationOptions>, TextDocumentSendFeature<(textDocument: TextDocument) => Promise<void>>, NotifyingFeature<TextDocument, { textDocument: { uri: string }, text?: string }> {}\n\n  export interface DidCloseTextDocumentFeatureShape extends DynamicFeature<TextDocumentRegistrationOptions>, TextDocumentSendFeature<(textDocument: TextDocument) => Promise<void>>, NotifyingFeature<TextDocument, { textDocument: { uri: string } }> {}\n\n  export interface TextDocumentContentProviderShape {\n    scheme: string\n    onDidChangeEmitter: Emitter<Uri>\n    provider: TextDocumentContentProvider\n  }\n\n  export interface WorkspaceProviderFeature<PR> {\n    getProviders(): PR[] | undefined\n  }\n\n  export interface TextDocumentProviderFeature<T> {\n    readonly registrationLength: number\n    /**\n    * Triggers the corresponding RPC method.\n    */\n    getProvider(textDocument: TextDocument): T | undefined\n  }\n\n  export interface CodeLensProviderShape {\n    provider?: CodeLensProvider\n    onDidChangeCodeLensEmitter: Emitter<void>\n  }\n\n  export interface SemanticTokensProviderShape {\n    range?: DocumentRangeSemanticTokensProvider\n    full?: DocumentSemanticTokensProvider\n    onDidChangeSemanticTokensEmitter: Emitter<void>\n  }\n\n  export interface InlineValueProviderShape {\n    provider: InlineValuesProvider\n    onDidChangeInlineValues: Emitter<void>\n  }\n\n  export interface InlayHintsProviderShape {\n    provider: InlayHintsProvider\n    onDidChangeInlayHints: Emitter<void>\n  }\n\n  export interface FoldingRangeProviderShape {\n    provider: FoldingRangeProvider\n    onDidChangeFoldingRange: Emitter<void>\n  }\n\n  export interface DiagnosticProviderShape {\n    /**\n     * An event that signals that the diagnostics should be refreshed for\n     * all documents.\n     */\n    onDidChangeDiagnosticsEmitter: Emitter<void>\n    /**\n     * The provider of diagnostics.\n     */\n    diagnostics: DiagnosticProvider\n    /**\n     * Forget the given document and remove all diagnostics.\n     *\n     * @param document The document to forget.\n     */\n    forget(document: TextDocument): void\n  }\n\n  export interface DiagnosticFeatureShape {\n    refresh(): void\n  }\n\n  /**\n   * A language server for manage a language server.\n   * It's recommended to use `services.registerLanguageClient` to register language client to serviers,\n   * you can have language client listed in `CocList services` and services could start the language client\n   * by `documentselector` of `clientOptions`.\n   */\n  export class LanguageClient {\n    readonly id: string\n    readonly name: string\n    constructor(id: string, name: string, serverOptions: ServerOptions, clientOptions: LanguageClientOptions, forceDebug?: boolean)\n    /**\n     * Create language client by name and options, don't forget to register language client\n     * to services by `services.registerLanguageClient`\n     */\n    constructor(name: string, serverOptions: ServerOptions, clientOptions: LanguageClientOptions, forceDebug?: boolean)\n\n    sendRequest<R, PR, E, RO>(type: ProtocolRequestType0<R, PR, E, RO> | RequestProtocolSignature0<R, PR, E, RO>, token?: CancellationToken): Promise<R>\n    sendRequest<P, R, PR, E, RO>(type: ProtocolRequestType<P, R, PR, E, RO> | RequestProtocolSignature<P, R, PR, E, RO>, params: P, token?: CancellationToken): Promise<R>\n    sendRequest<R, E>(type: RequestType0<R, E> | RequestSignature0<R, E>, token?: CancellationToken): Promise<R>\n    sendRequest<P, R, E>(type: RequestType<P, R, E> | RequestSignature<P, R, E>, params: P, token?: CancellationToken): Promise<R>\n    sendRequest<R>(method: string, token?: CancellationToken): Promise<R>\n    sendRequest<R>(method: string, param: any, token?: CancellationToken): Promise<R>\n\n    onRequest<R, PR, E, RO>(type: ProtocolRequestType0<R, PR, E, RO>, handler: RequestHandler0<R, E>): Disposable\n    onRequest<P, R, PR, E, RO>(type: ProtocolRequestType<P, R, PR, E, RO>, handler: RequestHandler<P, R, E>): Disposable\n    onRequest<R, E>(type: RequestType0<R, E>, handler: RequestHandler0<R, E>): Disposable\n    onRequest<P, R, E>(type: RequestType<P, R, E>, handler: RequestHandler<P, R, E>): Disposable\n    onRequest<R, E>(method: string, handler: (...params: any[]) => HandlerResult<R, E>): Disposable\n\n    sendNotification<RO>(type: ProtocolNotificationType0<RO> | NotificationProtocolSignature0<RO>): Promise<void>\n    sendNotification<P, RO>(type: ProtocolNotificationType<P, RO> | NotificationSignature<P>, params?: P): Promise<void>\n    sendNotification(type: NotificationType0 | NotificationSignature0): Promise<void>\n    sendNotification<P>(type: NotificationType<P> | NotificationSignature<P>, params?: P): Promise<void>\n    sendNotification(method: string): Promise<void>\n    sendNotification(method: string, params: any): Promise<void>\n\n    onNotification<RO>(type: ProtocolNotificationType0<RO>, handler: NotificationHandler0): Disposable\n    onNotification<P, RO>(type: ProtocolNotificationType<P, RO>, handler: NotificationHandler<P>): Disposable\n    onNotification(type: NotificationType0, handler: () => void): Disposable\n    onNotification<P>(type: NotificationType<P>, handler: (params: P) => void): Disposable\n    onNotification(method: string, handler: (...params: any[]) => void): Disposable\n\n    onProgress<P>(type: ProgressType<any>, token: string | number, handler: (params: P) => void): Disposable\n    sendProgress<P>(type: ProgressType<P>, token: string | number, value: P): Promise<void>\n\n    /**\n     * Append debug message to outputChannel\n     */\n    debug(message: string, data?: any, showNotification?: boolean): void\n    /**\n     * Append info message to outputChannel\n     */\n    info(message: string, data?: any, showNotification?: boolean): void\n    /**\n     * Append warning message to outputChannel\n     */\n    warn(message: string, data?: any, showNotification?: boolean): void\n    /**\n     * Append error message to outputChannel\n     */\n    error(message: string, data?: any, showNotification?: boolean | 'force'): void\n    /**\n     * Append trace message to traceOutputChannel or outputChannel\n     */\n    traceMessage(message: string, data?: any): void\n\n    readonly state: State\n    readonly middleware: Middleware\n    readonly initializeResult: InitializeResult | undefined\n    readonly clientOptions: LanguageClientOptions\n    readonly outputChannel: OutputChannel\n    /**\n     * Fired on language server state change.\n     */\n    readonly onDidChangeState: Event<StateChangeEvent>\n    readonly diagnostics: DiagnosticCollection | undefined\n    /**\n     * Current running state.\n     */\n    readonly serviceState: ServiceStat\n    readonly started: boolean\n    /**\n     * The server is running in debug mode by forceDebug or debug arguments of NodeJS.\n     */\n    readonly isInDebugMode: boolean\n    /**\n     * Check if server could start.\n     */\n    needsStart(): boolean\n    /**\n     * Check if server could stop.\n     */\n    needsStop(): boolean\n    /**\n     * Resolved when server ready\n     */\n    onReady(): Promise<void>\n    set trace(value: Trace)\n\n    /**\n     * Return true when the client is running.\n     */\n    isRunning(): boolean\n\n    /**\n     * Stop language server.\n     */\n    stop(): Promise<void>\n\n    /**\n     * Start language server, not needed when registered to services by `services.registerLanguageClient`\n     */\n    start(): Promise<void>\n    /**\n     * Restart language client.\n     */\n    restart(): Promise<void>\n\n    dispose(): Promise<void>\n    /**\n     * Register custom feature.\n     */\n    registerFeature(feature: StaticFeature | DynamicFeature<any>): void\n    /**\n     * Log failed request to outputChannel and throw error when necessary.\n     * @param type The request type.\n     * @param token CancellationToken used for request.\n     * @param error Request error.\n     * @param defaultValue Default return value when request cancelled or\n     * connection got disposed.\n     * @param showNotification Show message notification, default to true.\n     */\n    handleFailedRequest<T, P extends { method: string }>(type: P, token: CancellationToken | undefined, error: any, defaultValue: T, showNotification?: boolean): T\n    /**\n     * Create a default error handler.\n     */\n    createDefaultErrorHandler(maxRestartCount?: number): ErrorHandler\n\n    getFeature(request: 'workspace/executeCommand'): DynamicFeature<GeneralRegistrationOptions>\n    getFeature(request: 'workspace/didChangeWorkspaceFolders'): DynamicFeature<void>\n    getFeature(request: 'workspace/didChangeWatchedFiles'): DynamicFeature<DidChangeWatchedFilesRegistrationOptions>\n    getFeature(request: 'workspace/didChangeConfiguration'): DynamicFeature<DidChangeConfigurationRegistrationOptions>\n    getFeature(request: 'textDocument/didOpen'): DidOpenTextDocumentFeatureShape\n    getFeature(request: 'textDocument/didChange'): DidChangeTextDocumentFeatureShape\n    getFeature(request: 'textDocument/willSave'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentSendFeature<(textDocument: TextDocumentWillSaveEvent) => Promise<void>>\n    getFeature(request: 'textDocument/willSaveWaitUntil'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentSendFeature<(textDocument: TextDocument) => ProviderResult<TextEdit[]>>\n    getFeature(request: 'textDocument/didSave'): DidSaveTextDocumentFeatureShape\n    getFeature(request: 'textDocument/didClose'): DidCloseTextDocumentFeatureShape\n    getFeature(request: 'workspace/didCreateFiles'): DynamicFeature<GeneralRegistrationOptions> & { send: (event: FileCreateEvent) => Promise<void> }\n    getFeature(request: 'workspace/didRenameFiles'): DynamicFeature<GeneralRegistrationOptions> & { send: (event: FileRenameEvent) => Promise<void> }\n    getFeature(request: 'workspace/didDeleteFiles'): DynamicFeature<GeneralRegistrationOptions> & { send: (event: FileDeleteEvent) => Promise<void> }\n    getFeature(request: 'workspace/willCreateFiles'): DynamicFeature<GeneralRegistrationOptions> & { send: (event: FileWillCreateEvent) => Promise<void> }\n    getFeature(request: 'workspace/willRenameFiles'): DynamicFeature<GeneralRegistrationOptions> & { send: (event: FileWillRenameEvent) => Promise<void> }\n    getFeature(request: 'workspace/willDeleteFiles'): DynamicFeature<GeneralRegistrationOptions> & { send: (event: FileWillDeleteEvent) => Promise<void> }\n    getFeature(request: 'workspace/symbol'): DynamicFeature<TextDocumentRegistrationOptions> & WorkspaceProviderFeature<WorkspaceSymbolProvider>\n    getFeature(request: 'textDocument/completion'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<CompletionItemProvider>\n    getFeature(request: 'textDocument/hover'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<HoverProvider>\n    getFeature(request: 'textDocument/signatureHelp'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<SignatureHelpProvider>\n    getFeature(request: 'textDocument/definition'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DefinitionProvider>\n    getFeature(request: 'textDocument/references'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<ReferenceProvider>\n    getFeature(request: 'textDocument/documentHighlight'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DocumentHighlightProvider>\n    getFeature(request: 'textDocument/codeAction'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<CodeActionProvider>\n    getFeature(request: 'textDocument/codeLens'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<CodeLensProviderShape>\n    getFeature(request: 'textDocument/formatting'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DocumentFormattingEditProvider>\n    getFeature(request: 'textDocument/rangeFormatting'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DocumentRangeFormattingEditProvider>\n    getFeature(request: 'textDocument/onTypeFormatting'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<OnTypeFormattingEditProvider>\n    getFeature(request: 'textDocument/rename'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<RenameProvider>\n    getFeature(request: 'textDocument/documentSymbol'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DocumentSymbolProvider>\n    getFeature(request: 'textDocument/documentLink'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DocumentLinkProvider>\n    getFeature(request: 'textDocument/documentColor'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DocumentColorProvider>\n    getFeature(request: 'textDocument/declaration'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DeclarationProvider>\n    getFeature(request: 'textDocument/foldingRange'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<FoldingRangeProviderShape>\n    getFeature(request: 'textDocument/implementation'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<ImplementationProvider>\n    getFeature(request: 'textDocument/selectionRange'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<SelectionRangeProvider>\n    getFeature(request: 'textDocument/typeDefinition'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<TypeDefinitionProvider>\n    getFeature(request: 'textDocument/prepareCallHierarchy'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<CallHierarchyProvider>\n    getFeature(request: 'textDocument/semanticTokens'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<SemanticTokensProviderShape>\n    getFeature(request: 'textDocument/linkedEditingRange'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<LinkedEditingRangeProvider>\n    getFeature(request: 'textDocument/prepareTypeHierarchy'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<TypeHierarchyProvider>\n    getFeature(request: 'textDocument/inlineValue'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<InlineValueProviderShape>\n    getFeature(request: 'textDocument/inlayHint'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<InlayHintsProviderShape>\n    getFeature(request: 'textDocument/diagnostic'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<DiagnosticProviderShape> & DiagnosticFeatureShape\n    getFeature(request: 'workspace/textDocumentContent'): DynamicFeature<TextDocumentRegistrationOptions> & WorkspaceProviderFeature<TextDocumentContentProviderShape>\n    getFeature(request: 'textDocument/inlineCompletion'): DynamicFeature<TextDocumentRegistrationOptions> & TextDocumentProviderFeature<InlineCompletionItemProvider>\n  }\n\n  /**\n   * Monitor for setting change, restart language server when specified setting changed.\n   */\n  export class SettingMonitor {\n    constructor(client: LanguageClient, setting: string)\n    start(): Disposable\n  }\n  // }}\n}\n// vim: set tw=80 sw=2 ts=2 sts=2 et foldmarker={{,}} foldmethod=marker foldlevel=0 nofen:\n"
  }
]