[
  {
    "path": ".editorconfig",
    "content": "# See https://editorconfig.org\n\n# In Go files we indent with tabs but still \n# set indent_size to control the GitHub web viewer.  \n[*.go]\nindent_size=4\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/01-bug.yml",
    "content": "name: Bug Report\ndescription: File a bug report.\ntitle: \"<title>\"\nlabels: [\"bug\", \"triage\"]\nbody:\n- type: textarea\n  attributes:\n    label: Description\n    description: Description of the problem and steps to reproduce.\n  validations:\n    required: true\n- type: textarea\n  attributes:\n    label: Environment\n    description: |\n      examples:\n        - **Version**: 2.0.15 and/or commit hash ($ micro -version)\n        - **OS**: Debian\n        - **Terminal**: ptyxis\n    value: |\n        - Version:\n        - OS:\n        - Terminal:\n  validations:\n    required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/02-feature.yml",
    "content": "name: Feature Request\ndescription: File a feature request.\ntitle: \"<title>\"\nlabels: [\"feature\"]\nbody:\n- type: textarea\n  attributes:\n    label: Description\n    description: Description of the feature.\n  validations:\n    required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\n"
  },
  {
    "path": ".github/workflows/nightly.yaml",
    "content": "name: Nightly builds\non:\n  workflow_dispatch: # Allows manual trigger\n  schedule:\n    - cron: '0 0 * * *'\njobs:\n  nightly:\n    strategy:\n      matrix:\n        go-version: [1.23.x]\n        os: [ubuntu-latest]\n    runs-on: ${{ matrix.os }}\n    steps:\n    - name: Setup\n      uses: actions/setup-go@v5\n      with:\n        go-version: ${{ matrix.go-version }}\n        cache: false\n\n    - name: Checkout\n      uses: actions/checkout@v4\n      with:\n        ref: master\n        fetch-depth: 0\n        fetch-tags: true\n\n    - name: Build\n      run: tools/cross-compile.sh nightly\n\n    - name: Tag\n      uses: rickstaa/action-create-tag@v1\n      with:\n        tag: nightly\n        force_push_tag: true\n        message: \"Pre-release nightly\"\n\n    - name: Publish\n      uses: softprops/action-gh-release@v2\n      with:\n        name: nightly\n        tag_name: nightly\n        files: binaries/*\n        prerelease: true\n\n    - name: Cleanup\n      run: rm -rf binaries\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: Release builds\non:\n  workflow_dispatch: # Allows manual trigger\n  # push:\n  #   tags:\n  #     - 'v*.*.*' # automatically react on semantic versioned tags\njobs:\n  release:\n    strategy:\n      matrix:\n        go-version: [1.23.x]\n        os: [ubuntu-latest]\n    runs-on: ${{ matrix.os }}\n    steps:\n    - name: Setup\n      uses: actions/setup-go@v5\n      with:\n        go-version: ${{ matrix.go-version }}\n        cache: false\n\n    - name: Checkout\n      uses: actions/checkout@v4\n      with:\n        fetch-depth: 0\n        fetch-tags: true\n\n    - name: Build\n      run: tools/cross-compile.sh\n\n    - name: Publish\n      uses: softprops/action-gh-release@v2\n      with:\n        files: binaries/*\n\n    - name: Cleanup\n      run: rm -rf binaries\n"
  },
  {
    "path": ".github/workflows/test.yaml",
    "content": "on: [push, pull_request]\nname: Build and Test\njobs:\n  test:\n    strategy:\n      matrix:\n        go-version: [1.19.x, 1.23.x]\n        os: [ubuntu-latest, macos-latest, windows-latest]\n    runs-on: ${{ matrix.os }}\n    steps:\n    - uses: actions/setup-go@v5\n      with:\n        go-version: ${{ matrix.go-version }}\n        cache: false\n\n    - uses: actions/checkout@v4\n      with:\n        fetch-depth: 0\n        fetch-tags: true\n\n    - name: Build\n      run: |\n        make build\n\n    - name: Test\n      run: |\n        make test\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n\nmicro\nmicro.exe\n!cmd/micro\nbinaries/\ntmp.sh\ntest/\n.idea/\npackages/\ntodo.txt\ntest.txt\nlog.txt\n*.old\nbenchmark_results*\ntools/build-version\ntools/build-date\ntools/info-plist\ntools/vscode-tests/\n*.hdr\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2016-2020: Zachary Yedidia, et al.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in 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 OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "LICENSE-THIRD-PARTY",
    "content": "Third party libraries directly used by micro and their licenses\n================\n\ngithub.com/golang/go/LICENSE\n================\n\nCopyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n   * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n   * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n   * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\ngithub.com/blang/semver/LICENSE\n================\n\nThe MIT License\n\nCopyright (c) 2014 Benedikt Lang <github at benediktlang.de>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n\ngithub.com/gdamore/encoding/LICENSE\n================\n\n\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n\ngithub.com/go-errors/errors/LICENSE.MIT\n================\n\nCopyright (c) 2015 Conrad Irwin <conrad@bugsnag.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\ngithub.com/mattn/go-isatty/LICENSE\n================\n\nCopyright (c) Yasuhiro MATSUMOTO <mattn.jp@gmail.com>\n\nMIT License (Expat)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\ngithub.com/mattn/go-runewidth/LICENSE\n================\n\nThe MIT License (MIT)\n\nCopyright (c) 2016 Yasuhiro Matsumoto\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n\ngithub.com/mitchellh/go-homedir/LICENSE\n================\n\nThe MIT License (MIT)\n\nCopyright (c) 2013 Mitchell Hashimoto\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n\ngithub.com/sergi/go-diff/LICENSE\n================\n\nCopyright (c) 2012-2016 The go-diff Authors. All rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a\ncopy 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, EXPRESS\nOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n\n\n\ngithub.com/yuin/gopher-lua/LICENSE\n================\n\nThe MIT License (MIT)\n\nCopyright (c) 2015 Yusuke Inuzuka\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n\ngithub.com/atotto/clipboard/LICENSE\n================\ngithub.com/zyedidia/clipboard/LICENSE (fork)\n================\n\nCopyright (c) 2013 Ato Araki. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n   * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n   * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n   * Neither the name of @atotto. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\ngithub.com/gdamore/tcell/LICENSE\n================\ngithub.com/micro-editor/tcell/LICENSE (fork)\n================\n\n\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n\ngolang.org/x/text/LICENSE\n================\n\nCopyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n   * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n   * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n   * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\nlayeh.com/gopher-luar/LICENSE\n================\n\nMozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\n    means each individual or legal entity that creates, contributes to\n    the creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\n    means the combination of the Contributions of others (if any) used\n    by a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\n    means Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\n    means Source Code Form to which the initial Contributor has attached\n    the notice in Exhibit A, the Executable Form of such Source Code\n    Form, and Modifications of such Source Code Form, in each case\n    including portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\n    means\n\n    (a) that the initial Contributor has attached the notice described\n        in Exhibit B to the Covered Software; or\n\n    (b) that the Covered Software was made available under the terms of\n        version 1.1 or earlier of the License, but not also under the\n        terms of a Secondary License.\n\n1.6. \"Executable Form\"\n    means any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\n    means a work that combines Covered Software with other material, in \n    a separate file or files, that is not Covered Software.\n\n1.8. \"License\"\n    means this document.\n\n1.9. \"Licensable\"\n    means having the right to grant, to the maximum extent possible,\n    whether at the time of the initial grant or subsequently, any and\n    all of the rights conveyed by this License.\n\n1.10. \"Modifications\"\n    means any of the following:\n\n    (a) any file in Source Code Form that results from an addition to,\n        deletion from, or modification of the contents of Covered\n        Software; or\n\n    (b) any new file in Source Code Form that contains any Covered\n        Software.\n\n1.11. \"Patent Claims\" of a Contributor\n    means any patent claim(s), including without limitation, method,\n    process, and apparatus claims, in any patent Licensable by such\n    Contributor that would be infringed, but for the grant of the\n    License, by the making, using, selling, offering for sale, having\n    made, import, or transfer of either its Contributions or its\n    Contributor Version.\n\n1.12. \"Secondary License\"\n    means either the GNU General Public License, Version 2.0, the GNU\n    Lesser General Public License, Version 2.1, the GNU Affero General\n    Public License, Version 3.0, or any later versions of those\n    licenses.\n\n1.13. \"Source Code Form\"\n    means the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\n    means an individual or a legal entity exercising rights under this\n    License. For legal entities, \"You\" includes any entity that\n    controls, is controlled by, or is under common control with You. For\n    purposes of this definition, \"control\" means (a) the power, direct\n    or indirect, to cause the direction or management of such entity,\n    whether by contract or otherwise, or (b) ownership of more than\n    fifty percent (50%) of the outstanding shares or beneficial\n    ownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-exclusive license:\n\n(a) under intellectual property rights (other than patent or trademark)\n    Licensable by such Contributor to use, reproduce, make available,\n    modify, display, perform, distribute, and otherwise exploit its\n    Contributions, either on an unmodified basis, with Modifications, or\n    as part of a Larger Work; and\n\n(b) under Patent Claims of such Contributor to make, use, sell, offer\n    for sale, have made, import, and otherwise transfer either its\n    Contributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\n    or\n\n(b) for infringements caused by: (i) Your and any other third party's\n    modifications of Covered Software, or (ii) the combination of its\n    Contributions with other software (except as part of its Contributor\n    Version); or\n\n(c) under Patent Claims infringed by Covered Software in the absence of\n    its Contributions.\n\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\n    Form, as described in Section 3.1, and You must inform recipients of\n    the Executable Form how they can obtain a copy of such Source Code\n    Form by reasonable means in a timely manner, at a charge no more\n    than the cost of distribution to the recipient; and\n\n(b) You may distribute such Executable Form under the terms of this\n    License, or sublicense it under different terms, provided that the\n    license for the Executable Form does not attempt to limit or alter\n    the recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n*                                                                      *\n*  6. Disclaimer of Warranty                                           *\n*  -------------------------                                           *\n*                                                                      *\n*  Covered Software is provided under this License on an \"as is\"       *\n*  basis, without warranty of any kind, either expressed, implied, or  *\n*  statutory, including, without limitation, warranties that the       *\n*  Covered Software is free of defects, merchantable, fit for a        *\n*  particular purpose or non-infringing. The entire risk as to the     *\n*  quality and performance of the Covered Software is with You.        *\n*  Should any Covered Software prove defective in any respect, You     *\n*  (not any Contributor) assume the cost of any necessary servicing,   *\n*  repair, or correction. This disclaimer of warranty constitutes an   *\n*  essential part of this License. No use of any Covered Software is   *\n*  authorized under this License except under this disclaimer.         *\n*                                                                      *\n************************************************************************\n\n************************************************************************\n*                                                                      *\n*  7. Limitation of Liability                                          *\n*  --------------------------                                          *\n*                                                                      *\n*  Under no circumstances and under no legal theory, whether tort      *\n*  (including negligence), contract, or otherwise, shall any           *\n*  Contributor, or anyone who distributes Covered Software as          *\n*  permitted above, be liable to You for any direct, indirect,         *\n*  special, incidental, or consequential damages of any character      *\n*  including, without limitation, damages for lost profits, loss of    *\n*  goodwill, work stoppage, computer failure or malfunction, or any    *\n*  and all other commercial damages or losses, even if such party      *\n*  shall have been informed of the possibility of such damages. This   *\n*  limitation of liability shall not apply to liability for death or   *\n*  personal injury resulting from such party's negligence to the       *\n*  extent applicable law prohibits such limitation. Some               *\n*  jurisdictions do not allow the exclusion or limitation of           *\n*  incidental or consequential damages, so this exclusion and          *\n*  limitation may not apply to You.                                    *\n*                                                                      *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\n  This Source Code Form is subject to the terms of the Mozilla Public\n  License, v. 2.0. If a copy of the MPL was not distributed with this\n  file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\n  This Source Code Form is \"Incompatible With Secondary Licenses\", as\n  defined by the Mozilla Public License, v. 2.0.\n\ngithub.com/flynn/json5/LICENSE\n================\ngithub.com/micro-editor/json5/LICENSE (fork)\n================\n\nDecoder code based on package encoding/json from the Go language.\n\nCopyright (c) 2012 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n   * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n   * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n   * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n\nTest data based on the parse cases from https://github.com/json5/json5\n\nCopyright (c) 2012-2016 Aseem Kishore, and others.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in 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 OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\ngithub.com/james4k/terminal/LICENSE\n================\ngithub.com/micro-editor/terminal/LICENSE (fork)\n================\n\nCopyright (C) 2013 James Gray\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without liitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and thismssion notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\ngithub.com/kr/pty/License\n================\ngithub.com/zyedidia/pty/License (fork)\n================\n\nCopyright (c) 2011 Keith Rarick\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without limitation\nthe rights to use, copy, modify, merge, publish, distribute,\nsublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall\nbe included in all copies or substantial portions of the\nSoftware.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY\nKIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\nWARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\nPURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS\nOR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\nOTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\ngithub.com/npat-efault/poller/LICENSE.txt\n================\ngithub.com/zyedidia/poller/LICENSE.txt (fork)\n================\n\nCopyright (c) 2014, Nick Patavalis (npat@efault.net)\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\ngithub.com/zyedidia/glob\n================\n\nGlob is licensed under the MIT \"Expat\" License:\n\nCopyright (c) 2016: Zachary Yedidia.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in 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 OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\ngithub.com/dustin/go-humanize/LICENSE\n================\n\nCopyright (c) 2005-2008  Dustin Sallings <dustin@spy.net>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n<http://www.opensource.org/licenses/mit-license.php>\n\ngopkg.in/yaml.v2/LICENSE\n================\n\nCopyright 2011-2016 Canonical Ltd.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\ngithub.com/kballard/go-shellquote/LICENSE\n===============\n\nCopyright (C) 2014 Kevin Ballard\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\ngithub.com/stretchr/testify\n=================\n\nMIT License\n\nCopyright (c) 2012-2018 Mat Ryer and Tyler Bunnell\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: runtime build generate build-quick\n\nVERSION = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \\\n\tgo run tools/build-version.go)\nHASH = $(shell git rev-parse --short HEAD)\nDATE = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \\\n\tgo run tools/build-date.go)\nGOBIN ?= $(shell go env GOPATH)/bin\nGOVARS = -X github.com/micro-editor/micro/v2/internal/util.Version=$(VERSION) -X github.com/micro-editor/micro/v2/internal/util.CommitHash=$(HASH) -X 'github.com/micro-editor/micro/v2/internal/util.CompileDate=$(DATE)'\nDEBUGVAR = -X github.com/micro-editor/micro/v2/internal/util.Debug=ON\nVSCODE_TESTS_BASE_URL = 'https://raw.githubusercontent.com/microsoft/vscode/e6a45f4242ebddb7aa9a229f85555e8a3bd987e2/src/vs/editor/test/common/model/'\nCGO_ENABLED := $(if $(CGO_ENABLED),$(CGO_ENABLED),0)\n\nADDITIONAL_GO_LINKER_FLAGS := \"\"\nGOHOSTOS = $(shell go env GOHOSTOS)\nifeq ($(GOHOSTOS), darwin)\n\t# Native darwin resp. macOS builds need external and dynamic linking\n\tADDITIONAL_GO_LINKER_FLAGS += $(shell GOOS=$(GOHOSTOS) \\\n\t\tGOARCH=$(shell go env GOHOSTARCH) \\\n\t\tgo run tools/info-plist.go \"$(shell go env GOOS)\" \"$(VERSION)\")\n\tCGO_ENABLED = 1\nendif\n\nbuild: generate build-quick\n\nbuild-quick:\n\tCGO_ENABLED=$(CGO_ENABLED) go build -trimpath -ldflags \"-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)\" ./cmd/micro\n\nbuild-dbg:\n\tCGO_ENABLED=$(CGO_ENABLED) go build -trimpath -ldflags \"$(ADDITIONAL_GO_LINKER_FLAGS) $(DEBUGVAR)\" ./cmd/micro\n\nbuild-tags: fetch-tags build\n\nbuild-all: build\n\ninstall: generate\n\tgo install -ldflags \"-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)\" ./cmd/micro\n\ninstall-all: install\n\nfetch-tags:\n\tgit fetch --tags --force\n\ngenerate:\n\tGOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) go generate ./runtime\n\ntestgen:\n\tmkdir -p tools/vscode-tests\n\tcd tools/vscode-tests && \\\n\tcurl --remote-name-all $(VSCODE_TESTS_BASE_URL){editableTextModelAuto,editableTextModel,model.line}.test.ts\n\ttsc tools/vscode-tests/*.ts > /dev/null; true\n\tgo run tools/testgen.go tools/vscode-tests/*.js > buffer_generated_test.go\n\tmv buffer_generated_test.go internal/buffer\n\tgofmt -w internal/buffer/buffer_generated_test.go\n\ntest:\n\tgo test ./internal/...\n\tgo test ./cmd/...\n\nbench:\n\tfor i in 1 2 3; do \\\n\t\tgo test -bench=. ./internal/...; \\\n\tdone > benchmark_results\n\tbenchstat benchmark_results\n\nbench-baseline:\n\tfor i in 1 2 3; do \\\n\t\tgo test -bench=. ./internal/...; \\\n\tdone > benchmark_results_baseline\n\nbench-compare:\n\tfor i in 1 2 3; do \\\n\t\tgo test -bench=. ./internal/...; \\\n\tdone > benchmark_results\n\tbenchstat -alpha 0.15 benchmark_results_baseline benchmark_results\n\nclean:\n\trm -f micro\n"
  },
  {
    "path": "README.md",
    "content": "<img alt=\"micro logo\" src=\"./assets/micro-logo-drop.svg\" width=\"500px\"/>\n\n![Test Workflow](https://github.com/micro-editor/micro/actions/workflows/test.yaml/badge.svg)\n[![Go Report Card](https://goreportcard.com/badge/github.com/micro-editor/micro/v2)](https://goreportcard.com/report/github.com/micro-editor/micro/v2)\n[![Release](https://img.shields.io/github/release/micro-editor/micro.svg?label=Release)](https://github.com/micro-editor/micro/releases)\n[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/micro-editor/micro/blob/master/LICENSE)\n[![Join the chat at https://gitter.im/zyedidia/micro](https://badges.gitter.im/zyedidia/micro.svg)](https://gitter.im/zyedidia/micro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)\n[![Snap Status](https://snapcraft.io/micro/badge.svg)](https://snapcraft.io/micro)\n\n**micro** is a terminal-based text editor that aims to be easy to use and intuitive, while also taking advantage of the capabilities\nof modern terminals. It comes as a single, batteries-included, static binary with no dependencies; you can download and use it right now!\n\nAs its name indicates, micro aims to be somewhat of a successor to the nano editor by being easy to install and use.\nIt strives to be enjoyable as a full-time editor for people who prefer to work in a terminal, or those who regularly edit files over SSH.\n\nHere is a picture of micro editing its source code.\n\n![Screenshot](./assets/micro-solarized.png)\n\nTo see more screenshots of micro, showcasing some of the default color schemes, see [here](https://micro-editor.github.io).\n\nYou can also check out the website for Micro at https://micro-editor.github.io.\n\n- - -\n\n## Features\n\n- Easy to use and install.\n- No dependencies or external files are needed — just the binary you can download further down the page.\n- Multiple cursors.\n- Common keybindings (<kbd>Ctrl-s</kbd>, <kbd>Ctrl-c</kbd>, <kbd>Ctrl-v</kbd>, <kbd>Ctrl-z</kbd>, …).\n  - Keybindings can be rebound to your liking.\n- Sane defaults.\n  - You shouldn't have to configure much out of the box (and it is extremely easy to configure).\n- Splits and tabs.\n- nano-like menu to help you remember the keybindings.\n- Extremely good mouse support.\n  - This means mouse dragging to create a selection, double click to select by word, and triple click to select by line.\n- Cross-platform (it should work on all the platforms Go runs on).\n  - Note that while Windows is supported, Mingw/Cygwin is not (see below).\n- Plugin system (plugins are written in Lua).\n  - micro has a built-in plugin manager to automatically install, remove, and update plugins.\n- Built-in diff gutter.\n- Simple autocompletion.\n- Persistent undo.\n- Automatic linting and error notifications.\n- Syntax highlighting for over [130 languages](runtime/syntax).\n- Color scheme support.\n  - By default, micro comes with 16, 256, and true color themes.\n- True color support.\n- Copy and paste with the system clipboard.\n- Small and simple.\n- Easily configurable.\n- Macros.\n- Smart highlighting of trailing whitespace and tab vs space errors.\n- Common editor features such as undo/redo, line numbers, Unicode support, soft wrapping, …\n\n## Installation\n\nTo install micro, you can download a [prebuilt binary](https://github.com/micro-editor/micro/releases), or you can build it from source.\n\nIf you want more information about ways to install micro, see this [wiki page](https://github.com/micro-editor/micro/wiki/Installing-Micro).\n\nUse `micro -version` to get the version information after installing. It is only guaranteed that you are installing the most recent\nstable version if you install from the prebuilt binaries, Homebrew, or Snap.\n\nA desktop entry file and man page can be found in the [assets/packaging](https://github.com/micro-editor/micro/tree/master/assets/packaging) directory.\n\n### Pre-built binaries\n\nPre-built binaries are distributed in [releases](https://github.com/micro-editor/micro/releases).\n\nTo uninstall micro, simply remove the binary, and the configuration directory at `~/.config/micro`.\n\n#### Third-party quick-install script\n\n```bash\ncurl https://getmic.ro | bash\n```\n\nThe script will place the micro binary in the current directory. From there, you can move it to a directory on your path of your choosing (e.g. `sudo mv micro /usr/bin`). See its [GitHub repository](https://github.com/benweissmann/getmic.ro) for more information.\n\n#### Eget\n\nWith [Eget](https://github.com/zyedidia/eget) installed, you can easily get a pre-built binary:\n\n```\neget micro-editor/micro\n```\n\nUse `--tag VERSION` to download a specific tagged version.\n\n```\neget --tag nightly micro-editor/micro # download the nightly version (compiled every day at midnight UTC)\neget --tag v2.0.8 micro-editor/micro  # download version 2.0.8 rather than the latest release\n```\n\nYou can install `micro` by adding `--to /usr/local/bin` to the `eget` command, or move the binary manually to a directory on your `$PATH` after the download completes.\n\nSee [Eget](https://github.com/zyedidia/eget) for more information.\n\n### Package managers\n\nYou can install micro using Homebrew on Mac:\n\n```\nbrew install micro\n```\n\n**Note for Mac:** All micro keybindings use the control or alt (option) key, not the command\nkey. By default, macOS terminals do not forward alt key events. To fix this, please see\nthe section on [macOS terminals](https://github.com/micro-editor/micro#macos-terminal) further below.\n\nOn Linux, you can install micro through [snap](https://snapcraft.io/docs/core/install)\n\n```\nsnap install micro --classic\n```\n\nMicro is also available through other package managers on Linux such as dnf, AUR, Nix, and package managers\nfor other operating systems. These packages are not guaranteed to be up-to-date.\n\n<!-- * `apt install micro` (Ubuntu 20.04 `focal`, and Debian `unstable | testing | buster-backports`). At the moment, this package (2.0.1-1) is outdated and has a known bug where debug mode is enabled. -->\n\n* Linux:\n    * distro-specific package managers:\n        * `dnf install micro` (Fedora).\n        * `apt install micro` (Ubuntu and Debian).\n        * `pacman -S micro` (Arch Linux).\n        * `emerge app-editors/micro` (Gentoo).\n        * `zypper install micro-editor` (SUSE)\n        * `eopkg install micro` (Solus).\n        * `pacstall -I micro` (Pacstall).\n        * `apt-get install micro` (ALT Linux)\n        * See [wiki](https://github.com/micro-editor/micro/wiki/Installing-Micro) for details about CRUX, Termux.\n    * distro-agnostic package managers:\n        * `nix profile install nixpkgs#micro` (with [Nix](https://nixos.org/) and flakes enabled)\n        * `flox install micro` (with [Flox](https://flox.dev))\n* Windows: [Chocolatey](https://chocolatey.org), [Scoop](https://scoop.sh/) and [WinGet](https://learn.microsoft.com/en-us/windows/package-manager/winget/).\n    * `choco install micro`.\n    * `scoop install micro`.\n    * `winget install zyedidia.micro`\n* OpenBSD: Available in the ports tree and also available as a binary package.\n    * `pkg_add -v micro`.\n* NetBSD, macOS, Linux, Illumos, etc. with [pkgsrc](https://www.pkgsrc.org/)-current:\n    * `pkg_add micro`\n* macOS: Available in package managers.\n    * `sudo port install micro` (with [MacPorts](https://www.macports.org))\n    * `brew install micro` (with [Homebrew](https://brew.sh/))\n    * `nix profile install nixpkgs#micro` (with [Nix](https://nixos.org/) and flakes enabled)\n    * `flox install micro` (with [Flox](https://flox.dev))\n\n**Note for Linux desktop environments:**\n\nFor interfacing with the local system clipboard, the following tools need to be installed:\n* For X11, `xclip` or `xsel`\n* For [Wayland](https://wayland.freedesktop.org/), `wl-clipboard`\n\nWithout these tools installed, micro will use an internal clipboard for copy and paste, but it won't be accessible to external applications.\n\n### Building from source\n\nIf your operating system does not have a binary release, but does run Go, you can build from source.\n\nMake sure that you have Go version 1.19 or greater and Go modules are enabled.\n\n```\ngit clone https://github.com/micro-editor/micro\ncd micro\nmake build\nsudo mv micro /usr/local/bin # optional\n```\n\nThe binary will be placed in the current directory and can be moved to\nanywhere you like (for example `/usr/local/bin`).\n\nThe command `make install` will install the binary to `$GOPATH/bin` or `$GOBIN`.\n\nYou can install directly with `go get` (`go get github.com/micro-editor/micro/cmd/micro`) but this isn't\nrecommended because it doesn't build micro with version information (necessary for the plugin manager),\nand doesn't disable debug mode.\n\n### Fully static or dynamically linked binary\n\nBy default, the micro binary is linked statically to increase the portability of the prebuilt binaries.\nThis behavior can simply be overriden by providing `CGO_ENABLED=1` to the build target.\n\n```\nCGO_ENABLED=1 make build\n```\n\nAfterwards the micro binary will dynamically link with the present core system libraries.\n\n**Note for Mac:**\nNative macOS builds are done with `CGO_ENABLED=1` forced set to support adding the \"Information Property List\" in the linker step.\n\n### macOS terminal\n\nIf you are using macOS, you should consider using [iTerm2](https://iterm2.com/) instead of the default terminal (Terminal.app). The iTerm2 terminal has much better mouse support as well as better handling of key events. For best keybinding behavior, choose `xterm defaults` under `Preferences->Profiles->Keys->Presets...`, and select `Esc+` for `Left Option Key` in the same menu. The newest versions also support true color.\n\nIf you still insist on using the default Mac terminal, be sure to set `Use Option key as Meta key` under\n`Preferences->Profiles->Keyboard` to use <kbd>option</kbd> as <kbd>alt</kbd>.\n\n### WSL and Windows Console\n\nIf you use micro within WSL, it is highly recommended that you use the [Windows\nTerminal](https://apps.microsoft.com/store/detail/windows-terminal/9N0DX20HK701?hl=en-us&gl=us)\ninstead of the default Windows Console.\n\nIf you must use Windows Console for some reason, note that there is a bug in\nWindows Console WSL that causes a font change whenever micro tries to access\nthe external clipboard via powershell. To fix this, use an internal clipboard\nwith `set clipboard internal` (though your system clipboard will no longer be\navailable in micro).\n\n### Colors and syntax highlighting\n\nIf you open micro and it doesn't seem like syntax highlighting is working, this is probably because\nyou are using a terminal which does not support 256 color mode. Try changing the color scheme to `simple`\nby pressing <kbd>Ctrl-e</kbd> in micro and typing `set colorscheme simple`.\n\nIf you are using the default Ubuntu terminal, to enable 256 color mode make sure your `TERM` variable is set\nto `xterm-256color`.\n\nMany of the Windows terminals don't support more than 16 colors, which means\nthat micro's default color scheme won't look very good. You can either set\nthe color scheme to `simple`, or download and configure a better terminal emulator\nthan the Windows default.\n\n### Cygwin, Mingw, Plan9\n\nCygwin, Mingw, and Plan9 are unfortunately not officially supported. In Cygwin and Mingw, micro will often work when run using\nthe `winpty` utility:\n\n```\nwinpty micro.exe ...\n```\n\nMicro uses the amazing [tcell library](https://github.com/gdamore/tcell), but this\nmeans that micro is restricted to the platforms tcell supports. As a result, micro does not support\nPlan9 or Cygwin (although this may change in the future). Micro also doesn't support NaCl (which is deprecated anyway).\n\n## Usage\n\nOnce you have built the editor, start it by running `micro path/to/file.txt` or `micro` to open an empty buffer.\n\nmicro also supports creating buffers from `stdin`:\n\n```sh\nip a | micro\n```\n\nYou can move the cursor around with the arrow keys and mouse.\n\nYou can also use the mouse to manipulate the text. Simply clicking and dragging\nwill select text. You can also double click to enable word selection, and triple\nclick to enable line selection.\n\n## Documentation and Help\n\nmicro has a built-in help system which you can access by pressing <kbd>Ctrl-e</kbd> and typing `help`. Additionally, you can\nview the help files here:\n\n- [main help](https://github.com/micro-editor/micro/tree/master/runtime/help/help.md)\n- [keybindings](https://github.com/micro-editor/micro/tree/master/runtime/help/keybindings.md)\n- [commands](https://github.com/micro-editor/micro/tree/master/runtime/help/commands.md)\n- [colors](https://github.com/micro-editor/micro/tree/master/runtime/help/colors.md)\n- [options](https://github.com/micro-editor/micro/tree/master/runtime/help/options.md)\n- [plugins](https://github.com/micro-editor/micro/tree/master/runtime/help/plugins.md)\n\nI also recommend reading the [tutorial](https://github.com/micro-editor/micro/tree/master/runtime/help/tutorial.md) for\na brief introduction to the more powerful configuration features micro offers.\n\nThere is also an unofficial Discord, which you can join at https://discord.gg/nhWR6armnR.\n\n## Contributing\n\nIf you find any bugs, please report them! I am also happy to accept pull requests from anyone.\n\nYou can use the [GitHub issue tracker](https://github.com/micro-editor/micro/issues)\nto report bugs, ask questions, or suggest new features.\n\nFor a more informal setting to discuss the editor, you can join the [Gitter chat](https://gitter.im/zyedidia/micro) or the [Discord](https://discord.gg/nhWR6armnR). You can also use the [Discussions](https://github.com/micro-editor/micro/discussions) section on Github for a forum-like setting or for Q&A.\n\nSometimes I am unresponsive, and I apologize! If that happens, please ping me.\n"
  },
  {
    "path": "assets/packaging/deb/micro.postinst",
    "content": "#!/bin/sh\n\nset -e\n\nif [ \"$1\" = \"configure\" ] || [ \"$1\" = \"abort-upgrade\" ]; then\n    update-alternatives --install /usr/bin/editor editor /usr/bin/micro 40 \\\n      --slave /usr/share/man/man1/editor.1 editor.1 \\\n      /usr/share/man/man1/micro.1\nfi\n"
  },
  {
    "path": "assets/packaging/deb/micro.prerm",
    "content": "#!/bin/sh\n\nset -e\n\nif [ \"$1\" != \"upgrade\" ]; then\n    update-alternatives --remove editor /usr/bin/micro\nfi\n"
  },
  {
    "path": "assets/packaging/micro.1",
    "content": ".TH micro 1 \"2025-09-03\"\n.SH NAME\nmicro \\- A modern and intuitive terminal-based text editor\n.SH SYNOPSIS\n.B micro\n.RI [ OPTION ]...\\&\n.RI [ FILE ]...\\&\n.RI [+ LINE [: COL ]]\\&\n.RI [+/ REGEX ]\n.br\n.B micro\n.RI [ OPTION ]...\\&\n.RI [ FILE [: LINE [: COL ]]]...\\&\n\\& (only if the `parsecursor` option is enabled)\n.SH DESCRIPTION\nMicro is a terminal-based text editor that aims to be easy to use and intuitive, while also taking advantage of the full capabilities\nof modern terminals. It comes as one single, batteries-included, static binary with no dependencies.\n\nAs the name indicates, micro aims to be somewhat of a successor to the nano editor by being easy to install and use in a pinch, but micro also aims to be\nenjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).\n\nUse Ctrl-q to quit, Ctrl-s to save, and Ctrl-g to open the in-editor help menu.\n.SH OPTIONS\n.PP\n.B \\-clean\n.RS 4\nClean the configuration directory and exit\n.RE\n.PP\n.B \\-config-dir\n.I dir\n.RS 4\nSpecify a custom location for the configuration directory\n.RE\n.PP\n.IR FILE : LINE [: COL ]\n(only if the `parsecursor` option is enabled)\n.br\n.IR FILE \\ + LINE [: COL ]\n.RS 4\nSpecify a line and column to start the cursor at when opening a buffer\n.RE\n.PP\n.RI +/ REGEX\n.RS 4\nSpecify a regex to search for when opening a buffer\n.RE\n.PP\n.B \\-options\n.RS 4\nShow all options help and exit\n.RE\n.PP\n.B \\-debug\n.RS 4\nEnable debug mode (enables logging to ./log.txt)\n.RE\n.PP\n.B \\-profile\n.RS 4\nEnable CPU profiling (writes profile info to ./micro.prof so it can be analyzed later with \"go tool pprof micro.prof\")\n.RE\n.PP\n.B \\-version\n.RS 4\nShow the version number and information and exit\n.RE\n\nMicro's plugins can be managed at the command line with the following commands.\n.RS 4\n.PP\n.B \\-plugin install\n.RI [ PLUGIN ]...\n.RS 4\nInstall plugin(s)\n.RE\n.PP\n.B \\-plugin remove\n.RI [ PLUGIN ]...\n.RS 4\nRemove plugin(s)\n.RE\n.PP\n.B \\-plugin update\n.RI [ PLUGIN ]...\n.RS 4\nUpdate plugin(s) (if no argument is given, updates all plugins)\n.RE\n.PP\n.B \\-plugin search\n.RI [ PLUGIN ]...\n.RS 4\nSearch for a plugin\n.RE\n.PP\n.B \\-plugin list\n.RS 4\nList installed plugins\n.RE\n.PP\n.B \\-plugin available\n.RS 4\nList available plugins\n.RE\n.RE\n\nMicro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the settings.json\nfile (see 'help options').\n.RS 4\n.PP\n.BI \\-< option >\n.I value\n.RS 4\nSet `option` to `value` for this session.\n.br\nFor example: `micro -syntax off file.c`\n.RE\n.RE\n.PP\nUse `micro -options` to see the full list of configuration options.\n.SH CONFIGURATION\nMicro uses $MICRO_CONFIG_HOME as the configuration directory.\nIf this environment variable is not set, it uses $XDG_CONFIG_HOME/micro instead.\nIf that environment variable is not set, it uses ~/.config/micro as the configuration directory.\nIn the documentation, we use ~/.config/micro to refer to the configuration directory\n(even if it may in fact be somewhere else if you have set either of the above environment variables).\n.SH NOTICE\nThis manpage is intended only to serve as a quick guide to the invocation of \nmicro and is not intended to replace the full documentation included with micro\nwhich can be accessed from within micro. Micro tells you what key combination to\npress to get help in the lower right.\n.SH BUGS\nA comprehensive list of bugs will not be listed in this manpage. See the Github\npage at \\fBhttps://github.com/micro-editor/micro/issues\\fP for a list of known bugs\nand to report any newly encountered bugs you may find. We strive to correct\nbugs as swiftly as possible.\n.SH COPYRIGHT\nCopyright \\(co 2020 Zachary Yedidia, et al. MIT license.\nSee \\fBhttps://github.com/micro-editor/micro\\fP for details.\n"
  },
  {
    "path": "assets/packaging/micro.desktop",
    "content": "[Desktop Entry]\n\nName=Micro\nGenericName=Text Editor\nComment=Edit text files in a terminal\n\nIcon=micro\nType=Application\nCategories=Utility;TextEditor;Development;\nKeywords=text;editor;syntax;terminal;\n\nExec=micro %F\nStartupNotify=false\nTerminal=true\nMimeType=text/plain;text/x-chdr;text/x-csrc;text/x-c++hdr;text/x-c++src;text/x-java;text/x-dsrc;text/x-pascal;text/x-perl;text/x-python;application/x-php;application/x-httpd-php3;application/x-httpd-php4;application/x-httpd-php5;application/xml;text/html;text/css;text/x-sql;text/x-diff;\n"
  },
  {
    "path": "cmd/micro/clean.go",
    "content": "package main\n\nimport (\n\t\"bufio\"\n\t\"encoding/gob\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n)\n\nfunc shouldContinue() bool {\n\treader := bufio.NewReader(os.Stdin)\n\tfmt.Print(\"Continue [Y/n]: \")\n\ttext, err := reader.ReadString('\\n')\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn false\n\t}\n\n\ttext = strings.TrimRight(text, \"\\r\\n\")\n\n\treturn len(text) == 0 || strings.ToLower(text)[0] == 'y'\n}\n\n// CleanConfig performs cleanup in the user's configuration directory\nfunc CleanConfig() {\n\tfmt.Println(\"Cleaning your configuration directory at\", config.ConfigDir)\n\tfmt.Printf(\"Please consider backing up %s before continuing\\n\", config.ConfigDir)\n\n\tif !shouldContinue() {\n\t\tfmt.Println(\"Stopping early\")\n\t\treturn\n\t}\n\n\tfmt.Println(\"Cleaning default settings\")\n\n\tsettingsFile := filepath.Join(config.ConfigDir, \"settings.json\")\n\terr := config.WriteSettings(settingsFile)\n\tif err != nil {\n\t\tif errors.Is(err, util.ErrOverwrite) {\n\t\t\tfmt.Println(err.Error())\n\t\t} else {\n\t\t\tfmt.Println(\"Error writing settings.json file: \" + err.Error())\n\t\t}\n\t}\n\n\t// detect unused options\n\tvar unusedOptions []string\n\tdefaultSettings := config.DefaultAllSettings()\n\tfor k := range config.GlobalSettings {\n\t\tif _, ok := defaultSettings[k]; !ok {\n\t\t\tvalid := false\n\t\t\tfor _, p := range config.Plugins {\n\t\t\t\tif strings.HasPrefix(k, p.Name+\".\") || k == p.Name {\n\t\t\t\t\tvalid = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !valid {\n\t\t\t\tunusedOptions = append(unusedOptions, k)\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(unusedOptions) > 0 {\n\t\tfmt.Println(\"The following options are unused:\")\n\n\t\tsort.Strings(unusedOptions)\n\n\t\tfor _, s := range unusedOptions {\n\t\t\tfmt.Printf(\"%s (value: %v)\\n\", s, config.GlobalSettings[s])\n\t\t}\n\n\t\tfmt.Printf(\"These options will be removed from %s\\n\", settingsFile)\n\n\t\tif shouldContinue() {\n\t\t\tfor _, s := range unusedOptions {\n\t\t\t\tdelete(config.GlobalSettings, s)\n\t\t\t}\n\n\t\t\terr := config.OverwriteSettings(settingsFile)\n\t\t\tif err != nil {\n\t\t\t\tif errors.Is(err, util.ErrOverwrite) {\n\t\t\t\t\tfmt.Println(err.Error())\n\t\t\t\t} else {\n\t\t\t\t\tfmt.Println(\"Error overwriting settings.json file: \" + err.Error())\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfmt.Println(\"Removed unused options\")\n\t\t\tfmt.Print(\"\\n\\n\")\n\t\t}\n\t}\n\n\t// detect incorrectly formatted buffer/ files\n\tbuffersPath := filepath.Join(config.ConfigDir, \"buffers\")\n\tfiles, err := os.ReadDir(buffersPath)\n\tif err == nil {\n\t\tvar badFiles []string\n\t\tvar buffer buffer.SerializedBuffer\n\t\tfor _, f := range files {\n\t\t\tfname := filepath.Join(buffersPath, f.Name())\n\t\t\tfile, e := os.Open(fname)\n\n\t\t\tif e == nil {\n\t\t\t\tdecoder := gob.NewDecoder(file)\n\t\t\t\terr = decoder.Decode(&buffer)\n\n\t\t\t\tif err != nil && f.Name() != \"history\" {\n\t\t\t\t\tbadFiles = append(badFiles, fname)\n\t\t\t\t}\n\t\t\t\tfile.Close()\n\t\t\t}\n\t\t}\n\n\t\tif len(badFiles) > 0 {\n\t\t\tfmt.Printf(\"Detected %d files with an invalid format in %s\\n\", len(badFiles), buffersPath)\n\t\t\tfmt.Println(\"These files store cursor and undo history.\")\n\t\t\tfmt.Printf(\"Removing badly formatted files in %s\\n\", buffersPath)\n\n\t\t\tif shouldContinue() {\n\t\t\t\tremoved := 0\n\t\t\t\tfor _, f := range badFiles {\n\t\t\t\t\terr := os.Remove(f)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(err)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tremoved++\n\t\t\t\t}\n\n\t\t\t\tif removed == 0 {\n\t\t\t\t\tfmt.Println(\"Failed to remove files\")\n\t\t\t\t} else {\n\t\t\t\t\tfmt.Printf(\"Removed %d badly formatted files\\n\", removed)\n\t\t\t\t}\n\t\t\t\tfmt.Print(\"\\n\\n\")\n\t\t\t}\n\t\t}\n\t}\n\n\t// detect plugins/ directory\n\tplugins := filepath.Join(config.ConfigDir, \"plugins\")\n\tif stat, err := os.Stat(plugins); err == nil && stat.IsDir() {\n\t\tfmt.Printf(\"Found directory %s\\n\", plugins)\n\t\tfmt.Printf(\"Plugins should now be stored in %s\\n\", filepath.Join(config.ConfigDir, \"plug\"))\n\t\tfmt.Printf(\"Removing %s\\n\", plugins)\n\n\t\tif shouldContinue() {\n\t\t\tos.RemoveAll(plugins)\n\t\t}\n\n\t\tfmt.Print(\"\\n\\n\")\n\t}\n\n\tfmt.Println(\"Done cleaning\")\n}\n"
  },
  {
    "path": "cmd/micro/debug.go",
    "content": "package main\n\nimport (\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n)\n\n// NullWriter simply sends writes into the void\ntype NullWriter struct{}\n\n// Write is empty\nfunc (NullWriter) Write(data []byte) (n int, err error) {\n\treturn 0, nil\n}\n\n// InitLog sets up the debug log system for micro if it has been enabled by compile-time variables\nfunc InitLog() {\n\tif util.Debug == \"ON\" {\n\t\tf, err := os.OpenFile(\"log.txt\", os.O_RDWR|os.O_CREATE|os.O_TRUNC, util.FileMode)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"error opening file: %v\", err)\n\t\t}\n\n\t\tlog.SetOutput(f)\n\t\tlog.Println(\"Micro started\")\n\t} else {\n\t\tlog.SetOutput(NullWriter{})\n\t}\n}\n"
  },
  {
    "path": "cmd/micro/initlua.go",
    "content": "package main\n\nimport (\n\t\"log\"\n\t\"time\"\n\n\tlua \"github.com/yuin/gopher-lua\"\n\tluar \"layeh.com/gopher-luar\"\n\n\t\"github.com/micro-editor/micro/v2/internal/action\"\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/display\"\n\tulua \"github.com/micro-editor/micro/v2/internal/lua\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/micro/v2/internal/shell\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n)\n\nfunc init() {\n\tulua.L = lua.NewState()\n\tulua.L.SetGlobal(\"import\", luar.New(ulua.L, LuaImport))\n}\n\n// LuaImport is meant to be called from lua by a plugin and will import the given micro package\nfunc LuaImport(pkg string) *lua.LTable {\n\tswitch pkg {\n\tcase \"micro\":\n\t\treturn luaImportMicro()\n\tcase \"micro/shell\":\n\t\treturn luaImportMicroShell()\n\tcase \"micro/buffer\":\n\t\treturn luaImportMicroBuffer()\n\tcase \"micro/config\":\n\t\treturn luaImportMicroConfig()\n\tcase \"micro/util\":\n\t\treturn luaImportMicroUtil()\n\tdefault:\n\t\treturn ulua.Import(pkg)\n\t}\n}\n\nfunc luaImportMicro() *lua.LTable {\n\tpkg := ulua.L.NewTable()\n\n\tulua.L.SetField(pkg, \"TermMessage\", luar.New(ulua.L, screen.TermMessage))\n\tulua.L.SetField(pkg, \"TermError\", luar.New(ulua.L, screen.TermError))\n\tulua.L.SetField(pkg, \"InfoBar\", luar.New(ulua.L, action.GetInfoBar))\n\tulua.L.SetField(pkg, \"Log\", luar.New(ulua.L, log.Println))\n\tulua.L.SetField(pkg, \"SetStatusInfoFn\", luar.New(ulua.L, display.SetStatusInfoFnLua))\n\tulua.L.SetField(pkg, \"CurPane\", luar.New(ulua.L, func() *action.BufPane {\n\t\treturn action.MainTab().CurPane()\n\t}))\n\tulua.L.SetField(pkg, \"CurTab\", luar.New(ulua.L, action.MainTab))\n\tulua.L.SetField(pkg, \"Tabs\", luar.New(ulua.L, func() *action.TabList {\n\t\treturn action.Tabs\n\t}))\n\tulua.L.SetField(pkg, \"After\", luar.New(ulua.L, func(t time.Duration, f func()) {\n\t\ttime.AfterFunc(t, func() {\n\t\t\ttimerChan <- f\n\t\t})\n\t}))\n\n\treturn pkg\n}\n\nfunc luaImportMicroConfig() *lua.LTable {\n\tpkg := ulua.L.NewTable()\n\n\tulua.L.SetField(pkg, \"MakeCommand\", luar.New(ulua.L, action.MakeCommand))\n\tulua.L.SetField(pkg, \"FileComplete\", luar.New(ulua.L, buffer.FileComplete))\n\tulua.L.SetField(pkg, \"HelpComplete\", luar.New(ulua.L, action.HelpComplete))\n\tulua.L.SetField(pkg, \"OptionComplete\", luar.New(ulua.L, action.OptionComplete))\n\tulua.L.SetField(pkg, \"OptionValueComplete\", luar.New(ulua.L, action.OptionValueComplete))\n\tulua.L.SetField(pkg, \"NoComplete\", luar.New(ulua.L, nil))\n\tulua.L.SetField(pkg, \"TryBindKey\", luar.New(ulua.L, action.TryBindKeyPlug))\n\tulua.L.SetField(pkg, \"Reload\", luar.New(ulua.L, action.ReloadConfig))\n\tulua.L.SetField(pkg, \"AddRuntimeFileFromMemory\", luar.New(ulua.L, config.PluginAddRuntimeFileFromMemory))\n\tulua.L.SetField(pkg, \"AddRuntimeFilesFromDirectory\", luar.New(ulua.L, config.PluginAddRuntimeFilesFromDirectory))\n\tulua.L.SetField(pkg, \"AddRuntimeFile\", luar.New(ulua.L, config.PluginAddRuntimeFile))\n\tulua.L.SetField(pkg, \"ListRuntimeFiles\", luar.New(ulua.L, config.PluginListRuntimeFiles))\n\tulua.L.SetField(pkg, \"ReadRuntimeFile\", luar.New(ulua.L, config.PluginReadRuntimeFile))\n\tulua.L.SetField(pkg, \"NewRTFiletype\", luar.New(ulua.L, config.NewRTFiletype))\n\tulua.L.SetField(pkg, \"RTColorscheme\", luar.New(ulua.L, config.RTColorscheme))\n\tulua.L.SetField(pkg, \"RTSyntax\", luar.New(ulua.L, config.RTSyntax))\n\tulua.L.SetField(pkg, \"RTHelp\", luar.New(ulua.L, config.RTHelp))\n\tulua.L.SetField(pkg, \"RTPlugin\", luar.New(ulua.L, config.RTPlugin))\n\tulua.L.SetField(pkg, \"RegisterCommonOption\", luar.New(ulua.L, config.RegisterCommonOptionPlug))\n\tulua.L.SetField(pkg, \"RegisterGlobalOption\", luar.New(ulua.L, config.RegisterGlobalOptionPlug))\n\tulua.L.SetField(pkg, \"GetGlobalOption\", luar.New(ulua.L, config.GetGlobalOption))\n\tulua.L.SetField(pkg, \"SetGlobalOption\", luar.New(ulua.L, action.SetGlobalOptionPlug))\n\tulua.L.SetField(pkg, \"SetGlobalOptionNative\", luar.New(ulua.L, action.SetGlobalOptionNativePlug))\n\tulua.L.SetField(pkg, \"ConfigDir\", luar.New(ulua.L, config.ConfigDir))\n\n\treturn pkg\n}\n\nfunc luaImportMicroShell() *lua.LTable {\n\tpkg := ulua.L.NewTable()\n\n\tulua.L.SetField(pkg, \"ExecCommand\", luar.New(ulua.L, shell.ExecCommand))\n\tulua.L.SetField(pkg, \"RunCommand\", luar.New(ulua.L, shell.RunCommand))\n\tulua.L.SetField(pkg, \"RunBackgroundShell\", luar.New(ulua.L, shell.RunBackgroundShell))\n\tulua.L.SetField(pkg, \"RunInteractiveShell\", luar.New(ulua.L, shell.RunInteractiveShell))\n\tulua.L.SetField(pkg, \"JobStart\", luar.New(ulua.L, shell.JobStart))\n\tulua.L.SetField(pkg, \"JobSpawn\", luar.New(ulua.L, shell.JobSpawn))\n\tulua.L.SetField(pkg, \"JobStop\", luar.New(ulua.L, shell.JobStop))\n\tulua.L.SetField(pkg, \"JobSend\", luar.New(ulua.L, shell.JobSend))\n\tulua.L.SetField(pkg, \"RunTermEmulator\", luar.New(ulua.L, action.RunTermEmulator))\n\tulua.L.SetField(pkg, \"TermEmuSupported\", luar.New(ulua.L, action.TermEmuSupported))\n\n\treturn pkg\n}\n\nfunc luaImportMicroBuffer() *lua.LTable {\n\tpkg := ulua.L.NewTable()\n\n\tulua.L.SetField(pkg, \"NewMessage\", luar.New(ulua.L, buffer.NewMessage))\n\tulua.L.SetField(pkg, \"NewMessageAtLine\", luar.New(ulua.L, buffer.NewMessageAtLine))\n\tulua.L.SetField(pkg, \"MTInfo\", luar.New(ulua.L, buffer.MTInfo))\n\tulua.L.SetField(pkg, \"MTWarning\", luar.New(ulua.L, buffer.MTWarning))\n\tulua.L.SetField(pkg, \"MTError\", luar.New(ulua.L, buffer.MTError))\n\tulua.L.SetField(pkg, \"Loc\", luar.New(ulua.L, func(x, y int) buffer.Loc {\n\t\treturn buffer.Loc{x, y}\n\t}))\n\tulua.L.SetField(pkg, \"SLoc\", luar.New(ulua.L, func(line, row int) display.SLoc {\n\t\treturn display.SLoc{line, row}\n\t}))\n\tulua.L.SetField(pkg, \"BTDefault\", luar.New(ulua.L, buffer.BTDefault.Kind))\n\tulua.L.SetField(pkg, \"BTHelp\", luar.New(ulua.L, buffer.BTHelp.Kind))\n\tulua.L.SetField(pkg, \"BTLog\", luar.New(ulua.L, buffer.BTLog.Kind))\n\tulua.L.SetField(pkg, \"BTScratch\", luar.New(ulua.L, buffer.BTScratch.Kind))\n\tulua.L.SetField(pkg, \"BTRaw\", luar.New(ulua.L, buffer.BTRaw.Kind))\n\tulua.L.SetField(pkg, \"BTInfo\", luar.New(ulua.L, buffer.BTInfo.Kind))\n\tulua.L.SetField(pkg, \"NewBuffer\", luar.New(ulua.L, func(text, path string) *buffer.Buffer {\n\t\treturn buffer.NewBufferFromString(text, path, buffer.BTDefault)\n\t}))\n\tulua.L.SetField(pkg, \"NewBufferFromFile\", luar.New(ulua.L, func(path string) (*buffer.Buffer, error) {\n\t\treturn buffer.NewBufferFromFile(path, buffer.BTDefault)\n\t}))\n\tulua.L.SetField(pkg, \"ByteOffset\", luar.New(ulua.L, buffer.ByteOffset))\n\tulua.L.SetField(pkg, \"Log\", luar.New(ulua.L, buffer.WriteLog))\n\tulua.L.SetField(pkg, \"LogBuf\", luar.New(ulua.L, buffer.GetLogBuf))\n\n\treturn pkg\n}\n\nfunc luaImportMicroUtil() *lua.LTable {\n\tpkg := ulua.L.NewTable()\n\n\tulua.L.SetField(pkg, \"RuneAt\", luar.New(ulua.L, util.LuaRuneAt))\n\tulua.L.SetField(pkg, \"GetLeadingWhitespace\", luar.New(ulua.L, util.LuaGetLeadingWhitespace))\n\tulua.L.SetField(pkg, \"IsWordChar\", luar.New(ulua.L, util.LuaIsWordChar))\n\tulua.L.SetField(pkg, \"String\", luar.New(ulua.L, util.String))\n\tulua.L.SetField(pkg, \"Unzip\", luar.New(ulua.L, util.Unzip))\n\tulua.L.SetField(pkg, \"Version\", luar.New(ulua.L, util.Version))\n\tulua.L.SetField(pkg, \"SemVersion\", luar.New(ulua.L, util.SemVersion))\n\tulua.L.SetField(pkg, \"HttpRequest\", luar.New(ulua.L, util.HttpRequest))\n\tulua.L.SetField(pkg, \"CharacterCountInString\", luar.New(ulua.L, util.CharacterCountInString))\n\tulua.L.SetField(pkg, \"RuneStr\", luar.New(ulua.L, func(r rune) string {\n\t\treturn string(r)\n\t}))\n\n\treturn pkg\n}\n"
  },
  {
    "path": "cmd/micro/micro.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"runtime/pprof\"\n\t\"sort\"\n\t\"strconv\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/go-errors/errors\"\n\tisatty \"github.com/mattn/go-isatty\"\n\t\"github.com/micro-editor/micro/v2/internal/action\"\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n\t\"github.com/micro-editor/micro/v2/internal/clipboard\"\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/micro/v2/internal/shell\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n\t\"github.com/micro-editor/tcell/v2\"\n\tlua \"github.com/yuin/gopher-lua\"\n)\n\nvar (\n\t// Command line flags\n\tflagVersion   = flag.Bool(\"version\", false, \"Show the version number and information\")\n\tflagConfigDir = flag.String(\"config-dir\", \"\", \"Specify a custom location for the configuration directory\")\n\tflagOptions   = flag.Bool(\"options\", false, \"Show all option help\")\n\tflagDebug     = flag.Bool(\"debug\", false, \"Enable debug mode (prints debug info to ./log.txt)\")\n\tflagProfile   = flag.Bool(\"profile\", false, \"Enable CPU profiling (writes profile info to ./micro.prof)\")\n\tflagPlugin    = flag.String(\"plugin\", \"\", \"Plugin command\")\n\tflagClean     = flag.Bool(\"clean\", false, \"Clean configuration directory\")\n\toptionFlags   map[string]*string\n\n\tsighup chan os.Signal\n\n\ttimerChan chan func()\n)\n\nfunc InitFlags() {\n\t// Note: keep this in sync with the man page in assets/packaging/micro.1\n\tflag.Usage = func() {\n\t\tfmt.Println(\"Usage: micro [OPTION]... [FILE]... [+LINE[:COL]] [+/REGEX]\")\n\t\tfmt.Println(\"       micro [OPTION]... [FILE[:LINE[:COL]]]...  (only if the `parsecursor` option is enabled)\")\n\t\tfmt.Println(\"-clean\")\n\t\tfmt.Println(\"    \\tClean the configuration directory and exit\")\n\t\tfmt.Println(\"-config-dir dir\")\n\t\tfmt.Println(\"    \\tSpecify a custom location for the configuration directory\")\n\t\tfmt.Println(\"FILE:LINE[:COL] (only if the `parsecursor` option is enabled)\")\n\t\tfmt.Println(\"FILE +LINE[:COL]\")\n\t\tfmt.Println(\"    \\tSpecify a line and column to start the cursor at when opening a buffer\")\n\t\tfmt.Println(\"+/REGEX\")\n\t\tfmt.Println(\"    \\tSpecify a regex to search for when opening a buffer\")\n\t\tfmt.Println(\"-options\")\n\t\tfmt.Println(\"    \\tShow all options help and exit\")\n\t\tfmt.Println(\"-debug\")\n\t\tfmt.Println(\"    \\tEnable debug mode (enables logging to ./log.txt)\")\n\t\tfmt.Println(\"-profile\")\n\t\tfmt.Println(\"    \\tEnable CPU profiling (writes profile info to ./micro.prof\")\n\t\tfmt.Println(\"    \\tso it can be analyzed later with \\\"go tool pprof micro.prof\\\")\")\n\t\tfmt.Println(\"-version\")\n\t\tfmt.Println(\"    \\tShow the version number and information and exit\")\n\n\t\tfmt.Print(\"\\nMicro's plugins can be managed at the command line with the following commands.\\n\")\n\t\tfmt.Println(\"-plugin install [PLUGIN]...\")\n\t\tfmt.Println(\"    \\tInstall plugin(s)\")\n\t\tfmt.Println(\"-plugin remove [PLUGIN]...\")\n\t\tfmt.Println(\"    \\tRemove plugin(s)\")\n\t\tfmt.Println(\"-plugin update [PLUGIN]...\")\n\t\tfmt.Println(\"    \\tUpdate plugin(s) (if no argument is given, updates all plugins)\")\n\t\tfmt.Println(\"-plugin search [PLUGIN]...\")\n\t\tfmt.Println(\"    \\tSearch for a plugin\")\n\t\tfmt.Println(\"-plugin list\")\n\t\tfmt.Println(\"    \\tList installed plugins\")\n\t\tfmt.Println(\"-plugin available\")\n\t\tfmt.Println(\"    \\tList available plugins\")\n\n\t\tfmt.Print(\"\\nMicro's options can also be set via command line arguments for quick\\nadjustments. For real configuration, please use the settings.json\\nfile (see 'help options').\\n\\n\")\n\t\tfmt.Println(\"-<option> value\")\n\t\tfmt.Println(\"    \\tSet `option` to `value` for this session\")\n\t\tfmt.Println(\"    \\tFor example: `micro -syntax off file.c`\")\n\t\tfmt.Println(\"\\nUse `micro -options` to see the full list of configuration options\")\n\t}\n\n\toptionFlags = make(map[string]*string)\n\n\tfor k, v := range config.DefaultAllSettings() {\n\t\toptionFlags[k] = flag.String(k, \"\", fmt.Sprintf(\"The %s option. Default value: '%v'.\", k, v))\n\t}\n\n\tflag.Parse()\n\n\tif *flagVersion {\n\t\t// If -version was passed\n\t\tfmt.Println(\"Version:\", util.Version)\n\t\tfmt.Println(\"Commit hash:\", util.CommitHash)\n\t\tfmt.Println(\"Compiled on\", util.CompileDate)\n\t\texit(0)\n\t}\n\n\tif *flagOptions {\n\t\t// If -options was passed\n\t\tvar keys []string\n\t\tm := config.DefaultAllSettings()\n\t\tfor k := range m {\n\t\t\tkeys = append(keys, k)\n\t\t}\n\t\tsort.Strings(keys)\n\t\tfor _, k := range keys {\n\t\t\tv := m[k]\n\t\t\tfmt.Printf(\"-%s value\\n\", k)\n\t\t\tfmt.Printf(\"    \\tDefault value: '%v'\\n\", v)\n\t\t}\n\t\texit(0)\n\t}\n\n\tif util.Debug == \"OFF\" && *flagDebug {\n\t\tutil.Debug = \"ON\"\n\t}\n}\n\n// DoPluginFlags parses and executes any flags that require LoadAllPlugins (-plugin and -clean)\nfunc DoPluginFlags() {\n\tif *flagClean || *flagPlugin != \"\" {\n\t\tconfig.LoadAllPlugins()\n\n\t\tif *flagPlugin != \"\" {\n\t\t\targs := flag.Args()\n\n\t\t\tconfig.PluginCommand(os.Stdout, *flagPlugin, args)\n\t\t} else if *flagClean {\n\t\t\tCleanConfig()\n\t\t}\n\n\t\texit(0)\n\t}\n}\n\n// LoadInput determines which files should be loaded into buffers\n// based on the input stored in flag.Args()\nfunc LoadInput(args []string) []*buffer.Buffer {\n\t// There are a number of ways micro should start given its input\n\n\t// 1. If it is given a files in flag.Args(), it should open those\n\n\t// 2. If there is no input file and the input is not a terminal, that means\n\t// something is being piped in and the stdin should be opened in an\n\t// empty buffer\n\n\t// 3. If there is no input file and the input is a terminal, an empty buffer\n\t// should be opened\n\n\tbuffers := make([]*buffer.Buffer, 0, len(args))\n\n\tfiles := make([]string, 0, len(args))\n\n\tflagStartPos := buffer.Loc{-1, -1}\n\tposFlagr := regexp.MustCompile(`^\\+(\\d+)(?::(\\d+))?$`)\n\tposIndex := -1\n\n\tsearchText := \"\"\n\tsearchFlagr := regexp.MustCompile(`^\\+\\/(.+)$`)\n\tsearchIndex := -1\n\n\tfor i, a := range args {\n\t\tposMatch := posFlagr.FindStringSubmatch(a)\n\t\tif len(posMatch) == 3 && posMatch[2] != \"\" {\n\t\t\tline, err := strconv.Atoi(posMatch[1])\n\t\t\tif err != nil {\n\t\t\t\tscreen.TermMessage(err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcol, err := strconv.Atoi(posMatch[2])\n\t\t\tif err != nil {\n\t\t\t\tscreen.TermMessage(err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tflagStartPos = buffer.Loc{col - 1, line - 1}\n\t\t\tposIndex = i\n\t\t} else if len(posMatch) == 3 && posMatch[2] == \"\" {\n\t\t\tline, err := strconv.Atoi(posMatch[1])\n\t\t\tif err != nil {\n\t\t\t\tscreen.TermMessage(err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tflagStartPos = buffer.Loc{0, line - 1}\n\t\t\tposIndex = i\n\t\t} else {\n\t\t\tsearchMatch := searchFlagr.FindStringSubmatch(a)\n\t\t\tif len(searchMatch) == 2 {\n\t\t\t\tsearchText = searchMatch[1]\n\t\t\t\tsearchIndex = i\n\t\t\t} else {\n\t\t\t\tfiles = append(files, a)\n\t\t\t}\n\t\t}\n\t}\n\n\tcommand := buffer.Command{\n\t\tStartCursor:      flagStartPos,\n\t\tSearchRegex:      searchText,\n\t\tSearchAfterStart: searchIndex > posIndex,\n\t}\n\n\tif len(files) > 0 {\n\t\t// Option 1\n\t\t// We go through each file and load it\n\t\tfor i := 0; i < len(files); i++ {\n\t\t\tbuf, err := buffer.NewBufferFromFileWithCommand(files[i], buffer.BTDefault, command)\n\t\t\tif err != nil {\n\t\t\t\tscreen.TermMessage(err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// If the file didn't exist, input will be empty, and we'll open an empty buffer\n\t\t\tbuffers = append(buffers, buf)\n\t\t}\n\t} else {\n\t\tbtype := buffer.BTDefault\n\t\tif !isatty.IsTerminal(os.Stdout.Fd()) {\n\t\t\tbtype = buffer.BTStdout\n\t\t}\n\n\t\tif !isatty.IsTerminal(os.Stdin.Fd()) {\n\t\t\t// Option 2\n\t\t\t// The input is not a terminal, so something is being piped in\n\t\t\t// and we should read from stdin\n\t\t\tinput, err := io.ReadAll(os.Stdin)\n\t\t\tif err != nil {\n\t\t\t\tscreen.TermMessage(\"Error reading from stdin: \", err)\n\t\t\t\tinput = []byte{}\n\t\t\t}\n\t\t\tbuffers = append(buffers, buffer.NewBufferFromStringWithCommand(string(input), \"\", btype, command))\n\t\t} else {\n\t\t\t// Option 3, just open an empty buffer\n\t\t\tbuffers = append(buffers, buffer.NewBufferFromStringWithCommand(\"\", \"\", btype, command))\n\t\t}\n\t}\n\n\treturn buffers\n}\n\nfunc checkBackup(name string) error {\n\ttarget := filepath.Join(config.ConfigDir, name)\n\tbackup := target + util.BackupSuffix\n\tif info, err := os.Stat(backup); err == nil {\n\t\tinput, err := os.ReadFile(backup)\n\t\tif err == nil {\n\t\t\tt := info.ModTime()\n\t\t\tmsg := fmt.Sprintf(buffer.BackupMsg, target, t.Format(\"Mon Jan _2 at 15:04, 2006\"), backup)\n\t\t\tchoice := screen.TermPrompt(msg, []string{\"r\", \"i\", \"a\", \"recover\", \"ignore\", \"abort\"}, true)\n\n\t\t\tif choice%3 == 0 {\n\t\t\t\t// recover\n\t\t\t\terr := os.WriteFile(target, input, util.FileMode)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treturn os.Remove(backup)\n\t\t\t} else if choice%3 == 1 {\n\t\t\t\t// delete\n\t\t\t\treturn os.Remove(backup)\n\t\t\t} else if choice%3 == 2 {\n\t\t\t\t// abort\n\t\t\t\treturn errors.New(\"Aborted\")\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc exit(rc int) {\n\tfor _, b := range buffer.OpenBuffers {\n\t\tif !b.Modified() {\n\t\t\tb.Fini()\n\t\t}\n\t}\n\n\tif screen.Screen != nil {\n\t\tscreen.Screen.Fini()\n\t}\n\n\tos.Exit(rc)\n}\n\nfunc main() {\n\tdefer func() {\n\t\tif util.Stdout.Len() > 0 {\n\t\t\tfmt.Fprint(os.Stdout, util.Stdout.String())\n\t\t}\n\t\texit(0)\n\t}()\n\n\tvar err error\n\n\tInitFlags()\n\n\tif *flagProfile {\n\t\tf, err := os.Create(\"micro.prof\")\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"error creating CPU profile: \", err)\n\t\t}\n\t\tif err := pprof.StartCPUProfile(f); err != nil {\n\t\t\tlog.Fatal(\"error starting CPU profile: \", err)\n\t\t}\n\t\tdefer pprof.StopCPUProfile()\n\t}\n\n\tInitLog()\n\n\terr = config.InitConfigDir(*flagConfigDir)\n\tif err != nil {\n\t\tscreen.TermMessage(err)\n\t}\n\n\tconfig.InitRuntimeFiles(true)\n\tconfig.InitPlugins()\n\n\terr = checkBackup(\"settings.json\")\n\tif err != nil {\n\t\tscreen.TermMessage(err)\n\t\texit(1)\n\t}\n\n\terr = config.ReadSettings()\n\tif err != nil {\n\t\tscreen.TermMessage(err)\n\t}\n\terr = config.InitGlobalSettings()\n\tif err != nil {\n\t\tscreen.TermMessage(err)\n\t}\n\n\t// flag options\n\tfor k, v := range optionFlags {\n\t\tif *v != \"\" {\n\t\t\tnativeValue, err := config.GetNativeValue(k, *v)\n\t\t\tif err != nil {\n\t\t\t\tscreen.TermMessage(err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err = config.OptionIsValid(k, nativeValue); err != nil {\n\t\t\t\tscreen.TermMessage(err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconfig.GlobalSettings[k] = nativeValue\n\t\t\tconfig.VolatileSettings[k] = true\n\t\t}\n\t}\n\n\tDoPluginFlags()\n\n\terr = screen.Init()\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\tfmt.Println(\"Fatal: Micro could not initialize a Screen.\")\n\t\texit(1)\n\t}\n\n\tutil.Sigterm = make(chan os.Signal, 1)\n\tsighup = make(chan os.Signal, 1)\n\tsignal.Notify(util.Sigterm, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGABRT)\n\tsignal.Notify(sighup, syscall.SIGHUP)\n\n\tm := clipboard.SetMethod(config.GetGlobalOption(\"clipboard\").(string))\n\tclipErr := clipboard.Initialize(m)\n\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tif screen.Screen != nil {\n\t\t\t\tscreen.Screen.Fini()\n\t\t\t}\n\t\t\tif e, ok := err.(*lua.ApiError); ok {\n\t\t\t\tfmt.Println(\"Lua API error:\", e)\n\t\t\t} else {\n\t\t\t\tfmt.Println(\"Micro encountered an error:\", errors.Wrap(err, 2).ErrorStack(), \"\\nIf you can reproduce this error, please report it at https://github.com/micro-editor/micro/issues\")\n\t\t\t}\n\t\t\t// immediately backup all buffers with unsaved changes\n\t\t\tfor _, b := range buffer.OpenBuffers {\n\t\t\t\tif b.Modified() {\n\t\t\t\t\tb.Backup()\n\t\t\t\t}\n\t\t\t}\n\t\t\texit(1)\n\t\t}\n\t}()\n\n\terr = config.LoadAllPlugins()\n\tif err != nil {\n\t\tscreen.TermMessage(err)\n\t}\n\n\terr = checkBackup(\"bindings.json\")\n\tif err != nil {\n\t\tscreen.TermMessage(err)\n\t\texit(1)\n\t}\n\n\taction.InitBindings()\n\taction.InitCommands()\n\n\ttimerChan = make(chan func())\n\n\terr = config.RunPluginFn(\"preinit\")\n\tif err != nil {\n\t\tscreen.TermMessage(err)\n\t}\n\n\taction.InitGlobals()\n\tbuffer.SetMessager(action.InfoBar)\n\targs := flag.Args()\n\tb := LoadInput(args)\n\n\tif len(b) == 0 {\n\t\t// No buffers to open\n\t\tscreen.Screen.Fini()\n\t\truntime.Goexit()\n\t}\n\n\taction.InitTabs(b)\n\n\terr = config.RunPluginFn(\"init\")\n\tif err != nil {\n\t\tscreen.TermMessage(err)\n\t}\n\n\terr = config.RunPluginFn(\"postinit\")\n\tif err != nil {\n\t\tscreen.TermMessage(err)\n\t}\n\n\terr = config.InitColorscheme()\n\tif err != nil {\n\t\tscreen.TermMessage(err)\n\t}\n\n\tif clipErr != nil {\n\t\tlog.Println(clipErr, \" or change 'clipboard' option\")\n\t}\n\n\tconfig.StartAutoSave()\n\tif a := config.GetGlobalOption(\"autosave\").(float64); a > 0 {\n\t\tconfig.SetAutoTime(a)\n\t}\n\n\tscreen.Events = make(chan tcell.Event)\n\n\t// Here is the event loop which runs in a separate thread\n\tgo func() {\n\t\tfor {\n\t\t\tscreen.Lock()\n\t\t\te := screen.Screen.PollEvent()\n\t\t\tscreen.Unlock()\n\t\t\tif e != nil {\n\t\t\t\tscreen.Events <- e\n\t\t\t}\n\t\t}\n\t}()\n\n\t// clear the drawchan so we don't redraw excessively\n\t// if someone requested a redraw before we started displaying\n\tfor len(screen.DrawChan()) > 0 {\n\t\t<-screen.DrawChan()\n\t}\n\n\t// wait for initial resize event\n\tselect {\n\tcase event := <-screen.Events:\n\t\taction.Tabs.HandleEvent(event)\n\tcase <-time.After(10 * time.Millisecond):\n\t\t// time out after 10ms\n\t}\n\n\tfor {\n\t\tDoEvent()\n\t}\n}\n\n// DoEvent runs the main action loop of the editor\nfunc DoEvent() {\n\tvar event tcell.Event\n\n\t// Display everything\n\tscreen.Screen.Fill(' ', config.DefStyle)\n\tscreen.Screen.HideCursor()\n\taction.Tabs.Display()\n\tfor _, ep := range action.MainTab().Panes {\n\t\tep.Display()\n\t}\n\taction.MainTab().Display()\n\taction.InfoBar.Display()\n\tscreen.Screen.Show()\n\n\t// Check for new events\n\tselect {\n\tcase f := <-shell.Jobs:\n\t\t// If a new job has finished while running in the background we should execute the callback\n\t\tf.Function(f.Output, f.Args)\n\tcase <-config.Autosave:\n\t\tfor _, b := range buffer.OpenBuffers {\n\t\t\tb.AutoSave()\n\t\t}\n\tcase <-shell.CloseTerms:\n\t\taction.Tabs.CloseTerms()\n\tcase event = <-screen.Events:\n\tcase <-screen.DrawChan():\n\t\tfor len(screen.DrawChan()) > 0 {\n\t\t\t<-screen.DrawChan()\n\t\t}\n\tcase f := <-timerChan:\n\t\tf()\n\tcase <-sighup:\n\t\texit(0)\n\tcase <-util.Sigterm:\n\t\texit(0)\n\t}\n\n\tif e, ok := event.(*tcell.EventError); ok {\n\t\tlog.Println(\"tcell event error: \", e.Error())\n\n\t\tif e.Err() == io.EOF {\n\t\t\t// shutdown due to terminal closing/becoming inaccessible\n\t\t\texit(0)\n\t\t}\n\t\treturn\n\t}\n\n\tif event != nil {\n\t\t_, resize := event.(*tcell.EventResize)\n\t\tif resize {\n\t\t\taction.InfoBar.HandleEvent(event)\n\t\t\taction.Tabs.HandleEvent(event)\n\t\t} else if action.InfoBar.HasPrompt {\n\t\t\taction.InfoBar.HandleEvent(event)\n\t\t} else {\n\t\t\taction.Tabs.HandleEvent(event)\n\t\t}\n\t}\n\n\terr := config.RunPluginFn(\"onAnyEvent\")\n\tif err != nil {\n\t\tscreen.TermMessage(err)\n\t}\n}\n"
  },
  {
    "path": "cmd/micro/micro_test.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/go-errors/errors\"\n\t\"github.com/micro-editor/micro/v2/internal/action\"\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/tcell/v2\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar tempDir string\nvar sim tcell.SimulationScreen\n\nfunc init() {\n\tscreen.Events = make(chan tcell.Event, 8)\n}\n\nfunc startup(args []string) (tcell.SimulationScreen, error) {\n\tvar err error\n\n\ttempDir, err = os.MkdirTemp(\"\", \"micro_test\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = config.InitConfigDir(tempDir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tconfig.InitRuntimeFiles(true)\n\tconfig.InitPlugins()\n\n\terr = config.ReadSettings()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = config.InitGlobalSettings()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ts, err := screen.InitSimScreen()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tscreen.Screen.Fini()\n\t\t\tfmt.Println(\"Micro encountered an error:\", err)\n\t\t\t// immediately backup all buffers with unsaved changes\n\t\t\tfor _, b := range buffer.OpenBuffers {\n\t\t\t\tif b.Modified() {\n\t\t\t\t\tb.Backup()\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Print the stack trace too\n\t\t\tlog.Fatalf(errors.Wrap(err, 2).ErrorStack())\n\t\t}\n\t}()\n\n\terr = config.LoadAllPlugins()\n\tif err != nil {\n\t\tscreen.TermMessage(err)\n\t}\n\n\taction.InitBindings()\n\taction.InitCommands()\n\n\terr = config.InitColorscheme()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tb := LoadInput(args)\n\n\tif len(b) == 0 {\n\t\treturn nil, errors.New(\"No buffers opened\")\n\t}\n\n\taction.InitTabs(b)\n\taction.InitGlobals()\n\n\terr = config.RunPluginFn(\"init\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ts.InjectResize()\n\thandleEvent()\n\n\treturn s, nil\n}\n\nfunc cleanup() {\n\tos.RemoveAll(tempDir)\n}\n\nfunc handleEvent() {\n\tscreen.Lock()\n\te := screen.Screen.PollEvent()\n\tscreen.Unlock()\n\tif e != nil {\n\t\tscreen.Events <- e\n\t}\n\n\tfor len(screen.DrawChan()) > 0 || len(screen.Events) > 0 {\n\t\tDoEvent()\n\t}\n}\n\nfunc injectKey(key tcell.Key, r rune, mod tcell.ModMask) {\n\tsim.InjectKey(key, r, mod)\n\thandleEvent()\n}\n\nfunc injectMouse(x, y int, buttons tcell.ButtonMask, mod tcell.ModMask) {\n\tsim.InjectMouse(x, y, buttons, mod)\n\thandleEvent()\n}\n\nfunc injectString(str string) {\n\t// the tcell simulation screen event channel can only handle\n\t// 10 events at once, so we need to divide up the key events\n\t// into chunks of 10 and handle the 10 events before sending\n\t// another chunk of events\n\titers := len(str) / 10\n\textra := len(str) % 10\n\n\tfor i := 0; i < iters; i++ {\n\t\ts := i * 10\n\t\te := i*10 + 10\n\t\tsim.InjectKeyBytes([]byte(str[s:e]))\n\t\tfor i := 0; i < 10; i++ {\n\t\t\thandleEvent()\n\t\t}\n\t}\n\n\tsim.InjectKeyBytes([]byte(str[len(str)-extra:]))\n\tfor i := 0; i < extra; i++ {\n\t\thandleEvent()\n\t}\n}\n\nfunc openFile(file string) {\n\tinjectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)\n\tinjectString(fmt.Sprintf(\"open %s\", file))\n\tinjectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)\n}\n\nfunc findBuffer(file string) *buffer.Buffer {\n\tvar buf *buffer.Buffer\n\tfor _, b := range buffer.OpenBuffers {\n\t\tif b.Path == file {\n\t\t\tbuf = b\n\t\t}\n\t}\n\treturn buf\n}\n\nfunc createTestFile(t *testing.T, content string) string {\n\tf, err := os.CreateTemp(t.TempDir(), \"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\tif err := f.Close(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}()\n\n\tif _, err := f.WriteString(content); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn f.Name()\n}\n\nfunc TestMain(m *testing.M) {\n\tvar err error\n\tsim, err = startup([]string{})\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\n\tretval := m.Run()\n\tcleanup()\n\n\tos.Exit(retval)\n}\n\nfunc TestSimpleEdit(t *testing.T) {\n\tfile := createTestFile(t, \"base content\")\n\n\topenFile(file)\n\n\tif findBuffer(file) == nil {\n\t\tt.Fatalf(\"Could not find buffer %s\", file)\n\t}\n\n\tinjectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)\n\tinjectKey(tcell.KeyUp, 0, tcell.ModNone)\n\tinjectString(\"first line\")\n\n\t// test both kinds of backspace\n\tfor i := 0; i < len(\"ne\"); i++ {\n\t\tinjectKey(tcell.KeyBackspace, rune(tcell.KeyBackspace), tcell.ModNone)\n\t}\n\tfor i := 0; i < len(\" li\"); i++ {\n\t\tinjectKey(tcell.KeyBackspace2, rune(tcell.KeyBackspace2), tcell.ModNone)\n\t}\n\tinjectString(\"foobar\")\n\n\tinjectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)\n\n\tdata, err := os.ReadFile(file)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassert.Equal(t, \"firstfoobar\\nbase content\\n\", string(data))\n}\n\nfunc TestMouse(t *testing.T) {\n\tfile := createTestFile(t, \"base content\")\n\n\topenFile(file)\n\n\tif findBuffer(file) == nil {\n\t\tt.Fatalf(\"Could not find buffer %s\", file)\n\t}\n\n\t// buffer:\n\t// base content\n\t// the selections need to happen at different locations to avoid a double click\n\tinjectMouse(3, 0, tcell.Button1, tcell.ModNone)\n\tinjectKey(tcell.KeyLeft, 0, tcell.ModNone)\n\tinjectMouse(0, 0, tcell.ButtonNone, tcell.ModNone)\n\tinjectString(\"secondline\")\n\t// buffer:\n\t// secondlinebase content\n\tinjectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)\n\t// buffer:\n\t// secondline\n\t// base content\n\tinjectMouse(2, 0, tcell.Button1, tcell.ModNone)\n\tinjectMouse(0, 0, tcell.ButtonNone, tcell.ModNone)\n\tinjectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)\n\t// buffer:\n\t//\n\t// secondline\n\t// base content\n\tinjectKey(tcell.KeyUp, 0, tcell.ModNone)\n\tinjectString(\"firstline\")\n\t// buffer:\n\t// firstline\n\t// secondline\n\t// base content\n\tinjectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)\n\n\tdata, err := os.ReadFile(file)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassert.Equal(t, \"firstline\\nsecondline\\nbase content\\n\", string(data))\n}\n\nvar srTestStart = `foo\nfoo\nfoofoofoo\nErnleȝe foo æðelen\n`\nvar srTest2 = `test_string\ntest_string\ntest_stringtest_stringtest_string\nErnleȝe test_string æðelen\n`\nvar srTest3 = `test_foo\ntest_string\ntest_footest_stringtest_foo\nErnleȝe test_string æðelen\n`\n\nfunc TestSearchAndReplace(t *testing.T) {\n\tfile := createTestFile(t, srTestStart)\n\n\topenFile(file)\n\n\tif findBuffer(file) == nil {\n\t\tt.Fatalf(\"Could not find buffer %s\", file)\n\t}\n\n\tinjectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)\n\tinjectString(fmt.Sprintf(\"replaceall %s %s\", \"foo\", \"test_string\"))\n\tinjectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)\n\n\tinjectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)\n\n\tdata, err := os.ReadFile(file)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassert.Equal(t, srTest2, string(data))\n\n\tinjectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)\n\tinjectString(fmt.Sprintf(\"replace %s %s\", \"string\", \"foo\"))\n\tinjectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)\n\tinjectString(\"ynyny\")\n\tinjectKey(tcell.KeyEscape, 0, tcell.ModNone)\n\n\tinjectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)\n\n\tdata, err = os.ReadFile(file)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassert.Equal(t, srTest3, string(data))\n}\n\nfunc TestMultiCursor(t *testing.T) {\n\t// TODO\n}\n\nfunc TestSettingsPersistence(t *testing.T) {\n\t// TODO\n}\n\n// more tests (rendering, tabs, plugins)?\n"
  },
  {
    "path": "data/io.github.zyedidia.micro.metainfo.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<component type=\"desktop-application\">\n\t<id>io.github.zyedidia.micro</id>\n\t<launchable type=\"desktop-id\">micro.desktop</launchable>\n\t<name>Micro Text Editor</name>\n\t<summary>A modern and intuitive terminal-based text editor</summary>\n\t<description>\n\t\t<p>\n\t\t\tmicro is a terminal-based text editor that aims to be easy to use and\n\t\t\tintuitive, while also taking advantage of the capabilities of modern terminals.\n\t\t\tIt comes as a single, batteries-included, static binary with no dependencies;\n\t\t\tyou can download and use it right now!\n\t\t</p>\n\t\t<p>\n\t\t\tAs its name indicates, micro aims to be somewhat of a successor to the nano\n\t\t\teditor by being easy to install and use. It strives to be enjoyable as a full-time\n\t\t\teditor for people who prefer to work in a terminal, or those who regularly\n\t\t\tedit files over SSH.\n\t\t</p>\n\t</description>\n\t<metadata_license>MIT</metadata_license>\n\t<project_license>MIT</project_license>\n\t<categories>\n\t\t<category>Development</category>\n\t\t<category>TextEditor</category>\n\t</categories>\n\t<releases>\n\t\t\t<release version=\"2.0.15\" date=\"2025-12-31\"/>\n\t\t\t<release version=\"2.0.14\" date=\"2024-08-27\"/>\n\t\t\t<release version=\"2.0.13\" date=\"2023-10-22\"/>\n\t\t\t<release version=\"2.0.12\" date=\"2023-09-06\"/>\n\t\t\t<release version=\"2.0.11\" date=\"2022-08-01\"/>\n\t</releases>\n\t<provides>\n\t\t<binary>micro</binary>\n\t\t<id>com.github.zyedidia.micro</id>\n\t</provides>\n\t<developer_name>Zachary Yedidia</developer_name>\n\t<screenshots>\n\t\t<screenshot type=\"default\">\n\t\t\t<caption>Micro Text Editor editing its source code</caption>\n\t\t\t<image type=\"source\">https://raw.githubusercontent.com/micro-editor/micro/master/assets/micro-solarized.png</image>\n\t\t</screenshot>\n\t</screenshots>\n\t<content_rating type=\"oars-1.1\" />\n\t<url type=\"homepage\">https://micro-editor.github.io</url>\n\t<url type=\"bugtracker\">https://github.com/micro-editor/micro/issues</url>\n\t<url type=\"faq\">https://micro-editor.github.io/about.html</url>\n\t<url type=\"help\">https://micro-editor.github.io/about.html</url>\n\t<url type=\"contact\">https://github.com/zyedidia</url>\n\t<url type=\"vcs-browser\">https://github.com/micro-editor/micro</url>\n\t<url type=\"contribute\">https://github.com/micro-editor/micro#contributing</url>\n</component>\n"
  },
  {
    "path": "data/micro.json",
    "content": "{\n    \"$comment\": \"https://github.com/micro-editor/micro\",\n    \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n    \"title\": \"options\",\n    \"description\": \"A micro editor config schema\",\n    \"type\": \"object\",\n    \"properties\": {\n        \"autoindent\": {\n            \"description\": \"Whether to use the same indentation as a previous line\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": true\n        },\n        \"autosave\": {\n            \"description\": \"A delay between automatic saves\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"integer\",\n            \"minimum\": 0,\n            \"default\": 0\n        },\n        \"autosu\": {\n            \"description\": \"Whether attempt to use super user privileges\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"backup\": {\n            \"description\": \"Whether to backup all open buffers\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": true\n        },\n        \"backupdir\": {\n            \"description\": \"A directory to store backups\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"string\",\n            \"default\": \"\"\n        },\n        \"basename\": {\n            \"description\": \"Whether to show a basename instead of a full path\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"clipboard\": {\n            \"description\": \"A way to access the system clipboard\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"string\",\n            \"enum\": [\n                \"external\",\n                \"terminal\",\n                \"internal\"\n            ],\n            \"default\": \"external\"\n        },\n        \"colorcolumn\": {\n            \"description\": \"A position to display a column\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"integer\",\n            \"minimum\": 0,\n            \"default\": 0\n        },\n        \"colorscheme\": {\n            \"description\": \"A color scheme\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"string\",\n            \"enum\": [\n                \"atom-dark\",\n                \"bubblegum\",\n                \"cmc-16\",\n                \"cmc-tc\",\n                \"darcula\",\n                \"default\",\n                \"dracula-tc\",\n                \"dukedark-tc\",\n                \"dukelight-tc\",\n                \"dukeubuntu-tc\",\n                \"geany\",\n                \"gotham\",\n                \"gruvbox\",\n                \"gruvbox-tc\",\n                \"material-tc\",\n                \"monokai-dark\",\n                \"monokai\",\n                \"one-dark\",\n                \"railscast\",\n                \"simple\",\n                \"solarized\",\n                \"solarized-tc\",\n                \"sunny-day\",\n                \"twilight\",\n                \"zenburn\"\n            ],\n            \"default\": \"default\"\n        },\n        \"cursorline\": {\n            \"description\": \"Whether to highlight a line with a cursor with a different color\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": true\n        },\n        \"diffgutter\": {\n            \"description\": \"Whether to display diff inticators before lines\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"divchars\": {\n            \"description\": \"Divider chars for vertical and horizontal splits\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"string\",\n            \"default\": \"|-\"\n        },\n        \"divreverse\": {\n            \"description\": \"Whether to use inversed color scheme colors for splits\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": true\n        },\n        \"encoding\": {\n            \"description\": \"An encoding used to open and save files\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"string\",\n            \"default\": \"utf-8\"\n        },\n        \"eofnewline\": {\n            \"description\": \"Whether to add a missing trailing new line\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": true\n        },\n        \"fastdirty\": {\n            \"description\": \"Whether to use a fast algorithm to determine whether a file is changed\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"fileformat\": {\n            \"description\": \"A line ending format\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"string\",\n            \"enum\": [\n                \"unix\",\n                \"dos\"\n            ],\n            \"default\": \"unix\"\n        },\n        \"filetype\": {\n            \"description\": \"A filetype for the current buffer\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"string\",\n            \"default\": \"unknown\"\n        },\n        \"hlsearch\": {\n            \"description\": \"Whether to highlight all instances of a searched text after a successful search\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"incsearch\": {\n            \"description\": \"Whether to enable an incremental search in `Find` prompt\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": true\n        },\n        \"ignorecase\": {\n            \"description\": \"Whether to perform case-insensitive searches\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": true\n        },\n        \"indentchar\": {\n            \"description\": \"An indentation character\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"string\",\n            \"maxLength\": 1,\n            \"default\": \" \"\n        },\n        \"infobar\": {\n            \"description\": \"Whether to enable a line at the bottom where messages are printed\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": true\n        },\n        \"keepautoindent\": {\n            \"description\": \"Whether add a whitespace while using autoindent\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"keymenu\": {\n            \"description\": \"Whether to display nano-style key menu at the bottom\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"matchbrace\": {\n            \"description\": \"Whether to show matching braces\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": true\n        },\n        \"matchbracestyle\": {\n            \"description\": \"Whether to underline or highlight matching braces\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"string\",\n            \"enum\": [\n                \"underline\",\n                \"highlight\"\n            ],\n            \"default\": \"underline\"\n        },\n        \"mkparents\": {\n            \"description\": \"Whether to create missing directories\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"mouse\": {\n            \"description\": \"Whether to enable mouse support\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": true\n        },\n        \"paste\": {\n            \"description\": \"Whether to treat characters sent from the terminal in a single chunk as a paste event\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"parsecursor\": {\n            \"description\": \"Whether to extract a line number and a column to open files with from file names\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"permbackup\": {\n            \"description\": \"Whether to permanently save backups\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"pluginchannels\": {\n            \"description\": \"A file with list of plugin channels\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"string\",\n            \"default\": \"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json\"\n        },\n        \"pluginrepos\": {\n            \"description\": \"Plugin repositories\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"array\",\n            \"uniqueItems\": true,\n            \"items\": {\n                \"description\": \"A pluging repository\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n                \"type\": \"string\"\n            },\n            \"default\": []\n        },\n        \"readonly\": {\n            \"description\": \"Whether to forbid buffer editing\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"rmtrailingws\": {\n            \"description\": \"Whether to remove trailing whitespaces\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"ruler\": {\n            \"description\": \"Whether to display line numbers\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": true\n        },\n        \"relativeruler\": {\n            \"description\": \"Whether to display relative line numbers\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"savecursor\": {\n            \"description\": \"Whether to save cursor position in files\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"savehistory\": {\n            \"description\": \"Whether to save command history between closing and re-opening editor\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": true\n        },\n        \"saveundo\": {\n            \"description\": \"Whether to save undo after closing file\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"scrollbar\": {\n            \"description\": \"Whether to save undo after closing file\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"scrollmargin\": {\n            \"description\": \"A margin at which a view starts scrolling when a cursor approaches an edge of a view\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"integer\",\n            \"default\": 3\n        },\n        \"scrollspeed\": {\n            \"description\": \"Line count to scroll for one scroll event\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"integer\",\n            \"default\": 2\n        },\n        \"smartpaste\": {\n            \"description\": \"Whether to add a leading whitespace while pasting multiple lines\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": true\n        },\n        \"softwrap\": {\n            \"description\": \"Whether to wrap long lines\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"splitbottom\": {\n            \"description\": \"Whether to create a new horizontal split below the current one\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": true\n        },\n        \"splitright\": {\n            \"description\": \"Whether to create a new vertical split right of the current one\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": true\n        },\n        \"statusformatl\": {\n            \"description\": \"Format string of left-justified part of the statusline\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"string\",\n            \"default\": \"$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)\"\n        },\n        \"statusformatr\": {\n            \"description\": \"Format string of right-justified part of the statusline\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"string\",\n            \"default\": \"$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help\"\n        },\n        \"statusline\": {\n            \"description\": \"Whether to display a status line\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": true\n        },\n        \"sucmd\": {\n            \"description\": \"A super user command\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"string\",\n            \"default\": \"sudo\",\n            \"examples\": [\n                \"sudo\",\n                \"doas\"\n            ]\n        },\n        \"syntax\": {\n            \"description\": \"Whether to enable a syntax highlighting\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": true\n        },\n        \"tabmovement\": {\n            \"description\": \"Whether to navigate spaces at the beginning of lines as if they are tabs\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"tabhighlight\": {\n            \"description\": \"Whether to invert tab character colors\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"tabreverse\": {\n            \"description\": \"Whether to reverse tab bar colors\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": true\n        },\n        \"tabsize\": {\n            \"description\": \"A tab size\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"integer\",\n            \"default\": 4\n        },\n        \"tabstospaces\": {\n            \"description\": \"Whether to use spaces instead of tabs\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"useprimary\": {\n            \"description\": \"Whether to use primary clipboard to copy selections in the background\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": true\n        },\n        \"wordwrap\": {\n            \"description\": \"Whether to wrap long lines by words\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        },\n        \"xterm\": {\n            \"description\": \"Whether to assume that the current terminal is `xterm`\\nhttps://github.com/micro-editor/micro/blob/master/runtime/help/options.md#options\",\n            \"type\": \"boolean\",\n            \"default\": false\n        }\n    },\n    \"additionalProperties\": false\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/micro-editor/micro/v2\n\nrequire (\n\tgithub.com/blang/semver v3.5.1+incompatible\n\tgithub.com/dustin/go-humanize v1.0.0\n\tgithub.com/go-errors/errors v1.0.1\n\tgithub.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51\n\tgithub.com/mattn/go-isatty v0.0.20\n\tgithub.com/mattn/go-runewidth v0.0.16\n\tgithub.com/micro-editor/json5 v1.0.1-micro\n\tgithub.com/micro-editor/tcell/v2 v2.0.13\n\tgithub.com/micro-editor/terminal v0.0.0-20250324214352-e587e959c6b5\n\tgithub.com/mitchellh/go-homedir v1.1.0\n\tgithub.com/sergi/go-diff v1.1.0\n\tgithub.com/stretchr/testify v1.4.0\n\tgithub.com/yuin/gopher-lua v1.1.1\n\tgithub.com/zyedidia/clipper v0.1.1\n\tgithub.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3\n\tgolang.org/x/text v0.4.0\n\tgopkg.in/yaml.v2 v2.2.8\n\tlayeh.com/gopher-luar v1.0.11\n)\n\nrequire (\n\tgithub.com/creack/pty v1.1.18 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/gdamore/encoding v1.0.0 // indirect\n\tgithub.com/lucasb-eyer/go-colorful v1.0.3 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/rivo/uniseg v0.2.0 // indirect\n\tgithub.com/zyedidia/poller v1.0.1 // indirect\n\tgolang.org/x/sys v0.30.0 // indirect\n\tgolang.org/x/term v0.29.0 // indirect\n)\n\nreplace github.com/kballard/go-shellquote => github.com/micro-editor/go-shellquote v0.0.0-20250101105543-feb6c39314f5\n\nreplace layeh.com/gopher-luar v1.0.11 => github.com/layeh/gopher-luar v1.0.11\n\ngo 1.19\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=\ngithub.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=\ngithub.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=\ngithub.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=\ngithub.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=\ngithub.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=\ngithub.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=\ngithub.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/layeh/gopher-luar v1.0.11 h1:ss6t9OtykOiETBScJylSMPhuYAtOmpH5rSX10/wCcis=\ngithub.com/layeh/gopher-luar v1.0.11/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=\ngithub.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=\ngithub.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=\ngithub.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/micro-editor/go-shellquote v0.0.0-20250101105543-feb6c39314f5 h1:D7BPnsedXiKo/e8RTFX419/52ICNhU8UKPQGZ/0yiLc=\ngithub.com/micro-editor/go-shellquote v0.0.0-20250101105543-feb6c39314f5/go.mod h1:zaPgW/fDiW4MUfEwxpC+GB/bhvX44NJaNHmRAC9auHQ=\ngithub.com/micro-editor/json5 v1.0.1-micro h1:5Y4MuzhkmW0sQQNPvrIVevIOKi557qsznwjRr4iq1AI=\ngithub.com/micro-editor/json5 v1.0.1-micro/go.mod h1:cmlPHZ1JKOXNse0/3zwwKj/GUpzAVkzx4lZDkpHl4q0=\ngithub.com/micro-editor/tcell/v2 v2.0.13 h1:xyuSpBhSBsUH+bs7FER9IV2/TsQpBmCFiNWJVAEdT68=\ngithub.com/micro-editor/tcell/v2 v2.0.13/go.mod h1:ixpjICpoGp83FZVoLYFJPBwCAslHeTnvgPdhJVPLyy0=\ngithub.com/micro-editor/terminal v0.0.0-20250324214352-e587e959c6b5 h1:czSkYUNmHuWS2lv8VreufENEXZNOCGZcXd744YKf8yM=\ngithub.com/micro-editor/terminal v0.0.0-20250324214352-e587e959c6b5/go.mod h1:OszIG7ockt4osicVHq6gI2QmV4PBDK6H5/Bj8GDGv4Q=\ngithub.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/robertkrimen/otto v0.2.1 h1:FVP0PJ0AHIjC+N4pKCG9yCDz6LHNPCwi/GKID5pGGF0=\ngithub.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=\ngithub.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=\ngithub.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=\ngithub.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=\ngithub.com/zyedidia/clipper v0.1.1 h1:HBgguFNDq/QmSQKBnhy4sMKzILINr139VEgAhftOUTw=\ngithub.com/zyedidia/clipper v0.1.1/go.mod h1:7YApPNiiTZTXdKKZG92G50qj6mnWEX975Sdu65J7YpQ=\ngithub.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3 h1:oMHjjTLfGXVuyOQBYj5/td9WC0mw4g1xDBPovIqmHew=\ngithub.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3/go.mod h1:YKbIYP//Eln8eDgAJGI3IDvR3s4Tv9Z9TGIOumiyQ5c=\ngithub.com/zyedidia/poller v1.0.1 h1:Tt9S3AxAjXwWGNiC2TUdRJkQDZSzCBNVQ4xXiQ7440s=\ngithub.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=\ngolang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=\ngolang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=\ngolang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=\ngolang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\n"
  },
  {
    "path": "internal/action/actions.go",
    "content": "package action\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n\n\tshellquote \"github.com/kballard/go-shellquote\"\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n\t\"github.com/micro-editor/micro/v2/internal/clipboard\"\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/display\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/micro/v2/internal/shell\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n\t\"github.com/micro-editor/tcell/v2\"\n)\n\n// ScrollUp is not an action\nfunc (h *BufPane) ScrollUp(n int) {\n\tv := h.GetView()\n\tv.StartLine = h.Scroll(v.StartLine, -n)\n\th.SetView(v)\n}\n\n// ScrollDown is not an action\nfunc (h *BufPane) ScrollDown(n int) {\n\tv := h.GetView()\n\tv.StartLine = h.Scroll(v.StartLine, n)\n\th.SetView(v)\n}\n\n// ScrollAdjust can be used to shift the view so that the last line is at the\n// bottom if the user has scrolled past the last line.\nfunc (h *BufPane) ScrollAdjust() {\n\tv := h.GetView()\n\tend := h.SLocFromLoc(h.Buf.End())\n\tif h.Diff(v.StartLine, end) < h.BufView().Height-1 {\n\t\tv.StartLine = h.Scroll(end, -h.BufView().Height+1)\n\t}\n\th.SetView(v)\n}\n\n// ScrollReachedEnd returns true if the view is at the end of the buffer,\n// i.e. the last line of the buffer is in the view.\nfunc (h *BufPane) ScrollReachedEnd() bool {\n\tv := h.GetView()\n\tend := h.SLocFromLoc(h.Buf.End())\n\treturn h.Diff(v.StartLine, end) < h.BufView().Height\n}\n\n// MousePress is the event that should happen when a normal click happens\n// This is almost always bound to left click\nfunc (h *BufPane) MousePress(e *tcell.EventMouse) bool {\n\tb := h.Buf\n\tmx, my := e.Position()\n\t// ignore click on the status line\n\tif my >= h.BufView().Y+h.BufView().Height {\n\t\treturn false\n\t}\n\tmouseLoc := h.LocFromVisual(buffer.Loc{mx, my})\n\th.Cursor.Loc = mouseLoc\n\n\tif b.NumCursors() > 1 {\n\t\tb.ClearCursors()\n\t\th.Relocate()\n\t\th.Cursor = h.Buf.GetActiveCursor()\n\t\th.Cursor.Loc = mouseLoc\n\t}\n\tif time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {\n\t\tif h.DoubleClick {\n\t\t\t// Triple click\n\t\t\th.lastClickTime = time.Now()\n\n\t\t\th.TripleClick = true\n\t\t\th.DoubleClick = false\n\n\t\t\th.Cursor.SelectLine()\n\t\t\th.Cursor.CopySelection(clipboard.PrimaryReg)\n\t\t} else {\n\t\t\t// Double click\n\t\t\th.lastClickTime = time.Now()\n\n\t\t\th.DoubleClick = true\n\t\t\th.TripleClick = false\n\n\t\t\th.Cursor.SelectWord()\n\t\t\th.Cursor.CopySelection(clipboard.PrimaryReg)\n\t\t}\n\t} else {\n\t\th.DoubleClick = false\n\t\th.TripleClick = false\n\t\th.lastClickTime = time.Now()\n\n\t\th.Cursor.OrigSelection[0] = h.Cursor.Loc\n\t\th.Cursor.CurSelection[0] = h.Cursor.Loc\n\t\th.Cursor.CurSelection[1] = h.Cursor.Loc\n\t}\n\n\th.Cursor.StoreVisualX()\n\th.lastLoc = mouseLoc\n\th.Relocate()\n\treturn true\n}\n\nfunc (h *BufPane) MouseDrag(e *tcell.EventMouse) bool {\n\tmx, my := e.Position()\n\t// ignore drag on the status line\n\tif my >= h.BufView().Y+h.BufView().Height {\n\t\treturn false\n\t}\n\th.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my})\n\n\tif h.TripleClick {\n\t\th.Cursor.AddLineToSelection()\n\t} else if h.DoubleClick {\n\t\th.Cursor.AddWordToSelection()\n\t} else {\n\t\th.Cursor.SelectTo(h.Cursor.Loc)\n\t}\n\n\th.Cursor.StoreVisualX()\n\th.Relocate()\n\treturn true\n}\n\nfunc (h *BufPane) MouseRelease(e *tcell.EventMouse) bool {\n\t// We could finish the selection based on the release location as in the\n\t// commented out code below, to allow text selections even in a terminal\n\t// that doesn't support mouse motion events. But when the mouse click is\n\t// within the scroll margin, that would cause a scroll and selection\n\t// even for a simple mouse click, which is not good.\n\t// if !h.DoubleClick && !h.TripleClick {\n\t// \tmx, my := e.Position()\n\t// \th.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my})\n\t// \th.Cursor.SetSelectionEnd(h.Cursor.Loc)\n\t// }\n\n\tif h.Cursor.HasSelection() {\n\t\th.Cursor.CopySelection(clipboard.PrimaryReg)\n\t}\n\treturn true\n}\n\n// ScrollUpAction scrolls the view up\nfunc (h *BufPane) ScrollUpAction() bool {\n\th.ScrollUp(util.IntOpt(h.Buf.Settings[\"scrollspeed\"]))\n\treturn true\n}\n\n// ScrollDownAction scrolls the view down\nfunc (h *BufPane) ScrollDownAction() bool {\n\th.ScrollDown(util.IntOpt(h.Buf.Settings[\"scrollspeed\"]))\n\treturn true\n}\n\n// Center centers the view on the cursor\nfunc (h *BufPane) Center() bool {\n\tv := h.GetView()\n\tv.StartLine = h.Scroll(h.SLocFromLoc(h.Cursor.Loc), -h.BufView().Height/2)\n\th.SetView(v)\n\th.ScrollAdjust()\n\treturn true\n}\n\n// CursorToViewTop moves the cursor to the top of the view,\n// offset by scrollmargin unless at the beginning or end of the file\nfunc (h *BufPane) CursorToViewTop() bool {\n\tv := h.GetView()\n\th.Buf.ClearCursors()\n\tscrollmargin := int(h.Buf.Settings[\"scrollmargin\"].(float64))\n\tbStart := display.SLoc{0, 0}\n\tif v.StartLine == bStart {\n\t\tscrollmargin = 0\n\t}\n\th.Cursor.GotoLoc(h.LocFromVLoc(display.VLoc{\n\t\tSLoc:    h.Scroll(v.StartLine, scrollmargin),\n\t\tVisualX: 0,\n\t}))\n\treturn true\n}\n\n// CursorToViewCenter moves the cursor to the center of the view\nfunc (h *BufPane) CursorToViewCenter() bool {\n\tv := h.GetView()\n\th.Buf.ClearCursors()\n\th.Cursor.GotoLoc(h.LocFromVLoc(display.VLoc{\n\t\tSLoc:    h.Scroll(v.StartLine, h.BufView().Height/2),\n\t\tVisualX: 0,\n\t}))\n\treturn true\n}\n\n// CursorToViewBottom moves the cursor to the bottom of the view,\n// offset by scrollmargin unless at the beginning or end of the file\nfunc (h *BufPane) CursorToViewBottom() bool {\n\tv := h.GetView()\n\th.Buf.ClearCursors()\n\tscrollmargin := int(h.Buf.Settings[\"scrollmargin\"].(float64))\n\tbEnd := h.SLocFromLoc(h.Buf.End())\n\tlastLine := h.Scroll(v.StartLine, h.BufView().Height-1)\n\tif lastLine == bEnd {\n\t\tscrollmargin = 0\n\t}\n\th.Cursor.GotoLoc(h.LocFromVLoc(display.VLoc{\n\t\tSLoc:    h.Scroll(lastLine, -scrollmargin),\n\t\tVisualX: 0,\n\t}))\n\treturn true\n}\n\n// MoveCursorUp is not an action\nfunc (h *BufPane) MoveCursorUp(n int) {\n\tif !h.Buf.Settings[\"softwrap\"].(bool) {\n\t\th.Cursor.UpN(n)\n\t} else {\n\t\tvloc := h.VLocFromLoc(h.Cursor.Loc)\n\t\tsloc := h.Scroll(vloc.SLoc, -n)\n\t\tif sloc == vloc.SLoc {\n\t\t\t// we are at the beginning of buffer\n\t\t\th.Cursor.Loc = h.Buf.Start()\n\t\t\th.Cursor.StoreVisualX()\n\t\t} else {\n\t\t\tvloc.SLoc = sloc\n\t\t\tvloc.VisualX = h.Cursor.LastWrappedVisualX\n\t\t\th.Cursor.Loc = h.LocFromVLoc(vloc)\n\t\t}\n\t}\n}\n\n// MoveCursorDown is not an action\nfunc (h *BufPane) MoveCursorDown(n int) {\n\tif !h.Buf.Settings[\"softwrap\"].(bool) {\n\t\th.Cursor.DownN(n)\n\t} else {\n\t\tvloc := h.VLocFromLoc(h.Cursor.Loc)\n\t\tsloc := h.Scroll(vloc.SLoc, n)\n\t\tif sloc == vloc.SLoc {\n\t\t\t// we are at the end of buffer\n\t\t\th.Cursor.Loc = h.Buf.End()\n\t\t\th.Cursor.StoreVisualX()\n\t\t} else {\n\t\t\tvloc.SLoc = sloc\n\t\t\tvloc.VisualX = h.Cursor.LastWrappedVisualX\n\t\t\th.Cursor.Loc = h.LocFromVLoc(vloc)\n\t\t}\n\t}\n}\n\n// CursorUp moves the cursor up\nfunc (h *BufPane) CursorUp() bool {\n\th.Cursor.Deselect(true)\n\th.MoveCursorUp(1)\n\th.Relocate()\n\treturn true\n}\n\n// CursorDown moves the cursor down\nfunc (h *BufPane) CursorDown() bool {\n\tselectionEndNewline := h.Cursor.HasSelection() && h.Cursor.CurSelection[1].X == 0\n\th.Cursor.Deselect(false)\n\tif selectionEndNewline {\n\t\th.Cursor.Start()\n\t} else {\n\t\th.MoveCursorDown(1)\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// CursorLeft moves the cursor left\nfunc (h *BufPane) CursorLeft() bool {\n\tif h.Cursor.HasSelection() {\n\t\th.Cursor.Deselect(true)\n\t} else {\n\t\ttabstospaces := h.Buf.Settings[\"tabstospaces\"].(bool)\n\t\ttabmovement := h.Buf.Settings[\"tabmovement\"].(bool)\n\t\tif tabstospaces && tabmovement {\n\t\t\ttabsize := int(h.Buf.Settings[\"tabsize\"].(float64))\n\t\t\tline := h.Buf.LineBytes(h.Cursor.Y)\n\t\t\tif h.Cursor.X-tabsize >= 0 && util.IsSpaces(line[h.Cursor.X-tabsize:h.Cursor.X]) && util.IsBytesWhitespace(line[0:h.Cursor.X-tabsize]) {\n\t\t\t\tfor i := 0; i < tabsize; i++ {\n\t\t\t\t\th.Cursor.Left()\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\th.Cursor.Left()\n\t\t\t}\n\t\t} else {\n\t\t\th.Cursor.Left()\n\t\t}\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// CursorRight moves the cursor right\nfunc (h *BufPane) CursorRight() bool {\n\tif h.Cursor.HasSelection() {\n\t\th.Cursor.Deselect(false)\n\t} else {\n\t\ttabstospaces := h.Buf.Settings[\"tabstospaces\"].(bool)\n\t\ttabmovement := h.Buf.Settings[\"tabmovement\"].(bool)\n\t\tif tabstospaces && tabmovement {\n\t\t\ttabsize := int(h.Buf.Settings[\"tabsize\"].(float64))\n\t\t\tline := h.Buf.LineBytes(h.Cursor.Y)\n\t\t\tif h.Cursor.X+tabsize < util.CharacterCount(line) && util.IsSpaces(line[h.Cursor.X:h.Cursor.X+tabsize]) && util.IsBytesWhitespace(line[0:h.Cursor.X]) {\n\t\t\t\tfor i := 0; i < tabsize; i++ {\n\t\t\t\t\th.Cursor.Right()\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\th.Cursor.Right()\n\t\t\t}\n\t\t} else {\n\t\t\th.Cursor.Right()\n\t\t}\n\t}\n\n\th.Relocate()\n\treturn true\n}\n\n// WordRight moves the cursor one word to the right\nfunc (h *BufPane) WordRight() bool {\n\th.Cursor.Deselect(false)\n\th.Cursor.WordRight()\n\th.Relocate()\n\treturn true\n}\n\n// WordLeft moves the cursor one word to the left\nfunc (h *BufPane) WordLeft() bool {\n\th.Cursor.Deselect(true)\n\th.Cursor.WordLeft()\n\th.Relocate()\n\treturn true\n}\n\n// SubWordRight moves the cursor one sub-word to the right\nfunc (h *BufPane) SubWordRight() bool {\n\th.Cursor.Deselect(false)\n\th.Cursor.SubWordRight()\n\th.Relocate()\n\treturn true\n}\n\n// SubWordLeft moves the cursor one sub-word to the left\nfunc (h *BufPane) SubWordLeft() bool {\n\th.Cursor.Deselect(true)\n\th.Cursor.SubWordLeft()\n\th.Relocate()\n\treturn true\n}\n\n// SelectUp selects up one line\nfunc (h *BufPane) SelectUp() bool {\n\tif !h.Cursor.HasSelection() {\n\t\th.Cursor.OrigSelection[0] = h.Cursor.Loc\n\t}\n\th.MoveCursorUp(1)\n\th.Cursor.SelectTo(h.Cursor.Loc)\n\th.Relocate()\n\treturn true\n}\n\n// SelectDown selects down one line\nfunc (h *BufPane) SelectDown() bool {\n\tif !h.Cursor.HasSelection() {\n\t\th.Cursor.OrigSelection[0] = h.Cursor.Loc\n\t}\n\th.MoveCursorDown(1)\n\th.Cursor.SelectTo(h.Cursor.Loc)\n\th.Relocate()\n\treturn true\n}\n\n// SelectLeft selects the character to the left of the cursor\nfunc (h *BufPane) SelectLeft() bool {\n\tloc := h.Cursor.Loc\n\tcount := h.Buf.End()\n\tif loc.GreaterThan(count) {\n\t\tloc = count\n\t}\n\tif !h.Cursor.HasSelection() {\n\t\th.Cursor.OrigSelection[0] = loc\n\t}\n\th.Cursor.Left()\n\th.Cursor.SelectTo(h.Cursor.Loc)\n\th.Relocate()\n\treturn true\n}\n\n// SelectRight selects the character to the right of the cursor\nfunc (h *BufPane) SelectRight() bool {\n\tloc := h.Cursor.Loc\n\tcount := h.Buf.End()\n\tif loc.GreaterThan(count) {\n\t\tloc = count\n\t}\n\tif !h.Cursor.HasSelection() {\n\t\th.Cursor.OrigSelection[0] = loc\n\t}\n\th.Cursor.Right()\n\th.Cursor.SelectTo(h.Cursor.Loc)\n\th.Relocate()\n\treturn true\n}\n\n// SelectWordRight selects the word to the right of the cursor\nfunc (h *BufPane) SelectWordRight() bool {\n\tif !h.Cursor.HasSelection() {\n\t\th.Cursor.OrigSelection[0] = h.Cursor.Loc\n\t}\n\th.Cursor.WordRight()\n\th.Cursor.SelectTo(h.Cursor.Loc)\n\th.Relocate()\n\treturn true\n}\n\n// SelectWordLeft selects the word to the left of the cursor\nfunc (h *BufPane) SelectWordLeft() bool {\n\tif !h.Cursor.HasSelection() {\n\t\th.Cursor.OrigSelection[0] = h.Cursor.Loc\n\t}\n\th.Cursor.WordLeft()\n\th.Cursor.SelectTo(h.Cursor.Loc)\n\th.Relocate()\n\treturn true\n}\n\n// SelectSubWordRight selects the sub-word to the right of the cursor\nfunc (h *BufPane) SelectSubWordRight() bool {\n\tif !h.Cursor.HasSelection() {\n\t\th.Cursor.OrigSelection[0] = h.Cursor.Loc\n\t}\n\th.Cursor.SubWordRight()\n\th.Cursor.SelectTo(h.Cursor.Loc)\n\th.Relocate()\n\treturn true\n}\n\n// SelectSubWordLeft selects the sub-word to the left of the cursor\nfunc (h *BufPane) SelectSubWordLeft() bool {\n\tif !h.Cursor.HasSelection() {\n\t\th.Cursor.OrigSelection[0] = h.Cursor.Loc\n\t}\n\th.Cursor.SubWordLeft()\n\th.Cursor.SelectTo(h.Cursor.Loc)\n\th.Relocate()\n\treturn true\n}\n\n// StartOfText moves the cursor to the start of the text of the line\nfunc (h *BufPane) StartOfText() bool {\n\th.Cursor.Deselect(true)\n\th.Cursor.StartOfText()\n\th.Relocate()\n\treturn true\n}\n\n// StartOfTextToggle toggles the cursor between the start of the text of the line\n// and the start of the line\nfunc (h *BufPane) StartOfTextToggle() bool {\n\th.Cursor.Deselect(true)\n\tif h.Cursor.IsStartOfText() {\n\t\th.Cursor.Start()\n\t} else {\n\t\th.Cursor.StartOfText()\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// StartOfLine moves the cursor to the start of the line\nfunc (h *BufPane) StartOfLine() bool {\n\th.Cursor.Deselect(true)\n\th.Cursor.Start()\n\th.Relocate()\n\treturn true\n}\n\n// EndOfLine moves the cursor to the end of the line\nfunc (h *BufPane) EndOfLine() bool {\n\th.Cursor.Deselect(true)\n\th.Cursor.End()\n\th.Relocate()\n\treturn true\n}\n\n// SelectLine selects the entire current line\nfunc (h *BufPane) SelectLine() bool {\n\th.Cursor.SelectLine()\n\th.Relocate()\n\treturn true\n}\n\n// SelectToStartOfText selects to the start of the text on the current line\nfunc (h *BufPane) SelectToStartOfText() bool {\n\tif !h.Cursor.HasSelection() {\n\t\th.Cursor.OrigSelection[0] = h.Cursor.Loc\n\t}\n\th.Cursor.StartOfText()\n\th.Cursor.SelectTo(h.Cursor.Loc)\n\th.Relocate()\n\treturn true\n}\n\n// SelectToStartOfTextToggle toggles the selection between the start of the text\n// on the current line and the start of the line\nfunc (h *BufPane) SelectToStartOfTextToggle() bool {\n\tif !h.Cursor.HasSelection() {\n\t\th.Cursor.OrigSelection[0] = h.Cursor.Loc\n\t}\n\tif h.Cursor.IsStartOfText() {\n\t\th.Cursor.Start()\n\t} else {\n\t\th.Cursor.StartOfText()\n\t}\n\th.Cursor.SelectTo(h.Cursor.Loc)\n\th.Relocate()\n\treturn true\n}\n\n// SelectToStartOfLine selects to the start of the current line\nfunc (h *BufPane) SelectToStartOfLine() bool {\n\tif !h.Cursor.HasSelection() {\n\t\th.Cursor.OrigSelection[0] = h.Cursor.Loc\n\t}\n\th.Cursor.Start()\n\th.Cursor.SelectTo(h.Cursor.Loc)\n\th.Relocate()\n\treturn true\n}\n\n// SelectToEndOfLine selects to the end of the current line\nfunc (h *BufPane) SelectToEndOfLine() bool {\n\tif !h.Cursor.HasSelection() {\n\t\th.Cursor.OrigSelection[0] = h.Cursor.Loc\n\t}\n\th.Cursor.End()\n\th.Cursor.SelectTo(h.Cursor.Loc)\n\th.Relocate()\n\treturn true\n}\n\nfunc (h *BufPane) paragraphPrevious() {\n\tvar line int\n\t// Skip to the first non-empty line\n\tfor line = h.Cursor.Y; line > 0; line-- {\n\t\tif len(h.Buf.LineBytes(line)) != 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\t// Find the first empty line\n\tfor ; line > 0; line-- {\n\t\tif len(h.Buf.LineBytes(line)) == 0 {\n\t\t\th.Cursor.X = 0\n\t\t\th.Cursor.Y = line\n\t\t\tbreak\n\t\t}\n\t}\n\t// If no empty line was found, move the cursor to the start of the buffer\n\tif line == 0 {\n\t\th.Cursor.Loc = h.Buf.Start()\n\t}\n}\n\nfunc (h *BufPane) paragraphNext() {\n\tvar line int\n\t// Skip to the first non-empty line\n\tfor line = h.Cursor.Y; line < h.Buf.LinesNum(); line++ {\n\t\tif len(h.Buf.LineBytes(line)) != 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\t// Find the first empty line\n\tfor ; line < h.Buf.LinesNum(); line++ {\n\t\tif len(h.Buf.LineBytes(line)) == 0 {\n\t\t\th.Cursor.X = 0\n\t\t\th.Cursor.Y = line\n\t\t\tbreak\n\t\t}\n\t}\n\t// If no empty line was found, move the cursor to the end of the buffer\n\tif line == h.Buf.LinesNum() {\n\t\th.Cursor.Loc = h.Buf.End()\n\t}\n}\n\n// ParagraphPrevious moves the cursor to the first empty line that comes before\n// the paragraph closest to the cursor, or beginning of the buffer if there\n// isn't a paragraph\nfunc (h *BufPane) ParagraphPrevious() bool {\n\th.Cursor.Deselect(true)\n\th.paragraphPrevious()\n\th.Relocate()\n\treturn true\n}\n\n// ParagraphNext moves the cursor to the first empty line that comes after the\n// paragraph closest to the cursor, or end of the buffer if there isn't a\n// paragraph\nfunc (h *BufPane) ParagraphNext() bool {\n\th.Cursor.Deselect(true)\n\th.paragraphNext()\n\th.Relocate()\n\treturn true\n}\n\n// SelectToParagraphPrevious selects to the first empty line that comes before\n// the paragraph closest to the cursor, or beginning of the buffer if there\n// isn't a paragraph\nfunc (h *BufPane) SelectToParagraphPrevious() bool {\n\tif !h.Cursor.HasSelection() {\n\t\th.Cursor.OrigSelection[0] = h.Cursor.Loc\n\t}\n\th.paragraphPrevious()\n\th.Cursor.SelectTo(h.Cursor.Loc)\n\th.Relocate()\n\treturn true\n}\n\n// SelectToParagraphNext selects to the first empty line that comes after the\n// paragraph closest to the cursor, or end of the buffer if there isn't a\n// paragraph\nfunc (h *BufPane) SelectToParagraphNext() bool {\n\tif !h.Cursor.HasSelection() {\n\t\th.Cursor.OrigSelection[0] = h.Cursor.Loc\n\t}\n\th.paragraphNext()\n\th.Cursor.SelectTo(h.Cursor.Loc)\n\th.Relocate()\n\treturn true\n}\n\n// Retab changes all tabs to spaces or all spaces to tabs depending\n// on the user's settings\nfunc (h *BufPane) Retab() bool {\n\th.Buf.Retab()\n\th.Relocate()\n\treturn true\n}\n\n// CursorStart moves the cursor to the start of the buffer\nfunc (h *BufPane) CursorStart() bool {\n\th.Cursor.Deselect(true)\n\th.Cursor.X = 0\n\th.Cursor.Y = 0\n\th.Cursor.StoreVisualX()\n\th.Relocate()\n\treturn true\n}\n\n// CursorEnd moves the cursor to the end of the buffer\nfunc (h *BufPane) CursorEnd() bool {\n\th.Cursor.Deselect(true)\n\th.Cursor.Loc = h.Buf.End()\n\th.Cursor.StoreVisualX()\n\th.Relocate()\n\treturn true\n}\n\n// SelectToStart selects the text from the cursor to the start of the buffer\nfunc (h *BufPane) SelectToStart() bool {\n\tif !h.Cursor.HasSelection() {\n\t\th.Cursor.OrigSelection[0] = h.Cursor.Loc\n\t}\n\th.CursorStart()\n\th.Cursor.SelectTo(h.Buf.Start())\n\th.Relocate()\n\treturn true\n}\n\n// SelectToEnd selects the text from the cursor to the end of the buffer\nfunc (h *BufPane) SelectToEnd() bool {\n\tif !h.Cursor.HasSelection() {\n\t\th.Cursor.OrigSelection[0] = h.Cursor.Loc\n\t}\n\th.CursorEnd()\n\th.Cursor.SelectTo(h.Buf.End())\n\th.Relocate()\n\treturn true\n}\n\n// InsertNewline inserts a newline plus possible some whitespace if autoindent is on\nfunc (h *BufPane) InsertNewline() bool {\n\t// Insert a newline\n\tif h.Cursor.HasSelection() {\n\t\th.Cursor.DeleteSelection()\n\t\th.Cursor.ResetSelection()\n\t}\n\n\tws := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))\n\tcx := h.Cursor.X\n\th.Buf.Insert(h.Cursor.Loc, \"\\n\")\n\t// h.Cursor.Right()\n\n\tif h.Buf.Settings[\"autoindent\"].(bool) {\n\t\tif cx < len(ws) {\n\t\t\tws = ws[0:cx]\n\t\t}\n\t\th.Buf.Insert(h.Cursor.Loc, string(ws))\n\t\t// for i := 0; i < len(ws); i++ {\n\t\t// \th.Cursor.Right()\n\t\t// }\n\n\t\t// Remove the whitespaces if keepautoindent setting is off\n\t\tif util.IsSpacesOrTabs(h.Buf.LineBytes(h.Cursor.Y-1)) && !h.Buf.Settings[\"keepautoindent\"].(bool) {\n\t\t\tline := h.Buf.LineBytes(h.Cursor.Y - 1)\n\t\t\th.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: util.CharacterCount(line), Y: h.Cursor.Y - 1})\n\t\t}\n\t}\n\th.Cursor.StoreVisualX()\n\th.Relocate()\n\treturn true\n}\n\n// Backspace deletes the previous character\nfunc (h *BufPane) Backspace() bool {\n\tif h.Cursor.HasSelection() {\n\t\th.Cursor.DeleteSelection()\n\t\th.Cursor.ResetSelection()\n\t} else if h.Cursor.Loc.GreaterThan(h.Buf.Start()) {\n\t\t// We have to do something a bit hacky here because we want to\n\t\t// delete the line by first moving left and then deleting backwards\n\t\t// but the undo redo would place the cursor in the wrong place\n\t\t// So instead we move left, save the position, move back, delete\n\t\t// and restore the position\n\n\t\t// If the user is using spaces instead of tabs and they are deleting\n\t\t// whitespace at the start of the line, we should delete as if it's a\n\t\t// tab (tabSize number of spaces)\n\t\tlineStart := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X)\n\t\ttabSize := int(h.Buf.Settings[\"tabsize\"].(float64))\n\t\tif h.Buf.Settings[\"tabstospaces\"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && util.CharacterCount(lineStart)%tabSize == 0 {\n\t\t\tloc := h.Cursor.Loc\n\t\t\th.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)\n\t\t} else {\n\t\t\tloc := h.Cursor.Loc\n\t\t\th.Buf.Remove(loc.Move(-1, h.Buf), loc)\n\t\t}\n\t}\n\th.Cursor.StoreVisualX()\n\th.Relocate()\n\treturn true\n}\n\n// DeleteWordRight deletes the word to the right of the cursor\nfunc (h *BufPane) DeleteWordRight() bool {\n\th.SelectWordRight()\n\tif h.Cursor.HasSelection() {\n\t\th.Cursor.DeleteSelection()\n\t\th.Cursor.ResetSelection()\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// DeleteWordLeft deletes the word to the left of the cursor\nfunc (h *BufPane) DeleteWordLeft() bool {\n\th.SelectWordLeft()\n\tif h.Cursor.HasSelection() {\n\t\th.Cursor.DeleteSelection()\n\t\th.Cursor.ResetSelection()\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// DeleteSubWordRight deletes the sub-word to the right of the cursor\nfunc (h *BufPane) DeleteSubWordRight() bool {\n\th.SelectSubWordRight()\n\tif h.Cursor.HasSelection() {\n\t\th.Cursor.DeleteSelection()\n\t\th.Cursor.ResetSelection()\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// DeleteSubWordLeft deletes the sub-word to the left of the cursor\nfunc (h *BufPane) DeleteSubWordLeft() bool {\n\th.SelectSubWordLeft()\n\tif h.Cursor.HasSelection() {\n\t\th.Cursor.DeleteSelection()\n\t\th.Cursor.ResetSelection()\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// Delete deletes the next character\nfunc (h *BufPane) Delete() bool {\n\tif h.Cursor.HasSelection() {\n\t\th.Cursor.DeleteSelection()\n\t\th.Cursor.ResetSelection()\n\t} else {\n\t\tloc := h.Cursor.Loc\n\t\tif loc.LessThan(h.Buf.End()) {\n\t\t\th.Buf.Remove(loc, loc.Move(1, h.Buf))\n\t\t}\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// IndentSelection indents the current selection\nfunc (h *BufPane) IndentSelection() bool {\n\tif h.Cursor.HasSelection() {\n\t\tstart := h.Cursor.CurSelection[0]\n\t\tend := h.Cursor.CurSelection[1]\n\t\tif end.Y < start.Y {\n\t\t\tstart, end = end, start\n\t\t\th.Cursor.SetSelectionStart(start)\n\t\t\th.Cursor.SetSelectionEnd(end)\n\t\t}\n\n\t\tstartY := start.Y\n\t\tendY := end.Move(-1, h.Buf).Y\n\t\tendX := end.Move(-1, h.Buf).X\n\t\ttabsize := int(h.Buf.Settings[\"tabsize\"].(float64))\n\t\tindentsize := len(h.Buf.IndentString(tabsize))\n\t\tfor y := startY; y <= endY; y++ {\n\t\t\tif len(h.Buf.LineBytes(y)) > 0 {\n\t\t\t\th.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))\n\t\t\t\tif y == startY && start.X > 0 {\n\t\t\t\t\th.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))\n\t\t\t\t}\n\t\t\t\tif y == endY {\n\t\t\t\t\th.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\th.Buf.RelocateCursors()\n\n\t\th.Relocate()\n\t\treturn true\n\t}\n\treturn false\n}\n\n// IndentLine moves the current line forward one indentation\nfunc (h *BufPane) IndentLine() bool {\n\tif h.Cursor.HasSelection() {\n\t\treturn false\n\t}\n\n\ttabsize := int(h.Buf.Settings[\"tabsize\"].(float64))\n\tindentstr := h.Buf.IndentString(tabsize)\n\th.Buf.Insert(buffer.Loc{X: 0, Y: h.Cursor.Y}, indentstr)\n\th.Buf.RelocateCursors()\n\th.Relocate()\n\treturn true\n}\n\n// OutdentLine moves the current line back one indentation\nfunc (h *BufPane) OutdentLine() bool {\n\tif h.Cursor.HasSelection() {\n\t\treturn false\n\t}\n\n\tfor x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings[\"tabsize\"]))); x++ {\n\t\tif len(util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))) == 0 {\n\t\t\tbreak\n\t\t}\n\t\th.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y}, buffer.Loc{X: 1, Y: h.Cursor.Y})\n\t}\n\th.Buf.RelocateCursors()\n\th.Relocate()\n\treturn true\n}\n\n// OutdentSelection takes the current selection and moves it back one indent level\nfunc (h *BufPane) OutdentSelection() bool {\n\tif h.Cursor.HasSelection() {\n\t\tstart := h.Cursor.CurSelection[0]\n\t\tend := h.Cursor.CurSelection[1]\n\t\tif end.Y < start.Y {\n\t\t\tstart, end = end, start\n\t\t\th.Cursor.SetSelectionStart(start)\n\t\t\th.Cursor.SetSelectionEnd(end)\n\t\t}\n\n\t\tstartY := start.Y\n\t\tendY := end.Move(-1, h.Buf).Y\n\t\tfor y := startY; y <= endY; y++ {\n\t\t\tfor x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings[\"tabsize\"]))); x++ {\n\t\t\t\tif len(util.GetLeadingWhitespace(h.Buf.LineBytes(y))) == 0 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\th.Buf.Remove(buffer.Loc{X: 0, Y: y}, buffer.Loc{X: 1, Y: y})\n\t\t\t}\n\t\t}\n\t\th.Buf.RelocateCursors()\n\n\t\th.Relocate()\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Autocomplete cycles the suggestions and performs autocompletion if there are suggestions\nfunc (h *BufPane) Autocomplete() bool {\n\tb := h.Buf\n\n\tif h.Cursor.HasSelection() {\n\t\treturn false\n\t}\n\n\tif b.HasSuggestions {\n\t\tb.CycleAutocomplete(true)\n\t\treturn true\n\t}\n\n\tif h.Cursor.X == 0 {\n\t\treturn false\n\t}\n\tr := h.Cursor.RuneUnder(h.Cursor.X)\n\tprev := h.Cursor.RuneUnder(h.Cursor.X - 1)\n\tif !util.IsAutocomplete(prev) || util.IsWordChar(r) {\n\t\t// don't autocomplete if cursor is within a word\n\t\treturn false\n\t}\n\n\treturn b.Autocomplete(buffer.BufferComplete)\n}\n\n// CycleAutocompleteBack cycles back in the autocomplete suggestion list\nfunc (h *BufPane) CycleAutocompleteBack() bool {\n\tif h.Cursor.HasSelection() {\n\t\treturn false\n\t}\n\n\tif h.Buf.HasSuggestions {\n\t\th.Buf.CycleAutocomplete(false)\n\t\treturn true\n\t}\n\treturn false\n}\n\n// InsertTab inserts a tab or spaces\nfunc (h *BufPane) InsertTab() bool {\n\tb := h.Buf\n\tindent := b.IndentString(util.IntOpt(b.Settings[\"tabsize\"]))\n\ttabBytes := len(indent)\n\tbytesUntilIndent := tabBytes - (h.Cursor.GetVisualX(false) % tabBytes)\n\tb.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])\n\th.Relocate()\n\treturn true\n}\n\n// SaveAll saves all open buffers\nfunc (h *BufPane) SaveAll() bool {\n\tfor _, b := range buffer.OpenBuffers {\n\t\tb.Save()\n\t}\n\treturn true\n}\n\n// SaveCB performs a save and does a callback at the very end (after all prompts have been resolved)\nfunc (h *BufPane) SaveCB(action string, callback func()) bool {\n\t// If this is an empty buffer, ask for a filename\n\tif h.Buf.Path == \"\" {\n\t\th.SaveAsCB(action, callback)\n\t} else {\n\t\tnoPrompt := h.saveBufToFile(h.Buf.Path, action, callback)\n\t\tif noPrompt {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// Save the buffer to disk\nfunc (h *BufPane) Save() bool {\n\treturn h.SaveCB(\"Save\", nil)\n}\n\n// SaveAsCB performs a save as and does a callback at the very end (after all prompts have been resolved)\n// The callback is only called if the save was successful\nfunc (h *BufPane) SaveAsCB(action string, callback func()) bool {\n\tInfoBar.Prompt(\"Filename: \", \"\", \"Save\", nil, func(resp string, canceled bool) {\n\t\tif !canceled {\n\t\t\t// the filename might or might not be quoted, so unquote first then join the strings.\n\t\t\targs, err := shellquote.Split(resp)\n\t\t\tif err != nil {\n\t\t\t\tInfoBar.Error(\"Error parsing arguments: \", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif len(args) == 0 {\n\t\t\t\tInfoBar.Error(\"No filename given\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfilename := strings.Join(args, \" \")\n\t\t\tfileinfo, err := os.Stat(filename)\n\t\t\tif err != nil {\n\t\t\t\tif errors.Is(err, fs.ErrNotExist) || errors.Is(err, fs.ErrPermission) {\n\t\t\t\t\tnoPrompt := h.saveBufToFile(filename, action, callback)\n\t\t\t\t\tif noPrompt {\n\t\t\t\t\t\th.completeAction(action)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tInfoBar.Error(err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tInfoBar.YNPrompt(\n\t\t\t\t\tfmt.Sprintf(\"The file %s already exists in the directory, would you like to overwrite? Y/n\", fileinfo.Name()),\n\t\t\t\t\tfunc(yes, canceled bool) {\n\t\t\t\t\t\tif yes && !canceled {\n\t\t\t\t\t\t\tnoPrompt := h.saveBufToFile(filename, action, callback)\n\t\t\t\t\t\t\tif noPrompt {\n\t\t\t\t\t\t\t\th.completeAction(action)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t})\n\treturn false\n}\n\n// SaveAs saves the buffer to disk with the given name\nfunc (h *BufPane) SaveAs() bool {\n\treturn h.SaveAsCB(\"SaveAs\", nil)\n}\n\n// This function saves the buffer to `filename` and changes the buffer's path and name\n// to `filename` if the save is successful\n// The callback is only called if the save was successful\nfunc (h *BufPane) saveBufToFile(filename string, action string, callback func()) bool {\n\terr := h.Buf.SaveAs(filename)\n\tif err != nil {\n\t\tif errors.Is(err, fs.ErrPermission) {\n\t\t\tif runtime.GOOS == \"windows\" {\n\t\t\t\tInfoBar.Error(\"Permission denied. Save with sudo not supported on Windows\")\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\tsaveWithSudo := func() {\n\t\t\t\terr = h.Buf.SaveAsWithSudo(filename)\n\t\t\t\tif err != nil {\n\t\t\t\t\tInfoBar.Error(err)\n\t\t\t\t} else {\n\t\t\t\t\tInfoBar.Message(\"Saved \" + filename)\n\t\t\t\t\tif callback != nil {\n\t\t\t\t\t\tcallback()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif h.Buf.Settings[\"autosu\"].(bool) {\n\t\t\t\tsaveWithSudo()\n\t\t\t} else {\n\t\t\t\tInfoBar.YNPrompt(\n\t\t\t\t\tfmt.Sprintf(\"Permission denied. Do you want to save this file using %s? (y,n)\", config.GlobalSettings[\"sucmd\"].(string)),\n\t\t\t\t\tfunc(yes, canceled bool) {\n\t\t\t\t\t\tif yes && !canceled {\n\t\t\t\t\t\t\tsaveWithSudo()\n\t\t\t\t\t\t\th.completeAction(action)\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\treturn false\n\t\t\t}\n\t\t} else {\n\t\t\tInfoBar.Error(err)\n\t\t}\n\t} else {\n\t\tInfoBar.Message(\"Saved \" + filename)\n\t\tif callback != nil {\n\t\t\tcallback()\n\t\t}\n\t}\n\treturn true\n}\n\n// Find opens a prompt and searches forward for the input\nfunc (h *BufPane) Find() bool {\n\treturn h.find(true)\n}\n\n// FindLiteral is the same as Find() but does not support regular expressions\nfunc (h *BufPane) FindLiteral() bool {\n\treturn h.find(false)\n}\n\n// Search searches for a given string/regex in the buffer and selects the next\n// match if a match is found\n// This function behaves the same way as Find and FindLiteral actions:\n// it affects the buffer's LastSearch and LastSearchRegex (saved searches)\n// for use with FindNext and FindPrevious, and turns HighlightSearch on or off\n// according to hlsearch setting\nfunc (h *BufPane) Search(str string, useRegex bool, searchDown bool) error {\n\tmatch, found, err := h.Buf.FindNext(str, h.Buf.Start(), h.Buf.End(), h.Cursor.Loc, searchDown, useRegex)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif found {\n\t\th.Cursor.SetSelectionStart(match[0])\n\t\th.Cursor.SetSelectionEnd(match[1])\n\t\th.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]\n\t\th.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]\n\t\th.GotoLoc(h.Cursor.CurSelection[1])\n\t\th.Buf.LastSearch = str\n\t\th.Buf.LastSearchRegex = useRegex\n\t\th.Buf.HighlightSearch = h.Buf.Settings[\"hlsearch\"].(bool)\n\t} else {\n\t\th.Cursor.ResetSelection()\n\t}\n\treturn nil\n}\n\nfunc (h *BufPane) find(useRegex bool) bool {\n\th.searchOrig = h.Cursor.Loc\n\tprompt := \"Find: \"\n\tif useRegex {\n\t\tprompt = \"Find (regex): \"\n\t}\n\tvar eventCallback func(resp string)\n\tif h.Buf.Settings[\"incsearch\"].(bool) {\n\t\teventCallback = func(resp string) {\n\t\t\tmatch, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)\n\t\t\tif found {\n\t\t\t\th.Cursor.SetSelectionStart(match[0])\n\t\t\t\th.Cursor.SetSelectionEnd(match[1])\n\t\t\t\th.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]\n\t\t\t\th.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]\n\t\t\t\th.GotoLoc(match[1])\n\t\t\t} else {\n\t\t\t\th.GotoLoc(h.searchOrig)\n\t\t\t\th.Cursor.ResetSelection()\n\t\t\t}\n\t\t}\n\t}\n\tfindCallback := func(resp string, canceled bool) {\n\t\t// Finished callback\n\t\tif !canceled {\n\t\t\tmatch, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)\n\t\t\tif err != nil {\n\t\t\t\tInfoBar.Error(err)\n\t\t\t} else if found {\n\t\t\t\th.Cursor.SetSelectionStart(match[0])\n\t\t\t\th.Cursor.SetSelectionEnd(match[1])\n\t\t\t\th.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]\n\t\t\t\th.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]\n\t\t\t\th.GotoLoc(h.Cursor.CurSelection[1])\n\t\t\t\th.Buf.LastSearch = resp\n\t\t\t\th.Buf.LastSearchRegex = useRegex\n\t\t\t\th.Buf.HighlightSearch = h.Buf.Settings[\"hlsearch\"].(bool)\n\t\t\t} else {\n\t\t\t\th.Cursor.ResetSelection()\n\t\t\t\tInfoBar.Message(\"No matches found\")\n\t\t\t}\n\t\t} else {\n\t\t\th.Cursor.ResetSelection()\n\t\t}\n\t}\n\tpattern := string(h.Cursor.GetSelection())\n\tif useRegex && pattern != \"\" {\n\t\tpattern = regexp.QuoteMeta(pattern)\n\t}\n\tif eventCallback != nil && pattern != \"\" {\n\t\teventCallback(pattern)\n\t}\n\tInfoBar.Prompt(prompt, pattern, \"Find\", eventCallback, findCallback)\n\tif pattern != \"\" {\n\t\tInfoBar.SelectAll()\n\t}\n\treturn true\n}\n\n// ToggleHighlightSearch toggles highlighting all instances of the last used search term\nfunc (h *BufPane) ToggleHighlightSearch() bool {\n\th.Buf.HighlightSearch = !h.Buf.HighlightSearch\n\treturn true\n}\n\n// UnhighlightSearch unhighlights all instances of the last used search term\nfunc (h *BufPane) UnhighlightSearch() bool {\n\tif !h.Buf.HighlightSearch {\n\t\treturn false\n\t}\n\th.Buf.HighlightSearch = false\n\treturn true\n}\n\n// ResetSearch resets the last used search term\nfunc (h *BufPane) ResetSearch() bool {\n\tif h.Buf.LastSearch != \"\" {\n\t\th.Buf.LastSearch = \"\"\n\t\treturn true\n\t}\n\treturn false\n}\n\n// FindNext searches forwards for the last used search term\nfunc (h *BufPane) FindNext() bool {\n\tif h.Buf.LastSearch == \"\" {\n\t\treturn false\n\t}\n\t// If the cursor is at the start of a selection and we search we want\n\t// to search from the end of the selection in the case that\n\t// the selection is a search result in which case we wouldn't move at\n\t// at all which would be bad\n\tsearchLoc := h.Cursor.Loc\n\tif h.Cursor.HasSelection() {\n\t\tsearchLoc = h.Cursor.CurSelection[1]\n\t}\n\tmatch, found, err := h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.Buf.LastSearchRegex)\n\tif err != nil {\n\t\tInfoBar.Error(err)\n\t} else if found && searchLoc == match[0] && match[0] == match[1] {\n\t\t// skip empty match at present cursor location\n\t\tif searchLoc == h.Buf.End() {\n\t\t\tsearchLoc = h.Buf.Start()\n\t\t} else {\n\t\t\tsearchLoc = searchLoc.Move(1, h.Buf)\n\t\t}\n\t\tmatch, found, _ = h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.Buf.LastSearchRegex)\n\t}\n\tif found {\n\t\th.Cursor.SetSelectionStart(match[0])\n\t\th.Cursor.SetSelectionEnd(match[1])\n\t\th.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]\n\t\th.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]\n\t\th.GotoLoc(h.Cursor.CurSelection[1])\n\t} else {\n\t\th.Cursor.ResetSelection()\n\t}\n\treturn true\n}\n\n// FindPrevious searches backwards for the last used search term\nfunc (h *BufPane) FindPrevious() bool {\n\tif h.Buf.LastSearch == \"\" {\n\t\treturn false\n\t}\n\t// If the cursor is at the end of a selection and we search we want\n\t// to search from the beginning of the selection in the case that\n\t// the selection is a search result in which case we wouldn't move at\n\t// at all which would be bad\n\tsearchLoc := h.Cursor.Loc\n\tif h.Cursor.HasSelection() {\n\t\tsearchLoc = h.Cursor.CurSelection[0]\n\t}\n\tmatch, found, err := h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.Buf.LastSearchRegex)\n\tif err != nil {\n\t\tInfoBar.Error(err)\n\t} else if found && searchLoc == match[0] && match[0] == match[1] {\n\t\t// skip empty match at present cursor location\n\t\tif searchLoc == h.Buf.Start() {\n\t\t\tsearchLoc = h.Buf.End()\n\t\t} else {\n\t\t\tsearchLoc = searchLoc.Move(-1, h.Buf)\n\t\t}\n\t\tmatch, found, _ = h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.Buf.LastSearchRegex)\n\t}\n\tif found {\n\t\th.Cursor.SetSelectionStart(match[0])\n\t\th.Cursor.SetSelectionEnd(match[1])\n\t\th.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]\n\t\th.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]\n\t\th.GotoLoc(h.Cursor.CurSelection[1])\n\t} else {\n\t\th.Cursor.ResetSelection()\n\t}\n\treturn true\n}\n\n// DiffNext searches forward until the beginning of the next block of diffs\nfunc (h *BufPane) DiffNext() bool {\n\tcur := h.Cursor.Loc.Y\n\tdl, err := h.Buf.FindNextDiffLine(cur, true)\n\tif err != nil {\n\t\treturn false\n\t}\n\th.GotoLoc(buffer.Loc{0, dl})\n\treturn true\n}\n\n// DiffPrevious searches forward until the end of the previous block of diffs\nfunc (h *BufPane) DiffPrevious() bool {\n\tcur := h.Cursor.Loc.Y\n\tdl, err := h.Buf.FindNextDiffLine(cur, false)\n\tif err != nil {\n\t\treturn false\n\t}\n\th.GotoLoc(buffer.Loc{0, dl})\n\treturn true\n}\n\n// Undo undoes the last action\nfunc (h *BufPane) Undo() bool {\n\tif !h.Buf.Undo() {\n\t\treturn false\n\t}\n\tInfoBar.Message(\"Undid action\")\n\th.Relocate()\n\treturn true\n}\n\n// Redo redoes the last action\nfunc (h *BufPane) Redo() bool {\n\tif !h.Buf.Redo() {\n\t\treturn false\n\t}\n\tInfoBar.Message(\"Redid action\")\n\th.Relocate()\n\treturn true\n}\n\nfunc (h *BufPane) selectLines() int {\n\tif h.Cursor.HasSelection() {\n\t\tstart := h.Cursor.CurSelection[0]\n\t\tend := h.Cursor.CurSelection[1]\n\t\tif start.GreaterThan(end) {\n\t\t\tstart, end = end, start\n\t\t}\n\t\tif end.X == 0 {\n\t\t\tend = end.Move(-1, h.Buf)\n\t\t}\n\n\t\th.Cursor.Deselect(true)\n\t\th.Cursor.SetSelectionStart(buffer.Loc{0, start.Y})\n\t\th.Cursor.SetSelectionEnd(buffer.Loc{0, end.Y + 1})\n\t} else {\n\t\th.Cursor.SelectLine()\n\t}\n\n\tnlines := h.Cursor.CurSelection[1].Y - h.Cursor.CurSelection[0].Y\n\tif nlines == 0 && h.Cursor.HasSelection() {\n\t\t// selected last line and it is not empty\n\t\tnlines++\n\t}\n\treturn nlines\n}\n\n// Copy the selection to the system clipboard\nfunc (h *BufPane) Copy() bool {\n\tif !h.Cursor.HasSelection() {\n\t\treturn false\n\t}\n\th.Cursor.CopySelection(clipboard.ClipboardReg)\n\th.freshClip = false\n\tInfoBar.Message(\"Copied selection\")\n\th.Relocate()\n\treturn true\n}\n\n// CopyLine copies the current line to the clipboard. If there is a selection,\n// CopyLine copies all the lines that are (fully or partially) in the selection.\nfunc (h *BufPane) CopyLine() bool {\n\torigLoc := h.Cursor.Loc\n\torigLastVisualX := h.Cursor.LastVisualX\n\torigLastWrappedVisualX := h.Cursor.LastWrappedVisualX\n\torigSelection := h.Cursor.CurSelection\n\n\tnlines := h.selectLines()\n\tif nlines == 0 {\n\t\treturn false\n\t}\n\th.Cursor.CopySelection(clipboard.ClipboardReg)\n\th.freshClip = false\n\tif nlines > 1 {\n\t\tInfoBar.Message(fmt.Sprintf(\"Copied %d lines\", nlines))\n\t} else {\n\t\tInfoBar.Message(\"Copied line\")\n\t}\n\n\th.Cursor.Loc = origLoc\n\th.Cursor.LastVisualX = origLastVisualX\n\th.Cursor.LastWrappedVisualX = origLastWrappedVisualX\n\th.Cursor.CurSelection = origSelection\n\th.Relocate()\n\treturn true\n}\n\n// Cut the selection to the system clipboard\nfunc (h *BufPane) Cut() bool {\n\tif !h.Cursor.HasSelection() {\n\t\treturn false\n\t}\n\th.Cursor.CopySelection(clipboard.ClipboardReg)\n\th.Cursor.DeleteSelection()\n\th.Cursor.ResetSelection()\n\th.freshClip = false\n\tInfoBar.Message(\"Cut selection\")\n\n\th.Relocate()\n\treturn true\n}\n\n// CutLine cuts the current line to the clipboard. If there is a selection,\n// CutLine cuts all the lines that are (fully or partially) in the selection.\nfunc (h *BufPane) CutLine() bool {\n\tnlines := h.selectLines()\n\tif nlines == 0 {\n\t\treturn false\n\t}\n\ttotalLines := nlines\n\tif h.freshClip {\n\t\tif clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {\n\t\t\tInfoBar.Error(err)\n\t\t\treturn false\n\t\t} else {\n\t\t\tclipboard.WriteMulti(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())\n\t\t\ttotalLines = strings.Count(clip, \"\\n\") + nlines\n\t\t}\n\t} else {\n\t\th.Cursor.CopySelection(clipboard.ClipboardReg)\n\t}\n\th.freshClip = true\n\th.Cursor.DeleteSelection()\n\th.Cursor.ResetSelection()\n\th.Cursor.StoreVisualX()\n\tif totalLines > 1 {\n\t\tInfoBar.Message(fmt.Sprintf(\"Cut %d lines\", totalLines))\n\t} else {\n\t\tInfoBar.Message(\"Cut line\")\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// Duplicate the selection\nfunc (h *BufPane) Duplicate() bool {\n\tif !h.Cursor.HasSelection() {\n\t\treturn false\n\t}\n\th.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))\n\tInfoBar.Message(\"Duplicated selection\")\n\th.Relocate()\n\treturn true\n}\n\n// DuplicateLine duplicates the current line. If there is a selection, DuplicateLine\n// duplicates all the lines that are (fully or partially) in the selection.\nfunc (h *BufPane) DuplicateLine() bool {\n\tif h.Cursor.HasSelection() {\n\t\torigLoc := h.Cursor.Loc\n\t\torigLastVisualX := h.Cursor.LastVisualX\n\t\torigLastWrappedVisualX := h.Cursor.LastWrappedVisualX\n\t\torigSelection := h.Cursor.CurSelection\n\n\t\tstart := h.Cursor.CurSelection[0]\n\t\tend := h.Cursor.CurSelection[1]\n\t\tif start.GreaterThan(end) {\n\t\t\tstart, end = end, start\n\t\t}\n\t\tif end.X == 0 {\n\t\t\tend = end.Move(-1, h.Buf)\n\t\t}\n\n\t\th.Cursor.Deselect(true)\n\t\th.Cursor.Loc = end\n\t\th.Cursor.End()\n\t\tfor y := start.Y; y <= end.Y; y++ {\n\t\t\th.Buf.Insert(h.Cursor.Loc, \"\\n\"+string(h.Buf.LineBytes(y)))\n\t\t}\n\n\t\th.Cursor.Loc = origLoc\n\t\th.Cursor.LastVisualX = origLastVisualX\n\t\th.Cursor.LastWrappedVisualX = origLastWrappedVisualX\n\t\th.Cursor.CurSelection = origSelection\n\n\t\tif start.Y < end.Y {\n\t\t\tInfoBar.Message(fmt.Sprintf(\"Duplicated %d lines\", end.Y-start.Y+1))\n\t\t} else {\n\t\t\tInfoBar.Message(\"Duplicated line\")\n\t\t}\n\t} else {\n\t\th.Cursor.End()\n\t\th.Buf.Insert(h.Cursor.Loc, \"\\n\"+string(h.Buf.LineBytes(h.Cursor.Y)))\n\t\tInfoBar.Message(\"Duplicated line\")\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// DeleteLine deletes the current line. If there is a selection, DeleteLine\n// deletes all the lines that are (fully or partially) in the selection.\nfunc (h *BufPane) DeleteLine() bool {\n\tnlines := h.selectLines()\n\tif nlines == 0 {\n\t\treturn false\n\t}\n\th.Cursor.DeleteSelection()\n\th.Cursor.ResetSelection()\n\th.Cursor.StoreVisualX()\n\tif nlines > 1 {\n\t\tInfoBar.Message(fmt.Sprintf(\"Deleted %d lines\", nlines))\n\t} else {\n\t\tInfoBar.Message(\"Deleted line\")\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// MoveLinesUp moves up the current line or selected lines if any\nfunc (h *BufPane) MoveLinesUp() bool {\n\tif h.Cursor.HasSelection() {\n\t\tif h.Cursor.CurSelection[0].Y == 0 {\n\t\t\tInfoBar.Message(\"Cannot move further up\")\n\t\t\treturn false\n\t\t}\n\t\tstart := h.Cursor.CurSelection[0].Y\n\t\tend := h.Cursor.CurSelection[1].Y\n\t\tsel := 1\n\t\tif start > end {\n\t\t\tend, start = start, end\n\t\t\tsel = 0\n\t\t}\n\n\t\tcompensate := false\n\t\tif h.Cursor.CurSelection[sel].X != 0 {\n\t\t\tend++\n\t\t} else {\n\t\t\tcompensate = true\n\t\t}\n\n\t\th.Buf.MoveLinesUp(\n\t\t\tstart,\n\t\t\tend,\n\t\t)\n\t\tif compensate {\n\t\t\th.Cursor.CurSelection[sel].Y -= 1\n\t\t}\n\t} else {\n\t\tif h.Cursor.Loc.Y == 0 {\n\t\t\tInfoBar.Message(\"Cannot move further up\")\n\t\t\treturn false\n\t\t}\n\t\th.Buf.MoveLinesUp(\n\t\t\th.Cursor.Loc.Y,\n\t\t\th.Cursor.Loc.Y+1,\n\t\t)\n\t}\n\n\th.Relocate()\n\treturn true\n}\n\n// MoveLinesDown moves down the current line or selected lines if any\nfunc (h *BufPane) MoveLinesDown() bool {\n\tif h.Cursor.HasSelection() {\n\t\tif h.Cursor.CurSelection[1].Y >= h.Buf.LinesNum() {\n\t\t\tInfoBar.Message(\"Cannot move further down\")\n\t\t\treturn false\n\t\t}\n\t\tstart := h.Cursor.CurSelection[0].Y\n\t\tend := h.Cursor.CurSelection[1].Y\n\t\tsel := 1\n\t\tif start > end {\n\t\t\tend, start = start, end\n\t\t\tsel = 0\n\t\t}\n\n\t\tif h.Cursor.CurSelection[sel].X != 0 {\n\t\t\tend++\n\t\t}\n\n\t\th.Buf.MoveLinesDown(\n\t\t\tstart,\n\t\t\tend,\n\t\t)\n\t} else {\n\t\tif h.Cursor.Loc.Y >= h.Buf.LinesNum()-1 {\n\t\t\tInfoBar.Message(\"Cannot move further down\")\n\t\t\treturn false\n\t\t}\n\t\th.Buf.MoveLinesDown(\n\t\t\th.Cursor.Loc.Y,\n\t\t\th.Cursor.Loc.Y+1,\n\t\t)\n\t}\n\n\th.Relocate()\n\treturn true\n}\n\n// Paste whatever is in the system clipboard into the buffer\n// Delete and paste if the user has a selection\nfunc (h *BufPane) Paste() bool {\n\tclip, err := clipboard.ReadMulti(clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())\n\tif err != nil {\n\t\tInfoBar.Error(err)\n\t} else {\n\t\th.paste(clip)\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// PastePrimary pastes from the primary clipboard (only use on linux)\nfunc (h *BufPane) PastePrimary() bool {\n\tclip, err := clipboard.ReadMulti(clipboard.PrimaryReg, h.Cursor.Num, h.Buf.NumCursors())\n\tif err != nil {\n\t\tInfoBar.Error(err)\n\t} else {\n\t\th.paste(clip)\n\t}\n\th.Relocate()\n\treturn true\n}\n\nfunc (h *BufPane) paste(clip string) {\n\tif h.Buf.Settings[\"smartpaste\"].(bool) {\n\t\tif h.Cursor.X > 0 {\n\t\t\tleadingPasteWS := string(util.GetLeadingWhitespace([]byte(clip)))\n\t\t\tif leadingPasteWS != \" \" && strings.Contains(clip, \"\\n\"+leadingPasteWS) {\n\t\t\t\tleadingWS := string(util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y)))\n\t\t\t\tclip = strings.TrimPrefix(clip, leadingPasteWS)\n\t\t\t\tclip = strings.ReplaceAll(clip, \"\\n\"+leadingPasteWS, \"\\n\"+leadingWS)\n\t\t\t}\n\t\t}\n\t}\n\n\tif h.Cursor.HasSelection() {\n\t\th.Cursor.DeleteSelection()\n\t\th.Cursor.ResetSelection()\n\t}\n\n\th.Buf.Insert(h.Cursor.Loc, clip)\n\t// h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)\n\th.freshClip = false\n\tInfoBar.Message(\"Pasted clipboard\")\n}\n\n// JumpToMatchingBrace moves the cursor to the matching brace if it is\n// currently on a brace\nfunc (h *BufPane) JumpToMatchingBrace() bool {\n\tmatchingBrace, left, found := h.Buf.FindMatchingBrace(h.Cursor.Loc)\n\tif found {\n\t\tif h.Buf.Settings[\"matchbraceleft\"].(bool) {\n\t\t\tif left {\n\t\t\t\th.Cursor.GotoLoc(matchingBrace)\n\t\t\t} else {\n\t\t\t\th.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))\n\t\t\t}\n\t\t} else {\n\t\t\th.Cursor.GotoLoc(matchingBrace)\n\t\t}\n\t\th.Relocate()\n\t\treturn true\n\t}\n\treturn false\n}\n\n// SelectAll selects the entire buffer\nfunc (h *BufPane) SelectAll() bool {\n\th.Cursor.SetSelectionStart(h.Buf.Start())\n\th.Cursor.SetSelectionEnd(h.Buf.End())\n\t// Put the cursor at the beginning\n\th.Cursor.X = 0\n\th.Cursor.Y = 0\n\th.Relocate()\n\treturn true\n}\n\n// OpenFile opens a new file in the buffer\nfunc (h *BufPane) OpenFile() bool {\n\tInfoBar.Prompt(\"> \", \"open \", \"Open\", nil, func(resp string, canceled bool) {\n\t\tif !canceled {\n\t\t\th.HandleCommand(resp)\n\t\t}\n\t})\n\treturn true\n}\n\n// JumpLine asks the user to enter a line number to jump to\nfunc (h *BufPane) JumpLine() bool {\n\tInfoBar.Prompt(\"> \", \"goto \", \"Command\", nil, func(resp string, canceled bool) {\n\t\tif !canceled {\n\t\t\th.HandleCommand(resp)\n\t\t}\n\t})\n\treturn true\n}\n\n// Start moves the viewport to the start of the buffer\nfunc (h *BufPane) Start() bool {\n\tv := h.GetView()\n\tv.StartLine = display.SLoc{0, 0}\n\th.SetView(v)\n\treturn true\n}\n\n// End moves the viewport to the end of the buffer\nfunc (h *BufPane) End() bool {\n\tv := h.GetView()\n\tv.StartLine = h.Scroll(h.SLocFromLoc(h.Buf.End()), -h.BufView().Height+1)\n\th.SetView(v)\n\treturn true\n}\n\n// PageUp scrolls the view up a page\nfunc (h *BufPane) PageUp() bool {\n\tpageOverlap := int(h.Buf.Settings[\"pageoverlap\"].(float64))\n\th.ScrollUp(h.BufView().Height - pageOverlap)\n\treturn true\n}\n\n// PageDown scrolls the view down a page\nfunc (h *BufPane) PageDown() bool {\n\tpageOverlap := int(h.Buf.Settings[\"pageoverlap\"].(float64))\n\th.ScrollDown(h.BufView().Height - pageOverlap)\n\th.ScrollAdjust()\n\treturn true\n}\n\n// SelectPageUp selects up one page\nfunc (h *BufPane) SelectPageUp() bool {\n\tpageOverlap := int(h.Buf.Settings[\"pageoverlap\"].(float64))\n\tscrollAmount := h.BufView().Height - pageOverlap\n\tif !h.Cursor.HasSelection() {\n\t\th.Cursor.OrigSelection[0] = h.Cursor.Loc\n\t}\n\th.MoveCursorUp(scrollAmount)\n\th.Cursor.SelectTo(h.Cursor.Loc)\n\tif h.Cursor.Num == 0 {\n\t\th.ScrollUp(scrollAmount)\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// SelectPageDown selects down one page\nfunc (h *BufPane) SelectPageDown() bool {\n\tpageOverlap := int(h.Buf.Settings[\"pageoverlap\"].(float64))\n\tscrollAmount := h.BufView().Height - pageOverlap\n\tif !h.Cursor.HasSelection() {\n\t\th.Cursor.OrigSelection[0] = h.Cursor.Loc\n\t}\n\th.MoveCursorDown(scrollAmount)\n\th.Cursor.SelectTo(h.Cursor.Loc)\n\tif h.Cursor.Num == 0 && !h.ScrollReachedEnd() {\n\t\th.ScrollDown(scrollAmount)\n\t\th.ScrollAdjust()\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// CursorPageUp places the cursor a page up,\n// moving the view to keep cursor at the same relative position in the view\nfunc (h *BufPane) CursorPageUp() bool {\n\th.Cursor.Deselect(true)\n\tpageOverlap := int(h.Buf.Settings[\"pageoverlap\"].(float64))\n\tscrollAmount := h.BufView().Height - pageOverlap\n\th.MoveCursorUp(scrollAmount)\n\tif h.Cursor.Num == 0 {\n\t\th.ScrollUp(scrollAmount)\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// CursorPageDown places the cursor a page down,\n// moving the view to keep cursor at the same relative position in the view\nfunc (h *BufPane) CursorPageDown() bool {\n\tselectionEndNewline := h.Cursor.HasSelection() && h.Cursor.CurSelection[1].X == 0\n\th.Cursor.Deselect(false)\n\tpageOverlap := int(h.Buf.Settings[\"pageoverlap\"].(float64))\n\tscrollAmount := h.BufView().Height - pageOverlap\n\tif selectionEndNewline {\n\t\tscrollAmount--\n\t}\n\th.MoveCursorDown(scrollAmount)\n\tif h.Cursor.Num == 0 && !h.ScrollReachedEnd() {\n\t\th.ScrollDown(scrollAmount)\n\t\th.ScrollAdjust()\n\t}\n\tif selectionEndNewline {\n\t\th.Cursor.Start()\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// HalfPageUp scrolls the view up half a page\nfunc (h *BufPane) HalfPageUp() bool {\n\th.ScrollUp(h.BufView().Height / 2)\n\treturn true\n}\n\n// HalfPageDown scrolls the view down half a page\nfunc (h *BufPane) HalfPageDown() bool {\n\th.ScrollDown(h.BufView().Height / 2)\n\th.ScrollAdjust()\n\treturn true\n}\n\n// ToggleDiffGutter turns the diff gutter off and on\nfunc (h *BufPane) ToggleDiffGutter() bool {\n\tdiffgutter := !h.Buf.Settings[\"diffgutter\"].(bool)\n\th.Buf.SetOptionNative(\"diffgutter\", diffgutter)\n\tif diffgutter {\n\t\th.Buf.UpdateDiff()\n\t\tInfoBar.Message(\"Enabled diff gutter\")\n\t} else {\n\t\tInfoBar.Message(\"Disabled diff gutter\")\n\t}\n\treturn true\n}\n\n// ToggleRuler turns line numbers off and on\nfunc (h *BufPane) ToggleRuler() bool {\n\truler := !h.Buf.Settings[\"ruler\"].(bool)\n\th.Buf.SetOptionNative(\"ruler\", ruler)\n\tif ruler {\n\t\tInfoBar.Message(\"Enabled ruler\")\n\t} else {\n\t\tInfoBar.Message(\"Disabled ruler\")\n\t}\n\treturn true\n}\n\n// ClearStatus clears the infobar. It is an alias for ClearInfo.\nfunc (h *BufPane) ClearStatus() bool {\n\treturn h.ClearInfo()\n}\n\n// ToggleHelp toggles the help screen\nfunc (h *BufPane) ToggleHelp() bool {\n\tif h.Buf.Type == buffer.BTHelp {\n\t\th.Quit()\n\t} else {\n\t\thsplit := config.GlobalSettings[\"helpsplit\"] == \"hsplit\"\n\t\th.openHelp(\"help\", hsplit, false)\n\t}\n\treturn true\n}\n\n// ToggleKeyMenu toggles the keymenu option and resizes all tabs\nfunc (h *BufPane) ToggleKeyMenu() bool {\n\tconfig.GlobalSettings[\"keymenu\"] = !config.GetGlobalOption(\"keymenu\").(bool)\n\tTabs.Resize()\n\treturn true\n}\n\n// ShellMode opens a terminal to run a shell command\nfunc (h *BufPane) ShellMode() bool {\n\tInfoBar.Prompt(\"$ \", \"\", \"Shell\", nil, func(resp string, canceled bool) {\n\t\tif !canceled {\n\t\t\t// The true here is for openTerm to make the command interactive\n\t\t\tshell.RunInteractiveShell(resp, true, false)\n\t\t}\n\t})\n\n\treturn true\n}\n\n// CommandMode lets the user enter a command\nfunc (h *BufPane) CommandMode() bool {\n\tInfoBar.Prompt(\"> \", \"\", \"Command\", nil, func(resp string, canceled bool) {\n\t\tif !canceled {\n\t\t\th.HandleCommand(resp)\n\t\t}\n\t})\n\treturn true\n}\n\n// ToggleOverwriteMode lets the user toggle the text overwrite mode\nfunc (h *BufPane) ToggleOverwriteMode() bool {\n\th.Buf.OverwriteMode = !h.Buf.OverwriteMode\n\treturn true\n}\n\n// Escape leaves current mode\nfunc (h *BufPane) Escape() bool {\n\treturn true\n}\n\n// Deselect deselects on the current cursor\nfunc (h *BufPane) Deselect() bool {\n\tif !h.Cursor.HasSelection() {\n\t\treturn false\n\t}\n\th.Cursor.Deselect(true)\n\treturn true\n}\n\n// ClearInfo clears the infobar\nfunc (h *BufPane) ClearInfo() bool {\n\tif InfoBar.Msg == \"\" {\n\t\treturn false\n\t}\n\tInfoBar.Message(\"\")\n\treturn true\n}\n\n// ForceQuit closes the tab or view even if there are unsaved changes\n// (no prompt)\nfunc (h *BufPane) ForceQuit() bool {\n\th.Buf.Close()\n\tif len(h.tab.Panes) > 1 {\n\t\th.Unsplit()\n\t} else if len(Tabs.List) > 1 {\n\t\tTabs.RemoveTab(h.splitID)\n\t} else {\n\t\tscreen.Screen.Fini()\n\t\tInfoBar.Close()\n\t\truntime.Goexit()\n\t}\n\treturn true\n}\n\n// closePrompt displays a prompt to save the buffer before closing it to proceed\n// with a different action or command\nfunc (h *BufPane) closePrompt(action string, callback func()) {\n\tInfoBar.YNPrompt(\"Save changes to \"+h.Buf.GetName()+\" before closing? (y,n,esc)\", func(yes, canceled bool) {\n\t\tif !canceled && !yes {\n\t\t\tcallback()\n\t\t} else if !canceled && yes {\n\t\t\th.SaveCB(action, callback)\n\t\t}\n\t})\n}\n\n// Quit this will close the current tab or view that is open\nfunc (h *BufPane) Quit() bool {\n\tif h.Buf.Modified() && !h.Buf.Shared() {\n\t\tif config.GlobalSettings[\"autosave\"].(float64) > 0 && h.Buf.Path != \"\" {\n\t\t\t// autosave on means we automatically save when quitting\n\t\t\th.SaveCB(\"Quit\", func() {\n\t\t\t\th.ForceQuit()\n\t\t\t})\n\t\t} else {\n\t\t\th.closePrompt(\"Quit\", func() {\n\t\t\t\th.ForceQuit()\n\t\t\t})\n\t\t}\n\t} else {\n\t\th.ForceQuit()\n\t}\n\treturn true\n}\n\n// QuitAll quits the whole editor; all splits and tabs\nfunc (h *BufPane) QuitAll() bool {\n\tanyModified := false\n\tfor _, b := range buffer.OpenBuffers {\n\t\tif b.Modified() {\n\t\t\tanyModified = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tquit := func() {\n\t\tbuffer.CloseOpenBuffers()\n\t\tscreen.Screen.Fini()\n\t\tInfoBar.Close()\n\t\truntime.Goexit()\n\t}\n\n\tif anyModified {\n\t\tInfoBar.YNPrompt(\"Quit micro? (all open buffers will be closed without saving)\", func(yes, canceled bool) {\n\t\t\tif !canceled && yes {\n\t\t\t\tquit()\n\t\t\t}\n\t\t})\n\t} else {\n\t\tquit()\n\t}\n\n\treturn true\n}\n\n// AddTab adds a new tab with an empty buffer\nfunc (h *BufPane) AddTab() bool {\n\twidth, height := screen.Screen.Size()\n\tiOffset := config.GetInfoBarOffset()\n\tb := buffer.NewBufferFromString(\"\", \"\", buffer.BTDefault)\n\ttp := NewTabFromBuffer(0, 0, width, height-iOffset, b)\n\tTabs.AddTab(tp)\n\tTabs.SetActive(len(Tabs.List) - 1)\n\n\treturn true\n}\n\n// PreviousTab switches to the previous tab in the tab list\nfunc (h *BufPane) PreviousTab() bool {\n\tif Tabs.Active() == 0 {\n\t\treturn false\n\t}\n\tTabs.SetActive(Tabs.Active() - 1)\n\treturn true\n}\n\n// NextTab switches to the next tab in the tab list\nfunc (h *BufPane) NextTab() bool {\n\tif Tabs.Active() == len(Tabs.List)-1 {\n\t\treturn false\n\t}\n\tTabs.SetActive(Tabs.Active() + 1)\n\treturn true\n}\n\n// FirstTab switches to the first tab in the tab list\nfunc (h *BufPane) FirstTab() bool {\n\tif Tabs.Active() == 0 {\n\t\treturn false\n\t}\n\tTabs.SetActive(0)\n\treturn true\n}\n\n// LastTab switches to the last tab in the tab list\nfunc (h *BufPane) LastTab() bool {\n\tlastTabIndex := len(Tabs.List) - 1\n\tif Tabs.Active() == lastTabIndex {\n\t\treturn false\n\t}\n\tTabs.SetActive(lastTabIndex)\n\treturn true\n}\n\n// VSplitAction opens an empty vertical split\nfunc (h *BufPane) VSplitAction() bool {\n\th.VSplitBuf(buffer.NewBufferFromString(\"\", \"\", buffer.BTDefault))\n\n\treturn true\n}\n\n// HSplitAction opens an empty horizontal split\nfunc (h *BufPane) HSplitAction() bool {\n\th.HSplitBuf(buffer.NewBufferFromString(\"\", \"\", buffer.BTDefault))\n\n\treturn true\n}\n\n// Unsplit closes all splits in the current tab except the active one\nfunc (h *BufPane) Unsplit() bool {\n\ttab := h.tab\n\tn := tab.GetNode(h.splitID)\n\tok := n.Unsplit()\n\tif ok {\n\t\ttab.RemovePane(tab.GetPane(h.splitID))\n\t\ttab.Resize()\n\t\ttab.SetActive(len(tab.Panes) - 1)\n\n\t\treturn true\n\t}\n\treturn false\n}\n\n// NextSplit changes the view to the next split\nfunc (h *BufPane) NextSplit() bool {\n\tif h.tab.active == len(h.tab.Panes)-1 {\n\t\treturn false\n\t}\n\th.tab.SetActive(h.tab.active + 1)\n\treturn true\n}\n\n// PreviousSplit changes the view to the previous split\nfunc (h *BufPane) PreviousSplit() bool {\n\tif h.tab.active == 0 {\n\t\treturn false\n\t}\n\th.tab.SetActive(h.tab.active - 1)\n\treturn true\n}\n\n// FirstSplit changes the view to the first split\nfunc (h *BufPane) FirstSplit() bool {\n\tif h.tab.active == 0 {\n\t\treturn false\n\t}\n\th.tab.SetActive(0)\n\treturn true\n}\n\n// LastSplit changes the view to the last split\nfunc (h *BufPane) LastSplit() bool {\n\tlastPaneIdx := len(h.tab.Panes) - 1\n\tif h.tab.active == lastPaneIdx {\n\t\treturn false\n\t}\n\th.tab.SetActive(lastPaneIdx)\n\treturn true\n}\n\nvar curmacro []any\nvar recordingMacro bool\n\n// ToggleMacro toggles recording of a macro\nfunc (h *BufPane) ToggleMacro() bool {\n\trecordingMacro = !recordingMacro\n\tif recordingMacro {\n\t\tcurmacro = []any{}\n\t\tInfoBar.Message(\"Recording\")\n\t} else {\n\t\tInfoBar.Message(\"Stopped recording\")\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// PlayMacro plays back the most recently recorded macro\nfunc (h *BufPane) PlayMacro() bool {\n\tif recordingMacro {\n\t\treturn false\n\t}\n\tfor _, action := range curmacro {\n\t\tswitch t := action.(type) {\n\t\tcase rune:\n\t\t\th.DoRuneInsert(t)\n\t\tcase BufKeyAction:\n\t\t\tt(h)\n\t\t}\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word\nfunc (h *BufPane) SpawnMultiCursor() bool {\n\tspawner := h.Buf.GetCursor(h.Buf.NumCursors() - 1)\n\tif !spawner.HasSelection() {\n\t\tspawner.SelectWord()\n\t\th.multiWord = true\n\t\th.Relocate()\n\t\treturn true\n\t}\n\n\tsel := spawner.GetSelection()\n\tsearchStart := spawner.CurSelection[1]\n\n\tsearch := string(sel)\n\tsearch = regexp.QuoteMeta(search)\n\tif h.multiWord {\n\t\tsearch = \"\\\\b\" + search + \"\\\\b\"\n\t}\n\tmatch, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)\n\tif err != nil {\n\t\tInfoBar.Error(err)\n\t}\n\tif found {\n\t\tc := buffer.NewCursor(h.Buf, buffer.Loc{})\n\t\tc.SetSelectionStart(match[0])\n\t\tc.SetSelectionEnd(match[1])\n\t\tc.OrigSelection[0] = c.CurSelection[0]\n\t\tc.OrigSelection[1] = c.CurSelection[1]\n\t\tc.Loc = c.CurSelection[1]\n\n\t\th.Buf.AddCursor(c)\n\t\th.Buf.SetCurCursor(h.Buf.NumCursors() - 1)\n\t\th.Buf.MergeCursors()\n\t} else {\n\t\tInfoBar.Message(\"No matches found\")\n\t}\n\n\th.Relocate()\n\treturn true\n}\n\n// SpawnCursorAtLoc spawns a new cursor at a location and merges the cursors\nfunc (h *BufPane) SpawnCursorAtLoc(loc buffer.Loc) *buffer.Cursor {\n\tc := buffer.NewCursor(h.Buf, loc)\n\th.Buf.AddCursor(c)\n\th.Buf.MergeCursors()\n\treturn c\n}\n\n// SpawnMultiCursorUpN is not an action\nfunc (h *BufPane) SpawnMultiCursorUpN(n int) bool {\n\tlastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)\n\tif n > 0 && lastC.Y == 0 {\n\t\treturn false\n\t}\n\tif n < 0 && lastC.Y+1 == h.Buf.LinesNum() {\n\t\treturn false\n\t}\n\n\th.Buf.DeselectCursors()\n\n\tc := buffer.NewCursor(h.Buf, buffer.Loc{lastC.X, lastC.Y - n})\n\tc.LastVisualX = lastC.LastVisualX\n\tc.LastWrappedVisualX = lastC.LastWrappedVisualX\n\tc.X = c.GetCharPosInLine(h.Buf.LineBytes(c.Y), c.LastVisualX)\n\tc.Relocate()\n\n\th.Buf.AddCursor(c)\n\th.Buf.SetCurCursor(h.Buf.NumCursors() - 1)\n\th.Buf.MergeCursors()\n\n\th.Relocate()\n\treturn true\n}\n\n// SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less.\nfunc (h *BufPane) SpawnMultiCursorUp() bool {\n\treturn h.SpawnMultiCursorUpN(1)\n}\n\n// SpawnMultiCursorDown creates additional cursor, at the same X (if possible), one Y more.\nfunc (h *BufPane) SpawnMultiCursorDown() bool {\n\treturn h.SpawnMultiCursorUpN(-1)\n}\n\n// SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection\nfunc (h *BufPane) SpawnMultiCursorSelect() bool {\n\t// Avoid cases where multiple cursors already exist, that would create problems\n\tif h.Buf.NumCursors() > 1 {\n\t\treturn false\n\t}\n\n\tvar startLine int\n\tvar endLine int\n\n\ta, b := h.Cursor.CurSelection[0].Y, h.Cursor.CurSelection[1].Y\n\tif a > b {\n\t\tstartLine, endLine = b, a\n\t} else {\n\t\tstartLine, endLine = a, b\n\t}\n\n\tif h.Cursor.HasSelection() {\n\t\th.Cursor.ResetSelection()\n\t\th.Cursor.GotoLoc(buffer.Loc{0, startLine})\n\n\t\tfor i := startLine; i <= endLine; i++ {\n\t\t\tc := buffer.NewCursor(h.Buf, buffer.Loc{0, i})\n\t\t\tc.StoreVisualX()\n\t\t\th.Buf.AddCursor(c)\n\t\t}\n\t\th.Buf.MergeCursors()\n\t} else {\n\t\treturn false\n\t}\n\tInfoBar.Message(\"Added cursors from selection\")\n\treturn true\n}\n\n// MouseMultiCursor is a mouse action which puts a new cursor at the mouse position,\n// or removes a cursor if it is already there\nfunc (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {\n\tb := h.Buf\n\tmx, my := e.Position()\n\t// ignore click on the status line\n\tif my >= h.BufView().Y+h.BufView().Height {\n\t\treturn false\n\t}\n\tmouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})\n\n\tif h.Buf.NumCursors() > 1 {\n\t\tcursors := h.Buf.GetCursors()\n\t\tfor _, c := range cursors {\n\t\t\tif c.Loc == mouseLoc {\n\t\t\t\th.Buf.RemoveCursor(c.Num)\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\tc := buffer.NewCursor(b, mouseLoc)\n\tb.AddCursor(c)\n\tb.MergeCursors()\n\n\treturn true\n}\n\nfunc (h *BufPane) skipMultiCursor(forward bool) bool {\n\tlastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)\n\tif !lastC.HasSelection() {\n\t\treturn false\n\t}\n\tsel := lastC.GetSelection()\n\tsearchStart := lastC.CurSelection[1]\n\tif !forward {\n\t\tsearchStart = lastC.CurSelection[0]\n\t}\n\n\tsearch := string(sel)\n\tsearch = regexp.QuoteMeta(search)\n\tif h.multiWord {\n\t\tsearch = \"\\\\b\" + search + \"\\\\b\"\n\t}\n\n\tmatch, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, forward, true)\n\tif err != nil {\n\t\tInfoBar.Error(err)\n\t}\n\tif found {\n\t\tlastC.SetSelectionStart(match[0])\n\t\tlastC.SetSelectionEnd(match[1])\n\t\tlastC.OrigSelection[0] = lastC.CurSelection[0]\n\t\tlastC.OrigSelection[1] = lastC.CurSelection[1]\n\t\tlastC.Loc = lastC.CurSelection[1]\n\n\t\th.Buf.MergeCursors()\n\t\th.Buf.SetCurCursor(h.Buf.NumCursors() - 1)\n\t} else {\n\t\tInfoBar.Message(\"No matches found\")\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// SkipMultiCursor moves the current multiple cursor to the next available position\nfunc (h *BufPane) SkipMultiCursor() bool {\n\treturn h.skipMultiCursor(true)\n}\n\n// SkipMultiCursorBack moves the current multiple cursor to the previous available position\nfunc (h *BufPane) SkipMultiCursorBack() bool {\n\treturn h.skipMultiCursor(false)\n}\n\n// RemoveMultiCursor removes the latest multiple cursor\nfunc (h *BufPane) RemoveMultiCursor() bool {\n\tif h.Buf.NumCursors() > 1 {\n\t\th.Buf.RemoveCursor(h.Buf.NumCursors() - 1)\n\t\th.Buf.SetCurCursor(h.Buf.NumCursors() - 1)\n\t\th.Buf.UpdateCursors()\n\t} else if h.multiWord {\n\t\th.multiWord = false\n\t\th.Cursor.Deselect(true)\n\t} else {\n\t\treturn false\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// RemoveAllMultiCursors removes all cursors except the base cursor\nfunc (h *BufPane) RemoveAllMultiCursors() bool {\n\tif h.Buf.NumCursors() > 1 || h.multiWord {\n\t\th.Buf.ClearCursors()\n\t\th.multiWord = false\n\t} else {\n\t\treturn false\n\t}\n\th.Relocate()\n\treturn true\n}\n\n// None is an action that does nothing\nfunc (h *BufPane) None() bool {\n\treturn true\n}\n"
  },
  {
    "path": "internal/action/actions_other.go",
    "content": "//go:build plan9 || nacl || windows\n\npackage action\n\nfunc (*BufPane) Suspend() bool {\n\tInfoBar.Error(\"Suspend is only supported on BSD/Linux\")\n\treturn false\n}\n"
  },
  {
    "path": "internal/action/actions_posix.go",
    "content": "//go:build linux || darwin || dragonfly || solaris || openbsd || netbsd || freebsd\n\npackage action\n\nimport (\n\t\"syscall\"\n\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n)\n\n// Suspend sends micro to the background. This is the same as pressing CtrlZ in most unix programs.\n// This only works on linux and has no default binding.\n// This code was adapted from the suspend code in nsf/godit\nfunc (*BufPane) Suspend() bool {\n\tscreenb := screen.TempFini()\n\n\t// suspend the process\n\tpid := syscall.Getpid()\n\terr := syscall.Kill(pid, syscall.SIGSTOP)\n\tif err != nil {\n\t\tscreen.TermMessage(err)\n\t}\n\n\tscreen.TempStart(screenb)\n\n\treturn false\n}\n"
  },
  {
    "path": "internal/action/bindings.go",
    "content": "package action\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\t\"unicode\"\n\n\t\"github.com/micro-editor/json5\"\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n\t\"github.com/micro-editor/tcell/v2\"\n)\n\nvar Binder = map[string]func(e Event, action string){\n\t\"command\":  InfoMapEvent,\n\t\"buffer\":   BufMapEvent,\n\t\"terminal\": TermMapEvent,\n}\n\nfunc writeFile(name string, txt []byte) error {\n\treturn util.SafeWrite(name, txt, false)\n}\n\nfunc createBindingsIfNotExist(fname string) {\n\tif _, e := os.Stat(fname); errors.Is(e, fs.ErrNotExist) {\n\t\twriteFile(fname, []byte(\"{}\"))\n\t}\n}\n\n// InitBindings intializes the bindings map by reading from bindings.json\nfunc InitBindings() {\n\tvar parsed map[string]any\n\n\tfilename := filepath.Join(config.ConfigDir, \"bindings.json\")\n\tcreateBindingsIfNotExist(filename)\n\n\tif _, e := os.Stat(filename); e == nil {\n\t\tinput, err := os.ReadFile(filename)\n\t\tif err != nil {\n\t\t\tscreen.TermMessage(\"Error reading bindings.json file: \" + err.Error())\n\t\t\treturn\n\t\t}\n\n\t\terr = json5.Unmarshal(input, &parsed)\n\t\tif err != nil {\n\t\t\tscreen.TermMessage(\"Error reading bindings.json:\", err.Error())\n\t\t}\n\t}\n\n\tfor p, bind := range Binder {\n\t\tdefaults := DefaultBindings(p)\n\n\t\tfor k, v := range defaults {\n\t\t\tBindKey(k, v, bind)\n\t\t}\n\t}\n\n\tfor k, v := range parsed {\n\t\tswitch val := v.(type) {\n\t\tcase string:\n\t\t\tBindKey(k, val, Binder[\"buffer\"])\n\t\tcase map[string]any:\n\t\t\tbind, ok := Binder[k]\n\t\t\tif !ok || bind == nil {\n\t\t\t\tscreen.TermMessage(fmt.Sprintf(\"%s is not a valid pane type\", k))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfor e, a := range val {\n\t\t\t\ts, ok := a.(string)\n\t\t\t\tif !ok {\n\t\t\t\t\tscreen.TermMessage(\"Error reading bindings.json: non-string and non-map entry\", k)\n\t\t\t\t} else {\n\t\t\t\t\tBindKey(e, s, bind)\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tscreen.TermMessage(\"Error reading bindings.json: non-string and non-map entry\", k)\n\t\t}\n\t}\n}\n\nfunc BindKey(k, v string, bind func(e Event, a string)) {\n\tevent, err := findEvent(k)\n\tif err != nil {\n\t\tscreen.TermMessage(err)\n\t\treturn\n\t}\n\n\tif strings.HasPrefix(k, \"\\x1b\") {\n\t\tscreen.RegisterRawSeq(k)\n\t}\n\n\tbind(event, v)\n\n\t// switch e := event.(type) {\n\t// case KeyEvent:\n\t// \tInfoMapKey(e, v)\n\t// case KeySequenceEvent:\n\t// \tInfoMapKey(e, v)\n\t// case MouseEvent:\n\t// \tInfoMapMouse(e, v)\n\t// case RawEvent:\n\t// \tInfoMapKey(e, v)\n\t// }\n}\n\nvar r = regexp.MustCompile(\"<(.+?)>\")\n\nfunc findEvents(k string) (b KeySequenceEvent, ok bool, err error) {\n\tvar events []Event = nil\n\tfor len(k) > 0 {\n\t\tgroups := r.FindStringSubmatchIndex(k)\n\n\t\tif len(groups) > 3 {\n\t\t\tif events == nil {\n\t\t\t\tevents = make([]Event, 0, 3)\n\t\t\t}\n\n\t\t\te, ok := findSingleEvent(k[groups[2]:groups[3]])\n\t\t\tif !ok {\n\t\t\t\treturn KeySequenceEvent{}, false, errors.New(\"Invalid event \" + k[groups[2]:groups[3]])\n\t\t\t}\n\n\t\t\tevents = append(events, e)\n\n\t\t\tk = k[groups[3]+1:]\n\t\t} else {\n\t\t\treturn KeySequenceEvent{}, false, nil\n\t\t}\n\t}\n\n\treturn KeySequenceEvent{events}, true, nil\n}\n\n// findSingleEvent will find binding Key 'b' using string 'k'\nfunc findSingleEvent(k string) (b Event, ok bool) {\n\tmodifiers := tcell.ModNone\n\n\t// First, we'll strip off all the modifiers in the name and add them to the\n\t// ModMask\nmodSearch:\n\tfor {\n\t\tswitch {\n\t\tcase strings.HasPrefix(k, \"-\") && k != \"-\":\n\t\t\t// We optionally support dashes between modifiers\n\t\t\tk = k[1:]\n\t\tcase strings.HasPrefix(k, \"Ctrl\") && k != \"CtrlH\":\n\t\t\t// CtrlH technically does not have a 'Ctrl' modifier because it is really backspace\n\t\t\tk = k[4:]\n\t\t\tmodifiers |= tcell.ModCtrl\n\t\tcase strings.HasPrefix(k, \"Alt\"):\n\t\t\tk = k[3:]\n\t\t\tmodifiers |= tcell.ModAlt\n\t\tcase strings.HasPrefix(k, \"Shift\"):\n\t\t\tk = k[5:]\n\t\t\tmodifiers |= tcell.ModShift\n\t\tcase strings.HasPrefix(k, \"\\x1b\"):\n\t\t\treturn RawEvent{\n\t\t\t\tesc: k,\n\t\t\t}, true\n\t\tdefault:\n\t\t\tbreak modSearch\n\t\t}\n\t}\n\n\tif k == \"\" {\n\t\treturn KeyEvent{}, false\n\t}\n\n\t// Control is handled in a special way, since the terminal sends explicitly\n\t// marked escape sequences for control keys\n\t// We should check for Control keys first\n\tif modifiers&tcell.ModCtrl != 0 {\n\t\t// see if the key is in bindingKeys with the Ctrl prefix.\n\t\tk = string(unicode.ToUpper(rune(k[0]))) + k[1:]\n\t\tif code, ok := keyEvents[\"Ctrl\"+k]; ok {\n\t\t\treturn KeyEvent{\n\t\t\t\tcode: code,\n\t\t\t\tmod:  modifiers,\n\t\t\t}, true\n\t\t}\n\t}\n\n\t// See if we can find the key in bindingKeys\n\tif code, ok := keyEvents[k]; ok {\n\t\treturn KeyEvent{\n\t\t\tcode: code,\n\t\t\tmod:  modifiers,\n\t\t}, true\n\t}\n\n\tvar mstate MouseState = MousePress\n\tif strings.HasSuffix(k, \"Drag\") {\n\t\tk = k[:len(k)-4]\n\t\tmstate = MouseDrag\n\t} else if strings.HasSuffix(k, \"Release\") {\n\t\tk = k[:len(k)-7]\n\t\tmstate = MouseRelease\n\t}\n\t// See if we can find the key in bindingMouse\n\tif code, ok := mouseEvents[k]; ok {\n\t\treturn MouseEvent{\n\t\t\tbtn:   code,\n\t\t\tmod:   modifiers,\n\t\t\tstate: mstate,\n\t\t}, true\n\t}\n\n\t// If we were given one character, then we've got a rune.\n\tif len(k) == 1 {\n\t\treturn KeyEvent{\n\t\t\tcode: tcell.KeyRune,\n\t\t\tmod:  modifiers,\n\t\t\tr:    rune(k[0]),\n\t\t}, true\n\t}\n\n\t// We don't know what happened.\n\treturn KeyEvent{}, false\n}\n\nfunc findEvent(k string) (Event, error) {\n\tvar event Event\n\tevent, ok, err := findEvents(k)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !ok {\n\t\tevent, ok = findSingleEvent(k)\n\t\tif !ok {\n\t\t\treturn nil, errors.New(k + \" is not a bindable event\")\n\t\t}\n\t}\n\n\treturn event, nil\n}\n\nfunc eventsEqual(e1 Event, e2 Event) bool {\n\tseq1, ok1 := e1.(KeySequenceEvent)\n\tseq2, ok2 := e2.(KeySequenceEvent)\n\tif ok1 && ok2 {\n\t\tif len(seq1.keys) != len(seq2.keys) {\n\t\t\treturn false\n\t\t}\n\t\tfor i := 0; i < len(seq1.keys); i++ {\n\t\t\tif seq1.keys[i] != seq2.keys[i] {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\n\treturn e1 == e2\n}\n\n// TryBindKeyPlug tries to bind a key for the plugin without writing to bindings.json.\n// This operation can be rejected by lockbindings to prevent unexpected actions by the user.\nfunc TryBindKeyPlug(k, v string, overwrite bool) (bool, error) {\n\tif l, ok := config.GlobalSettings[\"lockbindings\"]; ok && l.(bool) {\n\t\treturn false, errors.New(\"bindings is locked by the user\")\n\t}\n\treturn TryBindKey(k, v, overwrite, false)\n}\n\n// TryBindKey tries to bind a key by writing to config.ConfigDir/bindings.json\n// Returns true if the keybinding already existed or is binded successfully and a possible error\nfunc TryBindKey(k, v string, overwrite bool, writeToFile bool) (bool, error) {\n\tvar e error\n\tvar parsed map[string]any\n\n\tfilename := filepath.Join(config.ConfigDir, \"bindings.json\")\n\tcreateBindingsIfNotExist(filename)\n\tif _, e = os.Stat(filename); e == nil {\n\t\tinput, err := os.ReadFile(filename)\n\t\tif err != nil {\n\t\t\treturn false, errors.New(\"Error reading bindings.json file: \" + err.Error())\n\t\t}\n\n\t\terr = json5.Unmarshal(input, &parsed)\n\t\tif err != nil {\n\t\t\treturn false, errors.New(\"Error reading bindings.json: \" + err.Error())\n\t\t}\n\n\t\tkey, err := findEvent(k)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tfound := false\n\t\tvar ev string\n\t\tfor ev = range parsed {\n\t\t\tif e, err := findEvent(ev); err == nil {\n\t\t\t\tif eventsEqual(e, key) {\n\t\t\t\t\tfound = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif found {\n\t\t\tif overwrite {\n\t\t\t\tparsed[ev] = v\n\t\t\t} else {\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t} else {\n\t\t\tparsed[k] = v\n\t\t}\n\n\t\tBindKey(k, v, Binder[\"buffer\"])\n\n\t\ttxt, _ := json.MarshalIndent(parsed, \"\", \"    \")\n\t\ttxt = append(txt, '\\n')\n\n\t\tif writeToFile {\n\t\t\treturn true, writeFile(filename, txt)\n\t\t} else {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\treturn false, e\n}\n\n// UnbindKey removes the binding for a key from the bindings.json file\nfunc UnbindKey(k string) error {\n\tvar e error\n\tvar parsed map[string]any\n\n\tfilename := filepath.Join(config.ConfigDir, \"bindings.json\")\n\tcreateBindingsIfNotExist(filename)\n\tif _, e = os.Stat(filename); e == nil {\n\t\tinput, err := os.ReadFile(filename)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"Error reading bindings.json file: \" + err.Error())\n\t\t}\n\n\t\terr = json5.Unmarshal(input, &parsed)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"Error reading bindings.json: \" + err.Error())\n\t\t}\n\n\t\tkey, err := findEvent(k)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor ev := range parsed {\n\t\t\tif e, err := findEvent(ev); err == nil {\n\t\t\t\tif eventsEqual(e, key) {\n\t\t\t\t\tdelete(parsed, ev)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif strings.HasPrefix(k, \"\\x1b\") {\n\t\t\tscreen.UnregisterRawSeq(k)\n\t\t}\n\n\t\tdefaults := DefaultBindings(\"buffer\")\n\t\tif a, ok := defaults[k]; ok {\n\t\t\tBindKey(k, a, Binder[\"buffer\"])\n\t\t} else if _, ok := config.Bindings[\"buffer\"][k]; ok {\n\t\t\tBufUnmap(key)\n\t\t\tdelete(config.Bindings[\"buffer\"], k)\n\t\t}\n\n\t\ttxt, _ := json.MarshalIndent(parsed, \"\", \"    \")\n\t\ttxt = append(txt, '\\n')\n\t\treturn writeFile(filename, txt)\n\t}\n\treturn e\n}\n\nvar mouseEvents = map[string]tcell.ButtonMask{\n\t\"MouseLeft\":       tcell.ButtonPrimary,\n\t\"MouseMiddle\":     tcell.ButtonMiddle,\n\t\"MouseRight\":      tcell.ButtonSecondary,\n\t\"MouseWheelUp\":    tcell.WheelUp,\n\t\"MouseWheelDown\":  tcell.WheelDown,\n\t\"MouseWheelLeft\":  tcell.WheelLeft,\n\t\"MouseWheelRight\": tcell.WheelRight,\n}\n\nvar keyEvents = map[string]tcell.Key{\n\t\"Up\":             tcell.KeyUp,\n\t\"Down\":           tcell.KeyDown,\n\t\"Right\":          tcell.KeyRight,\n\t\"Left\":           tcell.KeyLeft,\n\t\"UpLeft\":         tcell.KeyUpLeft,\n\t\"UpRight\":        tcell.KeyUpRight,\n\t\"DownLeft\":       tcell.KeyDownLeft,\n\t\"DownRight\":      tcell.KeyDownRight,\n\t\"Center\":         tcell.KeyCenter,\n\t\"PageUp\":         tcell.KeyPgUp,\n\t\"PageDown\":       tcell.KeyPgDn,\n\t\"Home\":           tcell.KeyHome,\n\t\"End\":            tcell.KeyEnd,\n\t\"Insert\":         tcell.KeyInsert,\n\t\"Delete\":         tcell.KeyDelete,\n\t\"Help\":           tcell.KeyHelp,\n\t\"Exit\":           tcell.KeyExit,\n\t\"Clear\":          tcell.KeyClear,\n\t\"Cancel\":         tcell.KeyCancel,\n\t\"Print\":          tcell.KeyPrint,\n\t\"Pause\":          tcell.KeyPause,\n\t\"Backtab\":        tcell.KeyBacktab,\n\t\"F1\":             tcell.KeyF1,\n\t\"F2\":             tcell.KeyF2,\n\t\"F3\":             tcell.KeyF3,\n\t\"F4\":             tcell.KeyF4,\n\t\"F5\":             tcell.KeyF5,\n\t\"F6\":             tcell.KeyF6,\n\t\"F7\":             tcell.KeyF7,\n\t\"F8\":             tcell.KeyF8,\n\t\"F9\":             tcell.KeyF9,\n\t\"F10\":            tcell.KeyF10,\n\t\"F11\":            tcell.KeyF11,\n\t\"F12\":            tcell.KeyF12,\n\t\"F13\":            tcell.KeyF13,\n\t\"F14\":            tcell.KeyF14,\n\t\"F15\":            tcell.KeyF15,\n\t\"F16\":            tcell.KeyF16,\n\t\"F17\":            tcell.KeyF17,\n\t\"F18\":            tcell.KeyF18,\n\t\"F19\":            tcell.KeyF19,\n\t\"F20\":            tcell.KeyF20,\n\t\"F21\":            tcell.KeyF21,\n\t\"F22\":            tcell.KeyF22,\n\t\"F23\":            tcell.KeyF23,\n\t\"F24\":            tcell.KeyF24,\n\t\"F25\":            tcell.KeyF25,\n\t\"F26\":            tcell.KeyF26,\n\t\"F27\":            tcell.KeyF27,\n\t\"F28\":            tcell.KeyF28,\n\t\"F29\":            tcell.KeyF29,\n\t\"F30\":            tcell.KeyF30,\n\t\"F31\":            tcell.KeyF31,\n\t\"F32\":            tcell.KeyF32,\n\t\"F33\":            tcell.KeyF33,\n\t\"F34\":            tcell.KeyF34,\n\t\"F35\":            tcell.KeyF35,\n\t\"F36\":            tcell.KeyF36,\n\t\"F37\":            tcell.KeyF37,\n\t\"F38\":            tcell.KeyF38,\n\t\"F39\":            tcell.KeyF39,\n\t\"F40\":            tcell.KeyF40,\n\t\"F41\":            tcell.KeyF41,\n\t\"F42\":            tcell.KeyF42,\n\t\"F43\":            tcell.KeyF43,\n\t\"F44\":            tcell.KeyF44,\n\t\"F45\":            tcell.KeyF45,\n\t\"F46\":            tcell.KeyF46,\n\t\"F47\":            tcell.KeyF47,\n\t\"F48\":            tcell.KeyF48,\n\t\"F49\":            tcell.KeyF49,\n\t\"F50\":            tcell.KeyF50,\n\t\"F51\":            tcell.KeyF51,\n\t\"F52\":            tcell.KeyF52,\n\t\"F53\":            tcell.KeyF53,\n\t\"F54\":            tcell.KeyF54,\n\t\"F55\":            tcell.KeyF55,\n\t\"F56\":            tcell.KeyF56,\n\t\"F57\":            tcell.KeyF57,\n\t\"F58\":            tcell.KeyF58,\n\t\"F59\":            tcell.KeyF59,\n\t\"F60\":            tcell.KeyF60,\n\t\"F61\":            tcell.KeyF61,\n\t\"F62\":            tcell.KeyF62,\n\t\"F63\":            tcell.KeyF63,\n\t\"F64\":            tcell.KeyF64,\n\t\"CtrlSpace\":      tcell.KeyCtrlSpace,\n\t\"CtrlA\":          tcell.KeyCtrlA,\n\t\"CtrlB\":          tcell.KeyCtrlB,\n\t\"CtrlC\":          tcell.KeyCtrlC,\n\t\"CtrlD\":          tcell.KeyCtrlD,\n\t\"CtrlE\":          tcell.KeyCtrlE,\n\t\"CtrlF\":          tcell.KeyCtrlF,\n\t\"CtrlG\":          tcell.KeyCtrlG,\n\t\"CtrlH\":          tcell.KeyCtrlH,\n\t\"CtrlI\":          tcell.KeyCtrlI,\n\t\"CtrlJ\":          tcell.KeyCtrlJ,\n\t\"CtrlK\":          tcell.KeyCtrlK,\n\t\"CtrlL\":          tcell.KeyCtrlL,\n\t\"CtrlM\":          tcell.KeyCtrlM,\n\t\"CtrlN\":          tcell.KeyCtrlN,\n\t\"CtrlO\":          tcell.KeyCtrlO,\n\t\"CtrlP\":          tcell.KeyCtrlP,\n\t\"CtrlQ\":          tcell.KeyCtrlQ,\n\t\"CtrlR\":          tcell.KeyCtrlR,\n\t\"CtrlS\":          tcell.KeyCtrlS,\n\t\"CtrlT\":          tcell.KeyCtrlT,\n\t\"CtrlU\":          tcell.KeyCtrlU,\n\t\"CtrlV\":          tcell.KeyCtrlV,\n\t\"CtrlW\":          tcell.KeyCtrlW,\n\t\"CtrlX\":          tcell.KeyCtrlX,\n\t\"CtrlY\":          tcell.KeyCtrlY,\n\t\"CtrlZ\":          tcell.KeyCtrlZ,\n\t\"CtrlLeftSq\":     tcell.KeyCtrlLeftSq,\n\t\"CtrlBackslash\":  tcell.KeyCtrlBackslash,\n\t\"CtrlRightSq\":    tcell.KeyCtrlRightSq,\n\t\"CtrlCarat\":      tcell.KeyCtrlCarat,\n\t\"CtrlUnderscore\": tcell.KeyCtrlUnderscore,\n\t\"Tab\":            tcell.KeyTab,\n\t\"Esc\":            tcell.KeyEsc,\n\t\"Escape\":         tcell.KeyEscape,\n\t\"Enter\":          tcell.KeyEnter,\n\t\"Backspace\":      tcell.KeyBackspace2,\n\t\"OldBackspace\":   tcell.KeyBackspace,\n\n\t// I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings\n\t\"PgUp\":   tcell.KeyPgUp,\n\t\"PgDown\": tcell.KeyPgDn,\n}\n"
  },
  {
    "path": "internal/action/bufpane.go",
    "content": "package action\n\nimport (\n\t\"strings\"\n\t\"time\"\n\n\tluar \"layeh.com/gopher-luar\"\n\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/display\"\n\tulua \"github.com/micro-editor/micro/v2/internal/lua\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n\t\"github.com/micro-editor/tcell/v2\"\n\tlua \"github.com/yuin/gopher-lua\"\n)\n\ntype BufAction any\n\n// BufKeyAction represents an action bound to a key.\ntype BufKeyAction func(*BufPane) bool\n\n// BufMouseAction is an action that must be bound to a mouse event.\ntype BufMouseAction func(*BufPane, *tcell.EventMouse) bool\n\n// BufBindings stores the bindings for the buffer pane type.\nvar BufBindings *KeyTree\n\n// BufKeyActionGeneral makes a general pane action from a BufKeyAction.\nfunc BufKeyActionGeneral(a BufKeyAction) PaneKeyAction {\n\treturn func(p Pane) bool {\n\t\treturn a(p.(*BufPane))\n\t}\n}\n\n// BufMouseActionGeneral makes a general pane mouse action from a BufKeyAction.\nfunc BufMouseActionGeneral(a BufMouseAction) PaneMouseAction {\n\treturn func(p Pane, me *tcell.EventMouse) bool {\n\t\treturn a(p.(*BufPane), me)\n\t}\n}\n\nfunc init() {\n\tBufBindings = NewKeyTree()\n}\n\n// LuaAction makes an action from a lua function. It returns either a BufKeyAction\n// or a BufMouseAction depending on the event type.\nfunc LuaAction(fn string, k Event) BufAction {\n\tluaFn := strings.Split(fn, \".\")\n\tif len(luaFn) <= 1 {\n\t\treturn nil\n\t}\n\tplName, plFn := luaFn[0], luaFn[1]\n\tpl := config.FindPlugin(plName)\n\tif pl == nil {\n\t\treturn nil\n\t}\n\n\tvar action BufAction\n\tswitch k.(type) {\n\tcase KeyEvent, KeySequenceEvent, RawEvent:\n\t\taction = BufKeyAction(func(h *BufPane) bool {\n\t\t\tval, err := pl.Call(plFn, luar.New(ulua.L, h))\n\t\t\tif err != nil {\n\t\t\t\tscreen.TermMessage(err)\n\t\t\t}\n\t\t\tif v, ok := val.(lua.LBool); !ok {\n\t\t\t\treturn false\n\t\t\t} else {\n\t\t\t\treturn bool(v)\n\t\t\t}\n\t\t})\n\tcase MouseEvent:\n\t\taction = BufMouseAction(func(h *BufPane, te *tcell.EventMouse) bool {\n\t\t\tval, err := pl.Call(plFn, luar.New(ulua.L, h), luar.New(ulua.L, te))\n\t\t\tif err != nil {\n\t\t\t\tscreen.TermMessage(err)\n\t\t\t}\n\t\t\tif v, ok := val.(lua.LBool); !ok {\n\t\t\t\treturn false\n\t\t\t} else {\n\t\t\t\treturn bool(v)\n\t\t\t}\n\t\t})\n\t}\n\treturn action\n}\n\n// BufMapEvent maps an event to an action\nfunc BufMapEvent(k Event, action string) {\n\tconfig.Bindings[\"buffer\"][k.Name()] = action\n\n\tvar actionfns []BufAction\n\tvar names []string\n\tvar types []byte\n\tfor i := 0; ; i++ {\n\t\tif action == \"\" {\n\t\t\tbreak\n\t\t}\n\n\t\tidx := util.IndexAnyUnquoted(action, \"&|,\")\n\t\ta := action\n\t\tif idx >= 0 {\n\t\t\ta = action[:idx]\n\t\t\ttypes = append(types, action[idx])\n\t\t\taction = action[idx+1:]\n\t\t} else {\n\t\t\ttypes = append(types, ' ')\n\t\t\taction = \"\"\n\t\t}\n\n\t\tvar afn BufAction\n\t\tif strings.HasPrefix(a, \"command:\") {\n\t\t\ta = strings.SplitN(a, \":\", 2)[1]\n\t\t\tafn = CommandAction(a)\n\t\t\tnames = append(names, \"\")\n\t\t} else if strings.HasPrefix(a, \"command-edit:\") {\n\t\t\ta = strings.SplitN(a, \":\", 2)[1]\n\t\t\tafn = CommandEditAction(a)\n\t\t\tnames = append(names, \"\")\n\t\t} else if strings.HasPrefix(a, \"lua:\") {\n\t\t\ta = strings.SplitN(a, \":\", 2)[1]\n\t\t\tafn = LuaAction(a, k)\n\t\t\tif afn == nil {\n\t\t\t\tscreen.TermMessage(\"Lua Error:\", a, \"does not exist\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsplit := strings.SplitN(a, \".\", 2)\n\t\t\tif len(split) > 1 {\n\t\t\t\ta = strings.Title(split[0]) + strings.Title(split[1])\n\t\t\t} else {\n\t\t\t\ta = strings.Title(a)\n\t\t\t}\n\n\t\t\tnames = append(names, a)\n\t\t} else if f, ok := BufKeyActions[a]; ok {\n\t\t\tafn = f\n\t\t\tnames = append(names, a)\n\t\t} else if f, ok := BufMouseActions[a]; ok {\n\t\t\tafn = f\n\t\t\tnames = append(names, a)\n\t\t} else {\n\t\t\tscreen.TermMessage(\"Error in bindings: action\", a, \"does not exist\")\n\t\t\tcontinue\n\t\t}\n\t\tactionfns = append(actionfns, afn)\n\t}\n\tbufAction := func(h *BufPane, te *tcell.EventMouse) bool {\n\t\tfor i, a := range actionfns {\n\t\t\tvar success bool\n\t\t\tif _, ok := MultiActions[names[i]]; ok {\n\t\t\t\tsuccess = true\n\t\t\t\tfor _, c := range h.Buf.GetCursors() {\n\t\t\t\t\th.Buf.SetCurCursor(c.Num)\n\t\t\t\t\th.Cursor = c\n\t\t\t\t\tsuccess = success && h.execAction(a, names[i], te)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\th.Buf.SetCurCursor(0)\n\t\t\t\th.Cursor = h.Buf.GetActiveCursor()\n\t\t\t\tsuccess = h.execAction(a, names[i], te)\n\t\t\t}\n\n\t\t\t// if the action changed the current pane, update the reference\n\t\t\th = MainTab().CurPane()\n\t\t\tif h == nil {\n\t\t\t\t// stop, in case the current pane is not a BufPane\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif (!success && types[i] == '&') || (success && types[i] == '|') {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\n\tswitch e := k.(type) {\n\tcase KeyEvent, KeySequenceEvent, RawEvent:\n\t\tBufBindings.RegisterKeyBinding(e, BufKeyActionGeneral(func(h *BufPane) bool {\n\t\t\treturn bufAction(h, nil)\n\t\t}))\n\tcase MouseEvent:\n\t\tBufBindings.RegisterMouseBinding(e, BufMouseActionGeneral(bufAction))\n\t}\n}\n\n// BufUnmap unmaps a key or mouse event from any action\nfunc BufUnmap(k Event) {\n\t// TODO\n\t// delete(BufKeyBindings, k)\n\t//\n\t// switch e := k.(type) {\n\t// case MouseEvent:\n\t// \tdelete(BufMouseBindings, e)\n\t// }\n}\n\n// The BufPane connects the buffer and the window\n// It provides a cursor (or multiple) and defines a set of actions\n// that can be taken on the buffer\n// The ActionHandler can access the window for necessary info about\n// visual positions for mouse clicks and scrolling\ntype BufPane struct {\n\tdisplay.BWindow\n\n\t// Buf is the buffer this BufPane views\n\tBuf *buffer.Buffer\n\t// Bindings stores the association of key events and actions\n\tbindings *KeyTree\n\n\t// Cursor is the currently active buffer cursor\n\tCursor *buffer.Cursor\n\n\t// Since tcell doesn't differentiate between a mouse press event\n\t// and a mouse move event with button pressed (nor between a mouse\n\t// release event and a mouse move event with no buttons pressed),\n\t// we need to keep track of whether or not the mouse was previously\n\t// pressed, to determine mouse release and mouse drag events.\n\t// Moreover, since in case of a release event tcell doesn't tell us\n\t// which button was released, we need to keep track of which\n\t// (possibly multiple) buttons were pressed previously.\n\tmousePressed map[MouseEvent]bool\n\n\t// This stores when the last click was\n\t// This is useful for detecting double and triple clicks\n\tlastClickTime time.Time\n\tlastLoc       buffer.Loc\n\n\t// freshClip returns true if one or more lines have been cut to the clipboard\n\t// and have never been pasted yet.\n\tfreshClip bool\n\n\t// Was the last mouse event actually a double click?\n\t// Useful for detecting triple clicks -- if a double click is detected\n\t// but the last mouse event was actually a double click, it's a triple click\n\tDoubleClick bool\n\t// Same here, just to keep track for mouse move events\n\tTripleClick bool\n\n\t// Should the current multiple cursor selection search based on word or\n\t// based on selection (false for selection, true for word)\n\tmultiWord bool\n\n\tsplitID uint64\n\ttab     *Tab\n\n\t// remember original location of a search in case the search is canceled\n\tsearchOrig buffer.Loc\n\n\t// The pane may not yet be fully initialized after its creation\n\t// since we may not know the window geometry yet. In such case we finish\n\t// its initialization a bit later, after the initial resize.\n\tinitialized bool\n}\n\nfunc newBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {\n\th := new(BufPane)\n\th.Buf = buf\n\th.BWindow = win\n\th.tab = tab\n\n\th.Cursor = h.Buf.GetActiveCursor()\n\th.mousePressed = make(map[MouseEvent]bool)\n\n\treturn h\n}\n\n// NewBufPane creates a new buffer pane with the given window.\nfunc NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {\n\th := newBufPane(buf, win, tab)\n\th.finishInitialize()\n\treturn h\n}\n\n// NewBufPaneFromBuf constructs a new pane from the given buffer and automatically\n// creates a buf window.\nfunc NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {\n\tw := display.NewBufWindow(0, 0, 0, 0, buf)\n\th := newBufPane(buf, w, tab)\n\t// Postpone finishing initializing the pane until we know the actual geometry\n\t// of the buf window.\n\treturn h\n}\n\n// TODO: make sure splitID and tab are set before finishInitialize is called\nfunc (h *BufPane) finishInitialize() {\n\th.initialRelocate()\n\th.initialized = true\n\n\terr := config.RunPluginFn(\"onBufPaneOpen\", luar.New(ulua.L, h))\n\tif err != nil {\n\t\tscreen.TermMessage(err)\n\t}\n}\n\n// Resize resizes the pane\nfunc (h *BufPane) Resize(width, height int) {\n\th.BWindow.Resize(width, height)\n\tif !h.initialized {\n\t\th.finishInitialize()\n\t}\n}\n\n// SetTab sets this pane's tab.\nfunc (h *BufPane) SetTab(t *Tab) {\n\th.tab = t\n}\n\n// Tab returns this pane's tab.\nfunc (h *BufPane) Tab() *Tab {\n\treturn h.tab\n}\n\nfunc (h *BufPane) ResizePane(size int) {\n\tn := h.tab.GetNode(h.splitID)\n\tn.ResizeSplit(size)\n\th.tab.Resize()\n}\n\n// PluginCB calls all plugin callbacks with a certain name and displays an\n// error if there is one and returns the aggregate boolean response.\n// The bufpane is passed as the first argument to the callbacks,\n// optional args are passed as the next arguments.\nfunc (h *BufPane) PluginCB(cb string, args ...any) bool {\n\tlargs := []lua.LValue{luar.New(ulua.L, h)}\n\tfor _, a := range args {\n\t\tlargs = append(largs, luar.New(ulua.L, a))\n\t}\n\n\tb, err := config.RunPluginFnBool(h.Buf.Settings, cb, largs...)\n\tif err != nil {\n\t\tscreen.TermMessage(err)\n\t}\n\treturn b\n}\n\nfunc (h *BufPane) resetMouse() {\n\tfor me := range h.mousePressed {\n\t\tdelete(h.mousePressed, me)\n\t}\n}\n\n// OpenBuffer opens the given buffer in this pane.\nfunc (h *BufPane) OpenBuffer(b *buffer.Buffer) {\n\th.Buf.Close()\n\th.Buf = b\n\th.BWindow.SetBuffer(b)\n\th.Cursor = b.GetActiveCursor()\n\th.Resize(h.GetView().Width, h.GetView().Height)\n\th.initialRelocate()\n\t// Set mouseReleased to true because we assume the mouse is not being\n\t// pressed when the editor is opened\n\th.resetMouse()\n\th.lastClickTime = time.Time{}\n}\n\n// GotoLoc moves the cursor to a new location and adjusts the view accordingly.\n// Use GotoLoc when the new location may be far away from the current location.\nfunc (h *BufPane) GotoLoc(loc buffer.Loc) {\n\tsloc := h.SLocFromLoc(loc)\n\td := h.Diff(h.SLocFromLoc(h.Cursor.Loc), sloc)\n\n\th.Cursor.GotoLoc(loc)\n\n\t// If the new location is far away from the previous one,\n\t// ensure the cursor is at 25% of the window height\n\theight := h.BufView().Height\n\tif util.Abs(d) >= height {\n\t\tv := h.GetView()\n\t\tv.StartLine = h.Scroll(sloc, -height/4)\n\t\th.ScrollAdjust()\n\t\tv.StartCol = 0\n\t}\n\th.Relocate()\n}\n\nfunc (h *BufPane) initialRelocate() {\n\tsloc := h.SLocFromLoc(h.Cursor.Loc)\n\theight := h.BufView().Height\n\n\t// If the initial cursor location is far away from the beginning\n\t// of the buffer, ensure the cursor is at 25% of the window height\n\tv := h.GetView()\n\tif h.Diff(display.SLoc{0, 0}, sloc) < height {\n\t\tv.StartLine = display.SLoc{0, 0}\n\t} else {\n\t\tv.StartLine = h.Scroll(sloc, -height/4)\n\t\th.ScrollAdjust()\n\t}\n\tv.StartCol = 0\n\th.Relocate()\n}\n\n// ID returns this pane's split id.\nfunc (h *BufPane) ID() uint64 {\n\treturn h.splitID\n}\n\n// SetID sets the split ID of this pane.\nfunc (h *BufPane) SetID(i uint64) {\n\th.splitID = i\n}\n\n// Name returns the BufPane's name.\nfunc (h *BufPane) Name() string {\n\tn := h.Buf.GetName()\n\tif h.Buf.Modified() {\n\t\tn += \" +\"\n\t}\n\treturn n\n}\n\n// ReOpen reloads the file opened in the bufpane from disk\nfunc (h *BufPane) ReOpen() {\n\th.Buf.ReOpen()\n\th.Relocate()\n}\n\nfunc (h *BufPane) getReloadSetting() string {\n\treloadSetting := h.Buf.Settings[\"reload\"]\n\treturn reloadSetting.(string)\n}\n\n// HandleEvent executes the tcell event properly\nfunc (h *BufPane) HandleEvent(event tcell.Event) {\n\tif h.Buf.ExternallyModified() && !h.Buf.ReloadDisabled {\n\t\treload := h.getReloadSetting()\n\n\t\tif reload == \"prompt\" {\n\t\t\tInfoBar.YNPrompt(\"The file on disk has changed. Reload file? (y,n,esc)\", func(yes, canceled bool) {\n\t\t\t\tif canceled {\n\t\t\t\t\th.Buf.DisableReload()\n\t\t\t\t}\n\t\t\t\tif !yes || canceled {\n\t\t\t\t\th.Buf.UpdateModTime()\n\t\t\t\t} else {\n\t\t\t\t\th.ReOpen()\n\t\t\t\t}\n\t\t\t})\n\t\t} else if reload == \"auto\" {\n\t\t\th.ReOpen()\n\t\t} else if reload == \"disabled\" {\n\t\t\th.Buf.DisableReload()\n\t\t} else {\n\t\t\tInfoBar.Message(\"Invalid reload setting\")\n\t\t}\n\t}\n\n\tswitch e := event.(type) {\n\tcase *tcell.EventRaw:\n\t\tre := RawEvent{\n\t\t\tesc: e.EscSeq(),\n\t\t}\n\t\th.DoKeyEvent(re)\n\tcase *tcell.EventPaste:\n\t\th.paste(e.Text())\n\t\th.Relocate()\n\tcase *tcell.EventKey:\n\t\tke := keyEvent(e)\n\n\t\tdone := h.DoKeyEvent(ke)\n\t\tif !done && e.Key() == tcell.KeyRune {\n\t\t\th.DoRuneInsert(e.Rune())\n\t\t}\n\tcase *tcell.EventMouse:\n\t\tif e.Buttons() != tcell.ButtonNone {\n\t\t\tme := MouseEvent{\n\t\t\t\tbtn:   e.Buttons(),\n\t\t\t\tmod:   metaToAlt(e.Modifiers()),\n\t\t\t\tstate: MousePress,\n\t\t\t}\n\t\t\tisDrag := len(h.mousePressed) > 0\n\n\t\t\tif e.Buttons() & ^(tcell.WheelUp|tcell.WheelDown|tcell.WheelLeft|tcell.WheelRight) != tcell.ButtonNone {\n\t\t\t\th.mousePressed[me] = true\n\t\t\t}\n\n\t\t\tif isDrag {\n\t\t\t\tme.state = MouseDrag\n\t\t\t}\n\t\t\th.DoMouseEvent(me, e)\n\t\t} else {\n\t\t\t// Mouse event with no click - mouse was just released.\n\t\t\t// If there were multiple mouse buttons pressed, we don't know which one\n\t\t\t// was actually released, so we assume they all were released.\n\t\t\tpressed := len(h.mousePressed) > 0\n\t\t\tfor me := range h.mousePressed {\n\t\t\t\tdelete(h.mousePressed, me)\n\n\t\t\t\tme.state = MouseRelease\n\t\t\t\th.DoMouseEvent(me, e)\n\t\t\t}\n\t\t\tif !pressed {\n\t\t\t\t// Propagate the mouse release in case the press wasn't for this BufPane\n\t\t\t\tTabs.ResetMouse()\n\t\t\t}\n\t\t}\n\t}\n\th.Buf.MergeCursors()\n\n\tif h.IsActive() {\n\t\t// Display any gutter messages for this line\n\t\tc := h.Buf.GetActiveCursor()\n\t\tnone := true\n\t\tfor _, m := range h.Buf.Messages {\n\t\t\tif c.Y == m.Start.Y || c.Y == m.End.Y {\n\t\t\t\tInfoBar.GutterMessage(m.Msg)\n\t\t\t\tnone = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif none && InfoBar.HasGutter {\n\t\t\tInfoBar.ClearGutter()\n\t\t}\n\t}\n\n\tcursors := h.Buf.GetCursors()\n\tfor _, c := range cursors {\n\t\tif c.NewTrailingWsY != c.Y && (!c.HasSelection() ||\n\t\t\t(c.NewTrailingWsY != c.CurSelection[0].Y && c.NewTrailingWsY != c.CurSelection[1].Y)) {\n\t\t\tc.NewTrailingWsY = -1\n\t\t}\n\t}\n}\n\n// Bindings returns the current bindings tree for this buffer.\nfunc (h *BufPane) Bindings() *KeyTree {\n\tif h.bindings != nil {\n\t\treturn h.bindings\n\t}\n\treturn BufBindings\n}\n\n// DoKeyEvent executes a key event by finding the action it is bound\n// to and executing it (possibly multiple times for multiple cursors).\n// Returns true if the action was executed OR if there are more keys\n// remaining to process before executing an action (if this is a key\n// sequence event). Returns false if no action found.\nfunc (h *BufPane) DoKeyEvent(e Event) bool {\n\tbinds := h.Bindings()\n\taction, more := binds.NextEvent(e, nil)\n\tif action != nil && !more {\n\t\taction(h)\n\t\tbinds.ResetEvents()\n\t\treturn true\n\t} else if action == nil && !more {\n\t\tbinds.ResetEvents()\n\t}\n\treturn more\n}\n\nfunc (h *BufPane) execAction(action BufAction, name string, te *tcell.EventMouse) bool {\n\tif name != \"Autocomplete\" && name != \"CycleAutocompleteBack\" {\n\t\th.Buf.HasSuggestions = false\n\t}\n\n\tif !h.PluginCB(\"pre\"+name, te) {\n\t\treturn false\n\t}\n\n\tvar success bool\n\tswitch a := action.(type) {\n\tcase BufKeyAction:\n\t\tsuccess = a(h)\n\tcase BufMouseAction:\n\t\tsuccess = a(h, te)\n\t}\n\tsuccess = success && h.PluginCB(\"on\"+name, te)\n\n\tif _, ok := MultiActions[name]; ok {\n\t\tif recordingMacro {\n\t\t\tif name != \"ToggleMacro\" && name != \"PlayMacro\" {\n\t\t\t\tcurmacro = append(curmacro, action)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn success\n}\n\nfunc (h *BufPane) completeAction(action string) {\n\th.PluginCB(\"on\" + action)\n}\n\nfunc (h *BufPane) HasKeyEvent(e Event) bool {\n\t// TODO\n\treturn true\n\t// _, ok := BufKeyBindings[e]\n\t// return ok\n}\n\n// DoMouseEvent executes a mouse event by finding the action it is bound\n// to and executing it\nfunc (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {\n\tbinds := h.Bindings()\n\taction, _ := binds.NextEvent(e, te)\n\tif action != nil {\n\t\taction(h)\n\t\tbinds.ResetEvents()\n\t\treturn true\n\t}\n\t// TODO\n\treturn false\n\n\t// if action, ok := BufMouseBindings[e]; ok {\n\t// \tif action(h, te) {\n\t// \t\th.Relocate()\n\t// \t}\n\t// \treturn true\n\t// } else if h.HasKeyEvent(e) {\n\t// \treturn h.DoKeyEvent(e)\n\t// }\n\t// return false\n}\n\n// DoRuneInsert inserts a given rune into the current buffer\n// (possibly multiple times for multiple cursors)\nfunc (h *BufPane) DoRuneInsert(r rune) {\n\tcursors := h.Buf.GetCursors()\n\tfor _, c := range cursors {\n\t\t// Insert a character\n\t\th.Buf.SetCurCursor(c.Num)\n\t\th.Cursor = c\n\t\tif !h.PluginCB(\"preRune\", string(r)) {\n\t\t\tcontinue\n\t\t}\n\t\tif c.HasSelection() {\n\t\t\tc.DeleteSelection()\n\t\t\tc.ResetSelection()\n\t\t}\n\n\t\tif h.Buf.OverwriteMode {\n\t\t\tnext := c.Loc\n\t\t\tnext.X++\n\t\t\th.Buf.Replace(c.Loc, next, string(r))\n\t\t} else {\n\t\t\th.Buf.Insert(c.Loc, string(r))\n\t\t}\n\t\tif recordingMacro {\n\t\t\tcurmacro = append(curmacro, r)\n\t\t}\n\t\th.Relocate()\n\t\th.PluginCB(\"onRune\", string(r))\n\t}\n}\n\n// VSplitIndex opens the given buffer in a vertical split on the given side.\nfunc (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {\n\te := NewBufPaneFromBuf(buf, h.tab)\n\te.splitID = h.tab.GetNode(h.splitID).VSplit(right)\n\tcurrentPaneIdx := h.tab.GetPane(h.splitID)\n\tif right {\n\t\tcurrentPaneIdx++\n\t}\n\th.tab.AddPane(e, currentPaneIdx)\n\th.tab.Resize()\n\th.tab.SetActive(currentPaneIdx)\n\treturn e\n}\n\n// HSplitIndex opens the given buffer in a horizontal split on the given side.\nfunc (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {\n\te := NewBufPaneFromBuf(buf, h.tab)\n\te.splitID = h.tab.GetNode(h.splitID).HSplit(bottom)\n\tcurrentPaneIdx := h.tab.GetPane(h.splitID)\n\tif bottom {\n\t\tcurrentPaneIdx++\n\t}\n\th.tab.AddPane(e, currentPaneIdx)\n\th.tab.Resize()\n\th.tab.SetActive(currentPaneIdx)\n\treturn e\n}\n\n// VSplitBuf opens the given buffer in a new vertical split.\nfunc (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {\n\treturn h.VSplitIndex(buf, h.Buf.Settings[\"splitright\"].(bool))\n}\n\n// HSplitBuf opens the given buffer in a new horizontal split.\nfunc (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {\n\treturn h.HSplitIndex(buf, h.Buf.Settings[\"splitbottom\"].(bool))\n}\n\n// Close this pane.\nfunc (h *BufPane) Close() {\n\th.Buf.Close()\n}\n\n// SetActive marks this pane as active.\nfunc (h *BufPane) SetActive(b bool) {\n\tif h.IsActive() == b {\n\t\treturn\n\t}\n\n\th.BWindow.SetActive(b)\n\tif b {\n\t\t// Display any gutter messages for this line\n\t\tc := h.Buf.GetActiveCursor()\n\t\tnone := true\n\t\tfor _, m := range h.Buf.Messages {\n\t\t\tif c.Y == m.Start.Y || c.Y == m.End.Y {\n\t\t\t\tInfoBar.GutterMessage(m.Msg)\n\t\t\t\tnone = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif none && InfoBar.HasGutter {\n\t\t\tInfoBar.ClearGutter()\n\t\t}\n\n\t\terr := config.RunPluginFn(\"onSetActive\", luar.New(ulua.L, h))\n\t\tif err != nil {\n\t\t\tscreen.TermMessage(err)\n\t\t}\n\t}\n}\n\n// BufKeyActions contains the list of all possible key actions the bufhandler could execute\nvar BufKeyActions = map[string]BufKeyAction{\n\t\"CursorUp\":                  (*BufPane).CursorUp,\n\t\"CursorDown\":                (*BufPane).CursorDown,\n\t\"CursorPageUp\":              (*BufPane).CursorPageUp,\n\t\"CursorPageDown\":            (*BufPane).CursorPageDown,\n\t\"CursorLeft\":                (*BufPane).CursorLeft,\n\t\"CursorRight\":               (*BufPane).CursorRight,\n\t\"CursorStart\":               (*BufPane).CursorStart,\n\t\"CursorEnd\":                 (*BufPane).CursorEnd,\n\t\"CursorToViewTop\":           (*BufPane).CursorToViewTop,\n\t\"CursorToViewCenter\":        (*BufPane).CursorToViewCenter,\n\t\"CursorToViewBottom\":        (*BufPane).CursorToViewBottom,\n\t\"SelectToStart\":             (*BufPane).SelectToStart,\n\t\"SelectToEnd\":               (*BufPane).SelectToEnd,\n\t\"SelectUp\":                  (*BufPane).SelectUp,\n\t\"SelectDown\":                (*BufPane).SelectDown,\n\t\"SelectLeft\":                (*BufPane).SelectLeft,\n\t\"SelectRight\":               (*BufPane).SelectRight,\n\t\"WordRight\":                 (*BufPane).WordRight,\n\t\"WordLeft\":                  (*BufPane).WordLeft,\n\t\"SubWordRight\":              (*BufPane).SubWordRight,\n\t\"SubWordLeft\":               (*BufPane).SubWordLeft,\n\t\"SelectWordRight\":           (*BufPane).SelectWordRight,\n\t\"SelectWordLeft\":            (*BufPane).SelectWordLeft,\n\t\"SelectSubWordRight\":        (*BufPane).SelectSubWordRight,\n\t\"SelectSubWordLeft\":         (*BufPane).SelectSubWordLeft,\n\t\"DeleteWordRight\":           (*BufPane).DeleteWordRight,\n\t\"DeleteWordLeft\":            (*BufPane).DeleteWordLeft,\n\t\"DeleteSubWordRight\":        (*BufPane).DeleteSubWordRight,\n\t\"DeleteSubWordLeft\":         (*BufPane).DeleteSubWordLeft,\n\t\"SelectLine\":                (*BufPane).SelectLine,\n\t\"SelectToStartOfLine\":       (*BufPane).SelectToStartOfLine,\n\t\"SelectToStartOfText\":       (*BufPane).SelectToStartOfText,\n\t\"SelectToStartOfTextToggle\": (*BufPane).SelectToStartOfTextToggle,\n\t\"SelectToEndOfLine\":         (*BufPane).SelectToEndOfLine,\n\t\"ParagraphPrevious\":         (*BufPane).ParagraphPrevious,\n\t\"ParagraphNext\":             (*BufPane).ParagraphNext,\n\t\"SelectToParagraphPrevious\": (*BufPane).SelectToParagraphPrevious,\n\t\"SelectToParagraphNext\":     (*BufPane).SelectToParagraphNext,\n\t\"InsertNewline\":             (*BufPane).InsertNewline,\n\t\"Backspace\":                 (*BufPane).Backspace,\n\t\"Delete\":                    (*BufPane).Delete,\n\t\"InsertTab\":                 (*BufPane).InsertTab,\n\t\"Save\":                      (*BufPane).Save,\n\t\"SaveAll\":                   (*BufPane).SaveAll,\n\t\"SaveAs\":                    (*BufPane).SaveAs,\n\t\"Find\":                      (*BufPane).Find,\n\t\"FindLiteral\":               (*BufPane).FindLiteral,\n\t\"FindNext\":                  (*BufPane).FindNext,\n\t\"FindPrevious\":              (*BufPane).FindPrevious,\n\t\"DiffNext\":                  (*BufPane).DiffNext,\n\t\"DiffPrevious\":              (*BufPane).DiffPrevious,\n\t\"Center\":                    (*BufPane).Center,\n\t\"Undo\":                      (*BufPane).Undo,\n\t\"Redo\":                      (*BufPane).Redo,\n\t\"Copy\":                      (*BufPane).Copy,\n\t\"CopyLine\":                  (*BufPane).CopyLine,\n\t\"Cut\":                       (*BufPane).Cut,\n\t\"CutLine\":                   (*BufPane).CutLine,\n\t\"Duplicate\":                 (*BufPane).Duplicate,\n\t\"DuplicateLine\":             (*BufPane).DuplicateLine,\n\t\"DeleteLine\":                (*BufPane).DeleteLine,\n\t\"MoveLinesUp\":               (*BufPane).MoveLinesUp,\n\t\"MoveLinesDown\":             (*BufPane).MoveLinesDown,\n\t\"IndentSelection\":           (*BufPane).IndentSelection,\n\t\"OutdentSelection\":          (*BufPane).OutdentSelection,\n\t\"Autocomplete\":              (*BufPane).Autocomplete,\n\t\"CycleAutocompleteBack\":     (*BufPane).CycleAutocompleteBack,\n\t\"OutdentLine\":               (*BufPane).OutdentLine,\n\t\"IndentLine\":                (*BufPane).IndentLine,\n\t\"Paste\":                     (*BufPane).Paste,\n\t\"PastePrimary\":              (*BufPane).PastePrimary,\n\t\"SelectAll\":                 (*BufPane).SelectAll,\n\t\"OpenFile\":                  (*BufPane).OpenFile,\n\t\"Start\":                     (*BufPane).Start,\n\t\"End\":                       (*BufPane).End,\n\t\"PageUp\":                    (*BufPane).PageUp,\n\t\"PageDown\":                  (*BufPane).PageDown,\n\t\"SelectPageUp\":              (*BufPane).SelectPageUp,\n\t\"SelectPageDown\":            (*BufPane).SelectPageDown,\n\t\"HalfPageUp\":                (*BufPane).HalfPageUp,\n\t\"HalfPageDown\":              (*BufPane).HalfPageDown,\n\t\"StartOfText\":               (*BufPane).StartOfText,\n\t\"StartOfTextToggle\":         (*BufPane).StartOfTextToggle,\n\t\"StartOfLine\":               (*BufPane).StartOfLine,\n\t\"EndOfLine\":                 (*BufPane).EndOfLine,\n\t\"ToggleHelp\":                (*BufPane).ToggleHelp,\n\t\"ToggleKeyMenu\":             (*BufPane).ToggleKeyMenu,\n\t\"ToggleDiffGutter\":          (*BufPane).ToggleDiffGutter,\n\t\"ToggleRuler\":               (*BufPane).ToggleRuler,\n\t\"ToggleHighlightSearch\":     (*BufPane).ToggleHighlightSearch,\n\t\"UnhighlightSearch\":         (*BufPane).UnhighlightSearch,\n\t\"ResetSearch\":               (*BufPane).ResetSearch,\n\t\"ClearStatus\":               (*BufPane).ClearStatus,\n\t\"ShellMode\":                 (*BufPane).ShellMode,\n\t\"CommandMode\":               (*BufPane).CommandMode,\n\t\"ToggleOverwriteMode\":       (*BufPane).ToggleOverwriteMode,\n\t\"Escape\":                    (*BufPane).Escape,\n\t\"Quit\":                      (*BufPane).Quit,\n\t\"QuitAll\":                   (*BufPane).QuitAll,\n\t\"ForceQuit\":                 (*BufPane).ForceQuit,\n\t\"AddTab\":                    (*BufPane).AddTab,\n\t\"PreviousTab\":               (*BufPane).PreviousTab,\n\t\"NextTab\":                   (*BufPane).NextTab,\n\t\"FirstTab\":                  (*BufPane).FirstTab,\n\t\"LastTab\":                   (*BufPane).LastTab,\n\t\"NextSplit\":                 (*BufPane).NextSplit,\n\t\"PreviousSplit\":             (*BufPane).PreviousSplit,\n\t\"FirstSplit\":                (*BufPane).FirstSplit,\n\t\"LastSplit\":                 (*BufPane).LastSplit,\n\t\"Unsplit\":                   (*BufPane).Unsplit,\n\t\"VSplit\":                    (*BufPane).VSplitAction,\n\t\"HSplit\":                    (*BufPane).HSplitAction,\n\t\"ToggleMacro\":               (*BufPane).ToggleMacro,\n\t\"PlayMacro\":                 (*BufPane).PlayMacro,\n\t\"Suspend\":                   (*BufPane).Suspend,\n\t\"ScrollUp\":                  (*BufPane).ScrollUpAction,\n\t\"ScrollDown\":                (*BufPane).ScrollDownAction,\n\t\"SpawnMultiCursor\":          (*BufPane).SpawnMultiCursor,\n\t\"SpawnMultiCursorUp\":        (*BufPane).SpawnMultiCursorUp,\n\t\"SpawnMultiCursorDown\":      (*BufPane).SpawnMultiCursorDown,\n\t\"SpawnMultiCursorSelect\":    (*BufPane).SpawnMultiCursorSelect,\n\t\"RemoveMultiCursor\":         (*BufPane).RemoveMultiCursor,\n\t\"RemoveAllMultiCursors\":     (*BufPane).RemoveAllMultiCursors,\n\t\"SkipMultiCursor\":           (*BufPane).SkipMultiCursor,\n\t\"SkipMultiCursorBack\":       (*BufPane).SkipMultiCursorBack,\n\t\"JumpToMatchingBrace\":       (*BufPane).JumpToMatchingBrace,\n\t\"JumpLine\":                  (*BufPane).JumpLine,\n\t\"Deselect\":                  (*BufPane).Deselect,\n\t\"ClearInfo\":                 (*BufPane).ClearInfo,\n\t\"None\":                      (*BufPane).None,\n\n\t// This was changed to InsertNewline but I don't want to break backwards compatibility\n\t\"InsertEnter\": (*BufPane).InsertNewline,\n}\n\n// BufMouseActions contains the list of all possible mouse actions the bufhandler could execute\nvar BufMouseActions = map[string]BufMouseAction{\n\t\"MousePress\":       (*BufPane).MousePress,\n\t\"MouseDrag\":        (*BufPane).MouseDrag,\n\t\"MouseRelease\":     (*BufPane).MouseRelease,\n\t\"MouseMultiCursor\": (*BufPane).MouseMultiCursor,\n}\n\n// MultiActions is a list of actions that should be executed multiple\n// times if there are multiple cursors (one per cursor)\n// Generally actions that modify global editor state like quitting or\n// saving should not be included in this list\nvar MultiActions = map[string]bool{\n\t\"CursorUp\":                  true,\n\t\"CursorDown\":                true,\n\t\"CursorPageUp\":              true,\n\t\"CursorPageDown\":            true,\n\t\"CursorLeft\":                true,\n\t\"CursorRight\":               true,\n\t\"CursorStart\":               true,\n\t\"CursorEnd\":                 true,\n\t\"SelectToStart\":             true,\n\t\"SelectToEnd\":               true,\n\t\"SelectUp\":                  true,\n\t\"SelectDown\":                true,\n\t\"SelectLeft\":                true,\n\t\"SelectRight\":               true,\n\t\"WordRight\":                 true,\n\t\"WordLeft\":                  true,\n\t\"SubWordRight\":              true,\n\t\"SubWordLeft\":               true,\n\t\"SelectWordRight\":           true,\n\t\"SelectWordLeft\":            true,\n\t\"SelectSubWordRight\":        true,\n\t\"SelectSubWordLeft\":         true,\n\t\"DeleteWordRight\":           true,\n\t\"DeleteWordLeft\":            true,\n\t\"DeleteSubWordRight\":        true,\n\t\"DeleteSubWordLeft\":         true,\n\t\"SelectLine\":                true,\n\t\"SelectToStartOfLine\":       true,\n\t\"SelectToStartOfText\":       true,\n\t\"SelectToStartOfTextToggle\": true,\n\t\"SelectToEndOfLine\":         true,\n\t\"ParagraphPrevious\":         true,\n\t\"ParagraphNext\":             true,\n\t\"InsertNewline\":             true,\n\t\"Backspace\":                 true,\n\t\"Delete\":                    true,\n\t\"InsertTab\":                 true,\n\t\"FindNext\":                  true,\n\t\"FindPrevious\":              true,\n\t\"CopyLine\":                  true,\n\t\"Copy\":                      true,\n\t\"Cut\":                       true,\n\t\"CutLine\":                   true,\n\t\"Duplicate\":                 true,\n\t\"DuplicateLine\":             true,\n\t\"DeleteLine\":                true,\n\t\"MoveLinesUp\":               true,\n\t\"MoveLinesDown\":             true,\n\t\"IndentSelection\":           true,\n\t\"OutdentSelection\":          true,\n\t\"OutdentLine\":               true,\n\t\"IndentLine\":                true,\n\t\"Paste\":                     true,\n\t\"PastePrimary\":              true,\n\t\"SelectPageUp\":              true,\n\t\"SelectPageDown\":            true,\n\t\"StartOfLine\":               true,\n\t\"StartOfText\":               true,\n\t\"StartOfTextToggle\":         true,\n\t\"EndOfLine\":                 true,\n\t\"JumpToMatchingBrace\":       true,\n}\n"
  },
  {
    "path": "internal/action/command.go",
    "content": "package action\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\tshellquote \"github.com/kballard/go-shellquote\"\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n\t\"github.com/micro-editor/micro/v2/internal/clipboard\"\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/micro/v2/internal/shell\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n)\n\n// A Command contains information about how to execute a command\n// It has the action for that command as well as a completer function\ntype Command struct {\n\taction    func(*BufPane, []string)\n\tcompleter buffer.Completer\n}\n\nvar commands map[string]Command\n\nfunc InitCommands() {\n\tcommands = map[string]Command{\n\t\t\"set\":         {(*BufPane).SetCmd, OptionValueComplete},\n\t\t\"setlocal\":    {(*BufPane).SetLocalCmd, OptionValueComplete},\n\t\t\"toggle\":      {(*BufPane).ToggleCmd, OptionValueComplete},\n\t\t\"togglelocal\": {(*BufPane).ToggleLocalCmd, OptionValueComplete},\n\t\t\"reset\":       {(*BufPane).ResetCmd, OptionValueComplete},\n\t\t\"show\":        {(*BufPane).ShowCmd, OptionComplete},\n\t\t\"showkey\":     {(*BufPane).ShowKeyCmd, nil},\n\t\t\"run\":         {(*BufPane).RunCmd, nil},\n\t\t\"bind\":        {(*BufPane).BindCmd, nil},\n\t\t\"unbind\":      {(*BufPane).UnbindCmd, nil},\n\t\t\"quit\":        {(*BufPane).QuitCmd, nil},\n\t\t\"goto\":        {(*BufPane).GotoCmd, nil},\n\t\t\"jump\":        {(*BufPane).JumpCmd, nil},\n\t\t\"save\":        {(*BufPane).SaveCmd, nil},\n\t\t\"replace\":     {(*BufPane).ReplaceCmd, nil},\n\t\t\"replaceall\":  {(*BufPane).ReplaceAllCmd, nil},\n\t\t\"vsplit\":      {(*BufPane).VSplitCmd, buffer.FileComplete},\n\t\t\"hsplit\":      {(*BufPane).HSplitCmd, buffer.FileComplete},\n\t\t\"tab\":         {(*BufPane).NewTabCmd, buffer.FileComplete},\n\t\t\"help\":        {(*BufPane).HelpCmd, HelpComplete},\n\t\t\"eval\":        {(*BufPane).EvalCmd, nil},\n\t\t\"log\":         {(*BufPane).ToggleLogCmd, nil},\n\t\t\"plugin\":      {(*BufPane).PluginCmd, PluginComplete},\n\t\t\"reload\":      {(*BufPane).ReloadCmd, nil},\n\t\t\"reopen\":      {(*BufPane).ReopenCmd, nil},\n\t\t\"cd\":          {(*BufPane).CdCmd, buffer.FileComplete},\n\t\t\"pwd\":         {(*BufPane).PwdCmd, nil},\n\t\t\"open\":        {(*BufPane).OpenCmd, buffer.FileComplete},\n\t\t\"tabmove\":     {(*BufPane).TabMoveCmd, nil},\n\t\t\"tabswitch\":   {(*BufPane).TabSwitchCmd, nil},\n\t\t\"term\":        {(*BufPane).TermCmd, nil},\n\t\t\"memusage\":    {(*BufPane).MemUsageCmd, nil},\n\t\t\"retab\":       {(*BufPane).RetabCmd, nil},\n\t\t\"raw\":         {(*BufPane).RawCmd, nil},\n\t\t\"textfilter\":  {(*BufPane).TextFilterCmd, nil},\n\t}\n}\n\n// MakeCommand is a function to easily create new commands\n// This can be called by plugins in Lua so that plugins can define their own commands\nfunc MakeCommand(name string, action func(bp *BufPane, args []string), completer buffer.Completer) {\n\tif action != nil {\n\t\tcommands[name] = Command{action, completer}\n\t}\n}\n\n// CommandEditAction returns a bindable function that opens a prompt with\n// the given string and executes the command when the user presses\n// enter\nfunc CommandEditAction(prompt string) BufKeyAction {\n\treturn func(h *BufPane) bool {\n\t\tInfoBar.Prompt(\"> \", prompt, \"Command\", nil, func(resp string, canceled bool) {\n\t\t\tif !canceled {\n\t\t\t\tMainTab().CurPane().HandleCommand(resp)\n\t\t\t}\n\t\t})\n\t\treturn false\n\t}\n}\n\n// CommandAction returns a bindable function which executes the\n// given command\nfunc CommandAction(cmd string) BufKeyAction {\n\treturn func(h *BufPane) bool {\n\t\tMainTab().CurPane().HandleCommand(cmd)\n\t\treturn false\n\t}\n}\n\nvar PluginCmds = []string{\"install\", \"remove\", \"update\", \"available\", \"list\", \"search\"}\n\n// PluginCmd installs, removes, updates, lists, or searches for given plugins\nfunc (h *BufPane) PluginCmd(args []string) {\n\tif len(args) < 1 {\n\t\tInfoBar.Error(\"Not enough arguments\")\n\t\treturn\n\t}\n\n\tif h.Buf.Type != buffer.BTLog {\n\t\th.OpenLogBuf()\n\t}\n\n\tconfig.PluginCommand(buffer.LogBuf, args[0], args[1:])\n}\n\n// RetabCmd changes all spaces to tabs or all tabs to spaces\n// depending on the user's settings\nfunc (h *BufPane) RetabCmd(args []string) {\n\th.Buf.Retab()\n}\n\n// RawCmd opens a new raw view which displays the escape sequences micro\n// is receiving in real-time\nfunc (h *BufPane) RawCmd(args []string) {\n\twidth, height := screen.Screen.Size()\n\tiOffset := config.GetInfoBarOffset()\n\ttp := NewTabFromPane(0, 0, width, height-iOffset, NewRawPane(nil))\n\tTabs.AddTab(tp)\n\tTabs.SetActive(len(Tabs.List) - 1)\n}\n\n// TextFilterCmd filters the selection through the command.\n// Selection goes to the command input.\n// On successful run command output replaces the current selection.\nfunc (h *BufPane) TextFilterCmd(args []string) {\n\tif len(args) == 0 {\n\t\tInfoBar.Error(\"usage: textfilter arguments\")\n\t\treturn\n\t}\n\n\tfor _, c := range h.Buf.GetCursors() {\n\t\tsel := c.GetSelection()\n\t\tfromSelection := len(sel) > 0\n\t\tif !fromSelection {\n\t\t\tc.SelectWord()\n\t\t\tsel = c.GetSelection()\n\t\t}\n\t\tvar bout, berr bytes.Buffer\n\t\tcmd := exec.Command(args[0], args[1:]...)\n\t\tcmd.Stdin = strings.NewReader(string(sel))\n\t\tcmd.Stderr = &berr\n\t\tcmd.Stdout = &bout\n\t\terr := cmd.Run()\n\t\tif err != nil {\n\t\t\tInfoBar.Error(err.Error() + \" \" + berr.String())\n\t\t\treturn\n\t\t}\n\t\tc.DeleteSelection()\n\t\tinsertStart := c.Loc\n\t\tinsertedText := bout.String()\n\t\th.Buf.Insert(c.Loc, insertedText)\n\n\t\tif fromSelection {\n\t\t\t// Select the pasted output if the input was selected\n\t\t\tcharCount := util.CharacterCountInString(insertedText)\n\t\t\tinsertEnd := insertStart.Move(charCount, h.Buf)\n\t\t\tc.SetSelectionStart(insertStart)\n\t\t\tc.SetSelectionEnd(insertEnd)\n\t\t\tc.Loc = insertEnd\n\t\t}\n\t}\n}\n\n// TabMoveCmd moves the current tab to a given index (starts at 1). The\n// displaced tabs are moved up.\nfunc (h *BufPane) TabMoveCmd(args []string) {\n\tif len(args) <= 0 {\n\t\tInfoBar.Error(\"Not enough arguments: provide an index, starting at 1\")\n\t\treturn\n\t}\n\n\tif len(args[0]) <= 0 {\n\t\tInfoBar.Error(\"Invalid argument: empty string\")\n\t\treturn\n\t}\n\n\tnum, err := strconv.Atoi(args[0])\n\tif err != nil {\n\t\tInfoBar.Error(\"Invalid argument: \", err)\n\t\treturn\n\t}\n\n\t// Preserve sign for relative move, if one exists\n\tvar shiftDirection byte\n\tif strings.Contains(\"-+\", string([]byte{args[0][0]})) {\n\t\tshiftDirection = args[0][0]\n\t}\n\n\t// Relative positions -> absolute positions\n\tidxFrom := Tabs.Active()\n\tidxTo := 0\n\toffset := util.Abs(num)\n\tif shiftDirection == '-' {\n\t\tidxTo = idxFrom - offset\n\t} else if shiftDirection == '+' {\n\t\tidxTo = idxFrom + offset\n\t} else {\n\t\tidxTo = offset - 1\n\t}\n\n\t// Restrain position to within the valid range\n\tidxTo = util.Clamp(idxTo, 0, len(Tabs.List)-1)\n\n\tactiveTab := Tabs.List[idxFrom]\n\tTabs.RemoveTab(activeTab.Panes[0].ID())\n\tTabs.List = append(Tabs.List, nil)\n\tcopy(Tabs.List[idxTo+1:], Tabs.List[idxTo:])\n\tTabs.List[idxTo] = activeTab\n\tTabs.Resize()\n\tTabs.UpdateNames()\n\tTabs.SetActive(idxTo)\n\t// InfoBar.Message(fmt.Sprintf(\"Moved tab from slot %d to %d\", idxFrom+1, idxTo+1))\n}\n\n// TabSwitchCmd switches to a given tab either by name or by number\nfunc (h *BufPane) TabSwitchCmd(args []string) {\n\tif len(args) > 0 {\n\t\tnum, err := strconv.Atoi(args[0])\n\t\tif err != nil {\n\t\t\t// Check for tab with this name\n\n\t\t\tfound := false\n\t\t\tfor i, t := range Tabs.List {\n\t\t\t\tif t.Panes[t.active].Name() == args[0] {\n\t\t\t\t\tTabs.SetActive(i)\n\t\t\t\t\tfound = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !found {\n\t\t\t\tInfoBar.Error(\"Could not find tab: \", err)\n\t\t\t}\n\t\t} else {\n\t\t\tnum--\n\t\t\tif num >= 0 && num < len(Tabs.List) {\n\t\t\t\tTabs.SetActive(num)\n\t\t\t} else {\n\t\t\t\tInfoBar.Error(\"Invalid tab index\")\n\t\t\t}\n\t\t}\n\t}\n}\n\n// CdCmd changes the current working directory\nfunc (h *BufPane) CdCmd(args []string) {\n\tif len(args) > 0 {\n\t\tpath, err := util.ReplaceHome(args[0])\n\t\tif err != nil {\n\t\t\tInfoBar.Error(err)\n\t\t\treturn\n\t\t}\n\t\terr = os.Chdir(path)\n\t\tif err != nil {\n\t\t\tInfoBar.Error(err)\n\t\t\treturn\n\t\t}\n\t\twd, _ := os.Getwd()\n\t\tfor _, b := range buffer.OpenBuffers {\n\t\t\tif len(b.Path) > 0 {\n\t\t\t\tb.Path, _ = util.MakeRelative(b.AbsPath, wd)\n\t\t\t\tif p, _ := filepath.Abs(b.Path); !strings.Contains(p, wd) {\n\t\t\t\t\tb.Path = b.AbsPath\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// MemUsageCmd prints micro's memory usage\n// Alloc shows how many bytes are currently in use\n// Sys shows how many bytes have been requested from the operating system\n// NumGC shows how many times the GC has been run\n// Note that Go commonly reserves more memory from the OS than is currently in-use/required\n// Additionally, even if Go returns memory to the OS, the OS does not always claim it because\n// there may be plenty of memory to spare\nfunc (h *BufPane) MemUsageCmd(args []string) {\n\tInfoBar.Message(util.GetMemStats())\n}\n\n// PwdCmd prints the current working directory\nfunc (h *BufPane) PwdCmd(args []string) {\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\tInfoBar.Message(err.Error())\n\t} else {\n\t\tInfoBar.Message(wd)\n\t}\n}\n\n// OpenCmd opens a new buffer with a given filename\nfunc (h *BufPane) OpenCmd(args []string) {\n\tif len(args) > 0 {\n\t\topen := func() {\n\t\t\tb, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault)\n\t\t\tif err != nil {\n\t\t\t\tInfoBar.Error(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\th.OpenBuffer(b)\n\t\t}\n\t\tif h.Buf.Modified() && !h.Buf.Shared() {\n\t\t\th.closePrompt(\"Save\", open)\n\t\t} else {\n\t\t\topen()\n\t\t}\n\t} else {\n\t\tInfoBar.Error(\"No filename\")\n\t}\n}\n\n// ToggleLogCmd toggles the log view\nfunc (h *BufPane) ToggleLogCmd(args []string) {\n\tif h.Buf.Type != buffer.BTLog {\n\t\th.OpenLogBuf()\n\t} else {\n\t\th.Quit()\n\t}\n}\n\n// ReloadCmd reloads all files (syntax files, colorschemes, plugins...)\nfunc (h *BufPane) ReloadCmd(args []string) {\n\treloadRuntime(true)\n}\n\n// ReloadConfig reloads only the configuration\nfunc ReloadConfig() {\n\treloadRuntime(false)\n}\n\nfunc reloadRuntime(reloadPlugins bool) {\n\tif reloadPlugins {\n\t\terr := config.RunPluginFn(\"deinit\")\n\t\tif err != nil {\n\t\t\tscreen.TermMessage(err)\n\t\t}\n\t}\n\n\tconfig.InitRuntimeFiles(true)\n\n\tif reloadPlugins {\n\t\tconfig.InitPlugins()\n\t}\n\n\terr := config.ReadSettings()\n\tif err != nil {\n\t\tscreen.TermMessage(err)\n\t} else {\n\t\tparsedSettings := config.ParsedSettings()\n\t\tdefaultSettings := config.DefaultAllSettings()\n\t\tfor k := range defaultSettings {\n\t\t\tif _, ok := config.VolatileSettings[k]; ok {\n\t\t\t\t// reload should not override volatile settings\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif _, ok := parsedSettings[k]; ok {\n\t\t\t\terr = doSetGlobalOptionNative(k, parsedSettings[k])\n\t\t\t} else {\n\t\t\t\terr = doSetGlobalOptionNative(k, defaultSettings[k])\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tscreen.TermMessage(err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif reloadPlugins {\n\t\terr = config.LoadAllPlugins()\n\t\tif err != nil {\n\t\t\tscreen.TermMessage(err)\n\t\t}\n\t}\n\n\tInitBindings()\n\tInitCommands()\n\n\tif reloadPlugins {\n\t\terr = config.RunPluginFn(\"preinit\")\n\t\tif err != nil {\n\t\t\tscreen.TermMessage(err)\n\t\t}\n\t\terr = config.RunPluginFn(\"init\")\n\t\tif err != nil {\n\t\t\tscreen.TermMessage(err)\n\t\t}\n\t\terr = config.RunPluginFn(\"postinit\")\n\t\tif err != nil {\n\t\t\tscreen.TermMessage(err)\n\t\t}\n\t}\n\n\terr = config.InitColorscheme()\n\tif err != nil {\n\t\tscreen.TermMessage(err)\n\t}\n\tfor _, b := range buffer.OpenBuffers {\n\t\tb.ReloadSettings(true)\n\t}\n}\n\n// ReopenCmd reopens the buffer (reload from disk)\nfunc (h *BufPane) ReopenCmd(args []string) {\n\tif h.Buf.Modified() {\n\t\tInfoBar.YNPrompt(\"Save file before reopen?\", func(yes, canceled bool) {\n\t\t\tif !canceled && yes {\n\t\t\t\th.Save()\n\t\t\t\th.ReOpen()\n\t\t\t} else if !canceled {\n\t\t\t\th.ReOpen()\n\t\t\t}\n\t\t})\n\t} else {\n\t\th.ReOpen()\n\t}\n}\n\nfunc (h *BufPane) openHelp(page string, hsplit bool, forceSplit bool) error {\n\tif data, err := config.FindRuntimeFile(config.RTHelp, page).Data(); err != nil {\n\t\treturn errors.New(fmt.Sprintf(\"Unable to load help text for %s: %v\", page, err))\n\t} else {\n\t\thelpBuffer := buffer.NewBufferFromString(string(data), page+\".md\", buffer.BTHelp)\n\t\thelpBuffer.SetName(\"Help \" + page)\n\t\thelpBuffer.SetOptionNative(\"hltaberrors\", false)\n\t\thelpBuffer.SetOptionNative(\"hltrailingws\", false)\n\n\t\tif h.Buf.Type == buffer.BTHelp && !forceSplit {\n\t\t\th.OpenBuffer(helpBuffer)\n\t\t} else if hsplit {\n\t\t\th.HSplitBuf(helpBuffer)\n\t\t} else {\n\t\t\th.VSplitBuf(helpBuffer)\n\t\t}\n\t}\n\treturn nil\n}\n\n// HelpCmd tries to open the given help page according to the split type\n// configured with the \"helpsplit\" option. It can be overridden by the optional\n// arguments \"-vpslit\" or \"-hsplit\". In case more than one help page is given\n// as argument then it opens all of them with the defined split type.\nfunc (h *BufPane) HelpCmd(args []string) {\n\thsplit := config.GlobalSettings[\"helpsplit\"] == \"hsplit\"\n\tif len(args) < 1 {\n\t\t// Open the default help if the user just typed \"> help\"\n\t\th.openHelp(\"help\", hsplit, false)\n\t} else {\n\t\tvar topics []string\n\t\tforceSplit := false\n\t\tconst errSplit = \"hsplit and vsplit are not allowed at the same time\"\n\t\tfor _, arg := range args {\n\t\t\tswitch arg {\n\t\t\tcase \"-vsplit\":\n\t\t\t\tif forceSplit {\n\t\t\t\t\tInfoBar.Error(errSplit)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\thsplit = false\n\t\t\t\tforceSplit = true\n\t\t\tcase \"-hsplit\":\n\t\t\t\tif forceSplit {\n\t\t\t\t\tInfoBar.Error(errSplit)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\thsplit = true\n\t\t\t\tforceSplit = true\n\t\t\tdefault:\n\t\t\t\ttopics = append(topics, arg)\n\t\t\t}\n\t\t}\n\n\t\tif len(topics) < 1 {\n\t\t\t// Do the same as without arg\n\t\t\th.openHelp(\"help\", hsplit, forceSplit)\n\t\t\treturn\n\t\t}\n\t\tif len(topics) > 1 {\n\t\t\tforceSplit = true\n\t\t}\n\n\t\tfor _, topic := range topics {\n\t\t\tif config.FindRuntimeFile(config.RTHelp, topic) != nil {\n\t\t\t\terr := h.openHelp(topic, hsplit, forceSplit)\n\t\t\t\tif err != nil {\n\t\t\t\t\tInfoBar.Error(err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tInfoBar.Error(\"Sorry, no help for \", topic)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// VSplitCmd opens one or more vertical splits with the files given as arguments\n// If no file is given, it opens an empty buffer in a new split\nfunc (h *BufPane) VSplitCmd(args []string) {\n\tif len(args) == 0 {\n\t\t// Open an empty vertical split\n\t\th.VSplitAction()\n\t\treturn\n\t}\n\n\tfor _, a := range args {\n\t\tbuf, err := buffer.NewBufferFromFile(a, buffer.BTDefault)\n\t\tif err != nil {\n\t\t\tInfoBar.Error(err)\n\t\t\treturn\n\t\t}\n\n\t\th.VSplitBuf(buf)\n\t}\n}\n\n// HSplitCmd opens one or more horizontal splits with the files given as arguments\n// If no file is given, it opens an empty buffer in a new split\nfunc (h *BufPane) HSplitCmd(args []string) {\n\tif len(args) == 0 {\n\t\t// Open an empty horizontal split\n\t\th.HSplitAction()\n\t\treturn\n\t}\n\n\tfor _, a := range args {\n\t\tbuf, err := buffer.NewBufferFromFile(a, buffer.BTDefault)\n\t\tif err != nil {\n\t\t\tInfoBar.Error(err)\n\t\t\treturn\n\t\t}\n\n\t\th.HSplitBuf(buf)\n\t}\n}\n\n// EvalCmd evaluates a lua expression\nfunc (h *BufPane) EvalCmd(args []string) {\n\tInfoBar.Error(\"Eval unsupported\")\n}\n\n// NewTabCmd opens one or more tabs with the files given as arguments\n// If no file is given, it opens an empty buffer in a new tab\nfunc (h *BufPane) NewTabCmd(args []string) {\n\twidth, height := screen.Screen.Size()\n\tiOffset := config.GetInfoBarOffset()\n\tif len(args) > 0 {\n\t\tfor _, a := range args {\n\t\t\tb, err := buffer.NewBufferFromFile(a, buffer.BTDefault)\n\t\t\tif err != nil {\n\t\t\t\tInfoBar.Error(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttp := NewTabFromBuffer(0, 0, width, height-1-iOffset, b)\n\t\t\tTabs.AddTab(tp)\n\t\t\tTabs.SetActive(len(Tabs.List) - 1)\n\t\t}\n\t} else {\n\t\tb := buffer.NewBufferFromString(\"\", \"\", buffer.BTDefault)\n\t\ttp := NewTabFromBuffer(0, 0, width, height-iOffset, b)\n\t\tTabs.AddTab(tp)\n\t\tTabs.SetActive(len(Tabs.List) - 1)\n\t}\n}\n\nfunc doSetGlobalOptionNative(option string, nativeValue any) error {\n\tif reflect.DeepEqual(config.GlobalSettings[option], nativeValue) {\n\t\treturn nil\n\t}\n\n\tconfig.GlobalSettings[option] = nativeValue\n\tconfig.ModifiedSettings[option] = true\n\tdelete(config.VolatileSettings, option)\n\n\tif option == \"colorscheme\" {\n\t\t// LoadSyntaxFiles()\n\t\tconfig.InitColorscheme()\n\t\tfor _, b := range buffer.OpenBuffers {\n\t\t\tb.UpdateRules()\n\t\t}\n\t} else if option == \"infobar\" || option == \"keymenu\" {\n\t\tTabs.Resize()\n\t} else if option == \"mouse\" {\n\t\tif !nativeValue.(bool) {\n\t\t\tscreen.Screen.DisableMouse()\n\t\t} else {\n\t\t\tscreen.Screen.EnableMouse()\n\t\t}\n\t} else if option == \"autosave\" {\n\t\tif nativeValue.(float64) > 0 {\n\t\t\tconfig.SetAutoTime(nativeValue.(float64))\n\t\t} else {\n\t\t\tconfig.SetAutoTime(0)\n\t\t}\n\t} else if option == \"paste\" {\n\t\tscreen.Screen.SetPaste(nativeValue.(bool))\n\t} else if option == \"clipboard\" {\n\t\tm := clipboard.SetMethod(nativeValue.(string))\n\t\terr := clipboard.Initialize(m)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tfor _, pl := range config.Plugins {\n\t\t\tif option == pl.Name {\n\t\t\t\tif nativeValue.(bool) && !pl.Loaded {\n\t\t\t\t\tpl.Load()\n\t\t\t\t\t_, err := pl.Call(\"init\")\n\t\t\t\t\tif err != nil && err != config.ErrNoSuchFunction {\n\t\t\t\t\t\tscreen.TermMessage(err)\n\t\t\t\t\t}\n\t\t\t\t} else if !nativeValue.(bool) && pl.Loaded {\n\t\t\t\t\t_, err := pl.Call(\"deinit\")\n\t\t\t\t\tif err != nil && err != config.ErrNoSuchFunction {\n\t\t\t\t\t\tscreen.TermMessage(err)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc SetGlobalOptionNative(option string, nativeValue any, writeToFile bool) error {\n\tif err := config.OptionIsValid(option, nativeValue); err != nil {\n\t\treturn err\n\t}\n\n\t// check for local option first...\n\tfor _, s := range config.LocalSettings {\n\t\tif s == option {\n\t\t\treturn MainTab().CurPane().Buf.SetOptionNative(option, nativeValue)\n\t\t}\n\t}\n\n\t// ...if it's not local continue with the globals...\n\tif err := doSetGlobalOptionNative(option, nativeValue); err != nil {\n\t\treturn err\n\t}\n\n\t// ...at last check the buffer locals\n\tfor _, b := range buffer.OpenBuffers {\n\t\tb.DoSetOptionNative(option, nativeValue)\n\t\tdelete(b.LocalSettings, option)\n\t}\n\n\tif !writeToFile {\n\t\treturn nil\n\t}\n\n\terr := config.WriteSettings(filepath.Join(config.ConfigDir, \"settings.json\"))\n\tif err != nil {\n\t\tif errors.Is(err, util.ErrOverwrite) {\n\t\t\tscreen.TermMessage(err)\n\t\t\terr = errors.Unwrap(err)\n\t\t}\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc SetGlobalOption(option, value string, writeToFile bool) error {\n\tif _, ok := config.GlobalSettings[option]; !ok {\n\t\treturn config.ErrInvalidOption\n\t}\n\n\tnativeValue, err := config.GetNativeValue(option, value)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn SetGlobalOptionNative(option, nativeValue, writeToFile)\n}\n\nfunc SetGlobalOptionNativePlug(option string, nativeValue any) error {\n\treturn SetGlobalOptionNative(option, nativeValue, false)\n}\n\nfunc SetGlobalOptionPlug(option, value string) error {\n\treturn SetGlobalOption(option, value, false)\n}\n\n// ResetCmd resets a setting to its default value\nfunc (h *BufPane) ResetCmd(args []string) {\n\tif len(args) < 1 {\n\t\tInfoBar.Error(\"Not enough arguments\")\n\t\treturn\n\t}\n\n\toption := args[0]\n\tdefaults := config.DefaultAllSettings()\n\n\tif _, ok := defaults[option]; ok {\n\t\tSetGlobalOptionNative(option, defaults[option], true)\n\t\treturn\n\t}\n\tInfoBar.Error(config.ErrInvalidOption)\n}\n\n// SetCmd sets an option\nfunc (h *BufPane) SetCmd(args []string) {\n\tif len(args) < 2 {\n\t\tInfoBar.Error(\"Not enough arguments\")\n\t\treturn\n\t}\n\n\toption := args[0]\n\tvalue := args[1]\n\n\terr := SetGlobalOption(option, value, true)\n\tif err == config.ErrInvalidOption {\n\t\terr := h.Buf.SetOption(option, value)\n\t\tif err != nil {\n\t\t\tInfoBar.Error(err)\n\t\t}\n\t} else if err != nil {\n\t\tInfoBar.Error(err)\n\t}\n}\n\n// SetLocalCmd sets an option local to the buffer\nfunc (h *BufPane) SetLocalCmd(args []string) {\n\tif len(args) < 2 {\n\t\tInfoBar.Error(\"Not enough arguments\")\n\t\treturn\n\t}\n\n\toption := args[0]\n\tvalue := args[1]\n\n\terr := h.Buf.SetOption(option, value)\n\tif err != nil {\n\t\tInfoBar.Error(err)\n\t}\n}\n\nfunc (h *BufPane) toggleOption(option string, local bool) error {\n\tvar curVal, newVal any\n\n\tif local {\n\t\tcurVal = h.Buf.Settings[option]\n\t} else {\n\t\tcurVal = config.GetGlobalOption(option)\n\t}\n\tif curVal == nil {\n\t\treturn config.ErrInvalidOption\n\t}\n\n\tif choices, ok := config.OptionChoices[option]; ok && len(choices) == 2 {\n\t\tif curVal == choices[0] {\n\t\t\tnewVal = choices[1]\n\t\t} else {\n\t\t\tnewVal = choices[0]\n\t\t}\n\t} else if curValBool, ok := curVal.(bool); ok {\n\t\tnewVal = !curValBool\n\t} else {\n\t\treturn config.ErrOptNotToggleable\n\t}\n\n\tif local {\n\t\tif err := h.Buf.SetOptionNative(option, newVal); err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tif err := SetGlobalOptionNative(option, newVal, true); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ToggleCmd toggles a toggleable option\nfunc (h *BufPane) ToggleCmd(args []string) {\n\tif len(args) < 1 {\n\t\tInfoBar.Error(\"Not enough arguments: provide a toggleable option\")\n\t\treturn\n\t}\n\tif err := h.toggleOption(args[0], false); err != nil {\n\t\tInfoBar.Error(err)\n\t}\n}\n\n// ToggleCmd toggles a toggleable option local to the buffer\nfunc (h *BufPane) ToggleLocalCmd(args []string) {\n\tif len(args) < 1 {\n\t\tInfoBar.Error(\"Not enough arguments: provide a toggleable option\")\n\t\treturn\n\t}\n\tif err := h.toggleOption(args[0], true); err != nil {\n\t\tInfoBar.Error(err)\n\t}\n}\n\n// ShowCmd shows the value of the given option\nfunc (h *BufPane) ShowCmd(args []string) {\n\tif len(args) < 1 {\n\t\tInfoBar.Error(\"Please provide an option to show\")\n\t\treturn\n\t}\n\n\tvar option any\n\tif opt, ok := h.Buf.Settings[args[0]]; ok {\n\t\toption = opt\n\t} else if opt, ok := config.GlobalSettings[args[0]]; ok {\n\t\toption = opt\n\t}\n\n\tif option == nil {\n\t\tInfoBar.Error(args[0], \" is not a valid option\")\n\t\treturn\n\t}\n\n\tInfoBar.Message(option)\n}\n\nfunc parseKeyArg(arg string) string {\n\t// If this is a raw escape sequence, convert it to its raw byte form\n\treturn strings.ReplaceAll(arg, \"\\\\x1b\", \"\\x1b\")\n}\n\n// ShowKeyCmd displays the action that a key is bound to\nfunc (h *BufPane) ShowKeyCmd(args []string) {\n\tif len(args) < 1 {\n\t\tInfoBar.Error(\"Please provide a key to show\")\n\t\treturn\n\t}\n\n\tevent, err := findEvent(parseKeyArg(args[0]))\n\tif err != nil {\n\t\tInfoBar.Error(err)\n\t\treturn\n\t}\n\tif action, ok := config.Bindings[\"buffer\"][event.Name()]; ok {\n\t\tInfoBar.Message(action)\n\t} else {\n\t\tInfoBar.Message(args[0], \" has no binding\")\n\t}\n}\n\n// BindCmd creates a new keybinding\nfunc (h *BufPane) BindCmd(args []string) {\n\tif len(args) < 2 {\n\t\tInfoBar.Error(\"Not enough arguments\")\n\t\treturn\n\t}\n\n\t_, err := TryBindKey(parseKeyArg(args[0]), args[1], true, true)\n\tif err != nil {\n\t\tif errors.Is(err, util.ErrOverwrite) {\n\t\t\tscreen.TermMessage(err)\n\t\t} else {\n\t\t\tInfoBar.Error(err)\n\t\t}\n\t}\n}\n\n// UnbindCmd binds a key to its default action\nfunc (h *BufPane) UnbindCmd(args []string) {\n\tif len(args) < 1 {\n\t\tInfoBar.Error(\"Not enough arguments\")\n\t\treturn\n\t}\n\n\terr := UnbindKey(parseKeyArg(args[0]))\n\tif err != nil {\n\t\tif errors.Is(err, util.ErrOverwrite) {\n\t\t\tscreen.TermMessage(err)\n\t\t} else {\n\t\t\tInfoBar.Error(err)\n\t\t}\n\t}\n}\n\n// RunCmd runs a shell command in the background\nfunc (h *BufPane) RunCmd(args []string) {\n\trunf, err := shell.RunBackgroundShell(shellquote.Join(args...))\n\tif err != nil {\n\t\tInfoBar.Error(err)\n\t} else {\n\t\tgo func() {\n\t\t\tInfoBar.Message(runf())\n\t\t\tscreen.Redraw()\n\t\t}()\n\t}\n}\n\n// QuitCmd closes the main view\nfunc (h *BufPane) QuitCmd(args []string) {\n\th.Quit()\n}\n\n// GotoCmd is a command that will send the cursor to a certain\n// position in the buffer\n// For example: `goto line`, or `goto line:col`\nfunc (h *BufPane) GotoCmd(args []string) {\n\tline, col, err := h.parseLineCol(args)\n\tif err != nil {\n\t\tInfoBar.Error(err)\n\t\treturn\n\t}\n\n\tif line < 0 {\n\t\tline = h.Buf.LinesNum() + 1 + line\n\t}\n\tline = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)\n\tcol = util.Clamp(col-1, 0, util.CharacterCount(h.Buf.LineBytes(line)))\n\n\th.RemoveAllMultiCursors()\n\th.Cursor.Deselect(true)\n\th.GotoLoc(buffer.Loc{col, line})\n}\n\n// JumpCmd is a command that will send the cursor to a certain relative\n// position in the buffer\n// For example: `jump line`, `jump -line`, or `jump -line:col`\nfunc (h *BufPane) JumpCmd(args []string) {\n\tline, col, err := h.parseLineCol(args)\n\tif err != nil {\n\t\tInfoBar.Error(err)\n\t\treturn\n\t}\n\n\tline = h.Buf.GetActiveCursor().Y + 1 + line\n\tline = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)\n\tcol = util.Clamp(col-1, 0, util.CharacterCount(h.Buf.LineBytes(line)))\n\n\th.RemoveAllMultiCursors()\n\th.Cursor.Deselect(true)\n\th.GotoLoc(buffer.Loc{col, line})\n}\n\n// parseLineCol is a helper to parse the input of GotoCmd and JumpCmd\nfunc (h *BufPane) parseLineCol(args []string) (line int, col int, err error) {\n\tif len(args) <= 0 {\n\t\treturn 0, 0, errors.New(\"Not enough arguments\")\n\t}\n\n\tline, col = 0, 0\n\tif strings.Contains(args[0], \":\") {\n\t\tparts := strings.SplitN(args[0], \":\", 2)\n\t\tline, err = strconv.Atoi(parts[0])\n\t\tif err != nil {\n\t\t\treturn 0, 0, err\n\t\t}\n\t\tcol, err = strconv.Atoi(parts[1])\n\t\tif err != nil {\n\t\t\treturn 0, 0, err\n\t\t}\n\t} else {\n\t\tline, err = strconv.Atoi(args[0])\n\t\tif err != nil {\n\t\t\treturn 0, 0, err\n\t\t}\n\t}\n\n\treturn line, col, nil\n}\n\n// SaveCmd saves the buffer optionally with an argument file name\nfunc (h *BufPane) SaveCmd(args []string) {\n\tif len(args) == 0 {\n\t\th.Save()\n\t} else {\n\t\th.saveBufToFile(args[0], \"SaveAs\", nil)\n\t}\n}\n\n// ReplaceCmd runs search and replace\nfunc (h *BufPane) ReplaceCmd(args []string) {\n\tif len(args) < 2 || len(args) > 4 {\n\t\t// We need to find both a search and replace expression\n\t\tInfoBar.Error(\"Invalid replace statement: \" + strings.Join(args, \" \"))\n\t\treturn\n\t}\n\n\tall := false\n\tnoRegex := false\n\n\tfoundSearch := false\n\tfoundReplace := false\n\tvar search string\n\tvar replaceStr string\n\tfor _, arg := range args {\n\t\tswitch arg {\n\t\tcase \"-a\":\n\t\t\tall = true\n\t\tcase \"-l\":\n\t\t\tnoRegex = true\n\t\tdefault:\n\t\t\tif !foundSearch {\n\t\t\t\tfoundSearch = true\n\t\t\t\tsearch = arg\n\t\t\t} else if !foundReplace {\n\t\t\t\tfoundReplace = true\n\t\t\t\treplaceStr = arg\n\t\t\t} else {\n\t\t\t\tInfoBar.Error(\"Invalid flag: \" + arg)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tif noRegex {\n\t\tsearch = regexp.QuoteMeta(search)\n\t}\n\n\treplace := []byte(replaceStr)\n\n\tvar regex *regexp.Regexp\n\tvar err error\n\tif h.Buf.Settings[\"ignorecase\"].(bool) {\n\t\tregex, err = regexp.Compile(\"(?im)\" + search)\n\t} else {\n\t\tregex, err = regexp.Compile(\"(?m)\" + search)\n\t}\n\tif err != nil {\n\t\t// There was an error with the user's regex\n\t\tInfoBar.Error(err)\n\t\treturn\n\t}\n\n\tnreplaced := 0\n\tstart := h.Buf.Start()\n\tend := h.Buf.End()\n\tsearchLoc := h.Cursor.Loc\n\tselection := h.Cursor.HasSelection()\n\tif selection {\n\t\tstart = h.Cursor.CurSelection[0]\n\t\tend = h.Cursor.CurSelection[1]\n\t\tsearchLoc = start // otherwise me might start at the end\n\t}\n\tif all {\n\t\tnreplaced, _ = h.Buf.ReplaceRegex(start, end, regex, replace, !noRegex)\n\t} else {\n\t\tinRange := func(l buffer.Loc) bool {\n\t\t\treturn l.GreaterEqual(start) && l.LessEqual(end)\n\t\t}\n\n\t\tlastMatchEnd := buffer.Loc{-1, -1}\n\t\tvar doReplacement func()\n\t\tdoReplacement = func() {\n\t\t\tlocs, found, err := h.Buf.FindNext(search, start, end, searchLoc, true, true)\n\t\t\tif err != nil {\n\t\t\t\tInfoBar.Error(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !found || !inRange(locs[0]) || !inRange(locs[1]) {\n\t\t\t\th.Cursor.ResetSelection()\n\t\t\t\th.Buf.RelocateCursors()\n\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif lastMatchEnd == locs[1] {\n\t\t\t\t// skip empty match right after previous match\n\t\t\t\tif searchLoc == end {\n\t\t\t\t\tsearchLoc = start\n\t\t\t\t\tlastMatchEnd = buffer.Loc{-1, -1}\n\t\t\t\t} else {\n\t\t\t\t\tsearchLoc = searchLoc.Move(1, h.Buf)\n\t\t\t\t}\n\t\t\t\tdoReplacement()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\th.Cursor.SetSelectionStart(locs[0])\n\t\t\th.Cursor.SetSelectionEnd(locs[1])\n\t\t\th.GotoLoc(locs[0])\n\t\t\th.Buf.LastSearch = search\n\t\t\th.Buf.LastSearchRegex = true\n\t\t\th.Buf.HighlightSearch = h.Buf.Settings[\"hlsearch\"].(bool)\n\n\t\t\tInfoBar.YNPrompt(\"Perform replacement (y,n,esc)\", func(yes, canceled bool) {\n\t\t\t\tif !canceled && yes {\n\t\t\t\t\t_, nrunes := h.Buf.ReplaceRegex(locs[0], locs[1], regex, replace, !noRegex)\n\n\t\t\t\t\tsearchLoc = locs[0]\n\t\t\t\t\tsearchLoc.X += nrunes + locs[0].Diff(locs[1], h.Buf)\n\t\t\t\t\tif end.Y == locs[1].Y {\n\t\t\t\t\t\tend = end.Move(nrunes, h.Buf)\n\t\t\t\t\t}\n\t\t\t\t\th.Cursor.Loc = searchLoc\n\t\t\t\t\tnreplaced++\n\t\t\t\t} else if !canceled && !yes {\n\t\t\t\t\tsearchLoc = locs[1]\n\t\t\t\t} else if canceled {\n\t\t\t\t\th.Cursor.ResetSelection()\n\t\t\t\t\th.Buf.RelocateCursors()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tlastMatchEnd = searchLoc\n\t\t\t\tdoReplacement()\n\t\t\t})\n\t\t}\n\t\tdoReplacement()\n\t}\n\n\th.Buf.RelocateCursors()\n\th.Relocate()\n\n\tvar s string\n\tif nreplaced > 1 {\n\t\ts = fmt.Sprintf(\"Replaced %d occurrences of %s\", nreplaced, search)\n\t} else if nreplaced == 1 {\n\t\ts = fmt.Sprintf(\"Replaced 1 occurrence of %s\", search)\n\t} else {\n\t\ts = fmt.Sprintf(\"Nothing matched %s\", search)\n\t}\n\n\tif selection {\n\t\ts += \" in selection\"\n\t}\n\n\tInfoBar.Message(s)\n}\n\n// ReplaceAllCmd replaces search term all at once\nfunc (h *BufPane) ReplaceAllCmd(args []string) {\n\t// aliased to Replace command\n\th.ReplaceCmd(append(args, \"-a\"))\n}\n\nfunc (h *BufPane) openTerm(args []string, newtab bool) {\n\tt := new(shell.Terminal)\n\terr := t.Start(args, false, true, nil, nil)\n\tif err != nil {\n\t\tInfoBar.Error(err)\n\t\treturn\n\t}\n\n\tpane := 0\n\tid := h.ID()\n\tif newtab {\n\t\th.AddTab()\n\t\tid = MainTab().Panes[pane].ID()\n\t} else {\n\t\tfor i, p := range MainTab().Panes {\n\t\t\tif p.IsActive() {\n\t\t\t\tpane = i\n\t\t\t\tid = p.ID()\n\t\t\t\tp.Close()\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tv := h.GetView()\n\ttp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())\n\tif err != nil {\n\t\tInfoBar.Error(err)\n\t\treturn\n\t}\n\tMainTab().Panes[pane] = tp\n\tMainTab().SetActive(pane)\n}\n\n// TermCmd opens a terminal in the current view\nfunc (h *BufPane) TermCmd(args []string) {\n\tif !TermEmuSupported {\n\t\tInfoBar.Error(\"Terminal emulator not supported on this system\")\n\t\treturn\n\t}\n\n\tif len(args) == 0 {\n\t\tsh := os.Getenv(\"SHELL\")\n\t\tif sh == \"\" {\n\t\t\tInfoBar.Error(\"Shell environment not found\")\n\t\t\treturn\n\t\t}\n\t\targs = []string{sh}\n\t}\n\n\t// If there is only one open file we make a new tab instead of overwriting it\n\tnewtab := len(MainTab().Panes) == 1 && len(Tabs.List) == 1\n\tif newtab {\n\t\th.openTerm(args, true)\n\t\treturn\n\t}\n\n\tif h.Buf.Modified() && !h.Buf.Shared() {\n\t\th.closePrompt(\"Save\", func() {\n\t\t\th.openTerm(args, false)\n\t\t})\n\t} else {\n\t\th.openTerm(args, false)\n\t}\n}\n\n// HandleCommand handles input from the user\nfunc (h *BufPane) HandleCommand(input string) {\n\targs, err := shellquote.Split(input)\n\tif err != nil {\n\t\tInfoBar.Error(\"Error parsing args \", err)\n\t\treturn\n\t}\n\n\tif len(args) == 0 {\n\t\treturn\n\t}\n\n\tinputCmd := args[0]\n\n\tif _, ok := commands[inputCmd]; !ok {\n\t\tInfoBar.Error(\"Unknown command \", inputCmd)\n\t} else {\n\t\tWriteLog(\"> \" + input + \"\\n\")\n\t\tcommands[inputCmd].action(h, args[1:])\n\t\tWriteLog(\"\\n\")\n\t}\n}\n"
  },
  {
    "path": "internal/action/defaults.go",
    "content": "package action\n\nvar termdefaults = map[string]string{\n\t\"<Ctrl-q><Ctrl-q>\": \"Exit\",\n\t\"<Ctrl-e><Ctrl-e>\": \"CommandMode\",\n\t\"<Ctrl-w><Ctrl-w>\": \"NextSplit|FirstSplit\",\n}\n\n// DefaultBindings returns a map containing micro's default keybindings\nfunc DefaultBindings(pane string) map[string]string {\n\tswitch pane {\n\tcase \"command\":\n\t\treturn infodefaults\n\tcase \"buffer\":\n\t\treturn bufdefaults\n\tcase \"terminal\":\n\t\treturn termdefaults\n\tdefault:\n\t\treturn map[string]string{}\n\t}\n}\n"
  },
  {
    "path": "internal/action/defaults_darwin.go",
    "content": "package action\n\nvar bufdefaults = map[string]string{\n\t\"Up\":             \"CursorUp\",\n\t\"Down\":           \"CursorDown\",\n\t\"Right\":          \"CursorRight\",\n\t\"Left\":           \"CursorLeft\",\n\t\"ShiftUp\":        \"SelectUp\",\n\t\"ShiftDown\":      \"SelectDown\",\n\t\"ShiftLeft\":      \"SelectLeft\",\n\t\"ShiftRight\":     \"SelectRight\",\n\t\"AltLeft\":        \"WordLeft\",\n\t\"AltRight\":       \"WordRight\",\n\t\"AltUp\":          \"MoveLinesUp\",\n\t\"AltDown\":        \"MoveLinesDown\",\n\t\"AltShiftRight\":  \"SelectWordRight\",\n\t\"AltShiftLeft\":   \"SelectWordLeft\",\n\t\"CtrlLeft\":       \"StartOfTextToggle\",\n\t\"CtrlRight\":      \"EndOfLine\",\n\t\"CtrlShiftLeft\":  \"SelectToStartOfTextToggle\",\n\t\"ShiftHome\":      \"SelectToStartOfTextToggle\",\n\t\"CtrlShiftRight\": \"SelectToEndOfLine\",\n\t\"ShiftEnd\":       \"SelectToEndOfLine\",\n\t\"CtrlUp\":         \"CursorStart\",\n\t\"CtrlDown\":       \"CursorEnd\",\n\t\"CtrlShiftUp\":    \"SelectToStart\",\n\t\"CtrlShiftDown\":  \"SelectToEnd\",\n\t\"Alt-{\":          \"ParagraphPrevious\",\n\t\"Alt-}\":          \"ParagraphNext\",\n\t\"Enter\":          \"InsertNewline\",\n\t\"CtrlH\":          \"Backspace\",\n\t\"Backspace\":      \"Backspace\",\n\t\"OldBackspace\":   \"Backspace\",\n\t\"Alt-CtrlH\":      \"DeleteWordLeft\",\n\t\"Alt-Backspace\":  \"DeleteWordLeft\",\n\t\"Tab\":            \"Autocomplete|IndentSelection|InsertTab\",\n\t\"Backtab\":        \"CycleAutocompleteBack|OutdentSelection|OutdentLine\",\n\t\"Ctrl-o\":         \"OpenFile\",\n\t\"Ctrl-s\":         \"Save\",\n\t\"Ctrl-f\":         \"Find\",\n\t\"Alt-F\":          \"FindLiteral\",\n\t\"Ctrl-n\":         \"FindNext\",\n\t\"Ctrl-p\":         \"FindPrevious\",\n\t\"Alt-[\":          \"DiffPrevious|CursorStart\",\n\t\"Alt-]\":          \"DiffNext|CursorEnd\",\n\t\"Ctrl-z\":         \"Undo\",\n\t\"Ctrl-y\":         \"Redo\",\n\t\"Ctrl-c\":         \"Copy|CopyLine\",\n\t\"Ctrl-x\":         \"Cut|CutLine\",\n\t\"Ctrl-k\":         \"CutLine\",\n\t\"Ctrl-d\":         \"Duplicate|DuplicateLine\",\n\t\"Ctrl-v\":         \"Paste\",\n\t\"Ctrl-a\":         \"SelectAll\",\n\t\"Ctrl-t\":         \"AddTab\",\n\t\"Alt-,\":          \"PreviousTab|LastTab\",\n\t\"Alt-.\":          \"NextTab|FirstTab\",\n\t\"Home\":           \"StartOfTextToggle\",\n\t\"End\":            \"EndOfLine\",\n\t\"CtrlHome\":       \"CursorStart\",\n\t\"CtrlEnd\":        \"CursorEnd\",\n\t\"PageUp\":         \"CursorPageUp\",\n\t\"PageDown\":       \"CursorPageDown\",\n\t\"CtrlPageUp\":     \"PreviousTab|LastTab\",\n\t\"CtrlPageDown\":   \"NextTab|FirstTab\",\n\t\"ShiftPageUp\":    \"SelectPageUp\",\n\t\"ShiftPageDown\":  \"SelectPageDown\",\n\t\"Ctrl-g\":         \"ToggleHelp\",\n\t\"Alt-g\":          \"ToggleKeyMenu\",\n\t\"Ctrl-r\":         \"ToggleRuler\",\n\t\"Ctrl-l\":         \"command-edit:goto \",\n\t\"Delete\":         \"Delete\",\n\t\"Ctrl-b\":         \"ShellMode\",\n\t\"Ctrl-q\":         \"Quit\",\n\t\"Ctrl-e\":         \"CommandMode\",\n\t\"Ctrl-w\":         \"NextSplit|FirstSplit\",\n\t\"Ctrl-u\":         \"ToggleMacro\",\n\t\"Ctrl-j\":         \"PlayMacro\",\n\t\"Insert\":         \"ToggleOverwriteMode\",\n\n\t// Emacs-style keybindings\n\t\"Alt-f\": \"WordRight\",\n\t\"Alt-b\": \"WordLeft\",\n\t\"Alt-a\": \"StartOfText\",\n\t\"Alt-e\": \"EndOfLine\",\n\t// \"Alt-p\": \"CursorUp\",\n\t// \"Alt-n\": \"CursorDown\",\n\n\t// Integration with file managers\n\t\"F2\":  \"Save\",\n\t\"F3\":  \"Find\",\n\t\"F4\":  \"Quit\",\n\t\"F7\":  \"Find\",\n\t\"F10\": \"Quit\",\n\t\"Esc\": \"Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch\",\n\n\t// Mouse bindings\n\t\"MouseWheelUp\":     \"ScrollUp\",\n\t\"MouseWheelDown\":   \"ScrollDown\",\n\t\"MouseLeft\":        \"MousePress\",\n\t\"MouseLeftDrag\":    \"MouseDrag\",\n\t\"MouseLeftRelease\": \"MouseRelease\",\n\t\"MouseMiddle\":      \"PastePrimary\",\n\t\"Ctrl-MouseLeft\":   \"MouseMultiCursor\",\n\n\t\"Alt-n\":        \"SpawnMultiCursor\",\n\t\"AltShiftUp\":   \"SpawnMultiCursorUp\",\n\t\"AltShiftDown\": \"SpawnMultiCursorDown\",\n\t\"Alt-m\":        \"SpawnMultiCursorSelect\",\n\t\"Alt-p\":        \"RemoveMultiCursor\",\n\t\"Alt-c\":        \"RemoveAllMultiCursors\",\n\t\"Alt-x\":        \"SkipMultiCursor\",\n}\n\nvar infodefaults = map[string]string{\n\t\"Up\":             \"HistoryUp\",\n\t\"Down\":           \"HistoryDown\",\n\t\"Right\":          \"CursorRight\",\n\t\"Left\":           \"CursorLeft\",\n\t\"ShiftUp\":        \"SelectUp\",\n\t\"ShiftDown\":      \"SelectDown\",\n\t\"ShiftLeft\":      \"SelectLeft\",\n\t\"ShiftRight\":     \"SelectRight\",\n\t\"AltLeft\":        \"WordLeft\",\n\t\"AltRight\":       \"WordRight\",\n\t\"AltUp\":          \"CursorStart\",\n\t\"AltDown\":        \"CursorEnd\",\n\t\"AltShiftRight\":  \"SelectWordRight\",\n\t\"AltShiftLeft\":   \"SelectWordLeft\",\n\t\"CtrlLeft\":       \"StartOfTextToggle\",\n\t\"CtrlRight\":      \"EndOfLine\",\n\t\"CtrlShiftLeft\":  \"SelectToStartOfTextToggle\",\n\t\"ShiftHome\":      \"SelectToStartOfTextToggle\",\n\t\"CtrlShiftRight\": \"SelectToEndOfLine\",\n\t\"ShiftEnd\":       \"SelectToEndOfLine\",\n\t\"CtrlUp\":         \"CursorStart\",\n\t\"CtrlDown\":       \"CursorEnd\",\n\t\"CtrlShiftUp\":    \"SelectToStart\",\n\t\"CtrlShiftDown\":  \"SelectToEnd\",\n\t\"Enter\":          \"ExecuteCommand\",\n\t\"CtrlH\":          \"Backspace\",\n\t\"Backspace\":      \"Backspace\",\n\t\"OldBackspace\":   \"Backspace\",\n\t\"Alt-CtrlH\":      \"DeleteWordLeft\",\n\t\"Alt-Backspace\":  \"DeleteWordLeft\",\n\t\"Tab\":            \"CommandComplete\",\n\t\"Backtab\":        \"CycleAutocompleteBack\",\n\t\"Ctrl-z\":         \"Undo\",\n\t\"Ctrl-y\":         \"Redo\",\n\t\"Ctrl-c\":         \"Copy\",\n\t\"Ctrl-x\":         \"Cut\",\n\t\"Ctrl-k\":         \"CutLine\",\n\t\"Ctrl-v\":         \"Paste\",\n\t\"Home\":           \"StartOfTextToggle\",\n\t\"End\":            \"EndOfLine\",\n\t\"CtrlHome\":       \"CursorStart\",\n\t\"CtrlEnd\":        \"CursorEnd\",\n\t\"Delete\":         \"Delete\",\n\t\"Ctrl-q\":         \"AbortCommand\",\n\t\"Ctrl-e\":         \"EndOfLine\",\n\t\"Ctrl-a\":         \"StartOfLine\",\n\t\"Ctrl-w\":         \"DeleteWordLeft\",\n\t\"Insert\":         \"ToggleOverwriteMode\",\n\t\"Ctrl-b\":         \"WordLeft\",\n\t\"Ctrl-f\":         \"WordRight\",\n\t\"Ctrl-d\":         \"DeleteWordLeft\",\n\t\"Ctrl-m\":         \"ExecuteCommand\",\n\t\"Ctrl-n\":         \"HistoryDown\",\n\t\"Ctrl-p\":         \"HistoryUp\",\n\t\"Ctrl-u\":         \"SelectToStart\",\n\n\t// Emacs-style keybindings\n\t\"Alt-f\": \"WordRight\",\n\t\"Alt-b\": \"WordLeft\",\n\t\"Alt-a\": \"StartOfText\",\n\t\"Alt-e\": \"EndOfLine\",\n\n\t// Integration with file managers\n\t\"F10\": \"AbortCommand\",\n\t\"Esc\": \"AbortCommand\",\n\n\t// Mouse bindings\n\t\"MouseWheelUp\":     \"HistoryUp\",\n\t\"MouseWheelDown\":   \"HistoryDown\",\n\t\"MouseLeft\":        \"MousePress\",\n\t\"MouseLeftDrag\":    \"MouseDrag\",\n\t\"MouseLeftRelease\": \"MouseRelease\",\n\t\"MouseMiddle\":      \"PastePrimary\",\n}\n"
  },
  {
    "path": "internal/action/defaults_other.go",
    "content": "//go:build !darwin\n// +build !darwin\n\npackage action\n\nvar bufdefaults = map[string]string{\n\t\"Up\":             \"CursorUp\",\n\t\"Down\":           \"CursorDown\",\n\t\"Right\":          \"CursorRight\",\n\t\"Left\":           \"CursorLeft\",\n\t\"ShiftUp\":        \"SelectUp\",\n\t\"ShiftDown\":      \"SelectDown\",\n\t\"ShiftLeft\":      \"SelectLeft\",\n\t\"ShiftRight\":     \"SelectRight\",\n\t\"CtrlLeft\":       \"WordLeft\",\n\t\"CtrlRight\":      \"WordRight\",\n\t\"AltUp\":          \"MoveLinesUp\",\n\t\"AltDown\":        \"MoveLinesDown\",\n\t\"CtrlShiftRight\": \"SelectWordRight\",\n\t\"CtrlShiftLeft\":  \"SelectWordLeft\",\n\t\"AltLeft\":        \"StartOfTextToggle\",\n\t\"AltRight\":       \"EndOfLine\",\n\t\"AltShiftLeft\":   \"SelectToStartOfTextToggle\",\n\t\"ShiftHome\":      \"SelectToStartOfTextToggle\",\n\t\"AltShiftRight\":  \"SelectToEndOfLine\",\n\t\"ShiftEnd\":       \"SelectToEndOfLine\",\n\t\"CtrlUp\":         \"CursorStart\",\n\t\"CtrlDown\":       \"CursorEnd\",\n\t\"CtrlShiftUp\":    \"SelectToStart\",\n\t\"CtrlShiftDown\":  \"SelectToEnd\",\n\t\"Alt-{\":          \"ParagraphPrevious\",\n\t\"Alt-}\":          \"ParagraphNext\",\n\t\"Enter\":          \"InsertNewline\",\n\t\"CtrlH\":          \"Backspace\",\n\t\"Backspace\":      \"Backspace\",\n\t\"OldBackspace\":   \"Backspace\",\n\t\"Alt-CtrlH\":      \"DeleteWordLeft\",\n\t\"Alt-Backspace\":  \"DeleteWordLeft\",\n\t\"Tab\":            \"Autocomplete|IndentSelection|InsertTab\",\n\t\"Backtab\":        \"CycleAutocompleteBack|OutdentSelection|OutdentLine\",\n\t\"Ctrl-o\":         \"OpenFile\",\n\t\"Ctrl-s\":         \"Save\",\n\t\"Ctrl-f\":         \"Find\",\n\t\"Alt-F\":          \"FindLiteral\",\n\t\"Ctrl-n\":         \"FindNext\",\n\t\"Ctrl-p\":         \"FindPrevious\",\n\t\"Alt-[\":          \"DiffPrevious|CursorStart\",\n\t\"Alt-]\":          \"DiffNext|CursorEnd\",\n\t\"Ctrl-z\":         \"Undo\",\n\t\"Ctrl-y\":         \"Redo\",\n\t\"Ctrl-c\":         \"Copy|CopyLine\",\n\t\"Ctrl-x\":         \"Cut|CutLine\",\n\t\"Ctrl-k\":         \"CutLine\",\n\t\"Ctrl-d\":         \"Duplicate|DuplicateLine\",\n\t\"Ctrl-v\":         \"Paste\",\n\t\"Ctrl-a\":         \"SelectAll\",\n\t\"Ctrl-t\":         \"AddTab\",\n\t\"Alt-,\":          \"PreviousTab|LastTab\",\n\t\"Alt-.\":          \"NextTab|FirstTab\",\n\t\"Home\":           \"StartOfTextToggle\",\n\t\"End\":            \"EndOfLine\",\n\t\"CtrlHome\":       \"CursorStart\",\n\t\"CtrlEnd\":        \"CursorEnd\",\n\t\"PageUp\":         \"CursorPageUp\",\n\t\"PageDown\":       \"CursorPageDown\",\n\t\"CtrlPageUp\":     \"PreviousTab|LastTab\",\n\t\"CtrlPageDown\":   \"NextTab|FirstTab\",\n\t\"ShiftPageUp\":    \"SelectPageUp\",\n\t\"ShiftPageDown\":  \"SelectPageDown\",\n\t\"Ctrl-g\":         \"ToggleHelp\",\n\t\"Alt-g\":          \"ToggleKeyMenu\",\n\t\"Ctrl-r\":         \"ToggleRuler\",\n\t\"Ctrl-l\":         \"command-edit:goto \",\n\t\"Delete\":         \"Delete\",\n\t\"Ctrl-b\":         \"ShellMode\",\n\t\"Ctrl-q\":         \"Quit\",\n\t\"Ctrl-e\":         \"CommandMode\",\n\t\"Ctrl-w\":         \"NextSplit|FirstSplit\",\n\t\"Ctrl-u\":         \"ToggleMacro\",\n\t\"Ctrl-j\":         \"PlayMacro\",\n\t\"Insert\":         \"ToggleOverwriteMode\",\n\n\t// Emacs-style keybindings\n\t\"Alt-f\": \"WordRight\",\n\t\"Alt-b\": \"WordLeft\",\n\t\"Alt-a\": \"StartOfText\",\n\t\"Alt-e\": \"EndOfLine\",\n\t// \"Alt-p\": \"CursorUp\",\n\t// \"Alt-n\": \"CursorDown\",\n\n\t// Integration with file managers\n\t\"F2\":  \"Save\",\n\t\"F3\":  \"Find\",\n\t\"F4\":  \"Quit\",\n\t\"F7\":  \"Find\",\n\t\"F10\": \"Quit\",\n\t\"Esc\": \"Escape,Deselect,ClearInfo,RemoveAllMultiCursors,UnhighlightSearch\",\n\n\t// Mouse bindings\n\t\"MouseWheelUp\":     \"ScrollUp\",\n\t\"MouseWheelDown\":   \"ScrollDown\",\n\t\"MouseLeft\":        \"MousePress\",\n\t\"MouseLeftDrag\":    \"MouseDrag\",\n\t\"MouseLeftRelease\": \"MouseRelease\",\n\t\"MouseMiddle\":      \"PastePrimary\",\n\t\"Ctrl-MouseLeft\":   \"MouseMultiCursor\",\n\n\t\"Alt-n\":        \"SpawnMultiCursor\",\n\t\"Alt-m\":        \"SpawnMultiCursorSelect\",\n\t\"AltShiftUp\":   \"SpawnMultiCursorUp\",\n\t\"AltShiftDown\": \"SpawnMultiCursorDown\",\n\t\"Alt-p\":        \"RemoveMultiCursor\",\n\t\"Alt-c\":        \"RemoveAllMultiCursors\",\n\t\"Alt-x\":        \"SkipMultiCursor\",\n}\n\nvar infodefaults = map[string]string{\n\t\"Up\":             \"HistoryUp\",\n\t\"Down\":           \"HistoryDown\",\n\t\"Right\":          \"CursorRight\",\n\t\"Left\":           \"CursorLeft\",\n\t\"ShiftUp\":        \"SelectUp\",\n\t\"ShiftDown\":      \"SelectDown\",\n\t\"ShiftLeft\":      \"SelectLeft\",\n\t\"ShiftRight\":     \"SelectRight\",\n\t\"AltLeft\":        \"StartOfTextToggle\",\n\t\"AltRight\":       \"EndOfLine\",\n\t\"AltUp\":          \"CursorStart\",\n\t\"AltDown\":        \"CursorEnd\",\n\t\"AltShiftRight\":  \"SelectWordRight\",\n\t\"AltShiftLeft\":   \"SelectWordLeft\",\n\t\"CtrlLeft\":       \"WordLeft\",\n\t\"CtrlRight\":      \"WordRight\",\n\t\"CtrlShiftLeft\":  \"SelectToStartOfTextToggle\",\n\t\"ShiftHome\":      \"SelectToStartOfTextToggle\",\n\t\"CtrlShiftRight\": \"SelectToEndOfLine\",\n\t\"ShiftEnd\":       \"SelectToEndOfLine\",\n\t\"CtrlUp\":         \"CursorStart\",\n\t\"CtrlDown\":       \"CursorEnd\",\n\t\"CtrlShiftUp\":    \"SelectToStart\",\n\t\"CtrlShiftDown\":  \"SelectToEnd\",\n\t\"Enter\":          \"ExecuteCommand\",\n\t\"CtrlH\":          \"Backspace\",\n\t\"Backspace\":      \"Backspace\",\n\t\"OldBackspace\":   \"Backspace\",\n\t\"Alt-CtrlH\":      \"DeleteWordLeft\",\n\t\"Alt-Backspace\":  \"DeleteWordLeft\",\n\t\"Tab\":            \"CommandComplete\",\n\t\"Backtab\":        \"CycleAutocompleteBack\",\n\t\"Ctrl-z\":         \"Undo\",\n\t\"Ctrl-y\":         \"Redo\",\n\t\"Ctrl-c\":         \"Copy\",\n\t\"Ctrl-x\":         \"Cut\",\n\t\"Ctrl-k\":         \"CutLine\",\n\t\"Ctrl-v\":         \"Paste\",\n\t\"Home\":           \"StartOfTextToggle\",\n\t\"End\":            \"EndOfLine\",\n\t\"CtrlHome\":       \"CursorStart\",\n\t\"CtrlEnd\":        \"CursorEnd\",\n\t\"Delete\":         \"Delete\",\n\t\"Ctrl-q\":         \"AbortCommand\",\n\t\"Ctrl-e\":         \"EndOfLine\",\n\t\"Ctrl-a\":         \"StartOfLine\",\n\t\"Ctrl-w\":         \"DeleteWordLeft\",\n\t\"Insert\":         \"ToggleOverwriteMode\",\n\t\"Ctrl-b\":         \"WordLeft\",\n\t\"Ctrl-f\":         \"WordRight\",\n\t\"Ctrl-d\":         \"DeleteWordLeft\",\n\t\"Ctrl-m\":         \"ExecuteCommand\",\n\t\"Ctrl-n\":         \"HistoryDown\",\n\t\"Ctrl-p\":         \"HistoryUp\",\n\t\"Ctrl-u\":         \"SelectToStart\",\n\n\t// Emacs-style keybindings\n\t\"Alt-f\": \"WordRight\",\n\t\"Alt-b\": \"WordLeft\",\n\t\"Alt-a\": \"StartOfText\",\n\t\"Alt-e\": \"EndOfLine\",\n\n\t// Integration with file managers\n\t\"F10\": \"AbortCommand\",\n\t\"Esc\": \"AbortCommand\",\n\n\t// Mouse bindings\n\t\"MouseWheelUp\":     \"HistoryUp\",\n\t\"MouseWheelDown\":   \"HistoryDown\",\n\t\"MouseLeft\":        \"MousePress\",\n\t\"MouseLeftDrag\":    \"MouseDrag\",\n\t\"MouseLeftRelease\": \"MouseRelease\",\n\t\"MouseMiddle\":      \"PastePrimary\",\n}\n"
  },
  {
    "path": "internal/action/events.go",
    "content": "package action\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/micro-editor/tcell/v2\"\n)\n\ntype Event interface {\n\tName() string\n}\n\n// RawEvent is simply an escape code\n// We allow users to directly bind escape codes\n// to get around some of a limitations of terminals\ntype RawEvent struct {\n\tesc string\n}\n\nfunc (r RawEvent) Name() string {\n\treturn r.esc\n}\n\n// KeyEvent is a key event containing a key code,\n// some possible modifiers (alt, ctrl, etc...) and\n// a rune if it was simply a character press\n// Note: to be compatible with tcell events,\n// for ctrl keys r=code\ntype KeyEvent struct {\n\tcode tcell.Key\n\tmod  tcell.ModMask\n\tr    rune\n\tany  bool\n}\n\nfunc metaToAlt(mod tcell.ModMask) tcell.ModMask {\n\tif mod&tcell.ModMeta != 0 {\n\t\tmod &= ^tcell.ModMeta\n\t\tmod |= tcell.ModAlt\n\t}\n\treturn mod\n}\n\nfunc keyEvent(e *tcell.EventKey) KeyEvent {\n\tke := KeyEvent{\n\t\tcode: e.Key(),\n\t\tmod:  metaToAlt(e.Modifiers()),\n\t}\n\tif e.Key() == tcell.KeyRune {\n\t\tke.r = e.Rune()\n\t}\n\treturn ke\n}\n\nfunc (k KeyEvent) Name() string {\n\tif k.any {\n\t\treturn \"<any>\"\n\t}\n\ts := \"\"\n\tm := []string{}\n\tif k.mod&tcell.ModShift != 0 {\n\t\tm = append(m, \"Shift\")\n\t}\n\tif k.mod&tcell.ModAlt != 0 {\n\t\tm = append(m, \"Alt\")\n\t}\n\tif k.mod&tcell.ModMeta != 0 {\n\t\tm = append(m, \"Meta\")\n\t}\n\tif k.mod&tcell.ModCtrl != 0 {\n\t\tm = append(m, \"Ctrl\")\n\t}\n\n\tok := false\n\tif s, ok = tcell.KeyNames[k.code]; !ok {\n\t\tif k.code == tcell.KeyRune {\n\t\t\ts = string(k.r)\n\t\t} else {\n\t\t\ts = fmt.Sprintf(\"Key[%d]\", k.code)\n\t\t}\n\t}\n\tif len(m) != 0 {\n\t\tif k.mod&tcell.ModCtrl != 0 && strings.HasPrefix(s, \"Ctrl-\") {\n\t\t\ts = s[5:]\n\t\t\tif len(s) == 1 {\n\t\t\t\ts = strings.ToLower(s)\n\t\t\t}\n\t\t}\n\t\treturn fmt.Sprintf(\"%s-%s\", strings.Join(m, \"-\"), s)\n\t}\n\treturn s\n}\n\n// A KeySequence defines a list of consecutive\n// events. All events in the sequence must be KeyEvents\n// or MouseEvents.\ntype KeySequenceEvent struct {\n\tkeys []Event\n}\n\nfunc (k KeySequenceEvent) Name() string {\n\tbuf := bytes.Buffer{}\n\tfor _, e := range k.keys {\n\t\tbuf.WriteByte('<')\n\t\tbuf.WriteString(e.Name())\n\t\tbuf.WriteByte('>')\n\t}\n\treturn buf.String()\n}\n\ntype MouseState int\n\nconst (\n\tMousePress = iota\n\tMouseDrag\n\tMouseRelease\n)\n\n// MouseEvent is a mouse event with a mouse button and\n// any possible key modifiers\ntype MouseEvent struct {\n\tbtn   tcell.ButtonMask\n\tmod   tcell.ModMask\n\tstate MouseState\n}\n\nfunc (m MouseEvent) Name() string {\n\tmod := \"\"\n\tif m.mod&tcell.ModShift != 0 {\n\t\tmod = \"Shift-\"\n\t}\n\tif m.mod&tcell.ModAlt != 0 {\n\t\tmod = \"Alt-\"\n\t}\n\tif m.mod&tcell.ModMeta != 0 {\n\t\tmod = \"Meta-\"\n\t}\n\tif m.mod&tcell.ModCtrl != 0 {\n\t\tmod = \"Ctrl-\"\n\t}\n\n\tstate := \"\"\n\tswitch m.state {\n\tcase MouseDrag:\n\t\tstate = \"Drag\"\n\tcase MouseRelease:\n\t\tstate = \"Release\"\n\t}\n\n\tfor k, v := range mouseEvents {\n\t\tif v == m.btn {\n\t\t\treturn fmt.Sprintf(\"%s%s%s\", mod, k, state)\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// ConstructEvent takes a tcell event and returns a micro\n// event. Note that tcell events can't express certain\n// micro events such as key sequences. This function is\n// mostly used for debugging/raw panes or constructing\n// intermediate micro events while parsing a sequence.\nfunc ConstructEvent(event tcell.Event) (Event, error) {\n\tswitch e := event.(type) {\n\tcase *tcell.EventKey:\n\t\treturn keyEvent(e), nil\n\tcase *tcell.EventRaw:\n\t\treturn RawEvent{\n\t\t\tesc: e.EscSeq(),\n\t\t}, nil\n\tcase *tcell.EventMouse:\n\t\treturn MouseEvent{\n\t\t\tbtn: e.Buttons(),\n\t\t\tmod: metaToAlt(e.Modifiers()),\n\t\t}, nil\n\t}\n\treturn nil, errors.New(\"No micro event equivalent\")\n}\n\n// A Handler will take a tcell event and execute it\n// appropriately\ntype Handler interface {\n\tHandleEvent(tcell.Event)\n\tHandleCommand(string)\n}\n"
  },
  {
    "path": "internal/action/globals.go",
    "content": "package action\n\nimport \"github.com/micro-editor/micro/v2/internal/buffer\"\n\n// InfoBar is the global info bar.\nvar InfoBar *InfoPane\n\n// LogBufPane is a global log buffer.\nvar LogBufPane *BufPane\n\n// InitGlobals initializes the log buffer and the info bar\nfunc InitGlobals() {\n\tInfoBar = NewInfoBar()\n\tbuffer.LogBuf = buffer.NewBufferFromString(\"\", \"\", buffer.BTLog)\n\tbuffer.LogBuf.SetName(\"Log\")\n}\n\n// GetInfoBar returns the infobar pane\nfunc GetInfoBar() *InfoPane {\n\treturn InfoBar\n}\n\n// WriteLog writes a string to the log buffer\nfunc WriteLog(s string) {\n\tbuffer.WriteLog(s)\n\tif LogBufPane != nil {\n\t\tLogBufPane.CursorEnd()\n\t}\n}\n\n// OpenLogBuf opens the log buffer from the current bufpane\n// If the current bufpane is a log buffer nothing happens,\n// otherwise the log buffer is opened in a horizontal split\nfunc (h *BufPane) OpenLogBuf() {\n\tLogBufPane = h.HSplitBuf(buffer.LogBuf)\n\tLogBufPane.CursorEnd()\n}\n"
  },
  {
    "path": "internal/action/infocomplete.go",
    "content": "package action\n\nimport (\n\t\"bytes\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n\t\"github.com/micro-editor/micro/v2/pkg/highlight\"\n)\n\n// This file is meant (for now) for autocompletion in command mode, not\n// while coding. This helps micro autocomplete commands and then filenames\n// for example with `vsplit filename`.\n\n// CommandComplete autocompletes commands\nfunc CommandComplete(b *buffer.Buffer) ([]string, []string) {\n\tc := b.GetActiveCursor()\n\tinput, argstart := b.GetArg()\n\n\tvar suggestions []string\n\tfor cmd := range commands {\n\t\tif strings.HasPrefix(cmd, input) {\n\t\t\tsuggestions = append(suggestions, cmd)\n\t\t}\n\t}\n\n\tsort.Strings(suggestions)\n\tcompletions := make([]string, len(suggestions))\n\tfor i := range suggestions {\n\t\tcompletions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)\n\t}\n\n\treturn completions, suggestions\n}\n\n// HelpComplete autocompletes help topics\nfunc HelpComplete(b *buffer.Buffer) ([]string, []string) {\n\tc := b.GetActiveCursor()\n\tinput, argstart := b.GetArg()\n\n\tvar suggestions []string\n\n\tfor _, file := range config.ListRuntimeFiles(config.RTHelp) {\n\t\ttopic := file.Name()\n\t\tif strings.HasPrefix(topic, input) {\n\t\t\tsuggestions = append(suggestions, topic)\n\t\t}\n\t}\n\n\tsort.Strings(suggestions)\n\tcompletions := make([]string, len(suggestions))\n\tfor i := range suggestions {\n\t\tcompletions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)\n\t}\n\treturn completions, suggestions\n}\n\n// colorschemeComplete tab-completes names of colorschemes.\n// This is just a heper value for OptionValueComplete\nfunc colorschemeComplete(input string) (string, []string) {\n\tvar suggestions []string\n\tfiles := config.ListRuntimeFiles(config.RTColorscheme)\n\n\tfor _, f := range files {\n\t\tif strings.HasPrefix(f.Name(), input) {\n\t\t\tsuggestions = append(suggestions, f.Name())\n\t\t}\n\t}\n\n\tvar chosen string\n\tif len(suggestions) == 1 {\n\t\tchosen = suggestions[0]\n\t}\n\n\treturn chosen, suggestions\n}\n\n// filetypeComplete autocompletes filetype\nfunc filetypeComplete(input string) (string, []string) {\n\tvar suggestions []string\n\n\t// We cannot match filetypes just by names of syntax files,\n\t// since those names may be different from the actual filetype values\n\t// specified inside syntax files (e.g. \"c++\" filetype in cpp.yaml).\n\t// So we need to parse filetype values out of those files.\n\tfor _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {\n\t\tdata, err := f.Data()\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\theader, err := highlight.MakeHeaderYaml(data)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\t// Prevent duplicated defaults\n\t\tif header.FileType == \"off\" || header.FileType == \"unknown\" {\n\t\t\tcontinue\n\t\t}\n\t\tif strings.HasPrefix(header.FileType, input) {\n\t\t\tsuggestions = append(suggestions, header.FileType)\n\t\t}\n\t}\nheaderLoop:\n\tfor _, f := range config.ListRuntimeFiles(config.RTSyntaxHeader) {\n\t\tdata, err := f.Data()\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\theader, err := highlight.MakeHeader(data)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, v := range suggestions {\n\t\t\tif v == header.FileType {\n\t\t\t\tcontinue headerLoop\n\t\t\t}\n\t\t}\n\t\tif strings.HasPrefix(header.FileType, input) {\n\t\t\tsuggestions = append(suggestions, header.FileType)\n\t\t}\n\t}\n\n\tif strings.HasPrefix(\"off\", input) {\n\t\tsuggestions = append(suggestions, \"off\")\n\t}\n\n\tvar chosen string\n\tif len(suggestions) == 1 {\n\t\tchosen = suggestions[0]\n\t}\n\n\treturn chosen, suggestions\n}\n\n// OptionComplete autocompletes options\nfunc OptionComplete(b *buffer.Buffer) ([]string, []string) {\n\tc := b.GetActiveCursor()\n\tinput, argstart := b.GetArg()\n\n\tvar suggestions []string\n\tfor option := range config.GlobalSettings {\n\t\tif strings.HasPrefix(option, input) {\n\t\t\tsuggestions = append(suggestions, option)\n\t\t}\n\t}\n\t// for option := range localSettings {\n\t// \tif strings.HasPrefix(option, input) && !contains(suggestions, option) {\n\t// \t\tsuggestions = append(suggestions, option)\n\t// \t}\n\t// }\n\n\tsort.Strings(suggestions)\n\tcompletions := make([]string, len(suggestions))\n\tfor i := range suggestions {\n\t\tcompletions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)\n\t}\n\treturn completions, suggestions\n}\n\n// OptionValueComplete completes values for various options\nfunc OptionValueComplete(b *buffer.Buffer) ([]string, []string) {\n\tc := b.GetActiveCursor()\n\tl := b.LineBytes(c.Y)\n\tl = util.SliceStart(l, c.X)\n\tinput, argstart := b.GetArg()\n\n\tcompleteValue := false\n\targs := bytes.Split(l, []byte{' '})\n\tif len(args) >= 2 {\n\t\t// localSettings := config.DefaultLocalSettings()\n\t\tfor option := range config.GlobalSettings {\n\t\t\tif option == string(args[len(args)-2]) {\n\t\t\t\tcompleteValue = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\t// for option := range localSettings {\n\t\t// \tif option == string(args[len(args)-2]) {\n\t\t// \t\tcompleteValue = true\n\t\t// \t\tbreak\n\t\t// \t}\n\t\t// }\n\t}\n\tif !completeValue {\n\t\treturn OptionComplete(b)\n\t}\n\n\tinputOpt := string(args[len(args)-2])\n\n\tinputOpt = strings.TrimSpace(inputOpt)\n\tvar suggestions []string\n\t// localSettings := config.DefaultLocalSettings()\n\tvar optionVal any\n\tfor k, option := range config.GlobalSettings {\n\t\tif k == inputOpt {\n\t\t\toptionVal = option\n\t\t}\n\t}\n\t// for k, option := range localSettings {\n\t// \tif k == inputOpt {\n\t// \t\toptionVal = option\n\t// \t}\n\t// }\n\n\tswitch optionVal.(type) {\n\tcase bool:\n\t\tif strings.HasPrefix(\"on\", input) {\n\t\t\tsuggestions = append(suggestions, \"on\")\n\t\t} else if strings.HasPrefix(\"true\", input) {\n\t\t\tsuggestions = append(suggestions, \"true\")\n\t\t}\n\t\tif strings.HasPrefix(\"off\", input) {\n\t\t\tsuggestions = append(suggestions, \"off\")\n\t\t} else if strings.HasPrefix(\"false\", input) {\n\t\t\tsuggestions = append(suggestions, \"false\")\n\t\t}\n\tcase string:\n\t\tswitch inputOpt {\n\t\tcase \"colorscheme\":\n\t\t\t_, suggestions = colorschemeComplete(input)\n\t\tcase \"filetype\":\n\t\t\t_, suggestions = filetypeComplete(input)\n\t\tcase \"sucmd\":\n\t\t\tif strings.HasPrefix(\"sudo\", input) {\n\t\t\t\tsuggestions = append(suggestions, \"sudo\")\n\t\t\t}\n\t\t\tif strings.HasPrefix(\"doas\", input) {\n\t\t\t\tsuggestions = append(suggestions, \"doas\")\n\t\t\t}\n\t\tdefault:\n\t\t\tif choices, ok := config.OptionChoices[inputOpt]; ok {\n\t\t\t\tfor _, choice := range choices {\n\t\t\t\t\tif strings.HasPrefix(choice, input) {\n\t\t\t\t\t\tsuggestions = append(suggestions, choice)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tsort.Strings(suggestions)\n\n\tcompletions := make([]string, len(suggestions))\n\tfor i := range suggestions {\n\t\tcompletions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)\n\t}\n\treturn completions, suggestions\n}\n\n// PluginCmdComplete autocompletes the plugin command\nfunc PluginCmdComplete(b *buffer.Buffer) ([]string, []string) {\n\tc := b.GetActiveCursor()\n\tinput, argstart := b.GetArg()\n\n\tvar suggestions []string\n\tfor _, cmd := range PluginCmds {\n\t\tif strings.HasPrefix(cmd, input) {\n\t\t\tsuggestions = append(suggestions, cmd)\n\t\t}\n\t}\n\n\tsort.Strings(suggestions)\n\tcompletions := make([]string, len(suggestions))\n\tfor i := range suggestions {\n\t\tcompletions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)\n\t}\n\treturn completions, suggestions\n}\n\n// PluginComplete completes values for the plugin command\nfunc PluginComplete(b *buffer.Buffer) ([]string, []string) {\n\tc := b.GetActiveCursor()\n\tl := b.LineBytes(c.Y)\n\tl = util.SliceStart(l, c.X)\n\tinput, argstart := b.GetArg()\n\n\tcompleteValue := false\n\targs := bytes.Split(l, []byte{' '})\n\tif len(args) >= 2 {\n\t\tfor _, cmd := range PluginCmds {\n\t\t\tif cmd == string(args[len(args)-2]) {\n\t\t\t\tcompleteValue = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tif !completeValue {\n\t\treturn PluginCmdComplete(b)\n\t}\n\n\tvar suggestions []string\n\tfor _, pl := range config.Plugins {\n\t\tif strings.HasPrefix(pl.Name, input) {\n\t\t\tsuggestions = append(suggestions, pl.Name)\n\t\t}\n\t}\n\tsort.Strings(suggestions)\n\n\tcompletions := make([]string, len(suggestions))\n\tfor i := range suggestions {\n\t\tcompletions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)\n\t}\n\treturn completions, suggestions\n}\n\n// PluginNameComplete completes with the names of loaded plugins\n// func PluginNameComplete(b *buffer.Buffer) ([]string, []string) {\n// \tc := b.GetActiveCursor()\n// \tinput, argstart := buffer.GetArg(b)\n//\n// \tvar suggestions []string\n// \tfor _, pp := range config.GetAllPluginPackages(nil) {\n// \t\tif strings.HasPrefix(pp.Name, input) {\n// \t\t\tsuggestions = append(suggestions, pp.Name)\n// \t\t}\n// \t}\n//\n// \tsort.Strings(suggestions)\n// \tcompletions := make([]string, len(suggestions))\n// \tfor i := range suggestions {\n// \t\tcompletions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)\n// \t}\n// \treturn completions, suggestions\n// }\n\n// // MakeCompletion registers a function from a plugin for autocomplete commands\n// func MakeCompletion(function string) Completion {\n// \tpluginCompletions = append(pluginCompletions, LuaFunctionComplete(function))\n// \treturn Completion(-len(pluginCompletions))\n// }\n"
  },
  {
    "path": "internal/action/infopane.go",
    "content": "package action\n\nimport (\n\t\"bytes\"\n\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/display\"\n\t\"github.com/micro-editor/micro/v2/internal/info\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n\t\"github.com/micro-editor/tcell/v2\"\n)\n\ntype InfoKeyAction func(*InfoPane)\n\nvar InfoBindings *KeyTree\nvar InfoBufBindings *KeyTree\n\nfunc init() {\n\tInfoBindings = NewKeyTree()\n\tInfoBufBindings = NewKeyTree()\n}\n\nfunc InfoMapEvent(k Event, action string) {\n\tconfig.Bindings[\"command\"][k.Name()] = action\n\n\tswitch e := k.(type) {\n\tcase KeyEvent, KeySequenceEvent, RawEvent:\n\t\tinfoMapKey(e, action)\n\tcase MouseEvent:\n\t\tinfoMapMouse(e, action)\n\t}\n}\n\nfunc infoMapKey(k Event, action string) {\n\tif f, ok := InfoKeyActions[action]; ok {\n\t\tInfoBindings.RegisterKeyBinding(k, InfoKeyActionGeneral(f))\n\t} else if f, ok := BufKeyActions[action]; ok {\n\t\tInfoBufBindings.RegisterKeyBinding(k, BufKeyActionGeneral(f))\n\t}\n}\n\nfunc infoMapMouse(k MouseEvent, action string) {\n\t// TODO: map mouse\n\tif f, ok := BufMouseActions[action]; ok {\n\t\tInfoBufBindings.RegisterMouseBinding(k, BufMouseActionGeneral(f))\n\t} else {\n\t\tinfoMapKey(k, action)\n\t}\n}\n\nfunc InfoKeyActionGeneral(a InfoKeyAction) PaneKeyAction {\n\treturn func(p Pane) bool {\n\t\ta(p.(*InfoPane))\n\t\treturn true\n\t}\n}\n\ntype InfoPane struct {\n\t*BufPane\n\t*info.InfoBuf\n}\n\nfunc NewInfoPane(ib *info.InfoBuf, w display.BWindow, tab *Tab) *InfoPane {\n\tip := new(InfoPane)\n\tip.InfoBuf = ib\n\tip.BufPane = NewBufPane(ib.Buffer, w, tab)\n\tip.BufPane.bindings = InfoBufBindings\n\n\treturn ip\n}\n\nfunc NewInfoBar() *InfoPane {\n\tib := info.NewBuffer()\n\tw := display.NewInfoWindow(ib)\n\treturn NewInfoPane(ib, w, nil)\n}\n\nfunc (h *InfoPane) Close() {\n\th.InfoBuf.Close()\n\th.BufPane.Close()\n}\n\nfunc (h *InfoPane) HandleEvent(event tcell.Event) {\n\tswitch e := event.(type) {\n\tcase *tcell.EventResize:\n\t\t// TODO\n\tcase *tcell.EventKey:\n\t\tke := keyEvent(e)\n\n\t\tdone := h.DoKeyEvent(ke)\n\t\thasYN := h.HasYN\n\t\tif e.Key() == tcell.KeyRune && hasYN {\n\t\t\ty := e.Rune() == 'y' || e.Rune() == 'Y'\n\t\t\tn := e.Rune() == 'n' || e.Rune() == 'N'\n\t\t\tif y || n {\n\t\t\t\th.YNResp = y\n\t\t\t\th.DonePrompt(false)\n\n\t\t\t\tInfoBindings.ResetEvents()\n\t\t\t\tInfoBufBindings.ResetEvents()\n\t\t\t}\n\t\t}\n\t\tif e.Key() == tcell.KeyRune && !done && !hasYN {\n\t\t\th.DoRuneInsert(e.Rune())\n\t\t\tdone = true\n\t\t}\n\t\tif done && h.HasPrompt && !hasYN {\n\t\t\tresp := string(h.LineBytes(0))\n\t\t\thist := h.History[h.PromptType]\n\t\t\tif resp != hist[h.HistoryNum] {\n\t\t\t\th.HistoryNum = len(hist) - 1\n\t\t\t\thist[h.HistoryNum] = resp\n\t\t\t\th.HistorySearch = false\n\t\t\t}\n\t\t\tif h.EventCallback != nil {\n\t\t\t\th.EventCallback(resp)\n\t\t\t}\n\t\t}\n\tdefault:\n\t\th.BufPane.HandleEvent(event)\n\t}\n}\n\n// DoKeyEvent executes a key event for the command bar, doing any overridden actions.\n// Returns true if the action was executed OR if there are more keys remaining\n// to process before executing an action (if this is a key sequence event).\n// Returns false if no action found.\nfunc (h *InfoPane) DoKeyEvent(e KeyEvent) bool {\n\taction, more := InfoBindings.NextEvent(e, nil)\n\tif action != nil && !more {\n\t\taction(h)\n\t\tInfoBindings.ResetEvents()\n\n\t\treturn true\n\t} else if action == nil && !more {\n\t\tInfoBindings.ResetEvents()\n\t\t// return false //TODO:?\n\t}\n\n\tif !more {\n\t\t// If no infopane action found, try to find a bufpane action.\n\t\t//\n\t\t// TODO: this is buggy. For example, if the command bar has the following\n\t\t// two bindings:\n\t\t//\n\t\t//   \"<Ctrl-x><Ctrl-p>\": \"HistoryUp\",\n\t\t//   \"<Ctrl-x><Ctrl-v>\": \"Paste\",\n\t\t//\n\t\t// the 2nd binding (with a bufpane action) doesn't work, since <Ctrl-x>\n\t\t// has been already consumed by the 1st binding (with an infopane action).\n\t\t//\n\t\t// We should either iterate both InfoBindings and InfoBufBindings keytrees\n\t\t// together, or just use the same keytree for both infopane and bufpane\n\t\t// bindings.\n\t\taction, more = InfoBufBindings.NextEvent(e, nil)\n\t\tif action != nil && !more {\n\t\t\taction(h.BufPane)\n\t\t\tInfoBufBindings.ResetEvents()\n\t\t\treturn true\n\t\t} else if action == nil && !more {\n\t\t\tInfoBufBindings.ResetEvents()\n\t\t}\n\t}\n\n\treturn more\n}\n\n// HistoryUp cycles history up\nfunc (h *InfoPane) HistoryUp() {\n\th.UpHistory(h.History[h.PromptType])\n}\n\n// HistoryDown cycles history down\nfunc (h *InfoPane) HistoryDown() {\n\th.DownHistory(h.History[h.PromptType])\n}\n\n// HistorySearchUp fetches the previous history item beginning with the text\n// in the infobuffer before cursor\nfunc (h *InfoPane) HistorySearchUp() {\n\th.SearchUpHistory(h.History[h.PromptType])\n}\n\n// HistorySearchDown fetches the next history item beginning with the text\n// in the infobuffer before cursor\nfunc (h *InfoPane) HistorySearchDown() {\n\th.SearchDownHistory(h.History[h.PromptType])\n}\n\n// Autocomplete begins autocompletion\nfunc (h *InfoPane) CommandComplete() {\n\tb := h.Buf\n\tif b.HasSuggestions {\n\t\tb.CycleAutocomplete(true)\n\t\treturn\n\t}\n\n\tc := b.GetActiveCursor()\n\tl := b.LineBytes(0)\n\tl = util.SliceStart(l, c.X)\n\n\targs := bytes.Split(l, []byte{' '})\n\tcmd := string(args[0])\n\n\tif h.PromptType == \"Command\" {\n\t\tif len(args) == 1 {\n\t\t\tb.Autocomplete(CommandComplete)\n\t\t} else if action, ok := commands[cmd]; ok {\n\t\t\tif action.completer != nil {\n\t\t\t\tb.Autocomplete(action.completer)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// by default use filename autocompletion\n\t\tb.Autocomplete(buffer.FileComplete)\n\t}\n}\n\n// ExecuteCommand completes the prompt\nfunc (h *InfoPane) ExecuteCommand() {\n\tif !h.HasYN {\n\t\th.DonePrompt(false)\n\t}\n}\n\n// AbortCommand cancels the prompt\nfunc (h *InfoPane) AbortCommand() {\n\th.DonePrompt(true)\n}\n\n// InfoKeyActions contains the list of all possible key actions the infopane could execute\nvar InfoKeyActions = map[string]InfoKeyAction{\n\t\"HistoryUp\":         (*InfoPane).HistoryUp,\n\t\"HistoryDown\":       (*InfoPane).HistoryDown,\n\t\"HistorySearchUp\":   (*InfoPane).HistorySearchUp,\n\t\"HistorySearchDown\": (*InfoPane).HistorySearchDown,\n\t\"CommandComplete\":   (*InfoPane).CommandComplete,\n\t\"ExecuteCommand\":    (*InfoPane).ExecuteCommand,\n\t\"AbortCommand\":      (*InfoPane).AbortCommand,\n}\n"
  },
  {
    "path": "internal/action/keytree.go",
    "content": "package action\n\nimport (\n\t\"bytes\"\n\n\t\"github.com/micro-editor/tcell/v2\"\n)\n\ntype PaneKeyAction func(Pane) bool\ntype PaneMouseAction func(Pane, *tcell.EventMouse) bool\ntype PaneKeyAnyAction func(Pane, []KeyEvent) bool\n\n// A KeyTreeNode stores a single node in the KeyTree (trie). The\n// children are stored as a map, and any node may store a list of\n// actions (the list will be nil if no actions correspond to a certain\n// node)\ntype KeyTreeNode struct {\n\tchildren map[Event]*KeyTreeNode\n\n\t// Only one of these actions may be active in the current\n\t// mode, and only one will be returned. If multiple actions\n\t// are active, it is undefined which one will be the one\n\t// returned.\n\tactions []TreeAction\n}\n\nfunc NewKeyTreeNode() *KeyTreeNode {\n\tn := new(KeyTreeNode)\n\tn.children = make(map[Event]*KeyTreeNode)\n\tn.actions = []TreeAction{}\n\treturn n\n}\n\n// A TreeAction stores an action, and a set of mode constraints for\n// the action to be active.\ntype TreeAction struct {\n\t// only one of these can be non-nil\n\taction PaneKeyAction\n\tany    PaneKeyAnyAction\n\tmouse  PaneMouseAction\n\n\tmodes []ModeConstraint\n}\n\n// A KeyTree is a data structure for storing keybindings. It maps\n// key events to actions, and maintains a set of currently enabled\n// modes, which affects the action that is returned for a key event.\n// The tree acts like a Trie for Events to handle sequence events.\ntype KeyTree struct {\n\troot  *KeyTreeNode\n\tmodes map[string]bool\n\n\tcursor KeyTreeCursor\n}\n\n// A KeyTreeCursor keeps track of the current location within the\n// tree, and stores any information from previous events that may\n// be needed to execute the action (values of wildcard events or\n// mouse events)\ntype KeyTreeCursor struct {\n\tnode *KeyTreeNode\n\n\trecordedEvents []Event\n\twildcards      []KeyEvent\n\tmouseInfo      *tcell.EventMouse\n}\n\n// MakeClosure uses the information stored in a key tree cursor to construct\n// a PaneKeyAction from a TreeAction (which may have a PaneKeyAction, PaneMouseAction,\n// or AnyAction)\nfunc (k *KeyTreeCursor) MakeClosure(a TreeAction) PaneKeyAction {\n\tif a.action != nil {\n\t\treturn a.action\n\t} else if a.any != nil {\n\t\treturn func(p Pane) bool {\n\t\t\treturn a.any(p, k.wildcards)\n\t\t}\n\t} else if a.mouse != nil {\n\t\treturn func(p Pane) bool {\n\t\t\treturn a.mouse(p, k.mouseInfo)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// NewKeyTree allocates and returns an empty key tree\nfunc NewKeyTree() *KeyTree {\n\troot := NewKeyTreeNode()\n\ttree := new(KeyTree)\n\n\ttree.root = root\n\ttree.modes = make(map[string]bool)\n\ttree.cursor = KeyTreeCursor{\n\t\tnode:      root,\n\t\twildcards: []KeyEvent{},\n\t\tmouseInfo: nil,\n\t}\n\n\treturn tree\n}\n\n// A ModeConstraint specifies that an action can only be executed\n// while a certain mode is enabled or disabled.\ntype ModeConstraint struct {\n\tmode     string\n\tdisabled bool\n}\n\n// RegisterKeyBinding registers a PaneKeyAction with an Event.\nfunc (k *KeyTree) RegisterKeyBinding(e Event, a PaneKeyAction) {\n\tk.registerBinding(e, TreeAction{\n\t\taction: a,\n\t\tany:    nil,\n\t\tmouse:  nil,\n\t\tmodes:  nil,\n\t})\n}\n\n// RegisterKeyAnyBinding registers a PaneKeyAnyAction with an Event.\n// The event should contain an \"any\" event.\nfunc (k *KeyTree) RegisterKeyAnyBinding(e Event, a PaneKeyAnyAction) {\n\tk.registerBinding(e, TreeAction{\n\t\taction: nil,\n\t\tany:    a,\n\t\tmouse:  nil,\n\t\tmodes:  nil,\n\t})\n}\n\n// RegisterMouseBinding registers a PaneMouseAction with an Event.\n// The event should contain a mouse event.\nfunc (k *KeyTree) RegisterMouseBinding(e Event, a PaneMouseAction) {\n\tk.registerBinding(e, TreeAction{\n\t\taction: nil,\n\t\tany:    nil,\n\t\tmouse:  a,\n\t\tmodes:  nil,\n\t})\n}\n\nfunc (k *KeyTree) registerBinding(e Event, a TreeAction) {\n\tswitch ev := e.(type) {\n\tcase KeyEvent, MouseEvent, RawEvent:\n\t\tnewNode, ok := k.root.children[e]\n\t\tif !ok {\n\t\t\tnewNode = NewKeyTreeNode()\n\t\t\tk.root.children[e] = newNode\n\t\t}\n\t\t// newNode.actions = append(newNode.actions, a)\n\t\tnewNode.actions = []TreeAction{a}\n\tcase KeySequenceEvent:\n\t\tn := k.root\n\t\tfor _, key := range ev.keys {\n\t\t\tnewNode, ok := n.children[key]\n\t\t\tif !ok {\n\t\t\t\tnewNode = NewKeyTreeNode()\n\t\t\t\tn.children[key] = newNode\n\t\t\t}\n\n\t\t\tn = newNode\n\t\t}\n\t\t// n.actions = append(n.actions, a)\n\t\tn.actions = []TreeAction{a}\n\t}\n}\n\n// NextEvent returns the action for the current sequence where e is the next\n// event. Even if the action was registered as a PaneKeyAnyAction or PaneMouseAction,\n// it will be returned as a PaneKeyAction closure where the appropriate arguments\n// have been provided.\n// If no action is associated with the given Event, or mode constraints are not\n// met for that action, nil is returned.\n// A boolean is returned to indicate if there is a conflict with this action. A\n// conflict occurs when there is an active action for this event but there are\n// bindings associated with further sequences starting with this event. The\n// calling function can decide what to do about the conflict (e.g. use a\n// timeout).\nfunc (k *KeyTree) NextEvent(e Event, mouse *tcell.EventMouse) (PaneKeyAction, bool) {\n\tn := k.cursor.node\n\tc, ok := n.children[e]\n\n\tif !ok {\n\t\treturn nil, false\n\t}\n\n\tmore := len(c.children) > 0\n\n\tk.cursor.node = c\n\n\tk.cursor.recordedEvents = append(k.cursor.recordedEvents, e)\n\n\tswitch ev := e.(type) {\n\tcase KeyEvent:\n\t\tif ev.any {\n\t\t\tk.cursor.wildcards = append(k.cursor.wildcards, ev)\n\t\t}\n\tcase MouseEvent:\n\t\tk.cursor.mouseInfo = mouse\n\t}\n\n\tif len(c.actions) > 0 {\n\t\t// check if actions are active\n\t\tfor _, a := range c.actions {\n\t\t\tactive := true\n\t\t\tfor _, mc := range a.modes {\n\t\t\t\t// if any mode constraint is not met, the action is not active\n\t\t\t\thasMode := k.modes[mc.mode]\n\t\t\t\tif hasMode != mc.disabled {\n\t\t\t\t\tactive = false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif active {\n\t\t\t\t// the first active action to be found is returned\n\t\t\t\treturn k.cursor.MakeClosure(a), more\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil, more\n}\n\n// ResetEvents sets the current sequence back to the initial value.\nfunc (k *KeyTree) ResetEvents() {\n\tk.cursor.node = k.root\n\tk.cursor.wildcards = []KeyEvent{}\n\tk.cursor.recordedEvents = []Event{}\n\tk.cursor.mouseInfo = nil\n}\n\n// RecordedEventsStr returns the list of recorded events as a string\nfunc (k *KeyTree) RecordedEventsStr() string {\n\tbuf := &bytes.Buffer{}\n\tfor _, e := range k.cursor.recordedEvents {\n\t\tbuf.WriteString(e.Name())\n\t}\n\treturn buf.String()\n}\n\n// DeleteBinding removes any currently active actions associated with the\n// given event.\nfunc (k *KeyTree) DeleteBinding(e Event) {\n\n}\n\n// DeleteAllBindings removes all actions associated with the given event,\n// regardless of whether they are active or not.\nfunc (k *KeyTree) DeleteAllBindings(e Event) {\n\n}\n\n// SetMode enables or disabled a given mode\nfunc (k *KeyTree) SetMode(mode string, en bool) {\n\tk.modes[mode] = en\n}\n\n// HasMode returns if the given mode is currently active\nfunc (k *KeyTree) HasMode(mode string) bool {\n\treturn k.modes[mode]\n}\n"
  },
  {
    "path": "internal/action/pane.go",
    "content": "package action\n\nimport (\n\t\"github.com/micro-editor/micro/v2/internal/display\"\n)\n\n// A Pane is a general interface for a window in the editor.\ntype Pane interface {\n\tHandler\n\tdisplay.Window\n\tID() uint64\n\tSetID(i uint64)\n\tName() string\n\tClose()\n\tSetTab(t *Tab)\n\tTab() *Tab\n}\n"
  },
  {
    "path": "internal/action/rawpane.go",
    "content": "package action\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n\t\"github.com/micro-editor/micro/v2/internal/display\"\n\t\"github.com/micro-editor/tcell/v2\"\n)\n\ntype RawPane struct {\n\t*BufPane\n}\n\nfunc NewRawPaneFromWin(b *buffer.Buffer, win display.BWindow, tab *Tab) *RawPane {\n\trh := new(RawPane)\n\trh.BufPane = NewBufPane(b, win, tab)\n\n\treturn rh\n}\n\nfunc NewRawPane(tab *Tab) *RawPane {\n\tb := buffer.NewBufferFromString(\"\", \"\", buffer.BTRaw)\n\tb.SetName(\"Raw event viewer\")\n\n\tw := display.NewBufWindow(0, 0, 0, 0, b)\n\n\treturn NewRawPaneFromWin(b, w, tab)\n}\n\nfunc (h *RawPane) HandleEvent(event tcell.Event) {\n\tswitch e := event.(type) {\n\tcase *tcell.EventKey:\n\t\tif e.Key() == tcell.KeyCtrlQ {\n\t\t\th.Quit()\n\t\t}\n\t}\n\n\th.Buf.Insert(h.Cursor.Loc, reflect.TypeOf(event).String()[7:])\n\n\te, err := ConstructEvent(event)\n\tif err == nil {\n\t\th.Buf.Insert(h.Cursor.Loc, fmt.Sprintf(\": %s\", e.Name()))\n\t}\n\n\th.Buf.Insert(h.Cursor.Loc, fmt.Sprintf(\": %q\\n\", event.EscSeq()))\n\n\th.Relocate()\n}\n"
  },
  {
    "path": "internal/action/tab.go",
    "content": "package action\n\nimport (\n\tluar \"layeh.com/gopher-luar\"\n\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/display\"\n\tulua \"github.com/micro-editor/micro/v2/internal/lua\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/micro/v2/internal/views\"\n\t\"github.com/micro-editor/tcell/v2\"\n)\n\n// The TabList is a list of tabs and a window to display the tab bar\n// at the top of the screen\ntype TabList struct {\n\t*display.TabWindow\n\tList []*Tab\n}\n\n// NewTabList creates a TabList from a list of buffers by creating a Tab\n// for each buffer\nfunc NewTabList(bufs []*buffer.Buffer) *TabList {\n\tw, h := screen.Screen.Size()\n\tiOffset := config.GetInfoBarOffset()\n\ttl := new(TabList)\n\ttl.List = make([]*Tab, len(bufs))\n\tif len(bufs) > 1 {\n\t\tfor i, b := range bufs {\n\t\t\ttl.List[i] = NewTabFromBuffer(0, 1, w, h-1-iOffset, b)\n\t\t}\n\t} else {\n\t\ttl.List[0] = NewTabFromBuffer(0, 0, w, h-iOffset, bufs[0])\n\t}\n\ttl.TabWindow = display.NewTabWindow(w, 0)\n\ttl.Names = make([]string, len(bufs))\n\n\treturn tl\n}\n\n// UpdateNames makes sure that the list of names the tab window has access to is\n// correct\nfunc (t *TabList) UpdateNames() {\n\tt.Names = t.Names[:0]\n\tfor _, p := range t.List {\n\t\tt.Names = append(t.Names, p.Panes[p.active].Name())\n\t}\n}\n\n// AddTab adds a new tab to this TabList\nfunc (t *TabList) AddTab(p *Tab) {\n\tt.List = append(t.List, p)\n\tt.Resize()\n\tt.UpdateNames()\n}\n\n// RemoveTab removes a tab with the given id from the TabList\nfunc (t *TabList) RemoveTab(id uint64) {\n\tfor i, p := range t.List {\n\t\tif len(p.Panes) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif p.Panes[0].ID() == id {\n\t\t\tcopy(t.List[i:], t.List[i+1:])\n\t\t\tt.List[len(t.List)-1] = nil\n\t\t\tt.List = t.List[:len(t.List)-1]\n\t\t\tif t.Active() >= len(t.List) {\n\t\t\t\tt.SetActive(len(t.List) - 1)\n\t\t\t}\n\t\t\tt.Resize()\n\t\t\tt.UpdateNames()\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// Resize resizes all elements within the tab list\n// One thing to note is that when there is only 1 tab\n// the tab bar should not be drawn so resizing must take\n// that into account\nfunc (t *TabList) Resize() {\n\tw, h := screen.Screen.Size()\n\tiOffset := config.GetInfoBarOffset()\n\tInfoBar.Resize(w, h-1)\n\tif len(t.List) > 1 {\n\t\tfor _, p := range t.List {\n\t\t\tp.Y = 1\n\t\t\tp.Node.Resize(w, h-1-iOffset)\n\t\t\tp.Resize()\n\t\t}\n\t} else if len(t.List) == 1 {\n\t\tt.List[0].Y = 0\n\t\tt.List[0].Node.Resize(w, h-iOffset)\n\t\tt.List[0].Resize()\n\t}\n\tt.TabWindow.Resize(w, h)\n}\n\n// HandleEvent checks for a resize event or a mouse event on the tab bar\n// otherwise it will forward the event to the currently active tab\nfunc (t *TabList) HandleEvent(event tcell.Event) {\n\tswitch e := event.(type) {\n\tcase *tcell.EventResize:\n\t\tt.Resize()\n\tcase *tcell.EventMouse:\n\t\tmx, my := e.Position()\n\t\tswitch e.Buttons() {\n\t\tcase tcell.Button1:\n\t\t\tif my == t.Y && len(t.List) > 1 {\n\t\t\t\tif mx == 0 {\n\t\t\t\t\tt.Scroll(-4)\n\t\t\t\t} else if mx == t.Width-1 {\n\t\t\t\t\tt.Scroll(4)\n\t\t\t\t} else {\n\t\t\t\t\tind := t.LocFromVisual(buffer.Loc{mx, my})\n\t\t\t\t\tif ind != -1 {\n\t\t\t\t\t\tt.SetActive(ind)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\tcase tcell.ButtonNone:\n\t\t\tif t.List[t.Active()].release {\n\t\t\t\t// Mouse release received, while already released\n\t\t\t\tt.ResetMouse()\n\t\t\t\treturn\n\t\t\t}\n\t\tcase tcell.WheelUp:\n\t\t\tif my == t.Y && len(t.List) > 1 {\n\t\t\t\tt.Scroll(4)\n\t\t\t\treturn\n\t\t\t}\n\t\tcase tcell.WheelDown:\n\t\t\tif my == t.Y && len(t.List) > 1 {\n\t\t\t\tt.Scroll(-4)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\tt.List[t.Active()].HandleEvent(event)\n}\n\n// Display updates the names and then displays the tab bar\nfunc (t *TabList) Display() {\n\tt.UpdateNames()\n\tif len(t.List) > 1 {\n\t\tt.TabWindow.Display()\n\t}\n}\n\nfunc (t *TabList) SetActive(a int) {\n\tt.TabWindow.SetActive(a)\n\n\tfor i, p := range t.List {\n\t\tif i == a {\n\t\t\tif !p.isActive {\n\t\t\t\tp.isActive = true\n\n\t\t\t\terr := config.RunPluginFn(\"onSetActive\", luar.New(ulua.L, p.CurPane()))\n\t\t\t\tif err != nil {\n\t\t\t\t\tscreen.TermMessage(err)\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tp.isActive = false\n\t\t}\n\t}\n}\n\n// ResetMouse resets the mouse release state after the screen was stopped\n// or the pane changed.\n// This prevents situations in which mouse releases are received at the wrong place\n// and the mouse state is still pressed.\nfunc (t *TabList) ResetMouse() {\n\tfor _, tab := range t.List {\n\t\tif !tab.release && tab.resizing != nil {\n\t\t\ttab.resizing = nil\n\t\t}\n\n\t\ttab.release = true\n\n\t\tfor _, p := range tab.Panes {\n\t\t\tif bp, ok := p.(*BufPane); ok {\n\t\t\t\tbp.resetMouse()\n\t\t\t}\n\t\t}\n\t}\n}\n\n// CloseTerms notifies term panes that a terminal job has finished.\nfunc (t *TabList) CloseTerms() {\n\tfor _, tab := range t.List {\n\t\tfor _, p := range tab.Panes {\n\t\t\tif tp, ok := p.(*TermPane); ok {\n\t\t\t\ttp.HandleTermClose()\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Tabs is the global tab list\nvar Tabs *TabList\n\nfunc InitTabs(bufs []*buffer.Buffer) {\n\tmultiopen := config.GetGlobalOption(\"multiopen\").(string)\n\tif multiopen == \"tab\" {\n\t\tTabs = NewTabList(bufs)\n\t} else {\n\t\tTabs = NewTabList(bufs[:1])\n\t\tfor _, b := range bufs[1:] {\n\t\t\tif multiopen == \"vsplit\" {\n\t\t\t\tMainTab().CurPane().VSplitBuf(b)\n\t\t\t} else { // default hsplit\n\t\t\t\tMainTab().CurPane().HSplitBuf(b)\n\t\t\t}\n\t\t}\n\t}\n\n\tscreen.RestartCallback = Tabs.ResetMouse\n}\n\nfunc MainTab() *Tab {\n\treturn Tabs.List[Tabs.Active()]\n}\n\n// A Tab represents a single tab\n// It consists of a list of edit panes (the open buffers),\n// a split tree (stored as just the root node), and a uiwindow\n// to display the UI elements like the borders between splits\ntype Tab struct {\n\t*views.Node\n\t*display.UIWindow\n\n\tisActive bool\n\n\tPanes  []Pane\n\tactive int\n\n\tresizing *views.Node // node currently being resized\n\t// captures whether the mouse is released\n\trelease bool\n}\n\n// NewTabFromBuffer creates a new tab from the given buffer\nfunc NewTabFromBuffer(x, y, width, height int, b *buffer.Buffer) *Tab {\n\tt := new(Tab)\n\tt.Node = views.NewRoot(x, y, width, height)\n\tt.UIWindow = display.NewUIWindow(t.Node)\n\tt.release = true\n\n\te := NewBufPaneFromBuf(b, t)\n\te.SetID(t.ID())\n\n\tt.Panes = append(t.Panes, e)\n\treturn t\n}\n\nfunc NewTabFromPane(x, y, width, height int, pane Pane) *Tab {\n\tt := new(Tab)\n\tt.Node = views.NewRoot(x, y, width, height)\n\tt.UIWindow = display.NewUIWindow(t.Node)\n\tt.release = true\n\tpane.SetTab(t)\n\tpane.SetID(t.ID())\n\n\tt.Panes = append(t.Panes, pane)\n\treturn t\n}\n\n// HandleEvent takes a tcell event and usually dispatches it to the current\n// active pane. However if the event is a resize or a mouse event where the user\n// is interacting with the UI (resizing splits) then the event is consumed here\n// If the event is a mouse press event in a pane, that pane will become active\n// and get the event\nfunc (t *Tab) HandleEvent(event tcell.Event) {\n\tswitch e := event.(type) {\n\tcase *tcell.EventMouse:\n\t\tmx, my := e.Position()\n\t\tbtn := e.Buttons()\n\t\tswitch {\n\t\tcase btn & ^(tcell.WheelUp|tcell.WheelDown|tcell.WheelLeft|tcell.WheelRight) != tcell.ButtonNone:\n\t\t\t// button press or drag\n\t\t\twasReleased := t.release\n\t\t\tt.release = false\n\n\t\t\tif btn == tcell.Button1 {\n\t\t\t\tif t.resizing != nil {\n\t\t\t\t\tvar size int\n\t\t\t\t\tif t.resizing.Kind == views.STVert {\n\t\t\t\t\t\tsize = mx - t.resizing.X\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsize = my - t.resizing.Y + 1\n\t\t\t\t\t}\n\t\t\t\t\tt.resizing.ResizeSplit(size)\n\t\t\t\t\tt.Resize()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif wasReleased {\n\t\t\t\t\tt.resizing = t.GetMouseSplitNode(buffer.Loc{mx, my})\n\t\t\t\t\tif t.resizing != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif wasReleased {\n\t\t\t\tfor i, p := range t.Panes {\n\t\t\t\t\tv := p.GetView()\n\t\t\t\t\tinpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height\n\t\t\t\t\tif inpane {\n\t\t\t\t\t\tt.SetActive(i)\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\tcase btn == tcell.ButtonNone:\n\t\t\t// button release\n\t\t\tt.release = true\n\t\t\tif t.resizing != nil {\n\t\t\t\tt.resizing = nil\n\t\t\t\treturn\n\t\t\t}\n\t\tdefault:\n\t\t\t// wheel move\n\t\t\tfor _, p := range t.Panes {\n\t\t\t\tv := p.GetView()\n\t\t\t\tinpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height\n\t\t\t\tif inpane {\n\t\t\t\t\tp.HandleEvent(event)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\tt.Panes[t.active].HandleEvent(event)\n}\n\n// SetActive changes the currently active pane to the specified index\nfunc (t *Tab) SetActive(i int) {\n\tt.active = i\n\tfor j, p := range t.Panes {\n\t\tif j == i {\n\t\t\tp.SetActive(true)\n\t\t} else {\n\t\t\tp.SetActive(false)\n\t\t}\n\t}\n}\n\n// AddPane adds a pane at a given index\nfunc (t *Tab) AddPane(pane Pane, i int) {\n\tif len(t.Panes) == i {\n\t\tt.Panes = append(t.Panes, pane)\n\t\treturn\n\t}\n\tt.Panes = append(t.Panes[:i+1], t.Panes[i:]...)\n\tt.Panes[i] = pane\n}\n\n// GetPane returns the pane with the given split index\nfunc (t *Tab) GetPane(splitid uint64) int {\n\tfor i, p := range t.Panes {\n\t\tif p.ID() == splitid {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn 0\n}\n\n// Remove pane removes the pane with the given index\nfunc (t *Tab) RemovePane(i int) {\n\tcopy(t.Panes[i:], t.Panes[i+1:])\n\tt.Panes[len(t.Panes)-1] = nil\n\tt.Panes = t.Panes[:len(t.Panes)-1]\n}\n\n// Resize resizes all panes according to their corresponding split nodes\nfunc (t *Tab) Resize() {\n\tfor _, p := range t.Panes {\n\t\tn := t.GetNode(p.ID())\n\t\tpv := p.GetView()\n\t\toffset := 0\n\t\tif n.X != 0 {\n\t\t\toffset = 1\n\t\t}\n\t\tpv.X, pv.Y = n.X+offset, n.Y\n\t\tp.SetView(pv)\n\t\tp.Resize(n.W-offset, n.H)\n\t}\n}\n\n// CurPane returns the currently active pane\nfunc (t *Tab) CurPane() *BufPane {\n\tp, ok := t.Panes[t.active].(*BufPane)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn p\n}\n"
  },
  {
    "path": "internal/action/terminal_supported.go",
    "content": "//go:build linux || darwin || dragonfly || solaris || openbsd || netbsd || freebsd\n\npackage action\n\nimport (\n\tshellquote \"github.com/kballard/go-shellquote\"\n\t\"github.com/micro-editor/micro/v2/internal/shell\"\n)\n\n// TermEmuSupported is a constant that marks if the terminal emulator is supported\nconst TermEmuSupported = true\n\n// RunTermEmulator starts a terminal emulator from a bufpane with the given input (command)\n// if wait is true it will wait for the user to exit by pressing enter once the executable has terminated\n// if getOutput is true it will redirect the stdout of the process to a pipe which will be passed to the\n// callback which is a function that takes a string and a list of optional user arguments\nfunc RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callback func(out string, userargs []any), userargs []any) error {\n\targs, err := shellquote.Split(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(args) == 0 {\n\t\treturn nil\n\t}\n\n\tt := new(shell.Terminal)\n\terr = t.Start(args, getOutput, wait, callback, userargs)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\th.AddTab()\n\tid := MainTab().Panes[0].ID()\n\n\tv := h.GetView()\n\n\ttp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())\n\tif err != nil {\n\t\treturn err\n\t}\n\tMainTab().Panes[0] = tp\n\tMainTab().SetActive(0)\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/action/terminal_unsupported.go",
    "content": "//go:build plan9 || nacl || windows\n\npackage action\n\nimport \"errors\"\n\n// TermEmuSupported is a constant that marks if the terminal emulator is supported\nconst TermEmuSupported = false\n\n// RunTermEmulator returns an error for unsupported systems (non-unix systems\nfunc RunTermEmulator(input string, wait bool, getOutput bool, callback func(out string, userargs []any), userargs []any) error {\n\treturn errors.New(\"Unsupported operating system\")\n}\n"
  },
  {
    "path": "internal/action/termpane.go",
    "content": "package action\n\nimport (\n\t\"errors\"\n\t\"runtime\"\n\n\t\"github.com/micro-editor/micro/v2/internal/clipboard\"\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/display\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/micro/v2/internal/shell\"\n\t\"github.com/micro-editor/tcell/v2\"\n\t\"github.com/micro-editor/terminal\"\n)\n\ntype TermKeyAction func(*TermPane)\n\nvar TermBindings *KeyTree\n\nfunc init() {\n\tTermBindings = NewKeyTree()\n}\n\nfunc TermKeyActionGeneral(a TermKeyAction) PaneKeyAction {\n\treturn func(p Pane) bool {\n\t\ta(p.(*TermPane))\n\t\treturn true\n\t}\n}\n\nfunc TermMapEvent(k Event, action string) {\n\tconfig.Bindings[\"terminal\"][k.Name()] = action\n\n\tswitch e := k.(type) {\n\tcase KeyEvent, KeySequenceEvent, RawEvent:\n\t\ttermMapKey(e, action)\n\tcase MouseEvent:\n\t\ttermMapMouse(e, action)\n\t}\n}\n\nfunc termMapKey(k Event, action string) {\n\tif f, ok := TermKeyActions[action]; ok {\n\t\tTermBindings.RegisterKeyBinding(k, TermKeyActionGeneral(f))\n\t}\n}\n\nfunc termMapMouse(k MouseEvent, action string) {\n\t// TODO: map mouse\n\ttermMapKey(k, action)\n}\n\ntype TermPane struct {\n\t*shell.Terminal\n\tdisplay.Window\n\n\tmouseReleased bool\n\tid            uint64\n\ttab           *Tab\n}\n\nfunc NewTermPane(x, y, w, h int, t *shell.Terminal, id uint64, tab *Tab) (*TermPane, error) {\n\tif !TermEmuSupported {\n\t\treturn nil, errors.New(\"Terminal emulator is not supported on this system\")\n\t}\n\n\tth := new(TermPane)\n\tth.Terminal = t\n\tth.id = id\n\tth.mouseReleased = true\n\tth.Window = display.NewTermWindow(x, y, w, h, t)\n\tth.tab = tab\n\treturn th, nil\n}\n\nfunc (t *TermPane) ID() uint64 {\n\treturn t.id\n}\n\nfunc (t *TermPane) SetID(i uint64) {\n\tt.id = i\n}\n\nfunc (t *TermPane) Name() string {\n\treturn t.Terminal.Name()\n}\n\nfunc (t *TermPane) SetTab(tab *Tab) {\n\tt.tab = tab\n}\n\nfunc (t *TermPane) Tab() *Tab {\n\treturn t.tab\n}\n\nfunc (t *TermPane) Close() {}\n\n// Quit closes this termpane\nfunc (t *TermPane) Quit() {\n\tt.Close()\n\tif len(MainTab().Panes) > 1 {\n\t\tt.Unsplit()\n\t} else if len(Tabs.List) > 1 {\n\t\tTabs.RemoveTab(t.id)\n\t} else {\n\t\tscreen.Screen.Fini()\n\t\tInfoBar.Close()\n\t\truntime.Goexit()\n\t}\n}\n\n// Unsplit removes this split\nfunc (t *TermPane) Unsplit() {\n\tn := MainTab().GetNode(t.id)\n\tn.Unsplit()\n\n\tMainTab().RemovePane(MainTab().GetPane(t.id))\n\tMainTab().Resize()\n\tMainTab().SetActive(len(MainTab().Panes) - 1)\n}\n\n// HandleEvent handles a tcell event by forwarding it to the terminal emulator\n// If the event is a mouse event and the program running in the emulator\n// does not have mouse support, the emulator will support selections and\n// copy-paste\nfunc (t *TermPane) HandleEvent(event tcell.Event) {\n\tif e, ok := event.(*tcell.EventKey); ok {\n\t\tke := keyEvent(e)\n\t\taction, more := TermBindings.NextEvent(ke, nil)\n\n\t\tif !more {\n\t\t\tif action != nil {\n\t\t\t\taction(t)\n\t\t\t\tTermBindings.ResetEvents()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tTermBindings.ResetEvents()\n\t\t}\n\n\t\tif more {\n\t\t\treturn\n\t\t}\n\n\t\tif t.Status == shell.TTDone {\n\t\t\tswitch e.Key() {\n\t\t\tcase tcell.KeyEscape, tcell.KeyCtrlQ, tcell.KeyEnter:\n\t\t\t\tt.Close()\n\t\t\t\tt.Quit()\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\tif e.Key() == tcell.KeyCtrlC && t.HasSelection() {\n\t\t\tclipboard.Write(t.GetSelection(t.GetView().Width), clipboard.ClipboardReg)\n\t\t\tInfoBar.Message(\"Copied selection to clipboard\")\n\t\t} else if t.Status != shell.TTDone {\n\t\t\tt.WriteString(event.EscSeq())\n\t\t}\n\t} else if _, ok := event.(*tcell.EventPaste); ok {\n\t\tif t.Status != shell.TTDone {\n\t\t\tt.WriteString(event.EscSeq())\n\t\t}\n\t} else if e, ok := event.(*tcell.EventMouse); !ok || t.State.Mode(terminal.ModeMouseMask) {\n\t\t// t.WriteString(event.EscSeq())\n\t} else {\n\t\tx, y := e.Position()\n\t\tv := t.GetView()\n\t\tx -= v.X\n\t\ty -= v.Y\n\n\t\tif e.Buttons() == tcell.Button1 {\n\t\t\tif !t.mouseReleased {\n\t\t\t\t// drag\n\t\t\t\tt.Selection[1].X = x\n\t\t\t\tt.Selection[1].Y = y\n\t\t\t} else {\n\t\t\t\tt.Selection[0].X = x\n\t\t\t\tt.Selection[0].Y = y\n\t\t\t\tt.Selection[1].X = x\n\t\t\t\tt.Selection[1].Y = y\n\t\t\t}\n\n\t\t\tt.mouseReleased = false\n\t\t} else if e.Buttons() == tcell.ButtonNone {\n\t\t\tif !t.mouseReleased {\n\t\t\t\tt.Selection[1].X = x\n\t\t\t\tt.Selection[1].Y = y\n\t\t\t}\n\t\t\tt.mouseReleased = true\n\t\t}\n\t}\n}\n\n// HandleTermClose is called when a terminal has finished its job\n// and should be closed. If that terminal is this termpane's terminal,\n// HandleTermClose will close the terminal and the termpane itself.\nfunc (t *TermPane) HandleTermClose() {\n\tif t.Status == shell.TTClose {\n\t\tt.Quit()\n\t}\n}\n\n// Exit closes the termpane\nfunc (t *TermPane) Exit() {\n\tt.Terminal.Close()\n\tt.Quit()\n}\n\n// CommandMode opens the termpane's command mode\nfunc (t *TermPane) CommandMode() {\n\tInfoBar.Prompt(\"> \", \"\", \"TerminalCommand\", nil, func(resp string, canceled bool) {\n\t\tif !canceled {\n\t\t\tt.HandleCommand(resp)\n\t\t}\n\t})\n}\n\n// NextSplit moves to the next split\nfunc (t *TermPane) NextSplit() {\n\ta := t.tab.active\n\tif a < len(t.tab.Panes)-1 {\n\t\ta++\n\t} else {\n\t\ta = 0\n\t}\n\n\tt.tab.SetActive(a)\n}\n\n// HandleCommand handles a command for the term pane\nfunc (t *TermPane) HandleCommand(input string) {\n\tInfoBar.Error(\"Commands are unsupported in term for now\")\n}\n\n// TermKeyActions contains the list of all possible key actions the termpane could execute\nvar TermKeyActions = map[string]TermKeyAction{\n\t\"Exit\":        (*TermPane).Exit,\n\t\"CommandMode\": (*TermPane).CommandMode,\n\t\"NextSplit\":   (*TermPane).NextSplit,\n}\n"
  },
  {
    "path": "internal/buffer/autocomplete.go",
    "content": "package buffer\n\nimport (\n\t\"bytes\"\n\t\"io/fs\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n)\n\n// A Completer is a function that takes a buffer and returns info\n// describing what autocompletions should be inserted at the current\n// cursor location\n// It returns a list of string suggestions which will be inserted at\n// the current cursor location if selected as well as a list of\n// suggestion names which can be displayed in an autocomplete box or\n// other UI element\ntype Completer func(*Buffer) ([]string, []string)\n\nfunc (b *Buffer) GetSuggestions() {\n\n}\n\n// Autocomplete starts the autocomplete process\nfunc (b *Buffer) Autocomplete(c Completer) bool {\n\tb.Completions, b.Suggestions = c(b)\n\tif len(b.Completions) != len(b.Suggestions) || len(b.Completions) == 0 {\n\t\treturn false\n\t}\n\tb.CurSuggestion = -1\n\tb.CycleAutocomplete(true)\n\treturn true\n}\n\n// CycleAutocomplete moves to the next suggestion\nfunc (b *Buffer) CycleAutocomplete(forward bool) {\n\tprevSuggestion := b.CurSuggestion\n\n\tif forward {\n\t\tb.CurSuggestion++\n\t} else {\n\t\tb.CurSuggestion--\n\t}\n\tif b.CurSuggestion >= len(b.Suggestions) {\n\t\tb.CurSuggestion = 0\n\t} else if b.CurSuggestion < 0 {\n\t\tb.CurSuggestion = len(b.Suggestions) - 1\n\t}\n\n\tc := b.GetActiveCursor()\n\tstart := c.Loc\n\tend := c.Loc\n\tif prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 {\n\t\tstart = end.Move(-util.CharacterCountInString(b.Completions[prevSuggestion]), b)\n\t}\n\n\tb.Replace(start, end, b.Completions[b.CurSuggestion])\n\tif len(b.Suggestions) > 1 {\n\t\tb.HasSuggestions = true\n\t}\n}\n\n// GetWord gets the most recent word separated by any separator\n// (whitespace, punctuation, any non alphanumeric character)\nfunc (b *Buffer) GetWord() ([]byte, int) {\n\tc := b.GetActiveCursor()\n\tl := b.LineBytes(c.Y)\n\tl = util.SliceStart(l, c.X)\n\n\tif c.X == 0 || util.IsWhitespace(b.RuneAt(c.Loc.Move(-1, b))) {\n\t\treturn []byte{}, -1\n\t}\n\n\tif util.IsNonWordChar(b.RuneAt(c.Loc.Move(-1, b))) {\n\t\treturn []byte{}, c.X\n\t}\n\n\targs := bytes.FieldsFunc(l, util.IsNonWordChar)\n\tinput := args[len(args)-1]\n\treturn input, c.X - util.CharacterCount(input)\n}\n\n// GetArg gets the most recent word (separated by ' ' only)\nfunc (b *Buffer) GetArg() (string, int) {\n\tc := b.GetActiveCursor()\n\tl := b.LineBytes(c.Y)\n\tl = util.SliceStart(l, c.X)\n\n\targs := bytes.Split(l, []byte{' '})\n\tinput := string(args[len(args)-1])\n\targstart := 0\n\tfor i, a := range args {\n\t\tif i == len(args)-1 {\n\t\t\tbreak\n\t\t}\n\t\targstart += util.CharacterCount(a) + 1\n\t}\n\n\treturn input, argstart\n}\n\n// FileComplete autocompletes filenames\nfunc FileComplete(b *Buffer) ([]string, []string) {\n\tc := b.GetActiveCursor()\n\tinput, argstart := b.GetArg()\n\n\tsep := string(os.PathSeparator)\n\tdirs := strings.Split(input, sep)\n\n\tvar files []fs.DirEntry\n\tvar err error\n\tif len(dirs) > 1 {\n\t\tdirectories := strings.Join(dirs[:len(dirs)-1], sep) + sep\n\n\t\tdirectories, _ = util.ReplaceHome(directories)\n\t\tfiles, err = os.ReadDir(directories)\n\t} else {\n\t\tfiles, err = os.ReadDir(\".\")\n\t}\n\n\tif err != nil {\n\t\treturn nil, nil\n\t}\n\n\tvar suggestions []string\n\tfor _, f := range files {\n\t\tname := f.Name()\n\t\tif f.IsDir() {\n\t\t\tname += sep\n\t\t}\n\t\tif strings.HasPrefix(name, dirs[len(dirs)-1]) {\n\t\t\tsuggestions = append(suggestions, name)\n\t\t}\n\t}\n\n\tsort.Strings(suggestions)\n\tcompletions := make([]string, len(suggestions))\n\tfor i := range suggestions {\n\t\tvar complete string\n\t\tif len(dirs) > 1 {\n\t\t\tcomplete = strings.Join(dirs[:len(dirs)-1], sep) + sep + suggestions[i]\n\t\t} else {\n\t\t\tcomplete = suggestions[i]\n\t\t}\n\t\tcompletions[i] = util.SliceEndStr(complete, c.X-argstart)\n\t}\n\n\treturn completions, suggestions\n}\n\n// BufferComplete autocompletes based on previous words in the buffer\nfunc BufferComplete(b *Buffer) ([]string, []string) {\n\tc := b.GetActiveCursor()\n\tinput, argstart := b.GetWord()\n\n\tif argstart == -1 {\n\t\treturn []string{}, []string{}\n\t}\n\n\tinputLen := util.CharacterCount(input)\n\n\tsuggestionsSet := make(map[string]struct{})\n\n\tvar suggestions []string\n\tfor i := c.Y; i >= 0; i-- {\n\t\tl := b.LineBytes(i)\n\t\twords := bytes.FieldsFunc(l, util.IsNonWordChar)\n\t\tfor _, w := range words {\n\t\t\tif bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {\n\t\t\t\tstrw := string(w)\n\t\t\t\tif _, ok := suggestionsSet[strw]; !ok {\n\t\t\t\t\tsuggestionsSet[strw] = struct{}{}\n\t\t\t\t\tsuggestions = append(suggestions, strw)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tfor i := c.Y + 1; i < b.LinesNum(); i++ {\n\t\tl := b.LineBytes(i)\n\t\twords := bytes.FieldsFunc(l, util.IsNonWordChar)\n\t\tfor _, w := range words {\n\t\t\tif bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {\n\t\t\t\tstrw := string(w)\n\t\t\t\tif _, ok := suggestionsSet[strw]; !ok {\n\t\t\t\t\tsuggestionsSet[strw] = struct{}{}\n\t\t\t\t\tsuggestions = append(suggestions, strw)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif len(suggestions) > 1 {\n\t\tsuggestions = append(suggestions, string(input))\n\t}\n\n\tcompletions := make([]string, len(suggestions))\n\tfor i := range suggestions {\n\t\tcompletions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)\n\t}\n\n\treturn completions, suggestions\n}\n"
  },
  {
    "path": "internal/buffer/backup.go",
    "content": "package buffer\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n)\n\nconst BackupMsg = `A backup was detected for:\n\n%s\n\nThis likely means that micro crashed while editing this file,\nor another instance of micro is currently editing this file,\nor an error occurred while saving this file so it may be corrupted.\n\nThe backup was created on %s and its path is:\n\n%s\n\n* 'recover' will apply the backup as unsaved changes to the current buffer.\n  When the buffer is closed, the backup will be removed.\n* 'ignore' will ignore the backup, discarding its changes. The backup file\n  will be removed.\n* 'abort' will abort the open operation, and instead open an empty buffer.\n\nOptions: [r]ecover, [i]gnore, [a]bort: `\n\nconst backupSeconds = 8\n\ntype backupRequestType int\n\nconst (\n\tbackupCreate = iota\n\tbackupRemove\n)\n\ntype backupRequest struct {\n\tbuf     *SharedBuffer\n\treqType backupRequestType\n}\n\nvar requestedBackups map[*SharedBuffer]bool\n\nfunc init() {\n\trequestedBackups = make(map[*SharedBuffer]bool)\n}\n\nfunc (b *SharedBuffer) RequestBackup() {\n\tbackupRequestChan <- backupRequest{buf: b, reqType: backupCreate}\n}\n\nfunc (b *SharedBuffer) CancelBackup() {\n\tbackupRequestChan <- backupRequest{buf: b, reqType: backupRemove}\n}\n\nfunc handleBackupRequest(br backupRequest) {\n\tswitch br.reqType {\n\tcase backupCreate:\n\t\t// schedule periodic backup\n\t\trequestedBackups[br.buf] = true\n\tcase backupRemove:\n\t\tbr.buf.RemoveBackup()\n\t\tdelete(requestedBackups, br.buf)\n\t}\n}\n\nfunc periodicBackup() {\n\tfor buf := range requestedBackups {\n\t\terr := buf.Backup()\n\t\tif err == nil {\n\t\t\tdelete(requestedBackups, buf)\n\t\t}\n\t}\n}\n\nfunc (b *SharedBuffer) backupDir() string {\n\tbackupdir, err := util.ReplaceHome(b.Settings[\"backupdir\"].(string))\n\tif backupdir == \"\" || err != nil {\n\t\tbackupdir = filepath.Join(config.ConfigDir, \"backups\")\n\t}\n\treturn backupdir\n}\n\nfunc (b *SharedBuffer) keepBackup() bool {\n\treturn b.forceKeepBackup || b.Settings[\"permbackup\"].(bool)\n}\n\nfunc (b *SharedBuffer) writeBackup(path string) (string, string, error) {\n\tbackupdir := b.backupDir()\n\tif _, err := os.Stat(backupdir); err != nil {\n\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\treturn \"\", \"\", err\n\t\t}\n\t\tif err = os.Mkdir(backupdir, os.ModePerm); err != nil {\n\t\t\treturn \"\", \"\", err\n\t\t}\n\t}\n\n\tname, resolveName := util.DetermineEscapePath(backupdir, path)\n\ttmp := name + util.BackupSuffix\n\n\t_, err := b.overwriteFile(tmp)\n\tif err != nil {\n\t\tos.Remove(tmp)\n\t\treturn name, resolveName, err\n\t}\n\terr = os.Rename(tmp, name)\n\tif err != nil {\n\t\tos.Remove(tmp)\n\t\treturn name, resolveName, err\n\t}\n\n\tif resolveName != \"\" {\n\t\terr = util.SafeWrite(resolveName, []byte(path), true)\n\t\tif err != nil {\n\t\t\treturn name, resolveName, err\n\t\t}\n\t}\n\n\treturn name, resolveName, nil\n}\n\nfunc (b *SharedBuffer) removeBackup(path string, resolveName string) {\n\tos.Remove(path)\n\tif resolveName != \"\" {\n\t\tos.Remove(resolveName)\n\t}\n}\n\n// Backup saves the buffer to the backups directory\nfunc (b *SharedBuffer) Backup() error {\n\tif !b.Settings[\"backup\"].(bool) || b.Path == \"\" || b.Type != BTDefault {\n\t\treturn nil\n\t}\n\n\t_, _, err := b.writeBackup(b.AbsPath)\n\treturn err\n}\n\n// RemoveBackup removes any backup file associated with this buffer\nfunc (b *SharedBuffer) RemoveBackup() {\n\tif b.keepBackup() || b.Path == \"\" || b.Type != BTDefault {\n\t\treturn\n\t}\n\tf, resolveName := util.DetermineEscapePath(b.backupDir(), b.AbsPath)\n\tb.removeBackup(f, resolveName)\n}\n\n// ApplyBackup applies the corresponding backup file to this buffer (if one exists)\n// Returns true if a backup was applied\nfunc (b *SharedBuffer) ApplyBackup(fsize int64) (bool, bool) {\n\tif b.Settings[\"backup\"].(bool) && !b.Settings[\"permbackup\"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {\n\t\tbackupfile, resolveName := util.DetermineEscapePath(b.backupDir(), b.AbsPath)\n\t\tif info, err := os.Stat(backupfile); err == nil {\n\t\t\tbackup, err := os.Open(backupfile)\n\t\t\tif err == nil {\n\t\t\t\tdefer backup.Close()\n\t\t\t\tt := info.ModTime()\n\t\t\t\tmsg := fmt.Sprintf(BackupMsg, b.Path, t.Format(\"Mon Jan _2 at 15:04, 2006\"), backupfile)\n\t\t\t\tchoice := screen.TermPrompt(msg, []string{\"r\", \"i\", \"a\", \"recover\", \"ignore\", \"abort\"}, true)\n\n\t\t\t\tif choice%3 == 0 {\n\t\t\t\t\t// recover\n\t\t\t\t\tb.LineArray = NewLineArray(uint64(fsize), FFAuto, backup)\n\t\t\t\t\tb.setModified()\n\t\t\t\t\treturn true, true\n\t\t\t\t} else if choice%3 == 1 {\n\t\t\t\t\t// delete\n\t\t\t\t\tb.removeBackup(backupfile, resolveName)\n\t\t\t\t} else if choice%3 == 2 {\n\t\t\t\t\treturn false, false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false, true\n}\n"
  },
  {
    "path": "internal/buffer/buffer.go",
    "content": "package buffer\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"crypto/md5\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tluar \"layeh.com/gopher-luar\"\n\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\tulua \"github.com/micro-editor/micro/v2/internal/lua\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n\t\"github.com/micro-editor/micro/v2/pkg/highlight\"\n\tdmp \"github.com/sergi/go-diff/diffmatchpatch\"\n\t\"golang.org/x/text/encoding\"\n\t\"golang.org/x/text/encoding/htmlindex\"\n\t\"golang.org/x/text/encoding/unicode\"\n\t\"golang.org/x/text/transform\"\n)\n\nvar (\n\t// OpenBuffers is a list of the currently open buffers\n\tOpenBuffers []*Buffer\n\t// LogBuf is a reference to the log buffer which can be opened with the\n\t// `> log` command\n\tLogBuf *Buffer\n)\n\n// The BufType defines what kind of buffer this is\ntype BufType struct {\n\tKind     int\n\tReadonly bool // The buffer cannot be edited\n\tScratch  bool // The buffer cannot be saved\n\tSyntax   bool // Syntax highlighting is enabled\n}\n\nvar (\n\t// BTDefault is a default buffer\n\tBTDefault = BufType{0, false, false, true}\n\t// BTHelp is a help buffer\n\tBTHelp = BufType{1, true, true, true}\n\t// BTLog is a log buffer\n\tBTLog = BufType{2, true, true, false}\n\t// BTScratch is a buffer that cannot be saved (for scratch work)\n\tBTScratch = BufType{3, false, true, false}\n\t// BTRaw is a buffer that shows raw terminal events\n\tBTRaw = BufType{4, false, true, false}\n\t// BTInfo is a buffer for inputting information\n\tBTInfo = BufType{5, false, true, false}\n\t// BTStdout is a buffer that only writes to stdout\n\t// when closed\n\tBTStdout = BufType{6, false, true, true}\n)\n\n// SharedBuffer is a struct containing info that is shared among buffers\n// that have the same file open\ntype SharedBuffer struct {\n\t*LineArray\n\t// Stores the last modification time of the file the buffer is pointing to\n\tModTime time.Time\n\t// Type of the buffer (e.g. help, raw, scratch etc..)\n\tType BufType\n\n\t// Path to the file on disk\n\tPath string\n\t// Absolute path to the file on disk\n\tAbsPath string\n\t// Name of the buffer on the status line\n\tname string\n\n\ttoStdout bool\n\n\t// Settings customized by the user\n\tSettings map[string]any\n\t// LocalSettings customized by the user for this buffer only\n\tLocalSettings map[string]bool\n\n\tencoding encoding.Encoding\n\n\tSuggestions   []string\n\tCompletions   []string\n\tCurSuggestion int\n\n\tMessages []*Message\n\n\tupdateDiffTimer   *time.Timer\n\tdiffBase          []byte\n\tdiffBaseLineCount int\n\tdiffLock          sync.RWMutex\n\tdiff              map[int]DiffStatus\n\n\tforceKeepBackup bool\n\n\t// ReloadDisabled allows the user to disable reloads if they\n\t// are viewing a file that is constantly changing\n\tReloadDisabled bool\n\n\tisModified bool\n\t// Whether or not suggestions can be autocompleted must be shared because\n\t// it changes based on how the buffer has changed\n\tHasSuggestions bool\n\n\t// The Highlighter struct actually performs the highlighting\n\tHighlighter *highlight.Highlighter\n\t// SyntaxDef represents the syntax highlighting definition being used\n\t// This stores the highlighting rules and filetype detection info\n\tSyntaxDef *highlight.Def\n\n\tModifiedThisFrame bool\n\n\t// Hash of the original buffer -- empty if fastdirty is on\n\torigHash [md5.Size]byte\n}\n\nfunc (b *SharedBuffer) insert(pos Loc, value []byte) {\n\tb.HasSuggestions = false\n\tb.LineArray.insert(pos, value)\n\tb.setModified()\n\n\tinslines := bytes.Count(value, []byte{'\\n'})\n\tb.MarkModified(pos.Y, pos.Y+inslines)\n}\n\nfunc (b *SharedBuffer) remove(start, end Loc) []byte {\n\tb.HasSuggestions = false\n\tdefer b.setModified()\n\tdefer b.MarkModified(start.Y, end.Y)\n\treturn b.LineArray.remove(start, end)\n}\n\nfunc (b *SharedBuffer) setModified() {\n\tif b.Type.Scratch {\n\t\treturn\n\t}\n\n\tif b.Settings[\"fastdirty\"].(bool) {\n\t\tb.isModified = true\n\t} else {\n\t\tvar buff [md5.Size]byte\n\n\t\tb.calcHash(&buff)\n\t\tb.isModified = buff != b.origHash\n\t}\n\n\tif b.isModified {\n\t\tb.RequestBackup()\n\t} else {\n\t\tb.CancelBackup()\n\t}\n}\n\n// calcHash calculates md5 hash of all lines in the buffer\nfunc (b *SharedBuffer) calcHash(out *[md5.Size]byte) {\n\th := md5.New()\n\n\tif len(b.lines) > 0 {\n\t\th.Write(b.lines[0].data)\n\n\t\tfor _, l := range b.lines[1:] {\n\t\t\tif b.Endings == FFDos {\n\t\t\t\th.Write([]byte{'\\r', '\\n'})\n\t\t\t} else {\n\t\t\t\th.Write([]byte{'\\n'})\n\t\t\t}\n\t\t\th.Write(l.data)\n\t\t}\n\t}\n\n\th.Sum((*out)[:0])\n}\n\n// MarkModified marks the buffer as modified for this frame\n// and performs rehighlighting if syntax highlighting is enabled\nfunc (b *SharedBuffer) MarkModified(start, end int) {\n\tb.ModifiedThisFrame = true\n\n\tstart = util.Clamp(start, 0, len(b.lines)-1)\n\tend = util.Clamp(end, 0, len(b.lines)-1)\n\n\tif b.Settings[\"syntax\"].(bool) && b.SyntaxDef != nil {\n\t\tl := -1\n\t\tfor i := start; i <= end; i++ {\n\t\t\tl = util.Max(b.Highlighter.ReHighlightStates(b, i), l)\n\t\t}\n\t\tb.Highlighter.HighlightMatches(b, start, l)\n\t}\n\n\tfor i := start; i <= end; i++ {\n\t\tb.LineArray.invalidateSearchMatches(i)\n\t}\n}\n\n// DisableReload disables future reloads of this sharedbuffer\nfunc (b *SharedBuffer) DisableReload() {\n\tb.ReloadDisabled = true\n}\n\nconst (\n\tDSUnchanged    = 0\n\tDSAdded        = 1\n\tDSModified     = 2\n\tDSDeletedAbove = 3\n)\n\ntype DiffStatus byte\n\ntype Command struct {\n\tStartCursor      Loc\n\tSearchRegex      string\n\tSearchAfterStart bool\n}\n\nvar emptyCommand = Command{\n\tStartCursor:      Loc{-1, -1},\n\tSearchRegex:      \"\",\n\tSearchAfterStart: false,\n}\n\n// Buffer stores the main information about a currently open file including\n// the actual text (in a LineArray), the undo/redo stack (in an EventHandler)\n// all the cursors, the syntax highlighting info, the settings for the buffer\n// and some misc info about modification time and path location.\n// The syntax highlighting info must be stored with the buffer because the syntax\n// highlighter attaches information to each line of the buffer for optimization\n// purposes so it doesn't have to rehighlight everything on every update.\n// Likewise for the search highlighting.\ntype Buffer struct {\n\t*EventHandler\n\t*SharedBuffer\n\n\tcursors     []*Cursor\n\tcurCursor   int\n\tStartCursor Loc\n\n\t// OptionCallback is called after a buffer option value is changed.\n\t// The display module registers its OptionCallback to ensure the buffer window\n\t// is properly updated when needed. This is a workaround for the fact that\n\t// the buffer module cannot directly call the display's API (it would mean\n\t// a circular dependency between packages).\n\tOptionCallback func(option string, nativeValue any)\n\n\t// The display module registers its own GetVisualX function for getting\n\t// the correct visual x location of a cursor when softwrap is used.\n\t// This is hacky. Maybe it would be better to move all the visual x logic\n\t// from buffer to display, but it would require rewriting a lot of code.\n\tGetVisualX func(loc Loc) int\n\n\t// Last search stores the last successful search\n\tLastSearch      string\n\tLastSearchRegex bool\n\t// HighlightSearch enables highlighting all instances of the last successful search\n\tHighlightSearch bool\n\n\t// OverwriteMode indicates that we are in overwrite mode (toggled by\n\t// Insert key by default) i.e. that typing a character shall replace the\n\t// character under the cursor instead of inserting a character before it.\n\tOverwriteMode bool\n}\n\n// NewBufferFromFileWithCommand opens a new buffer with a given command\n// If cmd.StartCursor is {-1, -1} the location does not overwrite what the cursor location\n// would otherwise be (start of file, or saved cursor position if `savecursor` is\n// enabled)\nfunc NewBufferFromFileWithCommand(path string, btype BufType, cmd Command) (*Buffer, error) {\n\tvar err error\n\tfilename := path\n\tif config.GetGlobalOption(\"parsecursor\").(bool) && cmd.StartCursor.X == -1 && cmd.StartCursor.Y == -1 {\n\t\tvar cursorPos []string\n\t\tfilename, cursorPos = util.GetPathAndCursorPosition(filename)\n\t\tcmd.StartCursor, err = ParseCursorLocation(cursorPos)\n\t\tif err != nil {\n\t\t\tcmd.StartCursor = Loc{-1, -1}\n\t\t}\n\t}\n\n\tfilename, err = util.ReplaceHome(filename)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfileInfo, serr := os.Stat(filename)\n\tif serr != nil && !errors.Is(serr, fs.ErrNotExist) {\n\t\treturn nil, serr\n\t}\n\tif serr == nil && fileInfo.IsDir() {\n\t\treturn nil, errors.New(\"Error: \" + filename + \" is a directory and cannot be opened\")\n\t}\n\tif serr == nil && !fileInfo.Mode().IsRegular() {\n\t\treturn nil, errors.New(\"Error: \" + filename + \" is not a regular file and cannot be opened\")\n\t}\n\n\tf, err := os.OpenFile(filename, os.O_WRONLY, 0)\n\treadonly := errors.Is(err, fs.ErrPermission)\n\tf.Close()\n\n\tfile, err := os.Open(filename)\n\tif err == nil {\n\t\tdefer file.Close()\n\t}\n\n\tvar buf *Buffer\n\tif errors.Is(err, fs.ErrNotExist) {\n\t\t// File does not exist -- create an empty buffer with that name\n\t\tbuf = NewBufferFromString(\"\", filename, btype)\n\t} else if err != nil {\n\t\treturn nil, err\n\t} else {\n\t\tbuf = NewBuffer(file, util.FSize(file), filename, btype, cmd)\n\t\tif buf == nil {\n\t\t\treturn nil, errors.New(\"could not open file\")\n\t\t}\n\t}\n\n\tif readonly && prompt != nil {\n\t\tprompt.Message(fmt.Sprintf(\"Warning: file is readonly - %s will be attempted when saving\", config.GlobalSettings[\"sucmd\"].(string)))\n\t\t// buf.SetOptionNative(\"readonly\", true)\n\t}\n\n\treturn buf, nil\n}\n\n// NewBufferFromFile opens a new buffer using the given path\n// It will also automatically handle `~`, and line/column with filename:l:c\n// It will return an empty buffer if the path does not exist\n// and an error if the file is a directory\nfunc NewBufferFromFile(path string, btype BufType) (*Buffer, error) {\n\treturn NewBufferFromFileWithCommand(path, btype, emptyCommand)\n}\n\n// NewBufferFromStringWithCommand creates a new buffer containing the given string\n// with a cursor loc and a search text\nfunc NewBufferFromStringWithCommand(text, path string, btype BufType, cmd Command) *Buffer {\n\treturn NewBuffer(strings.NewReader(text), int64(len(text)), path, btype, cmd)\n}\n\n// NewBufferFromString creates a new buffer containing the given string\nfunc NewBufferFromString(text, path string, btype BufType) *Buffer {\n\treturn NewBuffer(strings.NewReader(text), int64(len(text)), path, btype, emptyCommand)\n}\n\n// NewBuffer creates a new buffer from a given reader with a given path\n// Ensure that ReadSettings and InitGlobalSettings have been called before creating\n// a new buffer\n// Places the cursor at startcursor. If startcursor is -1, -1 places the\n// cursor at an autodetected location (based on savecursor or :LINE:COL)\nfunc NewBuffer(r io.Reader, size int64, path string, btype BufType, cmd Command) *Buffer {\n\tabsPath, err := filepath.Abs(path)\n\tif err != nil {\n\t\tabsPath = path\n\t}\n\n\tb := new(Buffer)\n\n\tfound := false\n\tif len(path) > 0 {\n\t\tfor _, buf := range OpenBuffers {\n\t\t\tif buf.AbsPath == absPath && buf.Type != BTInfo {\n\t\t\t\tfound = true\n\t\t\t\tb.SharedBuffer = buf.SharedBuffer\n\t\t\t\tb.EventHandler = buf.EventHandler\n\t\t\t}\n\t\t}\n\t}\n\n\thasBackup := false\n\tif !found {\n\t\tb.SharedBuffer = new(SharedBuffer)\n\t\tb.Type = btype\n\n\t\tb.AbsPath = absPath\n\t\tb.Path = path\n\n\t\tb.Settings = config.DefaultCommonSettings()\n\t\tb.LocalSettings = make(map[string]bool)\n\t\tfor k, v := range config.GlobalSettings {\n\t\t\tif _, ok := config.DefaultGlobalOnlySettings[k]; !ok {\n\t\t\t\t// make sure setting is not global-only\n\t\t\t\tb.Settings[k] = v\n\t\t\t}\n\t\t}\n\t\tconfig.UpdatePathGlobLocals(b.Settings, absPath)\n\n\t\tb.encoding, err = htmlindex.Get(b.Settings[\"encoding\"].(string))\n\t\tif err != nil {\n\t\t\tb.encoding = unicode.UTF8\n\t\t\tb.Settings[\"encoding\"] = \"utf-8\"\n\t\t}\n\n\t\tvar ok bool\n\t\thasBackup, ok = b.ApplyBackup(size)\n\n\t\tif !ok {\n\t\t\treturn NewBufferFromString(\"\", \"\", btype)\n\t\t}\n\t\tif !hasBackup {\n\t\t\treader := bufio.NewReader(transform.NewReader(r, b.encoding.NewDecoder()))\n\n\t\t\tvar ff FileFormat = FFAuto\n\n\t\t\tif size == 0 {\n\t\t\t\t// for empty files, use the fileformat setting instead of\n\t\t\t\t// autodetection\n\t\t\t\tswitch b.Settings[\"fileformat\"] {\n\t\t\t\tcase \"unix\":\n\t\t\t\t\tff = FFUnix\n\t\t\t\tcase \"dos\":\n\t\t\t\t\tff = FFDos\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// in case of autodetection treat as locally set\n\t\t\t\tb.LocalSettings[\"fileformat\"] = true\n\t\t\t}\n\n\t\t\tb.LineArray = NewLineArray(uint64(size), ff, reader)\n\t\t}\n\t\tb.EventHandler = NewEventHandler(b.SharedBuffer, b.cursors)\n\n\t\t// The last time this file was modified\n\t\tb.UpdateModTime()\n\t}\n\n\tif b.Settings[\"readonly\"].(bool) && b.Type == BTDefault {\n\t\tb.Type.Readonly = true\n\t}\n\n\tswitch b.Endings {\n\tcase FFUnix:\n\t\tb.Settings[\"fileformat\"] = \"unix\"\n\tcase FFDos:\n\t\tb.Settings[\"fileformat\"] = \"dos\"\n\t}\n\n\tb.UpdateRules()\n\t// we know the filetype now, so update per-filetype settings\n\tconfig.UpdateFileTypeLocals(b.Settings, b.Settings[\"filetype\"].(string))\n\n\tif _, err := os.Stat(filepath.Join(config.ConfigDir, \"buffers\")); errors.Is(err, fs.ErrNotExist) {\n\t\tos.Mkdir(filepath.Join(config.ConfigDir, \"buffers\"), os.ModePerm)\n\t}\n\n\tif cmd.StartCursor.X != -1 && cmd.StartCursor.Y != -1 {\n\t\tb.StartCursor = cmd.StartCursor\n\t} else if b.Settings[\"savecursor\"].(bool) || b.Settings[\"saveundo\"].(bool) {\n\t\terr := b.Unserialize()\n\t\tif err != nil {\n\t\t\tscreen.TermMessage(err)\n\t\t}\n\t}\n\n\tb.AddCursor(NewCursor(b, b.StartCursor))\n\tb.GetActiveCursor().Relocate()\n\n\tif cmd.SearchRegex != \"\" {\n\t\tmatch, found, _ := b.FindNext(cmd.SearchRegex, b.Start(), b.End(), b.StartCursor, true, true)\n\t\tif found {\n\t\t\tif cmd.SearchAfterStart {\n\t\t\t\t// Search from current cursor and move it accordingly\n\t\t\t\tb.GetActiveCursor().SetSelectionStart(match[0])\n\t\t\t\tb.GetActiveCursor().SetSelectionEnd(match[1])\n\t\t\t\tb.GetActiveCursor().OrigSelection[0] = b.GetActiveCursor().CurSelection[0]\n\t\t\t\tb.GetActiveCursor().OrigSelection[1] = b.GetActiveCursor().CurSelection[1]\n\t\t\t\tb.GetActiveCursor().GotoLoc(match[1])\n\t\t\t}\n\t\t\tb.LastSearch = cmd.SearchRegex\n\t\t\tb.LastSearchRegex = true\n\t\t\tb.HighlightSearch = b.Settings[\"hlsearch\"].(bool)\n\t\t}\n\t}\n\n\tif !b.Settings[\"fastdirty\"].(bool) && !found {\n\t\tif size > LargeFileThreshold {\n\t\t\t// If the file is larger than LargeFileThreshold fastdirty needs to be on\n\t\t\tb.Settings[\"fastdirty\"] = true\n\t\t} else if !hasBackup {\n\t\t\t// since applying a backup does not save the applied backup to disk, we should\n\t\t\t// not calculate the original hash based on the backup data\n\t\t\tb.calcHash(&b.origHash)\n\t\t}\n\t}\n\n\terr = config.RunPluginFn(\"onBufferOpen\", luar.New(ulua.L, b))\n\tif err != nil {\n\t\tscreen.TermMessage(err)\n\t}\n\n\tOpenBuffers = append(OpenBuffers, b)\n\n\treturn b\n}\n\n// CloseOpenBuffers removes all open buffers\nfunc CloseOpenBuffers() {\n\tfor i, buf := range OpenBuffers {\n\t\tbuf.Fini()\n\t\tOpenBuffers[i] = nil\n\t}\n\tOpenBuffers = OpenBuffers[:0]\n}\n\n// Close removes this buffer from the list of open buffers\nfunc (b *Buffer) Close() {\n\tfor i, buf := range OpenBuffers {\n\t\tif b == buf {\n\t\t\tb.Fini()\n\t\t\tcopy(OpenBuffers[i:], OpenBuffers[i+1:])\n\t\t\tOpenBuffers[len(OpenBuffers)-1] = nil\n\t\t\tOpenBuffers = OpenBuffers[:len(OpenBuffers)-1]\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// Fini should be called when a buffer is closed and performs\n// some cleanup\nfunc (b *Buffer) Fini() {\n\tif !b.Modified() {\n\t\tb.Serialize()\n\t}\n\tb.CancelBackup()\n\n\tif b.Type == BTStdout {\n\t\tfmt.Fprint(util.Stdout, string(b.Bytes()))\n\t}\n}\n\n// GetName returns the name that should be displayed in the statusline\n// for this buffer\nfunc (b *Buffer) GetName() string {\n\tname := b.name\n\tif name == \"\" {\n\t\tif b.Path == \"\" {\n\t\t\treturn \"No name\"\n\t\t}\n\t\tname = b.Path\n\t}\n\tif b.Settings[\"basename\"].(bool) {\n\t\treturn filepath.Base(name)\n\t}\n\treturn name\n}\n\n// SetName changes the name for this buffer\nfunc (b *Buffer) SetName(s string) {\n\tb.name = s\n}\n\n// Insert inserts the given string of text at the start location\nfunc (b *Buffer) Insert(start Loc, text string) {\n\tif !b.Type.Readonly {\n\t\tb.EventHandler.cursors = b.cursors\n\t\tb.EventHandler.active = b.curCursor\n\t\tb.EventHandler.Insert(start, text)\n\t}\n}\n\n// Remove removes the characters between the start and end locations\nfunc (b *Buffer) Remove(start, end Loc) {\n\tif !b.Type.Readonly {\n\t\tb.EventHandler.cursors = b.cursors\n\t\tb.EventHandler.active = b.curCursor\n\t\tb.EventHandler.Remove(start, end)\n\t}\n}\n\n// FileType returns the buffer's filetype\nfunc (b *Buffer) FileType() string {\n\treturn b.Settings[\"filetype\"].(string)\n}\n\n// ExternallyModified returns whether the file being edited has\n// been modified by some external process\nfunc (b *Buffer) ExternallyModified() bool {\n\tmodTime, err := util.GetModTime(b.Path)\n\tif err == nil {\n\t\treturn modTime != b.ModTime\n\t}\n\treturn false\n}\n\n// UpdateModTime updates the modtime of this file\nfunc (b *Buffer) UpdateModTime() (err error) {\n\tb.ModTime, err = util.GetModTime(b.Path)\n\treturn\n}\n\n// ReOpen reloads the current buffer from disk\nfunc (b *Buffer) ReOpen() error {\n\tfile, err := os.Open(b.Path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer file.Close()\n\n\tenc, err := htmlindex.Get(b.Settings[\"encoding\"].(string))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treader := bufio.NewReader(transform.NewReader(file, enc.NewDecoder()))\n\tdata, err := io.ReadAll(reader)\n\ttxt := string(data)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\tb.EventHandler.ApplyDiff(txt)\n\n\terr = b.UpdateModTime()\n\tif !b.Settings[\"fastdirty\"].(bool) {\n\t\tif len(data) > LargeFileThreshold {\n\t\t\tb.Settings[\"fastdirty\"] = true\n\t\t} else {\n\t\t\tb.calcHash(&b.origHash)\n\t\t}\n\t}\n\tb.isModified = false\n\tb.RelocateCursors()\n\treturn err\n}\n\n// RelocateCursors relocates all cursors (makes sure they are in the buffer)\nfunc (b *Buffer) RelocateCursors() {\n\tfor _, c := range b.cursors {\n\t\tc.Relocate()\n\t}\n}\n\n// DeselectCursors removes selection from all cursors\nfunc (b *Buffer) DeselectCursors() {\n\tfor _, c := range b.cursors {\n\t\tc.Deselect(true)\n\t}\n}\n\n// RuneAt returns the rune at a given location in the buffer\nfunc (b *Buffer) RuneAt(loc Loc) rune {\n\tline := b.LineBytes(loc.Y)\n\tif len(line) > 0 {\n\t\ti := 0\n\t\tfor len(line) > 0 {\n\t\t\tr, _, size := util.DecodeCharacter(line)\n\t\t\tline = line[size:]\n\n\t\t\tif i == loc.X {\n\t\t\t\treturn r\n\t\t\t}\n\n\t\t\ti++\n\t\t}\n\t}\n\treturn '\\n'\n}\n\n// WordAt returns the word around a given location in the buffer\nfunc (b *Buffer) WordAt(loc Loc) []byte {\n\tif len(b.LineBytes(loc.Y)) == 0 || !util.IsWordChar(b.RuneAt(loc)) {\n\t\treturn []byte{}\n\t}\n\n\tstart := loc\n\tend := loc.Move(1, b)\n\n\tfor start.X > 0 && util.IsWordChar(b.RuneAt(start.Move(-1, b))) {\n\t\tstart.X--\n\t}\n\n\tlineLen := util.CharacterCount(b.LineBytes(loc.Y))\n\tfor end.X < lineLen && util.IsWordChar(b.RuneAt(end)) {\n\t\tend.X++\n\t}\n\n\treturn b.Substr(start, end)\n}\n\n// Shared returns if there are other buffers with the same file as this buffer\nfunc (b *Buffer) Shared() bool {\n\tfor _, buf := range OpenBuffers {\n\t\tif buf != b && buf.SharedBuffer == b.SharedBuffer {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// Modified returns if this buffer has been modified since\n// being opened\nfunc (b *Buffer) Modified() bool {\n\treturn b.isModified\n}\n\n// Size returns the number of bytes in the current buffer\nfunc (b *Buffer) Size() int {\n\tnb := 0\n\tfor i := 0; i < b.LinesNum(); i++ {\n\t\tnb += len(b.LineBytes(i))\n\n\t\tif i != b.LinesNum()-1 {\n\t\t\tif b.Endings == FFDos {\n\t\t\t\tnb++ // carriage return\n\t\t\t}\n\t\t\tnb++ // newline\n\t\t}\n\t}\n\treturn nb\n}\n\nfunc parseDefFromFile(f config.RuntimeFile, header *highlight.Header) *highlight.Def {\n\tdata, err := f.Data()\n\tif err != nil {\n\t\tscreen.TermMessage(\"Error loading syntax file \" + f.Name() + \": \" + err.Error())\n\t\treturn nil\n\t}\n\n\tif header == nil {\n\t\theader, err = highlight.MakeHeaderYaml(data)\n\t\tif err != nil {\n\t\t\tscreen.TermMessage(\"Error parsing header for syntax file \" + f.Name() + \": \" + err.Error())\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tfile, err := highlight.ParseFile(data)\n\tif err != nil {\n\t\tscreen.TermMessage(\"Error parsing syntax file \" + f.Name() + \": \" + err.Error())\n\t\treturn nil\n\t}\n\n\tsyndef, err := highlight.ParseDef(file, header)\n\tif err != nil {\n\t\tscreen.TermMessage(\"Error parsing syntax file \" + f.Name() + \": \" + err.Error())\n\t\treturn nil\n\t}\n\n\treturn syndef\n}\n\n// findRealRuntimeSyntaxDef finds a specific syntax definition\n// in the user's custom syntax files\nfunc findRealRuntimeSyntaxDef(name string, header *highlight.Header) *highlight.Def {\n\tfor _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {\n\t\tif f.Name() == name {\n\t\t\tsyndef := parseDefFromFile(f, header)\n\t\t\tif syndef != nil {\n\t\t\t\treturn syndef\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// findRuntimeSyntaxDef finds a specific syntax definition\n// in the built-in syntax files\nfunc findRuntimeSyntaxDef(name string, header *highlight.Header) *highlight.Def {\n\tfor _, f := range config.ListRuntimeFiles(config.RTSyntax) {\n\t\tif f.Name() == name {\n\t\t\tsyndef := parseDefFromFile(f, header)\n\t\t\tif syndef != nil {\n\t\t\t\treturn syndef\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc resolveIncludes(syndef *highlight.Def) {\n\tincludes := highlight.GetIncludes(syndef)\n\tif len(includes) == 0 {\n\t\treturn\n\t}\n\n\tvar files []*highlight.File\n\tfor _, f := range config.ListRuntimeFiles(config.RTSyntax) {\n\t\tdata, err := f.Data()\n\t\tif err != nil {\n\t\t\tscreen.TermMessage(\"Error loading syntax file \" + f.Name() + \": \" + err.Error())\n\t\t\tcontinue\n\t\t}\n\n\t\theader, err := highlight.MakeHeaderYaml(data)\n\t\tif err != nil {\n\t\t\tscreen.TermMessage(\"Error parsing syntax file \" + f.Name() + \": \" + err.Error())\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, i := range includes {\n\t\t\tif header.FileType == i {\n\t\t\t\tfile, err := highlight.ParseFile(data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tscreen.TermMessage(\"Error parsing syntax file \" + f.Name() + \": \" + err.Error())\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tfiles = append(files, file)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif len(files) >= len(includes) {\n\t\t\tbreak\n\t\t}\n\t}\n\n\thighlight.ResolveIncludes(syndef, files)\n}\n\n// UpdateRules updates the syntax rules and filetype for this buffer\n// This is called when the colorscheme changes\nfunc (b *Buffer) UpdateRules() {\n\tif !b.Type.Syntax {\n\t\treturn\n\t}\n\tft := b.Settings[\"filetype\"].(string)\n\tif ft == \"off\" {\n\t\tb.ClearMatches()\n\t\tb.SyntaxDef = nil\n\t\treturn\n\t}\n\n\tb.SyntaxDef = nil\n\n\t// syntaxFileInfo is an internal helper structure\n\t// to store properties of one single syntax file\n\ttype syntaxFileInfo struct {\n\t\theader    *highlight.Header\n\t\tfileName  string\n\t\tsyntaxDef *highlight.Def\n\t}\n\n\tfnameMatches := []syntaxFileInfo{}\n\theaderMatches := []syntaxFileInfo{}\n\tsyntaxFile := \"\"\n\tfoundDef := false\n\tvar header *highlight.Header\n\t// search for the syntax file in the user's custom syntax files\n\tfor _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {\n\t\tif f.Name() == \"default\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tdata, err := f.Data()\n\t\tif err != nil {\n\t\t\tscreen.TermMessage(\"Error loading syntax file \" + f.Name() + \": \" + err.Error())\n\t\t\tcontinue\n\t\t}\n\n\t\theader, err = highlight.MakeHeaderYaml(data)\n\t\tif err != nil {\n\t\t\tscreen.TermMessage(\"Error parsing header for syntax file \" + f.Name() + \": \" + err.Error())\n\t\t\tcontinue\n\t\t}\n\n\t\tmatchedFileType := false\n\t\tmatchedFileName := false\n\t\tmatchedFileHeader := false\n\n\t\tif ft == \"unknown\" || ft == \"\" {\n\t\t\tif header.MatchFileName(b.Path) {\n\t\t\t\tmatchedFileName = true\n\t\t\t}\n\t\t\tif len(fnameMatches) == 0 && header.MatchFileHeader(b.lines[0].data) {\n\t\t\t\tmatchedFileHeader = true\n\t\t\t}\n\t\t} else if header.FileType == ft {\n\t\t\tmatchedFileType = true\n\t\t}\n\n\t\tif matchedFileType || matchedFileName || matchedFileHeader {\n\t\t\tfile, err := highlight.ParseFile(data)\n\t\t\tif err != nil {\n\t\t\t\tscreen.TermMessage(\"Error parsing syntax file \" + f.Name() + \": \" + err.Error())\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsyndef, err := highlight.ParseDef(file, header)\n\t\t\tif err != nil {\n\t\t\t\tscreen.TermMessage(\"Error parsing syntax file \" + f.Name() + \": \" + err.Error())\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif matchedFileType {\n\t\t\t\tb.SyntaxDef = syndef\n\t\t\t\tsyntaxFile = f.Name()\n\t\t\t\tfoundDef = true\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif matchedFileName {\n\t\t\t\tfnameMatches = append(fnameMatches, syntaxFileInfo{header, f.Name(), syndef})\n\t\t\t} else if matchedFileHeader {\n\t\t\t\theaderMatches = append(headerMatches, syntaxFileInfo{header, f.Name(), syndef})\n\t\t\t}\n\t\t}\n\t}\n\n\tif !foundDef {\n\t\t// search for the syntax file in the built-in syntax files\n\t\tfor _, f := range config.ListRuntimeFiles(config.RTSyntaxHeader) {\n\t\t\tdata, err := f.Data()\n\t\t\tif err != nil {\n\t\t\t\tscreen.TermMessage(\"Error loading syntax header file \" + f.Name() + \": \" + err.Error())\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\theader, err = highlight.MakeHeader(data)\n\t\t\tif err != nil {\n\t\t\t\tscreen.TermMessage(\"Error reading syntax header file\", f.Name(), err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif ft == \"unknown\" || ft == \"\" {\n\t\t\t\tif header.MatchFileName(b.Path) {\n\t\t\t\t\tfnameMatches = append(fnameMatches, syntaxFileInfo{header, f.Name(), nil})\n\t\t\t\t}\n\t\t\t\tif len(fnameMatches) == 0 && header.MatchFileHeader(b.lines[0].data) {\n\t\t\t\t\theaderMatches = append(headerMatches, syntaxFileInfo{header, f.Name(), nil})\n\t\t\t\t}\n\t\t\t} else if header.FileType == ft {\n\t\t\t\tsyntaxFile = f.Name()\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif syntaxFile == \"\" {\n\t\tmatches := fnameMatches\n\t\tif len(matches) == 0 {\n\t\t\tmatches = headerMatches\n\t\t}\n\n\t\tlength := len(matches)\n\t\tif length > 0 {\n\t\t\tsignatureMatch := false\n\t\t\tif length > 1 {\n\t\t\t\t// multiple matching syntax files found, try to resolve the ambiguity\n\t\t\t\t// using signatures\n\t\t\t\tdetectlimit := util.IntOpt(b.Settings[\"detectlimit\"])\n\t\t\t\tlineCount := len(b.lines)\n\t\t\t\tlimit := lineCount\n\t\t\t\tif detectlimit > 0 && lineCount > detectlimit {\n\t\t\t\t\tlimit = detectlimit\n\t\t\t\t}\n\n\t\t\tmatchLoop:\n\t\t\t\tfor _, m := range matches {\n\t\t\t\t\tif m.header.HasFileSignature() {\n\t\t\t\t\t\tfor i := 0; i < limit; i++ {\n\t\t\t\t\t\t\tif m.header.MatchFileSignature(b.lines[i].data) {\n\t\t\t\t\t\t\t\tsyntaxFile = m.fileName\n\t\t\t\t\t\t\t\tif m.syntaxDef != nil {\n\t\t\t\t\t\t\t\t\tb.SyntaxDef = m.syntaxDef\n\t\t\t\t\t\t\t\t\tfoundDef = true\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\theader = m.header\n\t\t\t\t\t\t\t\tsignatureMatch = true\n\t\t\t\t\t\t\t\tbreak matchLoop\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif length == 1 || !signatureMatch {\n\t\t\t\tsyntaxFile = matches[0].fileName\n\t\t\t\tif matches[0].syntaxDef != nil {\n\t\t\t\t\tb.SyntaxDef = matches[0].syntaxDef\n\t\t\t\t\tfoundDef = true\n\t\t\t\t}\n\t\t\t\theader = matches[0].header\n\t\t\t}\n\t\t}\n\t}\n\n\tif syntaxFile != \"\" && !foundDef {\n\t\t// we found a syntax file using a syntax header file\n\t\tb.SyntaxDef = findRuntimeSyntaxDef(syntaxFile, header)\n\t}\n\n\tif b.SyntaxDef != nil {\n\t\tb.Settings[\"filetype\"] = b.SyntaxDef.FileType\n\t} else {\n\t\t// search for the default file in the user's custom syntax files\n\t\tb.SyntaxDef = findRealRuntimeSyntaxDef(\"default\", nil)\n\t\tif b.SyntaxDef == nil {\n\t\t\t// search for the default file in the built-in syntax files\n\t\t\tb.SyntaxDef = findRuntimeSyntaxDef(\"default\", nil)\n\t\t}\n\t}\n\n\tif b.SyntaxDef != nil {\n\t\tresolveIncludes(b.SyntaxDef)\n\t}\n\n\tif b.SyntaxDef != nil {\n\t\tb.Highlighter = highlight.NewHighlighter(b.SyntaxDef)\n\t\tif b.Settings[\"syntax\"].(bool) {\n\t\t\tgo func() {\n\t\t\t\tb.Highlighter.HighlightStates(b)\n\t\t\t\tb.Highlighter.HighlightMatches(b, 0, b.End().Y)\n\t\t\t\tscreen.Redraw()\n\t\t\t}()\n\t\t}\n\t}\n}\n\n// ClearMatches clears all of the syntax highlighting for the buffer\nfunc (b *Buffer) ClearMatches() {\n\tfor i := range b.lines {\n\t\tb.SetMatch(i, nil)\n\t\tb.SetState(i, nil)\n\t}\n}\n\n// IndentString returns this buffer's indent method (a tabstop or n spaces\n// depending on the settings)\nfunc (b *Buffer) IndentString(tabsize int) string {\n\tif b.Settings[\"tabstospaces\"].(bool) {\n\t\treturn util.Spaces(tabsize)\n\t}\n\treturn \"\\t\"\n}\n\n// SetCursors resets this buffer's cursors to a new list\nfunc (b *Buffer) SetCursors(c []*Cursor) {\n\tb.cursors = c\n\tb.EventHandler.cursors = b.cursors\n\tb.EventHandler.active = b.curCursor\n}\n\n// AddCursor adds a new cursor to the list\nfunc (b *Buffer) AddCursor(c *Cursor) {\n\tb.cursors = append(b.cursors, c)\n\tb.EventHandler.cursors = b.cursors\n\tb.EventHandler.active = b.curCursor\n\tb.UpdateCursors()\n}\n\n// SetCurCursor sets the current cursor\nfunc (b *Buffer) SetCurCursor(n int) {\n\tb.curCursor = n\n}\n\n// GetActiveCursor returns the main cursor in this buffer\nfunc (b *Buffer) GetActiveCursor() *Cursor {\n\treturn b.cursors[b.curCursor]\n}\n\n// GetCursor returns the nth cursor\nfunc (b *Buffer) GetCursor(n int) *Cursor {\n\treturn b.cursors[n]\n}\n\n// GetCursors returns the list of cursors in this buffer\nfunc (b *Buffer) GetCursors() []*Cursor {\n\treturn b.cursors\n}\n\n// NumCursors returns the number of cursors\nfunc (b *Buffer) NumCursors() int {\n\treturn len(b.cursors)\n}\n\n// MergeCursors merges any cursors that are at the same position\n// into one cursor\nfunc (b *Buffer) MergeCursors() {\n\tvar cursors []*Cursor\n\tfor i := 0; i < len(b.cursors); i++ {\n\t\tc1 := b.cursors[i]\n\t\tif c1 != nil {\n\t\t\tfor j := 0; j < len(b.cursors); j++ {\n\t\t\t\tc2 := b.cursors[j]\n\t\t\t\tif c2 != nil && i != j && c1.Loc == c2.Loc {\n\t\t\t\t\tb.cursors[j] = nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tcursors = append(cursors, c1)\n\t\t}\n\t}\n\n\tb.cursors = cursors\n\n\tfor i := range b.cursors {\n\t\tb.cursors[i].Num = i\n\t}\n\n\tif b.curCursor >= len(b.cursors) {\n\t\tb.curCursor = len(b.cursors) - 1\n\t}\n\tb.EventHandler.cursors = b.cursors\n\tb.EventHandler.active = b.curCursor\n}\n\n// UpdateCursors updates all the cursors indices\nfunc (b *Buffer) UpdateCursors() {\n\tb.EventHandler.cursors = b.cursors\n\tb.EventHandler.active = b.curCursor\n\tfor i, c := range b.cursors {\n\t\tc.Num = i\n\t}\n}\n\nfunc (b *Buffer) RemoveCursor(i int) {\n\tcopy(b.cursors[i:], b.cursors[i+1:])\n\tb.cursors[len(b.cursors)-1] = nil\n\tb.cursors = b.cursors[:len(b.cursors)-1]\n\tb.curCursor = util.Clamp(b.curCursor, 0, len(b.cursors)-1)\n\tb.UpdateCursors()\n}\n\n// ClearCursors removes all extra cursors\nfunc (b *Buffer) ClearCursors() {\n\tfor i := 1; i < len(b.cursors); i++ {\n\t\tb.cursors[i] = nil\n\t}\n\tb.cursors = b.cursors[:1]\n\tb.UpdateCursors()\n\tb.curCursor = 0\n\tb.GetActiveCursor().Deselect(true)\n}\n\n// MoveLinesUp moves the range of lines up one row\nfunc (b *Buffer) MoveLinesUp(start int, end int) {\n\tif start < 1 || start >= end || end > len(b.lines) {\n\t\treturn\n\t}\n\tl := string(b.LineBytes(start - 1))\n\tif end == len(b.lines) {\n\t\tb.insert(\n\t\t\tLoc{\n\t\t\t\tutil.CharacterCount(b.lines[end-1].data),\n\t\t\t\tend - 1,\n\t\t\t},\n\t\t\t[]byte{'\\n'},\n\t\t)\n\t}\n\tb.Insert(\n\t\tLoc{0, end},\n\t\tl+\"\\n\",\n\t)\n\tb.Remove(\n\t\tLoc{0, start - 1},\n\t\tLoc{0, start},\n\t)\n}\n\n// MoveLinesDown moves the range of lines down one row\nfunc (b *Buffer) MoveLinesDown(start int, end int) {\n\tif start < 0 || start >= end || end >= len(b.lines) {\n\t\treturn\n\t}\n\tl := string(b.LineBytes(end))\n\tb.Insert(\n\t\tLoc{0, start},\n\t\tl+\"\\n\",\n\t)\n\tend++\n\tb.Remove(\n\t\tLoc{0, end},\n\t\tLoc{0, end + 1},\n\t)\n}\n\nvar BracePairs = [][2]rune{\n\t{'(', ')'},\n\t{'{', '}'},\n\t{'[', ']'},\n}\n\nfunc (b *Buffer) findMatchingBrace(braceType [2]rune, start Loc, char rune) (Loc, bool) {\n\tvar i int\n\tif char == braceType[0] {\n\t\tfor y := start.Y; y < b.LinesNum(); y++ {\n\t\t\tl := []rune(string(b.LineBytes(y)))\n\t\t\txInit := 0\n\t\t\tif y == start.Y {\n\t\t\t\txInit = start.X\n\t\t\t}\n\t\t\tfor x := xInit; x < len(l); x++ {\n\t\t\t\tr := l[x]\n\t\t\t\tif r == braceType[0] {\n\t\t\t\t\ti++\n\t\t\t\t} else if r == braceType[1] {\n\t\t\t\t\ti--\n\t\t\t\t\tif i == 0 {\n\t\t\t\t\t\treturn Loc{x, y}, true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else if char == braceType[1] {\n\t\tfor y := start.Y; y >= 0; y-- {\n\t\t\tl := []rune(string(b.lines[y].data))\n\t\t\txInit := len(l) - 1\n\t\t\tif y == start.Y {\n\t\t\t\txInit = start.X\n\t\t\t}\n\t\t\tfor x := xInit; x >= 0; x-- {\n\t\t\t\tr := l[x]\n\t\t\t\tif r == braceType[1] {\n\t\t\t\t\ti++\n\t\t\t\t} else if r == braceType[0] {\n\t\t\t\t\ti--\n\t\t\t\t\tif i == 0 {\n\t\t\t\t\t\treturn Loc{x, y}, true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn start, false\n}\n\n// If there is a brace character (for example '{' or ']') at the given start location,\n// FindMatchingBrace returns the location of the matching brace for it (for example '}'\n// or '['). The second returned value is true if there was no matching brace found\n// for given starting location but it was found for the location one character left\n// of it. The third returned value is true if the matching brace was found at all.\nfunc (b *Buffer) FindMatchingBrace(start Loc) (Loc, bool, bool) {\n\t// TODO: maybe can be more efficient with utf8 package\n\tcurLine := []rune(string(b.LineBytes(start.Y)))\n\n\t// first try to find matching brace for the given location (it has higher priority)\n\tif start.X >= 0 && start.X < len(curLine) {\n\t\tstartChar := curLine[start.X]\n\n\t\tfor _, bp := range BracePairs {\n\t\t\tif startChar == bp[0] || startChar == bp[1] {\n\t\t\t\tmb, found := b.findMatchingBrace(bp, start, startChar)\n\t\t\t\tif found {\n\t\t\t\t\treturn mb, false, true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif b.Settings[\"matchbraceleft\"].(bool) {\n\t\t// failed to find matching brace for the given location, so try to find matching\n\t\t// brace for the location one character left of it\n\t\tif start.X-1 >= 0 && start.X-1 < len(curLine) {\n\t\t\tleftChar := curLine[start.X-1]\n\t\t\tleft := Loc{start.X - 1, start.Y}\n\n\t\t\tfor _, bp := range BracePairs {\n\t\t\t\tif leftChar == bp[0] || leftChar == bp[1] {\n\t\t\t\t\tmb, found := b.findMatchingBrace(bp, left, leftChar)\n\t\t\t\t\tif found {\n\t\t\t\t\t\treturn mb, true, true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn start, false, false\n}\n\n// Retab changes all tabs to spaces or vice versa\nfunc (b *Buffer) Retab() {\n\ttoSpaces := b.Settings[\"tabstospaces\"].(bool)\n\ttabsize := util.IntOpt(b.Settings[\"tabsize\"])\n\n\tfor i := 0; i < b.LinesNum(); i++ {\n\t\tl := b.LineBytes(i)\n\n\t\tws := util.GetLeadingWhitespace(l)\n\t\tif len(ws) != 0 {\n\t\t\tif toSpaces {\n\t\t\t\tws = bytes.ReplaceAll(ws, []byte{'\\t'}, bytes.Repeat([]byte{' '}, tabsize))\n\t\t\t} else {\n\t\t\t\tws = bytes.ReplaceAll(ws, bytes.Repeat([]byte{' '}, tabsize), []byte{'\\t'})\n\t\t\t}\n\t\t}\n\n\t\tl = bytes.TrimLeft(l, \" \\t\")\n\n\t\tb.Lock()\n\t\tb.lines[i].data = append(ws, l...)\n\t\tb.Unlock()\n\n\t\tb.MarkModified(i, i)\n\t}\n\n\tb.setModified()\n}\n\n// ParseCursorLocation turns a cursor location like 10:5 (LINE:COL)\n// into a loc\nfunc ParseCursorLocation(cursorPositions []string) (Loc, error) {\n\tstartpos := Loc{0, 0}\n\tvar err error\n\n\t// if no positions are available exit early\n\tif cursorPositions == nil {\n\t\treturn startpos, errors.New(\"No cursor positions were provided.\")\n\t}\n\n\tstartpos.Y, err = strconv.Atoi(cursorPositions[0])\n\tstartpos.Y--\n\tif err == nil {\n\t\tif len(cursorPositions) > 1 {\n\t\t\tstartpos.X, err = strconv.Atoi(cursorPositions[1])\n\t\t\tif startpos.X > 0 {\n\t\t\t\tstartpos.X--\n\t\t\t}\n\t\t}\n\t}\n\n\treturn startpos, err\n}\n\n// Line returns the string representation of the given line number\nfunc (b *Buffer) Line(i int) string {\n\treturn string(b.LineBytes(i))\n}\n\nfunc (b *Buffer) Write(bytes []byte) (n int, err error) {\n\tb.EventHandler.InsertBytes(b.End(), bytes)\n\treturn len(bytes), nil\n}\n\nfunc (b *Buffer) updateDiff(synchronous bool) {\n\tb.diffLock.Lock()\n\tdefer b.diffLock.Unlock()\n\n\tb.diff = make(map[int]DiffStatus)\n\n\tif b.diffBase == nil {\n\t\treturn\n\t}\n\n\tdiffer := dmp.New()\n\n\tif !synchronous {\n\t\tb.Lock()\n\t}\n\tbytes := b.Bytes()\n\tif !synchronous {\n\t\tb.Unlock()\n\t}\n\n\tbaseRunes, bufferRunes, _ := differ.DiffLinesToRunes(string(b.diffBase), string(bytes))\n\tdiffs := differ.DiffMainRunes(baseRunes, bufferRunes, false)\n\tlineN := 0\n\n\tfor _, diff := range diffs {\n\t\tlineCount := len([]rune(diff.Text))\n\n\t\tswitch diff.Type {\n\t\tcase dmp.DiffEqual:\n\t\t\tlineN += lineCount\n\t\tcase dmp.DiffInsert:\n\t\t\tvar status DiffStatus\n\t\t\tif b.diff[lineN] == DSDeletedAbove {\n\t\t\t\tstatus = DSModified\n\t\t\t} else {\n\t\t\t\tstatus = DSAdded\n\t\t\t}\n\t\t\tfor i := 0; i < lineCount; i++ {\n\t\t\t\tb.diff[lineN] = status\n\t\t\t\tlineN++\n\t\t\t}\n\t\tcase dmp.DiffDelete:\n\t\t\tb.diff[lineN] = DSDeletedAbove\n\t\t}\n\t}\n}\n\n// UpdateDiff computes the diff between the diff base and the buffer content.\n// The update may be performed synchronously or asynchronously.\n// If an asynchronous update is already pending when UpdateDiff is called,\n// UpdateDiff does not schedule another update.\nfunc (b *Buffer) UpdateDiff() {\n\tif b.updateDiffTimer != nil {\n\t\treturn\n\t}\n\n\tlineCount := b.LinesNum()\n\tif b.diffBaseLineCount > lineCount {\n\t\tlineCount = b.diffBaseLineCount\n\t}\n\n\tif lineCount < 1000 {\n\t\tb.updateDiff(true)\n\t} else if lineCount < 30000 {\n\t\tb.updateDiffTimer = time.AfterFunc(500*time.Millisecond, func() {\n\t\t\tb.updateDiffTimer = nil\n\t\t\tb.updateDiff(false)\n\t\t\tscreen.Redraw()\n\t\t})\n\t} else {\n\t\t// Don't compute diffs for very large files\n\t\tb.diffLock.Lock()\n\t\tb.diff = make(map[int]DiffStatus)\n\t\tb.diffLock.Unlock()\n\t}\n}\n\n// SetDiffBase sets the text that is used as the base for diffing the buffer content\nfunc (b *Buffer) SetDiffBase(diffBase []byte) {\n\tb.diffBase = diffBase\n\tif diffBase == nil {\n\t\tb.diffBaseLineCount = 0\n\t} else {\n\t\tb.diffBaseLineCount = strings.Count(string(diffBase), \"\\n\")\n\t}\n\tb.UpdateDiff()\n}\n\n// DiffStatus returns the diff status for a line in the buffer\nfunc (b *Buffer) DiffStatus(lineN int) DiffStatus {\n\tb.diffLock.RLock()\n\tdefer b.diffLock.RUnlock()\n\t// Note that the zero value for DiffStatus is equal to DSUnchanged\n\treturn b.diff[lineN]\n}\n\n// FindNextDiffLine returns the line number of the next block of diffs.\n// If `startLine` is already in a block of diffs, lines in that block are skipped.\nfunc (b *Buffer) FindNextDiffLine(startLine int, forward bool) (int, error) {\n\tif b.diff == nil {\n\t\treturn 0, errors.New(\"no diff data\")\n\t}\n\tstartStatus, ok := b.diff[startLine]\n\tif !ok {\n\t\tstartStatus = DSUnchanged\n\t}\n\tcurLine := startLine\n\tfor {\n\t\tcurStatus, ok := b.diff[curLine]\n\t\tif !ok {\n\t\t\tcurStatus = DSUnchanged\n\t\t}\n\t\tif curLine < 0 || curLine > b.LinesNum() {\n\t\t\treturn 0, errors.New(\"no next diff hunk\")\n\t\t}\n\t\tif curStatus != startStatus {\n\t\t\tif startStatus != DSUnchanged && curStatus == DSUnchanged {\n\t\t\t\t// Skip over the block of unchanged text\n\t\t\t\tstartStatus = DSUnchanged\n\t\t\t} else {\n\t\t\t\treturn curLine, nil\n\t\t\t}\n\t\t}\n\t\tif forward {\n\t\t\tcurLine++\n\t\t} else {\n\t\t\tcurLine--\n\t\t}\n\t}\n}\n\n// SearchMatch returns true if the given location is within a match of the last search.\n// It is used for search highlighting\nfunc (b *Buffer) SearchMatch(pos Loc) bool {\n\treturn b.LineArray.SearchMatch(b, pos)\n}\n\n// WriteLog writes a string to the log buffer\nfunc WriteLog(s string) {\n\tLogBuf.EventHandler.Insert(LogBuf.End(), s)\n}\n\n// GetLogBuf returns the log buffer\nfunc GetLogBuf() *Buffer {\n\treturn LogBuf\n}\n"
  },
  {
    "path": "internal/buffer/buffer_generated_test.go",
    "content": "// This file is generated from VSCode model tests by the testgen tool.\n// DO NOT EDIT THIS FILE BY HAND; your changes will be overwritten!\n\npackage buffer\n\nimport \"testing\"\n\nfunc TestAuto1(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"ioe\",\n\t\t\t\"\",\n\t\t\t\"yjct\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{1, 0},\n\t\t\t\tend:   Loc{1, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"b\",\n\t\t\t\t\t\"r\",\n\t\t\t\t\t\"fq\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{3, 0},\n\t\t\t\tend:   Loc{0, 1},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"ib\",\n\t\t\t\"r\",\n\t\t\t\"fqoe\",\n\t\t\t\"\",\n\t\t\t\"yjct\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t},\n\t)\n}\n\nfunc TestAuto2(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"f\",\n\t\t\t\"littnhskrq\",\n\t\t\t\"utxvsizqnk\",\n\t\t\t\"lslqz\",\n\t\t\t\"jxn\",\n\t\t\t\"gmm\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{1, 0},\n\t\t\t\tend:   Loc{1, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"o\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{3, 1},\n\t\t\t\tend:   Loc{3, 1},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"zaq\",\n\t\t\t\t\t\"avb\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{4, 1},\n\t\t\t\tend:   Loc{1, 5},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"jlr\",\n\t\t\t\t\t\"zl\",\n\t\t\t\t\t\"j\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"f\",\n\t\t\t\"o\",\n\t\t\t\"litzaq\",\n\t\t\t\"avbtjlr\",\n\t\t\t\"zl\",\n\t\t\t\"jmm\",\n\t\t},\n\t)\n}\n\nfunc TestAuto3(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"ofw\",\n\t\t\t\"qsxmziuvzw\",\n\t\t\t\"rp\",\n\t\t\t\"qsnymek\",\n\t\t\t\"elth\",\n\t\t\t\"wmgzbwudxz\",\n\t\t\t\"iwsdkndh\",\n\t\t\t\"bujlbwb\",\n\t\t\t\"asuouxfv\",\n\t\t\t\"xuccnb\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{2, 3},\n\t\t\t\tend:   Loc{2, 3},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"ofw\",\n\t\t\t\"qsxmziuvzw\",\n\t\t\t\"rp\",\n\t\t\t\"qsnymek\",\n\t\t\t\"elth\",\n\t\t\t\"wmgzbwudxz\",\n\t\t\t\"iwsdkndh\",\n\t\t\t\"bujlbwb\",\n\t\t\t\"asuouxfv\",\n\t\t\t\"xuccnb\",\n\t\t},\n\t)\n}\n\nfunc TestAuto4(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"fefymj\",\n\t\t\t\"qum\",\n\t\t\t\"vmiwxxaiqq\",\n\t\t\t\"dz\",\n\t\t\t\"lnqdgorosf\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{2, 0},\n\t\t\t\tend:   Loc{4, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"hp\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{6, 0},\n\t\t\t\tend:   Loc{0, 1},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"kcg\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"mpx\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{1, 1},\n\t\t\t\tend:   Loc{1, 1},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"aw\",\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{1, 1},\n\t\t\t\tend:   Loc{1, 1},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"vqr\",\n\t\t\t\t\t\"mo\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{1, 3},\n\t\t\t\tend:   Loc{2, 4},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"xyc\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"fehpmjkcg\",\n\t\t\t\"\",\n\t\t\t\"mpxq\",\n\t\t\t\"aw\",\n\t\t\t\"vqr\",\n\t\t\t\"moum\",\n\t\t\t\"vmiwxxaiqq\",\n\t\t\t\"dxycqdgorosf\",\n\t\t},\n\t)\n}\n\nfunc TestBug19872UndoIsFunky(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"something\",\n\t\t\t\" A\",\n\t\t\t\"\",\n\t\t\t\" B\",\n\t\t\t\"something else\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 1},\n\t\t\t\tend:   Loc{1, 1},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{0, 2},\n\t\t\t\tend:   Loc{1, 3},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"something\",\n\t\t\t\"A\",\n\t\t\t\"B\",\n\t\t\t\"something else\",\n\t\t},\n\t)\n}\n\nfunc TestBug19872UndoIsFunky_2(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"something\",\n\t\t\t\"A\",\n\t\t\t\"B\",\n\t\t\t\"something else\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 1},\n\t\t\t\tend:   Loc{0, 1},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\" \",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{0, 2},\n\t\t\t\tend:   Loc{0, 2},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t\t\" \",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"something\",\n\t\t\t\" A\",\n\t\t\t\"\",\n\t\t\t\" B\",\n\t\t\t\"something else\",\n\t\t},\n\t)\n}\n\nfunc TestInsertEmptyText(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"My First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{0, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"My First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t)\n}\n\nfunc TestLastOpIsNoOp(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"My First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{1, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{0, 3},\n\t\t\t\tend:   Loc{0, 3},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"y First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t)\n}\n\nfunc TestInsertTextWithoutNewline1(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"My First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{0, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"foo \",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"foo My First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t)\n}\n\nfunc TestInsertTextWithoutNewline2(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"My First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{2, 0},\n\t\t\t\tend:   Loc{2, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\" foo\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"My foo First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t)\n}\n\nfunc TestInsertOneNewline(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"My First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{3, 0},\n\t\t\t\tend:   Loc{3, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"My \",\n\t\t\t\"First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t)\n}\n\nfunc TestInsertTextWithOneNewline(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"My First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{2, 0},\n\t\t\t\tend:   Loc{2, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\" new line\",\n\t\t\t\t\t\"No longer\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"My new line\",\n\t\t\t\"No longer First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t)\n}\n\nfunc TestInsertTextWithTwoNewlines(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"My First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{2, 0},\n\t\t\t\tend:   Loc{2, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\" new line\",\n\t\t\t\t\t\"One more line in the middle\",\n\t\t\t\t\t\"No longer\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"My new line\",\n\t\t\t\"One more line in the middle\",\n\t\t\t\"No longer First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t)\n}\n\nfunc TestInsertTextWithManyNewlines(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"My First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{2, 0},\n\t\t\t\tend:   Loc{2, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"My\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\t\" First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t)\n}\n\nfunc TestInsertMultipleNewlines(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"My First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{2, 0},\n\t\t\t\tend:   Loc{2, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{14, 2},\n\t\t\t\tend:   Loc{14, 2},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"a\",\n\t\t\t\t\t\"b\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"My\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t\t\" First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Linea\",\n\t\t\t\"b\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t)\n}\n\nfunc TestDeleteEmptyText(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"My First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{0, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"My First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t)\n}\n\nfunc TestDeleteTextFromOneLine(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"My First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{1, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"y First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t)\n}\n\nfunc TestDeleteTextFromOneLine2(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"My First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{2, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"a\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"a First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t)\n}\n\nfunc TestDeleteAllTextFromALine(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"My First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{13, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t)\n}\n\nfunc TestDeleteTextFromTwoLines(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"My First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{3, 0},\n\t\t\t\tend:   Loc{5, 1},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"My Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t)\n}\n\nfunc TestDeleteTextFromManyLines(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"My First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{3, 0},\n\t\t\t\tend:   Loc{4, 2},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"My Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t)\n}\n\nfunc TestDeleteEverything(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"My First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"1\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{1, 4},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"\",\n\t\t},\n\t)\n}\n\nfunc TestTwoUnrelatedEdits(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"My First Line\",\n\t\t\t\"\\t\\tMy Second Line\",\n\t\t\t\"    Third Line\",\n\t\t\t\"\",\n\t\t\t\"123\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 1},\n\t\t\t\tend:   Loc{2, 1},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\\t\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{0, 2},\n\t\t\t\tend:   Loc{4, 2},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"My First Line\",\n\t\t\t\"\\tMy Second Line\",\n\t\t\t\"Third Line\",\n\t\t\t\"\",\n\t\t\t\"123\",\n\t\t},\n\t)\n}\n\nfunc TestTwoEditsOnOneLine(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"\\t\\tfirst\\t    \",\n\t\t\t\"\\t\\tsecond line\",\n\t\t\t\"\\tthird line\",\n\t\t\t\"fourth line\",\n\t\t\t\"\\t\\t<!@#fifth#@!>\\t\\t\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{2, 4},\n\t\t\t\tend:   Loc{6, 4},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{11, 4},\n\t\t\t\tend:   Loc{15, 4},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"\\t\\tfirst\\t    \",\n\t\t\t\"\\t\\tsecond line\",\n\t\t\t\"\\tthird line\",\n\t\t\t\"fourth line\",\n\t\t\t\"\\t\\tfifth\\t\\t\",\n\t\t},\n\t)\n}\n\nfunc TestManyEdits(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"{\\\"x\\\" : 1}\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{1, 0},\n\t\t\t\tend:   Loc{1, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\\n  \",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{4, 0},\n\t\t\t\tend:   Loc{5, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{8, 0},\n\t\t\t\tend:   Loc{8, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\\n\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"{\",\n\t\t\t\"  \\\"x\\\": 1\",\n\t\t\t\"}\",\n\t\t},\n\t)\n}\n\nfunc TestManyEditsReversed(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"{\",\n\t\t\t\"  \\\"x\\\": 1\",\n\t\t\t\"}\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{1, 0},\n\t\t\t\tend:   Loc{2, 1},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{5, 1},\n\t\t\t\tend:   Loc{5, 1},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\" \",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{8, 1},\n\t\t\t\tend:   Loc{0, 2},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"{\\\"x\\\" : 1}\",\n\t\t},\n\t)\n}\n\nfunc TestReplacingNewlines1(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"{\",\n\t\t\t\"\\\"a\\\": true,\",\n\t\t\t\"\",\n\t\t\t\"\\\"b\\\": true\",\n\t\t\t\"}\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{1, 0},\n\t\t\t\tend:   Loc{0, 1},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"\\t\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{10, 1},\n\t\t\t\tend:   Loc{0, 3},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"\\t\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"{\",\n\t\t\t\"\\t\\\"a\\\": true,\",\n\t\t\t\"\\t\\\"b\\\": true\",\n\t\t\t\"}\",\n\t\t},\n\t)\n}\n\nfunc TestReplacingNewlines2(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"some text\",\n\t\t\t\"some more text\",\n\t\t\t\"now comes an empty line\",\n\t\t\t\"\",\n\t\t\t\"after empty line\",\n\t\t\t\"and the last line\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{4, 0},\n\t\t\t\tend:   Loc{0, 2},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\" text\",\n\t\t\t\t\t\"some more text\",\n\t\t\t\t\t\"some more text\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{1, 2},\n\t\t\t\tend:   Loc{0, 3},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"o more lines\",\n\t\t\t\t\t\"asd\",\n\t\t\t\t\t\"asd\",\n\t\t\t\t\t\"asd\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{0, 4},\n\t\t\t\tend:   Loc{5, 4},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"zzzzzzzz\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{10, 4},\n\t\t\t\tend:   Loc{15, 5},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"1\",\n\t\t\t\t\t\"2\",\n\t\t\t\t\t\"3\",\n\t\t\t\t\t\"4\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"some text\",\n\t\t\t\"some more text\",\n\t\t\t\"some more textno more lines\",\n\t\t\t\"asd\",\n\t\t\t\"asd\",\n\t\t\t\"asd\",\n\t\t\t\"zzzzzzzz empt1\",\n\t\t\t\"2\",\n\t\t\t\"3\",\n\t\t\t\"4ne\",\n\t\t},\n\t)\n}\n\nfunc TestAdvanced1(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\" {       \\\"d\\\": [\",\n\t\t\t\"             null\",\n\t\t\t\"        ] /*comment*/\",\n\t\t\t\"        ,\\\"e\\\": /*comment*/ [null] }\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{1, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{2, 0},\n\t\t\t\tend:   Loc{9, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"  \",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{15, 0},\n\t\t\t\tend:   Loc{13, 1},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"    \",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{17, 1},\n\t\t\t\tend:   Loc{8, 2},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"  \",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{21, 2},\n\t\t\t\tend:   Loc{8, 3},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{9, 3},\n\t\t\t\tend:   Loc{9, 3},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"  \",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{27, 3},\n\t\t\t\tend:   Loc{27, 3},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"    \",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{31, 3},\n\t\t\t\tend:   Loc{31, 3},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"  \",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{32, 3},\n\t\t\t\tend:   Loc{33, 3},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"{\",\n\t\t\t\"  \\\"d\\\": [\",\n\t\t\t\"    null\",\n\t\t\t\"  ] /*comment*/,\",\n\t\t\t\"  \\\"e\\\": /*comment*/ [\",\n\t\t\t\"    null\",\n\t\t\t\"  ]\",\n\t\t\t\"}\",\n\t\t},\n\t)\n}\n\nfunc TestAdvancedSimplified(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"   abc\",\n\t\t\t\" ,def\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{3, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{6, 0},\n\t\t\t\tend:   Loc{1, 1},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{2, 1},\n\t\t\t\tend:   Loc{2, 1},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"abc,\",\n\t\t\t\"def\",\n\t\t},\n\t)\n}\n\nfunc TestIssue144(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"package caddy\",\n\t\t\t\"\",\n\t\t\t\"func main() {\",\n\t\t\t\"\\tfmt.Println(\\\"Hello World! :)\\\")\",\n\t\t\t\"}\",\n\t\t\t\"\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{0, 5},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"package caddy\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"import \\\"fmt\\\"\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"func main() {\",\n\t\t\t\t\t\"\\tfmt.Println(\\\"Hello World! :)\\\")\",\n\t\t\t\t\t\"}\",\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"package caddy\",\n\t\t\t\"\",\n\t\t\t\"import \\\"fmt\\\"\",\n\t\t\t\"\",\n\t\t\t\"func main() {\",\n\t\t\t\"\\tfmt.Println(\\\"Hello World! :)\\\")\",\n\t\t\t\"}\",\n\t\t\t\"\",\n\t\t},\n\t)\n}\n\nfunc TestIssue2586ReplacingSelectedEndOfLineWithNewlineLocksUpTheDocument(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"something\",\n\t\t\t\"interesting\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{9, 0},\n\t\t\t\tend:   Loc{0, 1},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"something\",\n\t\t\t\"interesting\",\n\t\t},\n\t)\n}\n\nfunc TestIssue3980(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"class A {\",\n\t\t\t\"    someProperty = false;\",\n\t\t\t\"    someMethod() {\",\n\t\t\t\"    this.someMethod();\",\n\t\t\t\"    }\",\n\t\t\t\"}\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{7, 0},\n\t\t\t\tend:   Loc{8, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{16, 2},\n\t\t\t\tend:   Loc{17, 2},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{17, 2},\n\t\t\t\tend:   Loc{17, 2},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"    \",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{4, 3},\n\t\t\t\tend:   Loc{4, 3},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"    \",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"class A\",\n\t\t\t\"{\",\n\t\t\t\"    someProperty = false;\",\n\t\t\t\"    someMethod()\",\n\t\t\t\"    {\",\n\t\t\t\"        this.someMethod();\",\n\t\t\t\"    }\",\n\t\t\t\"}\",\n\t\t},\n\t)\n}\n\nfunc TestTouchingEditsTwoInsertsAtTheSamePosition(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{0, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"a\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{0, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"b\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"abhello world\",\n\t\t},\n\t)\n}\n\nfunc TestTouchingEditsInsertAndReplaceTouching(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{0, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"b\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{2, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"ab\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"babllo world\",\n\t\t},\n\t)\n}\n\nfunc TestTouchingEditsTwoTouchingReplaces(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{1, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"H\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{1, 0},\n\t\t\t\tend:   Loc{2, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"E\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"HEllo world\",\n\t\t},\n\t)\n}\n\nfunc TestTouchingEditsTwoTouchingDeletes(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{1, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{1, 0},\n\t\t\t\tend:   Loc{2, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"llo world\",\n\t\t},\n\t)\n}\n\nfunc TestTouchingEditsInsertAndReplace(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{0, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"H\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{2, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"e\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"Hello world\",\n\t\t},\n\t)\n}\n\nfunc TestTouchingEditsReplaceAndInsert(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{2, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"H\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tstart: Loc{2, 0},\n\t\t\t\tend:   Loc{2, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"e\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"Hello world\",\n\t\t},\n\t)\n}\n\nfunc TestSingleDelete1(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{1, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"ello world\",\n\t\t},\n\t)\n}\n\nfunc TestSingleDelete2(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"helloworld\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{2, 0},\n\t\t\t\tend:   Loc{7, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"herld\",\n\t\t},\n\t)\n}\n\nfunc TestSingleDelete3(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{5, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\" world\",\n\t\t},\n\t)\n}\n\nfunc TestSingleDelete4(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{1, 0},\n\t\t\t\tend:   Loc{6, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"hworld\",\n\t\t},\n\t)\n}\n\nfunc TestSingleDelete5(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{11, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"\",\n\t\t},\n\t)\n}\n\nfunc TestMultiDelete6(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t\t\"hello world\",\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{5, 0},\n\t\t\t\tend:   Loc{5, 2},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t)\n}\n\nfunc TestMultiDelete7(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t\t\"hello world\",\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{11, 0},\n\t\t\t\tend:   Loc{11, 2},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t)\n}\n\nfunc TestMultiDelete8(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t\t\"hello world\",\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{0, 2},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t)\n}\n\nfunc TestMultiDelete9(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t\t\"hello world\",\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{11, 0},\n\t\t\t\tend:   Loc{0, 2},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"hello worldhello world\",\n\t\t},\n\t)\n}\n\nfunc TestSingleInsert1(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{0, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"xx\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"xxhello world\",\n\t\t},\n\t)\n}\n\nfunc TestSingleInsert2(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{1, 0},\n\t\t\t\tend:   Loc{1, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"xx\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"hxxello world\",\n\t\t},\n\t)\n}\n\nfunc TestSingleInsert3(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{5, 0},\n\t\t\t\tend:   Loc{5, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"xx\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"helloxx world\",\n\t\t},\n\t)\n}\n\nfunc TestSingleInsert4(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{6, 0},\n\t\t\t\tend:   Loc{6, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"xx\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"hello xxworld\",\n\t\t},\n\t)\n}\n\nfunc TestSingleInsert5(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{11, 0},\n\t\t\t\tend:   Loc{11, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"xx\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"hello worldxx\",\n\t\t},\n\t)\n}\n\nfunc TestMultiInsert6(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{0, 0},\n\t\t\t\tend:   Loc{0, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\\n\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"\",\n\t\t\t\"hello world\",\n\t\t},\n\t)\n}\n\nfunc TestMultiInsert7(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{11, 0},\n\t\t\t\tend:   Loc{11, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\\n\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t\t\"\",\n\t\t},\n\t)\n}\n\nfunc TestMultiInsert8(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{6, 0},\n\t\t\t\tend:   Loc{6, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"\\n\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"hello \",\n\t\t\t\"world\",\n\t\t},\n\t)\n}\n\nfunc TestMultiInsert9(t *testing.T) {\n\tcheck(\n\t\tt,\n\t\t[]string{\n\t\t\t\"hello world\",\n\t\t\t\"hello world\",\n\t\t},\n\t\t[]operation{\n\t\t\t{\n\t\t\t\tstart: Loc{6, 0},\n\t\t\t\tend:   Loc{6, 0},\n\t\t\t\ttext: []string{\n\t\t\t\t\t\"xx\\nyy\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t[]string{\n\t\t\t\"hello xx\",\n\t\t\t\"yyworld\",\n\t\t\t\"hello world\",\n\t\t},\n\t)\n}\n"
  },
  {
    "path": "internal/buffer/buffer_test.go",
    "content": "package buffer\n\nimport (\n\t\"math/rand\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\tulua \"github.com/micro-editor/micro/v2/internal/lua\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n\t\"github.com/stretchr/testify/assert\"\n\tlua \"github.com/yuin/gopher-lua\"\n)\n\ntype operation struct {\n\tstart Loc\n\tend   Loc\n\ttext  []string\n}\n\nfunc init() {\n\tulua.L = lua.NewState()\n\tconfig.InitRuntimeFiles(false)\n\tconfig.InitGlobalSettings()\n\tconfig.GlobalSettings[\"backup\"] = false\n\tconfig.GlobalSettings[\"fastdirty\"] = true\n}\n\nfunc check(t *testing.T, before []string, operations []operation, after []string) {\n\tassert := assert.New(t)\n\n\tb := NewBufferFromString(strings.Join(before, \"\\n\"), \"\", BTDefault)\n\n\tassert.NotEqual(\"\", b.GetName())\n\tassert.Equal(false, b.ExternallyModified())\n\tassert.Equal(false, b.Modified())\n\tassert.Equal(1, b.NumCursors())\n\n\tcheckText := func(lines []string) {\n\t\tassert.Equal([]byte(strings.Join(lines, \"\\n\")), b.Bytes())\n\t\tassert.Equal(len(lines), b.LinesNum())\n\t\tfor i, s := range lines {\n\t\t\tassert.Equal(s, b.Line(i))\n\t\t\tassert.Equal([]byte(s), b.LineBytes(i))\n\t\t}\n\t}\n\n\tcheckText(before)\n\n\tvar cursors []*Cursor\n\n\tfor _, op := range operations {\n\t\tcursor := NewCursor(b, op.start)\n\t\tcursor.SetSelectionStart(op.start)\n\t\tcursor.SetSelectionEnd(op.end)\n\t\tb.AddCursor(cursor)\n\t\tcursors = append(cursors, cursor)\n\t}\n\n\tassert.Equal(1+len(operations), b.NumCursors())\n\n\tfor i, op := range operations {\n\t\tcursor := cursors[i]\n\t\tb.SetCurCursor(cursor.Num)\n\t\tcursor.DeleteSelection()\n\t\tb.Insert(cursor.Loc, strings.Join(op.text, \"\\n\"))\n\t}\n\n\tcheckText(after)\n\n\t// must have exactly two events per operation (delete and insert)\n\tfor range operations {\n\t\tb.UndoOneEvent()\n\t\tb.UndoOneEvent()\n\t}\n\n\tcheckText(before)\n\n\tfor i, op := range operations {\n\t\tcursor := cursors[i]\n\t\tif op.start == op.end {\n\t\t\tassert.Equal(op.start, cursor.Loc)\n\t\t} else {\n\t\t\tassert.Equal(op.start, cursor.CurSelection[0])\n\t\t\tassert.Equal(op.end, cursor.CurSelection[1])\n\t\t}\n\t}\n\n\tfor range operations {\n\t\tb.RedoOneEvent()\n\t\tb.RedoOneEvent()\n\t}\n\n\tcheckText(after)\n\n\tb.Close()\n}\n\nconst maxLineLength = 200\n\nvar alphabet = []rune(\" abcdeäم📚\")\n\nfunc randomString(length int) string {\n\trunes := make([]rune, length)\n\tfor i := range runes {\n\t\trunes[i] = alphabet[rand.Intn(len(alphabet))]\n\t}\n\treturn string(runes)\n}\n\nfunc randomText(nLines int) string {\n\tlines := make([]string, nLines)\n\tfor i := range lines {\n\t\tlines[i] = randomString(rand.Intn(maxLineLength + 1))\n\t}\n\treturn strings.Join(lines, \"\\n\")\n}\n\nfunc benchCreateAndClose(testingB *testing.B, nLines int) {\n\trand.Seed(int64(nLines))\n\n\ttext := randomText(nLines)\n\n\ttestingB.ResetTimer()\n\n\tfor i := 0; i < testingB.N; i++ {\n\t\tb := NewBufferFromString(text, \"\", BTDefault)\n\t\tb.Close()\n\t}\n}\n\nfunc benchRead(testingB *testing.B, nLines int) {\n\trand.Seed(int64(nLines))\n\n\tb := NewBufferFromString(randomText(nLines), \"\", BTDefault)\n\n\ttestingB.ResetTimer()\n\n\tfor i := 0; i < testingB.N; i++ {\n\t\tb.Bytes()\n\t\tfor j := 0; j < b.LinesNum(); j++ {\n\t\t\tb.Line(j)\n\t\t\tb.LineBytes(j)\n\t\t}\n\t}\n\n\ttestingB.StopTimer()\n\n\tb.Close()\n}\n\nfunc benchEdit(testingB *testing.B, nLines, nCursors int) {\n\trand.Seed(int64(nLines + nCursors))\n\n\tb := NewBufferFromString(randomText(nLines), \"\", BTDefault)\n\n\tregionSize := nLines / nCursors\n\n\toperations := make([]operation, nCursors)\n\tfor i := range operations {\n\t\tstartLine := (i * regionSize) + rand.Intn(regionSize-5)\n\t\tstartColumn := rand.Intn(util.CharacterCountInString(b.Line(startLine)) + 1)\n\t\tendLine := startLine + 1 + rand.Intn(5)\n\t\tendColumn := rand.Intn(util.CharacterCountInString(b.Line(endLine)) + 1)\n\n\t\toperations[i] = operation{\n\t\t\tstart: Loc{startColumn, startLine},\n\t\t\tend:   Loc{endColumn, endLine},\n\t\t\ttext:  []string{randomText(2 + rand.Intn(4))},\n\t\t}\n\t}\n\n\ttestingB.ResetTimer()\n\n\tfor i := 0; i < testingB.N; i++ {\n\t\tb.SetCursors([]*Cursor{})\n\n\t\tvar cursors []*Cursor\n\n\t\tfor _, op := range operations {\n\t\t\tcursor := NewCursor(b, op.start)\n\t\t\tcursor.SetSelectionStart(op.start)\n\t\t\tcursor.SetSelectionEnd(op.end)\n\t\t\tb.AddCursor(cursor)\n\t\t\tcursors = append(cursors, cursor)\n\t\t}\n\n\t\tfor j, op := range operations {\n\t\t\tcursor := cursors[j]\n\t\t\tb.SetCurCursor(cursor.Num)\n\t\t\tcursor.DeleteSelection()\n\t\t\tb.Insert(cursor.Loc, op.text[0])\n\t\t}\n\n\t\tfor b.UndoStack.Peek() != nil {\n\t\t\tb.UndoOneEvent()\n\t\t}\n\t}\n\n\ttestingB.StopTimer()\n\n\tb.Close()\n}\n\nfunc BenchmarkCreateAndClose10Lines(b *testing.B) {\n\tbenchCreateAndClose(b, 10)\n}\n\nfunc BenchmarkCreateAndClose100Lines(b *testing.B) {\n\tbenchCreateAndClose(b, 100)\n}\n\nfunc BenchmarkCreateAndClose1000Lines(b *testing.B) {\n\tbenchCreateAndClose(b, 1000)\n}\n\nfunc BenchmarkCreateAndClose10000Lines(b *testing.B) {\n\tbenchCreateAndClose(b, 10000)\n}\n\nfunc BenchmarkCreateAndClose100000Lines(b *testing.B) {\n\tbenchCreateAndClose(b, 100000)\n}\n\nfunc BenchmarkCreateAndClose1000000Lines(b *testing.B) {\n\tbenchCreateAndClose(b, 1000000)\n}\n\nfunc BenchmarkRead10Lines(b *testing.B) {\n\tbenchRead(b, 10)\n}\n\nfunc BenchmarkRead100Lines(b *testing.B) {\n\tbenchRead(b, 100)\n}\n\nfunc BenchmarkRead1000Lines(b *testing.B) {\n\tbenchRead(b, 1000)\n}\n\nfunc BenchmarkRead10000Lines(b *testing.B) {\n\tbenchRead(b, 10000)\n}\n\nfunc BenchmarkRead100000Lines(b *testing.B) {\n\tbenchRead(b, 100000)\n}\n\nfunc BenchmarkRead1000000Lines(b *testing.B) {\n\tbenchRead(b, 1000000)\n}\n\nfunc BenchmarkEdit10Lines1Cursor(b *testing.B) {\n\tbenchEdit(b, 10, 1)\n}\n\nfunc BenchmarkEdit100Lines1Cursor(b *testing.B) {\n\tbenchEdit(b, 100, 1)\n}\n\nfunc BenchmarkEdit100Lines10Cursors(b *testing.B) {\n\tbenchEdit(b, 100, 10)\n}\n\nfunc BenchmarkEdit1000Lines1Cursor(b *testing.B) {\n\tbenchEdit(b, 1000, 1)\n}\n\nfunc BenchmarkEdit1000Lines10Cursors(b *testing.B) {\n\tbenchEdit(b, 1000, 10)\n}\n\nfunc BenchmarkEdit1000Lines100Cursors(b *testing.B) {\n\tbenchEdit(b, 1000, 100)\n}\n\nfunc BenchmarkEdit10000Lines1Cursor(b *testing.B) {\n\tbenchEdit(b, 10000, 1)\n}\n\nfunc BenchmarkEdit10000Lines10Cursors(b *testing.B) {\n\tbenchEdit(b, 10000, 10)\n}\n\nfunc BenchmarkEdit10000Lines100Cursors(b *testing.B) {\n\tbenchEdit(b, 10000, 100)\n}\n\nfunc BenchmarkEdit10000Lines1000Cursors(b *testing.B) {\n\tbenchEdit(b, 10000, 1000)\n}\n\nfunc BenchmarkEdit100000Lines1Cursor(b *testing.B) {\n\tbenchEdit(b, 100000, 1)\n}\n\nfunc BenchmarkEdit100000Lines10Cursors(b *testing.B) {\n\tbenchEdit(b, 100000, 10)\n}\n\nfunc BenchmarkEdit100000Lines100Cursors(b *testing.B) {\n\tbenchEdit(b, 100000, 100)\n}\n\nfunc BenchmarkEdit100000Lines1000Cursors(b *testing.B) {\n\tbenchEdit(b, 100000, 1000)\n}\n\nfunc BenchmarkEdit1000000Lines1Cursor(b *testing.B) {\n\tbenchEdit(b, 1000000, 1)\n}\n\nfunc BenchmarkEdit1000000Lines10Cursors(b *testing.B) {\n\tbenchEdit(b, 1000000, 10)\n}\n\nfunc BenchmarkEdit1000000Lines100Cursors(b *testing.B) {\n\tbenchEdit(b, 1000000, 100)\n}\n\nfunc BenchmarkEdit1000000Lines1000Cursors(b *testing.B) {\n\tbenchEdit(b, 1000000, 1000)\n}\n"
  },
  {
    "path": "internal/buffer/cursor.go",
    "content": "package buffer\n\nimport (\n\t\"github.com/micro-editor/micro/v2/internal/clipboard\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n)\n\n// InBounds returns whether the given location is a valid character position in the given buffer\nfunc InBounds(pos Loc, buf *Buffer) bool {\n\tif pos.Y < 0 || pos.Y >= len(buf.lines) || pos.X < 0 || pos.X > util.CharacterCount(buf.LineBytes(pos.Y)) {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// The Cursor struct stores the location of the cursor in the buffer\n// as well as the selection\ntype Cursor struct {\n\tbuf *Buffer\n\tLoc\n\n\t// Last visual x position of the cursor. Used in cursor up/down movements\n\t// for remembering the original x position when moving to a line that is\n\t// shorter than current x position.\n\tLastVisualX int\n\t// Similar to LastVisualX but takes softwrapping into account, i.e. last\n\t// visual x position in a visual (wrapped) line on the screen, which may be\n\t// different from the line in the buffer.\n\tLastWrappedVisualX int\n\n\t// The current selection as a range of character numbers (inclusive)\n\tCurSelection [2]Loc\n\t// The original selection as a range of character numbers\n\t// This is used for line and word selection where it is necessary\n\t// to know what the original selection was\n\tOrigSelection [2]Loc\n\n\t// The line number where a new trailing whitespace has been added\n\t// or -1 if there is no new trailing whitespace at this cursor.\n\t// This is used for checking if a trailing whitespace should be highlighted\n\tNewTrailingWsY int\n\n\t// Which cursor index is this (for multiple cursors)\n\tNum int\n}\n\nfunc NewCursor(b *Buffer, l Loc) *Cursor {\n\tc := &Cursor{\n\t\tbuf: b,\n\t\tLoc: l,\n\n\t\tNewTrailingWsY: -1,\n\t}\n\tc.StoreVisualX()\n\treturn c\n}\n\nfunc (c *Cursor) SetBuf(b *Buffer) {\n\tc.buf = b\n}\n\nfunc (c *Cursor) Buf() *Buffer {\n\treturn c.buf\n}\n\n// Goto puts the cursor at the given cursor's location and gives\n// the current cursor its selection too\nfunc (c *Cursor) Goto(b Cursor) {\n\tc.X, c.Y = b.X, b.Y\n\tc.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection\n\tc.StoreVisualX()\n}\n\n// GotoLoc puts the cursor at the given cursor's location and gives\n// the current cursor its selection too\nfunc (c *Cursor) GotoLoc(l Loc) {\n\tc.X, c.Y = l.X, l.Y\n\tc.StoreVisualX()\n}\n\n// GetVisualX returns the x value of the cursor in visual spaces\nfunc (c *Cursor) GetVisualX(wrap bool) int {\n\tif wrap && c.buf.GetVisualX != nil {\n\t\treturn c.buf.GetVisualX(c.Loc)\n\t}\n\n\tif c.X <= 0 {\n\t\tc.X = 0\n\t\treturn 0\n\t}\n\n\tbytes := c.buf.LineBytes(c.Y)\n\ttabsize := int(c.buf.Settings[\"tabsize\"].(float64))\n\n\treturn util.StringWidth(bytes, c.X, tabsize)\n}\n\n// GetCharPosInLine gets the char position of a visual x y\n// coordinate (this is necessary because tabs are 1 char but\n// 4 visual spaces)\nfunc (c *Cursor) GetCharPosInLine(b []byte, visualPos int) int {\n\ttabsize := int(c.buf.Settings[\"tabsize\"].(float64))\n\treturn util.GetCharPosInLine(b, visualPos, tabsize)\n}\n\n// Start moves the cursor to the start of the line it is on\nfunc (c *Cursor) Start() {\n\tc.X = 0\n\tc.StoreVisualX()\n}\n\n// StartOfText moves the cursor to the first non-whitespace rune of\n// the line it is on\nfunc (c *Cursor) StartOfText() {\n\tc.Start()\n\tfor util.IsWhitespace(c.RuneUnder(c.X)) {\n\t\tif c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {\n\t\t\tbreak\n\t\t}\n\t\tc.Right()\n\t}\n}\n\n// IsStartOfText returns whether the cursor is at the first\n// non-whitespace rune of the line it is on\nfunc (c *Cursor) IsStartOfText() bool {\n\tx := 0\n\tfor util.IsWhitespace(c.RuneUnder(x)) {\n\t\tif x == util.CharacterCount(c.buf.LineBytes(c.Y)) {\n\t\t\tbreak\n\t\t}\n\t\tx++\n\t}\n\treturn c.X == x\n}\n\n// End moves the cursor to the end of the line it is on\nfunc (c *Cursor) End() {\n\tc.X = util.CharacterCount(c.buf.LineBytes(c.Y))\n\tc.StoreVisualX()\n}\n\n// CopySelection copies the user's selection to either \"primary\"\n// or \"clipboard\"\nfunc (c *Cursor) CopySelection(target clipboard.Register) {\n\tif c.HasSelection() {\n\t\tif target != clipboard.PrimaryReg || c.buf.Settings[\"useprimary\"].(bool) {\n\t\t\tclipboard.WriteMulti(string(c.GetSelection()), target, c.Num, c.buf.NumCursors())\n\t\t}\n\t}\n}\n\n// ResetSelection resets the user's selection\nfunc (c *Cursor) ResetSelection() {\n\tc.CurSelection[0] = c.buf.Start()\n\tc.CurSelection[1] = c.buf.Start()\n}\n\n// SetSelectionStart sets the start of the selection\nfunc (c *Cursor) SetSelectionStart(pos Loc) {\n\tc.CurSelection[0] = pos\n}\n\n// SetSelectionEnd sets the end of the selection\nfunc (c *Cursor) SetSelectionEnd(pos Loc) {\n\tc.CurSelection[1] = pos\n}\n\n// HasSelection returns whether or not the user has selected anything\nfunc (c *Cursor) HasSelection() bool {\n\treturn c.CurSelection[0] != c.CurSelection[1]\n}\n\n// DeleteSelection deletes the currently selected text\nfunc (c *Cursor) DeleteSelection() {\n\tif c.CurSelection[0].GreaterThan(c.CurSelection[1]) {\n\t\tc.buf.Remove(c.CurSelection[1], c.CurSelection[0])\n\t\tc.Loc = c.CurSelection[1]\n\t} else if !c.HasSelection() {\n\t\treturn\n\t} else {\n\t\tc.buf.Remove(c.CurSelection[0], c.CurSelection[1])\n\t\tc.Loc = c.CurSelection[0]\n\t}\n}\n\n// Deselect closes the cursor's current selection\n// Start indicates whether the cursor should be placed\n// at the start or end of the selection\nfunc (c *Cursor) Deselect(start bool) {\n\tif c.HasSelection() {\n\t\tif start {\n\t\t\tc.Loc = c.CurSelection[0]\n\t\t} else {\n\t\t\tc.Loc = c.CurSelection[1]\n\t\t}\n\t\tc.ResetSelection()\n\t\tc.StoreVisualX()\n\t}\n}\n\n// GetSelection returns the cursor's selection\nfunc (c *Cursor) GetSelection() []byte {\n\tif InBounds(c.CurSelection[0], c.buf) && InBounds(c.CurSelection[1], c.buf) {\n\t\tif c.CurSelection[0].GreaterThan(c.CurSelection[1]) {\n\t\t\treturn c.buf.Substr(c.CurSelection[1], c.CurSelection[0])\n\t\t}\n\t\treturn c.buf.Substr(c.CurSelection[0], c.CurSelection[1])\n\t}\n\treturn []byte{}\n}\n\n// SelectLine selects the current line\nfunc (c *Cursor) SelectLine() {\n\tc.Start()\n\tc.SetSelectionStart(c.Loc)\n\tc.End()\n\tif len(c.buf.lines)-1 > c.Y {\n\t\tc.SetSelectionEnd(c.Loc.Move(1, c.buf))\n\t} else {\n\t\tc.SetSelectionEnd(c.Loc)\n\t}\n\n\tc.OrigSelection = c.CurSelection\n}\n\n// AddLineToSelection adds the current line to the selection\nfunc (c *Cursor) AddLineToSelection() {\n\tif c.Loc.LessThan(c.OrigSelection[0]) {\n\t\tc.Start()\n\t\tc.SetSelectionStart(c.Loc)\n\t\tc.SetSelectionEnd(c.OrigSelection[1])\n\t}\n\tif c.Loc.GreaterThan(c.OrigSelection[1]) {\n\t\tc.End()\n\t\tc.SetSelectionEnd(c.Loc.Move(1, c.buf))\n\t\tc.SetSelectionStart(c.OrigSelection[0])\n\t}\n\n\tif c.Loc.LessThan(c.OrigSelection[1]) && c.Loc.GreaterThan(c.OrigSelection[0]) {\n\t\tc.CurSelection = c.OrigSelection\n\t}\n}\n\n// UpN moves the cursor up N lines (if possible)\nfunc (c *Cursor) UpN(amount int) {\n\tproposedY := c.Y - amount\n\tif proposedY < 0 {\n\t\tproposedY = 0\n\t} else if proposedY >= len(c.buf.lines) {\n\t\tproposedY = len(c.buf.lines) - 1\n\t}\n\n\tbytes := c.buf.LineBytes(proposedY)\n\tc.X = c.GetCharPosInLine(bytes, c.LastVisualX)\n\n\tif c.X > util.CharacterCount(bytes) || (amount < 0 && proposedY == c.Y) {\n\t\tc.X = util.CharacterCount(bytes)\n\t\tc.StoreVisualX()\n\t}\n\n\tif c.X < 0 || (amount > 0 && proposedY == c.Y) {\n\t\tc.X = 0\n\t\tc.StoreVisualX()\n\t}\n\n\tc.Y = proposedY\n}\n\n// DownN moves the cursor down N lines (if possible)\nfunc (c *Cursor) DownN(amount int) {\n\tc.UpN(-amount)\n}\n\n// Up moves the cursor up one line (if possible)\nfunc (c *Cursor) Up() {\n\tc.UpN(1)\n}\n\n// Down moves the cursor down one line (if possible)\nfunc (c *Cursor) Down() {\n\tc.DownN(1)\n}\n\n// Left moves the cursor left one cell (if possible) or to\n// the previous line if it is at the beginning\nfunc (c *Cursor) Left() {\n\tif c.Loc == c.buf.Start() {\n\t\treturn\n\t}\n\tif c.X > 0 {\n\t\tc.X--\n\t} else {\n\t\tc.Up()\n\t\tc.End()\n\t}\n\tc.StoreVisualX()\n}\n\n// Right moves the cursor right one cell (if possible) or\n// to the next line if it is at the end\nfunc (c *Cursor) Right() {\n\tif c.Loc == c.buf.End() {\n\t\treturn\n\t}\n\tif c.X < util.CharacterCount(c.buf.LineBytes(c.Y)) {\n\t\tc.X++\n\t} else {\n\t\tc.Down()\n\t\tc.Start()\n\t}\n\tc.StoreVisualX()\n}\n\n// Relocate makes sure that the cursor is inside the bounds\n// of the buffer If it isn't, it moves it to be within the\n// buffer's lines\nfunc (c *Cursor) Relocate() {\n\tif c.Y < 0 {\n\t\tc.Y = 0\n\t} else if c.Y >= len(c.buf.lines) {\n\t\tc.Y = len(c.buf.lines) - 1\n\t}\n\n\tif c.X < 0 {\n\t\tc.X = 0\n\t} else if c.X > util.CharacterCount(c.buf.LineBytes(c.Y)) {\n\t\tc.X = util.CharacterCount(c.buf.LineBytes(c.Y))\n\t}\n}\n\n// SelectWord selects the word the cursor is currently on\nfunc (c *Cursor) SelectWord() {\n\tif len(c.buf.LineBytes(c.Y)) == 0 {\n\t\treturn\n\t}\n\n\tif !util.IsWordChar(c.RuneUnder(c.X)) {\n\t\tc.SetSelectionStart(c.Loc)\n\t\tc.SetSelectionEnd(c.Loc.Move(1, c.buf))\n\t\tc.OrigSelection = c.CurSelection\n\t\treturn\n\t}\n\n\tforward, backward := c.X, c.X\n\n\tfor backward > 0 && util.IsWordChar(c.RuneUnder(backward-1)) {\n\t\tbackward--\n\t}\n\n\tc.SetSelectionStart(Loc{backward, c.Y})\n\tc.OrigSelection[0] = c.CurSelection[0]\n\n\tlineLen := util.CharacterCount(c.buf.LineBytes(c.Y)) - 1\n\tfor forward < lineLen && util.IsWordChar(c.RuneUnder(forward+1)) {\n\t\tforward++\n\t}\n\n\tc.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf))\n\tc.OrigSelection[1] = c.CurSelection[1]\n\tc.Loc = c.CurSelection[1]\n}\n\n// AddWordToSelection adds the word the cursor is currently on\n// to the selection\nfunc (c *Cursor) AddWordToSelection() {\n\tif c.Loc.GreaterThan(c.OrigSelection[0]) && c.Loc.LessThan(c.OrigSelection[1]) {\n\t\tc.CurSelection = c.OrigSelection\n\t\treturn\n\t}\n\n\tif c.Loc.LessThan(c.OrigSelection[0]) {\n\t\tbackward := c.X\n\n\t\tfor backward > 0 && util.IsWordChar(c.RuneUnder(backward-1)) {\n\t\t\tbackward--\n\t\t}\n\n\t\tc.SetSelectionStart(Loc{backward, c.Y})\n\t\tc.SetSelectionEnd(c.OrigSelection[1])\n\t}\n\n\tif c.Loc.GreaterThan(c.OrigSelection[1]) {\n\t\tforward := c.X\n\n\t\tlineLen := util.CharacterCount(c.buf.LineBytes(c.Y)) - 1\n\t\tfor forward < lineLen && util.IsWordChar(c.RuneUnder(forward+1)) {\n\t\t\tforward++\n\t\t}\n\n\t\tc.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf))\n\t\tc.SetSelectionStart(c.OrigSelection[0])\n\t}\n\n\tc.Loc = c.CurSelection[1]\n}\n\n// SelectTo selects from the current cursor location to the given\n// location\nfunc (c *Cursor) SelectTo(loc Loc) {\n\tif loc.GreaterThan(c.OrigSelection[0]) {\n\t\tc.SetSelectionStart(c.OrigSelection[0])\n\t\tc.SetSelectionEnd(loc)\n\t} else {\n\t\tc.SetSelectionStart(loc)\n\t\tc.SetSelectionEnd(c.OrigSelection[0])\n\t}\n}\n\n// WordRight moves the cursor one word to the right\nfunc (c *Cursor) WordRight() {\n\tif c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {\n\t\tc.Right()\n\t\treturn\n\t}\n\tfor util.IsWhitespace(c.RuneUnder(c.X)) {\n\t\tif c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {\n\t\t\treturn\n\t\t}\n\t\tc.Right()\n\t}\n\tif util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) &&\n\t\tutil.IsNonWordChar(c.RuneUnder(c.X+1)) {\n\t\tfor util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) {\n\t\t\tif c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tc.Right()\n\t\t}\n\t\treturn\n\t}\n\tc.Right()\n\tfor util.IsWordChar(c.RuneUnder(c.X)) {\n\t\tif c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {\n\t\t\treturn\n\t\t}\n\t\tc.Right()\n\t}\n}\n\n// WordLeft moves the cursor one word to the left\nfunc (c *Cursor) WordLeft() {\n\tif c.X == 0 {\n\t\tc.Left()\n\t\treturn\n\t}\n\tc.Left()\n\tfor util.IsWhitespace(c.RuneUnder(c.X)) {\n\t\tif c.X == 0 {\n\t\t\treturn\n\t\t}\n\t\tc.Left()\n\t}\n\tif util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) &&\n\t\tutil.IsNonWordChar(c.RuneUnder(c.X-1)) {\n\t\tfor util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) {\n\t\t\tif c.X == 0 {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tc.Left()\n\t\t}\n\t\tc.Right()\n\t\treturn\n\t}\n\tc.Left()\n\tfor util.IsWordChar(c.RuneUnder(c.X)) {\n\t\tif c.X == 0 {\n\t\t\treturn\n\t\t}\n\t\tc.Left()\n\t}\n\tc.Right()\n}\n\n// SubWordRight moves the cursor one sub-word to the right\nfunc (c *Cursor) SubWordRight() {\n\tif c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {\n\t\tc.Right()\n\t\treturn\n\t}\n\tif util.IsWhitespace(c.RuneUnder(c.X)) {\n\t\tfor util.IsWhitespace(c.RuneUnder(c.X)) {\n\t\t\tif c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tc.Right()\n\t\t}\n\t\treturn\n\t}\n\tif util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) {\n\t\tfor util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) {\n\t\t\tif c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tc.Right()\n\t\t}\n\t\treturn\n\t}\n\tif util.IsSubwordDelimiter(c.RuneUnder(c.X)) {\n\t\tfor util.IsSubwordDelimiter(c.RuneUnder(c.X)) {\n\t\t\tif c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tc.Right()\n\t\t}\n\t\tif util.IsWhitespace(c.RuneUnder(c.X)) {\n\t\t\treturn\n\t\t}\n\t}\n\tif c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {\n\t\treturn\n\t}\n\tif util.IsUpperLetter(c.RuneUnder(c.X)) &&\n\t\tutil.IsUpperLetter(c.RuneUnder(c.X+1)) {\n\t\tfor util.IsUpperAlphanumeric(c.RuneUnder(c.X)) {\n\t\t\tif c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tc.Right()\n\t\t}\n\t\tif util.IsLowerAlphanumeric(c.RuneUnder(c.X)) {\n\t\t\tc.Left()\n\t\t}\n\t} else {\n\t\tc.Right()\n\t\tfor util.IsLowerAlphanumeric(c.RuneUnder(c.X)) {\n\t\t\tif c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tc.Right()\n\t\t}\n\t}\n}\n\n// SubWordLeft moves the cursor one sub-word to the left\nfunc (c *Cursor) SubWordLeft() {\n\tif c.X == 0 {\n\t\tc.Left()\n\t\treturn\n\t}\n\tc.Left()\n\tif util.IsWhitespace(c.RuneUnder(c.X)) {\n\t\tfor util.IsWhitespace(c.RuneUnder(c.X)) {\n\t\t\tif c.X == 0 {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tc.Left()\n\t\t}\n\t\tc.Right()\n\t\treturn\n\t}\n\tif util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) {\n\t\tfor util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) {\n\t\t\tif c.X == 0 {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tc.Left()\n\t\t}\n\t\tc.Right()\n\t\treturn\n\t}\n\tif util.IsSubwordDelimiter(c.RuneUnder(c.X)) {\n\t\tfor util.IsSubwordDelimiter(c.RuneUnder(c.X)) {\n\t\t\tif c.X == 0 {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tc.Left()\n\t\t}\n\t\tif util.IsWhitespace(c.RuneUnder(c.X)) {\n\t\t\tc.Right()\n\t\t\treturn\n\t\t}\n\t}\n\tif c.X == 0 {\n\t\treturn\n\t}\n\tif util.IsUpperLetter(c.RuneUnder(c.X)) &&\n\t\tutil.IsUpperLetter(c.RuneUnder(c.X-1)) {\n\t\tfor util.IsUpperAlphanumeric(c.RuneUnder(c.X)) {\n\t\t\tif c.X == 0 {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tc.Left()\n\t\t}\n\t\tif !util.IsUpperAlphanumeric(c.RuneUnder(c.X)) {\n\t\t\tc.Right()\n\t\t}\n\t} else {\n\t\tfor util.IsLowerAlphanumeric(c.RuneUnder(c.X)) {\n\t\t\tif c.X == 0 {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tc.Left()\n\t\t}\n\t\tif !util.IsAlphanumeric(c.RuneUnder(c.X)) {\n\t\t\tc.Right()\n\t\t}\n\t}\n}\n\n// RuneUnder returns the rune under the given x position\nfunc (c *Cursor) RuneUnder(x int) rune {\n\tline := c.buf.LineBytes(c.Y)\n\tif len(line) == 0 || x >= util.CharacterCount(line) {\n\t\treturn '\\n'\n\t} else if x < 0 {\n\t\tx = 0\n\t}\n\ti := 0\n\tfor len(line) > 0 {\n\t\tr, _, size := util.DecodeCharacter(line)\n\t\tline = line[size:]\n\n\t\tif i == x {\n\t\t\treturn r\n\t\t}\n\n\t\ti++\n\t}\n\treturn '\\n'\n}\n\nfunc (c *Cursor) StoreVisualX() {\n\tc.LastVisualX = c.GetVisualX(false)\n\tc.LastWrappedVisualX = c.GetVisualX(true)\n}\n"
  },
  {
    "path": "internal/buffer/eventhandler.go",
    "content": "package buffer\n\nimport (\n\t\"bytes\"\n\t\"time\"\n\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\tulua \"github.com/micro-editor/micro/v2/internal/lua\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n\tdmp \"github.com/sergi/go-diff/diffmatchpatch\"\n\tluar \"layeh.com/gopher-luar\"\n)\n\nconst (\n\t// Opposite and undoing events must have opposite values\n\n\t// TextEventInsert represents an insertion event\n\tTextEventInsert = 1\n\t// TextEventRemove represents a deletion event\n\tTextEventRemove = -1\n\t// TextEventReplace represents a replace event\n\tTextEventReplace = 0\n\n\tundoThreshold = 1000 // If two events are less than n milliseconds apart, undo both of them\n)\n\n// TextEvent holds data for a manipulation on some text that can be undone\ntype TextEvent struct {\n\tC Cursor\n\n\tEventType int\n\tDeltas    []Delta\n\tTime      time.Time\n}\n\n// A Delta is a change to the buffer\ntype Delta struct {\n\tText  []byte\n\tStart Loc\n\tEnd   Loc\n}\n\n// DoTextEvent runs a text event\nfunc (eh *EventHandler) DoTextEvent(t *TextEvent, useUndo bool) {\n\toldl := eh.buf.LinesNum()\n\n\tif useUndo {\n\t\teh.Execute(t)\n\t} else {\n\t\tExecuteTextEvent(t, eh.buf)\n\t}\n\n\tif len(t.Deltas) != 1 {\n\t\treturn\n\t}\n\n\ttext := t.Deltas[0].Text\n\tstart := t.Deltas[0].Start\n\tlastnl := -1\n\tvar endX int\n\tvar textX int\n\tif t.EventType == TextEventInsert {\n\t\tlinecount := eh.buf.LinesNum() - oldl\n\t\ttextcount := util.CharacterCount(text)\n\t\tlastnl = bytes.LastIndex(text, []byte{'\\n'})\n\t\tif lastnl >= 0 {\n\t\t\tendX = util.CharacterCount(text[lastnl+1:])\n\t\t\ttextX = endX\n\t\t} else {\n\t\t\tendX = start.X + textcount\n\t\t\ttextX = textcount\n\t\t}\n\t\tt.Deltas[0].End = clamp(Loc{endX, start.Y + linecount}, eh.buf.LineArray)\n\t}\n\tend := t.Deltas[0].End\n\n\tfor _, c := range eh.cursors {\n\t\tmove := func(loc Loc) Loc {\n\t\t\tif t.EventType == TextEventInsert {\n\t\t\t\tif start.Y != loc.Y && loc.GreaterThan(start) {\n\t\t\t\t\tloc.Y += end.Y - start.Y\n\t\t\t\t} else if loc.Y == start.Y && loc.GreaterEqual(start) {\n\t\t\t\t\tloc.Y += end.Y - start.Y\n\t\t\t\t\tif lastnl >= 0 {\n\t\t\t\t\t\tloc.X += textX - start.X\n\t\t\t\t\t} else {\n\t\t\t\t\t\tloc.X += textX\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn loc\n\t\t\t} else {\n\t\t\t\tif loc.Y != end.Y && loc.GreaterThan(end) {\n\t\t\t\t\tloc.Y -= end.Y - start.Y\n\t\t\t\t} else if loc.Y == end.Y && loc.GreaterEqual(end) {\n\t\t\t\t\tloc = loc.MoveLA(-DiffLA(start, end, eh.buf.LineArray), eh.buf.LineArray)\n\t\t\t\t}\n\t\t\t\treturn loc\n\t\t\t}\n\t\t}\n\t\tc.Loc = move(c.Loc)\n\t\tc.CurSelection[0] = move(c.CurSelection[0])\n\t\tc.CurSelection[1] = move(c.CurSelection[1])\n\t\tc.OrigSelection[0] = move(c.OrigSelection[0])\n\t\tc.OrigSelection[1] = move(c.OrigSelection[1])\n\t\tc.Relocate()\n\t\tc.StoreVisualX()\n\t}\n\n\tif useUndo {\n\t\teh.updateTrailingWs(t)\n\t}\n}\n\n// ExecuteTextEvent runs a text event\nfunc ExecuteTextEvent(t *TextEvent, buf *SharedBuffer) {\n\tif t.EventType == TextEventInsert {\n\t\tfor _, d := range t.Deltas {\n\t\t\tbuf.insert(d.Start, d.Text)\n\t\t}\n\t} else if t.EventType == TextEventRemove {\n\t\tfor i, d := range t.Deltas {\n\t\t\tt.Deltas[i].Text = buf.remove(d.Start, d.End)\n\t\t}\n\t} else if t.EventType == TextEventReplace {\n\t\tfor i, d := range t.Deltas {\n\t\t\tt.Deltas[i].Text = buf.remove(d.Start, d.End)\n\t\t\tbuf.insert(d.Start, d.Text)\n\t\t\tt.Deltas[i].Start = d.Start\n\t\t\tt.Deltas[i].End = Loc{d.Start.X + util.CharacterCount(d.Text), d.Start.Y}\n\t\t}\n\t\tfor i, j := 0, len(t.Deltas)-1; i < j; i, j = i+1, j-1 {\n\t\t\tt.Deltas[i], t.Deltas[j] = t.Deltas[j], t.Deltas[i]\n\t\t}\n\t}\n}\n\n// UndoTextEvent undoes a text event\nfunc (eh *EventHandler) UndoTextEvent(t *TextEvent) {\n\tt.EventType = -t.EventType\n\teh.DoTextEvent(t, false)\n}\n\n// EventHandler executes text manipulations and allows undoing and redoing\ntype EventHandler struct {\n\tbuf       *SharedBuffer\n\tcursors   []*Cursor\n\tactive    int\n\tUndoStack *TEStack\n\tRedoStack *TEStack\n}\n\n// NewEventHandler returns a new EventHandler\nfunc NewEventHandler(buf *SharedBuffer, cursors []*Cursor) *EventHandler {\n\teh := new(EventHandler)\n\teh.UndoStack = new(TEStack)\n\teh.RedoStack = new(TEStack)\n\teh.buf = buf\n\teh.cursors = cursors\n\treturn eh\n}\n\n// ApplyDiff takes a string and runs the necessary insertion and deletion events to make\n// the buffer equal to that string\n// This means that we can transform the buffer into any string and still preserve undo/redo\n// through insert and delete events\nfunc (eh *EventHandler) ApplyDiff(new string) {\n\tdiffer := dmp.New()\n\tdiff := differ.DiffMain(string(eh.buf.Bytes()), new, false)\n\tloc := eh.buf.Start()\n\tfor _, d := range diff {\n\t\tif d.Type == dmp.DiffDelete {\n\t\t\teh.Remove(loc, loc.MoveLA(util.CharacterCountInString(d.Text), eh.buf.LineArray))\n\t\t} else {\n\t\t\tif d.Type == dmp.DiffInsert {\n\t\t\t\teh.Insert(loc, d.Text)\n\t\t\t}\n\t\t\tloc = loc.MoveLA(util.CharacterCountInString(d.Text), eh.buf.LineArray)\n\t\t}\n\t}\n}\n\n// Insert creates an insert text event and executes it\nfunc (eh *EventHandler) Insert(start Loc, textStr string) {\n\ttext := []byte(textStr)\n\teh.InsertBytes(start, text)\n}\n\n// InsertBytes creates an insert text event and executes it\nfunc (eh *EventHandler) InsertBytes(start Loc, text []byte) {\n\tif len(text) == 0 {\n\t\treturn\n\t}\n\tstart = clamp(start, eh.buf.LineArray)\n\te := &TextEvent{\n\t\tC:         *eh.cursors[eh.active],\n\t\tEventType: TextEventInsert,\n\t\tDeltas:    []Delta{{text, start, Loc{0, 0}}},\n\t\tTime:      time.Now(),\n\t}\n\teh.DoTextEvent(e, true)\n}\n\n// Remove creates a remove text event and executes it\nfunc (eh *EventHandler) Remove(start, end Loc) {\n\tif start == end {\n\t\treturn\n\t}\n\tstart = clamp(start, eh.buf.LineArray)\n\tend = clamp(end, eh.buf.LineArray)\n\te := &TextEvent{\n\t\tC:         *eh.cursors[eh.active],\n\t\tEventType: TextEventRemove,\n\t\tDeltas:    []Delta{{[]byte{}, start, end}},\n\t\tTime:      time.Now(),\n\t}\n\teh.DoTextEvent(e, true)\n}\n\n// MultipleReplace creates an multiple insertions executes them\nfunc (eh *EventHandler) MultipleReplace(deltas []Delta) {\n\te := &TextEvent{\n\t\tC:         *eh.cursors[eh.active],\n\t\tEventType: TextEventReplace,\n\t\tDeltas:    deltas,\n\t\tTime:      time.Now(),\n\t}\n\teh.Execute(e)\n}\n\n// Replace deletes from start to end and replaces it with the given string\nfunc (eh *EventHandler) Replace(start, end Loc, replace string) {\n\teh.Remove(start, end)\n\teh.Insert(start, replace)\n}\n\n// Execute a textevent and add it to the undo stack\nfunc (eh *EventHandler) Execute(t *TextEvent) {\n\tif eh.RedoStack.Len() > 0 {\n\t\teh.RedoStack = new(TEStack)\n\t}\n\teh.UndoStack.Push(t)\n\n\tb, err := config.RunPluginFnBool(nil, \"onBeforeTextEvent\", luar.New(ulua.L, eh.buf), luar.New(ulua.L, t))\n\tif err != nil {\n\t\tscreen.TermMessage(err)\n\t}\n\n\tif !b {\n\t\treturn\n\t}\n\n\tExecuteTextEvent(t, eh.buf)\n}\n\n// Undo the first event in the undo stack. Returns false if the stack is empty.\nfunc (eh *EventHandler) Undo() bool {\n\tt := eh.UndoStack.Peek()\n\tif t == nil {\n\t\treturn false\n\t}\n\n\tstartTime := t.Time.UnixNano() / int64(time.Millisecond)\n\tendTime := startTime - (startTime % undoThreshold)\n\n\tfor {\n\t\tt = eh.UndoStack.Peek()\n\t\tif t == nil {\n\t\t\tbreak\n\t\t}\n\n\t\tif t.Time.UnixNano()/int64(time.Millisecond) < endTime {\n\t\t\tbreak\n\t\t}\n\n\t\teh.UndoOneEvent()\n\t}\n\treturn true\n}\n\n// UndoOneEvent undoes one event\nfunc (eh *EventHandler) UndoOneEvent() {\n\t// This event should be undone\n\t// Pop it off the stack\n\tt := eh.UndoStack.Pop()\n\tif t == nil {\n\t\treturn\n\t}\n\t// Undo it\n\t// Modifies the text event\n\teh.UndoTextEvent(t)\n\n\t// Set the cursor in the right place\n\tif t.C.Num >= 0 && t.C.Num < len(eh.cursors) {\n\t\teh.cursors[t.C.Num].Goto(t.C)\n\t\teh.cursors[t.C.Num].NewTrailingWsY = t.C.NewTrailingWsY\n\t}\n\n\t// Push it to the redo stack\n\teh.RedoStack.Push(t)\n}\n\n// Redo the first event in the redo stack. Returns false if the stack is empty.\nfunc (eh *EventHandler) Redo() bool {\n\tt := eh.RedoStack.Peek()\n\tif t == nil {\n\t\treturn false\n\t}\n\n\tstartTime := t.Time.UnixNano() / int64(time.Millisecond)\n\tendTime := startTime - (startTime % undoThreshold) + undoThreshold\n\n\tfor {\n\t\tt = eh.RedoStack.Peek()\n\t\tif t == nil {\n\t\t\tbreak\n\t\t}\n\n\t\tif t.Time.UnixNano()/int64(time.Millisecond) > endTime {\n\t\t\tbreak\n\t\t}\n\n\t\teh.RedoOneEvent()\n\t}\n\treturn true\n}\n\n// RedoOneEvent redoes one event\nfunc (eh *EventHandler) RedoOneEvent() {\n\tt := eh.RedoStack.Pop()\n\tif t == nil {\n\t\treturn\n\t}\n\n\tif t.C.Num >= 0 && t.C.Num < len(eh.cursors) {\n\t\teh.cursors[t.C.Num].Goto(t.C)\n\t\teh.cursors[t.C.Num].NewTrailingWsY = t.C.NewTrailingWsY\n\t}\n\n\t// Modifies the text event\n\teh.UndoTextEvent(t)\n\n\teh.UndoStack.Push(t)\n}\n\n// updateTrailingWs updates the cursor's trailing whitespace status after a text event\nfunc (eh *EventHandler) updateTrailingWs(t *TextEvent) {\n\tif len(t.Deltas) != 1 {\n\t\treturn\n\t}\n\ttext := t.Deltas[0].Text\n\tstart := t.Deltas[0].Start\n\tend := t.Deltas[0].End\n\n\tc := eh.cursors[eh.active]\n\tisEol := func(loc Loc) bool {\n\t\treturn loc.X == util.CharacterCount(eh.buf.LineBytes(loc.Y))\n\t}\n\tif t.EventType == TextEventInsert && c.Loc == end && isEol(end) {\n\t\tvar addedTrailingWs bool\n\t\taddedAfterWs := false\n\t\taddedWsOnly := false\n\t\tif start.Y == end.Y {\n\t\t\taddedTrailingWs = util.HasTrailingWhitespace(text)\n\t\t\taddedWsOnly = util.IsBytesWhitespace(text)\n\t\t\taddedAfterWs = start.X > 0 && util.IsWhitespace(c.buf.RuneAt(Loc{start.X - 1, start.Y}))\n\t\t} else {\n\t\t\tlastnl := bytes.LastIndex(text, []byte{'\\n'})\n\t\t\taddedTrailingWs = util.HasTrailingWhitespace(text[lastnl+1:])\n\t\t}\n\n\t\tif addedTrailingWs && !(addedAfterWs && addedWsOnly) {\n\t\t\tc.NewTrailingWsY = c.Y\n\t\t} else if !addedTrailingWs {\n\t\t\tc.NewTrailingWsY = -1\n\t\t}\n\t} else if t.EventType == TextEventRemove && c.Loc == start && isEol(start) {\n\t\tremovedAfterWs := util.HasTrailingWhitespace(eh.buf.LineBytes(start.Y))\n\t\tvar removedWsOnly bool\n\t\tif start.Y == end.Y {\n\t\t\tremovedWsOnly = util.IsBytesWhitespace(text)\n\t\t} else {\n\t\t\tfirstnl := bytes.Index(text, []byte{'\\n'})\n\t\t\tremovedWsOnly = util.IsBytesWhitespace(text[:firstnl])\n\t\t}\n\n\t\tif removedAfterWs && !removedWsOnly {\n\t\t\tc.NewTrailingWsY = c.Y\n\t\t} else if !removedAfterWs {\n\t\t\tc.NewTrailingWsY = -1\n\t\t}\n\t} else if c.NewTrailingWsY != -1 && start.Y != end.Y && c.Loc.GreaterThan(start) &&\n\t\t((t.EventType == TextEventInsert && c.Y == c.NewTrailingWsY+(end.Y-start.Y)) ||\n\t\t\t(t.EventType == TextEventRemove && c.Y == c.NewTrailingWsY-(end.Y-start.Y))) {\n\t\t// The cursor still has its new trailingws\n\t\t// but its line number was shifted by insert or remove of lines above\n\t\tc.NewTrailingWsY = c.Y\n\t}\n}\n"
  },
  {
    "path": "internal/buffer/line_array.go",
    "content": "package buffer\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"io\"\n\t\"sync\"\n\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n\t\"github.com/micro-editor/micro/v2/pkg/highlight\"\n)\n\n// Finds the byte index of the nth rune in a byte slice\nfunc runeToByteIndex(n int, txt []byte) int {\n\tif n == 0 {\n\t\treturn 0\n\t}\n\n\tcount := 0\n\ti := 0\n\tfor len(txt) > 0 {\n\t\t_, _, size := util.DecodeCharacter(txt)\n\n\t\ttxt = txt[size:]\n\t\tcount += size\n\t\ti++\n\n\t\tif i == n {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn count\n}\n\n// A searchState contains the search match info for a single line\ntype searchState struct {\n\tsearch     string\n\tuseRegex   bool\n\tignorecase bool\n\tmatch      [][2]int\n\tdone       bool\n}\n\n// A Line contains the data in bytes as well as a highlight state, match\n// and a flag for whether the highlighting needs to be updated\ntype Line struct {\n\tdata []byte\n\n\tstate highlight.State\n\tmatch highlight.LineMatch\n\tlock  sync.Mutex\n\n\t// The search states for the line, used for highlighting of search matches,\n\t// separately from the syntax highlighting.\n\t// A map is used because the line array may be shared between multiple buffers\n\t// (multiple instances of the same file opened in different edit panes)\n\t// which have distinct searches, so in the general case there are multiple\n\t// searches per a line, one search per a Buffer containing this line.\n\tsearch map[*Buffer]*searchState\n}\n\nconst (\n\t// Line ending file formats\n\tFFAuto = 0 // Autodetect format\n\tFFUnix = 1 // LF line endings (unix style '\\n')\n\tFFDos  = 2 // CRLF line endings (dos style '\\r\\n')\n)\n\ntype FileFormat byte\n\n// A LineArray simply stores and array of lines and makes it easy to insert\n// and delete in it\ntype LineArray struct {\n\tlines    []Line\n\tEndings  FileFormat\n\tinitsize uint64\n\tlock     sync.Mutex\n}\n\n// Append efficiently appends lines together\n// It allocates an additional 10000 lines if the original estimate\n// is incorrect\nfunc Append(slice []Line, data ...Line) []Line {\n\tl := len(slice)\n\tif l+len(data) > cap(slice) { // reallocate\n\t\tnewSlice := make([]Line, (l+len(data))+10000)\n\t\tcopy(newSlice, slice)\n\t\tslice = newSlice\n\t}\n\tslice = slice[0 : l+len(data)]\n\tfor i, c := range data {\n\t\tslice[l+i] = c\n\t}\n\treturn slice\n}\n\n// NewLineArray returns a new line array from an array of bytes\nfunc NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray {\n\tla := new(LineArray)\n\n\tla.lines = make([]Line, 0, 1000)\n\tla.initsize = size\n\n\tbr := bufio.NewReader(reader)\n\tvar loaded int\n\n\tla.Endings = endings\n\n\tn := 0\n\tfor {\n\t\tdata, err := br.ReadBytes('\\n')\n\t\t// Detect the line ending by checking to see if there is a '\\r' char\n\t\t// before the '\\n'\n\t\t// Even if the file format is set to DOS, the '\\r' is removed so\n\t\t// that all lines end with '\\n'\n\t\tdlen := len(data)\n\t\tif dlen > 1 && data[dlen-2] == '\\r' {\n\t\t\tdata = append(data[:dlen-2], '\\n')\n\t\t\tif la.Endings == FFAuto {\n\t\t\t\tla.Endings = FFDos\n\t\t\t}\n\t\t\tdlen = len(data)\n\t\t} else if dlen > 0 {\n\t\t\tif la.Endings == FFAuto {\n\t\t\t\tla.Endings = FFUnix\n\t\t\t}\n\t\t}\n\n\t\t// If we are loading a large file (greater than 1000) we use the file\n\t\t// size and the length of the first 1000 lines to try to estimate\n\t\t// how many lines will need to be allocated for the rest of the file\n\t\t// We add an extra 10000 to the original estimate to be safe and give\n\t\t// plenty of room for expansion\n\t\tif n >= 1000 && loaded >= 0 {\n\t\t\ttotalLinesNum := int(float64(size) * (float64(n) / float64(loaded)))\n\t\t\tnewSlice := make([]Line, len(la.lines), totalLinesNum+10000)\n\t\t\tcopy(newSlice, la.lines)\n\t\t\tla.lines = newSlice\n\t\t\tloaded = -1\n\t\t}\n\n\t\t// Counter for the number of bytes in the first 1000 lines\n\t\tif loaded >= 0 {\n\t\t\tloaded += dlen\n\t\t}\n\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tla.lines = Append(la.lines, Line{\n\t\t\t\t\tdata:  data,\n\t\t\t\t\tstate: nil,\n\t\t\t\t\tmatch: nil,\n\t\t\t\t})\n\t\t\t}\n\t\t\t// Last line was read\n\t\t\tbreak\n\t\t} else {\n\t\t\tla.lines = Append(la.lines, Line{\n\t\t\t\tdata:  data[:dlen-1],\n\t\t\t\tstate: nil,\n\t\t\t\tmatch: nil,\n\t\t\t})\n\t\t}\n\t\tn++\n\t}\n\n\treturn la\n}\n\n// Bytes returns the string that should be written to disk when\n// the line array is saved\nfunc (la *LineArray) Bytes() []byte {\n\tb := new(bytes.Buffer)\n\t// initsize should provide a good estimate\n\tb.Grow(int(la.initsize + 4096))\n\tfor i, l := range la.lines {\n\t\tb.Write(l.data)\n\t\tif i != len(la.lines)-1 {\n\t\t\tif la.Endings == FFDos {\n\t\t\t\tb.WriteByte('\\r')\n\t\t\t}\n\t\t\tb.WriteByte('\\n')\n\t\t}\n\t}\n\treturn b.Bytes()\n}\n\n// newlineBelow adds a newline below the given line number\nfunc (la *LineArray) newlineBelow(y int) {\n\tla.lines = append(la.lines, Line{\n\t\tdata:  []byte{' '},\n\t\tstate: nil,\n\t\tmatch: nil,\n\t})\n\tcopy(la.lines[y+2:], la.lines[y+1:])\n\tla.lines[y+1] = Line{\n\t\tdata:  []byte{},\n\t\tstate: la.lines[y].state,\n\t\tmatch: nil,\n\t}\n}\n\n// Inserts a byte array at a given location\nfunc (la *LineArray) insert(pos Loc, value []byte) {\n\tla.lock.Lock()\n\tdefer la.lock.Unlock()\n\n\tx, y := runeToByteIndex(pos.X, la.lines[pos.Y].data), pos.Y\n\tfor i := 0; i < len(value); i++ {\n\t\tif value[i] == '\\n' || (value[i] == '\\r' && i < len(value)-1 && value[i+1] == '\\n') {\n\t\t\tla.split(Loc{x, y})\n\t\t\tx = 0\n\t\t\ty++\n\n\t\t\tif value[i] == '\\r' {\n\t\t\t\ti++\n\t\t\t}\n\n\t\t\tcontinue\n\t\t}\n\t\tla.insertByte(Loc{x, y}, value[i])\n\t\tx++\n\t}\n}\n\n// InsertByte inserts a byte at a given location\nfunc (la *LineArray) insertByte(pos Loc, value byte) {\n\tla.lines[pos.Y].data = append(la.lines[pos.Y].data, 0)\n\tcopy(la.lines[pos.Y].data[pos.X+1:], la.lines[pos.Y].data[pos.X:])\n\tla.lines[pos.Y].data[pos.X] = value\n}\n\n// joinLines joins the two lines a and b\nfunc (la *LineArray) joinLines(a, b int) {\n\tla.lines[a].data = append(la.lines[a].data, la.lines[b].data...)\n\tla.deleteLine(b)\n}\n\n// split splits a line at a given position\nfunc (la *LineArray) split(pos Loc) {\n\tla.newlineBelow(pos.Y)\n\tla.lines[pos.Y+1].data = append(la.lines[pos.Y+1].data, la.lines[pos.Y].data[pos.X:]...)\n\tla.lines[pos.Y+1].state = la.lines[pos.Y].state\n\tla.lines[pos.Y].state = nil\n\tla.lines[pos.Y].match = nil\n\tla.lines[pos.Y+1].match = nil\n\tla.deleteToEnd(Loc{pos.X, pos.Y})\n}\n\n// removes from start to end\nfunc (la *LineArray) remove(start, end Loc) []byte {\n\tla.lock.Lock()\n\tdefer la.lock.Unlock()\n\n\tsub := la.Substr(start, end)\n\tstartX := runeToByteIndex(start.X, la.lines[start.Y].data)\n\tendX := runeToByteIndex(end.X, la.lines[end.Y].data)\n\tif start.Y == end.Y {\n\t\tla.lines[start.Y].data = append(la.lines[start.Y].data[:startX], la.lines[start.Y].data[endX:]...)\n\t} else {\n\t\tla.deleteLines(start.Y+1, end.Y-1)\n\t\tla.deleteToEnd(Loc{startX, start.Y})\n\t\tla.deleteFromStart(Loc{endX - 1, start.Y + 1})\n\t\tla.joinLines(start.Y, start.Y+1)\n\t}\n\treturn sub\n}\n\n// deleteToEnd deletes from the end of a line to the position\nfunc (la *LineArray) deleteToEnd(pos Loc) {\n\tla.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X]\n}\n\n// deleteFromStart deletes from the start of a line to the position\nfunc (la *LineArray) deleteFromStart(pos Loc) {\n\tla.lines[pos.Y].data = la.lines[pos.Y].data[pos.X+1:]\n}\n\n// deleteLine deletes the line number\nfunc (la *LineArray) deleteLine(y int) {\n\tla.lines = la.lines[:y+copy(la.lines[y:], la.lines[y+1:])]\n}\n\nfunc (la *LineArray) deleteLines(y1, y2 int) {\n\tla.lines = la.lines[:y1+copy(la.lines[y1:], la.lines[y2+1:])]\n}\n\n// Substr returns the string representation between two locations\nfunc (la *LineArray) Substr(start, end Loc) []byte {\n\tstartX := runeToByteIndex(start.X, la.lines[start.Y].data)\n\tendX := runeToByteIndex(end.X, la.lines[end.Y].data)\n\tif start.Y == end.Y {\n\t\tsrc := la.lines[start.Y].data[startX:endX]\n\t\tdest := make([]byte, len(src))\n\t\tcopy(dest, src)\n\t\treturn dest\n\t}\n\tstr := make([]byte, 0, len(la.lines[start.Y+1].data)*(end.Y-start.Y))\n\tstr = append(str, la.lines[start.Y].data[startX:]...)\n\tstr = append(str, '\\n')\n\tfor i := start.Y + 1; i <= end.Y-1; i++ {\n\t\tstr = append(str, la.lines[i].data...)\n\t\tstr = append(str, '\\n')\n\t}\n\tstr = append(str, la.lines[end.Y].data[:endX]...)\n\treturn str\n}\n\n// LinesNum returns the number of lines in the buffer\nfunc (la *LineArray) LinesNum() int {\n\treturn len(la.lines)\n}\n\n// Start returns the start of the buffer\nfunc (la *LineArray) Start() Loc {\n\treturn Loc{0, 0}\n}\n\n// End returns the location of the last character in the buffer\nfunc (la *LineArray) End() Loc {\n\tnumlines := len(la.lines)\n\treturn Loc{util.CharacterCount(la.lines[numlines-1].data), numlines - 1}\n}\n\n// LineBytes returns line n as an array of bytes\nfunc (la *LineArray) LineBytes(lineN int) []byte {\n\tif lineN >= len(la.lines) || lineN < 0 {\n\t\treturn []byte{}\n\t}\n\treturn la.lines[lineN].data\n}\n\n// State gets the highlight state for the given line number\nfunc (la *LineArray) State(lineN int) highlight.State {\n\tla.lines[lineN].lock.Lock()\n\tdefer la.lines[lineN].lock.Unlock()\n\treturn la.lines[lineN].state\n}\n\n// SetState sets the highlight state at the given line number\nfunc (la *LineArray) SetState(lineN int, s highlight.State) {\n\tla.lines[lineN].lock.Lock()\n\tdefer la.lines[lineN].lock.Unlock()\n\tla.lines[lineN].state = s\n}\n\n// SetMatch sets the match at the given line number\nfunc (la *LineArray) SetMatch(lineN int, m highlight.LineMatch) {\n\tla.lines[lineN].lock.Lock()\n\tdefer la.lines[lineN].lock.Unlock()\n\tla.lines[lineN].match = m\n}\n\n// Match retrieves the match for the given line number\nfunc (la *LineArray) Match(lineN int) highlight.LineMatch {\n\tla.lines[lineN].lock.Lock()\n\tdefer la.lines[lineN].lock.Unlock()\n\treturn la.lines[lineN].match\n}\n\n// Locks the whole LineArray\nfunc (la *LineArray) Lock() {\n\tla.lock.Lock()\n}\n\n// Unlocks the whole LineArray\nfunc (la *LineArray) Unlock() {\n\tla.lock.Unlock()\n}\n\n// SearchMatch returns true if the location `pos` is within a match\n// of the last search for the buffer `b`.\n// It is used for efficient highlighting of search matches (separately\n// from the syntax highlighting).\n// SearchMatch searches for the matches if it is called first time\n// for the given line or if the line was modified. Otherwise the\n// previously found matches are used.\n//\n// The buffer `b` needs to be passed because the line array may be shared\n// between multiple buffers (multiple instances of the same file opened\n// in different edit panes) which have distinct searches, so SearchMatch\n// needs to know which search to match against.\nfunc (la *LineArray) SearchMatch(b *Buffer, pos Loc) bool {\n\tif b.LastSearch == \"\" {\n\t\treturn false\n\t}\n\n\tlineN := pos.Y\n\tif la.lines[lineN].search == nil {\n\t\tla.lines[lineN].search = make(map[*Buffer]*searchState)\n\t}\n\ts, ok := la.lines[lineN].search[b]\n\tif !ok {\n\t\t// Note: here is a small harmless leak: when the buffer `b` is closed,\n\t\t// `s` is not deleted from the map. It means that the buffer\n\t\t// will not be garbage-collected until the line array is garbage-collected,\n\t\t// i.e. until all the buffers sharing this file are closed.\n\t\ts = new(searchState)\n\t\tla.lines[lineN].search[b] = s\n\t}\n\tif !ok || s.search != b.LastSearch || s.useRegex != b.LastSearchRegex ||\n\t\ts.ignorecase != b.Settings[\"ignorecase\"].(bool) {\n\t\ts.search = b.LastSearch\n\t\ts.useRegex = b.LastSearchRegex\n\t\ts.ignorecase = b.Settings[\"ignorecase\"].(bool)\n\t\ts.done = false\n\t}\n\n\tif !s.done {\n\t\ts.match = nil\n\t\tstart := Loc{0, lineN}\n\t\tend := Loc{util.CharacterCount(la.lines[lineN].data), lineN}\n\t\tfor start.X < end.X {\n\t\t\tm, found, _ := b.FindNext(b.LastSearch, start, end, start, true, b.LastSearchRegex)\n\t\t\tif !found {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\ts.match = append(s.match, [2]int{m[0].X, m[1].X})\n\n\t\t\tstart.X = m[1].X\n\t\t\tif m[1].X == m[0].X {\n\t\t\t\tstart.X = m[1].X + 1\n\t\t\t}\n\t\t}\n\n\t\ts.done = true\n\t}\n\n\tfor _, m := range s.match {\n\t\tif pos.X >= m[0] && pos.X < m[1] {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// invalidateSearchMatches marks search matches for the given line as outdated.\n// It is called when the line is modified.\nfunc (la *LineArray) invalidateSearchMatches(lineN int) {\n\tif la.lines[lineN].search != nil {\n\t\tfor _, s := range la.lines[lineN].search {\n\t\t\ts.done = false\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/buffer/line_array_test.go",
    "content": "package buffer\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar unicode_txt = `An preost wes on leoden, Laȝamon was ihoten\nHe wes Leovenaðes sone -- liðe him be Drihten.\nHe wonede at Ernleȝe at æðelen are chirechen,\nUppen Sevarne staþe, sel þar him þuhte,\nOnfest Radestone, þer he bock radde.`\n\nvar la *LineArray\n\nfunc init() {\n\treader := strings.NewReader(unicode_txt)\n\tla = NewLineArray(uint64(len(unicode_txt)), FFAuto, reader)\n}\n\nfunc TestSplit(t *testing.T) {\n\tla.insert(Loc{17, 1}, []byte{'\\n'})\n\tassert.Equal(t, len(la.lines), 6)\n\tsub1 := la.Substr(Loc{0, 1}, Loc{17, 1})\n\tsub2 := la.Substr(Loc{0, 2}, Loc{30, 2})\n\n\tassert.Equal(t, []byte(\"He wes Leovenaðes\"), sub1)\n\tassert.Equal(t, []byte(\" sone -- liðe him be Drihten.\"), sub2)\n}\n\nfunc TestJoin(t *testing.T) {\n\tla.remove(Loc{47, 1}, Loc{0, 2})\n\tassert.Equal(t, len(la.lines), 5)\n\tsub := la.Substr(Loc{0, 1}, Loc{47, 1})\n\tbytes := la.Bytes()\n\n\tassert.Equal(t, []byte(\"He wes Leovenaðes sone -- liðe him be Drihten.\"), sub)\n\tassert.Equal(t, unicode_txt, string(bytes))\n}\n\nfunc TestInsert(t *testing.T) {\n\tla.insert(Loc{20, 3}, []byte(\" foobar\"))\n\tsub1 := la.Substr(Loc{0, 3}, Loc{50, 3})\n\n\tassert.Equal(t, []byte(\"Uppen Sevarne staþe, foobar sel þar him þuhte,\"), sub1)\n\n\tla.insert(Loc{25, 2}, []byte(\"H̼̥̯͇͙̕͘͞e̸̦̞̠̣̰͙̼̥̦̼̖̬͕͕̰̯̫͇̕ĺ̜̠̩̯̯͙̼̭̠͕̮̞͜l̶͓̫̞̮͈͞ͅo̸͔͙̳̠͈̮̼̳͙̥̲͜͠\"))\n\n\tsub2 := la.Substr(Loc{0, 2}, Loc{60, 2})\n\tassert.Equal(t, []byte(\"He wonede at Ernleȝe at æH̼̥̯͇͙̕͘͞e̸̦̞̠̣̰͙̼̥̦̼̖̬͕͕̰̯̫͇̕ĺ̜̠̩̯̯͙̼̭̠͕̮̞͜l̶͓̫̞̮͈͞ͅo̸͔͙̳̠͈̮̼̳͙̥̲͜͠ðelen are chirechen,\"), sub2)\n}\n\nfunc TestRemove(t *testing.T) {\n\tla.remove(Loc{20, 3}, Loc{27, 3})\n\tla.remove(Loc{25, 2}, Loc{30, 2})\n\n\tbytes := la.Bytes()\n\tassert.Equal(t, unicode_txt, string(bytes))\n}\n"
  },
  {
    "path": "internal/buffer/loc.go",
    "content": "package buffer\n\nimport (\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n)\n\n// Loc stores a location\ntype Loc struct {\n\tX, Y int\n}\n\n// LessThan returns true if b is smaller\nfunc (l Loc) LessThan(b Loc) bool {\n\tif l.Y < b.Y {\n\t\treturn true\n\t}\n\treturn l.Y == b.Y && l.X < b.X\n}\n\n// GreaterThan returns true if b is bigger\nfunc (l Loc) GreaterThan(b Loc) bool {\n\tif l.Y > b.Y {\n\t\treturn true\n\t}\n\treturn l.Y == b.Y && l.X > b.X\n}\n\n// GreaterEqual returns true if b is greater than or equal to b\nfunc (l Loc) GreaterEqual(b Loc) bool {\n\tif l.Y > b.Y {\n\t\treturn true\n\t}\n\tif l.Y == b.Y && l.X > b.X {\n\t\treturn true\n\t}\n\treturn l == b\n}\n\n// LessEqual returns true if b is less than or equal to b\nfunc (l Loc) LessEqual(b Loc) bool {\n\tif l.Y < b.Y {\n\t\treturn true\n\t}\n\tif l.Y == b.Y && l.X < b.X {\n\t\treturn true\n\t}\n\treturn l == b\n}\n\n// Clamp clamps a loc between start and end\nfunc (l Loc) Clamp(start, end Loc) Loc {\n\tif l.GreaterEqual(end) {\n\t\treturn end\n\t} else if l.LessThan(start) {\n\t\treturn start\n\t}\n\treturn l\n}\n\n// The following functions require a buffer to know where newlines are\n\n// Diff returns the distance between two locations\nfunc DiffLA(a, b Loc, buf *LineArray) int {\n\tif a.Y == b.Y {\n\t\tif a.X > b.X {\n\t\t\treturn a.X - b.X\n\t\t}\n\t\treturn b.X - a.X\n\t}\n\n\t// Make sure a is guaranteed to be less than b\n\tif b.LessThan(a) {\n\t\ta, b = b, a\n\t}\n\n\tloc := 0\n\tfor i := a.Y + 1; i < b.Y; i++ {\n\t\t// + 1 for the newline\n\t\tloc += util.CharacterCount(buf.LineBytes(i)) + 1\n\t}\n\tloc += util.CharacterCount(buf.LineBytes(a.Y)) - a.X + b.X + 1\n\treturn loc\n}\n\n// This moves the location one character to the right\nfunc (l Loc) right(buf *LineArray) Loc {\n\tif l == buf.End() {\n\t\treturn Loc{l.X + 1, l.Y}\n\t}\n\tvar res Loc\n\tif l.X < util.CharacterCount(buf.LineBytes(l.Y)) {\n\t\tres = Loc{l.X + 1, l.Y}\n\t} else {\n\t\tres = Loc{0, l.Y + 1}\n\t}\n\treturn res\n}\n\n// This moves the given location one character to the left\nfunc (l Loc) left(buf *LineArray) Loc {\n\tif l == buf.Start() {\n\t\treturn Loc{l.X - 1, l.Y}\n\t}\n\tvar res Loc\n\tif l.X > 0 {\n\t\tres = Loc{l.X - 1, l.Y}\n\t} else {\n\t\tres = Loc{util.CharacterCount(buf.LineBytes(l.Y - 1)), l.Y - 1}\n\t}\n\treturn res\n}\n\n// MoveLA moves the cursor n characters to the left or right\n// It moves the cursor left if n is negative\nfunc (l Loc) MoveLA(n int, buf *LineArray) Loc {\n\tif n > 0 {\n\t\tfor i := 0; i < n; i++ {\n\t\t\tl = l.right(buf)\n\t\t}\n\t\treturn l\n\t}\n\tfor i := 0; i < util.Abs(n); i++ {\n\t\tl = l.left(buf)\n\t}\n\treturn l\n}\n\n// Diff returns the difference between two locs\nfunc (l Loc) Diff(b Loc, buf *Buffer) int {\n\treturn DiffLA(l, b, buf.LineArray)\n}\n\n// Move moves a loc n characters\nfunc (l Loc) Move(n int, buf *Buffer) Loc {\n\treturn l.MoveLA(n, buf.LineArray)\n}\n\n// ByteOffset is just like ToCharPos except it counts bytes instead of runes\nfunc ByteOffset(pos Loc, buf *Buffer) int {\n\tx, y := pos.X, pos.Y\n\tloc := 0\n\tfor i := 0; i < y; i++ {\n\t\t// + 1 for the newline\n\t\tloc += len(buf.Line(i)) + 1\n\t}\n\tloc += len(buf.Line(y)[:x])\n\treturn loc\n}\n\n// clamps a loc within a buffer\nfunc clamp(pos Loc, la *LineArray) Loc {\n\treturn pos.Clamp(la.Start(), la.End())\n}\n"
  },
  {
    "path": "internal/buffer/message.go",
    "content": "package buffer\n\nimport (\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/tcell/v2\"\n)\n\ntype MsgType int\n\nconst (\n\tMTInfo = iota\n\tMTWarning\n\tMTError\n)\n\n// Message represents the information for a gutter message\ntype Message struct {\n\t// The Msg iteslf\n\tMsg string\n\t// Start and End locations for the message\n\tStart, End Loc\n\t// The Kind stores the message type\n\tKind MsgType\n\t// The Owner of the message\n\tOwner string\n}\n\n// NewMessage creates a new gutter message\nfunc NewMessage(owner string, msg string, start, end Loc, kind MsgType) *Message {\n\treturn &Message{\n\t\tMsg:   msg,\n\t\tStart: start,\n\t\tEnd:   end,\n\t\tKind:  kind,\n\t\tOwner: owner,\n\t}\n}\n\n// NewMessageAtLine creates a new gutter message at a given line\nfunc NewMessageAtLine(owner string, msg string, line int, kind MsgType) *Message {\n\tstart := Loc{-1, line - 1}\n\tend := start\n\treturn NewMessage(owner, msg, start, end, kind)\n}\n\nfunc (m *Message) Style() tcell.Style {\n\tswitch m.Kind {\n\tcase MTInfo:\n\t\tif style, ok := config.Colorscheme[\"gutter-info\"]; ok {\n\t\t\treturn style\n\t\t}\n\tcase MTWarning:\n\t\tif style, ok := config.Colorscheme[\"gutter-warning\"]; ok {\n\t\t\treturn style\n\t\t}\n\tcase MTError:\n\t\tif style, ok := config.Colorscheme[\"gutter-error\"]; ok {\n\t\t\treturn style\n\t\t}\n\t}\n\treturn config.DefStyle\n}\n\nfunc (b *Buffer) AddMessage(m *Message) {\n\tb.Messages = append(b.Messages, m)\n}\n\nfunc (b *Buffer) removeMsg(i int) {\n\tcopy(b.Messages[i:], b.Messages[i+1:])\n\tb.Messages[len(b.Messages)-1] = nil\n\tb.Messages = b.Messages[:len(b.Messages)-1]\n}\n\nfunc (b *Buffer) ClearMessages(owner string) {\n\tfor i := len(b.Messages) - 1; i >= 0; i-- {\n\t\tif b.Messages[i].Owner == owner {\n\t\t\tb.removeMsg(i)\n\t\t}\n\t}\n}\n\nfunc (b *Buffer) ClearAllMessages() {\n\tb.Messages = make([]*Message, 0)\n}\n\ntype Messager interface {\n\tMessage(msg ...any)\n}\n\nvar prompt Messager\n\nfunc SetMessager(m Messager) {\n\tprompt = m\n}\n"
  },
  {
    "path": "internal/buffer/save.go",
    "content": "package buffer\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"io/fs\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"time\"\n\t\"unicode\"\n\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n\t\"golang.org/x/text/transform\"\n)\n\n// LargeFileThreshold is the number of bytes when fastdirty is forced\n// because hashing is too slow\nconst LargeFileThreshold = 50000\n\ntype wrappedFile struct {\n\tname        string\n\twriteCloser io.WriteCloser\n\twithSudo    bool\n\tscreenb     bool\n\tcmd         *exec.Cmd\n\tsigChan     chan os.Signal\n}\n\ntype saveResponse struct {\n\tsize int\n\terr  error\n}\n\ntype saveRequest struct {\n\tbuf              *Buffer\n\tpath             string\n\twithSudo         bool\n\tnewFile          bool\n\tsaveResponseChan chan saveResponse\n}\n\nvar saveRequestChan chan saveRequest\nvar backupRequestChan chan backupRequest\n\nfunc init() {\n\t// Both saveRequestChan and backupRequestChan need to be non-buffered\n\t// so the save/backup goroutine receives both save and backup requests\n\t// in the same order the main goroutine sends them.\n\tsaveRequestChan = make(chan saveRequest)\n\tbackupRequestChan = make(chan backupRequest)\n\n\tgo func() {\n\t\tduration := backupSeconds * float64(time.Second)\n\t\tbackupTicker := time.NewTicker(time.Duration(duration))\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase sr := <-saveRequestChan:\n\t\t\t\tsize, err := sr.buf.safeWrite(sr.path, sr.withSudo, sr.newFile)\n\t\t\t\tsr.saveResponseChan <- saveResponse{size, err}\n\t\t\tcase br := <-backupRequestChan:\n\t\t\t\thandleBackupRequest(br)\n\t\t\tcase <-backupTicker.C:\n\t\t\t\tperiodicBackup()\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc openFile(name string, withSudo bool) (wrappedFile, error) {\n\tvar err error\n\tvar writeCloser io.WriteCloser\n\tvar screenb bool\n\tvar cmd *exec.Cmd\n\tvar sigChan chan os.Signal\n\n\tif withSudo {\n\t\tconv := \"notrunc\"\n\t\t// TODO: both platforms do not support dd with conv=fsync yet\n\t\tif !(runtime.GOOS == \"illumos\" || runtime.GOOS == \"netbsd\") {\n\t\t\tconv += \",fsync\"\n\t\t}\n\n\t\tcmd = exec.Command(config.GlobalSettings[\"sucmd\"].(string), \"dd\", \"bs=4k\", \"conv=\"+conv, \"of=\"+name)\n\t\twriteCloser, err = cmd.StdinPipe()\n\t\tif err != nil {\n\t\t\treturn wrappedFile{}, err\n\t\t}\n\n\t\tsigChan = make(chan os.Signal, 1)\n\t\tsignal.Reset(os.Interrupt)\n\t\tsignal.Notify(sigChan, os.Interrupt)\n\n\t\tscreenb = screen.TempFini()\n\t\t// need to start the process now, otherwise when we flush the file\n\t\t// contents to its stdin it might hang because the kernel's pipe size\n\t\t// is too small to handle the full file contents all at once\n\t\terr = cmd.Start()\n\t\tif err != nil {\n\t\t\tscreen.TempStart(screenb)\n\n\t\t\tsignal.Notify(util.Sigterm, os.Interrupt)\n\t\t\tsignal.Stop(sigChan)\n\n\t\t\treturn wrappedFile{}, err\n\t\t}\n\t} else {\n\t\twriteCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, util.FileMode)\n\t\tif err != nil {\n\t\t\treturn wrappedFile{}, err\n\t\t}\n\t}\n\n\treturn wrappedFile{name, writeCloser, withSudo, screenb, cmd, sigChan}, nil\n}\n\nfunc (wf wrappedFile) Truncate() error {\n\tif wf.withSudo {\n\t\t// we don't need to stop the screen here, since it is still stopped\n\t\t// by openFile()\n\t\t// truncate might not be available on every platfom, so use dd instead\n\t\tcmd := exec.Command(config.GlobalSettings[\"sucmd\"].(string), \"dd\", \"count=0\", \"of=\"+wf.name)\n\t\treturn cmd.Run()\n\t}\n\treturn wf.writeCloser.(*os.File).Truncate(0)\n}\n\nfunc (wf wrappedFile) Write(b *SharedBuffer) (int, error) {\n\tfile := bufio.NewWriter(transform.NewWriter(wf.writeCloser, b.encoding.NewEncoder()))\n\n\tb.Lock()\n\tdefer b.Unlock()\n\n\tif len(b.lines) == 0 {\n\t\treturn 0, nil\n\t}\n\n\t// end of line\n\tvar eol []byte\n\tif b.Endings == FFDos {\n\t\teol = []byte{'\\r', '\\n'}\n\t} else {\n\t\teol = []byte{'\\n'}\n\t}\n\n\terr := wf.Truncate()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\t// write lines\n\tsize, err := file.Write(b.lines[0].data)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tfor _, l := range b.lines[1:] {\n\t\tif _, err = file.Write(eol); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tif _, err = file.Write(l.data); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tsize += len(eol) + len(l.data)\n\t}\n\n\terr = file.Flush()\n\tif err == nil && !wf.withSudo {\n\t\t// Call Sync() on the file to make sure the content is safely on disk.\n\t\tf := wf.writeCloser.(*os.File)\n\t\terr = f.Sync()\n\t}\n\treturn size, err\n}\n\nfunc (wf wrappedFile) Close() error {\n\terr := wf.writeCloser.Close()\n\tif wf.withSudo {\n\t\t// wait for dd to finish and restart the screen if we used sudo\n\t\terr := wf.cmd.Wait()\n\t\tscreen.TempStart(wf.screenb)\n\n\t\tsignal.Notify(util.Sigterm, os.Interrupt)\n\t\tsignal.Stop(wf.sigChan)\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn err\n}\n\nfunc (b *SharedBuffer) overwriteFile(name string) (int, error) {\n\tfile, err := openFile(name, false)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tsize, err := file.Write(b)\n\n\terr2 := file.Close()\n\tif err2 != nil && err == nil {\n\t\terr = err2\n\t}\n\treturn size, err\n}\n\n// Save saves the buffer to its default path\nfunc (b *Buffer) Save() error {\n\treturn b.SaveAs(b.Path)\n}\n\n// AutoSave saves the buffer to its default path\nfunc (b *Buffer) AutoSave() error {\n\tif !b.Modified() {\n\t\treturn nil\n\t}\n\treturn b.saveToFile(b.Path, false, true)\n}\n\n// SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist\nfunc (b *Buffer) SaveAs(filename string) error {\n\treturn b.saveToFile(filename, false, false)\n}\n\nfunc (b *Buffer) SaveWithSudo() error {\n\treturn b.SaveAsWithSudo(b.Path)\n}\n\nfunc (b *Buffer) SaveAsWithSudo(filename string) error {\n\treturn b.saveToFile(filename, true, false)\n}\n\nfunc (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error {\n\tvar err error\n\tif b.Type.Readonly {\n\t\treturn errors.New(\"Cannot save readonly buffer\")\n\t}\n\tif b.Type.Scratch {\n\t\treturn errors.New(\"Cannot save scratch buffer\")\n\t}\n\n\tif !autoSave && b.Settings[\"rmtrailingws\"].(bool) {\n\t\tfor i, l := range b.lines {\n\t\t\tleftover := util.CharacterCount(bytes.TrimRightFunc(l.data, unicode.IsSpace))\n\n\t\t\tlinelen := util.CharacterCount(l.data)\n\t\t\tb.Remove(Loc{leftover, i}, Loc{linelen, i})\n\t\t}\n\n\t\tb.RelocateCursors()\n\t}\n\n\tif b.Settings[\"eofnewline\"].(bool) {\n\t\tend := b.End()\n\t\tif b.RuneAt(Loc{end.X - 1, end.Y}) != '\\n' {\n\t\t\tb.insert(end, []byte{'\\n'})\n\t\t}\n\t}\n\n\tfilename, err = util.ReplaceHome(filename)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnewFile := false\n\tfileInfo, err := os.Stat(filename)\n\tif err != nil {\n\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\treturn err\n\t\t}\n\t\tnewFile = true\n\t}\n\tif err == nil && fileInfo.IsDir() {\n\t\treturn errors.New(\"Error: \" + filename + \" is a directory and cannot be saved\")\n\t}\n\tif err == nil && !fileInfo.Mode().IsRegular() {\n\t\treturn errors.New(\"Error: \" + filename + \" is not a regular file and cannot be saved\")\n\t}\n\n\tabsFilename, err := filepath.Abs(filename)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Get the leading path to the file | \".\" is returned if there's no leading path provided\n\tif dirname := filepath.Dir(absFilename); dirname != \".\" {\n\t\t// Check if the parent dirs don't exist\n\t\tif _, statErr := os.Stat(dirname); errors.Is(statErr, fs.ErrNotExist) {\n\t\t\t// Prompt to make sure they want to create the dirs that are missing\n\t\t\tif b.Settings[\"mkparents\"].(bool) {\n\t\t\t\t// Create all leading dir(s) since they don't exist\n\t\t\t\tif mkdirallErr := os.MkdirAll(dirname, os.ModePerm); mkdirallErr != nil {\n\t\t\t\t\t// If there was an error creating the dirs\n\t\t\t\t\treturn mkdirallErr\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn errors.New(\"Parent dirs don't exist, enable 'mkparents' for auto creation\")\n\t\t\t}\n\t\t}\n\t}\n\n\tsaveResponseChan := make(chan saveResponse)\n\tsaveRequestChan <- saveRequest{b, absFilename, withSudo, newFile, saveResponseChan}\n\tresult := <-saveResponseChan\n\terr = result.err\n\tif err != nil {\n\t\tif errors.Is(err, util.ErrOverwrite) {\n\t\t\tscreen.TermMessage(err)\n\t\t\terr = errors.Unwrap(err)\n\n\t\t\tb.UpdateModTime()\n\t\t}\n\t\treturn err\n\t}\n\n\tif !b.Settings[\"fastdirty\"].(bool) {\n\t\tif result.size > LargeFileThreshold {\n\t\t\t// For large files 'fastdirty' needs to be on\n\t\t\tb.Settings[\"fastdirty\"] = true\n\t\t} else {\n\t\t\tb.calcHash(&b.origHash)\n\t\t}\n\t}\n\n\tnewPath := b.Path != filename\n\tif newPath {\n\t\tb.RemoveBackup()\n\t}\n\n\tb.Path = filename\n\tb.AbsPath = absFilename\n\tb.isModified = false\n\tb.UpdateModTime()\n\n\tif newPath {\n\t\t// need to update glob-based and filetype-based settings\n\t\tb.ReloadSettings(true)\n\t}\n\n\terr = b.Serialize()\n\treturn err\n}\n\n// safeWrite writes the buffer to a file in a \"safe\" way, preventing loss of the\n// contents of the file if it fails to write the new contents.\n// This means that the file is not overwritten directly but by writing to the\n// backup file first.\nfunc (b *SharedBuffer) safeWrite(path string, withSudo bool, newFile bool) (int, error) {\n\tfile, err := openFile(path, withSudo)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tdefer func() {\n\t\tif newFile && err != nil {\n\t\t\tos.Remove(path)\n\t\t}\n\t}()\n\n\t// Try to backup first before writing\n\tbackupName, resolveName, err := b.writeBackup(path)\n\tif err != nil {\n\t\tfile.Close()\n\t\treturn 0, err\n\t}\n\n\t// Backup saved, so cancel pending periodic backup, if any\n\tdelete(requestedBackups, b)\n\n\tb.forceKeepBackup = true\n\tsize := 0\n\t{\n\t\t// If we failed to write or close, keep the backup we made\n\t\tsize, err = file.Write(b)\n\t\tif err != nil {\n\t\t\tfile.Close()\n\t\t\treturn 0, util.OverwriteError{err, backupName}\n\t\t}\n\n\t\terr = file.Close()\n\t\tif err != nil {\n\t\t\treturn 0, util.OverwriteError{err, backupName}\n\t\t}\n\t}\n\tb.forceKeepBackup = false\n\n\tif !b.keepBackup() {\n\t\tb.removeBackup(backupName, resolveName)\n\t}\n\n\treturn size, err\n}\n"
  },
  {
    "path": "internal/buffer/search.go",
    "content": "package buffer\n\nimport (\n\t\"regexp\"\n\t\"unicode/utf8\"\n\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n)\n\n// We want \"^\" and \"$\" to match only the beginning/end of a line, not the\n// beginning/end of the search region if it is in the middle of a line.\n// In that case we use padded regexps to require a rune before or after\n// the match. (This also affects other empty-string patters like \"\\\\b\".)\n// The following two flags indicate the padding used.\nconst (\n\tpadStart = 1 << iota\n\tpadEnd\n)\n\nfunc findLineParams(b *Buffer, start, end Loc, i int, r *regexp.Regexp) ([]byte, int, int, *regexp.Regexp) {\n\tl := b.LineBytes(i)\n\tcharpos := 0\n\tpadMode := 0\n\n\tif i == end.Y {\n\t\tnchars := util.CharacterCount(l)\n\t\tend.X = util.Clamp(end.X, 0, nchars)\n\t\tif end.X < nchars {\n\t\t\tl = util.SliceStart(l, end.X+1)\n\t\t\tpadMode |= padEnd\n\t\t}\n\t}\n\n\tif i == start.Y {\n\t\tnchars := util.CharacterCount(l)\n\t\tstart.X = util.Clamp(start.X, 0, nchars)\n\t\tif start.X > 0 {\n\t\t\tcharpos = start.X - 1\n\t\t\tl = util.SliceEnd(l, charpos)\n\t\t\tpadMode |= padStart\n\t\t}\n\t}\n\n\tif padMode != 0 {\n\t\tre, err := regexp.Compile(r.String() + `\\E`)\n\t\tif err == nil {\n\t\t\t// r contains \\Q without closing \\E\n\t\t\tr = re\n\t\t}\n\n\t\tif padMode == padStart {\n\t\t\tr = regexp.MustCompile(\".(?:\" + r.String() + \")\")\n\t\t} else if padMode == padEnd {\n\t\t\tr = regexp.MustCompile(\"(?:\" + r.String() + \").\")\n\t\t} else {\n\t\t\t// padMode == padStart|padEnd\n\t\t\tr = regexp.MustCompile(\".(?:\" + r.String() + \").\")\n\t\t}\n\t}\n\n\treturn l, charpos, padMode, r\n}\n\nfunc (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {\n\tlastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))\n\tif start.Y > b.LinesNum()-1 {\n\t\tstart.X = lastcn - 1\n\t}\n\tif end.Y > b.LinesNum()-1 {\n\t\tend.X = lastcn\n\t}\n\tstart.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)\n\tend.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)\n\n\tif start.GreaterThan(end) {\n\t\tstart, end = end, start\n\t}\n\n\tfor i := start.Y; i <= end.Y; i++ {\n\t\tl, charpos, padMode, rPadded := findLineParams(b, start, end, i, r)\n\n\t\tmatch := rPadded.FindIndex(l)\n\n\t\tif match != nil {\n\t\t\tif padMode&padStart != 0 {\n\t\t\t\t_, size := utf8.DecodeRune(l[match[0]:])\n\t\t\t\tmatch[0] += size\n\t\t\t}\n\t\t\tif padMode&padEnd != 0 {\n\t\t\t\t_, size := utf8.DecodeLastRune(l[:match[1]])\n\t\t\t\tmatch[1] -= size\n\t\t\t}\n\t\t\tstart := Loc{charpos + util.RunePos(l, match[0]), i}\n\t\t\tend := Loc{charpos + util.RunePos(l, match[1]), i}\n\t\t\treturn [2]Loc{start, end}, true\n\t\t}\n\t}\n\treturn [2]Loc{}, false\n}\n\nfunc (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {\n\tlastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))\n\tif start.Y > b.LinesNum()-1 {\n\t\tstart.X = lastcn - 1\n\t}\n\tif end.Y > b.LinesNum()-1 {\n\t\tend.X = lastcn\n\t}\n\tstart.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)\n\tend.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)\n\n\tif start.GreaterThan(end) {\n\t\tstart, end = end, start\n\t}\n\n\tfor i := end.Y; i >= start.Y; i-- {\n\t\tcharCount := util.CharacterCount(b.LineBytes(i))\n\t\tfrom := Loc{0, i}.Clamp(start, end)\n\t\tto := Loc{charCount, i}.Clamp(start, end)\n\n\t\tallMatches := b.findAll(r, from, to)\n\t\tif allMatches != nil {\n\t\t\tmatch := allMatches[len(allMatches)-1]\n\t\t\treturn [2]Loc{match[0], match[1]}, true\n\t\t}\n\t}\n\treturn [2]Loc{}, false\n}\n\nfunc (b *Buffer) findAll(r *regexp.Regexp, start, end Loc) [][2]Loc {\n\tvar matches [][2]Loc\n\tloc := start\n\tfor {\n\t\tmatch, found := b.findDown(r, loc, end)\n\t\tif !found {\n\t\t\tbreak\n\t\t}\n\t\tmatches = append(matches, match)\n\t\tif match[0] != match[1] {\n\t\t\tloc = match[1]\n\t\t} else if match[1] != end {\n\t\t\tloc = match[1].Move(1, b)\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn matches\n}\n\n// FindNext finds the next occurrence of a given string in the buffer\n// It returns the start and end location of the match (if found) and\n// a boolean indicating if it was found\n// May also return an error if the search regex is invalid\nfunc (b *Buffer) FindNext(s string, start, end, from Loc, down bool, useRegex bool) ([2]Loc, bool, error) {\n\tif s == \"\" {\n\t\treturn [2]Loc{}, false, nil\n\t}\n\n\tvar r *regexp.Regexp\n\tvar err error\n\n\tif !useRegex {\n\t\ts = regexp.QuoteMeta(s)\n\t}\n\n\tif b.Settings[\"ignorecase\"].(bool) {\n\t\tr, err = regexp.Compile(\"(?i)\" + s)\n\t} else {\n\t\tr, err = regexp.Compile(s)\n\t}\n\n\tif err != nil {\n\t\treturn [2]Loc{}, false, err\n\t}\n\n\tvar found bool\n\tvar l [2]Loc\n\tif down {\n\t\tl, found = b.findDown(r, from, end)\n\t\tif !found {\n\t\t\tl, found = b.findDown(r, start, end)\n\t\t}\n\t} else {\n\t\tl, found = b.findUp(r, from, start)\n\t\tif !found {\n\t\t\tl, found = b.findUp(r, end, start)\n\t\t}\n\t}\n\treturn l, found, nil\n}\n\n// ReplaceRegex replaces all occurrences of 'search' with 'replace' in the given area\n// and returns the number of replacements made and the number of characters\n// added or removed on the last line of the range\nfunc (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte, captureGroups bool) (int, int) {\n\tif start.GreaterThan(end) {\n\t\tstart, end = end, start\n\t}\n\n\tcharsEnd := util.CharacterCount(b.LineBytes(end.Y))\n\tfound := 0\n\tvar deltas []Delta\n\n\tfor i := start.Y; i <= end.Y; i++ {\n\t\tl := b.LineBytes(i)\n\t\tcharCount := util.CharacterCount(l)\n\t\tif (i == start.Y && start.X > 0) || (i == end.Y && end.X < charCount) {\n\t\t\t// This replacement code works in general, but it creates a separate\n\t\t\t// modification for each match. We only use it for the first and last\n\t\t\t// lines, which may use padded regexps\n\n\t\t\tfrom := Loc{0, i}.Clamp(start, end)\n\t\t\tto := Loc{charCount, i}.Clamp(start, end)\n\t\t\tmatches := b.findAll(search, from, to)\n\t\t\tfound += len(matches)\n\n\t\t\tfor j := len(matches) - 1; j >= 0; j-- {\n\t\t\t\t// if we counted upwards, the different deltas would interfere\n\t\t\t\tmatch := matches[j]\n\t\t\t\tvar newText []byte\n\t\t\t\tif captureGroups {\n\t\t\t\t\tnewText = search.ReplaceAll(b.Substr(match[0], match[1]), replace)\n\t\t\t\t} else {\n\t\t\t\t\tnewText = replace\n\t\t\t\t}\n\t\t\t\tdeltas = append(deltas, Delta{newText, match[0], match[1]})\n\t\t\t}\n\t\t} else {\n\t\t\tnewLine := search.ReplaceAllFunc(l, func(in []byte) []byte {\n\t\t\t\tfound++\n\t\t\t\tvar result []byte\n\t\t\t\tif captureGroups {\n\t\t\t\t\tmatch := search.FindSubmatchIndex(in)\n\t\t\t\t\tresult = search.Expand(result, replace, in, match)\n\t\t\t\t} else {\n\t\t\t\t\tresult = replace\n\t\t\t\t}\n\t\t\t\treturn result\n\t\t\t})\n\t\t\tdeltas = append(deltas, Delta{newLine, Loc{0, i}, Loc{charCount, i}})\n\t\t}\n\t}\n\n\tb.MultipleReplace(deltas)\n\n\treturn found, util.CharacterCount(b.LineBytes(end.Y)) - charsEnd\n}\n"
  },
  {
    "path": "internal/buffer/serialize.go",
    "content": "package buffer\n\nimport (\n\t\"bytes\"\n\t\"encoding/gob\"\n\t\"errors\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n)\n\n// The SerializedBuffer holds the types that get serialized when a buffer is saved\n// These are used for the savecursor and saveundo options\ntype SerializedBuffer struct {\n\tEventHandler *EventHandler\n\tCursor       Loc\n\tModTime      time.Time\n}\n\n// Serialize serializes the buffer to config.ConfigDir/buffers\nfunc (b *Buffer) Serialize() error {\n\tif !b.Settings[\"savecursor\"].(bool) && !b.Settings[\"saveundo\"].(bool) {\n\t\treturn nil\n\t}\n\tif b.Path == \"\" {\n\t\treturn nil\n\t}\n\n\tbuffer := SerializedBuffer{\n\t\tCursor:  b.GetActiveCursor().Loc,\n\t\tModTime: b.ModTime,\n\t}\n\tif b.Settings[\"saveundo\"].(bool) {\n\t\tbuffer.EventHandler = b.EventHandler\n\t}\n\n\tvar buf bytes.Buffer\n\terr := gob.NewEncoder(&buf).Encode(buffer)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tname, resolveName := util.DetermineEscapePath(filepath.Join(config.ConfigDir, \"buffers\"), b.AbsPath)\n\terr = util.SafeWrite(name, buf.Bytes(), true)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif resolveName != \"\" {\n\t\terr = util.SafeWrite(resolveName, []byte(b.AbsPath), true)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Unserialize loads the buffer info from config.ConfigDir/buffers\nfunc (b *Buffer) Unserialize() error {\n\t// If either savecursor or saveundo is turned on, we need to load the serialized information\n\t// from ~/.config/micro/buffers\n\tif b.Path == \"\" {\n\t\treturn nil\n\t}\n\tname, _ := util.DetermineEscapePath(filepath.Join(config.ConfigDir, \"buffers\"), b.AbsPath)\n\tfile, err := os.Open(name)\n\tif err == nil {\n\t\tdefer file.Close()\n\t\tvar buffer SerializedBuffer\n\t\tdecoder := gob.NewDecoder(file)\n\t\terr = decoder.Decode(&buffer)\n\t\tif err != nil {\n\t\t\treturn errors.New(err.Error() + \"\\nYou may want to remove the files in ~/.config/micro/buffers (these files\\nstore the information for the 'saveundo' and 'savecursor' options) if\\nthis problem persists.\\nThis may be caused by upgrading to version 2.0, and removing the 'buffers'\\ndirectory will reset the cursor and undo history and solve the problem.\")\n\t\t}\n\t\tif b.Settings[\"savecursor\"].(bool) {\n\t\t\tb.StartCursor = buffer.Cursor\n\t\t}\n\n\t\tif b.Settings[\"saveundo\"].(bool) && buffer.EventHandler != nil {\n\t\t\t// We should only use last time's eventhandler if the file wasn't modified by someone else in the meantime\n\t\t\tif b.ModTime == buffer.ModTime {\n\t\t\t\tb.EventHandler = buffer.EventHandler\n\t\t\t\tb.EventHandler.cursors = b.cursors\n\t\t\t\tb.EventHandler.buf = b.SharedBuffer\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/buffer/settings.go",
    "content": "package buffer\n\nimport (\n\t\"crypto/md5\"\n\t\"reflect\"\n\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\tulua \"github.com/micro-editor/micro/v2/internal/lua\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"golang.org/x/text/encoding/htmlindex\"\n\t\"golang.org/x/text/encoding/unicode\"\n\tluar \"layeh.com/gopher-luar\"\n)\n\nfunc (b *Buffer) ReloadSettings(reloadFiletype bool) {\n\tsettings := config.ParsedSettings()\n\tconfig.UpdatePathGlobLocals(settings, b.AbsPath)\n\n\toldFiletype := b.Settings[\"filetype\"].(string)\n\n\t_, local := b.LocalSettings[\"filetype\"]\n\t_, volatile := config.VolatileSettings[\"filetype\"]\n\tif reloadFiletype && !local && !volatile {\n\t\t// need to update filetype before updating other settings based on it\n\t\tb.Settings[\"filetype\"] = \"unknown\"\n\t\tif v, ok := settings[\"filetype\"]; ok {\n\t\t\tb.Settings[\"filetype\"] = v\n\t\t}\n\t}\n\n\t// update syntax rules, which will also update filetype if needed\n\tb.UpdateRules()\n\n\tcurFiletype := b.Settings[\"filetype\"].(string)\n\tif oldFiletype != curFiletype {\n\t\tb.doCallbacks(\"filetype\", oldFiletype, curFiletype)\n\t}\n\n\tconfig.UpdateFileTypeLocals(settings, curFiletype)\n\n\tfor k, v := range config.DefaultCommonSettings() {\n\t\tif k == \"filetype\" {\n\t\t\t// prevent recursion\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := config.VolatileSettings[k]; ok {\n\t\t\t// reload should not override volatile settings\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := b.LocalSettings[k]; ok {\n\t\t\t// reload should not override local settings\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := settings[k]; ok {\n\t\t\tb.DoSetOptionNative(k, settings[k])\n\t\t} else {\n\t\t\tb.DoSetOptionNative(k, v)\n\t\t}\n\t}\n}\n\nfunc (b *Buffer) DoSetOptionNative(option string, nativeValue any) {\n\toldValue := b.Settings[option]\n\tif reflect.DeepEqual(oldValue, nativeValue) {\n\t\treturn\n\t}\n\n\tb.Settings[option] = nativeValue\n\n\tif option == \"fastdirty\" {\n\t\tif !nativeValue.(bool) {\n\t\t\tif b.Size() > LargeFileThreshold {\n\t\t\t\tb.Settings[\"fastdirty\"] = true\n\t\t\t} else {\n\t\t\t\tif !b.isModified {\n\t\t\t\t\tb.calcHash(&b.origHash)\n\t\t\t\t} else {\n\t\t\t\t\t// prevent using an old stale origHash value\n\t\t\t\t\tb.origHash = [md5.Size]byte{}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else if option == \"statusline\" {\n\t\tscreen.Redraw()\n\t} else if option == \"filetype\" {\n\t\tb.ReloadSettings(false)\n\t} else if option == \"fileformat\" {\n\t\tswitch b.Settings[\"fileformat\"].(string) {\n\t\tcase \"unix\":\n\t\t\tb.Endings = FFUnix\n\t\tcase \"dos\":\n\t\t\tb.Endings = FFDos\n\t\t}\n\t\tb.setModified()\n\t} else if option == \"syntax\" {\n\t\tif !nativeValue.(bool) {\n\t\t\tb.ClearMatches()\n\t\t} else {\n\t\t\tb.UpdateRules()\n\t\t}\n\t} else if option == \"encoding\" {\n\t\tenc, err := htmlindex.Get(b.Settings[\"encoding\"].(string))\n\t\tif err != nil {\n\t\t\tenc = unicode.UTF8\n\t\t\tb.Settings[\"encoding\"] = \"utf-8\"\n\t\t}\n\t\tb.encoding = enc\n\t\tb.setModified()\n\t} else if option == \"readonly\" && b.Type.Kind == BTDefault.Kind {\n\t\tb.Type.Readonly = nativeValue.(bool)\n\t} else if option == \"hlsearch\" {\n\t\tfor _, buf := range OpenBuffers {\n\t\t\tif b.SharedBuffer == buf.SharedBuffer {\n\t\t\t\tbuf.HighlightSearch = nativeValue.(bool)\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfor _, pl := range config.Plugins {\n\t\t\tif option == pl.Name {\n\t\t\t\tif nativeValue.(bool) {\n\t\t\t\t\tif !pl.Loaded {\n\t\t\t\t\t\tpl.Load()\n\t\t\t\t\t}\n\t\t\t\t\t_, err := pl.Call(\"init\")\n\t\t\t\t\tif err != nil && err != config.ErrNoSuchFunction {\n\t\t\t\t\t\tscreen.TermMessage(err)\n\t\t\t\t\t}\n\t\t\t\t} else if !nativeValue.(bool) && pl.Loaded {\n\t\t\t\t\t_, err := pl.Call(\"deinit\")\n\t\t\t\t\tif err != nil && err != config.ErrNoSuchFunction {\n\t\t\t\t\t\tscreen.TermMessage(err)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tb.doCallbacks(option, oldValue, nativeValue)\n}\n\nfunc (b *Buffer) SetOptionNative(option string, nativeValue any) error {\n\tif err := config.OptionIsValid(option, nativeValue); err != nil {\n\t\treturn err\n\t}\n\n\tb.DoSetOptionNative(option, nativeValue)\n\tb.LocalSettings[option] = true\n\n\treturn nil\n}\n\n// SetOption sets a given option to a value just for this buffer\nfunc (b *Buffer) SetOption(option, value string) error {\n\tif _, ok := b.Settings[option]; !ok {\n\t\treturn config.ErrInvalidOption\n\t}\n\n\tnativeValue, err := config.GetNativeValue(option, value)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn b.SetOptionNative(option, nativeValue)\n}\n\nfunc (b *Buffer) doCallbacks(option string, oldValue any, newValue any) {\n\tif b.OptionCallback != nil {\n\t\tb.OptionCallback(option, newValue)\n\t}\n\n\tif err := config.RunPluginFn(\"onBufferOptionChanged\",\n\t\tluar.New(ulua.L, b), luar.New(ulua.L, option),\n\t\tluar.New(ulua.L, oldValue), luar.New(ulua.L, newValue)); err != nil {\n\t\tscreen.TermMessage(err)\n\t}\n}\n"
  },
  {
    "path": "internal/buffer/stack.go",
    "content": "package buffer\n\n// TEStack is a simple implementation of a LIFO stack for text events\ntype TEStack struct {\n\tTop  *Element\n\tSize int\n}\n\n// An Element which is stored in the Stack\ntype Element struct {\n\tValue *TextEvent\n\tNext  *Element\n}\n\n// Len returns the stack's length\nfunc (s *TEStack) Len() int {\n\treturn s.Size\n}\n\n// Push a new element onto the stack\nfunc (s *TEStack) Push(value *TextEvent) {\n\ts.Top = &Element{value, s.Top}\n\ts.Size++\n}\n\n// Pop removes the top element from the stack and returns its value\n// If the stack is empty, return nil\nfunc (s *TEStack) Pop() (value *TextEvent) {\n\tif s.Size > 0 {\n\t\tvalue, s.Top = s.Top.Value, s.Top.Next\n\t\ts.Size--\n\t\treturn\n\t}\n\treturn nil\n}\n\n// Peek returns the top element of the stack without removing it\nfunc (s *TEStack) Peek() *TextEvent {\n\tif s.Size > 0 {\n\t\treturn s.Top.Value\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/buffer/stack_test.go",
    "content": "package buffer\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestStack(t *testing.T) {\n\ts := new(TEStack)\n\te1 := &TextEvent{\n\t\tEventType: TextEventReplace,\n\t\tTime:      time.Now(),\n\t}\n\te2 := &TextEvent{\n\t\tEventType: TextEventInsert,\n\t\tTime:      time.Now(),\n\t}\n\ts.Push(e1)\n\ts.Push(e2)\n\n\tp := s.Peek()\n\tassert.Equal(t, p.EventType, TextEventInsert)\n\tp = s.Pop()\n\tassert.Equal(t, p.EventType, TextEventInsert)\n\tp = s.Peek()\n\tassert.Equal(t, p.EventType, TextEventReplace)\n\tp = s.Pop()\n\tassert.Equal(t, p.EventType, TextEventReplace)\n\tp = s.Pop()\n\tassert.Nil(t, p)\n\tp = s.Peek()\n\tassert.Nil(t, p)\n}\n"
  },
  {
    "path": "internal/clipboard/clipboard.go",
    "content": "package clipboard\n\nimport (\n\t\"errors\"\n\n\t\"github.com/zyedidia/clipper\"\n)\n\ntype Method int\n\nconst (\n\t// External relies on external tools for accessing the clipboard\n\t// These include xclip, xsel, wl-clipboard for linux, pbcopy/pbpaste on Mac,\n\t// and Syscalls on Windows.\n\tExternal Method = iota\n\t// Terminal uses the terminal to manage the clipboard via OSC 52. Many\n\t// terminals do not support OSC 52, in which case this method won't work.\n\tTerminal\n\t// Internal just manages the clipboard with an internal buffer and doesn't\n\t// attempt to interface with the system clipboard\n\tInternal\n)\n\n// CurrentMethod is the method used to store clipboard information\nvar CurrentMethod Method = Internal\n\n// A Register is a buffer used to store text. The system clipboard has the 'clipboard'\n// and 'primary' (linux-only) registers, but other registers may be used internal to micro.\ntype Register int\n\nconst (\n\t// ClipboardReg is the main system clipboard\n\tClipboardReg Register = -1\n\t// PrimaryReg is the system primary clipboard (linux only)\n\tPrimaryReg = -2\n)\n\nvar clipboard clipper.Clipboard\n\n// Initialize attempts to initialize the clipboard using the given method\nfunc Initialize(m Method) error {\n\tvar err error\n\tswitch m {\n\tcase External:\n\t\tclips := make([]clipper.Clipboard, 0, len(clipper.Clipboards)+1)\n\t\tclips = append(clips, &clipper.Custom{\n\t\t\tName: \"micro-clip\",\n\t\t})\n\t\tclips = append(clips, clipper.Clipboards...)\n\t\tclipboard, err = clipper.GetClipboard(clips...)\n\t}\n\tif err != nil {\n\t\tCurrentMethod = Internal\n\t}\n\treturn err\n}\n\n// SetMethod changes the clipboard access method\nfunc SetMethod(m string) Method {\n\tswitch m {\n\tcase \"internal\":\n\t\tCurrentMethod = Internal\n\tcase \"external\":\n\t\tCurrentMethod = External\n\tcase \"terminal\":\n\t\tCurrentMethod = Terminal\n\t}\n\treturn CurrentMethod\n}\n\n// Read reads from a clipboard register\nfunc Read(r Register) (string, error) {\n\treturn read(r, CurrentMethod)\n}\n\n// Write writes text to a clipboard register\nfunc Write(text string, r Register) error {\n\treturn write(text, r, CurrentMethod)\n}\n\n// ReadMulti reads text from a clipboard register for a certain multi-cursor\nfunc ReadMulti(r Register, num, ncursors int) (string, error) {\n\tclip, err := Read(r)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif ValidMulti(r, clip, ncursors) {\n\t\treturn multi.getText(r, num), nil\n\t}\n\treturn clip, nil\n}\n\n// WriteMulti writes text to a clipboard register for a certain multi-cursor\nfunc WriteMulti(text string, r Register, num int, ncursors int) error {\n\treturn writeMulti(text, r, num, ncursors, CurrentMethod)\n}\n\n// ValidMulti checks if the internal multi-clipboard is valid and up-to-date\n// with the system clipboard\nfunc ValidMulti(r Register, clip string, ncursors int) bool {\n\treturn multi.isValid(r, clip, ncursors)\n}\n\nfunc writeMulti(text string, r Register, num int, ncursors int, m Method) error {\n\tmulti.writeText(text, r, num, ncursors)\n\treturn write(multi.getAllText(r), r, m)\n}\n\nfunc read(r Register, m Method) (string, error) {\n\tswitch m {\n\tcase External:\n\t\tswitch r {\n\t\tcase ClipboardReg:\n\t\t\tb, e := clipboard.ReadAll(clipper.RegClipboard)\n\t\t\treturn string(b), e\n\t\tcase PrimaryReg:\n\t\t\tb, e := clipboard.ReadAll(clipper.RegPrimary)\n\t\t\treturn string(b), e\n\t\tdefault:\n\t\t\treturn internal.read(r), nil\n\t\t}\n\tcase Internal:\n\t\treturn internal.read(r), nil\n\tcase Terminal:\n\t\tswitch r {\n\t\tcase ClipboardReg:\n\t\t\t// terminal paste works by sending an esc sequence to the\n\t\t\t// terminal to trigger a paste event\n\t\t\treturn terminal.read(\"clipboard\")\n\t\tcase PrimaryReg:\n\t\t\treturn terminal.read(\"primary\")\n\t\tdefault:\n\t\t\treturn internal.read(r), nil\n\t\t}\n\t}\n\treturn \"\", errors.New(\"Invalid clipboard method\")\n}\n\nfunc write(text string, r Register, m Method) error {\n\tswitch m {\n\tcase External:\n\t\tswitch r {\n\t\tcase ClipboardReg:\n\t\t\treturn clipboard.WriteAll(clipper.RegClipboard, []byte(text))\n\t\tcase PrimaryReg:\n\t\t\treturn clipboard.WriteAll(clipper.RegPrimary, []byte(text))\n\t\tdefault:\n\t\t\tinternal.write(text, r)\n\t\t}\n\tcase Internal:\n\t\tinternal.write(text, r)\n\tcase Terminal:\n\t\tswitch r {\n\t\tcase ClipboardReg:\n\t\t\treturn terminal.write(text, \"c\")\n\t\tcase PrimaryReg:\n\t\t\treturn terminal.write(text, \"p\")\n\t\tdefault:\n\t\t\tinternal.write(text, r)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/clipboard/internal.go",
    "content": "package clipboard\n\ntype internalClipboard map[Register]string\n\nvar internal internalClipboard\n\nfunc init() {\n\tinternal = make(internalClipboard)\n}\n\nfunc (c internalClipboard) read(r Register) string {\n\treturn c[r]\n}\n\nfunc (c internalClipboard) write(text string, r Register) {\n\tc[r] = text\n}\n"
  },
  {
    "path": "internal/clipboard/multi.go",
    "content": "package clipboard\n\nimport (\n\t\"bytes\"\n)\n\n// For storing multi cursor clipboard contents\ntype multiClipboard map[Register][]string\n\nvar multi multiClipboard\n\nfunc (c multiClipboard) getAllText(r Register) string {\n\tcontent := c[r]\n\tif content == nil {\n\t\treturn \"\"\n\t}\n\n\tbuf := &bytes.Buffer{}\n\tfor _, s := range content {\n\t\tbuf.WriteString(s)\n\t}\n\treturn buf.String()\n}\n\nfunc (c multiClipboard) getText(r Register, num int) string {\n\tcontent := c[r]\n\tif content == nil || len(content) <= num {\n\t\treturn \"\"\n\t}\n\n\treturn content[num]\n}\n\n// isValid checks if the text stored in this multi-clipboard is the same as the\n// text stored in the system clipboard (provided as an argument), and therefore\n// if it is safe to use the multi-clipboard for pasting instead of the system\n// clipboard.\nfunc (c multiClipboard) isValid(r Register, clipboard string, ncursors int) bool {\n\tcontent := c[r]\n\tif content == nil || len(content) != ncursors {\n\t\treturn false\n\t}\n\n\treturn clipboard == c.getAllText(r)\n}\n\nfunc (c multiClipboard) writeText(text string, r Register, num int, ncursors int) {\n\tcontent := c[r]\n\tif content == nil || len(content) != ncursors {\n\t\tcontent = make([]string, ncursors, ncursors)\n\t\tc[r] = content\n\t}\n\n\tif num >= ncursors {\n\t\treturn\n\t}\n\n\tcontent[num] = text\n}\n\nfunc init() {\n\tmulti = make(multiClipboard)\n}\n"
  },
  {
    "path": "internal/clipboard/terminal.go",
    "content": "package clipboard\n\nimport (\n\t\"errors\"\n\t\"time\"\n\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/tcell/v2\"\n)\n\ntype terminalClipboard struct{}\n\nvar terminal terminalClipboard\n\nfunc (t terminalClipboard) read(reg string) (string, error) {\n\tscreen.Screen.GetClipboard(reg)\n\t// wait at most 200ms for response\n\tfor {\n\t\tselect {\n\t\tcase event := <-screen.Events:\n\t\t\te, ok := event.(*tcell.EventPaste)\n\t\t\tif ok {\n\t\t\t\treturn e.Text(), nil\n\t\t\t}\n\t\tcase <-time.After(200 * time.Millisecond):\n\t\t\treturn \"\", errors.New(\"No clipboard received from terminal\")\n\t\t}\n\t}\n}\n\nfunc (t terminalClipboard) write(text, reg string) error {\n\treturn screen.Screen.SetClipboard(text, reg)\n}\n"
  },
  {
    "path": "internal/config/autosave.go",
    "content": "package config\n\nimport (\n\t\"time\"\n)\n\nvar Autosave chan bool\nvar autotime chan float64\n\nfunc init() {\n\tAutosave = make(chan bool)\n\tautotime = make(chan float64)\n}\n\nfunc SetAutoTime(a float64) {\n\tautotime <- a\n}\n\nfunc StartAutoSave() {\n\tgo func() {\n\t\tvar a float64\n\t\tvar t *time.Timer\n\t\tvar elapsed <-chan time.Time\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase a = <-autotime:\n\t\t\t\tif t != nil {\n\t\t\t\t\tt.Stop()\n\t\t\t\t\tfor len(elapsed) > 0 {\n\t\t\t\t\t\t<-elapsed\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif a > 0 {\n\t\t\t\t\tif t != nil {\n\t\t\t\t\t\tt.Reset(time.Duration(a * float64(time.Second)))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt = time.NewTimer(time.Duration(a * float64(time.Second)))\n\t\t\t\t\t\telapsed = t.C\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase <-elapsed:\n\t\t\t\tif a > 0 {\n\t\t\t\t\tt.Reset(time.Duration(a * float64(time.Second)))\n\t\t\t\t\tAutosave <- true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n}\n"
  },
  {
    "path": "internal/config/colorscheme.go",
    "content": "package config\n\nimport (\n\t\"errors\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/micro-editor/tcell/v2\"\n)\n\n// DefStyle is Micro's default style\nvar DefStyle tcell.Style = tcell.StyleDefault\n\n// Colorscheme is the current colorscheme\nvar Colorscheme map[string]tcell.Style\n\n// GetColor takes in a syntax group and returns the colorscheme's style for that group\nfunc GetColor(color string) tcell.Style {\n\tst := DefStyle\n\tif color == \"\" {\n\t\treturn st\n\t}\n\tgroups := strings.Split(color, \".\")\n\tif len(groups) > 1 {\n\t\tcurGroup := \"\"\n\t\tfor i, g := range groups {\n\t\t\tif i != 0 {\n\t\t\t\tcurGroup += \".\"\n\t\t\t}\n\t\t\tcurGroup += g\n\t\t\tif style, ok := Colorscheme[curGroup]; ok {\n\t\t\t\tst = style\n\t\t\t}\n\t\t}\n\t} else if style, ok := Colorscheme[color]; ok {\n\t\tst = style\n\t} else {\n\t\tst = StringToStyle(color)\n\t}\n\n\treturn st\n}\n\n// ColorschemeExists checks if a given colorscheme exists\nfunc ColorschemeExists(colorschemeName string) bool {\n\treturn FindRuntimeFile(RTColorscheme, colorschemeName) != nil\n}\n\n// InitColorscheme picks and initializes the colorscheme when micro starts\nfunc InitColorscheme() error {\n\tColorscheme = make(map[string]tcell.Style)\n\tDefStyle = tcell.StyleDefault\n\n\tc, err := LoadDefaultColorscheme()\n\tif err == nil {\n\t\tColorscheme = c\n\t} else {\n\t\t// The colorscheme setting seems broken (maybe because we have not validated\n\t\t// it earlier, see comment in verifySetting()). So reset it to the default\n\t\t// colorscheme and try again.\n\t\tGlobalSettings[\"colorscheme\"] = DefaultGlobalOnlySettings[\"colorscheme\"]\n\t\tif c, err2 := LoadDefaultColorscheme(); err2 == nil {\n\t\t\tColorscheme = c\n\t\t}\n\t}\n\n\treturn err\n}\n\n// LoadDefaultColorscheme loads the default colorscheme from $(ConfigDir)/colorschemes\nfunc LoadDefaultColorscheme() (map[string]tcell.Style, error) {\n\tvar parsedColorschemes []string\n\treturn LoadColorscheme(GlobalSettings[\"colorscheme\"].(string), &parsedColorschemes)\n}\n\n// LoadColorscheme loads the given colorscheme from a directory\nfunc LoadColorscheme(colorschemeName string, parsedColorschemes *[]string) (map[string]tcell.Style, error) {\n\tc := make(map[string]tcell.Style)\n\tfile := FindRuntimeFile(RTColorscheme, colorschemeName)\n\tif file == nil {\n\t\treturn c, errors.New(colorschemeName + \" is not a valid colorscheme\")\n\t}\n\tif data, err := file.Data(); err != nil {\n\t\treturn c, errors.New(\"Error loading colorscheme: \" + err.Error())\n\t} else {\n\t\tvar err error\n\t\tc, err = ParseColorscheme(file.Name(), string(data), parsedColorschemes)\n\t\tif err != nil {\n\t\t\treturn c, err\n\t\t}\n\t}\n\treturn c, nil\n}\n\n// ParseColorscheme parses the text definition for a colorscheme and returns the corresponding object\n// Colorschemes are made up of color-link statements linking a color group to a list of colors\n// For example, color-link keyword (blue,red) makes all keywords have a blue foreground and\n// red background\nfunc ParseColorscheme(name string, text string, parsedColorschemes *[]string) (map[string]tcell.Style, error) {\n\tvar err error\n\tcolorParser := regexp.MustCompile(`color-link\\s+(\\S*)\\s+\"(.*)\"`)\n\tincludeParser := regexp.MustCompile(`include\\s+\"(.*)\"`)\n\tlines := strings.Split(text, \"\\n\")\n\tc := make(map[string]tcell.Style)\n\n\tif parsedColorschemes != nil {\n\t\t*parsedColorschemes = append(*parsedColorschemes, name)\n\t}\n\nlineLoop:\n\tfor _, line := range lines {\n\t\tif strings.TrimSpace(line) == \"\" ||\n\t\t\tstrings.TrimSpace(line)[0] == '#' {\n\t\t\t// Ignore this line\n\t\t\tcontinue\n\t\t}\n\n\t\tmatches := includeParser.FindSubmatch([]byte(line))\n\t\tif len(matches) == 2 {\n\t\t\t// support includes only in case parsedColorschemes are given\n\t\t\tif parsedColorschemes != nil {\n\t\t\t\tinclude := string(matches[1])\n\t\t\t\tfor _, name := range *parsedColorschemes {\n\t\t\t\t\t// check for circular includes...\n\t\t\t\t\tif name == include {\n\t\t\t\t\t\t// ...and prevent them\n\t\t\t\t\t\tcontinue lineLoop\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tincludeScheme, err := LoadColorscheme(include, parsedColorschemes)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn c, err\n\t\t\t\t}\n\t\t\t\tfor k, v := range includeScheme {\n\t\t\t\t\tc[k] = v\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tmatches = colorParser.FindSubmatch([]byte(line))\n\t\tif len(matches) == 3 {\n\t\t\tlink := string(matches[1])\n\t\t\tcolors := string(matches[2])\n\n\t\t\tstyle := StringToStyle(colors)\n\t\t\tc[link] = style\n\n\t\t\tif link == \"default\" {\n\t\t\t\tDefStyle = style\n\t\t\t}\n\t\t} else {\n\t\t\terr = errors.New(\"Color-link statement is not valid: \" + line)\n\t\t}\n\t}\n\n\treturn c, err\n}\n\n// StringToStyle returns a style from a string\n// The strings must be in the format \"extra foregroundcolor,backgroundcolor\"\n// The 'extra' can be bold, reverse, italic or underline\nfunc StringToStyle(str string) tcell.Style {\n\tvar fg, bg string\n\tspaceSplit := strings.Split(str, \" \")\n\tsplit := strings.Split(spaceSplit[len(spaceSplit)-1], \",\")\n\tif len(split) > 1 {\n\t\tfg, bg = split[0], split[1]\n\t} else {\n\t\tfg = split[0]\n\t}\n\tfg = strings.TrimSpace(fg)\n\tbg = strings.TrimSpace(bg)\n\n\tvar fgColor, bgColor tcell.Color\n\tvar ok bool\n\tif fg == \"\" || fg == \"default\" {\n\t\tfgColor, _, _ = DefStyle.Decompose()\n\t} else {\n\t\tfgColor, ok = StringToColor(fg)\n\t\tif !ok {\n\t\t\tfgColor, _, _ = DefStyle.Decompose()\n\t\t}\n\t}\n\tif bg == \"\" || bg == \"default\" {\n\t\t_, bgColor, _ = DefStyle.Decompose()\n\t} else {\n\t\tbgColor, ok = StringToColor(bg)\n\t\tif !ok {\n\t\t\t_, bgColor, _ = DefStyle.Decompose()\n\t\t}\n\t}\n\n\tstyle := DefStyle.Foreground(fgColor).Background(bgColor)\n\tif strings.Contains(str, \"bold\") {\n\t\tstyle = style.Bold(true)\n\t}\n\tif strings.Contains(str, \"italic\") {\n\t\tstyle = style.Italic(true)\n\t}\n\tif strings.Contains(str, \"reverse\") {\n\t\tstyle = style.Reverse(true)\n\t}\n\tif strings.Contains(str, \"underline\") {\n\t\tstyle = style.Underline(true)\n\t}\n\treturn style\n}\n\n// StringToColor returns a tcell color from a string representation of a color\n// We accept either bright... or light... to mean the brighter version of a color\nfunc StringToColor(str string) (tcell.Color, bool) {\n\tswitch str {\n\tcase \"black\":\n\t\treturn tcell.ColorBlack, true\n\tcase \"red\":\n\t\treturn tcell.ColorMaroon, true\n\tcase \"green\":\n\t\treturn tcell.ColorGreen, true\n\tcase \"yellow\":\n\t\treturn tcell.ColorOlive, true\n\tcase \"blue\":\n\t\treturn tcell.ColorNavy, true\n\tcase \"magenta\":\n\t\treturn tcell.ColorPurple, true\n\tcase \"cyan\":\n\t\treturn tcell.ColorTeal, true\n\tcase \"white\":\n\t\treturn tcell.ColorSilver, true\n\tcase \"brightblack\", \"lightblack\":\n\t\treturn tcell.ColorGray, true\n\tcase \"brightred\", \"lightred\":\n\t\treturn tcell.ColorRed, true\n\tcase \"brightgreen\", \"lightgreen\":\n\t\treturn tcell.ColorLime, true\n\tcase \"brightyellow\", \"lightyellow\":\n\t\treturn tcell.ColorYellow, true\n\tcase \"brightblue\", \"lightblue\":\n\t\treturn tcell.ColorBlue, true\n\tcase \"brightmagenta\", \"lightmagenta\":\n\t\treturn tcell.ColorFuchsia, true\n\tcase \"brightcyan\", \"lightcyan\":\n\t\treturn tcell.ColorAqua, true\n\tcase \"brightwhite\", \"lightwhite\":\n\t\treturn tcell.ColorWhite, true\n\tcase \"default\":\n\t\treturn tcell.ColorDefault, true\n\tdefault:\n\t\t// Check if this is a 256 color\n\t\tif num, err := strconv.Atoi(str); err == nil {\n\t\t\treturn GetColor256(num), true\n\t\t}\n\t\t// Check if this is a truecolor hex value\n\t\tif len(str) == 7 && str[0] == '#' {\n\t\t\treturn tcell.GetColor(str), true\n\t\t}\n\t\treturn tcell.ColorDefault, false\n\t}\n}\n\n// GetColor256 returns the tcell color for a number between 0 and 255\nfunc GetColor256(color int) tcell.Color {\n\tif color == 0 {\n\t\treturn tcell.ColorDefault\n\t}\n\treturn tcell.PaletteColor(color)\n}\n"
  },
  {
    "path": "internal/config/colorscheme_test.go",
    "content": "package config\n\nimport (\n\t\"testing\"\n\n\t\"github.com/micro-editor/tcell/v2\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestSimpleStringToStyle(t *testing.T) {\n\ts := StringToStyle(\"lightblue,magenta\")\n\n\tfg, bg, _ := s.Decompose()\n\n\tassert.Equal(t, tcell.ColorBlue, fg)\n\tassert.Equal(t, tcell.ColorPurple, bg)\n}\n\nfunc TestAttributeStringToStyle(t *testing.T) {\n\ts := StringToStyle(\"bold cyan,brightcyan\")\n\n\tfg, bg, attr := s.Decompose()\n\n\tassert.Equal(t, tcell.ColorTeal, fg)\n\tassert.Equal(t, tcell.ColorAqua, bg)\n\tassert.NotEqual(t, 0, attr&tcell.AttrBold)\n}\n\nfunc TestMultiAttributesStringToStyle(t *testing.T) {\n\ts := StringToStyle(\"bold italic underline cyan,brightcyan\")\n\n\tfg, bg, attr := s.Decompose()\n\n\tassert.Equal(t, tcell.ColorTeal, fg)\n\tassert.Equal(t, tcell.ColorAqua, bg)\n\tassert.NotEqual(t, 0, attr&tcell.AttrBold)\n\tassert.NotEqual(t, 0, attr&tcell.AttrItalic)\n\tassert.NotEqual(t, 0, attr&tcell.AttrUnderline)\n}\n\nfunc TestColor256StringToStyle(t *testing.T) {\n\ts := StringToStyle(\"128,60\")\n\n\tfg, bg, _ := s.Decompose()\n\n\tassert.Equal(t, tcell.Color128, fg)\n\tassert.Equal(t, tcell.Color60, bg)\n}\n\nfunc TestColorHexStringToStyle(t *testing.T) {\n\ts := StringToStyle(\"#deadbe,#ef1234\")\n\n\tfg, bg, _ := s.Decompose()\n\n\tassert.Equal(t, tcell.NewRGBColor(222, 173, 190), fg)\n\tassert.Equal(t, tcell.NewRGBColor(239, 18, 52), bg)\n}\n\nfunc TestColorschemeParser(t *testing.T) {\n\ttestColorscheme := `color-link default \"#F8F8F2,#282828\"\ncolor-link comment \"#75715E,#282828\"\n# comment\ncolor-link identifier \"#66D9EF,#282828\" #comment\ncolor-link constant \"#AE81FF,#282828\"\ncolor-link constant.string \"#E6DB74,#282828\"\ncolor-link constant.string.char \"#BDE6AD,#282828\"`\n\n\tc, err := ParseColorscheme(\"testColorscheme\", testColorscheme, nil)\n\tassert.Nil(t, err)\n\n\tfg, bg, _ := c[\"comment\"].Decompose()\n\tassert.Equal(t, tcell.NewRGBColor(117, 113, 94), fg)\n\tassert.Equal(t, tcell.NewRGBColor(40, 40, 40), bg)\n}\n"
  },
  {
    "path": "internal/config/config.go",
    "content": "package config\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"path/filepath\"\n\n\thomedir \"github.com/mitchellh/go-homedir\"\n)\n\nvar ConfigDir string\n\n// InitConfigDir finds the configuration directory for micro according to the XDG spec.\n// If no directory is found, it creates one.\nfunc InitConfigDir(flagConfigDir string) error {\n\tvar e error\n\n\tmicroHome := os.Getenv(\"MICRO_CONFIG_HOME\")\n\tif microHome == \"\" {\n\t\t// The user has not set $MICRO_CONFIG_HOME so we'll try $XDG_CONFIG_HOME\n\t\txdgHome := os.Getenv(\"XDG_CONFIG_HOME\")\n\t\tif xdgHome == \"\" {\n\t\t\t// The user has not set $XDG_CONFIG_HOME so we should act like it was set to ~/.config\n\t\t\thome, err := homedir.Dir()\n\t\t\tif err != nil {\n\t\t\t\treturn errors.New(\"Error finding your home directory\\nCan't load config files: \" + err.Error())\n\t\t\t}\n\t\t\txdgHome = filepath.Join(home, \".config\")\n\t\t}\n\n\t\tmicroHome = filepath.Join(xdgHome, \"micro\")\n\t}\n\tConfigDir = microHome\n\n\tif len(flagConfigDir) > 0 {\n\t\tif _, err := os.Stat(flagConfigDir); os.IsNotExist(err) {\n\t\t\te = errors.New(\"Error: \" + flagConfigDir + \" does not exist. Defaulting to \" + ConfigDir + \".\")\n\t\t} else {\n\t\t\tConfigDir = flagConfigDir\n\t\t\treturn nil\n\t\t}\n\t}\n\n\t// Create micro config home directory if it does not exist\n\t// This creates parent directories and does nothing if it already exists\n\terr := os.MkdirAll(ConfigDir, os.ModePerm)\n\tif err != nil {\n\t\treturn errors.New(\"Error creating configuration directory: \" + err.Error())\n\t}\n\n\treturn e\n}\n"
  },
  {
    "path": "internal/config/globals.go",
    "content": "package config\n\nconst (\n\tDoubleClickThreshold = 400 // How many milliseconds to wait before a second click is not a double click\n)\n\nvar Bindings map[string]map[string]string\n\nfunc init() {\n\tBindings = map[string]map[string]string{\n\t\t\"command\":  make(map[string]string),\n\t\t\"buffer\":   make(map[string]string),\n\t\t\"terminal\": make(map[string]string),\n\t}\n}\n"
  },
  {
    "path": "internal/config/plugin.go",
    "content": "package config\n\nimport (\n\t\"errors\"\n\t\"log\"\n\n\tulua \"github.com/micro-editor/micro/v2/internal/lua\"\n\tlua \"github.com/yuin/gopher-lua\"\n)\n\n// ErrNoSuchFunction is returned when Call is executed on a function that does not exist\nvar ErrNoSuchFunction = errors.New(\"No such function exists\")\n\n// LoadAllPlugins loads all detected plugins (in runtime/plugins and ConfigDir/plugins)\nfunc LoadAllPlugins() error {\n\tvar reterr error\n\tfor _, p := range Plugins {\n\t\terr := p.Load()\n\t\tif err != nil {\n\t\t\treterr = err\n\t\t}\n\t}\n\treturn reterr\n}\n\n// RunPluginFn runs a given function in all plugins\n// returns an error if any of the plugins had an error\nfunc RunPluginFn(fn string, args ...lua.LValue) error {\n\tvar reterr error\n\tfor _, p := range Plugins {\n\t\tif !p.IsLoaded() {\n\t\t\tcontinue\n\t\t}\n\t\t_, err := p.Call(fn, args...)\n\t\tif err != nil && err != ErrNoSuchFunction {\n\t\t\treterr = errors.New(\"Plugin \" + p.Name + \": \" + err.Error())\n\t\t}\n\t}\n\treturn reterr\n}\n\n// RunPluginFnBool runs a function in all plugins and returns\n// false if any one of them returned false\n// also returns an error if any of the plugins had an error\nfunc RunPluginFnBool(settings map[string]any, fn string, args ...lua.LValue) (bool, error) {\n\tvar reterr error\n\tretbool := true\n\tfor _, p := range Plugins {\n\t\tif !p.IsLoaded() || (settings != nil && settings[p.Name] == false) {\n\t\t\tcontinue\n\t\t}\n\t\tval, err := p.Call(fn, args...)\n\t\tif err == ErrNoSuchFunction {\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\treterr = errors.New(\"Plugin \" + p.Name + \": \" + err.Error())\n\t\t\tcontinue\n\t\t}\n\t\tif v, ok := val.(lua.LBool); ok {\n\t\t\tretbool = retbool && bool(v)\n\t\t}\n\t}\n\treturn retbool, reterr\n}\n\n// Plugin stores information about the source files/info for a plugin\ntype Plugin struct {\n\tDirName string        // name of plugin folder\n\tName    string        // name of plugin\n\tInfo    *PluginInfo   // json file containing info\n\tSrcs    []RuntimeFile // lua files\n\tLoaded  bool\n\tBuiltin bool\n}\n\n// IsLoaded returns if a plugin is enabled\nfunc (p *Plugin) IsLoaded() bool {\n\tif v, ok := GlobalSettings[p.Name]; ok {\n\t\treturn v.(bool) && p.Loaded\n\t}\n\treturn true\n}\n\n// Plugins is a list of all detected plugins (enabled or disabled)\nvar Plugins []*Plugin\n\n// Load creates an option for the plugin and runs all source files\nfunc (p *Plugin) Load() error {\n\tif v, ok := GlobalSettings[p.Name]; ok && !v.(bool) {\n\t\treturn nil\n\t}\n\tfor _, f := range p.Srcs {\n\t\tdat, err := f.Data()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = ulua.LoadFile(p.Name, f.Name(), dat)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tp.Loaded = true\n\tRegisterCommonOption(p.Name, true)\n\treturn nil\n}\n\n// Call calls a given function in this plugin\nfunc (p *Plugin) Call(fn string, args ...lua.LValue) (lua.LValue, error) {\n\tplug := ulua.L.GetGlobal(p.Name)\n\tif plug == lua.LNil {\n\t\tlog.Println(\"Plugin does not exist:\", p.Name, \"at\", p.DirName, \":\", p)\n\t\treturn nil, nil\n\t}\n\tluafn := ulua.L.GetField(plug, fn)\n\tif luafn == lua.LNil {\n\t\treturn nil, ErrNoSuchFunction\n\t}\n\terr := ulua.L.CallByParam(lua.P{\n\t\tFn:      luafn,\n\t\tNRet:    1,\n\t\tProtect: true,\n\t}, args...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tret := ulua.L.Get(-1)\n\tulua.L.Pop(1)\n\treturn ret, nil\n}\n\n// FindPlugin returns the plugin with the given name\nfunc FindPlugin(name string) *Plugin {\n\tvar pl *Plugin\n\tfor _, p := range Plugins {\n\t\tif !p.IsLoaded() {\n\t\t\tcontinue\n\t\t}\n\t\tif p.Name == name {\n\t\t\tpl = p\n\t\t\tbreak\n\t\t}\n\t}\n\treturn pl\n}\n"
  },
  {
    "path": "internal/config/plugin_installer.go",
    "content": "package config\n\nimport (\n\t\"archive/zip\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/blang/semver\"\n\t\"github.com/micro-editor/json5\"\n\tulua \"github.com/micro-editor/micro/v2/internal/lua\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n\tlua \"github.com/yuin/gopher-lua\"\n)\n\nvar (\n\tallPluginPackages PluginPackages\n)\n\n// CorePluginName is a plugin dependency name for the micro core.\nconst CorePluginName = \"micro\"\n\n// PluginChannel contains an url to a json list of PluginRepository\ntype PluginChannel string\n\n// PluginChannels is a slice of PluginChannel\ntype PluginChannels []PluginChannel\n\n// PluginRepository contains an url to json file containing PluginPackages\ntype PluginRepository string\n\n// PluginPackage contains the meta-data of a plugin and all available versions\ntype PluginPackage struct {\n\tName        string\n\tDescription string\n\tAuthor      string\n\tTags        []string\n\tVersions    PluginVersions\n\tBuiltin     bool\n}\n\n// PluginPackages is a list of PluginPackage instances.\ntype PluginPackages []*PluginPackage\n\n// PluginVersion descripes a version of a PluginPackage. Containing a version, download url and also dependencies.\ntype PluginVersion struct {\n\tpack    *PluginPackage\n\tVersion semver.Version\n\tUrl     string\n\tRequire PluginDependencies\n}\n\nfunc (pv *PluginVersion) Pack() *PluginPackage {\n\treturn pv.pack\n}\n\n// PluginVersions is a slice of PluginVersion\ntype PluginVersions []*PluginVersion\n\n// PluginDependency descripes a dependency to another plugin or micro itself.\ntype PluginDependency struct {\n\tName  string\n\tRange semver.Range\n}\n\n// PluginDependencies is a slice of PluginDependency\ntype PluginDependencies []*PluginDependency\n\nfunc (pp *PluginPackage) String() string {\n\tbuf := new(bytes.Buffer)\n\tbuf.WriteString(\"Plugin: \")\n\tbuf.WriteString(pp.Name)\n\tif pp.Builtin {\n\t\tbuf.WriteString(\" (built-in)\")\n\t}\n\tbuf.WriteRune('\\n')\n\tif pp.Author != \"\" {\n\t\tbuf.WriteString(\"Author: \")\n\t\tbuf.WriteString(pp.Author)\n\t\tbuf.WriteRune('\\n')\n\t}\n\tif pp.Description != \"\" {\n\t\tbuf.WriteRune('\\n')\n\t\tbuf.WriteString(pp.Description)\n\t}\n\treturn buf.String()\n}\n\nfunc fetchAllSources(count int, fetcher func(i int) PluginPackages) PluginPackages {\n\twgQuery := new(sync.WaitGroup)\n\twgQuery.Add(count)\n\n\tresults := make(chan PluginPackages)\n\n\twgDone := new(sync.WaitGroup)\n\twgDone.Add(1)\n\tvar packages PluginPackages\n\tfor i := 0; i < count; i++ {\n\t\tgo func(i int) {\n\t\t\tresults <- fetcher(i)\n\t\t\twgQuery.Done()\n\t\t}(i)\n\t}\n\tgo func() {\n\t\tpackages = make(PluginPackages, 0)\n\t\tfor res := range results {\n\t\t\tpackages = append(packages, res...)\n\t\t}\n\t\twgDone.Done()\n\t}()\n\twgQuery.Wait()\n\tclose(results)\n\twgDone.Wait()\n\treturn packages\n}\n\n// Fetch retrieves all available PluginPackages from the given channels\nfunc (pc PluginChannels) Fetch(out io.Writer) PluginPackages {\n\treturn fetchAllSources(len(pc), func(i int) PluginPackages {\n\t\treturn pc[i].Fetch(out)\n\t})\n}\n\n// Fetch retrieves all available PluginPackages from the given channel\nfunc (pc PluginChannel) Fetch(out io.Writer) PluginPackages {\n\tresp, err := http.Get(string(pc))\n\tif err != nil {\n\t\tfmt.Fprintln(out, \"Failed to query plugin channel:\\n\", err)\n\t\treturn PluginPackages{}\n\t}\n\tdefer resp.Body.Close()\n\tdecoder := json5.NewDecoder(resp.Body)\n\n\tvar repositories []PluginRepository\n\tif err := decoder.Decode(&repositories); err != nil {\n\t\tfmt.Fprintln(out, \"Failed to decode channel data:\\n\", err)\n\t\treturn PluginPackages{}\n\t}\n\treturn fetchAllSources(len(repositories), func(i int) PluginPackages {\n\t\treturn repositories[i].Fetch(out)\n\t})\n}\n\n// Fetch retrieves all available PluginPackages from the given repository\nfunc (pr PluginRepository) Fetch(out io.Writer) PluginPackages {\n\tresp, err := http.Get(string(pr))\n\tif err != nil {\n\t\tfmt.Fprintln(out, \"Failed to query plugin repository:\\n\", err)\n\t\treturn PluginPackages{}\n\t}\n\tdefer resp.Body.Close()\n\tdecoder := json5.NewDecoder(resp.Body)\n\n\tvar plugins PluginPackages\n\tif err := decoder.Decode(&plugins); err != nil {\n\t\tfmt.Fprintln(out, \"Failed to decode repository data:\\n\", err)\n\t\treturn PluginPackages{}\n\t}\n\tif len(plugins) > 0 {\n\t\treturn PluginPackages{plugins[0]}\n\t}\n\treturn nil\n\t// return plugins\n}\n\n// UnmarshalJSON unmarshals raw json to a PluginVersion\nfunc (pv *PluginVersion) UnmarshalJSON(data []byte) error {\n\tvar values struct {\n\t\tVersion semver.Version\n\t\tUrl     string\n\t\tRequire map[string]string\n\t}\n\n\tif err := json5.Unmarshal(data, &values); err != nil {\n\t\treturn err\n\t}\n\tpv.Version = values.Version\n\tpv.Url = values.Url\n\tpv.Require = make(PluginDependencies, 0)\n\n\tfor k, v := range values.Require {\n\t\t// don't add the dependency if it's the core and\n\t\t// we have a unknown version number.\n\t\t// in that case just accept that dependency (which equals to not adding it.)\n\t\tif k != CorePluginName || !isUnknownCoreVersion() {\n\t\t\tif vRange, err := semver.ParseRange(v); err == nil {\n\t\t\t\tpv.Require = append(pv.Require, &PluginDependency{k, vRange})\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// UnmarshalJSON unmarshals raw json to a PluginPackage\nfunc (pp *PluginPackage) UnmarshalJSON(data []byte) error {\n\tvar values struct {\n\t\tName        string\n\t\tDescription string\n\t\tAuthor      string\n\t\tTags        []string\n\t\tVersions    PluginVersions\n\t}\n\tif err := json5.Unmarshal(data, &values); err != nil {\n\t\treturn err\n\t}\n\tpp.Name = values.Name\n\tpp.Description = values.Description\n\tpp.Author = values.Author\n\tpp.Tags = values.Tags\n\tpp.Versions = values.Versions\n\tfor _, v := range pp.Versions {\n\t\tv.pack = pp\n\t}\n\treturn nil\n}\n\n// GetAllPluginPackages gets all PluginPackages which may be available.\nfunc GetAllPluginPackages(out io.Writer) PluginPackages {\n\tif allPluginPackages == nil {\n\t\tgetOption := func(name string) []string {\n\t\t\tdata := GetGlobalOption(name)\n\t\t\tif strs, ok := data.([]string); ok {\n\t\t\t\treturn strs\n\t\t\t}\n\t\t\tif ifs, ok := data.([]any); ok {\n\t\t\t\tresult := make([]string, len(ifs))\n\t\t\t\tfor i, urlIf := range ifs {\n\t\t\t\t\tif url, ok := urlIf.(string); ok {\n\t\t\t\t\t\tresult[i] = url\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn result\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\n\t\tchannels := PluginChannels{}\n\t\tfor _, url := range getOption(\"pluginchannels\") {\n\t\t\tchannels = append(channels, PluginChannel(url))\n\t\t}\n\t\trepos := []PluginRepository{}\n\t\tfor _, url := range getOption(\"pluginrepos\") {\n\t\t\trepos = append(repos, PluginRepository(url))\n\t\t}\n\t\tallPluginPackages = fetchAllSources(len(repos)+1, func(i int) PluginPackages {\n\t\t\tif i == 0 {\n\t\t\t\treturn channels.Fetch(out)\n\t\t\t}\n\t\t\treturn repos[i-1].Fetch(out)\n\t\t})\n\t}\n\treturn allPluginPackages\n}\n\nfunc (pv PluginVersions) find(ppName string) *PluginVersion {\n\tfor _, v := range pv {\n\t\tif v.pack.Name == ppName {\n\t\t\treturn v\n\t\t}\n\t}\n\treturn nil\n}\n\n// Len returns the number of pluginversions in this slice\nfunc (pv PluginVersions) Len() int {\n\treturn len(pv)\n}\n\n// Swap two entries of the slice\nfunc (pv PluginVersions) Swap(i, j int) {\n\tpv[i], pv[j] = pv[j], pv[i]\n}\n\n// Less returns true if the version at position i is greater then the version at position j (used for sorting)\nfunc (pv PluginVersions) Less(i, j int) bool {\n\treturn pv[i].Version.GT(pv[j].Version)\n}\n\n// Match returns true if the package matches a given search text\nfunc (pp PluginPackage) Match(text string) bool {\n\ttext = strings.ToLower(text)\n\tfor _, t := range pp.Tags {\n\t\tif strings.ToLower(t) == text {\n\t\t\treturn true\n\t\t}\n\t}\n\tif strings.Contains(strings.ToLower(pp.Name), text) {\n\t\treturn true\n\t}\n\n\tif strings.Contains(strings.ToLower(pp.Description), text) {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\n// IsInstallable returns true if the package can be installed.\nfunc (pp PluginPackage) IsInstallable(out io.Writer) error {\n\t_, err := GetAllPluginPackages(out).Resolve(GetInstalledVersions(true), PluginDependencies{\n\t\t&PluginDependency{\n\t\t\tName:  pp.Name,\n\t\t\tRange: semver.Range(func(v semver.Version) bool { return true }),\n\t\t}})\n\treturn err\n}\n\n// SearchPlugin retrieves a list of all PluginPackages which match the given search text and\n// could be or are already installed\nfunc SearchPlugin(out io.Writer, texts []string) (plugins PluginPackages) {\n\tplugins = make(PluginPackages, 0)\n\npluginLoop:\n\tfor _, pp := range GetAllPluginPackages(out) {\n\t\tfor _, text := range texts {\n\t\t\tif !pp.Match(text) {\n\t\t\t\tcontinue pluginLoop\n\t\t\t}\n\t\t}\n\n\t\tif err := pp.IsInstallable(out); err == nil {\n\t\t\tplugins = append(plugins, pp)\n\t\t}\n\t}\n\treturn\n}\n\nfunc isUnknownCoreVersion() bool {\n\t_, err := semver.ParseTolerant(util.Version)\n\treturn err != nil\n}\n\nfunc newStaticPluginVersion(name, version string, builtin bool) *PluginVersion {\n\tvers, err := semver.ParseTolerant(version)\n\n\tif err != nil {\n\t\tif vers, err = semver.ParseTolerant(\"0.0.0-\" + version); err != nil {\n\t\t\tvers = semver.MustParse(\"0.0.0-unknown\")\n\t\t}\n\t}\n\tpl := &PluginPackage{\n\t\tName:    name,\n\t\tBuiltin: builtin,\n\t}\n\tpv := &PluginVersion{\n\t\tpack:    pl,\n\t\tVersion: vers,\n\t}\n\tpl.Versions = PluginVersions{pv}\n\treturn pv\n}\n\n// GetInstalledVersions returns a list of all currently installed plugins including an entry for\n// micro itself. This can be used to resolve dependencies.\nfunc GetInstalledVersions(withCore bool) PluginVersions {\n\tresult := PluginVersions{}\n\tif withCore {\n\t\tresult = append(result, newStaticPluginVersion(CorePluginName, util.Version, true))\n\t}\n\n\tfor _, p := range Plugins {\n\t\tif !p.IsLoaded() {\n\t\t\tcontinue\n\t\t}\n\t\tversion := GetInstalledPluginVersion(p.Name)\n\t\tif pv := newStaticPluginVersion(p.Name, version, p.Builtin); pv != nil {\n\t\t\tresult = append(result, pv)\n\t\t}\n\t}\n\n\treturn result\n}\n\n// GetInstalledPluginVersion returns the string of the exported VERSION variable of a loaded plugin\nfunc GetInstalledPluginVersion(name string) string {\n\tplugin := ulua.L.GetGlobal(name)\n\tif plugin != lua.LNil {\n\t\tversion := ulua.L.GetField(plugin, \"VERSION\")\n\t\tif str, ok := version.(lua.LString); ok {\n\t\t\treturn string(str)\n\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// DownloadAndInstall downloads and installs the given plugin and version\nfunc (pv *PluginVersion) DownloadAndInstall(out io.Writer) error {\n\tfmt.Fprintf(out, \"Downloading %q (%s) from %q\\n\", pv.pack.Name, pv.Version, pv.Url)\n\tresp, err := http.Get(pv.Url)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close()\n\tdata, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn err\n\t}\n\tzipbuf := bytes.NewReader(data)\n\tz, err := zip.NewReader(zipbuf, zipbuf.Size())\n\tif err != nil {\n\t\treturn err\n\t}\n\ttargetDir := filepath.Join(ConfigDir, \"plug\", pv.pack.Name)\n\tdirPerm := os.FileMode(0755)\n\tif err = os.MkdirAll(targetDir, dirPerm); err != nil {\n\t\treturn err\n\t}\n\n\t// Check if all files in zip are in the same directory.\n\t// this might be the case if the plugin zip contains the whole plugin dir\n\t// instead of its content.\n\tvar prefix string\n\tallPrefixed := false\n\tfor i, f := range z.File {\n\t\tparts := strings.Split(f.Name, \"/\")\n\t\tif i == 0 {\n\t\t\tprefix = parts[0]\n\t\t} else if parts[0] != prefix {\n\t\t\tallPrefixed = false\n\t\t\tbreak\n\t\t} else {\n\t\t\t// switch to true since we have at least a second file\n\t\t\tallPrefixed = true\n\t\t}\n\t}\n\n\t// Install files and directory's\n\tfor _, f := range z.File {\n\t\tparts := strings.Split(f.Name, \"/\")\n\t\tif allPrefixed {\n\t\t\tparts = parts[1:]\n\t\t}\n\n\t\ttargetName := filepath.Join(targetDir, filepath.Join(parts...))\n\t\tif f.FileInfo().IsDir() {\n\t\t\tif err := os.MkdirAll(targetName, dirPerm); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\tbasepath := filepath.Dir(targetName)\n\n\t\t\tif err := os.MkdirAll(basepath, dirPerm); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tcontent, err := f.Open()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer content.Close()\n\t\t\ttarget, err := os.Create(targetName)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer target.Close()\n\t\t\tif _, err = io.Copy(target, content); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (pl PluginPackages) Get(name string) *PluginPackage {\n\tfor _, p := range pl {\n\t\tif p.Name == name {\n\t\t\treturn p\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (pl PluginPackages) GetAllVersions(name string) PluginVersions {\n\tresult := make(PluginVersions, 0)\n\tp := pl.Get(name)\n\tif p != nil {\n\t\tresult = append(result, p.Versions...)\n\t}\n\treturn result\n}\n\nfunc (req PluginDependencies) Join(other PluginDependencies) PluginDependencies {\n\tm := make(map[string]*PluginDependency)\n\tfor _, r := range req {\n\t\tm[r.Name] = r\n\t}\n\tfor _, o := range other {\n\t\tcur, ok := m[o.Name]\n\t\tif ok {\n\t\t\tm[o.Name] = &PluginDependency{\n\t\t\t\to.Name,\n\t\t\t\to.Range.AND(cur.Range),\n\t\t\t}\n\t\t} else {\n\t\t\tm[o.Name] = o\n\t\t}\n\t}\n\tresult := make(PluginDependencies, 0, len(m))\n\tfor _, v := range m {\n\t\tresult = append(result, v)\n\t}\n\treturn result\n}\n\n// Resolve resolves dependencies between different plugins\nfunc (all PluginPackages) Resolve(selectedVersions PluginVersions, open PluginDependencies) (PluginVersions, error) {\n\tif len(open) == 0 {\n\t\treturn selectedVersions, nil\n\t}\n\tcurrentRequirement, stillOpen := open[0], open[1:]\n\tif currentRequirement != nil {\n\t\tif selVersion := selectedVersions.find(currentRequirement.Name); selVersion != nil {\n\t\t\tif currentRequirement.Range(selVersion.Version) {\n\t\t\t\treturn all.Resolve(selectedVersions, stillOpen)\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\"unable to find a matching version for \\\"%s\\\"\", currentRequirement.Name)\n\t\t}\n\t\tavailableVersions := all.GetAllVersions(currentRequirement.Name)\n\t\tsort.Sort(availableVersions)\n\n\t\tfor _, version := range availableVersions {\n\t\t\tif currentRequirement.Range(version.Version) {\n\t\t\t\tresolved, err := all.Resolve(append(selectedVersions, version), stillOpen.Join(version.Require))\n\n\t\t\t\tif err == nil {\n\t\t\t\t\treturn resolved, nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn nil, fmt.Errorf(\"unable to find a matching version for \\\"%s\\\"\", currentRequirement.Name)\n\t}\n\treturn selectedVersions, nil\n}\n\nfunc (pv PluginVersions) install(out io.Writer) {\n\tanyInstalled := false\n\tcurrentlyInstalled := GetInstalledVersions(true)\n\n\tfor _, sel := range pv {\n\t\tif sel.pack.Name != CorePluginName {\n\t\t\tshouldInstall := true\n\t\t\tif pv := currentlyInstalled.find(sel.pack.Name); pv != nil {\n\t\t\t\tif pv.Version.NE(sel.Version) {\n\t\t\t\t\tfmt.Fprintln(out, \"Uninstalling\", sel.pack.Name)\n\t\t\t\t\tUninstallPlugin(out, sel.pack.Name)\n\t\t\t\t} else {\n\t\t\t\t\tshouldInstall = false\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif shouldInstall {\n\t\t\t\tif err := sel.DownloadAndInstall(out); err != nil {\n\t\t\t\t\tfmt.Fprintln(out, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tanyInstalled = true\n\t\t\t}\n\t\t}\n\t}\n\tif anyInstalled {\n\t\tfmt.Fprintln(out, \"One or more plugins installed.\")\n\t} else {\n\t\tfmt.Fprintln(out, \"Nothing to install / update\")\n\t}\n}\n\n// UninstallPlugin deletes the plugin folder of the given plugin\nfunc UninstallPlugin(out io.Writer, name string) {\n\tfor _, p := range Plugins {\n\t\tif !p.IsLoaded() {\n\t\t\tcontinue\n\t\t}\n\t\tif p.Name == name {\n\t\t\tp.Loaded = false\n\t\t\tif err := os.RemoveAll(filepath.Join(ConfigDir, \"plug\", p.DirName)); err != nil {\n\t\t\t\tfmt.Fprintln(out, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n}\n\n// Install installs the plugin\nfunc (pl PluginPackage) Install(out io.Writer) {\n\tselected, err := GetAllPluginPackages(out).Resolve(GetInstalledVersions(true), PluginDependencies{\n\t\t&PluginDependency{\n\t\t\tName:  pl.Name,\n\t\t\tRange: semver.Range(func(v semver.Version) bool { return true }),\n\t\t}})\n\tif err != nil {\n\t\tfmt.Fprintln(out, err)\n\t\treturn\n\t}\n\tselected.install(out)\n}\n\n// UpdatePlugins updates the given plugins\nfunc UpdatePlugins(out io.Writer, plugins []string) {\n\t// if no plugins are specified, update all installed plugins.\n\tif len(plugins) == 0 {\n\t\tfor _, p := range Plugins {\n\t\t\tif !p.IsLoaded() || p.Builtin || p.Name == \"initlua\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tplugins = append(plugins, p.Name)\n\t\t}\n\t}\n\n\tfmt.Fprintln(out, \"Checking for plugin updates\")\n\tmicroVersion := PluginVersions{\n\t\tnewStaticPluginVersion(CorePluginName, util.Version, true),\n\t}\n\n\tvar updates = make(PluginDependencies, 0)\n\tfor _, name := range plugins {\n\t\tpv := GetInstalledPluginVersion(name)\n\t\tr, err := semver.ParseRange(\">=\" + pv) // Try to get newer versions.\n\t\tif err == nil {\n\t\t\tupdates = append(updates, &PluginDependency{\n\t\t\t\tName:  name,\n\t\t\t\tRange: r,\n\t\t\t})\n\t\t}\n\t}\n\n\tselected, err := GetAllPluginPackages(out).Resolve(microVersion, updates)\n\tif err != nil {\n\t\tfmt.Fprintln(out, err)\n\t\treturn\n\t}\n\tselected.install(out)\n}\n\nfunc PluginCommand(out io.Writer, cmd string, args []string) {\n\tswitch cmd {\n\tcase \"install\":\n\t\tinstalledVersions := GetInstalledVersions(false)\n\t\tfor _, plugin := range args {\n\t\t\tpp := GetAllPluginPackages(out).Get(plugin)\n\t\t\tif pp == nil {\n\t\t\t\tfmt.Fprintln(out, \"Unknown plugin \\\"\"+plugin+\"\\\"\")\n\t\t\t} else if err := pp.IsInstallable(out); err != nil {\n\t\t\t\tfmt.Fprintln(out, \"Error installing \"+plugin+\": \", err)\n\t\t\t} else {\n\t\t\t\tfor _, installed := range installedVersions {\n\t\t\t\t\tif pp.Name == installed.Pack().Name {\n\t\t\t\t\t\tif pp.Versions[0].Version.Compare(installed.Version) == 1 {\n\t\t\t\t\t\t\tfmt.Fprintln(out, pp.Name, \"is already installed but out-of-date: use 'plugin update \"+pp.Name+\"' to update\")\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfmt.Fprintln(out, pp.Name, \"is already installed\")\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpp.Install(out)\n\t\t\t}\n\t\t}\n\n\tcase \"remove\":\n\t\tremoved := \"\"\n\t\tfor _, plugin := range args {\n\t\t\tif plugin == \"initlua\" {\n\t\t\t\tfmt.Fprintln(out, \"initlua cannot be removed, but can be disabled via settings.\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// check if the plugin exists.\n\t\t\tfor _, p := range Plugins {\n\t\t\t\tif p.Name == plugin && p.Builtin {\n\t\t\t\t\tfmt.Fprintln(out, p.Name, \"is a built-in plugin which cannot be removed, but can be disabled via settings.\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif p.Name == plugin {\n\t\t\t\t\tUninstallPlugin(out, plugin)\n\t\t\t\t\tremoved += plugin + \" \"\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tremoved = strings.TrimSpace(removed)\n\t\tif removed != \"\" {\n\t\t\tfmt.Fprintln(out, \"Removed\", removed)\n\t\t} else {\n\t\t\tfmt.Fprintln(out, \"No plugins removed\")\n\t\t}\n\tcase \"update\":\n\t\tUpdatePlugins(out, args)\n\tcase \"list\":\n\t\tplugins := GetInstalledVersions(false)\n\t\tfmt.Fprintln(out, \"The following plugins are currently installed:\")\n\t\tfor _, p := range plugins {\n\t\t\tif p.Pack().Name == \"initlua\" {\n\t\t\t\tfmt.Fprintf(out, \"%s\\n\", \"initlua\")\n\t\t\t} else if p.Pack().Builtin {\n\t\t\t\tfmt.Fprintf(out, \"%s (built-in)\\n\", p.Pack().Name)\n\t\t\t} else {\n\t\t\t\tfmt.Fprintf(out, \"%s (%s)\\n\", p.Pack().Name, p.Version)\n\t\t\t}\n\t\t}\n\tcase \"search\":\n\t\tplugins := SearchPlugin(out, args)\n\t\tfmt.Fprintln(out, len(plugins), \"plugins found\")\n\t\tfor _, p := range plugins {\n\t\t\tfmt.Fprintln(out, \"----------------\")\n\t\t\tfmt.Fprintln(out, p.String())\n\t\t}\n\t\tfmt.Fprintln(out, \"----------------\")\n\tcase \"available\":\n\t\tpackages := GetAllPluginPackages(out)\n\t\tfmt.Fprintln(out, \"Available Plugins:\")\n\t\tfor _, pkg := range packages {\n\t\t\tfmt.Fprintln(out, pkg.Name)\n\t\t}\n\tdefault:\n\t\tfmt.Fprintln(out, \"Invalid plugin command\")\n\t}\n}\n"
  },
  {
    "path": "internal/config/plugin_installer_test.go",
    "content": "package config\n\nimport (\n\t\"testing\"\n\n\t\"github.com/blang/semver\"\n\n\t\"github.com/micro-editor/json5\"\n)\n\nfunc TestDependencyResolving(t *testing.T) {\n\tjs := `\n[{\n  \"Name\": \"Foo\",\n  \"Versions\": [{ \"Version\": \"1.0.0\" }, { \"Version\": \"1.5.0\" },{ \"Version\": \"2.0.0\" }]\n}, {\n  \"Name\": \"Bar\",\n  \"Versions\": [{ \"Version\": \"1.0.0\", \"Require\": {\"Foo\": \">1.0.0 <2.0.0\"} }]\n}, {\n  \"Name\": \"Unresolvable\",\n  \"Versions\": [{ \"Version\": \"1.0.0\", \"Require\": {\"Foo\": \"<=1.0.0\", \"Bar\": \">0.0.0\"} }]\n\t}]\n`\n\tvar all PluginPackages\n\terr := json5.Unmarshal([]byte(js), &all)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tselected, err := all.Resolve(PluginVersions{}, PluginDependencies{\n\t\t&PluginDependency{\"Bar\", semver.MustParseRange(\">=1.0.0\")},\n\t})\n\n\tcheck := func(name, version string) {\n\t\tv := selected.find(name)\n\t\texpected := semver.MustParse(version)\n\t\tif v == nil {\n\t\t\tt.Errorf(\"Failed to resolve %s\", name)\n\t\t} else if expected.NE(v.Version) {\n\t\t\tt.Errorf(\"%s resolved in wrong version %v\", name, v)\n\t\t}\n\t}\n\n\tif err != nil {\n\t\tt.Error(err)\n\t} else {\n\t\tcheck(\"Foo\", \"1.5.0\")\n\t\tcheck(\"Bar\", \"1.0.0\")\n\t}\n\n\tselected, err = all.Resolve(PluginVersions{}, PluginDependencies{\n\t\t&PluginDependency{\"Unresolvable\", semver.MustParseRange(\">0.0.0\")},\n\t})\n\tif err == nil {\n\t\tt.Error(\"Unresolvable package resolved:\", selected)\n\t}\n}\n"
  },
  {
    "path": "internal/config/plugin_manager.go",
    "content": "package config\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n)\n\nvar (\n\tErrMissingName = errors.New(\"Missing or empty name field\")\n\tErrMissingDesc = errors.New(\"Missing or empty description field\")\n\tErrMissingSite = errors.New(\"Missing or empty website field\")\n)\n\n// PluginInfo contains all the needed info about a plugin\n// The info is just strings and are not used beyond that (except\n// the Site and Install fields should be valid URLs). This means\n// that the requirements for example can be formatted however the\n// plugin maker decides, the fields will only be parsed by humans\n// Name: name of plugin\n// Desc: description of plugin\n// Site: home website of plugin\n// Install: install link for plugin (can be link to repo or zip file)\n// Vstr: version\n// Require: list of dependencies and requirements\ntype PluginInfo struct {\n\tName string `json:\"Name\"`\n\tDesc string `json:\"Description\"`\n\tSite string `json:\"Website\"`\n}\n\n// NewPluginInfo parses a JSON input into a valid PluginInfo struct\n// Returns an error if there are any missing fields or any invalid fields\n// There are no optional fields in a plugin info json file\nfunc NewPluginInfo(data []byte) (*PluginInfo, error) {\n\tvar info []PluginInfo\n\n\tdec := json.NewDecoder(bytes.NewReader(data))\n\t// dec.DisallowUnknownFields() // Force errors\n\n\tif err := dec.Decode(&info); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &info[0], nil\n}\n"
  },
  {
    "path": "internal/config/rtfiles.go",
    "content": "package config\n\nimport (\n\t\"errors\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\n\trt \"github.com/micro-editor/micro/v2/runtime\"\n)\n\nconst (\n\tRTColorscheme  = 0\n\tRTSyntax       = 1\n\tRTHelp         = 2\n\tRTPlugin       = 3\n\tRTSyntaxHeader = 4\n)\n\nvar (\n\tNumTypes = 5 // How many filetypes are there\n)\n\ntype RTFiletype int\n\n// RuntimeFile allows the program to read runtime data like colorschemes or syntax files\ntype RuntimeFile interface {\n\t// Name returns a name of the file without paths or extensions\n\tName() string\n\t// Data returns the content of the file.\n\tData() ([]byte, error)\n}\n\n// allFiles contains all available files, mapped by filetype\nvar allFiles [][]RuntimeFile\nvar realFiles [][]RuntimeFile\n\nfunc init() {\n\tinitRuntimeVars()\n}\n\nfunc initRuntimeVars() {\n\tallFiles = make([][]RuntimeFile, NumTypes)\n\trealFiles = make([][]RuntimeFile, NumTypes)\n}\n\n// NewRTFiletype creates a new RTFiletype\nfunc NewRTFiletype() int {\n\tNumTypes++\n\tallFiles = append(allFiles, []RuntimeFile{})\n\trealFiles = append(realFiles, []RuntimeFile{})\n\treturn NumTypes - 1\n}\n\n// some file on filesystem\ntype realFile string\n\n// some asset file\ntype assetFile string\n\n// a file with the data stored in memory\ntype memoryFile struct {\n\tname string\n\tdata []byte\n}\n\nfunc (mf memoryFile) Name() string {\n\treturn mf.name\n}\nfunc (mf memoryFile) Data() ([]byte, error) {\n\treturn mf.data, nil\n}\n\nfunc (rf realFile) Name() string {\n\tfn := filepath.Base(string(rf))\n\treturn fn[:len(fn)-len(filepath.Ext(fn))]\n}\n\nfunc (rf realFile) Data() ([]byte, error) {\n\treturn os.ReadFile(string(rf))\n}\n\nfunc (af assetFile) Name() string {\n\tfn := filepath.Base(string(af))\n\treturn fn[:len(fn)-len(filepath.Ext(fn))]\n}\n\nfunc (af assetFile) Data() ([]byte, error) {\n\treturn rt.Asset(string(af))\n}\n\n// AddRuntimeFile registers a file for the given filetype\nfunc AddRuntimeFile(fileType RTFiletype, file RuntimeFile) {\n\tallFiles[fileType] = append(allFiles[fileType], file)\n}\n\n// AddRealRuntimeFile registers a file for the given filetype\nfunc AddRealRuntimeFile(fileType RTFiletype, file RuntimeFile) {\n\tallFiles[fileType] = append(allFiles[fileType], file)\n\trealFiles[fileType] = append(realFiles[fileType], file)\n}\n\n// AddRuntimeFilesFromDirectory registers each file from the given directory for\n// the filetype which matches the file-pattern\nfunc AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string) {\n\tfiles, _ := os.ReadDir(directory)\n\tfor _, f := range files {\n\t\tif ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {\n\t\t\tfullPath := filepath.Join(directory, f.Name())\n\t\t\tAddRealRuntimeFile(fileType, realFile(fullPath))\n\t\t}\n\t}\n}\n\n// AddRuntimeFilesFromAssets registers each file from the given asset-directory for\n// the filetype which matches the file-pattern\nfunc AddRuntimeFilesFromAssets(fileType RTFiletype, directory, pattern string) {\n\tfiles, err := rt.AssetDir(directory)\n\tif err != nil {\n\t\treturn\n\t}\n\nassetLoop:\n\tfor _, f := range files {\n\t\tif ok, _ := filepath.Match(pattern, f); ok {\n\t\t\taf := assetFile(filepath.Join(directory, f))\n\t\t\tfor _, rf := range realFiles[fileType] {\n\t\t\t\tif af.Name() == rf.Name() {\n\t\t\t\t\tcontinue assetLoop\n\t\t\t\t}\n\t\t\t}\n\t\t\tAddRuntimeFile(fileType, af)\n\t\t}\n\t}\n}\n\n// FindRuntimeFile finds a runtime file of the given filetype and name\n// will return nil if no file was found\nfunc FindRuntimeFile(fileType RTFiletype, name string) RuntimeFile {\n\tfor _, f := range ListRuntimeFiles(fileType) {\n\t\tif f.Name() == name {\n\t\t\treturn f\n\t\t}\n\t}\n\treturn nil\n}\n\n// ListRuntimeFiles lists all known runtime files for the given filetype\nfunc ListRuntimeFiles(fileType RTFiletype) []RuntimeFile {\n\treturn allFiles[fileType]\n}\n\n// ListRealRuntimeFiles lists all real runtime files (on disk) for a filetype\n// these runtime files will be ones defined by the user and loaded from the config directory\nfunc ListRealRuntimeFiles(fileType RTFiletype) []RuntimeFile {\n\treturn realFiles[fileType]\n}\n\n// InitRuntimeFiles initializes all assets files and the config directory.\n// If `user` is false, InitRuntimeFiles ignores the config directory and\n// initializes asset files only.\nfunc InitRuntimeFiles(user bool) {\n\tadd := func(fileType RTFiletype, dir, pattern string) {\n\t\tif user {\n\t\t\tAddRuntimeFilesFromDirectory(fileType, filepath.Join(ConfigDir, dir), pattern)\n\t\t}\n\t\tAddRuntimeFilesFromAssets(fileType, filepath.Join(\"runtime\", dir), pattern)\n\t}\n\n\tinitRuntimeVars()\n\n\tadd(RTColorscheme, \"colorschemes\", \"*.micro\")\n\tadd(RTSyntax, \"syntax\", \"*.yaml\")\n\tadd(RTSyntaxHeader, \"syntax\", \"*.hdr\")\n\tadd(RTHelp, \"help\", \"*.md\")\n}\n\n// InitPlugins initializes the plugins\nfunc InitPlugins() {\n\tPlugins = Plugins[:0]\n\tinitlua := filepath.Join(ConfigDir, \"init.lua\")\n\n\tif _, err := os.Stat(initlua); !os.IsNotExist(err) {\n\t\tp := new(Plugin)\n\t\tp.Name = \"initlua\"\n\t\tp.DirName = \"initlua\"\n\t\tp.Srcs = append(p.Srcs, realFile(initlua))\n\t\tp.Builtin = false\n\t\tPlugins = append(Plugins, p)\n\t}\n\n\t// Search ConfigDir for plugin-scripts\n\tplugdir := filepath.Join(ConfigDir, \"plug\")\n\tfiles, _ := os.ReadDir(plugdir)\n\n\tisID := regexp.MustCompile(`^[_A-Za-z0-9]+$`).MatchString\n\n\tfor _, d := range files {\n\t\tplugpath := filepath.Join(plugdir, d.Name())\n\t\tif stat, err := os.Stat(plugpath); err == nil && stat.IsDir() {\n\t\t\tsrcs, _ := os.ReadDir(plugpath)\n\t\t\tp := new(Plugin)\n\t\t\tp.Name = d.Name()\n\t\t\tp.DirName = d.Name()\n\t\t\tfor _, f := range srcs {\n\t\t\t\tif strings.HasSuffix(f.Name(), \".lua\") {\n\t\t\t\t\tp.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))\n\t\t\t\t} else if strings.HasSuffix(f.Name(), \".json\") {\n\t\t\t\t\tdata, err := os.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tp.Info, err = NewPluginInfo(data)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tp.Name = p.Info.Name\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !isID(p.Name) || len(p.Srcs) <= 0 {\n\t\t\t\tlog.Println(p.Name, \"is not a plugin\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tPlugins = append(Plugins, p)\n\t\t}\n\t}\n\n\tplugdir = filepath.Join(\"runtime\", \"plugins\")\n\tif files, err := rt.AssetDir(plugdir); err == nil {\n\touter:\n\t\tfor _, d := range files {\n\t\t\tfor _, p := range Plugins {\n\t\t\t\tif p.Name == d {\n\t\t\t\t\tlog.Println(p.Name, \"built-in plugin overridden by user-defined one\")\n\t\t\t\t\tcontinue outer\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif srcs, err := rt.AssetDir(filepath.Join(plugdir, d)); err == nil {\n\t\t\t\tp := new(Plugin)\n\t\t\t\tp.Name = d\n\t\t\t\tp.DirName = d\n\t\t\t\tp.Builtin = true\n\t\t\t\tfor _, f := range srcs {\n\t\t\t\t\tif strings.HasSuffix(f, \".lua\") {\n\t\t\t\t\t\tp.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f)))\n\t\t\t\t\t} else if strings.HasSuffix(f, \".json\") {\n\t\t\t\t\t\tdata, err := rt.Asset(filepath.Join(plugdir, d, f))\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tp.Info, err = NewPluginInfo(data)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tp.Name = p.Info.Name\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !isID(p.Name) || len(p.Srcs) <= 0 {\n\t\t\t\t\tlog.Println(p.Name, \"is not a plugin\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tPlugins = append(Plugins, p)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// PluginReadRuntimeFile allows plugin scripts to read the content of a runtime file\nfunc PluginReadRuntimeFile(fileType RTFiletype, name string) string {\n\tif file := FindRuntimeFile(fileType, name); file != nil {\n\t\tif data, err := file.Data(); err == nil {\n\t\t\treturn string(data)\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// PluginListRuntimeFiles allows plugins to lists all runtime files of the given type\nfunc PluginListRuntimeFiles(fileType RTFiletype) []string {\n\tfiles := ListRuntimeFiles(fileType)\n\tresult := make([]string, len(files))\n\tfor i, f := range files {\n\t\tresult[i] = f.Name()\n\t}\n\treturn result\n}\n\n// PluginAddRuntimeFile adds a file to the runtime files for a plugin\nfunc PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) error {\n\tpl := FindPlugin(plugin)\n\tif pl == nil {\n\t\treturn errors.New(\"Plugin \" + plugin + \" does not exist\")\n\t}\n\tpldir := pl.DirName\n\tfullpath := filepath.Join(ConfigDir, \"plug\", pldir, filePath)\n\tif _, err := os.Stat(fullpath); err == nil {\n\t\tAddRealRuntimeFile(filetype, realFile(fullpath))\n\t} else {\n\t\tfullpath = filepath.Join(\"runtime\", \"plugins\", pldir, filePath)\n\t\tAddRuntimeFile(filetype, assetFile(fullpath))\n\t}\n\treturn nil\n}\n\n// PluginAddRuntimeFilesFromDirectory adds files from a directory to the runtime files for a plugin\nfunc PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, directory, pattern string) error {\n\tpl := FindPlugin(plugin)\n\tif pl == nil {\n\t\treturn errors.New(\"Plugin \" + plugin + \" does not exist\")\n\t}\n\tpldir := pl.DirName\n\tfullpath := filepath.Join(ConfigDir, \"plug\", pldir, directory)\n\tif _, err := os.Stat(fullpath); err == nil {\n\t\tAddRuntimeFilesFromDirectory(filetype, fullpath, pattern)\n\t} else {\n\t\tfullpath = filepath.Join(\"runtime\", \"plugins\", pldir, directory)\n\t\tAddRuntimeFilesFromAssets(filetype, fullpath, pattern)\n\t}\n\treturn nil\n}\n\n// PluginAddRuntimeFileFromMemory adds a file to the runtime files for a plugin from a given string\nfunc PluginAddRuntimeFileFromMemory(filetype RTFiletype, filename, data string) {\n\tAddRealRuntimeFile(filetype, memoryFile{filename, []byte(data)})\n}\n"
  },
  {
    "path": "internal/config/rtfiles_test.go",
    "content": "package config\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc init() {\n\tInitRuntimeFiles(false)\n}\n\nfunc TestAddFile(t *testing.T) {\n\tAddRuntimeFile(RTPlugin, memoryFile{\"foo.lua\", []byte(\"hello world\\n\")})\n\tAddRuntimeFile(RTSyntax, memoryFile{\"bar\", []byte(\"some syntax file\\n\")})\n\n\tf1 := FindRuntimeFile(RTPlugin, \"foo.lua\")\n\tassert.NotNil(t, f1)\n\tassert.Equal(t, \"foo.lua\", f1.Name())\n\tdata, err := f1.Data()\n\tassert.Nil(t, err)\n\tassert.Equal(t, []byte(\"hello world\\n\"), data)\n\n\tf2 := FindRuntimeFile(RTSyntax, \"bar\")\n\tassert.NotNil(t, f2)\n\tassert.Equal(t, \"bar\", f2.Name())\n\tdata, err = f2.Data()\n\tassert.Nil(t, err)\n\tassert.Equal(t, []byte(\"some syntax file\\n\"), data)\n}\n\nfunc TestFindFile(t *testing.T) {\n\tf := FindRuntimeFile(RTSyntax, \"go\")\n\tassert.NotNil(t, f)\n\tassert.Equal(t, \"go\", f.Name())\n\tdata, err := f.Data()\n\tassert.Nil(t, err)\n\tassert.Equal(t, []byte(\"filetype: go\"), data[:12])\n\n\te := FindRuntimeFile(RTSyntax, \"foobar\")\n\tassert.Nil(t, e)\n}\n"
  },
  {
    "path": "internal/config/settings.go",
    "content": "package config\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/micro-editor/json5\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n\t\"github.com/zyedidia/glob\"\n\t\"golang.org/x/text/encoding/htmlindex\"\n)\n\ntype optionValidator func(string, any) error\n\n// a list of settings that need option validators\nvar optionValidators = map[string]optionValidator{\n\t\"autosave\":        validateNonNegativeValue,\n\t\"clipboard\":       validateChoice,\n\t\"colorcolumn\":     validateNonNegativeValue,\n\t\"colorscheme\":     validateColorscheme,\n\t\"detectlimit\":     validateNonNegativeValue,\n\t\"encoding\":        validateEncoding,\n\t\"fileformat\":      validateChoice,\n\t\"helpsplit\":       validateChoice,\n\t\"matchbracestyle\": validateChoice,\n\t\"multiopen\":       validateChoice,\n\t\"pageoverlap\":     validateNonNegativeValue,\n\t\"reload\":          validateChoice,\n\t\"scrollmargin\":    validateNonNegativeValue,\n\t\"scrollspeed\":     validateNonNegativeValue,\n\t\"tabsize\":         validatePositiveValue,\n\t\"truecolor\":       validateChoice,\n}\n\n// a list of settings with pre-defined choices\nvar OptionChoices = map[string][]string{\n\t\"clipboard\":       {\"internal\", \"external\", \"terminal\"},\n\t\"fileformat\":      {\"unix\", \"dos\"},\n\t\"helpsplit\":       {\"hsplit\", \"vsplit\"},\n\t\"matchbracestyle\": {\"underline\", \"highlight\"},\n\t\"multiopen\":       {\"tab\", \"hsplit\", \"vsplit\"},\n\t\"reload\":          {\"prompt\", \"auto\", \"disabled\"},\n\t\"truecolor\":       {\"auto\", \"on\", \"off\"},\n}\n\n// a list of settings that can be globally and locally modified and their\n// default values\nvar defaultCommonSettings = map[string]any{\n\t\"autoindent\":      true,\n\t\"autosu\":          false,\n\t\"backup\":          true,\n\t\"backupdir\":       \"\",\n\t\"basename\":        false,\n\t\"colorcolumn\":     float64(0),\n\t\"cursorline\":      true,\n\t\"detectlimit\":     float64(100),\n\t\"diffgutter\":      false,\n\t\"encoding\":        \"utf-8\",\n\t\"eofnewline\":      true,\n\t\"fastdirty\":       false,\n\t\"fileformat\":      defaultFileFormat(),\n\t\"filetype\":        \"unknown\",\n\t\"hlsearch\":        false,\n\t\"hltaberrors\":     false,\n\t\"hltrailingws\":    false,\n\t\"ignorecase\":      true,\n\t\"incsearch\":       true,\n\t\"indentchar\":      \" \", // Deprecated\n\t\"keepautoindent\":  false,\n\t\"matchbrace\":      true,\n\t\"matchbraceleft\":  true,\n\t\"matchbracestyle\": \"underline\",\n\t\"mkparents\":       false,\n\t\"pageoverlap\":     float64(2),\n\t\"permbackup\":      false,\n\t\"readonly\":        false,\n\t\"relativeruler\":   false,\n\t\"reload\":          \"prompt\",\n\t\"rmtrailingws\":    false,\n\t\"ruler\":           true,\n\t\"savecursor\":      false,\n\t\"saveundo\":        false,\n\t\"scrollbar\":       false,\n\t\"scrollmargin\":    float64(3),\n\t\"scrollspeed\":     float64(2),\n\t\"showchars\":       \"\",\n\t\"smartpaste\":      true,\n\t\"softwrap\":        false,\n\t\"splitbottom\":     true,\n\t\"splitright\":      true,\n\t\"statusformatl\":   \"$(filename) $(modified)$(overwrite)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)\",\n\t\"statusformatr\":   \"$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help\",\n\t\"statusline\":      true,\n\t\"syntax\":          true,\n\t\"tabmovement\":     false,\n\t\"tabsize\":         float64(4),\n\t\"tabstospaces\":    false,\n\t\"truecolor\":       \"auto\",\n\t\"useprimary\":      true,\n\t\"wordwrap\":        false,\n}\n\n// a list of settings that should only be globally modified and their\n// default values\nvar DefaultGlobalOnlySettings = map[string]any{\n\t\"autosave\":       float64(0),\n\t\"clipboard\":      \"external\",\n\t\"colorscheme\":    \"default\",\n\t\"divchars\":       \"|-\",\n\t\"divreverse\":     true,\n\t\"fakecursor\":     defaultFakeCursor(),\n\t\"helpsplit\":      \"hsplit\",\n\t\"infobar\":        true,\n\t\"keymenu\":        false,\n\t\"lockbindings\":   false,\n\t\"mouse\":          true,\n\t\"multiopen\":      \"tab\",\n\t\"parsecursor\":    false,\n\t\"paste\":          false,\n\t\"pluginchannels\": []string{\"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json\"},\n\t\"pluginrepos\":    []string{},\n\t\"savehistory\":    true,\n\t\"scrollbarchar\":  \"|\",\n\t\"sucmd\":          \"sudo\",\n\t\"tabhighlight\":   false,\n\t\"tabreverse\":     true,\n\t\"xterm\":          false,\n}\n\n// a list of settings that should never be globally modified\nvar LocalSettings = []string{\n\t\"filetype\",\n\t\"readonly\",\n}\n\nvar (\n\tErrInvalidOption    = errors.New(\"Invalid option\")\n\tErrInvalidValue     = errors.New(\"Invalid value\")\n\tErrOptNotToggleable = errors.New(\"Option not toggleable\")\n\n\t// The options that the user can set\n\tGlobalSettings map[string]any\n\n\t// This is the raw parsed json\n\tparsedSettings     map[string]any\n\tsettingsParseError bool\n\n\t// ModifiedSettings is a map of settings which should be written to disk\n\t// because they have been modified by the user in this session\n\tModifiedSettings map[string]bool\n\n\t// VolatileSettings is a map of settings which should not be written to disk\n\t// because they have been temporarily set for this session only\n\tVolatileSettings map[string]bool\n)\n\nfunc writeFile(name string, txt []byte) error {\n\treturn util.SafeWrite(name, txt, false)\n}\n\nfunc init() {\n\tModifiedSettings = make(map[string]bool)\n\tVolatileSettings = make(map[string]bool)\n}\n\nfunc validateParsedSettings() error {\n\tvar err error\n\tdefaults := DefaultAllSettings()\n\tfor k, v := range parsedSettings {\n\t\tif strings.HasPrefix(reflect.TypeOf(v).String(), \"map\") {\n\t\t\tif strings.HasPrefix(k, \"ft:\") {\n\t\t\t\tfor k1, v1 := range v.(map[string]any) {\n\t\t\t\t\tif _, ok := defaults[k1]; ok {\n\t\t\t\t\t\tif e := verifySetting(k1, v1, defaults[k1]); e != nil {\n\t\t\t\t\t\t\terr = e\n\t\t\t\t\t\t\tparsedSettings[k].(map[string]any)[k1] = defaults[k1]\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif _, e := glob.Compile(k); e != nil {\n\t\t\t\t\terr = errors.New(\"Error with glob setting \" + k + \": \" + e.Error())\n\t\t\t\t\tdelete(parsedSettings, k)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tfor k1, v1 := range v.(map[string]any) {\n\t\t\t\t\tif _, ok := defaults[k1]; ok {\n\t\t\t\t\t\tif e := verifySetting(k1, v1, defaults[k1]); e != nil {\n\t\t\t\t\t\t\terr = e\n\t\t\t\t\t\t\tparsedSettings[k].(map[string]any)[k1] = defaults[k1]\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif k == \"autosave\" {\n\t\t\t// if autosave is a boolean convert it to float\n\t\t\ts, ok := v.(bool)\n\t\t\tif ok {\n\t\t\t\tif s {\n\t\t\t\t\tparsedSettings[\"autosave\"] = 8.0\n\t\t\t\t} else {\n\t\t\t\t\tparsedSettings[\"autosave\"] = 0.0\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif _, ok := defaults[k]; ok {\n\t\t\tif e := verifySetting(k, v, defaults[k]); e != nil {\n\t\t\t\terr = e\n\t\t\t\tparsedSettings[k] = defaults[k]\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t}\n\treturn err\n}\n\nfunc ReadSettings() error {\n\tparsedSettings = make(map[string]any)\n\tfilename := filepath.Join(ConfigDir, \"settings.json\")\n\tif _, e := os.Stat(filename); e == nil {\n\t\tinput, err := os.ReadFile(filename)\n\t\tif err != nil {\n\t\t\tsettingsParseError = true\n\t\t\treturn errors.New(\"Error reading settings.json file: \" + err.Error())\n\t\t}\n\t\tif !strings.HasPrefix(string(input), \"null\") {\n\t\t\t// Unmarshal the input into the parsed map\n\t\t\terr = json5.Unmarshal(input, &parsedSettings)\n\t\t\tif err != nil {\n\t\t\t\tsettingsParseError = true\n\t\t\t\treturn errors.New(\"Error reading settings.json: \" + err.Error())\n\t\t\t}\n\t\t\terr = validateParsedSettings()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc ParsedSettings() map[string]any {\n\ts := make(map[string]any)\n\tfor k, v := range parsedSettings {\n\t\ts[k] = v\n\t}\n\treturn s\n}\n\nfunc verifySetting(option string, value any, def any) error {\n\tvar interfaceArr []any\n\tvalType := reflect.TypeOf(value)\n\tdefType := reflect.TypeOf(def)\n\tassignable := false\n\n\tswitch option {\n\tcase \"pluginrepos\", \"pluginchannels\":\n\t\tassignable = valType.AssignableTo(reflect.TypeOf(interfaceArr))\n\tdefault:\n\t\tassignable = defType.AssignableTo(valType)\n\t}\n\tif !assignable {\n\t\treturn fmt.Errorf(\"Error: setting '%s' has incorrect type (%s), using default value: %v (%s)\", option, valType, def, defType)\n\t}\n\n\tif option == \"colorscheme\" {\n\t\t// Plugins are not initialized yet, so do not verify if the colorscheme\n\t\t// exists yet, since the colorscheme may be added by a plugin later.\n\t\treturn nil\n\t}\n\n\tif err := OptionIsValid(option, value); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// InitGlobalSettings initializes the options map and sets all options to their default values\n// Must be called after ReadSettings\nfunc InitGlobalSettings() error {\n\tvar err error\n\tGlobalSettings = DefaultAllSettings()\n\n\tfor k, v := range parsedSettings {\n\t\tif !strings.HasPrefix(reflect.TypeOf(v).String(), \"map\") {\n\t\t\tGlobalSettings[k] = v\n\t\t}\n\t}\n\treturn err\n}\n\n// UpdatePathGlobLocals scans the already parsed settings and sets the options locally\n// based on whether the path matches a glob\n// Must be called after ReadSettings\nfunc UpdatePathGlobLocals(settings map[string]any, path string) {\n\tfor k, v := range parsedSettings {\n\t\tif strings.HasPrefix(reflect.TypeOf(v).String(), \"map\") && !strings.HasPrefix(k, \"ft:\") {\n\t\t\tg, _ := glob.Compile(k)\n\t\t\tif g.MatchString(path) {\n\t\t\t\tfor k1, v1 := range v.(map[string]any) {\n\t\t\t\t\tsettings[k1] = v1\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// UpdateFileTypeLocals scans the already parsed settings and sets the options locally\n// based on whether the filetype matches to \"ft:\"\n// Must be called after ReadSettings\nfunc UpdateFileTypeLocals(settings map[string]any, filetype string) {\n\tfor k, v := range parsedSettings {\n\t\tif strings.HasPrefix(reflect.TypeOf(v).String(), \"map\") && strings.HasPrefix(k, \"ft:\") {\n\t\t\tif filetype == k[3:] {\n\t\t\t\tfor k1, v1 := range v.(map[string]any) {\n\t\t\t\t\tif k1 != \"filetype\" {\n\t\t\t\t\t\tsettings[k1] = v1\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// WriteSettings writes the settings to the specified filename as JSON\nfunc WriteSettings(filename string) error {\n\tif settingsParseError {\n\t\t// Don't write settings if there was a parse error\n\t\t// because this will delete the settings.json if it\n\t\t// is invalid. Instead we should allow the user to fix\n\t\t// it manually.\n\t\treturn nil\n\t}\n\n\tvar err error\n\tif _, e := os.Stat(ConfigDir); e == nil {\n\t\tdefaults := DefaultAllSettings()\n\n\t\t// remove any options froms parsedSettings that have since been marked as default\n\t\tfor k, v := range parsedSettings {\n\t\t\tif !strings.HasPrefix(reflect.TypeOf(v).String(), \"map\") {\n\t\t\t\tcur, okcur := GlobalSettings[k]\n\t\t\t\t_, vol := VolatileSettings[k]\n\t\t\t\tif def, ok := defaults[k]; ok && okcur && !vol && reflect.DeepEqual(cur, def) {\n\t\t\t\t\tdelete(parsedSettings, k)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// add any options to parsedSettings that have since been marked as non-default\n\t\tfor k, v := range GlobalSettings {\n\t\t\tif def, ok := defaults[k]; !ok || !reflect.DeepEqual(v, def) {\n\t\t\t\tif _, wr := ModifiedSettings[k]; wr {\n\t\t\t\t\tparsedSettings[k] = v\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttxt, _ := json.MarshalIndent(parsedSettings, \"\", \"    \")\n\t\ttxt = append(txt, '\\n')\n\t\terr = writeFile(filename, txt)\n\t}\n\treturn err\n}\n\n// OverwriteSettings writes the current settings to settings.json and\n// resets any user configuration of local settings present in settings.json\nfunc OverwriteSettings(filename string) error {\n\tsettings := make(map[string]any)\n\n\tvar err error\n\tif _, e := os.Stat(ConfigDir); e == nil {\n\t\tdefaults := DefaultAllSettings()\n\t\tfor k, v := range GlobalSettings {\n\t\t\tif def, ok := defaults[k]; !ok || !reflect.DeepEqual(v, def) {\n\t\t\t\tif _, wr := ModifiedSettings[k]; wr {\n\t\t\t\t\tsettings[k] = v\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttxt, _ := json.MarshalIndent(parsedSettings, \"\", \"    \")\n\t\ttxt = append(txt, '\\n')\n\t\terr = writeFile(filename, txt)\n\t}\n\treturn err\n}\n\n// RegisterCommonOptionPlug creates a new option (called pl.name). This is meant to be called by plugins to add options.\nfunc RegisterCommonOptionPlug(pl string, name string, defaultvalue any) error {\n\treturn RegisterCommonOption(pl+\".\"+name, defaultvalue)\n}\n\n// RegisterGlobalOptionPlug creates a new global-only option (named pl.name)\nfunc RegisterGlobalOptionPlug(pl string, name string, defaultvalue any) error {\n\treturn RegisterGlobalOption(pl+\".\"+name, defaultvalue)\n}\n\n// RegisterCommonOption creates a new option\nfunc RegisterCommonOption(name string, defaultvalue any) error {\n\tif _, ok := GlobalSettings[name]; !ok {\n\t\tGlobalSettings[name] = defaultvalue\n\t}\n\tdefaultCommonSettings[name] = defaultvalue\n\treturn nil\n}\n\n// RegisterGlobalOption creates a new global-only option\nfunc RegisterGlobalOption(name string, defaultvalue any) error {\n\tif _, ok := GlobalSettings[name]; !ok {\n\t\tGlobalSettings[name] = defaultvalue\n\t}\n\tDefaultGlobalOnlySettings[name] = defaultvalue\n\treturn nil\n}\n\n// GetGlobalOption returns the global value of the given option\nfunc GetGlobalOption(name string) any {\n\treturn GlobalSettings[name]\n}\n\nfunc defaultFileFormat() string {\n\tif runtime.GOOS == \"windows\" {\n\t\treturn \"dos\"\n\t}\n\treturn \"unix\"\n}\n\nfunc defaultFakeCursor() bool {\n\t_, wt := os.LookupEnv(\"WT_SESSION\")\n\tif runtime.GOOS == \"windows\" && !wt {\n\t\t// enabled for windows consoles where the cursor is slow\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc GetInfoBarOffset() int {\n\toffset := 0\n\tif GetGlobalOption(\"infobar\").(bool) {\n\t\toffset++\n\t}\n\tif GetGlobalOption(\"keymenu\").(bool) {\n\t\toffset += 2\n\t}\n\treturn offset\n}\n\n// DefaultCommonSettings returns a map of all common buffer settings\n// and their default values\nfunc DefaultCommonSettings() map[string]any {\n\tcommonsettings := make(map[string]any)\n\tfor k, v := range defaultCommonSettings {\n\t\tcommonsettings[k] = v\n\t}\n\treturn commonsettings\n}\n\n// DefaultAllSettings returns a map of all common buffer & global-only settings\n// and their default values\nfunc DefaultAllSettings() map[string]any {\n\tallsettings := make(map[string]any)\n\tfor k, v := range defaultCommonSettings {\n\t\tallsettings[k] = v\n\t}\n\tfor k, v := range DefaultGlobalOnlySettings {\n\t\tallsettings[k] = v\n\t}\n\treturn allsettings\n}\n\n// GetNativeValue parses and validates a value for a given option\nfunc GetNativeValue(option, value string) (any, error) {\n\tcurVal := GetGlobalOption(option)\n\tif curVal == nil {\n\t\treturn nil, ErrInvalidOption\n\t}\n\n\tswitch kind := reflect.TypeOf(curVal).Kind(); kind {\n\tcase reflect.Bool:\n\t\tb, err := util.ParseBool(value)\n\t\tif err != nil {\n\t\t\treturn nil, ErrInvalidValue\n\t\t}\n\t\treturn b, nil\n\tcase reflect.String:\n\t\treturn value, nil\n\tcase reflect.Float64:\n\t\tf, err := strconv.ParseFloat(value, 64)\n\t\tif err != nil {\n\t\t\treturn nil, ErrInvalidValue\n\t\t}\n\t\treturn f, nil\n\tdefault:\n\t\treturn nil, ErrInvalidValue\n\t}\n}\n\n// OptionIsValid checks if a value is valid for a certain option\nfunc OptionIsValid(option string, value any) error {\n\tif validator, ok := optionValidators[option]; ok {\n\t\treturn validator(option, value)\n\t}\n\n\treturn nil\n}\n\n// Option validators\n\nfunc validatePositiveValue(option string, value any) error {\n\tnativeValue, ok := value.(float64)\n\n\tif !ok {\n\t\treturn errors.New(\"Expected numeric type for \" + option)\n\t}\n\n\tif nativeValue < 1 {\n\t\treturn errors.New(option + \" must be greater than 0\")\n\t}\n\n\treturn nil\n}\n\nfunc validateNonNegativeValue(option string, value any) error {\n\tnativeValue, ok := value.(float64)\n\n\tif !ok {\n\t\treturn errors.New(\"Expected numeric type for \" + option)\n\t}\n\n\tif nativeValue < 0 {\n\t\treturn errors.New(option + \" must be non-negative\")\n\t}\n\n\treturn nil\n}\n\nfunc validateChoice(option string, value any) error {\n\tif choices, ok := OptionChoices[option]; ok {\n\t\tval, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn errors.New(\"Expected string type for \" + option)\n\t\t}\n\n\t\tfor _, v := range choices {\n\t\t\tif val == v {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\n\t\tchoicesStr := strings.Join(choices, \", \")\n\t\treturn errors.New(option + \" must be one of: \" + choicesStr)\n\t}\n\n\treturn errors.New(\"Option has no pre-defined choices\")\n}\n\nfunc validateColorscheme(option string, value any) error {\n\tcolorscheme, ok := value.(string)\n\n\tif !ok {\n\t\treturn errors.New(\"Expected string type for colorscheme\")\n\t}\n\n\tif !ColorschemeExists(colorscheme) {\n\t\treturn errors.New(colorscheme + \" is not a valid colorscheme\")\n\t}\n\n\treturn nil\n}\n\nfunc validateEncoding(option string, value any) error {\n\t_, err := htmlindex.Get(value.(string))\n\treturn err\n}\n"
  },
  {
    "path": "internal/display/bufwindow.go",
    "content": "package display\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\n\trunewidth \"github.com/mattn/go-runewidth\"\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n\t\"github.com/micro-editor/tcell/v2\"\n)\n\n// The BufWindow provides a way of displaying a certain section of a buffer.\ntype BufWindow struct {\n\t*View\n\n\t// Buffer being shown in this window\n\tBuf *buffer.Buffer\n\n\tactive bool\n\n\tsline *StatusLine\n\n\tbufWidth         int\n\tbufHeight        int\n\tgutterOffset     int\n\thasMessage       bool\n\tmaxLineNumLength int\n\tdrawDivider      bool\n}\n\n// NewBufWindow creates a new window at a location in the screen with a width and height\nfunc NewBufWindow(x, y, width, height int, buf *buffer.Buffer) *BufWindow {\n\tw := new(BufWindow)\n\tw.View = new(View)\n\tw.X, w.Y, w.Width, w.Height = x, y, width, height\n\tw.SetBuffer(buf)\n\tw.active = true\n\n\tw.sline = NewStatusLine(w)\n\n\treturn w\n}\n\n// SetBuffer sets this window's buffer.\nfunc (w *BufWindow) SetBuffer(b *buffer.Buffer) {\n\tw.Buf = b\n\tb.OptionCallback = func(option string, nativeValue any) {\n\t\tif option == \"softwrap\" {\n\t\t\tif nativeValue.(bool) {\n\t\t\t\tw.StartCol = 0\n\t\t\t} else {\n\t\t\t\tw.StartLine.Row = 0\n\t\t\t}\n\t\t}\n\n\t\tif option == \"softwrap\" || option == \"wordwrap\" {\n\t\t\tw.Relocate()\n\t\t\tfor _, c := range w.Buf.GetCursors() {\n\t\t\t\tc.LastWrappedVisualX = c.GetVisualX(true)\n\t\t\t}\n\t\t}\n\n\t\tif option == \"diffgutter\" || option == \"ruler\" || option == \"scrollbar\" ||\n\t\t\toption == \"statusline\" {\n\t\t\tw.updateDisplayInfo()\n\t\t\tw.Relocate()\n\t\t}\n\t}\n\tb.GetVisualX = func(loc buffer.Loc) int {\n\t\treturn w.VLocFromLoc(loc).VisualX\n\t}\n}\n\n// GetView gets the view.\nfunc (w *BufWindow) GetView() *View {\n\treturn w.View\n}\n\n// SetView sets the view.\nfunc (w *BufWindow) SetView(view *View) {\n\tw.View = view\n}\n\n// Resize resizes this window.\nfunc (w *BufWindow) Resize(width, height int) {\n\tw.Width, w.Height = width, height\n\tw.updateDisplayInfo()\n\n\tw.Relocate()\n}\n\n// SetActive marks the window as active.\nfunc (w *BufWindow) SetActive(b bool) {\n\tw.active = b\n}\n\n// IsActive returns true if this window is active.\nfunc (w *BufWindow) IsActive() bool {\n\treturn w.active\n}\n\n// BufView returns the width, height and x,y location of the actual buffer.\n// It is not exactly the same as the whole window which also contains gutter,\n// ruler, scrollbar and statusline.\nfunc (w *BufWindow) BufView() View {\n\treturn View{\n\t\tX:         w.X + w.gutterOffset,\n\t\tY:         w.Y,\n\t\tWidth:     w.bufWidth,\n\t\tHeight:    w.bufHeight,\n\t\tStartLine: w.StartLine,\n\t\tStartCol:  w.StartCol,\n\t}\n}\n\nfunc (w *BufWindow) updateDisplayInfo() {\n\tb := w.Buf\n\n\tw.drawDivider = false\n\tif !b.Settings[\"statusline\"].(bool) {\n\t\t_, h := screen.Screen.Size()\n\t\tinfoY := h\n\t\tif config.GetGlobalOption(\"infobar\").(bool) {\n\t\t\tinfoY--\n\t\t}\n\t\tif w.Y+w.Height != infoY {\n\t\t\tw.drawDivider = true\n\t\t}\n\t}\n\n\tw.bufHeight = w.Height\n\tif b.Settings[\"statusline\"].(bool) || w.drawDivider {\n\t\tw.bufHeight--\n\t}\n\n\tscrollbarWidth := 0\n\tif w.Buf.Settings[\"scrollbar\"].(bool) && w.Buf.LinesNum() > w.Height && w.Width > 0 {\n\t\tscrollbarWidth = 1\n\t}\n\n\tw.hasMessage = len(b.Messages) > 0\n\n\t// We need to know the string length of the largest line number\n\t// so we can pad appropriately when displaying line numbers\n\tw.maxLineNumLength = len(strconv.Itoa(b.LinesNum()))\n\n\tw.gutterOffset = 0\n\tif w.hasMessage {\n\t\tw.gutterOffset += 2\n\t}\n\tif b.Settings[\"diffgutter\"].(bool) {\n\t\tw.gutterOffset++\n\t}\n\tif b.Settings[\"ruler\"].(bool) {\n\t\tw.gutterOffset += w.maxLineNumLength + 1\n\t}\n\n\tif w.gutterOffset > w.Width-scrollbarWidth {\n\t\tw.gutterOffset = w.Width - scrollbarWidth\n\t}\n\n\tprevBufWidth := w.bufWidth\n\tw.bufWidth = w.Width - w.gutterOffset - scrollbarWidth\n\n\tif w.bufWidth != prevBufWidth && w.Buf.Settings[\"softwrap\"].(bool) {\n\t\tfor _, c := range w.Buf.GetCursors() {\n\t\t\tc.LastWrappedVisualX = c.GetVisualX(true)\n\t\t}\n\t}\n}\n\nfunc (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style) {\n\ttabsize := util.IntOpt(w.Buf.Settings[\"tabsize\"])\n\twidth := 0\n\tbloc := buffer.Loc{0, lineN}\n\tb := w.Buf.LineBytes(lineN)\n\tcurStyle := config.DefStyle\n\tvar s *tcell.Style\n\tfor len(b) > 0 {\n\t\tr, _, size := util.DecodeCharacter(b)\n\n\t\tcurStyle, found := w.getStyle(curStyle, bloc)\n\t\tif found {\n\t\t\ts = &curStyle\n\t\t}\n\n\t\tw := 0\n\t\tswitch r {\n\t\tcase '\\t':\n\t\t\tts := tabsize - (width % tabsize)\n\t\t\tw = ts\n\t\tdefault:\n\t\t\tw = runewidth.RuneWidth(r)\n\t\t}\n\t\tif width+w > n {\n\t\t\treturn b, n - width, bloc.X, s\n\t\t}\n\t\twidth += w\n\t\tb = b[size:]\n\t\tbloc.X++\n\t}\n\treturn b, n - width, bloc.X, s\n}\n\n// Clear resets all cells in this window to the default style\nfunc (w *BufWindow) Clear() {\n\tfor y := 0; y < w.Height; y++ {\n\t\tfor x := 0; x < w.Width; x++ {\n\t\t\tscreen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)\n\t\t}\n\t}\n}\n\n// Relocate moves the view window so that the cursor is in view\n// This is useful if the user has scrolled far away, and then starts typing\n// Returns true if the window location is moved\nfunc (w *BufWindow) Relocate() bool {\n\tb := w.Buf\n\theight := w.bufHeight\n\tret := false\n\tactiveC := w.Buf.GetActiveCursor()\n\tscrollmargin := int(b.Settings[\"scrollmargin\"].(float64))\n\n\tc := w.SLocFromLoc(activeC.Loc)\n\tbStart := SLoc{0, 0}\n\tbEnd := w.SLocFromLoc(b.End())\n\n\tif c.LessThan(w.Scroll(w.StartLine, scrollmargin)) && c.GreaterThan(w.Scroll(bStart, scrollmargin-1)) {\n\t\tw.StartLine = w.Scroll(c, -scrollmargin)\n\t\tret = true\n\t} else if c.LessThan(w.StartLine) {\n\t\tw.StartLine = c\n\t\tret = true\n\t}\n\tif c.GreaterThan(w.Scroll(w.StartLine, height-1-scrollmargin)) && c.LessEqual(w.Scroll(bEnd, -scrollmargin)) {\n\t\tw.StartLine = w.Scroll(c, -height+1+scrollmargin)\n\t\tret = true\n\t} else if c.GreaterThan(w.Scroll(bEnd, -scrollmargin)) && c.GreaterThan(w.Scroll(w.StartLine, height-1)) {\n\t\tw.StartLine = w.Scroll(bEnd, -height+1)\n\t\tret = true\n\t}\n\n\t// horizontal relocation (scrolling)\n\tif !b.Settings[\"softwrap\"].(bool) {\n\t\tcx := activeC.GetVisualX(false)\n\t\trw := runewidth.RuneWidth(activeC.RuneUnder(activeC.X))\n\t\tif rw == 0 {\n\t\t\trw = 1 // tab or newline\n\t\t}\n\n\t\tif cx < w.StartCol {\n\t\t\tw.StartCol = cx\n\t\t\tret = true\n\t\t}\n\t\tif cx+rw > w.StartCol+w.bufWidth {\n\t\t\tw.StartCol = cx - w.bufWidth + rw\n\t\t\tret = true\n\t\t}\n\t}\n\treturn ret\n}\n\n// LocFromVisual takes a visual location (x and y position) and returns the\n// position in the buffer corresponding to the visual location\n// If the requested position does not correspond to a buffer location it returns\n// the nearest position\nfunc (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {\n\tvx := svloc.X - w.X - w.gutterOffset\n\tif vx < 0 {\n\t\tvx = 0\n\t}\n\tvloc := VLoc{\n\t\tSLoc:    w.Scroll(w.StartLine, svloc.Y-w.Y),\n\t\tVisualX: vx + w.StartCol,\n\t}\n\treturn w.LocFromVLoc(vloc)\n}\n\nfunc (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) {\n\tchar := ' '\n\ts := config.DefStyle\n\tfor _, m := range w.Buf.Messages {\n\t\tif m.Start.Y == bloc.Y || m.End.Y == bloc.Y {\n\t\t\ts = m.Style()\n\t\t\tchar = '>'\n\t\t\tbreak\n\t\t}\n\t}\n\tfor i := 0; i < 2 && vloc.X < w.gutterOffset; i++ {\n\t\tscreen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)\n\t\tvloc.X++\n\t}\n}\n\nfunc (w *BufWindow) drawDiffGutter(backgroundStyle tcell.Style, softwrapped bool, vloc *buffer.Loc, bloc *buffer.Loc) {\n\tif vloc.X >= w.gutterOffset {\n\t\treturn\n\t}\n\n\tsymbol := ' '\n\tstyleName := \"\"\n\n\tswitch w.Buf.DiffStatus(bloc.Y) {\n\tcase buffer.DSAdded:\n\t\tsymbol = '\\u258C' // Left half block\n\t\tstyleName = \"diff-added\"\n\tcase buffer.DSModified:\n\t\tsymbol = '\\u258C' // Left half block\n\t\tstyleName = \"diff-modified\"\n\tcase buffer.DSDeletedAbove:\n\t\tif !softwrapped {\n\t\t\tsymbol = '\\u2594' // Upper one eighth block\n\t\t\tstyleName = \"diff-deleted\"\n\t\t}\n\t}\n\n\tstyle := backgroundStyle\n\tif s, ok := config.Colorscheme[styleName]; ok {\n\t\tforeground, _, _ := s.Decompose()\n\t\tstyle = style.Foreground(foreground)\n\t}\n\n\tscreen.SetContent(w.X+vloc.X, w.Y+vloc.Y, symbol, nil, style)\n\tvloc.X++\n}\n\nfunc (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, vloc *buffer.Loc, bloc *buffer.Loc) {\n\tcursorLine := w.Buf.GetActiveCursor().Loc.Y\n\tvar lineInt int\n\tif w.Buf.Settings[\"relativeruler\"] == false || cursorLine == bloc.Y {\n\t\tlineInt = bloc.Y + 1\n\t} else {\n\t\tlineInt = bloc.Y - cursorLine\n\t}\n\tlineNum := []rune(strconv.Itoa(util.Abs(lineInt)))\n\n\t// Write the spaces before the line number if necessary\n\tfor i := 0; i < w.maxLineNumLength-len(lineNum) && vloc.X < w.gutterOffset; i++ {\n\t\tscreen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)\n\t\tvloc.X++\n\t}\n\t// Write the actual line number\n\tfor i := 0; i < len(lineNum) && vloc.X < w.gutterOffset; i++ {\n\t\tif softwrapped || (w.bufWidth == 0 && w.Buf.Settings[\"softwrap\"] == true) {\n\t\t\tscreen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)\n\t\t} else {\n\t\t\tscreen.SetContent(w.X+vloc.X, w.Y+vloc.Y, lineNum[i], nil, lineNumStyle)\n\t\t}\n\t\tvloc.X++\n\t}\n\n\t// Write the extra space\n\tif vloc.X < w.gutterOffset {\n\t\tscreen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)\n\t\tvloc.X++\n\t}\n}\n\n// getStyle returns the highlight style for the given character position\n// If there is no change to the current highlight style it just returns that\nfunc (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc) (tcell.Style, bool) {\n\tif group, ok := w.Buf.Match(bloc.Y)[bloc.X]; ok {\n\t\ts := config.GetColor(group.String())\n\t\treturn s, true\n\t}\n\treturn style, false\n}\n\nfunc (w *BufWindow) showCursor(x, y int, main bool) {\n\tif w.active {\n\t\tif main {\n\t\t\tscreen.ShowCursor(x, y)\n\t\t} else {\n\t\t\tscreen.ShowFakeCursorMulti(x, y)\n\t\t}\n\t}\n}\n\n// displayBuffer draws the buffer being shown in this window on the screen.Screen\nfunc (w *BufWindow) displayBuffer() {\n\tb := w.Buf\n\n\tif w.Height <= 0 || w.Width <= 0 {\n\t\treturn\n\t}\n\n\tmaxWidth := w.gutterOffset + w.bufWidth\n\n\tif b.ModifiedThisFrame {\n\t\tif b.Settings[\"diffgutter\"].(bool) {\n\t\t\tb.UpdateDiff()\n\t\t}\n\t\tb.ModifiedThisFrame = false\n\t}\n\n\tvar matchingBraces []buffer.Loc\n\t// bracePairs is defined in buffer.go\n\tif b.Settings[\"matchbrace\"].(bool) {\n\t\tfor _, c := range b.GetCursors() {\n\t\t\tif c.HasSelection() {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmb, left, found := b.FindMatchingBrace(c.Loc)\n\t\t\tif found {\n\t\t\t\tmatchingBraces = append(matchingBraces, mb)\n\t\t\t\tif !left {\n\t\t\t\t\tif b.Settings[\"matchbracestyle\"].(string) != \"highlight\" {\n\t\t\t\t\t\tmatchingBraces = append(matchingBraces, c.Loc)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tmatchingBraces = append(matchingBraces, c.Loc.Move(-1, b))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tlineNumStyle := config.DefStyle\n\tif style, ok := config.Colorscheme[\"line-number\"]; ok {\n\t\tlineNumStyle = style\n\t}\n\tcurNumStyle := config.DefStyle\n\tif style, ok := config.Colorscheme[\"current-line-number\"]; ok {\n\t\tif !b.Settings[\"cursorline\"].(bool) {\n\t\t\tcurNumStyle = lineNumStyle\n\t\t} else {\n\t\t\tcurNumStyle = style\n\t\t}\n\t}\n\n\tsoftwrap := b.Settings[\"softwrap\"].(bool)\n\twordwrap := softwrap && b.Settings[\"wordwrap\"].(bool)\n\n\ttabsize := util.IntOpt(b.Settings[\"tabsize\"])\n\tcolorcolumn := util.IntOpt(b.Settings[\"colorcolumn\"])\n\n\t// this represents the current draw position\n\t// within the current window\n\tvloc := buffer.Loc{X: 0, Y: 0}\n\tif softwrap {\n\t\t// the start line may be partially out of the current window\n\t\tvloc.Y = -w.StartLine.Row\n\t}\n\n\t// this represents the current draw position in the buffer (char positions)\n\tbloc := buffer.Loc{X: -1, Y: w.StartLine.Line}\n\n\tcursors := b.GetCursors()\n\n\tcurStyle := config.DefStyle\n\n\t// Parse showchars which is in the format of key1=val1,key2=val2,...\n\tspacechars := \" \"\n\ttabchars := b.Settings[\"indentchar\"].(string)\n\tvar indentspacechars string\n\tvar indenttabchars string\n\tfor _, entry := range strings.Split(b.Settings[\"showchars\"].(string), \",\") {\n\t\tsplit := strings.SplitN(entry, \"=\", 2)\n\t\tif len(split) < 2 {\n\t\t\tcontinue\n\t\t}\n\t\tkey, val := split[0], split[1]\n\t\tswitch key {\n\t\tcase \"space\":\n\t\t\tspacechars = val\n\t\tcase \"tab\":\n\t\t\ttabchars = val\n\t\tcase \"ispace\":\n\t\t\tindentspacechars = val\n\t\tcase \"itab\":\n\t\t\tindenttabchars = val\n\t\t}\n\t}\n\n\tfor ; vloc.Y < w.bufHeight; vloc.Y++ {\n\t\tvloc.X = 0\n\n\t\tcurrentLine := false\n\t\tfor _, c := range cursors {\n\t\t\tif !c.HasSelection() && bloc.Y == c.Y && w.active {\n\t\t\t\tcurrentLine = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\ts := lineNumStyle\n\t\tif currentLine {\n\t\t\ts = curNumStyle\n\t\t}\n\n\t\tif vloc.Y >= 0 {\n\t\t\tif w.hasMessage {\n\t\t\t\tw.drawGutter(&vloc, &bloc)\n\t\t\t}\n\n\t\t\tif b.Settings[\"diffgutter\"].(bool) {\n\t\t\t\tw.drawDiffGutter(s, false, &vloc, &bloc)\n\t\t\t}\n\n\t\t\tif b.Settings[\"ruler\"].(bool) {\n\t\t\t\tw.drawLineNum(s, false, &vloc, &bloc)\n\t\t\t}\n\t\t} else {\n\t\t\tvloc.X = w.gutterOffset\n\t\t}\n\n\t\tbline := b.LineBytes(bloc.Y)\n\t\tblineLen := util.CharacterCount(bline)\n\n\t\tleadingwsEnd := len(util.GetLeadingWhitespace(bline))\n\t\ttrailingwsStart := blineLen - util.CharacterCount(util.GetTrailingWhitespace(bline))\n\n\t\tline, nColsBeforeStart, bslice, startStyle := w.getStartInfo(w.StartCol, bloc.Y)\n\t\tif startStyle != nil {\n\t\t\tcurStyle = *startStyle\n\t\t}\n\t\tbloc.X = bslice\n\n\t\t// returns the rune to be drawn, style of it and if the bg should be preserved\n\t\tgetRuneStyle := func(r rune, style tcell.Style, showoffset int, linex int, isplaceholder bool) (rune, tcell.Style, bool) {\n\t\t\tif nColsBeforeStart > 0 || vloc.Y < 0 || isplaceholder {\n\t\t\t\treturn r, style, false\n\t\t\t}\n\n\t\t\tfor _, mb := range matchingBraces {\n\t\t\t\tif mb.X == bloc.X && mb.Y == bloc.Y {\n\t\t\t\t\tif b.Settings[\"matchbracestyle\"].(string) == \"highlight\" {\n\t\t\t\t\t\tif s, ok := config.Colorscheme[\"match-brace\"]; ok {\n\t\t\t\t\t\t\treturn r, s, false\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn r, style.Reverse(true), false\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn r, style.Underline(true), false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif r != '\\t' && r != ' ' {\n\t\t\t\treturn r, style, false\n\t\t\t}\n\n\t\t\tvar indentrunes []rune\n\t\t\tswitch r {\n\t\t\tcase '\\t':\n\t\t\t\tif bloc.X < leadingwsEnd && indenttabchars != \"\" {\n\t\t\t\t\tindentrunes = []rune(indenttabchars)\n\t\t\t\t} else {\n\t\t\t\t\tindentrunes = []rune(tabchars)\n\t\t\t\t}\n\t\t\tcase ' ':\n\t\t\t\tif linex%tabsize == 0 && bloc.X < leadingwsEnd && indentspacechars != \"\" {\n\t\t\t\t\tindentrunes = []rune(indentspacechars)\n\t\t\t\t} else {\n\t\t\t\t\tindentrunes = []rune(spacechars)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar drawrune rune\n\t\t\tif showoffset < len(indentrunes) {\n\t\t\t\tdrawrune = indentrunes[showoffset]\n\t\t\t} else {\n\t\t\t\t// use space if no showchars or after we showed showchars\n\t\t\t\tdrawrune = ' '\n\t\t\t}\n\n\t\t\tif s, ok := config.Colorscheme[\"indent-char\"]; ok {\n\t\t\t\tfg, _, _ := s.Decompose()\n\t\t\t\tstyle = style.Foreground(fg)\n\t\t\t}\n\n\t\t\tpreservebg := false\n\t\t\tif b.Settings[\"hltaberrors\"].(bool) && bloc.X < leadingwsEnd {\n\t\t\t\tif s, ok := config.Colorscheme[\"tab-error\"]; ok {\n\t\t\t\t\tif b.Settings[\"tabstospaces\"].(bool) && r == '\\t' {\n\t\t\t\t\t\tfg, _, _ := s.Decompose()\n\t\t\t\t\t\tstyle = style.Background(fg)\n\t\t\t\t\t\tpreservebg = true\n\t\t\t\t\t} else if !b.Settings[\"tabstospaces\"].(bool) && r == ' ' {\n\t\t\t\t\t\tfg, _, _ := s.Decompose()\n\t\t\t\t\t\tstyle = style.Background(fg)\n\t\t\t\t\t\tpreservebg = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif b.Settings[\"hltrailingws\"].(bool) {\n\t\t\t\tif s, ok := config.Colorscheme[\"trailingws\"]; ok {\n\t\t\t\t\tif bloc.X >= trailingwsStart && bloc.X < blineLen {\n\t\t\t\t\t\thl := true\n\t\t\t\t\t\tfor _, c := range cursors {\n\t\t\t\t\t\t\tif c.NewTrailingWsY == bloc.Y {\n\t\t\t\t\t\t\t\thl = false\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif hl {\n\t\t\t\t\t\t\tfg, _, _ := s.Decompose()\n\t\t\t\t\t\t\tstyle = style.Background(fg)\n\t\t\t\t\t\t\tpreservebg = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn drawrune, style, preservebg\n\t\t}\n\n\t\tdraw := func(r rune, combc []rune, style tcell.Style, highlight bool, showcursor bool, preservebg bool) {\n\t\t\tdefer func() {\n\t\t\t\tif nColsBeforeStart <= 0 {\n\t\t\t\t\tvloc.X++\n\t\t\t\t}\n\t\t\t\tnColsBeforeStart--\n\t\t\t}()\n\n\t\t\tif nColsBeforeStart > 0 || vloc.Y < 0 {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif highlight {\n\t\t\t\tif w.Buf.HighlightSearch && w.Buf.SearchMatch(bloc) {\n\t\t\t\t\tstyle = config.DefStyle.Reverse(true)\n\t\t\t\t\tif s, ok := config.Colorscheme[\"hlsearch\"]; ok {\n\t\t\t\t\t\tstyle = s\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t_, origBg, _ := style.Decompose()\n\t\t\t\t_, defBg, _ := config.DefStyle.Decompose()\n\n\t\t\t\t// syntax or hlsearch highlighting with non-default background takes precedence\n\t\t\t\t// over cursor-line and color-column\n\t\t\t\tif !preservebg && origBg != defBg {\n\t\t\t\t\tpreservebg = true\n\t\t\t\t}\n\n\t\t\t\tfor _, c := range cursors {\n\t\t\t\t\tif c.HasSelection() &&\n\t\t\t\t\t\t(bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||\n\t\t\t\t\t\t\tbloc.LessThan(c.CurSelection[0]) && bloc.GreaterEqual(c.CurSelection[1])) {\n\t\t\t\t\t\t// The current character is selected\n\t\t\t\t\t\tstyle = config.DefStyle.Reverse(true)\n\n\t\t\t\t\t\tif s, ok := config.Colorscheme[\"selection\"]; ok {\n\t\t\t\t\t\t\tstyle = s\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif b.Settings[\"cursorline\"].(bool) && w.active && !preservebg &&\n\t\t\t\t\t\t!c.HasSelection() && c.Y == bloc.Y {\n\t\t\t\t\t\tif s, ok := config.Colorscheme[\"cursor-line\"]; ok {\n\t\t\t\t\t\t\tfg, _, _ := s.Decompose()\n\t\t\t\t\t\t\tstyle = style.Background(fg)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor _, m := range b.Messages {\n\t\t\t\t\tif bloc.GreaterEqual(m.Start) && bloc.LessThan(m.End) ||\n\t\t\t\t\t\tbloc.LessThan(m.End) && bloc.GreaterEqual(m.Start) {\n\t\t\t\t\t\tstyle = style.Underline(true)\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif s, ok := config.Colorscheme[\"color-column\"]; ok {\n\t\t\t\t\tif colorcolumn != 0 && vloc.X-w.gutterOffset+w.StartCol == colorcolumn && !preservebg {\n\t\t\t\t\t\tfg, _, _ := s.Decompose()\n\t\t\t\t\t\tstyle = style.Background(fg)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tscreen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, combc, style)\n\n\t\t\tif showcursor {\n\t\t\t\tfor _, c := range cursors {\n\t\t\t\t\tif c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {\n\t\t\t\t\t\tw.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\twrap := func() {\n\t\t\tvloc.X = 0\n\n\t\t\tif vloc.Y >= 0 {\n\t\t\t\tif w.hasMessage {\n\t\t\t\t\tw.drawGutter(&vloc, &bloc)\n\t\t\t\t}\n\t\t\t\tif b.Settings[\"diffgutter\"].(bool) {\n\t\t\t\t\tw.drawDiffGutter(lineNumStyle, true, &vloc, &bloc)\n\t\t\t\t}\n\n\t\t\t\t// This will draw an empty line number because the current line is wrapped\n\t\t\t\tif b.Settings[\"ruler\"].(bool) {\n\t\t\t\t\tw.drawLineNum(lineNumStyle, true, &vloc, &bloc)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tvloc.X = w.gutterOffset\n\t\t\t}\n\t\t}\n\n\t\ttype glyph struct {\n\t\t\tr     rune\n\t\t\tcombc []rune\n\t\t\tstyle tcell.Style\n\t\t\twidth int\n\t\t}\n\n\t\tvar word []glyph\n\t\tif wordwrap {\n\t\t\tword = make([]glyph, 0, w.bufWidth)\n\t\t} else {\n\t\t\tword = make([]glyph, 0, 1)\n\t\t}\n\t\twordwidth := 0\n\n\t\ttotalwidth := w.StartCol - nColsBeforeStart\n\t\tfor len(line) > 0 && vloc.X < maxWidth {\n\t\t\tr, combc, size := util.DecodeCharacter(line)\n\t\t\tline = line[size:]\n\n\t\t\tloc := buffer.Loc{X: bloc.X + len(word), Y: bloc.Y}\n\t\t\tcurStyle, _ = w.getStyle(curStyle, loc)\n\n\t\t\twidth := 0\n\n\t\t\tlinex := totalwidth\n\t\t\tswitch r {\n\t\t\tcase '\\t':\n\t\t\t\tts := tabsize - (totalwidth % tabsize)\n\t\t\t\twidth = util.Min(ts, maxWidth-vloc.X)\n\t\t\t\ttotalwidth += ts\n\t\t\tdefault:\n\t\t\t\twidth = runewidth.RuneWidth(r)\n\t\t\t\ttotalwidth += width\n\t\t\t}\n\n\t\t\tword = append(word, glyph{r, combc, curStyle, width})\n\t\t\twordwidth += width\n\n\t\t\t// Collect a complete word to know its width.\n\t\t\t// If wordwrap is off, every single character is a complete \"word\".\n\t\t\tif wordwrap {\n\t\t\t\tif !util.IsWhitespace(r) && len(line) > 0 && wordwidth < w.bufWidth {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If a word (or just a wide rune) does not fit in the window\n\t\t\tif vloc.X+wordwidth > maxWidth && vloc.X > w.gutterOffset {\n\t\t\t\tfor vloc.X < maxWidth {\n\t\t\t\t\tdraw(' ', nil, config.DefStyle, false, false, true)\n\t\t\t\t}\n\n\t\t\t\t// We either stop or we wrap to draw the word in the next line\n\t\t\t\tif !softwrap {\n\t\t\t\t\tbreak\n\t\t\t\t} else {\n\t\t\t\t\tvloc.Y++\n\t\t\t\t\tif vloc.Y >= w.bufHeight {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\twrap()\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor _, r := range word {\n\t\t\t\tdrawrune, drawstyle, preservebg := getRuneStyle(r.r, r.style, 0, linex, false)\n\t\t\t\tdraw(drawrune, r.combc, drawstyle, true, true, preservebg)\n\n\t\t\t\t// Draw extra characters for tabs or wide runes\n\t\t\t\tfor i := 1; i < r.width; i++ {\n\t\t\t\t\tif r.r == '\\t' {\n\t\t\t\t\t\tdrawrune, drawstyle, preservebg = getRuneStyle('\\t', r.style, i, linex+i, false)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdrawrune, drawstyle, preservebg = getRuneStyle(' ', r.style, i, linex+i, true)\n\t\t\t\t\t}\n\t\t\t\t\tdraw(drawrune, nil, drawstyle, true, false, preservebg)\n\t\t\t\t}\n\t\t\t\tbloc.X++\n\t\t\t}\n\n\t\t\tword = word[:0]\n\t\t\twordwidth = 0\n\n\t\t\t// If we reach the end of the window then we either stop or we wrap for softwrap\n\t\t\tif vloc.X >= maxWidth {\n\t\t\t\tif !softwrap {\n\t\t\t\t\tbreak\n\t\t\t\t} else {\n\t\t\t\t\tvloc.Y++\n\t\t\t\t\tif vloc.Y >= w.bufHeight {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\twrap()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tstyle := config.DefStyle\n\t\tfor _, c := range cursors {\n\t\t\tif b.Settings[\"cursorline\"].(bool) && w.active &&\n\t\t\t\t!c.HasSelection() && c.Y == bloc.Y {\n\t\t\t\tif s, ok := config.Colorscheme[\"cursor-line\"]; ok {\n\t\t\t\t\tfg, _, _ := s.Decompose()\n\t\t\t\t\tstyle = style.Background(fg)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor i := vloc.X; i < maxWidth; i++ {\n\t\t\tcurStyle := style\n\t\t\tif s, ok := config.Colorscheme[\"color-column\"]; ok {\n\t\t\t\tif colorcolumn != 0 && i-w.gutterOffset+w.StartCol == colorcolumn {\n\t\t\t\t\tfg, _, _ := s.Decompose()\n\t\t\t\t\tcurStyle = style.Background(fg)\n\t\t\t\t}\n\t\t\t}\n\t\t\tscreen.SetContent(i+w.X, vloc.Y+w.Y, ' ', nil, curStyle)\n\t\t}\n\n\t\tif vloc.X != maxWidth {\n\t\t\t// Display newline within a selection\n\t\t\tdrawrune, drawstyle, preservebg := getRuneStyle(' ', config.DefStyle, 0, totalwidth, true)\n\t\t\tdraw(drawrune, nil, drawstyle, true, true, preservebg)\n\t\t}\n\n\t\tbloc.X = w.StartCol\n\t\tbloc.Y++\n\t\tif bloc.Y >= b.LinesNum() {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc (w *BufWindow) displayStatusLine() {\n\tif w.Buf.Settings[\"statusline\"].(bool) {\n\t\tw.sline.Display()\n\t} else if w.drawDivider {\n\t\tdivchars := config.GetGlobalOption(\"divchars\").(string)\n\t\tif util.CharacterCountInString(divchars) != 2 {\n\t\t\tdivchars = \"|-\"\n\t\t}\n\n\t\t_, _, size := util.DecodeCharacterInString(divchars)\n\t\tdivchar, combc, _ := util.DecodeCharacterInString(divchars[size:])\n\n\t\tdividerStyle := config.DefStyle\n\t\tif style, ok := config.Colorscheme[\"divider\"]; ok {\n\t\t\tdividerStyle = style\n\t\t}\n\n\t\tdivreverse := config.GetGlobalOption(\"divreverse\").(bool)\n\t\tif divreverse {\n\t\t\tdividerStyle = dividerStyle.Reverse(true)\n\t\t}\n\n\t\tfor x := w.X; x < w.X+w.Width; x++ {\n\t\t\tscreen.SetContent(x, w.Y+w.Height-1, divchar, combc, dividerStyle)\n\t\t}\n\t}\n}\n\nfunc (w *BufWindow) displayScrollBar() {\n\tif w.Buf.Settings[\"scrollbar\"].(bool) && w.Buf.LinesNum() > w.Height {\n\t\tscrollX := w.X + w.Width - 1\n\t\tbarsize := int(float64(w.Height) / float64(w.Buf.LinesNum()) * float64(w.Height))\n\t\tif barsize < 1 {\n\t\t\tbarsize = 1\n\t\t}\n\t\tbarstart := w.Y + int(float64(w.StartLine.Line)/float64(w.Buf.LinesNum())*float64(w.Height))\n\n\t\tscrollBarStyle := config.DefStyle.Reverse(true)\n\t\tif style, ok := config.Colorscheme[\"scrollbar\"]; ok {\n\t\t\tscrollBarStyle = style\n\t\t}\n\n\t\tscrollBarChar := config.GetGlobalOption(\"scrollbarchar\").(string)\n\t\tif util.CharacterCountInString(scrollBarChar) != 1 {\n\t\t\tscrollBarChar = \"|\"\n\t\t}\n\t\tscrollBarRune := []rune(scrollBarChar)\n\n\t\tfor y := barstart; y < util.Min(barstart+barsize, w.Y+w.bufHeight); y++ {\n\t\t\tscreen.SetContent(scrollX, y, scrollBarRune[0], nil, scrollBarStyle)\n\t\t}\n\t}\n}\n\n// Display displays the buffer and the statusline\nfunc (w *BufWindow) Display() {\n\tw.updateDisplayInfo()\n\n\tw.displayStatusLine()\n\tw.displayScrollBar()\n\tw.displayBuffer()\n}\n"
  },
  {
    "path": "internal/display/infowindow.go",
    "content": "package display\n\nimport (\n\trunewidth \"github.com/mattn/go-runewidth\"\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/info\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n\t\"github.com/micro-editor/tcell/v2\"\n)\n\ntype InfoWindow struct {\n\t*info.InfoBuf\n\t*View\n\n\thscroll int\n}\n\nfunc (i *InfoWindow) errStyle() tcell.Style {\n\terrStyle := config.DefStyle.\n\t\tForeground(tcell.ColorBlack).\n\t\tBackground(tcell.ColorMaroon)\n\n\tif _, ok := config.Colorscheme[\"error-message\"]; ok {\n\t\terrStyle = config.Colorscheme[\"error-message\"]\n\t}\n\n\treturn errStyle\n}\n\nfunc (i *InfoWindow) defStyle() tcell.Style {\n\tdefStyle := config.DefStyle\n\n\tif _, ok := config.Colorscheme[\"message\"]; ok {\n\t\tdefStyle = config.Colorscheme[\"message\"]\n\t}\n\n\treturn defStyle\n}\n\nfunc NewInfoWindow(b *info.InfoBuf) *InfoWindow {\n\tiw := new(InfoWindow)\n\tiw.InfoBuf = b\n\tiw.View = new(View)\n\n\tiw.Width, iw.Y = screen.Screen.Size()\n\tiw.Y--\n\n\treturn iw\n}\n\nfunc (i *InfoWindow) Resize(w, h int) {\n\ti.Width = w\n\ti.Y = h\n}\n\nfunc (i *InfoWindow) SetBuffer(b *buffer.Buffer) {\n\ti.InfoBuf.Buffer = b\n}\n\nfunc (i *InfoWindow) Relocate() bool   { return false }\nfunc (i *InfoWindow) GetView() *View   { return i.View }\nfunc (i *InfoWindow) SetView(v *View)  {}\nfunc (i *InfoWindow) SetActive(b bool) {}\nfunc (i *InfoWindow) IsActive() bool   { return true }\n\nfunc (i *InfoWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc {\n\tc := i.Buffer.GetActiveCursor()\n\tl := i.Buffer.LineBytes(0)\n\tn := util.CharacterCountInString(i.Msg)\n\treturn buffer.Loc{c.GetCharPosInLine(l, vloc.X-n), 0}\n}\n\nfunc (i *InfoWindow) BufView() View {\n\treturn View{\n\t\tX:         0,\n\t\tY:         i.Y,\n\t\tWidth:     i.Width,\n\t\tHeight:    1,\n\t\tStartLine: SLoc{0, 0},\n\t\tStartCol:  0,\n\t}\n}\n\nfunc (i *InfoWindow) Scroll(s SLoc, n int) SLoc        { return s }\nfunc (i *InfoWindow) Diff(s1, s2 SLoc) int             { return 0 }\nfunc (i *InfoWindow) SLocFromLoc(loc buffer.Loc) SLoc  { return SLoc{0, 0} }\nfunc (i *InfoWindow) VLocFromLoc(loc buffer.Loc) VLoc  { return VLoc{SLoc{0, 0}, loc.X} }\nfunc (i *InfoWindow) LocFromVLoc(vloc VLoc) buffer.Loc { return buffer.Loc{vloc.VisualX, 0} }\n\nfunc (i *InfoWindow) Clear() {\n\tfor x := 0; x < i.Width; x++ {\n\t\tscreen.SetContent(x, i.Y, ' ', nil, i.defStyle())\n\t}\n}\n\nfunc (i *InfoWindow) displayBuffer() {\n\tb := i.Buffer\n\tline := b.LineBytes(0)\n\tactiveC := b.GetActiveCursor()\n\n\tblocX := 0\n\tvlocX := util.CharacterCountInString(i.Msg)\n\n\ttabsize := 4\n\tline, nColsBeforeStart, bslice := util.SliceVisualEnd(line, blocX, tabsize)\n\tblocX = bslice\n\n\tdraw := func(r rune, combc []rune, style tcell.Style) {\n\t\tif nColsBeforeStart <= 0 {\n\t\t\tbloc := buffer.Loc{X: blocX, Y: 0}\n\t\t\tif activeC.HasSelection() &&\n\t\t\t\t(bloc.GreaterEqual(activeC.CurSelection[0]) && bloc.LessThan(activeC.CurSelection[1]) ||\n\t\t\t\t\tbloc.LessThan(activeC.CurSelection[0]) && bloc.GreaterEqual(activeC.CurSelection[1])) {\n\t\t\t\t// The current character is selected\n\t\t\t\tstyle = i.defStyle().Reverse(true)\n\n\t\t\t\tif s, ok := config.Colorscheme[\"selection\"]; ok {\n\t\t\t\t\tstyle = s\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tscreen.SetContent(vlocX, i.Y, r, combc, style)\n\t\t\tvlocX += runewidth.RuneWidth(r)\n\t\t}\n\t\tnColsBeforeStart--\n\t}\n\n\ttotalwidth := blocX - nColsBeforeStart\n\tfor len(line) > 0 {\n\t\tcurVX := vlocX\n\t\tcurBX := blocX\n\t\tr, combc, size := util.DecodeCharacter(line)\n\n\t\twidth := 0\n\n\t\tswitch r {\n\t\tcase '\\t':\n\t\t\twidth = tabsize - (totalwidth % tabsize)\n\t\t\tfor j := 0; j < width; j++ {\n\t\t\t\tdraw(' ', nil, i.defStyle())\n\t\t\t}\n\t\tdefault:\n\t\t\twidth = runewidth.RuneWidth(r)\n\t\t\tdraw(r, combc, i.defStyle())\n\t\t}\n\n\t\tblocX++\n\t\tline = line[size:]\n\n\t\tif activeC.X == curBX {\n\t\t\tscreen.ShowCursor(curVX, i.Y)\n\t\t}\n\t\ttotalwidth += width\n\t\tif vlocX >= i.Width {\n\t\t\tbreak\n\t\t}\n\t}\n\tif activeC.X == blocX {\n\t\tscreen.ShowCursor(vlocX, i.Y)\n\t}\n}\n\nvar keydisplay = []string{\"^Q Quit, ^S Save, ^O Open, ^G Help, ^E Command Bar, ^K Cut Line\", \"^F Find, ^Z Undo, ^Y Redo, ^A Select All, ^D Duplicate Line, ^T New Tab\"}\n\nfunc (i *InfoWindow) displayKeyMenu() {\n\t// TODO: maybe make this based on the actual keybindings\n\n\tfor y := 0; y < len(keydisplay); y++ {\n\t\tfor x := 0; x < i.Width; x++ {\n\t\t\tif x < len(keydisplay[y]) {\n\t\t\t\tscreen.SetContent(x, i.Y-len(keydisplay)+y, rune(keydisplay[y][x]), nil, i.defStyle())\n\t\t\t} else {\n\t\t\t\tscreen.SetContent(x, i.Y-len(keydisplay)+y, ' ', nil, i.defStyle())\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (i *InfoWindow) totalSize() int {\n\tsum := 0\n\tfor _, n := range i.Suggestions {\n\t\tsum += runewidth.StringWidth(n) + 1\n\t}\n\treturn sum\n}\n\nfunc (i *InfoWindow) scrollToSuggestion() {\n\tx := 0\n\ts := i.totalSize()\n\n\tfor j, n := range i.Suggestions {\n\t\tc := util.CharacterCountInString(n)\n\t\tif j == i.CurSuggestion {\n\t\t\tif x+c >= i.hscroll+i.Width {\n\t\t\t\ti.hscroll = util.Clamp(x+c+1-i.Width, 0, s-i.Width)\n\t\t\t} else if x < i.hscroll {\n\t\t\t\ti.hscroll = util.Clamp(x-1, 0, s-i.Width)\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\tx += c + 1\n\t}\n\n\tif s-i.Width <= 0 {\n\t\ti.hscroll = 0\n\t}\n}\n\nfunc (i *InfoWindow) Display() {\n\tif i.HasPrompt || config.GlobalSettings[\"infobar\"].(bool) {\n\t\ti.Clear()\n\t\tx := 0\n\t\tif config.GetGlobalOption(\"keymenu\").(bool) {\n\t\t\ti.displayKeyMenu()\n\t\t}\n\n\t\tif !i.HasPrompt && !i.HasMessage && !i.HasError {\n\t\t\treturn\n\t\t}\n\t\ti.Clear()\n\t\tstyle := i.defStyle()\n\n\t\tif i.HasError {\n\t\t\tstyle = i.errStyle()\n\t\t}\n\n\t\tdisplay := i.Msg\n\t\tfor _, c := range display {\n\t\t\tscreen.SetContent(x, i.Y, c, nil, style)\n\t\t\tx += runewidth.RuneWidth(c)\n\t\t}\n\n\t\tif i.HasPrompt {\n\t\t\ti.displayBuffer()\n\t\t}\n\t}\n\n\tif i.HasSuggestions && len(i.Suggestions) > 1 {\n\t\ti.scrollToSuggestion()\n\n\t\tx := -i.hscroll\n\t\tdone := false\n\n\t\tstatusLineStyle := config.DefStyle.Reverse(true)\n\t\tif style, ok := config.Colorscheme[\"statusline.suggestions\"]; ok {\n\t\t\tstatusLineStyle = style\n\t\t} else if style, ok := config.Colorscheme[\"statusline\"]; ok {\n\t\t\tstatusLineStyle = style\n\t\t}\n\t\tkeymenuOffset := 0\n\t\tif config.GetGlobalOption(\"keymenu\").(bool) {\n\t\t\tkeymenuOffset = len(keydisplay)\n\t\t}\n\n\t\tdraw := func(r rune, s tcell.Style) {\n\t\t\ty := i.Y - keymenuOffset - 1\n\t\t\trw := runewidth.RuneWidth(r)\n\t\t\tfor j := 0; j < rw; j++ {\n\t\t\t\tc := r\n\t\t\t\tif j > 0 {\n\t\t\t\t\tc = ' '\n\t\t\t\t}\n\n\t\t\t\tif x == i.Width-1 && !done {\n\t\t\t\t\tscreen.SetContent(i.Width-1, y, '>', nil, s)\n\t\t\t\t\tx++\n\t\t\t\t\tbreak\n\t\t\t\t} else if x == 0 && i.hscroll > 0 {\n\t\t\t\t\tscreen.SetContent(0, y, '<', nil, s)\n\t\t\t\t} else if x >= 0 && x < i.Width {\n\t\t\t\t\tscreen.SetContent(x, y, c, nil, s)\n\t\t\t\t}\n\t\t\t\tx++\n\t\t\t}\n\t\t}\n\n\t\tfor j, s := range i.Suggestions {\n\t\t\tstyle := statusLineStyle\n\t\t\tif i.CurSuggestion == j {\n\t\t\t\tstyle = style.Reverse(true)\n\t\t\t}\n\t\t\tfor _, r := range s {\n\t\t\t\tdraw(r, style)\n\t\t\t\t// screen.SetContent(x, i.Y-keymenuOffset-1, r, nil, style)\n\t\t\t}\n\t\t\tdraw(' ', statusLineStyle)\n\t\t}\n\n\t\tfor x < i.Width {\n\t\t\tdraw(' ', statusLineStyle)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/display/softwrap.go",
    "content": "package display\n\nimport (\n\trunewidth \"github.com/mattn/go-runewidth\"\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n)\n\n// SLoc represents a vertical scrolling location, i.e. a location of a visual line\n// in the buffer. When softwrap is enabled, a buffer line may be displayed as\n// multiple visual lines (rows). So SLoc stores a number of a line in the buffer\n// and a number of a row within this line.\ntype SLoc struct {\n\tLine, Row int\n}\n\n// LessThan returns true if s is less b\nfunc (s SLoc) LessThan(b SLoc) bool {\n\tif s.Line < b.Line {\n\t\treturn true\n\t}\n\treturn s.Line == b.Line && s.Row < b.Row\n}\n\n// GreaterThan returns true if s is bigger than b\nfunc (s SLoc) GreaterThan(b SLoc) bool {\n\tif s.Line > b.Line {\n\t\treturn true\n\t}\n\treturn s.Line == b.Line && s.Row > b.Row\n}\n\n// LessEqual returns true if s is less than or equal to b\nfunc (s SLoc) LessEqual(b SLoc) bool {\n\tif s.Line < b.Line {\n\t\treturn true\n\t}\n\tif s.Line == b.Line && s.Row < b.Row {\n\t\treturn true\n\t}\n\treturn s == b\n}\n\n// GreaterEqual returns true if s is bigger than or equal to b\nfunc (s SLoc) GreaterEqual(b SLoc) bool {\n\tif s.Line > b.Line {\n\t\treturn true\n\t}\n\tif s.Line == b.Line && s.Row > b.Row {\n\t\treturn true\n\t}\n\treturn s == b\n}\n\n// VLoc represents a location in the buffer as a visual location in the\n// linewrapped buffer.\ntype VLoc struct {\n\tSLoc\n\tVisualX int\n}\n\ntype SoftWrap interface {\n\tScroll(s SLoc, n int) SLoc\n\tDiff(s1, s2 SLoc) int\n\tSLocFromLoc(loc buffer.Loc) SLoc\n\tVLocFromLoc(loc buffer.Loc) VLoc\n\tLocFromVLoc(vloc VLoc) buffer.Loc\n}\n\nfunc (w *BufWindow) getVLocFromLoc(loc buffer.Loc) VLoc {\n\tvloc := VLoc{SLoc: SLoc{loc.Y, 0}, VisualX: 0}\n\n\tif loc.X <= 0 {\n\t\treturn vloc\n\t}\n\n\tif w.bufWidth <= 0 {\n\t\treturn vloc\n\t}\n\n\twordwrap := w.Buf.Settings[\"wordwrap\"].(bool)\n\ttabsize := util.IntOpt(w.Buf.Settings[\"tabsize\"])\n\n\tline := w.Buf.LineBytes(loc.Y)\n\tx := 0\n\ttotalwidth := 0\n\n\twordwidth := 0\n\twordoffset := 0\n\n\tfor len(line) > 0 {\n\t\tr, _, size := util.DecodeCharacter(line)\n\t\tline = line[size:]\n\n\t\twidth := 0\n\t\tswitch r {\n\t\tcase '\\t':\n\t\t\tts := tabsize - (totalwidth % tabsize)\n\t\t\twidth = util.Min(ts, w.bufWidth-vloc.VisualX)\n\t\t\ttotalwidth += ts\n\t\tdefault:\n\t\t\twidth = runewidth.RuneWidth(r)\n\t\t\ttotalwidth += width\n\t\t}\n\n\t\twordwidth += width\n\n\t\t// Collect a complete word to know its width.\n\t\t// If wordwrap is off, every single character is a complete \"word\".\n\t\tif wordwrap {\n\t\t\tif !util.IsWhitespace(r) && len(line) > 0 && wordwidth < w.bufWidth {\n\t\t\t\tif x < loc.X {\n\t\t\t\t\twordoffset += width\n\t\t\t\t\tx++\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// If a word (or just a wide rune) does not fit in the window\n\t\tif vloc.VisualX+wordwidth > w.bufWidth && vloc.VisualX > 0 {\n\t\t\tvloc.Row++\n\t\t\tvloc.VisualX = 0\n\t\t}\n\n\t\tif x == loc.X {\n\t\t\tvloc.VisualX += wordoffset\n\t\t\treturn vloc\n\t\t}\n\t\tx++\n\n\t\tvloc.VisualX += wordwidth\n\n\t\twordwidth = 0\n\t\twordoffset = 0\n\n\t\tif vloc.VisualX >= w.bufWidth {\n\t\t\tvloc.Row++\n\t\t\tvloc.VisualX = 0\n\t\t}\n\t}\n\treturn vloc\n}\n\nfunc (w *BufWindow) getLocFromVLoc(svloc VLoc) buffer.Loc {\n\tloc := buffer.Loc{X: 0, Y: svloc.Line}\n\n\tif w.bufWidth <= 0 {\n\t\treturn loc\n\t}\n\n\twordwrap := w.Buf.Settings[\"wordwrap\"].(bool)\n\ttabsize := util.IntOpt(w.Buf.Settings[\"tabsize\"])\n\n\tline := w.Buf.LineBytes(svloc.Line)\n\tvloc := VLoc{SLoc: SLoc{svloc.Line, 0}, VisualX: 0}\n\n\ttotalwidth := 0\n\n\tvar widths []int\n\tif wordwrap {\n\t\twidths = make([]int, 0, w.bufWidth)\n\t} else {\n\t\twidths = make([]int, 0, 1)\n\t}\n\twordwidth := 0\n\n\tfor len(line) > 0 {\n\t\tr, _, size := util.DecodeCharacter(line)\n\t\tline = line[size:]\n\n\t\twidth := 0\n\t\tswitch r {\n\t\tcase '\\t':\n\t\t\tts := tabsize - (totalwidth % tabsize)\n\t\t\twidth = util.Min(ts, w.bufWidth-vloc.VisualX)\n\t\t\ttotalwidth += ts\n\t\tdefault:\n\t\t\twidth = runewidth.RuneWidth(r)\n\t\t\ttotalwidth += width\n\t\t}\n\n\t\twidths = append(widths, width)\n\t\twordwidth += width\n\n\t\t// Collect a complete word to know its width.\n\t\t// If wordwrap is off, every single character is a complete \"word\".\n\t\tif wordwrap {\n\t\t\tif !util.IsWhitespace(r) && len(line) > 0 && wordwidth < w.bufWidth {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// If a word (or just a wide rune) does not fit in the window\n\t\tif vloc.VisualX+wordwidth > w.bufWidth && vloc.VisualX > 0 {\n\t\t\tif vloc.Row == svloc.Row {\n\t\t\t\tif wordwrap {\n\t\t\t\t\t// it's a word, not a wide rune\n\t\t\t\t\tloc.X--\n\t\t\t\t}\n\t\t\t\treturn loc\n\t\t\t}\n\t\t\tvloc.Row++\n\t\t\tvloc.VisualX = 0\n\t\t}\n\n\t\tfor i := range widths {\n\t\t\tvloc.VisualX += widths[i]\n\t\t\tif vloc.Row == svloc.Row && vloc.VisualX > svloc.VisualX {\n\t\t\t\treturn loc\n\t\t\t}\n\t\t\tloc.X++\n\t\t}\n\n\t\twidths = widths[:0]\n\t\twordwidth = 0\n\n\t\tif vloc.VisualX >= w.bufWidth {\n\t\t\tvloc.Row++\n\t\t\tvloc.VisualX = 0\n\t\t}\n\t}\n\treturn loc\n}\n\nfunc (w *BufWindow) getRowCount(line int) int {\n\teol := buffer.Loc{X: util.CharacterCount(w.Buf.LineBytes(line)), Y: line}\n\treturn w.getVLocFromLoc(eol).Row + 1\n}\n\nfunc (w *BufWindow) scrollUp(s SLoc, n int) SLoc {\n\tfor n > 0 {\n\t\tif n <= s.Row {\n\t\t\ts.Row -= n\n\t\t\tn = 0\n\t\t} else if s.Line > 0 {\n\t\t\ts.Line--\n\t\t\tn -= s.Row + 1\n\t\t\ts.Row = w.getRowCount(s.Line) - 1\n\t\t} else {\n\t\t\ts.Row = 0\n\t\t\tbreak\n\t\t}\n\t}\n\treturn s\n}\n\nfunc (w *BufWindow) scrollDown(s SLoc, n int) SLoc {\n\tfor n > 0 {\n\t\trc := w.getRowCount(s.Line)\n\t\tif n < rc-s.Row {\n\t\t\ts.Row += n\n\t\t\tn = 0\n\t\t} else if s.Line < w.Buf.LinesNum()-1 {\n\t\t\ts.Line++\n\t\t\tn -= rc - s.Row\n\t\t\ts.Row = 0\n\t\t} else {\n\t\t\ts.Row = rc - 1\n\t\t\tbreak\n\t\t}\n\t}\n\treturn s\n}\n\nfunc (w *BufWindow) scroll(s SLoc, n int) SLoc {\n\tif n < 0 {\n\t\treturn w.scrollUp(s, -n)\n\t}\n\treturn w.scrollDown(s, n)\n}\n\nfunc (w *BufWindow) diff(s1, s2 SLoc) int {\n\tn := 0\n\tfor s1.LessThan(s2) {\n\t\tif s1.Line < s2.Line {\n\t\t\tn += w.getRowCount(s1.Line) - s1.Row\n\t\t\ts1.Line++\n\t\t\ts1.Row = 0\n\t\t} else {\n\t\t\tn += s2.Row - s1.Row\n\t\t\ts1.Row = s2.Row\n\t\t}\n\t}\n\treturn n\n}\n\n// Scroll returns the location which is n visual lines below the location s\n// i.e. the result of scrolling n lines down. n can be negative,\n// which means scrolling up. The returned location is guaranteed to be\n// within the buffer boundaries.\nfunc (w *BufWindow) Scroll(s SLoc, n int) SLoc {\n\tif !w.Buf.Settings[\"softwrap\"].(bool) {\n\t\ts.Line = util.Clamp(s.Line+n, 0, w.Buf.LinesNum()-1)\n\t\treturn s\n\t}\n\treturn w.scroll(s, n)\n}\n\n// Diff returns the difference (the vertical distance) between two SLocs.\nfunc (w *BufWindow) Diff(s1, s2 SLoc) int {\n\tif !w.Buf.Settings[\"softwrap\"].(bool) {\n\t\treturn s2.Line - s1.Line\n\t}\n\tif s1.GreaterThan(s2) {\n\t\treturn -w.diff(s2, s1)\n\t}\n\treturn w.diff(s1, s2)\n}\n\n// SLocFromLoc takes a position in the buffer and returns the location\n// of the visual line containing this position.\nfunc (w *BufWindow) SLocFromLoc(loc buffer.Loc) SLoc {\n\tif !w.Buf.Settings[\"softwrap\"].(bool) {\n\t\treturn SLoc{loc.Y, 0}\n\t}\n\treturn w.getVLocFromLoc(loc).SLoc\n}\n\n// VLocFromLoc takes a position in the buffer and returns the corresponding\n// visual location in the linewrapped buffer.\nfunc (w *BufWindow) VLocFromLoc(loc buffer.Loc) VLoc {\n\tif !w.Buf.Settings[\"softwrap\"].(bool) {\n\t\ttabsize := util.IntOpt(w.Buf.Settings[\"tabsize\"])\n\n\t\tvisualx := util.StringWidth(w.Buf.LineBytes(loc.Y), loc.X, tabsize)\n\t\treturn VLoc{SLoc{loc.Y, 0}, visualx}\n\t}\n\treturn w.getVLocFromLoc(loc)\n}\n\n// LocFromVLoc takes a visual location in the linewrapped buffer and returns\n// the position in the buffer corresponding to this visual location.\nfunc (w *BufWindow) LocFromVLoc(vloc VLoc) buffer.Loc {\n\tif !w.Buf.Settings[\"softwrap\"].(bool) {\n\t\ttabsize := util.IntOpt(w.Buf.Settings[\"tabsize\"])\n\n\t\tx := util.GetCharPosInLine(w.Buf.LineBytes(vloc.Line), vloc.VisualX, tabsize)\n\t\treturn buffer.Loc{x, vloc.Line}\n\t}\n\treturn w.getLocFromVLoc(vloc)\n}\n"
  },
  {
    "path": "internal/display/statusline.go",
    "content": "package display\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\tluar \"layeh.com/gopher-luar\"\n\n\trunewidth \"github.com/mattn/go-runewidth\"\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\tulua \"github.com/micro-editor/micro/v2/internal/lua\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n\tlua \"github.com/yuin/gopher-lua\"\n)\n\n// StatusLine represents the information line at the bottom\n// of each window\n// It gives information such as filename, whether the file has been\n// modified, filetype, cursor location\ntype StatusLine struct {\n\tInfo map[string]func(*buffer.Buffer) string\n\n\twin *BufWindow\n}\n\nvar statusInfo = map[string]func(*buffer.Buffer) string{\n\t\"filename\": func(b *buffer.Buffer) string {\n\t\treturn b.GetName()\n\t},\n\t\"line\": func(b *buffer.Buffer) string {\n\t\treturn strconv.Itoa(b.GetActiveCursor().Y + 1)\n\t},\n\t\"col\": func(b *buffer.Buffer) string {\n\t\treturn strconv.Itoa(b.GetActiveCursor().X + 1)\n\t},\n\t\"modified\": func(b *buffer.Buffer) string {\n\t\tif b.Modified() {\n\t\t\treturn \"+ \"\n\t\t}\n\t\tif b.Type.Readonly {\n\t\t\treturn \"[ro] \"\n\t\t}\n\t\treturn \"\"\n\t},\n\t\"overwrite\": func(b *buffer.Buffer) string {\n\t\tif b.OverwriteMode && !b.Type.Readonly {\n\t\t\treturn \"[ovwr] \"\n\t\t}\n\t\treturn \"\"\n\t},\n\t\"lines\": func(b *buffer.Buffer) string {\n\t\treturn strconv.Itoa(b.LinesNum())\n\t},\n\t\"percentage\": func(b *buffer.Buffer) string {\n\t\treturn strconv.Itoa((b.GetActiveCursor().Y + 1) * 100 / b.LinesNum())\n\t},\n}\n\nfunc SetStatusInfoFnLua(fn string) {\n\tluaFn := strings.Split(fn, \".\")\n\tif len(luaFn) <= 1 {\n\t\treturn\n\t}\n\tplName, plFn := luaFn[0], luaFn[1]\n\tpl := config.FindPlugin(plName)\n\tif pl == nil {\n\t\treturn\n\t}\n\tstatusInfo[fn] = func(b *buffer.Buffer) string {\n\t\tif pl == nil || !pl.IsLoaded() {\n\t\t\treturn \"\"\n\t\t}\n\t\tval, err := pl.Call(plFn, luar.New(ulua.L, b))\n\t\tif err == nil {\n\t\t\tif v, ok := val.(lua.LString); !ok {\n\t\t\t\tscreen.TermMessage(plFn, \"should return a string\")\n\t\t\t\treturn \"\"\n\t\t\t} else {\n\t\t\t\treturn string(v)\n\t\t\t}\n\t\t}\n\t\treturn \"\"\n\t}\n}\n\n// NewStatusLine returns a statusline bound to a window\nfunc NewStatusLine(win *BufWindow) *StatusLine {\n\ts := new(StatusLine)\n\ts.win = win\n\treturn s\n}\n\n// FindOpt finds a given option in the current buffer's settings\nfunc (s *StatusLine) FindOpt(opt string) any {\n\tif val, ok := s.win.Buf.Settings[opt]; ok {\n\t\treturn val\n\t}\n\treturn \"null\"\n}\n\nvar formatParser = regexp.MustCompile(`\\$\\(.+?\\)`)\n\n// Display draws the statusline to the screen\nfunc (s *StatusLine) Display() {\n\t// We'll draw the line at the lowest line in the window\n\ty := s.win.Height + s.win.Y - 1\n\n\twinX := s.win.X\n\n\tb := s.win.Buf\n\t// autocomplete suggestions (for the buffer, not for the infowindow)\n\tif b.HasSuggestions && len(b.Suggestions) > 1 {\n\t\tstatusLineStyle := config.DefStyle.Reverse(true)\n\t\tif style, ok := config.Colorscheme[\"statusline.suggestions\"]; ok {\n\t\t\tstatusLineStyle = style\n\t\t} else if style, ok := config.Colorscheme[\"statusline\"]; ok {\n\t\t\tstatusLineStyle = style\n\t\t}\n\t\tx := 0\n\t\tfor j, sug := range b.Suggestions {\n\t\t\tstyle := statusLineStyle\n\t\t\tif b.CurSuggestion == j {\n\t\t\t\tstyle = style.Reverse(true)\n\t\t\t}\n\t\t\tfor _, r := range sug {\n\t\t\t\tscreen.SetContent(winX+x, y, r, nil, style)\n\t\t\t\tx++\n\t\t\t\tif x >= s.win.Width {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tscreen.SetContent(winX+x, y, ' ', nil, statusLineStyle)\n\t\t\tx++\n\t\t\tif x >= s.win.Width {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tfor x < s.win.Width {\n\t\t\tscreen.SetContent(winX+x, y, ' ', nil, statusLineStyle)\n\t\t\tx++\n\t\t}\n\t\treturn\n\t}\n\n\tformatter := func(match []byte) []byte {\n\t\tname := match[2 : len(match)-1]\n\t\tif bytes.HasPrefix(name, []byte(\"opt\")) {\n\t\t\toption := name[4:]\n\t\t\treturn fmt.Append(nil, s.FindOpt(string(option)))\n\t\t} else if bytes.HasPrefix(name, []byte(\"bind\")) {\n\t\t\tbinding := string(name[5:])\n\t\t\tfor k, v := range config.Bindings[\"buffer\"] {\n\t\t\t\tif v == binding {\n\t\t\t\t\treturn []byte(k)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn []byte(\"null\")\n\t\t} else {\n\t\t\tif fn, ok := statusInfo[string(name)]; ok {\n\t\t\t\treturn []byte(fn(s.win.Buf))\n\t\t\t}\n\t\t\treturn []byte{}\n\t\t}\n\t}\n\n\tleftText := []byte(s.win.Buf.Settings[\"statusformatl\"].(string))\n\tleftText = formatParser.ReplaceAllFunc(leftText, formatter)\n\trightText := []byte(s.win.Buf.Settings[\"statusformatr\"].(string))\n\trightText = formatParser.ReplaceAllFunc(rightText, formatter)\n\n\tstatusLineStyle := config.DefStyle.Reverse(true)\n\tif s.win.IsActive() {\n\t\tif style, ok := config.Colorscheme[\"statusline\"]; ok {\n\t\t\tstatusLineStyle = style\n\t\t}\n\t} else {\n\t\tif style, ok := config.Colorscheme[\"statusline.inactive\"]; ok {\n\t\t\tstatusLineStyle = style\n\t\t} else if style, ok := config.Colorscheme[\"statusline\"]; ok {\n\t\t\tstatusLineStyle = style\n\t\t}\n\t}\n\n\tleftLen := util.StringWidth(leftText, util.CharacterCount(leftText), 1)\n\trightLen := util.StringWidth(rightText, util.CharacterCount(rightText), 1)\n\n\tfor x := 0; x < s.win.Width; x++ {\n\t\tif x < leftLen {\n\t\t\tr, combc, size := util.DecodeCharacter(leftText)\n\t\t\tleftText = leftText[size:]\n\t\t\trw := runewidth.RuneWidth(r)\n\t\t\tfor j := 0; j < rw; j++ {\n\t\t\t\tc := r\n\t\t\t\tif j > 0 {\n\t\t\t\t\tc = ' '\n\t\t\t\t\tcombc = nil\n\t\t\t\t\tx++\n\t\t\t\t}\n\t\t\t\tscreen.SetContent(winX+x, y, c, combc, statusLineStyle)\n\t\t\t}\n\t\t} else if x >= s.win.Width-rightLen && x < rightLen+s.win.Width-rightLen {\n\t\t\tr, combc, size := util.DecodeCharacter(rightText)\n\t\t\trightText = rightText[size:]\n\t\t\trw := runewidth.RuneWidth(r)\n\t\t\tfor j := 0; j < rw; j++ {\n\t\t\t\tc := r\n\t\t\t\tif j > 0 {\n\t\t\t\t\tc = ' '\n\t\t\t\t\tcombc = nil\n\t\t\t\t\tx++\n\t\t\t\t}\n\t\t\t\tscreen.SetContent(winX+x, y, c, combc, statusLineStyle)\n\t\t\t}\n\t\t} else {\n\t\t\tscreen.SetContent(winX+x, y, ' ', nil, statusLineStyle)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/display/tabwindow.go",
    "content": "package display\n\nimport (\n\trunewidth \"github.com/mattn/go-runewidth\"\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n\t\"github.com/micro-editor/tcell/v2\"\n)\n\ntype TabWindow struct {\n\tNames   []string\n\tactive  int\n\tY       int\n\tWidth   int\n\thscroll int\n}\n\nfunc NewTabWindow(w int, y int) *TabWindow {\n\ttw := new(TabWindow)\n\ttw.Width = w\n\ttw.Y = y\n\treturn tw\n}\n\nfunc (w *TabWindow) Resize(width, height int) {\n\tw.Width = width\n}\n\nfunc (w *TabWindow) LocFromVisual(vloc buffer.Loc) int {\n\tx := -w.hscroll\n\n\tfor i, n := range w.Names {\n\t\tx++\n\t\ts := util.CharacterCountInString(n)\n\t\tif vloc.Y == w.Y && vloc.X < x+s {\n\t\t\treturn i\n\t\t}\n\t\tx += s\n\t\tx += 3\n\t\tif x >= w.Width {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (w *TabWindow) Scroll(amt int) {\n\tw.hscroll += amt\n\ts := w.TotalSize()\n\tw.hscroll = util.Clamp(w.hscroll, 0, s-w.Width)\n\n\tif s-w.Width <= 0 {\n\t\tw.hscroll = 0\n\t}\n}\n\nfunc (w *TabWindow) TotalSize() int {\n\tsum := 2\n\tfor _, n := range w.Names {\n\t\tsum += runewidth.StringWidth(n) + 4\n\t}\n\treturn sum - 4\n}\n\nfunc (w *TabWindow) Active() int {\n\treturn w.active\n}\n\nfunc (w *TabWindow) SetActive(a int) {\n\tw.active = a\n\tx := 2\n\ts := w.TotalSize()\n\n\tfor i, n := range w.Names {\n\t\tc := util.CharacterCountInString(n)\n\t\tif i == a {\n\t\t\tif x+c >= w.hscroll+w.Width {\n\t\t\t\tw.hscroll = util.Clamp(x+c+1-w.Width, 0, s-w.Width)\n\t\t\t} else if x < w.hscroll {\n\t\t\t\tw.hscroll = util.Clamp(x-4, 0, s-w.Width)\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\tx += c + 4\n\t}\n\n\tif s-w.Width <= 0 {\n\t\tw.hscroll = 0\n\t}\n}\n\nfunc (w *TabWindow) Display() {\n\tx := -w.hscroll\n\tdone := false\n\n\tglobalTabReverse := config.GetGlobalOption(\"tabreverse\").(bool)\n\tglobalTabHighlight := config.GetGlobalOption(\"tabhighlight\").(bool)\n\n\t// xor of reverse and tab highlight to get tab character (as in filename and surrounding characters) reverse state\n\ttabCharHighlight := (globalTabReverse || globalTabHighlight) && !(globalTabReverse && globalTabHighlight)\n\n\treverseStyles := func(reverse bool) (tcell.Style, tcell.Style) {\n\t\ttabBarStyle := config.DefStyle.Reverse(reverse)\n\t\tif style, ok := config.Colorscheme[\"tabbar\"]; ok {\n\t\t\ttabBarStyle = style\n\t\t}\n\t\ttabBarActiveStyle := tabBarStyle\n\t\tif style, ok := config.Colorscheme[\"tabbar.active\"]; ok {\n\t\t\ttabBarActiveStyle = style\n\t\t}\n\t\treturn tabBarStyle, tabBarActiveStyle\n\t}\n\n\tdraw := func(r rune, n int, active bool, reversed bool) {\n\t\ttabBarStyle, tabBarActiveStyle := reverseStyles(reversed)\n\n\t\tstyle := tabBarStyle\n\t\tif active {\n\t\t\tstyle = tabBarActiveStyle\n\t\t}\n\t\tfor i := 0; i < n; i++ {\n\t\t\trw := runewidth.RuneWidth(r)\n\t\t\tfor j := 0; j < rw; j++ {\n\t\t\t\tc := r\n\t\t\t\tif j > 0 {\n\t\t\t\t\tc = ' '\n\t\t\t\t}\n\t\t\t\tif x == w.Width-1 && !done {\n\t\t\t\t\tscreen.SetContent(w.Width-1, w.Y, '>', nil, tabBarStyle)\n\t\t\t\t\tx++\n\t\t\t\t\tbreak\n\t\t\t\t} else if x == 0 && w.hscroll > 0 {\n\t\t\t\t\tscreen.SetContent(0, w.Y, '<', nil, tabBarStyle)\n\t\t\t\t} else if x >= 0 && x < w.Width {\n\t\t\t\t\tscreen.SetContent(x, w.Y, c, nil, style)\n\t\t\t\t}\n\t\t\t\tx++\n\t\t\t}\n\t\t}\n\t}\n\n\tfor i, n := range w.Names {\n\t\tif i == w.active {\n\t\t\tdraw('[', 1, true, tabCharHighlight)\n\t\t} else {\n\t\t\tdraw(' ', 1, false, tabCharHighlight)\n\t\t}\n\n\t\tfor _, c := range n {\n\t\t\tdraw(c, 1, i == w.active, tabCharHighlight)\n\t\t}\n\n\t\tif i == len(w.Names)-1 {\n\t\t\tdone = true\n\t\t}\n\n\t\tif i == w.active {\n\t\t\tdraw(']', 1, true, tabCharHighlight)\n\t\t\tdraw(' ', 2, true, globalTabReverse)\n\t\t} else {\n\t\t\tdraw(' ', 1, false, tabCharHighlight)\n\t\t\tdraw(' ', 2, false, globalTabReverse)\n\t\t}\n\n\t\tif x >= w.Width {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif x < w.Width {\n\t\tdraw(' ', w.Width-x, false, globalTabReverse)\n\t}\n}\n"
  },
  {
    "path": "internal/display/termwindow.go",
    "content": "package display\n\nimport (\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/micro/v2/internal/shell\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n\t\"github.com/micro-editor/tcell/v2\"\n\t\"github.com/micro-editor/terminal\"\n)\n\ntype TermWindow struct {\n\t*View\n\t*shell.Terminal\n\n\tactive bool\n}\n\nfunc NewTermWindow(x, y, w, h int, term *shell.Terminal) *TermWindow {\n\ttw := new(TermWindow)\n\ttw.View = new(View)\n\ttw.Terminal = term\n\ttw.X, tw.Y = x, y\n\ttw.Resize(w, h)\n\treturn tw\n}\n\n// Resize informs the terminal of a resize event\nfunc (w *TermWindow) Resize(width, height int) {\n\tif config.GetGlobalOption(\"statusline\").(bool) {\n\t\theight--\n\t}\n\tw.Term.Resize(width, height)\n\tw.Width, w.Height = width, height\n}\n\nfunc (w *TermWindow) SetActive(b bool) {\n\tw.active = b\n}\n\nfunc (w *TermWindow) IsActive() bool {\n\treturn w.active\n}\n\nfunc (w *TermWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc {\n\treturn vloc\n}\n\nfunc (w *TermWindow) Clear() {\n\tfor y := 0; y < w.Height; y++ {\n\t\tfor x := 0; x < w.Width; x++ {\n\t\t\tscreen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)\n\t\t}\n\t}\n}\n\nfunc (w *TermWindow) Relocate() bool { return true }\nfunc (w *TermWindow) GetView() *View {\n\treturn w.View\n}\nfunc (w *TermWindow) SetView(v *View) {\n\tw.View = v\n}\n\n// Display displays this terminal in a view\nfunc (w *TermWindow) Display() {\n\tw.State.Lock()\n\tdefer w.State.Unlock()\n\n\tvar l buffer.Loc\n\tfor y := 0; y < w.Height; y++ {\n\t\tfor x := 0; x < w.Width; x++ {\n\t\t\tl.X, l.Y = x, y\n\t\t\tc, f, b := w.State.Cell(x, y)\n\n\t\t\tfg, bg := int(f), int(b)\n\t\t\tif f == terminal.DefaultFG {\n\t\t\t\tfg = int(tcell.ColorDefault)\n\t\t\t}\n\t\t\tif b == terminal.DefaultBG {\n\t\t\t\tbg = int(tcell.ColorDefault)\n\t\t\t}\n\t\t\tst := tcell.StyleDefault.Foreground(config.GetColor256(fg)).Background(config.GetColor256(bg))\n\n\t\t\tif l.LessThan(w.Selection[1]) && l.GreaterEqual(w.Selection[0]) || l.LessThan(w.Selection[0]) && l.GreaterEqual(w.Selection[1]) {\n\t\t\t\tst = st.Reverse(true)\n\t\t\t}\n\n\t\t\tscreen.SetContent(w.X+x, w.Y+y, c, nil, st)\n\t\t}\n\t}\n\tif config.GetGlobalOption(\"statusline\").(bool) {\n\t\tstatusLineStyle := config.DefStyle.Reverse(true)\n\t\tif style, ok := config.Colorscheme[\"statusline\"]; ok {\n\t\t\tstatusLineStyle = style\n\t\t}\n\n\t\ttext := []byte(w.Name())\n\t\ttextLen := util.CharacterCount(text)\n\t\tfor x := 0; x < w.Width; x++ {\n\t\t\tif x < textLen {\n\t\t\t\tr, combc, size := util.DecodeCharacter(text)\n\t\t\t\ttext = text[size:]\n\t\t\t\tscreen.SetContent(w.X+x, w.Y+w.Height, r, combc, statusLineStyle)\n\t\t\t} else {\n\t\t\t\tscreen.SetContent(w.X+x, w.Y+w.Height, ' ', nil, statusLineStyle)\n\t\t\t}\n\t\t}\n\t}\n\tif w.State.CursorVisible() && w.active {\n\t\tcurx, cury := w.State.Cursor()\n\t\tif curx < w.Width && cury < w.Height {\n\t\t\tscreen.ShowCursor(curx+w.X, cury+w.Y)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/display/uiwindow.go",
    "content": "package display\n\nimport (\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n\t\"github.com/micro-editor/micro/v2/internal/views\"\n)\n\ntype UIWindow struct {\n\troot *views.Node\n}\n\nfunc NewUIWindow(n *views.Node) *UIWindow {\n\tuw := new(UIWindow)\n\tuw.root = n\n\treturn uw\n}\n\nfunc (w *UIWindow) drawNode(n *views.Node) {\n\tcs := n.Children()\n\tdividerStyle := config.DefStyle\n\tif style, ok := config.Colorscheme[\"divider\"]; ok {\n\t\tdividerStyle = style\n\t}\n\n\tdivchars := config.GetGlobalOption(\"divchars\").(string)\n\tif util.CharacterCountInString(divchars) != 2 {\n\t\tdivchars = \"|-\"\n\t}\n\n\tdivchar, combc, _ := util.DecodeCharacterInString(divchars)\n\n\tdivreverse := config.GetGlobalOption(\"divreverse\").(bool)\n\tif divreverse {\n\t\tdividerStyle = dividerStyle.Reverse(true)\n\t}\n\n\tfor i, c := range cs {\n\t\tif c.Kind == views.STVert {\n\t\t\tif i != len(cs)-1 {\n\t\t\t\tfor h := 0; h < c.H; h++ {\n\t\t\t\t\tscreen.SetContent(c.X+c.W, c.Y+h, divchar, combc, dividerStyle)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tw.drawNode(c)\n\t}\n}\n\nfunc (w *UIWindow) Display() {\n\tw.drawNode(w.root)\n}\n\nfunc (w *UIWindow) GetMouseSplitNode(vloc buffer.Loc) *views.Node {\n\tvar mouseLoc func(*views.Node) *views.Node\n\tmouseLoc = func(n *views.Node) *views.Node {\n\t\tcs := n.Children()\n\t\tfor i, c := range cs {\n\t\t\tif c.Kind == views.STVert {\n\t\t\t\tif i != len(cs)-1 {\n\t\t\t\t\tif vloc.X == c.X+c.W && vloc.Y >= c.Y && vloc.Y < c.Y+c.H {\n\t\t\t\t\t\treturn c\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if c.Kind == views.STHoriz {\n\t\t\t\tif i != len(cs)-1 {\n\t\t\t\t\tif vloc.Y == c.Y+c.H-1 && vloc.X >= c.X && vloc.X < c.X+c.W {\n\t\t\t\t\t\treturn c\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor _, c := range cs {\n\t\t\tm := mouseLoc(c)\n\t\t\tif m != nil {\n\t\t\t\treturn m\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\treturn mouseLoc(w.root)\n}\nfunc (w *UIWindow) Resize(width, height int) {}\nfunc (w *UIWindow) SetActive(b bool)         {}\n"
  },
  {
    "path": "internal/display/window.go",
    "content": "package display\n\nimport (\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n)\n\ntype View struct {\n\tX, Y          int // X,Y location of the view\n\tWidth, Height int // Width and height of the view\n\n\t// Start line of the view (for vertical scroll)\n\tStartLine SLoc\n\n\t// Start column of the view (for horizontal scroll)\n\t// note that since the starting column of every line is different if the view\n\t// is scrolled, StartCol is a visual index (will be the same for every line)\n\tStartCol int\n}\n\ntype Window interface {\n\tDisplay()\n\tClear()\n\tRelocate() bool\n\tGetView() *View\n\tSetView(v *View)\n\tLocFromVisual(vloc buffer.Loc) buffer.Loc\n\tResize(w, h int)\n\tSetActive(b bool)\n\tIsActive() bool\n}\n\ntype BWindow interface {\n\tWindow\n\tSoftWrap\n\tSetBuffer(b *buffer.Buffer)\n\tBufView() View\n}\n"
  },
  {
    "path": "internal/info/gutter.go",
    "content": "package info\n\n// A GutterMessage is a message displayed on the side of the editor\ntype GutterMessage struct {\n\tlineNum int\n\tmsg     string\n\tkind    int\n}\n\n// These are the different types of messages\nconst (\n\t// GutterInfo represents a simple info message\n\tGutterInfo = iota\n\t// GutterWarning represents a compiler warning\n\tGutterWarning\n\t// GutterError represents a compiler error\n\tGutterError\n)\n"
  },
  {
    "path": "internal/info/history.go",
    "content": "package info\n\nimport (\n\t\"bytes\"\n\t\"encoding/gob\"\n\t\"errors\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n)\n\n// LoadHistory attempts to load user history from configDir/buffers/history\n// into the history map\n// The savehistory option must be on\nfunc (i *InfoBuf) LoadHistory() {\n\tif config.GetGlobalOption(\"savehistory\").(bool) {\n\t\tfile, err := os.Open(filepath.Join(config.ConfigDir, \"buffers\", \"history\"))\n\t\tvar decodedMap map[string][]string\n\t\tif err != nil {\n\t\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\t\ti.Error(\"Error loading history: \", err)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tdefer file.Close()\n\t\terr = gob.NewDecoder(file).Decode(&decodedMap)\n\t\tif err != nil {\n\t\t\ti.Error(\"Error decoding history: \", err)\n\t\t\treturn\n\t\t}\n\n\t\tif decodedMap != nil {\n\t\t\ti.History = decodedMap\n\t\t}\n\t}\n}\n\n// SaveHistory saves the user's command history to configDir/buffers/history\n// only if the savehistory option is on\nfunc (i *InfoBuf) SaveHistory() {\n\tif config.GetGlobalOption(\"savehistory\").(bool) {\n\t\t// Don't save history past 100\n\t\tfor k, v := range i.History {\n\t\t\tif len(v) > 100 {\n\t\t\t\ti.History[k] = v[len(i.History[k])-100:]\n\t\t\t}\n\t\t}\n\n\t\tvar buf bytes.Buffer\n\t\terr := gob.NewEncoder(&buf).Encode(i.History)\n\t\tif err != nil {\n\t\t\tscreen.TermMessage(\"Error encoding history: \", err)\n\t\t\treturn\n\t\t}\n\n\t\tfilename := filepath.Join(config.ConfigDir, \"buffers\", \"history\")\n\t\terr = util.SafeWrite(filename, buf.Bytes(), true)\n\t\tif err != nil {\n\t\t\tscreen.TermMessage(\"Error saving history: \", err)\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// AddToHistory adds a new item to the history for the prompt type `ptype`.\n// This function is not used by micro itself. It is useful for plugins\n// which add their own items to the history, bypassing the infobar command line.\nfunc (i *InfoBuf) AddToHistory(ptype string, item string) {\n\tif i.HasPrompt && i.PromptType == ptype {\n\t\treturn\n\t}\n\n\tif _, ok := i.History[ptype]; !ok {\n\t\ti.History[ptype] = []string{item}\n\t} else {\n\t\ti.History[ptype] = append(i.History[ptype], item)\n\n\t\t// avoid duplicates\n\t\th := i.History[ptype]\n\t\tfor j := len(h) - 2; j >= 0; j-- {\n\t\t\tif h[j] == h[len(h)-1] {\n\t\t\t\ti.History[ptype] = append(h[:j], h[j+1:]...)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\n// UpHistory fetches the previous item in the history\nfunc (i *InfoBuf) UpHistory(history []string) {\n\tif i.HistoryNum > 0 && i.HasPrompt && !i.HasYN {\n\t\ti.HistoryNum--\n\t\ti.Replace(i.Start(), i.End(), history[i.HistoryNum])\n\t\ti.Buffer.GetActiveCursor().GotoLoc(i.End())\n\t}\n}\n\n// DownHistory fetches the next item in the history\nfunc (i *InfoBuf) DownHistory(history []string) {\n\tif i.HistoryNum < len(history)-1 && i.HasPrompt && !i.HasYN {\n\t\ti.HistoryNum++\n\t\ti.Replace(i.Start(), i.End(), history[i.HistoryNum])\n\t\ti.Buffer.GetActiveCursor().GotoLoc(i.End())\n\t}\n}\n\n// SearchUpHistory fetches the previous item in the history\n// beginning with the text in the infobuffer before cursor\nfunc (i *InfoBuf) SearchUpHistory(history []string) {\n\tif i.HistoryNum > 0 && i.HasPrompt && !i.HasYN {\n\t\ti.searchHistory(history, false)\n\t}\n}\n\n// SearchDownHistory fetches the next item in the history\n// beginning with the text in the infobuffer before cursor\nfunc (i *InfoBuf) SearchDownHistory(history []string) {\n\tif i.HistoryNum < len(history)-1 && i.HasPrompt && !i.HasYN {\n\t\ti.searchHistory(history, true)\n\t}\n}\n\nfunc (i *InfoBuf) searchHistory(history []string, down bool) {\n\tline := string(i.LineBytes(0))\n\tc := i.Buffer.GetActiveCursor()\n\n\tif !i.HistorySearch || !strings.HasPrefix(line, i.HistorySearchPrefix) {\n\t\ti.HistorySearch = true\n\t\ti.HistorySearchPrefix = util.SliceStartStr(line, c.X)\n\t}\n\n\tfound := -1\n\tif down {\n\t\tfor j := i.HistoryNum + 1; j < len(history); j++ {\n\t\t\tif strings.HasPrefix(history[j], i.HistorySearchPrefix) {\n\t\t\t\tfound = j\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfor j := i.HistoryNum - 1; j >= 0; j-- {\n\t\t\tif strings.HasPrefix(history[j], i.HistorySearchPrefix) {\n\t\t\t\tfound = j\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tif found != -1 {\n\t\ti.HistoryNum = found\n\t\ti.Replace(i.Start(), i.End(), history[found])\n\t\tc.GotoLoc(i.End())\n\t}\n}\n"
  },
  {
    "path": "internal/info/infobuffer.go",
    "content": "package info\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n)\n\n// The InfoBuf displays messages and other info at the bottom of the screen.\n// It is represented as a buffer and a message with a style.\ntype InfoBuf struct {\n\t*buffer.Buffer\n\n\tHasPrompt  bool\n\tHasMessage bool\n\tHasError   bool\n\tHasYN      bool\n\n\tPromptType string\n\n\tMsg    string\n\tYNResp bool\n\n\t// This map stores the history for all the different kinds of uses Prompt has\n\t// It's a map of history type -> history array\n\tHistory    map[string][]string\n\tHistoryNum int\n\t// HistorySearch indicates whether we are searching for history items\n\t// beginning with HistorySearchPrefix\n\tHistorySearch       bool\n\tHistorySearchPrefix string\n\n\t// Is the current message a message from the gutter\n\tHasGutter bool\n\n\tPromptCallback func(resp string, canceled bool)\n\tEventCallback  func(resp string)\n\tYNCallback     func(yes bool, canceled bool)\n}\n\n// NewBuffer returns a new infobuffer\nfunc NewBuffer() *InfoBuf {\n\tib := new(InfoBuf)\n\tib.History = make(map[string][]string)\n\n\tib.Buffer = buffer.NewBufferFromString(\"\", \"\", buffer.BTInfo)\n\tib.LoadHistory()\n\n\treturn ib\n}\n\n// Close performs any cleanup necessary when shutting down the infobuffer\nfunc (i *InfoBuf) Close() {\n\ti.SaveHistory()\n}\n\n// Message sends a message to the user\nfunc (i *InfoBuf) Message(msg ...any) {\n\t// only display a new message if there isn't an active prompt\n\t// this is to prevent overwriting an existing prompt to the user\n\tif !i.HasPrompt {\n\t\tdisplayMessage := fmt.Sprint(msg...)\n\t\t// if there is no active prompt then style and display the message as normal\n\t\ti.Msg = displayMessage\n\t\ti.HasMessage, i.HasError = true, false\n\t}\n}\n\n// GutterMessage displays a message and marks it as a gutter message\nfunc (i *InfoBuf) GutterMessage(msg ...any) {\n\ti.Message(msg...)\n\ti.HasGutter = true\n}\n\n// ClearGutter clears the info bar and unmarks the message\nfunc (i *InfoBuf) ClearGutter() {\n\ti.HasGutter = false\n\ti.Message(\"\")\n}\n\n// Error sends an error message to the user\nfunc (i *InfoBuf) Error(msg ...any) {\n\t// only display a new message if there isn't an active prompt\n\t// this is to prevent overwriting an existing prompt to the user\n\tif !i.HasPrompt {\n\t\t// if there is no active prompt then style and display the message as normal\n\t\ti.Msg = fmt.Sprint(msg...)\n\t\ti.HasMessage, i.HasError = false, true\n\t}\n\t// TODO: add to log?\n}\n\n// Prompt starts a prompt for the user, it takes a prompt, a possibly partially filled in msg\n// and callbacks executed when the user executes an event and when the user finishes the prompt\n// The eventcb passes the current user response as the argument and donecb passes the user's message\n// and a boolean indicating if the prompt was canceled\nfunc (i *InfoBuf) Prompt(prompt string, msg string, ptype string, eventcb func(string), donecb func(string, bool)) {\n\t// If we get another prompt mid-prompt we cancel the one getting overwritten\n\tif i.HasPrompt {\n\t\ti.DonePrompt(true)\n\t}\n\n\tif _, ok := i.History[ptype]; !ok {\n\t\ti.History[ptype] = []string{\"\"}\n\t} else {\n\t\ti.History[ptype] = append(i.History[ptype], \"\")\n\t}\n\ti.HistoryNum = len(i.History[ptype]) - 1\n\ti.HistorySearch = false\n\n\ti.PromptType = ptype\n\ti.Msg = prompt\n\ti.HasPrompt = true\n\ti.HasMessage, i.HasError, i.HasYN = false, false, false\n\ti.HasGutter = false\n\ti.PromptCallback = donecb\n\ti.EventCallback = eventcb\n\ti.Buffer.Insert(i.Buffer.Start(), msg)\n}\n\n// YNPrompt creates a yes or no prompt, and the callback returns the yes/no result and whether\n// the prompt was canceled\nfunc (i *InfoBuf) YNPrompt(prompt string, donecb func(bool, bool)) {\n\tif i.HasPrompt {\n\t\ti.DonePrompt(true)\n\t}\n\n\ti.Msg = prompt\n\ti.HasPrompt = true\n\ti.HasYN = true\n\ti.HasMessage, i.HasError = false, false\n\ti.HasGutter = false\n\ti.YNCallback = donecb\n}\n\n// DonePrompt finishes the current prompt and indicates whether or not it was canceled\nfunc (i *InfoBuf) DonePrompt(canceled bool) {\n\thadYN := i.HasYN\n\ti.HasPrompt = false\n\ti.HasYN = false\n\ti.HasGutter = false\n\tif !hadYN {\n\t\tif i.PromptCallback != nil {\n\t\t\tif canceled {\n\t\t\t\ti.Replace(i.Start(), i.End(), \"\")\n\t\t\t\th := i.History[i.PromptType]\n\t\t\t\ti.History[i.PromptType] = h[:len(h)-1]\n\t\t\t\ti.PromptCallback(\"\", true)\n\t\t\t} else {\n\t\t\t\tresp := string(i.LineBytes(0))\n\t\t\t\ti.Replace(i.Start(), i.End(), \"\")\n\t\t\t\th := i.History[i.PromptType]\n\t\t\t\th[len(h)-1] = resp\n\n\t\t\t\t// avoid duplicates\n\t\t\t\tfor j := len(h) - 2; j >= 0; j-- {\n\t\t\t\t\tif h[j] == h[len(h)-1] {\n\t\t\t\t\t\ti.History[i.PromptType] = append(h[:j], h[j+1:]...)\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\ti.PromptCallback(resp, false)\n\t\t\t}\n\t\t\t// i.PromptCallback = nil\n\t\t}\n\t}\n\tif i.YNCallback != nil && hadYN {\n\t\ti.YNCallback(i.YNResp, canceled)\n\t}\n}\n\n// Reset resets the infobuffer's msg and info\nfunc (i *InfoBuf) Reset() {\n\ti.Msg = \"\"\n\ti.HasPrompt, i.HasMessage, i.HasError = false, false, false\n\ti.HasGutter = false\n}\n"
  },
  {
    "path": "internal/lua/lua.go",
    "content": "package lua\n\nimport (\n\t\"archive/zip\"\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"math\"\n\t\"math/rand\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode/utf8\"\n\n\thumanize \"github.com/dustin/go-humanize\"\n\tlua \"github.com/yuin/gopher-lua\"\n\tluar \"layeh.com/gopher-luar\"\n)\n\nvar L *lua.LState\n\n// LoadFile loads a lua file\nfunc LoadFile(module string, file string, data []byte) error {\n\tpluginDef := []byte(\"module(\\\"\" + module + \"\\\", package.seeall)\")\n\n\tif fn, err := L.Load(bytes.NewReader(append(pluginDef, data...)), file); err != nil {\n\t\treturn err\n\t} else {\n\t\tL.Push(fn)\n\t\treturn L.PCall(0, lua.MultRet, nil)\n\t}\n}\n\n// Import allows a lua plugin to import a package\nfunc Import(pkg string) *lua.LTable {\n\tswitch pkg {\n\tcase \"fmt\":\n\t\treturn importFmt()\n\tcase \"io\":\n\t\treturn importIo()\n\tcase \"io/ioutil\", \"ioutil\":\n\t\treturn importIoUtil()\n\tcase \"net\":\n\t\treturn importNet()\n\tcase \"math\":\n\t\treturn importMath()\n\tcase \"math/rand\":\n\t\treturn importMathRand()\n\tcase \"os\":\n\t\treturn importOs()\n\tcase \"runtime\":\n\t\treturn importRuntime()\n\tcase \"path\":\n\t\treturn importPath()\n\tcase \"path/filepath\", \"filepath\":\n\t\treturn importFilePath()\n\tcase \"strings\":\n\t\treturn importStrings()\n\tcase \"regexp\":\n\t\treturn importRegexp()\n\tcase \"errors\":\n\t\treturn importErrors()\n\tcase \"time\":\n\t\treturn importTime()\n\tcase \"unicode/utf8\", \"utf8\":\n\t\treturn importUtf8()\n\tcase \"humanize\":\n\t\treturn importHumanize()\n\tcase \"net/http\", \"http\":\n\t\treturn importHTTP()\n\tcase \"archive/zip\":\n\t\treturn importArchiveZip()\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc importFmt() *lua.LTable {\n\tpkg := L.NewTable()\n\n\tL.SetField(pkg, \"Errorf\", luar.New(L, fmt.Errorf))\n\tL.SetField(pkg, \"Fprint\", luar.New(L, fmt.Fprint))\n\tL.SetField(pkg, \"Fprintf\", luar.New(L, fmt.Fprintf))\n\tL.SetField(pkg, \"Fprintln\", luar.New(L, fmt.Fprintln))\n\tL.SetField(pkg, \"Fscan\", luar.New(L, fmt.Fscan))\n\tL.SetField(pkg, \"Fscanf\", luar.New(L, fmt.Fscanf))\n\tL.SetField(pkg, \"Fscanln\", luar.New(L, fmt.Fscanln))\n\tL.SetField(pkg, \"Print\", luar.New(L, fmt.Print))\n\tL.SetField(pkg, \"Printf\", luar.New(L, fmt.Printf))\n\tL.SetField(pkg, \"Println\", luar.New(L, fmt.Println))\n\tL.SetField(pkg, \"Scan\", luar.New(L, fmt.Scan))\n\tL.SetField(pkg, \"Scanf\", luar.New(L, fmt.Scanf))\n\tL.SetField(pkg, \"Scanln\", luar.New(L, fmt.Scanln))\n\tL.SetField(pkg, \"Sprint\", luar.New(L, fmt.Sprint))\n\tL.SetField(pkg, \"Sprintf\", luar.New(L, fmt.Sprintf))\n\tL.SetField(pkg, \"Sprintln\", luar.New(L, fmt.Sprintln))\n\tL.SetField(pkg, \"Sscan\", luar.New(L, fmt.Sscan))\n\tL.SetField(pkg, \"Sscanf\", luar.New(L, fmt.Sscanf))\n\tL.SetField(pkg, \"Sscanln\", luar.New(L, fmt.Sscanln))\n\n\treturn pkg\n}\n\nfunc importIo() *lua.LTable {\n\tpkg := L.NewTable()\n\n\tL.SetField(pkg, \"Copy\", luar.New(L, io.Copy))\n\tL.SetField(pkg, \"CopyN\", luar.New(L, io.CopyN))\n\tL.SetField(pkg, \"EOF\", luar.New(L, io.EOF))\n\tL.SetField(pkg, \"ErrClosedPipe\", luar.New(L, io.ErrClosedPipe))\n\tL.SetField(pkg, \"ErrNoProgress\", luar.New(L, io.ErrNoProgress))\n\tL.SetField(pkg, \"ErrShortBuffer\", luar.New(L, io.ErrShortBuffer))\n\tL.SetField(pkg, \"ErrShortWrite\", luar.New(L, io.ErrShortWrite))\n\tL.SetField(pkg, \"ErrUnexpectedEOF\", luar.New(L, io.ErrUnexpectedEOF))\n\tL.SetField(pkg, \"LimitReader\", luar.New(L, io.LimitReader))\n\tL.SetField(pkg, \"MultiReader\", luar.New(L, io.MultiReader))\n\tL.SetField(pkg, \"MultiWriter\", luar.New(L, io.MultiWriter))\n\tL.SetField(pkg, \"NewSectionReader\", luar.New(L, io.NewSectionReader))\n\tL.SetField(pkg, \"Pipe\", luar.New(L, io.Pipe))\n\tL.SetField(pkg, \"ReadAll\", luar.New(L, io.ReadAll))\n\tL.SetField(pkg, \"ReadAtLeast\", luar.New(L, io.ReadAtLeast))\n\tL.SetField(pkg, \"ReadFull\", luar.New(L, io.ReadFull))\n\tL.SetField(pkg, \"TeeReader\", luar.New(L, io.TeeReader))\n\tL.SetField(pkg, \"WriteString\", luar.New(L, io.WriteString))\n\n\treturn pkg\n}\n\nfunc importIoUtil() *lua.LTable {\n\tpkg := L.NewTable()\n\n\tL.SetField(pkg, \"ReadAll\", luar.New(L, ioutil.ReadAll))\n\tL.SetField(pkg, \"ReadDir\", luar.New(L, ioutil.ReadDir))\n\tL.SetField(pkg, \"ReadFile\", luar.New(L, ioutil.ReadFile))\n\tL.SetField(pkg, \"WriteFile\", luar.New(L, ioutil.WriteFile))\n\n\treturn pkg\n}\n\nfunc importNet() *lua.LTable {\n\tpkg := L.NewTable()\n\n\tL.SetField(pkg, \"CIDRMask\", luar.New(L, net.CIDRMask))\n\tL.SetField(pkg, \"Dial\", luar.New(L, net.Dial))\n\tL.SetField(pkg, \"DialIP\", luar.New(L, net.DialIP))\n\tL.SetField(pkg, \"DialTCP\", luar.New(L, net.DialTCP))\n\tL.SetField(pkg, \"DialTimeout\", luar.New(L, net.DialTimeout))\n\tL.SetField(pkg, \"DialUDP\", luar.New(L, net.DialUDP))\n\tL.SetField(pkg, \"DialUnix\", luar.New(L, net.DialUnix))\n\tL.SetField(pkg, \"ErrWriteToConnected\", luar.New(L, net.ErrWriteToConnected))\n\tL.SetField(pkg, \"FileConn\", luar.New(L, net.FileConn))\n\tL.SetField(pkg, \"FileListener\", luar.New(L, net.FileListener))\n\tL.SetField(pkg, \"FilePacketConn\", luar.New(L, net.FilePacketConn))\n\tL.SetField(pkg, \"FlagBroadcast\", luar.New(L, net.FlagBroadcast))\n\tL.SetField(pkg, \"FlagLoopback\", luar.New(L, net.FlagLoopback))\n\tL.SetField(pkg, \"FlagMulticast\", luar.New(L, net.FlagMulticast))\n\tL.SetField(pkg, \"FlagPointToPoint\", luar.New(L, net.FlagPointToPoint))\n\tL.SetField(pkg, \"FlagUp\", luar.New(L, net.FlagUp))\n\tL.SetField(pkg, \"IPv4\", luar.New(L, net.IPv4))\n\tL.SetField(pkg, \"IPv4Mask\", luar.New(L, net.IPv4Mask))\n\tL.SetField(pkg, \"IPv4allrouter\", luar.New(L, net.IPv4allrouter))\n\tL.SetField(pkg, \"IPv4allsys\", luar.New(L, net.IPv4allsys))\n\tL.SetField(pkg, \"IPv4bcast\", luar.New(L, net.IPv4bcast))\n\tL.SetField(pkg, \"IPv4len\", luar.New(L, net.IPv4len))\n\tL.SetField(pkg, \"IPv4zero\", luar.New(L, net.IPv4zero))\n\tL.SetField(pkg, \"IPv6interfacelocalallnodes\", luar.New(L, net.IPv6interfacelocalallnodes))\n\tL.SetField(pkg, \"IPv6len\", luar.New(L, net.IPv6len))\n\tL.SetField(pkg, \"IPv6linklocalallnodes\", luar.New(L, net.IPv6linklocalallnodes))\n\tL.SetField(pkg, \"IPv6linklocalallrouters\", luar.New(L, net.IPv6linklocalallrouters))\n\tL.SetField(pkg, \"IPv6loopback\", luar.New(L, net.IPv6loopback))\n\tL.SetField(pkg, \"IPv6unspecified\", luar.New(L, net.IPv6unspecified))\n\tL.SetField(pkg, \"IPv6zero\", luar.New(L, net.IPv6zero))\n\tL.SetField(pkg, \"InterfaceAddrs\", luar.New(L, net.InterfaceAddrs))\n\tL.SetField(pkg, \"InterfaceByIndex\", luar.New(L, net.InterfaceByIndex))\n\tL.SetField(pkg, \"InterfaceByName\", luar.New(L, net.InterfaceByName))\n\tL.SetField(pkg, \"Interfaces\", luar.New(L, net.Interfaces))\n\tL.SetField(pkg, \"JoinHostPort\", luar.New(L, net.JoinHostPort))\n\tL.SetField(pkg, \"Listen\", luar.New(L, net.Listen))\n\tL.SetField(pkg, \"ListenIP\", luar.New(L, net.ListenIP))\n\tL.SetField(pkg, \"ListenMulticastUDP\", luar.New(L, net.ListenMulticastUDP))\n\tL.SetField(pkg, \"ListenPacket\", luar.New(L, net.ListenPacket))\n\tL.SetField(pkg, \"ListenTCP\", luar.New(L, net.ListenTCP))\n\tL.SetField(pkg, \"ListenUDP\", luar.New(L, net.ListenUDP))\n\tL.SetField(pkg, \"ListenUnix\", luar.New(L, net.ListenUnix))\n\tL.SetField(pkg, \"ListenUnixgram\", luar.New(L, net.ListenUnixgram))\n\tL.SetField(pkg, \"LookupAddr\", luar.New(L, net.LookupAddr))\n\tL.SetField(pkg, \"LookupCNAME\", luar.New(L, net.LookupCNAME))\n\tL.SetField(pkg, \"LookupHost\", luar.New(L, net.LookupHost))\n\tL.SetField(pkg, \"LookupIP\", luar.New(L, net.LookupIP))\n\tL.SetField(pkg, \"LookupMX\", luar.New(L, net.LookupMX))\n\tL.SetField(pkg, \"LookupNS\", luar.New(L, net.LookupNS))\n\tL.SetField(pkg, \"LookupPort\", luar.New(L, net.LookupPort))\n\tL.SetField(pkg, \"LookupSRV\", luar.New(L, net.LookupSRV))\n\tL.SetField(pkg, \"LookupTXT\", luar.New(L, net.LookupTXT))\n\tL.SetField(pkg, \"ParseCIDR\", luar.New(L, net.ParseCIDR))\n\tL.SetField(pkg, \"ParseIP\", luar.New(L, net.ParseIP))\n\tL.SetField(pkg, \"ParseMAC\", luar.New(L, net.ParseMAC))\n\tL.SetField(pkg, \"Pipe\", luar.New(L, net.Pipe))\n\tL.SetField(pkg, \"ResolveIPAddr\", luar.New(L, net.ResolveIPAddr))\n\tL.SetField(pkg, \"ResolveTCPAddr\", luar.New(L, net.ResolveTCPAddr))\n\tL.SetField(pkg, \"ResolveUDPAddr\", luar.New(L, net.ResolveUDPAddr))\n\tL.SetField(pkg, \"ResolveUnixAddr\", luar.New(L, net.ResolveUnixAddr))\n\tL.SetField(pkg, \"SplitHostPort\", luar.New(L, net.SplitHostPort))\n\n\treturn pkg\n}\n\nfunc importMath() *lua.LTable {\n\tpkg := L.NewTable()\n\n\tL.SetField(pkg, \"Abs\", luar.New(L, math.Abs))\n\tL.SetField(pkg, \"Acos\", luar.New(L, math.Acos))\n\tL.SetField(pkg, \"Acosh\", luar.New(L, math.Acosh))\n\tL.SetField(pkg, \"Asin\", luar.New(L, math.Asin))\n\tL.SetField(pkg, \"Asinh\", luar.New(L, math.Asinh))\n\tL.SetField(pkg, \"Atan\", luar.New(L, math.Atan))\n\tL.SetField(pkg, \"Atan2\", luar.New(L, math.Atan2))\n\tL.SetField(pkg, \"Atanh\", luar.New(L, math.Atanh))\n\tL.SetField(pkg, \"Cbrt\", luar.New(L, math.Cbrt))\n\tL.SetField(pkg, \"Ceil\", luar.New(L, math.Ceil))\n\tL.SetField(pkg, \"Copysign\", luar.New(L, math.Copysign))\n\tL.SetField(pkg, \"Cos\", luar.New(L, math.Cos))\n\tL.SetField(pkg, \"Cosh\", luar.New(L, math.Cosh))\n\tL.SetField(pkg, \"Dim\", luar.New(L, math.Dim))\n\tL.SetField(pkg, \"Erf\", luar.New(L, math.Erf))\n\tL.SetField(pkg, \"Erfc\", luar.New(L, math.Erfc))\n\tL.SetField(pkg, \"Exp\", luar.New(L, math.Exp))\n\tL.SetField(pkg, \"Exp2\", luar.New(L, math.Exp2))\n\tL.SetField(pkg, \"Expm1\", luar.New(L, math.Expm1))\n\tL.SetField(pkg, \"Float32bits\", luar.New(L, math.Float32bits))\n\tL.SetField(pkg, \"Float32frombits\", luar.New(L, math.Float32frombits))\n\tL.SetField(pkg, \"Float64bits\", luar.New(L, math.Float64bits))\n\tL.SetField(pkg, \"Float64frombits\", luar.New(L, math.Float64frombits))\n\tL.SetField(pkg, \"Floor\", luar.New(L, math.Floor))\n\tL.SetField(pkg, \"Frexp\", luar.New(L, math.Frexp))\n\tL.SetField(pkg, \"Gamma\", luar.New(L, math.Gamma))\n\tL.SetField(pkg, \"Hypot\", luar.New(L, math.Hypot))\n\tL.SetField(pkg, \"Ilogb\", luar.New(L, math.Ilogb))\n\tL.SetField(pkg, \"Inf\", luar.New(L, math.Inf))\n\tL.SetField(pkg, \"IsInf\", luar.New(L, math.IsInf))\n\tL.SetField(pkg, \"IsNaN\", luar.New(L, math.IsNaN))\n\tL.SetField(pkg, \"J0\", luar.New(L, math.J0))\n\tL.SetField(pkg, \"J1\", luar.New(L, math.J1))\n\tL.SetField(pkg, \"Jn\", luar.New(L, math.Jn))\n\tL.SetField(pkg, \"Ldexp\", luar.New(L, math.Ldexp))\n\tL.SetField(pkg, \"Lgamma\", luar.New(L, math.Lgamma))\n\tL.SetField(pkg, \"Log\", luar.New(L, math.Log))\n\tL.SetField(pkg, \"Log10\", luar.New(L, math.Log10))\n\tL.SetField(pkg, \"Log1p\", luar.New(L, math.Log1p))\n\tL.SetField(pkg, \"Log2\", luar.New(L, math.Log2))\n\tL.SetField(pkg, \"Logb\", luar.New(L, math.Logb))\n\tL.SetField(pkg, \"Max\", luar.New(L, math.Max))\n\tL.SetField(pkg, \"Min\", luar.New(L, math.Min))\n\tL.SetField(pkg, \"Mod\", luar.New(L, math.Mod))\n\tL.SetField(pkg, \"Modf\", luar.New(L, math.Modf))\n\tL.SetField(pkg, \"NaN\", luar.New(L, math.NaN))\n\tL.SetField(pkg, \"Nextafter\", luar.New(L, math.Nextafter))\n\tL.SetField(pkg, \"Pow\", luar.New(L, math.Pow))\n\tL.SetField(pkg, \"Pow10\", luar.New(L, math.Pow10))\n\tL.SetField(pkg, \"Remainder\", luar.New(L, math.Remainder))\n\tL.SetField(pkg, \"Signbit\", luar.New(L, math.Signbit))\n\tL.SetField(pkg, \"Sin\", luar.New(L, math.Sin))\n\tL.SetField(pkg, \"Sincos\", luar.New(L, math.Sincos))\n\tL.SetField(pkg, \"Sinh\", luar.New(L, math.Sinh))\n\tL.SetField(pkg, \"Sqrt\", luar.New(L, math.Sqrt))\n\tL.SetField(pkg, \"Tan\", luar.New(L, math.Tan))\n\tL.SetField(pkg, \"Tanh\", luar.New(L, math.Tanh))\n\tL.SetField(pkg, \"Trunc\", luar.New(L, math.Trunc))\n\tL.SetField(pkg, \"Y0\", luar.New(L, math.Y0))\n\tL.SetField(pkg, \"Y1\", luar.New(L, math.Y1))\n\tL.SetField(pkg, \"Yn\", luar.New(L, math.Yn))\n\n\treturn pkg\n}\n\nfunc importMathRand() *lua.LTable {\n\tpkg := L.NewTable()\n\n\tL.SetField(pkg, \"ExpFloat64\", luar.New(L, rand.ExpFloat64))\n\tL.SetField(pkg, \"Float32\", luar.New(L, rand.Float32))\n\tL.SetField(pkg, \"Float64\", luar.New(L, rand.Float64))\n\tL.SetField(pkg, \"Int\", luar.New(L, rand.Int))\n\tL.SetField(pkg, \"Int31\", luar.New(L, rand.Int31))\n\tL.SetField(pkg, \"Int31n\", luar.New(L, rand.Int31n))\n\tL.SetField(pkg, \"Int63\", luar.New(L, rand.Int63))\n\tL.SetField(pkg, \"Int63n\", luar.New(L, rand.Int63n))\n\tL.SetField(pkg, \"Intn\", luar.New(L, rand.Intn))\n\tL.SetField(pkg, \"NormFloat64\", luar.New(L, rand.NormFloat64))\n\tL.SetField(pkg, \"Perm\", luar.New(L, rand.Perm))\n\tL.SetField(pkg, \"Seed\", luar.New(L, rand.Seed))\n\tL.SetField(pkg, \"Uint32\", luar.New(L, rand.Uint32))\n\n\treturn pkg\n}\n\nfunc importOs() *lua.LTable {\n\tpkg := L.NewTable()\n\n\tL.SetField(pkg, \"Args\", luar.New(L, os.Args))\n\tL.SetField(pkg, \"Chdir\", luar.New(L, os.Chdir))\n\tL.SetField(pkg, \"Chmod\", luar.New(L, os.Chmod))\n\tL.SetField(pkg, \"Chown\", luar.New(L, os.Chown))\n\tL.SetField(pkg, \"Chtimes\", luar.New(L, os.Chtimes))\n\tL.SetField(pkg, \"Clearenv\", luar.New(L, os.Clearenv))\n\tL.SetField(pkg, \"Create\", luar.New(L, os.Create))\n\tL.SetField(pkg, \"DevNull\", luar.New(L, os.DevNull))\n\tL.SetField(pkg, \"Environ\", luar.New(L, os.Environ))\n\tL.SetField(pkg, \"ErrExist\", luar.New(L, os.ErrExist))\n\tL.SetField(pkg, \"ErrInvalid\", luar.New(L, os.ErrInvalid))\n\tL.SetField(pkg, \"ErrNotExist\", luar.New(L, os.ErrNotExist))\n\tL.SetField(pkg, \"ErrPermission\", luar.New(L, os.ErrPermission))\n\tL.SetField(pkg, \"Exit\", luar.New(L, os.Exit))\n\tL.SetField(pkg, \"Expand\", luar.New(L, os.Expand))\n\tL.SetField(pkg, \"ExpandEnv\", luar.New(L, os.ExpandEnv))\n\tL.SetField(pkg, \"FindProcess\", luar.New(L, os.FindProcess))\n\tL.SetField(pkg, \"Getegid\", luar.New(L, os.Getegid))\n\tL.SetField(pkg, \"Getenv\", luar.New(L, os.Getenv))\n\tL.SetField(pkg, \"Geteuid\", luar.New(L, os.Geteuid))\n\tL.SetField(pkg, \"Getgid\", luar.New(L, os.Getgid))\n\tL.SetField(pkg, \"Getgroups\", luar.New(L, os.Getgroups))\n\tL.SetField(pkg, \"Getpagesize\", luar.New(L, os.Getpagesize))\n\tL.SetField(pkg, \"Getpid\", luar.New(L, os.Getpid))\n\tL.SetField(pkg, \"Getuid\", luar.New(L, os.Getuid))\n\tL.SetField(pkg, \"Getwd\", luar.New(L, os.Getwd))\n\tL.SetField(pkg, \"Hostname\", luar.New(L, os.Hostname))\n\tL.SetField(pkg, \"Interrupt\", luar.New(L, os.Interrupt))\n\tL.SetField(pkg, \"IsExist\", luar.New(L, os.IsExist))\n\tL.SetField(pkg, \"IsNotExist\", luar.New(L, os.IsNotExist))\n\tL.SetField(pkg, \"IsPathSeparator\", luar.New(L, os.IsPathSeparator))\n\tL.SetField(pkg, \"IsPermission\", luar.New(L, os.IsPermission))\n\tL.SetField(pkg, \"Kill\", luar.New(L, os.Kill))\n\tL.SetField(pkg, \"Lchown\", luar.New(L, os.Lchown))\n\tL.SetField(pkg, \"Link\", luar.New(L, os.Link))\n\tL.SetField(pkg, \"Lstat\", luar.New(L, os.Lstat))\n\tL.SetField(pkg, \"Mkdir\", luar.New(L, os.Mkdir))\n\tL.SetField(pkg, \"MkdirAll\", luar.New(L, os.MkdirAll))\n\tL.SetField(pkg, \"ModeAppend\", luar.New(L, os.ModeAppend))\n\tL.SetField(pkg, \"ModeCharDevice\", luar.New(L, os.ModeCharDevice))\n\tL.SetField(pkg, \"ModeDevice\", luar.New(L, os.ModeDevice))\n\tL.SetField(pkg, \"ModeDir\", luar.New(L, os.ModeDir))\n\tL.SetField(pkg, \"ModeExclusive\", luar.New(L, os.ModeExclusive))\n\tL.SetField(pkg, \"ModeNamedPipe\", luar.New(L, os.ModeNamedPipe))\n\tL.SetField(pkg, \"ModePerm\", luar.New(L, os.ModePerm))\n\tL.SetField(pkg, \"ModeSetgid\", luar.New(L, os.ModeSetgid))\n\tL.SetField(pkg, \"ModeSetuid\", luar.New(L, os.ModeSetuid))\n\tL.SetField(pkg, \"ModeSocket\", luar.New(L, os.ModeSocket))\n\tL.SetField(pkg, \"ModeSticky\", luar.New(L, os.ModeSticky))\n\tL.SetField(pkg, \"ModeSymlink\", luar.New(L, os.ModeSymlink))\n\tL.SetField(pkg, \"ModeTemporary\", luar.New(L, os.ModeTemporary))\n\tL.SetField(pkg, \"ModeType\", luar.New(L, os.ModeType))\n\tL.SetField(pkg, \"NewFile\", luar.New(L, os.NewFile))\n\tL.SetField(pkg, \"NewSyscallError\", luar.New(L, os.NewSyscallError))\n\tL.SetField(pkg, \"O_APPEND\", luar.New(L, os.O_APPEND))\n\tL.SetField(pkg, \"O_CREATE\", luar.New(L, os.O_CREATE))\n\tL.SetField(pkg, \"O_EXCL\", luar.New(L, os.O_EXCL))\n\tL.SetField(pkg, \"O_RDONLY\", luar.New(L, os.O_RDONLY))\n\tL.SetField(pkg, \"O_RDWR\", luar.New(L, os.O_RDWR))\n\tL.SetField(pkg, \"O_SYNC\", luar.New(L, os.O_SYNC))\n\tL.SetField(pkg, \"O_TRUNC\", luar.New(L, os.O_TRUNC))\n\tL.SetField(pkg, \"O_WRONLY\", luar.New(L, os.O_WRONLY))\n\tL.SetField(pkg, \"Open\", luar.New(L, os.Open))\n\tL.SetField(pkg, \"OpenFile\", luar.New(L, os.OpenFile))\n\tL.SetField(pkg, \"PathListSeparator\", luar.New(L, os.PathListSeparator))\n\tL.SetField(pkg, \"PathSeparator\", luar.New(L, os.PathSeparator))\n\tL.SetField(pkg, \"Pipe\", luar.New(L, os.Pipe))\n\tL.SetField(pkg, \"ReadDir\", luar.New(L, os.ReadDir))\n\tL.SetField(pkg, \"ReadFile\", luar.New(L, os.ReadFile))\n\tL.SetField(pkg, \"Readlink\", luar.New(L, os.Readlink))\n\tL.SetField(pkg, \"Remove\", luar.New(L, os.Remove))\n\tL.SetField(pkg, \"RemoveAll\", luar.New(L, os.RemoveAll))\n\tL.SetField(pkg, \"Rename\", luar.New(L, os.Rename))\n\tL.SetField(pkg, \"SEEK_CUR\", luar.New(L, io.SeekCurrent))\n\tL.SetField(pkg, \"SEEK_END\", luar.New(L, io.SeekEnd))\n\tL.SetField(pkg, \"SEEK_SET\", luar.New(L, io.SeekStart))\n\tL.SetField(pkg, \"SameFile\", luar.New(L, os.SameFile))\n\tL.SetField(pkg, \"Setenv\", luar.New(L, os.Setenv))\n\tL.SetField(pkg, \"StartProcess\", luar.New(L, os.StartProcess))\n\tL.SetField(pkg, \"Stat\", luar.New(L, os.Stat))\n\tL.SetField(pkg, \"Stderr\", luar.New(L, os.Stderr))\n\tL.SetField(pkg, \"Stdin\", luar.New(L, os.Stdin))\n\tL.SetField(pkg, \"Stdout\", luar.New(L, os.Stdout))\n\tL.SetField(pkg, \"Symlink\", luar.New(L, os.Symlink))\n\tL.SetField(pkg, \"TempDir\", luar.New(L, os.TempDir))\n\tL.SetField(pkg, \"Truncate\", luar.New(L, os.Truncate))\n\tL.SetField(pkg, \"UserHomeDir\", luar.New(L, os.UserHomeDir))\n\tL.SetField(pkg, \"WriteFile\", luar.New(L, os.WriteFile))\n\n\treturn pkg\n}\n\nfunc importRuntime() *lua.LTable {\n\tpkg := L.NewTable()\n\n\tL.SetField(pkg, \"GC\", luar.New(L, runtime.GC))\n\tL.SetField(pkg, \"GOARCH\", luar.New(L, runtime.GOARCH))\n\tL.SetField(pkg, \"GOMAXPROCS\", luar.New(L, runtime.GOMAXPROCS))\n\tL.SetField(pkg, \"GOOS\", luar.New(L, runtime.GOOS))\n\tL.SetField(pkg, \"GOROOT\", luar.New(L, runtime.GOROOT))\n\n\treturn pkg\n}\n\nfunc importPath() *lua.LTable {\n\tpkg := L.NewTable()\n\n\tL.SetField(pkg, \"Base\", luar.New(L, path.Base))\n\tL.SetField(pkg, \"Clean\", luar.New(L, path.Clean))\n\tL.SetField(pkg, \"Dir\", luar.New(L, path.Dir))\n\tL.SetField(pkg, \"ErrBadPattern\", luar.New(L, path.ErrBadPattern))\n\tL.SetField(pkg, \"Ext\", luar.New(L, path.Ext))\n\tL.SetField(pkg, \"IsAbs\", luar.New(L, path.IsAbs))\n\tL.SetField(pkg, \"Join\", luar.New(L, path.Join))\n\tL.SetField(pkg, \"Match\", luar.New(L, path.Match))\n\tL.SetField(pkg, \"Split\", luar.New(L, path.Split))\n\n\treturn pkg\n}\n\nfunc importFilePath() *lua.LTable {\n\tpkg := L.NewTable()\n\n\tL.SetField(pkg, \"Abs\", luar.New(L, filepath.Abs))\n\tL.SetField(pkg, \"Base\", luar.New(L, filepath.Base))\n\tL.SetField(pkg, \"Clean\", luar.New(L, filepath.Clean))\n\tL.SetField(pkg, \"Dir\", luar.New(L, filepath.Dir))\n\tL.SetField(pkg, \"EvalSymlinks\", luar.New(L, filepath.EvalSymlinks))\n\tL.SetField(pkg, \"Ext\", luar.New(L, filepath.Ext))\n\tL.SetField(pkg, \"FromSlash\", luar.New(L, filepath.FromSlash))\n\tL.SetField(pkg, \"Glob\", luar.New(L, filepath.Glob))\n\tL.SetField(pkg, \"HasPrefix\", luar.New(L, filepath.HasPrefix))\n\tL.SetField(pkg, \"IsAbs\", luar.New(L, filepath.IsAbs))\n\tL.SetField(pkg, \"Join\", luar.New(L, filepath.Join))\n\tL.SetField(pkg, \"Match\", luar.New(L, filepath.Match))\n\tL.SetField(pkg, \"Rel\", luar.New(L, filepath.Rel))\n\tL.SetField(pkg, \"Split\", luar.New(L, filepath.Split))\n\tL.SetField(pkg, \"SplitList\", luar.New(L, filepath.SplitList))\n\tL.SetField(pkg, \"ToSlash\", luar.New(L, filepath.ToSlash))\n\tL.SetField(pkg, \"VolumeName\", luar.New(L, filepath.VolumeName))\n\n\treturn pkg\n}\n\nfunc importStrings() *lua.LTable {\n\tpkg := L.NewTable()\n\n\tL.SetField(pkg, \"Contains\", luar.New(L, strings.Contains))\n\tL.SetField(pkg, \"ContainsAny\", luar.New(L, strings.ContainsAny))\n\tL.SetField(pkg, \"ContainsRune\", luar.New(L, strings.ContainsRune))\n\tL.SetField(pkg, \"Count\", luar.New(L, strings.Count))\n\tL.SetField(pkg, \"EqualFold\", luar.New(L, strings.EqualFold))\n\tL.SetField(pkg, \"Fields\", luar.New(L, strings.Fields))\n\tL.SetField(pkg, \"FieldsFunc\", luar.New(L, strings.FieldsFunc))\n\tL.SetField(pkg, \"HasPrefix\", luar.New(L, strings.HasPrefix))\n\tL.SetField(pkg, \"HasSuffix\", luar.New(L, strings.HasSuffix))\n\tL.SetField(pkg, \"Index\", luar.New(L, strings.Index))\n\tL.SetField(pkg, \"IndexAny\", luar.New(L, strings.IndexAny))\n\tL.SetField(pkg, \"IndexByte\", luar.New(L, strings.IndexByte))\n\tL.SetField(pkg, \"IndexFunc\", luar.New(L, strings.IndexFunc))\n\tL.SetField(pkg, \"IndexRune\", luar.New(L, strings.IndexRune))\n\tL.SetField(pkg, \"Join\", luar.New(L, strings.Join))\n\tL.SetField(pkg, \"LastIndex\", luar.New(L, strings.LastIndex))\n\tL.SetField(pkg, \"LastIndexAny\", luar.New(L, strings.LastIndexAny))\n\tL.SetField(pkg, \"LastIndexFunc\", luar.New(L, strings.LastIndexFunc))\n\tL.SetField(pkg, \"Map\", luar.New(L, strings.Map))\n\tL.SetField(pkg, \"NewReader\", luar.New(L, strings.NewReader))\n\tL.SetField(pkg, \"NewReplacer\", luar.New(L, strings.NewReplacer))\n\tL.SetField(pkg, \"Repeat\", luar.New(L, strings.Repeat))\n\tL.SetField(pkg, \"Replace\", luar.New(L, strings.Replace))\n\tL.SetField(pkg, \"Split\", luar.New(L, strings.Split))\n\tL.SetField(pkg, \"SplitAfter\", luar.New(L, strings.SplitAfter))\n\tL.SetField(pkg, \"SplitAfterN\", luar.New(L, strings.SplitAfterN))\n\tL.SetField(pkg, \"SplitN\", luar.New(L, strings.SplitN))\n\tL.SetField(pkg, \"Title\", luar.New(L, strings.Title))\n\tL.SetField(pkg, \"ToLower\", luar.New(L, strings.ToLower))\n\tL.SetField(pkg, \"ToLowerSpecial\", luar.New(L, strings.ToLowerSpecial))\n\tL.SetField(pkg, \"ToTitle\", luar.New(L, strings.ToTitle))\n\tL.SetField(pkg, \"ToTitleSpecial\", luar.New(L, strings.ToTitleSpecial))\n\tL.SetField(pkg, \"ToUpper\", luar.New(L, strings.ToUpper))\n\tL.SetField(pkg, \"ToUpperSpecial\", luar.New(L, strings.ToUpperSpecial))\n\tL.SetField(pkg, \"Trim\", luar.New(L, strings.Trim))\n\tL.SetField(pkg, \"TrimFunc\", luar.New(L, strings.TrimFunc))\n\tL.SetField(pkg, \"TrimLeft\", luar.New(L, strings.TrimLeft))\n\tL.SetField(pkg, \"TrimLeftFunc\", luar.New(L, strings.TrimLeftFunc))\n\tL.SetField(pkg, \"TrimPrefix\", luar.New(L, strings.TrimPrefix))\n\tL.SetField(pkg, \"TrimRight\", luar.New(L, strings.TrimRight))\n\tL.SetField(pkg, \"TrimRightFunc\", luar.New(L, strings.TrimRightFunc))\n\tL.SetField(pkg, \"TrimSpace\", luar.New(L, strings.TrimSpace))\n\tL.SetField(pkg, \"TrimSuffix\", luar.New(L, strings.TrimSuffix))\n\n\treturn pkg\n}\n\nfunc importRegexp() *lua.LTable {\n\tpkg := L.NewTable()\n\n\tL.SetField(pkg, \"Match\", luar.New(L, regexp.Match))\n\tL.SetField(pkg, \"MatchReader\", luar.New(L, regexp.MatchReader))\n\tL.SetField(pkg, \"MatchString\", luar.New(L, regexp.MatchString))\n\tL.SetField(pkg, \"QuoteMeta\", luar.New(L, regexp.QuoteMeta))\n\tL.SetField(pkg, \"Compile\", luar.New(L, regexp.Compile))\n\tL.SetField(pkg, \"CompilePOSIX\", luar.New(L, regexp.CompilePOSIX))\n\tL.SetField(pkg, \"MustCompile\", luar.New(L, regexp.MustCompile))\n\tL.SetField(pkg, \"MustCompilePOSIX\", luar.New(L, regexp.MustCompilePOSIX))\n\n\treturn pkg\n}\n\nfunc importErrors() *lua.LTable {\n\tpkg := L.NewTable()\n\n\tL.SetField(pkg, \"New\", luar.New(L, errors.New))\n\n\treturn pkg\n}\n\nfunc importTime() *lua.LTable {\n\tpkg := L.NewTable()\n\n\tL.SetField(pkg, \"Sleep\", luar.New(L, time.Sleep))\n\tL.SetField(pkg, \"Since\", luar.New(L, time.Since))\n\tL.SetField(pkg, \"FixedZone\", luar.New(L, time.FixedZone))\n\tL.SetField(pkg, \"LoadLocation\", luar.New(L, time.LoadLocation))\n\tL.SetField(pkg, \"Date\", luar.New(L, time.Date))\n\tL.SetField(pkg, \"Now\", luar.New(L, time.Now))\n\tL.SetField(pkg, \"Parse\", luar.New(L, time.Parse))\n\tL.SetField(pkg, \"ParseDuration\", luar.New(L, time.ParseDuration))\n\tL.SetField(pkg, \"ParseInLocation\", luar.New(L, time.ParseInLocation))\n\tL.SetField(pkg, \"Unix\", luar.New(L, time.Unix))\n\tL.SetField(pkg, \"Nanosecond\", luar.New(L, time.Nanosecond))\n\tL.SetField(pkg, \"Microsecond\", luar.New(L, time.Microsecond))\n\tL.SetField(pkg, \"Millisecond\", luar.New(L, time.Millisecond))\n\tL.SetField(pkg, \"Second\", luar.New(L, time.Second))\n\tL.SetField(pkg, \"Minute\", luar.New(L, time.Minute))\n\tL.SetField(pkg, \"Hour\", luar.New(L, time.Hour))\n\n\t// TODO: these raw Go timer APIs don't provide any synchronization\n\t// with micro. Stop exposing them to lua once plugins switch to using\n\t// the safer micro.After() interface instead. See issue #3209\n\tL.SetField(pkg, \"After\", luar.New(L, time.After))\n\tL.SetField(pkg, \"AfterFunc\", luar.New(L, time.AfterFunc))\n\tL.SetField(pkg, \"NewTicker\", luar.New(L, time.NewTicker))\n\tL.SetField(pkg, \"NewTimer\", luar.New(L, time.NewTimer))\n\tL.SetField(pkg, \"Tick\", luar.New(L, time.Tick))\n\n\treturn pkg\n}\n\nfunc importUtf8() *lua.LTable {\n\tpkg := L.NewTable()\n\n\tL.SetField(pkg, \"DecodeLastRune\", luar.New(L, utf8.DecodeLastRune))\n\tL.SetField(pkg, \"DecodeLastRuneInString\", luar.New(L, utf8.DecodeLastRuneInString))\n\tL.SetField(pkg, \"DecodeRune\", luar.New(L, utf8.DecodeRune))\n\tL.SetField(pkg, \"DecodeRuneInString\", luar.New(L, utf8.DecodeRuneInString))\n\tL.SetField(pkg, \"EncodeRune\", luar.New(L, utf8.EncodeRune))\n\tL.SetField(pkg, \"FullRune\", luar.New(L, utf8.FullRune))\n\tL.SetField(pkg, \"FullRuneInString\", luar.New(L, utf8.FullRuneInString))\n\tL.SetField(pkg, \"RuneCount\", luar.New(L, utf8.RuneCount))\n\tL.SetField(pkg, \"RuneCountInString\", luar.New(L, utf8.RuneCountInString))\n\tL.SetField(pkg, \"RuneLen\", luar.New(L, utf8.RuneLen))\n\tL.SetField(pkg, \"RuneStart\", luar.New(L, utf8.RuneStart))\n\tL.SetField(pkg, \"Valid\", luar.New(L, utf8.Valid))\n\tL.SetField(pkg, \"ValidRune\", luar.New(L, utf8.ValidRune))\n\tL.SetField(pkg, \"ValidString\", luar.New(L, utf8.ValidString))\n\n\treturn pkg\n}\n\nfunc importHumanize() *lua.LTable {\n\tpkg := L.NewTable()\n\n\tL.SetField(pkg, \"Bytes\", luar.New(L, humanize.Bytes))\n\tL.SetField(pkg, \"Ordinal\", luar.New(L, humanize.Ordinal))\n\n\treturn pkg\n}\n\nfunc importHTTP() *lua.LTable {\n\tpkg := L.NewTable()\n\n\tL.SetField(pkg, \"Get\", luar.New(L, http.Get))\n\tL.SetField(pkg, \"Post\", luar.New(L, http.Post))\n\n\treturn pkg\n}\n\nfunc importArchiveZip() *lua.LTable {\n\tpkg := L.NewTable()\n\n\tL.SetField(pkg, \"OpenReader\", luar.New(L, zip.OpenReader))\n\tL.SetField(pkg, \"NewReader\", luar.New(L, zip.NewReader))\n\tL.SetField(pkg, \"NewWriter\", luar.New(L, zip.NewWriter))\n\n\treturn pkg\n}\n"
  },
  {
    "path": "internal/screen/message.go",
    "content": "package screen\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// TermMessage sends a message to the user in the terminal. This usually occurs before\n// micro has been fully initialized -- ie if there is an error in the syntax highlighting\n// regular expressions\n// The function must be called when the Screen is not initialized\n// This will write the message, and wait for the user\n// to press and key to continue\nfunc TermMessage(msg ...any) {\n\tscreenb := TempFini()\n\n\tfmt.Println(msg...)\n\tfmt.Print(\"\\nPress enter to continue\")\n\n\treader := bufio.NewReader(os.Stdin)\n\treader.ReadString('\\n')\n\n\tTempStart(screenb)\n}\n\n// TermPrompt prints a prompt and requests the user for a response\n// The result is matched against a list of options and the index of\n// the match is returned\n// If wait is true, the prompt re-prompts until a valid option is\n// chosen, otherwise if wait is false, -1 is returned for no match\nfunc TermPrompt(prompt string, options []string, wait bool) int {\n\tscreenb := TempFini()\n\n\tidx := -1\n\t// same behavior as do { ... } while (wait && idx == -1)\n\tfor ok := true; ok; ok = wait && idx == -1 {\n\t\treader := bufio.NewReader(os.Stdin)\n\t\tfmt.Print(prompt)\n\t\tresp, _ := reader.ReadString('\\n')\n\t\tresp = strings.TrimSpace(resp)\n\n\t\tfor i, opt := range options {\n\t\t\tif resp == opt {\n\t\t\t\tidx = i\n\t\t\t}\n\t\t}\n\n\t\tif wait && idx == -1 {\n\t\t\tfmt.Println(\"\\nInvalid choice.\")\n\t\t}\n\t}\n\n\tTempStart(screenb)\n\n\treturn idx\n}\n\n// TermError sends an error to the user in the terminal. Like TermMessage except formatted\n// as an error\nfunc TermError(filename string, lineNum int, err string) {\n\tTermMessage(filename + \", \" + strconv.Itoa(lineNum) + \": \" + err)\n}\n"
  },
  {
    "path": "internal/screen/screen.go",
    "content": "package screen\n\nimport (\n\t\"errors\"\n\t\"log\"\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/micro-editor/micro/v2/internal/config\"\n\t\"github.com/micro-editor/tcell/v2\"\n)\n\n// Screen is the tcell screen we use to draw to the terminal\n// Synchronization is used because we poll the screen on a separate\n// thread and sometimes the screen is shut down by the main thread\n// (for example on TermMessage) so we don't want to poll a nil/shutdown\n// screen. TODO: maybe we should worry about polling and drawing at the\n// same time too.\nvar Screen tcell.Screen\n\n// Events is the channel of tcell events\nvar Events chan (tcell.Event)\n\n// RestartCallback is called when the screen is restarted after it was\n// temporarily shut down\nvar RestartCallback func()\n\n// The lock is necessary since the screen is polled on a separate thread\nvar lock sync.Mutex\n\n// drawChan is a channel that will cause the screen to redraw when\n// written to even if no event user event has occurred\nvar drawChan chan bool\n\n// rawSeq is the list of raw escape sequences that are bound to some actions\n// via keybindings and thus should be parsed by tcell. We need to register\n// them in tcell every time we reinitialize the screen, so we need to remember\n// them in a list\nvar rawSeq = make([]string, 0)\n\n// Lock locks the screen lock\nfunc Lock() {\n\tlock.Lock()\n}\n\n// Unlock unlocks the screen lock\nfunc Unlock() {\n\tlock.Unlock()\n}\n\n// Redraw schedules a redraw with the draw channel\nfunc Redraw() {\n\tselect {\n\tcase drawChan <- true:\n\tdefault:\n\t\t// channel is full\n\t}\n}\n\n// DrawChan returns the draw channel\nfunc DrawChan() chan bool {\n\treturn drawChan\n}\n\ntype screenCell struct {\n\tx, y  int\n\tr     rune\n\tcombc []rune\n\tstyle tcell.Style\n}\n\nvar lastCursor screenCell\n\n// ShowFakeCursor displays a cursor at the given position by modifying the\n// style of the given column instead of actually using the terminal cursor\n// This can be useful in certain terminals such as the windows console where\n// modifying the cursor location is slow and frequent modifications cause flashing\n// This keeps track of the most recent fake cursor location and resets it when\n// a new fake cursor location is specified\nfunc ShowFakeCursor(x, y int) {\n\tr, combc, style, _ := Screen.GetContent(x, y)\n\tScreen.SetContent(lastCursor.x, lastCursor.y, lastCursor.r, lastCursor.combc, lastCursor.style)\n\tScreen.SetContent(x, y, r, combc, config.DefStyle.Reverse(true))\n\n\tlastCursor.x, lastCursor.y = x, y\n\tlastCursor.r = r\n\tlastCursor.combc = combc\n\tlastCursor.style = style\n}\n\nfunc UseFake() bool {\n\treturn config.GetGlobalOption(\"fakecursor\").(bool)\n}\n\n// ShowFakeCursorMulti is the same as ShowFakeCursor except it does not\n// reset previous locations of the cursor\n// Fake cursors are also necessary to display multiple cursors\nfunc ShowFakeCursorMulti(x, y int) {\n\tr, _, _, _ := Screen.GetContent(x, y)\n\tScreen.SetContent(x, y, r, nil, config.DefStyle.Reverse(true))\n}\n\n// ShowCursor puts the cursor at the given location using a fake cursor\n// if enabled or using the terminal cursor otherwise\n// By default only the windows console will use a fake cursor\nfunc ShowCursor(x, y int) {\n\tif UseFake() {\n\t\tShowFakeCursor(x, y)\n\t} else {\n\t\tScreen.ShowCursor(x, y)\n\t}\n}\n\n// SetContent sets a cell at a point on the screen and makes sure that it is\n// synced with the last cursor location\nfunc SetContent(x, y int, mainc rune, combc []rune, style tcell.Style) {\n\tif !Screen.CanDisplay(mainc, true) {\n\t\tmainc = '�'\n\t}\n\n\tScreen.SetContent(x, y, mainc, combc, style)\n\tif UseFake() && lastCursor.x == x && lastCursor.y == y {\n\t\tlastCursor.r = mainc\n\t\tlastCursor.style = style\n\t\tlastCursor.combc = combc\n\t}\n}\n\n// RegisterRawSeq registers a raw escape sequence that should be parsed by tcell\nfunc RegisterRawSeq(r string) {\n\tfor _, seq := range rawSeq {\n\t\tif seq == r {\n\t\t\treturn\n\t\t}\n\t}\n\trawSeq = append(rawSeq, r)\n\n\tif Screen != nil {\n\t\tScreen.RegisterRawSeq(r)\n\t}\n}\n\n// UnregisterRawSeq unregisters a raw escape sequence that should be parsed by tcell\nfunc UnregisterRawSeq(r string) {\n\tfor i, seq := range rawSeq {\n\t\tif seq == r {\n\t\t\trawSeq[i] = rawSeq[len(rawSeq)-1]\n\t\t\trawSeq = rawSeq[:len(rawSeq)-1]\n\t\t}\n\t}\n\n\tif Screen != nil {\n\t\tScreen.UnregisterRawSeq(r)\n\t}\n}\n\n// TempFini shuts the screen down temporarily\nfunc TempFini() bool {\n\tscreenWasNil := Screen == nil\n\n\tif !screenWasNil {\n\t\tScreen.Fini()\n\t\tLock()\n\t\tScreen = nil\n\t}\n\treturn screenWasNil\n}\n\n// TempStart restarts the screen after it was temporarily disabled\nfunc TempStart(screenWasNil bool) {\n\tif !screenWasNil {\n\t\tInit()\n\t\tUnlock()\n\n\t\tif RestartCallback != nil {\n\t\t\tRestartCallback()\n\t\t}\n\t}\n}\n\n// Init creates and initializes the tcell screen\nfunc Init() error {\n\tdrawChan = make(chan bool, 8)\n\n\t// Should we enable true color?\n\ttruecolor := config.GetGlobalOption(\"truecolor\").(string)\n\tif truecolor == \"on\" || (truecolor == \"auto\" && os.Getenv(\"MICRO_TRUECOLOR\") == \"1\") {\n\t\tos.Setenv(\"TCELL_TRUECOLOR\", \"enable\")\n\t} else if truecolor == \"off\" {\n\t\tos.Setenv(\"TCELL_TRUECOLOR\", \"disable\")\n\t} else {\n\t\t// For \"auto\", tcell already autodetects truecolor by default\n\t}\n\n\tvar oldTerm string\n\tmodifiedTerm := false\n\tsetXterm := func() {\n\t\toldTerm = os.Getenv(\"TERM\")\n\t\tos.Setenv(\"TERM\", \"xterm-256color\")\n\t\tmodifiedTerm = true\n\t}\n\n\tif config.GetGlobalOption(\"xterm\").(bool) {\n\t\tsetXterm()\n\t}\n\n\t// Initilize tcell\n\tvar err error\n\tScreen, err = tcell.NewScreen()\n\tif err != nil {\n\t\tlog.Println(\"Warning: during screen initialization:\", err)\n\t\tlog.Println(\"Falling back to TERM=xterm-256color\")\n\t\tsetXterm()\n\t\tScreen, err = tcell.NewScreen()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif err = Screen.Init(); err != nil {\n\t\treturn err\n\t}\n\n\tScreen.SetPaste(config.GetGlobalOption(\"paste\").(bool))\n\n\t// restore TERM\n\tif modifiedTerm {\n\t\tos.Setenv(\"TERM\", oldTerm)\n\t}\n\n\tif config.GetGlobalOption(\"mouse\").(bool) {\n\t\tScreen.EnableMouse()\n\t}\n\n\tfor _, r := range rawSeq {\n\t\tScreen.RegisterRawSeq(r)\n\t}\n\n\treturn nil\n}\n\n// InitSimScreen initializes a simulation screen for testing purposes\nfunc InitSimScreen() (tcell.SimulationScreen, error) {\n\tdrawChan = make(chan bool, 8)\n\n\t// Initilize tcell\n\tvar err error\n\ts := tcell.NewSimulationScreen(\"\")\n\tif s == nil {\n\t\treturn nil, errors.New(\"Failed to get a simulation screen\")\n\t}\n\tif err = s.Init(); err != nil {\n\t\treturn nil, err\n\t}\n\n\ts.SetSize(80, 24)\n\tScreen = s\n\n\tif config.GetGlobalOption(\"mouse\").(bool) {\n\t\tScreen.EnableMouse()\n\t}\n\n\treturn s, nil\n}\n"
  },
  {
    "path": "internal/shell/job.go",
    "content": "package shell\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"os/exec\"\n)\n\nvar Jobs chan JobFunction\n\nfunc init() {\n\tJobs = make(chan JobFunction, 100)\n}\n\n// Jobs are the way plugins can run processes in the background\n// A job is simply a process that gets executed asynchronously\n// There are callbacks for when the job exits, when the job creates stdout\n// and when the job creates stderr\n\n// These jobs run in a separate goroutine but the lua callbacks need to be\n// executed in the main thread (where the Lua VM is running) so they are\n// put into the jobs channel which gets read by the main loop\n\n// JobFunction is a representation of a job (this data structure is what is loaded\n// into the jobs channel)\ntype JobFunction struct {\n\tFunction func(string, []any)\n\tOutput   string\n\tArgs     []any\n}\n\n// A CallbackFile is the data structure that makes it possible to catch stderr and stdout write events\ntype CallbackFile struct {\n\tio.Writer\n\n\tcallback func(string, []any)\n\targs     []any\n}\n\n// Job stores the executing command for the job, and the stdin pipe\ntype Job struct {\n\t*exec.Cmd\n\tStdin io.WriteCloser\n}\n\nfunc (f *CallbackFile) Write(data []byte) (int, error) {\n\t// This is either stderr or stdout\n\t// In either case we create a new job function callback and put it in the jobs channel\n\tjobFunc := JobFunction{f.callback, string(data), f.args}\n\tJobs <- jobFunc\n\treturn f.Writer.Write(data)\n}\n\n// JobStart starts a shell command in the background with the given callbacks\n// It returns an *exec.Cmd as the job id\nfunc JobStart(cmd string, onStdout, onStderr, onExit func(string, []any), userargs ...any) *Job {\n\treturn JobSpawn(\"sh\", []string{\"-c\", cmd}, onStdout, onStderr, onExit, userargs...)\n}\n\n// JobSpawn starts a process with args in the background with the given callbacks\n// It returns an *exec.Cmd as the job id\nfunc JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(string, []any), userargs ...any) *Job {\n\t// Set up everything correctly if the functions have been provided\n\tproc := exec.Command(cmdName, cmdArgs...)\n\tvar outbuf bytes.Buffer\n\tif onStdout != nil {\n\t\tproc.Stdout = &CallbackFile{&outbuf, onStdout, userargs}\n\t} else {\n\t\tproc.Stdout = &outbuf\n\t}\n\tif onStderr != nil {\n\t\tproc.Stderr = &CallbackFile{&outbuf, onStderr, userargs}\n\t} else {\n\t\tproc.Stderr = &outbuf\n\t}\n\tstdin, _ := proc.StdinPipe()\n\n\tgo func() {\n\t\t// Run the process in the background and create the onExit callback\n\t\tproc.Run()\n\t\tif onExit != nil {\n\t\t\tjobFunc := JobFunction{onExit, outbuf.String(), userargs}\n\t\t\tJobs <- jobFunc\n\t\t}\n\t}()\n\n\treturn &Job{proc, stdin}\n}\n\n// JobStop kills a job\nfunc JobStop(j *Job) {\n\tj.Process.Kill()\n}\n\n// JobSend sends the given data into the job's stdin stream\nfunc JobSend(j *Job, data string) {\n\tj.Stdin.Write([]byte(data))\n}\n"
  },
  {
    "path": "internal/shell/shell.go",
    "content": "package shell\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/signal\"\n\n\tshellquote \"github.com/kballard/go-shellquote\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/micro/v2/internal/util\"\n)\n\n// ExecCommand executes a command using exec\n// It returns any output/errors\nfunc ExecCommand(name string, arg ...string) (string, error) {\n\tvar err error\n\tcmd := exec.Command(name, arg...)\n\toutputBytes := &bytes.Buffer{}\n\tcmd.Stdout = outputBytes\n\tcmd.Stderr = outputBytes\n\terr = cmd.Start()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\terr = cmd.Wait() // wait for command to finish\n\toutstring := outputBytes.String()\n\treturn outstring, err\n}\n\n// RunCommand executes a shell command and returns the output/error\nfunc RunCommand(input string) (string, error) {\n\targs, err := shellquote.Split(input)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif len(args) == 0 {\n\t\treturn \"\", errors.New(\"No arguments\")\n\t}\n\tinputCmd := args[0]\n\n\treturn ExecCommand(inputCmd, args[1:]...)\n}\n\n// RunBackgroundShell runs a shell command in the background\n// It returns a function which will run the command and returns a string\n// message result\nfunc RunBackgroundShell(input string) (func() string, error) {\n\targs, err := shellquote.Split(input)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(args) == 0 {\n\t\treturn nil, errors.New(\"No arguments\")\n\t}\n\tinputCmd := args[0]\n\treturn func() string {\n\t\toutput, err := RunCommand(input)\n\n\t\tstr := output\n\t\tif err != nil {\n\t\t\tstr = fmt.Sprint(inputCmd, \" exited with error: \", err, \": \", output)\n\t\t}\n\t\treturn str\n\t}, nil\n}\n\n// RunInteractiveShell runs a shellcommand interactively\nfunc RunInteractiveShell(input string, wait bool, getOutput bool) (string, error) {\n\targs, err := shellquote.Split(input)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif len(args) == 0 {\n\t\treturn \"\", errors.New(\"No arguments\")\n\t}\n\tinputCmd := args[0]\n\n\t// Shut down the screen because we're going to interact directly with the shell\n\tscreenb := screen.TempFini()\n\n\targs = args[1:]\n\n\t// Set up everything for the command\n\toutputBytes := &bytes.Buffer{}\n\tcmd := exec.Command(inputCmd, args...)\n\tcmd.Stdin = os.Stdin\n\tif getOutput {\n\t\tcmd.Stdout = io.MultiWriter(os.Stdout, outputBytes)\n\t} else {\n\t\tcmd.Stdout = os.Stdout\n\t}\n\tcmd.Stderr = os.Stderr\n\n\t// This is a trap for Ctrl-C so that it doesn't kill micro\n\t// micro is killed if the signal is ignored only on Windows, so it is\n\t// received\n\tc := make(chan os.Signal, 1)\n\tsignal.Reset(os.Interrupt)\n\tsignal.Notify(c, os.Interrupt)\n\terr = cmd.Start()\n\tif err == nil {\n\t\terr = cmd.Wait()\n\t\tif wait {\n\t\t\t// This is just so we don't return right away and let the user press enter to return\n\t\t\tscreen.TermMessage(\"\")\n\t\t}\n\t} else {\n\t\tscreen.TermMessage(err)\n\t}\n\n\toutput := outputBytes.String()\n\n\t// Start the screen back up\n\tscreen.TempStart(screenb)\n\n\tsignal.Notify(util.Sigterm, os.Interrupt)\n\tsignal.Stop(c)\n\n\treturn output, err\n}\n\n// UserCommand runs the shell command\n// The openTerm argument specifies whether a terminal should be opened (for viewing output\n// or interacting with stdin)\n// func UserCommand(input string, openTerm bool, waitToFinish bool) string {\n// \tif !openTerm {\n// \t\tRunBackgroundShell(input)\n// \t\treturn \"\"\n// \t} else {\n// \t\toutput, _ := RunInteractiveShell(input, waitToFinish, false)\n// \t\treturn output\n// \t}\n// }\n"
  },
  {
    "path": "internal/shell/terminal.go",
    "content": "package shell\n\nimport (\n\t\"bytes\"\n\t\"os/exec\"\n\t\"strconv\"\n\n\t\"github.com/micro-editor/micro/v2/internal/buffer\"\n\t\"github.com/micro-editor/micro/v2/internal/screen\"\n\t\"github.com/micro-editor/terminal\"\n)\n\ntype TermType int\ntype CallbackFunc func(string)\n\nconst (\n\tTTClose   = iota // Should be closed\n\tTTRunning        // Currently running a command\n\tTTDone           // Finished running a command\n)\n\nvar CloseTerms chan bool\n\nfunc init() {\n\tCloseTerms = make(chan bool)\n}\n\n// A Terminal holds information for the terminal emulator\ntype Terminal struct {\n\tState     terminal.State\n\tTerm      *terminal.VT\n\ttitle     string\n\tStatus    TermType\n\tSelection [2]buffer.Loc\n\twait      bool\n\tgetOutput bool\n\toutput    *bytes.Buffer\n\tcallback  CallbackFunc\n}\n\n// HasSelection returns whether this terminal has a valid selection\nfunc (t *Terminal) HasSelection() bool {\n\treturn t.Selection[0] != t.Selection[1]\n}\n\nfunc (t *Terminal) Name() string {\n\treturn t.title\n}\n\n// GetSelection returns the selected text\nfunc (t *Terminal) GetSelection(width int) string {\n\tstart := t.Selection[0]\n\tend := t.Selection[1]\n\tif start.GreaterThan(end) {\n\t\tstart, end = end, start\n\t}\n\tvar ret string\n\tvar l buffer.Loc\n\tfor y := start.Y; y <= end.Y; y++ {\n\t\tfor x := 0; x < width; x++ {\n\t\t\tl.X, l.Y = x, y\n\t\t\tif l.GreaterEqual(start) && l.LessThan(end) {\n\t\t\t\tc, _, _ := t.State.Cell(x, y)\n\t\t\t\tret += string(c)\n\t\t\t}\n\t\t}\n\t}\n\treturn ret\n}\n\n// Start begins a new command in this terminal with a given view\nfunc (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback func(out string, userargs []any), userargs []any) error {\n\tif len(execCmd) <= 0 {\n\t\treturn nil\n\t}\n\n\tcmd := exec.Command(execCmd[0], execCmd[1:]...)\n\tt.output = nil\n\tif getOutput {\n\t\tt.output = bytes.NewBuffer([]byte{})\n\t\tcmd.Stdout = t.output\n\t}\n\tTerm, _, err := terminal.Start(&t.State, cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tt.Term = Term\n\tt.getOutput = getOutput\n\tt.Status = TTRunning\n\tt.title = execCmd[0] + \":\" + strconv.Itoa(cmd.Process.Pid)\n\tt.wait = wait\n\tt.callback = func(out string) {\n\t\tcallback(out, userargs)\n\t}\n\n\tgo func() {\n\t\tfor {\n\t\t\terr := Term.Parse()\n\t\t\tif err != nil {\n\t\t\t\tTerm.Write([]byte(\"Press enter to close\"))\n\t\t\t\tscreen.Redraw()\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tscreen.Redraw()\n\t\t}\n\t\tt.Stop()\n\t}()\n\n\treturn nil\n}\n\n// Stop stops execution of the terminal and sets the Status\n// to TTDone\nfunc (t *Terminal) Stop() {\n\tt.Term.File().Close()\n\tt.Term.Close()\n\tif t.wait {\n\t\tt.Status = TTDone\n\t} else {\n\t\tt.Close()\n\t\tCloseTerms <- true\n\t}\n}\n\n// Close sets the Status to TTClose indicating that the terminal\n// is done and should be closed\nfunc (t *Terminal) Close() {\n\tt.Status = TTClose\n\t// call the lua function that the user has given as a callback\n\tif t.getOutput {\n\t\tif t.callback != nil {\n\t\t\tJobs <- JobFunction{\n\t\t\t\tFunction: func(out string, args []any) {\n\t\t\t\t\tt.callback(out)\n\t\t\t\t},\n\t\t\t\tOutput: t.output.String(),\n\t\t\t\tArgs:   nil,\n\t\t\t}\n\t\t}\n\t}\n}\n\n// WriteString writes a given string to this terminal's pty\nfunc (t *Terminal) WriteString(str string) {\n\tt.Term.File().WriteString(str)\n}\n"
  },
  {
    "path": "internal/util/lua.go",
    "content": "package util\n\n// LuaRuneAt is a helper function for lua plugins to return the rune\n// at an index within a string\nfunc LuaRuneAt(str string, runeidx int) string {\n\ti := 0\n\tfor len(str) > 0 {\n\t\tr, _, size := DecodeCharacterInString(str)\n\n\t\tstr = str[size:]\n\n\t\tif i == runeidx {\n\t\t\treturn string(r)\n\t\t}\n\n\t\ti++\n\t}\n\treturn \"\"\n}\n\n// LuaGetLeadingWhitespace returns the leading whitespace of a string (used by lua plugins)\nfunc LuaGetLeadingWhitespace(s string) string {\n\tws := []byte{}\n\tfor len(s) > 0 {\n\t\tr, _, size := DecodeCharacterInString(s)\n\t\tif r == ' ' || r == '\\t' {\n\t\t\tws = append(ws, byte(r))\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\n\t\ts = s[size:]\n\t}\n\treturn string(ws)\n}\n\n// LuaIsWordChar returns true if the first rune in a string is a word character\nfunc LuaIsWordChar(s string) bool {\n\tr, _, _ := DecodeCharacterInString(s)\n\treturn IsWordChar(r)\n}\n"
  },
  {
    "path": "internal/util/profile.go",
    "content": "package util\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"runtime\"\n\t\"time\"\n\n\thumanize \"github.com/dustin/go-humanize\"\n)\n\n// GetMemStats returns a string describing the memory usage and gc time used so far\nfunc GetMemStats() string {\n\tvar memstats runtime.MemStats\n\truntime.ReadMemStats(&memstats)\n\treturn fmt.Sprintf(\"Alloc: %s, Sys: %s, GC: %d, PauseTotalNs: %dns\", humanize.Bytes(memstats.Alloc), humanize.Bytes(memstats.Sys), memstats.NumGC, memstats.PauseTotalNs)\n}\n\nfunc Tic(s string) time.Time {\n\tlog.Println(\"START:\", s)\n\treturn time.Now()\n}\n\nfunc Toc(start time.Time) {\n\tend := time.Now()\n\tlog.Println(\"END: ElapsedTime in seconds:\", end.Sub(start))\n}\n"
  },
  {
    "path": "internal/util/unicode.go",
    "content": "package util\n\nimport (\n\t\"unicode\"\n\t\"unicode/utf8\"\n)\n\n// Unicode is annoying. A \"code point\" (rune in Go-speak) may need up to\n// 4 bytes to represent it. In general, a code point will represent a\n// complete character, but this is not always the case. A character with\n// accents may be made up of multiple code points (the code point for the\n// original character, and additional code points for each accent/marking).\n// The functions below are meant to help deal with these additional \"combining\"\n// code points. In underlying operations (search, replace, etc...), micro will\n// treat a character with combining code points as just the original code point.\n// For rendering, micro will display the combining characters. It's not perfect\n// but it's pretty good.\n\nvar minMark = rune(unicode.Mark.R16[0].Lo)\n\nfunc isMark(r rune) bool {\n\t// Fast path\n\tif r < minMark {\n\t\treturn false\n\t}\n\treturn unicode.In(r, unicode.Mark)\n}\n\n// DecodeCharacter returns the next character from an array of bytes\n// A character is a rune along with any accompanying combining runes\nfunc DecodeCharacter(b []byte) (rune, []rune, int) {\n\tr, size := utf8.DecodeRune(b)\n\tb = b[size:]\n\tc, s := utf8.DecodeRune(b)\n\n\tvar combc []rune\n\tfor isMark(c) {\n\t\tcombc = append(combc, c)\n\t\tsize += s\n\n\t\tb = b[s:]\n\t\tc, s = utf8.DecodeRune(b)\n\t}\n\n\treturn r, combc, size\n}\n\n// DecodeCharacterInString returns the next character from a string\n// A character is a rune along with any accompanying combining runes\nfunc DecodeCharacterInString(str string) (rune, []rune, int) {\n\tr, size := utf8.DecodeRuneInString(str)\n\tstr = str[size:]\n\tc, s := utf8.DecodeRuneInString(str)\n\n\tvar combc []rune\n\tfor isMark(c) {\n\t\tcombc = append(combc, c)\n\t\tsize += s\n\n\t\tstr = str[s:]\n\t\tc, s = utf8.DecodeRuneInString(str)\n\t}\n\n\treturn r, combc, size\n}\n\n// CharacterCount returns the number of characters in a byte array\n// Similar to utf8.RuneCount but for unicode characters\nfunc CharacterCount(b []byte) int {\n\ts := 0\n\n\tfor len(b) > 0 {\n\t\tr, size := utf8.DecodeRune(b)\n\t\tif !isMark(r) {\n\t\t\ts++\n\t\t}\n\n\t\tb = b[size:]\n\t}\n\n\treturn s\n}\n\n// CharacterCount returns the number of characters in a string\n// Similar to utf8.RuneCountInString but for unicode characters\nfunc CharacterCountInString(str string) int {\n\ts := 0\n\n\tfor _, r := range str {\n\t\tif !isMark(r) {\n\t\t\ts++\n\t\t}\n\t}\n\n\treturn s\n}\n"
  },
  {
    "path": "internal/util/util.go",
    "content": "package util\n\nimport (\n\t\"archive/zip\"\n\t\"bytes\"\n\t\"crypto/md5\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/user\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n\n\t\"github.com/blang/semver\"\n\trunewidth \"github.com/mattn/go-runewidth\"\n)\n\nvar (\n\t// These variables should be set by the linker when compiling\n\n\t// Version is the version number or commit hash\n\tVersion = \"0.0.0-unknown\"\n\t// SemVersion is the Semantic version\n\tSemVersion semver.Version\n\t// CommitHash is the commit this version was built on\n\tCommitHash = \"Unknown\"\n\t// CompileDate is the date this binary was compiled on\n\tCompileDate = \"Unknown\"\n\t// Debug logging\n\tDebug = \"OFF\"\n\n\t// Stdout is a buffer that is written to stdout when micro closes\n\tStdout *bytes.Buffer\n\t// Sigterm is a channel where micro exits when written\n\tSigterm chan os.Signal\n\n\t// To be used for fails on (over-)write with safe writes\n\tErrOverwrite = OverwriteError{}\n)\n\n// To be used for file writes before umask is applied\nconst FileMode os.FileMode = 0666\n\nconst BackupSuffix = \".micro-backup\"\n\nconst OverwriteFailMsg = `An error occurred while writing to the file:\n\n%s\n\nThe file may be corrupted now. The good news is that it has been\nsuccessfully backed up. Next time you open this file with Micro,\nMicro will ask if you want to recover it from the backup.\n\nThe backup path is:\n\n%s`\n\n// OverwriteError is a custom error to add additional information\ntype OverwriteError struct {\n\tWhat       error\n\tBackupName string\n}\n\nfunc (e OverwriteError) Error() string {\n\treturn fmt.Sprintf(OverwriteFailMsg, e.What, e.BackupName)\n}\n\nfunc (e OverwriteError) Is(target error) bool {\n\treturn target == ErrOverwrite\n}\n\nfunc (e OverwriteError) Unwrap() error {\n\treturn e.What\n}\n\nfunc init() {\n\tvar err error\n\tSemVersion, err = semver.Make(Version)\n\tif err != nil {\n\t\tfmt.Println(\"Invalid version: \", Version, err)\n\t}\n\n\tStdout = new(bytes.Buffer)\n}\n\n// SliceEnd returns a byte slice where the index is a rune index\n// Slices off the start of the slice\nfunc SliceEnd(slc []byte, index int) []byte {\n\tlen := len(slc)\n\ti := 0\n\ttotalSize := 0\n\tfor totalSize < len {\n\t\tif i >= index {\n\t\t\treturn slc[totalSize:]\n\t\t}\n\n\t\t_, _, size := DecodeCharacter(slc[totalSize:])\n\t\ttotalSize += size\n\t\ti++\n\t}\n\n\treturn slc[totalSize:]\n}\n\n// SliceEndStr is the same as SliceEnd but for strings\nfunc SliceEndStr(str string, index int) string {\n\tlen := len(str)\n\ti := 0\n\ttotalSize := 0\n\tfor totalSize < len {\n\t\tif i >= index {\n\t\t\treturn str[totalSize:]\n\t\t}\n\n\t\t_, _, size := DecodeCharacterInString(str[totalSize:])\n\t\ttotalSize += size\n\t\ti++\n\t}\n\n\treturn str[totalSize:]\n}\n\n// SliceStart returns a byte slice where the index is a rune index\n// Slices off the end of the slice\nfunc SliceStart(slc []byte, index int) []byte {\n\tlen := len(slc)\n\ti := 0\n\ttotalSize := 0\n\tfor totalSize < len {\n\t\tif i >= index {\n\t\t\treturn slc[:totalSize]\n\t\t}\n\n\t\t_, _, size := DecodeCharacter(slc[totalSize:])\n\t\ttotalSize += size\n\t\ti++\n\t}\n\n\treturn slc[:totalSize]\n}\n\n// SliceStartStr is the same as SliceStart but for strings\nfunc SliceStartStr(str string, index int) string {\n\tlen := len(str)\n\ti := 0\n\ttotalSize := 0\n\tfor totalSize < len {\n\t\tif i >= index {\n\t\t\treturn str[:totalSize]\n\t\t}\n\n\t\t_, _, size := DecodeCharacterInString(str[totalSize:])\n\t\ttotalSize += size\n\t\ti++\n\t}\n\n\treturn str[:totalSize]\n}\n\n// SliceVisualEnd will take a byte slice and slice off the start\n// up to a given visual index. If the index is in the middle of a\n// rune the number of visual columns into the rune will be returned\n// It will also return the char pos of the first character of the slice\nfunc SliceVisualEnd(b []byte, n, tabsize int) ([]byte, int, int) {\n\twidth := 0\n\ti := 0\n\tfor len(b) > 0 {\n\t\tr, _, size := DecodeCharacter(b)\n\n\t\tw := 0\n\t\tswitch r {\n\t\tcase '\\t':\n\t\t\tts := tabsize - (width % tabsize)\n\t\t\tw = ts\n\t\tdefault:\n\t\t\tw = runewidth.RuneWidth(r)\n\t\t}\n\t\tif width+w > n {\n\t\t\treturn b, n - width, i\n\t\t}\n\t\twidth += w\n\t\tb = b[size:]\n\t\ti++\n\t}\n\treturn b, n - width, i\n}\n\n// Abs is a simple absolute value function for ints\nfunc Abs(n int) int {\n\tif n < 0 {\n\t\treturn -n\n\t}\n\treturn n\n}\n\n// StringWidth returns the visual width of a byte array indexed from 0 to n (rune index)\n// with a given tabsize\nfunc StringWidth(b []byte, n, tabsize int) int {\n\tif n <= 0 {\n\t\treturn 0\n\t}\n\ti := 0\n\twidth := 0\n\tfor len(b) > 0 {\n\t\tr, _, size := DecodeCharacter(b)\n\t\tb = b[size:]\n\n\t\tswitch r {\n\t\tcase '\\t':\n\t\t\tts := tabsize - (width % tabsize)\n\t\t\twidth += ts\n\t\tdefault:\n\t\t\twidth += runewidth.RuneWidth(r)\n\t\t}\n\n\t\ti++\n\n\t\tif i == n {\n\t\t\treturn width\n\t\t}\n\t}\n\treturn width\n}\n\n// Min takes the min of two ints\nfunc Min(a, b int) int {\n\tif a > b {\n\t\treturn b\n\t}\n\treturn a\n}\n\n// Max takes the max of two ints\nfunc Max(a, b int) int {\n\tif a > b {\n\t\treturn a\n\t}\n\treturn b\n}\n\n// FSize gets the size of a file\nfunc FSize(f *os.File) int64 {\n\tfi, _ := f.Stat()\n\treturn fi.Size()\n}\n\n// IsWordChar returns whether or not a rune is a 'word character'\n// Word characters are defined as numbers, letters or sub-word delimiters\nfunc IsWordChar(r rune) bool {\n\treturn IsAlphanumeric(r) || IsSubwordDelimiter(r)\n}\n\n// IsNonWordChar returns whether or not a rune is not a 'word character'\n// Non word characters are defined as all characters not being numbers, letters or sub-word delimiters\n// See IsWordChar()\nfunc IsNonWordChar(r rune) bool {\n\treturn !IsWordChar(r)\n}\n\n// IsSubwordDelimiter returns whether or not a rune is a 'sub-word delimiter character'\n// i.e. is considered a part of the word and is used as a delimiter between sub-words of the word.\n// For now the only sub-word delimiter character is '_'.\nfunc IsSubwordDelimiter(r rune) bool {\n\treturn r == '_'\n}\n\n// IsAlphanumeric returns whether or not a rune is an 'alphanumeric character'\n// Alphanumeric characters are defined as numbers or letters\nfunc IsAlphanumeric(r rune) bool {\n\treturn unicode.IsLetter(r) || unicode.IsNumber(r)\n}\n\n// IsUpperAlphanumeric returns whether or not a rune is an 'upper alphanumeric character'\n// Upper alphanumeric characters are defined as numbers or upper-case letters\nfunc IsUpperAlphanumeric(r rune) bool {\n\treturn IsUpperLetter(r) || unicode.IsNumber(r)\n}\n\n// IsLowerAlphanumeric returns whether or not a rune is a 'lower alphanumeric character'\n// Lower alphanumeric characters are defined as numbers or lower-case letters\nfunc IsLowerAlphanumeric(r rune) bool {\n\treturn IsLowerLetter(r) || unicode.IsNumber(r)\n}\n\n// IsUpperLetter returns whether or not a rune is an 'upper letter character'\n// Upper letter characters are defined as upper-case letters\nfunc IsUpperLetter(r rune) bool {\n\t// unicode.IsUpper() returns true for letters only\n\treturn unicode.IsUpper(r)\n}\n\n// IsLowerLetter returns whether or not a rune is a 'lower letter character'\n// Lower letter characters are defined as lower-case letters\nfunc IsLowerLetter(r rune) bool {\n\t// unicode.IsLower() returns true for letters only\n\treturn unicode.IsLower(r)\n}\n\n// Spaces returns a string with n spaces\nfunc Spaces(n int) string {\n\treturn strings.Repeat(\" \", n)\n}\n\n// IsSpaces checks if a given string is only spaces\nfunc IsSpaces(str []byte) bool {\n\tfor _, c := range str {\n\t\tif c != ' ' {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\n// IsSpacesOrTabs checks if a given string contains only spaces and tabs\nfunc IsSpacesOrTabs(str []byte) bool {\n\tfor _, c := range str {\n\t\tif c != ' ' && c != '\\t' {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\n// IsWhitespace returns true if the given rune is a space, tab, or newline\nfunc IsWhitespace(c rune) bool {\n\treturn unicode.IsSpace(c)\n}\n\n// IsBytesWhitespace returns true if the given bytes are all whitespace\nfunc IsBytesWhitespace(b []byte) bool {\n\tfor _, c := range b {\n\t\tif !IsWhitespace(rune(c)) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// RunePos returns the rune index of a given byte index\n// Make sure the byte index is not between code points\nfunc RunePos(b []byte, i int) int {\n\treturn CharacterCount(b[:i])\n}\n\n// IndexAnyUnquoted returns the first position in s of a character from chars.\n// Escaped (with backslash) and quoted (with single or double quotes) characters\n// are ignored. Returns -1 if not successful\nfunc IndexAnyUnquoted(s, chars string) int {\n\tvar e bool\n\tvar q rune\n\tfor i, r := range s {\n\t\tif e {\n\t\t\te = false\n\t\t} else if (q == 0 || q == '\"') && r == '\\\\' {\n\t\t\te = true\n\t\t} else if r == q {\n\t\t\tq = 0\n\t\t} else if q == 0 && (r == '\\'' || r == '\"') {\n\t\t\tq = r\n\t\t} else if q == 0 && strings.IndexRune(chars, r) >= 0 {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\n// MakeRelative will attempt to make a relative path between path and base\nfunc MakeRelative(path, base string) (string, error) {\n\tif len(path) > 0 {\n\t\trel, err := filepath.Rel(base, path)\n\t\tif err != nil {\n\t\t\treturn path, err\n\t\t}\n\t\treturn rel, nil\n\t}\n\treturn path, nil\n}\n\n// ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's\n// home directory. Does nothing if the path does not start with '~'.\nfunc ReplaceHome(path string) (string, error) {\n\tif !strings.HasPrefix(path, \"~\") {\n\t\treturn path, nil\n\t}\n\n\tvar userData *user.User\n\tvar err error\n\n\thomeString := strings.Split(path, \"/\")[0]\n\tif homeString == \"~\" {\n\t\tuserData, err = user.Current()\n\t\tif err != nil {\n\t\t\treturn \"\", errors.New(\"Could not find user: \" + err.Error())\n\t\t}\n\t} else {\n\t\tuserData, err = user.Lookup(homeString[1:])\n\t\tif err != nil {\n\t\t\treturn \"\", errors.New(\"Could not find user: \" + err.Error())\n\t\t}\n\t}\n\n\thome := userData.HomeDir\n\n\treturn strings.Replace(path, homeString, home, 1), nil\n}\n\n// GetPathAndCursorPosition returns a filename without everything following a `:`\n// This is used for opening files like util.go:10:5 to specify a line and column\n// Special cases like Windows Absolute path (C:\\myfile.txt:10:5) are handled correctly.\nfunc GetPathAndCursorPosition(path string) (string, []string) {\n\tre := regexp.MustCompile(`([\\s\\S]+?)(?::(\\d+))(?::(\\d+))?$`)\n\tmatch := re.FindStringSubmatch(path)\n\t// no lines/columns were specified in the path, return just the path with no cursor location\n\tif len(match) == 0 {\n\t\treturn path, nil\n\t} else if match[len(match)-1] != \"\" {\n\t\t// if the last capture group match isn't empty then both line and column were provided\n\t\treturn match[1], match[2:]\n\t}\n\t// if it was empty, then only a line was provided, so default to column 0\n\treturn match[1], []string{match[2], \"0\"}\n}\n\n// GetModTime returns the last modification time for a given file\nfunc GetModTime(path string) (time.Time, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil {\n\t\treturn time.Now(), err\n\t}\n\treturn info.ModTime(), nil\n}\n\nfunc HashStringMd5(str string) string {\n\treturn fmt.Sprintf(\"%x\", md5.Sum([]byte(str)))\n}\n\n// EscapePathUrl encodes the path in URL query form\nfunc EscapePathUrl(path string) string {\n\treturn url.QueryEscape(filepath.ToSlash(path))\n}\n\n// EscapePathLegacy replaces every path separator in a given path with a %\nfunc EscapePathLegacy(path string) string {\n\tpath = filepath.ToSlash(path)\n\tif runtime.GOOS == \"windows\" {\n\t\t// ':' is not valid in a path name on Windows but is ok on Unix\n\t\tpath = strings.ReplaceAll(path, \":\", \"%\")\n\t}\n\treturn strings.ReplaceAll(path, \"/\", \"%\")\n}\n\n// DetermineEscapePath escapes a path, determining whether it should be escaped\n// using URL encoding (preferred, since it encodes unambiguously) or\n// legacy encoding with '%' (for backward compatibility, if the legacy-escaped\n// path exists in the given directory).\n// In case the length of the escaped path (plus the backup extension) exceeds\n// the filename length limit, a hash of the path is returned instead. In such\n// case the second return value is the name of a file the original path should\n// be saved to (since the original path cannot be derived from its hash).\n// Otherwise the second return value is an empty string.\nfunc DetermineEscapePath(dir string, path string) (string, string) {\n\turl := filepath.Join(dir, EscapePathUrl(path))\n\tif _, err := os.Stat(url); err == nil {\n\t\treturn url, \"\"\n\t}\n\n\tlegacy := filepath.Join(dir, EscapePathLegacy(path))\n\tif _, err := os.Stat(legacy); err == nil {\n\t\treturn legacy, \"\"\n\t}\n\n\tif len(url)+len(BackupSuffix) > 255 {\n\t\thash := HashStringMd5(path)\n\t\treturn filepath.Join(dir, hash), filepath.Join(dir, hash+\".path\")\n\t}\n\n\treturn url, \"\"\n}\n\n// GetLeadingWhitespace returns the leading whitespace of the given byte array\nfunc GetLeadingWhitespace(b []byte) []byte {\n\tws := []byte{}\n\tfor len(b) > 0 {\n\t\tr, _, size := DecodeCharacter(b)\n\t\tif r == ' ' || r == '\\t' {\n\t\t\tws = append(ws, byte(r))\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\n\t\tb = b[size:]\n\t}\n\treturn ws\n}\n\n// GetTrailingWhitespace returns the trailing whitespace of the given byte array\nfunc GetTrailingWhitespace(b []byte) []byte {\n\tws := []byte{}\n\tfor len(b) > 0 {\n\t\tr, size := utf8.DecodeLastRune(b)\n\t\tif IsWhitespace(r) {\n\t\t\tws = append([]byte(string(r)), ws...)\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\n\t\tb = b[:len(b)-size]\n\t}\n\treturn ws\n}\n\n// HasTrailingWhitespace returns true if the given byte array ends with a whitespace\nfunc HasTrailingWhitespace(b []byte) bool {\n\tr, _ := utf8.DecodeLastRune(b)\n\treturn IsWhitespace(r)\n}\n\n// IntOpt turns a float64 setting to an int\nfunc IntOpt(opt any) int {\n\treturn int(opt.(float64))\n}\n\n// GetCharPosInLine gets the char position of a visual x y\n// coordinate (this is necessary because tabs are 1 char but\n// 4 visual spaces)\nfunc GetCharPosInLine(b []byte, visualPos int, tabsize int) int {\n\t// Scan rune by rune until we exceed the visual width that we are\n\t// looking for. Then we can return the character position we have found\n\ti := 0     // char pos\n\twidth := 0 // string visual width\n\tfor len(b) > 0 {\n\t\tr, _, size := DecodeCharacter(b)\n\t\tb = b[size:]\n\n\t\tswitch r {\n\t\tcase '\\t':\n\t\t\tts := tabsize - (width % tabsize)\n\t\t\twidth += ts\n\t\tdefault:\n\t\t\twidth += runewidth.RuneWidth(r)\n\t\t}\n\n\t\tif width >= visualPos {\n\t\t\tif width == visualPos {\n\t\t\t\ti++\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\ti++\n\t}\n\n\treturn i\n}\n\n// ParseBool is almost exactly like strconv.ParseBool, except it also accepts 'on' and 'off'\n// as 'true' and 'false' respectively\nfunc ParseBool(str string) (bool, error) {\n\tif str == \"on\" {\n\t\treturn true, nil\n\t}\n\tif str == \"off\" {\n\t\treturn false, nil\n\t}\n\treturn strconv.ParseBool(str)\n}\n\n// Clamp clamps a value between min and max\nfunc Clamp(val, min, max int) int {\n\tif val < min {\n\t\tval = min\n\t} else if val > max {\n\t\tval = max\n\t}\n\treturn val\n}\n\n// IsAutocomplete returns whether a character should begin an autocompletion.\nfunc IsAutocomplete(c rune) bool {\n\treturn c == '.' || IsWordChar(c)\n}\n\n// String converts a byte array to a string (for lua plugins)\nfunc String(s []byte) string {\n\treturn string(s)\n}\n\n// Unzip unzips a file to given folder\nfunc Unzip(src, dest string) error {\n\tr, err := zip.OpenReader(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer r.Close()\n\n\tos.MkdirAll(dest, 0755)\n\n\t// Closure to address file descriptors issue with all the deferred .Close() methods\n\textractAndWriteFile := func(f *zip.File) error {\n\t\trc, err := f.Open()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer rc.Close()\n\n\t\tpath := filepath.Join(dest, f.Name)\n\n\t\t// Check for ZipSlip (Directory traversal)\n\t\tif !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {\n\t\t\treturn fmt.Errorf(\"illegal file path: %s\", path)\n\t\t}\n\n\t\tif f.FileInfo().IsDir() {\n\t\t\tos.MkdirAll(path, f.Mode())\n\t\t} else {\n\t\t\tos.MkdirAll(filepath.Dir(path), f.Mode())\n\t\t\tf, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer f.Close()\n\n\t\t\t_, err = io.Copy(f, rc)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\tfor _, f := range r.File {\n\t\terr := extractAndWriteFile(f)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// HttpRequest returns a new http.Client for making custom requests (for lua plugins)\nfunc HttpRequest(method string, url string, headers []string) (resp *http.Response, err error) {\n\tclient := http.Client{}\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor i := 0; i < len(headers); i += 2 {\n\t\treq.Header.Add(headers[i], headers[i+1])\n\t}\n\treturn client.Do(req)\n}\n\n// SafeWrite writes bytes to a file in a \"safe\" way, preventing loss of the\n// contents of the file if it fails to write the new contents.\n// This means that the file is not overwritten directly but by writing to a\n// temporary file first.\n//\n// If rename is true, write is performed atomically, by renaming the temporary\n// file to the target file after the data is successfully written to the\n// temporary file. This guarantees that the file will not remain in a corrupted\n// state, but it also has limitations, e.g. the file should not be a symlink\n// (otherwise SafeWrite silently replaces this symlink with a regular file),\n// the file creation date in Linux is not preserved (since the file inode\n// changes) etc. Use SafeWrite with rename=true for files that are only created\n// and used by micro for its own needs and are not supposed to be used directly\n// by the user.\n//\n// If rename is false, write is performed by overwriting the target file after\n// the data is successfully written to the temporary file.\n// This means that the target file may remain corrupted if overwrite fails,\n// but in such case the temporary file is preserved as a backup so the file\n// can be recovered later. So it is less convenient than atomic write but more\n// universal. Use SafeWrite with rename=false for files that may be managed\n// directly by the user, like settings.json and bindings.json.\nfunc SafeWrite(path string, bytes []byte, rename bool) error {\n\tvar err error\n\tif _, err = os.Stat(path); err != nil {\n\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\treturn err\n\t\t}\n\t\t// Force rename for new files!\n\t\trename = true\n\t}\n\n\tvar file *os.File\n\tif !rename {\n\t\tfile, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE, FileMode)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer file.Close()\n\t}\n\n\ttmp := path + BackupSuffix\n\terr = os.WriteFile(tmp, bytes, FileMode)\n\tif err != nil {\n\t\tos.Remove(tmp)\n\t\treturn err\n\t}\n\n\tif rename {\n\t\terr = os.Rename(tmp, path)\n\t} else {\n\t\terr = file.Truncate(0)\n\t\tif err == nil {\n\t\t\t_, err = file.Write(bytes)\n\t\t}\n\t\tif err == nil {\n\t\t\terr = file.Sync()\n\t\t}\n\t}\n\tif err != nil {\n\t\tif rename {\n\t\t\tos.Remove(tmp)\n\t\t} else {\n\t\t\terr = OverwriteError{err, tmp}\n\t\t}\n\t\treturn err\n\t}\n\n\tif !rename {\n\t\tos.Remove(tmp)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/util/util_test.go",
    "content": "package util\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestStringWidth(t *testing.T) {\n\tbytes := []byte(\"\\tPot să \\tmănânc sticlă și ea nu mă rănește.\")\n\n\tn := StringWidth(bytes, 23, 4)\n\tassert.Equal(t, 26, n)\n}\n\nfunc TestSliceVisualEnd(t *testing.T) {\n\ts := []byte(\"\\thello\")\n\tslc, n, _ := SliceVisualEnd(s, 2, 4)\n\tassert.Equal(t, []byte(\"\\thello\"), slc)\n\tassert.Equal(t, 2, n)\n\n\tslc, n, _ = SliceVisualEnd(s, 1, 4)\n\tassert.Equal(t, []byte(\"\\thello\"), slc)\n\tassert.Equal(t, 1, n)\n\n\tslc, n, _ = SliceVisualEnd(s, 4, 4)\n\tassert.Equal(t, []byte(\"hello\"), slc)\n\tassert.Equal(t, 0, n)\n\n\tslc, n, _ = SliceVisualEnd(s, 5, 4)\n\tassert.Equal(t, []byte(\"ello\"), slc)\n\tassert.Equal(t, 0, n)\n}\n"
  },
  {
    "path": "internal/views/splits.go",
    "content": "package views\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype SplitType uint8\n\nconst (\n\tSTVert  = 0\n\tSTHoriz = 1\n\tSTUndef = 2\n)\n\nvar idcounter uint64\n\n// NewID returns a new unique id\nfunc NewID() uint64 {\n\tidcounter++\n\treturn idcounter\n}\n\n// A View is a size and location of a split\ntype View struct {\n\tX, Y int\n\tW, H int\n}\n\n// A Node describes a split in the tree\n// If a node is a leaf node then it corresponds to a buffer that is being\n// displayed otherwise it has a number of children of the opposite type\n// (vertical splits have horizontal children and vice versa)\ntype Node struct {\n\tView\n\n\tKind SplitType\n\n\tparent   *Node\n\tchildren []*Node\n\n\t// Nodes can be marked as non resizable if they shouldn't be rescaled\n\t// when the terminal window is resized or when a new split is added\n\t// Only the splits on the edges of the screen can be marked as non resizable\n\tcanResize bool\n\t// A node may also be marked with proportional scaling. This means that when\n\t// the window is resized the split maintains its proportions\n\tpropScale bool\n\n\t// Defines the proportion of the screen this node should take up if propScale is\n\t// on\n\tpropW, propH float64\n\t// The id is unique for each leaf node and provides a way to keep track of a split\n\t// The id cannot be 0\n\tid uint64\n}\n\n// NewNode returns a new node with the given specifications\nfunc NewNode(Kind SplitType, x, y, w, h int, parent *Node, id uint64) *Node {\n\tn := new(Node)\n\tn.Kind = Kind\n\tn.canResize = true\n\tn.propScale = true\n\tn.X, n.Y, n.W, n.H = x, y, w, h\n\tn.children = make([]*Node, 0)\n\tn.parent = parent\n\tn.id = id\n\tif parent != nil {\n\t\tn.propW, n.propH = float64(w)/float64(parent.W), float64(h)/float64(parent.H)\n\t} else {\n\t\tn.propW, n.propH = 1, 1\n\t}\n\n\treturn n\n}\n\n// NewRoot returns an empty Node with a size and location\n// The type of the node will be determined by the first action on the node\n// In other words, a lone split is neither horizontal nor vertical, it only\n// becomes one or the other after a vsplit or hsplit is made\nfunc NewRoot(x, y, w, h int) *Node {\n\tn1 := NewNode(STUndef, x, y, w, h, nil, NewID())\n\n\treturn n1\n}\n\n// IsLeaf returns if this node is a leaf node\nfunc (n *Node) IsLeaf() bool {\n\treturn len(n.children) == 0\n}\n\n// ID returns this node's id or 0 if it is not viewable\nfunc (n *Node) ID() uint64 {\n\tif n.IsLeaf() {\n\t\treturn n.id\n\t}\n\treturn 0\n}\n\n// CanResize returns if this node can be resized\nfunc (n *Node) CanResize() bool {\n\treturn n.canResize\n}\n\n// PropScale returns if this node is proportionally scaled\nfunc (n *Node) PropScale() bool {\n\treturn n.propScale\n}\n\n// SetResize sets the resize flag\nfunc (n *Node) SetResize(b bool) {\n\tn.canResize = b\n}\n\n// SetPropScale sets the propScale flag\nfunc (n *Node) SetPropScale(b bool) {\n\tn.propScale = b\n}\n\n// Children returns this node's children\nfunc (n *Node) Children() []*Node {\n\treturn n.children\n}\n\n// GetNode returns the node with the given id in the tree of children\n// that this node has access to or nil if the node with that id cannot be found\nfunc (n *Node) GetNode(id uint64) *Node {\n\tif n.id == id && n.IsLeaf() {\n\t\treturn n\n\t}\n\tfor _, c := range n.children {\n\t\tif c.id == id && c.IsLeaf() {\n\t\t\treturn c\n\t\t}\n\t\tgc := c.GetNode(id)\n\t\tif gc != nil {\n\t\t\treturn gc\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (n *Node) vResizeSplit(i int, size int) bool {\n\tif i < 0 || i >= len(n.children) {\n\t\treturn false\n\t}\n\tvar c1, c2 *Node\n\tif i == len(n.children)-1 {\n\t\tc1, c2 = n.children[i-1], n.children[i]\n\t} else {\n\t\tc1, c2 = n.children[i], n.children[i+1]\n\t}\n\ttoth := c1.H + c2.H\n\tif size >= toth {\n\t\treturn false\n\t}\n\tc2.Y = c1.Y + size\n\tc1.Resize(c1.W, size)\n\tc2.Resize(c2.W, toth-size)\n\tn.markSizes()\n\tn.alignSizes(n.W, n.H)\n\treturn true\n}\nfunc (n *Node) hResizeSplit(i int, size int) bool {\n\tif i < 0 || i >= len(n.children) {\n\t\treturn false\n\t}\n\tvar c1, c2 *Node\n\tif i == len(n.children)-1 {\n\t\tc1, c2 = n.children[i-1], n.children[i]\n\t} else {\n\t\tc1, c2 = n.children[i], n.children[i+1]\n\t}\n\ttotw := c1.W + c2.W\n\tif size >= totw {\n\t\treturn false\n\t}\n\tc2.X = c1.X + size\n\tc1.Resize(size, c1.H)\n\tc2.Resize(totw-size, c2.H)\n\tn.markSizes()\n\tn.alignSizes(n.W, n.H)\n\treturn true\n}\n\n// ResizeSplit resizes a certain split to a given size\nfunc (n *Node) ResizeSplit(size int) bool {\n\t// TODO: `size < 0` does not work for some reason\n\tif size <= 0 {\n\t\treturn false\n\t}\n\tif len(n.parent.children) <= 1 {\n\t\t// cannot resize a lone node\n\t\treturn false\n\t}\n\tind := 0\n\tfor i, c := range n.parent.children {\n\t\tif c.id == n.id {\n\t\t\tind = i\n\t\t}\n\t}\n\tif n.parent.Kind == STVert {\n\t\treturn n.parent.vResizeSplit(ind, size)\n\t}\n\treturn n.parent.hResizeSplit(ind, size)\n}\n\n// Resize sets this node's size and resizes all children accordingly\nfunc (n *Node) Resize(w, h int) {\n\tn.W, n.H = w, h\n\n\tif n.IsLeaf() {\n\t\treturn\n\t}\n\n\tx, y := n.X, n.Y\n\ttotw, toth := 0, 0\n\tfor _, c := range n.children {\n\t\tcW := int(float64(w) * c.propW)\n\t\tcH := int(float64(h) * c.propH)\n\n\t\tc.X, c.Y = x, y\n\t\tc.Resize(cW, cH)\n\t\tif n.Kind == STHoriz {\n\t\t\tx += cW\n\t\t\ttotw += cW\n\t\t} else {\n\t\t\ty += cH\n\t\t\ttoth += cH\n\t\t}\n\t}\n\n\tn.alignSizes(totw, toth)\n}\n\nfunc (n *Node) alignSizes(totw, toth int) {\n\t// Make sure that there are no off-by-one problems with the rounding\n\t// of the sizes by making the final split fill the screen\n\tif n.Kind == STVert && toth != n.H {\n\t\tlast := n.children[len(n.children)-1]\n\t\tlast.Resize(last.W, last.H+n.H-toth)\n\t} else if n.Kind == STHoriz && totw != n.W {\n\t\tlast := n.children[len(n.children)-1]\n\t\tlast.Resize(last.W+n.W-totw, last.H)\n\t}\n}\n\n// Resets all proportions for children\nfunc (n *Node) markSizes() {\n\tfor _, c := range n.children {\n\t\tc.propW = float64(c.W) / float64(n.W)\n\t\tc.propH = float64(c.H) / float64(n.H)\n\t\tc.markSizes()\n\t}\n}\n\nfunc (n *Node) markResize() {\n\tn.markSizes()\n\tn.Resize(n.W, n.H)\n}\n\n// vsplits a vertical split and returns the id of the new split\nfunc (n *Node) vVSplit(right bool) uint64 {\n\tind := 0\n\tfor i, c := range n.parent.children {\n\t\tif c.id == n.id {\n\t\t\tind = i\n\t\t}\n\t}\n\treturn n.parent.hVSplit(ind, right)\n}\n\n// hsplits a horizontal split\nfunc (n *Node) hHSplit(bottom bool) uint64 {\n\tind := 0\n\tfor i, c := range n.parent.children {\n\t\tif c.id == n.id {\n\t\t\tind = i\n\t\t}\n\t}\n\treturn n.parent.vHSplit(ind, bottom)\n}\n\n// Returns the size of the non-resizable area and the number of resizable\n// splits\nfunc (n *Node) getResizeInfo(h bool) (int, int) {\n\tnumr := 0\n\tnumnr := 0\n\tnonr := 0\n\tfor _, c := range n.children {\n\t\tif !c.CanResize() {\n\t\t\tif h {\n\t\t\t\tnonr += c.H\n\t\t\t} else {\n\t\t\t\tnonr += c.W\n\t\t\t}\n\t\t\tnumnr++\n\t\t} else {\n\t\t\tnumr++\n\t\t}\n\t}\n\n\t// if there are no resizable splits make them all resizable\n\tif numr == 0 {\n\t\tnumr = numnr\n\t}\n\n\treturn nonr, numr\n}\n\nfunc (n *Node) applyNewSize(size int, h bool) {\n\ta := n.X\n\tif h {\n\t\ta = n.Y\n\t}\n\tfor _, c := range n.children {\n\t\tif h {\n\t\t\tc.Y = a\n\t\t} else {\n\t\t\tc.X = a\n\t\t}\n\t\tif c.CanResize() {\n\t\t\tif h {\n\t\t\t\tc.Resize(c.W, size)\n\t\t\t} else {\n\t\t\t\tc.Resize(size, c.H)\n\t\t\t}\n\t\t}\n\t\tif h {\n\t\t\ta += c.H\n\t\t} else {\n\t\t\ta += c.H\n\t\t}\n\t}\n\tn.markResize()\n}\n\n// hsplits a vertical split\nfunc (n *Node) vHSplit(i int, right bool) uint64 {\n\tif n.IsLeaf() {\n\t\tnewid := NewID()\n\t\thn1 := NewNode(STHoriz, n.X, n.Y, n.W, n.H/2, n, n.id)\n\t\thn2 := NewNode(STHoriz, n.X, n.Y+hn1.H, n.W, n.H/2, n, newid)\n\t\tif !right {\n\t\t\thn1.id, hn2.id = hn2.id, hn1.id\n\t\t}\n\n\t\tn.children = append(n.children, hn1, hn2)\n\t\tn.markResize()\n\t\treturn newid\n\t} else {\n\t\tnonrh, numr := n.getResizeInfo(true)\n\n\t\t// size of resizable area\n\t\theight := (n.H - nonrh) / (numr + 1)\n\n\t\tnewid := NewID()\n\t\thn := NewNode(STHoriz, n.X, 0, n.W, height, n, newid)\n\n\t\t// insert the node into the correct slot\n\t\tn.children = append(n.children, nil)\n\t\tinspos := i\n\t\tif right {\n\t\t\tinspos++\n\t\t}\n\t\tcopy(n.children[inspos+1:], n.children[inspos:])\n\t\tn.children[inspos] = hn\n\n\t\tn.applyNewSize(height, true)\n\t\treturn newid\n\t}\n}\n\n// vsplits a horizontal split\nfunc (n *Node) hVSplit(i int, right bool) uint64 {\n\tif n.IsLeaf() {\n\t\tnewid := NewID()\n\t\tvn1 := NewNode(STVert, n.X, n.Y, n.W/2, n.H, n, n.id)\n\t\tvn2 := NewNode(STVert, n.X+vn1.W, n.Y, n.W/2, n.H, n, newid)\n\t\tif !right {\n\t\t\tvn1.id, vn2.id = vn2.id, vn1.id\n\t\t}\n\n\t\tn.children = append(n.children, vn1, vn2)\n\t\tn.markResize()\n\t\treturn newid\n\t} else {\n\t\tnonrw, numr := n.getResizeInfo(false)\n\n\t\twidth := (n.W - nonrw) / (numr + 1)\n\n\t\tnewid := NewID()\n\t\tvn := NewNode(STVert, 0, n.Y, width, n.H, n, newid)\n\n\t\t// Inser the node into the correct slot\n\t\tn.children = append(n.children, nil)\n\t\tinspos := i\n\t\tif right {\n\t\t\tinspos++\n\t\t}\n\t\tcopy(n.children[inspos+1:], n.children[inspos:])\n\t\tn.children[inspos] = vn\n\n\t\tn.applyNewSize(width, false)\n\t\treturn newid\n\t}\n}\n\n// HSplit creates a horizontal split and returns the id of the new split\n// bottom specifies if the new split should be created on the top or bottom\n// of the current split\nfunc (n *Node) HSplit(bottom bool) uint64 {\n\tif !n.IsLeaf() {\n\t\treturn 0\n\t}\n\tif n.parent == nil {\n\t\tn.Kind = STVert\n\t}\n\tif n.Kind == STVert {\n\t\treturn n.vHSplit(0, bottom)\n\t}\n\treturn n.hHSplit(bottom)\n}\n\n// VSplit creates a vertical split and returns the id of the new split\n// right specifies if the new split should be created on the right or left\n// of the current split\nfunc (n *Node) VSplit(right bool) uint64 {\n\tif !n.IsLeaf() {\n\t\treturn 0\n\t}\n\tif n.parent == nil {\n\t\tn.Kind = STHoriz\n\t}\n\tif n.Kind == STHoriz {\n\t\treturn n.hVSplit(0, right)\n\t}\n\treturn n.vVSplit(right)\n}\n\n// unsplits the child of a split\nfunc (n *Node) unsplit(i int) {\n\tcopy(n.children[i:], n.children[i+1:])\n\tn.children[len(n.children)-1] = nil\n\tn.children = n.children[:len(n.children)-1]\n\n\th := n.Kind == STVert\n\tnonrs, numr := n.getResizeInfo(h)\n\tif numr == 0 {\n\t\t// This means that this was the last child\n\t\t// The parent will get cleaned up in the next iteration and\n\t\t// will resolve all sizing issues with its parent\n\t\treturn\n\t}\n\tsize := (n.W - nonrs) / numr\n\tif h {\n\t\tsize = (n.H - nonrs) / numr\n\t}\n\tn.applyNewSize(size, h)\n}\n\n// Unsplit deletes this split and resizes everything\n// else accordingly\nfunc (n *Node) Unsplit() bool {\n\tif !n.IsLeaf() || n.parent == nil {\n\t\treturn false\n\t}\n\tind := 0\n\tfor i, c := range n.parent.children {\n\t\tif c.id == n.id {\n\t\t\tind = i\n\t\t}\n\t}\n\tn.parent.unsplit(ind)\n\tif n.parent.IsLeaf() {\n\t\treturn n.parent.Unsplit()\n\t}\n\n\tn.parent.flatten()\n\treturn true\n}\n\n// flattens the tree by removing unnecessary intermediate parents that have only one child\n// and handles the side effect of it\nfunc (n *Node) flatten() {\n\tif len(n.children) != 1 {\n\t\treturn\n\t}\n\n\t// Special case for root node\n\tif n.parent == nil {\n\t\t*n = *n.children[0]\n\t\tn.parent = nil\n\t\tfor _, c := range n.children {\n\t\t\tc.parent = n\n\t\t}\n\t\tif len(n.children) == 0 {\n\t\t\tn.Kind = STUndef\n\t\t}\n\t\treturn\n\t}\n\n\tind := 0\n\tfor i, c := range n.parent.children {\n\t\tif c.id == n.id {\n\t\t\tind = i\n\t\t}\n\t}\n\n\t// Replace current node with its child node to remove chained parent\n\tsuccessor := n.children[0]\n\tn.parent.children[ind] = successor\n\tsuccessor.parent = n.parent\n\n\t// Maintain the tree in a consistent state: any child node's kind (horiz vs vert)\n\t// should be the opposite of its parent's kind.\n\tif successor.IsLeaf() {\n\t\tsuccessor.Kind = n.Kind\n\t} else {\n\t\t// If the successor node has children, that means it is a chained parent as well.\n\t\t// Therefore it can be replaced by its own children.\n\t\torigsize := len(n.parent.children)\n\n\t\t// Let's say we have 5 children and want to replace [2] with its children [a] [b] [c]\n\t\t// [0] [1] [2] [3] [4] --> [0] [1] [a] [b] [c] [3] [4]\n\t\t// insertcount will be `3 - 1 = 2` in this case\n\t\tinsertcount := len(successor.children) - 1\n\n\t\tn.parent.children = append(n.parent.children, make([]*Node, insertcount)...)\n\t\tcopy(n.parent.children[ind+insertcount+1:], n.parent.children[ind+1:origsize])\n\t\tcopy(n.parent.children[ind:], successor.children)\n\n\t\tfor i := 0; i < len(successor.children); i++ {\n\t\t\tn.parent.children[ind+i].parent = n.parent\n\t\t}\n\t}\n\n\t// Update propW and propH since the parent of the children has been updated,\n\t// so the children have new siblings\n\tn.parent.markSizes()\n}\n\n// String returns the string form of the node and all children (used for debugging)\nfunc (n *Node) String() string {\n\tvar strf func(n *Node, ident int) string\n\tstrf = func(n *Node, ident int) string {\n\t\tmarker := \"\"\n\t\tif n.Kind == STHoriz {\n\t\t\tmarker = \"-\"\n\t\t} else if n.Kind == STVert {\n\t\t\tmarker = \"|\"\n\t\t}\n\n\t\tvar parentId uint64 = 0\n\t\tif n.parent != nil {\n\t\t\tparentId = n.parent.id\n\t\t}\n\n\t\tstr := fmt.Sprint(strings.Repeat(\"\\t\", ident), marker, n.View, n.id, parentId)\n\t\tif n.IsLeaf() {\n\t\t\tstr += \"🍁\"\n\t\t}\n\t\tstr += \"\\n\"\n\t\tfor _, c := range n.children {\n\t\t\tstr += strf(c, ident+1)\n\t\t}\n\t\treturn str\n\t}\n\treturn strf(n, 0)\n}\n"
  },
  {
    "path": "internal/views/splits_test.go",
    "content": "package views\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nfunc TestHSplit(t *testing.T) {\n\troot := NewRoot(0, 0, 80, 80)\n\tn1 := root.VSplit(true)\n\troot.GetNode(n1).VSplit(true)\n\troot.GetNode(root.id).ResizeSplit(7)\n\troot.Resize(120, 120)\n\n\tfmt.Println(root.String())\n}\n"
  },
  {
    "path": "pkg/highlight/highlighter.go",
    "content": "package highlight\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n)\n\nfunc sliceStart(slc []byte, index int) []byte {\n\tlen := len(slc)\n\ti := 0\n\ttotalSize := 0\n\tfor totalSize < len {\n\t\tif i >= index {\n\t\t\treturn slc[totalSize:]\n\t\t}\n\n\t\t_, _, size := DecodeCharacter(slc[totalSize:])\n\t\ttotalSize += size\n\t\ti++\n\t}\n\n\treturn slc[totalSize:]\n}\n\nfunc sliceEnd(slc []byte, index int) []byte {\n\tlen := len(slc)\n\ti := 0\n\ttotalSize := 0\n\tfor totalSize < len {\n\t\tif i >= index {\n\t\t\treturn slc[:totalSize]\n\t\t}\n\n\t\t_, _, size := DecodeCharacter(slc[totalSize:])\n\t\ttotalSize += size\n\t\ti++\n\t}\n\n\treturn slc[:totalSize]\n}\n\n// RunePos returns the rune index of a given byte index\n// This could cause problems if the byte index is between code points\nfunc runePos(p int, str []byte) int {\n\tif p < 0 {\n\t\treturn 0\n\t}\n\tif p >= len(str) {\n\t\treturn CharacterCount(str)\n\t}\n\treturn CharacterCount(str[:p])\n}\n\n// A State represents the region at the end of a line\ntype State *region\n\n// LineStates is an interface for a buffer-like object which can also store the states and matches for every line\ntype LineStates interface {\n\tLineBytes(n int) []byte\n\tLinesNum() int\n\tState(lineN int) State\n\tSetState(lineN int, s State)\n\tSetMatch(lineN int, m LineMatch)\n\tLock()\n\tUnlock()\n}\n\n// A Highlighter contains the information needed to highlight a string\ntype Highlighter struct {\n\tlastRegion *region\n\tDef        *Def\n}\n\n// NewHighlighter returns a new highlighter from the given syntax definition\nfunc NewHighlighter(def *Def) *Highlighter {\n\th := new(Highlighter)\n\th.Def = def\n\treturn h\n}\n\n// LineMatch represents the syntax highlighting matches for one line. Each index where the coloring is changed is marked with that\n// color's group (represented as one byte)\ntype LineMatch map[int]Group\n\nfunc findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte) []int {\n\tvar strbytes []byte\n\tif skip != nil {\n\t\tstrbytes = skip.ReplaceAllFunc(str, func(match []byte) []byte {\n\t\t\tres := make([]byte, CharacterCount(match))\n\t\t\treturn res\n\t\t})\n\t} else {\n\t\tstrbytes = str\n\t}\n\n\tmatch := regex.FindIndex(strbytes)\n\tif match == nil {\n\t\treturn nil\n\t}\n\t// return []int{match.Index, match.Index + match.Length}\n\treturn []int{runePos(match[0], str), runePos(match[1], str)}\n}\n\nfunc findAllIndex(regex *regexp.Regexp, str []byte) [][]int {\n\tmatches := regex.FindAllIndex(str, -1)\n\tfor i, m := range matches {\n\t\tmatches[i][0] = runePos(m[0], str)\n\t\tmatches[i][1] = runePos(m[1], str)\n\t}\n\treturn matches\n}\n\nfunc (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, curRegion *region, statesOnly bool) LineMatch {\n\tlineLen := CharacterCount(line)\n\tif start == 0 {\n\t\tif !statesOnly {\n\t\t\tif _, ok := highlights[0]; !ok {\n\t\t\t\thighlights[0] = curRegion.group\n\t\t\t}\n\t\t}\n\t}\n\n\tvar firstRegion *region\n\tfirstLoc := []int{lineLen, 0}\n\tsearchNesting := true\n\tendLoc := findIndex(curRegion.end, curRegion.skip, line)\n\tif endLoc != nil {\n\t\tif start == endLoc[0] {\n\t\t\tsearchNesting = false\n\t\t} else {\n\t\t\tfirstLoc = endLoc\n\t\t}\n\t}\n\tif searchNesting {\n\t\tfor _, r := range curRegion.rules.regions {\n\t\t\tloc := findIndex(r.start, r.skip, line)\n\t\t\tif loc != nil {\n\t\t\t\tif loc[0] < firstLoc[0] {\n\t\t\t\t\tfirstLoc = loc\n\t\t\t\t\tfirstRegion = r\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif firstRegion != nil && firstLoc[0] != lineLen {\n\t\tif !statesOnly {\n\t\t\thighlights[start+firstLoc[0]] = firstRegion.limitGroup\n\t\t}\n\t\th.highlightEmptyRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), statesOnly)\n\t\th.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), firstRegion, statesOnly)\n\t\treturn highlights\n\t}\n\n\tif !statesOnly {\n\t\tfullHighlights := make([]Group, lineLen)\n\t\tfor i := 0; i < len(fullHighlights); i++ {\n\t\t\tfullHighlights[i] = curRegion.group\n\t\t}\n\n\t\tif searchNesting {\n\t\t\tfor _, p := range curRegion.rules.patterns {\n\t\t\t\tif curRegion.group == curRegion.limitGroup || p.group == curRegion.limitGroup {\n\t\t\t\t\tmatches := findAllIndex(p.regex, line)\n\t\t\t\t\tfor _, m := range matches {\n\t\t\t\t\t\tif (endLoc == nil) || (m[0] < endLoc[0]) {\n\t\t\t\t\t\t\tfor i := m[0]; i < m[1]; i++ {\n\t\t\t\t\t\t\t\tfullHighlights[i] = p.group\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor i, h := range fullHighlights {\n\t\t\tif i == 0 || h != fullHighlights[i-1] {\n\t\t\t\thighlights[start+i] = h\n\t\t\t}\n\t\t}\n\t}\n\n\tloc := endLoc\n\tif loc != nil {\n\t\tif !statesOnly {\n\t\t\thighlights[start+loc[0]] = curRegion.limitGroup\n\t\t}\n\t\tif curRegion.parent == nil {\n\t\t\tif !statesOnly {\n\t\t\t\thighlights[start+loc[1]] = 0\n\t\t\t}\n\t\t\th.highlightEmptyRegion(highlights, start+loc[1], canMatchEnd, lineNum, sliceStart(line, loc[1]), statesOnly)\n\t\t\treturn highlights\n\t\t}\n\t\tif !statesOnly {\n\t\t\thighlights[start+loc[1]] = curRegion.parent.group\n\t\t}\n\t\th.highlightRegion(highlights, start+loc[1], canMatchEnd, lineNum, sliceStart(line, loc[1]), curRegion.parent, statesOnly)\n\t\treturn highlights\n\t}\n\n\tif canMatchEnd {\n\t\th.lastRegion = curRegion\n\t}\n\n\treturn highlights\n}\n\nfunc (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, statesOnly bool) LineMatch {\n\tlineLen := CharacterCount(line)\n\tif lineLen == 0 {\n\t\tif canMatchEnd {\n\t\t\th.lastRegion = nil\n\t\t}\n\t\treturn highlights\n\t}\n\n\tvar firstRegion *region\n\tfirstLoc := []int{lineLen, 0}\n\tfor _, r := range h.Def.rules.regions {\n\t\tloc := findIndex(r.start, r.skip, line)\n\t\tif loc != nil {\n\t\t\tif loc[0] < firstLoc[0] {\n\t\t\t\tfirstLoc = loc\n\t\t\t\tfirstRegion = r\n\t\t\t}\n\t\t}\n\t}\n\tif firstRegion != nil && firstLoc[0] != lineLen {\n\t\tif !statesOnly {\n\t\t\thighlights[start+firstLoc[0]] = firstRegion.limitGroup\n\t\t}\n\t\th.highlightEmptyRegion(highlights, start, false, lineNum, sliceEnd(line, firstLoc[0]), statesOnly)\n\t\th.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), firstRegion, statesOnly)\n\t\treturn highlights\n\t}\n\n\tif statesOnly {\n\t\tif canMatchEnd {\n\t\t\th.lastRegion = nil\n\t\t}\n\n\t\treturn highlights\n\t}\n\n\tfullHighlights := make([]Group, len(line))\n\tfor _, p := range h.Def.rules.patterns {\n\t\tmatches := findAllIndex(p.regex, line)\n\t\tfor _, m := range matches {\n\t\t\tfor i := m[0]; i < m[1]; i++ {\n\t\t\t\tfullHighlights[i] = p.group\n\t\t\t}\n\t\t}\n\t}\n\tfor i, h := range fullHighlights {\n\t\tif i == 0 || h != fullHighlights[i-1] {\n\t\t\t// if _, ok := highlights[start+i]; !ok {\n\t\t\thighlights[start+i] = h\n\t\t\t// }\n\t\t}\n\t}\n\n\tif canMatchEnd {\n\t\th.lastRegion = nil\n\t}\n\n\treturn highlights\n}\n\n// HighlightString syntax highlights a string\n// Use this function for simple syntax highlighting and use the other functions for\n// more advanced syntax highlighting. They are optimized for quick rehighlighting of the same\n// text with minor changes made\nfunc (h *Highlighter) HighlightString(input string) []LineMatch {\n\tlines := strings.Split(input, \"\\n\")\n\tvar lineMatches []LineMatch\n\n\tfor i := 0; i < len(lines); i++ {\n\t\tline := []byte(lines[i])\n\t\thighlights := make(LineMatch)\n\n\t\tif i == 0 || h.lastRegion == nil {\n\t\t\tlineMatches = append(lineMatches, h.highlightEmptyRegion(highlights, 0, true, i, line, false))\n\t\t} else {\n\t\t\tlineMatches = append(lineMatches, h.highlightRegion(highlights, 0, true, i, line, h.lastRegion, false))\n\t\t}\n\t}\n\n\treturn lineMatches\n}\n\n// HighlightStates correctly sets all states for the buffer\nfunc (h *Highlighter) HighlightStates(input LineStates) {\n\tfor i := 0; ; i++ {\n\t\tinput.Lock()\n\t\tif i >= input.LinesNum() {\n\t\t\tinput.Unlock()\n\t\t\tbreak\n\t\t}\n\n\t\tline := input.LineBytes(i)\n\t\t// highlights := make(LineMatch)\n\n\t\tif i == 0 || h.lastRegion == nil {\n\t\t\th.highlightEmptyRegion(nil, 0, true, i, line, true)\n\t\t} else {\n\t\t\th.highlightRegion(nil, 0, true, i, line, h.lastRegion, true)\n\t\t}\n\n\t\tcurState := h.lastRegion\n\n\t\tinput.SetState(i, curState)\n\t\tinput.Unlock()\n\t}\n}\n\n// HighlightMatches sets the matches for each line from startline to endline\n// It sets all other matches in the buffer to nil to conserve memory\n// This assumes that all the states are set correctly\nfunc (h *Highlighter) HighlightMatches(input LineStates, startline, endline int) {\n\tfor i := startline; i <= endline; i++ {\n\t\tinput.Lock()\n\t\tif i >= input.LinesNum() {\n\t\t\tinput.Unlock()\n\t\t\tbreak\n\t\t}\n\n\t\tline := input.LineBytes(i)\n\t\thighlights := make(LineMatch)\n\n\t\tvar match LineMatch\n\t\tif i == 0 || input.State(i-1) == nil {\n\t\t\tmatch = h.highlightEmptyRegion(highlights, 0, true, i, line, false)\n\t\t} else {\n\t\t\tmatch = h.highlightRegion(highlights, 0, true, i, line, input.State(i-1), false)\n\t\t}\n\n\t\tinput.SetMatch(i, match)\n\t\tinput.Unlock()\n\t}\n}\n\n// ReHighlightStates will scan down from `startline` and set the appropriate end of line state\n// for each line until it comes across a line whose state does not change\n// returns the number of the final line\nfunc (h *Highlighter) ReHighlightStates(input LineStates, startline int) int {\n\t// lines := input.LineData()\n\n\th.lastRegion = nil\n\tif startline > 0 {\n\t\tinput.Lock()\n\t\tif startline-1 < input.LinesNum() {\n\t\t\th.lastRegion = input.State(startline - 1)\n\t\t}\n\t\tinput.Unlock()\n\t}\n\tfor i := startline; ; i++ {\n\t\tinput.Lock()\n\t\tif i >= input.LinesNum() {\n\t\t\tinput.Unlock()\n\t\t\tbreak\n\t\t}\n\n\t\tline := input.LineBytes(i)\n\t\t// highlights := make(LineMatch)\n\n\t\t// var match LineMatch\n\t\tif i == 0 || h.lastRegion == nil {\n\t\t\th.highlightEmptyRegion(nil, 0, true, i, line, true)\n\t\t} else {\n\t\t\th.highlightRegion(nil, 0, true, i, line, h.lastRegion, true)\n\t\t}\n\t\tcurState := h.lastRegion\n\t\tlastState := input.State(i)\n\n\t\tinput.SetState(i, curState)\n\t\tinput.Unlock()\n\n\t\tif curState == lastState {\n\t\t\treturn i\n\t\t}\n\t}\n\n\treturn input.LinesNum() - 1\n}\n\n// ReHighlightLine will rehighlight the state and match for a single line\nfunc (h *Highlighter) ReHighlightLine(input LineStates, lineN int) {\n\tinput.Lock()\n\tdefer input.Unlock()\n\n\tline := input.LineBytes(lineN)\n\thighlights := make(LineMatch)\n\n\th.lastRegion = nil\n\tif lineN > 0 {\n\t\th.lastRegion = input.State(lineN - 1)\n\t}\n\n\tvar match LineMatch\n\tif lineN == 0 || h.lastRegion == nil {\n\t\tmatch = h.highlightEmptyRegion(highlights, 0, true, lineN, line, false)\n\t} else {\n\t\tmatch = h.highlightRegion(highlights, 0, true, lineN, line, h.lastRegion, false)\n\t}\n\tcurState := h.lastRegion\n\n\tinput.SetMatch(lineN, match)\n\tinput.SetState(lineN, curState)\n}\n"
  },
  {
    "path": "pkg/highlight/parser.go",
    "content": "package highlight\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"regexp\"\n\n\t\"gopkg.in/yaml.v2\"\n)\n\n// A Group represents a syntax group\ntype Group uint8\n\n// Groups contains all of the groups that are defined\n// You can access them in the map via their string name\nvar Groups map[string]Group\nvar numGroups Group\n\n// String returns the group name attached to the specific group\nfunc (g Group) String() string {\n\tfor k, v := range Groups {\n\t\tif v == g {\n\t\t\treturn k\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// A Def is a full syntax definition for a language\n// It has a filetype, information about how to detect the filetype based\n// on filename or header (the first line of the file)\n// Then it has the rules which define how to highlight the file\ntype Def struct {\n\t*Header\n\trules *rules\n}\n\ntype Header struct {\n\tFileType       string\n\tFileNameRegex  *regexp.Regexp\n\tHeaderRegex    *regexp.Regexp\n\tSignatureRegex *regexp.Regexp\n}\n\ntype HeaderYaml struct {\n\tFileType string `yaml:\"filetype\"`\n\tDetect   struct {\n\t\tFNameRegexStr     string `yaml:\"filename\"`\n\t\tHeaderRegexStr    string `yaml:\"header\"`\n\t\tSignatureRegexStr string `yaml:\"signature\"`\n\t} `yaml:\"detect\"`\n}\n\ntype File struct {\n\tFileType string\n\tyamlSrc  map[any]any\n}\n\n// A Pattern is one simple syntax rule\n// It has a group that the rule belongs to, as well as\n// the regular expression to match the pattern\ntype pattern struct {\n\tgroup Group\n\tregex *regexp.Regexp\n}\n\n// rules defines which patterns and regions can be used to highlight\n// a filetype\ntype rules struct {\n\tregions  []*region\n\tpatterns []*pattern\n\tincludes []string\n}\n\n// A region is a highlighted region (such as a multiline comment, or a string)\n// It belongs to a group, and has start and end regular expressions\n// A region also has rules of its own that only apply when matching inside the\n// region and also rules from the above region do not match inside this region\n// Note that a region may contain more regions\ntype region struct {\n\tgroup      Group\n\tlimitGroup Group\n\tparent     *region\n\tstart      *regexp.Regexp\n\tend        *regexp.Regexp\n\tskip       *regexp.Regexp\n\trules      *rules\n}\n\nfunc init() {\n\tGroups = make(map[string]Group)\n}\n\n// MakeHeader takes a header (.hdr file) file and parses the header\n// Header files make parsing more efficient when you only want to compute\n// on the headers of syntax files\n// A yaml file might take ~400us to parse while a header file only takes ~20us\nfunc MakeHeader(data []byte) (*Header, error) {\n\tlines := bytes.Split(data, []byte{'\\n'})\n\tif len(lines) < 4 {\n\t\treturn nil, errors.New(\"Header file has incorrect format\")\n\t}\n\theader := new(Header)\n\tvar err error\n\theader.FileType = string(lines[0])\n\tfnameRegexStr := string(lines[1])\n\theaderRegexStr := string(lines[2])\n\tsignatureRegexStr := string(lines[3])\n\n\tif fnameRegexStr != \"\" {\n\t\theader.FileNameRegex, err = regexp.Compile(fnameRegexStr)\n\t}\n\tif err == nil && headerRegexStr != \"\" {\n\t\theader.HeaderRegex, err = regexp.Compile(headerRegexStr)\n\t}\n\tif err == nil && signatureRegexStr != \"\" {\n\t\theader.SignatureRegex, err = regexp.Compile(signatureRegexStr)\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn header, nil\n}\n\n// MakeHeaderYaml takes a yaml spec for a syntax file and parses the\n// header\nfunc MakeHeaderYaml(data []byte) (*Header, error) {\n\tvar hdrYaml HeaderYaml\n\terr := yaml.Unmarshal(data, &hdrYaml)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\theader := new(Header)\n\theader.FileType = hdrYaml.FileType\n\n\tif hdrYaml.Detect.FNameRegexStr != \"\" {\n\t\theader.FileNameRegex, err = regexp.Compile(hdrYaml.Detect.FNameRegexStr)\n\t}\n\tif err == nil && hdrYaml.Detect.HeaderRegexStr != \"\" {\n\t\theader.HeaderRegex, err = regexp.Compile(hdrYaml.Detect.HeaderRegexStr)\n\t}\n\tif err == nil && hdrYaml.Detect.SignatureRegexStr != \"\" {\n\t\theader.SignatureRegex, err = regexp.Compile(hdrYaml.Detect.SignatureRegexStr)\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn header, nil\n}\n\n// MatchFileName will check the given file name with the stored regex\nfunc (header *Header) MatchFileName(filename string) bool {\n\tif header.FileNameRegex != nil {\n\t\treturn header.FileNameRegex.MatchString(filename)\n\t}\n\n\treturn false\n}\n\nfunc (header *Header) MatchFileHeader(firstLine []byte) bool {\n\tif header.HeaderRegex != nil {\n\t\treturn header.HeaderRegex.Match(firstLine)\n\t}\n\n\treturn false\n}\n\n// HasFileSignature checks the presence of a stored signature\nfunc (header *Header) HasFileSignature() bool {\n\treturn header.SignatureRegex != nil\n}\n\n// MatchFileSignature will check the given line with the stored regex\nfunc (header *Header) MatchFileSignature(line []byte) bool {\n\tif header.SignatureRegex != nil {\n\t\treturn header.SignatureRegex.Match(line)\n\t}\n\n\treturn false\n}\n\nfunc ParseFile(input []byte) (f *File, err error) {\n\t// This is just so if we have an error, we can exit cleanly and return the parse error to the user\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tvar ok bool\n\t\t\terr, ok = r.(error)\n\t\t\tif !ok {\n\t\t\t\terr = fmt.Errorf(\"pkg: %v\", r)\n\t\t\t}\n\t\t}\n\t}()\n\n\tvar rules map[any]any\n\tif err = yaml.Unmarshal(input, &rules); err != nil {\n\t\treturn nil, err\n\t}\n\n\tf = new(File)\n\tf.yamlSrc = rules\n\n\tfor k, v := range rules {\n\t\tif k == \"filetype\" {\n\t\t\tfiletype := v.(string)\n\n\t\t\tif filetype == \"\" {\n\t\t\t\treturn nil, errors.New(\"empty filetype\")\n\t\t\t}\n\n\t\t\tf.FileType = filetype\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif f.FileType == \"\" {\n\t\treturn nil, errors.New(\"missing filetype\")\n\t}\n\n\treturn f, err\n}\n\n// ParseDef parses an input syntax file into a highlight Def\nfunc ParseDef(f *File, header *Header) (s *Def, err error) {\n\t// This is just so if we have an error, we can exit cleanly and return the parse error to the user\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tvar ok bool\n\t\t\terr, ok = r.(error)\n\t\t\tif !ok {\n\t\t\t\terr = fmt.Errorf(\"pkg: %v\", r)\n\t\t\t}\n\t\t}\n\t}()\n\n\tsrc := f.yamlSrc\n\n\ts = new(Def)\n\ts.Header = header\n\n\tfor k, v := range src {\n\t\tif k == \"rules\" {\n\t\t\tinputRules := v.([]any)\n\n\t\t\trules, err := parseRules(inputRules, nil)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\ts.rules = rules\n\t\t}\n\t}\n\n\tif s.rules == nil {\n\t\t// allow empty rules\n\t\ts.rules = &rules{}\n\t}\n\n\treturn s, err\n}\n\n// HasIncludes returns whether this syntax def has any include statements\nfunc HasIncludes(d *Def) bool {\n\thasIncludes := len(d.rules.includes) > 0\n\tfor _, r := range d.rules.regions {\n\t\thasIncludes = hasIncludes || hasIncludesInRegion(r)\n\t}\n\treturn hasIncludes\n}\n\nfunc hasIncludesInRegion(region *region) bool {\n\thasIncludes := len(region.rules.includes) > 0\n\tfor _, r := range region.rules.regions {\n\t\thasIncludes = hasIncludes || hasIncludesInRegion(r)\n\t}\n\treturn hasIncludes\n}\n\n// GetIncludes returns a list of filetypes that are included by this syntax def\nfunc GetIncludes(d *Def) []string {\n\tincludes := d.rules.includes\n\tfor _, r := range d.rules.regions {\n\t\tincludes = append(includes, getIncludesInRegion(r)...)\n\t}\n\treturn includes\n}\n\nfunc getIncludesInRegion(region *region) []string {\n\tincludes := region.rules.includes\n\tfor _, r := range region.rules.regions {\n\t\tincludes = append(includes, getIncludesInRegion(r)...)\n\t}\n\treturn includes\n}\n\n// ResolveIncludes will sort out the rules for including other filetypes\n// You should call this after parsing all the Defs\nfunc ResolveIncludes(def *Def, files []*File) {\n\tresolveIncludesInDef(files, def)\n}\n\nfunc resolveIncludesInDef(files []*File, d *Def) {\n\tfor _, lang := range d.rules.includes {\n\t\tfor _, searchFile := range files {\n\t\t\tif lang == searchFile.FileType {\n\t\t\t\tsearchDef, _ := ParseDef(searchFile, nil)\n\t\t\t\td.rules.patterns = append(d.rules.patterns, searchDef.rules.patterns...)\n\t\t\t\td.rules.regions = append(d.rules.regions, searchDef.rules.regions...)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, r := range d.rules.regions {\n\t\tresolveIncludesInRegion(files, r)\n\t\tr.parent = nil\n\t}\n}\n\nfunc resolveIncludesInRegion(files []*File, region *region) {\n\tfor _, lang := range region.rules.includes {\n\t\tfor _, searchFile := range files {\n\t\t\tif lang == searchFile.FileType {\n\t\t\t\tsearchDef, _ := ParseDef(searchFile, nil)\n\t\t\t\tregion.rules.patterns = append(region.rules.patterns, searchDef.rules.patterns...)\n\t\t\t\tregion.rules.regions = append(region.rules.regions, searchDef.rules.regions...)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, r := range region.rules.regions {\n\t\tresolveIncludesInRegion(files, r)\n\t\tr.parent = region\n\t}\n}\n\nfunc parseRules(input []any, curRegion *region) (ru *rules, err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tvar ok bool\n\t\t\terr, ok = r.(error)\n\t\t\tif !ok {\n\t\t\t\terr = fmt.Errorf(\"pkg: %v\", r)\n\t\t\t}\n\t\t}\n\t}()\n\tru = new(rules)\n\n\tfor _, v := range input {\n\t\trule := v.(map[any]any)\n\t\tfor k, val := range rule {\n\t\t\tgroup := k\n\n\t\t\tswitch object := val.(type) {\n\t\t\tcase string:\n\t\t\t\tif object == \"\" {\n\t\t\t\t\treturn nil, fmt.Errorf(\"Empty rule %s\", k)\n\t\t\t\t}\n\n\t\t\t\tif k == \"include\" {\n\t\t\t\t\tru.includes = append(ru.includes, object)\n\t\t\t\t} else {\n\t\t\t\t\t// Pattern\n\t\t\t\t\tr, err := regexp.Compile(object)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\n\t\t\t\t\tgroupStr := group.(string)\n\t\t\t\t\tif _, ok := Groups[groupStr]; !ok {\n\t\t\t\t\t\tnumGroups++\n\t\t\t\t\t\tGroups[groupStr] = numGroups\n\t\t\t\t\t}\n\t\t\t\t\tgroupNum := Groups[groupStr]\n\t\t\t\t\tru.patterns = append(ru.patterns, &pattern{groupNum, r})\n\t\t\t\t}\n\t\t\tcase map[any]any:\n\t\t\t\t// region\n\t\t\t\tregion, err := parseRegion(group.(string), object, curRegion)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tru.regions = append(ru.regions, region)\n\t\t\tdefault:\n\t\t\t\treturn nil, fmt.Errorf(\"Bad type %T\", object)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn ru, nil\n}\n\nfunc parseRegion(group string, regionInfo map[any]any, prevRegion *region) (r *region, err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tvar ok bool\n\t\t\terr, ok = r.(error)\n\t\t\tif !ok {\n\t\t\t\terr = fmt.Errorf(\"pkg: %v\", r)\n\t\t\t}\n\t\t}\n\t}()\n\n\tr = new(region)\n\tif _, ok := Groups[group]; !ok {\n\t\tnumGroups++\n\t\tGroups[group] = numGroups\n\t}\n\tgroupNum := Groups[group]\n\tr.group = groupNum\n\tr.parent = prevRegion\n\n\t// start is mandatory\n\tif start, ok := regionInfo[\"start\"]; ok {\n\t\tstart := start.(string)\n\t\tif start == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"Empty start in %s\", group)\n\t\t}\n\n\t\tr.start, err = regexp.Compile(start)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\treturn nil, fmt.Errorf(\"Missing start in %s\", group)\n\t}\n\n\t// end is mandatory\n\tif end, ok := regionInfo[\"end\"]; ok {\n\t\tend := end.(string)\n\t\tif end == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"Empty end in %s\", group)\n\t\t}\n\n\t\tr.end, err = regexp.Compile(end)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\treturn nil, fmt.Errorf(\"Missing end in %s\", group)\n\t}\n\n\t// skip is optional\n\tif skip, ok := regionInfo[\"skip\"]; ok {\n\t\tskip := skip.(string)\n\t\tif skip == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"Empty skip in %s\", group)\n\t\t}\n\n\t\tr.skip, err = regexp.Compile(skip)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// limit-color is optional\n\tif groupStr, ok := regionInfo[\"limit-group\"]; ok {\n\t\tgroupStr := groupStr.(string)\n\t\tif groupStr == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"Empty limit-group in %s\", group)\n\t\t}\n\n\t\tif _, ok := Groups[groupStr]; !ok {\n\t\t\tnumGroups++\n\t\t\tGroups[groupStr] = numGroups\n\t\t}\n\t\tgroupNum := Groups[groupStr]\n\t\tr.limitGroup = groupNum\n\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\tr.limitGroup = r.group\n\t}\n\n\t// rules are optional\n\tif rules, ok := regionInfo[\"rules\"]; ok {\n\t\tr.rules, err = parseRules(rules.([]any), r)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif r.rules == nil {\n\t\t// allow empty rules\n\t\tr.rules = &rules{}\n\t}\n\n\treturn r, nil\n}\n"
  },
  {
    "path": "pkg/highlight/unicode.go",
    "content": "package highlight\n\nimport (\n\t\"unicode\"\n\t\"unicode/utf8\"\n)\n\nvar minMark = rune(unicode.Mark.R16[0].Lo)\n\nfunc isMark(r rune) bool {\n\t// Fast path\n\tif r < minMark {\n\t\treturn false\n\t}\n\treturn unicode.In(r, unicode.Mark)\n}\n\n// DecodeCharacter returns the next character from an array of bytes\n// A character is a rune along with any accompanying combining runes\nfunc DecodeCharacter(b []byte) (rune, []rune, int) {\n\tr, size := utf8.DecodeRune(b)\n\tb = b[size:]\n\tc, s := utf8.DecodeRune(b)\n\n\tvar combc []rune\n\tfor isMark(c) {\n\t\tcombc = append(combc, c)\n\t\tsize += s\n\n\t\tb = b[s:]\n\t\tc, s = utf8.DecodeRune(b)\n\t}\n\n\treturn r, combc, size\n}\n\n// DecodeCharacterInString returns the next character from a string\n// A character is a rune along with any accompanying combining runes\nfunc DecodeCharacterInString(str string) (rune, []rune, int) {\n\tr, size := utf8.DecodeRuneInString(str)\n\tstr = str[size:]\n\tc, s := utf8.DecodeRuneInString(str)\n\n\tvar combc []rune\n\tfor isMark(c) {\n\t\tcombc = append(combc, c)\n\t\tsize += s\n\n\t\tstr = str[s:]\n\t\tc, s = utf8.DecodeRuneInString(str)\n\t}\n\n\treturn r, combc, size\n}\n\n// CharacterCount returns the number of characters in a byte array\n// Similar to utf8.RuneCount but for unicode characters\nfunc CharacterCount(b []byte) int {\n\ts := 0\n\n\tfor len(b) > 0 {\n\t\tr, size := utf8.DecodeRune(b)\n\t\tif !isMark(r) {\n\t\t\ts++\n\t\t}\n\n\t\tb = b[size:]\n\t}\n\n\treturn s\n}\n\n// CharacterCount returns the number of characters in a string\n// Similar to utf8.RuneCountInString but for unicode characters\nfunc CharacterCountInString(str string) int {\n\ts := 0\n\n\tfor _, r := range str {\n\t\tif !isMark(r) {\n\t\t\ts++\n\t\t}\n\t}\n\n\treturn s\n}\n"
  },
  {
    "path": "runtime/README.md",
    "content": "# Runtime files for Micro\n\nThis directory will be embedded in the Go binary for portability, but it may just as well be put in `~/.config/micro`. If you would like to make your own colorschemes\nand syntax files, you can put them in `~/.config/micro/colorschemes` and `~/.config/micro/syntax` respectively.\n\n"
  },
  {
    "path": "runtime/colorschemes/atom-dark.micro",
    "content": "color-link default \"#C5C8C6,#1D1F21\"\ncolor-link comment \"#7C7C7C,#1D1F21\"\ncolor-link identifier \"#F9EE98,#1D1F21\"\ncolor-link constant \"#FF73FD,#1D1F21\"\ncolor-link constant.string \"#A8FF60,#1D1F21\"\ncolor-link statement \"#96CBFE,#1D1F21\"\ncolor-link symbol \"#96CBFE,#1D1F21\"\ncolor-link preproc \"#62B1FE,#1D1F21\"\ncolor-link type \"#C6C5FE,#1D1F21\"\ncolor-link special \"#A6E22E,#1D1F21\"\ncolor-link underlined \"#D33682,#1D1F21\"\ncolor-link error \"bold #FF4444,#1D1F21\"\ncolor-link todo \"bold #FF8844,#1D1F21\"\ncolor-link hlsearch \"#000000,#B4EC85\"\ncolor-link statusline \"#1D1F21,#C5C8C6\"\ncolor-link tabbar \"#1D1F21,#C5C8C6\"\ncolor-link indent-char \"#505050,#1D1F21\"\ncolor-link line-number \"#656866,#232526\"\ncolor-link current-line-number \"#656866,#1D1F21\"\ncolor-link diff-added \"#00AF00\"\ncolor-link diff-modified \"#FFAF00\"\ncolor-link diff-deleted \"#D70000\"\ncolor-link gutter-error \"#FF4444,#1D1F21\"\ncolor-link gutter-warning \"#EEEE77,#1D1F21\"\ncolor-link cursor-line \"#2D2F31\"\ncolor-link color-column \"#2D2F31\"\n#color-link symbol.brackets \"#96CBFE,#1D1F21\"\n#No extended types (bool in C, etc.)\n#color-link type.extended \"default\"\n#Plain brackets\ncolor-link match-brace \"#1D1F21,#62B1FE\"\ncolor-link tab-error \"#D75F5F\"\ncolor-link trailingws \"#D75F5F\"\n"
  },
  {
    "path": "runtime/colorschemes/bubblegum.micro",
    "content": "color-link default \"241,231\"\ncolor-link comment \"246,231\"\ncolor-link constant \"130,231\"\ncolor-link constant.string \"136,231\"\ncolor-link constant.number \"131,231\"\ncolor-link identifier \"133,231\"\ncolor-link statement \"32,231\"\ncolor-link symbol \"32,231\"\ncolor-link preproc \"28,231\"\ncolor-link type \"61,231\"\ncolor-link special \"167,231\"\ncolor-link error \"231, 160\"\ncolor-link underlined \"underline 241,231\"\ncolor-link todo \"246,231\"\ncolor-link hlsearch \"231,136\"\ncolor-link statusline \"241,254\"\ncolor-link tabbar \"241,254\"\ncolor-link diff-added \"34\"\ncolor-link diff-modified \"214\"\ncolor-link diff-deleted \"160\"\ncolor-link gutter-error \"197,231\"\ncolor-link gutter-warning \"134,231\"\ncolor-link line-number \"246,254\"\ncolor-link cursor-line \"254\"\ncolor-link color-column \"254\"\n#No extended types (bool in C, &c.) and plain brackets\ncolor-link type.extended \"241,231\"\ncolor-link symbol.brackets \"241,231\"\ncolor-link match-brace \"231,28\"\ncolor-link tab-error \"210\"\ncolor-link trailingws \"210\"\n"
  },
  {
    "path": "runtime/colorschemes/cmc-16.micro",
    "content": "#CaptainMcClellan's personal color scheme.\n#16 colour version.\ncolor-link comment \"bold black\"\ncolor-link constant \"cyan\"\ncolor-link constant.bool \"bold cyan\"\ncolor-link constant.bool.true \"bold green\"\ncolor-link constant.bool.false \"bold red\"\ncolor-link constant.string \"yellow\"\ncolor-link constant.string.url \"underline blue, white\"\n#color-link constant.number \"constant\"\ncolor-link constant.specialChar \"bold magenta\"\ncolor-link identifier \"bold red\"\ncolor-link identifier.macro \"bold red\"\ncolor-link identifier.var \"bold blue\"\n#color-link identifier.class \"bold green\"\ncolor-link identifier.class \"bold white\"\ncolor-link statement \"bold yellow\"\ncolor-link symbol \"red\"\ncolor-link symbol.brackets \"blue\"\ncolor-link symbol.tag \"bold blue\"\ncolor-link symbol.tag.extended \"bold green\"\ncolor-link preproc \"bold cyan\"\ncolor-link type \"green\"\ncolor-link type.keyword \"bold green\"\ncolor-link special \"magenta\"\ncolor-link ignore \"default\"\ncolor-link error \"bold ,brightred\"\ncolor-link todo \"underline black,brightyellow\"\ncolor-link hlsearch \"white,darkgreen\"\ncolor-link indent-char \",brightgreen\"\ncolor-link line-number \"green\"\ncolor-link line-number.scrollbar \"green\"\ncolor-link statusline \"white,blue\"\ncolor-link tabbar \"white,blue\"\ncolor-link current-line-number \"red\"\ncolor-link current-line-number.scroller \"red\"\ncolor-link diff-added \"green\"\ncolor-link diff-modified \"yellow\"\ncolor-link diff-deleted \"red\"\ncolor-link gutter-error \",red\"\ncolor-link gutter-warning \"red\"\ncolor-link color-column \"cyan\"\ncolor-link underlined.url \"underline blue, white\"\ncolor-link divider \"blue\"\ncolor-link match-brace \"black,cyan\"\ncolor-link tab-error \"brightred\"\ncolor-link trailingws \"brightred\"\n"
  },
  {
    "path": "runtime/colorschemes/cmc-tc.micro",
    "content": "#CaptainMcClellan's personal colour scheme.\n#Full colour edition.\ncolor-link default \"#aaaaaa,#1e2124\"\ncolor-link comment \"bold #555555\"\ncolor-link constant \"#008888\"\n#color-link constant.string \"#888800\"\ncolor-link constant.string \"#a85700\"\ncolor-link constant.specialChar \"bold #ccccff\"\ncolor-link identifier \"bold #e34234\"\ncolor-link identifier.macro \"bold #e34234\"\ncolor-link identifier.var \"bold #5757ff\"\ncolor-link identifier.class \"bold #ffffff\"\ncolor-link statement \"bold #ffff55\"\ncolor-link symbol \"#722f37\"\ncolor-link symbol.brackets \"#4169e1\"\ncolor-link symbol.tag \"#5757ff\"\ncolor-link preproc \"bold #55ffff\"\ncolor-link type \"#3eb489\"\ncolor-link type.keyword \"bold #bdecb6\"\ncolor-link special \"#b57edc\"\ncolor-link ignore \"default\"\ncolor-link error \"bold ,#e34234\"\ncolor-link todo \"bold underline #888888,#f26522\"\ncolor-link hlsearch \"#b7b7b7,#32593d\"\ncolor-link indent-char \",#bdecb6\"\ncolor-link line-number \"#bdecb6,#36393e\"\ncolor-link line-number.scrollbar \"#3eb489\"\ncolor-link statusline \"#aaaaaa,#8a496b\"\ncolor-link tabbar \"#aaaaaa,#8a496b\"\ncolor-link current-line-number \"bold #e34234,#424549\"\ncolor-link current-line-number.scroller \"red\"\ncolor-link diff-added \"#00AF00\"\ncolor-link diff-modified \"#FFAF00\"\ncolor-link diff-deleted \"#D70000\"\ncolor-link gutter-error \",#e34234\"\ncolor-link gutter-warning \"#e34234\"\ncolor-link color-column \"#f26522\"\ncolor-link constant.bool \"bold #55ffff\"\ncolor-link constant.bool.true \"bold #85ff85\"\ncolor-link constant.bool.false \"bold #ff8585\"\ncolor-link match-brace \"#1e2124,#55ffff\"\ncolor-link tab-error \"#d75f5f\"\ncolor-link trailingws \"#d75f5f\"\n"
  },
  {
    "path": "runtime/colorschemes/darcula.micro",
    "content": "color-link default \"#CCCCCC,#242424\"\ncolor-link comment \"#707070,#242424\"\ncolor-link identifier \"#FFC66D,#242424\"\ncolor-link constant \"#7A9EC2,#242424\"\ncolor-link constant.string \"#6A8759,#242424\"\ncolor-link constant.string.char \"#6A8759,#242424\"\ncolor-link statement \"#CC8242,#242424\"\ncolor-link symbol \"#CCCCCC,#242424\"\ncolor-link preproc \"#CC8242,#242424\"\ncolor-link type \"#CC8242,#242424\"\ncolor-link special \"#CC8242,#242424\"\ncolor-link underlined \"#D33682,#242424\"\ncolor-link error \"bold #CB4B16,#242424\"\ncolor-link todo \"bold #D33682,#242424\"\ncolor-link hlsearch \"#CCCCCC,#32593D\"\ncolor-link statusline \"#242424,#CCCCCC\"\ncolor-link tabbar \"#242424,#CCCCCC\"\ncolor-link indent-char \"#4F4F4F,#242424\"\ncolor-link line-number \"#666666,#2C2C2C\"\ncolor-link current-line-number \"#666666,#242424\"\ncolor-link diff-added \"#00AF00\"\ncolor-link diff-modified \"#FFAF00\"\ncolor-link diff-deleted \"#D70000\"\ncolor-link gutter-error \"#CB4B16,#242424\"\ncolor-link gutter-warning \"#E6DB74,#242424\"\ncolor-link cursor-line \"#2C2C2C\"\ncolor-link color-column \"#2C2C2C\"\n#No extended types; Plain brackets.\ncolor-link type.extended \"default\"\n#color-link symbol.brackets \"default\"\ncolor-link symbol.tag \"#AE81FF,#242424\"\ncolor-link match-brace \"#242424,#7A9EC2\"\ncolor-link tab-error \"#D75F5F\"\ncolor-link trailingws \"#D75F5F\"\n"
  },
  {
    "path": "runtime/colorschemes/default.micro",
    "content": "include \"monokai\"\n"
  },
  {
    "path": "runtime/colorschemes/dracula-tc.micro",
    "content": "color-link default \"#F8F8F2,#282A36\"\ncolor-link comment \"#6272A4\"\n\ncolor-link identifier \"#50FA7B\"\ncolor-link identifier.class \"#8BE9FD\"\ncolor-link identifier.var \"#F8F8F2\"\n\ncolor-link constant \"#BD93F9\"\ncolor-link constant.number \"#F8F8F2\"\ncolor-link constant.string \"#F1FA8C\"\n\ncolor-link symbol \"#FF79C6\"\ncolor-link symbol.brackets \"#F8F8F2\"\ncolor-link symbol.tag \"#AE81FF\"\n\ncolor-link type \"italic #8BE9FD\"\ncolor-link type.keyword \"#FF79C6\"\n\ncolor-link special \"#FF79C6\"\ncolor-link statement \"#FF79C6\"\ncolor-link preproc \"#FF79C6\"\n\ncolor-link underlined \"#FF79C6\"\ncolor-link error \"bold #FF5555\"\ncolor-link todo \"bold #FF79C6\"\n\ncolor-link hlsearch \"#282A36,#50FA7B\"\n\ncolor-link diff-added \"#50FA7B\"\ncolor-link diff-modified \"#FFB86C\"\ncolor-link diff-deleted \"#FF5555\"\n\ncolor-link gutter-error \"#FF5555\"\ncolor-link gutter-warning \"#E6DB74\"\n\ncolor-link statusline \"#282A36,#F8F8F2\"\ncolor-link tabbar \"#282A36,#F8F8F2\"\ncolor-link indent-char \"#6272A4\"\ncolor-link line-number \"#6272A4\"\ncolor-link current-line-number \"#F8F8F2\"\n\ncolor-link cursor-line \"#44475A,#F8F8F2\"\ncolor-link color-column \"#44475A\"\ncolor-link type.extended \"default\"\n\ncolor-link match-brace \"#282A36,#FF79C6\"\n\ncolor-link tab-error \"#D75F5F\"\ncolor-link trailingws \"#D75F5F\"\n"
  },
  {
    "path": "runtime/colorschemes/dukedark-tc.micro",
    "content": "color-link color-column \"#001e28\"\ncolor-link comment \"#608b4e,#001e28\"\ncolor-link constant.bool \"#fd971f,#001e28\"\ncolor-link constant \"#fd971f,#001e28\"\ncolor-link constant.string \"#a0f000,#001e28\"\ncolor-link constant.string.char \"#a0f000,#001e28\"\ncolor-link constant.string.url \"#a0f000,#001e28\"\ncolor-link current-line-number \"bold #fd971f,#001e28\"\ncolor-link cursor-line \"#001923\"\ncolor-link default \"#ffffff,#001e28\"\ncolor-link diff-added \"#00c8a0,#001e28\"\ncolor-link diff-modified \"#fd971f,#001e28\"\ncolor-link diff-deleted \"#cb4b16,#001e28\"\ncolor-link divider \"#001e28,#d0d0d0\"\ncolor-link error \"#cb4b16,#001e28\"\ncolor-link gutter-error \"#cb4b16,#001e28\"\ncolor-link gutter-warning \"#fce94f,#001e28\"\ncolor-link hlsearch \"#ffffff,#005028\"\ncolor-link identifier \"#00c8a0,#001e28\"\ncolor-link identifier.class \"#00c8a0,#001e28\"\ncolor-link indent-char \"#a0a0a0,#001e28\"\ncolor-link line-number \"#a0a0a0,#001923\"\ncolor-link preproc \"bold #5aaae6,#001e28\"\ncolor-link special \"#a6e22e,#001e28\"\ncolor-link statement \"bold #5aaae6,#001e28\"\ncolor-link statusline \"#ffffff,#0078c8\"\ncolor-link symbol \"#00c8a0,#001e28\"\ncolor-link symbol.brackets \"#ffffff,#001e28\"\ncolor-link symbol.tag \"bold #5aaae6,#001e28\"\ncolor-link tabbar \"#001e28,#ffffff\"\ncolor-link todo \"#fce94f,#001e28\"\ncolor-link type \"bold #3cc83c,#001e28\"\ncolor-link type.keyword \"bold #5aaae6,#001e28\"\ncolor-link type.extended \"#ffffff,#001e28\"\ncolor-link underlined \"#608b4e,#001e28\"\ncolor-link match-brace \"#001e28,#5aaae6\"\ncolor-link tab-error \"#d75f5f\"\ncolor-link trailingws \"#d75f5f\"\n"
  },
  {
    "path": "runtime/colorschemes/dukelight-tc.micro",
    "content": "color-link color-column \"#f0f0f0\"\ncolor-link comment \"#3f7f5f,#f0f0f0\"\ncolor-link constant.bool \"#641e00,#f0f0f0\"\ncolor-link constant \"#641e00,#f0f0f0\"\ncolor-link constant.string \"#0000ff,#f0f0f0\"\ncolor-link constant.string.char \"#0000ff,#f0f0f0\"\ncolor-link constant.string.url \"#0000ff,#f0f0f0\"\ncolor-link current-line-number \"bold #004080,#f0f0f0\"\ncolor-link cursor-line \"#e6e6e6\"\ncolor-link default \"#000000,#f0f0f0\"\ncolor-link diff-added \"#008040,#f0f0f0\"\ncolor-link diff-modified \"#641e00,#f0f0f0\"\ncolor-link diff-deleted \"#500000,#f0f0f0\"\ncolor-link divider \"#f0f0f0,#004080\"\ncolor-link error \"#500000,#f0f0f0\"\ncolor-link gutter-error \"#500000,#f0f0f0\"\ncolor-link gutter-warning \"#dcc800,#f0f0f0\"\ncolor-link hlsearch \"#000000,#b8d8e8\"\ncolor-link identifier \"bold #0078a0,#f0f0f0\"\ncolor-link identifier.class \"bold #0078a0,#f0f0f0\"\ncolor-link indent-char \"#404040,#f0f0f0\"\ncolor-link line-number \"#404040,#e6e6e6\"\ncolor-link preproc \"bold #780050,#f0f0f0\"\ncolor-link special \"bold #0078a0,#f0f0f0\"\ncolor-link statement \"bold #780050,#f0f0f0\"\ncolor-link statusline \"#ffffff,#0078c8\"\ncolor-link symbol \"bold #0078a0,#f0f0f0\"\ncolor-link symbol.brackets \"#000000,#f0f0f0\"\ncolor-link symbol.tag \"bold #780050,#f0f0f0\"\ncolor-link tabbar \"#f0f0f0,#004080\"\ncolor-link todo \"#dcc800,#f0f0f0\"\ncolor-link type \"bold #004080,#f0f0f0\"\ncolor-link type.keyword \"bold #780050,#f0f0f0\"\ncolor-link type.extended \"#000000,#f0f0f0\"\ncolor-link underlined \"#3f7f5f,#f0f0f0\"\ncolor-link match-brace \"#f0f0f0,#780050\"\ncolor-link tab-error \"#ff8787\"\ncolor-link trailingws \"#ff8787\"\n"
  },
  {
    "path": "runtime/colorschemes/dukeubuntu-tc.micro",
    "content": "color-link color-column \"#2d0023\"\ncolor-link comment \"#886484,#2d0023\"\ncolor-link constant.bool \"#fd971f,#2d0023\"\ncolor-link constant \"#fd971f,#2d0023\"\ncolor-link constant.string \"#a0f000,#2d0023\"\ncolor-link constant.string.char \"#a0f000,#2d0023\"\ncolor-link constant.string.url \"#a0f000,#2d0023\"\ncolor-link current-line-number \"bold #fd971f,#2d0023\"\ncolor-link cursor-line \"#230019\"\ncolor-link default \"#ffffff,#2d0023\"\ncolor-link diff-added \"#00c8a0,#2d0023\"\ncolor-link diff-modified \"#fd971f,#2d0023\"\ncolor-link diff-deleted \"#cb4b16,#2d0023\"\ncolor-link divider \"#2d0023,#d0d0d0\"\ncolor-link error \"#cb4b16,#2d0023\"\ncolor-link gutter-error \"#cb4b16,#2d0023\"\ncolor-link gutter-warning \"#fce94f,#2d0023\"\ncolor-link hlsearch \"#ffffff,#005028\"\ncolor-link identifier \"#00c8a0,#2d0023\"\ncolor-link identifier.class \"#00c8a0,#2d0023\"\ncolor-link indent-char \"#a0a0a0,#2d0023\"\ncolor-link line-number \"#a0a0a0,#230019\"\ncolor-link preproc \"bold #5aaae6,#2d0023\"\ncolor-link special \"#a6e22e,#2d0023\"\ncolor-link statement \"bold #5aaae6,#2d0023\"\ncolor-link statusline \"#ffffff,#0078c8\"\ncolor-link symbol \"#00c8a0,#2d0023\"\ncolor-link symbol.brackets \"#ffffff,#2d0023\"\ncolor-link symbol.tag \"bold #5aaae6,#2d0023\"\ncolor-link tabbar \"#2d0023,#ffffff\"\ncolor-link todo \"#fce94f,#2d0023\"\ncolor-link type \"bold #3cc83c,#2d0023\"\ncolor-link type.keyword \"bold #5aaae6,#2d0023\"\ncolor-link type.extended \"#ffffff,#2d0023\"\ncolor-link underlined \"#886484,#2d0023\"\ncolor-link match-brace \"#2d0023,#5aaae6\"\ncolor-link tab-error \"#d75f5f\"\ncolor-link trailingws \"#d75f5f\"\n"
  },
  {
    "path": "runtime/colorschemes/geany.micro",
    "content": "#Geany\ncolor-link comment \"red\"\ncolor-link constant \"default\"\ncolor-link constant.string \"bold yellow\"\ncolor-link identifier \"default\"\ncolor-link preproc \"cyan\"\ncolor-link special \"blue\"\ncolor-link statement \"blue\"\ncolor-link symbol \"default\"\ncolor-link symbol.tag \"bold blue\"\ncolor-link type \"blue\"\ncolor-link type.extended \"default\"\ncolor-link error \"red\"\ncolor-link todo \"bold cyan\"\ncolor-link hlsearch \"black,brightcyan\"\ncolor-link indent-char \"bold black\"\ncolor-link line-number \"\"\ncolor-link current-line-number \"\"\ncolor-link statusline \"black,white\"\ncolor-link tabbar \"black,white\"\ncolor-link color-column \"bold geren\"\ncolor-link diff-added \"green\"\ncolor-link diff-modified \"yellow\"\ncolor-link diff-deleted \"red\"\ncolor-link gutter-error \",red\"\ncolor-link gutter-warning \"red\"\ncolor-link match-brace \"black,cyan\"\ncolor-link tab-error \"brightred\"\ncolor-link trailingws \"brightred\"\n"
  },
  {
    "path": "runtime/colorschemes/gotham.micro",
    "content": "color-link default \"#99D1CE,#0C1014\"\ncolor-link comment \"#245361,#0C1014\"\ncolor-link identifier \"#599CAB,#0C1014\"\ncolor-link constant \"#D26937,#0C1014\"\ncolor-link constant.string \"#2AA889,#0C1014\"\ncolor-link constant.string.char \"#D3EBE9,#0C1014\"\ncolor-link statement \"#599CAB,#0C1014\"\ncolor-link preproc \"#C23127,#0C1014\"\ncolor-link type \"#D26937,#0C1014\"\ncolor-link special \"#D26937,#0C1014\"\ncolor-link underlined \"#EDB443,#0C1014\"\ncolor-link error \"bold #C23127,#0C1014\"\ncolor-link todo \"bold #888CA6,#0C1014\"\ncolor-link hlsearch \"#091F2E,#EDB443\"\ncolor-link statusline \"#091F2E,#599CAB\"\ncolor-link indent-char \"#505050,#0C1014\"\ncolor-link line-number \"#245361,#11151C\"\ncolor-link current-line-number \"#599CAB,#11151C\"\ncolor-link diff-added \"#00AF00\"\ncolor-link diff-modified \"#FFAF00\"\ncolor-link diff-deleted \"#D70000\"\ncolor-link gutter-error \"#C23127,#11151C\"\ncolor-link gutter-warning \"#EDB443,#11151C\"\ncolor-link cursor-line \"#091F2E\"\ncolor-link color-column \"#11151C\"\ncolor-link symbol \"#99D1CE,#0C1014\"\ncolor-link match-brace \"#0C1014,#D26937\"\ncolor-link tab-error \"#D75F5F\"\ncolor-link trailingws \"#D75F5F\"\n"
  },
  {
    "path": "runtime/colorschemes/gruvbox-tc.micro",
    "content": "color-link default \"#ebdbb2,#282828\"\ncolor-link comment \"#928374,#282828\"\ncolor-link symbol \"#d79921,#282828\"\ncolor-link constant \"#d3869b,#282828\"\ncolor-link constant.string \"#b8bb26,#282828\"\ncolor-link constant.string.char \"#b8bb26,#282828\"\ncolor-link identifier \"#8ec07c,#282828\"\ncolor-link statement \"#fb4934,#282828\"\ncolor-link preproc \"#fb4934,235\"\ncolor-link type \"#fb4934,#282828\"\ncolor-link special \"#d79921,#282828\"\ncolor-link underlined \"underline #458588,#282828\"\ncolor-link error \"#9d0006,#282828\"\ncolor-link todo \"bold #ebdbb2,#282828\"\ncolor-link hlsearch \"#282828,#fabd2f\"\ncolor-link diff-added \"#00AF00\"\ncolor-link diff-modified \"#FFAF00\"\ncolor-link diff-deleted \"#D70000\"\ncolor-link gutter-error \"#fb4934,#282828\"\ncolor-link gutter-warning \"#d79921,#282828\"\ncolor-link line-number \"#665c54,#3c3836\"\ncolor-link current-line-number \"#d79921,#282828\"\ncolor-link cursor-line \"#3c3836\"\ncolor-link color-column \"#79740e\"\ncolor-link statusline \"#ebdbb2,#665c54\"\ncolor-link tabbar \"#ebdbb2,#665c54\"\ncolor-link match-brace \"#282828,#d3869b\"\ncolor-link tab-error \"#d75f5f\"\ncolor-link trailingws \"#d75f5f\"\n"
  },
  {
    "path": "runtime/colorschemes/gruvbox.micro",
    "content": "color-link default \"223,235\"\ncolor-link comment \"243,235\"\ncolor-link constant \"175,235\"\ncolor-link constant.string \"142,235\"\ncolor-link identifier \"109,235\"\ncolor-link statement \"124,235\"\ncolor-link symbol \"124,235\"\ncolor-link preproc \"72,235\"\ncolor-link type \"214,235\"\ncolor-link special \"172,235\"\ncolor-link underlined \"underline 109,235\"\ncolor-link error \"235,124\"\ncolor-link todo \"bold 223,235\"\ncolor-link hlsearch \"235,214\"\ncolor-link diff-added \"34\"\ncolor-link diff-modified \"214\"\ncolor-link diff-deleted \"160\"\ncolor-link line-number \"243,237\"\ncolor-link current-line-number \"172,235\"\ncolor-link cursor-line \"237\"\ncolor-link color-column \"237\"\ncolor-link statusline \"223,237\"\ncolor-link tabbar \"223,237\"\ncolor-link match-brace \"235,72\"\ncolor-link tab-error \"167\"\ncolor-link trailingws \"167\"\n"
  },
  {
    "path": "runtime/colorschemes/material-tc.micro",
    "content": "color-link color-column \"#263238\"\ncolor-link comment \"#4F6875,#263238\"\ncolor-link constant \"#F07178,#263238\"\ncolor-link constant.number \"#F78C6A,#263238\"\ncolor-link constant.specialChar \"#89DDF3,#263238\"\ncolor-link constant.string \"#C3E88D,#263238\"\ncolor-link current-line-number \"#80DEEA,#263238\"\ncolor-link cursor-line \"#283942\"\ncolor-link default \"#EEFFFF,#263238\"\ncolor-link diff-added \"#00AF00\"\ncolor-link diff-modified \"#FFAF00\"\ncolor-link diff-deleted \"#D70000\"\ncolor-link divider \"#263238,#80DEEA\"\ncolor-link error \"bold #263238,#F07178\"\ncolor-link gutter-error \"#EEFFFF,#F07178\"\ncolor-link gutter-warning \"#EEFFFF,#FFF176\"\ncolor-link hlsearch \"#FFFFFF,#546E7A\"\ncolor-link identifier \"#82AAFF,#263238\"\ncolor-link identifier.macro \"#FFCB6B,#263238\"\ncolor-link indent-char \"#505050,#263238\"\ncolor-link line-number \"#656866,#283942\"\ncolor-link preproc \"#C792EA,#263238\"\ncolor-link scrollbar \"#80DEEA,#283942\"\ncolor-link special \"#C792EA,#263238\"\ncolor-link statement \"#C792EA,#263238\"\ncolor-link statusline \"#80DEEA,#3b4d56\"\ncolor-link symbol \"#96CBFE,#263238\"\ncolor-link symbol.brackets \"#89DDF3,#263238\"\ncolor-link symbol.operator \"#C792EA,#263238\"\ncolor-link tabbar \"#80DEEA,#3b4d56\"\ncolor-link todo \"bold #C792EA,#263238\"\ncolor-link type \"#FFCB6B,#263238\"\ncolor-link underlined \"underline #EEFFFF,#263238\"\ncolor-link match-brace \"#263238,#C792EA\"\ncolor-link tab-error \"#D75F5F\"\ncolor-link trailingws \"#D75F5F\"\n"
  },
  {
    "path": "runtime/colorschemes/monokai-dark.micro",
    "content": "color-link default \"#D5D8D6,#1D0000\"\ncolor-link comment \"#75715E\"\ncolor-link identifier \"#66D9EF\"\ncolor-link constant \"#AE81FF\"\ncolor-link constant.string \"#E6DB74\"\ncolor-link constant.string.char \"#BDE6AD\"\ncolor-link statement \"#F92672\"\ncolor-link preproc \"#CB4B16\"\ncolor-link type \"#66D9EF\"\ncolor-link special \"#A6E22E\"\ncolor-link underlined \"#D33682\"\ncolor-link error \"bold #CB4B16\"\ncolor-link todo \"bold #D33682\"\ncolor-link hlsearch \"#1D0000,#E6DB74\"\ncolor-link statusline \"#282828,#F8F8F2\"\ncolor-link indent-char \"#505050,#282828\"\ncolor-link line-number \"#AAAAAA,#282828\"\ncolor-link current-line-number \"#AAAAAA,#1D0000\"\ncolor-link diff-added \"#00AF00\"\ncolor-link diff-modified \"#FFAF00\"\ncolor-link diff-deleted \"#D70000\"\ncolor-link gutter-error \"#CB4B16\"\ncolor-link gutter-warning \"#E6DB74\"\ncolor-link cursor-line \"#323232\"\ncolor-link color-column \"#323232\"\ncolor-link match-brace \"#1D0000,#AE81FF\"\ncolor-link tab-error \"#D75F5F\"\ncolor-link trailingws \"#D75F5F\"\n"
  },
  {
    "path": "runtime/colorschemes/monokai.micro",
    "content": "color-link default \"#F8F8F2,#282828\"\ncolor-link comment \"#75715E,#282828\"\ncolor-link identifier \"#66D9EF,#282828\"\ncolor-link constant \"#AE81FF,#282828\"\ncolor-link constant.string \"#E6DB74,#282828\"\ncolor-link constant.string.char \"#BDE6AD,#282828\"\ncolor-link statement \"#F92672,#282828\"\ncolor-link symbol.operator \"#F92672,#282828\"\ncolor-link preproc \"#CB4B16,#282828\"\ncolor-link type \"#66D9EF,#282828\"\ncolor-link special \"#A6E22E,#282828\"\ncolor-link underlined \"#D33682,#282828\"\ncolor-link error \"bold #CB4B16,#282828\"\ncolor-link todo \"bold #D33682,#282828\"\ncolor-link hlsearch \"#282828,#E6DB74\"\ncolor-link statusline \"#282828,#F8F8F2\"\ncolor-link tabbar \"#282828,#F8F8F2\"\ncolor-link indent-char \"#505050,#282828\"\ncolor-link line-number \"#AAAAAA,#323232\"\ncolor-link current-line-number \"#AAAAAA,#282828\"\ncolor-link diff-added \"#00AF00\"\ncolor-link diff-modified \"#FFAF00\"\ncolor-link diff-deleted \"#D70000\"\ncolor-link gutter-error \"#CB4B16,#282828\"\ncolor-link gutter-warning \"#E6DB74,#282828\"\ncolor-link cursor-line \"#323232\"\ncolor-link color-column \"#323232\"\n#No extended types; Plain brackets.\ncolor-link type.extended \"default\"\n#color-link symbol.brackets \"default\"\ncolor-link symbol.tag \"#AE81FF,#282828\"\ncolor-link match-brace \"#282828,#AE81FF\"\ncolor-link tab-error \"#D75F5F\"\ncolor-link trailingws \"#D75F5F\"\n"
  },
  {
    "path": "runtime/colorschemes/one-dark.micro",
    "content": "color-link default \"#ABB2BF,#21252C\"\ncolor-link color-column \"#282C34\"\ncolor-link comment \"#5C6370\"\ncolor-link constant \"#C678DD\"\ncolor-link constant.number \"#E5C07B\"\ncolor-link constant.string \"#98C379\"\ncolor-link constant.string.char \"#BDE6AD\"\ncolor-link constant.specialChar \"#DDF2A4\"\ncolor-link current-line-number \"#C6C6C6,#21252C\"\ncolor-link cursor-line \"#282C34\"\ncolor-link divider \"#ABB2BF\"\ncolor-link error \"#D2A8A1\"\ncolor-link diff-added \"#00AF00\"\ncolor-link diff-modified \"#FFAF00\"\ncolor-link diff-deleted \"#D70000\"\ncolor-link gutter-error \"#9B859D\"\ncolor-link gutter-warning \"#9B859D\"\ncolor-link hlsearch \"#21252C,#E5C07B\"\ncolor-link identifier \"#61AFEF\"\ncolor-link identifier.class \"#C678DD\"\ncolor-link identifier.var \"#C678DD\"\ncolor-link indent-char \"#515151\"\ncolor-link line-number \"#636D83,#282C34\"\ncolor-link preproc \"#E0C589\"\ncolor-link special \"#E0C589\"\ncolor-link statement \"#C678DD\"\ncolor-link statusline \"#282828,#ABB2BF\"\ncolor-link symbol \"#AC885B\"\ncolor-link symbol.brackets \"#ABB2BF\"\ncolor-link symbol.operator \"#C678DD\"\ncolor-link symbol.tag \"#AC885B\"\ncolor-link tabbar \"#F2F0EC,#2D2D2D\"\ncolor-link todo \"#8B98AB\"\ncolor-link type \"#66D9EF\"\ncolor-link type.keyword \"#C678DD\"\ncolor-link underlined \"#8996A8\"\ncolor-link match-brace \"#21252C,#C678DD\"\ncolor-link tab-error \"#D75F5F\"\ncolor-link trailingws \"#D75F5F\"\n"
  },
  {
    "path": "runtime/colorschemes/railscast.micro",
    "content": "color-link default \"#e6e1dc,#2b2b2b\"\ncolor-link comment \"#bc9458,#2b2b2b\"\ncolor-link statement \"#cc7833,#2b2b2b\"\ncolor-link constant \"#a5c261,#2b2b2b\"\ncolor-link constant.bool \"#6d9cbe,#2b2b2b\"\ncolor-link constant.specialChar \"#459231,#2b2b2b\"\ncolor-link type \"#6d9cbe,#2b2b2b\"\ncolor-link preproc \"#cc7833,#2b2b2b\"\ncolor-link special \"#cc7833,#2b2b2b\"\ncolor-link underlined \"#cc7833,#2b2b2b\"\ncolor-link todo \"bold #cc7833,#2b2b2b\"\ncolor-link error \"bold #cc7833,#2b2b2b\"\ncolor-link gutter-error \"#cc7833,#11151C\"\ncolor-link hlsearch \"#e6e1dc,#474d5c\"\ncolor-link indent-char \"#414141,#2b2b2b\"\ncolor-link line-number \"#a1a1a1,#232323\"\ncolor-link current-line-number \"#e6e1dc,#2b2b2b\"\ncolor-link diff-added \"#00AF00\"\ncolor-link diff-modified \"#FFAF00\"\ncolor-link diff-deleted \"#D70000\"\ncolor-link gutter-warning \"#a5c261,#11151C\"\ncolor-link symbol \"#edb753,#2b2b2b\"\ncolor-link symbol.operator \"#cc7833,#2b2b2b\"\ncolor-link symbol.brackets \"#cc7833,#2b2b2b\"\ncolor-link identifier \"#edb753,#2b2b2b\"\ncolor-link statusline \"#b1b1b1,#232323\"\ncolor-link tabbar \"bold #b1b1b1,#232323\"\ncolor-link cursor-line \"#353535\"\ncolor-link color-column \"#353535\"\ncolor-link space \"underline #e6e1dc,#2b2b2b\"\ncolor-link tab-error \"#d75f5f\"\ncolor-link trailingws \"#d75f5f\"\n\n#the Python syntax definition are wrong. This is not how you should do decorators!\ncolor-link brightgreen \"#edb753,#2b2b2b\"\n\ncolor-link match-brace \"#2b2b2b,#a5c261\"\n"
  },
  {
    "path": "runtime/colorschemes/simple.micro",
    "content": "color-link comment \"blue\"\ncolor-link constant \"red\"\ncolor-link identifier \"cyan\"\ncolor-link statement \"yellow\"\ncolor-link symbol \"yellow\"\ncolor-link preproc \"magenta\"\ncolor-link type \"green\"\ncolor-link special \"magenta\"\ncolor-link ignore \"default\"\ncolor-link error \",brightred\"\ncolor-link todo \",brightyellow\"\ncolor-link hlsearch \"black,yellow\"\ncolor-link statusline \"black,white\"\ncolor-link indent-char \"black\"\ncolor-link line-number \"yellow\"\ncolor-link current-line-number \"red\"\ncolor-link diff-added \"green\"\ncolor-link diff-modified \"yellow\"\ncolor-link diff-deleted \"red\"\ncolor-link gutter-error \",red\"\ncolor-link gutter-warning \"red\"\n#Cursor line causes readability issues. Disabled for now.\n#color-link cursor-line \"white,black\"\ncolor-link color-column \"white\"\n#No extended types. (bool in C)\ncolor-link type.extended \"default\"\n#No bracket highlighting.\ncolor-link symbol.brackets \"default\"\n#Color shebangs the comment color\ncolor-link preproc.shebang \"comment\"\ncolor-link match-brace \",magenta\"\ncolor-link tab-error \"brightred\"\ncolor-link trailingws \"brightred\"\n"
  },
  {
    "path": "runtime/colorschemes/solarized-tc.micro",
    "content": "color-link default \"#839496,#002833\"\ncolor-link comment \"#586E75,#002833\"\ncolor-link identifier \"#268BD2,#002833\"\ncolor-link constant \"#2AA198,#002833\"\ncolor-link constant.specialChar \"#DC322F,#002833\"\ncolor-link statement \"#859900,#002833\"\ncolor-link symbol \"#859900,#002833\"\ncolor-link preproc \"#CB4B16,#002833\"\ncolor-link type \"#B58900,#002833\"\ncolor-link special \"#268BD2,#002833\"\ncolor-link underlined \"#D33682,#002833\"\ncolor-link error \"bold #CB4B16,#002833\"\ncolor-link todo \"bold #D33682,#002833\"\ncolor-link hlsearch \"#002833,#B58900\"\ncolor-link statusline \"#003541,#839496\"\ncolor-link tabbar \"#003541,#839496\"\ncolor-link indent-char \"#003541,#002833\"\ncolor-link line-number \"#586E75,#003541\"\ncolor-link current-line-number \"#586E75,#002833\"\ncolor-link diff-added \"#00AF00\"\ncolor-link diff-modified \"#FFAF00\"\ncolor-link diff-deleted \"#D70000\"\ncolor-link gutter-error \"#003541,#CB4B16\"\ncolor-link gutter-warning \"#CB4B16,#002833\"\ncolor-link cursor-line \"#003541\"\ncolor-link color-column \"#003541\"\ncolor-link type.extended \"#839496,#002833\"\ncolor-link symbol.brackets \"#839496,#002833\"\ncolor-link match-brace \"#002833,#268BD2\"\ncolor-link tab-error \"#D75F5F\"\ncolor-link trailingws \"#D75F5F\"\n"
  },
  {
    "path": "runtime/colorschemes/solarized.micro",
    "content": "color-link comment \"bold brightgreen\"\ncolor-link constant \"cyan\"\ncolor-link constant.specialChar \"red\"\ncolor-link identifier \"blue\"\ncolor-link statement \"green\"\ncolor-link symbol \"green\"\ncolor-link preproc \"brightred\"\ncolor-link type \"yellow\"\ncolor-link special \"blue\"\ncolor-link underlined \"magenta\"\ncolor-link error \"bold brightred\"\ncolor-link todo \"bold magenta\"\ncolor-link hlsearch \"black,yellow\"\ncolor-link statusline \"black,brightblue\"\ncolor-link tabbar \"black,brightblue\"\ncolor-link indent-char \"black\"\ncolor-link line-number \"bold brightgreen,black\"\ncolor-link current-line-number \"bold brightgreen,default\"\ncolor-link diff-added \"green\"\ncolor-link diff-modified \"yellow\"\ncolor-link diff-deleted \"red\"\ncolor-link gutter-error \"black,brightred\"\ncolor-link gutter-warning \"brightred,default\"\ncolor-link cursor-line \"black\"\ncolor-link color-column \"black\"\ncolor-link type.extended \"default\"\ncolor-link symbol.brackets \"default\"\ncolor-link match-brace \",blue\"\ncolor-link tab-error \"brightred\"\ncolor-link trailingws \"brightred\"\n"
  },
  {
    "path": "runtime/colorschemes/sunny-day.micro",
    "content": "color-link default \"0,230\"\ncolor-link comment \"244\"\ncolor-link constant.string \"17\"\ncolor-link constant \"88\"\ncolor-link identifier \"22\"\ncolor-link statement \"0,230\"\ncolor-link symbol \"89\"\ncolor-link preproc \"22\"\ncolor-link type \"88\"\ncolor-link special \"22\"\ncolor-link underlined \"61,230\"\ncolor-link error \"88\"\ncolor-link todo \"210\"\ncolor-link hlsearch \"0,253\"\ncolor-link statusline \"233,229\"\ncolor-link tabbar \"233,229\"\ncolor-link indent-char \"229\"\ncolor-link line-number \"244\"\ncolor-link diff-added \"34\"\ncolor-link diff-modified \"214\"\ncolor-link diff-deleted \"160\"\ncolor-link gutter-error \"88\"\ncolor-link gutter-warning \"88\"\ncolor-link cursor-line \"229\"\n#color-link color-column \"196\"\ncolor-link current-line-number \"246\"\ncolor-link match-brace \"230,22\"\ncolor-link tab-error \"210\"\ncolor-link trailingws \"210\"\n"
  },
  {
    "path": "runtime/colorschemes/twilight.micro",
    "content": "# Twilight color scheme\ncolor-link default \"#F8F8F8,#141414\"\ncolor-link color-column \"#1B1B1B\"\ncolor-link comment \"#5F5A60\"\ncolor-link constant \"#CF6A4C\"\n#color-link constant.number \"#CF6A4C\"\ncolor-link constant.specialChar \"#DDF2A4\"\ncolor-link constant.string \"#8F9D6A\"\ncolor-link current-line-number \"#868686,#1B1B1B\"\ncolor-link cursor-line \"#1B1B1B\"\ncolor-link divider \"#1E1E1E\"\ncolor-link error \"#D2A8A1\"\ncolor-link diff-added \"#00AF00\"\ncolor-link diff-modified \"#FFAF00\"\ncolor-link diff-deleted \"#D70000\"\ncolor-link gutter-error \"#9B859D\"\ncolor-link gutter-warning \"#9B859D\"\ncolor-link hlsearch \"#141414,#C0C000\"\ncolor-link identifier \"#9B703F\"\ncolor-link identifier.class \"#DAD085\"\ncolor-link identifier.var \"#7587A6\"\ncolor-link indent-char \"#515151\"\ncolor-link line-number \"#868686,#1B1B1B\"\ncolor-link current-line-number \"#868686,#141414\"\ncolor-link preproc \"#E0C589\"\ncolor-link special \"#E0C589\"\ncolor-link statement \"#CDA869\"\ncolor-link statusline \"#515151,#1E1E1E\"\ncolor-link symbol \"#AC885B\"\ncolor-link symbol.brackets \"#F8F8F8\"\ncolor-link symbol.operator \"#CDA869\"\ncolor-link symbol.tag \"#AC885B\"\ncolor-link tabbar \"#F2F0EC,#2D2D2D\"\ncolor-link todo \"#8B98AB\"\ncolor-link type \"#F9EE98\"\ncolor-link type.keyword \"#CDA869\"\ncolor-link underlined \"#8996A8\"\ncolor-link match-brace \"#141414,#E0C589\"\ncolor-link tab-error \"#D75F5F\"\ncolor-link trailingws \"#D75F5F\"\n"
  },
  {
    "path": "runtime/colorschemes/zenburn.micro",
    "content": "color-link default \"188,237\"\ncolor-link comment \"108,237\"\ncolor-link constant.string \"174,237\"\ncolor-link constant.number \"116,237\"\ncolor-link constant \"181,237\"\ncolor-link identifier \"223,237\"\ncolor-link statement \"223,237\"\ncolor-link symbol \"223,237\"\ncolor-link preproc \"223,237\"\ncolor-link type \"187,237\"\ncolor-link special \"181,237\"\ncolor-link underlined \"188,237\"\ncolor-link error \"115,236\"\ncolor-link todo \"bold 254,237\"\ncolor-link hlsearch \"230,22\"\ncolor-link statusline \"186,236\"\ncolor-link tabbar \"186,236\"\ncolor-link indent-char \"238,237\"\ncolor-link line-number \"248,238\"\ncolor-link diff-added \"34\"\ncolor-link diff-modified \"214\"\ncolor-link diff-deleted \"160\"\ncolor-link gutter-error \"237,174\"\ncolor-link gutter-warning \"174,237\"\ncolor-link cursor-line \"238\"\ncolor-link color-column \"238\"\ncolor-link current-line-number \"188,237\"\ncolor-link match-brace \"237,223\"\ncolor-link tab-error \"167\"\ncolor-link trailingws \"167\"\n"
  },
  {
    "path": "runtime/help/colors.md",
    "content": "# Colors\n\nThis help page aims to cover two aspects of micro's syntax highlighting engine:\n\n* How to create colorschemes and use them.\n* How to create syntax files to add to the list of languages micro can\n  highlight.\n\n## Colorschemes\n\nTo change your colorscheme, press `Ctrl-e` in micro to bring up the command\nprompt, and type:\n\n```\nset colorscheme twilight\n```\n\n(or whichever colorscheme you choose).\n\nMicro comes with a number of colorschemes by default. The colorschemes that you\ncan display will depend on what kind of color support your terminal has.\n\nOmit color-link default \"[fg color],[bg color]\" will make the background color match the terminal's, and transparency if set.\n\nModern terminals tend to have a palette of 16 user-configurable colors (these\ncolors can often be configured in the terminal preferences), and additional\ncolor support comes in three flavors.\n\n* 16-color: A colorscheme that uses the 16 default colors will always work but\n  will only look good if the 16 default colors have been configured to the\n  user's liking. Using a colorscheme that only uses the 16 colors from the\n  terminal palette will also preserve the terminal's theme from other\n  applications since the terminal will often use those same colors for other\n  applications. Default colorschemes of this type include `simple` and\n  `solarized`.\n\n* 256-color: Almost all terminals support displaying an additional 240 colors\n  on top of the 16 user-configurable colors (creating 256 colors total).\n  Colorschemes which use 256-color are portable because they will look the\n  same regardless of the configured 16-color palette. However, the color\n  range is fairly limited due to the small number of colors available.\n  Default 256-color colorschemes include `monokai`, `twilight`, `zenburn`,\n  `darcula` and more.\n\n* true-color: Some terminals support displaying \"true color\" with 16 million\n  colors using standard RGB values. This mode will be able to support\n  displaying any colorscheme, but it should be noted that the user-configured\n  16-color palette is ignored when using true-color mode (this means the\n  colors while using the terminal emulator will be slightly off). Not all\n  terminals support true color but at this point most do (see below).\n  True-color colorschemes in micro typically end with `-tc`, such as\n  `solarized-tc`, `atom-dark`, `material-tc`, etc... If true color is not\n  enabled but a true color colorscheme is used, micro will do its best to\n  approximate the colors to the available 256 colors.\n\nHere is the list of colorschemes:\n\n### 256 color\n\nThese should work and look nice in most terminals. I recommend these\nthemes the most.\n\n* `monokai` (also the `default` colorscheme)\n* `zenburn`\n* `gruvbox`\n* `darcula`\n* `twilight`\n* `railscast`\n* `bubblegum` (light theme)\n\n### 16 color\n\nThese may vary widely based on the 16 colors selected for your terminal.\n\n* `simple`\n* `solarized` (must have the solarized color palette in your terminal to use\n   this colorscheme properly)\n* `cmc-16`\n* `cmc-paper`\n* `geany`\n\n### True color\n\nMicro enables true color support by default as long as it detects that the\nterminal supports it (which is usually indicated by the environment variable\n`COLORTERM` being set to `truecolor`, `24bit` or `24-bit`). You can also force\nenabling it unconditionally by setting the option `truecolor` to `on` (or\nalternatively by setting the environment variable `MICRO_TRUECOLOR` to 1, which\nis supported for backward compatibility).\n\n* `solarized-tc`: this is the solarized colorscheme for true color.\n* `atom-dark`: this colorscheme is based off of Atom's \"dark\" colorscheme.\n* `cmc-tc`: A true colour variant of the cmc theme.  It requires true color to\n   look its best. Use cmc-16 if your terminal doesn't support true color.\n* `gruvbox-tc`: The true color version of the gruvbox colorscheme\n* `material-tc`: Colorscheme based off of Google's Material Design palette\n\n## Creating a Colorscheme\n\nMicro's colorschemes are also extremely simple to create. The default ones can\nbe found\n[here](https://github.com/micro-editor/micro/tree/master/runtime/colorschemes).\n\nCustom colorschemes should be placed in the `~/.config/micro/colorschemes`\ndirectory.\n\nA number of custom directives are placed in a `.micro` file. Colorschemes are\ntypically only 18-30 lines in total.\n\nTo create the colorscheme you need to link highlight groups with\nactual colors. This is done using the `color-link` command.\n\nFor example, to highlight all comments in green, you would use the command:\n\n```\ncolor-link comment \"green\"\n```\n\nBackground colors can also be specified with a comma:\n\n```\ncolor-link comment \"green,blue\"\n```\n\nThis will give the comments a blue background.\n\nIf you would like no foreground you can just use a comma with nothing in front:\n\n```\ncolor-link comment \",blue\"\n```\n\nYou can also put bold, italic, or underline in front of the color:\n\n```\ncolor-link comment \"bold red\"\n```\n\n---\n\nThere are three different ways to specify the color.\n\nColor terminals usually have 16 colors that are preset by the user. This means\nthat you cannot depend on those colors always being the same. You can use those\ncolors with the names `black, red, green, yellow, blue, magenta, cyan, white`\nand the bright variants of each one (brightblack, brightred...).\n\nThen you can use the terminals 256 colors by using their numbers 1-256 (numbers\n1-16 will refer to the named colors).\n\nIf the user's terminal supports true color, then you can also specify colors\nexactly using their hex codes. If the terminal is not true color but micro is\ntold to use a true color colorscheme it will attempt to map the colors to the\navailable 256 colors.\n\nGenerally colorschemes which require true color terminals to look good are\nmarked with a `-tc` suffix and colorschemes which supply a white background are\nmarked with a `-paper` suffix.\n\n---\n\nHere is a list of the colorscheme groups that you can use:\n\n* default (color of the background and foreground for unhighlighted text)\n* comment\n* identifier\n* constant\n* statement\n* symbol\n* preproc\n* type\n* special\n* underlined\n* error\n* todo\n* selection (Color of the text selection)\n* statusline (Color of the statusline)\n* statusline.inactive (Color of the statusline of inactive split panes)\n* statusline.suggestions (Color of the autocomplete suggestions menu)\n* tabbar (Color of the tabbar that lists open files)\n* tabbar.active (Color of the active tab in the tabbar)\n* indent-char (Color of the character which indicates tabs if the option is\n  enabled)\n* line-number\n* gutter-info\n* gutter-error\n* gutter-warning\n* diff-added\n* diff-modified\n* diff-deleted\n* cursor-line\n* current-line-number\n* color-column\n* ignore\n* scrollbar\n* divider (Color of the divider between vertical splits)\n* message (Color of messages in the bottom line of the screen)\n* error-message (Color of error messages in the bottom line of the screen)\n* match-brace (Color of matching brackets when `matchbracestyle` is set to `highlight`)\n* hlsearch (Color of highlighted search results when `hlsearch` is enabled)\n* tab-error (Color of tab vs space errors when `hltaberrors` is enabled)\n* trailingws (Color of trailing whitespaces when `hltrailingws` is enabled)\n\nColorschemes must be placed in the `~/.config/micro/colorschemes` directory to\nbe used.\n\n---\n\nIn addition to the main colorscheme groups, there are subgroups that you can\nspecify by adding `.subgroup` to the group. If you're creating your own custom\nsyntax files, you can make use of your own subgroups.\n\nIf micro can't match the subgroup, it'll default to the root group, so  it's\nsafe and recommended to use subgroups in your custom syntax files.\n\nFor example if `constant.string` is found in your colorscheme, micro will us\nthat for highlighting strings. If it's not found, it will use constant instead.\nMicro tries to match the largest set of groups it can find in the colorscheme\ndefinitions, so if, for example `constant.bool.true` is found then micro will\nuse that. If `constant.bool.true` is not found but `constant.bool` is found\nmicro will use `constant.bool`. If not, it uses `constant`.\n\nHere's a list of subgroups used in micro's built-in syntax files.\n\n* comment.bright (Some filetypes have distinctions between types of comments)\n* constant.bool\n* constant.bool.true\n* constant.bool.false\n* constant.number\n* constant.specialChar\n* constant.string\n* constant.string.url\n* identifier.class (Also used for functions)\n* identifier.macro\n* identifier.var\n* preproc.shebang (The #! at the beginning of a file that tells the os what\n  script interpreter to use)\n* symbol.brackets (`{}()[]` and sometimes `<>`)\n* symbol.operator (Color operator symbols differently)\n* symbol.tag (For html tags, among other things)\n* type.keyword (If you want a special highlight for keywords like `private`)\n\nIn the future, plugins may also be able to use color groups for styling.\n\n---\n\nLast but not least it's even possible to use `include` followed by the\ncolorscheme name as string to include a different colorscheme within a new one.\nAdditionally the groups can then be extended or overwritten. The `default.micro`\ntheme can be seen as an example, which links to the chosen default colorscheme.\n\n## Syntax files\n\nThe syntax files are written in yaml-format and specify how to highlight\nlanguages.\n\nMicro's builtin syntax highlighting tries very hard to be sane, sensible and\nprovide ample coverage of the meaningful elements of a language. Micro has\nsyntax files built in for over 100 languages now! However, there may be\nsituations where you find Micro's highlighting to be insufficient or not to\nyour liking. The good news is that you can create your own syntax files, and\nplace them in  `~/.config/micro/syntax` and Micro will use those instead.\n\n### Filetype definition\n\nYou must start the syntax file by declaring the filetype:\n\n```\nfiletype: go\n```\n\n### Detect definition\n\nThen you must provide information about how to detect the filetype:\n\n```\ndetect:\n    filename: \"\\\\.go$\"\n```\n\nMicro will match this regex against a given filename to detect the filetype.\n\nIn addition to the `filename` regex (or even instead of it) you can provide\na `header` regex that will check the first line of the file. For example:\n\n```\ndetect:\n    filename: \"\\\\.ya?ml$\"\n    header: \"%YAML\"\n```\n\nThis is useful in cases when the given file name is not sufficient to determine\nthe filetype, e.g. with the above example, if a YAML file has no `.yaml`\nextension but may contain a `%YAML` directive in its first line.\n\n`filename` takes precedence over `header`, i.e. if there is a syntax file that\nmatches the file with a filetype by the `filename` and another syntax file that\nmatches the same file with another filetype by the `header`, the first filetype\nwill be used.\n\nFinally, in addition to `filename` and/or `header` (but not instead of them)\nyou may also provide an optional `signature` regex which is useful for resolving\nambiguities when there are multiple syntax files matching the same file with\ndifferent filetypes. If a `signature` regex is given, micro will match a certain\namount of first lines in the file (this amount is determined by the `detectlimit`\noption) against this regex, and if any of the lines match, this syntax file's\nfiletype will be preferred over other matching filetypes.\n\nFor example, to distinguish C++ header files from C and Objective-C header files\nthat have the same `.h` extension:\n\n```\ndetect:\n    filename: \"\\\\.c(c|pp|xx)$|\\\\.h(h|pp|xx)?$\"\n    signature: \"namespace|template|public|protected|private\"\n```\n\n### Syntax rules\n\nNext you must provide the syntax highlighting rules. There are two types of\nrules: patterns and regions. A pattern is matched on a single line and usually\na single word as well. A region highlights between two patterns over multiple\nlines and may have rules of its own inside the region.\n\nHere are some example patterns in Go:\n\n```\nrules:\n    - special: \"\\\\b(break|case|continue|default|go|goto|range|return)\\\\b\"\n    - statement: \"\\\\b(else|for|if|switch)\\\\b\"\n    - preproc: \"\\\\b(package|import|const|var|type|struct|func|go|defer|iota)\\\\b\"\n```\n\nThe order of patterns does matter as patterns lower in the file will overwrite\nthe ones defined above them.\n\nAnd here are some example regions for Go:\n\n```\n- constant.string:\n    start: \"\\\"\"\n    end: \"\\\"\"\n    rules:\n        - constant.specialChar: \"%.\"\n        - constant.specialChar: \"\\\\\\\\[abfnrtv'\\\\\\\"\\\\\\\\]\"\n        - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})\"\n\n- comment:\n    start: \"//\"\n    end: \"$\"\n    rules:\n        - todo: \"(TODO|XXX|FIXME):?\"\n\n- comment:\n    start: \"/\\\\*\"\n    end: \"\\\\*/\"\n    rules:\n        - todo: \"(TODO|XXX|FIXME):?\"\n```\n\nNotice how the regions may contain rules inside of them. Any inner rules that\nare matched are then skipped when searching for the end of the region. For\nexample, when highlighting `\"foo \\\" bar\"`, since `\\\"` is matched by an inner\nrule in the region, it is skipped. Likewise for `\"foo \\\\\" bar`, since `\\\\` is\nmatched by an inner rule, it is skipped, and then the `\"` is found and the\nstring ends at the correct place.\n\nYou may also explicitly mark skip regexes if you don't want them to be\nhighlighted. For example:\n\n```\n- constant.string:\n    start: \"\\\"\"\n    end: \"\\\"\"\n    skip: \"\\\\.\"\n```\n\n#### Includes\n\nYou may also include rules from other syntax files as embedded languages. For\nexample, the following is possible for html:\n\n```\n- default:\n    start: \"<script.*?>\"\n    end: \"</script.*?>\"\n    rules:\n        - include: \"javascript\"\n\n- default:\n    start: \"<style.*?>\"\n    end: \"</style.*?>\"\n    rules:\n        - include: \"css\"\n```\n\nNote that nested include (i.e. including syntax files that include other syntax\nfiles) is not supported yet.\n\n### Default syntax highlighting\n\nIf micro cannot detect the filetype of the file, it falls back to using the\ndefault syntax highlighting for it, which highlights just the bare minimum:\nemail addresses, URLs etc.\n\nJust like in other cases, you can override the default highlighting by adding\nyour own custom `default.yaml` file to `~/.config/micro/syntax`.\n\nFor example, if you work with various config files that use the `#` sign to mark\nthe beginning of a comment, you can use the following custom `default.yaml` to\nhighlight those comments by default:\n\n```\nfiletype: unknown\n\ndetect:\n    filename: \"\"\n\nrules:\n    - comment: \"(^|\\\\s)#.*$\"\n```\n"
  },
  {
    "path": "runtime/help/commands.md",
    "content": "# Command bar\n\nThe command bar is opened by pressing `Ctrl-e`. It is a single-line buffer,\nmeaning that all keybindings from a normal buffer are supported (as well\nas mouse and selection).\n\nWhen running a command, you can use extra syntax that micro will expand before\nrunning the command. To use an argument with a space in it, put it in\nquotes. The command bar parser uses the same rules for parsing arguments that\n`/bin/sh` would use (single quotes, double quotes, escaping). The command bar\ndoes not look up environment variables.\n\n# Commands\n\nMicro provides the following commands that can be executed at the command-bar\nby pressing `Ctrl-e` and entering the command. Arguments are placed in single\nquotes here but these are not necessary when entering the command in micro.\n\n* `bind 'key' 'action'`: creates a keybinding from key to action. See the\n   `keybindings` documentation for more information about binding keys.\n   This command will modify `bindings.json` and overwrite any bindings to\n   `key` that already exist.\n\n* `help ['topic'] ['flags']`: opens the corresponding help topics.\n   If no topic is provided opens the default help screen. If multiple topics are\n   provided (separated via ` `) they are opened all as splits.\n   Help topics are stored as `.md` files in the `runtime/help` directory of\n   the source tree, which is embedded in the final binary.\n   The `flags` are optional.\n   * `-hsplit`: Opens the help topic in a horizontal split\n   * `-vsplit`: Opens the help topic in a vertical split\n\n   The default split type is defined by the global `helpsplit` option.\n\n* `save ['filename']`: saves the current buffer. If the file is provided it\n   will 'save as' the filename.\n\n* `quit`: quits micro.\n\n* `goto 'line[:col]'`: goes to the given absolute line (and optional column)\n   number.\n   A negative number can be passed to go inward from the end of the file.\n   Example: -5 goes to the 5th-last line in the file.\n\n* `jump 'line[:col]'`: goes to the given relative number from the current\n   line (and optional absolute column) number.\n   Example: -5 jumps 5 lines up in the file, while (+)3 jumps 3 lines down.\n\n* `replace 'search' 'value' ['flags']`: This will replace `search` with `value`.\n   The `flags` are optional. Possible flags are:\n   * `-a`: Replace all occurrences at once\n   * `-l`: Do a literal search instead of a regex search\n\n   Note that `search` must be a valid regex (unless `-l` is passed). If one\n   of the arguments does not have any spaces in it, you may omit the quotes.\n\n   In case the search is done non-literal (without `-l`), the 'value'\n   is interpreted as a template:\n   * `$3` or `${3}` substitutes the submatch of the 3rd (capturing group)\n   * `$foo` or `${foo}` substitutes the submatch of the (?P<foo>named group)\n   * You have to write `$$` to substitute a literal dollar.\n\n* `replaceall 'search' 'value'`: this will replace all occurrences of `search`\n   with `value` without user confirmation.\n\n   See `replace` command for more information.\n\n* `set 'option' 'value'`: sets the option to value. See the `options` help\n   topic for a list of options you can set. This will modify your\n   `settings.json` with the new value.\n\n* `setlocal 'option' 'value'`: sets the option to value locally (only in the\n   current buffer). This will *not* modify `settings.json`.\n\n* `toggle 'option'`: toggles the option. Only works with options that accept\n   exactly two values. This will modify your `settings.json` with the new value.\n\n* `togglelocal 'option'`: toggles the option locally (only in the\n   current buffer). Only works with options that accept exactly two values.\n   This will *not* modify `settings.json`.\n\n* `reset 'option'`: resets the given option to its default value.\n\n* `show 'option'`: shows the current value of the given option.\n\n* `showkey 'key'`: Show the action(s) bound to a given key. For example\n   running `> showkey Ctrl-c` will display `Copy`.\n\n* `run 'sh-command'`: runs the given shell command in the background. The\n   command's output will be displayed in one line when it finishes running.\n\n* `vsplit ['filename']`: opens a vertical split with `filename`. If no filename\n   is provided, a vertical split is opened with an empty buffer. If multiple\n   files are provided (separated via ` `) they are opened all as splits.\n\n* `hsplit ['filename']`: same as `vsplit` but opens a horizontal split instead\n   of a vertical split.\n\n* `tab ['filename']`: opens the given file in a new tab. If no filename\n   is provided, a tab is opened with an empty buffer. If multiple files are\n   provided (separated via ` `) they are opened all as tabs.\n\n* `tabmove '[-+]n'`: Moves the active tab to another slot. `n` is an integer.\n   If `n` is prefixed with `-` or `+`, then it represents a relative position\n   (e.g. `tabmove +2` moves the tab to the right by `2`). If `n` has no prefix,\n   it represents an absolute position (e.g. `tabmove 2` moves the tab to slot `2`).\n\n* `tabswitch 'tab'`: This command will switch to the specified tab. The `tab`\n   can either be a tab number, or a name of a tab.\n\n* `textfilter 'sh-command'`: filters the current selection through a shell\n   command as standard input and replaces the selection with the stdout of\n   the shell command.  For example, to sort a list of numbers, first select\n   them, and then execute `> textfilter sort -n`.\n\n* `log`: opens a log of all messages and debug statements.\n\n* `plugin list`: lists all installed plugins.\n\n* `plugin install 'pl'`: install a plugin.\n\n* `plugin remove 'pl'`: remove a plugin.\n\n* `plugin update ['pl']`: update a plugin (if no arguments are provided\n   updates all plugins).\n\n* `plugin search 'pl'`: search available plugins for a keyword.\n\n* `plugin available`: show available plugins that can be installed.\n\n* `reload`: reloads all runtime files (settings, keybindings, syntax files,\n   colorschemes, plugins). All plugins will be unloaded by running their\n   `deinit()` function (if it exists), and then loaded again by calling the\n   `preinit()`, `init()` and `postinit()` functions (if they exist).\n\n* `cd 'path'`: Change the working directory to the given `path`.\n\n* `pwd`: Print the current working directory.\n\n* `open 'filename'`: Open a file in the current buffer.\n\n* `reopen`: Reopens the current file from disk.\n\n* `retab`: Replaces all leading tabs with spaces or leading spaces with tabs\n   depending on the value of `tabstospaces`.\n\n* `raw`: micro will open a new tab and show the escape sequence for every event\n   it receives from the terminal. This shows you what micro actually sees from\n   the terminal and helps you see which bindings aren't possible and why. This\n   is most useful for debugging keybindings.\n\n* `term ['exec']`: Open a terminal emulator running the given executable. If no\n   executable is given, this will open the default shell in the terminal\n   emulator.\n\n---\n\nThe following commands are provided by the default plugins:\n\n* `lint`: Lint the current file for errors.\n* `comment`: automatically comment or uncomment current selection or line.\n"
  },
  {
    "path": "runtime/help/copypaste.md",
    "content": "Copy and paste are essential features in micro but can be\nconfusing to get right especially when running micro over SSH\nbecause there are multiple methods. This help document will explain\nthe various methods for copying and pasting, how they work,\nand the best methods for doing so over SSH.\n\n# OSC 52 (terminal clipboard)\n\nIf possible, setting the `clipboard` option to `terminal` will give\nbest results because it will work over SSH and locally. However, there\nis limited support among terminal emulators for the terminal clipboard\n(which uses the OSC 52 protocol to communicate clipboard contents).\nHere is a list of terminal emulators and their status:\n\n* `Kitty`: supported, but only writing is enabled by default. To enable\n   reading, add `read-primary` and `read-clipboard` to the\n   `clipboard_control` option.\n\n* `iTerm2`: only copying (writing to clipboard) is supported. Must be enabled in\n   `Preferences->General-> Selection->Applications in terminal may access clipboard`.\n   You can use `Command-v` to paste.\n\n* `st`: supported.\n\n* `rxvt-unicode`: not natively supported, but there is a Perl extension\n   [here](https://anti.teamidiot.de/static/nei/*/Code/urxvt/).\n\n* `xterm`: supported, but disabled by default. It can be enabled by putting\n   the following in `.Xresources` or `.Xdefaults`:\n   `XTerm*disallowedWindowOps: 20,21,SetXprop`.\n\n* `gnome-terminal`: does not support OSC 52.\n\n* `alacritty`: supported. Since 0.13.0, reading has been disabled by default.\n   To reenable it, set the `terminal.osc52` option to `CopyPaste`.\n\n* `foot`: supported.\n\n* `wezterm`: only copying (writing to clipboard) is supported.\n\n\n**Summary:** If you want copy and paste to work over SSH, then you\nshould set `clipboard` to `terminal`, and make sure your terminal\nsupports OSC 52.\n\n# Pasting\n\n## Recommendations (TL;DR)\n\nThe recommended method of pasting is the following:\n\n* If you are not working over SSH, use the micro keybinding (`Ctrl-v`\n  by default) to perform pastes. If on Linux, install `xclip` or\n  `xsel` beforehand.\n\n* If you are working over SSH, use the terminal keybinding\n  (`Ctrl-Shift-v` or `Command-v`) to perform pastes. If your terminal\n  does not support bracketed paste, when performing a paste first\n  enable the `paste` option, and when finished disable the option.\n\n## Micro paste events\n\nMicro is an application that runs within the terminal. This means\nthat the terminal sends micro events, such as key events, mouse\nevents, resize events, and paste events. Micro's default keybinding\nfor paste is `Ctrl-v`. This means that when micro receives the key\nevent saying `Ctrl-v` has been pressed from the terminal, it will\nattempt to access the system clipboard and effect a paste. The\nsystem clipboard will be accessed through `pbpaste` on MacOS\n(installed by default), `xclip` or `xsel` on Linux (these\napplications must be installed by the user) or a system call on\nWindows.\n\n## Terminal paste events\n\nFor certain keypresses, the terminal will not send an event to\nmicro and will instead do something itself. In this document,\nsuch keypresses will be called \"terminal keybindings.\" Often\nthere will be a terminal keybinding for pasting and copying. On\nMacOS these are Command-v and Command-c and on Linux `Ctrl-Shift-v`\nand `Ctrl-Shift-c`. When the terminal keybinding for paste is\nexecuted, your terminal will access the system clipboard, and send\nmicro either a paste event or a list of key events (one key for each\ncharacter in the paste), depending on whether or not your terminal\nsupports sending paste events (called bracketed paste).\n\nIf your terminal supports bracketed paste, then it will send a paste\nevent and everything will work well. However, if your terminal\nsends a list of key events, this can cause issues because micro\nwill think you manually entered each character and may add closing\nbrackets or automatic indentation, which will mess up the pasted\ntext. To avoid this, you can temporarily enable the `paste` option\nwhile you perform the paste. When paste option is on, micro will\naggregate lists of multiple key events into larger paste events.\nIt is a good idea to disable the `paste` option during normal use\nas occasionally if you are typing quickly, the terminal will send\nthe key events as lists of characters that were in fact manually\nentered.\n\n## Pasting over SSH\n\nWhen working over SSH, micro is running on the remote machine and\nyour terminal is running on your local machine. Therefore if you\nwould like to paste, using `Ctrl-v` (micro's keybinding) will not\nwork because when micro attempts to access the system clipboard,\nit will access the remote machine's clipboard rather than the local\nmachine's clipboard. On the other hand, the terminal keybinding\nfor paste will access your local clipboard and send the text over\nthe network as a paste event, which is what you want.\n\n# Copying\n\n# Recommendations (TL;DR)\n\nThe recommended method of copying is the following:\n\n* If you are not working over SSH, use the micro keybinding (`Ctrl-c` by\n  default) to perform copies. If on Linux, install `xclip` or `xsel`\n  beforehand.\n\n* If you are working over SSH, use the terminal keybinding\n  (`Ctrl-Shift-c` or `Command-c`) to perform copies. You must first disable\n  the `mouse` option to perform a terminal selection, and you may wish\n  to disable line numbers and diff indicators (`ruler` and `diffgutter`\n  options) and close other splits. This method will only be able to copy\n  characters that are displayed on the screen (you will not be able to\n  copy more than one page's worth of characters).\n\nCopying follows a similar discussion to the one above about pasting.\nThe primary difference is before performing a copy, the application\ndoing the copy must be told what text needs to be copied.\n\nMicro has a keybinding (`Ctrl-c`) for copying and will access the system\nclipboard to perform the copy. The text that micro will copy into is\nthe text that is currently selected in micro (usually such text is\ndisplayed with a white background). When the `mouse` option is enabled,\nthe mouse can be used to select text, as well as other keybindings,\nsuch as ShiftLeft, etc...\n\nThe terminal also has a keybinding (`Ctrl-Shift-c` or `Command-c`) to perform\na copy, and the text that it copies is the text selected by the terminal's\nselection (*not* micro's selection). To select text with the terminal\nselection, micro's mouse support must first be disabled by turning the\n`mouse` option off. The terminal, unlike micro, has no sense of different\nbuffers/splits and what the different characters being displayed are. This\nmeans that for copying multiple lines using the terminal selection, you\nshould first disable line numbers and diff indicators (turn off the `ruler`\nand `diffgutter` options), otherwise they might be part of your selection\nand copied.\n"
  },
  {
    "path": "runtime/help/defaultkeys.md",
    "content": "# Default Keys\n\nBelow are simple charts of the default hotkeys and their functions. For more\ninformation about binding custom hotkeys or changing default bindings, please\nrun `> help keybindings`\n\nPlease remember that *all* keys here are rebindable! If you don't like it, you\ncan change it!\n\n### Power user\n\n| Key       | Description of function                                                                           |\n|---------- |-------------------------------------------------------------------------------------------------- |\n| Ctrl-e    | Open a command prompt for running commands (see `> help commands` for a list of valid commands).  |\n| Tab       | In command prompt, it will autocomplete if possible.                                              |\n| Ctrl-b    | Run a shell command (this will close micro while your command executes).                          |\n\n### Navigation\n\n| Key                         | Description of function                                                                   |\n|---------------------------- |------------------------------------------------------------------------------------------ |\n| Arrows                      | Move the cursor around                                                                    |\n| Shift-arrows                | Move and select text                                                                      |\n| Alt(Ctrl on Mac)-LeftArrow  | Move to the beginning of the current line                                                 |\n| Alt(Ctrl on Mac)-RightArrow | Move to the end of the current line                                                       |\n| Home                        | Move to the beginning of text on the current line                                         |\n| End                         | Move to the end of the current line                                                       |\n| Ctrl(Alt on Mac)-LeftArrow  | Move cursor one word left                                                                 |\n| Ctrl(Alt on Mac)-RightArrow | Move cursor one word right                                                                |\n| Alt-{                       | Move cursor to previous empty line, or beginning of document                              |\n| Alt-}                       | Move cursor to next empty line, or end of document                                        |\n| PageUp                      | Move cursor up one page                                                                   |\n| PageDown                    | Move cursor down one page                                                                 |\n| Ctrl-Home or Ctrl-UpArrow   | Move cursor to start of document                                                          |\n| Ctrl-End or Ctrl-DownArrow  | Move cursor to end of document                                                            |\n| Ctrl-l                      | Jump to a line in the file (prompts with #)                                               |\n| Ctrl-w                      | Cycle between splits in the current tab (use `> vsplit` or `> hsplit` to create a split)  |\n\n### Tabs\n\n| Key     | Description of function   |\n|-------- |-------------------------- |\n| Ctrl-t  | Open a new tab            |\n| Alt-,   | Previous tab              |\n| Alt-.   | Next tab                  |\n\n### Find Operations\n\n| Key       | Description of function                   |\n|---------- |------------------------------------------ |\n| Ctrl-f    | Find (opens prompt)                       |\n| Ctrl-n    | Find next instance of current search      |\n| Ctrl-p    | Find previous instance of current search  |\n\nNote: `Ctrl-n` and `Ctrl-p` should be used from the main buffer, not from inside\nthe search prompt. After `Ctrl-f`, press enter to complete the search and then\nyou can use `Ctrl-n` and `Ctrl-p` to cycle through matches.\n\n### File Operations\n\n| Key       | Description of function                                           |\n|---------- |------------------------------------------------------------------ |\n| Ctrl-q    | Close current file (quits micro if this is the last file open)    |\n| Ctrl-o    | Open a file (prompts for filename)                                |\n| Ctrl-s    | Save current file                                                 |\n\n### Text operations\n\n| Key                                 | Description of function                   |\n|------------------------------------ |------------------------------------------ |\n| Ctrl(Alt on Mac)-Shift-RightArrow   | Select word right                         |\n| Ctrl(Alt on Mac)-Shift-LeftArrow    | Select word left                          |\n| Alt(Ctrl on Mac)-Shift-LeftArrow    | Select to start of current line           |\n| Alt(Ctrl on Mac)-Shift-RightArrow   | Select to end of current line             |\n| Shift-Home                          | Select to start of current line           |\n| Shift-End                           | Select to end of current line             |\n| Ctrl-Shift-UpArrow                  | Select to start of file                   |\n| Ctrl-Shift-DownArrow                | Select to end of file                     |\n| Ctrl-x                              | Cut selected text                         |\n| Ctrl-c                              | Copy selected text                        |\n| Ctrl-v                              | Paste                                     |\n| Ctrl-k                              | Cut current line                          |\n| Ctrl-d                              | Duplicate current line                    |\n| Ctrl-z                              | Undo                                      |\n| Ctrl-y                              | Redo                                      |\n| Alt-UpArrow                         | Move current line or selected lines up    |\n| Alt-DownArrow                       | Move current line or selected lines down  |\n| Alt-Backspace or Alt-Ctrl-h         | Delete word left                          |\n| Ctrl-a                              | Select all                                |\n| Tab                                 | Indent selected text                      |\n| Shift-Tab                           | Unindent selected text                    |\n\n### Macros\n\n| Key       | Description of function                                                           |\n|---------- |---------------------------------------------------------------------------------- |\n| Ctrl-u    | Toggle macro recording (press Ctrl-u to start recording and press again to stop)  |\n| Ctrl-j    | Run latest recorded macro                                                         |\n\n### Multiple cursors\n\n| Key               | Description of function                                                                       |\n|------------------ |---------------------------------------------------------------------------------------------- |\n| Alt-n             | Create new multiple cursor from selection (will select current word if no current selection)  |\n| Alt-Shift-Up      | Spawn a new cursor on the line above the current one                                          |\n| Alt-Shift-Down    | Spawn a new cursor on the line below the current one                                          |\n| Alt-p             | Remove latest multiple cursor                                                                 |\n| Alt-c             | Remove all multiple cursors (cancel)                                                          |\n| Alt-x             | Skip multiple cursor selection                                                                |\n| Alt-m             | Spawn a new cursor at the beginning of every line in the current selection                    |\n| Ctrl-MouseLeft    | Place a multiple cursor at any location                                                       |\n\n### Other\n\n| Key       | Description of function                                                               |\n|---------- |-------------------------------------------------------------------------------------- |\n| Ctrl-g    | Open help file                                                                        |\n| Ctrl-h    | Backspace (old terminals do not support the backspace key and use Ctrl+H instead)     |\n| Ctrl-r    | Toggle the line number ruler                                                          |\n\n### Emacs style actions\n\n| Key       | Description of function   |\n|---------- |-------------------------- |\n| Alt-f     | Next word                 |\n| Alt-b     | Previous word             |\n| Alt-a     | Move to start of line     |\n| Alt-e     | Move to end of line       |\n\n### Function keys.\n\nWarning! The function keys may not work in all terminals!\n\n| Key   | Description of function   |\n|------ |-------------------------- |\n| F1    | Open help                 |\n| F2    | Save                      |\n| F3    | Find                      |\n| F4    | Quit                      |\n| F7    | Find                      |\n| F10   | Quit                      |\n"
  },
  {
    "path": "runtime/help/help.md",
    "content": "# Micro help text\n\nMicro is an easy to use, intuitive, text editor that takes advantage of the\nfull capabilities of modern terminals.\n\nMicro can be controlled by commands entered on the command bar, or with\nkeybindings. To open the command bar, press `Ctrl-e`: the `>` prompt will\ndisplay. From now on, when the documentation shows a command to run (such as\n`> help`), press `Ctrl-e` and type the command followed by enter.\n\nFor a list of the default keybindings, run `> help defaultkeys`.\nFor more information on keybindings, see `> help keybindings`.\nTo toggle a short list of important keybindings, press Alt-g.\n\n## Quick-start\n\nTo quit, press `Ctrl-q`. Save by pressing `Ctrl-s`. Press `Ctrl-e`, as previously\nmentioned, to start typing commands. To see which commands are available, at the\nprompt, press tab, or view the help topic with `> help commands`.\n\nMove the cursor around with the mouse or with the arrow keys. Enter text simply\nby pressing character keys.\n\nIf the colorscheme doesn't look good, you can change it with\n`> set colorscheme ...`. You can press tab to see the available colorschemes,\nor see more information about colorschemes and syntax highlighting with `> help\ncolors`.\n\nPress `Ctrl-w` to move between splits, and type `> vsplit filename` or\n`> hsplit filename` to open a new split.\n\n## Accessing more help\n\nMicro has a built-in help system which can be accessed with the `> help` command.\n\nTo view help for the various available topics, press `Ctrl-e` to access command\nmode and type in `> help` followed by a topic. Typing just `> help` will open\nthis page.\n\nHere are the available help topics:\n\n* `tutorial`: A brief tutorial which gives an overview of all the other help\n   topics\n* `keybindings`: Gives a full list of the default keybindings as well as how to\n   rebind them\n* `defaultkeys`: Gives a more straight-forward list of the hotkey commands and\n   what they do\n* `commands`: Gives a list of all the commands and what they do\n* `options`: Gives a list of all the options you can customize\n* `plugins`: Explains how micro's plugin system works and how to create your own\n   plugins\n* `colors`: Explains micro's colorscheme and syntax highlighting engine and how\n   to create your own colorschemes or add new languages to the engine\n\nFor example, to open the help page on plugins you would run `> help plugins`.\n\nI recommend looking at the `tutorial` help file because it is short for each\nsection and gives concrete examples of how to use the various configuration\noptions in micro. However, it does not give the in-depth documentation that the\nother topics provide.\n"
  },
  {
    "path": "runtime/help/keybindings.md",
    "content": "# Keybindings\n\nMicro has a plethora of hotkeys that make it easy and powerful to use and all\nhotkeys are fully customizable to your liking.\n\nCustom keybindings are stored internally in micro if changed with the `> bind`\ncommand or can also be added in the file `~/.config/micro/bindings.json` as\ndiscussed below. For a list of the default keybindings in the json format used\nby micro, please see the end of this file. For a more user-friendly list with\nexplanations of what the default hotkeys are and what they do, please see\n`> help defaultkeys` (a json formatted list of default keys is included\nat the end of this document).\n\nIf `~/.config/micro/bindings.json` does not exist, you can simply create it.\nMicro will know what to do with it.\n\nYou can use Ctrl + arrows to move word by word (Alt + arrows for Mac). Alt + left and right\nmove the cursor to the start and end of the line (Ctrl + left/right for Mac), and Ctrl + up and down move the\ncursor to the start and end of the buffer.\n\nYou can hold shift with all of these movement actions to select while moving.\n\n## Rebinding keys\n\nThe bindings may be rebound using the `~/.config/micro/bindings.json` file.\nEach key is bound to an action.\n\nFor example, to bind `Ctrl-y` to undo and `Ctrl-z` to redo, you could put the\nfollowing in the `bindings.json` file.\n\n```json\n{\n    \"Ctrl-y\": \"Undo\",\n    \"Ctrl-z\": \"Redo\"\n}\n```\n\n**Note:** The syntax `<Modifier><key>` is equivalent to `<Modifier>-<key>`. In\naddition, `Ctrl-Shift` bindings are not supported by terminals, and are the same\nas simply `Ctrl` bindings. This means that `CtrlG`, `Ctrl-G`, and `Ctrl-g` all\nmean the same thing. However, for `Alt` this is not the case: `AltG` and `Alt-G`\nmean `Alt-Shift-g`, while `Alt-g` does not require the Shift modifier.\n\nIn addition to editing your `~/.config/micro/bindings.json`, you can run\n`>bind <keycombo> <action>` For a list of bindable actions, see below.\n\nYou can also chain commands when rebinding. For example, if you want `Alt-s` to\nsave and quit you can bind it like so:\n\n```json\n{\n    \"Alt-s\": \"Save,Quit\"\n}\n```\n\nEach action will return a success flag. Actions can be chained such that\nthe chain only continues when there are successes, or failures, or either.\nThe `,` separator will always chain to the next action. The `|` separator\nwill abort the chain if the action preceding it succeeds, and the `&` will\nabort the chain if the action preceding it fails. For example, in the default\nbindings, tab is bound as\n\n```\n\"Tab\": \"Autocomplete|IndentSelection|InsertTab\"\n```\n\nThis means that if the `Autocomplete` action is successful, the chain will\nabort. Otherwise, it will try `IndentSelection`, and if that fails too, it\nwill execute `InsertTab`. To use `,`, `|` or `&` in an action (as an argument\nto a command, for example), escape it with `\\` or wrap it in single or double\nquotes.\n\nIf the action has an `onAction` lua callback, for example `onAutocomplete` (see\n`> help plugins`), then the action is only considered successful if the action\nitself succeeded *and* the callback returned true. If there are multiple\n`onAction` callbacks for this action, registered by multiple plugins, then the\naction is only considered successful if the action itself succeeded and all the\ncallbacks returned true.\n\n## Binding commands\n\nYou can also bind a key to execute a command in command mode (see\n`help commands`). Simply prepend the binding with `command:`. For example:\n\n```json\n{\n    \"Alt-p\": \"command:pwd\"\n}\n```\n\n**Note for macOS**: By default, macOS terminals do not forward alt events and\ninstead insert unicode characters. To fix this, do the following:\n\n* iTerm2: select `Esc+` for `Left Option Key` in `Preferences->Profiles->Keys`.\n* Terminal.app: Enable `Use Option key as Meta key` in `Preferences->Profiles->Keyboard`.\n\nNow when you press `Alt-p` the `pwd` command will be executed which will show\nyour working directory in the infobar.\n\nYou can also bind an \"editable\" command with `command-edit:`. This means that\nmicro won't immediately execute the command when you press the binding, but\ninstead just place the string in the infobar in command mode. For example,\nyou could rebind `Ctrl-g` to `> help`:\n\n```json\n{\n    \"Ctrl-g\": \"command-edit:help \"\n}\n```\n\nNow when you press `Ctrl-g`, `help` will appear in the command bar and your\ncursor will be placed after it (note the space in the json that controls the\ncursor placement).\n\n## Binding Lua functions\n\nYou can also bind a key to a Lua function provided by a plugin, or by your own\n`~/.config/micro/init.lua`. For example:\n\n```json\n{\n    \"Alt-q\": \"lua:foo.bar\"\n}\n```\n\nwhere `foo` is the name of the plugin and `bar` is the name of the lua function\nin it, e.g.:\n\n```lua\nlocal micro = import(\"micro\")\n\nfunction bar(bp)\n    micro.InfoBar():Message(\"Bar action triggered\")\n    return true\nend\n```\n\nSee `> help plugins` for more informations on how to write lua functions.\n\nFor `~/.config/micro/init.lua` the plugin name is `initlua` (so the keybinding\nin this example would be `\"Alt-q\": \"lua:initlua.bar\"`).\n\nThe currently active bufpane is passed to the lua function as the argument. If\nthe key is a mouse button, e.g. `MouseLeft` or `MouseWheelUp`, the mouse event\ninfo is passed to the lua function as the second argument, of type\n`*tcell.EventMouse`. See https://pkg.go.dev/github.com/micro-editor/tcell/v2#EventMouse\nfor the description of this type and its methods.\n\nThe return value of the lua function defines whether the action has succeeded.\nThis is used when chaining lua functions with other actions. They can be chained\nthe same way as regular actions as described above, for example:\n\n```\n\"Alt-q\": \"lua:initlua.bar|Quit\"\n```\n\n## Binding raw escape sequences\n\nOnly read this section if you are interested in binding keys that aren't on the\nlist of supported keys for binding.\n\nOne of the drawbacks of using a terminal-based editor is that the editor must\nget all of its information about key events through the terminal. The terminal\nsends these events in the form of escape sequences often (but not always)\nstarting with `0x1b`.\n\nFor example, if micro reads `\\x1b[1;5D`, on most terminals this will mean the\nuser pressed CtrlLeft.\n\nFor many key chords though, the terminal won't send any escape code or will\nsend an escape code already in use. For example for `CtrlBackspace`, my\nterminal sends `\\u007f` (note this doesn't start with `0x1b`), which it also\nsends for `Backspace` meaning micro can't bind `CtrlBackspace`.\n\nHowever, some terminals do allow you to bind keys to send specific escape\nsequences you define. Then from micro you can directly bind those escape\nsequences to actions. For example, to bind `CtrlBackspace` you can instruct\nyour terminal to send `\\x1bctrlback` and then bind it in `bindings.json`:\n\n```json\n{\n    \"\\u001bctrlback\": \"DeleteWordLeft\"\n}\n```\n\nHere are some instructions for sending raw escapes in different terminals\n\n### iTerm2\n\nIn iTerm2, you can do this in  `Preferences->Profiles->Keys` then click the\n`+`, input your keybinding, and for the `Action` select `Send Escape Sequence`.\nFor the above example your would type `ctrlback` into the box (the `\\x1b`) is\nautomatically sent by iTerm2.\n\n### Linux using loadkeys\n\nYou can do this in linux using the loadkeys program.\n\nComing soon!\n\n## Unbinding keys\n\nIt is also possible to disable any of the default key bindings by use of the\n`None` action in the user's `bindings.json` file.\n\n## Bindable actions and bindable keys\n\nThe list of default keybindings contains most of the possible actions and keys\nwhich you can use, but not all of them. Here is a full list of both.\n\nFull list of possible actions:\n\n```\nCursorUp\nCursorDown\nCursorPageUp\nCursorPageDown\nCursorLeft\nCursorRight\nCursorStart\nCursorEnd\nCursorToViewTop\nCursorToViewCenter\nCursorToViewBottom\nSelectToStart\nSelectToEnd\nSelectUp\nSelectDown\nSelectLeft\nSelectRight\nWordRight\nWordLeft\nSubWordRight\nSubWordLeft\nSelectWordRight\nSelectWordLeft\nSelectSubWordRight\nSelectSubWordLeft\nDeleteWordRight\nDeleteWordLeft\nDeleteSubWordRight\nDeleteSubWordLeft\nSelectLine\nSelectToStartOfLine\nSelectToStartOfText\nSelectToStartOfTextToggle\nSelectToEndOfLine\nParagraphPrevious\nParagraphNext\nSelectToParagraphPrevious\nSelectToParagraphNext\nInsertNewline\nBackspace\nDelete\nInsertTab\nSave\nSaveAll\nSaveAs\nFind\nFindLiteral\nFindNext\nFindPrevious\nDiffNext\nDiffPrevious\nCenter\nUndo\nRedo\nCopy\nCopyLine\nCut\nCutLine\nDuplicate\nDuplicateLine\nDeleteLine\nMoveLinesUp\nMoveLinesDown\nIndentSelection\nOutdentSelection\nAutocomplete\nCycleAutocompleteBack\nOutdentLine\nIndentLine\nPaste\nPastePrimary\nSelectAll\nOpenFile\nStart\nEnd\nPageUp\nPageDown\nSelectPageUp\nSelectPageDown\nHalfPageUp\nHalfPageDown\nStartOfText\nStartOfTextToggle\nStartOfLine\nEndOfLine\nToggleHelp\nToggleKeyMenu\nToggleDiffGutter\nToggleRuler\nToggleHighlightSearch\nUnhighlightSearch\nResetSearch\nClearStatus\nShellMode\nCommandMode\nToggleOverwriteMode\nEscape\nQuit\nQuitAll\nForceQuit\nAddTab\nPreviousTab\nNextTab\nFirstTab\nLastTab\nNextSplit\nPreviousSplit\nFirstSplit\nLastSplit\nUnsplit\nVSplit\nHSplit\nToggleMacro\nPlayMacro\nSuspend (Unix only)\nScrollUp\nScrollDown\nSpawnMultiCursor\nSpawnMultiCursorUp\nSpawnMultiCursorDown\nSpawnMultiCursorSelect\nRemoveMultiCursor\nRemoveAllMultiCursors\nSkipMultiCursor\nSkipMultiCursorBack\nJumpToMatchingBrace\nJumpLine\nDeselect\nClearInfo\nNone\n```\n\nThe `StartOfTextToggle` and `SelectToStartOfTextToggle` actions toggle between\njumping to the start of the text (first) and start of the line.\n\nThe `CutLine` action cuts the current line and adds it to the previously cut\nlines in the clipboard since the last paste (rather than just replaces the\nclipboard contents with this line). So you can cut multiple, not necessarily\nconsecutive lines to the clipboard just by pressing `Ctrl-k` multiple times,\nwithout selecting them. If you want the more traditional behavior i.e. just\nrewrite the clipboard every time, you can use `CopyLine,DeleteLine` action\ninstead of `CutLine`.\n\nYou can also bind some mouse actions (these must be bound to mouse buttons)\n\n```\nMousePress\nMouseDrag\nMouseRelease\nMouseMultiCursor\n```\n\nHere is the list of all possible keys you can bind:\n\n```\nUp\nDown\nRight\nLeft\nUpLeft\nUpRight\nDownLeft\nDownRight\nCenter\nPageUp\nPageDown\nHome\nEnd\nInsert\nDelete\nHelp\nExit\nClear\nCancel\nPrint\nPause\nBacktab\nF1\nF2\nF3\nF4\nF5\nF6\nF7\nF8\nF9\nF10\nF11\nF12\nF13\nF14\nF15\nF16\nF17\nF18\nF19\nF20\nF21\nF22\nF23\nF24\nF25\nF26\nF27\nF28\nF29\nF30\nF31\nF32\nF33\nF34\nF35\nF36\nF37\nF38\nF39\nF40\nF41\nF42\nF43\nF44\nF45\nF46\nF47\nF48\nF49\nF50\nF51\nF52\nF53\nF54\nF55\nF56\nF57\nF58\nF59\nF60\nF61\nF62\nF63\nF64\nCtrlSpace\nCtrl-a\nCtrl-b\nCtrl-c\nCtrl-d\nCtrl-e\nCtrl-f\nCtrl-g\nCtrl-h\nCtrl-i\nCtrl-j\nCtrl-k\nCtrl-l\nCtrl-m\nCtrl-n\nCtrl-o\nCtrl-p\nCtrl-q\nCtrl-r\nCtrl-s\nCtrl-t\nCtrl-u\nCtrl-v\nCtrl-w\nCtrl-x\nCtrl-y\nCtrl-z\nCtrlLeftSq\nCtrlBackslash\nCtrlRightSq\nCtrlCarat\nCtrlUnderscore\nBackspace\nOldBackspace\nTab\nEsc\nEscape\nEnter\n```\n\nYou can also bind some mouse buttons (they may be bound to normal actions or\nmouse actions)\n\n```\nMouseLeft\nMouseLeftDrag\nMouseLeftRelease\nMouseMiddle\nMouseMiddleDrag\nMouseMiddleRelease\nMouseRight\nMouseRightDrag\nMouseRightRelease\nMouseWheelUp\nMouseWheelDown\nMouseWheelLeft\nMouseWheelRight\n```\n\n## Key sequences\n\nKey sequences can be bound by specifying valid keys one after another in brackets, such\nas `<Ctrl-x><Ctrl-c>`.\n\n# Default keybinding configuration.\n\nA select few keybindings are different on MacOS compared to other\noperating systems. This is because different OSes have different\nconventions for text editing defaults.\n\n```json\n{\n    \"Up\":             \"CursorUp\",\n    \"Down\":           \"CursorDown\",\n    \"Right\":          \"CursorRight\",\n    \"Left\":           \"CursorLeft\",\n    \"ShiftUp\":        \"SelectUp\",\n    \"ShiftDown\":      \"SelectDown\",\n    \"ShiftLeft\":      \"SelectLeft\",\n    \"ShiftRight\":     \"SelectRight\",\n    \"AltLeft\":        \"WordLeft\", (Mac)\n    \"AltRight\":       \"WordRight\", (Mac)\n    \"AltUp\":          \"MoveLinesUp\",\n    \"AltDown\":        \"MoveLinesDown\",\n    \"CtrlShiftRight\": \"SelectWordRight\",\n    \"CtrlShiftLeft\":  \"SelectWordLeft\",\n    \"AltLeft\":        \"StartOfTextToggle\",\n    \"AltRight\":       \"EndOfLine\",\n    \"AltShiftRight\":  \"SelectWordRight\", (Mac)\n    \"AltShiftLeft\":   \"SelectWordLeft\", (Mac)\n    \"CtrlLeft\":       \"StartOfText\", (Mac)\n    \"CtrlRight\":      \"EndOfLine\", (Mac)\n    \"AltShiftLeft\":   \"SelectToStartOfTextToggle\",\n    \"CtrlShiftLeft\":  \"SelectToStartOfTextToggle\", (Mac)\n    \"ShiftHome\":      \"SelectToStartOfTextToggle\",\n    \"AltShiftRight\":  \"SelectToEndOfLine\",\n    \"CtrlShiftRight\": \"SelectToEndOfLine\", (Mac)\n    \"ShiftEnd\":       \"SelectToEndOfLine\",\n    \"CtrlUp\":         \"CursorStart\",\n    \"CtrlDown\":       \"CursorEnd\",\n    \"CtrlShiftUp\":    \"SelectToStart\",\n    \"CtrlShiftDown\":  \"SelectToEnd\",\n    \"Alt-{\":          \"ParagraphPrevious\",\n    \"Alt-}\":          \"ParagraphNext\",\n    \"Enter\":          \"InsertNewline\",\n    \"Ctrl-h\":         \"Backspace\",\n    \"Backspace\":      \"Backspace\",\n    \"Alt-CtrlH\":      \"DeleteWordLeft\",\n    \"Alt-Backspace\":  \"DeleteWordLeft\",\n    \"Tab\":            \"Autocomplete|IndentSelection|InsertTab\",\n    \"Backtab\":        \"OutdentSelection|OutdentLine\",\n    \"Ctrl-o\":         \"OpenFile\",\n    \"Ctrl-s\":         \"Save\",\n    \"Ctrl-f\":         \"Find\",\n    \"Alt-F\":          \"FindLiteral\",\n    \"Ctrl-n\":         \"FindNext\",\n    \"Ctrl-p\":         \"FindPrevious\",\n    \"Alt-[\":          \"DiffPrevious|CursorStart\",\n    \"Alt-]\":          \"DiffNext|CursorEnd\",\n    \"Ctrl-z\":         \"Undo\",\n    \"Ctrl-y\":         \"Redo\",\n    \"Ctrl-c\":         \"Copy|CopyLine\",\n    \"Ctrl-x\":         \"Cut|CutLine\",\n    \"Ctrl-k\":         \"CutLine\",\n    \"Ctrl-d\":         \"Duplicate|DuplicateLine\",\n    \"Ctrl-v\":         \"Paste\",\n    \"Ctrl-a\":         \"SelectAll\",\n    \"Ctrl-t\":         \"AddTab\",\n    \"Alt-,\":          \"PreviousTab|LastTab\",\n    \"Alt-.\":          \"NextTab|FirstTab\",\n    \"Home\":           \"StartOfText\",\n    \"End\":            \"EndOfLine\",\n    \"CtrlHome\":       \"CursorStart\",\n    \"CtrlEnd\":        \"CursorEnd\",\n    \"PageUp\":         \"CursorPageUp\",\n    \"PageDown\":       \"CursorPageDown\",\n    \"CtrlPageUp\":     \"PreviousTab|LastTab\",\n    \"CtrlPageDown\":   \"NextTab|FirstTab\",\n    \"ShiftPageUp\":    \"SelectPageUp\",\n    \"ShiftPageDown\":  \"SelectPageDown\",\n    \"Ctrl-g\":         \"ToggleHelp\",\n    \"Alt-g\":          \"ToggleKeyMenu\",\n    \"Ctrl-r\":         \"ToggleRuler\",\n    \"Ctrl-l\":         \"command-edit:goto \",\n    \"Delete\":         \"Delete\",\n    \"Ctrl-b\":         \"ShellMode\",\n    \"Ctrl-q\":         \"Quit\",\n    \"Ctrl-e\":         \"CommandMode\",\n    \"Ctrl-w\":         \"NextSplit|FirstSplit\",\n    \"Ctrl-u\":         \"ToggleMacro\",\n    \"Ctrl-j\":         \"PlayMacro\",\n    \"Insert\":         \"ToggleOverwriteMode\",\n\n    // Emacs-style keybindings\n    \"Alt-f\": \"WordRight\",\n    \"Alt-b\": \"WordLeft\",\n    \"Alt-a\": \"StartOfLine\",\n    \"Alt-e\": \"EndOfLine\",\n\n    // Integration with file managers\n    \"F2\":  \"Save\",\n    \"F3\":  \"Find\",\n    \"F4\":  \"Quit\",\n    \"F7\":  \"Find\",\n    \"F10\": \"Quit\",\n    \"Esc\": \"Escape\",\n\n    // Mouse bindings\n    \"MouseWheelUp\":     \"ScrollUp\",\n    \"MouseWheelDown\":   \"ScrollDown\",\n    \"MouseLeft\":        \"MousePress\",\n    \"MouseLeftDrag\":    \"MouseDrag\",\n    \"MouseLeftRelease\": \"MouseRelease\",\n    \"MouseMiddle\":      \"PastePrimary\",\n    \"Ctrl-MouseLeft\":   \"MouseMultiCursor\",\n\n    // Multi-cursor bindings\n    \"Alt-n\":        \"SpawnMultiCursor\",\n    \"AltShiftUp\":   \"SpawnMultiCursorUp\",\n    \"AltShiftDown\": \"SpawnMultiCursorDown\",\n    \"Alt-m\":        \"SpawnMultiCursorSelect\",\n    \"Alt-p\":        \"RemoveMultiCursor\",\n    \"Alt-c\":        \"RemoveAllMultiCursors\",\n    \"Alt-x\":        \"SkipMultiCursor\",\n}\n```\n\n## Pane type bindings\n\nKeybindings can be specified for different pane types as well. For example, to\nmake a binding that only affects the command bar, use the `command` subgroup:\n\n```\n{\n    \"command\": {\n        \"Ctrl-w\": \"WordLeft\"\n    }\n}\n```\n\nThe possible pane types are `buffer` (normal buffer), `command` (command bar),\nand `terminal` (terminal pane). The defaults for the command and terminal panes\nare given below:\n\n```\n{\n    \"terminal\": {\n        \"<Ctrl-q><Ctrl-q>\": \"Exit\",\n        \"<Ctrl-e><Ctrl-e>\": \"CommandMode\",\n        \"<Ctrl-w><Ctrl-w>\": \"NextSplit\"\n    },\n\n    \"command\": {\n        \"Up\":             \"HistoryUp\",\n        \"Down\":           \"HistoryDown\",\n        \"Right\":          \"CursorRight\",\n        \"Left\":           \"CursorLeft\",\n        \"ShiftUp\":        \"SelectUp\",\n        \"ShiftDown\":      \"SelectDown\",\n        \"ShiftLeft\":      \"SelectLeft\",\n        \"ShiftRight\":     \"SelectRight\",\n        \"AltLeft\":        \"StartOfTextToggle\",\n        \"AltRight\":       \"EndOfLine\",\n        \"AltUp\":          \"CursorStart\",\n        \"AltDown\":        \"CursorEnd\",\n        \"AltShiftRight\":  \"SelectWordRight\",\n        \"AltShiftLeft\":   \"SelectWordLeft\",\n        \"CtrlLeft\":       \"WordLeft\",\n        \"CtrlRight\":      \"WordRight\",\n        \"CtrlShiftLeft\":  \"SelectToStartOfTextToggle\",\n        \"ShiftHome\":      \"SelectToStartOfTextToggle\",\n        \"CtrlShiftRight\": \"SelectToEndOfLine\",\n        \"ShiftEnd\":       \"SelectToEndOfLine\",\n        \"CtrlUp\":         \"CursorStart\",\n        \"CtrlDown\":       \"CursorEnd\",\n        \"CtrlShiftUp\":    \"SelectToStart\",\n        \"CtrlShiftDown\":  \"SelectToEnd\",\n        \"Enter\":          \"ExecuteCommand\",\n        \"CtrlH\":          \"Backspace\",\n        \"Backspace\":      \"Backspace\",\n        \"OldBackspace\":   \"Backspace\",\n        \"Alt-CtrlH\":      \"DeleteWordLeft\",\n        \"Alt-Backspace\":  \"DeleteWordLeft\",\n        \"Tab\":            \"CommandComplete\",\n        \"Backtab\":        \"CycleAutocompleteBack\",\n        \"Ctrl-z\":         \"Undo\",\n        \"Ctrl-y\":         \"Redo\",\n        \"Ctrl-c\":         \"Copy\",\n        \"Ctrl-x\":         \"Cut\",\n        \"Ctrl-k\":         \"CutLine\",\n        \"Ctrl-v\":         \"Paste\",\n        \"Home\":           \"StartOfTextToggle\",\n        \"End\":            \"EndOfLine\",\n        \"CtrlHome\":       \"CursorStart\",\n        \"CtrlEnd\":        \"CursorEnd\",\n        \"Delete\":         \"Delete\",\n        \"Ctrl-q\":         \"AbortCommand\",\n        \"Ctrl-e\":         \"EndOfLine\",\n        \"Ctrl-a\":         \"StartOfLine\",\n        \"Ctrl-w\":         \"DeleteWordLeft\",\n        \"Insert\":         \"ToggleOverwriteMode\",\n        \"Ctrl-b\":         \"WordLeft\",\n        \"Ctrl-f\":         \"WordRight\",\n        \"Ctrl-d\":         \"DeleteWordLeft\",\n        \"Ctrl-m\":         \"ExecuteCommand\",\n        \"Ctrl-n\":         \"HistoryDown\",\n        \"Ctrl-p\":         \"HistoryUp\",\n        \"Ctrl-u\":         \"SelectToStart\",\n\n        // Emacs-style keybindings\n        \"Alt-f\": \"WordRight\",\n        \"Alt-b\": \"WordLeft\",\n        \"Alt-a\": \"StartOfText\",\n        \"Alt-e\": \"EndOfLine\",\n\n        // Integration with file managers\n        \"F10\": \"AbortCommand\",\n        \"Esc\": \"AbortCommand\",\n\n        // Mouse bindings\n        \"MouseWheelUp\":     \"HistoryUp\",\n        \"MouseWheelDown\":   \"HistoryDown\",\n        \"MouseLeft\":        \"MousePress\",\n        \"MouseLeftDrag\":    \"MouseDrag\",\n        \"MouseLeftRelease\": \"MouseRelease\",\n        \"MouseMiddle\":      \"PastePrimary\"\n    }\n}\n```\n\n## Final notes\n\nNote: On some old terminal emulators and on Windows machines, `Ctrl-h` should be\nused for backspace.\n\nAdditionally, alt keys can be bound by using `Alt-key`. For example `Alt-a` or\n`Alt-Up`. Micro supports an optional `-` between modifiers like `Alt` and\n`Ctrl` so `Alt-a` could be rewritten as `Alta` (case matters for alt bindings).\nThis is why in the default keybindings you can see `AltShiftLeft` instead of\n`Alt-ShiftLeft` (they are equivalent).\n\nPlease note that terminal emulators are strange applications and micro only\nreceives key events that the terminal decides to send. Some terminal emulators\nmay not send certain events even if this document says micro can receive the\nevent. To see exactly what micro receives from the terminal when you press a\nkey, run the `> raw` command.\n"
  },
  {
    "path": "runtime/help/options.md",
    "content": "# Options\n\nMicro stores all of the user configuration in its configuration directory.\n\nMicro uses `$MICRO_CONFIG_HOME` as the configuration directory. If this\nenvironment variable is not set, it uses `$XDG_CONFIG_HOME/micro` instead. If\nthat environment variable is not set, it uses `~/.config/micro` as the\nconfiguration directory. In the documentation, we use `~/.config/micro` to\nrefer to the configuration directory (even if it may in fact be somewhere else\nif you have set either of the above environment variables).\n\nHere are the available options:\n\n* `autoindent`: when creating a new line, use the same indentation as the\n   previous line.\n\n    default value: `true`\n\n* `autosave`: automatically save the buffer every n seconds, where n is the\n   value of the autosave option. Also when quitting on a modified buffer, micro\n   will automatically save and quit. Be warned, this option saves the buffer\n   without prompting the user, so data may be overwritten. If this option is\n   set to `0`, no autosaving is performed.\n\n    default value: `0`\n\n* `autosu`: When a file is saved that the user doesn't have permission to\n   modify, micro will ask if the user would like to use super user\n   privileges to save the file. If this option is enabled, micro will\n   automatically attempt to use super user privileges to save without\n   asking the user.\n\n    default value: `false`\n\n* `backup`: micro will automatically keep backups of all open buffers. Backups\n   are stored in `~/.config/micro/backups` and are removed when the buffer is\n   closed cleanly. In the case of a system crash or a micro crash, the contents\n   of the buffer can be recovered automatically by opening the file that was\n   being edited before the crash, or manually by searching for the backup in\n   the backup directory. Backups are made in the background for newly modified\n   buffers every 8 seconds, or when micro detects a crash.\n\n    default value: `true`\n\n* `backupdir`: the directory micro should place backups in. For the default\n   value of `\"\"` (empty string), the backup directory will be\n   `ConfigDir/backups`, which is `~/.config/micro/backups` by default. The\n   directory specified for backups will be created if it does not exist.\n\n    default value: `\"\"` (empty string)\n\n* `basename`: in the infobar and tabbar, show only the basename of the file\n   being edited rather than the full path.\n\n    default value: `false`\n\n* `clipboard`: specifies how micro should access the system clipboard.\n   Possible values are:\n    * `external`: accesses clipboard via an external tool, such as xclip/xsel\n       or wl-clipboard on Linux, pbcopy/pbpaste on MacOS, and system calls on\n       Windows. On Linux, if you do not have one of the tools installed, or if\n       they are not working, micro will throw an error and use an internal\n       clipboard.\n    * `terminal`: accesses the clipboard via your terminal emulator. Note that\n       there is limited support among terminal emulators for this feature\n       (called OSC 52). Terminals that are known to work are Kitty (enable\n       reading with `clipboard_control` setting), iTerm2 (only copying),\n       st, rxvt-unicode and xterm if enabled (see `> help copypaste` for\n       details). Note that Gnome-terminal does not support this feature. With\n       this setting, copy-paste **will** work over ssh. See `> help copypaste`\n       for details.\n    * `internal`: micro will use an internal clipboard.\n\n    default value: `external`\n\n* `colorcolumn`: if this is not set to 0, it will display a column at the\n   specified column. This is useful if you want column 80 to be highlighted\n   special for example.\n\n    default value: `0`\n\n* `colorscheme`: use the given colorscheme. This setting is `global only`.\n   The colorscheme can be either one of the colorschemes that micro comes with\n   by default (such as `default`, `solarized` or `solarized-tc`) which are\n   embedded in the micro binary, or a custom colorscheme stored in\n   `~/.config/micro/colorschemes/$(option).micro` where `$(option)` is the\n   option value. You can read more about micro's colorschemes and see the list\n   of default colorschemes in `> help colors`.\n\n    default value: `default`\n\n* `cursorline`: highlight the line that the cursor is on in a different color\n   (the color is defined by the colorscheme you are using).\n\n    default value: `true`\n\n* `detectlimit`: if this is not set to 0, it will limit the amount of first\n   lines in a file that are matched to determine the filetype.\n   A higher limit means better accuracy of guessing the filetype, but also\n   taking more time.\n\n   default value: `100`\n\n* `diffgutter`: display diff indicators before lines.\n\n    default value: `false`\n\n* `divchars`: specifies the \"divider\" characters used for the dividing line\n   between vertical/horizontal splits. The first character is for vertical\n   dividers, and the second is for horizontal dividers. By default, for\n   horizontal splits the statusline serves as a divider, but if the statusline\n   is disabled the horizontal divider character will be used.\n\n    default value: `|-`\n\n* `divreverse`: colorschemes provide the color (foreground and background) for\n   the characters displayed in split dividers. With this option enabled, the\n   colors specified by the colorscheme will be reversed (foreground and\n   background colors swapped).\n\n    default value: `true`\n\n* `encoding`: the encoding to open and save files with. Supported encodings\n   are listed at https://www.w3.org/TR/encoding/.\n\n    default value: `utf-8`\n\n* `eofnewline`: micro will automatically add a newline to the end of the\n   file if one does not exist.\n\n    default value: `true`\n\n* `fakecursor`: forces micro to render the cursor using terminal colors rather\n   than the actual terminal cursor. This is useful when the terminal's cursor is\n   slow or otherwise unavailable/undesirable to use.\n   Note: This option defaults to `true` in case `micro` is used in the legacy\n   Windows Console.\n\n    default value: `false`\n\n* `fastdirty`: this determines what kind of algorithm micro uses to determine\n   if a buffer is modified or not. When `fastdirty` is on, micro just uses a\n   boolean `modified` that is set to `true` as soon as the user makes an edit.\n   This is fast, but can be inaccurate. If `fastdirty` is off, then micro will\n   hash the current buffer against a hash of the original file (created when\n   the buffer was loaded). This is more accurate but obviously more resource\n   intensive. This option will be automatically enabled for the current buffer\n   if the file size exceeds 50KB.\n\n    default value: `false`\n\n* `fileformat`: this determines what kind of line endings micro will use for\n   the file. Unix line endings are just `\\n` (linefeed) whereas dos line\n   endings are `\\r\\n` (carriage return + linefeed). The two possible values for\n   this option are `unix` and `dos`. The fileformat will be automatically\n   detected (when you open an existing file) and displayed on the statusline,\n   but this option is useful if you would like to change the line endings or if\n   you are starting a new file. Changing this option while editing a file will\n   change its line endings. Opening a file with this option set will only have\n   an effect if the file is empty/newly created, because otherwise the fileformat\n   will be automatically detected from the existing line endings.\n\n    default value: `unix` on Unix systems, `dos` on Windows\n\n* `filetype`: sets the filetype for the current buffer. Set this option to\n   `off` to completely disable filetype detection.\n\n    default value: `unknown`. This will be automatically overridden depending\n    on the file you open.\n\n* `helpsplit`: sets the split type to be used by the `help` command.\n   Possible values:\n    * `vsplit`: open help in a vertical split pane\n    * `hsplit`: open help in a horizontal split pane\n\n    default value: `hsplit`\n\n* `hlsearch`: highlight all instances of the searched text after a successful\n   search. This highlighting can be temporarily turned off via the\n   `UnhighlightSearch` action (triggered by the Esc key by default) or toggled\n   on/off via the `ToggleHighlightSearch` action. Note that these actions don't\n   change the `hlsearch` setting. As long as `hlsearch` is set to true, the next\n   search will have the highlighting turned on again.\n\n    default value: `false`\n\n* `hltaberrors`: highlight tabs when spaces are expected, and spaces when tabs\n   are expected. More precisely: if `tabstospaces` option is on, highlight\n   all tab characters; if `tabstospaces` is off, highlight space characters\n   in the initial indent part of the line.\n\n    default value: `false`\n\n* `hltrailingws`: highlight trailing whitespaces at ends of lines. Note that\n   it doesn't highlight newly added trailing whitespaces that naturally occur\n   while typing text. It highlights only nasty forgotten trailing whitespaces.\n\n    default value: `false`\n\n* `ignorecase`: perform case-insensitive searches.\n\n    default value: `true`\n\n* `incsearch`: enable incremental search in \"Find\" prompt (matching as you type).\n\n    default value: `true`\n\n* `indentchar`: sets the character to be shown to display tab characters.\n   This option is **deprecated**, use the `tab` key in `showchars` option instead.\n\n    default value: ` ` (space)\n\n* `infobar`: enables the line at the bottom of the editor where messages are\n   printed. This option is `global only`.\n\n    default value: `true`\n\n* `keepautoindent`: when using autoindent, whitespace is added for you. This\n   option determines if when you move to the next line without any insertions\n   the whitespace that was added should be deleted to remove trailing\n   whitespace. By default, the autoindent whitespace is deleted if the line\n   was left empty.\n\n    default value: `false`\n\n* `keymenu`: display the nano-style key menu at the bottom of the screen. Note\n   that ToggleKeyMenu is bound to `Alt-g` by default and this is displayed in\n   the statusline. To disable the key binding, bind `Alt-g` to `None`.\n\n    default value: `false`\n\n* `lockbindings`: prevent plugins and lua scripts from binding any keys.\n   Any custom actions must be binded manually either via commands like `bind`\n   or by modifying the `bindings.json` file.\n\n    default value: `false`\n\n* `matchbrace`: show matching braces for '()', '{}', '[]' when the cursor\n   is on a brace character or (if `matchbraceleft` is enabled) next to it.\n\n    default value: `true`\n\n* `matchbraceleft`: simulate I-beam cursor behavior (cursor located not on a\n   character but \"between\" characters): when showing matching braces, if there\n   is no brace character directly under the cursor, match the brace character\n   to the left of the cursor instead. Also when jumping to the matching brace,\n   move the cursor either to the matching brace character or to the character\n   next to it, depending on whether the initial cursor position was on the\n   brace character or next to it (i.e. \"inside\" or \"outside\" the braces).\n   With `matchbraceleft` disabled, micro will only match the brace directly\n   under the cursor and will only jump to precisely to the matching brace.\n\n    default value: `true`\n\n* `matchbracestyle`: whether to underline or highlight matching braces when\n   `matchbrace` is enabled. The color of highlight is determined by the `match-brace`\n   field in the current theme. Possible values:\n    * `underline`: underline matching braces.\n    * `highlight`: use `match-brace` style from the current theme.\n\n    default value: `underline`\n\n* `mkparents`: if a file is opened on a path that does not exist, the file\n   cannot be saved because the parent directories don't exist. This option lets\n   micro automatically create the parent directories in such a situation.\n\n    default value: `false`\n\n* `mouse`: mouse support. When mouse support is disabled,\n   usually the terminal will be able to access mouse events which can be useful\n   if you want to copy from the terminal instead of from micro (if over ssh for\n   example, because the terminal has access to the local clipboard and micro\n   does not).\n\n    default value: `true`\n\n* `multiopen`: specifies how to layout multiple files opened at startup.\n   Most useful as a command-line option, like `-multiopen vsplit`. Possible\n   values correspond to commands (see `> help commands`) that open files:\n    * `tab`: open each file in a separate tab.\n    * `vsplit`: open files side-by-side.\n    * `hsplit`: open files stacked top to bottom.\n\n    default value: `tab`\n\n* `pageoverlap`: the number of lines from the current view to keep in view\n   when paging up or down. If this is set to 2, for instance, and you page\n   down, the last two lines of the previous page will be the first two lines\n   of the next page.\n\n    default value: `2`\n\n* `parsecursor`: if enabled, this will cause micro to parse filenames such as\n   `file.txt:10:5` as requesting to open `file.txt` with the cursor at line 10\n   and column 5. The column number can also be dropped to open the file at a\n   given line and column 0. Note that with this option enabled it is not possible\n   to open a file such as `file.txt:10:5`, where `:10:5` is part of the filename.\n   It is also possible to open a file with a certain cursor location by using the\n   `+LINE:COL` flag syntax. See `micro -help` for the command line options.\n\n    default value: `false`\n\n* `paste`: treat characters sent from the terminal in a single chunk as a paste\n   event rather than a series of manual key presses. If you are pasting using\n   the terminal keybinding (not `Ctrl-v`, which is micro's default paste\n   keybinding) then it is a good idea to enable this option during the paste\n   and disable once the paste is over. See `> help copypaste` for details about\n   copying and pasting in a terminal environment.\n\n    default value: `false`\n\n* `permbackup`: this option causes backups (see `backup` option) to be\n   permanently saved. With permanent backups, micro will not remove backups when\n   files are closed and will never apply them to existing files. Use this option\n   if you are interested in manually managing your backup files.\n\n    default value: `false`\n\n* `pluginchannels`: list of URLs pointing to plugin channels for downloading and\n   installing plugins. A plugin channel consists of a json file with links to\n   plugin repos, which store information about plugin versions and download URLs.\n   By default, this option points to the official plugin channel hosted on GitHub\n   at https://github.com/micro-editor/plugin-channel.\n\n    default value: `https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json`\n\n* `pluginrepos`: a list of links to plugin repositories.\n\n    default value: ``\n\n* `readonly`: when enabled, disallows edits to the buffer. It is recommended\n   to only ever set this option locally using `setlocal`.\n\n    default value: `false`\n\n* `relativeruler`: make line numbers display relatively. If set to true, all\n   lines except for the line that the cursor is located will display the distance\n   from the cursor's line.\n\n    default value: `false`\n\n* `reload`: controls the reload behavior of the current buffer in case the file\n   has changed. The available options are `prompt`, `auto` & `disabled`.\n\n   default value: `prompt`\n\n* `rmtrailingws`: micro will automatically trim trailing whitespaces at ends of\n   lines.\n   Note: This setting overrides `keepautoindent` and isn't used at timed `autosave`\n   or forced `autosave` in case the buffer didn't change. A manual save will\n   involve the action regardless if the buffer has been changed or not.\n\n    default value: `false`\n\n* `ruler`: display line numbers.\n\n    default value: `true`\n\n* `savecursor`: remember where the cursor was last time the file was opened and\n   put it there when you open the file again. Information is saved to\n   `~/.config/micro/buffers/`\n\n    default value: `false`\n\n* `savehistory`: remember command history between closing and re-opening\n   micro. Information is saved to `~/.config/micro/buffers/history`.\n\n    default value: `true`\n\n* `saveundo`: when this option is on, undo is saved even after you close a file\n   so if you close and reopen a file, you can keep undoing. Information is\n   saved to `~/.config/micro/buffers/`.\n\n    default value: `false`\n\n* `scrollbar`: display a scroll bar\n\n    default value: `false`\n\n* `scrollbarchar`: specifies the character used for displaying the scrollbar\n\n    default value: `|`\n\n* `scrollmargin`: margin at which the view starts scrolling when the cursor\n   approaches the edge of the view.\n\n    default value: `3`\n\n* `scrollspeed`: amount of lines to scroll for one scroll event.\n\n    default value: `2`\n\n* `showchars`: sets what characters to be shown to display various invisible\n   characters in the file. The characters shown will not be inserted into files.\n   This option is specified in the form of `key1=value1,key2=value2,...`.\n\n   Here are the list of keys:\n   - `space`: space characters\n   - `tab`: tab characters. If set, overrides the `indentchar` option.\n   - `ispace`: space characters at indent position before the first visible\n               character in a line. If this is not set, `space` will be shown\n               instead.\n   - `itab`: tab characters before the first visible character in a line.\n             If this is not set, `tab` will be shown instead.\n\n   Only `tab` and `itab` can display multiple characters (if possible),\n   otherwise only the first character will be displayed.\n\n   An example of this option value could be `tab=>,space=.,itab=|>,ispace=|`\n\n   The color of the shown character is determined by the `indent-char`\n   field in the current theme rather than the default text color.\n\n    default value: ``\n\n* `smartpaste`: add leading whitespace when pasting multiple lines.\n   This will attempt to preserve the current indentation level when pasting an\n   unindented block.\n\n    default value: `true`\n\n* `softwrap`: wrap lines that are too long to fit on the screen.\n\n    default value: `false`\n\n* `splitbottom`: when a horizontal split is created, create it below the\n   current split.\n\n    default value: `true`\n\n* `splitright`: when a vertical split is created, create it to the right of the\n   current split.\n\n    default value: `true`\n\n* `statusformatl`: format string definition for the left-justified part of the\n   statusline. Special directives should be placed inside `$()`. Special\n   directives include: `filename`, `modified`, `line`, `col`, `lines`,\n   `percentage`, `opt`, `overwrite`, `bind`.\n   The `opt` and `bind` directives take either an option or an action afterward\n   and fill in the value of the option or the key bound to the action.\n\n    default value: `$(filename) $(modified)$(overwrite)($(line),$(col)) $(status.paste)|\n                    ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)`\n\n* `statusformatr`: format string definition for the right-justified part of the\n   statusline.\n\n    default value: `$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help`\n\n* `statusline`: display the status line at the bottom of the screen.\n\n    default value: `true`\n\n* `sucmd`: specifies the super user command. On most systems this is \"sudo\" but\n   on BSD it can be \"doas.\" This option can be customized and is only used when\n   saving with su.\n\n    default value: `sudo`\n\n* `syntax`: enables syntax highlighting.\n\n    default value: `true`\n\n* `tabhighlight`: inverts the tab characters' (filename, save indicator, etc)\n   colors with respect to the tab bar.\n\n    default value: `false`\n\n* `tabmovement`: navigate spaces at the beginning of lines as if they are tabs\n   (e.g. move over 4 spaces at once). This option only does anything if\n   `tabstospaces` is on.\n\n    default value: `false`\n\n* `tabreverse`: reverses the tab bar colors when active.\n\n    default value: `true`\n\n* `tabsize`: the size in spaces that a tab character should be displayed with.\n\n    default value: `4`\n\n* `tabstospaces`: use spaces instead of tabs. Note: This option will be\n   overridden by [the `ftoptions` plugin](https://github.com/micro-editor/micro/blob/master/runtime/plugins/ftoptions/ftoptions.lua)\n   for certain filetypes. To disable this behavior, add `\"ftoptions\": false` to\n   your config. See [issue #2213](https://github.com/micro-editor/micro/issues/2213)\n   for more details.\n\n    default value: `false`\n\n* `truecolor`: controls whether micro will use true colors (24-bit colors) when\n   using a colorscheme with true colors, such as `solarized-tc` or `atom-dark`.\n   * `auto`: enable usage of true color if micro detects that it is supported by\n      the terminal, otherwise disable it.\n   * `on`: force usage of true color even if micro does not detect its support\n      by the terminal (of course this is not guaranteed to work well unless the\n      terminal actually supports true color).\n   * `off`: disable true color usage.\n\n   Note: The change will take effect after the next start of `micro`.\n\n   default value: `auto`\n\n* `useprimary` (only useful on unix): defines whether or not micro will use the\n   primary clipboard to copy selections in the background. This does not affect\n   the normal clipboard using `Ctrl-c` and `Ctrl-v`.\n\n    default value: `true`\n\n* `wordwrap`: wrap long lines by words, i.e. break at spaces. This option\n   only does anything if `softwrap` is on.\n\n    default value: `false`\n\n* `xterm`: micro will assume that the terminal it is running in conforms to\n  `xterm-256color` regardless of what the `$TERM` variable actually contains.\n   Enabling this option may cause unwanted effects if your terminal in fact\n   does not conform to the `xterm-256color` standard.\n\n    default value: `false`\n\n---\n\nPlugin options: all plugins come with a special option to enable or disable\nthem. The option is a boolean with the same name as the plugin itself.\n\nBy default, the following plugins are provided, each with an option to enable\nor disable them:\n\n* `autoclose`: automatically closes brackets, quotes, etc...\n* `comment`: provides automatic commenting for a number of languages\n* `ftoptions`: alters some default options depending on the filetype\n* `linter`: provides extensible linting for many languages\n* `literate`: provides advanced syntax highlighting for the Literate\n   programming tool.\n* `status`: provides some extensions to the status line (integration with\n   Git and more).\n* `diff`: integrates the `diffgutter` option with Git. If you are in a Git\n   directory, the diff gutter will show changes with respect to the most\n   recent Git commit rather than the diff since opening the file.\n\nAny option you set in the editor will be saved to the file\n`~/.config/micro/settings.json` so, in effect, your configuration file will be\ncreated for you. If you'd like to take your configuration with you to another\nmachine, simply copy the `settings.json` to the other machine.\n\n## Settings.json file\n\nThe `settings.json` file should go in your configuration directory (by default\nat `~/.config/micro`), and should contain only options which have been modified\nfrom their default setting. Here is the full list of options in json format,\nso that you can see what the formatting should look like.\n\n```json\n{\n    \"autoclose\": true,\n    \"autoindent\": true,\n    \"autosave\": 0,\n    \"autosu\": false,\n    \"backup\": true,\n    \"backupdir\": \"\",\n    \"basename\": false,\n    \"clipboard\": \"external\",\n    \"colorcolumn\": 0,\n    \"colorscheme\": \"default\",\n    \"comment\": true,\n    \"cursorline\": true,\n    \"detectlimit\": 100,\n    \"diff\": true,\n    \"diffgutter\": false,\n    \"divchars\": \"|-\",\n    \"divreverse\": true,\n    \"encoding\": \"utf-8\",\n    \"eofnewline\": true,\n    \"fakecursor\": false,\n    \"fastdirty\": false,\n    \"fileformat\": \"unix\",\n    \"filetype\": \"unknown\",\n    \"ftoptions\": true,\n    \"helpsplit\": \"hsplit\",\n    \"hlsearch\": false,\n    \"hltaberrors\": false,\n    \"hltrailingws\": false,\n    \"ignorecase\": true,\n    \"incsearch\": true,\n    \"indentchar\": \" \",\n    \"infobar\": true,\n    \"initlua\": true,\n    \"keepautoindent\": false,\n    \"keymenu\": false,\n    \"linter\": true,\n    \"literate\": true,\n    \"matchbrace\": true,\n    \"matchbraceleft\": true,\n    \"matchbracestyle\": \"underline\",\n    \"mkparents\": false,\n    \"mouse\": true,\n    \"multiopen\": \"tab\",\n    \"pageoverlap\": 2,\n    \"parsecursor\": false,\n    \"paste\": false,\n    \"permbackup\": false,\n    \"pluginchannels\": [\n        \"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json\"\n    ],\n    \"pluginrepos\": [],\n    \"readonly\": false,\n    \"relativeruler\": false,\n    \"reload\": \"prompt\",\n    \"rmtrailingws\": false,\n    \"ruler\": true,\n    \"savecursor\": false,\n    \"savehistory\": true,\n    \"saveundo\": false,\n    \"scrollbar\": false,\n    \"scrollbarchar\": \"|\",\n    \"scrollmargin\": 3,\n    \"scrollspeed\": 2,\n    \"showchars\": \"\",\n    \"smartpaste\": true,\n    \"softwrap\": false,\n    \"splitbottom\": true,\n    \"splitright\": true,\n    \"status\": true,\n    \"statusformatl\": \"$(filename) $(modified)$(overwrite)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)\",\n    \"statusformatr\": \"$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help\",\n    \"statusline\": true,\n    \"sucmd\": \"sudo\",\n    \"syntax\": true,\n    \"tabhighlight\": true,\n    \"tabmovement\": false,\n    \"tabreverse\": false,\n    \"tabsize\": 4,\n    \"tabstospaces\": false,\n    \"useprimary\": true,\n    \"wordwrap\": false,\n    \"xterm\": false\n}\n```\n\n## Global and local settings\n\nYou can set these settings either globally or locally. Locally means that the\nsetting won't be saved to `~/.config/micro/settings.json` and that it will only\nbe set in the current buffer. Setting an option globally is the default, and\nwill set the option in all buffers. Use the `setlocal` command to set an option\nlocally rather than globally.\n\nThe `colorscheme` option is global only, and the `filetype` option is local\nonly. To set an option locally, use `setlocal` instead of `set`.\n\nIn the `settings.json` file you can also put set options locally by specifying\neither a glob or a filetype. Here is an example which has `tabstospaces` on for\nall files except Go files, and `tabsize` 4 for all files except Ruby files:\n\n```json\n{\n    \"ft:go\": {\n        \"tabstospaces\": false\n    },\n    \"ft:ruby\": {\n        \"tabsize\": 2\n    },\n    \"tabstospaces\": true,\n    \"tabsize\": 4\n}\n```\n\nOr similarly you can match with globs:\n\n```json\n{\n    \"*.go\": {\n        \"tabstospaces\": false\n    },\n    \"*.rb\": {\n        \"tabsize\": 2\n    },\n    \"tabstospaces\": true,\n    \"tabsize\": 4\n}\n```\n"
  },
  {
    "path": "runtime/help/plugins.md",
    "content": "# Plugins\n\nThis help topic is about creating plugins. If you need help installing or\nmanaging plugins, look for `plugin` commands in `help commands`. If you want to\nenable or disable a plugin, look for `Plugin options` in `help options`.\n\nMicro supports creating plugins with a simple Lua system. Plugins are\nfolders containing Lua files and possibly other source files placed\nin `~/.config/micro/plug`. The plugin directory (within `plug`) should\ncontain at least one Lua file and a `repo.json` file. The `repo.json` file\nprovides additional information such as the name of the plugin, the\nplugin's website, dependencies, etc.\n[Here is an example `repo.json` file](https://github.com/micro-editor/updated-plugins/blob/master/go-plugin/repo.json)\nfrom the go plugin, which has the following file structure:\n\n```\n~/.config/micro/plug/go-plugin/\n    go.lua\n    repo.json\n    help/\n        go-plugin.md\n```\n\nThe `go.lua` file contains the main code for the plugin, though the\ncode may be distributed across multiple Lua files. The `repo.json`\nfile contains information about the plugin, such as the website,\ndescription, version, and any requirements. Plugins may also\nhave additional files that can be added to micro's runtime files,\nof which there are 5 types:\n\n* Colorschemes\n* Syntax files\n* Help files\n* Plugin files\n* Syntax header files\n\nIn most cases, a plugin will want to add help files, but in certain\ncases a plugin may also want to add colorschemes or syntax files.\nNo directory structure is enforced, but keeping runtime files in their\nown directories is good practice.\n\n## Lua callbacks\n\nPlugins use Lua but also have access to many functions, both from micro\nand from the Go standard library. Plugins can also define functions that micro\nwill call when certain events happen. Here is the list of callbacks\nthat micro defines:\n\n* `init()`: this function should be used for your plugin initialization.\n   This function is called after buffers have been initialized.\n\n* `preinit()`: initialization function called before buffers have been\n   initialized.\n\n* `postinit()`: initialization function called after the `init()` function of\n   all plugins has been called.\n\n* `deinit()`: cleanup function called when your plugin is unloaded or reloaded.\n\n* `onBufferOpen(buf)`: runs when a buffer is opened. The input contains\n   the buffer object.\n\n* `onBufferOptionChanged(buf, option, old, new)`: runs when an option of the\n   buffer has changed. The input contains the buffer object, the option name,\n   the old and the new value.\n\n* `onBufPaneOpen(bufpane)`: runs when a bufpane is opened. The input\n   contains the bufpane object.\n\n* `onSetActive(bufpane)`: runs when changing the currently active bufpane.\n\n* `onAction(bufpane)`: runs when `Action` is triggered by the user, where\n   `Action` is a bindable action (see `> help keybindings`). A bufpane\n   is passed as input. The function should return a boolean defining\n   whether the action was successful, which is used when the action is\n   chained with other actions (see `> help keybindings`) to determine whether\n   the next actions in the chain should be executed or not.\n\n   If the action is a mouse action, e.g. `MousePress`, the mouse event info\n   is passed to the callback as an extra argument of type `*tcell.EventMouse`.\n   See https://pkg.go.dev/github.com/micro-editor/tcell/v2#EventMouse for the\n   description of this type and its methods.\n\n* `preAction(bufpane)`: runs immediately before `Action` is triggered\n   by the user. Returns a boolean which defines whether the action should\n   be canceled.\n\n   Similarly to `onAction`, if the action is a mouse action, the mouse event\n   info is passed to the callback as an extra argument of type\n   `*tcell.EventMouse`.\n\n* `onRune(bufpane, rune)`: runs when the composed rune has been inserted\n\n* `preRune(bufpane, rune)`: runs before the composed rune will be inserted\n\n* `onAnyEvent()`: runs when literally anything happens. It is useful for\n   detecting various changes of micro's state that cannot be detected\n   using other callbacks.\n\nFor example, a function that is run every time the user saves the buffer\nwould be:\n\n```lua\nfunction onSave(bp)\n    ...\n    return false\nend\n```\n\nThe `bp` variable is a reference to the bufpane the action is being executed\nwithin. This is almost always the current bufpane.\n\nAll available actions are listed in the keybindings section of the help.\n\n## Accessing micro functions\n\nSome of micro's internal information is exposed in the form of packages, which\ncan be imported by Lua plugins. A package can be imported in Lua, and a value\nwithin it can be accessed using the following syntax:\n\n```lua\nlocal micro = import(\"micro\")\nmicro.Log(\"Hello\")\n```\n\nThe packages and their contents are listed below (in Go type signatures):\n\n* `micro`\n    - `TermMessage(msg any...)`: temporarily close micro and print a\n       message\n\n    - `TermError(filename string, lineNum int, err string)`: temporarily close\n       micro and print an error formatted as `filename, lineNum: err`.\n\n    - `InfoBar() *InfoPane`: return the infobar BufPane object.\n\n    - `Log(msg any...)`: write a message to `log.txt` (requires\n       `-debug` flag, or binary built with `build-dbg`).\n\n    - `SetStatusInfoFn(fn string)`: register the given lua function as\n       accessible from the statusline formatting options.\n\n    - `CurPane() *BufPane`: returns the current BufPane, or nil if the\n       current pane is not a BufPane.\n\n    - `CurTab() *Tab`: returns the current tab.\n\n    - `Tabs() *TabList`: returns the global tab list.\n\n    - `After(t time.Duration, f func())`: run function `f` in the background\n       after time `t` elapses. See https://pkg.go.dev/time#Duration for the\n       usage of `time.Duration`.\n\n    Relevant links:\n    [Time](https://pkg.go.dev/time#Duration)\n    [BufPane](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/action#BufPane)\n    [InfoPane](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/action#InfoPane)\n    [Tab](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/action#Tab)\n    [TabList](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/action#TabList)\n\n* `micro/config`\n    - `MakeCommand(name string, action func(bp *BufPane, args[]string),\n                   completer buffer.Completer)`:\n       create a command with the given name, and lua callback function when\n       the command is run. A completer may also be given to specify how\n       autocompletion should work with the custom command. Any lua function\n       that takes a Buffer argument and returns a pair of string arrays is a\n       valid completer, as are the built in completers below:\n\n    - `FileComplete`: autocomplete using files in the current directory\n    - `HelpComplete`: autocomplete using names of help documents\n    - `OptionComplete`: autocomplete using names of options\n    - `OptionValueComplete`: autocomplete using names of options, and valid\n       values afterwards\n    - `NoComplete`: no autocompletion suggestions\n\n    - `TryBindKey(k, v string, overwrite bool) (bool, error)`:\n       bind the key `k` to the string `v`. If `overwrite` is true, this will\n       overwrite any existing binding to key `k`.\n       Returns true if the binding was made, and a possible error.\n       This operation can be rejected by `lockbindings` to prevent undesired\n       actions by the user.\n\n    - `Reload()`: reload configuration files.\n\n    - `AddRuntimeFileFromMemory(filetype RTFiletype, filename, data string)`:\n       add a runtime file to the `filetype` runtime filetype, with name\n       `filename` and data `data`.\n\n    - `AddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype,\n                                    directory, pattern string)`:\n       add runtime files for the given plugin with the given RTFiletype from\n       a directory within the plugin root. Only adds files that match the\n       pattern using Go's `filepath.Match`\n\n    - `AddRuntimeFile(plugin string, filetype RTFiletype, filepath string)`:\n       add a given file inside the plugin root directory as a runtime file\n       to the given RTFiletype category.\n\n    - `ListRuntimeFiles(fileType RTFiletype) []string`: returns a list of\n       names of runtime files of the given type.\n\n    - `ReadRuntimeFile(fileType RTFiletype, name string) string`: returns the\n       contents of a given runtime file.\n\n    - `NewRTFiletype() int`: creates a new RTFiletype, and returns its value.\n\n    - `RTColorscheme`: runtime files for colorschemes.\n    - `RTSyntax`: runtime files for syntax files.\n    - `RTHelp`: runtime files for help documents.\n    - `RTPlugin`: runtime files for plugin source code.\n\n    - `RegisterCommonOption(pl string, name string, defaultvalue any)`:\n       registers a new option for the given plugin. The name of the\n       option will be `pl.name`, and will have the given default value. Since\n       this registers a common option, the option will be modifiable on a\n       per-buffer basis, while also having a global value (in the\n       GlobalSettings map).\n\n    - `RegisterGlobalOption(pl string, name string, defaultvalue any)`:\n       same as `RegisterCommonOption`, but the option cannot be modified\n       locally to each buffer.\n\n    - `GetGlobalOption(name string) any`: returns the value of a\n       given plugin in the `GlobalSettings` map.\n\n    - `SetGlobalOption(option, value string) error`: sets an option to a\n       given value. This will try to convert the value into the proper\n       type for the option. Can return an error if the option name is not\n       valid, or the value can not be converted.\n\n    - `SetGlobalOptionNative(option string, value any) error`: sets\n       an option to a given value, where the type of value is the actual\n       type of the value internally. Can return an error if the provided value\n       is not valid for the given option.\n\n    - `ConfigDir`: the path to micro's currently active config directory.\n\n    Relevant links:\n    [Buffer](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/buffer#Buffer)\n    [buffer.Completer](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/buffer#Completer)\n    [Error](https://pkg.go.dev/builtin#error)\n    [filepath.Match](https://pkg.go.dev/path/filepath#Match)\n\n* `micro/shell`\n    - `ExecCommand(name string, arg ...string) (string, error)`: runs an\n       executable with the given arguments, and pipes the output (stderr\n       and stdout) of the executable to an internal buffer, which is\n       returned as a string, along with a possible error.\n\n    - `RunCommand(input string) (string, error)`: same as `ExecCommand`,\n       except this uses micro's argument parser to parse the arguments from\n       the input. For example, `cat 'hello world.txt' file.txt`, will pass\n       two arguments in the `ExecCommand` argument list (quoting arguments\n       will preserve spaces).\n\n    - `RunBackgroundShell(input string) (func() string, error)`: returns a\n       function that will run the given shell command and return its output.\n\n    - `RunInteractiveShell(input string, wait bool, getOutput bool)\n                          (string, error)`:\n       temporarily closes micro and runs the given command in the terminal.\n       If `wait` is true, micro will wait for the user to press enter before\n       returning to text editing. If `getOutput` is true, micro will redirect\n       stdout from the command to the returned string.\n\n    - `JobStart(cmd string, onStdout, onStderr,\n                onExit func(string, []any), userargs ...any)\n                *exec.Cmd`:\n       Starts a background job by running the shell on the given command\n       (using `sh -c`). Three callbacks can be provided which will be called\n       when the command generates stdout, stderr, or exits. The userargs will\n       be passed to the callbacks, along with the output as the first\n       argument of the callback. Returns the started command.\n\n    - `JobSpawn(cmd string, cmdArgs []string, onStdout, onStderr,\n                onExit func(string, []any), userargs ...any)\n                *exec.Cmd`:\n       same as `JobStart`, except doesn't run the command through the shell\n       and instead takes as inputs the list of arguments. Returns the started\n       command.\n\n    - `JobStop(cmd *exec.Cmd)`: kills a job.\n    - `JobSend(cmd *exec.Cmd, data string)`: sends some data to a job's stdin.\n\n    - `RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool,\n                       callback func(out string, userargs []any),\n                       userargs []any) error`:\n       starts a terminal emulator from a given BufPane with the input command.\n       If `wait` is true, it will wait for the user to exit by pressing enter\n       once the executable has terminated, and if `getOutput` is true, it will\n       redirect the stdout of the process to a pipe, which will be passed to\n       the callback, which is a function that takes a string and a list of\n       optional user arguments. This function returns an error on systems\n       where the terminal emulator is not supported.\n\n    - `TermEmuSupported`: true on systems where the terminal emulator is\n       supported and false otherwise. Supported systems:\n        * Linux\n        * MacOS\n        * Dragonfly\n        * OpenBSD\n        * FreeBSD\n\n    Relevant links:\n    [Cmd](https://pkg.go.dev/os/exec#Cmd)\n    [BufPane](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/action#BufPane)\n    [Error](https://pkg.go.dev/builtin#error)\n\n* `micro/buffer`\n    - `NewMessage(owner string, msg string, start, end, Loc, kind MsgType)\n                  *Message`:\n       creates a new message with an owner over a range defined by the start\n       and end locations.\n\n    - `NewMessageAtLine(owner string, msg string, line int, kindMsgType)\n                        *Message`:\n       creates a new message with owner, type, and text at a given line.\n\n    - `MTInfo`: info message.\n    - `MTWarning`: warning message.\n    - `MTError` error message.\n\n    - `Loc(x, y int) Loc`: creates a new location struct.\n    - `SLoc(line, row int) display.SLoc`: creates a new scrolling location struct.\n\n    - `BTDefault`: default buffer type.\n    - `BTHelp`: help buffer type.\n    - `BTLog`: log buffer type.\n    - `BTScratch`: scratch buffer type (cannot be saved).\n    - `BTRaw`: raw buffer type.\n    - `BTInfo`: info buffer type.\n\n    - `NewBuffer(text, path string) *Buffer`: creates a new buffer with the\n       given text at a certain path.\n\n    - `NewBufferFromFile(path string) (*Buffer, error)`: creates a new\n       buffer by reading the file at the given path from disk. Returns an error\n       if the read operation fails (for example, due to the file not existing).\n\n    - `ByteOffset(pos Loc, buf *Buffer) int`: returns the byte index of the\n       given position in a buffer.\n\n    - `Log(s string)`: writes a string to the log buffer.\n    - `LogBuf() *Buffer`: returns the log buffer.\n\n    Relevant links:\n    [Message](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/buffer#Message)\n    [Loc](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/buffer#Loc)\n    [display.SLoc](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/display#SLoc)\n    [Buffer](https://pkg.go.dev/github.com/micro-editor/micro/v2/internal/buffer#Buffer)\n    [Error](https://pkg.go.dev/builtin#error)\n\n* `micro/util`\n    - `RuneAt(str string, idx int) string`: returns the utf8 rune at a\n       given index within a string.\n    - `GetLeadingWhitespace(s string) string`: returns the leading\n       whitespace of a string.\n    - `IsWordChar(s string) bool`: returns true if the first rune in a\n       string is a word character.\n    - `String(b []byte) string`: converts a byte array to a string.\n    - `Unzip(src, dest string) error`: unzips a file to given folder.\n    - `Version`: micro's version number or commit hash\n    - `SemVersion`: micro's semantic version\n    - `HttpRequest(method string, url string, headers []string)\n                  (http.Response, error)`: makes a http request.\n    - `CharacterCountInString(str string) int`: returns the number of\n       characters in a string\n    - `RuneStr(r rune) string`: converts a rune to a string.\n\n    Relevant links:\n    [Rune](https://pkg.go.dev/builtin#rune)\n\nThis may seem like a small list of available functions, but some of the objects\nreturned by the functions have many methods. The Lua plugin may access any\npublic methods of an object returned by any of the functions above.\nUnfortunately, it is not possible to list all the available functions on this\npage. Please go to the internal documentation at\nhttps://pkg.go.dev/github.com/micro-editor/micro/v2/internal to see the full list\nof available methods. Note that only methods of types that are available to\nplugins via the functions above can be called from a plugin. For an even more\ndetailed reference, see the source code on Github.\n\nFor example, with a BufPane object called `bp`, you could call the `Save`\nfunction in Lua with `bp:Save()`.\n\nNote that Lua uses the `:` syntax to call a function rather than Go's `.`\nsyntax.\n\n```go\nmicro.InfoBar().Message()\n```\n\nturns to\n\n```lua\nmicro.InfoBar():Message()\n```\n\n## Accessing the Go standard library\n\nIt is possible for your lua code to access many of the functions in the Go\nstandard library.\n\nSimply import the package you'd like, and then you can use it. For example:\n\n```lua\nlocal ioutil = import(\"io/ioutil\")\nlocal fmt = import(\"fmt\")\nlocal micro = import(\"micro\")\n\nlocal data, err = ioutil.ReadFile(\"SomeFile.txt\")\n\nif err ~= nil then\n    micro.InfoBar():Error(\"Error reading file: SomeFile.txt\")\nelse\n    -- Data is returned as an array of bytes\n    -- Using Sprintf will convert it to a string\n    local str = fmt.Sprintf(\"%s\", data)\n\n    -- Do something with the file you just read!\n    -- ...\nend\n```\n\nHere are the packages from the Go standard library that you can access.\nNearly all functions from these packages are supported. For an exact\nlist of functions that are supported, you can look through `lua.go`\n(which should be easy to understand).\n\n* [fmt](https://pkg.go.dev/fmt)\n* [io](https://pkg.go.dev/io)\n* [io/ioutil](https://pkg.go.dev/io/ioutil)\n* [net](https://pkg.go.dev/net)\n* [math](https://pkg.go.dev/math)\n* [math/rand](https://pkg.go.dev/math/rand)\n* [os](https://pkg.go.dev/os)\n* [runtime](https://pkg.go.dev/runtime)\n* [path](https://pkg.go.dev/path)\n* [filepath](https://pkg.go.dev/filepath)\n* [strings](https://pkg.go.dev/strings)\n* [regexp](https://pkg.go.dev/regexp)\n* [errors](https://pkg.go.dev/errors)\n* [time](https://pkg.go.dev/time)\n* [unicode/utf8](https://pkg.go.dev/unicode/utf8)\n* [archive/zip](https://pkg.go.dev/archive/zip)\n* [net/http](https://pkg.go.dev/net/http)\n\nThe following functions from the go-humanize package are also available:\n\n* `humanize`:\n    - `Bytes(s uint64) string`: produces a human readable representation of\n       an SI size.\n    - `Ordinal(x int) string`: gives you the input number in a rank/ordinal\n       format.\n\n[The Lua standard library](https://www.lua.org/manual/5.1/manual.html#5) is also\navailable to plugins, though it is rather small.\n\n## Adding help files, syntax files, or colorschemes in your plugin\n\nYou can use the `AddRuntimeFile(name string, type config.RTFiletype,\n                                path string)`\nfunction to add various kinds of files to your plugin. For example, if you'd\nlike to add a help topic to your plugin called `test`, you would create a\n`test.md` file and call the function:\n\n```lua\nconfig = import(\"micro/config\")\nconfig.AddRuntimeFile(\"test\", config.RTHelp, \"test.md\")\n```\n\nUse `AddRuntimeFilesFromDirectory(name, type, dir, pattern)` to add a number of\nfiles to the runtime. To read the content of a runtime file, use\n`ReadRuntimeFile(fileType, name string)` or `ListRuntimeFiles(fileType string)`\nfor all runtime files. In addition, there is `AddRuntimeFileFromMemory` which\nadds a runtime file based on a string that may have been constructed at\nruntime.\n\n## Default plugins\n\nThe following plugins come pre-installed with micro:\n\n* `autoclose`: automatically closes brackets, quotes, etc...\n* `comment`: provides automatic commenting for a number of languages\n* `ftoptions`: alters some default options (notably indentation) depending on\n   the filetype\n* `linter`: provides extensible linting for many languages\n* `literate`: provides advanced syntax highlighting for the Literate\n   programming tool.\n* `status`: provides some extensions to the status line (integration with\n   Git and more).\n* `diff`: integrates the `diffgutter` option with Git. If you are in a Git\n   directory, the diff gutter will show changes with respect to the most\n   recent Git commit rather than the diff since opening the file.\n\nSee `> help linter`, `> help comment`, and `> help status` for additional\ndocumentation specific to those plugins.\n\nThese are good examples for many use-cases if you are looking to write\nyour own plugins.\n\n## Plugin Manager\n\nMicro also has a built in plugin manager, which you can invoke with the\n`> plugin ...` command, or in the shell with `micro -plugin ...`.\n\nFor the valid commands you can use, see the `commands` help topic.\n\nThe manager fetches plugins from the channels (which is simply a list of plugin\nmetadata) which it knows about. By default, micro only knows about the [official\nchannel](https://github.com/micro-editor/plugin-channel) but you can\nadd your own third-party channels using the `pluginchannels` option and you can\ndirectly link third-party plugins to allow installation through the plugin\nmanager with the `pluginrepos` option.\n\nIf you'd like to publish a plugin you've made as an official plugin, you should\nupload your plugin online (preferably to Github) and add a `repo.json` file.\nThis file will contain the metadata for your plugin. Here is an example:\n\n```json\n[{\n  \"Name\": \"pluginname\",\n  \"Description\": \"Here is a nice concise description of my plugin\",\n  \"Website\": \"https://github.com/user/plugin\",\n  \"Tags\": [\"python\", \"linting\"],\n  \"Versions\": [\n    {\n      \"Version\": \"1.0.0\",\n      \"Url\": \"https://github.com/user/plugin/archive/v1.0.0.zip\",\n      \"Require\": {\n        \"micro\": \">=1.0.3\"\n      }\n    }\n  ]\n}]\n```\n\nThen open a pull request at the [official plugin channel](https://github.com/micro-editor/plugin-channel),\nadding a link to the raw `repo.json` that is in your plugin repository.\n\nTo make updating the plugin work, the first line of your plugin's lua code\nshould contain the version of the plugin. (Like this: `VERSION = \"1.0.0\"`)\nPlease make sure to use [semver](https://semver.org/) for versioning.\n"
  },
  {
    "path": "runtime/help/tutorial.md",
    "content": "# Tutorial\n\nThis is a brief intro to micro's configuration system that will give some\nsimple examples showing how to configure settings, rebind keys, and use\n`init.lua` to configure micro to your liking.\n\nHopefully you'll find this useful.\n\nSee `> help defaultkeys` for a list an explanation of the default keybindings.\n\n### Settings\n\nIn micro, your settings are stored in `~/.config/micro/settings.json`, a file\nthat is created the first time you run micro. It is a json file which holds all\nthe settings and their values. To change an option, you can either change the\nvalue in the `settings.json` file, or you can type it in directly while using\nmicro.\n\nPress Ctrl-e to go to command mode, and type `set option value` (in the\nfuture, I will use `> set option value` to indicate pressing Ctrl-e). The change\nwill take effect immediately and will also be saved to the `settings.json` file\nso that the setting will stick even after you close micro.\n\nYou can also set options locally which means that the setting will only have\nthe value you give it in the buffer you set it in. For example, if you have two\nsplits open, and you type `> setlocal tabsize 2`, the tabsize will only be 2 in\nthe current buffer. Also micro will not save this local change to the\n`settings.json` file. However, you can still set options locally in the\n`settings.json` file. For example, if you want the `tabsize` to be 2 only in\nRuby files, and 4 otherwise, you could put the following in `settings.json`:\n\n```json\n{\n    \"*.rb\": {\n        \"tabsize\": 2\n    },\n    \"tabsize\": 4\n}\n```\n\nMicro will set the `tabsize` to 2 only in files which match the glob `*.rb`.\n\nIf you would like to know more about all the available options, see the\n`options` topic (`> help options`).\n\n### Keybindings\n\nKeybindings work in much the same way as options. You configure them using the\n`~/.config/micro/bindings.json` file.\n\nFor example if you would like to bind `Ctrl-r` to redo you could put the\nfollowing in `bindings.json`:\n\n```json\n{\n    \"Ctrl-r\": \"Redo\"\n}\n```\n\nVery simple.\n\nYou can also bind keys while in micro by using the `> bind key action` command,\nbut the bindings you make with the command won't be saved to the\n`bindings.json` file.\n\nFor more information about keybindings, like which keys can be bound, and what\nactions are available, see the `keybindings` help topic (`> help keybindings`).\n\n### Configuration with Lua\n\nIf you need more power than the json files provide, you can use the `init.lua`\nfile. Create it in `~/.config/micro`. This file is a lua file that is run when\nmicro starts and is essentially a one-file plugin. The plugin name is\n`initlua`.\n\nThis example will show you how to use the `init.lua` file by creating a binding\nto `Ctrl-r` which will execute the bash command `go run` on the current file,\ngiven that the current file is a Go file.\n\nYou can do that by putting the following in `init.lua`:\n\n```lua\nlocal config = import(\"micro/config\")\nlocal shell = import(\"micro/shell\")\n\nfunction init()\n    -- true means overwrite any existing binding to Ctrl-r\n    -- this will modify the bindings.json file\n    config.TryBindKey(\"Ctrl-r\", \"lua:initlua.gorun\", true)\nend\n\nfunction gorun(bp)\n    local buf = bp.Buf\n    if buf:FileType() == \"go\" then\n        -- the true means run in the foreground\n        -- the false means send output to stdout (instead of returning it)\n        shell.RunInteractiveShell(\"go run \" .. buf.Path, true, false)\n    end\nend\n```\n\nAlternatively, you could get rid of the `TryBindKey` line, and put this line in\nthe `bindings.json` file:\n\n```json\n{\n    \"Ctrl-r\": \"lua:initlua.gorun\"\n}\n```\n\nFor more information about plugins and the lua system that micro uses, see the\n`plugins` help topic (`> help plugins`).\n"
  },
  {
    "path": "runtime/plugins/autoclose/autoclose.lua",
    "content": "VERSION = \"1.0.0\"\n\nlocal uutil = import(\"micro/util\")\nlocal utf8 = import(\"utf8\")\nlocal autoclosePairs = {\"\\\"\\\"\", \"''\", \"``\", \"()\", \"{}\", \"[]\"}\nlocal autoNewlinePairs = {\"()\", \"{}\", \"[]\"}\n\nfunction charAt(str, i)\n    -- lua indexing is one off from go\n    return uutil.RuneAt(str, i-1)\nend\n\nfunction onRune(bp, r)\n    for i = 1, #autoclosePairs do\n        if r == charAt(autoclosePairs[i], 2) then\n            local curLine = bp.Buf:Line(bp.Cursor.Y)\n\n            if charAt(curLine, bp.Cursor.X+1) == charAt(autoclosePairs[i], 2) then\n                bp:Backspace()\n                bp:CursorRight()\n                break\n            end\n\n            if bp.Cursor.X > 1 and (uutil.IsWordChar(charAt(curLine, bp.Cursor.X-1)) or charAt(curLine, bp.Cursor.X-1) == charAt(autoclosePairs[i], 1)) then\n                break\n            end\n        end\n        if r == charAt(autoclosePairs[i], 1) then\n            local curLine = bp.Buf:Line(bp.Cursor.Y)\n\n            if bp.Cursor.X == uutil.CharacterCountInString(curLine) or not uutil.IsWordChar(charAt(curLine, bp.Cursor.X+1)) then\n                -- the '-' here is to derefence the pointer to bp.Cursor.Loc which is automatically made\n                -- when converting go structs to lua\n                -- It needs to be dereferenced because the function expects a non pointer struct\n                bp.Buf:Insert(-bp.Cursor.Loc, charAt(autoclosePairs[i], 2))\n                bp:CursorLeft()\n                break\n            end\n        end\n    end\n    return true\nend\n\nfunction preInsertNewline(bp)\n    local curLine = bp.Buf:Line(bp.Cursor.Y)\n    local curRune = charAt(curLine, bp.Cursor.X)\n    local nextRune = charAt(curLine, bp.Cursor.X+1)\n    local ws = uutil.GetLeadingWhitespace(curLine)\n\n    for i = 1, #autoNewlinePairs do\n        if curRune == charAt(autoNewlinePairs[i], 1) then\n            if nextRune == charAt(autoNewlinePairs[i], 2) then\n                bp.Buf:Insert(-bp.Cursor.Loc, \"\\n\" .. ws)\n                bp:StartOfLine()\n                bp:CursorLeft()\n                bp:InsertNewline()\n                bp:InsertTab()\n                return false\n            end\n        end\n    end\n\n    return true\nend\n\nfunction preBackspace(bp)\n    for i = 1, #autoclosePairs do\n        local curLine = bp.Buf:Line(bp.Cursor.Y)\n        if charAt(curLine, bp.Cursor.X+1) == charAt(autoclosePairs[i], 2) and charAt(curLine, bp.Cursor.X) == charAt(autoclosePairs[i], 1) then\n            bp:Delete()\n        end\n    end\n\n    return true\nend\n"
  },
  {
    "path": "runtime/plugins/comment/comment.lua",
    "content": "VERSION = \"1.0.0\"\n\nlocal util = import(\"micro/util\")\nlocal config = import(\"micro/config\")\nlocal buffer = import(\"micro/buffer\")\nlocal micro = import(\"micro\")\n\nlocal ft = {}\n\nft[\"apacheconf\"] = \"# %s\"\nft[\"batch\"] = \":: %s\"\nft[\"c\"] = \"// %s\"\nft[\"c++\"] = \"// %s\"\nft[\"cmake\"] = \"# %s\"\nft[\"conf\"] = \"# %s\"\nft[\"crystal\"] = \"# %s\"\nft[\"css\"] = \"/* %s */\"\nft[\"d\"] = \"// %s\"\nft[\"dart\"] = \"// %s\"\nft[\"dockerfile\"] = \"# %s\"\nft[\"elm\"] = \"-- %s\"\nft[\"fish\"] = \"# %s\"\nft[\"gdscript\"] = \"# %s\"\nft[\"glsl\"] = \"// %s\"\nft[\"go\"] = \"// %s\"\nft[\"haskell\"] = \"-- %s\"\nft[\"html\"] = \"<!-- %s -->\"\nft[\"ini\"] = \"; %s\"\nft[\"java\"] = \"// %s\"\nft[\"javascript\"] = \"// %s\"\nft[\"jinja2\"] = \"{# %s #}\"\nft[\"json\"] = \"// %s\"\nft[\"julia\"] = \"# %s\"\nft[\"kotlin\"] = \"// %s\"\nft[\"lua\"] = \"-- %s\"\nft[\"markdown\"] = \"<!-- %s -->\"\nft[\"nginx\"] = \"# %s\"\nft[\"nim\"] = \"# %s\"\nft[\"objc\"] = \"// %s\"\nft[\"ocaml\"] = \"(* %s *)\"\nft[\"pascal\"] = \"{ %s }\"\nft[\"perl\"] = \"# %s\"\nft[\"php\"] = \"// %s\"\nft[\"pony\"] = \"// %s\"\nft[\"powershell\"] = \"# %s\"\nft[\"proto\"] = \"// %s\"\nft[\"python\"] = \"# %s\"\nft[\"python3\"] = \"# %s\"\nft[\"ruby\"] = \"# %s\"\nft[\"rust\"] = \"// %s\"\nft[\"scala\"] = \"// %s\"\nft[\"shell\"] = \"# %s\"\nft[\"sql\"] = \"-- %s\"\nft[\"swift\"] = \"// %s\"\nft[\"tex\"] = \"% %s\"\nft[\"toml\"] = \"# %s\"\nft[\"twig\"] = \"{# %s #}\"\nft[\"typescript\"] = \"// %s\"\nft[\"v\"] = \"// %s\"\nft[\"xml\"] = \"<!-- %s -->\"\nft[\"yaml\"] = \"# %s\"\nft[\"zig\"] = \"// %s\"\nft[\"zscript\"] = \"// %s\"\nft[\"zsh\"] = \"# %s\"\n\nfunction updateCommentType(buf)\n    -- NOTE: Using DoSetOptionNative to avoid LocalSettings[option] = true\n    -- so that \"comment.type\" can be reset by a \"filetype\" change to default.\n    if (buf.Settings[\"comment.type\"] == \"\") then\n        -- NOTE: This won't get triggered if a filetype is change via `setlocal filetype`\n        -- since it is not registered with `RegisterGlobalOption()``\n        if buf.Settings[\"commenttype\"] ~= nil then\n            buf:DoSetOptionNative(\"comment.type\", buf.Settings[\"commenttype\"])\n        else\n            if (ft[buf.Settings[\"filetype\"]] ~= nil) then\n                buf:DoSetOptionNative(\"comment.type\", ft[buf.Settings[\"filetype\"]])\n            else\n                buf:DoSetOptionNative(\"comment.type\", \"# %s\")\n            end\n        end\n    end\nend\n\nfunction isCommented(bp, lineN, commentRegex)\n    local line = bp.Buf:Line(lineN)\n    local regex = commentRegex:gsub(\"%s+\", \"%s*\")\n    if string.match(line, regex) then\n        return true\n    end\n    return false\nend\n\nfunction commentLine(bp, lineN, indentLen)\n    updateCommentType(bp.Buf)\n\n    local line = bp.Buf:Line(lineN)\n    local commentType = bp.Buf.Settings[\"comment.type\"]\n    local sel = -bp.Cursor.CurSelection\n    local curpos = -bp.Cursor.Loc\n    local index = string.find(commentType, \"%%s\") - 1\n    local indent = string.sub(line, 1, indentLen)\n    local trimmedLine = string.sub(line, indentLen + 1)\n    trimmedLine = trimmedLine:gsub(\"%%\", \"%%%%\")\n    local commentedLine = commentType:gsub(\"%%s\", trimmedLine)\n    bp.Buf:Replace(buffer.Loc(0, lineN), buffer.Loc(#line, lineN), indent .. commentedLine)\n    if bp.Cursor:HasSelection() then\n        bp.Cursor.CurSelection[1].Y = sel[1].Y\n        bp.Cursor.CurSelection[2].Y = sel[2].Y\n        bp.Cursor.CurSelection[1].X = sel[1].X\n        bp.Cursor.CurSelection[2].X = sel[2].X\n    else\n        bp.Cursor.X = curpos.X + index\n        bp.Cursor.Y = curpos.Y\n    end\n    bp.Cursor:Relocate()\n    bp.Cursor:StoreVisualX()\nend\n\nfunction uncommentLine(bp, lineN, commentRegex)\n    updateCommentType(bp.Buf)\n\n    local line = bp.Buf:Line(lineN)\n    local commentType = bp.Buf.Settings[\"comment.type\"]\n    local sel = -bp.Cursor.CurSelection\n    local curpos = -bp.Cursor.Loc\n    local index = string.find(commentType, \"%%s\") - 1\n    if not string.match(line, commentRegex) then\n        commentRegex = commentRegex:gsub(\"%s+\", \"%s*\")\n    end\n    if string.match(line, commentRegex) then\n        uncommentedLine = string.match(line, commentRegex)\n        bp.Buf:Replace(buffer.Loc(0, lineN), buffer.Loc(#line, lineN), util.GetLeadingWhitespace(line) .. uncommentedLine)\n        if bp.Cursor:HasSelection() then\n            bp.Cursor.CurSelection[1].Y = sel[1].Y\n            bp.Cursor.CurSelection[2].Y = sel[2].Y\n            bp.Cursor.CurSelection[1].X = sel[1].X\n            bp.Cursor.CurSelection[2].X = sel[2].X\n        else\n            bp.Cursor.X = curpos.X - index\n            bp.Cursor.Y = curpos.Y\n        end\n    end\n    bp.Cursor:Relocate()\n    bp.Cursor:StoreVisualX()\nend\n\nfunction toggleCommentLine(bp, lineN, commentRegex)\n    if isCommented(bp, lineN, commentRegex) then\n        uncommentLine(bp, lineN, commentRegex)\n    else\n        commentLine(bp, lineN, #util.GetLeadingWhitespace(bp.Buf:Line(lineN)))\n    end\nend\n\nfunction toggleCommentSelection(bp, startLine, endLine, commentRegex)\n    local allComments = true\n    for line = startLine, endLine do\n        if not isCommented(bp, line, commentRegex) then\n            allComments = false\n            break\n        end\n    end\n\n    -- NOTE: we assume that the indentation is either tabs only or spaces only\n    local indentMin = -1\n    if not allComments then\n        for line = startLine, endLine do\n            local indentLen = #util.GetLeadingWhitespace(bp.Buf:Line(line))\n            if indentMin == -1 or indentLen < indentMin then\n                indentMin = indentLen\n            end\n        end\n    end\n\n    for line = startLine, endLine do\n        if allComments then\n            uncommentLine(bp, line, commentRegex)\n        else\n            commentLine(bp, line, indentMin)\n        end\n    end\nend\n\nfunction comment(bp, args)\n    updateCommentType(bp.Buf)\n\n    local commentType = bp.Buf.Settings[\"comment.type\"]\n    local commentRegex = \"^%s*\" .. commentType:gsub(\"%%\",\"%%%%\"):gsub(\"%$\",\"%$\"):gsub(\"%)\",\"%)\"):gsub(\"%(\",\"%(\"):gsub(\"%?\",\"%?\"):gsub(\"%*\", \"%*\"):gsub(\"%-\", \"%-\"):gsub(\"%.\", \"%.\"):gsub(\"%+\", \"%+\"):gsub(\"%]\", \"%]\"):gsub(\"%[\", \"%[\"):gsub(\"%%%%s\", \"(.*)\")\n\n    if bp.Cursor:HasSelection() then\n        if bp.Cursor.CurSelection[1]:GreaterThan(-bp.Cursor.CurSelection[2]) then\n            local endLine = bp.Cursor.CurSelection[1].Y\n            if bp.Cursor.CurSelection[1].X == 0 then\n                endLine = endLine - 1\n            end\n            toggleCommentSelection(bp, bp.Cursor.CurSelection[2].Y, endLine, commentRegex)\n        else\n            local endLine = bp.Cursor.CurSelection[2].Y\n            if bp.Cursor.CurSelection[2].X == 0 then\n                endLine = endLine - 1\n            end\n            toggleCommentSelection(bp, bp.Cursor.CurSelection[1].Y, endLine, commentRegex)\n        end\n    else\n        toggleCommentLine(bp, bp.Cursor.Y, commentRegex)\n    end\nend\n\nfunction string.starts(String,Start)\n    return string.sub(String,1,string.len(Start))==Start\nend\n\nfunction preinit()\n    config.RegisterCommonOption(\"comment\", \"type\", \"\")\nend\n\nfunction init()\n    config.MakeCommand(\"comment\", comment, config.NoComplete)\n    config.TryBindKey(\"Alt-/\", \"lua:comment.comment\", false)\n    config.TryBindKey(\"CtrlUnderscore\", \"lua:comment.comment\", false)\n    config.AddRuntimeFile(\"comment\", config.RTHelp, \"help/comment.md\")\nend\n"
  },
  {
    "path": "runtime/plugins/comment/help/comment.md",
    "content": "# Comment Plugin\n\nThe comment plugin provides auto commenting/uncommenting.\nThe default binding to comment/uncomment a line is `Alt-/`\nand `CtrlUnderscore`, which is equivalent in most terminals\nto `Ctrl-/`. You can easily modify that in your `bindings.json`\nfile:\n\n```json\n{\n    \"Alt-g\": \"lua:comment.comment\"\n}\n```\n\nYou can also execute a command which will do the same thing as\nthe binding:\n\n```\n> comment\n```\n\nIf you have a selection, the plugin will comment all the lines\nselected.\n\nThe comment type will be auto detected based on the filetype,\nbut it is only available for certain filetypes:\n\n* apacheconf: `# %s`\n* bat: `:: %s`\n* c: `// %s`\n* c++: `// %s`\n* cmake: `# %s`\n* conf: `# %s`\n* crystal: `# %s`\n* css: `/* %s */`\n* d: `// %s`\n* dart: `// %s`\n* dockerfile: `# %s`\n* elm: `-- %s`\n* fish: `# %s`\n* gdscript: `# %s`\n* glsl: `// %s`\n* go: `// %s`\n* haskell: `-- %s`\n* html: `<!-- %s -->`\n* ini: `; %s`\n* java: `// %s`\n* javascript: `// %s`\n* jinja2: `{# %s #}`\n* json: `// %s`\n* julia: `# %s`\n* kotlin: `// %s`\n* lua: `-- %s`\n* markdown: `<!-- %s -->`\n* nginx: `# %s`\n* nim: `# %s`\n* objc: `// %s`\n* pascal: `{ %s }`\n* perl: `# %s`\n* php: `// %s`\n* pony: `// %s`\n* powershell: `# %s`\n* proto: `// %s`\n* python: `# %s`\n* python3: `# %s`\n* ruby: `# %s`\n* rust: `// %s`\n* scala: `// %s`\n* shell: `# %s`\n* sql: `-- %s`\n* swift: `// %s`\n* tex: `% %s`\n* toml: `# %s`\n* twig: `{# %s #}`\n* v: `// %s`\n* xml: `<!-- %s -->`\n* yaml: `# %s`\n* zig: `// %s`\n* zscript: `// %s`\n* zsh: `# %s`\n\nIf your filetype is not available here, you can simply modify\nthe `comment.type` option:\n\n```\nset comment.type \"/* %s */\"\n```\n\nOr in your `settings.json`:\n\n```json\n{\n    \"*.c\": {\n        \"comment.type\": \"/* %s */\"\n    }\n}\n```\n\n`commenttype` (without the dot) is the legacy option that is\nsuperseded by `comment.type`. `commenttype` is still supported\nbut deprecated.\n**Use `comment.type` instead.**\n"
  },
  {
    "path": "runtime/plugins/diff/diff.lua",
    "content": "VERSION = \"1.0.0\"\n\nlocal os = import(\"os\")\nlocal filepath = import(\"path/filepath\")\nlocal shell = import(\"micro/shell\")\n\nfunction onBufferOpen(buf)\n\tif buf.Settings[\"diffgutter\"] and (not buf.Type.Scratch) and (buf.Path ~= \"\") then\n\t\t-- check that file exists\n\t\tlocal _, err = os.Stat(buf.AbsPath)\n\t\tif err == nil then\n\t\t\tlocal dirName, fileName = filepath.Split(buf.AbsPath)\n\t\t\tlocal diffBase, err = shell.ExecCommand(\"git\", \"-C\", dirName, \"show\", \"HEAD:./\" .. fileName)\n\t\t\tif err ~= nil then\n\t\t\t\tdiffBase = buf:Bytes()\n\t\t\tend\n\t\t\tbuf:SetDiffBase(diffBase)\n\t\tend\n\tend\nend\n"
  },
  {
    "path": "runtime/plugins/ftoptions/ftoptions.lua",
    "content": "VERSION = \"1.0.0\"\n\nfunction onBufferOpen(b)\n    local ft = b:FileType()\n\n    if ft == \"go\" or\n    ft == \"makefile\" then\n        b:SetOption(\"tabstospaces\", \"off\")\n    elseif ft == \"fish\" or\n           ft == \"python\" or\n           ft == \"python2\" or\n           ft == \"python3\" or\n           ft == \"yaml\" or\n           ft == \"nim\" then\n        b:SetOption(\"tabstospaces\", \"on\")\n    end\nend\n"
  },
  {
    "path": "runtime/plugins/linter/help/linter.md",
    "content": "# Linter\n\nThe linter plugin runs a compiler or linter on your source code\nand parses the resulting output so that the messages and line numbers\ncan be viewed from within micro. By default, the plugin supports the\nfollowing filetypes and linters:\n\n* **c**: gcc\n* **c++**: g++\n* **d**: dmd\n* **d**: ldc2\n* **d**: gdc\n* **go**: go build\n* **go**: go vet\n* **haskell**: hlint\n* **java**: javac\n* **javascript**: jshint\n* **javascript**: eslint\n* **literate**: lit\n* **lua**: luacheck\n* **nim**: nim\n* **nix**: nix-linter\n* **objective-c**: clang\n* **python**: flake8\n* **python**: mypy\n* **python**: pyflakes\n* **python**: pylint\n* **python**: ruff\n* **rust**: cargo clippy\n* **shell**: shfmt\n* **shell**: shellcheck\n* **swift**: swiftc (MacOS and Linux only)\n* **yaml**: yamllint\n\nIf the linter plugin is enabled and the file corresponds to one of\nthese filetypes, each time the buffer is saved, or when the `> lint`\ncommand is executed, micro will run the corresponding utility in the\nbackground and display the messages when it completes.\n\nThe linter plugin also allows users to extend the supported filetypes.\nFrom inside another micro plugin, the function `linter.makeLinter` can\nbe called to register a new filetype. Here is the spec for the `makeLinter`\nfunction:\n\n`linter.makeLinter(name, filetype, cmd, args, errorformat, os, whitelist, domatch, loffset, coffset, callback)`\n\n* **name**: name of the linter\n* **filetype**: filetype to check for to use linter\n* **cmd**: main linter process that is executed\n* **args**: arguments to pass to the linter process\n    * use %f to refer to the current file name\n    * use %d to refer to the current directory name\n* **errorformat**: how to parse the linter/compiler process output\n    %f: file, %l: line number, %m: error/warning message\n* **os**: list of OSs this linter is supported or unsupported on\n    optional param, default: {}\n* **whitelist**: should the OS list be a blacklist (do not run the linter for these OSs)\n           or a whitelist (only run the linter for these OSs)\n    optional param, default: false (should blacklist)\n* **domatch**: should the filetype be interpreted as a lua pattern to match with\n         the actual filetype, or should the linter only activate on an exact match\n    optional param, default: false (require exact match)\n* **loffset**: line offset will be added to the line number returned by the linter\n         useful if the linter returns 0-indexed lines\n    optional param, default: 0\n* **coffset**: column offset will be added to the col number returned by the linter\n         useful if the linter returns 0-indexed columns\n    optional param, default: 0\n* **callback**: function to call before executing the linter, if it returns\n          false the lint is canceled. The callback is passed the buf.\n    optional param, default: nil\n\nBelow is an example for including a linter for any filetype using\nthe `misspell` linter which checks for misspelled words in a file.\n\n```lua\nfunction init()\n    -- uses the default linter plugin\n    -- matches any filetype\n    linter.makeLinter(\"misspell\", \"\", \"misspell\", {\"%f\"}, \"%f:%l:%c: %m\", {}, false, true)\nend\n```\n\nHere is an example for a more typical use-case, where the linter will only match one filetype (C in this case):\n\n```lua\nfunction init()\n    linter.makeLinter(\"gcc\", \"c\", \"gcc\", {\"-fsyntax-only\", \"-Wall\", \"-Wextra\", \"%f\"}, \"%f:%l:%c:.+: %m\")\nend\n```\n"
  },
  {
    "path": "runtime/plugins/linter/linter.lua",
    "content": "VERSION = \"1.0.0\"\n\nlocal micro = import(\"micro\")\nlocal runtime = import(\"runtime\")\nlocal filepath = import(\"path/filepath\")\nlocal shell = import(\"micro/shell\")\nlocal buffer = import(\"micro/buffer\")\nlocal config = import(\"micro/config\")\nlocal util = import(\"micro/util\")\nlocal os = import(\"os\")\n\nlocal linters = {}\n\n-- creates a linter entry, call from within an initialization function, not\n-- directly at initial load time\n--\n-- name: name of the linter\n-- filetype: filetype to check for to use linter\n-- cmd: main linter process that is executed\n-- args: arguments to pass to the linter process\n--     use %f to refer to the current file name\n--     use %d to refer to the current directory name\n-- errorformat: how to parse the linter/compiler process output\n--     %f: file, %l: line number, %m: error/warning message\n-- os: list of OSs this linter is supported or unsupported on\n--     optional param, default: {}\n-- whitelist: should the OS list be a blacklist (do not run the linter for these OSs)\n--            or a whitelist (only run the linter for these OSs)\n--     optional param, default: false (should blacklist)\n-- domatch: should the filetype be interpreted as a lua pattern to match with\n--          the actual filetype, or should the linter only activate on an exact match\n--     optional param, default: false (require exact match)\n-- loffset: line offset will be added to the line number returned by the linter\n--          useful if the linter returns 0-indexed lines\n--     optional param, default: 0\n-- coffset: column offset will be added to the col number returned by the linter\n--          useful if the linter returns 0-indexed columns\n--     optional param, default: 0\n-- callback: function to call before executing the linter, if it returns\n--           false the lint is canceled. The callback is passed the buf.\n--     optional param, default: nil\nfunction makeLinter(name, filetype, cmd, args, errorformat, os, whitelist, domatch, loffset, coffset, callback)\n    if linters[name] == nil then\n        linters[name] = {}\n        linters[name].filetype = filetype\n        linters[name].cmd = cmd\n        linters[name].args = args\n        linters[name].errorformat = errorformat\n        linters[name].os = os or {}\n        linters[name].whitelist = whitelist or false\n        linters[name].domatch = domatch or false\n        linters[name].loffset = loffset or 0\n        linters[name].coffset = coffset or 0\n        linters[name].callback = callback or nil\n    end\nend\n\nfunction removeLinter(name)\n    linters[name] = nil\nend\n\nfunction preinit()\n    local devnull = \"/dev/null\"\n    if runtime.GOOS == \"windows\" then\n        devnull = \"NUL\"\n    end\n\n    makeLinter(\"gcc\", \"c\", \"gcc\", {\"-fsyntax-only\", \"-Wall\", \"-Wextra\", \"%f\"}, \"%f:%l:%c:.+: %m\")\n    makeLinter(\"g++\", \"c++\", \"g++\", {\"-fsyntax-only\",\"-Wall\", \"-Wextra\", \"%f\"}, \"%f:%l:%c:.+: %m\")\n    makeLinter(\"dmd\", \"d\", \"dmd\", {\"-color=off\", \"-o-\", \"-w\", \"-wi\", \"-c\", \"%f\"}, \"%f%(%l%):.+: %m\")\n    makeLinter(\"ldc2\", \"d\", \"ldc2\", {\"--o-\", \"--vcolumns\", \"-w\", \"-c\", \"%f\"}, \"%f%(%l,%c%):[^:]+: %m\")\n    makeLinter(\"gdc\", \"d\", \"gdc\", {\"-fsyntax-only\",\"-Wall\", \"-Wextra\", \"%f\"}, \"%f:%l:%c:.+: %m\")\n    makeLinter(\"eslint\", \"javascript\", \"eslint\", {\"-f\",\"compact\",\"%f\"}, \"%f: line %l, col %c, %m\")\n    makeLinter(\"gobuild\", \"go\", \"go\", {\"build\", \"-o\", devnull, \"%d\"}, \"%f:%l:%c:? %m\")\n    makeLinter(\"govet\", \"go\", \"go\", {\"vet\"}, \"%f:%l:%c: %m\")\n    makeLinter(\"clippy\", \"rust\", \"cargo\", {\"clippy\", \"--message-format\", \"short\"}, \"%f:%l:%c: %m\")\n    makeLinter(\"hlint\", \"haskell\", \"hlint\", {\"%f\"}, \"%f:%(?%l[,:]%c%)?.-: %m\")\n    makeLinter(\"javac\", \"java\", \"javac\", {\"-d\", \"%d\", \"%f\"}, \"%f:%l: error: %m\")\n    makeLinter(\"jshint\", \"javascript\", \"jshint\", {\"%f\"}, \"%f: line %l,.+, %m\")\n    makeLinter(\"literate\", \"literate\", \"lit\", {\"-c\", \"%f\"}, \"%f:%l:%m\", {}, false, true)\n    makeLinter(\"luacheck\", \"lua\", \"luacheck\", {\"--no-color\", \"%f\"}, \"%f:%l:%c: %m\")\n    makeLinter(\"nim\", \"nim\", \"nim\", {\"check\", \"--listFullPaths\", \"--stdout\", \"--hints:off\", \"%f\"}, \"%f.%l, %c. %m\")\n    makeLinter(\"clang\", \"objective-c\", \"xcrun\", {\"clang\", \"-fsyntax-only\", \"-Wall\", \"-Wextra\", \"%f\"}, \"%f:%l:%c:.+: %m\")\n    makeLinter(\"pyflakes\", \"python\", \"pyflakes\", {\"%f\"}, \"%f:%l:.-:? %m\")\n    makeLinter(\"mypy\", \"python\", \"mypy\", {\"%f\"}, \"%f:%l: %m\")\n    makeLinter(\"pylint\", \"python\", \"pylint\", {\"--output-format=parseable\", \"--reports=no\", \"%f\"}, \"%f:%l: %m\")\n    makeLinter(\"ruff\", \"python\", \"ruff\", {\"check\", \"--output-format=concise\", \"%f\"}, \"%f:%l:%c: %m\")\n    makeLinter(\"flake8\", \"python\", \"flake8\", {\"%f\"}, \"%f:%l:%c: %m\")\n    makeLinter(\"shfmt\", \"shell\", \"shfmt\", {\"%f\"}, \"%f:%l:%c: %m\")\n    makeLinter(\"shellcheck\", \"shell\", \"shellcheck\", {\"-f\", \"gcc\", \"%f\"}, \"%f:%l:%c:.+: %m\")\n    makeLinter(\"swiftc\", \"swift\", \"xcrun\", {\"swiftc\", \"%f\"}, \"%f:%l:%c:.+: %m\", {\"darwin\"}, true)\n    makeLinter(\"swiftc-linux\", \"swift\", \"swiftc\", {\"%f\"}, \"%f:%l:%c:.+: %m\", {\"linux\"}, true)\n    makeLinter(\"yaml\", \"yaml\", \"yamllint\", {\"--format\", \"parsable\", \"%f\"}, \"%f:%l:%c:.+ %m\")\n    makeLinter(\"nix-linter\", \"nix\", \"nix-linter\", {\"%f\"}, \"%m at %f:%l:%c\", {\"linux\"}, true)\n\n    config.MakeCommand(\"lint\", function(bp, args)\n        bp:Save()\n        runLinter(bp.Buf)\n    end, config.NoComplete)\n\n    config.AddRuntimeFile(\"linter\", config.RTHelp, \"help/linter.md\")\nend\n\nfunction contains(list, element)\n    for k, v in pairs(list) do\n        if v == element then\n            return true\n        end\n    end\n    return false\nend\n\nfunction checkFtMatch(ft, v)\n    local ftmatch = ft == v.filetype\n    if v.domatch then\n        ftmatch = string.match(ft, v.filetype)\n    end\n\n    local hasOS = contains(v.os, runtime.GOOS)\n    if not hasOS and v.whitelist then\n        ftmatch = false\n    end\n    if hasOS and not v.whitelist then\n        ftmatch = false\n    end\n    return ftmatch\nend\n\nfunction runLinter(buf)\n    local ft = buf:FileType()\n    local file = buf.Path\n    local dir = \".\" .. util.RuneStr(os.PathSeparator) .. filepath.Dir(file)\n\n    for k, v in pairs(linters) do\n        if checkFtMatch(ft, v) then\n            local args = {}\n            for k, arg in pairs(v.args) do\n                args[k] = arg:gsub(\"%%f\", file):gsub(\"%%d\", dir)\n            end\n            lint(buf, k, v.cmd, args, v.errorformat, v.loffset, v.coffset, v.callback)\n        end\n    end\nend\n\nfunction onSave(bp)\n    runLinter(bp.Buf)\n    return true\nend\n\nfunction onBufferOptionChanged(buf, option, old, new)\n    if option == \"filetype\" then\n        if old ~= new then\n            for k, v in pairs(linters) do\n                if checkFtMatch(old, v) then\n                    buf:ClearMessages(k)\n                end\n            end\n        end\n    end\n    return true\nend\n\nfunction lint(buf, linter, cmd, args, errorformat, loff, coff, callback)\n    buf:ClearMessages(linter)\n\n    if callback ~= nil then\n        if not callback(buf) then\n            return\n        end\n    end\n\n    shell.JobSpawn(cmd, args, nil, nil, onExit, buf, linter, errorformat, loff, coff)\nend\n\nfunction onExit(output, args)\n    local buf, linter, errorformat, loff, coff = args[1], args[2], args[3], args[4], args[5]\n    local lines = split(output, \"\\n\")\n\n    local regex = errorformat:gsub(\"%%f\", \"(..-)\"):gsub(\"%%l\", \"(%d+)\"):gsub(\"%%c\", \"(%d+)\"):gsub(\"%%m\", \"(.+)\")\n    for _,line in ipairs(lines) do\n        -- Trim whitespace\n        line = line:match(\"^%s*(.+)%s*$\")\n        if string.find(line, regex) then\n            local file, line, col, msg = string.match(line, regex)\n            local hascol = true\n            if not string.find(errorformat, \"%%c\") then\n                hascol = false\n                msg = col\n            elseif col == nil then\n                hascol = false\n            end\n            if basename(buf.Path) == basename(file) then\n                local bmsg = nil\n                if hascol then\n                    local mstart = buffer.Loc(tonumber(col-1+coff), tonumber(line-1+loff))\n                    local mend = buffer.Loc(tonumber(col+coff), tonumber(line-1+loff))\n                    bmsg = buffer.NewMessage(linter, msg, mstart, mend, buffer.MTError)\n                else\n                    bmsg = buffer.NewMessageAtLine(linter, msg, tonumber(line+loff), buffer.MTError)\n                end\n                buf:AddMessage(bmsg)\n            end\n        end\n    end\nend\n\nfunction split(str, sep)\n    local result = {}\n    local regex = (\"([^%s]+)\"):format(sep)\n    for each in str:gmatch(regex) do\n        table.insert(result, each)\n    end\n    return result\nend\n\nfunction basename(file)\n    local sep = \"/\"\n    if runtime.GOOS == \"windows\" then\n        sep = \"\\\\\"\n    end\n    local name = string.gsub(file, \"(.*\" .. sep .. \")(.*)\", \"%2\")\n    return name\nend\n"
  },
  {
    "path": "runtime/plugins/literate/README.md",
    "content": "# Literate Micro\n\nSupport for reading `.lit` files from [zyedidia/Literate](https://github.com/zyedidia/Literate).\n\nThis plugin will automatically detect the filetype and highlight the correct language inside the code blocks.\n"
  },
  {
    "path": "runtime/plugins/literate/literate.lua",
    "content": "VERSION = \"1.0.0\"\n\nlocal config = import(\"micro/config\")\n\nfunction startswith(str, start)\n   return string.sub(str,1,string.len(start))==start\nend\n\nfunction endswith(str, endStr)\n   return endStr=='' or string.sub(str,-string.len(endStr))==endStr\nend\n\nfunction split(string, sep)\n    local sep, fields = sep or \":\", {}\n    local pattern = string.format(\"([^%s]+)\", sep)\n    string:gsub(pattern, function(c) fields[#fields+1] = c end)\n    return fields\nend\n\nfunction onBufferOpen(buf)\n    if not endswith(buf.Path, \".lit\") then\n        return\n    end\n\n    local codetype = \"unknown\"\n    for i=1,buf:LinesNum() do\n        local line = buf:Line(i-1)\n        if startswith(line, \"@code_type\") then\n            codetype = split(line, \" \")[2]\n            break\n        end\n    end\n\n    local syntaxFile = \"\"\n    syntaxFile = syntaxFile .. \"filetype: literate-\" .. codetype .. \"\\n\"\n    syntaxFile = syntaxFile .. \"detect:\\n\"\n    syntaxFile = syntaxFile .. \"    filename: \\\"\\\\\\\\.lit$\\\"\\n\"\n    syntaxFile = syntaxFile .. \"rules:\\n\"\n    syntaxFile = syntaxFile .. \"    - include: \\\"markdown\\\"\\n\"\n    syntaxFile = syntaxFile .. \"    - special: \\\"^(@s|@title|@code_type|@comment_type|@include|@change|@change_end)\\\"\\n\"\n    syntaxFile = syntaxFile .. \"    - special: \\\"(@add_css|@overwrite_css|@colorscheme|@compiler|@error_format|@book)\\\"\\n\"\n    syntaxFile = syntaxFile .. \"    - default:\\n\"\n    syntaxFile = syntaxFile .. \"        start: \\\"---.*$\\\"\\n\"\n    syntaxFile = syntaxFile .. \"        end: \\\"---$\\\"\\n\"\n    syntaxFile = syntaxFile .. \"        limit-group: \\\"identifier\\\"\\n\"\n    syntaxFile = syntaxFile .. \"        rules:\\n\"\n    syntaxFile = syntaxFile .. \"            - special:\\n\"\n    syntaxFile = syntaxFile .. \"                start: \\\"@\\\\\\\\{\\\"\\n\"\n    syntaxFile = syntaxFile .. \"                end: \\\"\\\\\\\\}\\\"\\n\"\n    syntaxFile = syntaxFile .. \"            - include: \" .. codetype .. \"\\n\"\n\n    config.AddRuntimeFileFromMemory(config.RTSyntax, \"literate.yaml\", syntaxFile)\n    config.Reload()\n    buf:UpdateRules()\nend\n"
  },
  {
    "path": "runtime/plugins/status/help/status.md",
    "content": "# Status\n\nThe status plugin provides some functions for modifying the status line.\n\nUsing the `statusformatl` and `statusformatr` options, the exact contents\nof the status line can be modified. Please see the documentation for\nthose options (`> help options`) for more information.\n\nThis plugin provides functions that can be used in the status line format:\n\n* `status.branch`: returns the name of the current git branch in the repository\n   where the file is located.\n* `status.hash`: returns the hash of the current git commit in the repository\n   where the file is located.\n* `status.paste`: returns \"\" if the paste option is disabled and \"PASTE\"\n   if it is enabled.\n* `status.lines`: returns the number of lines in the buffer.\n* `status.vcol`: returns the visual column number of the cursor.\n* `status.bytes`: returns the number of bytes in the current buffer.\n* `status.size`: returns the size of the current buffer in a human-readable\n   format.\n"
  },
  {
    "path": "runtime/plugins/status/status.lua",
    "content": "VERSION = \"1.0.0\"\n\nlocal micro = import(\"micro\")\nlocal buffer = import(\"micro/buffer\")\nlocal config = import(\"micro/config\")\nlocal shell = import(\"micro/shell\")\nlocal filepath = import(\"filepath\")\nlocal humanize = import(\"humanize\")\nlocal strings = import(\"strings\")\n\nfunction init()\n    micro.SetStatusInfoFn(\"status.branch\")\n    micro.SetStatusInfoFn(\"status.hash\")\n    micro.SetStatusInfoFn(\"status.paste\")\n    micro.SetStatusInfoFn(\"status.vcol\")\n    micro.SetStatusInfoFn(\"status.lines\")\n    micro.SetStatusInfoFn(\"status.bytes\")\n    micro.SetStatusInfoFn(\"status.size\")\n    config.AddRuntimeFile(\"status\", config.RTHelp, \"help/status.md\")\nend\n\nfunction lines(b)\n    return tostring(b:LinesNum())\nend\n\nfunction vcol(b)\n    return tostring(b:GetActiveCursor():GetVisualX(false))\nend\n\nfunction bytes(b)\n    return tostring(b:Size())\nend\n\nfunction size(b)\n    return humanize.Bytes(b:Size())\nend\n\nlocal function parseRevision(b, opt)\n    if b.Type.Kind ~= buffer.BTInfo then\n        local dir = filepath.Dir(b.Path)\n        local str, err = shell.ExecCommand(\"git\", \"-C\", dir, \"rev-parse\", opt, \"HEAD\")\n        if err == nil then\n            return strings.TrimSpace(str)\n        end\n    end\n    return \"\"\nend\n\nfunction branch(b)\n    return parseRevision(b, \"--abbrev-ref\")\nend\n\nfunction hash(b)\n    return parseRevision(b, \"--short\")\nend\n\nfunction paste(b)\n    if config.GetGlobalOption(\"paste\") then\n        return \"PASTE \"\n    end\n    return \"\"\nend\n"
  },
  {
    "path": "runtime/runtime.go",
    "content": "package config\n\nimport (\n\t\"embed\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\n//go:generate go run syntax/make_headers.go syntax\n\n//go:embed colorschemes help plugins syntax\nvar runtime embed.FS\n\nfunc fixPath(name string) string {\n\treturn strings.TrimLeft(filepath.ToSlash(name), \"runtime/\")\n}\n\n// AssetDir lists file names in folder\nfunc AssetDir(name string) ([]string, error) {\n\tname = fixPath(name)\n\tentries, err := runtime.ReadDir(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tnames := make([]string, len(entries))\n\tfor i, entry := range entries {\n\t\tnames[i] = entry.Name()\n\t}\n\treturn names, nil\n}\n\n// Asset returns a file content\nfunc Asset(name string) ([]byte, error) {\n\tname = fixPath(name)\n\treturn runtime.ReadFile(name)\n}\n"
  },
  {
    "path": "runtime/runtime_test.go",
    "content": "package config\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestAssetDir(t *testing.T) {\n\tt.Parallel()\n\t// Test AssetDir\n\tentries, err := AssetDir(\"syntax\")\n\tassert.NoError(t, err)\n\tassert.Contains(t, entries, \"go.yaml\")\n\tassert.True(t, len(entries) > 5)\n}\n"
  },
  {
    "path": "runtime/syntax/LICENSE",
    "content": "Micro's syntax files are licensed under the MIT \"Expat\" License:\n\nCopyright (c) 2020: Zachary Yedidia, et al.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in 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 OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "runtime/syntax/PowerShell.yaml",
    "content": "# PowerShell syntax highlighting file for micro - https://micro-editor.github.io/\n# PowerShell syntax taken from: https://github.com/PowerShell/EditorSyntax\n\nfiletype: powershell\n\ndetect:\n    filename: \"\\\\.ps(1|m1|d1)$\"\n\nrules:\n    # - comment.block:           # Block Comment\n    # - comment.doc:             # Doc Comment\n    # - comment.line:            # Line Comment\n    # - comment.shebang:         # Shebang Line\n\n    # - constant:                # Constant\n    # - constant.bool:           # Constant (true, false)\n    # - constant.interpolation:\n    # - constant.number:         # Constant (null)\n    # - constant.specialChar:\n    # - constant.string:         # String\n    # - constant.string.char:\n    # - constant.string.url:     # Uri\n    # - constant.unicode:\n\n    # - identifier:              # Also used for functions\n    # - identifier.class:        # Also used for functions\n    # - identifier.macro:\n    # - identifier.var:\n\n    # - preproc:                 # Preprocessor\n    # - preproc.DebugIdentifier: # Preprocessor\n    # - preproc.shebang:         # The #! at the beginning of a file that tells the os what script interpreter to use\n\n    # - special:                 # Special (global|local|private|script|using|workflow)\n\n    # - statement:               # Statements Keywords\n    # - statement.built_in:\n    # - statement.declaration:   # Declaration Keywords\n    # - statement.meta:          # Meta\n    # - statement.reserved:      # Reserved Keywords\n\n    # - symbol\n    # - symbol.brackets:         # {}()[] and sometimes <>\n    # - symbol.operator:         # Operators\n    # - symbol.tag:              # For html tags, among other things\n\n    # - type\n    # - type.collections:        # Collections (array, hashtable)\n    # - type.ctypes:             # CTypes (CBool, CChar, etc.)\n    # - type.keyword:            # If you want a special highlight for keywords like 'private'\n    # - type.storage:            # Storage Types (int, uint, string, etc.)\n\n    # Class\n    - identifier.class: \"class +[A-Za-z0-9]+ *((:) +[A-Za-z0-9.]+)?\"\n    - identifier.class: \"(function)(?:([[:space:]][A-Za-z0-9]+[[:space:]]*))\"\n\n    # Verbs taken from PwSh 6.0.2\n    - identifier: \"(Add|Approve|Assert|Backup|Block|Build|Checkpoint|Clear|Close|Compare|Complete|Compress|Confirm|Connect|Convert|ConvertFrom|ConvertTo|Copy)[-][A-Za-z0-9]+\"\n    - identifier: \"(Debug|Deny|Deploy|Disable|Disconnect|Dismount|Edit|Enable|Enter|Exit|Expand|Export|Find|Format|Get|Grant|Group|Hide)[-][A-Za-z0-9]+\"\n    - identifier: \"(Import|Initialize|Install|Invoke|Join|Limit|Lock|Measure|Merge|Mount|Move|New|Open|Optimize|Out|Ping|Pop|Protect|Publish|Push)[-][A-Za-z0-9]+\"\n    - identifier: \"(Read|Receive|Redo|Register|Remove|Rename|Repair|Request|Reset|Resize|Resolve|Restart|Restore|Resume|Revoke)[-][A-Za-z0-9]+\"\n    - identifier: \"(Save|Search|Select|Send|Set|Show|Skip|Split|Start|Step|Stop|Submit|Suspend|Switch|Sync|Test|Trace)[-][A-Za-z0-9]+\"\n    - identifier: \"(Unblock|Undo|Uninstall|Unlock|Unprotect|Unpublish|Unregister|Update|Use|Wait|Watch|Write)[-][A-Za-z0-9]+\"\n    - identifier.var: \"\\\\$(?i)((Global|Local|Private|Script|Using|Workflow)[:])?[A-Za-z0-9]*\"\n\n\n    # Expression and types\n    - type: \"\\\\[\\\\b([A-Za-z]+|[A-Za-z]+[0-9]+)\\\\b\\\\]\"\n\n    # Keywords\n    - statement: \"\\\\b(alias|as|begin|break|catch|continue|data|default|define|do|dynamicparam)\\\\b\"\n    - statement: \"\\\\b(else|elseif|end|exit|finally|for|foreach|foreach-object|from|if|in|inlinescript)\\\\b\"\n    - statement: \"\\\\b(parallel|param|process|return|switch|throw|trap|try|until|using|var|where|where-object|while)\\\\b\"\n\n    # Special Keywords\n    - special: \"\\\\b(break|continue|exit)\\\\b\"\n\n    - symbol.brackets: \"(\\\\{|\\\\})\"\n    - symbol.brackets: \"(\\\\(|\\\\))\"\n    - symbol.brackets: \"(\\\\[|\\\\])\"\n    - symbol.operator: \"[\\\\-+/*=<>?:!~%&|]\"\n    - symbol.operator: \"[[:space:]][-](ne|eq|gt|ge|lt|le|like|notlike|match|notmatch|contains|notcontains|in|notin|replace|is|isnot)[[:space:]]\"\n\n    # Constants\n    - constant.bool: \"\\\\b\\\\$(true|false|null)\\\\b\"\n    - constant.number: \"\\\\b([0-9._]+|0x[A-Fa-f0-9_]+|0b[0-1_]+)[FL]?\\\\b\"\n\n    # Expression Mode String\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        #skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([btnfr]|'|\\\\\\\"|\\\\\\\\)\"\n            - constant.specialChar: \"\\\\\\\\u[A-Fa-f0-9]{4}\"\n\n    # Argument Mode String\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        #skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([btnfr]|'|\\\\\\\"|\\\\\\\\)\"\n            - constant.specialChar: \"\\\\\\\\u[A-Fa-f0-9]{4}\"\n\n    # Line Comment\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME|BUG):?\"\n\n    # Block Comment\n    - comment:\n        start: \"<#\"\n        end: \"#>\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME|BUG):?\"\n\n    # Embedded C#\n    - default:\n        start: \"@\\\"\"\n        end: \"\\\"@\"\n        rules:\n            - include: \"csharp\"\n\n    # Todo\n    - todo: \"(TODO|XXX|FIXME|BUG):?\"\n"
  },
  {
    "path": "runtime/syntax/README.md",
    "content": "# Syntax Files\n\nHere are micro's syntax files.\n\nEach yaml file specifies how to detect the filetype based on file extension or header (first line of the line).\nIn addition, a signature can be provided to help resolving ambiguities when multiple matching filetypes are detected.\nThen there are patterns and regions linked to highlight groups which tell micro how to highlight that filetype.\n\nYou can read more about how to write syntax files (and colorschemes) in the [colors](../help/colors.md) documentation.\n\n# Legacy '.micro' filetype\n\nMicro used to use the `.micro` filetype for syntax files which is no longer supported. If you have `.micro`\nsyntax files that you would like to convert to the new filetype, you can use the [`syntax_converter.go`](./syntax_converter.go) program (also located in this directory):\n\n```\n$ go run syntax_converter.go c.micro > c.yaml\n```\n\nMost of the syntax files here have been converted using that tool.\n\nNote that the tool isn't perfect and though it is unlikely, you may run into some small issues that you will have to fix manually\n(about 4 files from this directory had issues after being converted).\n\n# Micro syntax highlighting files\n\nThese are the syntax highlighting files for micro. To install them, just\nput all the syntax files in `~/.config/micro/syntax`.\n\nThey are taken from Nano, specifically from [this repository](https://github.com/scopatz/nanorc).\nMicro syntax files are almost identical to Nano's, except for some key differences:\n\n* Micro does not use `icolor`. Instead, for a case insensitive match, use the case insensitive flag (`i`) in the regular expression\n    * For example, `icolor green \".*\"` would become `color green \"(?i).*\"`\n\n# Using with colorschemes\n\nNot all of these files have been converted to use micro's colorscheme feature. Most of them just hardcode the colors, which can be problematic depending on the colorscheme you use.\n\nHere is a list of the files that have been converted to properly use colorschemes:\n\n* vi\n* go\n* c\n* d\n* markdown\n* html\n* lua\n* swift\n* rust\n* java\n* javascript\n* pascal\n* python\n* ruby\n* sh\n* git\n* tex\n* solidity\n\n# License\n\nSee [LICENSE](LICENSE).\n"
  },
  {
    "path": "runtime/syntax/ada.yaml",
    "content": "filetype: ada\n\ndetect:\n    filename: \"(\\\\.ads$|\\\\.adb$|\\\\.ada$)\"\n\nrules:\n    # Operators\n    - symbol.operator: ([.:;,+*|=!?\\\\%]|<|>|/|-|&)\n    - symbol.brackets: \"[(){}]|\\\\[|\\\\]\"\n    # keyword.reserved\n    - statement.reserved: \\b(abort|abs|abstract|accept|access|aliased|all|and|array|at|begin|body|case)\\b\n    - statement.reserved: \\b(constant|declare|delay|delta|digits|do|else|elsif|end|entry|exception|exit|for|function)\\b\n    - statement.reserved: \\b(generic|goto|if|in|interface|is|limited|loop|mod|new|not|null|of|or|others|out|overriding)\\b\n    - statement.reserved: \\b(package|pragma|private|procedure|protected|raise|range|record|rem|renames|return|requeue)\\b\n    - statement.reserved: \\b(reverse|select|separate|some|subtype|synchronized|tagged|task|terminate|then|type|until)\\b\n    - statement.reserved: \\b(use|when|while|with|xor)\\b\n\n    # Constant\n    - constant.bool: \\b(TRUE|FALSE)\n    - constant.number: ([0-9]+)\n\n    # Storage Types\n    - type.storage: \\b(INTEGER|NATURAL|POSITIVE|FLOAT|CHARACTER|STRING)\\b\n    - type.storage: \\b(LONG_INTEGER|SHORT_INTEGER|LONG_FLOAT|SHORT_FLOAT)\\b\n\n    #Character\n    - constant.string.char: \\'.\\'\n\n    # String\n    - constant.string:\n        start: \\\"\n        end: \\\"\n        skip: \\\\.\n        rules:\n            - constant.specialChar: (\\\\0|\\\\\\\\|\\\\t|\\\\n|\\\\r|\\\\\"|\\\\')\n            - constant.interpolation: \\\\\\([[:graph:]]*\\)\n            - constant.unicode: \\\\u\\{[[:xdigit:]]+}\n\n    # Line Comment\n    - comment.line: \"--.*\"\n\n    # Todo\n    - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/apacheconf.yaml",
    "content": "filetype: apacheconf\n\ndetect:\n    filename: \"httpd\\\\.conf|mime\\\\.types|vhosts\\\\.d\\\\\\\\*|\\\\.htaccess\"\n\nrules:\n    - identifier: \"(AcceptMutex|AcceptPathInfo|AccessFileName|Action|AddAlt|AddAltByEncoding|AddAltByType|AddCharset|AddDefaultCharset|AddDescription|AddEncoding)\"\n    - identifier: \"(AddHandler|AddIcon|AddIconByEncoding|AddIconByType|AddInputFilter|AddLanguage|AddModuleInfo|AddOutputFilter|AddOutputFilterByType|AddType|Alias|AliasMatch)\"\n    - identifier: \"(Allow|AllowCONNECT|AllowEncodedSlashes|AllowOverride|Anonymous|Anonymous_Authoritative|Anonymous_LogEmail|Anonymous_MustGiveEmail|Anonymous_NoUserID)\"\n    - identifier: \"(Anonymous_VerifyEmail|AssignUserID|AuthAuthoritative|AuthDBMAuthoritative|AuthDBMGroupFile|AuthDBMType|AuthDBMUserFile|AuthDigestAlgorithm)\"\n    - identifier: \"(AuthDigestDomain|AuthDigestFile|AuthDigestGroupFile|AuthDigestNcCheck|AuthDigestNonceFormat|AuthDigestNonceLifetime|AuthDigestQop|AuthDigestShmemSize)\"\n    - identifier: \"(AuthGroupFile|AuthLDAPAuthoritative|AuthLDAPBindDN|AuthLDAPBindPassword|AuthLDAPCharsetConfig|AuthLDAPCompareDNOnServer|AuthLDAPDereferenceAliases)\"\n    - identifier: \"(AuthLDAPEnabled|AuthLDAPFrontPageHack|AuthLDAPGroupAttribute|AuthLDAPGroupAttributeIsDN|AuthLDAPRemoteUserIsDN|AuthLDAPUrl|AuthName|AuthType|AuthUserFile)\"\n    - identifier: \"(BrowserMatch|BrowserMatchNoCase|BS2000Account|BufferedLogs|CacheDefaultExpire|CacheDirLength|CacheDirLevels|CacheDisable|CacheEnable|CacheExpiryCheck)\"\n    - identifier: \"(CacheFile|CacheForceCompletion|CacheGcClean|CacheGcDaily|CacheGcInterval|CacheGcMemUsage|CacheGcUnused|CacheIgnoreCacheControl|CacheIgnoreHeaders)\"\n    - identifier: \"(CacheIgnoreNoLastMod|CacheLastModifiedFactor|CacheMaxExpire|CacheMaxFileSize|CacheMinFileSize|CacheNegotiatedDocs|CacheRoot|CacheSize|CacheTimeMargin)\"\n    - identifier: \"(CGIMapExtension|CharsetDefault|CharsetOptions|CharsetSourceEnc|CheckSpelling|ChildPerUserID|ContentDigest|CookieDomain|CookieExpires|CookieLog|CookieName)\"\n    - identifier: \"(CookieStyle|CookieTracking|CoreDumpDirectory|CustomLog|Dav|DavDepthInfinity|DavLockDB|DavMinTimeout|DefaultIcon|DefaultLanguage|DefaultType)\"\n    - identifier: \"(DeflateBufferSize|DeflateCompressionLevel|DeflateFilterNote|DeflateMemLevel|DeflateWindowSize|Deny|Directory|DirectoryIndex|DirectoryMatch|DirectorySlash)\"\n    - identifier: \"(DocumentRoot|DumpIOInput|DumpIOOutput|EnableExceptionHook|EnableMMAP|EnableSendfile|ErrorDocument|ErrorLog|Example|ExpiresActive|ExpiresByType)\"\n    - identifier: \"(ExpiresDefault|ExtendedStatus|ExtFilterDefine|ExtFilterOptions|FileETag|Files|FilesMatch|ForceLanguagePriority|ForceType|ForensicLog|Group|Header)\"\n    - identifier: \"(HeaderName|HostnameLookups|IdentityCheck|IfDefine|IfModule|IfVersion|ImapBase|ImapDefault|ImapMenu|Include|IndexIgnore|IndexOptions|IndexOrderDefault)\"\n    - identifier: \"(ISAPIAppendLogToErrors|ISAPIAppendLogToQuery|ISAPICacheFile|ISAPIFakeAsync|ISAPILogNotSupported|ISAPIReadAheadBuffer|KeepAlive|KeepAliveTimeout)\"\n    - identifier: \"(LanguagePriority|LDAPCacheEntries|LDAPCacheTTL|LDAPConnectionTimeout|LDAPOpCacheEntries|LDAPOpCacheTTL|LDAPSharedCacheFile|LDAPSharedCacheSize)\"\n    - identifier: \"(LDAPTrustedCA|LDAPTrustedCAType|Limit|LimitExcept|LimitInternalRecursion|LimitRequestBody|LimitRequestFields|LimitRequestFieldSize|LimitRequestLine)\"\n    - identifier: \"(LimitXMLRequestBody|Listen|ListenBackLog|LoadFile|LoadModule|Location|LocationMatch|LockFile|LogFormat|LogLevel|MaxClients|MaxKeepAliveRequests)\"\n    - identifier: \"(MaxMemFree|MaxRequestsPerChild|MaxRequestsPerThread|MaxSpareServers|MaxSpareThreads|MaxThreads|MaxThreadsPerChild|MCacheMaxObjectCount|MCacheMaxObjectSize)\"\n    - identifier: \"(MCacheMaxStreamingBuffer|MCacheMinObjectSize|MCacheRemovalAlgorithm|MCacheSize|MetaDir|MetaFiles|MetaSuffix|MimeMagicFile|MinSpareServers|MinSpareThreads)\"\n    - identifier: \"(MMapFile|ModMimeUsePathInfo|MultiviewsMatch|NameVirtualHost|NoProxy|NumServers|NWSSLTrustedCerts|NWSSLUpgradeable|Options|Order|PassEnv|PidFile)\"\n    - identifier: \"(ProtocolEcho|Proxy|ProxyBadHeader|ProxyBlock|ProxyDomain|ProxyErrorOverride|ProxyIOBufferSize|ProxyMatch|ProxyMaxForwards|ProxyPass|ProxyPassReverse)\"\n    - identifier: \"(ProxyPreserveHost|ProxyReceiveBufferSize|ProxyRemote|ProxyRemoteMatch|ProxyRequests|ProxyTimeout|ProxyVia|ReadmeName|Redirect|RedirectMatch)\"\n    - identifier: \"(RedirectPermanent|RedirectTemp|RemoveCharset|RemoveEncoding|RemoveHandler|RemoveInputFilter|RemoveLanguage|RemoveOutputFilter|RemoveType|RequestHeader)\"\n    - identifier: \"(Require|RewriteBase|RewriteCond|RewriteEngine|RewriteLock|RewriteLog|RewriteLogLevel|RewriteMap|RewriteOptions|RewriteRule|RLimitCPU|RLimitMEM|RLimitNPROC)\"\n    - identifier: \"(Satisfy|ScoreBoardFile|Script|ScriptAlias|ScriptAliasMatch|ScriptInterpreterSource|ScriptLog|ScriptLogBuffer|ScriptLogLength|ScriptSock|SecureListen)\"\n    - identifier: \"(SendBufferSize|ServerAdmin|ServerAlias|ServerLimit|ServerName|ServerPath|ServerRoot|ServerSignature|ServerTokens|SetEnv|SetEnvIf|SetEnvIfNoCase|SetHandler)\"\n    - identifier: \"(SetInputFilter|SetOutputFilter|SSIEndTag|SSIErrorMsg|SSIStartTag|SSITimeFormat|SSIUndefinedEcho|SSLCACertificateFile|SSLCACertificatePath)\"\n    - identifier: \"(SSLCARevocationFile|SSLCARevocationPath|SSLCertificateChainFile|SSLCertificateFile|SSLCertificateKeyFile|SSLCipherSuite|SSLEngine|SSLMutex|SSLOptions)\"\n    - identifier: \"(SSLPassPhraseDialog|SSLProtocol|SSLProxyCACertificateFile|SSLProxyCACertificatePath|SSLProxyCARevocationFile|SSLProxyCARevocationPath|SSLProxyCipherSuite)\"\n    - identifier: \"(SSLProxyEngine|SSLProxyMachineCertificateFile|SSLProxyMachineCertificatePath|SSLProxyProtocol|SSLProxyVerify|SSLProxyVerifyDepth|SSLRandomSeed|SSLRequire)\"\n    - identifier: \"(SSLRequireSSL|SSLSessionCache|SSLSessionCacheTimeout|SSLUserName|SSLVerifyClient|SSLVerifyDepth|StartServers|StartThreads|SuexecUserGroup|ThreadLimit)\"\n    - identifier: \"(ThreadsPerChild|ThreadStackSize|TimeOut|TraceEnable|TransferLog|TypesConfig|UnsetEnv|UseCanonicalName|User|UserDir|VirtualDocumentRoot)\"\n    - identifier: \"(VirtualDocumentRootIP|VirtualHost|VirtualScriptAlias|VirtualScriptAliasIP|Win32DisableAcceptEx|XBitHack)\"\n    - symbol.tag: \"<[^>]+>\"\n    - identifier: \"</?[A-Za-z]+\"\n    - identifier: \"(<|</|>)\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n"
  },
  {
    "path": "runtime/syntax/arduino.yaml",
    "content": "filetype: ino\n\ndetect:\n    filename: \"\\\\.ino$\"\n\nrules:\n    - identifier: \"\\\\b[A-Z_][0-9A-Z_]+\\\\b\"\n\n      ## Sized (u)int types\n    - type: \"\\\\b((s?size)|((u_?)?int(8|16|32|64|ptr)))_t\\\\b\"\n\n      ## Constants\n    - constant: \"(?i)\\\\b(HIGH|LOW|INPUT|OUTPUT)\\\\b\"\n\n      ## Serial Print\n    - constant: \"(?i)\\\\b(DEC|BIN|HEX|OCT|BYTE)\\\\b\"\n\n      ## PI Constants\n    - constant: \"(?i)\\\\b(PI|HALF_PI|TWO_PI)\\\\b\"\n\n      ## ShiftOut\n    - constant: \"(?i)\\\\b(LSBFIRST|MSBFIRST)\\\\b\"\n\n      ## Attach Interrupt\n    - constant: \"(?i)\\\\b(CHANGE|FALLING|RISING)\\\\b\"\n\n      ## Analog Reference\n    - constant: \"(?i)\\\\b(DEFAULT|EXTERNAL|INTERNAL|INTERNAL1V1|INTERNAL2V56)\\\\b\"\n\n      ## === FUNCTIONS === ##\n\n      ## Data Types\n    - type: \"\\\\b(boolean|byte|char|float|int|long|word)\\\\b\"\n\n      ## Control Structions\n    - statement: \"\\\\b(case|class|default|do|double|else|false|for|if|new|null|private|protected|public|short|signed|static|String|switch|this|throw|try|true|unsigned|void|while)\\\\b\"\n    - statement: \"\\\\b(goto|continue|break|return)\\\\b\"\n\n      ## Math\n    - identifier: \"\\\\b(abs|acos|asin|atan|atan2|ceil|constrain|cos|degrees|exp|floor|log|map|max|min|radians|random|randomSeed|round|sin|sq|sqrt|tan)\\\\b\"\n\n      ## Bits & Bytes\n    - identifier: \"\\\\b(bitRead|bitWrite|bitSet|bitClear|bit|highByte|lowByte)\\\\b\"\n\n      ## Analog I/O\n    - identifier: \"\\\\b(analogReference|analogRead|analogWrite)\\\\b\"\n\n      ## External Interrupts\n    - identifier: \"\\\\b(attachInterrupt|detachInterrupt)\\\\b\"\n\n      ## Time\n    - identifier: \"\\\\b(delay|delayMicroseconds|millis|micros)\\\\b\"\n\n      ## Digital I/O\n    - identifier: \"\\\\b(pinMode|digitalWrite|digitalRead)\\\\b\"\n\n      ## Interrupts\n    - identifier: \"\\\\b(interrupts|noInterrupts)\\\\b\"\n\n      ## Advanced I/O\n    - identifier: \"\\\\b(noTone|pulseIn|shiftIn|shiftOut|tone)\\\\b\"\n\n      ## Serial\n    - identifier: \"\\\\b(Serial|Serial1|Serial2|Serial3|begin|end|peek|read|print|println|available|flush)\\\\b\"\n\n      ## Structure\n    - identifier: \"\\\\b(setup|loop)\\\\b\"\n\n      ##\n    - statement: \"^[[:space:]]*#[[:space:]]*(define|include(_next)?|(un|ifn?)def|endif|el(if|se)|if|warning|error|pragma)\"\n\n      ## GCC builtins\n    - constant: \"(__attribute__[[:space:]]*\\\\(\\\\([^)]*\\\\)\\\\)|__(aligned|asm|builtin|hidden|inline|packed|restrict|section|typeof|weak)__)\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - preproc: \"..+\"\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n"
  },
  {
    "path": "runtime/syntax/asciidoc.yaml",
    "content": "filetype: asciidoc\n\ndetect:\n    filename: \"\\\\.(asc|asciidoc|adoc)$\"\n\nrules:\n    # main header\n    - preproc: \"^====+$\"\n      # h1\n    - statement: \"^==[[:space:]].*$\"\n    - statement: \"^----+$\"\n      # h2\n    - symbol: \"^===[[:space:]].*$\"\n    - symbol: \"^~~~~+$\"\n      # h4\n    - type: \"^====[[:space:]].*$\"\n    - type: \"^\\\\^\\\\^\\\\^\\\\^+$\"\n      # h5\n    - constant: \"^=====[[:space:]].*$\"\n    - constant: \"^\\\\+\\\\+\\\\+\\\\++$\"\n\n      # attributes\n    - type.keyword: \":.*:\"\n    - identifier.macro: \"\\\\{[a-z0-9]*\\\\}\"\n    - identifier: \"\\\\\\\\\\\\{[a-z0-9]*\\\\}\"\n    - identifier: \"\\\\+\\\\+\\\\+\\\\{[a-z0-9]*\\\\}\\\\+\\\\+\\\\+\"\n\n      # Paragraph Title\n    - statement: \"^\\\\..*$\"\n\n      # source\n    - identifier: \"^\\\\[(source,.+|NOTE|TIP|IMPORTANT|WARNING|CAUTION)\\\\]\"\n\n      # Other markup\n    - constant.string: \".*[[:space:]]\\\\+$\"\n    - constant.string: \"_[^_]+_\"\n    - constant.string: \"\\\\*[^\\\\*]+\\\\*\"\n    - constant.string: \"\\\\+[^\\\\+]+\\\\+\"\n    - constant.string: \"`[^`]+`\"\n    - constant.string: \"\\\\^[^\\\\^]+\\\\^\"\n    - constant.string: \"~[^~]+~\"\n    - constant.string: \"'[^']+'\"\n\n    - constant: \"`{1,2}[^']+'{1,2}\"\n\n      # bullets\n    - symbol: \"^[[:space:]]*[\\\\*\\\\.-]{1,5}[[:space:]]\"\n\n      # anchors\n    - \"bold default\": \"\\\\[\\\\[.*\\\\]\\\\]\"\n    - \"bold default\": \"<<.*>>\"\n"
  },
  {
    "path": "runtime/syntax/asm.yaml",
    "content": "filetype: asm\n\ndetect:\n    filename: \"\\\\.(S|s|asm)$\"\n\nrules:\n    # This file is made mainly for NASM assembly\n\n    ## Instructions\n    # x86\n    - statement: \"\\\\b(?i)(mov|aaa|aad|aam|aas|adc|add|and|call|cbw|clc|cld|cli|cmc|cmp|cmpsb|cmpsw|cwd|daa|das|dec|div|esc|hlt|idiv|imul|in|inc|int|into|iret|ja|jae|jb|jbe|jc|je|jg|jge|jl|jle|jna|jnae|jnb|jnbe|jnc|jne|jng|jnge|jnl|jnle|jno|jnp|jns|jnz|jo|jp|jpe|jpo|js|jz|jcxz|jmp|lahf|lds|lea|les|lock|lodsb|lodsw|loop|loope|loopne|loopnz|loopz|movsb|movsw|mul|neg|nop|or|pop|popf|push|pushf|rcl|rcr|rep|repe|repne|repnz|repz|ret|retn|retf|rol|ror|sahf|sal|sar|sbb|scasb|scasw|shl|shr|stc|std|sti|stosb|stosw|sub|test|wait|xchg|xlat|xor)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(bound|enter|ins|leave|outs|popa|pusha)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(arpl|clts|lar|lgdt|lidt|lldt|lmsw|loadall|lsl|ltr|sgdt|sidt|sldt|smsw|str|verr|verw)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(bsf|bsr|bt|btc|btr|bts|cdq|cmpsd|cwde|insd|iret|iretd|iretf|jecxz|lfs|lgs|lss|lodsd|loopw|loopew|loopnew|loopnzw|loopzw|loopd|looped|loopned|loopnzd|loopzd|cr|tr|dr|movsd|movsx|movzx|outsd|popad|popfd|pushad|pushfd|scasd|seta|setae|setb|setbe|setc|sete|setg|setge|setl|setle|setna|setnae|setnb|setnbe|setnc|setne|setng|setnge|setnl|setnle|setno|setnp|setns|setnz|seto|setp|setpe|setpo|sets|setz|shdl|shrd|stosd)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(bswap|cmpxcgh|invd|invlpg|wbinvd|xadd)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(cpuid|cmpxchg8b|rdmsr|rdtsc|wrmsr|rsm)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(rdpmc)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(syscall|sysret)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(cmova|cmovae|cmovb|cmovbe|cmovc|cmove|cmovg|cmovge|cmovl|cmovle|cmovna|cmovnae|cmovnb|cmovnbe|cmovnc|cmovne|cmovng|cmovnge|cmovnle|cmovno|cmovpn|cmovns|cmovnz|cmovo|cmovp|cmovpe|cmovpo|cmovs|cmovz|sysenter|sysexit|ud2)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(maskmovq|movntps|movntq|prefetch0|prefetch1|prefetch2|prefetchnta|sfence)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(clflush|lfence|maskmovdqu|mfence|movntdq|movnti|movntpd|pause)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(monitor|mwait)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(cdqe|cqo|cmpsq|cmpxchg16b|iretq|jrcxz|lodsq|movsdx|popfq|pushfq|rdtscp|scasq|stosq|swapgs)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(clgi|invlpga|skinit|stgi|vmload|vmmcall|vmrun|vmsave)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(vmptrdl|vmptrst|vmclear|vmread|vmwrite|vmcall|vmlaunch|vmresume|vmxoff|vmxon)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(lzcnt|popcnt)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(bextr|blcfill|blci|blcic|blcmask|blcs|blsfill|blsic|t1mskc|tzmsk)(?-i)\\\\b\"\n\n      # x87\n    - statement: \"\\\\b(?i)(f2xm1|fabs|fadd|faddp|fbld|fbstp|fchs|fclex|fcom|fcomp|fcompp|fdecstp|fdisi|fdiv|fvidp|fdivr|fdivrp|feni|ffree|fiadd|ficom|ficomp|fidiv|fidivr|fild|fimul|fincstp|finit|fist|fistp|fisub|fisubr|fld|fld1|fldcw|fldenv|fldenvw|fldl2e|fldl2t|fldlg2|fldln2|fldpi|fldz|fmul|fmulp|fnclex|fndisi|fneni|fninit|fnop|fnsave|fnsavenew|fnstcw|fnstenv|fnstenvw|fnstsw|fpatan|fprem|fptan|frndint|frstor|frstorw|fsave|fsavew|fscale|fsqrt|fst|fstcw|fstenv|fstenvw|fstp|fstpsw|fsub|fsubp|fsubr|fsubrp|ftst|fwait|fxam|fxch|fxtract|fyl2x|fyl2xp1)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(fsetpm)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(fcos|fldenvd|fsaved|fstenvd|fprem1|frstord|fsin|fsincos|fstenvd|fucom|fucomp|fucompp)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(fcmovb|fcmovbe|fcmove|fcmove|fcmovnb|fcmovnbe|fcmovne|fcmovnu|fcmovu)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(fcomi|fcomip|fucomi|fucomip)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(fxrstor|fxsave)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(fisttp)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(ffreep)(?-i)\\\\b\"\n\n      # SIMD\n    - statement: \"\\\\b(?i)(emms|movd|movq|packssdw|packsswb|packuswb|paddb|paddw|paddd|paddsb|paddsw|paddusb|paddusw|pand|pandn|por|pxor|pcmpeqb|pcmpeqw|pcmpeqd|pcmpgtb|pcmpgtw|pcmpgtd|pmaddwd|pmulhw|pmullw|psllw|pslld|psllq|psrad|psraw|psrlw|psrld|psrlq|psubb|psubw|psubd|psubsb|psubsw|psubusb|punpckhbw|punpckhwd|punpckhdq|punkcklbw|punpckldq|punpcklwd)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(paveb|paddsiw|pmagw|pdistib|psubsiw|pmwzb|pmulhrw|pmvnzb|pmvlzb|pmvgezb|pmulhriw|pmachriw)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(femms|pavgusb|pf2id|pfacc|pfadd|pfcmpeq|pfcmpge|pfcmpgt|pfmax|pfmin|pfmul|pfrcp|pfrcpit1|pfrcpit2|pfrsqit1|pfrsqrt|pfsub|pfsubr|pi2fd|pmulhrw|prefetch|prefetchw)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(pf2iw|pfnacc|pfpnacc|pi2fw|pswapd)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(pfrsqrtv|pfrcpv)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(addps|addss|cmpps|cmpss|comiss|cvtpi2ps|cvtps2pi|cvtsi2ss|cvtss2si|cvttps2pi|cvttss2si|divps|divss|ldmxcsr|maxps|maxss|minps|minss|movaps|movhlps|movhps|movlhps|movlps|movmskps|movntps|movss|movups|mulps|mulss|rcpps|rcpss|rsqrtps|rsqrtss|shufps|sqrtps|sqrtss|stmxcsr|subps|subss|ucomiss|unpckhps|unpcklps)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(andnps|andps|orps|pavgb|pavgw|pextrw|pinsrw|pmaxsw|pmaxub|pminsw|pminub|pmovmskb|pmulhuw|psadbw|pshufw|xorps)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(movups|movss|movlps|movhlps|movlps|unpcklps|unpckhps|movhps|movlhps|prefetchnta|prefetch0|prefetch1|prefetch2|nop|movaps|cvtpi2ps|cvtsi2ss|cvtps2pi|cvttss2si|cvtps2pi|cvtss2si|ucomiss|comiss|sqrtps|sqrtss|rsqrtps|rsqrtss|rcpps|andps|orps|xorps|addps|addss|mulps|mulss|subps|subss|minps|minss|divps|divss|maxps|maxss|pshufw|ldmxcsr|stmxcsr|sfence|cmpps|cmpss|pinsrw|pextrw|shufps|pmovmskb|pminub|pmaxub|pavgb|pavgw|pmulhuw|movntq|pminsw|pmaxsw|psadbw|maskmovq)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(addpd|addsd|addnpd|cmppd|cmpsd)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(addpd|addsd|andnpd|andpd|cmppd|cmpsd|comisd|cvtdq2pd|cvtdq2ps|cvtpd2dq|cvtpd2pi|cvtpd2ps|cvtpi2pd|cvtps2dq|cvtps2pd|cvtsd2si|cvtsd2ss|cvtsi2sd|cvtss2sd|cvttpd2dq|cvttpd2pi|cvttps2dq|cvttsd2si|divpd|divsd|maxpd|maxsd|minpd|minsd|movapd|movhpd|movlpd|movmskpd|movsd|movupd|mulpd|mulsd|orpd|shufpd|sqrtpd|sqrtsd|subpd|subsd|ucomisd|unpckhpd|unpcklpd|xorpd)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(movdq2q|movdqa|movdqu|movq2dq|paddq|psubq|pmuludq|pshufhw|pshuflw|pshufd|pslldq|psrldq|punpckhqdq|punpcklqdq)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(addsubpd|addsubps|haddpd|haddps|hsubpd|hsubps|movddup|movshdup|movsldu)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(lddqu)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(psignw|psignd|psignb|pshufb|pmulhrsw|pmaddubsw|phsubw|phsubsw|phsubd|phaddw|phaddsw|phaddd|palignr|pabsw|pabsd|pabsb)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(dpps|dppd|blendps|blendpd|blendvps|blendvpd|roundps|roundss|roundpd|roundsd|insertps|extractps)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(mpsadbw|phminposuw|pmulld|pmuldq|pblendvb|pblendw|pminsb|pmaxsb|pminuw|pmaxuw|pminud|pmaxud|pminsd|pmaxsd|pinsrb|pinsrd/pinsrq|pextrb|pextrw|pextrd/pextrq|pmovsxbw|pmovzxbw|pmovsxbd|pmovzxbd|pmovsxbq|pmovzxbq|pmovsxwd|pmovzxwd|pmovsxwq|pmovzxwq|pmovsxdq|pmovzxdq|ptest|pcmpeqq|packusdw|movntdqa)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(extrq|insertq|movntsd|movntss)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(crc32|pcmpestri|pcmpestrm|pcmpistri|pcmpistrm|pcmpgtq)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(vfmaddpd|vfmaddps|vfmaddsd|vfmaddss|vfmaddsubpd|vfmaddsubps|vfmsubaddpd|vfmsubaddps|vfmsubpd|vfmsubps|vfmsubsd|vfmsubss|vfnmaddpd|vfnmaddps|vfnmaddsd|vfnmaddss|vfnmsubps|vfnmsubsd|vfnmsubss)(?-i)\\\\b\"\n\n      # Crypto\n    - statement: \"\\\\b(?i)(aesenc|aesenclast|aesdec|aesdeclast|aeskeygenassist|aesimc)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(sha1rnds4|sha1nexte|sha1msg1|sha1msg2|sha256rnds2|sha256msg1|sha256msg2)(?-i)\\\\b\"\n\n      # Undocumented\n    - statement: \"\\\\b(?i)(aam|aad|salc|icebp|loadall|loadalld|ud1)(?-i)\\\\b\"\n\n      ## Registers\n    - identifier: \"\\\\b(?i)(al|ah|bl|bh|cl|ch|dl|dh|bpl|sil|r8b|r9b|r10b|r11b|dil|spl|r12b|r13b|r14b|r15)(?-i)\\\\b\"\n    - identifier: \"\\\\b(?i)(cw|sw|tw|fp_ds|fp_opc|fp_ip|fp_dp|fp_cs|cs|ss|ds|es|fs|gs|gdtr|idtr|tr|ldtr|ax|bx|cx|dx|bp|si|r8w|r9w|r10w|r11w|di|sp|r12w|r13w|r14w|r15w|ip)(?-i)\\\\b\"\n    - identifier: \"\\\\b(?i)(fp_dp|fp_ip|eax|ebx|ecx|edx|ebp|esi|r8d|r9d|r10d|r11d|edi|esp|r12d|r13d|r14d|r15d|eip|eflags|mxcsr)(?-i)\\\\b\"\n    - identifier: \"\\\\b(?i)(mm0|mm1|mm2|mm3|mm4|mm5|mm6|mm7|rax|rbx|rcx|rdx|rbp|rsi|r8|r9|r10|r11|rdi|rsp|r12|r13|r14|r15|rip|rflags|cr0|cr1|cr2|cr3|cr4|cr5|cr6|cr7|cr8|cr9|cr10|cr11|cr12|cr13|cr14|cr15|msw|dr0|dr1|dr2|dr3|r4|dr5|dr6|dr7|dr8|dr9|dr10|dr11|dr12|dr13|dr14|dr15)(?-i)\\\\b\"\n    - identifier: \"\\\\b(?i)(st0|st1|st2|st3|st4|st5|st6|st7)(?-i)\\\\b\"\n    - identifier: \"\\\\b(?i)(xmm0|xmm1|xmm2|xmm3|xmm4|xmm5|xmm6|xmm7|xmm8|xmm9|xmm10|xmm11|xmm12|xmm13|xmm14|xmm15)(?-i)\\\\b\"\n    - identifier: \"\\\\b(?i)(ymm0|ymm1|ymm2|ymm3|ymm4|ymm5|ymm6|ymm7|ymm8|ymm9|ymm10|ymm11|ymm12|ymm13|ymm14|ymm15)(?-i)\\\\b\"\n    - identifier: \"\\\\b(?i)(zmm0|zmm1|zmm2|zmm3|zmm4|zmm5|zmm6|zmm7|zmm8|zmm9|zmm10|zmm11|zmm12|zmm13|zmm14|zmm15|zmm16|zmm17|zmm18|zmm19|zmm20|zmm21|zmm22|zmm23|zmm24|zmm25|zmm26|zmm27|zmm28|zmm29|zmm30|zmm31)(?-i)\\\\b\"\n\n      ## Constants\n      # Number - it works\n    - constant.number: \"\\\\b(|h|A|0x)+[0-9]+(|h|A)+\\\\b\"\n    - constant.number: \"\\\\b0x[0-9 a-f A-F]+\\\\b\"\n\n      ## Preprocessor (NASM)\n    - preproc: \"%+(\\\\+|\\\\?|\\\\?\\\\?|)[a-z A-Z 0-9]+\"\n    - preproc: \"%\\\\[[. a-z A-Z 0-9]*\\\\]\"\n\n      ## Other\n    - statement: \"\\\\b(?i)(extern|global|section|segment|_start|\\\\.text|\\\\.data|\\\\.bss)(?-i)\\\\b\"\n    - statement: \"\\\\b(?i)(db|dw|dd|dq|dt|ddq|do)(?-i)\\\\b\"\n    - identifier: \"[a-z A-Z 0-9 _]+:\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \";\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    ## C-like comments (supported by some assemblers)\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/ats.yaml",
    "content": "filetype: ATS\n\ndetect:\n    filename: \"\\\\.(d|h|s)ats$\"\n\nrules:\n    - default: \\b[[:alnum:]]+[^0-9A-Za-z]\n\n    # Operators\n    - symbol.operator: \"[.:;+`|~<>?='\\\\&]|/|%|-|,|!|\\\\*|@|\\\\#\"\n    - symbol.operator: \\^\n\n    # Types, abstract types and some predefined sorts\n    - type: \\b(addr|age?z|bool|char|cls|schar|uchar|double|ldouble|eff|exn|float|int(ptr)?|lincloptr|uint)\\b\n    - type: \\b(lint|ulint|llint|ullint|nat|ptr|real|ref|size_t|ssize_t|sint|usint|string|tkind|viewt|v?t0p|vt|void)\\b\n    - type: \\b(prop|t[@0]ype|type|viewt[@0]ype|viewtype|vt[@0]ype|vtype|view)\\b\n    - type: \\b(prop[+-]|t[@0]ype[+-]|type[+-]|viewt[@0]ype[+-]|viewtype[+-]|vt[@0]ype[+-]|vtype[+-]|view[+-])\n\n    # Statements\n    - statement: \\b(abstype|abst|absprop|absviewt|absvt(ype)?|absview|and|andalso|as|(re)?assume|begin|(pr)?case|s?case)\\b\n    - statement: \\b(classdec|dataprop|data(v|view)?type|dataview|datasort|do|dynload|else|end|exception|extype|extva(r|l)|s?if)\\b\n    - statement: \\b(ifcase|import|for|in|infix(l|r)?|let|local|macrodef|macdef|not|of|op|or|orelse|overload|(pre|post|non)fix)\\b\n    - statement: \\b(propdef|rec|sortdef|stacst|stadef|staload|stavar|sta(tic)?|symelim|symintr|tkindef|then|try|viewdef|v?typedef)\\b\n    - statement: \\b(viewtypedef|(pr)?va(l|r)|when|where|while|with|withtype|withprop|withv(iew)?type|withview)\\b\n    - statement: \\b(abst[@0]ype|absviewt[@0]?ype|absvt[@0]ype|abstbox|abstflat|absvtbox|absvtflat|absimpl|absreimpl|abs)\\b\n    - statement: \\b(case[+-]|(pr)?va(l|r)[+-]|for\\*|while\\*)\n\n    # Numbers\n    - constant.number: \\b[0-9.]+([eE][+-]?[0-9]+)?[fFlL]?\\b\n    - constant.number: \\b0[xX][0-9A-Fa-f]*(\\.[0-9A-Fa-f]*)?[pP][+-]?[0-9]+[fFlL]?\\b\n    - constant.number: \\b([0-9]+|0[xX][0-9A-Fa-f]+)[lLuU]*\\b\n\n    # Function-related keywords, special functions and namespaces. Not really identifiers\n    - identifier: \\b(fix|(pr)?fu?n|fnx|castfn|praxi|extern|lam|llam|(pr)?implement|(pr)?implmnt)\\b\n    - identifier: \\b(fix@|fold@|free@|lam@|llam@|addr@|view@|ref@|fn\\*)\n    - identifier: \\$\\w*\\b\n\n    # Other keywords, function effects...\n    - special: (\\$(arrpsz|arrptrsize|break|continue|d2ctype|delay|effmask_(ntm|exn|ref|wrt|all)))\\b\n    - special: (\\$(effmask|extern|extype_struct|extype|extkind|extval|extfcall|extmcall|ldelay|literal))\\b\n    - special: (\\$(li?st_vt|li?st_t|li?st|myfilename|myfunction|mylocation|raise|rec(ord)?_vt))\\b\n    - special: (\\$(rec(ord)?_t|rec(ord)?|showtype|solver_assert|solver_verify|tempenver))\\b\n    - special: (\\$(tup(le)?_vt|tup(le)?_t|tup(le)?|tyrep|vararg|vcopyenv_vt|vcopyenv_v))\\b\n    - special: \\!(wrt|exnref|exnwrt|exn|refwrt|ref|all|ntm|laz)\\b\n    - special: \\b(fun(0|1)|(lin)?cloptr(0|1)?|cloref(0|1)?|clo(0|1)?|lin|prf)\\b\n    - special: \\b(f?print(ln)?!|prerr(ln)?!|tupz!)\n\n    # Some C macros and other ATS macros\n    - preproc: ^[[:space:]]*#[[:space:]]*(define|pragma|include|(un|ifn?)def|endif|el(if|se)|if|warning|error|assert)\\b\n    - preproc: ^[[:space:]]*#[[:space:]]*(codegen2|codegen3|elifdef|elifndef|prerr|print|require|then|staload|dynload)\\b\n\n    # Boolean values\n    - constant.bool: \\b(true|false|null)\\b\n\n\n    # The \"%{ ... %}\" block inserts foreign code into ATS at compile-time\n    # Code inside of it is generally C or JavaScript\n    - default:\n        start: \"%{[#^$]?\"\n        end: \"%}\"\n        skip: \"\\\\.\"\n        limit-group: symbol.operator\n        rules:\n            - include: \"c\"\n            - include: \"javascript\"\n\n\n    # Strings and chars\n    - constant.string: \\\"[^\"]*\\\"\n    - constant.string: \\'[^']*\\'\n\n\n    # Comments\n    # \"////\" comments everything until it reaches EOF\n    - comment.block:\n        start: ////\n        end: $a\n        rules:\n            - todo: (TODO|XXX|FIXME)\n\n    - comment.line:\n        start: //\n        end: $\n        rules:\n            - todo: (TODO|XXX|FIXME)\n\n    # ML-like block comment\n    - comment.block:\n        start: \\(\\*\n        end: \\*\\)\n        rules:\n            - todo: (TODO|XXX|FIXME)\n\n    # C-like block comment\n    - comment.block:\n        start: /\\*\n        end: \\*\\/\n        rules:\n            - todo: (TODO|XXX|FIXME)\n"
  },
  {
    "path": "runtime/syntax/awk.yaml",
    "content": "filetype: awk\n\ndetect:\n    filename: \"\\\\.awk$\"\n    header: \"^#!.*bin/(env +)?awk( |$)\"\n\nrules:\n    - preproc: \"\\\\$[A-Za-z0-9_!@#$*?\\\\-]+\"\n    - preproc: \"\\\\b(ARGC|ARGIND|ARGV|BINMODE|CONVFMT|ENVIRON|ERRNO|FIELDWIDTHS)\\\\b\"\n    - preproc: \"\\\\b(FILENAME|FNR|FS|IGNORECASE|LINT|NF|NR|OFMT|OFS|ORS)\\\\b\"\n    - preproc: \"\\\\b(PROCINFO|RS|RT|RSTART|RLENGTH|SUBSEP|TEXTDOMAIN)\\\\b\"\n    - identifier.class: \"\\\\b(function|extension|BEGIN|END)\\\\b\"\n    - symbol.operator: \"[\\\\-+*/%^|!=&<>?;:]|\\\\\\\\|\\\\[|\\\\]\"\n    - statement:  \"\\\\b(for|if|while|do|else|in|delete|exit)\\\\b\"\n    - special:  \"\\\\b(break|continue|return)\\\\b\"\n    - statement: \"\\\\b(close|getline|next|nextfile|print|printf|system|fflush)\\\\b\"\n    - statement: \"\\\\b(atan2|cos|exp|int|log|rand|sin|sqrt|srand)\\\\b\"\n    - statement: \"\\\\b(asort|asorti|gensub|gsub|index|length|match)\\\\b\"\n    - statement: \"\\\\b(split|sprintf|strtonum|sub|substr|tolower|toupper)\\\\b\"\n    - statement: \"\\\\b(mktime|strftime|systime)\\\\b\"\n    - statement: \"\\\\b(and|compl|lshift|or|rshift|xor)\\\\b\"\n    - statement: \"\\\\b(bindtextdomain|dcgettext|dcngettext)\\\\b\"\n    - special:   \"/.*[^\\\\\\\\]/\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n"
  },
  {
    "path": "runtime/syntax/b.yaml",
    "content": "filetype: B\n\ndetect:\n    filename: '\\.b$'\n    # core control words + storage classes (Thompson B-ish)\n    signature: '\\b(if|else|while|switch|case|default|break|return|goto|extrn|auto)\\b'\n\nrules:\n    # -------------------------\n    # Comments (B: /* ... */)\n    # -------------------------\n    - comment:\n        start: '/\\*'\n        end: '\\*/'\n        rules: []\n\n    # Optional: // line comments (convenient, not “original” B)\n    - comment:\n        start: '//'\n        end: '$'\n        rules: []\n\n    # -------------------------\n    # Strings + escapes\n    # -------------------------\n    - constant.string:\n        start: '\"'\n        end: '\"'\n        skip: '\\\\\\\\.'\n        rules:\n            # common escapes: \\n \\t \\e \\r \\0 \\\" \\\\ \\( \\) \\*\n            - constant.specialChar: '\\\\\\\\([0netr\"\\\\\\\\\\\\*\\\\(\\\\)])'\n            # printf-ish: %s %c %d %o and %%\n            - constant.specialChar: '%(%|[scdo])'\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: '\\\\\\\\.'\n        rules:\n            - constant.specialChar: '\\\\\\\\([0netr\"\\\\\\\\\\\\*\\\\(\\\\)])'\n            - constant.specialChar: '%(%|[scdo])'\n\n    # -------------------------\n    # Numbers\n    # (leading 0 commonly used for octal constants)\n    # -------------------------\n    - constant.number: '\\b0[0-7]+\\b'\n    - constant.number: '\\b[0-9]+\\b'\n\n    # -------------------------\n    # Keywords / storage (keep tight + old-school)\n    # -------------------------\n    - statement: '\\b(if|else|while|switch|case|default|break|return|goto)\\b'\n    - type: '\\b(extrn|auto)\\b'\n\n    # -------------------------\n    # Common B library calls (/etc/libb.a era list)\n    # -------------------------\n    - constant.builtin: '\\b(char|getchr|putchr|exit|printf|seek|setuid|stat|time|unlink|wait|lchar|chdir|chmod|chown|close|creat|execl|execv|fork|fstat|getuid|intr|link|makdir|open|read|write|ctime)\\b'\n\n    # -------------------------\n    # Labels and function-ish identifiers\n    # -------------------------\n    # label (often at bol)\n    - identifier: '^\\s*[_A-Za-z][_A-Za-z0-9]*\\s*:'\n    # function call/def name before '('\n    - identifier: '\\b[_A-Za-z][_A-Za-z0-9]*\\s*\\('\n\n    # -------------------------\n    # Operators (order matters: longer first)\n    # Thompson-ish assignment operators in B are =+, =-, =*, =/, =%, =<<, =>>, =& , =|\n    # -------------------------\n    - symbol.operator: '(=<<|=>>|=\\+|=-|=\\*|=/|=%|=&|=\\|)'\n    - symbol.operator: '(==|!=|<=|>=|<<|>>)'\n    - symbol.operator: '(\\+\\+|--|\\*\\*)'\n    - symbol.operator: '[-+*/%&|^~!=<>?:=]'\n\n    # -------------------------\n    # Brackets\n    # -------------------------\n    - symbol.brackets: '[(){}\\[\\]]'\n\n    # -------------------------\n    # Identifiers / variables (last so keywords win)\n    # -------------------------\n    - identifier: '\\b[_A-Za-z][_A-Za-z0-9]*\\b'\n"
  },
  {
    "path": "runtime/syntax/bat.yaml",
    "content": "filetype: batch\n\ndetect:\n  filename: \"(\\\\.bat$|\\\\.cmd$)\"\n\nrules:\n  # Numbers\n  - constant.number: \"\\\\b[0-9]+\\\\b\"\n  # Brackets and symbols\n  - special: \"(\\\\{|\\\\}|\\\\(|\\\\)|\\\\;|\\\\]|\\\\[|`|\\\\\\\\|\\\\$|<|>|!|=|&|\\\\|)\"\n  # Conditionals and control flow\n  # note (?i) means case insensitive match\n  - type: \"\\\\b(?i)(case|do|done|elif|else|esac|exit|fi|for|function|if|in|local|read|return|select|then|until|while)\\\\b\"\n  - type: \"\\\\b(?i)(equ|neq|lss|leq|gtr|geq|on|off)\\\\b\"\n  - type: \"\\\\b(?i)(goto|for|in|do|call|exit|not|exist|errorlevel|defined)\\\\b\"\n  - type: \"\\\\b(?i)(prn|nul|lpt3|lpt2|lpt1|con|com4|com3|com2|com1|aux)\\\\b\"\n  # keywords\n  - statement: \"\\\\b(?i)(adprep|append|arp|assoc|at|atmadm|attrib|auditpol|autochk|autoconv|autofmt|bcdboot|bcdedit|bdehdcfg|bitsadmin|bootcfg|break|brea)\\\\b\"\n  - statement: \"\\\\b(?i)(cacls|cd|certreq|certutil|chcp|change|choice|cipher|chdir|chkdsk|chkntfs|chglogon|chgport|chgusr|clip|cls|clscluadmin|cluster|cmd|cmdkey|cmstp|color)\\\\b\"\n  - statement: \"\\\\b(?i)(comp|compact|convert|copy|cprofile|cscript|csvde|date|dcdiag|dcgpofix|dcpromo|defra|del|dfscmd|dfsdiag|dfsrmig|diantz|dir|dirquota|diskcomp|diskcopy|diskpart|diskperf|diskraid|diskshadow|dispdiag|doin|dnscmd|doskey|driverquery|dsacls|dsadd|dsamain|dsdbutil|dsget|dsmgmt|dsmod|dsmove|dsquery|dsrm)\\\\b\"\n  - statement: \"\\\\b(?i)(echo|edit|endlocal|erase|esentutl|eventcreate|eventquery|eventtriggers|evntcmd|expand|extract)\\\\b\"\n  - statement: \"\\\\b(?i)(fc|filescrn|find|findstr|finger|flattemp|fonde|forfiles|format|freedisk|fs|fsutil|ftp|ftype|fveupdate|getmac|gettype|gpfixup|gpresult|gpupdate|graftabl)\\\\b\"\n  - statement: \"\\\\b(?i)(hashgen|hep|help|helpctr|hostname|icacls|iisreset|inuse|ipconfig|ipxroute|irftp|ismserv|jetpack|keyb|klist|ksetup|ktmutil|ktpass|label|ldifd|ldp|lodctr|logman|logoff|lpq|lpr|macfile)\\\\b\"\n  - statement: \"\\\\b(?i)(makecab|manage-bde|mapadmin|md|mkdir|mklink|mmc|mode|more|mount|mountvol|move|mqbup|mqsvc|mqtgsvc|msdt|msg|msiexec|msinfo32|mstsc|nbtstat|net computer|net group)\\\\b\"\n  - statement: \"\\\\b(?i)(net localgroup|net print|net session|net share|net start|net stop|net use|net user|net view|net|netcfg|netdiag|netdom|netsh|netstat|nfsadmin|nfsshare|nfsstat|nlb)\\\\b\"\n  - statement: \"\\\\b(?i)(nlbmgr|nltest|nslookup|ntackup|ntcmdprompt|ntdsutil|ntfrsutl|openfiles|pagefileconfig|path|pathping|pause|pbadmin|pentnt|perfmon|ping|pnpunatten|pnputil|popd)\\\\b\"\n  - statement: \"\\\\b(?i)(powercfg|powershell|powershell_ise|print|prncnfg|prndrvr|prnjobs|prnmngr|prnport|prnqctl|prompt|pubprn|pushd|pushprinterconnections|pwlauncher|qappsrv|qprocess)\\\\b\"\n  - statement: \"\\\\b(?i)(query|quser|qwinsta|rasdial|rcp|rd|rdpsign|regentc|recover|redircmp|redirusr|reg|regini|regsvr32|relog|ren|rename|rendom|repadmin|repair-bde|replace|reset|restore)\\\\b\"\n  - statement: \"\\\\b(?i)(rxec|risetup|rmdir|robocopy|route|rpcinfo|rpcping|rsh|runas|rundll32|rwinsta|scp|sc|setlocal|session|schtasks|scwcmd|secedit|serverceipoptin|servrmanagercmd|serverweroptin|set|setspn)\\\\b\"\n  - statement: \"\\\\b(?i)(setx|sfc|shadow|shift|showmount|shutdown|sort|ssh|start|storrept|subst|sxstrace|ysocmgr|systeminfo|takeown|tapicfg|taskkill|tasklist|tcmsetup|telnet|tftp|time)\\\\b\"\n  - statement: \"\\\\b(?i)(timeout|title|tlntadmn|tpmvscmgr|tpmvscmgr|tacerpt|tracert|tree|tscon|tsdiscon|tsecimp|tskill|tsprof|type|typeperf|tzutil|uddiconfig|umount|unlodctr|ver|verify)\\\\b\"\n  - statement: \"\\\\b(?i)(verifier|verif|vol|vssadmin|w32tm|waitfor|wbadmin|wdsutil|wecutil|wevtutil|where|whoami|winnt|winnt32|winpop|winrm|winrs|winsat|wlbs|mic|wscript|xcopy)\\\\b\"\n  # / Flags\n  - constant: \"(/\\\\w+)\"\n  # Variables\n  - special: \"(%%\\\\w+)\"\n  - special: \"(%\\\\w+%)\"\n  # Conditional flags\n  - type: \"--[a-z-]+\"\n  - type: \"\\\\ -[a-z]+\"\n\n  - identifier: \"\\\\$\\\\{?[0-9A-Z_!@#$*?-]+\\\\}?\"\n  - identifier: \"\\\\$\\\\{?[0-9A-Z_!@#$*?-]+\\\\}?\"\n  # \"\" String\n  - constant.string:\n      start: \\\"\n      end: \\\"\n      skip: \\.\n      rules:\n        - constant.specialChar: (\\\\0|\\\\\\\\|\\\\t|\\\\n|\\\\r|\\\\\"|\\\\')\n        - constant.unicode: \\\\u\\{[[:xdigit:]]+}\n  # '' string\n  - constant.string: \"(\\\\'.+\\\\')\"\n  # rem as comment\n  - comment.rem: \"(?i)(rem\\\\s.*)\"\n  # :: as comment\n  - comment.rem: \"(?i)(\\\\:\\\\:\\\\s.*)\"\n"
  },
  {
    "path": "runtime/syntax/c.yaml",
    "content": "filetype: c\n\ndetect:\n    filename: \"(\\\\.(c|C)$|\\\\.(h|H)$|\\\\.ii?$|\\\\.(def)$)\"\n\nrules:\n    - identifier: \"\\\\b[A-Z_][0-9A-Z_]+\\\\b\"\n    - type: \"\\\\b(_Atomic|_BitInt|float|double|_Decimal32|_Decimal64|_Decimal128|_Complex|complex|_Imaginary|imaginary|_Bool|bool|char|int|short|long|enum|void|struct|union|typedef|typeof|typeof_unqual|(un)?signed|inline|_Noreturn)\\\\b\"\n    - type: \"\\\\b((s?size)|((u_?)?int(8|16|32|64|ptr))|char(8|16|32)|wchar)_t\\\\b\"\n      # GCC float/decimal/fixed types\n    - type: \"\\\\b(_Float16|__fp16|_Float32|_Float32x|_Float64|_Float64x|__float80|_Float128|_Float128x|__float128|__ibm128|__int128|_Fract|_Sat|_Accum)\\\\b\"\n    - type: \"\\\\b[a-z_][0-9a-z_]+(_t|_T)\\\\b\"\n    - statement: \"\\\\b(auto|volatile|register|restrict|_Alignas|alignas|_Alignof|alignof|static|const|constexpr|extern|_Thread_local|thread_local)\\\\b\"\n    - statement: \"\\\\b(for|if|while|do|else|case|default|switch|_Generic|_Static_assert|static_assert)\\\\b\"\n    - statement: \"\\\\b(goto|continue|break|return)\\\\b\"\n    - statement: \"\\\\b(asm|fortran)\\\\b\"\n    - preproc: \"^[[:space:]]*#[[:space:]]*(define|embed|pragma|include|(un|ifn?)def|endif|el(if|ifdef|ifndef|se)|if|line|warning|error|__has_include|__has_embed|__has_c_attribute)\"\n    - preproc: \"^[[:space:]]*_Pragma\\\\b\"\n      # GCC builtins\n    - statement: \"__attribute__[[:space:]]*\\\\(\\\\([^)]*\\\\)\\\\)\"\n    - statement: \"__(aligned|asm|builtin|extension|hidden|inline|packed|restrict|section|typeof|weak)__\"\n      # Operator Color\n    - symbol.operator: \"[-+*/%=<>.:;,~&|^!?]|\\\\b(offsetof|sizeof)\\\\b\"\n    - symbol.brackets: \"[(){}]|\\\\[|\\\\]\"\n      # Integer Constants\n    - constant.number: \"(\\\\b([1-9][0-9]*|0[0-7]*|0[Xx][0-9A-Fa-f]+|0[Bb][01]+)([Uu][Ll]?[Ll]?|[Ll][Ll]?[Uu]?)?\\\\b)\"\n      # Decimal Floating Constants\n    - constant.number: \"(\\\\b(([0-9]*[.][0-9]+|[0-9]+[.][0-9]*)([Ee][+-]?[0-9]+)?|[0-9]+[Ee][+-]?[0-9]+)[FfLl]?\\\\b)\"\n      # Hexadecimal Floating Constants\n    - constant.number: \"(\\\\b0[Xx]([0-9A-Za-z]*[.][0-9A-Za-z]+|[0-9A-Za-z]+[.][0-9A-Za-z]*)[Pp][+-]?[0-9]+[FfLl]?\\\\b)\"\n    - constant.bool: \"(\\\\b(true|false|NULL|nullptr|TRUE|FALSE)\\\\b)\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([\\\"'abfnrtv\\\\\\\\]|[0-3]?[0-7]{1,2}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n              # TODO: Revert back to - error: \"..+\" once #3127 is merged\n            - error: \"[[:graph:]]{2,}'\"\n            - constant.specialChar: \"\\\\\\\\([\\\"'abfnrtv\\\\\\\\]|[0-3]?[0-7]{1,2}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})\"\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n"
  },
  {
    "path": "runtime/syntax/caddyfile.yaml",
    "content": "filetype: caddyfile\n\ndetect:\n    filename: \"Caddyfile\"\n\nrules:\n    - identifier: \"^\\\\s*\\\\S+(\\\\s|$)\"\n    - type: \"^([\\\\w.:/-]+,? ?)+[,{]$\"\n    - constant.specialChar: \"\\\\s{$\"\n    - constant.specialChar: \"^\\\\s*}$\"\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - preproc: \"\\\\{(\\\\w+|\\\\$\\\\w+|%\\\\w+%)\\\\}\"\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules: []\n\n"
  },
  {
    "path": "runtime/syntax/cake.yaml",
    "content": "filetype: cake\ndetect:\n    filename: \"\\\\.cake$\"\n\nrules:\n    - include: \"csharp\"\n    - preproc: \"^[[:space:]]*#(addin|break|l(oad)?|module|r(eference)?|tool)\"\n"
  },
  {
    "path": "runtime/syntax/clojure.yaml",
    "content": "filetype: clojure\n\ndetect:\n    filename: \"\\\\.(clj[sc]?|edn)$\"\n\nrules:\n\n    # Constants\n    - constant.bool: \"\\\\b(true|false)\\\\b\"\n    - constant.macro: \"\\\\b(nil)\\\\b\"\n      # Valid numbers\n    - constant.number: \"[\\\\-]?[0-9]+?\\\\b\"\n    - constant.number: \"0x[0-9][A-Fa-f]+?\\\\b\"\n    - constant.number: \"[\\\\-]?(3[0-6]|2[0-9]|1[0-9]|[2-9])r[0-9A-Z]+?\\\\b\"\n      # Invalid numbers\n    - error: \"[\\\\-]?([4-9][0-9]|3[7-9]|1|0)r[0-9A-Z]+?\\\\b\"\n\n      # Symbols\n    - symbol.operator: \"[=>+\\\\-*/'?]\"\n\n      # Types/casting\n    - type: \"\\\\b(byte|short|(big)?int(eger)?|long|float|num|bigdec|rationalize)\\\\b\"\n\n      # String highlighting\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"(\\\\\\\\u[0-9A-fa-f]{4,4}|\\\\\\\\newline|\\\\\\\\space|\\\\\\\\tab|\\\\\\\\formfeed|\\\\\\\\backspace|\\\\\\\\return|\\\\\\\\.)\"\n\n      # Comments\n    - comment:\n        start: \";\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n"
  },
  {
    "path": "runtime/syntax/cmake.yaml",
    "content": "filetype: cmake\n\ndetect:\n    filename: \"(CMakeLists\\\\.txt|\\\\.cmake)$\"\n\nrules:\n    - identifier.var: \"^[[:space:]]*[A-Z0-9_]+\"\n    - preproc: \"^[[:space:]]*(include|include_directories|include_external_msproject)\\\\b\"\n\n    - statement: \"^[[:space:]]*\\\\b((else|end)?if|else|(end)?while|(end)?foreach|break)\\\\b\"\n    - statement: \"\\\\b(COPY|NOT|COMMAND|PROPERTY|POLICY|TARGET|EXISTS|IS_(DIRECTORY|ABSOLUTE)|DEFINED)\\\\b[[:space:]]\"\n    - statement: \"[[:space:]]\\\\b(OR|AND|IS_NEWER_THAN|MATCHES|(STR|VERSION_)?(LESS|GREATER|EQUAL))\\\\b[[:space:]]\"\n\n    - special: \"^[[:space:]]*\\\\b((end)?(function|macro)|return)\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - preproc:\n        start: \"\\\\$(\\\\{|ENV\\\\{)\"\n        end: \"\\\\}\"\n        rules: []\n\n    - identifier.macro: \"\\\\b(APPLE|UNIX|WIN32|CYGWIN|BORLAND|MINGW|MSVC(_IDE|60|71|80|90)?)\\\\b\"\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n"
  },
  {
    "path": "runtime/syntax/coffeescript.yaml",
    "content": "filetype: coffeescript\n\ndetect:\n    filename: \"\\\\.coffee$\"\n\nrules:\n    - symbol.operator: \"([-+/*=<>!~%?:&|]|[.]{3})|\\\\b(and|or|is|isnt|not)\\\\b\"\n    - identifier.class: \"([A-Za-z_][A-Za-z0-9_]*:[[:space:]]*(->|\\\\()|->)\"\n    - symbol.brackets: \"[()]\"\n\n    - statement:  \"\\\\b(await|when|catch|continue|debugger|default|by|until)\\\\b\"\n    - statement:  \"\\\\b(delete|do|else|export|finally|for|class|extends|while|then)\\\\b\"\n    - statement:  \"\\\\b(get|if|import|from|in|instanceof|new|reject|resolve|return)\\\\b\"\n    - statement:  \"\\\\b(set|super|switch|this|throw|try|typeof|with|yield|unless)\\\\b\"\n\n    - constant.bool:  \"\\\\b(true|false|yes|no|on|off)\\\\b\"\n    - constant.bool.false: \"\\\\b(false|no|off)\\\\b\"\n    - constant.bool.true: \"\\\\b(true|yes|on)\\\\b\"\n\n    - constant.number: \"\\\\b[-+]?([1-9][0-9]*|0[0-7]*|0x[0-9a-fA-F]+)([uU][lL]?|[lL][uU]?)?\\\\b\"\n    - constant.number: \"\\\\b[-+]?([0-9]+\\\\.[0-9]*|[0-9]*\\\\.[0-9]+)([EePp][+-]?[0-9]+)?[fFlL]?\"\n    - constant.number: \"\\\\b[-+]?([0-9]+[EePp][+-]?[0-9]+)[fFlL]?\"\n    - identifier: \"@[A-Za-z0-9_]*\"\n\n    - error: \"\\\\b(enum|implements|interface|package|private|protected|public)\"\n    - constant: \"\\\\b(globalThis|Infinity|null|undefined|NaN)\\\\b\"\n    - constant: \"\\\\b(null|undefined|NaN)\\\\b\"\n    - constant: \"\\\\b(true|false|yes|no|on|off)\\\\b\"\n    - type: \"\\\\b(Array|Boolean|Date|Enumerator|Error|Function|Generator|Map|Math)\\\\b\"\n    - type: \"\\\\b(Number|Object|Promise|Proxy|Reflect|RegExp|Set|String|Symbol|WeakMap|WeakSet)\\\\b\"\n    - type: \"\\\\b(BigInt64Array|BigUint64Array|Float32Array|Float64Array|Int16Array)\\\\b\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \"###\"\n        end: \"###\"\n        rules:  []\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/colortest.yaml",
    "content": "filetype: colortest\n\ndetect:\n    filename: \"ColorTest$\"\n\nrules:\n    - black: \"\\\\bPLAIN\\\\b\"\n    - red: \"\\\\bred\\\\b\"\n    - green: \"\\\\bgreen\\\\b\"\n    - yellow: \"\\\\byellow\\\\b\"\n    - blue: \"\\\\bblue\\\\b\"\n    - magenta: \"\\\\bmagenta\\\\b\"\n    - cyan: \"\\\\bcyan\\\\b\"\n    - brightred: \"\\\\bbrightred\\\\b\"\n    - brightgreen: \"\\\\bbrightgreen\\\\b\"\n    - brightyellow: \"\\\\bbrightyellow\\\\b\"\n    - brightblue: \"\\\\bbrightblue\\\\b\"\n    - brightmagenta: \"\\\\bbrightmagenta\\\\b\"\n    - brightcyan: \"\\\\bbrightcyan\\\\b\"\n"
  },
  {
    "path": "runtime/syntax/conky.yaml",
    "content": "filetype: conky\n\ndetect:\n    filename: \"(\\\\.*conkyrc.*$|conky.conf)\"\n\nrules:\n    - type: \"\\\\b(alignment|append_file|background|border_inner_margin|border_outer_margin|border_width|color0|color1|color2|color3|color4|color5|color6|color7|color8|color9|colorN|cpu_avg_samples|default_bar_height|default_bar_width|default_color|default_gauge_height|default_gauge_width|default_graph_height|default_graph_width|default_outline_color|default_shade_color|diskio_avg_samples|display|double_buffer|draw_borders|draw_graph_borders|draw_outline|draw_shades|extra_newline|font|format_human_readable|gap_x|gap_y|http_refresh|if_up_strictness|imap|imlib_cache_flush_interval|imlib_cache_size|lua_draw_hook_post|lua_draw_hook_pre|lua_load|lua_shutdown_hook|lua_startup_hook|mail_spool|max_port_monitor_connections|max_text_width|max_user_text|maximum_width|minimum_height|minimum_width|mpd_host|mpd_password|mpd_port|music_player_interval|mysql_host|mysql_port|mysql_user|mysql_password|mysql_db|net_avg_samples|no_buffers|nvidia_display|out_to_console|out_to_http|out_to_ncurses|out_to_stderr|out_to_x|override_utf8_locale|overwrite_file|own_window|own_window_class|own_window_colour|own_window_hints|own_window_title|own_window_transparent|own_window_type|pad_percents|pop3|sensor_device|short_units|show_graph_range|show_graph_scale|stippled_borders|temperature_unit|template|template0|template1|template2|template3|template4|template5|template6|template7|template8|template9|text|text_buffer_size|times_in_seconds|top_cpu_separate|top_name_width|total_run_times|update_interval|update_interval_on_battery|uppercase|use_spacer|use_xft|xftalpha|xftfont)\\\\b\"\n\n      # Configuration item constants\n    - statement: \"\\\\b(above|below|bottom_left|bottom_right|bottom_middle|desktop|dock|no|none|normal|override|skip_pager|skip_taskbar|sticky|top_left|top_right|top_middle|middle_left|middle_right|middle_middle|undecorated|yes)\\\\b\"\n\n      # Variables\n    - preproc: \"\\\\b(acpiacadapter|acpifan|acpitemp|addr|addrs|alignc|alignr|apcupsd|apcupsd_cable|apcupsd_charge|apcupsd_lastxfer|apcupsd_linev|apcupsd_load|apcupsd_loadbar|apcupsd_loadgauge|apcupsd_loadgraph|apcupsd_model|apcupsd_name|apcupsd_status|apcupsd_temp|apcupsd_timeleft|apcupsd_upsmode|apm_adapter|apm_battery_life|apm_battery_time|audacious_bar|audacious_bitrate|audacious_channels|audacious_filename|audacious_frequency|audacious_length|audacious_length_seconds|audacious_main_volume|audacious_playlist_length|audacious_playlist_position|audacious_position|audacious_position_seconds|audacious_status|audacious_title|battery|battery_bar|battery_percent|battery_short|battery_time|blink|bmpx_album|bmpx_artist|bmpx_bitrate|bmpx_title|bmpx_track|bmpx_uri|buffers|cached|cmdline_to_pid|color|color0|color1|color2|color3|color4|color5|color6|color7|color8|color9|combine|conky_build_arch|conky_build_date|conky_version|cpu|cpubar|cpugauge|cpugraph|curl|desktop|desktop_name|desktop_number|disk_protect|diskio|diskio_read|diskio_write|diskiograph|diskiograph_read|diskiograph_write|distribution|downspeed|downspeedf|downspeedgraph|draft_mails|else|endif|entropy_avail|entropy_bar|entropy_perc|entropy_poolsize|eval|eve|exec|execbar|execgauge|execgraph|execi|execibar|execigauge|execigraph|execp|execpi|flagged_mails|font|format_time|forwarded_mails|freq|freq_g|fs_bar|fs_bar_free|fs_free|fs_free_perc|fs_size|fs_type|fs_used|fs_used_perc|goto|gw_iface|gw_ip|hddtemp|head|hr|hwmon|i2c|i8k_ac_status|i8k_bios|i8k_buttons_status|i8k_cpu_temp|i8k_left_fan_rpm|i8k_left_fan_status|i8k_right_fan_rpm|i8k_right_fan_status|i8k_serial|i8k_version|ibm_brightness|ibm_fan|ibm_temps|ibm_volume|ical|iconv_start|iconv_stop|if_empty|if_existing|if_gw|if_match|if_mixer_mute|if_mounted|if_mpd_playing|if_running|if_smapi_bat_installed|if_up|if_updatenr|if_xmms2_connected|image|imap_messages|imap_unseen|ioscheduler|irc|kernel|laptop_mode|lines|loadavg|loadgraph|lua|lua_bar|lua_gauge|lua_graph|lua_parse|machine|mails|mboxscan|mem|memwithbuffers|membar|memwithbuffersbar|memeasyfree|memfree|memgauge|memgraph|memmax|memperc|mixer|mixerbar|mixerl|mixerlbar|mixerr|mixerrbar|moc_album|moc_artist|moc_bitrate|moc_curtime|moc_file|moc_rate|moc_song|moc_state|moc_timeleft|moc_title|moc_totaltime|monitor|monitor_number|mpd_album|mpd_artist|mpd_bar|mpd_bitrate|mpd_elapsed|mpd_file|mpd_length|mpd_name|mpd_percent|mpd_random|mpd_repeat|mpd_smart|mpd_status|mpd_title|mpd_track|mpd_vol|mysql|nameserver|new_mails|nodename|nodename_short|no_update|nvidia|obsd_product|obsd_sensors_fan|obsd_sensors_temp|obsd_sensors_volt|obsd_vendor|offset|outlinecolor|pb_battery|pid_chroot|pid_cmdline|pid_cwd|pid_environ|pid_environ_list|pid_exe|pid_nice|pid_openfiles|pid_parent|pid_priority|pid_state|pid_state_short|pid_stderr|pid_stdin|pid_stdout|pid_threads|pid_thread_list|pid_time_kernelmode|pid_time_usermode|pid_time|pid_uid|pid_euid|pid_suid|pid_fsuid|pid_gid|pid_egid|pid_sgid|pid_fsgid|pid_read|pid_vmpeak|pid_vmsize|pid_vmlck|pid_vmhwm|pid_vmrss|pid_vmdata|pid_vmstk|pid_vmexe|pid_vmlib|pid_vmpte|pid_write|platform|pop3_unseen|pop3_used|processes|read_tcp|read_udp|replied_mails|rss|running_processes|running_threads|scroll|seen_mails|shadecolor|smapi|smapi_bat_bar|smapi_bat_perc|smapi_bat_power|smapi_bat_temp|sony_fanspeed|stippled_hr|stock|swap|swapbar|swapfree|swapmax|swapperc|sysname|tab|tail|tcp_ping|tcp_portmon|template0|template1|template2|template3|template4|template5|template6|template7|template8|template9|texeci|texecpi|threads|time|to_bytes|top|top_io|top_mem|top_time|totaldown|totalup|trashed_mails|tztime|gid_name|uid_name|unflagged_mails|unforwarded_mails|unreplied_mails|unseen_mails|updates|upspeed|upspeedf|upspeedgraph|uptime|uptime_short|user_names|user_number|user_terms|user_times|user_time|utime|voffset|voltage_mv|voltage_v|weather|wireless_ap|wireless_bitrate|wireless_essid|wireless_link_bar|wireless_link_qual|wireless_link_qual_max|wireless_link_qual_perc|wireless_mode|words|xmms2_album|xmms2_artist|xmms2_bar|xmms2_bitrate|xmms2_comment|xmms2_date|xmms2_duration|xmms2_elapsed|xmms2_genre|xmms2_id|xmms2_percent|xmms2_playlist|xmms2_size|xmms2_smart|xmms2_status|xmms2_timesplayed|xmms2_title|xmms2_tracknr|xmms2_url)\\\\b\"\n\n    - identifier.var: \"\\\\$\\\\{?[0-9A-Z_!@#$*?-]+\\\\}?\"\n    - symbol.operator: \"(\\\\{|\\\\}|\\\\(|\\\\)|\\\\;|\\\\]|\\\\[|`|\\\\\\\\|\\\\$|<|>|!|=|&|\\\\|)\"\n    - constant.macro: \"^TEXT$\"\n"
  },
  {
    "path": "runtime/syntax/cpp.yaml",
    "content": "filetype: c++\n\ndetect:\n    filename: \"(\\\\.c(c|pp|xx)$|\\\\.h(h|pp|xx)?$|\\\\.ii?$|\\\\.(def)$)\"\n    signature: \"\\\\b(namespace|class|public|protected|private|template|constexpr|noexcept|nullptr|throw)\\\\b\"\n\nrules:\n    - identifier: \"\\\\b[A-Z_][0-9A-Z_]*\\\\b\"\n    - type: \"\\\\b(auto|float|double|bool|char|int|short|long|enum|void|struct|union|typedef|(un)?signed|inline)\\\\b\"\n    - type: \"\\\\b(((s?size)|((u_?)?int(8|16|32|64|ptr))|char(8|16|32))_t|wchar_t)\\\\b\"\n    - type: \"\\\\b[a-z_][0-9a-z_]+(_t|_T)\\\\b\"\n    - type: \"\\\\b(final|override)\\\\b\"\n    - statement: \"\\\\b(volatile|const(expr|eval|init)?|mutable|register|thread_local|static|extern|decltype|explicit|virtual)\\\\b\"\n    - statement: \"\\\\b(class|namespace|template|typename|this|friend|using|public|protected|private|noexcept)\\\\b\"\n    - statement: \"\\\\b(concept|requires)\\\\b\"\n    - statement: \"\\\\b(import|export|module)\\\\b\"\n    - statement: \"\\\\b(for|if|while|do|else|case|default|switch)\\\\b\"\n    - statement: \"\\\\b(try|throw|catch|operator|new|delete|static_assert)\\\\b\"\n    - statement: \"\\\\b(goto|continue|break|return)\\\\b\"\n    - preproc: \"^[[:space:]]*#[[:space:]]*(define|pragma|include|(un|ifn?)def|endif|el(if|se)|if|warning|error)|_Pragma\"\n\n      # Conditionally-supported/extension keywords\n    - statement: \"\\\\b(asm|fortran)\\\\b\"\n\n      # GCC builtins\n    - statement: \"(__attribute__[[:space:]]*\\\\(\\\\([^)]*\\\\)\\\\)|__(aligned|asm|builtin|hidden|inline|packed|restrict|section|typeof|weak)__)\"\n\n      # Operator Color\n    - symbol.operator: \"[-+*/%=<>.:;,~&|^!?]|\\\\b(sizeof|alignof|typeid|(and|or|xor|not)(_eq)?|bitor|compl|bitand|(const|dynamic|reinterpret|static)_cast)\\\\b\"\n      # Parenthetical Color\n    - symbol.brackets: \"[(){}]|\\\\[|\\\\]\"\n\n      # Integer Literals\n    - constant.number: \"(\\\\b([0-9]|0[0-7]|0[Xx][0-9A-Fa-f]|0[Bb][01])([Uu][Ll]?[Ll]?|[Ll][Ll]?[Uu]?)?\\\\b)\"      # Base case (Without ' separtor)\n    - constant.number: \"(\\\\b([1-9][0-9']*[0-9])([Uu][Ll]?[Ll]?|[Ll][Ll]?[Uu]?)?\\\\b)\"                            # Decimal\n    - constant.number: \"(\\\\b(0[0-7][0-7']*[0-7])([Uu][Ll]?[Ll]?|[Ll][Ll]?[Uu]?)?\\\\b)\"                           # Oct\n    - constant.number: \"(\\\\b(0[Xx][0-9A-Fa-f][0-9A-Fa-f']*[0-9A-Fa-f])([Uu][Ll]?[Ll]?|[Ll][Ll]?[Uu]?)?\\\\b)\"     # Hex\n    - constant.number: \"(\\\\b(0[Bb][01][01']*[01])([Uu][Ll]?[Ll]?|[Ll][Ll]?[Uu]?)?\\\\b)\"                          # Binary\n\n      # Decimal Floating-point Literals\n    - constant.number: \"(([0-9]?[.]?\\\\b[0-9]+)([Ee][+-]?[0-9]+)?[FfLl]?\\\\b)\"                                    # Base case optional interger part with exponent base case\n    - constant.number: \"(\\\\b([0-9]+[.][0-9]?)([Ee][+-]?[0-9]+)?[FfLl]?)\"                                        # Base case optional fractional part with exponent base case\n    - constant.number: \"(([0-9]?[.]?\\\\b[0-9]+)([Ee][+-]?[0-9][0-9']*[0-9])?[FfLl]?\\\\b)\"                         # Base case optional interger part with exponent\n    - constant.number: \"(\\\\b([0-9]+[.][0-9]?)([Ee][+-]?[0-9][0-9']*[0-9])?[FfLl]?)\"                             # Base case optional fractional part with exponent\n\n    - constant.number: \"(([0-9][0-9']*[0-9])?[.]?\\\\b([0-9][0-9']*[0-9])+([Ee][+-]?[0-9]+)?[FfLl]?\\\\b)\"              # Optional interger part with exponent base case\n    - constant.number: \"(\\\\b([0-9][0-9']*[0-9])+[.]([0-9][0-9']*[0-9])?([Ee][+-]?[0-9]+)?[FfLl]?)\"                  # Optional fractional part with exponent base case\n    - constant.number: \"(([0-9][0-9']*[0-9])?[.]?\\\\b([0-9][0-9']*[0-9])+([Ee][+-]?[0-9][0-9']*[0-9])?[FfLl]?\\\\b)\"   # Optional interger part with exponent\n    - constant.number: \"(\\\\b([0-9][0-9']*[0-9])+[.]([0-9][0-9']*[0-9])?([Ee][+-]?[0-9][0-9']*[0-9])?[FfLl]?)\"       # Optional fractional part with exponent\n\n      # Hexadecimal Floating-point Literals\n    - constant.number: \"(\\\\b0[Xx]([0-9a-zA-Z]?[.]?[0-9a-zA-Z]+)([Pp][+-]?[0-9]+)?[FfLl]?\\\\b)\"                   # Base case optional interger part with exponent base case\n    - constant.number: \"(\\\\b0[Xx]([0-9a-zA-Z]+[.][0-9a-zA-Z]?)([Pp][+-]?[0-9]+)?[FfLl]?)\"                       # Base case optional fractional part with exponent base case\n    - constant.number: \"(\\\\b0[Xx]([0-9a-zA-Z]?[.]?[0-9a-zA-Z]+)([Pp][+-]?[0-9][0-9']*[0-9])?[FfLl]?\\\\b)\"        # Base case optional interger part with exponent\n    - constant.number: \"(\\\\b0[Xx]([0-9a-zA-Z]+[.][0-9a-zA-Z]?)([Pp][+-]?[0-9][0-9']*[0-9])?[FfLl]?)\"            # Base case optional fractional part with exponent\n\n    - constant.number: \"(\\\\b0[Xx]([0-9a-zA-Z][0-9a-zA-Z']*[0-9a-zA-Z])?[.]?([0-9a-zA-Z][0-9a-zA-Z']*[0-9a-zA-Z])+([Pp][+-]?[0-9]+)?[FfLl]?\\\\b)\"             # Optional interger part with exponent base case\n    - constant.number: \"(\\\\b0[Xx]([0-9a-zA-Z][0-9a-zA-Z']*[0-9a-zA-Z])+[.]([0-9a-zA-Z][0-9a-zA-Z']*[0-9a-zA-Z])?([Pp][+-]?[0-9]+)?[FfLl]?)\"                 # Optional fractional part with exponent base case\n    - constant.number: \"(\\\\b0[Xx]([0-9a-zA-Z][0-9a-zA-Z']*[0-9a-zA-Z])?[.]?([0-9a-zA-Z][0-9a-zA-Z']*[0-9a-zA-Z])+([Pp][+-]?[0-9][0-9']*[0-9])?[FfLl]?\\\\b)\"  # Optional interger part with exponent\n    - constant.number: \"(\\\\b0[Xx]([0-9a-zA-Z][0-9a-zA-Z']*[0-9a-zA-Z])+[.]([0-9a-zA-Z][0-9a-zA-Z']*[0-9a-zA-Z])?([Pp][+-]?[0-9][0-9']*[0-9])?[FfLl]?)\"      # Optional fractional part with exponent\n\n    - constant.bool: \"(\\\\b(true|false|NULL|nullptr|TRUE|FALSE)\\\\b)\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([\\\"'abfnrtv\\\\\\\\]|[0-3]?[0-7]{1,2}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"(\\\\\\\\.)|\\\\b([1-9][0-9']+[0-9]|0[0-7']+[0-7]|0[Xx][0-9A-Fa-f][0-9A-Fa-f']+[0-9A-Fa-f]|0[Bb][01][01']*[01])([Uu][Ll]?[Ll]?|[Ll][Ll]?[Uu]?)?\\\\b\"\n        rules:\n              # TODO: Revert back to - error: \"..+\" once #3127 is merged\n            - error: \"[[:graph:]]{2,}'\"\n            - constant.specialChar: \"\\\\\\\\([\\\"'abfnrtv\\\\\\\\]|[0-3]?[0-7]{1,2}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})\"\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n"
  },
  {
    "path": "runtime/syntax/crontab.yaml",
    "content": "filetype: crontab\n\ndetect:\n    filename: \"crontab$|/tmp/crontab\\\\.\\\\w+$\"\n    header: \"^#.*?/etc/crontab\"\n\nrules:\n      #              The time and date fields are:\n      #              field          allowed values\n      #              -----          --------------\n      #              minute         0-59\n      #              hour           0-23\n      #              day of month   0-31\n      #              month          0-12 (or names, see below)\n      #              day of week    0-7 (0 or 7 is Sun, or use names)\n\n    - statement: \"^([\\\\*0-9,\\\\-\\\\/]+)\\\\s+([\\\\*0-9,\\\\-\\\\/]+)\\\\s+([\\\\*0-9,\\\\-\\\\/]+)\\\\s+(([\\\\*0-9,\\\\-\\\\/]+)|(\\\\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\\\\b))\\\\s+(([\\\\*0-9,\\\\-\\\\/]+)|(\\\\b(sun|mon|tue|wed|thu|fri|sat)\\\\b))\\\\s+(.*)$\\\\n?\"\n    - constant:  \"^([\\\\*0-9,\\\\-\\\\/]+)\\\\s+([\\\\*0-9,\\\\-\\\\/]+)\\\\s+([\\\\*0-9,\\\\-\\\\/]+)\\\\s+(([\\\\*0-9,\\\\-\\\\/]+)|(\\\\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\\\\b))\\\\s+(([\\\\*0-9,\\\\-\\\\/]+)|(\\\\b(sun|mon|tue|wed|thu|fri|sat)\\\\b))\"\n\n      # Shell Values\n    - type: \"^[A-Z]+\\\\=\"\n\n      # Months and weekday keywords\n    - constant: \"\\\\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\\\\b\"\n    - constant: \"\\\\b(sun|mon|tue|wed|thu|fri|sat)\\\\b\"\n    - type: \"\\\\@(reboot|yearly|annually|monthly|weekly|daily|midnight|hourly)\\\\b\"\n\n      # Conditionals\n    - special: \"(\\\\{|\\\\}|\\\\(|\\\\)|\\\\;|\\\\]|\\\\[|`|\\\\\\\\|\\\\$|<|>|^|!|=|&|\\\\|)\"\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n"
  },
  {
    "path": "runtime/syntax/crystal.yaml",
    "content": "filetype: crystal\n\ndetect:\n    filename: \"\\\\.cr$\"\n\nrules:\n    # Asciibetical list of reserved words\n    - statement: \"\\\\b(abstract|alias|as|asm|begin|break|case|class|def|do|else|elsif|end|ensure|enum|extend|for|fun|if|in|include|instance_sizeof|lib|loop|macro|module|next|of|out|pointerof|private|protected|raise|require|rescue|return|select|self|sizeof|spawn|struct|super|then|type|typeof|uninitialized|union|unless|until|verbatim|when|while|with|yield)\\\\b\"\n      # Constants\n    - constant: \"\\\\b(true|false|nil)\\\\b\"\n    - constant.number: \"\\\\b[0-9]+\\\\b\"\n      # Ones that can't be in the same regex because they include non-words.\n      # The nil? one has to be after the constants.\n    - statement: \"\\\\b(nil\\\\?|as(\\\\?|\\\\b)|is_a\\\\?|responds_to\\\\?)\"\n    - type: \"(\\\\$|@|@@)?\\\\b[A-Z]+[0-9A-Z_a-z]*\"\n      # Crystal \"symbols\"\n    - constant:  \"([ \t]|^):[0-9A-Z_]+\\\\b\"\n      # Some unique things we want to stand out\n    - constant: \"\\\\b(__FILE__|__LINE__)\\\\b\"\n      # Regular expressions\n    - constant: \"/([^/]|(\\\\\\\\/))*/[iomx]*|%r\\\\{([^}]|(\\\\\\\\}))*\\\\}[iomx]*\"\n\n      # Shell command expansion is in `backticks` or like %x{this}.  These are\n      # \"double-quotish\" (to use a perlism).\n    - constant.string: \"`[^`]*`|%x\\\\{[^}]*\\\\}\"\n\n    - constant.string:\n        start: \"`\"\n        end: \"`\"\n        rules: []\n\n    - constant.string:\n        start: \"%x\\\\{\"\n        end: \"\\\\}\"\n        rules: []\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n            - symbol.brackets:\n                start: \"#\\\\{\"\n                end: \"\\\\}\"\n                rules:\n                    - default: \".*\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment.bright:\n        start: \"##\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - constant:\n        start: \"<<-?'?EOT'?\"\n        end: \"^EOT\"\n        rules: []\n\n"
  },
  {
    "path": "runtime/syntax/csharp.yaml",
    "content": "filetype: csharp\n\ndetect:\n    filename: \"\\\\.cs$\"\n\nrules:\n    # Class\n    - identifier.class: \"class +[A-Za-z0-9]+ *((:) +[A-Za-z0-9.]+)?\"\n\n      # Annotation\n    - identifier.var: \"@[A-Za-z]+\"\n\n    - preproc: \"^[[:space:]]*#[[:space:]]*(define|elif|else|endif|endregion|error|if|line|nullable|pragma|region|undef|warning)\"\n    - identifier: \"([A-Za-z0-9_]*[[:space:]]*[()])\"\n    - type: \"\\\\b(bool|byte|sbyte|char|decimal|double|float|IntPtr|int|uint|long|ulong|managed|unmanaged|nint|nuint|object|short|ushort|string|base|this|var|void)\\\\b\"\n    - statement: \"\\\\b(alias|as|case|catch|checked|default|do|dynamic|else|finally|fixed|for|foreach|goto|if|is|lock|new|null|return|switch|throw|try|unchecked|when|while|with)\\\\b\"\n    - statement: \"\\\\b(abstract|add|and|args|async|await|class|const|delegate|enum|event|explicit|extern|file|get|global|implicit|in|init|internal|interface|nameof|namespace|not|notnull|operator|or|out|override|params|partial|private|protected|public|readonly|record|ref|remove|required|scoped|sealed|set|sizeof|stackalloc|static|struct|typeof|unsafe|using|value|virtual|volatile|yield)\\\\b\"\n      # LINQ-only keywords (ones that cannot be used outside of a LINQ query - lots others can)\n    - statement: \"\\\\b(from|where|select|group|info|orderby|join|let|in|on|equals|by|ascending|descending)\\\\b\"\n    - special: \"\\\\b(break|continue)\\\\b\"\n    - constant.bool: \"\\\\b(true|false)\\\\b\"\n    - symbol.operator: \"[\\\\-+/*=<>?:!~%&|]\"\n    - constant.number: \"\\\\b([0-9._]+|0x[A-Fa-f0-9_]+|0b[0-1_]+)[FL]?\\\\b\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([btnfr]|'|\\\\\\\"|\\\\\\\\)\"\n            - constant.specialChar: \"\\\\\\\\u[A-Fa-f0-9]{4}\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([btnfr]|'|\\\\\\\"|\\\\\\\\)\"\n            - constant.specialChar: \"\\\\\\\\u[A-Fa-f0-9]{4}\"\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n"
  },
  {
    "path": "runtime/syntax/css.yaml",
    "content": "filetype: css\n\ndetect:\n    filename: \"\\\\.(css|scss)$\"\n\nrules:\n    # Classes and IDs\n    - statement: \"(?i).\"\n    # - normal:\n    #     start: \"\\\\{\"\n    #     end: \"\\\\}\"\n    #     rules: []\n    # css commands\n    - type: \"(align-content|align-items|alignment-baseline|align-self|all|animation|animation-delay|animation-direction|animation-duration|animation-fill-mode|animation-iteration-count|animation-name|animation-play-state|animation-timing-function|appearance|azimuth|backdrop-filter|backface-visibility|background|background-attachment|background-blend-mode|background-clip|background-color|background-image|background-origin|background-position|background-repeat|background-size|baseline-shift|bookmark-label|bookmark-level|bookmark-state|border|border-bottom|border-bottom-color|border-bottom-left-radius|border-bottom-right-radius|border-bottom-style|border-bottom-width|border-boundary|border-collapse|border-color|border-image|border-image-outset|border-image-repeat|border-image-slice|border-image-source|border-image-width|border-left|border-left-color|border-left-style|border-left-width|border-radius|border-right|border-right-color|border-right-style|border-right-width|border-spacing|border-style|border-top|border-top-color|border-top-left-radius|border-top-right-radius|border-top-style|border-top-width|border-width|bottom|box-decoration-break|box-shadow|box-sizing|box-snap|box-suppress|break-after|break-before|break-inside|caption-side|caret|caret-animation|caret-color|caret-shape|chains|clear|clip|clip-path|clip-rule|color|color-interpolation-filters|column-count|column-fill|column-gap|column-rule|column-rule-color|column-rule-style|column-rule-width|columns|column-span|column-width|content|continue|counter-increment|counter-reset|counter-set|cue|cue-after|cue-before|cursor|direction|display|dominant-baseline|elevation|empty-cells|filter|flex|flex-basis|flex-direction|flex-flow|flex-grow|flex-shrink|flex-wrap|float|float-defer|float-offset|float-reference|flood-color|flood-opacity|flow|flow-from|flow-into|font|font-family|font-feature-settings|font-kerning|font-language-override|font-size|font-size-adjust|font-stretch|font-style|font-synthesis|font-variant|font-variant-alternates|font-variant-caps|font-variant-east-asian|font-variant-ligatures|font-variant-numeric|font-variant-position|font-weight|footnote-display|footnote-policy|gap|glyph-orientation-vertical|grid|grid-area|grid-auto-columns|grid-auto-flow|grid-auto-rows|grid-column|grid-column-end|grid-column-gap|grid-column-start|grid-gap|grid-row|grid-row-end|grid-row-gap|grid-row-start|grid-template|grid-template-areas|grid-template-columns|grid-template-rows|hanging-punctuation|height|hyphenate-character|hyphenate-limit-chars|hyphenate-limit-last|hyphenate-limit-lines|hyphenate-limit-zone|hyphens|image-orientation|image-rendering|image-resolution|initial-letter|initial-letter-align|initial-letter-wrap|isolation|justify-content|justify-items|justify-self|left|letter-spacing|lighting-color|line-break|line-grid|line-height|line-snap|list-style|list-style-image|list-style-position|list-style-type|margin|margin-bottom|margin-left|margin-right|margin-top|marker|marker-end|marker-knockout-left|marker-knockout-right|marker-mid|marker-pattern|marker-segment|marker-side|marker-start|marquee-direction|marquee-loop|marquee-speed|marquee-style|mask|mask-border|mask-border-mode|mask-border-outset|mask-border-repeat|mask-border-slice|mask-border-source|mask-border-width|mask-clip|mask-composite|mask-image|mask-mode|mask-origin|mask-position|mask-repeat|mask-size|mask-type|max-height|max-lines|max-width|min-height|min-width|mix-blend-mode|motion|motion-offset|motion-path|motion-rotation|nav-down|nav-left|nav-right|nav-up|object-fit|object-position|offset-after|offset-before|offset-end|offset-start|opacity|order|orphans|outline|outline-color|outline-offset|outline-style|outline-width|overflow|overflow-style|overflow-wrap|overflow-x|overflow-y|padding|padding-bottom|padding-left|padding-right|padding-top|page|page-break-after|page-break-before|page-break-inside|pause|pause-after|pause-before|perspective|perspective-origin|pitch|pitch-range|play-during|pointer-events|polar-anchor|polar-angle|polar-distance|polar-origin|position|presentation-level|quotes|region-fragment|resize|rest|rest-after|rest-before|richness|right|rotation|rotation-point|ruby-align|ruby-merge|ruby-position|running|scrollbar-color|scroll-behavior|scroll-snap-align|scroll-snap-margin|scroll-snap-margin-block|scroll-snap-margin-block-end|scroll-snap-margin-block-start|scroll-snap-margin-bottom|scroll-snap-margin-inline|scroll-snap-margin-inline-end|scroll-snap-margin-inline-start|scroll-snap-margin-left|scroll-snap-margin-right|scroll-snap-margin-top|scroll-snap-padding|scroll-snap-padding-block|scroll-snap-padding-block-end|scroll-snap-padding-block-start|scroll-snap-padding-bottom|scroll-snap-padding-inline|scroll-snap-padding-inline-end|scroll-snap-padding-inline-start|scroll-snap-padding-left|scroll-snap-padding-right|scroll-snap-padding-top|scroll-snap-type|shape-image-threshold|shape-inside|shape-margin|shape-outside|size|speak|speak-as|speak-header|speak-numeral|speak-punctuation|speech-rate|stress|string-set|stroke|stroke-alignment|stroke-dashadjust|stroke-dasharray|stroke-dashcorner|stroke-dashoffset|stroke-linecap|stroke-linejoin|stroke-miterlimit|stroke-opacity|stroke-width|table-layout|tab-size|text-align|text-align-all|text-align-last|text-combine-upright|text-decoration|text-decoration-color|text-decoration-line|text-decoration-skip|text-decoration-style|text-emphasis|text-emphasis-color|text-emphasis-position|text-emphasis-style|text-indent|text-justify|text-orientation|text-overflow|text-shadow|text-space-collapse|text-space-trim|text-spacing|text-transform|text-underline-offset|text-underline-position|text-wrap|top|transform|transform-box|transform-origin|transform-style|transition|transition-delay|transition-duration|transition-property|transition-timing-function|unicode-bidi|user-select|vertical-align|visibility|voice-balance|voice-duration|voice-family|voice-pitch|voice-range|voice-rate|voice-stress|voice-volume|volume|white-space|widows|width|will-change|word-break|word-spacing|word-wrap|wrap-after|wrap-before|wrap-flow|wrap-inside|wrap-through|writing-mode|z-index):\"\n    # - default:\n    #     start: \":\"\n    #     end: \"[;^\\\\{]\"\n    #     rules: []\n    - special: \"!important\"\n    - identifier: \":active|:focus|:hover|:link|:visited|:link|:after|:before|$\"\n    - special: \"(\\\\{|\\\\}|\\\\(|\\\\)|\\\\;|:|\\\\]|~|<|>|,)\"\n    # SCSS Varaibles\n    - statement: \"@import|@mixin|@extend\"\n    # Strings\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - special: \"\\\"|'\"\n    # Comments & TODOs\n    - comment:\n        start: \"\\\\/\\\\*\"\n        end: \"\\\\*\\\\/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n"
  },
  {
    "path": "runtime/syntax/csx.yaml",
    "content": "filetype: csharp-script\ndetect:\n    filename: \"\\\\.csx$\"\n    header: \"^#!.*/(env +)?dotnet-script( |$)\"\n\nrules:\n    - include: \"csharp\"\n    - preproc: \"\\\\B(\\\\#!|\\\\#[r|load|]+\\\\b)\"\n"
  },
  {
    "path": "runtime/syntax/cuda.yaml",
    "content": "filetype: cuda\n\ndetect:\n    filename: \"(\\\\.cu[h]?$)\"\n\nrules:\n    - identifier: \"\\\\b[A-Z_][0-9A-Z_]*\\\\b\"\n    - type: \"\\\\b(float|double|bool|char|int|short|long|enum|void|struct|union|typedef|(un)?signed|inline)\\\\b\"\n    - type: \"\\\\b(((s?size)|((u_?)?int(8|16|32|64|ptr))|char(8|16|32))_t|wchar_t)\\\\b\"\n    - type: \"\\\\b[a-z_][0-9a-z_]+(_t|_T)\\\\b\"\n    - type: \"\\\\b(final|override)\\\\b\"\n    - type.keyword: \"\\\\b(auto|volatile|const(expr|eval|init)?|mutable|register|thread_local|static|extern|decltype|explicit|virtual)\\\\b\"\n    - statement: \"\\\\b(class|namespace|template|typename|this|friend|using|public|protected|private|noexcept)\\\\b\"\n    - statement: \"\\\\b(concept|requires)\\\\b\"\n    - statement: \"\\\\b(import|export|module)\\\\b\"\n    - statement: \"\\\\b(for|if|while|do|else|case|default|switch)\\\\b\"\n    - statement: \"\\\\b(try|throw|catch|operator|new|delete|static_assert)\\\\b\"\n    - statement: \"\\\\b(goto|continue|break|return)\\\\b\"\n    - preproc: \"^[[:space:]]*#[[:space:]]*(define|pragma|include|(un|ifn?)def|endif|el(if|se)|if|warning|error)|_Pragma\"\n\n      # Conditionally-supported/extension keywords\n    - statement: \"\\\\b(asm|fortran)\\\\b\"\n\n      # GCC builtins\n    - statement: \"(__attribute__[[:space:]]*\\\\(\\\\([^)]*\\\\)\\\\)|__(aligned|asm|builtin|hidden|inline|packed|restrict|section|typeof|weak)__)\"\n\n      # CUDA specific keywords\n    - statement: \"__(global|device|host|shared)__\"\n\n      # Operator Color\n    - symbol.operator: \"[-+*/%=<>.:;,~&|^!?]|\\\\b(sizeof|alignof|typeid|(and|or|xor|not)(_eq)?|bitor|compl|bitand|(const|dynamic|reinterpret|static)_cast)\\\\b\"\n      # Parenthetical Color\n    - symbol.brackets: \"[(){}]|\\\\[|\\\\]\"\n      # Integer Literals\n    - constant.number: \"(\\\\b([1-9][0-9']*|0[0-7']*|0[Xx][0-9a-fA-F']+|0[Bb][01]+)([Uu]?[Ll][Ll]?|[Ll][Ll]?[Uu]?)?\\\\b)\"\n      # Decimal Floating-point Literals\n    - constant.number: \"(\\\\b(([0-9']*[.][0-9']+|[0-9']+[.][0-9']*)([Ee][+-]?[0-9']+)?|[0-9']+[Ee][+-]?[0-9']+)[FfLl]?\\\\b)\"\n      # Hexadecimal Floating-point Literals\n    - constant.number: \"(\\\\b0[Xx]([0-9a-zA-Z']*[.][0-9a-zA-Z']+|[0-9a-zA-Z']+[.][0-9a-zA-Z']*)[Pp][+-]?[0-9']+[FfLl]?\\\\b)\"\n    - constant.bool: \"(\\\\b(true|false|NULL|nullptr)\\\\b)\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([\\\"'abfnrtv\\\\\\\\]|[0-3]?[0-7]{1,2}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - error: \"..+\"\n            - constant.specialChar: \"\\\\\\\\([\\\"'abfnrtv\\\\\\\\]|[0-3]?[0-7]{1,2}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})\"\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n"
  },
  {
    "path": "runtime/syntax/cython.yaml",
    "content": "filetype: cython\n\ndetect:\n    filename: \"\\\\.pyx$|\\\\.pxd$|\\\\.pyi$\"\n\nrules:\n    # Python Keyword Color\n    - statement: \"\\\\b(and|as|assert|class|def|DEF|del|elif|ELIF|else|ELSE|except|exec|finally|for|from|global|if|IF|import|in|is|lambda|map|not|or|pass|print|raise|try|while|with|yield)\\\\b\"\n    - special: \"\\\\b(continue|break|return)\\\\b\"\n\n      # Cython Keyword Color\n    - identifier.macro: \"\\\\b(cdef|cimport|cpdef|cppclass|ctypedef|extern|include|namespace|property|struct)\\\\b\"\n    - type: \"\\\\b(bint|char|double|int|public|void|unsigned)\\\\b\"\n\n      # Operator Color\n    - symbol: \"[.:;,+*|=!\\\\%]|<|>|/|-|&\"\n\n      # Parenthetical Color\n    - symbol.brackets: \"[(){}]|\\\\[|\\\\]\"\n\n    - constant.string:\n        start: \"\\\"\\\"\\\"\"\n        end: \"\\\"\\\"\\\"\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'''\"\n        end: \"'''\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n"
  },
  {
    "path": "runtime/syntax/d.yaml",
    "content": "filetype: d\n\ndetect:\n    filename: \"\\\\.(d(i|d)?)$\"\n\nrules:\n    # Operators and punctuation\n    - statement: \"(\\\\*|/|%|\\\\+|-|>>|<<|>>>|&|\\\\^(\\\\^)?|\\\\||~)?=\"\n    - statement: \"\\\\.\\\\.(\\\\.)?|!|\\\\*|&|~|\\\\(|\\\\)|\\\\[|\\\\]|\\\\\\\\|/|\\\\+|-|%|<|>|\\\\?|:|;\"\n    # Octal integer literals are deprecated\n    - error: \"(0[0-7_]*)(L[uU]?|[uU]L?)?\"\n    # Decimal integer literals\n    - constant.number: \"([0-9]|[1-9][0-9_]*)(L[uU]?|[uU]L?)?\\\\b\"\n    # Binary integer literals\n    - constant: \"(0[bB][01_]*)(L[uU]?|[uU]L?)?\"\n    # Decimal float literals\n    - constant.number: \"[0-9][0-9_]*\\\\.([0-9][0-9_]*)([eE][+-]?([0-9][0-9_]*))?[fFL]?i?\"\n    - constant.number: \"[0-9][0-9_]*([eE][+-]?([0-9][0-9_]*))[fFL]?i?\"\n    - constant.number: \"[^.]\\\\.([0-9][0-9_]*)([eE][+-]?([0-9][0-9_]*))?[fFL]?i?\"\n    - constant.number: \"[0-9][0-9_]*([fFL]?i|[fF])\"\n    # Hexadecimal integer literals\n    - constant.number: \"(0[xX]([0-9a-fA-F][0-9a-fA-F_]*|[0-9a-fA-F_]*[0-9a-fA-F]))(L[uU]?|[uU]L?)?\"\n    # Hexadecimal float literals\n    - constant.number: \"0[xX]([0-9a-fA-F][0-9a-fA-F_]*|[0-9a-fA-F_]*[0-9a-fA-F])(\\\\.[0-9a-fA-F][0-9a-fA-F_]*|[0-9a-fA-F_]*[0-9a-fA-F])?[pP][+-]?([0-9][0-9_]*)[fFL]?i?\"\n    - constant.number: \"0[xX]\\\\.([0-9a-fA-F][0-9a-fA-F_]*|[0-9a-fA-F_]*[0-9a-fA-F])[pP][+-]?([0-9][0-9_]*)[fFL]?i?\"\n    # Character literals\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    # Keywords\n    # a-e\n    - statement: \"\\\\b(abstract|alias|align|asm|assert|auto|body|break|case|cast|catch|class|const|continue|debug|default|delegate|do|else|enum|export|extern)\\\\b\"\n    # f-l\n    - statement: \"\\\\b(false|final|finally|for|foreach|foreach_reverse|function|goto|if|immutable|import|in|inout|interface|invariant|is|lazy)\\\\b\"\n    # m-r\n    - statement: \"\\\\b(macro|mixin|module|new|nothrow|null|out|override|package|pragma|private|protected|public|pure|ref|return)\\\\b\"\n    # s-w\n    - statement: \"\\\\b(scope|shared|static|struct|super|switch|synchronized|template|this|throw|true|try|typeid|typeof|union|unittest|version|while|with)\\\\b\"\n    # __\n    - statement: \"\\\\b(__FILE__|__MODULE__|__LINE__|__FUNCTION__|__PRETTY_FUNCTION__|__gshared|__traits|__vector|__parameters)\\\\b\"\n    # Deprecated keywords\n    - error: \"\\\\b(delete|deprecated|typedef|volatile)\\\\b\"\n    # Primitive types\n    - type: \"\\\\b(bool|byte|cdouble|cent|cfloat|char|creal|dchar|double|float|idouble|ifloat|int|ireal|long|real|short|ubyte|ucent|uint|ulong|ushort|void|wchar)\\\\b\"\n    # Globally defined symbols\n    - type: \"\\\\b(string|wstring|dstring|size_t|ptrdiff_t)\\\\b\"\n    # Special tokens\n    - constant: \"\\\\b(__DATE__|__EOF__|__TIME__|__TIMESTAMP__|__VENDOR__|__VERSION__)\\\\b\"\n    # String literals\n    # DoubleQuotedString\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    # WysiwygString\n    - constant.string:\n        start: \"r\\\"\"\n        end: \"\\\"\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - constant.string:\n        start: \"`\"\n        end: \"`\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    # HexString\n    - constant.string:\n        start: \"x\\\"\"\n        end: \"\\\"\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    # DelimitedString\n    - constant.string:\n        start: \"q\\\"\\\\(\"\n        end: \"\\\\)\\\"\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - constant.string:\n        start: \"q\\\"\\\\{\"\n        end: \"q\\\"\\\\}\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - constant.string:\n        start: \"q\\\"\\\\[\"\n        end: \"q\\\"\\\\]\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - constant.string:\n        start: \"q\\\"<\"\n        end: \"q\\\">\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - constant.string:\n        start: \"q\\\"[^({[<\\\"][^\\\"]*$\"\n        end: \"^[^\\\"]+\\\"\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - constant.string:\n        start: \"q\\\"([^({[<\\\"])\"\n        end: \"\\\"\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    # Comments\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules: []\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules: []\n    - comment:\n        start: \"/\\\\+\"\n        end: \"\\\\+/\"\n        rules: []\n\n"
  },
  {
    "path": "runtime/syntax/dart.yaml",
    "content": "filetype: dart\n\ndetect:\n    filename: \"\\\\.dart$\"\n\nrules:\n    - constant.number: \"\\\\b[-+]?([1-9][0-9]*|0[0-7]*|0x[0-9a-fA-F]+)([uU][lL]?|[lL][uU]?)?\\\\b\"\n    - constant.number: \"\\\\b[-+]?([0-9]+\\\\.[0-9]*|[0-9]*\\\\.[0-9]+)([EePp][+-]?[0-9]+)?[fFlL]?\"\n    - constant.number: \"\\\\b[-+]?([0-9]+[EePp][+-]?[0-9]+)[fFlL]?\"\n    - identifier: \"[A-Za-z_][A-Za-z0-9_]*[[:space:]]*[(]\"\n    - statement: \"\\\\b(break|case|catch|continue|default|else|finally)\\\\b\"\n    - statement: \"\\\\b(for|function|get|if|in|as|is|new|return|set|switch|final|await|async|sync)\\\\b\"\n    - statement: \"\\\\b(switch|this|throw|try|var|void|while|with|import|library|part|const|export)\\\\b\"\n    - constant: \"\\\\b(true|false|null)\\\\b\"\n    - type: \"\\\\b(List|String)\\\\b\"\n    - type: \"\\\\b(int|num|double|bool)\\\\b\"\n    - statement: \"[-+/*=<>!~%?:&|]\"\n    - constant: \"/[^*]([^/]|(\\\\\\\\/))*[^\\\\\\\\]/[gim]*\"\n    - constant: \"\\\\\\\\[0-7][0-7]?[0-7]?|\\\\\\\\x[0-9a-fA-F]+|\\\\\\\\[bfnrt'\\\"\\\\?\\\\\\\\]\"\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"TODO:?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"TODO:?\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n"
  },
  {
    "path": "runtime/syntax/default.yaml",
    "content": "filetype: unknown\n\ndetect:\n    filename: \"\"\n\nrules:\n    # Mails\n    - special: \"[[:alnum:].%_+-]+@[[:alnum:].-]+\"\n    # URLs\n    - identifier: \"(https?|ftp|ssh)://\\\\S*[^])>\\\\s,.]\"\n"
  },
  {
    "path": "runtime/syntax/dockerfile.yaml",
    "content": "filetype: dockerfile\n\ndetect:\n    filename: \"((Docker|Container)file[^/]*$|\\\\.(docker|container)file$)\"\n\nrules:\n    ## Keywords\n    - type.keyword: \"(?i)^(FROM|MAINTAINER|RUN|CMD|LABEL|EXPOSE|ENV|ADD|COPY|ENTRYPOINT|VOLUME|USER|WORKDIR|ONBUILD|ARG|HEALTHCHECK|STOPSIGNAL|SHELL)[[:space:]]\"\n\n      ## Brackets & parenthesis\n    - statement: \"(\\\\(|\\\\)|\\\\[|\\\\])\"\n\n      ## Double ampersand\n    - special: \"&&\"\n\n      ## Comments\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n"
  },
  {
    "path": "runtime/syntax/dot.yaml",
    "content": "filetype: dot\n\ndetect:\n    filename: \"\\\\.(dot|gv)$\"\n\nrules:\n    - type:   \"\\\\b(digraph|edge|graph|node|subgraph)\\\\b\"\n    - statement: \"\\\\b(arrow(head|size|tail)|(bg|fill|font)?color|center|constraint|decorateP|dir|distortion|font(name|size)|head(clip|label)|height|label(angle|distance|font(color|name|size))?|layer(s)?|margin|mclimit|minlen|name|nodesep|nslimit|ordering|orientation|page(dir)?|peripheries|port_label_distance|rank(dir|sep)?|ratio|regular|rotate|same(head|tail)|shape(file)?|sides|size|skew|style|tail(clip|label)|URL|weight|width)\\\\b\"\n    - symbol:  \"=|->|--\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n"
  },
  {
    "path": "runtime/syntax/elixir.yaml",
    "content": "filetype: elixir\n\ndetect:\n    filename: \"\\\\.ex$|\\\\.exs$\"\n\nrules:\n    - statement: \"\\\\b(abs|trunc|rem|div|round|max|min|and|or|not|throw|raise|reraise|hd|tl|in|length|elem|put_elem|destructure|to_(string|charlist)|is_(atom|binary|bitstring|boolean|float|function|integer|list|map|nil|number|pid|port|reference|tuple)|(bit|byte|map|tuple)_size|binary_part|def(delegate|exception|guard|guardp|impl|macro|macrop|module|overridable|p|protocol|struct)?|sigil_[crswCRSWDNT]|if|else|unless|cond|binding|node|self|spawn|spawn_link|spawn_monitor|send|exit|struct|get_and_update_in|get_in|put_in|pop_in|update_in|apply|inspect|make_ref|use|do|end)\\\\b\"\n    - statement: \"\\\\b(alias|import|require|case|fn|receive|after|try|catch|rescue|super|quote|unquote|unquote_splicing|for|with)\\\\b\"\n\n    - constant: \"\\\\b\\\\[A-Z]+\\\\b\"\n    - constant.number: \"\\\\b[0-9]+\\\\b\"\n\n    - constant.string: \"`[^`]*`|%x\\\\{[^}]*\\\\}\"\n    - constant.string: \"\\\"([^\\\"]|(\\\\\\\\\\\"))*\\\"|%[QW]?\\\\{[^}]*\\\\}|%[QW]?\\\\([^)]*\\\\)|%[QW]?<[^>]*>|%[QW]?\\\\[[^]]*\\\\]|%[QW]?\\\\$[^$]*\\\\$|%[QW]?\\\\^[^^]*\\\\^|%[QW]?![^!]*!\"\n    - constant.string: \"'([^']|(\\\\\\\\'))*'|%[qw]\\\\{[^}]*\\\\}|%[qw]\\\\([^)]*\\\\)|%[qw]<[^>]*>|%[qw]\\\\[[^]]*\\\\]|%[qw]\\\\$[^$]*\\\\$|%[qw]\\\\^[^^]*\\\\^|%[qw]![^!]*!\"\n\n    - symbol.brackets: \"\\\\{|\\\\}|\\\\[|\\\\]|\\\\(|\\\\)\"\n\n    - comment: \"#[^{].*$|#$\"\n    - comment.bright: \"##[^{].*$|##$\"\n\n    - type.keyword: \"\\\\:[a-zA-Z][a-zA-Z0-9_]*\"\n    - type.keyword: \"\\\\b(describe|test)\"\n    - statement: \"\\\\b(expected|assert|assert_raise|assert_in_delta|assert_received|catch_error|catch_throw|flunk|refute|refute_in_delta|refute_received)\\\\b\"\n    - symbol.tag: \"^\\\\s*\\\\@[a-zA-Z][a-zA-Z0-9_]*\\\\b\"\n\n    - identifier.macro: \"\\\\b(__CALLER__|__DIR__|__ENV__|__MODULE__|__aliases__|__block__|defmacro)\\\\b\"\n\n    - todo: \"(XXX|TODO|FIXME|\\\\?\\\\?\\\\?)\"\n    - preproc.shebang: \"\\\\W*#!.+?( |$)\"\n"
  },
  {
    "path": "runtime/syntax/elm.yaml",
    "content": "filetype: elm\n\ndetect:\n    filename: \"\\\\.elm$\"\n\nrules:\n    - statement: \"\\\\b(as|alias|case|else|exposing|if|import|in|let|module|of|port|then|type|)\\\\b\"\n    - statement: \"(\\\\=|\\\\:|\\\\->)\"\n    - type: \"\\\\b([A-Z][A-Za-z\\\\d]*)\\\\b\"\n    - identifier: \"^([a-z][A-Za-z\\\\d]*)\\\\b\"\n    - constant.string:\n        start: \"\\\"\\\"\\\"\"\n        end: \"\\\"\\\"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - comment:\n        start: \"--\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n    - comment:\n        start: \"\\\\{-\"\n        end: \"-\\\\}\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/erb.yaml",
    "content": "filetype: erb\n\ndetect:\n    filename: \"\\\\.erb$|\\\\.rhtml$\"\n\nrules:\n    - error: \"<[^!].*?>\"\n    - symbol.tag: \"(?i)<[/]?(a(bbr|cronym|ddress|pplet|rea|rticle|side|udio)?|b(ase(font)?|d(i|o)|ig|lockquote|r)?|ca(nvas|ption)|center|cite|co(de|l|lgroup)|d(ata(list)?|d|el|etails|fn|ialog|ir|l|t)|em(bed)?|fieldset|fig(caption|ure)|font|form|(i)?frame|frameset|h[1-6]|hr|i|img|in(put|s)|kbd|keygen|label|legend|li(nk)?|ma(in|p|rk)|menu(item)?|met(a|er)|nav|no(frames|script)|o(l|pt(group|ion)|utput)|p(aram|icture|re|rogress)?|q|r(p|t|uby)|s(trike)?|samp|se(ction|lect)|small|source|span|strong|su(b|p|mmary)|textarea|time|track|u(l)?|var|video|wbr)( .*|>)*?>\"\n    - symbol.tag.extended: \"(?i)<[/]?(body|div|html|head(er)?|footer|title|table|t(body|d|h(ead)?|r|foot))( .*|>)*?>\"\n    - preproc: \"(?i)<[/]?(script|style)( .*|>)*?>\"\n    - special: \"&[^;[[:space:]]]*;\"\n    - symbol: \"[:=]\"\n    - identifier: \"(alt|bgcolor|height|href|id|label|longdesc|name|onclick|onfocus|onload|onmouseover|size|span|src|style|target|type|value|width)=\"\n    - constant.string: \"\\\"[^\\\"]*\\\"\"\n    - constant.number: \"(?i)#[0-9a-fA-F]{6,6}\"\n    - constant.string.url: \"(ftp(s)?|http(s)?|git|chrome)://[^ \t]+\"\n    - comment: \"<!--.+?-->\"\n    - preproc: \"<!DOCTYPE.+?>\"\n    - default:\n        start: \"<%\"\n        end: \"%>\"\n        rules: []\n\n    - preproc: \"<%|%>\"\n    - red: \"&[^;[[:space:]]]*;\"\n    - statement: \"\\\\b(BEGIN|END|alias|and|begin|break|case|class|def|defined\\\\?|do|else|elsif|end|ensure|false|for|if|in|module|next|nil|not|or|redo|rescue|retry|return|self|super|then|true|undef|unless|until|when|while|yield)\\\\b\"\n    - identifier.var: \"(\\\\$|@|@@)?\\\\b[A-Z]+[0-9A-Z_a-z]*\"\n    - magenta: \"(?i)([  ]|^):[0-9A-Z_]+\\\\b\"\n    - identifier.macro: \"\\\\b(__FILE__|__LINE__)\\\\b\"\n    - brightmagenta: \"!/([^/]|(\\\\\\\\/))*/[iomx]*|%r\\\\{([^}]|(\\\\\\\\}))*\\\\}[iomx]*\"\n    - brightblue: \"`[^`]*`|%x\\\\{[^}]*\\\\}\"\n    - constant.string: \"\\\"([^\\\"]|(\\\\\\\\\\\"))*\\\"|%[QW]?\\\\{[^}]*\\\\}|%[QW]?\\\\([^)]*\\\\)|%[QW]?<[^>]*>|%[QW]?\\\\[[^]]*\\\\]|%[QW]?\\\\$[^$]*\\\\$|%[QW]?\\\\^[^^]*\\\\^|%[QW]?![^!]*!\"\n    - brightgreen: \"#\\\\{[^}]*\\\\}\"\n    - green: \"'([^']|(\\\\\\\\'))*'|%[qw]\\\\{[^}]*\\\\}|%[qw]\\\\([^)]*\\\\)|%[qw]<[^>]*>|%[qw]\\\\[[^]]*\\\\]|%[qw]\\\\$[^$]*\\\\$|%[qw]\\\\^[^^]*\\\\^|%[qw]![^!]*!\"\n    - comment: \"#[^{].*$|#$\"\n    - comment.bright: \"##[^{].*$|##$\"\n    - identifier.macro:\n        start: \"<<-?'?EOT'?\"\n        end: \"^EOT\"\n        rules: []\n\n    - todo: \"(XXX|TODO|FIXME|\\\\?\\\\?\\\\?)\"\n"
  },
  {
    "path": "runtime/syntax/erlang.yaml",
    "content": "filetype: erlang\n\ndetect:\n    filename: \"\\\\.erl$\"\n\nrules:\n    - identifier: \"\\\\b[A-Z][0-9a-z_]*\\\\b\"\n    # See: https://erlang.org/doc/reference_manual/data_types.html\n    - constant.number: \"\\\\b[0-9]+(\\\\.[0-9]+)?(e-?[0-9]+)?\\\\b\"\n    - constant.number: \"\\\\b[0-9]{1,2}\\\\#[a-zA-Z0-9]+\\\\b\"\n    - constant.bool: \"\\\\b(true|false)\\\\b\"\n    - constant.number: \"\\\\$\\\\\\\\?\\\\S{1}\"\n    # See: https://erlang.org/doc/reference_manual/introduction.html\n    - statement: \"\\\\b(after|and|andalso|band|begin|bnot|bor|bsl|bsr|bxor|case|catch|cond|div|end|fun|if|let|not|of|or|orelse|receive|rem|try|when|xor)\\\\b\"\n    # See: https://erlang.org/doc/reference_manual/macros.html\n    - preproc: \"\\\\-(module|export|record|include|include_lib|define|undef|ifdef|ifndef|else|endif|if|elif|error|warning)\\\\b\"\n    - identifier.macro: \"\\\\?[A-Z0-9_]+\\\\b\"\n    # See: https://erlang.org/doc/man/erlang.html\n    - special: \"\\\\b(ext_binary|binary|iovec|message_queue_data|time(_unit|stamp)|abs|apply|atom(_to_binary|_to_list)|binary_(part|to_atom|to_existing_atom|to_float|to_integer|to_list|to_term)|bit(_size|string_to_list)|byte_size|ceil|check_(old_code|process_code)|date|delete_module|demonitor|disconnect_node|element|erase|error|exit|float(_to_binary|_to_list)?|floor|garbage_collect|get|group_leader|halt|integer(_to_binary|to_list)|iolist_(size|to_binary)|is_(alive|atom|binary|bitstring|boolean|float|function|integer|list|map|map_key|number|pid|port|process_alive|record|reference|tuple|length)|link|list_to_(atom|binary|bitstring|existing_atom|float|integer|pid|port|ref|tuple)|load_module|make_ref|map_(get|size)|max|min|module_loaded|monitor(_node)?|nodes?|now|open_port|pid_to_list|port(_close|command|connect|control|to_list)|pre_loaded|process(_flag|_info|es)|purge_module|put|register(ed)?|round|self|setelement|size|spawn(_link|_monitor|_opt|_binary)?|statistics|trunc|tuple_(size|to_list)|unlink|unregister|whereis)\\\\b\"\n    # See: https://erlang.org/doc/reference_manual/data_types.html#atom\n    - symbol:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules: []\n#            - constant.specialChar: \"%.\"\n#            - constant.specialChar: \"\\\\\\\\[abfnrtv'\\\\\\\"\\\\\\\\]\"\n#            - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})\"\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"%.\"\n            - constant.specialChar: \"\\\\\\\\[abfnrtv'\\\\\\\"\\\\\\\\]\"\n            - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})\"\n    - comment:\n        start: \"\\\\(\\\\*\"\n        end: \"\\\\*\\\\)\"\n        rules:\n            - todo: \"(TODO|FIXME|WONTFIX|NOTE|HACK):?\"\n    - comment:\n        start: \"%\"\n        end: \"$\"\n        rules: []\n"
  },
  {
    "path": "runtime/syntax/fish.yaml",
    "content": "filetype: fish\n\ndetect:\n    filename: \"\\\\.fish$\"\n    header: \"^#!.*/(env +)?fish( |$)\"\n\nrules:\n      # Numbers\n    - constant: \"\\\\b[0-9]+\\\\b\"\n\n      # Conditionals and control flow\n    - statement: \"\\\\b(and|begin|break|case|continue|else|end|for|function|if|in|not|or|return|select|shift|switch|while)\\\\b\"\n    - special: \"(\\\\{|\\\\}|\\\\(|\\\\)|\\\\;|\\\\]|\\\\[|`|\\\\\\\\|\\\\$|<|>|^|!|=|&|\\\\|)\"\n\n      # Fish commands\n    - type: \"\\\\b(bg|bind|block|breakpoint|builtin|cd|count|command|commandline|complete|dirh|dirs|echo|emit|eval|exec|exit|fg|fish|fish_config|fish_ident|fish_pager|fish_prompt|fish_right_prompt|fish_update_completions|fishd|funced|funcsave|functions|help|history|jobs|math|mimedb|nextd|open|popd|prevd|psub|pushd|pwd|random|read|set|set_color|source|status|string|trap|type|ulimit|umask|vared)\\\\b\"\n\n      # Common linux commands\n    - type: \"\\\\b((g|ig)?awk|bash|dash|find|\\\\w{0,4}grep|kill|killall|\\\\w{0,4}less|make|pkill|sed|sh|tar)\\\\b\"\n\n      # Coreutils commands\n    - type: \"\\\\b(base64|basename|cat|chcon|chgrp|chmod|chown|chroot|cksum|comm|cp|csplit|cut|date|dd|df|dir|dircolors|dirname|du|env|expand|expr|factor|false|fmt|fold|head|hostid|id|install|join|link|ln|logname|ls|md5sum|mkdir|mkfifo|mknod|mktemp|mv|nice|nl|nohup|nproc|numfmt|od|paste|pathchk|pinky|pr|printenv|printf|ptx|pwd|readlink|realpath|rm|rmdir|runcon|seq|(sha1|sha224|sha256|sha384|sha512)sum|shred|shuf|sleep|sort|split|stat|stdbuf|stty|sum|sync|tac|tail|tee|test|time|timeout|touch|tr|true|truncate|tsort|tty|uname|unexpand|uniq|unlink|users|vdir|wc|who|whoami|yes)\\\\b\"\n\n      # Conditional flags\n    - statement: \"--[a-z-]+\"\n    - statement: \"\\\\ -[a-z]+\"\n\n    - identifier: \"(?i)\\\\{?\\\\$[0-9A-Z_!@#$*?-]+\\\\}?\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules: []\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n"
  },
  {
    "path": "runtime/syntax/forth.yaml",
    "content": "filetype: forth\n\ndetect:\n    filename: \"\\\\.(forth|4th|fs|fs8|ft|fth|frt)$\"\n\nrules:\n    - identifier: \"\\\\b[A-Za-z_0-9-]*\\\\b\"\n\n    - statement: \"\\\\b(?i:(if|else|then|do|loop|case|endcase|of|endof|begin|while|repeat|until|again|unloop|leave|exit|done|next|\\\\?do|\\\\+do|\\\\-do|\\\\+loop|\\\\-loop|\\\\?leave))\\\\b\"\n\n    - statement: \"(^:|;$)\"\n\n    - type: \"\\\\b(?i:(variable|constant|cells))\\\\b\"\n\n    - special: \"\\\\B[?.]\\\\B\" #for some reason, \\b and \\B are inverted for symbols\n\n    - constant.number: \"\\\\b[0-9]+\\\\b\"\n\n    - constant.string:\n        start: \"\\\\b([Ss.]\\\" )\"\n        end: \"\\\"\"\n        rules: []\n\n    - comment:\n        start: \"\\\\(\"\n        end: \"\\\\)\"\n        rules:\n            - todo: \"(TODO|NOTE|XXX|FIXME):?\"\n\n    - comment:\n        start: \"\\\\\\\\\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|NOTE|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/fortran.yaml",
    "content": "filetype: fortran\n\ndetect:\n    filename: \"\\\\.([Ff]|[Ff]90|[Ff]95|[Ff][Oo][Rr])$\"\n\nrules:\n    - type:  \"(?i)\\\\b(action|advance|all|allocatable|allocated|any|apostrophe)\\\\b\"\n    - type:  \"(?i)\\\\b(append|asis|assign|assignment|associated|bind|character|common)\\\\b\"\n    - type:  \"(?i)\\\\b(complex|data|default|delim|dimension|double precision)\\\\b\"\n    - type:  \"(?i)\\\\b(elemental|enum|enumerator|epsilon|external|file|fmt|form|format|huge)\\\\b\"\n    - type:  \"(?i)\\\\b(implicit|include|index|inquire|integer|intent|interface)\\\\b\"\n    - type:  \"(?i)\\\\b(intrinsic|iostat|kind|logical|module|none|null|only)\\\\\\\\b\"\n    - type:  \"(?i)\\\\b(operator|optional|pack|parameter|pointer|position|private)\\\\b\"\n    - type:  \"(?i)\\\\b(program|public|real|recl|recursive|selected_int_kind)\\\\b\"\n    - type:  \"(?i)\\\\b(selected_real_kind|subroutine|status|module|function|logical)\\\\b\"\n\n    - constant:  \"(?i)\\\\b(abs|achar|adjustl|adjustr|allocate|bit_size|call|char)\\\\b\"\n    - constant:  \"(?i)\\\\b(close|contains|count|cpu_time|cshift|date_and_time)\\\\b\"\n    - constant:  \"(?i)\\\\b(deallocate|digits|dot_product|eor|eoshift|iachar)\\\\b\"\n    - constant:  \"(?i)\\\\b(iand|ibclr|ibits|ibset|ichar|ieor|iolength|ior|ishft|ishftc)\\\\b\"\n    - constant:  \"(?i)\\\\b(lbound|len|len_trim|matmul|maxexponent|maxloc|maxval|merge)\\\\b\"\n    - constant:  \"(?i)\\\\b(minexponent|minloc|minval|mvbits|namelist|nearest|nullify)\\\\b\"\n    - constant:  \"(?i)\\\\b(open|pad|present|print|product|pure|quote|radix)\\\\b\"\n    - constant:  \"(?i)\\\\b(random_number|random_seed|range|read|readwrite|replace)\\\\b\"\n    - constant:  \"(?i)\\\\b(reshape|rewind|save|scan|sequence|shape|sign|size|spacing)\\\\b\"\n    - constant:  \"(?i)\\\\b(spread|sum|system_clock|target|transfer|transpose|trim)\\\\b\"\n    - constant:  \"(?i)\\\\b(ubound|unpack|verify|write|tiny|type|use|yes|true|false|not)\\\\b\"\n\n    - constant.number: \"\\\\b([0-9]+)\\\\b\"\n\n    - statement: \"(?i)\\\\b(.and.|case|do|else|else?if|else?where|end|end?do|end?if)\\\\b\"\n    - statement: \"(?i)\\\\b(end?select|.eqv.|forall|if|lge|lgt|lle|llt|.neqv.|.not.)\\\\b\"\n    - statement: \"(?i)\\\\b(or|and|repeat|select|case|then|where|while|import)\\\\b\"\n\n    - special: \"(?i)\\\\b(continue|cycle|exit|go?to|result|return)\\\\b\"\n\n      #Operator Color\n    - symbol.operator: \"[.:;,+*|=!\\\\%]|/|-|>|<|&\"\n\n      #Parenthetical Color\n    - symbol.bracket: \"[(){}]|\\\\[|\\\\]\"\n\n      # Add preprocessor commands.\n    - preproc: \"^[[:space:]]*#[[:space:]]*(define|include|(un|ifn?)def|endif|el(if|se)|if|warning|error)\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \"!\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/freebsd-kernel.yaml",
    "content": "filetype: freebsd-kernel\n\ndetect:\n    filename: \"GENERIC$\"\n\nrules:\n    - identifier: \"^(cpu|ident|options|makeoptions|device|include)\"\n    - statement: \"\\\\s\\\\S*\"\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/fsharp.yaml",
    "content": "filetype: fsharp\n\ndetect:\n    filename: \"\\\\.fs?$\"\n\nrules:\n    - identifier: \"\\\\b[A-Z][0-9a-z_]{2,}\\\\b\"\n      #declarations\n    - statement: \"\\\\b(let|val|method|in|and|rec|private|virtual|constraint)\\\\b\"\n      #structure items\n    - type: \"\\\\b(type|open|class|module|exception|external)\\\\b\"\n      #patterns\n    - statement: \"\\\\b(fun|function|functor|match|try|with)\\\\b\"\n      #patterns-modifiers\n    - statement: \"\\\\b(as|when|of)\\\\b\"\n      #conditions\n    - statement: \"\\\\b(if|then|else)\\\\b\"\n      #blocs\n    - type: \"\\\\b(begin|end|object|struct|sig|for|while|do|done|to|downto)\\\\b\"\n      #constantes\n    - constant.bool: \"\\\\b(true|false)\\\\b\"\n      #modules/classes\n    - special: \"\\\\b(include|inherit|initializer)\\\\b\"\n      #expr modifiers\n    - special: \"\\\\b(new|ref|mutable|lazy|assert|raise)\\\\b\"\n      #keywords which don't exist in ocaml\n    - type: \"\\\\b(base|delegate|downcast|extern|finally|fixed|global|inline|interface|internal|let!|member|namespace|null|override|private|public)\\\\b\"\n    - type: \"\\\\b(return|return!|select|static|upcast|use|use!|void|yield|yield!)\\\\b\"\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"%.\"\n            - constant.specialChar: \"\\\\\\\\[abfnrtv'\\\\\\\"\\\\\\\\]\"\n            - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})\"\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"%.\"\n            - constant.specialChar: \"\\\\\\\\[abfnrtv'\\\\\\\"\\\\\\\\]\"\n            - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})\"\n    - comment:\n        start: \"\\\\(\\\\*\"\n        end: \"\\\\*\\\\)\"\n        rules: []\n"
  },
  {
    "path": "runtime/syntax/gdscript.yaml",
    "content": "filetype: gdscript\n\ndetect:\n    filename: \"\\\\.gd$\"\n\nrules:\n    # Built-in constants\n    - constant: \"\\\\b(INF|NAN|PI|TAU)\\\\b\"\n    - constant.bool: \"\\\\b(null|true|false)\\\\b\"\n    # Built-in functions\n    - identifier: \"\\\\b(abs|acos|asin|atan|atan2|ceil|clamp|convert|cos|cosh|db2linear|decimals|deg2rad|ease|exp|float|floor|fmod|fposmod|hash|int|isinf|isnan|lerp|linear2db|load|log|max|min|nearest_po2|pow|preload|print|printerr|printraw|prints|printt|rad2deg|rand_range|rand_seed|randomize|randi|randf|range|round|seed|sin|slerp|sqrt|str|str2var|tan|typeof|var2str|weakref)\\\\b\"\n    # Built-in node names\n    - identifier: \"\\\\b(AnimationPlayer|AnimationTreePlayer|Button|Control|Engine|HTTPClient|HTTPRequest|Input|InputEvent|MainLoop|Node|Node2D|OS|SceneTree|Spatial|StreamPeer|PacketPeer|PacketPeerUDP|Timer|Tween)\\\\b\"\n    # Types\n    - type: \"\\\\b(AABB|Array|Basis|Color|Dictionary|NodePath|Object|Plane|PoolByteArray|PoolColorArray|PoolIntArray|PoolRealArray|PoolVector2Array|PoolVector3Array|Quat|Rect2|RID|String|Transform|Transform2D|Vector2|Vector3)\\\\b\"\n    # Definitions\n    - identifier: \"func [a-zA-Z_0-9]+\"\n    # Keywords\n    - statement: \"\\\\b(and|as|assert|break|breakpoint|class|const|continue|elif|else|enum|export|extends|for|func|if|in|is|map|master|mastersync|match|not|onready|or|pass|remote|remotesync|return|self|setget|slave|slavesync|signal|sync|tool|var|while|yield)\\\\b\"\n\n    # Operators\n    - statement: \"[.:;,+*|=!\\\\%@]|<|>|/|-|&\"\n\n    # Parentheses\n    - statement: \"[(){}]|\\\\[|\\\\]\"\n\n    # Numbers\n    - constant: \"\\\\b[0-9]+\\\\b\"\n    - constant.number: \"\\\\b([0-9]+|0x[0-9a-fA-F]*)\\\\b|'.'\"\n\n    - comment:\n        start: \"\\\"\\\"\\\"\"\n        end: \"\\\"\\\"\\\"\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"'''\"\n        end: \"'''\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})\"\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/gemini.yaml",
    "content": "filetype: gemini\n\ndetect:\n    filename: \"\\\\.(gmi|gemini)$\"\n\nrules:\n      # link lines\n    - constant: \"^=>[[:space:]].*\"\n      # preformatted text lines\n    - special:\n        start: \"^```\"\n        end: \"^```\"\n        rules: []\n      # heading lines\n    - special:  \"^#{1,3}.*\"\n      # unordered list items\n    - identifier: \"^\\\\*[[:space:]]\"\n      # quote lines\n    - statement:  \"^>.*\"\n"
  },
  {
    "path": "runtime/syntax/gentoo-ebuild.yaml",
    "content": "filetype: ebuild\n\ndetect:\n    filename: \"\\\\.e(build|class)$\"\n\nrules:\n    # All the standard portage functions\n    - identifier: \"^src_(unpack|compile|install|test)|^pkg_(config|nofetch|setup|(pre|post)(inst|rm))\"\n      # Highlight bash related syntax\n    - statement: \"\\\\b(case|do|done|elif|else|esac|exit|fi|for|function|if|in|local|read|return|select|shift|then|time|until|while|continue|break)\\\\b\"\n    - statement: \"(\\\\{|\\\\}|\\\\(|\\\\)|\\\\;|\\\\]|\\\\[|`|\\\\\\\\|\\\\$|<|>|!|=|&|\\\\|)\"\n    - statement: \"-(e|d|f|r|g|u|w|x|L)\\\\b\"\n    - statement: \"-(eq|ne|gt|lt|ge|le|s|n|z)\\\\b\"\n      # Highlight variables ... official portage ones in red, all others in bright red\n    - preproc: \"\\\\$\\\\{?[a-zA-Z_0-9]+\\\\}?\"\n    - special: \"\\\\b(ARCH|HOMEPAGE|DESCRIPTION|IUSE|SRC_URI|LICENSE|SLOT|KEYWORDS|FILESDIR|WORKDIR|(P|R)?DEPEND|PROVIDE|DISTDIR|RESTRICT|USERLAND)\\\\b\"\n    - special: \"\\\\b(S|D|T|PV|PF|P|PN|A)\\\\b|\\\\bC(XX)?FLAGS\\\\b|\\\\bLDFLAGS\\\\b|\\\\bC(HOST|TARGET|BUILD)\\\\b\"\n      # Highlight portage commands\n    - identifier: \"\\\\buse(_(with|enable))?\\\\b [!a-zA-Z0-9_+ -]*|inherit.*\"\n    - statement: \"\\\\be(begin|end|conf|install|make|warn|infon?|error|log|patch|new(group|user))\\\\b\"\n    - statement: \"\\\\bdie\\\\b|\\\\buse(_(with|enable))?\\\\b|\\\\binherit\\\\b|\\\\bhas\\\\b|\\\\b(has|best)_version\\\\b|\\\\bunpack\\\\b\"\n    - statement: \"\\\\b(do|new)(ins|s?bin|doc|lib(\\\\.so|\\\\.a)|man|info|exe|initd|confd|envd|pam|menu|icon)\\\\b\"\n    - statement: \"\\\\bdo(python|sed|dir|hard|sym|html|jar|mo)\\\\b|\\\\bkeepdir\\\\b\"\n    - statement: \"prepall(docs|info|man|strip)|prep(info|lib|lib\\\\.(so|a)|man|strip)\"\n    - statement: \"\\\\b(doc|ins|exe)into\\\\b|\\\\bf(owners|perms)\\\\b|\\\\b(exe|ins|dir)opts\\\\b\"\n      # Highlight common commands used in ebuilds\n    - type: \"\\\\bmake\\\\b|\\\\b(cat|cd|chmod|chown|cp|echo|env|export|grep|let|ln|mkdir|mv|rm|sed|set|tar|touch|unset)\\\\b\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n"
  },
  {
    "path": "runtime/syntax/gentoo-etc-portage.yaml",
    "content": "filetype: etc-portage\n\ndetect:\n    filename: \"\\\\.(keywords|mask|unmask|use)(/.+)?$\"\n\nrules:\n    # Use flags:\n    - constant.bool.false: \"[[:space:]]+\\\\+?[a-zA-Z0-9_-]+\"\n    - constant.bool.true: \"[[:space:]]+-[a-zA-Z0-9_-]+\"\n    # Likely version numbers:\n    - special: \"-[[:digit:]].*([[:space:]]|$)\"\n    # Accepted arches:\n    - identifier.class: \"[~-]?\\\\b(alpha|amd64|arm|hppa|ia64|mips|ppc|ppc64|s390|sh|sparc|x86|x86-fbsd)\\\\b\"\n    - identifier.class: \"[[:space:]][~-]?\\\\*\"\n    # Categories:\n    - statement: \"^[[:space:]]*.*/\"\n    # Masking regulators:\n    - symbol: \"^[[:space:]]*(=|~|<|<=|=<|>|>=|=>)\"\n    # Comments:\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules: []\n"
  },
  {
    "path": "runtime/syntax/git-commit.yaml",
    "content": "filetype: git-commit\n\ndetect:\n    filename: '^(.*[\\\\/])?(COMMIT_EDITMSG|TAG_EDITMSG|MERGE_MSG)$'\n\nrules:\n    # File changes\n    - type.keyword: \"#[[:space:]](deleted|modified|new file|renamed):[[:space:]].*\"\n    - type.keyword: \"#[[:space:]]deleted:\"\n    - type.keyword: \"#[[:space:]]modified:\"\n    - type.keyword: \"#[[:space:]]new file:\"\n    - type.keyword: \"#[[:space:]]renamed:\"\n    - type.keyword: \"^#[[:space:]]Changes.*[:]\"\n    - type.keyword: \"^#[[:space:]]Your branch and '[^']+\"\n    - type.keyword: \"^#[[:space:]]Your branch and '\"\n    - type.keyword: \"^#[[:space:]]On branch [^ ]+\"\n    - type.keyword: \"^#[[:space:]]On branch\"\n    # Color keywords for closing issues (such as on Github)\n    - type.keyword: \"\\\\b(?i)((fix(es|ed)?|close(s|d)?) #[0-9]+)\\\\b\"\n\n    # Comments\n    - comment.line:\n        start: \"^#\"\n        end: \"$\"\n        rules: []\n\n    # Diffs (i.e. git commit --verbose)\n    - default:\n        start: \"^diff --git\"\n        # Diff output puts a space before file contents on each line so this\n        # should never match valid diff output and extend highlighting to the\n        # end of the file\n        end: \"^ENDOFFILE\"\n        rules:\n            - include: \"patch\"\n"
  },
  {
    "path": "runtime/syntax/git-config.yaml",
    "content": "filetype: git-config\n\ndetect:\n    filename: 'git(config|modules)$|^(.*[\\\\/])?\\.?git[\\\\/]config$'\n\nrules:\n    - constant: \"\\\\<(true|false)\\\\>\"\n    - type.keyword: \"^[[:space:]]*[^=]*=\"\n    - constant: \"^[[:space:]]*\\\\[.*\\\\]$\"\n    - constant: \"\\\"(\\\\\\\\.|[^\\\"])*\\\"|'(\\\\\\\\.|[^'])*'\"\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules: []\n"
  },
  {
    "path": "runtime/syntax/git-rebase-todo.yaml",
    "content": "filetype: git-rebase-todo\n\ndetect:\n    filename: '^(.*[\\\\/])?git\\-rebase\\-todo$'\n\nrules:\n    # Rebase commands\n    - statement: \"^(p(ick)?|r(eword)?|e(dit)?|s(quash)?|f(ixup)?|x|exec|b(reak)?|d(rop)?|l(abel)?|t|reset|m(erge)?)\\\\b\"\n    # Commit IDs\n    - identifier: \"\\\\b([0-9a-fA-F]{7,40})\\\\b\"\n\n    # Color keywords for Github (and others)\n    - type.keyword: \"\\\\b(?i)((fix(es|ed)?|close(s|d)?) #[0-9]+)\\\\b\"\n\n    # Comments\n    - comment.line:\n        start: \"^#\"\n        end: \"$\"\n        rules: []\n"
  },
  {
    "path": "runtime/syntax/glsl.yaml",
    "content": "filetype: glsl\n\ndetect:\n    filename: \"\\\\.(frag|vert|fp|vp|glsl)$\"\n\nrules:\n    - identifier: \"[A-Za-z_][A-Za-z0-9_]*[[:space:]]*[()]\"\n    - type: \"\\\\b(void|bool|bvec2|bvec3|bvec4|int|ivec2|ivec3|ivec4|float|vec2|vec3|vec4|mat2|mat3|mat4|struct|sampler1D|sampler2D|sampler3D|samplerCUBE|sampler1DShadow|sampler2DShadow)\\\\b\"\n    - identifier: \"\\\\bgl_(DepthRangeParameters|PointParameters|MaterialParameters|LightSourceParameters|LightModelParameters|LightModelProducts|LightProducts|FogParameters)\\\\b\"\n    - statement: \"\\\\b(const|attribute|varying|uniform|in|out|inout|if|else|return|discard|while|for|do)\\\\b\"\n    - statement: \"\\\\b(break|continue)\\\\b\"\n    - constant.bool: \"\\\\b(true|false)\\\\b\"\n    - symbol.operator: \"[-+/*=<>?:!~%&|^]\"\n    - constant.number: \"\\\\b([0-9]+|0x[0-9a-fA-F]*)\\\\b\"\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"TODO:?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"TODO:?\"\n"
  },
  {
    "path": "runtime/syntax/gnuplot.yaml",
    "content": "filetype: gnuplot\n\ndetect:\n    filename: \"\\\\.(gnu|gpi|plt|gp)$\"\n\nrules:\n    - statement: \"\\\\b(set|unset|plot|splot|replot|if|else|do|for|while|fit)\\\\b\"\n    - symbol.operator: \"[-+/*=<>?:!~%&|^$]\"\n    - constant.number: \"\\\\b([0-9]+|0x[0-9a-fA-F]*)\\\\b\"\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"TODO:?\"\n"
  },
  {
    "path": "runtime/syntax/go.yaml",
    "content": "filetype: go\n\ndetect:\n    filename: \"\\\\.go$\"\n\nrules:\n    # Conditionals and control flow\n    - special: \"\\\\b(break|case|continue|default|go|goto|range|return|println|fallthrough)\\\\b\"\n    - statement: \"\\\\b(else|for|if|switch|select)\\\\b\"\n    - preproc: \"\\\\b(package|import|const|var|type|struct|func|defer|iota|make|new|copy|len|cap|panic|append|close|delete|print|recover)\\\\b\"\n    - symbol.operator: \"[-+/*=<>!~%&|^]|:=\"\n\n      # Types\n    - symbol: \"(,|\\\\.)\"\n    - type: \"\\\\b(u?int(8|16|32|64)?|float(32|64)|complex(64|128))\\\\b\"\n    - type: \"\\\\b(uintptr|byte|rune|string|interface|bool|map|chan|error)\\\\b\"\n    - type.keyword: \"\\\\b(struct)\\\\b\"\n    - constant.bool: \"\\\\b(true|false|nil)\\\\b\"\n\n      # Brackets\n    - symbol.brackets: \"(\\\\{|\\\\})\"\n    - symbol.brackets: \"(\\\\(|\\\\))\"\n    - symbol.brackets: \"(\\\\[|\\\\])\"\n\n      # Numbers and strings\n    - constant.number: \"\\\\b([0-9]+|0x[0-9a-fA-F]*)\\\\b|'.'\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"%.\"\n            - constant.specialChar: \"\\\\\\\\[abfnrtv'\\\\\\\"\\\\\\\\]\"\n            - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - error: \"..+\"\n            - constant.specialChar: \"%.\"\n            - constant.specialChar: \"\\\\\\\\[abfnrtv'\\\\\\\"\\\\\\\\]\"\n            - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})\"\n\n    - constant.string:\n        start: \"`\"\n        end: \"`\"\n        rules: []\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/godoc.yaml",
    "content": "# godoc\n# example: go doc -all | micro\n\nfiletype: godoc\n\ndetect:\n    filename: \"\\\\.godoc$\"\n    header: package.*import\n\nrules:\n    - preproc: \"^[^ ].*\"\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/golo.yaml",
    "content": "filetype: golo\n\ndetect:\n    filename: \"\\\\.golo$\"\n\nrules:\n    - type: \"\\\\b(function|fun|)\\\\b\"\n    - type: \"\\\\b(struct|DynamicObject|union|AdapterFabric|Adapter|DynamicVariable|Observable)\\\\b\"\n    - type: \"\\\\b(list|set|array|vector|tuple|map)\\\\b\"\n    - type: \"\\\\b(Ok|Error|Empty|None|Some|Option|Result|Result.ok|Result.fail|Result.error|Result.empty|Optional.empty|Optional.of)\\\\b\"\n\n    - identifier.class: \"\\\\b(augment|pimp)\\\\b\"\n    - identifier.class: \"\\\\b(interfaces|implements|extends|overrides|maker|newInstance)\\\\b\"\n    - identifier.class: \"\\\\b(isEmpty|isNone|isPresent|isSome|iterator|flattened|toList|flatMap|`and|orElseGet|`or|toResult|apply|either)\\\\b\"\n    - identifier.class: \"\\\\b(result|option|trying|raising|nullify|catching)\\\\b\"\n    - identifier.class: \"\\\\b(promise|setFuture|failedFuture|all|any)\\\\b\"\n    - identifier.class: \"\\\\b(initialize|initializeWithinThread|start|future|fallbackTo|onSet|onFail|cancel|enqueue)\\\\b\"\n    - identifier.class: \"\\\\b(println|print|raise|readln|readPassword|secureReadPassword|requireNotNull|require|newTypedArray|range|reversedRange|mapEntry|asInterfaceInstance|asFunctionalInterface|isClosure|fileToText|textToFile|fileExists|currentDir|sleep|uuid|isArray|arrayTypeOf|charValue|intValue|longValue|doubleValue|floatValue|removeByIndex|box)\\\\b\"\n    - identifier.class: \"\\\\b(likelySupported|reset|bold|underscore|blink|reverse_video|concealed|fg_black|fg_red|fg_green|fg_yellow|fg_blue|fg_magenta|fg_cyan|fg_white|bg_black|bg_red|bg_green|bg_yellow|bg_blue|bg_magenta|bg_cyan|bg_white|cursor_position|cursor_save_position|cursor_restore_position|cursor_up|cursor_down|cursor_forward|cursor_backward|erase_display|erase_line)\\\\b\"\n    - identifier.class: \"\\\\b(emptyList|cons|lazyList|fromIter|generator|repeat|iterate)\\\\b\"\n    - identifier.class: \"\\\\b(asLazyList|foldl|foldr|take|takeWhile|drop|dropWhile|subList)\\\\b\"\n    - identifier.class: \"\\\\b(import)\\\\b\"\n    - identifier.class: \"\\\\b(module)\\\\b\"\n    - identifier.class: \"\\\\b(JSON)\\\\b\"\n    - identifier.class: \"\\\\b(stringify|parse|toJSON|toDynamicObject|updateFromJSON)\\\\b\"\n    - identifier.class: \"\\\\b(newInstance|define|getKey|getValue|properties|fallback)\\\\b\"\n    - identifier.class: \"\\\\b(times|upTo|downTo)\\\\b\"\n    - identifier.class: \"\\\\b(format|toInt|toInteger|toDouble|toFloat|toLong)\\\\b\"\n    - identifier.class: \"\\\\b(head|tail|isEmpty|reduce|each|count|exists)\\\\b\"\n    - identifier.class: \"\\\\b(newWithSameType|destruct|append|add|addIfAbsent|prepend|insert|last|unmodifiableView|find|filter|map|join|reverse|reversed|order|ordered|removeAt|include|exclude|remove|delete|has|contains|getOrElse|toArray)\\\\b\"\n    - identifier.class: \"\\\\b(add|addTo|succ|pred|mul|neg|sub|rsub|div|rdiv|mod|rmod|pow|rpow|str|lt|gt|eq|ne|ge|le|`and|`or|`not|xor|even|odd|contains|isEmpty|`is|`isnt|`oftype|`orIfNull|fst|snd|getitem|setitem|getter|id|const|False|True|Null|curry|uncurry|unary|spreader|varargs|swapArgs|swapCurry|swapCouple|swap|invokeWith|pipe|compose|io|andThen|until|recur|cond)\\\\b\"\n    - identifier.class: \"\\\\b(toUpperCase|equals|startsWith)\\\\b\"\n\n    - statement: \"\\\\b(if|else|then|when|case|match|otherwise)\\\\b\"\n    - special: \"\\\\b(with|break|continue|return)\\\\b\"\n    - error: \"\\\\b(try|catch|finally|throw)\\\\b\"\n    - identifier: \"\\\\b(super|this|let|var|local)\\\\b\"\n    - symbol.brackets: \"[(){}]|\\\\[|\\\\]\"\n    - statement: \"\\\\b(for|while|foreach|in)\\\\b\"\n    - constant: \"\\\\b(and|in|is|not|or|isnt|orIfNull)\\\\b\"\n\n    - constant.bool: \"\\\\b(true|false)\\\\b\"\n    - constant:  \"\\\\b(null|undefined)\\\\b\"\n\n    - symbol.operator: \"[\\\\-+/*=<>!~%&|^]|:=\"\n    - constant.number:   \"\\\\b([0-9]+|0x[0-9a-fA-F]*)\\\\b|'.'\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"----\"\n        end: \"----\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n"
  },
  {
    "path": "runtime/syntax/gomod.yaml",
    "content": "filetype: gomod\n\ndetect:\n    filename: \"go.mod\"\n\nrules:\n    # URL\n    - type: \"(^|[ \\\\t])+\\\\b([a-zA-Z0-9-]+\\\\.?)+(/[a-zA-Z0-9-_\\\\.]+)*\\\\b\"\n\n    # Keywords\n    - special: \"(^|[ \\\\t])+\\\\b(module|go)\\\\b\"\n    - preproc: \"(^|[ \\\\t])+\\\\b(toolchain|require|exclude|replace|retract)\\\\b\"\n    - symbol.operator: \"=>\"\n\n    # Brackets\n    - type: \"(\\\\(|\\\\))\"\n\n    # Go version\n    - type: \"(^|[ \\\\t])+([0-9]+\\\\.?)+\"\n\n    # Version\n    - constant.string: \"(^|[ \\\\t])+v([0-9]+\\\\.?){3}.*\"\n    - constant.number: \"(^|[ \\\\t])+v([0-9]+\\\\.?){3}\"\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(indirect):?\"\n\n# (^|[ \\\\t])+ means after start of string or space or tab character\n"
  },
  {
    "path": "runtime/syntax/graphql.yaml",
    "content": "filetype: graphql\n\ndetect:\n    filename: \"\\\\.(gql|graphql)$\"\n\nrules:\n    - type: \"\\\\b(?:(query|mutation|subscription|type|input|scalar|fragment|schema|union|on|extends?))\\\\b\"\n\n    # scalar types\n    - statement: \"\\\\b(ID|Int|Float|Boolean|String|Datetime|Null)\\\\b\"\n\n    # introspection types\n    - statement: \"(__\\\\w+)\"\n\n    # parameters\n    - statement: \"((\\\\w+)(?:\\\\:([\\\\s]*)?)(?:\\\\$))\"\n\n    # directive locations\n    - statement: \"\\\\b(QUERY|MUTATION|SUBSCRIPTION|FIELD|FRAGMENT_DEFINITION|FRAGMENT_SPREAD|INLINE_FRAGMENT|SCHEMA|SCALAR|OBJECT|FIELD_DEFINITION|ARGUMENT_DEFINITION|INTERFACE|UNION|ENUM|ENUM_VALUE|INPUT_OBJECT|INPUT_FIELD_DEFINITION)\\\\b\"\n\n    # directives\n    - constant: \"(@\\\\w+)\"\n\n    # root types\n    - constant: \"\\\\b(Query|Mutation|Subscription|Schema|Root)\\\\b\"\n\n    # variables\n    - special: \"(\\\\$\\\\w+)\"\n\n    # required symbol\n    - special: \"(!)\"\n\n    - symbol: \"(:|=|\\\\||\\\\(|\\\\)|\\\\{|\\\\}|\\\\[|\\\\])\"\n\n    - constant.bool: \"\\\\b(true|false)\\\\b\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules: []\n"
  },
  {
    "path": "runtime/syntax/groff.yaml",
    "content": "filetype: groff\n\ndetect:\n    filename: \"\\\\.m[ems]$|\\\\.rof|\\\\.tmac$|^tmac.\"\n\nrules:\n    - statement: \"^\\\\.(ds|nr) [^[[:space:]]]*\"\n    - constant.specialChar: \"\\\\\\\\.\"\n    - constant.specialChar: \"\\\\\\\\f.|\\\\\\\\f\\\\(..|\\\\\\\\s(\\\\+|\\\\-)?[0-9]\"\n    - constant: \"(\\\\\\\\|\\\\\\\\\\\\\\\\)n(.|\\\\(..)\"\n    - constant:\n        start: \"(\\\\\\\\|\\\\\\\\\\\\\\\\)n\\\\[\"\n        end: \"]\"\n        rules: []\n\n    - type: \"^\\\\.[[:space:]]*[^[[:space:]]]*\"\n    - comment: \"^\\\\.\\\\\\\\\\\".*$\"\n    - constant.string: \"(\\\\\\\\|\\\\\\\\\\\\\\\\)\\\\*(.|\\\\(..)\"\n    - constant.string:\n        start: \"(\\\\\\\\|\\\\\\\\\\\\\\\\)\\\\*\\\\[\"\n        end: \"]\"\n        rules: []\n\n    - constant.specialChar: \"\\\\\\\\\\\\(..\"\n    - constant.specialChar:\n        start: \"\\\\\\\\\\\\[\"\n        end: \"]\"\n        rules: []\n\n    - identifier.macro: \"\\\\\\\\\\\\\\\\\\\\$[1-9]\"\n"
  },
  {
    "path": "runtime/syntax/groovy.yaml",
    "content": "filetype: groovy\n\ndetect:\n    filename: \"(\\\\.(groovy|gy|gvy|gsh|gradle)$|^[Jj]enkinsfile$)\"\n    header: \"^#!.*/(env +)?groovy *$\"\n\nrules:\n    # And the style guide for constants is CONSTANT_CASE\n    - identifier: \"\\\\b[A-Z_$]+\\\\b\"\n    # The style guide for JVM languages is PascalCase for classes and interfaces\n    - identifier.class: \"\\\\b[A-Z][a-zA-Z0-9$]+\\\\b\"\n\n    # Primitive types\n    - type: \"\\\\b(byte|short|int|long|float|double|char|boolean|void)\\\\b\"\n\n    # Type-related keywords\n    - type.keyword: \"\\\\b(private|public|protected|static|final|var|def)\\\\b\"\n\n    # Keywords\n    - statement: \"\\\\b(for|while|do|if|else|switch|case|default|try|catch|finally)\\\\b\"\n    - statement: \"\\\\b(break|continue|return|throw|assert)\\\\b\"\n    - statement: \"\\\\b(package|import|class|interface|trait|enum|extends|implements|throws)\\\\b\"\n    - statement: \"\\\\b(this|super)\\\\b\"\n    # Unsused, but reserved keywords\n    - statement: \"\\\\b(goto|const)\\\\b\"\n\n    # Operators and punctuation\n    - symbol.operator: \"[-+*/%=<>^~&|!?:;,.@]|\\\\b(in|is|as|instanceof|new)\\\\b\"\n    - symbol.brackets: \"[(){}]|\\\\[|\\\\]\"\n\n    # Decimal integer literal\n    - constant.number: \"(?i)\\\\b[1-9]([_0-9]*[0-9])?[GLIDF]?\\\\b\"\n    # Binary integer literal\n    - constant.number: \"(?i)\\\\b0b[01]([01_]*[01])?[GLIDF]?\\\\b\"\n    # Octal integer literal\n    - constant.number: \"(?i)\\\\b0[0-7]([0-7_]*[0-7])?[GLIDF]?\\\\b\"\n    # Hexadecimal integer literal\n    - constant.number: \"(?i)\\\\b0x[0-9a-fA-F]([0-9a-f_]*[0-9a-fA-F])?[GLIDF]?\\\\b\"\n    # Floating-point literal\n    - constant.number: \"(?i)\\\\b[0-9]([0-9_]*[0-9])?([.][0-9]([0-9_]*[0-9])?)?(e[+-]?[0-9]([0-9_]*[0-9])?)?[DF]?\\\\b\"\n    - constant.bool: \"\\\\b(true|false|null)\\\\b\"\n\n    # Annotations\n    - identifier: \"@[A-Za-z_$][A-Za-z0-9_$]*\\\\b\"\n\n    # Triple-double-quoted strings\n    - constant.string:\n        start: \"\\\"\\\"\\\"\"\n        end: \"\\\"\\\"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([\\\"'bfnrst\\\\x24\\\\\\\\]|u[a-fA-F0-9]{4})\"\n            - identifier.var: \"\\\\x24[\\\\w\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u00FF\\u0100-\\uFFFE]+([.][a-zA-Z0-9_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u00FF\\u0100-\\uFFFE]+)*\"\n            - identifier:\n                start: \"[$][{]\"\n                end: \"[}]\"\n                rules: []\n\n    # Triple-single-quoted strings\n    - constant.string:\n        start: \"'''\"\n        end: \"'''\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([\\\"'bfnrst\\\\x24\\\\\\\\]|u[a-fA-F0-9]{4})\"\n\n    # Nesting ${} are never going to be matched correctly with just regex either, so highlighting will break if one is to nest interpolation\n    # Double-quoted strings\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([\\\"'bfnrst\\\\x24\\\\\\\\]|u[a-fA-F0-9]{4})\"\n            - identifier.var: \"\\\\x24[\\\\w\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u00FF\\u0100-\\uFFFE]+([.][a-zA-Z0-9_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u00FF\\u0100-\\uFFFE]+)*\"\n            - identifier: \"\\\\x24[{].*[}]\"\n\n    # Single-quoted strings\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([\\\"'bfnrst\\\\x24\\\\\\\\]|u[a-fA-F0-9]{4})\"\n\n    # Slashy strings are left out, because they match in unwanted places pretty much all the time\n    # Dollar-slashy strings\n    - constant.string:\n        start: \"[$]/\"\n        end: \"/[$]\"\n        rules: []\n\n    # Single-line comments\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    # Multiline comments\n    - comment:\n        start: \"/[*]\"\n        end: \"[*]/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    # Groovydoc comments\n    - comment:\n        start: \"/[*][*]@?\"\n        end: \"[*]/\"\n        rules: []\n"
  },
  {
    "path": "runtime/syntax/haml.yaml",
    "content": "filetype: haml\n\ndetect:\n    filename: \"\\\\.haml$\"\n\nrules:\n    - symbol: \"-|=\"\n    - default: \"->|=>\"\n    - constant: \"([  ]|^)%[0-9A-Za-z_]+>\"\n    - special: \":[0-9A-Za-z_]+>\"\n    - type: \"\\\\.[A-Za-z_]+>\"\n    - constant.string: \"\\\"([^\\\"]|(\\\\\\\\\\\"))*\\\"|%[QW]?\\\\{[^}]*\\\\}|%[QW]?\\\\([^)]*\\\\)|%[QW]?<[^>]*>|%[QW]?\\\\$[^$]*\\\\$|%[QW]?\\\\^[^^]*\\\\^|%[QW]?![^!]*!\"\n    - constant.string: \"'([^']|(\\\\\\\\'))*'|%[qw]\\\\{[^}]*\\\\}|%[qw]\\\\([^)]*\\\\)|%[qw]<[^>]*>|%[qw]\\\\[[^]]*\\\\]|%[qw]\\\\$[^$]*\\\\$|%[qw]\\\\^[^^]*\\\\^|%[qw]![^!]*!\"\n    - identifier: \"#\\\\{[^}]*\\\\}\"\n    - identifier.var: \"(@|@@)[0-9A-Z_a-z]+\"\n    - comment: \"#[^{].*$|#$\"\n"
  },
  {
    "path": "runtime/syntax/hare.yaml",
    "content": "filetype: hare\n\ndetect:\n    filename: \"\\\\.ha$\"\n\nrules:\n    - identifier: \"\\\\b[A-Z_][0-9A-Z_]+\\\\b\"\n\n    - type: \"\\\\b(bool|char|str|rune|void)\\\\b\"\n    - type: \"\\\\b(f32|f64|uint|int|u8|u16|u32|u64|i8|i16|i32|i64|uintptr)\\\\b\"\n\n    - statement: \"\\\\b(case|else|for|if|switch)\\\\b\"\n    - statement: \"\\\\b(continue|break|return)\\\\b\"\n\n    - special: \"\\\\b(as|const|def|defer|enum|export|fn|is|let|match|static|struct|type|union|yield|_)\\\\b\"\n    - preproc: \"\\\\b(abort|alloc|append|assert|delete|free|insert|len|nullable|offset|size)\\\\b\"\n    - preproc: \"^use .+;\"\n    - preproc: \"\\\\@([a-zA-Z_][0-9a-zA-Z_]+)\\\\b\"\n\n    - constant: \"\\\\b(false|null|true)\\\\b\"\n    - constant.number: \"\\\\b(0x[0-9A-Fa-f]+(i(8|16|32|64)?|u(8|16|32|64)?|z)?)\\\\b\"\n    - constant.number: \"\\\\b(0o[0-7]+(i(8|16|32|64)?|u(8|16|32|64)?|z)?)\\\\b\"\n    - constant.number: \"\\\\b(0b[01]+(i(8|16|32|64)?|u(8|16|32|64)?|z)?)\\\\b\"\n\n    - constant.specialChar: \"\\\\\\\".*\\\\\\\"\"\n    - constant.specialChar: \"`.*`\"\n    - constant.specialChar: \"'([^'\\\\\\\\]|\\\\\\\\(0|a|b|f|n|r|t|v|\\\\\\\\|'|\\\\\\\"|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8}))'\"\n\n    - symbol.operator: \"([.:;,+*|=!\\\\%]|<|>|/|-|&)\"\n    - symbol.brackets: \"[(){}]|\\\\[|\\\\]\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([\\\"'abfnrtv\\\\\\\\]|[0-3]?[0-7]{1,2}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - error: \"..+\"\n            - constant.specialChar: \"\\\\\\\\([\\\"'abfnrtv\\\\\\\\]|[0-3]?[0-7]{1,2}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})\"\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n"
  },
  {
    "path": "runtime/syntax/haskell.yaml",
    "content": "filetype: haskell\n\ndetect:\n    filename: \"\\\\.hs$\"\n\nrules:\n    - symbol.operator: \"[!#$%&:*+/<=>?@.\\\\\\\\^\\\\|~\\\\p{Sm}\\\\-]+\"\n\n    # Identifiers (with or without a module name)\n    - type: \"\\\\b([A-Z][A-Za-z0-9_]*\\\\.)*[A-Z]+[A-Za-z0-9_']*\\\\b\"\n    - default: \"\\\\b([A-Z][A-Za-z0-9_]*\\\\.)*[a-z][A-Za-z0-9_']*\\\\b\"\n\n    - statement: \";\"\n    - symbol.bracket: \"[\\\\(\\\\)\\\\[\\\\]\\\\{\\\\}]\"\n    - special: \"`[A-Za-z0-9']+`\"\n\n    # Keywords\n    - statement: \"\\\\b(case|of|class|data|default|deriving|do|forall|foreign|hiding|if|then|else|import|infix|infixl|infixr|instance|let|in|mdo|module|newtype|qualified|type|where)\\\\b\"\n\n    # Data constructors\n    - constant.bool: \"\\\\b(True|False)\\\\b\"\n    - constant: \"\\\\b(Nothing|Just|Left|Right|LT|EQ|GT)\\\\b\"\n\n    - constant: \"\\\\(\\\\)\"  # Unit\n    - constant.number: \"\\\\b(0[xX][0-9A-Fa-f]+|0[oO][0-7]+|0[bB][01]+|[-]?[0-9]+([.][0-9]+)?([eE][+-]?[0-9]+)?)\\\\b\"\n\n    # Data classes\n    - identifier.class: \"\\\\b(Additive|Applicative|Bounded|Data|Enum|Eq|Floating|Foldable|Fractional|Functor|Integral|Monad|MonadPlus|Monoid|Num|Ord|Read|Real|RealFloat|RealFrac|Semigroup|Show|Traversable|Typeable|Zip)[ ]\"\n\n    # Strings\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - special: \"\\\\\\\\&\"\n            - constant.specialChar: \"\\\\\\\\([abfnrtv\\\"'\\\\\\\\]|[0-9]+|x[0-9a-fA-F]+|o[0-7]+|NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE|DC[1-4]|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS|US|SP|DEL)\"\n\n    # Comments\n    - comment:\n        start: \"--\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"\\\\{-\"\n        end: \"-\\\\}\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - identifier.macro: \"undefined\"\n"
  },
  {
    "path": "runtime/syntax/hc.yaml",
    "content": "filetype: hc\n\ndetect:\n    filename: \"(\\\\.(hc|HC)$|\\\\.(hh|HH)$|\\\\.ii?$|\\\\.(def)$)\"\n\nrules:\n    - identifier: \"\\\\b[A-Z_][0-9A-Z_]+\\\\b\"\n    - type: \"\\\\b(F64|I8|U8|I16|U16|I32|U32|I64|U64|sizeof|enum|U0|static|extern|struct|union|class|intern|public|argc|argv|asm)\\\\b\"\n\n    - statement: \"\\\\b(for|if|while|do|else|case|default|switch)\\\\b\"\n    - statement: \"\\\\b(try|catch|throw|goto|continue|break|return)\\\\b\"\n    - preproc: \"^[[:space:]]*#[[:space:]]*(define|pragma|include|(un|ifn?)def|endif|el(if|se)|if|help_index|ifjit|ifaot|exe)\"\n\n      # Operator Color\n    - symbol.operator: \"([.:;,+*|=!\\\\%]|<|>|/|-|&)\"\n    - symbol.brackets: \"[(){}]|\\\\[|\\\\]\"\n      # Integer Constants\n    - constant.number: \"(\\\\b([1-9][0-9]*|0[0-7]*|0[Xx][0-9A-Fa-f]+|0[Bb][01]+)([Uu]?[Ll][Ll]?|[Ll][Ll]?[Uu]?)?\\\\b)\"\n      # Decimal Floating Constants\n    - constant.number: \"(\\\\b(([0-9]*[.][0-9]+|[0-9]+[.][0-9]*)([Ee][+-]?[0-9]+)?|[0-9]+[Ee][+-]?[0-9]+)[FfLl]?\\\\b)\"\n      # Hexadecimal Floating Constants\n    - constant.number: \"(\\\\b0[Xx]([0-9A-Za-z]*[.][0-9A-Za-z]+|[0-9A-Za-z]+[.][0-9A-Za-z]*)[Pp][+-]?[0-9]+[FfLl]?\\\\b)\"\n    - constant.number: \"NULL\"\n    - constant.number: \"TRUE\"\n    - constant.number: \"FALSE\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([\\\"'abfnrtv\\\\\\\\]|[0-3]?[0-7]{1,2}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - error: \"..+\"\n            - constant.specialChar: \"\\\\\\\\([\\\"'abfnrtv\\\\\\\\]|[0-3]?[0-7]{1,2}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})\"\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/html.yaml",
    "content": "filetype: html\n\ndetect:\n    filename: \"\\\\.htm[l]?$\"\n\nrules:\n    # Doctype is case-insensitive\n    - preproc: \"<!(?i)(DOCTYPE html.*)>\"\n    # Opening tag\n    - symbol.tag:\n        start: \"<(a|abbr|acronym|address|applet|area|article|aside|audio|b|base|bdi|bdo|big|blockquote|body|br|button|canvas|caption|center|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|dialog|dir|div|dl|dt|em|embed|fieldset|figcaption|figure|font|footer|form|frame|frameset|h[1-6]|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|main|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|s|samp|section|select|small|source|span|strike|strong|sub|summary|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|track|tt|u|ul|var|video|wbr)\\\\b\"\n        end: \">\"\n        rules:\n          - identifier: \"\\\\b(placeholder|style|alt|bgcolor|height|href|id|(aria|data)\\\\-.+|label|longdesc|name|on(click|focus|load|mouseover)|size|span|src|target|type|value|width|class|charset|content|rel|integrity|crossorigin|for|onsubmit|lang|role)\\\\b\"\n          - special: \"\\\\b(required)\\\\b\"\n          # Match double-quote strings\n          - constant.string:\n                start: \"\\\"\"\n                end: \"\\\"\"\n                skip: \"\\\\\\\\.\"\n                rules:\n                  - constant.specialChar: \"\\\\\\\\.\"\n                  - constant.string.url: \"((ftp(s)?|http(s)?|git|chrome)://[^\\\\s]+)\"\n          # Match single-quote strings\n          - constant.string:\n                start: \"'\"\n                end: \"'\"\n                skip: \"\\\\\\\\.\"\n                rules:\n                  - constant.specialChar: \"\\\\\\\\.\"\n                  - constant.string.url: \"((ftp(s)?|http(s)?|git|chrome)://[^\\\\s]+)\"\n          # Highlight the equals and any colon between words\n          - symbol: \"\\\\b(=|:\\\\b)\"\n\n    # Closing tag\n    - symbol.tag:\n        start: \"</(a|abbr|acronym|address|applet|area|article|aside|audio|b|base|bdi|bdo|big|blockquote|body|br|button|canvas|caption|center|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|dialog|dir|div|dl|dt|em|embed|fieldset|figcaption|figure|font|footer|form|frame|frameset|h[1-6]|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|main|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|s|samp|section|select|small|source|span|strike|strong|sub|summary|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|track|tt|u|ul|var|video|wbr)\\\\b\"\n        end: \">\"\n        rules:\n          # Anything in the closing tag is an error\n          - error: \".\"\n\n    # Reserved entities like a&#12; &nbsp; and &#x12A;\n    - special: \"(([a-zA-Z]&#[0-9]+|&[a-zA-Z]+|&#[a-zA-Z0-9]+);)\"\n\n    # TODO: Add `limit-rules` to both the `default` rules below once it's implemented into Micro\n    - default:\n        start: \"<script.*?>\"\n        end: \"</script.*?>\"\n        limit-group: symbol.tag\n        rules:\n          - include: \"javascript\"\n\n    - default:\n        start: \"<style.*?>\"\n        end: \"</style.*?>\"\n        limit-group: symbol.tag\n        rules:\n          - include: \"css\"\n\n    # This weird empty comment thing is technically valid\n    - comment: \"<!>\"\n\n    - comment.block:\n        start: \"<!\\\\-\\\\-\"\n        end: \"\\\\-\\\\->\"\n        rules:\n          - todo: \"(FIXME|NOTE|TODO):?\"\n          # While technically not a \"true\" error, these are recommended to not be used inside a comment\n          - error: \"(\\\\-\\\\-|>)\"\n"
  },
  {
    "path": "runtime/syntax/html4.yaml",
    "content": "filetype: html4\n\ndetect:\n    filename: \"\\\\.htm[l]?4$\"\n    header: \"<!DOCTYPE HTML PUBLIC \\\"-//W3C//DTD HTML 4.01//EN|http://www.w3.org/TR/html4/strict.dtd\\\">\"\n\nrules:\n    - error: \"<[^!].*?>\"\n    - symbol.tag: \"(?i)<[/]?(a(bbr|cronym|ddress|pplet|rea|rticle|side|udio)?|b(ase(font)?|d(i|o)|ig|lockquote|r)?|ca(nvas|ption)|center|cite|co(de|l|lgroup)|d(ata(list)?|d|el|etails|fn|ialog|ir|l|t)|em(bed)?|fieldset|fig(caption|ure)|font|form|(i)?frame|frameset|h[1-6]|hr|i|img|in(put|s)|kbd|keygen|label|legend|li(nk)?|ma(in|p|rk)|menu(item)?|met(a|er)|nav|no(frames|script)|o(l|pt(group|ion)|utput)|p(aram|icture|re|rogress)?|q|r(p|t|uby)|s(trike)?|samp|se(ction|lect)|small|source|span|strong|su(b|p|mmary)|textarea|time|track|u(l)?|var|video|wbr)( .*|>)*?>\"\n    - symbol.tag.extended: \"(?i)<[/]?(body|div|html|head(er)?|footer|title|table|t(body|d|h(ead)?|r|foot))( .*)*?>\"\n    - preproc: \"(?i)<[/]?(script|style)( .*)*?>\"\n    - special: \"&[^;[[:space:]]]*;\"\n    - symbol: \"[:=]\"\n    - identifier: \"(alt|bgcolor|height|href|id|label|longdesc|name|on(click|focus|load|mouseover)|size|span|src|style|target|type|value|width)=\"\n    - constant.string: \"\\\"[^\\\"]*\\\"\"\n    - constant.number: \"(?i)#[0-9a-fA-F]{6,6}\"\n    - default:\n        start: \">\"\n        end: \"<\"\n        rules: []\n\n    - symbol.tag: \"<|>\"\n    - constant.string.url: \"(ftp(s)?|http(s)?|git|chrome)://[^ \t]+\"\n    - comment: \"<!--.+?-->\"\n    - preproc: \"<!DOCTYPE.+?>\"\n"
  },
  {
    "path": "runtime/syntax/html5.yaml",
    "content": "filetype: html5\n\ndetect:\n    filename: \"\\\\.htm[l]?5$\"\n    header: \"<!DOCTYPE html5>\"\n\nrules:\n    - error: \"<[^!].*?>\"\n    - symbol.tag: \"(?i)<[/]?(a|a(bbr|ddress|rea|rticle|side|udio)|b|b(ase|d(i|o)|lockquote|r|utton)|ca(nvas|ption)|center|cite|co(de|l|lgroup)|d(ata|atalist|d|el|etails|fn|ialog|l|t)|em|embed|fieldset|fig(caption|ure)|form|iframe|h[1-6]|hr|i|img|in(put|s)|kbd|keygen|label|legend|li|link|ma(in|p|rk)|menu|menuitem|met(a|er)|nav|noscript|o(bject|l|pt(group|ion)|utput)|p|param|picture|pre|progress|q|r(p|t|uby)|s|samp|se(ction|lect)|small|source|span|strong|su(b|p|mmary)|textarea|time|track|u|ul|var|video|wbr)( .*)*?>\"\n    - symbol.tag.extended: \"(?i)<[/]?(body|div|html|head(er)?|footer|title|table|t(body|d|h(ead)?|r|foot))( .*)*?>\"\n    - preproc: \"(?i)<[/]?(script|style)( .*)*?>\"\n    - special: \"&[^;[[:space:]]]*;\"\n    - symbol: \"[:=]\"\n    - identifier: \"(alt|bgcolor|height|href|id|label|longdesc|name|on(click|focus|load|mouseover)|size|span|src|style|target|type|value|width)=\"\n    - constant.string: \"\\\"[^\\\"]*\\\"\"\n    - constant.number: \"(?i)#[0-9a-fA-F]{6,6}\"\n    - default:\n        start: \">\"\n        end: \"<\"\n        rules: []\n\n    - symbol.tag: \"<|>\"\n    - constant.string.url: \"(ftp(s)?|http(s)?|git|chrome)://[^ \t]+\"\n    - comment: \"<!--.+?-->\"\n    - preproc: \"<!DOCTYPE.+?>\"\n"
  },
  {
    "path": "runtime/syntax/ini.yaml",
    "content": "filetype: ini\n\ndetect:\n    filename: \"\\\\.(ini|desktop|lfl|override|tscn|tres)$|(mimeapps\\\\.list|pinforc|setup\\\\.cfg|project\\\\.godot)$|weechat/.+\\\\.conf$\"\n\nrules:\n    - constant.bool.true: \"\\\\btrue\\\\b\"\n    - constant.bool.false: \"\\\\bfalse\\\\b\"\n    - identifier: \"^[[:space:]]*[^=]*=\"\n    - special: \"^[[:space:]]*\\\\[.*\\\\]$\"\n    - statement: \"[=;]\"\n    - constant.string: \"\\\"(\\\\\\\\.|[^\\\"])*\\\"|'(\\\\\\\\.|[^'])*'\"\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n    - comment:\n        start: \";\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/inputrc.yaml",
    "content": "filetype: inputrc\n\ndetect:\n    filename: \"inputrc$\"\n\nrules:\n    - constant.bool.false: \"\\\\b(off|none)\\\\b\"\n    - constant.bool.true: \"\\\\bon\\\\b\"\n    - preproc: \"\\\\bset|\\\\$include\\\\b\"\n    - constant.string: \"\\\"(\\\\\\\\.|[^\\\"])*\\\"|'(\\\\\\\\.|[^'])*'\"\n    - constant.specialChar: \"\\\\\\\\.?\"\n    - comment: \"(^|[[:space:]])#([^{].*)?$\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n    - indent-char: \"\t+ +| +\t+\"\n"
  },
  {
    "path": "runtime/syntax/java.yaml",
    "content": "filetype: java\n\ndetect:\n    filename: \"\\\\.java$\"\n\nrules:\n    - type: \"\\\\b(boolean|byte|char|double|float|int|long|new|var|short|this|transient|void)\\\\b\"\n    - statement: \"\\\\b(break|case|catch|continue|default|do|else|finally|for|if|return|switch|throw|try|while)\\\\b\"\n    - type: \"\\\\b(abstract|class|extends|final|implements|import|instanceof|interface|native|package|private|protected|public|static|strictfp|super|synchronized|throws|volatile)\\\\b\"\n    - constant: \"\\\\b(true|false|null)\\\\b\"\n    - constant.number: \"\\\\b[0-9]+\\\\b\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - preproc: \"..+\"\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules: []\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules: []\n\n"
  },
  {
    "path": "runtime/syntax/javascript.yaml",
    "content": "filetype: javascript\n\ndetect:\n    filename: \"(\\\\.(m|c)?js$|\\\\.es[5678]?$)\"\n    header: \"^#!.*/(env +)?node( |$)\"\n\nrules:\n    - constant.number: \"\\\\b[-+]?([1-9][0-9]*|0[0-7]*|0x[0-9a-fA-F]+)([uU][lL]?|[lL][uU]?)?\\\\b\"\n    - constant.number: \"\\\\b[-+]?([0-9]+\\\\.[0-9]*|[0-9]*\\\\.[0-9]+)([EePp][+-]?[0-9]+)?[fFlL]?\"\n    - constant.number: \"\\\\b[-+]?([0-9]+[EePp][+-]?[0-9]+)[fFlL]?\"\n    #- identifier: \"[A-Za-z_][A-Za-z0-9_]*[[:space:]]*[(]\"\n    # ^ this is not correct usage of the identifier color\n    - symbol.brackets: \"[(){}]|\\\\[|\\\\]\"\n    - symbol.operator: \"([-+/*=<>!~%?:&|]|[.]{3})\"\n    - statement: \"\\\\b(async|await|break|case|catch|const|continue|debugger|default)\\\\b\"\n    - statement: \"\\\\b(delete|do|else|export|finally|for|function\\\\*?|class|extends)\\\\b\"\n    - statement: \"\\\\b(get|if|import|from|in|of|instanceof|let|new|reject|resolve|return)\\\\b\"\n    - statement: \"\\\\b(set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\\\\b\"\n    # reserved but unassigned\n    - error: \"\\\\b(enum|implements|interface|package|private|protected|public)\\\\b\"\n    - constant: \"\\\\b(globalThis|Infinity|null|undefined|NaN)\\\\b\"\n    - constant: \"\\\\b(null|undefined|NaN)\\\\b\"\n    - constant: \"\\\\b(true|false)\\\\b\"\n    - type: \"\\\\b(Array|Boolean|Date|Enumerator|Error|Function|Generator|Map|Math)\\\\b\"\n    - type: \"\\\\b(Number|Object|Promise|Proxy|Reflect|RegExp|Set|String|Symbol|WeakMap|WeakSet)\\\\b\"\n    - type: \"\\\\b(BigInt64Array|BigUint64Array|Float32Array|Float64Array|Int16Array)\\\\b\"\n\n    # - constant: \"/[^*]([^/]|(\\\\\\\\/))*[^\\\\\\\\]/[gim]*\"\n    - constant: \"\\\\\\\\[0-7][0-7]?[0-7]?|\\\\\\\\x[0-9a-fA-F]+|\\\\\\\\[bfnrt'\\\"\\\\?\\\\\\\\]\"\n    - comment: \"^#!.*/(env +)?node( |$)\"\n\n    - identifier: \"\\\\b(alert|decodeURI|decodeURIComponent|document|encodeURI|encodeURIComponent|escape|eval|isFinite|isNaN|parseFloat|parseInt|unescape|uneval|window)\\\\b\"\n    - identifier: \"\\\\b(Intl|WebAssembly)\\\\b\"\n    - identifier: \"\\\\b(Arguments)\\\\b\"\n\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"`\"\n        end: \"`\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n            - identifier: \"\\\\x24\\\\{.*?\\\\}\"\n\n    - constant.bool: \"\\\\b(true|false)\\\\b\"\n    - constant.bool.false: \"\\\\b(false)\\\\b\"\n    - constant.bool.true: \"\\\\b(true)\\\\b\"\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME)\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n            # function documentation\n            - identifier: \"\\\\s\\\\*\\\\s.*\"\n            - todo: \"(TODO|XXX|FIXME)\"\n"
  },
  {
    "path": "runtime/syntax/jinja2.yaml",
    "content": "filetype: jinja2\n\nrules:\n  - include: \"html\"\n  - special: \"({{|}}|{%-?|-?%})\"\n  - default:\n        start: \"({%-?|{{)\"\n        end: \"(-?%}|}})\"\n        limit-group: special\n        rules:\n          - include: \"python\"\n          - statement: \"\\\\b(ignore missing|with(out)? context|block|call|endblock|endcall|endfilter|endfor|endmacro|endraw|endset|extends|filter|for|include|macro|raw|recursive|scoped|set)\\\\b\"\n          - identifier.builtinfunc: \"\\\\b(attr|batch|capitalize|center|count|d|default|dictsort|e|escape|filesizeformat|first|forceescape|groupby|indent|join|last|length|lower|pprint|random|reject|rejectattr|replace|reverse|safe|select|selectattr|striptags|title|tojson|trim|truncate|unique|upper|urlencode|urlize|wordcount|wordwrap|xmlattr)\\\\b\"\n          - identifier.builtintest: \"\\\\b(callable|defined|divisibleby|eq|equalto|escaped|even|ge|gt|iterable|le|lower|lt|mapping|ne|none|number|odd|sameas|sequence|string|undefined|upper)\\\\b\"\n          - identifier.defaultglobal: \"\\\\b(lipsum|cycler|joiner|namespace)\\\\b\"\n  - comment:\n        start: \"{#\"\n        end: \"#}\"\n        rules: []\n"
  },
  {
    "path": "runtime/syntax/json.yaml",
    "content": "filetype: json\n\ndetect:\n    filename: \"\\\\.json$\"\n    header: \"^\\\\{$\"\n\nrules:\n    - constant.number: \"\\\\b[-+]?([1-9][0-9]*|0[0-7]*|0x[0-9a-fA-F]+)([uU][lL]?|[lL][uU]?)?\\\\b\"\n    - constant.number: \"\\\\b[-+]?([0-9]+\\\\.[0-9]*|[0-9]*\\\\.[0-9]+)([EePp][+-]?[0-9]+)?[fFlL]?\"\n    - constant.number: \"\\\\b[-+]?([0-9]+[EePp][+-]?[0-9]+)[fFlL]?\"\n    - constant: \"\\\\b(null)\\\\b\"\n    - constant: \"\\\\b(true|false)\\\\b\"\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - statement: \"\\\\\\\"(\\\\\\\\\\\"|[^\\\"])*\\\\\\\"[[:space:]]*:\\\"  \\\"'(\\\\'|[^'])*'[[:space:]]*:\"\n    - constant: \"\\\\\\\\u[0-9a-fA-F]{4}|\\\\\\\\[bfnrt'\\\"/\\\\\\\\]\"\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/jsonnet.yaml",
    "content": "filetype: jsonnet\n\ndetect:\n    filename: \"\\\\.jsonnet$\"\n\n# Spec: https://jsonnet.org/ref/spec.html\n\nrules:\n    # built-in objects\n    # FIXME: $ won't match\n    - constant: \"\\\\b(self|\\\\$|super)\\\\b\"\n    # boolean constants\n    - constant.bool: \"\\\\b(null|true|false)\\\\b\"\n    # the standard library\n    - identifier: \"\\\\bstd\\\\.(extVar|thisFile|type|length|objectHas|objectFields|objectHasAll|objectFieldsAll|prune|mapWithKey|abs|sign|max|min|pow|exp|log|exponent|mantissa|floor|ceil|sqrt|sin|cos|tan|asin|acos|atan|mod|assertEqual|toString|codepoint|char|substr|findSubstr|startsWith|endsWith|split|splitLimit|strReplace|asciiUpper|asciiLower|stringChars|format|escapeStringDollars|escapeStringPython|parseInt|parseOctal|parseHex|parseJson|encodeUTF8|decodeUTF8|manifestIni|manifestPython|manifestPythonVars|manifestJsonEx|manifestYamlDoc|manifestYamlStream|manifestXmlJsonml|makeArray|count|find|map|mapWithIndex|filterMap|filter|foldl|foldr|range|join|lines|flattenArrays|sort|uniq|set|setInter|setUnion|setDiff|setMember|base64|base64DecodeBytes|base64Decode|md5|mergePatch|trace)\\\\b\"\n    # unquoted object keys\n    - type: \"[_a-zA-Z][_a-zA-Z0-9]*\\\\s*:\"\n    # object key separator\n    - statement: \":\"\n    # keywords\n    - statement: \"\\\\b(assert|else|error|for|function|if|import|importstr|in|local|tailstrict|then)\\\\b\"\n    # operators\n    - symbol.operator: \"([.;,+*|=!\\\\%]|<|>|/|-|&)\"\n    # parentheses\n    - symbol.brackets: \"([(){}]|\\\\[|\\\\])\"\n    # numbers\n    - constant.number: \"\\\\b(0|([1-9][0-9]*))(\\\\.[0-9]+)?([eE][\\\\+-]?[0-9]+)?\\\\b\"\n\n    # double-quoted string\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\\\\"\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\u[0-9a-fA-F]{4}|\\\\\\\\[bfnrt'\\\"/\\\\\\\\]\"\n\n    # single-quoted string\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\'\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\u[0-9a-fA-F]{4}|\\\\\\\\[bfnrt'\\\"/\\\\\\\\]\"\n\n    # double-quoted verbatim string\n    - constant.string:\n        start: \"@\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\\\\"\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\\\\"\"\n\n    # single-quoted verbatim string\n    - constant.string:\n        start: \"@'\"\n        end: \"'\"\n        skip: \"\\\\\\\\'\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\'\"\n\n    # block string\n    - constant.string:\n        # FIXME:\n        # This isn't quite right.\n        # The spec says this:\n\n        # beginning with |||, followed by optional whitespace and a new-line.\n        # The next non-blank line must be prefixed with some non-zero length\n        # whitespace W. The block ends at the first subsequent line that does\n        # not begin with W, and it is an error if this line does not contain\n        # some optional whitespace followed by |||.\n\n        # We need to match ^(\\s+) on the first non-blank line after |||\n        # Then we need to skip ^\\1.*$\n\n        start: \"\\\\|\\\\|\\\\| *$\"\n        end: \"^ *\\\\|\\\\|\\\\|\"\n        rules: []\n\n    # multi-line comment\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    # single-line comment\n    - comment:\n        start: \"#|(//)\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/julia.yaml",
    "content": "filetype: julia\n\ndetect:\n    filename: \"\\\\.jl$\"\n    header: \"^#!.*/(env +)?julia( |$)\"\n\nrules:\n\n    # built-in objects\n    - constant.bool: \"\\\\b(true|false)\\\\b\"\n    - constant: \"\\\\b(nothing|missing)\\\\b\"\n      # built-in attributes\n    - constant: \"__[A-Za-z0-9_]+__\"\n      # definitions\n    - identifier: \"[A-Za-z_][A-Za-z0-9_]*[[:space:]]*[(]\"\n      # keywords\n    - statement: \"\\\\b(baremodule|begin|break|catch|const|continue|do|else|elseif|end|export|finally|for|function|global|if|import|let|local|macro|module|public|quote|return|struct|try|using|while)\\\\b\"\n    - statement: \"\\\\b(abstract\\\\s+type|primitive\\\\s+type|mutable\\\\s+struct)\\\\b\"\n      # decorators\n    - identifier.macro: \"@[A-Za-z0-9_]+\"\n      # operators\n    - symbol.operator: \"[:+*|=!%~<>/\\\\-?&\\\\\\\\÷∈∉∘]|\\\\b(in|isa|where)\\\\b\"\n      # for some reason having ^ in the same regex with the other operators broke things\n    - symbol.operator: \"\\\\^\"\n      # parentheses\n    - symbol.brackets: \"([(){}]|\\\\[|\\\\])\"\n      # numbers\n    - constant.number: \"\\\\b([0-9]+(_[0-9]+)*|0x[0-9a-fA-F]+(_[0-9a-fA-F]+)*|0b[01]+(_[01]+)*|0o[0-7]+(_[0-7]+)*|Inf(16|32|64)?|NaN(16|32|64)?)\\\\b\"\n\n    - constant.string:\n        start: \"\\\"\\\"\\\"\"\n        end: \"\\\"\\\"\\\"\"\n        rules: []\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([\\\"'abfnrtv\\\\\\\\]|[0-3]?[0-7]{1,2}|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{1,4}|U[0-9A-Fa-f]{1,8})\"\n\n    # Lifted from Rust's syntax highlighting\n    - constant.string: \"'(\\\\\\\\.|.)'\"\n    - constant.string:\n        start: \"'\\\"\"\n        end: \"'\"\n        rules: []\n\n    - comment:\n        start: \"#=\"\n        end: \"=#\"\n        rules: []\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules: []\n"
  },
  {
    "path": "runtime/syntax/justfile.yaml",
    "content": "# For more information, see https://github.com/casey/just\nfiletype: 'justfile'\n\ndetect:\n    filename: \"(^\\\\.?[Jj]ustfile|\\\\.just)$\"\n    header: \"^#!.*/(env +)?[bg]?just --justfile\"\n\nrules:\n    - preproc: \"\\\\<(ifeq|ifdef|ifneq|ifndef|else|endif)\\\\>\"\n    - statement: \"^(export|include|override)\\\\>\"\n    - symbol.operator: \"^[^:=\t]+:\"\n    - symbol.operator: \"([=,%]|\\\\+=|\\\\?=|:=|&&|\\\\|\\\\|)\"\n    - statement: \"\\\\$\\\\((abspath|addprefix|addsuffix|and|basename|call|dir)[[:space:]]\"\n    - statement: \"\\\\$\\\\((error|eval|filter|filter-out|findstring|firstword)[[:space:]]\"\n    - statement: \"\\\\$\\\\((flavor|foreach|if|info|join|lastword|notdir|or)[[:space:]]\"\n    - statement: \"\\\\$\\\\((origin|patsubst|realpath|shell|sort|strip|suffix)[[:space:]]\"\n    - statement: \"\\\\$\\\\((value|warning|wildcard|word|wordlist|words)[[:space:]]\"\n    # default functions - probably shouldn't be overwritten by assignment\n    - statement: \"\\\\b(arch|os|os_family|env_var|invocation_directory|justfile|justfile_directory|just_executable|lowercase|quote|replace|trim|trim_end|trim_end|trim_end_match|trim_end_matches|trim_start|trim_start_match|trim_start_matches|uppercase)\\\\b\"\n    - identifier: \"^.+:\"\n    - identifier: \"[()$]\"\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - identifier: \"\\\\$+(\\\\{[^} ]+\\\\}|\\\\([^) ]+\\\\))\"\n    - identifier: \"\\\\$[@^<*?%|+]|\\\\$\\\\([@^<*?%+-][DF]\\\\)\"\n    - identifier: \"\\\\$\\\\$|\\\\\\\\.?\"\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules: []\n"
  },
  {
    "path": "runtime/syntax/keymap.yaml",
    "content": "filetype: keymap\n\ndetect:\n    filename: \"\\\\.(k|key)?map$|Xmodmap$\"\n\nrules:\n    - statement: \"\\\\b(add|clear|compose|keycode|keymaps|keysym|remove|string)\\\\b\"\n    - statement: \"\\\\b(control|alt|shift)\\\\b\"\n    - constant.number: \"\\\\b[0-9]+\\\\b\"\n    - special: \"=\"\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - comment:\n        start: \"^!\"\n        end: \"$\"\n        rules: []\n\n"
  },
  {
    "path": "runtime/syntax/kickstart.yaml",
    "content": "filetype: kickstart\n\ndetect:\n    filename: \"\\\\.ks$|\\\\.kickstart$\"\n\nrules:\n    - special: \"%[a-z]+\"\n    - statement: \"^[[:space:]]*(install|cdrom|text|graphical|volgroup|logvol|reboot|timezone|lang|keyboard|authconfig|firstboot|rootpw|user|firewall|selinux|repo|part|partition|clearpart|bootloader)\"\n    - constant: \"--(name|mirrorlist|baseurl|utc)(=|\\\\>)\"\n    - statement: \"\\\\$(releasever|basearch)\\\\>\"\n    - brightblack: \"^@[A-Za-z][A-Za-z-]*\"\n    - brightred: \"^-@[a-zA-Z0-9*-]+\"\n    - red: \"^-[a-zA-Z0-9*-]+\"\n    - comment: \"(^|[[:space:]])#([^{].*)?$\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n    - indent-char: \"\t+ +| +\t+\"\n"
  },
  {
    "path": "runtime/syntax/kotlin.yaml",
    "content": "filetype: kotlin\n\ndetect:\n    filename: \"\\\\.kts?$\"\n\nrules:\n\n    # Operators\n    - symbol.operator: ([.:;,+*|=!?\\\\%]|<|>|/|-|&)\n\n    # Statements Keywords\n    - statement: \\b(as|by|class|constructor|companion|const|fun|import|in|infix|interface|inline|is|out|operator|package|return|suspend|super|this|when|val|var)\\b\n    - statement.properties: \\b(get|set)\\b\n    - statement.control: \\b(break|continue|else|do|if|try|catch|finally|for|while)\\b\n    - statement.class: \\b(abstract|annotation|data|enum|final|open|sealed)\\b\n    - statement.member: \\b(override|lateinit|init)\\b\n    - statement.access: \\b(internal|private|protected|public)\\b\n    - statement.parameter: \\b(crossinline|noinline|reified|vararg)\\b\n\n    # Expression and types\n    - type: \\b(dynamic|object|throw|typealias)\\b\n\n    # Meta\n    - statement.meta: \\@(\\bfile|delegate|field|get|property|receiver|set|setparam|param|)\\b\n\n    # Constant\n    - constant: \\b(true|false|null)\n    - constant.number: ([0-9]+)\n\n    # Storage Types\n    - type.storage: \\b(Byte|UByte|Char|Double|Float|Int|UInt|Long|ULong|Short|UShort|Boolean|Unit|Nothing)\\b\n\n    # Collections\n    - type.collections: \\b(Array)\\b\n\n     # String\n    - constant.string:\n        start: \\\"\n        end: \\\"\n        skip: \\\\.\n        rules:\n            - constant.specialChar: (\\\\0|\\\\\\\\|\\\\t|\\\\n|\\\\r|\\\\\"|\\\\')\n            - constant.unicode: \\\\u\\{[[:xdigit:]]+}\n\n    # Shebang Line\n    - comment.shebang: ^(#!).*\n\n    # Line Comment\n    - comment.line: \"//.*\"\n\n    # Block Comment\n    - comment.block:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    # Doc Block Comment\n    - comment.block:\n        start: \"/\\\\*\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    # Todo\n    - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/kvlang.yaml",
    "content": "filetype: \"kvlang\"\n\ndetect:\n    filename: \"\\\\.kv$\"\n\nrules:\n# layouts\n- special: \"\\\\b[a-z].+\"\n- identifier: \"\\\\b(self|app|root)\\\\b\"\n\n- type: \"\\\\b[A-Z].+\"\n- type: \"\\\\b(AnchorLayout|BoxLayout|FloatLayout|RelativeLayout|GridLayout|PageLayout|StackLayout)\\\\b\"\n\n- type: \"\\\\b(canvas)\\\\b\"\n\n# functions\n- identifier.function: \"[a-zA-Z_0-9]+\\\\(\"\n\n# built-in functions\n- type: \"\\\\b(abs|all|any|ascii|bin|bool|breakpoint|bytearray|bytes)\\\\b\"\n- type: \"\\\\b(callable|chr|classmethod|compile|copyright|credits|oct)\\\\b\"\n- type: \"\\\\b(delattr|dict|dir|display|divmod|enumerate|eval|filter)\\\\b\"\n- type: \"\\\\b(float|format|frozenset|get_ipython|getattr|globals|type)\\\\b\"\n- type: \"\\\\b(hash|help|hex|id|input|int|isinstance|issubclass|iter|len)\\\\b\"\n- type: \"\\\\b(license|list|locals|map|max|memoryview|min|next|object)\\\\b\"\n- type: \"\\\\b(open|ord|pow|print|property|range|repr|reversed|round|set)\\\\b\"\n- type: \"\\\\b(setattr|slice|sorted|staticmethod|hasattr|super|tuple|str)\\\\b\"\n- type: \"\\\\b(vars|zip|exec|sum|complex)\\\\b\"\n\n# keywords\n- statement.built_in: \"\\\\b(and|as|assert|async|await|break|class|continue|def)\\\\b\"\n- statement.built_in: \"\\\\b(del|elif|else|except|finally|for|from|global|if)\\\\b\"\n- statement.built_in: \"\\\\b(import|in|is|lambda|nonlocal|not|or|pass|raise)\\\\b\"\n- statement.built_in: \"\\\\b(return|try|while|with|yield|match|case)\\\\b\"\n\n# operators\n- symbol.operator: \"([~^.:;,+*|=!\\\\%]|<|>|/|-|&)\"\n\n# parentheses\n- symbol.brackets: \"([(){}]|\\\\[|\\\\])\"\n\n# numbers\n- constant.number: \"\\\\b[0-9](_?[0-9])*(\\\\.([0-9](_?[0-9])*)?)?(e[0-9](_?[0-9])*)?\\\\b\" # decimal\n- constant.number: \"\\\\b0b(_?[01])+\\\\b\"     # bin\n- constant.number: \"\\\\b0o(_?[0-7])+\\\\b\"    # oct\n- constant.number: \"\\\\b0x(_?[0-9a-f])+\\\\b\" # hex\n\n- constant.bool.none: \"\\\\b(None)\\\\b\"\n- constant.bool.true: \"\\\\b(True)\\\\b\"\n- constant.bool.false: \"\\\\b(False)\\\\b\"\n\n# strings\n- constant.string:\n    start: \"\\\"\"\n    end: \"(\\\"|$)\"\n    skip: \"\\\\\\\\.\"\n    rules: []\n- constant.string:\n    start: \"'\"\n    end: \"('|$)\"\n    skip: \"\\\\\\\\.\"\n    rules: []\n\n- comment:\n    start: \"#\"\n    end: \"$\"\n    rules: []\n"
  },
  {
    "path": "runtime/syntax/ledger.yaml",
    "content": "filetype: ledger\n\ndetect:\n    filename: \"(^|\\\\.|/)(ledger|ldgr|beancount|bnct)$\"\n\nrules:\n    - special: \"^([0-9]{4}(/|-)[0-9]{2}(/|-)[0-9]{2}|[=~]) .*\"\n    - constant: \"^[0-9]{4}(/|-)[0-9]{2}(/|-)[0-9]{2}\"\n    - statement: \"^~ .*\"\n    - identifier.var: \"^= .*\"\n    - identifier: \"^[[:space:]]+(![[:space:]]+)?\\\\(?[A-Za-z ]+(:[A-Za-z ]+)*\\\\)?\"\n    - identifier: \"^[[:space:]]+(![[:space:]]+)?\\\\(?[A-Za-z_\\\\-]+(:[A-Za-z_\\\\-]+)*\\\\)?\"\n    - symbol: \"[*!]\"\n    - comment: \"^[[:space:]]*;.*\"\n"
  },
  {
    "path": "runtime/syntax/lfe.yaml",
    "content": "filetype: lfe\n\ndetect:\n    filename: \"lfe$|\\\\.lfe$\"\n\nrules:\n    - symbol.brackets: \"\\\\(|\\\\)\"\n    - type: \"defun|define-syntax|define|defmacro|defmodule|export\"\n    - constant: \"\\\\ [A-Za-z][A-Za-z0-9_-]+\\\\ \"\n    - symbol.operator: \"\\\\(([\\\\-+*/<>]|<=|>=)|'\"\n    - constant.number: \"\\\\b[0-9]+\\\\b\"\n    - constant.string: \"\\\\\\\"(\\\\\\\\.|[^\\\"])*\\\\\\\"\"\n    - special: \"['|`][A-Za-z][A-Za-z0-9_\\\\-]+\"\n    - constant.specialChar: \"\\\\\\\\.?\"\n    - comment: \"(^|[[:space:]]);.*\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n    - indent-char: \"\t+ +| +\t+\"\n"
  },
  {
    "path": "runtime/syntax/lilypond.yaml",
    "content": "filetype: lilypond\n\ndetect:\n    filename: \"\\\\.ly$|\\\\.ily$|\\\\.lly$\"\n\nrules:\n    - constant.number: \"\\\\d+\"\n    - identifier: \"\\\\b(staff|spacing|signature|routine|notes|handler|corrected|beams|arpeggios|Volta_engraver|Voice|Vertical_align_engraver|Vaticana_ligature_engraver|VaticanaVoice|VaticanaStaff|Tweak_engraver|Tuplet_engraver|Trill_spanner_engraver|Timing_translator|Time_signature_performer|Time_signature_engraver|Tie_performer|Tie_engraver|Text_spanner_engraver|Text_engraver|Tempo_performer|Tab_tie_follow_engraver|Tab_staff_symbol_engraver|Tab_note_heads_engraver|TabVoice|TabStaff|System_start_delimiter_engraver|Stem_engraver|Stanza_number_engraver|Stanza_number_align_engraver|Staff_symbol_engraver|Staff_performer|Staff_collecting_engraver|StaffGroup|Staff|Spanner_break_forbid_engraver|Span_bar_stub_engraver|Span_bar_engraver|Span_arpeggio_engraver|Spacing_engraver|Slur_performer|Slur_engraver|Slash_repeat_engraver|Separating_line_group_engraver|Script_row_engraver|Script_engraver|Script_column_engraver|Score|Rhythmic_column_engraver|RhythmicStaff|Rest_engraver|Rest_collision_engraver|Repeat_tie_engraver|Repeat_acknowledge_engraver|Pure_from_neighbor_engraver|Pitched_trill_engraver|Pitch_squash_engraver|Piano_pedal_performer|Piano_pedal_engraver|Piano_pedal_align_engraver|PianoStaff|Phrasing_slur_engraver|PetrucciVoice|PetrucciStaff|Percent_repeat_engraver|Part_combine_engraver|Parenthesis_engraver|Paper_column_engraver|Output_property_engraver|Ottava_spanner_engraver|OneStaff|NullVoice|Note_spacing_engraver|Note_performer|Note_name_engraver|Note_heads_engraver|Note_head_line_engraver|NoteName\\\\|NoteHead|New_fingering_engraver|Multi_measure_rest_engraver|Midi_control_function_performer|Metronome_mark_engraver|Mensural_ligature_engraver|MensuralVoice|MensuralStaff|Mark_engraver|Lyrics|Lyric_performer|Lyric_engraver|Ligature_bracket_engraver|Ledger_line_engraver|Laissez_vibrer_engraver|Kievan_ligature_engraver|KievanVoice|KievanStaff|Key_performer|Key_engraver|Keep_alive_together_engraver|Instrument_switch_engraver|Instrument_name_engraver|Hyphen_engraver|Grob_pq_engraver|GregorianTranscriptionVoice|GregorianTranscriptionStaff|GrandStaff|Grace_spacing_engraver|Grace_engraver|Grace_beam_engraver|Grace_auto_beam_engraver|Global|Glissando_engraver|Fretboard_engraver|FretBoards|Forbid_line_break_engraver|Footnote_engraver|Font_size_engraver|Fingering_engraver|Fingering_column_engraver|Figured_bass_position_engraver|Figured_bass_engraver|FiguredBass|Extender_engraver|Episema_engraver|Dynamics|Dynamic_performer|Dynamic_engraver|Dynamic_align_engraver|Drum_notes_engraver|Drum_note_performer|DrumVoice|DrumStaff|Double_percent_repeat_engraver|Dots_engraver|Dot_column_engraver|Devnull|Default_bar_line_engraver|Custos_engraver|Cue_clef_engraver|CueVoice|Control_track_performer|Concurrent_hairpin_engraver|Collision_engraver|Cluster_spanner_engraver|Clef_engraver|Chord_tremolo_engraver|Chord_name_engraver|ChordNames|ChoirStaff|Breathing_sign_engraver|Break_align_engraver|Bend_engraver|Beam_performer|Beam_engraver|Beam_collision_engraver|Bar_number_engraver|Bar_engraver|Axis_group_engraver|Auto_beam_engraver|Arpeggio_engraver|Accidental_engraver|Score)\\\\b\"\n    - statement: \"[-_^]?\\\\\\\\[-A-Za-z_]+\"\n    - preproc: \"\\\\b(((gisis|gis|geses|ges|g|fisis|fis|feses|fes|f|eisis|eis|eeses|ees|e|disis|dis|deses|des|d|cisis|cis|ceses|ces|c|bisis|bis|beses|bes|b|aisis|ais|aeses|aes|a)[,']*[?!]?)|s|r|R|q)(128|64|32|16|8|4|2|1|\\\\\\\\breve|\\\\\\\\longa|\\\\\\\\maxima)?([^\\\\\\\\\\\\w]|_|\\\\b)\"\n    - special: \"[(){}<>]|\\\\[|\\\\]\"\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - comment:\n        start: \"%\\\\{\"\n        end: \"%\\\\}\"\n        rules: []\n    - comment:\n        start: \"%\"\n        end: \"$\"\n        rules: []\n\n"
  },
  {
    "path": "runtime/syntax/lisp.yaml",
    "content": "filetype: lisp\n\ndetect:\n    filename: \"(emacs|zile)$|\\\\.(el|li?sp|scm|ss|rkt)$\"\n\nrules:\n    - default: \"\\\\([a-z-]+\"\n    - symbol: \"\\\\(([\\\\-+*/<>]|<=|>=)|'\"\n    - constant.number: \"\\\\b[0-9]+b>\"\n    - special: \"\\\\bnil\\\\b\"\n    - preproc: \"\\\\b[tT]b>\"\n    - constant.string: \"\\\\\\\"(\\\\\\\\.|[^\\\"])*\\\\\\\"\"\n    - constant.specialChar: \"'[A-Za-z][A-Za-z0-9_-]+\"\n    - constant.specialChar: \"\\\\\\\\.?\"\n    - comment: \"(^|[[:space:]]);.*\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n    - indent-char: \"\t+ +| +\t+\"\n"
  },
  {
    "path": "runtime/syntax/log.yaml",
    "content": "filetype: log\n\ndetect:\n    filename: \"(\\\\.log|log\\\\.txt)$\"\n\nrules:\n- diff-modified: \"\\\\b(WARN(ING)?|[Ww]arn(ing)?|w(r)?n|w|W/)\\\\b\"\n- diff-modified: \"\\\\b(CRITICAL|[Cc]ritical)\\\\b\"\n\n- constant: \"\\\\b(INFO(RMATION)?|[Ii]nfo(rmation)?|[Ii]n(f)?|i|I/)\\\\b\"\n- constant: \"\\\\b(DEBUG|[Dd]ebug|dbug|dbg|de|d|D/)\\\\b\"\n- constant: \"\\\\b(VERBOSE|[Vv]erbose|V/)\\\\b\"\n- constant: \"\\\\b(ALERT|[Aa]lert)\\\\b\"\n\n- preproc: \"\\\\b(TRACE|Trace|NOTICE|VERBOSE|verb|vrb|vb|v)\\\\b\"\n\n- gutter-error: \"\\\\b(ERROR|[Ee]rr(or)?|[Ee]r(or)?|e|E\\\\x2F)\\\\b\"\n- gutter-error: \"\\\\b(FATAL|[Ff]atal)\\\\b\"\n- gutter-error: \"\\\\b(EMERGENCY|[Ee]mergency)\\\\b\"\n- gutter-error: \"\\\\b(FAIL(URE)?|[Ff]ail(ure)?)\\\\b\"\n\n# constants\n- constant.bool.true: \"\\\\b(YES|yes|Y|y|ON|on|TRUE|True|true)\\\\b\"\n- constant.bool.false: \"\\\\b(NO|no|N|n|OFF|off|FALSE|False|false)\\\\b\"\n- constant.bool.false: \"\\\\b(None|null|nil)\\\\b\"\n\n# numbers\n- constant.number: \"\\\\b[0-9](_?[0-9])*(\\\\.([0-9](_?[0-9])*)?)?(e[0-9](_?[0-9])*)?\\\\b\" # decimal\n- constant.number: \"\\\\b0b(_?[01])+\\\\b\"      # bin\n- constant.number: \"\\\\b0o(_?[0-7])+\\\\b\"     # oct\n- constant.number: \"\\\\b0x(_?[0-9a-f])+\\\\b\"  # hex\n\n# operators\n- symbol.operator: \"([~^.:;,+*|=!\\\\%]|<|>|/|-|&)\"\n\n# parentheses\n- symbol.brackets: \"([(){}]|\\\\[|\\\\])\"\n\n# string\n- constant.string:\n    start: \"\\\"\"\n    end: \"(\\\"|$)\"\n    skip: \"\\\\\\\\.\"\n    rules:\n      - constant.specialChar: \"\\\\\\\\.\"\n\n- constant.string:\n    start: \"'\"\n    end: \"('|$)\"\n    skip: \"\\\\\\\\.\"\n    rules:\n      - constant.specialChar: \"\\\\\\\\.\"\n\n# file\n- preproc: \"\\\\b(FILE|File|file)\\\\b\"\n\n# time\n- identifier: \"\\\\b((([Mm]on|[Tt]ues|[Ww]ed(nes)?|[Tt]hur(s)?|[Ff]ri|[Ss]at(ur)?|[Ss]un)(day)?\\\\s)?([Jj]an(uary)?|[Ff]eb(ruary)?|[Mm]ar(ch)?|[Aa]pr(il)?|[Mm]ay|[Jj]un(e)?|[Jj]ul(y)?|[Aa]ug(ust)?|[Aa]go|[Ss]ep(tember)?|[Oo]ct(ober)?|[Nn]ov(ember)?|[Dd]ec(ember)?)\\\\s\\\\d{1,2},?(\\\\s\\\\d{4})?)\\\\b\"  # date\n- identifier: \"\\\\b(\\\\d{2,4}[-/\\\\.]?\\\\d{2,3}[-/\\\\.]?\\\\d{2,4})\\\\b\"  # date\n- identifier: \"\\\\b(\\\\d{2}:\\\\d{2}(:\\\\d{2})?([\\\\.,]?\\\\d{1,8}[\\\\.\\\\+,]?\\\\d{1,8}?)?([\\\\.\\\\+,]?\\\\d{1,8}[\\\\.\\\\+,]?\\\\d{1,8}?)?([\\\\.\\\\+,]?\\\\d{1,8}?)?(\\\\s-\\\\d{0,4})?)\\\\b\"  # time\n- identifier: \"^([0-2][0-9][0-2][0-9][-/]?[0-9][0-9][-/]?[0-9][0-9])\"\n# - identifier: \"^([0-2][0-9][0-2][0-9][-/]?[0-9][0-9][-/]?[0-9][0-9]\\\\s[0-9][0-9]:[0-9][0-9](:[0-9][0-9])?(\\\\.?[0-9][0-9][0-9])?)\"\n- identifier: \"^(\\\\d{4}[-/]?\\\\d{2}[-/]?\\\\d{2}\\\\s\\\\d{2}:\\\\d{2}(:\\\\d{2})?(\\\\.?\\\\d{2,8})?)\"\n- identifier: \"^([0-2][0-9]|[0-2]-?[0-9][0-9]-?[0-9][0-9])\\\\-([0-1][0-9])\\\\-([0-3][0-9]) ([0-2][0-9])\\\\:([0-5][0-9])\\\\:([0-5][0-9]),([0-9][0-9][0-9])\"\n# Complete precision:\n- identifier: \"^(\\\\d{4}-[01]\\\\d-[0-3]\\\\dT[0-2]\\\\d:[0-5]\\\\d:[0-5]\\\\d\\\\.\\\\d+([+-][0-2]\\\\d:[0-5]\\\\d|Z))\"\n# No milliseconds:\n- identifier: \"^(\\\\d{4}-[01]\\\\d-[0-3]\\\\dT[0-2]\\\\d:[0-5]\\\\d:[0-5]\\\\d([+-][0-2]\\\\d:[0-5]\\\\d|Z))\"\n# No Seconds:\n- identifier: \"^(\\\\d{4}-[01]\\\\d-[0-3]\\\\dT[0-2]\\\\d:[0-5]\\\\d([+-][0-2]\\\\d:[0-5]\\\\d|Z))\"\n# Putting it all together:\n- identifier: \"^(\\\\d{4}-[01]\\\\d-[0-3]\\\\dT[0-2]\\\\d:[0-5]\\\\d:[0-5]\\\\d\\\\.\\\\d+([+-][0-2]\\\\d:[0-5]\\\\d|Z))|(\\\\d{4}-[01]\\\\d-[0-3]\\\\dT[0-2]\\\\d:[0-5]\\\\d:[0-5]\\\\d([+-][0-2]\\\\d:[0-5]\\\\d|Z))|(\\\\d{4}-[01]\\\\d-[0-3]\\\\dT[0-2]\\\\d:[0-5]\\\\d([+-][0-2]\\\\d:[0-5]\\\\d|Z))\"\n# Complete precision:\n- identifier: \"^(\\\\d{4}-[01]\\\\d-[0-3]\\\\dT[0-2]\\\\d:[0-5]\\\\d:[0-5]\\\\d\\\\.\\\\d+)\"\n# No milliseconds\n- identifier: \"^(\\\\d{4}-[01]\\\\d-[0-3]\\\\dT[0-2]\\\\d:[0-5]\\\\d:[0-5]\\\\d)\"\n#  No Seconds\n- identifier: \"^(\\\\d{4}-[01]\\\\d-[0-3]\\\\dT[0-2]\\\\d:[0-5]\\\\d)\"\n# Putting it all together\n- identifier: \"^(\\\\d{4}-[01]\\\\d-[0-3]\\\\dT[0-2]\\\\d:[0-5]\\\\d:[0-5]\\\\d\\\\.\\\\d+)|(\\\\d{4}-[01]\\\\d-[0-3]\\\\dT[0-2]\\\\d:[0-5]\\\\d:[0-5]\\\\d)|(\\\\d{4}-[01]\\\\d-[0-3]\\\\dT[0-2]\\\\d:[0-5]\\\\d)\"\n\n# link\n- constant.string.url:\n    start: \"https?://\"\n    end: \"\\\\s\"\n    rules: []\n\n# path\n# - constant.string.url: \"\\\\b(.+)/([^/]+)\\\\b\"  # linux\n# - constant.string.url: \"\\\\b(^[a-zA-Z]:)\\\\b\"  # windowns\n\n- diff-modified: \"([Cc]ommit:)\\\\s\\\\w+\\\\[\\\\w+]\"\n"
  },
  {
    "path": "runtime/syntax/lua.yaml",
    "content": "filetype: lua\n\ndetect:\n    filename: \"\\\\.lua$\"\n\nrules:\n    - statement: \"\\\\b(do|end|while|break|repeat|until|if|elseif|then|else|for|in|function|local|return|goto)\\\\b\"\n    - statement: \"\\\\b(not|and|or)\\\\b\"\n    - statement: \"\\\\b(debug|string|math|table|io|coroutine|os|utf8|bit32)\\\\b\\\\.\"\n    - statement: \"\\\\b(_ENV|_G|_VERSION|assert|collectgarbage|dofile|error|getfenv|getmetatable|ipairs|load|loadfile|module|next|pairs|pcall|print|rawequal|rawget|rawlen|rawset|require|select|setfenv|setmetatable|tonumber|tostring|type|unpack|xpcall)\\\\s*\\\\(\"\n    - identifier: \"io\\\\.\\\\b(close|flush|input|lines|open|output|popen|read|tmpfile|type|write)\\\\b\"\n    - identifier: \"math\\\\.\\\\b(abs|acos|asin|atan2|atan|ceil|cosh|cos|deg|exp|floor|fmod|frexp|huge|ldexp|log10|log|max|maxinteger|min|mininteger|modf|pi|pow|rad|random|randomseed|sin|sqrt|tan|tointeger|type|ult)\\\\b\"\n    - identifier: \"os\\\\.\\\\b(clock|date|difftime|execute|exit|getenv|remove|rename|setlocale|time|tmpname)\\\\b\"\n    - identifier: \"package\\\\.\\\\b(config|cpath|loaded|loadlib|path|preload|seeall|searchers|searchpath)\\\\b\"\n    - identifier: \"string\\\\.\\\\b(byte|char|dump|find|format|gmatch|gsub|len|lower|match|pack|packsize|rep|reverse|sub|unpack|upper)\\\\b\"\n    - identifier: \"table\\\\.\\\\b(concat|insert|maxn|move|pack|remove|sort|unpack)\\\\b\"\n    - identifier: \"utf8\\\\.\\\\b(char|charpattern|codes|codepoint|len|offset)\\\\b\"\n    - identifier: \"coroutine\\\\.\\\\b(create|isyieldable|resume|running|status|wrap|yield)\\\\b\"\n    - identifier: \"debug\\\\.\\\\b(debug|getfenv|gethook|getinfo|getlocal|getmetatable|getregistry|getupvalue|getuservalue|setfenv|sethook|setlocal|setmetatable|setupvalue|setuservalue|traceback|upvalueid|upvaluejoin)\\\\b\"\n    - identifier: \"bit32\\\\.\\\\b(arshift|band|bnot|bor|btest|bxor|extract|replace|lrotate|lshift|rrotate|rshift)\\\\b\"\n    - identifier: \"\\\\:\\\\b(close|flush|lines|read|seek|setvbuf|write|byte|char|dump|find|format|gmatch|gsub|len|lower|match|pack|packsize|rep|reverse|sub|unpack|upper)\\\\b\"\n    - identifier: \"\\\\b(self|arg)\\\\b\"\n    - constant: \"\\\\b(false|nil|true)\\\\b\"\n    - statement: \"(\\\\b(dofile|require|include)|%q|%!|%Q|%r|%x)\\\\b\"\n\n    - symbol.brackets: \"[(){}\\\\[\\\\]]\"\n    - symbol: \"(\\\\*|//|/|%|\\\\+|-|\\\\^|>|>=|<|<=|~=|=|[\\\\.]{2,3}|#)\"\n\n    - constant.number: \"\\\\b((0[xX](([0-9A-Fa-f]+\\\\.[0-9A-Fa-f]*)|(\\\\.?[0-9A-Fa-f]+))([pP][-+]?[0-9]+)?)|((([0-9]+\\\\.[0-9]*)|(\\\\.?[0-9]+))([eE][-+]?[0-9]+)?))\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([abfnrtvz\\\\'\\\"]|[0-9]{1,3}|x[0-9a-fA-F][0-9a-fA-F]|u\\\\{[0-9a-fA-F]+\\\\})\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([abfnrtvz\\\\'\\\"]|[0-9]{1,3}|x[0-9a-fA-F][0-9a-fA-F]|u\\\\{[0-9a-fA-F]+\\\\})\"\n\n    - constant.string:\n        start: \"\\\\[\\\\[\"\n        end: \"\\\\]\\\\]\"\n        rules: []\n\n# support first few lengths of \"long brackets\" explicitly\n# brackets longer than that will give false positives\n\n    - constant.string:\n        start: \"\\\\[=\\\\[\"\n        end: \"\\\\]=\\\\]\"\n        rules: []\n\n    - constant.string:\n        start: \"\\\\[==\\\\[\"\n        end: \"\\\\]==\\\\]\"\n        rules: []\n\n    - constant.string:\n        start: \"\\\\[===\\\\[\"\n        end: \"\\\\]===\\\\]\"\n        rules: []\n\n    - constant.string:\n        start: \"\\\\[====+\\\\[\"\n        end: \"\\\\]====+\\\\]\"\n        rules: []\n\n    - comment.block:\n        start: \"\\\\-\\\\-\\\\[\\\\[\"\n        end: \"\\\\]\\\\]\"\n        rules:\n            - todo: \"(TODO|NOTE|FIXME):?\"\n\n# support long brackets, same as with multiline strings\n\n    - comment.block:\n        start: \"\\\\-\\\\-\\\\[=\\\\[\"\n        end: \"\\\\]=\\\\]\"\n        rules:\n            - todo: \"(TODO|NOTE|FIXME):?\"\n\n    - comment.block:\n        start: \"\\\\-\\\\-\\\\[==\\\\[\"\n        end: \"\\\\]==\\\\]\"\n        rules:\n            - todo: \"(TODO|NOTE|FIXME):?\"\n\n    - comment.block:\n        start: \"\\\\-\\\\-\\\\[===\\\\[\"\n        end: \"\\\\]===\\\\]\"\n        rules:\n            - todo: \"(TODO|NOTE|FIXME):?\"\n\n    - comment.block:\n        start: \"\\\\-\\\\-\\\\[====+\\\\[\"\n        end: \"\\\\]====+\\\\]\"\n        rules:\n            - todo: \"(TODO|NOTE|FIXME):?\"\n\n# this has to go after block comment or block comment does not work\n\n    - comment:\n        start: \"\\\\-\\\\-\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|NOTE|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/mail.yaml",
    "content": "filetype: mail\n\ndetect:\n    filename: \"(.*/mutt-.*|\\\\.eml)$\"\n    header: \"^From .* \\\\d+:\\\\d+:\\\\d+ \\\\d+\"\n\nrules:\n    - type: \"^From .*\"\n    - identifier: \"^[^[:space:]]+:\"\n    - preproc: \"^List-(Id|Archive|Subscribe|Unsubscribe|Post|Help):\"\n    - constant: \"^(To|From):\"\n    - constant.string:\n        start: \"^Subject:.*\"\n        end: \"$\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - statement: \"<?[^@[:space:]]+@[^[:space:]]+>?\"\n    - default:\n        start: \"^\\\\n\\\\n\"\n        end: \".*\"\n        rules: []\n    - comment:\n        start: \"^>.*\"\n        end: \"$\"\n        rules: []\n"
  },
  {
    "path": "runtime/syntax/make_headers.go",
    "content": "//go:build ignore\n// +build ignore\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\tyaml \"gopkg.in/yaml.v2\"\n)\n\ntype HeaderYaml struct {\n\tFileType string `yaml:\"filetype\"`\n\tDetect   struct {\n\t\tFNameRgx     string `yaml:\"filename\"`\n\t\tHeaderRgx    string `yaml:\"header\"`\n\t\tSignatureRgx string `yaml:\"signature\"`\n\t} `yaml:\"detect\"`\n}\n\ntype Header struct {\n\tFileType     string\n\tFNameRgx     string\n\tHeaderRgx    string\n\tSignatureRgx string\n}\n\nfunc main() {\n\tif len(os.Args) > 1 {\n\t\tos.Chdir(os.Args[1])\n\t}\n\tfiles, _ := os.ReadDir(\".\")\n\tfor _, f := range files {\n\t\tfname := f.Name()\n\t\tif strings.HasSuffix(fname, \".yaml\") {\n\t\t\tconvert(fname[:len(fname)-5])\n\t\t}\n\t}\n}\n\nfunc convert(name string) {\n\tfilename := name + \".yaml\"\n\tvar hdr HeaderYaml\n\tsource, err := os.ReadFile(filename)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\terr = yaml.Unmarshal(source, &hdr)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tencode(name, hdr)\n}\n\nfunc encode(name string, c HeaderYaml) {\n\tf, _ := os.Create(name + \".hdr\")\n\tf.WriteString(c.FileType + \"\\n\")\n\tf.WriteString(c.Detect.FNameRgx + \"\\n\")\n\tf.WriteString(c.Detect.HeaderRgx + \"\\n\")\n\tf.WriteString(c.Detect.SignatureRgx + \"\\n\")\n\tf.Close()\n}\n\nfunc decode(name string) Header {\n\tstart := time.Now()\n\tdata, _ := os.ReadFile(name + \".hdr\")\n\tstrs := bytes.Split(data, []byte{'\\n'})\n\tvar hdr Header\n\thdr.FileType = string(strs[0])\n\thdr.FNameRgx = string(strs[1])\n\thdr.HeaderRgx = string(strs[2])\n\thdr.SignatureRgx = string(strs[3])\n\tfmt.Printf(\"took %v\\n\", time.Since(start))\n\n\treturn hdr\n}\n"
  },
  {
    "path": "runtime/syntax/makefile.yaml",
    "content": "filetype: makefile\n\ndetect:\n    filename: \"([Mm]akefile|\\\\.ma?k)$\"\n    header: \"^#!.*/(env +)?[bg]?make( |$)\"\n\nrules:\n    - preproc: \"\\\\<(ifeq|ifdef|ifneq|ifndef|else|endif)\\\\>\"\n    - statement: \"^(export|include|override)\\\\>\"\n    - symbol.operator: \"^[^:=\t]+:\"\n    - symbol.operator: \"([=,%]|\\\\+=|\\\\?=|:=|&&|\\\\|\\\\|)\"\n    - statement: \"\\\\$\\\\((abspath|addprefix|addsuffix|and|basename|call|dir)[[:space:]]\"\n    - statement: \"\\\\$\\\\((error|eval|filter|filter-out|findstring|firstword)[[:space:]]\"\n    - statement: \"\\\\$\\\\((flavor|foreach|if|info|join|lastword|notdir|or)[[:space:]]\"\n    - statement: \"\\\\$\\\\((origin|patsubst|realpath|shell|sort|strip|suffix)[[:space:]]\"\n    - statement: \"\\\\$\\\\((value|warning|wildcard|word|wordlist|words)[[:space:]]\"\n    - identifier: \"^.+:\"\n    - identifier: \"[()$]\"\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - identifier: \"\\\\$+(\\\\{[^} ]+\\\\}|\\\\([^) ]+\\\\))\"\n    - identifier: \"\\\\$[@^<*?%|+]|\\\\$\\\\([@^<*?%+-][DF]\\\\)\"\n    - identifier: \"\\\\$\\\\$|\\\\\\\\.?\"\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules: []\n\n"
  },
  {
    "path": "runtime/syntax/man.yaml",
    "content": "filetype: man\n\ndetect:\n    filename: \"\\\\.[1-9]x?$\"\n\nrules:\n    - green: \"\\\\.(S|T)H.*$\"\n    - brightgreen: \"\\\\.(S|T)H|\\\\.TP\"\n    - brightred: \"\\\\.(BR?|I[PR]?).*$\"\n    - brightblue: \"\\\\.(BR?|I[PR]?|PP)\"\n    - brightwhite: \"\\\\\\\\f[BIPR]\"\n    - yellow: \"\\\\.(br|DS|RS|RE|PD)\"\n"
  },
  {
    "path": "runtime/syntax/markdown.yaml",
    "content": "filetype: markdown\n\ndetect:\n    filename: \"\\\\.(livemd|md|mkd|mkdn|markdown)$\"\n\nrules:\n    # Tables (Github extension)\n    - type: \".*[ :]\\\\|[ :].*\"\n\n      # quotes\n    - statement:  \"^>.*\"\n\n      # Emphasis\n    - type: \"(^|[[:space:]])(_[^ ][^_]*_|\\\\*[^ ][^*]*\\\\*)\"\n\n      # Strong emphasis\n    - type: \"(^|[[:space:]])(__[^ ][^_]*__|\\\\*\\\\*[^ ][^*]*\\\\*\\\\*)\"\n\n      # strike-through\n    - type: \"(^|[[:space:]])~~[^ ][^~]*~~\"\n\n      # horizontal rules\n    - special: \"^(---+|===+|___+|\\\\*\\\\*\\\\*+)\\\\s*$\"\n\n      # headlines\n    - special:  \"^#{1,6}.*\"\n\n      # lists\n    - identifier:   \"^[[:space:]]*[\\\\*+-] |^[[:space:]]*[0-9]+\\\\. \"\n\n      # misc\n    - preproc:   \"(\\\\(([CcRr]|[Tt][Mm])\\\\)|\\\\.{3}|(^|[[:space:]])\\\\-\\\\-($|[[:space:]]))\"\n\n      # links\n    - constant: \"\\\\[[^]]+\\\\]\"\n    - constant: \"\\\\[([^][]|\\\\[[^]]*\\\\])*\\\\]\\\\([^)]+\\\\)\"\n\n      # images\n    - underlined: \"!\\\\[[^][]*\\\\](\\\\([^)]+\\\\)|\\\\[[^]]+\\\\])\"\n\n      # urls\n    - underlined: \"https?://[^ )>]+\"\n\n    - special: \"^```$\"\n\n    - special:\n        start: \"`\"\n        end: \"`\"\n        rules: []\n"
  },
  {
    "path": "runtime/syntax/mc.yaml",
    "content": "# sendmail config files\n\nfiletype: mc\n\ndetect:\n    filename: \"\\\\.mc$\"\n\nrules:\n    - statement: \"^(divert|VERSIONID|OSTYPE|DOMAIN|FEATURE|define)\"\n    - statement: \"^(DAEMON_OPTIONS|MAILER)\"\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules: []\n    - comment:\n        start: \"dnl\"\n        end: \"$\"\n        rules: []\n    - constant.string:\n        start: \"`\"\n        end: \"'\"\n        rules: []\n\n"
  },
  {
    "path": "runtime/syntax/meson.yaml",
    "content": "filetype: meson\n\ndetect:\n    filename: \"(meson\\\\.build|meson_options\\\\.txt|meson\\\\.options)\"\n\nrules:\n\n    # refer to https://mesonbuild.com/Syntax.html\n\n    - statement: \"\\\\b(elif|else|if|endif)\\\\b\"\n    - statement: \"\\\\b(foreach|endforeach)\\\\b\"\n    - statement: \"\\\\b(continue|break)\\\\b\"\n    - statement: \"\\\\b(and|not|or|in)\\\\b\"\n\n    - symbol.operator: \"[<>?:+*/-]|[+!<>=]?=\"\n    - symbol.brackets: \"[(){}\\\\[\\\\]]\"\n\n    - constant.number: \"\\\\b(0|[1-9][0-9]*)\\\\b\"  # decimal\n    - constant.number: \"\\\\b(0b[01]+)\\\\b\"        # bin\n    - constant.number: \"\\\\b(0o[0-7]+)\\\\b\"       # oct\n    - constant.number: \"\\\\b(0x[0-9a-fA-F]+)\\\\b\" # hex\n\n    # meson builtins\n    - identifier: \"\\\\b(add_global_arguments|add_global_link_arguments|add_languages|add_project_arguments|add_project_dependencies)\\\\b\"\n    - identifier: \"\\\\b(add_project_link_arguments|add_test_setup|alias_target|assert|benchmark|both_libraries|build_machine|build_target|configuration_data)\\\\b\"\n    - identifier: \"\\\\b(configure_file|custom_target|debug|declare_dependency|dependency|disabler|environment|error|executable|files)\\\\b\"\n    - identifier: \"\\\\b(find_program|generator|get_option|get_variable|host_machine|import|include_directories|install_data|install_emptydir)\\\\b\"\n    - identifier: \"\\\\b(install_headers|install_man|install_subdir|install_symlink|is_disabler|is_variable|jar|join_paths|library|meson)\\\\b\"\n    - identifier: \"\\\\b(message|option|project|range|run_command|run_target|set_variable|shared_library|shared_module|static_library)\\\\b\"\n    - identifier: \"\\\\b(structured_sources|subdir|subdir_done|subproject|summary|target_machine|test|unset_variable|vcs_tag|warning)\\\\b\"\n\n    - constant.bool: \"\\\\b(true|false)\\\\b\"\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules: []\n\n    # multiline strings do not support escape sequences\n    - constant.string:\n        start: \"'''\"\n        end: \"'''\"\n        rules: []\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\[abfnrtv\\\\\\\\']\"\n            - constant.specialChar: \"\\\\\\\\([0-7]{1,3}|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|N\\\\{[^\\\\}]+\\\\})\"\n"
  },
  {
    "path": "runtime/syntax/micro.yaml",
    "content": "filetype: micro\n\ndetect:\n    filename: \"\\\\.(micro)$\"\n\nrules:\n    - statement: \"\\\\b(syntax|color(-link)?)\\\\b\"\n    - statement: \"\\\\b(start=|end=)\\\\b\"\n    # Simple one-liners\n    - identifier: \"\\\\b(default|number|statement|underlined|error|todo|statusline|indent-char|cursor\\\\-line|color\\\\-column|ignore|divider|tabbar)\\\\b\"\n    # Separate identifiers to keep \"complex\" regex clean\n    - identifier: \"\\\\b(special(Char)?)\\\\b\"\n    - identifier: \"\\\\b((current\\\\-)?line\\\\-number)\\\\b\"\n    - identifier: \"\\\\b(gutter\\\\-(info|error|warning){1})\\\\b\"\n    - identifier: \"\\\\b(comment(\\\\.bright)?)\\\\b\"\n    - identifier: \"\\\\b(symbol(\\\\.(brackets|operator|tag))?)\\\\b\"\n    - identifier: \"\\\\b(identifier(\\\\.(class|macro|var))?)\\\\b\"\n    - identifier: \"\\\\b(constant(\\\\.(bool(\\\\.(true|false){1})?|number|specialChar|string(\\\\.url)?){1})?)\\\\b\"\n    - identifier: \"\\\\b(preproc(\\\\.shebang)?)\\\\b\"\n    - identifier: \"\\\\b(type(\\\\.keyword)?)\\\\b\"\n    - constant.number: \"\\\\b(|h|A|0x)+[0-9]+(|h|A)+\\\\b\"\n    - constant.number: \"\\\\b0x[0-9 a-f A-F]+\\\\b\"\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n          - todo: \"(FIXME|TODO|NOTE):?\"\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - constant.number: \"#[0-9 A-F a-f]+\"\n"
  },
  {
    "path": "runtime/syntax/mpdconf.yaml",
    "content": "filetype: mpd\n\ndetect:\n    filename: \"mpd\\\\.conf$\"\n\nrules:\n    - statement: \"\\\\b(user|group|bind_to_address|host|port|plugin|name|type)\\\\b\"\n    - statement: \"\\\\b((music|playlist)_directory|(db|log|state|pid|sticker)_file)\\\\b\"\n    - special: \"^(input|audio_output|decoder)[[:space:]]*\\\\{|\\\\}\"\n    - constant.string: \"\\\"(\\\\\\\\.|[^\\\"])*\\\"|'(\\\\\\\\.|[^'])*'\"\n    - comment: \"(^|[[:space:]])#([^{].*)?$\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n    - indent-char: \"\t+ +| +\t+\"\n"
  },
  {
    "path": "runtime/syntax/msbuild.yaml",
    "content": "filetype: msbuild\ndetect:\n    filename: \"\\\\.(.*proj|props|targets|tasks)$\"\n\nrules:\n    - include: \"xml\"\n"
  },
  {
    "path": "runtime/syntax/nanorc.yaml",
    "content": "filetype: nanorc\n\ndetect:\n    filename: \"\\\\.?nanorc$\"\n\nrules:\n    - default: \"(?i)^[[:space:]]*((un)?set|include|syntax|i?color).*$\"\n    - type: \"(?i)^[[:space:]]*(set|unset)[[:space:]]+(autoindent|backup|backupdir|backwards|boldtext|brackets|casesensitive|const|cut|fill|historylog|matchbrackets|morespace|mouse|multibuffer|noconvert|nofollow|nohelp|nonewlines|nowrap|operatingdir|preserve|punct)\\\\>|^[[:space:]]*(set|unset)[[:space:]]+(quickblank|quotestr|rebinddelete|rebindkeypad|regexp|smarthome|smooth|speller|suspend|tabsize|tabstospaces|tempfile|undo|view|whitespace|wordbounds)\\\\b\"\n    - preproc: \"(?i)^[[:space:]]*(set|unset|include|syntax|header)\\\\b\"\n    - constant.bool.true: \"(?i)(set)\\\\b\"\n    - constant.bool.false: \"(?i)(unset)\\\\b\"\n    - identifier: \"(?i)^[[:space:]]*(i)?color[[:space:]]*(bright)?(white|black|red|blue|green|yellow|magenta|cyan)?(,(white|black|red|blue|green|yellow|magenta|cyan))?\\\\b\"\n    - special: \"(?i)^[[:space:]]*(i)?color\\\\b|\\\\b(start|end)=\"\n    - constant.string: \"\\\"(\\\\\\\\.|[^\\\"])*\\\"\"\n    - comment: \"^[[:space:]]*#.*$\"\n    - comment.bright: \"^[[:space:]]*##.*$\"\n"
  },
  {
    "path": "runtime/syntax/nftables.yaml",
    "content": "filetype: nftables\n\ndetect:\n    filename: \"(nftables\\\\.(conf|rules)$|nftables(\\\\.rules)?\\\\.d/)\"\n    header: \"^(#!.*/(env +)?nft( |$)|flush +ruleset)\"\n\nrules:\n    - type: \"\\\\b(chain|counter|map|rule|ruleset|set|table)\\\\b\"\n    - type: \"\\\\b(ether|inet|i(cm)?p(x|(v?(4|6))?)|tcp|udp|8021q)\\\\b\"\n    - special: \"\\\\b(element(s)?|hook|policy|priority|type|state)\\\\b\"\n    - identifier: \"\\\\b(ct|iif|iifname|meta|oif|oifname|th|dport|sport|saddr|daddr|l4proto)\\\\b\"\n    - statement: \"\\\\b(accept|drop|goto|jump|log|masquerade|reject|limit|queue)\\\\b\"\n    - preproc: \"\\\\b(add|define|flush|include|delete)\\\\b\"\n    - symbol.operator: \"[<>.&|^!=:;,@]|\\\\b(and|ge|gt|le|lt|or|xor)\\\\b\"\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        rules: []\n      # Integer Constants\n    - constant.number: \"\\\\b([0-9]+)\\\\b\"\n    - constant.number: \"\\\\b(0x[0-9a-fA-F]+)\\\\b\"\n    - identifier.var: \"[$@][a-zA-Z_.][a-zA-Z0-9_/.-]*\"\n    - comment: \"(^|[[:space:]])#([^{].*)?$\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n    - indent-char: \"    + +| +  +\"\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/nginx.yaml",
    "content": "filetype: nginx\n\ndetect:\n    filename: \"nginx.*\\\\.conf$|\\\\.nginx$|\\\\.sub(domain|folder)\\\\.conf$\"\n    header: \"^(server|upstream)[a-z ]*\\\\{$\"\n\nrules:\n    - preproc: \"\\\\b(events|server|http|location|upstream)[[:space:]]*\\\\{\"\n    - statement: \"(^|[[:space:]{;])(access_log|add_after_body|add_before_body|add_header|addition_types|aio|alias|allow|ancient_browser|ancient_browser_value|auth_basic|auth_basic_user_file|autoindex|autoindex_exact_size|autoindex_localtime|break|charset|charset_map|charset_types|chunked_transfer_encoding|client_body_buffer_size|client_body_in_file_only|client_body_in_single_buffer|client_body_temp_path|client_body_timeout|client_header_buffer_size|client_header_timeout|client_max_body_size|connection_pool_size|create_full_put_path|daemon|dav_access|dav_methods|default_type|deny|directio|directio_alignment|disable_symlinks|empty_gif|env|error_log|error_page|expires|fastcgi_buffer_size|fastcgi_buffers|fastcgi_busy_buffers_size|fastcgi_cache|fastcgi_cache_bypass|fastcgi_cache_key|fastcgi_cache_lock|fastcgi_cache_lock_timeout|fastcgi_cache_min_uses|fastcgi_cache_path|fastcgi_cache_use_stale|fastcgi_cache_valid|fastcgi_connect_timeout|fastcgi_hide_header|fastcgi_ignore_client_abort|fastcgi_ignore_headers|fastcgi_index|fastcgi_intercept_errors|fastcgi_keep_conn|fastcgi_max_temp_file_size|fastcgi_next_upstream|fastcgi_no_cache|fastcgi_param|fastcgi_pass|fastcgi_pass_header|fastcgi_read_timeout|fastcgi_send_timeout|fastcgi_split_path_info|fastcgi_store|fastcgi_store_access|fastcgi_temp_file_write_size|fastcgi_temp_path|flv|geo|geoip_city|geoip_country|gzip|gzip_buffers|gzip_comp_level|gzip_disable|gzip_http_version|gzip_min_length|gzip_proxied|gzip_static|gzip_types|gzip_vary|if|if_modified_since|ignore_invalid_headers|image_filter|image_filter_buffer|image_filter_jpeg_quality|image_filter_sharpen|image_filter_transparency|include|index|internal|ip_hash|keepalive|keepalive_disable|keepalive_requests|keepalive_timeout|large_client_header_buffers|limit_conn|limit_conn_log_level|limit_conn_zone|limit_except|limit_rate|limit_rate_after|limit_req|limit_req_log_level|limit_req_zone|limit_zone|lingering_close|lingering_time|lingering_timeout|listen|location|log_format|log_not_found|log_subrequest|map|map_hash_bucket_size|map_hash_max_size|master_process|max_ranges|memcached_buffer_size|memcached_connect_timeout|memcached_next_upstream|memcached_pass|memcached_read_timeout|memcached_send_timeout|merge_slashes|min_delete_depth|modern_browser|modern_browser_value|mp4|mp4_buffer_size|mp4_max_buffer_size|msie_padding|msie_refresh|open_file_cache|open_file_cache_errors|open_file_cache_min_uses|open_file_cache_valid|open_log_file_cache|optimize_server_names|override_charset|pcre_jit|perl|perl_modules|perl_require|perl_set|pid|port_in_redirect|postpone_output|proxy_buffer_size|proxy_buffering|proxy_buffers|proxy_busy_buffers_size|proxy_cache|proxy_cache_bypass|proxy_cache_key|proxy_cache_lock|proxy_cache_lock_timeout|proxy_cache_min_uses|proxy_cache_path|proxy_cache_use_stale|proxy_cache_valid|proxy_connect_timeout|proxy_cookie_domain|proxy_cookie_path|proxy_hide_header|proxy_http_version|proxy_ignore_client_abort|proxy_ignore_headers|proxy_intercept_errors|proxy_max_temp_file_size|proxy_next_upstream|proxy_no_cache|proxy_pass|proxy_pass_header|proxy_read_timeout|proxy_redirect|proxy_send_timeout|proxy_set_header|proxy_ssl_session_reuse|proxy_store|proxy_store_access|proxy_temp_file_write_size|proxy_temp_path|random_index|read_ahead|real_ip_header|recursive_error_pages|request_pool_size|reset_timedout_connection|resolver|resolver_timeout|return|rewrite|root|satisfy|satisfy_any|secure_link_secret|send_lowat|send_timeout|sendfile|sendfile_max_chunk|server|server|server_name|server_name_in_redirect|server_names_hash_bucket_size|server_names_hash_max_size|server_tokens|set|set_real_ip_from|source_charset|split_clients|ssi|ssi_silent_errors|ssi_types|ssl|ssl_certificate|ssl_certificate_key|ssl_ciphers|ssl_client_certificate|ssl_crl|ssl_dhparam|ssl_engine|ssl_prefer_server_ciphers|ssl_protocols|ssl_session_cache|ssl_session_timeout|ssl_verify_client|ssl_verify_depth|sub_filter|sub_filter_once|sub_filter_types|tcp_nodelay|tcp_nopush|timer_resolution|try_files|types|types_hash_bucket_size|types_hash_max_size|underscores_in_headers|uninitialized_variable_warn|upstream|user|userid|userid_domain|userid_expires|userid_name|userid_p3p|userid_path|userid_service|valid_referers|variables_hash_bucket_size|variables_hash_max_size|worker_priority|worker_processes|worker_rlimit_core|worker_rlimit_nofile|working_directory|xml_entities|xslt_stylesheet|xslt_types)([[:space:]]|$)\"\n    - constant.bool.true: \"\\\\b(on)\\\\b\"\n    - constant.bool.false: \"\\\\b(off)\\\\b\"\n    - identifier: \"\\\\$[A-Za-z][A-Za-z0-9_]*\"\n    - symbol: \"[*]\"\n    - constant-string: \"\\\"(\\\\\\\\.|[^\\\"])*\\\"|'(\\\\\\\\.|[^'])*'\"\n    - constant.string:\n        start: \"'$\"\n        end: \"';$\"\n        rules: []\n\n    - comment: \"(^|[[:space:]])#([^{].*)?$\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n    - indent-char: \"\t+ +| +\t+\"\n"
  },
  {
    "path": "runtime/syntax/nim.yaml",
    "content": "filetype: nim\n\ndetect:\n    filename: \"\\\\.nims?$|nim.cfg\"\n\nrules:\n    - preproc: \"[\\\\{\\\\|]\\\\b(atom|lit|sym|ident|call|lvalue|sideeffect|nosideeffect|param|genericparam|module|type|let|var|const|result|proc|method|iterator|converter|macro|template|field|enumfield|forvar|label|nk[a-zA-Z]+|alias|noalias)\\\\b[\\\\}\\\\|]\"\n    - statement: \"\\\\b(addr|and|as|asm|atomic|bind|block|break|case|cast|concept|const|continue|converter|defer|discard|distinct|div|do|elif|else|end|enum|except|export|finally|for|from|func|generic|if|import|in|include|interface|is|isnot|iterator|let|macro|method|mixin|mod|nil|not|notin|object|of|or|out|proc|ptr|raise|ref|return|shl|shr|static|template|try|tuple|type|using|var|when|while|with|without|xor|yield)\\\\b\"\n    - statement: \"\\\\b(deprecated|noSideEffect|constructor|destructor|override|procvar|compileTime|noReturn|acyclic|final|shallow|pure|asmNoStackFrame|error|fatal|warning|hint|line|linearScanEnd|computedGoto|unroll|immediate|checks|boundsChecks|overflowChecks|nilChecks|assertations|warnings|hints|optimization|patterns|callconv|push|pop|global|pragma|experimental|bitsize|volatile|noDecl|header|incompleteStruct|compile|link|passC|passL|emit|importc|importcpp|importobjc|codegenDecl|injectStmt|intdefine|strdefine|varargs|exportc|extern|bycopy|byref|union|packed|unchecked|dynlib|cdecl|thread|gcsafe|threadvar|guard|locks|compileTime)\\\\b\"\n    - symbol.operator: \"[=\\\\+\\\\-\\\\*/<>@\\\\$~&%\\\\|!\\\\?\\\\^\\\\.:\\\\\\\\]+\"\n    - special: \"\\\\{\\\\.|\\\\.\\\\}|\\\\[\\\\.|\\\\.\\\\]|\\\\(\\\\.|\\\\.\\\\)|;|,|`\"\n    - statement: \"\\\\.\\\\.\"\n    - type: \"\\\\b(int|cint|int8|int16|int32|int64|uint|uint8|uint16|uint32|uint64|float|float32|float64|bool|char|enum|string|cstring|cstringArray|cdouble|csize_t|pointer|array|openarray|seq|varargs|tuple|object|set|void|auto|cshort|clong|range|nil|T|untyped|typedesc)\\\\b\"\n    - type: \"'[iI](8|16|32|64)?\\\\b|'[uU](8|16|32|64)?\\\\b|'[fF](32|64|128)?\\\\b|'[dD]\\\\b\"\n    - constant.number: \"\\\\b[0-9]+\\\\b\"\n    - constant.number: \"\\\\b0[xX][0-9A-Fa-f][0-9_A-Fa-f]+\\\\b\"\n    - constant.number: \"\\\\b0[ocC][0-7][0-7_]+\\\\b\"\n    - constant.number: \"\\\\b0[bB][01][01_]+\\\\b\"\n    - constant.number: \"\\\\b[0-9_]((\\\\.?)[0-9_]+)?[eE][+\\\\-][0-9][0-9_]+\\\\b\"\n    - constant.string: \"\\\"(\\\\\\\\.|[^\\\"])*\\\"|'(\\\\\\\\.|[^'])*'\"\n    - comment: \"[[:space:]]*(?:[^\\\\\\\\]|^)#.*$\"\n    - comment:\n        start: \"\\\\#\\\\[\"\n        end: \"\\\\]\\\\#\"\n        rules: []\n\n    - todo: \"(TODO|FIXME|XXX):?\"\n"
  },
  {
    "path": "runtime/syntax/nix.yaml",
    "content": "filetype: nix\n\ndetect:\n    filename: \"\\\\.nix$\"\n\nrules:\n    - special: \"\\\\b(Ellipsis|null|self|super|true|false|abort)\\\\b\"\n    - statement: \"\\\\b(let|in|with|import|rec|inherit)\\\\b\"\n    - symbol.operator: \"([~^.:;,+*|=!\\\\%@]|<|>|/|-|&)\"\n    - symbol.brackets: \"([(){}]|\\\\[|\\\\])\"\n\n    - constant.number: \"\\\\b[0-9](_?[0-9])*(\\\\.([0-9](_?[0-9])*)?)?(e[0-9](_?[0-9])*)?\\\\b\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        rules: []\n\n    - constant.string:\n        start: \"''\"\n        end: \"''\"\n        rules: []\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules: []\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules: []\n"
  },
  {
    "path": "runtime/syntax/nu.yaml",
    "content": "filetype: nu\n\ndetect:\n    filename: \"\\\\.nu$\"\n\nrules:\n    - symbol: \"[-+/*=<>!~%?:&|]\"\n    # https://www.nushell.sh/book/command_reference.html\n    - statement: \"\\\\b(agg-groups|agg|alias|all-false|all-true|all?|ansi gradient)\\\\b\"\n    - statement: \"\\\\b(ansi strip|ansi|any?|append|append|arg-max)\\\\b\"\n    - statement: \"\\\\b(arg-min|arg-sort|arg-true|arg-unique|as-date)\\\\b\"\n    - statement: \"\\\\b(as-datetime|as|benchmark|build-string|cache|cal|cd)\\\\b\"\n    - statement: \"\\\\b(char|clear|col|collect|columns|compact|complete)\\\\b\"\n    - statement: \"\\\\b(concatenate|config|config env|config nu|contains)\\\\b\"\n    - statement: \"\\\\b(count|count-null|cp|cumulative|date format|date humanize)\\\\b\"\n    - statement: \"\\\\b(date list-timezone|date now|date to-record|date to-table)\\\\b\"\n    - statement: \"\\\\b(date to-timezone|date|db and|db as|db col|db collect)\\\\b\"\n    - statement: \"\\\\b(db describe|db fn|db from|db group-by|db join|db limit)\\\\b\"\n    - statement: \"\\\\b(db open|db or|db order-by|db over|db query|db schema)\\\\b\"\n    - statement: \"\\\\b(db select|db testing|db where|db|debug|decode)\\\\b\"\n    - statement: \"\\\\b(def-env|default|def|describe|describe|detect columns)\\\\b\"\n    - statement: \"\\\\b(df-not|do|drop|drop|drop column|drop nth|drop-duplicates)\\\\b\"\n    - statement: \"\\\\b(drop-nulls|dtypes|du|each while|each|echo|empty?)\\\\b\"\n    - statement: \"\\\\b(enter|env|error make|every|exec|exit|explode)\\\\b\"\n    - statement: \"\\\\b(export alias|export def|export def-env|export env)\\\\b\"\n    - statement: \"\\\\b(export extern|export|expr-not|extern|fetch|fill-na)\\\\b\"\n    - statement: \"\\\\b(fill-null|filter-with|find|first|flatten)\\\\b\"\n    - statement: \"\\\\b(fmt|format filesize|format|for|from csv|from eml)\\\\b\"\n    - statement: \"\\\\b(from ics|from ini|from json|from nuon|from ods|from ssv)\\\\b\"\n    - statement: \"\\\\b(from toml|from tsv|from url|from vcf|from xlsx|from xml)\\\\b\"\n    - statement: \"\\\\b(from yaml|from yml|from|get-day|get-hour|get-minute)\\\\b\"\n    - statement: \"\\\\b(get-month|get-nanosecond|get-ordinal|get-second|get-week)\\\\b\"\n    - statement: \"\\\\b(get-weekday|get-year|get|glob|grid|group-by)\\\\b\"\n    - statement: \"\\\\b(group|gstat|g|hash base64|hash md5|hash sha256|hash)\\\\b\"\n    - statement: \"\\\\b(headers|help|hide|histogram|history|if|ignore)\\\\b\"\n    - statement: \"\\\\b(inc|input|insert|into binary|into bool|into datetime|into decimal)\\\\b\"\n    - statement: \"\\\\b(into duration|into filesize|into int|into string|into)\\\\b\"\n    - statement: \"\\\\b(is-admin|is-duplicated|is-in|is-not-null)\\\\b\"\n    - statement: \"\\\\b(is-null|is-unique|join|keep|keep until)\\\\b\"\n    - statement: \"\\\\b(keep while|keybindings default|keybindings listen|keybindings list)\\\\b\"\n    - statement: \"\\\\b(keybindings|kill|last|length|let-env|let)\\\\b\"\n    - statement: \"\\\\b(lines|list|lit|load-env|ls|ls-df|match|math abs)\\\\b\"\n    - statement: \"\\\\b(math avg|math ceil|math eval|math floor|math max)\\\\b\"\n    - statement: \"\\\\b(math median|math min|math mode|math product|math round)\\\\b\"\n    - statement: \"\\\\b(math sqrt|math stddev|math sum|math variance|math|max)\\\\b\"\n    - statement: \"\\\\b(mean|median|melt|merge|metadata)\\\\b\"\n    - statement: \"\\\\b(min|mkdir|module|move|mv|n|n-unique|n-unique)\\\\b\"\n    - statement: \"\\\\b(nth|nu-highlight|open|open-df|otherwise|overlay)\\\\b\"\n    - statement: \"\\\\b(overlay add|overlay list|overlay new|overlay remove|p)\\\\b\"\n    - statement: \"\\\\b(par-each|parse|path basename|path dirname|path exists)\\\\b\"\n    - statement: \"\\\\b(path expand|path join|path parse|path relative-to|path split)\\\\b\"\n    - statement: \"\\\\b(path type|path|pivot|post|prepend|print|ps|quantile)\\\\b\"\n    - statement: \"\\\\b(quantile|query json|query web|query xml|query|random bool)\\\\b\"\n    - statement: \"\\\\b(random chars|random decimal|random dice|random integer)\\\\b\"\n    - statement: \"\\\\b(random uuid|random|range|reduce|register|reject|rename)\\\\b\"\n    - statement: \"\\\\b(replace|replace-all|reverse|reverse|rm|roll down)\\\\b\"\n    - statement: \"\\\\b(roll left|roll right|roll up|rolling|roll|rotate)\\\\b\"\n    - statement: \"\\\\b(run-external|sample|save|select|select|seq|seq char)\\\\b\"\n    - statement: \"\\\\b(seq date|set|set-with-idx|shape|shells|shift|shuffle)\\\\b\"\n    - statement: \"\\\\b(size|skip until|skip while|skip|sleep|slice|sort)\\\\b\"\n    - statement: \"\\\\b(sort-by|source|split chars|split column|split row)\\\\b\"\n    - statement: \"\\\\b(split-by|split|std|std|str camel-case|str capitalize)\\\\b\"\n    - statement: \"\\\\b(str collect|str contains|str downcase|str ends-with|str find-replace)\\\\b\"\n    - statement: \"\\\\b(str index-of|str kebab-case|str length|str lpad|str pascal-case)\\\\b\"\n    - statement: \"\\\\b(str replace|str reverse|str rpad|str screaming-snake-case)\\\\b\"\n    - statement: \"\\\\b(str snake-case|str starts-with|str substring|str title-case)\\\\b\"\n    - statement: \"\\\\b(str to-datetime|str to-decimal|str to-int|str trim|str upcase)\\\\b\"\n    - statement: \"\\\\b(str-lengths|str-slice|strftime|str|sum|sys|table)\\\\b\"\n    - statement: \"\\\\b(take until|take while|take|term size|to csv)\\\\b\"\n    - statement: \"\\\\b(to html|to json|to md|to nuon|to text|to toml|to tsv)\\\\b\"\n    - statement: \"\\\\b(to url|to xml|to yaml|to-csv|to-df|to-dummies|to-lazy)\\\\b\"\n    - statement: \"\\\\b(to-lowercase|to-nu|to-parquet|to-uppercase|touch|to)\\\\b\"\n    - statement: \"\\\\b(transpose|tutor|unalias|uniq|unique|update|update cells)\\\\b\"\n    - statement: \"\\\\b(upsert|url host|url path|url query|url scheme|url)\\\\b\"\n    - statement: \"\\\\b(use|value-counts|var|version|view-source|watch)\\\\b\"\n    - statement: \"\\\\b(when|where|which|window|with-column|with-env|wrap)\\\\b\"\n    # https://www.nushell.sh/book/types_of_data.html#booleans\n    - constant: \"\\\\b(false|true)\\\\b\"\n    - constant.number: \"\\\\b[-+]?([1-9][0-9])*\\\\b\"\n    # https://www.nushell.sh/book/types_of_data.html#binary-data\n    - constant.number: \"\\\\b[-+]?(0(x|b|o)\\\\[[0-9a-fA-F ]+\\\\])\"\n    # https://www.nushell.sh/book/types_of_data.html#file-sizes\n    - constant.number: \"\\\\b[-+]?([0-9]+[BbMmGgTtPp][i]?[Bb]?)?\\\\b\"\n    # https://www.nushell.sh/book/types_of_data.html#duration\n    - constant.number: \"\\\\b[-+]?([0-9]+[num]?[s])?\\\\b\"\n    - constant.number: \"\\\\b[-+]?([0-9]+(sec|min|hr|day|wk))?\\\\b\"\n    # https://www.nushell.sh/book/types_of_data.html#dates\n    - constant.number: \"\\\\b([0-9]+[-][0-9]+[-][0-9]+([T][0-9]+[:][0-9]+[:][0-9]+)?([\\\\+][0-9]+[:][0-9]+)?)\\\\b\"\n    # https://www.nushell.sh/book/types_of_data.html#ranges\n    - constant.number: \"([0-9]+(\\\\.\\\\.)[0-9]+)?\"\n    # https://www.nushell.sh/book/types_of_data.html#open-ended-ranges\n    - constant.number: \"((\\\\.\\\\.)[0-9]+)?\"\n    - constant.number: \"([0-9]+(\\\\.\\\\.))?\"\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules: []\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(FIXME|TODO|NOTE):?\"\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n"
  },
  {
    "path": "runtime/syntax/objc.yaml",
    "content": "filetype: objective-c\n\ndetect:\n    filename: \"\\\\.(m|mm|h)$\"\n    signature: \"(obj|objective)-c|#import|@(encode|end|interface|implementation|selector|protocol|synchronized|try|catch|finally|property|optional|required|import|autoreleasepool)\"\n\nrules:\n    - type: \"\\\\b(float|double|CGFloat|id|bool|BOOL|Boolean|char|int|short|long|sizeof|enum|void|static|const|struct|union|typedef|extern|(un)?signed|inline|Class|SEL|IMP|NS(U)?Integer)\\\\b\"\n    - type: \"\\\\b((s?size)|((u_?)?int(8|16|32|64|ptr)))_t\\\\b\"\n    - type: \"\\\\b[A-Z][A-Z][[:alnum:]]*\\\\b\"\n    - type: \"\\\\b[A-Za-z0-9_]*_t\\\\b\"\n    - type: \"\\\\bdispatch_[a-zA-Z0-9_]*_t\\\\b\"\n\n    - statement: \"(__attribute__[[:space:]]*\\\\(\\\\([^)]*\\\\)\\\\)|__(aligned|asm|builtin|hidden|inline|packed|restrict|section|typeof|weak)__|__unused|_Nonnull|_Nullable|__block|__builtin.*)\"\n    - statement: \"\\\\b(class|namespace|template|public|protected|private|typename|this|friend|virtual|using|mutable|volatile|register|explicit)\\\\b\"\n    - statement: \"\\\\b(for|if|while|do|else|case|default|switch)\\\\b\"\n    - statement: \"\\\\b(try|throw|catch|operator|new|delete)\\\\b\"\n    - statement: \"\\\\b(goto|continue|break|return)\\\\b\"\n    - statement: \"\\\\b(nonatomic|atomic|readonly|readwrite|strong|weak|assign)\\\\b\"\n    - statement: \"@(encode|end|interface|implementation|class|selector|protocol|synchronized|try|catch|finally|property|optional|required|import|autoreleasepool)\"\n\n    - preproc: \"^[[:space:]]*#[[:space:]]*(define|include|import|(un|ifn?)def|endif|el(if|se)|if|warning|error|pragma).*$\"\n    - preproc: \"__[A-Z0-9_]*__\"\n\n    - special: \"^[[:space:]]*[#|@][[:space:]]*(import|include)[[:space:]]*[\\\"|<].*\\\\/?[>|\\\"][[:space:]]*$\"\n\n    - statement: \"([.:;,+*|=!\\\\%\\\\[\\\\]]|<|>|/|-|&)\"\n\n    - constant.number: \"(\\\\b(-?)?[0-9]+\\\\b|\\\\b\\\\[0-9]+\\\\.[0-9]+\\\\b|\\\\b0x[0-9a-fA-F]+\\\\b)\"\n    - constant: \"(@\\\\[(\\\\\\\\.|[^\\\\]])*\\\\]|@\\\\{(\\\\\\\\.|[^\\\\}])*\\\\}|@\\\\((\\\\\\\\.|[^\\\\)])*\\\\))\"\n    - constant: \"\\\\b<(\\\\\\\\.[^\\\\>])*\\\\>\\\\b\"\n    - constant: \"\\\\b(nil|NULL|YES|NO|TRUE|true|FALSE|false|self)\\\\b\"\n    - constant: \"\\\\bk[[:alnum]]*\\\\b\"\n    - constant.string: \"'.'\"\n\n    - constant.string:\n        start: \"@\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/ocaml.yaml",
    "content": "filetype: ocaml\n\ndetect:\n    filename: \"\\\\.mli?$\"\n\nrules:\n    - identifier: \"\\\\b[A-Z][0-9a-z_]{2,}\\\\b\"\n      #declarations\n    - statement: \"\\\\b(let|val|method|in|and|rec|private|virtual|constraint)\\\\b\"\n      #structure items\n    - type: \"\\\\b(type|open|class|module|exception|external)\\\\b\"\n      #patterns\n    - statement: \"\\\\b(fun|function|functor|match|try|with)\\\\b\"\n      #patterns-modifiers\n    - statement: \"\\\\b(as|when|of)\\\\b\"\n      #conditions\n    - statement: \"\\\\b(if|then|else)\\\\b\"\n      #blocs\n    - type: \"\\\\b(begin|end|object|struct|sig|for|while|do|done|to|downto)\\\\b\"\n    - type: \"'[0-9A-Za-z_]+\"\n      #constantes\n    - constant.bool: \"\\\\b(true|false)\\\\b\"\n      #modules/classes\n    - special: \"\\\\b(include|inherit|initializer)\\\\b\"\n      #expr modifiers\n    - special: \"\\\\b(new|ref|mutable|lazy|assert|raise)\\\\b\"\n      #character literal\n    - constant.string: \"'(\\\\\\\\[0-7]{3}|\\\\\\\\x[A-Fa-f0-9]{2}|\\\\\\\\u[A-Fa-f0-9]{4}|\\\\\\\\U[A-Fa-f0-9]{8}|\\\\\\\\[abfnrtv'\\\\\\\"\\\\\\\\]|.)'\"\n    - constant.specialChar: \"\\\\\\\\[abfnrtv'\\\\\\\"\\\\\\\\]\"\n    - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})\"\n      #string literal\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"%.\"\n            - constant.specialChar: \"\\\\\\\\[abfnrtv'\\\\\\\"\\\\\\\\]\"\n            - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})\"\n    - comment:\n        start: \"\\\\(\\\\*\"\n        end: \"\\\\*\\\\)\"\n        rules: []\n"
  },
  {
    "path": "runtime/syntax/octave.yaml",
    "content": "# References\n# https://github.com/micro-editor/micro/blob/master/runtime/syntax/go.yaml\n# https://github.com/vim-scripts/octave.vim--/blob/master/syntax/octave.vim\n#\n# TODO\n# include only needed operators\n# ... highlighting\n# built-in function highlighting?\n# highlight eps/pi/e etc. as functions when followed by ()\n# what are skip and error fields in strings?\n# multiline comments not working\n\nfiletype: octave\n\ndetect:\n    filename: \"\\\\.m$\"\n\nrules:\n    # Statements https://www.gnu.org/software/octave/doc/v4.0.0/Statements.html\n    - statement: \"\\\\b(function|endfunction|return|end|global|persistent)\\\\b\"\n    - statement: \"\\\\b(if|elseif|else|endif|switch|case|otherwise|endswitch)\\\\b\"\n    - statement: \"\\\\b(while|endwhile|do|until|for|endfor|parfor|endparfor|break|continue)\\\\b\"\n    - statement: \"\\\\b(unwind_protect|unwind_protect_cleanup|end_unwind_protect|try|catch|end_try_catch)\\\\b\"\n\n    # Operators\n    - symbol.operator: \"[-+/*=<>!~%&|^]|:=\"\n\n    # Brackets\n    - symbol.brackets: \"(\\\\{|\\\\})\"\n    - symbol.brackets: \"(\\\\(|\\\\))\"\n    - symbol.brackets: \"(\\\\[|\\\\])\"\n\n    # Commas\n    - symbol: \",\"\n\n    # Numbers https://www.gnu.org/software/octave/doc/v4.0.1/Mathematical-Constants.html\n    - constant.number: \"\\\\b([0-9]+|0x[0-9a-fA-F]*)\\\\b|'.'\"\n    - constant.number: \"\\\\b(pi|e|I|Inf|NaN|eps|realmax|realmin)\\\\b|\"\n\n    # Boolean\n    - constant.bool: \"\\\\b(true|false)\\\\b\"\n\n    # Strings https://www.gnu.org/software/octave/doc/v4.0.1/Strings.html\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"%\"\n            - constant.specialChar: \"\\\\\\\\[abfnrtv'\\\\\\\"\\\\\\\\]\"\n            - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - error: \"..+\"\n            - constant.specialChar: \"%\"\n            - constant.specialChar: \"\\\\\\\\[abfnrtv'\\\\\\\"\\\\\\\\]\"\n            - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})\"\n\n    # Comments https://www.gnu.org/software/octave/doc/v4.2.1/Comments.html\n    - comment:\n        start: \"%\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n    - comment:\n        start: \"%{\"\n        end: \"%}\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n    - comment:\n        start: \"#{\"\n        end: \"#}\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/odin.yaml",
    "content": "filetype: odin\n\ndetect:\n    filename: \"\\\\.odin$\"\n\nrules:\n    # Conditionals and control flow\n    - special: \"\\\\b(asm|auto_cast|break|case|cast|context|continue|do|dynamic|fallthrough|return|transmute|using|where)\\\\b\"\n    - statement: \"\\\\b(else|for|if|switch|in|not_in|or_else|or_return|when)\\\\b\"\n    - preproc: \"\\\\b(assert|package|foreign|import|proc|defer|make|new|free|delete|copy|len|cap|append|raw_data)\\\\b\"\n    - preproc: \"\\\\b((size|align|offset|type|type_info|typeid)_of|offset_of_by_string)\\\\b\"\n    - preproc: \"\\\\b(swizzle|complex|quaternion|real|imag|jmag|kmag|conj|expand_to_tuple|min|max|abs|clamp|soa_zip|soa_unzip|transpose|outer_product|hadamard_product|matrix_flatten)\\\\b\"\n    - symbol.operator: \"[-+/*=<>!~%&|^@]|:\\\\s*=|:\\\\s*:|\\\\?\"\n\n      # Types\n    - symbol: \"(,|\\\\.)\"\n    - type: \"\\\\b(b(8|16|32|64)|(i|u)(8|(16|32|64|128)(le|be)?)|f(16|32|64)(le|be)?|complex(32|64|128)|quaternion(64|128|256))\\\\b\"\n    - type: \"\\\\b(any|bool|byte|rune|u?int|uintptr|rawptr|c?string|map|matrix|typeid)\\\\b\"\n    - type.keyword: \"\\\\b(distinct|struct|enum|union|bit_set)\\\\b\"\n    - constant.bool: \"\\\\b(true|false|nil)\\\\b\"\n\n      # Brackets\n    - symbol.brackets: \"(\\\\{|\\\\})\"\n    - symbol.brackets: \"(\\\\(|\\\\))\"\n    - symbol.brackets: \"(\\\\[|\\\\])\"\n\n      # Numbers and strings\n    - constant.number: \"\\\\b(0b[01]*|0o[0-7]*|0x[0-9a-fA-F]*|[0-9_]+|0d[0-9]*|0z[0-9abAB]*)\\\\b|'.'\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"%.\"\n            - constant.specialChar: \"\\\\\\\\[abfnrtv'\\\\\\\"\\\\\\\\]\"\n            - constant.specialChar: \"\\\\\\\\([0-7]{1,3}|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - error: \"..+\"\n            - constant.specialChar: \"%.\"\n            - constant.specialChar: \"\\\\\\\\[abfnrtv'\\\\\\\"\\\\\\\\]\"\n            - constant.specialChar: \"\\\\\\\\([0-7]{1,3}|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})\"\n\n    - constant.string:\n        start: \"`\"\n        end: \"`\"\n        rules: []\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"TODO:?|NOTE(\\\\(.*\\\\))?:?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"TODO:?|NOTE(\\\\(.*\\\\))?:?\"\n"
  },
  {
    "path": "runtime/syntax/pascal.yaml",
    "content": "filetype: pascal\n\ndetect:\n    filename: \"\\\\.pas$\"\n\nrules:\n    - type: \"\\\\b(?i:(string|ansistring|widestring|shortstring|char|ansichar|widechar|boolean|byte|shortint|word|smallint|longword|cardinal|longint|integer|int64|single|currency|double|extended))\\\\b\"\n    - statement: \"\\\\b(?i:(and|asm|array|begin|break|case|const|constructor|continue|destructor|div|do|downto|else|end|file|for|function|goto|if|implementation|in|inline|interface|label|mod|not|object|of|on|operator|or|packed|procedure|program|record|repeat|resourcestring|set|shl|shr|then|to|type|unit|until|uses|var|while|with|xor))\\\\b\"\n    - statement: \"\\\\b(?i:(as|class|dispose|except|exit|exports|finalization|finally|inherited|initialization|is|library|new|on|out|property|raise|self|threadvar|try))\\\\b\"\n    - statement: \"\\\\b(?i:(absolute|abstract|alias|assembler|cdecl|cppdecl|default|export|external|forward|generic|index|local|name|nostackframe|oldfpccall|override|pascal|private|protected|public|published|read|register|reintroduce|safecall|softfloat|specialize|stdcall|virtual|write))\\\\b\"\n    - constant: \"\\\\b(?i:(false|true|nil))\\\\b\"\n    - special:\n        start: \"asm\"\n        end: \"end\"\n        rules: []\n    - constant.number: \"\\\\$[0-9A-Fa-f]+\"\n    - constant.number: \"\\\\b[+-]?[0-9]+([.]?[0-9]+)?(?i:e[+-]?[0-9]+)?\"\n    - constant.string:\n        start: \"#[0-9]{1,}\"\n        end: \"$\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - preproc:\n        start: \"{\\\\$\"\n        end: \"}\"\n        rules: []\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules: []\n    - comment:\n        start: \"\\\\(\\\\*\"\n        end: \"\\\\*\\\\)\"\n        rules: []\n    - comment:\n        start: \"({)(?:[^$])\"\n        end: \"}\"\n        rules: []\n\n"
  },
  {
    "path": "runtime/syntax/patch.yaml",
    "content": "filetype: patch\n\ndetect:\n    filename: \"\\\\.(patch|diff)$\"\n    header: \"^diff\"\n\nrules:\n    - brightgreen: \"^\\\\+.*\"\n    - green: \"^\\\\+\\\\+\\\\+.*\"\n    - brightblue: \"^ .*\"\n    - brightred: \"^-.*\"\n    - red: \"^---.*\"\n    - brightyellow: \"^@@.*\"\n    - magenta: \"^diff.*\"\n"
  },
  {
    "path": "runtime/syntax/peg.yaml",
    "content": "filetype: peg\n\ndetect:\n    filename: \"\\\\.l?peg$\"\n\nrules:\n    - identifier: \"^[[:space:]]*[A-Za-z][A-Za-z0-9_]*[[:space:]]*<-\"\n    - constant.number: \"\\\\^[+-]?[0-9]+\"\n    - symbol.operator: \"[-+*?^/!&]|->|<-|=>\"\n    - identifier.var: \"%[A-Za-z][A-Za-z0-9_]*\"\n    - special: \"\\\\[[^]]*\\\\]\"\n    - constant.string: \"\\\"(\\\\\\\\.|[^\\\"])*\\\"|'(\\\\\\\\.|[^'])*'\"\n    - comment: \"(^|[[:space:]])\\\\-\\\\-.*$\"\n    - todo: \"TODO:?\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n    - indent-char: \"\t+ +| +\t+\"\n"
  },
  {
    "path": "runtime/syntax/perl.yaml",
    "content": "filetype: perl\n\ndetect:\n    filename: \"\\\\.p[lmp]$\"\n    header: \"^#!.*/(env +)?perl( |$)\"\n\nrules:\n    - type: \"\\\\b(accept|alarm|atan2|bin(d|mode)|c(aller|homp|h(dir|mod|op|own|root)|lose(dir)?|onnect|os|rypt)|d(bm(close|open)|efined|elete|ie|o|ump)|e(ach|of|val|x(ec|ists|it|p))|f(cntl|ileno|lock|ork))\\\\b|\\\\b(get(c|login|peername|pgrp|ppid|priority|pwnam|(host|net|proto|serv)byname|pwuid|grgid|(host|net)byaddr|protobynumber|servbyport)|([gs]et|end)(pw|gr|host|net|proto|serv)ent|getsock(name|opt)|gmtime|goto|grep|hex|index|int|ioctl|join)\\\\b|\\\\b(keys|kill|last|length|link|listen|local(time)?|log|lstat|m|mkdir|msg(ctl|get|snd|rcv)|next|oct|open(dir)?|ord|pack|pipe|pop|printf?|push|q|qq|qx|rand|re(ad(dir|link)?|cv|say|do|name|quire|set|turn|verse|winddir)|rindex|rmdir|s|scalar|seek(dir)?)\\\\b|\\\\b(se(lect|mctl|mget|mop|nd|tpgrp|tpriority|tsockopt)|shift|shm(ctl|get|read|write)|shutdown|sin|sleep|socket(pair)?|sort|spli(ce|t)|sprintf|sqrt|srand|stat|study|substr|symlink|sys(call|read|tem|write)|tell(dir)?|time|tr(y)?|truncate|umask)\\\\b|\\\\b(un(def|link|pack|shift)|utime|values|vec|wait(pid)?|wantarray|warn|write)\\\\b\"\n    - statement: \"\\\\b(continue|else|elsif|do|for|foreach|if|unless|until|while|eq|ne|lt|gt|le|ge|cmp|x|my|sub|use|package|can|isa)\\\\b\"\n\n    - special: \"\\\\-\\\\>\"\n    - symbol: \"(,|\\\\.)\"\n\n    #regexes\n    - identifier.macro: \"m?\\\\/.*?\\\\/[a-z]*\"\n    - identifier.macro: \"m?\\\\|.*?\\\\|[a-z]*\"\n    - identifier.macro: \"\\\\bs/.*?/.*?/[a-z]*\"\n    - identifier.macro: \"\\\\bs\\\\|.*?\\\\|.*?\\\\|[a-z]*\"\n\n    - constant.string:\n        start: '\"'\n        end: '\"'\n        skip: '\\\\\"'\n        rules:\n            - identifier.var: '[\\\\$@%].[a-zA-Z0-9_]*'\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\'\"\n        rules: []\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules: []\n\n    - constant.string: \"\\\"\\\\(.*\\\\)\\\"|qq?\\\\|.*\\\\||qq?\\\\{.*\\\\}|qq?\\\\/.*\\\\/\"\n    - constant.number: \"\\\\b([0-9]*[.])?[0-9]+\"\n    - constant.number: \"\\\\b[0-9]+\"\n    - constant.number: \"\\\\b0x[a-f0-9]+\"\n    - constant.string.url: \"`(.+?)`\"\n    - identifier.var: '[\\\\$@%].[a-zA-Z0-9_]*'\n\n    - preproc:\n        start: \"(^use| = new)\"\n        end: \";\"\n        rules: []\n\n    - comment:\n        start: \"^=\"\n        end: \"^=cut\"\n        rules: []\n\n    - identifier.macro:\n        start: \"<< 'STOP'\"\n        end: \"STOP\"\n        rules: []\n"
  },
  {
    "path": "runtime/syntax/php.yaml",
    "content": "filetype: php\n\ndetect:\n    filename: \"\\\\.php[2345s~]?$\"\n\nrules:\n    - symbol.operator: \"<|>\"\n    - error: \"<[^!].*?>\"\n    - symbol.tag: \"(?i)<[/]?(a(bbr|cronym|ddress|pplet|rea|rticle|side|udio)?|b(ase(font)?|d(i|o)|ig|lockquote|r)?|ca(nvas|ption)|center|cite|co(de|l|lgroup)|d(ata(list)?|d|el|etails|fn|ialog|ir|l|t)|em(bed)?|fieldset|fig(caption|ure)|font|form|(i)?frame|frameset|h[1-6]|hr|i|img|in(put|s)|kbd|keygen|label|legend|li(nk)?|ma(in|p|rk)|menu(item)?|met(a|er)|nav|no(frames|script)|o(l|pt(group|ion)|utput)|p(aram|icture|re|rogress)?|q|r(p|t|uby)|s(trike)?|samp|se(ction|lect)|small|source|span|strong|su(b|p|mmary)|textarea|time|track|u(l)?|var|video|wbr)( .*|>)*?>\"\n    - symbol.tag.extended: \"(?i)<[/]?(body|div|html|head(er)?|footer|title|table|t(body|d|h(ead)?|r|foot))( .*|>)*?>\"\n    - preproc: \"(?i)<[/]?(script|style)( .*|>)*?>\"\n    - preproc: \"<\\\\?(php|=)?\"\n    - preproc: \"\\\\?>\"\n    - preproc: \"<!DOCTYPE.+?>\"\n    - special: \"&[^;[[:space:]]]*;\"\n    - symbol: \"[:=]\"\n    - identifier: \"(alt|bgcolor|height|href|label|longdesc|name|onclick|onfocus|onload|onmouseover|size|span|src|style|target|type|value|width)=\"\n    - constant.number: \"(?i)#[0-9a-fA-F]{6,6}\"\n    - constant.string.url: \"(ftp(s)?|http(s)?|git|chrome)://[^ \t]+\"\n    - comment: \"<!--.+?-->\"\n    - default: \"<\\\\?(php|=)\\\" end=\\\"\\\\?>\"\n    - identifier.class: \"([a-zA-Z0-9_-]+)\\\\(\"\n    - type: \"\\\\b(array|bool|callable|float|int|iterable|object|mixed|string|void)\\\\b\"\n    - identifier.class: \"[a-zA-Z\\\\\\\\]+::\"\n    - identifier: \"\\\\b([A-Z][a-zA-Z0-9_]+)\\\\b\"\n    - identifier: \"([A-Z0-9_]+)[;|\\\\s|\\\\)|,]\"\n    - type.keyword: \"\\\\b(global|final|public|private|protected|static|const|var)\\\\b\"\n    - statement: \"\\\\b(abstract|catch|class|declare|do|else(if)?|end(declare|for(each)?|if|switch|while)|enum|finally|for(each)|function|if|interface|namespace|switch|trait|try|while)\\\\b\"\n    - identifier: \"\\\\bnew\\\\s+([a-zA-Z0-9\\\\\\\\]+)\"\n    - special: \"\\\\b(as|and|break|case|clone|continue|default|die|fn|echo|empty|eval|exit|extends|goto|or|include(_once)?|implements|instanceof|insteadof|isset|list|match|new|print|return|require(_once)?|unset|use|throw|xor|yield(\\\\s+from))\\\\b\"\n    - constant.bool: \"\\\\b(true|false|null|TRUE|FALSE|NULL)\\\\b\"\n    - constant: \"[\\\\s|=|\\\\s|\\\\(|/|+|-|\\\\*|\\\\[]\"\n    - constant.number: \"[0-9]\"\n    - identifier: \"(\\\\$this|parent|self|\\\\$this->)\"\n    - symbol.operator: \"(=>|===|!==|==|!=|&&|\\\\|\\\\||::|=|->|\\\\!)\"\n    - identifier.var: \"(\\\\$[a-zA-Z0-9\\\\-_]+)\"\n    - symbol.operator: \"[\\\\(|\\\\)|/|+|\\\\-|\\\\*|\\\\[|.|,|;]\"\n    - symbol.brackets: \"(\\\\[|\\\\]|\\\\{|\\\\}|[()])\"\n\n    - comment:\n        start: \"(^|[[:space:]])*(//|#)\"\n        end: \"$\"\n        rules: []\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules: []\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\[abfnrtv'\\\\\\\"\\\\\\\\]\"\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\[abfnrtv'\\\\\\\"\\\\\\\\]\"\n"
  },
  {
    "path": "runtime/syntax/pkg-config.yaml",
    "content": "filetype: pc\n\ndetect:\n    filename: \"\\\\.pc$\"\n\nrules:\n    - preproc: \"^(Name|Description|URL|Version|Conflicts|Cflags):\"\n    - preproc: \"^(Requires|Libs)(\\\\.private)?:\"\n    - symbol.operator: \"=\"\n    - identifier.var: \"\\\\$\\\\{[A-Za-z_][A-Za-z0-9_]*\\\\}\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n    - indent-char: \"\t+ +| +\t+\"\n"
  },
  {
    "path": "runtime/syntax/po.yaml",
    "content": "filetype: po\n\ndetect:\n    filename: \"\\\\.pot?$\"\n\nrules:\n    - preproc: \"\\\\b(msgid|msgstr)\\\\b\"\n    - constant.string: \"\\\"(\\\\\\\\.|[^\\\"])*\\\"|'(\\\\\\\\.|[^'])*'\"\n    - special: \"\\\\\\\\.?\"\n    - comment: \"(^|[[:space:]])#([^{].*)?$\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n    - indent-char: \"\t+ +| +\t+\"\n"
  },
  {
    "path": "runtime/syntax/pony.yaml",
    "content": "filetype: pony\n\ndetect:\n    filename: \"\\\\.pony$\"\n\nrules:\n    - statement: \"\\\\b(type|interface|trait|primitive|class|struct|actor)\\\\b\"\n    - statement: \"\\\\b(compiler_intrinsic)\\\\b\"\n    - statement: \"\\\\b(use)\\\\b\"\n    - statement: \"\\\\b(var|let|embed)\\\\b\"\n    - statement: \"\\\\b(new|be|fun)\\\\b\"\n    - statement: \"\\\\b(iso|trn|ref|val|box|tag|consume)\\\\b\"\n    - statement: \"\\\\b(break|continue|return|error)\\\\b\"\n    - statement: \"\\\\b(if|then|elseif|else|end|match|where|try|with|as|recover|object|lambda|as|digestof|ifdef)\\\\b\"\n    - statement: \"\\\\b(while|do|repeat|until|for|in)\\\\b\"\n    - statement: \"(\\\\?|=>)\"\n    - statement: \"(\\\\||\\\\&|\\\\,|\\\\^)\"\n    - symbol.operator: \"(\\\\-|\\\\+|\\\\*|/|\\\\!|%|<<|>>)\"\n    - symbol.operator: \"(==|!=|<=|>=|<|>)\"\n    - statement: \"\\\\b(is|isnt|not|and|or|xor)\\\\b\"\n    - type: \"\\\\b(_*[A-Z][_a-zA-Z0-9\\\\']*)\\\\b\"\n    - constant: \"\\\\b(this)\\\\b\"\n    - constant.bool: \"\\\\b(true|false)\\\\b\"\n    - constant.number: \"\\\\b((0b[0-1_]*)|(0o[0-7_]*)|(0x[0-9a-fA-F_]*)|([0-9_]+(\\\\.[0-9_]+)?((e|E)(\\\\\\\\+|-)?[0-9_]+)?))\\\\b\"\n    - constant.string: \"\\\"(\\\\\\\\.|[^\\\"])*\\\"\"\n    - comment:\n        start: \"\\\"\\\"\\\"[^\\\"]*\"\n        end: \"\\\"\\\"\\\"\"\n        rules: []\n\n    - comment: \"(^|[[:space:]])//.*\"\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules: []\n\n    - todo: \"TODO:?\"\n"
  },
  {
    "path": "runtime/syntax/pov.yaml",
    "content": "filetype: pov\n\ndetect:\n    filename: \"\\\\.(pov|POV|povray|POVRAY)$\"\n\nrules:\n    - preproc: \"^[[:space:]]*#[[:space:]]*(declare)\"\n    - statement: \"\\\\b(sphere|cylinder|translate|matrix|rotate|scale)\\\\b\"\n    - statement: \"\\\\b(orthographic|location|up|right|direction|clipped_by)\\\\b\"\n    - statement: \"\\\\b(fog_type|fog_offset|fog_alt|rgb|distance|transform)\\\\b\"\n    - identifier: \"^\\\\b(texture)\\\\b\"\n    - identifier: \"\\\\b(light_source|background)\\\\b\"\n    - identifier: \"\\\\b(fog|object|camera)\\\\b\"\n    - symbol.operator: \"(\\\\{|\\\\}|\\\\(|\\\\)|\\\\;|\\\\]|\\\\[|`|\\\\\\\\|\\\\$|<|>|!|=|&|\\\\|)\"\n    - special: \"\\\\b(union|group|subgroup)\\\\b\"\n    - comment: \"//.*\"\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules: []\n\n"
  },
  {
    "path": "runtime/syntax/privoxy-action.yaml",
    "content": "filetype: privoxy-action\n\ndetect:\n    filename: \"\\\\.action$\"\n\nrules:\n    - constant.bool.false: \"[{[:space:]]\\\\-block([[:space:]{}]|$)\"\n    - constant.bool.true: \"[{[:space:]]\\\\+block([[:space:]{}]|$)\"\n    - constant.bool.false: \"-(add-header|change-x-forwarded-for|client-header-filter|client-header-tagger|content-type-overwrite|crunch-client-header|crunch-if-none-match|crunch-incoming-cookies|crunch-outgoing-cookies|crunch-server-header|deanimate-gifs|downgrade-http-version|fast-redirects|filter|force-text-mode|forward-override|handle-as-empty-document|handle-as-image|hide-accept-language|hide-content-disposition|hide-from-header|hide-if-modified-since|hide-referrer|hide-user-agent|limit-connect|overwrite-last-modified|prevent-compression|redirect|server-header-filter|server-header-tagger|session-cookies-only|set-image-blocker)\"\n    - constant.bool.true: \"\\\\+(add-header|change-x-forwarded-for|client-header-filter|client-header-tagger|content-type-overwrite|crunch-client-header|crunch-if-none-match|crunch-incoming-cookies|crunch-outgoing-cookies|crunch-server-header|deanimate-gifs|downgrade-http-version|fast-redirects|filter|force-text-mode|forward-override|handle-as-empty-document|handle-as-image|hide-accept-language|hide-content-disposition|hide-from-header|hide-if-modified-since|hide-referrer|hide-user-agent|limit-connect|overwrite-last-modified|prevent-compression|redirect|server-header-filter|server-header-tagger|session-cookies-only|set-image-blocker)\"\n    - constant.specialChar: \"\\\\\\\\.?\"\n    - comment: \"(^|[[:space:]])#([^{].*)?$\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n    - indent-char: \"\t+ +| +\t+\"\n"
  },
  {
    "path": "runtime/syntax/privoxy-config.yaml",
    "content": "filetype: privoxy-config\n\ndetect:\n    filename: \"privoxy/config$\"\n\nrules:\n    - statement: \"(accept-intercepted-requests|actionsfile|admin-address|allow-cgi-request-crunching|buffer-limit|compression-level|confdir|connection-sharing|debug|default-server-timeout|deny-access|enable-compression|enable-edit-actions|enable-remote-http-toggle|enable-remote-toggle|enforce-blocks|filterfile|forward|forwarded-connect-retries|forward-socks4|forward-socks4a|forward-socks5|handle-as-empty-doc-returns-ok|hostname|keep-alive-timeout|listen-address|logdir|logfile|max-client-connections|permit-access|proxy-info-url|single-threaded|socket-timeout|split-large-forms|templdir|toggle|tolerate-pipelining|trustfile|trust-info-url|user-manual)[[:space:]]\"\n    - comment: \"(^|[[:space:]])#([^{].*)?$\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n    - indent-char: \"\t+ +| +\t+\"\n"
  },
  {
    "path": "runtime/syntax/privoxy-filter.yaml",
    "content": "filetype: privoxy-filter\n\ndetect:\n    filename: \"\\\\.filter$\"\n\nrules:\n    - statement: \"^(FILTER|CLIENT-HEADER-FILTER|CLIENT-HEADER-TAGGER|SERVER-HEADER-FILTER|SERVER-HEADER-TAGGER): [a-z-]+\"\n    - identifier: \"^(FILTER|CLIENT-HEADER-FILTER|CLIENT-HEADER-TAGGER|SERVER-HEADER-FILTER|SERVER-HEADER-TAGGER):\"\n    - constant.specialChar: \"\\\\\\\\.?\"\n    - comment: \"(^|[[:space:]])#([^{].*)?$\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n    - indent-char: \"\t+ +| +\t+\"\n"
  },
  {
    "path": "runtime/syntax/proto.yaml",
    "content": "filetype: proto\n\ndetect:\n    filename: \"(\\\\.(proto)$$)\"\n\nrules:\n    - identifier: \"\\\\b[A-Z_][0-9A-Z_]+\\\\b\"\n    - type: \"\\\\b(int(8|16|32|64))|string|bytes|repeated|bool|required|map|optional|oneof|union\\\\b\"\n    - statement: \"\\\\b(import|service|enum|syntax|package|option|message|rpc|returns|extensions|to)\\\\b\"\n    - constant: \"'\\\\\\\\(([0-3]?[0-7]{1,2}))'\"\n    - constant: \"'\\\\\\\\x[0-9A-Fa-f]{1,2}'\"\n    - symbol.brackets: \"[(){}]|\\\\[|\\\\]\"\n    - constant.number: \"(\\\\b[0-9]+\\\\b|\\\\b0x[0-9A-Fa-f]+\\\\b)\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - preproc: \"..+\"\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/prql.yaml",
    "content": "# https://prql-lang.org/\n# https://github.com/PRQL/prql\nfiletype: prql\n\ndetect:\n    filename: \"\\\\.prql$\"\n\nrules:\n    - statement: \"\\\\b(let|module|into|case|type|func)\\\\b\"\n\n      # Types\n    - type: \"\\\\b(u?int(8|16|32|64)?|float(32|64)|bool|text|date|time|timestamp)\\\\b\"\n    - type.keyword: \"\\\\b(enum)\\\\b\"\n    - constant.bool: \"\\\\b(true|false|null|this|that)\\\\b\"\n\n      # Built-in functions\n    - identifier: \"\\\\b(abs|floor|ceil|pi|exp|ln|log10|log|sqrt|degrees|radians|cos|acos|sin|asin|tan|atan|pow|round)\\\\b\" # Math module\n    - identifier: \"\\\\b(min|max|sum|average|stddev|all|any|concat_array|count)\\\\b\" # Aggregate functions\n    - identifier: \"\\\\b(lag|lead|first|last|rank|rank_dense|row_number)\\\\b\" # Window functions\n    - identifier: \"\\\\b(tuple_every|tuple_map|tuple_zip|_eq|_is_null)\\\\b\" # Tuple functions\n    - identifier: \"\\\\b(as|in|from_text)\\\\b\" # Misc\n    - identifier: \"\\\\b(lower|upper|ltrim|rtrim|trim|length|extract|replace|starts_with|contains|ends_with)\\\\b\" # Text module\n    - identifier: \"\\\\b(to_text)\\\\b\" # Date module\n    - identifier: \"\\\\b(read_parquet|read_csv)\\\\b\" # File-reading functions\n\n      # Modules\n    - identifier.class: \"\\\\b(math|text|date|prql)\\\\b\"\n\n      # Transforms\n    - statement: \"\\\\b(aggregate|derive|filter|from|group|join|select|sort|take|window)\\\\b\"\n\n      # Operators\n    - symbol.operator: \"([~^.:;,+*|=!\\\\%@?]|<|>|/|-|&)\"\n\n      # Brackets\n    - symbol.brackets: \"[{}()\\\\[\\\\]]\"\n\n      # Numbers\n    - constant.number: \"\\\\b[0-9](_?[0-9])*(\\\\.([0-9](_?[0-9])*)?)?(e[0-9](_?[0-9])*)?\\\\b\" # decimal\n    - constant.number: \"\\\\b0b(_?[01])+\\\\b\"     # bin\n    - constant.number: \"\\\\b0o(_?[0-7])+\\\\b\"    # oct\n    - constant.number: \"\\\\b0x(_?[0-9a-fA-F])+\\\\b\" # hex\n    - constant: \"\\\\b[0-9]+(years|months|weeks|days|hours|minutes|seconds|milliseconds|microseconds)\\\\b\"\n\n    - constant.string:\n        start: \"[frs]?\\\"\\\"\\\"\"\n        end: \"\\\"\\\"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\[bfnrt'\\\"\\\\\\\\]\"\n            - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u\\\\{[0-9A-Fa-f]{1,6}\\\\})\"\n\n    - constant.string:\n        start: \"[frs]?'''\"\n        end: \"'''\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\[bfnrt'\\\"\\\\\\\\]\"\n            - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u\\\\{[0-9A-Fa-f]{1,6}\\\\})\"\n\n    - constant.string:\n        start: \"[frs]?\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\[bfnrt'\\\"\\\\\\\\]\"\n            - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u\\\\{[0-9A-Fa-f]{1,6}\\\\})\"\n\n    - constant.string:\n        start: \"[frs]?'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\[bfnrt'\\\"\\\\\\\\]\"\n            - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u\\\\{[0-9A-Fa-f]{1,6}\\\\})\"\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|FIXME|NOTE):?\"\n\n      # Decorators\n    - preproc: \"@\\\\{([a-z]+(=[a-z0-9]+,?)?)*\\\\}\"\n"
  },
  {
    "path": "runtime/syntax/puppet.yaml",
    "content": "filetype: puppet\n\ndetect:\n    filename: \"\\\\.pp$\"\n\nrules:\n    - default: \"^[[:space:]]([a-z][a-z0-9_]+)\"\n    - identifier.var: \"\\\\$[a-z:][a-z0-9_:]+\"\n    - type: \"\\\\b(augeas|computer|cron|exec|file|filebucket|group|host|interface|k5login|macauthorization|mailalias|maillist|mcx|mount|nagios_command|nagios_contact|nagios_contactgroup|nagios_host|nagios_hostdependency|nagios_hostescalation|nagios_hostextinfo|nagios_hostgroup|nagios_service|nagios_servicedependency|nagios_serviceescalation|nagios_serviceextinfo|nagios_servicegroup|nagios_timeperiod|notify|package|resources|router|schedule|scheduled_task|selboolean|selmodule|service|ssh_authorized_key|sshkey|stage|tidy|user|vlan|yumrepo|zfs|zone|zpool|anchor)\\\\b\"\n    - statement: \"\\\\b(class|define|if|else|undef|inherits)\\\\b\"\n    - symbol: \"(=|-|~|>)\"\n    - identifier.var: \"(\\\\$|@|@@)?\\\\b[A-Z]+[0-9A-Z_a-z]*\"\n    - symbol: \"([      ]|^):[0-9A-Z_]+\\\\b\"\n    - constant: \"/([^/]|(\\\\\\\\/))*/[iomx]*|%r\\\\{([^}]|(\\\\\\\\}))*\\\\}[iomx]*\"\n    - constant.string: \"`[^`]*`|%x\\\\{[^}]*\\\\}\"\n    - constant.string: \"\\\"([^\\\"]|(\\\\\\\\\\\"))*\\\"|%[QW]?\\\\{[^}]*\\\\}|%[QW]?\\\\([^)]*\\\\)|%[QW]?<[^>]*>|%[QW]?\\\\[[^]]*\\\\]|%[QW]?\\\\$[^$]*\\\\$|%[QW]?\\\\^[^^]*\\\\^|%[QW]?![^!]*!\"\n    - special: \"\\\\$\\\\{[^}]*\\\\}\"\n    - constant.string: \"'([^']|(\\\\\\\\'))*'|%[qw]\\\\{[^}]*\\\\}|%[qw]\\\\([^)]*\\\\)|%[qw]<[^>]*>|%[qw]\\\\[[^]]*\\\\]|%[qw]\\\\$[^$]*\\\\$|%[qw]\\\\^[^^]*\\\\^|%[qw]![^!]*!\"\n    - comment: \"#[^{].*$|#$\"\n    - comment.bright: \"##[^{].*$|##$\"\n    - todo: \"(XXX|TODO|FIXME|\\\\?\\\\?\\\\?)\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n"
  },
  {
    "path": "runtime/syntax/python2.yaml",
    "content": "filetype: python2\n\ndetect:\n    filename: \"\\\\.py2$\"\n    header: \"^#!.*/(env +)?python2$\"\n\nrules:\n\n    # built-in objects\n    - constant: \"\\\\b(None|self|True|False)\\\\b\"\n      # built-in attributes\n    - constant: \"\\\\b(__bases__|__builtin__|__class__|__debug__|__dict__|__doc__|__file__|__members__|__methods__|__name__|__self__)\\\\b\"\n      # built-in functions\n    - identifier: \"\\\\b(abs|apply|callable|chr|cmp|compile|delattr|dir|divmod|eval|exec|execfile|filter|format|getattr|globals|hasattr|hash|help|hex|id|input|intern|isinstance|issubclass|len|locals|max|min|next|oct|open|ord|pow|range|raw_input|reduce|reload|repr|round|setattr|unichr|vars|zip|__import__)\\\\b\"\n      # special method names\n    - identifier: \"\\\\b(__abs__|__add__|__and__|__call__|__cmp__|__coerce__|__complex__|__concat__|__contains__|__del__|__delattr__|__delitem__|__dict__|__delslice__|__div__|__divmod__|__float__|__getattr__|__getitem__|__getslice__|__hash__|__hex__|__init__|__int__|__inv__|__invert__|__len__|__long__|__lshift__|__mod__|__mul__|__neg__|__nonzero__|__oct__|__or__|__pos__|__pow__|__radd__|__rand__|__rcmp__|__rdiv__|__rdivmod__|__repeat__|__repr__|__rlshift__|__rmod__|__rmul__|__ror__|__rpow__|__rrshift__|__rshift__|__rsub__|__rxor__|__setattr__|__setitem__|__setslice__|__str__|__sub__|__xor__)\\\\b\"\n      # types\n    - type: \"\\\\b(basestring|bool|buffer|bytearray|bytes|classmethod|complex|dict|enumerate|file|float|frozenset|int|list|long|map|memoryview|object|property|reversed|set|slice|staticmethod|str|super|tuple|type|unicode|xrange)\\\\b\"\n      # definitions\n    - identifier: \"def [a-zA-Z_0-9]+\"\n      # keywords\n    - statement: \"\\\\b(and|as|assert|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|not|or|pass|print|raise|return|try|while|with|yield)\\\\b\"\n      # decorators\n    - preproc: \"^\\\\s*@[^(]*\"\n      # operators\n    - symbol.operator: \"([.:;,+*|=!\\\\%@]|<|>|/|-|&)\"\n      # parentheses\n    - symbol.brackets: \"([(){}]|\\\\[|\\\\])\"\n      # numbers\n    - constant.number: \"\\\\b[0-9]+\\\\b\"\n\n    - constant.string:\n        start: \"\\\"\\\"\\\"\"\n        end: \"\\\"\\\"\\\"\"\n        rules: []\n\n    - constant.string:\n        start: \"'''\"\n        end: \"'''\"\n        rules: []\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules: []\n\n"
  },
  {
    "path": "runtime/syntax/python3.yaml",
    "content": "filetype: python\n\ndetect:\n    filename: \"\\\\.py(3|w)?$\"\n    header: \"^#!.*/(env +)?python(3)?$\"\n\nrules:\n    # built-in objects\n    - constant: \"\\\\b(Ellipsis|None|self|cls|True|False)\\\\b\"\n      # built-in attributes\n    - constant: \"\\\\b(__bases__|__builtin__|__class__|__debug__|__dict__|__doc__|__file__|__members__|__methods__|__name__|__self__)\\\\b\"\n      # built-in functions\n    - identifier: \"\\\\b(abs|all|any|ascii|bin|bool|breakpoint|bytearray|bytes|callable|chr|classmethod|compile|complex|delattr|dir|divmod|eval|exec|format|getattr|globals|hasattr|hash|help|hex|id|input|isinstance|issubclass|iter|len|locals|max|min|next|nonlocal|oct|open|ord|pow|print|repr|round|setattr|sorted|sum|vars|__import__)\\\\b\"\n      # special method names\n    - identifier: \"\\\\b__(abs|add|and|call|cmp|coerce|complex|concat|contains|delattr|delitem|delslice|del|dict|divmod|div|float|getattr|getitem|getslice|hash|hex|iadd|iand|iconcat|ifloordiv|ilshift|imatmul|imod|imul|init|int|invert|inv|ior|ipow|irshift|isub|iter|itruediv|ixor|len|long|lshift|mod|mul|neg|next|nonzero|oct|or|pos|pow|radd|rand|rcmp|rdivmod|rdiv|repeat|repr|rlshift|rmod|rmul|ror|rpow|rrshift|rshift|rsub|rxor|setattr|setitem|setslice|str|sub|xor)__\\\\b\"\n      # types\n    - type: \"\\\\b(bool|bytearray|bytes|classmethod|complex|dict|enumerate|filter|float|frozenset|int|list|map|memoryview|object|property|range|reversed|set|slice|staticmethod|str|super|tuple|type|zip)\\\\b\"\n      # definitions\n    - identifier: \"def [a-zA-Z_0-9]+\"\n      # keywords\n    - statement: \"\\\\b(and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|raise|return|try|while|with|yield)\\\\b\"\n      # decorators\n    - preproc: \"^\\\\s*@[^(]*\"\n      # operators\n    - symbol.operator: \"([~^.:;,+*|=!\\\\%@]|<|>|/|-|&)\"\n      # parentheses\n    - symbol.brackets: \"([(){}]|\\\\[|\\\\])\"\n      # numbers\n    - constant.number: \"\\\\b[0-9](_?[0-9])*(\\\\.([0-9](_?[0-9])*)?)?(e[0-9](_?[0-9])*)?\\\\b\" # decimal\n    - constant.number: \"\\\\b0b(_?[01])+\\\\b\"     # bin\n    - constant.number: \"\\\\b0o(_?[0-7])+\\\\b\"    # oct\n    - constant.number: \"\\\\b0x(_?[0-9a-fA-F])+\\\\b\" # hex\n\n    - constant.string:\n        start: \"\\\"\\\"\\\"\"\n        end: \"\\\"\\\"\\\"\"\n        rules: []\n\n    - constant.string:\n        start: \"'''\"\n        end: \"'''\"\n        rules: []\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"(\\\"|$)\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"('|$)\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:  # AKA Code tags (PEP 350)\n            - todo: \"(TODO|FIXME|HACK|BUG|NOTE|FAQ|MNEMONIC|REQ|RFE|IDEA|PORT|\\\\?\\\\?\\\\?|!!!|GLOSS|SEE|TODOC|STAT|RVD|CRED):?\"\n"
  },
  {
    "path": "runtime/syntax/r.yaml",
    "content": "filetype: r\n\ndetect:\n    filename: \"\\\\.(r|R)$\"\n\nrules:\n\n    - statement: \"\\\\b(library|require|break|else|for|function|if|ifelse|in|next|names|switch|repeat|print|try|tryCatch|isTRUE|return|while)\\\\b\"\n    - constant: \"\\\\b(T|TRUE|F|FALSE|NULL|Inf|NaN|NA|NA_integer_|NA_real_|NA_complex_|NA_character_)\\\\b\"\n    - constant.number: \"(\\\\b[0-9]+\\\\b|\\\\b0x[0-9A-Fa-f]+\\\\b)\"\n    - symbol.operator: \"([.:;,+*|=!\\\\%]|<|>|/|-|&|\\\\^|\\\\$)\"\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n"
  },
  {
    "path": "runtime/syntax/raku.yaml",
    "content": "filetype: raku\n\ndetect:\n    filename: \"\\\\.(p(l|m|od)?6|raku(mod|doc|test)?|nqp)$\"\n\nrules:\n    - type: \"\\\\b(accept|alarm|atan2|bin(d|mode)|c(aller|h(dir|mod|op|own|root)|lose(dir)?|onnect|os|rypt)|d(bm(close|open)|efined|elete|ie|o|ump)|e(ach|of|val|x(ec|ists|it|p))|f(cntl|ileno|lock|ork)|get(c|login|peername|pgrp|ppid|priority|pwnam|(host|net|proto|serv)byname|pwuid|grgid|(host|net)byaddr|protobynumber|servbyport)|([gs]et|end)(pw|gr|host|net|proto|serv)ent|getsock(name|opt)|gmtime|goto|grep|hex|index|int|ioctl|join|keys|kill|last|length|link|listen|local(time)?|log|lstat|m|mkdir|msg(ctl|get|snd|rcv)|next|oct|open(dir)?|ord|pack|pipe|pop|printf?|push|q|qq|qx|rand|re(ad(dir|link)?|cv|do|name|quire|set|turn|verse|winddir)|rindex|rmdir|s|scalar|seek|seekdir|se(lect|mctl|mget|mop|nd|tpgrp|tpriority|tsockopt)|shift|shm(ctl|get|read|write)|shutdown|sin|sleep|socket(pair)?|sort|spli(ce|t)|sprintf|sqrt|srand|stat|study|substr|symlink|sys(call|read|tem|write)|tell(dir)?|time|tr|y|truncate|umask|un(def|link|pack|shift)|utime|values|vec|wait(pid)?|wantarray|warn|write)\\\\b\"\n    - statement: \"\\\\b(continue|else|elsif|do|for|foreach|if|unless|until|while|eq|ne|lt|gt|le|ge|cmp|x|my|sub|use|package|can|isa)\\\\b\"\n    - special: \"\\\\b(has|is|class|role|given|when|BUILD|multi|returns|method|submethod|slurp|say|sub)\\\\b\"\n\n    - identifier: \"[$@%&](\\\\.|!|\\\\^)?([[:alpha:]]|_)\"\n    - identifier: \"[$@%&](\\\\.|!|^)?([[:alpha:]]|_)([[:alnum:]]|-|_)*([[:alnum:]]|_)\"\n    - identifier: \"[$@%&](\\\\?|\\\\*)([A-Z])([A-Z]|-)*([A-Z])\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - preproc:\n        start: \"(^use| = new)\"\n        end: \";\"\n        rules: []\n\n    - identifier.macro:\n        start: \"<<EOSQL\"\n        end: \"EOSQL\"\n        rules: []\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules: []\n"
  },
  {
    "path": "runtime/syntax/reST.yaml",
    "content": "filetype: rst\n\ndetect:\n    filename: \"\\\\.rest$|\\\\.rst$\"\n\nrules:\n    - statement: \"\\\\*\\\\*[^*]+\\\\*\\\\*\"\n    - preproc: \"::\"\n    - constant.string: \"`[^`]+`_{1,2}\"\n    - constant.string: \"``[^`]+``\"\n    - identifier: \"^\\\\.\\\\. .*$\"\n    - identifier: \"^__ .*$\"\n    - type: \"^###+$\"\n    - type: \"^\\\\*\\\\*\\\\*+$\"\n    - special: \"^===+$\"\n    - special: \"^---+$\"\n    - special: \"^\\\\^\\\\^\\\\^+$\"\n    - special: \"^\\\"\\\"\\\"+$\"\n"
  },
  {
    "path": "runtime/syntax/renpy.yaml",
    "content": "filetype: renpy\n\ndetect:\n    filename: \"\\\\.rpy$\"\n\nrules:\n    # Script language keywords.\n    - statement: \"\\\\b(python|init|early|define|default|label|call|jump|image|layeredimage|screen|style|transform|menu|show|hide|scene|at|with|zorder|behind|pause|play|stop|fadeout|fadein|queue)\\\\b\"\n    # ATL keywords.\n    - type: \"\\\\b(repeat|block|choice|parallel|(x|y|)(pos|offset|anchor|align|center|tile|zoom)|time|linear|easein|alpha|subpixel)\\\\b\"\n    - identifier: \"\\\\bpersistent\\\\b\"\n    - special: \"\\\\$ \"\n    # Tab characters are not allowed in Renpy scripts.\n    - error: \"\\\\t\"\n    - include: python\n"
  },
  {
    "path": "runtime/syntax/rpmspec.yaml",
    "content": "filetype: rpmspec\n\ndetect:\n    filename: \"\\\\.spec$|\\\\.rpmspec$\"\n\nrules:\n    - preproc: \"\\\\b(Icon|ExclusiveOs|ExcludeOs):\"\n    - preproc: \"\\\\b(BuildArch|BuildArchitectures|ExclusiveArch|ExcludeArch):\"\n    - preproc: \"\\\\b(Conflicts|Obsoletes|Provides|Requires|Requires\\\\(.*\\\\)|Enhances|Suggests|BuildConflicts|BuildRequires|Recommends|PreReq|Supplements):\"\n    - preproc: \"\\\\b(Epoch|Serial|Nosource|Nopatch):\"\n    - preproc: \"\\\\b(AutoReq|AutoProv|AutoReqProv):\"\n    - preproc: \"\\\\b(Copyright|License|Summary|Summary\\\\(.*\\\\)|Distribution|Vendor|Packager|Group|Source[0-9]*|Patch[0-9]*|BuildRoot|Prefix):\"\n    - preproc: \"\\\\b(Name|Version|Release|Url|URL):\"\n    - preproc:\n        start: \"^(Source|Patch)\"\n        end: \":\"\n        rules: []\n\n    - preproc: \"(i386|i486|i586|i686|athlon|ia64|alpha|alphaev5|alphaev56|alphapca56|alphaev6|alphaev67|sparc|sparcv9|sparc64armv3l|armv4b|armv4lm|ips|mipsel|ppc|ppc|iseries|ppcpseries|ppc64|m68k|m68kmint|Sgi|rs6000|i370|s390x|s390|noarch)\"\n    - preproc: \"(ifarch|ifnarch|ifos|ifnos)\"\n    - constant.string: \"\\\"(\\\\\\\\.|[^\\\"])*\\\"|'(\\\\\\\\.|[^'])*'\"\n    - statement: \"%(if|else|endif|define|global|undefine)\"\n    - statement: \"%_?([A-Z_a-z_0-9_]*)\"\n    - statement:\n        start: \"%\\\\{\"\n        end: \"\\\\}\"\n        rules: []\n\n    - statement:\n        start: \"%\\\\{__\"\n        end: \"\\\\}\"\n        rules: []\n\n    - statement: \"\\\\$(RPM_BUILD_ROOT)\\\\>\"\n    - special: \"^%(build$|changelog|check$|clean$|description)\"\n    - special: \"^%(files|install$|package|prep$)\"\n    - special: \"^%(pre|preun|pretrans|post|postun|posttrans)\"\n    - special: \"^%(trigger|triggerin|triggerpostun|triggerun|verifyscript)\"\n    - comment: \"(^|[[:space:]])#([^{].*)?$\"\n    - constant: \"^\\\\*.*$\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n    - indent-char: \"\t+ +| +\t+\"\n    - todo: \"TODO:?\"\n"
  },
  {
    "path": "runtime/syntax/ruby.yaml",
    "content": "filetype: ruby\n\ndetect:\n    filename: '\\.(rb|rake|gemspec)$|^(.*[\\\\/])?(Gemfile|config\\.ru|Rakefile|Capfile|Vagrantfile|Guardfile|Appfile|Fastfile|Pluginfile|Podfile|\\.?[Bb]rewfile)$'\n    header: \"^#!.*/(env +)?ruby( |$)\"\n\nrules:\n    - comment.bright:\n        start: \"##\"\n        end: \"$\"\n        rules:\n            - todo: \"(XXX|TODO|FIXME|BUG|\\\\?\\\\?\\\\?)\"\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(XXX|TODO|FIXME|BUG|\\\\?\\\\?\\\\?)\"\n\n    - statement: \"\\\\b(BEGIN|END|alias|and|begin|break|case|class|def|defined\\\\?|do|else|elsif|end|ensure|for|if|in|module|next|nil|not|or|private|protected|public|redo|rescue|retry|return|self|super|then|undef|unless|until|when|while|yield)\\\\b\"\n    - constant: \"(\\\\$|@|@@)?\\\\b[A-Z]+[0-9A-Z_a-z]*\"\n    - constant.number: \"(?i)\\\\b0x[0-9a-fA-F][0-9a-f_]*\\\\b\"\n    - constant.number: \"(?i)\\\\b0b[01][01_]*\\\\b\"\n    - constant.number: \"(?i)\\\\b[0-9][0-9_]*(['.'][0-9_]+)?(e[\\\\-]?[0-9_]+)?\\\\b\"\n    # Predefined global variables\n    - constant:\n        start: \"[$]([!@&`'+~=/\\\\\\\\,;.<>*$?:\\\"_]|-[A-Za-z0-9_]|[0-9]+)\"\n        end: \"\\\\B|\\\\b\"\n        rules: []\n    # Ruby \"Symbols\"\n    - constant: \"(i?)([ \t]|^):[0-9A-Z_]+\\\\b\"\n    - constant: \"\\\\b(__FILE__|__LINE__)\\\\b\"\n    - constant: \"/([^/]|(\\\\\\\\/))*/[iomx]*|%r\\\\{([^}]|(\\\\\\\\}))*\\\\}[iomx]*\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules: []\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - symbol.brackets:\n                start: \"#\\\\{\"\n                end: \"\\\\}\"\n                rules:\n                    - default: \".*\"\n\n    - constant.string.exec:\n        start: \"`\"\n        end: \"`\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - symbol.brackets:\n                start: \"#\\\\{\"\n                end: \"\\\\}\"\n                rules:\n                    - default: \".*\"\n\n    - constant.string: \"%[QW]?\\\\{[^}]*\\\\}|%[QW]?\\\\([^)]*\\\\)|%[QW]?<[^>]*>|%[QW]?\\\\[[^]]*\\\\]|%[QW]?\\\\$[^$]*\\\\$|%[QW]?\\\\^[^^]*\\\\^|%[QW]?![^!]*!\"\n    - constant.string: \"%[qw]\\\\{[^}]*\\\\}|%[qw]\\\\([^)]*\\\\)|%[qw]<[^>]*>|%[qw]\\\\[[^]]*\\\\]|%[qw]\\\\$[^$]*\\\\$|%[qw]\\\\^[^^]*\\\\^|%[qw]![^!]*!\"\n    - constant.string.exec: \"%[x]\\\\{[^}]*\\\\}|%[x]\\\\([^)]*\\\\)|%[x]<[^>]*>|%[x]\\\\[[^]]*\\\\]|%[x]\\\\$[^$]*\\\\$|%[x]\\\\^[^^]*\\\\^|%[x]![^!]*!\"\n    - constant.bool: \"\\\\b(true|false|nil|TRUE|FALSE|NIL)\\\\b\"\n    - symbol.operator: \"[-+/*=<>!~%&|^]|\\\\b:\"\n    - symbol.brackets: \"([(){}]|\\\\[|\\\\])\"\n    - constant.macro:\n        start: \"<<-?'?EOT'?\"\n        end: \"^EOT\"\n        rules: []\n\n    - preproc.shebang: \"^#!.+?( |$)\"\n"
  },
  {
    "path": "runtime/syntax/rust.yaml",
    "content": "filetype: rust\n\ndetect:\n    filename: \"\\\\.rs$\"\n\nrules:\n    # function definition\n    - identifier: \"fn [a-z0-9_]+\"\n      # Reserved words\n    - statement: \"\\\\b(abstract|alignof|as|async|await|become|box|break|const|continue|crate|do|dyn|else|enum|extern|false|final|fn|for|gen|if|impl|in|let|loop|macro|match|mod|move|mut|offsetof|override|priv|pub|pure|ref|return|sizeof|static|self|struct|super|true|trait|type|typeof|try|union|unsafe|unsized|use|virtual|where|while|yield)\\\\b\"\n      # macros\n    - special: \"[a-z_]+!\"\n      # Constants\n    - constant: \"\\\\b[A-Z][A-Z_0-9]+\\\\b\"\n      # Numbers\n    - constant.number: \"\\\\b[0-9]+\\\\b\"\n      # Booleans\n    - constant: \"\\\\b(true|false)\\\\b\"\n      # Traits/Enums/Structs/Types/etc.\n    - type: \"\\\\b[A-Z]+[a-zA-Z_0-9]*[a-z]+[a-zA-Z_0-9]*\\\\b\"\n      # Builtin types that start with lowercase.\n    - type: \"\\\\b(bool|str|char|((i|u)(8|16|32|64|128|size))|f(16|32|64|128))\\\\b\"\n\n    - constant.string:\n        start: \"[bc]?\\\"\"\n        end: \"\\\"\"\n        skip: '\\\\.'\n        rules:\n            - constant.specialChar: '\\\\.'\n\n    - constant.string:\n        start: \"[bc]?r#\\\"\"\n        end: \"\\\"#\"\n        rules: []\n\n    - constant.string:\n        start: \"[bc]?r##\\\"\"\n        end: \"\\\"##\"\n        rules: []\n\n    - constant.string:\n        start: \"[bc]?r###\\\"\"\n        end: \"\\\"###\"\n        rules: []\n\n    - constant.string:\n        start: \"[bc]?r####+\\\"\"\n        end: \"\\\"####+\"\n        rules: []\n\n    # Character literals\n    # NOTE: This is an ugly hack to work around the fact that rust uses\n    # single quotes both for character literals and lifetimes.\n    # Match all character literals.\n    - constant.string: \"'(\\\\\\\\.|.)'\"\n    # Match the '\"' literal which would otherwise match\n    # as a double quoted string and destroy the highlighting.\n    - constant.string:\n        start: \"'\\\"\"\n        end: \"'\"\n        rules: []\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - special:\n        start: \"#!\\\\[\"\n        end: \"\\\\]\"\n        rules: []\n"
  },
  {
    "path": "runtime/syntax/sage.yaml",
    "content": "filetype: sage\n\ndetect:\n    filename: \"\\\\.sage$\"\n    header: \"^#!.*/(env +)?sage( |$)\"\n\nrules:\n\n    # built-in objects\n    - constant: \"\\\\b(None|self|True|False)\\\\b\"\n      # built-in attributes\n    - constant: \"\\\\b(__bases__|__builtin__|__class__|__debug__|__dict__|__doc__|__file__|__members__|__methods__|__name__|__self__)\\\\b\"\n      # built-in functions\n    - identifier: \"\\\\b(abs|apply|callable|chr|cmp|compile|delattr|dir|divmod|eval|exec|execfile|filter|format|getattr|globals|hasattr|hash|help|hex|id|input|intern|isinstance|issubclass|len|locals|max|min|next|oct|open|ord|pow|range|raw_input|reduce|reload|repr|round|setattr|unichr|vars|zip|__import__)\\\\b\"\n      # special method names\n    - identifier: \"\\\\b(__abs__|__add__|__and__|__call__|__cmp__|__coerce__|__complex__|__concat__|__contains__|__del__|__delattr__|__delitem__|__dict__|__delslice__|__div__|__divmod__|__float__|__getattr__|__getitem__|__getslice__|__hash__|__hex__|__init__|__int__|__inv__|__invert__|__len__|__long__|__lshift__|__mod__|__mul__|__neg__|__nonzero__|__oct__|__or__|__pos__|__pow__|__radd__|__rand__|__rcmp__|__rdiv__|__rdivmod__|__repeat__|__repr__|__rlshift__|__rmod__|__rmul__|__ror__|__rpow__|__rrshift__|__rshift__|__rsub__|__rxor__|__setattr__|__setitem__|__setslice__|__str__|__sub__|__xor__)\\\\b\"\n      # types\n    - type: \"\\\\b(basestring|bool|buffer|bytearray|bytes|classmethod|complex|dict|enumerate|file|float|frozenset|int|list|long|map|memoryview|object|property|reversed|set|slice|staticmethod|str|super|tuple|type|unicode|xrange)\\\\b\"\n      # definitions\n    - identifier: \"def [a-zA-Z_0-9]+\"\n      # keywords\n    - statement: \"\\\\b(and|as|assert|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|not|or|pass|print|raise|return|try|while|with|yield)\\\\b\"\n      # decorators\n    - brightgreen: \"@.*[(]\"\n      # operators\n    - statement: \"([.:;,+*|=!\\\\%@]|<|>|/|-|&)\"\n      # parentheses\n    - statement: \"([(){}]|\\\\[|\\\\])\"\n      # numbers\n    - constant.number: \"\\\\b[0-9]+\\\\b\"\n\n    - comment:\n        start: \"\\\"\\\"\\\"\"\n        end: \"\\\"\\\"\\\"\"\n        rules: []\n\n    - comment:\n        start: \"'''\"\n        end: \"'''\"\n        rules: []\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules: []\n\n"
  },
  {
    "path": "runtime/syntax/scad.yaml",
    "content": "filetype: OpenSCAD\n\n# OpenSCAD is a functional programming language used for representing\n# 2D/3D models for use in the program of the same name.\n#\n# The following documents were used as reference material:\n# https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language\n# https://openscad.org/cheatsheet/index.html\n\ndetect:\n    filename: \"\\\\.scad$\"\n\nrules:\n    - identifier: \"\\\\b(function|module) +[a-z0-9_]+\"\n\n    - statement: \"\\\\b(abs|acos|asin|assert|atan|atan2|ceil|child|children|chr|circle|color|concat|cos|cross|cube|cylinder|difference|dxf_cross|dxf_dim|each|echo|else|exp|floor|for|function|hull|if|import|import_dxf|intersection|intersection_for|is_bool|is_function|is_list|is_num|is_string|is_undef|len|let|linear_extrude|ln|log|lookup|max|min|minkowski|mirror|module|multmatrix|norm|offset|ord|parent_module|polygon|polyhedron|pow|projection|rands|render|resize|rotate|rotate_extrude|round|scale|search|sign|sin|sphere|sqrt|square|str|surface|tan|text|translate|union|version|version_num)\\\\b\"\n\n    - symbol: \"[,\\\\.;:?]\"\n    - symbol.operator: \"[-+*/%^<>!=]|[<=>!]=|&&|\\\\|\\\\|\"\n    - symbol.brackets: \"[{(<>)}]|\\\\[|\\\\]\"\n\n    # modifiers that change interpretation of the subtree after it\n    - special: \"[#%!*]\"\n\n    # special variables start with a dollar sign\n    - special: \"\\\\B\\\\$[a-z]+\\\\b\"\n\n    - preproc:\n        start: \"^ *(use|include) <\"\n        end: \">;?\"\n        rules: []\n\n    - constant.number: \"\\\\b[-+]?[0-9]*\\\\.?[0-9]+([eE][-+]?[0-9]+)?|PI|inf|nan\\\\b\"\n    - constant.bool: \"\\\\b(true|false)\\\\b\"\n    - constant: \"\\\\b(undef)\\\\b\"\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/scala.yaml",
    "content": "filetype: scala\n\ndetect:\n    filename: \"\\\\.sc(ala)?$|\\\\.sbt$\"\n\nrules:\n    - type: \"\\\\b(boolean|byte|char|double|float|int|long|new|short|this|transient|void)\\\\b\"\n    - statement: \"\\\\b(match|val|var|break|case|catch|continue|default|do|else|finally|for|if|return|switch|throw|try|while)\\\\b\"\n    - statement: \"\\\\b(def|object|case|trait|lazy|implicit|abstract|class|extends|with|final|implements|override|import|instanceof|interface|native|package|private|protected|public|static|strictfp|super|synchronized|throws|volatile|sealed)\\\\b\"\n    - constant.string:\n        start: \"\\\"\\\"\\\"\"\n        end: \"\\\"\\\"\\\"\"\n        rules: []\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - constant: \"\\\\b(true|false|null)\\\\b\"\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules: []\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules: []\n    - comment:\n        start: \"/\\\\*\\\\*\"\n        end: \"\\\\*/\"\n        rules: []\n\n"
  },
  {
    "path": "runtime/syntax/sed.yaml",
    "content": "filetype: sed\n\ndetect:\n    filename: \"\\\\.sed$\"\n    header: \"^#!.*bin/(env +)?sed( |$)\"\n\nrules:\n    - symbol.operator: \"[|^$.*+]\"\n    - constant.number: \"\\\\{[0-9]+,?[0-9]*\\\\}\"\n    - constant.specialChar: \"\\\\\\\\.\"\n    - comment: \"(^|[[:space:]])#([^{].*)?$\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n    - indent-char: \"\t+ +| +\t+\"\n"
  },
  {
    "path": "runtime/syntax/sh.yaml",
    "content": "filetype: shell\n\n# Detection based on filename is rather complicated as there are many\n# different file extensions and special filenames in use.\n# This expressions aims to capture them all while not matching\n# filenames that coincidentally contain the same substring.\n#\n# File extensions:\n# * .sh\n# * .bash\n# * .ash\n# * .ebuild (Gentoo ebuild format)\n#\n# Special filenames:\n# * .bashrc, .bash_aliases, .bash_functions .bash_profile\n# * profile, .profile (/etc/profile or ~/.profile)\n# * Pkgfile\n# * pkgmk.conf\n# * rc.conf\n# * PKGBUILD (Arch Linux build scripts)\n# * APKBUILD\n#\n# Fix command (fc) files:\n# * bash-fc. (followed by a random string)\ndetect:\n    filename: \"(\\\\.(sh|bash|ash|ebuild)$|(\\\\.bash(rc|_aliases|_functions|_profile)|\\\\.?profile|Pkgfile|pkgmk\\\\.conf|rc\\\\.conf|PKGBUILD|APKBUILD)$|bash-fc\\\\.)\"\n    header: \"^#!.*/(env +)?(ba)?(a)?(mk)?sh( |$)\"\n\nrules:\n    # Numbers\n    - constant.number: \"\\\\b[0-9]+\\\\b\"\n      # Conditionals and control flow\n    - statement: \"\\\\b(break|case|continue|do|done|elif|else|esac|exec|exit|fi|for|function|if|in|return|select|then|trap|until|wait|while)\\\\b\"\n    - special: \"[`$<>!=&~^\\\\{\\\\}\\\\(\\\\)\\\\;\\\\]\\\\[]+\"\n      # Shell commands\n    - type: \"\\\\b(cd|command|echo|eval|export|getopts|let|local|read|set|shift|time|umask|unset)\\\\b\"\n      # Common linux commands\n    - type: \"\\\\b((g|ig)?awk|bash|dash|find|getopt|\\\\w{0,4}grep|kill|killall|\\\\w{0,4}less|make|pkill|sed|sh|tar)\\\\b\"\n      # Coreutils commands\n    - type: \"\\\\b(base64|basename|cat|chcon|chgrp|chmod|chown|chroot|cksum|comm|cp|csplit|cut|date|dd|df|dir|dircolors|dirname|du|env|expand|expr|factor|false|fmt|fold|head|hostid|id|install|join|link|ln|logname|ls|md5sum|mkdir|mkfifo|mknod|mktemp|mv|nice|nl|nohup|nproc|numfmt|od|paste|pathchk|pinky|pr|printenv|printf|ptx|pwd|readlink|realpath|rm|rmdir|runcon|seq|(sha1|sha224|sha256|sha384|sha512)sum|shred|shuf|sleep|sort|split|stat|stdbuf|stty|sum|sync|tac|tail|tee|test|time|timeout|touch|tr|true|truncate|tsort|tty|uname|unexpand|uniq|unlink|users|vdir|wc|who|whoami|yes)\\\\b\"\n      # Conditional flags\n    - statement: \"(\\\\s|^)(--?[A-Za-z0-9][\\\\w-]*)\"\n\n    - identifier: \"\\\\$\\\\{[\\\\w:!%&=+#~@*^$?, .\\\\-\\\\/\\\\[\\\\]]+\\\\}\"\n    - identifier: \"\\\\$([0-9!#@*$?-]|[A-Za-z_]\\\\w*)\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules: []\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules: []\n\n    - constant.string:\n        start: \"<<[^\\\\s]+[-~.]*[A-Za-z0-9]+$\"\n        end: \"^[^\\\\s]+[A-Za-z0-9]+$\"\n        skip: \"\\\\\\\\.\"\n        rules: []\n\n    - comment:\n        start: \"(^|\\\\s)#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/sls.yaml",
    "content": "filetype: salt\n\ndetect:\n    filename: \"\\\\.sls$\"\n\nrules:\n    - identifier.var: \"^[^ -].*:$\"\n    - identifier.var: \".*:\"\n    - default: \"salt:\"\n    - constant.number: \"/*[0-9]/*\"\n    - constant.bool: \"\\\\b(True|False)\\\\b\"\n    - constant.string: \"\\\"(\\\\\\\\.|[^\\\"])*\\\"|'(\\\\\\\\.|[^'])*'\"\n    - special: \"\\\\b(grain|grains|compound|pcre|grain_pcre|list|pillar)\\\\b\"\n    - comment: \"^#.*\"\n    - statement: \"\\\\b(if|elif|else|or|not|and|endif|end)\\\\b\"\n"
  },
  {
    "path": "runtime/syntax/smalltalk.yaml",
    "content": "filetype: smalltalk\n\ndetect:\n    filename: \"\\\\.(st|sources|changes)$\"\n\nrules:\n    - statement: \"\\\\b(self|nil|true|false|ifTrue|ifFalse|whileTrue|whileFalse)\\\\b\"\n    - constant: \"(\\\\$|@|@@)?\\\\b[A-Z]+[0-9A-Z_a-z]*\"\n    - constant.number: \"(?i)\\\\b0x[0-9a-fA-F][0-9a-f_]*\\\\b\"\n    - constant.number: \"(?i)\\\\b0b[01][01_]*\\\\b\"\n    - constant.number: \"(?i)\\\\b[0-9][0-9_]*(['.'][0-9_]+)?(e[\\\\-]?[0-9_]+)?\\\\b\"\n    # Ruby \"Symbols\"\n    - constant: \"(i?)([ \t]|^):[0-9A-Z_]+\\\\b\"\n    - constant: \"\\\\b(__FILE__|__LINE__)\\\\b\"\n    - constant: \"/([^/]|(\\\\\\\\/))*/[iomx]*|%r\\\\{([^}]|(\\\\\\\\}))*\\\\}[iomx]*\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules: []\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - symbol.brackets:\n                start: \"#\\\\{\"\n                end: \"\\\\}\"\n                rules:\n                    - default: \".*\"\n\n    - constant.string.exec:\n        start: \"`\"\n        end: \"`\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - symbol.brackets:\n                start: \"#\\\\{\"\n                end: \"\\\\}\"\n                rules:\n                    - default: \".*\"\n\n    - constant.string: \"%[QW]?\\\\{[^}]*\\\\}|%[QW]?\\\\([^)]*\\\\)|%[QW]?<[^>]*>|%[QW]?\\\\[[^]]*\\\\]|%[QW]?\\\\$[^$]*\\\\$|%[QW]?\\\\^[^^]*\\\\^|%[QW]?![^!]*!\"\n    - constant.string: \"%[qw]\\\\{[^}]*\\\\}|%[qw]\\\\([^)]*\\\\)|%[qw]<[^>]*>|%[qw]\\\\[[^]]*\\\\]|%[qw]\\\\$[^$]*\\\\$|%[qw]\\\\^[^^]*\\\\^|%[qw]![^!]*!\"\n    - constant.string.exec: \"%[x]\\\\{[^}]*\\\\}|%[x]\\\\([^)]*\\\\)|%[x]<[^>]*>|%[x]\\\\[[^]]*\\\\]|%[x]\\\\$[^$]*\\\\$|%[x]\\\\^[^^]*\\\\^|%[x]![^!]*!\"\n    - symbol.operator: \"[-+/*=<>!~%&|^]|\\\\b:\"\n    - symbol.brackets: \"([(){}]|\\\\[|\\\\])\"\n    - constant.macro:\n        start: \"<<-?'?EOT'?\"\n        end: \"^EOT\"\n        rules: []\n\n    - preproc.shebang: \"^#!.+?( |$)\"\n"
  },
  {
    "path": "runtime/syntax/solidity.yaml",
    "content": "filetype: solidity\n\ndetect:\n    filename: \"\\\\.sol$\"\n\nrules:\n    - preproc: \"\\\\b(contract|library|pragma)\\\\b\"\n    - constant.number: \"\\\\b[-]?([0-9]+|0x[0-9a-fA-F]+)\\\\b\"\n    - identifier: \"[a-zA-Z][_a-zA-Z0-9]*[[:space:]]*\"\n    - statement: \"\\\\b(assembly|break|continue|do|for|function|if|else|new|return|returns|while)\\\\b\"\n    - special: \"\\\\b(\\\\.send|throw)\\\\b\" # make sure they are very visible\n    - type.keyword: \"\\\\b(anonymous|constant|indexed|payable|public|private|external|internal)\\\\b\"\n    - constant: \"\\\\b(block(\\\\.(blockhash|coinbase|difficulty|gaslimit|number|timestamp))?|msg(\\\\.(data|gas|sender|value))?|now|tx(\\\\.(gasprice|origin))?)\\\\b\"\n    - constant: \"\\\\b(keccak256|sha3|sha256|ripemd160|ecrecover|addmod|mulmod|this|super|selfdestruct|\\\\.balance)\\\\b\"\n    - constant: \"\\\\b(true|false)\\\\b\"\n    - constant: \"\\\\b(wei|szabo|finney|ether|seconds|minutes|hours|days|weeks|years)\\\\b\"\n    - type: \"\\\\b(address|bool|mapping|string|var|int(\\\\d*)|uint(\\\\d*)|byte(\\\\d*)|fixed(\\\\d*)|ufixed(\\\\d*))\\\\b\"\n    - error: \"\\\\b(abstract|after|case|catch|default|final|in|inline|interface|let|match|null|of|pure|relocatable|static|switch|try|type|typeof|view)\\\\b\"\n    - operator: \"[-+/*=<>!~%?:&|]\"\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules: []\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules: []\n    - todo: \"TODO:?\"\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n"
  },
  {
    "path": "runtime/syntax/sql.yaml",
    "content": "filetype: sql\n\ndetect:\n    filename: \"\\\\.sql$|sqliterc$\"\n\nrules:\n    - statement: \"(?i)\\\\b(ALL|ASC|AS|ALTER|AND|ADD|AUTO_INCREMENT)\\\\b\"\n    - statement: \"(?i)\\\\b(BETWEEN|BINARY|BOTH|BY|BOOLEAN)\\\\b\"\n    - statement: \"(?i)\\\\b(CHANGE|CHECK|COLUMNS|COLUMN|CROSS|CREATE)\\\\b\"\n    - statement: \"(?i)\\\\b(DATABASES|DATABASE|DATA|DELAYED|DESCRIBE|DESC|DISTINCT|DELETE|DROP|DEFAULT)\\\\b\"\n    - statement: \"(?i)\\\\b(ENCLOSED|ESCAPED|EXISTS|EXPLAIN)\\\\b\"\n    - statement: \"(?i)\\\\b(FIELDS|FIELD|FLUSH|FOR|FOREIGN|FUNCTION|FROM)\\\\b\"\n    - statement: \"(?i)\\\\b(GROUP|GRANT|HAVING)\\\\b\"\n    - statement: \"(?i)\\\\b(IGNORE|INDEX|INFILE|INSERT|INNER|INTO|IDENTIFIED|IN|IS|IF)\\\\b\"\n    - statement: \"(?i)\\\\b(JOIN|KEYS|KILL|KEY)\\\\b\"\n    - statement: \"(?i)\\\\b(LEADING|LIKE|LIMIT|LINES|LOAD|LOCAL|LOCK|LOW_PRIORITY|LEFT|LANGUAGE)\\\\b\"\n    - statement: \"(?i)\\\\b(MODIFY|NATURAL|NOT|NULL|NEXTVAL)\\\\b\"\n    - statement: \"(?i)\\\\b(OPTIMIZE|OPTION|OPTIONALLY|ORDER|OUTFILE|OR|OUTER|ON)\\\\b\"\n    - statement: \"(?i)\\\\b(PROCEDURE|PROCEDURAL|PRIMARY)\\\\b\"\n    - statement: \"(?i)\\\\b(READ|REFERENCES|REGEXP|RENAME|REPLACE|RETURN|REVOKE|RLIKE|RIGHT)\\\\b\"\n    - statement: \"(?i)\\\\b(SHOW|SONAME|STATUS|STRAIGHT_JOIN|SELECT|SETVAL|SET)\\\\b\"\n    - statement: \"(?i)\\\\b(TABLES|TERMINATED|TO|TRAILING|TRUNCATE|TABLE|TEMPORARY|TRIGGER|TRUSTED)\\\\b\"\n    - statement: \"(?i)\\\\b(UNIQUE|UNLOCK|USE|USING|UPDATE|VALUES|VARIABLES|VIEW)\\\\b\"\n    - statement: \"(?i)\\\\b(WITH|WRITE|WHERE|ZEROFILL|TYPE|XOR)\\\\b\"\n    - type: \"(?i)\\\\b(VARCHAR|TINYINT|TEXT|DATE|SMALLINT|MEDIUMINT|INT|INTEGER|BIGINT|FLOAT|DOUBLE|DECIMAL|DATETIME|TIMESTAMP|TIME|YEAR|UNSIGNED|CHAR|TINYBLOB|TINYTEXT|BLOB|MEDIUMBLOB|MEDIUMTEXT|LONGBLOB|LONGTEXT|ENUM|BOOL|BINARY|VARBINARY)\\\\b\"\n    - preproc: \"(?i)\\\\.\\\\b(databases|dump|echo|exit|explain|header(s)?|help)\\\\b\"\n    - preproc: \"(?i)\\\\.\\\\b(import|indices|mode|nullvalue|output|prompt|quit|read)\\\\b\"\n    - preproc: \"(?i)\\\\.\\\\b(schema|separator|show|tables|timeout|width)\\\\b\"\n    - constant.bool: \"\\\\b(ON|OFF)\\\\b\"\n    - constant.number: \"\\\\b([0-9]+)\\\\b\"\n    - constant.string: \"\\\"(\\\\\\\\.|[^\\\"])*\\\"|'(\\\\\\\\.|[^'])*'\"\n    - constant.string: \"`(\\\\\\\\.|[^\\\\\\\\`])*`\"\n    - comment: \"\\\\-\\\\-.*$\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n    - indent-char: \"\t+ +| +\t+\"\n"
  },
  {
    "path": "runtime/syntax/stata.yaml",
    "content": "filetype: stata\n\ndetect:\n    filename: \"\\\\.a?do$\"\n\nrules:\n    - constant.string:\n        start: \"`\\\"\"\n        end: \"\\\"'\"\n        rules:\n            - identifier.macro:\n                start: \"`\"\n                end: \"'\"\n                rules: []\n            - identifier.macro: \"\\\\$\\\\w+\"\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        rules:\n            - identifier.macro:\n                start: \"`\"\n                end: \"'\"\n                rules: []\n            - identifier.macro: \"\\\\$\\\\w+\"\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    # Built-in functions\n    - identifier:\n        \"\\\\b(_caller|abbrev|abs|acos|acosh|asin|asinh|atan|atan2|atanh|autocode|betaden|binomial|binomialp|binomialtail|binormal|bofd|byteorder|c|cauchy|cauchyden|cauchytail|Cdhms|ceil|char|chi2|chi2den|chi2tail|Chms|cholesky|chop|clip|Clock|cloglog|Cmdyhms|Cofc|Cofd|coleqnumb|collatorlocale|collatorversion|colnfreeparms|colnumb|colsof|comb|cond|corr|cos|cosh|date|day|det|dgammapda|dgammapdada|dgammapdadx|dgammapdx|dgammapdxdx|dhms|diag|diag0cnt|digamma|dofb|dofC|dofh|dofm|dofq|dofw|dofy|dow|doy|dunnettprob|e|el|epsdouble|epsfloat|exp|exponential|exponentialden|exponentialtail|F|Fden|fileexists|fileread|filereaderror|filewrite|float|floor|fmtwidth|Ftail|gammaden|gammap|gammaptail|hadamard|halfyear|halfyearly|has_eprop|hh|hhC|hms|hofd|hours|hypergeometric|hypergeometricp|I|ibeta|ibetatail|igaussian|igaussianden|igaussiantail|indexnot|inlist|inrange|int|inv|invbinomial|invbinomialtail|invcauchy|invcauchytail|invchi2|invchi2tail|invcloglog|invdunnettprob|invexponential|invexponentialtail|invF|invFtail|invgammap|invgammaptail|invibeta|invibetatail|invigaussian|invigaussiantail|invlaplace|invlaplacetail|invlogistic|invlogistictail|invlogit|invnbinomial|invnbinomialtail|invnchi2|invnchi2tail|invnF|invnFtail|invnibeta|invnormal|invnt|invnttail|invpoisson|invpoissontail|invsym|invt|invttail|invtukeyprob|invweibull|invweibullph|invweibullphtail|invweibulltail|irecode|issymmetric|J|laplace|laplaceden|laplacetail|ln|lncauchyden|lnfactorial|lngamma|lnigammaden|lnigaussianden|lniwishartden|lnlaplaceden|lnmvnormalden|lnnormal|lnnormalden|lnwishartden|log|log10|logistic|logisticden|logistictail|logit|matmissing|matrix|matuniform|max|maxbyte|maxdouble|maxfloat|maxint|maxlong|mdy|mdyhms|missing|min|minbyte|mindouble|minfloat|minint|minlong|minutes|mm|mmC|mod|mofd|month|monthly|mreldif|msofhours|msofminutes|msofseconds|nbetaden|nbinomial|nbinomialp|nbinomialtail|nchi2|nchi2den|nchi2tail|nF|nFden|nFtail|nibeta|normal|normalden|npnchi2|npnF|npnt|nt|ntden|nttail|nullmat|plural|poisson|poissonp|poissontail|qofd|quarter|quarterly|r|rbeta|rbinomial|rcauchy|rchi2|real|recode|regexm|regexr|regexs|reldif|replay|return|rexponential|rgamma|rhypergeometric|rigaussian|rlaplace|rlogistic|rnbinomial|rnormal|round|roweqnumb|rownfreeparms|rownumb|rowsof|rpoisson|rt|runiform|runiformint|rweibull|rweibullph|s|scalar|seconds|sign|sin|sinh|smallestdouble|soundex|sqrt|ss|ssC|strcat|strdup|strofreal|string|strtrim|stritrim|strltrim|strrtrim|strlen|strupper|strlower|strproper|strmatch|strpos|strrpos|strreverse|strtoname|subinstr|subinword|substr|sum|sweep|t|tan|tanh|tC|td|tden|th|tin|tm|tobytes|tq|trace|trigamma|ttail|tukeyprob|tw|twithin|uchar|udstrlen|udsubstr|uisdigit|uisletter|ustrcompare|ustrsortkey|ustrcompareex|ustrsortkeyex|ustrfix|ustrto|ustrfrom|ustrlen|ustrinvalidcnt|usubstr|ustrleft|ustrright|ustrupper|ustrlower|ustrtitle|ustrtrim|ustrltrim|ustrrtrim|ustrnormalize|ustrpos|ustrrpos|ustrregexm|ustrregexrf|ustrregexra|ustrregexs|ustrreverse|ustrunescape|ustrtohex|ustrtoname|ustrword|ustrwordcount|usubinstr|vec|vecdiag|week|weekly|weibull|weibullden|weibullph|weibullphden|weibullphtail|weibulltail|wofd|word|wordbreaklocale|year|yearly|yh|ym|yofd|yq|yw)\\\\b\"\n    # Built-in commands\n    - statement:\n        \"\\\\b(if|else|else\\\\s+if|in|foreach|for|forv|forva|forval|forvalu|forvalue|forvalues|by|bys|bysort|quietly|qui|about|ac|ac_7|acprplot|acprplot_7|adjust|ado|adopath|adoupdate|alpha|ameans|an|ano|anov|anova|anova_estat|anova_terms|anovadef|aorder|ap|app|appe|appen|append|arch|arch_dr|arch_estat|arch_p|archlm|areg|areg_p|args|arima|arima_dr|arima_estat|arima_p|as|asmprobit|asmprobit_estat|asmprobit_lf|asmprobit_mfx__dlg|asmprobit_p|ass|asse|asser|assert|avplot|avplot_7|avplots|avplots_7|bcskew0|bgodfrey|binreg|bip0_lf|biplot|bipp_lf|bipr_lf|bipr_p|biprobit|bitest|bitesti|bitowt|blogit|bmemsize|boot|bootsamp|bootstrap|bootstrap_8|boxco_l|boxco_p|boxcox|boxcox_6|boxcox_p|bprobit|br|break|brier|bro|brow|brows|browse|brr|brrstat|bs|bs_7|bsampl_w|bsample|bsample_7|bsqreg|bstat|bstat_7|bstat_8|bstrap|bstrap_7|ca|ca_estat|ca_p|cabiplot|camat|canon|canon_8|canon_8_p|canon_estat|canon_p|cap|caprojection|capt|captu|captur|capture|cat|cc|cchart|cchart_7|cci|cd|censobs_table|centile|cf|char|chdir|checkdlgfiles|checkestimationsample|checkhlpfiles|checksum|chelp|ci|cii|cl|class|classutil|clear|cli|clis|clist|clo|clog|clog_lf|clog_p|clogi|clogi_sw|clogit|clogit_lf|clogit_p|clogitp|clogl_sw|cloglog|clonevar|clslistarray|cluster|cluster_measures|cluster_stop|cluster_tree|cluster_tree_8|clustermat|cmdlog|cnr|cnre|cnreg|cnreg_p|cnreg_sw|cnsreg|codebook|collaps4|collapse|colormult_nb|colormult_nw|compare|compress|conf|confi|confir|confirm|conren|cons|const|constr|constra|constrai|constrain|constraint|continue|contract|copy|copyright|copysource|cor|corc|corr|corr2data|corr_anti|corr_kmo|corr_smc|corre|correl|correla|correlat|correlate|corrgram|cou|coun|count|cox|cox_p|cox_sw|coxbase|coxhaz|coxvar|cprplot|cprplot_7|crc|cret|cretu|cretur|creturn|cross|cs|cscript|cscript_log|csi|ct|ct_is|ctset|ctst_5|ctst_st|cttost|cumsp|cumsp_7|cumul|cusum|cusum_7|cutil|d|datasig|datasign|datasigna|datasignat|datasignatu|datasignatur|datasignature|datetof|db|dbeta|de|dec|deco|decod|decode|deff|des|desc|descr|descri|describ|describe|destring|dfbeta|dfgls|dfuller|di|di_g|dir|dirstats|dis|discard|disp|disp_res|disp_s|displ|displa|display|distinct|do|doe|doed|doedi|doedit|dotplot|dotplot_7|dprobit|drawnorm|drop|ds|ds_util|dstdize|duplicates|durbina|dwstat|dydx|e|ed|edi|edit|egen|eivreg|emdef|end|en|enc|enco|encod|encode|eq|erase|ereg|ereg_lf|ereg_p|ereg_sw|ereghet|ereghet_glf|ereghet_glf_sh|ereghet_gp|ereghet_ilf|ereghet_ilf_sh|ereghet_ip|eret|eretu|eretur|ereturn|err|erro|error|est|est_cfexist|est_cfname|est_clickable|est_expand|est_hold|est_table|est_unhold|est_unholdok|estat|estat_default|estat_summ|estat_vce_only|esti|estimates|etodow|etof|etomdy|ex|exi|exit|expand|expandcl|fac|fact|facto|factor|factor_estat|factor_p|factor_pca_rotated|factor_rotate|factormat|fcast|fcast_compute|fcast_graph|fdades|fdadesc|fdadescr|fdadescri|fdadescrib|fdadescribe|fdasav|fdasave|fdause|fh_st|open|read|close|file|filefilter|fillin|find_hlp_file|findfile|findit|findit_7|fit|fl|fli|flis|flist|for5_0|form|forma|format|fpredict|frac_154|frac_adj|frac_chk|frac_cox|frac_ddp|frac_dis|frac_dv|frac_in|frac_mun|frac_pp|frac_pq|frac_pv|frac_wgt|frac_xo|fracgen|fracplot|fracplot_7|fracpoly|fracpred|fron_ex|fron_hn|fron_p|fron_tn|fron_tn2|frontier|ftodate|ftoe|ftomdy|ftowdate|g|gamhet_glf|gamhet_gp|gamhet_ilf|gamhet_ip|gamma|gamma_d2|gamma_p|gamma_sw|gammahet|gdi_hexagon|gdi_spokes|ge|gen|gene|gener|genera|generat|generate|genrank|genstd|genvmean|gettoken|gl|gladder|gladder_7|glim_l01|glim_l02|glim_l03|glim_l04|glim_l05|glim_l06|glim_l07|glim_l08|glim_l09|glim_l10|glim_l11|glim_l12|glim_lf|glim_mu|glim_nw1|glim_nw2|glim_nw3|glim_p|glim_v1|glim_v2|glim_v3|glim_v4|glim_v5|glim_v6|glim_v7|glm|glm_6|glm_p|glm_sw|glmpred|glo|glob|globa|global|glogit|glogit_8|glogit_p|gmeans|gnbre_lf|gnbreg|gnbreg_5|gnbreg_p|gomp_lf|gompe_sw|gomper_p|gompertz|gompertzhet|gomphet_glf|gomphet_glf_sh|gomphet_gp|gomphet_ilf|gomphet_ilf_sh|gomphet_ip|gphdot|gphpen|gphprint|gprefs|gprobi_p|gprobit|gprobit_8|gr|gr7|gr_copy|gr_current|gr_db|gr_describe|gr_dir|gr_draw|gr_draw_replay|gr_drop|gr_edit|gr_editviewopts|gr_example|gr_example2|gr_export|gr_print|gr_qscheme|gr_query|gr_read|gr_rename|gr_replay|gr_save|gr_set|gr_setscheme|gr_table|gr_undo|gr_use|graph|graph7|grebar|greigen|greigen_7|greigen_8|grmeanby|grmeanby_7|gs_fileinfo|gs_filetype|gs_graphinfo|gs_stat|gsort|gwood|h|hadimvo|hareg|hausman|haver|he|heck_d2|heckma_p|heckman|heckp_lf|heckpr_p|heckprob|hel|help|hereg|hetpr_lf|hetpr_p|hetprob|hettest|hexdump|hilite|hist|hist_7|histogram|hlogit|hlu|hmeans|hotel|hotelling|hprobit|hreg|hsearch|icd9|icd9_ff|icd9p|iis|impute|imtest|inbase|include|inf|infi|infil|infile|infix|inp|inpu|input|ins|insheet|insp|inspe|inspec|inspect|integ|inten|intreg|intreg_7|intreg_p|intrg2_ll|intrg_ll|intrg_ll2|ipolate|iqreg|ir|irf|irf_create|irfm|iri|is_svy|is_svysum|isid|istdize|ivprob_1_lf|ivprob_lf|ivprobit|ivprobit_p|ivreg|ivreg_footnote|ivtob_1_lf|ivtob_lf|ivtobit|ivtobit_p|jackknife|jacknife|jknife|jknife_6|jknife_8|jkstat|joinby|kalarma1|kap|kap_3|kapmeier|kappa|kapwgt|kdensity|kdensity_7|keep|ksm|ksmirnov|ktau|kwallis|l|la|lab|labe|label|labelbook|ladder|levels|levelsof|leverage|lfit|lfit_p|li|lincom|line|linktest|lis|list|lloghet_glf|lloghet_glf_sh|lloghet_gp|lloghet_ilf|lloghet_ilf_sh|lloghet_ip|llogi_sw|llogis_p|llogist|llogistic|llogistichet|lnorm_lf|lnorm_sw|lnorma_p|lnormal|lnormalhet|lnormhet_glf|lnormhet_glf_sh|lnormhet_gp|lnormhet_ilf|lnormhet_ilf_sh|lnormhet_ip|lnskew0|loadingplot|loc|loca|local|log|logi|logis_lf|logistic|logistic_p|logit|logit_estat|logit_p|loglogs|logrank|loneway|lookfor|lookup|lowess|lowess_7|lpredict|lrecomp|lroc|lroc_7|lrtest|ls|lsens|lsens_7|lsens_x|lstat|ltable|ltable_7|ltriang|lv|lvr2plot|lvr2plot_7|m|ma|mac|macr|macro|makecns|man|manova|manova_estat|manova_p|manovatest|mantel|mark|markin|markout|marksample|mat|mat_capp|mat_order|mat_put_rr|mat_rapp|mata|mata_clear|mata_describe|mata_drop|mata_matdescribe|mata_matsave|mata_matuse|mata_memory|mata_mlib|mata_mosave|mata_rename|mata_which|matalabel|matcproc|matlist|matname|matr|matri|matrix|matrix_input__dlg|matstrik|mcc|mcci|md0_|md1_|md1debug_|md2_|md2debug_|mds|mds_estat|mds_p|mdsconfig|mdslong|mdsmat|mdsshepard|mdytoe|mdytof|me_derd|mean|means|median|memory|memsize|meqparse|mer|merg|merge|mfp|mfx|mhelp|mhodds|minbound|mixed_ll|mixed_ll_reparm|mkassert|mkdir|mkmat|mkspline|ml|ml_5|ml_adjs|ml_bhhhs|ml_c_d|ml_check|ml_clear|ml_cnt|ml_debug|ml_defd|ml_e0|ml_e0_bfgs|ml_e0_cycle|ml_e0_dfp|ml_e0i|ml_e1|ml_e1_bfgs|ml_e1_bhhh|ml_e1_cycle|ml_e1_dfp|ml_e2|ml_e2_cycle|ml_ebfg0|ml_ebfr0|ml_ebfr1|ml_ebh0q|ml_ebhh0|ml_ebhr0|ml_ebr0i|ml_ecr0i|ml_edfp0|ml_edfr0|ml_edfr1|ml_edr0i|ml_eds|ml_eer0i|ml_egr0i|ml_elf|ml_elf_bfgs|ml_elf_bhhh|ml_elf_cycle|ml_elf_dfp|ml_elfi|ml_elfs|ml_enr0i|ml_enrr0|ml_erdu0|ml_erdu0_bfgs|ml_erdu0_bhhh|ml_erdu0_bhhhq|ml_erdu0_cycle|ml_erdu0_dfp|ml_erdu0_nrbfgs|ml_exde|ml_footnote|ml_geqnr|ml_grad0|ml_graph|ml_hbhhh|ml_hd0|ml_hold|ml_init|ml_inv|ml_log|ml_max|ml_mlout|ml_mlout_8|ml_model|ml_nb0|ml_opt|ml_p|ml_plot|ml_query|ml_rdgrd|ml_repor|ml_s_e|ml_score|ml_searc|ml_technique|ml_unhold|mleval|mlf_|mlmatbysum|mlmatsum|mlog|mlogi|mlogit|mlogit_footnote|mlogit_p|mlopts|mlsum|mlvecsum|mnl0_|mor|more|mov|move|mprobit|mprobit_lf|mprobit_p|mrdu0_|mrdu1_|mvdecode|mvencode|mvreg|mvreg_estat|n|nbreg|nbreg_al|nbreg_lf|nbreg_p|nbreg_sw|nestreg|net|newey|newey_7|newey_p|news|nl|nl_7|nl_9|nl_9_p|nl_p|nl_p_7|nlcom|nlcom_p|nlexp2|nlexp2_7|nlexp2a|nlexp2a_7|nlexp3|nlexp3_7|nlgom3|nlgom3_7|nlgom4|nlgom4_7|nlinit|nllog3|nllog3_7|nllog4|nllog4_7|nlog_rd|nlogit|nlogit_p|nlogitgen|nlogittree|nlpred|no|nobreak|noi|nois|noisi|noisil|noisily|note|notes|notes_dlg|nptrend|numlabel|numlist|odbc|old_ver|olo|olog|ologi|ologi_sw|ologit|ologit_p|ologitp|on|one|onew|onewa|oneway|op_colnm|op_comp|op_diff|op_inv|op_str|opr|opro|oprob|oprob_sw|oprobi|oprobi_p|oprobit|oprobitp|opts_exclusive|order|orthog|orthpoly|ou|out|outf|outfi|outfil|outfile|outs|outsh|outshe|outshee|outsheet|ovtest|pac|pac_7|palette|parse|parse_dissim|pause|pca|pca_8|pca_display|pca_estat|pca_p|pca_rotate|pcamat|pchart|pchart_7|pchi|pchi_7|pcorr|pctile|pentium|pergram|pergram_7|permute|permute_8|personal|peto_st|pkcollapse|pkcross|pkequiv|pkexamine|pkexamine_7|pkshape|pksumm|pksumm_7|pl|plo|plot|plugin|pnorm|pnorm_7|poisgof|poiss_lf|poiss_sw|poisso_p|poisson|poisson_estat|post|postclose|postfile|postutil|pperron|pr|prais|prais_e|prais_e2|prais_p|predict|predictnl|preserve|print|pro|prob|probi|probit|probit_estat|probit_p|proc_time|procoverlay|procrustes|procrustes_estat|procrustes_p|profiler|prog|progr|progra|program|prop|proportion|prtest|prtesti|pwcorr|pwd|q|s|qby|qbys|qchi|qchi_7|qladder|qladder_7|qnorm|qnorm_7|qqplot|qqplot_7|qreg|qreg_c|qreg_p|qreg_sw|qu|quadchk|quantile|quantile_7|que|quer|query|range|ranksum|ratio|rchart|rchart_7|rcof|recast|reclink|recode|reg|reg3|reg3_p|regdw|regr|regre|regre_p2|regres|regres_p|regress|regress_estat|regriv_p|remap|ren|rena|renam|rename|renpfix|repeat|replace|report|reshape|restore|ret|retu|retur|return|rm|rmdir|robvar|roccomp|roccomp_7|roccomp_8|rocf_lf|rocfit|rocfit_8|rocgold|rocplot|rocplot_7|roctab|roctab_7|rolling|rologit|rologit_p|rot|rota|rotat|rotate|rotatemat|rreg|rreg_p|ru|run|runtest|rvfplot|rvfplot_7|rvpplot|rvpplot_7|sa|safesum|sample|sampsi|sav|save|savedresults|saveold|sc|sca|scal|scala|scalar|scatter|scm_mine|sco|scob_lf|scob_p|scobi_sw|scobit|scor|score|scoreplot|scoreplot_help|scree|screeplot|screeplot_help|sdtest|sdtesti|se|search|separate|seperate|serrbar|serrbar_7|serset|set|set_defaults|sfrancia|sh|she|shel|shell|shewhart|shewhart_7|signestimationsample|signrank|signtest|simul|simul_7|simulate|simulate_8|sktest|sleep|slogit|slogit_d2|slogit_p|smooth|snapspan|so|sor|sort|spearman|spikeplot|spikeplot_7|spikeplt|spline_x|split|sqreg|sqreg_p|sret|sretu|sretur|sreturn|ssc|st|st_ct|st_hc|st_hcd|st_hcd_sh|st_is|st_issys|st_note|st_promo|st_set|st_show|st_smpl|st_subid|stack|statsby|statsby_8|stbase|stci|stci_7|stcox|stcox_estat|stcox_fr|stcox_fr_ll|stcox_p|stcox_sw|stcoxkm|stcoxkm_7|stcstat|stcurv|stcurve|stcurve_7|stdes|stem|stepwise|stereg|stfill|stgen|stir|stjoin|stmc|stmh|stphplot|stphplot_7|stphtest|stphtest_7|stptime|strate|strate_7|streg|streg_sw|streset|sts|sts_7|stset|stsplit|stsum|sttocc|sttoct|stvary|stweib|su|suest|suest_8|sum|summ|summa|summar|summari|summariz|summarize|sunflower|sureg|survcurv|survsum|svar|svar_p|svmat|svy|svy_disp|svy_dreg|svy_est|svy_est_7|svy_estat|svy_get|svy_gnbreg_p|svy_head|svy_header|svy_heckman_p|svy_heckprob_p|svy_intreg_p|svy_ivreg_p|svy_logistic_p|svy_logit_p|svy_mlogit_p|svy_nbreg_p|svy_ologit_p|svy_oprobit_p|svy_poisson_p|svy_probit_p|svy_regress_p|svy_sub|svy_sub_7|svy_x|svy_x_7|svy_x_p|svydes|svydes_8|svygen|svygnbreg|svyheckman|svyheckprob|svyintreg|svyintreg_7|svyintrg|svyivreg|svylc|svylog_p|svylogit|svymarkout|svymarkout_8|svymean|svymlog|svymlogit|svynbreg|svyolog|svyologit|svyoprob|svyoprobit|svyopts|svypois|svypois_7|svypoisson|svyprobit|svyprobt|svyprop|svyprop_7|svyratio|svyreg|svyreg_p|svyregress|svyset|svyset_7|svyset_8|svytab|svytab_7|svytest|svytotal|sw|sw_8|swcnreg|swcox|swereg|swilk|swlogis|swlogit|swologit|swoprbt|swpois|swprobit|swqreg|swtobit|swweib|symmetry|symmi|symplot|symplot_7|syntax|sysdescribe|sysdir|sysuse|szroeter|ta|tab|tab1|tab2|tab_or|tabd|tabdi|tabdis|tabdisp|tabi|table|tabodds|tabodds_7|tabstat|tabu|tabul|tabula|tabulat|tabulate|te|tempfile|tempname|tempvar|tes|test|testnl|testparm|teststd|tetrachoric|time_it|timer|tis|tob|tobi|tobit|tobit_p|tobit_sw|token|tokeni|tokeniz|tokenize|tostring|total|translate|translator|transmap|treat_ll|treatr_p|treatreg|trim|trnb_cons|trnb_mean|trpoiss_d2|trunc_ll|truncr_p|truncreg|tsappend|tset|tsfill|tsline|tsline_ex|tsreport|tsrevar|tsrline|tsset|tssmooth|tsunab|ttest|ttesti|tut_chk|tut_wait|tutorial|tw|tware_st|two|twoway|twoway__fpfit_serset|twoway__function_gen|twoway__histogram_gen|twoway__ipoint_serset|twoway__ipoints_serset|twoway__kdensity_gen|twoway__lfit_serset|twoway__normgen_gen|twoway__pci_serset|twoway__qfit_serset|twoway__scatteri_serset|twoway__sunflower_gen|twoway_ksm_serset|ty|typ|type|typeof|u|unab|unabbrev|unabcmd|update|us|use|uselabel|var|var_mkcompanion|var_p|varbasic|varfcast|vargranger|varirf|varirf_add|varirf_cgraph|varirf_create|varirf_ctable|varirf_describe|varirf_dir|varirf_drop|varirf_erase|varirf_graph|varirf_ograph|varirf_rename|varirf_set|varirf_table|varlist|varlmar|varnorm|varsoc|varstable|varstable_w|varstable_w2|varwle|vce|vec|vec_fevd|vec_mkphi|vec_p|vec_p_w|vecirf_create|veclmar|veclmar_w|vecnorm|vecnorm_w|vecrank|vecstable|verinst|vers|versi|versio|version|view|viewsource|vif|vwls|wdatetof|webdescribe|webseek|webuse|weib1_lf|weib2_lf|weib_lf|weib_lf0|weibhet_glf|weibhet_glf_sh|weibhet_glfa|weibhet_glfa_sh|weibhet_gp|weibhet_ilf|weibhet_ilf_sh|weibhet_ilfa|weibhet_ilfa_sh|weibhet_ip|weibu_sw|weibul_p|weibull|weibull_c|weibull_s|weibullhet|wh|whelp|whi|which|whil|while|wilc_st|wilcoxon|win|wind|windo|window|winexec|wntestb|wntestb_7|wntestq|xchart|xchart_7|xcorr|xcorr_7|xi|xi_6|xmlsav|xmlsave|xmluse|xpose|xsh|xshe|xshel|xshell|xt_iis|xt_tis|xtab_p|xtabond|xtbin_p|xtclog|xtcloglog|xtcloglog_8|xtcloglog_d2|xtcloglog_pa_p|xtcloglog_re_p|xtcnt_p|xtcorr|xtdata|xtdes|xtfront_p|xtfrontier|xtgee|xtgee_elink|xtgee_estat|xtgee_makeivar|xtgee_p|xtgee_plink|xtgls|xtgls_p|xthaus|xthausman|xtht_p|xthtaylor|xtile|xtint_p|xtintreg|xtintreg_8|xtintreg_d2|xtintreg_p|xtivp_1|xtivp_2|xtivreg|xtline|xtline_ex|xtlogit|xtlogit_8|xtlogit_d2|xtlogit_fe_p|xtlogit_pa_p|xtlogit_re_p|xtmixed|xtmixed_estat|xtmixed_p|xtnb_fe|xtnb_lf|xtnbreg|xtnbreg_pa_p|xtnbreg_refe_p|xtpcse|xtpcse_p|xtpois|xtpoisson|xtpoisson_d2|xtpoisson_pa_p|xtpoisson_refe_p|xtpred|xtprobit|xtprobit_8|xtprobit_d2|xtprobit_re_p|xtps_fe|xtps_lf|xtps_ren|xtps_ren_8|xtrar_p|xtrc|xtrc_p|xtrchh|xtrefe_p|xtreg|xtreg_be|xtreg_fe|xtreg_ml|xtreg_pa_p|xtreg_re|xtregar|xtrere_p|xtset|xtsf_ll|xtsf_llti|xtsum|xttab|xttest0|xttobit|xttobit_8|xttobit_p|xttrans|yx|yxview__barlike_draw|yxview_area_draw|yxview_bar_draw|yxview_dot_draw|yxview_dropline_draw|yxview_function_draw|yxview_iarrow_draw|yxview_ilabels_draw|yxview_normal_draw|yxview_pcarrow_draw|yxview_pcbarrow_draw|yxview_pccapsym_draw|yxview_pcscatter_draw|yxview_pcspike_draw|yxview_rarea_draw|yxview_rbar_draw|yxview_rbarm_draw|yxview_rcap_draw|yxview_rcapsym_draw|yxview_rconnected_draw|yxview_rline_draw|yxview_rscatter_draw|yxview_rspike_draw|yxview_spike_draw|yxview_sunflower_draw|zap_s|zinb|zinb_llf|zinb_plf|zip|zip_llf|zip_p|zip_plf|zt_ct_5|zt_hc_5|zt_hcd_5|zt_is_5|zt_iss_5|zt_sho_5|zt_smp_5|ztbase_5|ztcox_5|ztdes_5|ztereg_5|ztfill_5|ztgen_5|ztir_5|ztjoin_5|ztnb|ztnb_p|ztp|ztp_p|zts_5|ztset_5|ztspli_5|ztsum_5|zttoct_5|ztvary_5|ztweib_5)\\\\b\"\n    - constant.number: \"\\\\b[+-]?([0-9]+(\\\\.[0-9]+)?|\\\\.[0-9]+|\\\\.)([eE][+-]?[0-9]+)?[i]?\\\\b\"\n    - symbol.operator: \"-|==|<=|>=|<|>|&|!=\"\n    - symbol.operator: \"\\\\*|\\\\+|\\\\^|/|!|~|=|~=\"\n    - symbol.brackets: \"[\\\\{\\\\}\\\\(\\\\)\\\\[\\\\]]\"\n\n    - identifier: \"%-?\\\\d{1,2}(\\\\.\\\\d{1,2})?[gfe]c?\"\n    - identifier: \"%(21x|16H|16L|8H|8L)\"\n    - identifier: \"%-?(tc|tC|td|tw|tm|tq|th|ty|tg).{0,32}\"\n    - identifier: \"%[-~]?\\\\d{1,4}s\"\n\n    - identifier.macro: \"\\\\$\\\\w{1,32}\"\n    - identifier.macro:\n        start: \"`\"\n        end: \"'\"\n        rules: []\n\n    - comment:\n        start: \"///?\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"^\\\\s*\\\\*\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/svelte.yaml",
    "content": "filetype: svelte\n\ndetect:\n    filename: \"\\\\.svelte$\"\n\nrules:\n    - default:\n        start: \"<script>\"\n        end: \"</script>\"\n        rules:\n            - include: \"javascript\"\n\n    - default:\n        start: \"<script lang=\\\"ts\\\">\"\n        end: \"</script>\"\n        rules:\n            - include: \"typescript\"\n    - default:\n        start: \"<style.*?>\"\n        end: \"</style.*?>\"\n        rules:\n            - include: \"css\"\n    - default:\n        start: \"^\"\n        end: \"$\"\n        rules:\n            - include: \"html5\"\n"
  },
  {
    "path": "runtime/syntax/swift.yaml",
    "content": "filetype: swift\n\ndetect:\n    filename: \"\\\\.swift$\"\n    header: \"^#!.*bin/(env +)?swift( |$)\"\n\nrules:\n\n    # Patterns\n    - type: \\b(_)\\b\n\n    # Operators\n    - symbol.operator: ([.:;,+*|=!?\\\\%]|<|>|/|-|&)\n\n    # Declaration Keywords\n    - statement.declaration: \\b(associatedtype|class|deinit|enum|extension|fileprivate|func|import|init)\\b\n    - statement.declaration: \\b(inout|internal|let|open|operator|private|protocol|public|static|struct|subscript|typealias|var)\\b\n\n    # Statements Keywords\n    - statement: \\b(break|case|continue|default|defer|do|else|fallthrough|for|guard)\\b\n    - statement: \\b(if|inif|repeat|return|switch|where|while)\\b\n\n    # keyword.reserved\n    - statement.reserved: \\b(associativity|convenience|dynamic|didSet|final|get|infix|indirect|lazy|left|mutating)\\b\n    - statement.reserved: \\b(none|nonmutating|override|postfix|precedence|prefix|Protocol|required)\\b\n    - statement.reserved: \\b(right|set|Type|unowned|weak|willSet)\\b\n\n    # Expression and types\n    - type: \\b(as|Any|catch|is|rethrows|super|self|throw|throws|try)\\b\n\n    - statement.built_in: \\b(abs|advance|alignof|alignofValue|anyGenerator|assert|assertionFailure|bridgeFromObjectiveC)\\b\n    - statement.built_in: \\b(bridgeFromObjectiveCUnconditional|bridgeToObjectiveC|bridgeToObjectiveCUnconditional|contains)\\b\n    - statement.built_in: \\b(count|countElements|countLeadingZeros|debugPrint|debugPrintln|distance|dropFirst|dropLast|dump|encodeBitsAsWords)\\b\n    - statement.built_in: \\b(enumerate|equal|fatalError|filter|find|getBridgedObjectiveCType|getVaList|indices|insertionSort)\\b\n    - statement.built_in: \\b(isBridgedToObjectiveC|isBridgedVerbatimToObjectiveC|isUniquelyReferenced|isUniquelyReferencedNonObjC)\\b\n    - statement.built_in: \\b(join|lexicographicalCompare|map|max|maxElement|min|minElement|numericCast|overlaps|partition|posix)\\b\n    - statement.built_in: \\b(precondition|preconditionFailure|print|println|quickSort|readLine|reduce|reflect)\\b\n    - statement.built_in: \\b(reinterpretCast!reverse|roundUpToAlignment|sizeof|sizeofValue|sort|split|startsWith|stride)\\b\n    - statement.built_in: \\b(strideof|strideofValue|swap|toString|transcode|underestimateCount|unsafeAddressOf|unsafeBitCast)\\b\n    - statement.built_in: \\b(unsafeDowncast|unsafeUnwrap|unsafeReflect|withExtendedLifetime|withObjectAtPlusZero|withUnsafePointer)\\b\n    - statement.built_in: \\b(withUnsafePointerToObject|withUnsafeMutablePointer|withUnsafeMutablePointers|withUnsafePointer)\\b\n    - statement.built_in: \\b(withUnsafePointers|withVaList|zip)\\b\n\n    # Meta\n    - statement.meta: \\@\\b(autoclosure|available|convention|exported|IBAction|IBDesignable|IBOutlet|IBInspectable|infix)\\b\n    - statement.meta: \\@\\b(lazy|noreturn|noescape|nonobjc|NSApplicationMain|NSCopying|NSManaged|objc|prefix|postfix)\\b\n    - statement.meta: \\@\\b(required|testable|warn_unused_result|UIApplicationMain)\\b\n\n    #preprocessor\n    - preproc: ^[[:space:]]*#[[:space:]]*(define|else|elseif|endif|if|selector)\\b\n    - preproc.DebugIdentifier: \\b(__COLUMN__|__FILE__|__FUNCTION__|__LINE__)\\b\n    - preproc.DebugIdentifier: ^[[:space:]]*#[[:space:]]*(column|file|function|line)\\b\n\n    # Constant\n    - constant: \\b(true|false|nil)\n    - constant.number: ([0-9]+)\n\n    # Storage Types\n    - type.storage: \\b((U)?Int(8|16|32|64))\\b\n    - type.storage: \\b(Int|UInt|String|Bit|Bool|Character|Double|Optional|Float|Range)\\b\n    - type.storage: \\b(AnyObject)\\b\n\n    # Collections\n    - type.collections: \\b(Array|Dictionary|Set)\\b\n\n    # Ctypes\n    - type.ctypes: \\b(CBool|CChar|CUnsignedChar|CShort|CUnsignedShort|CInt|CUnsignedInt|CLong|CUnsignedLong|CLongLong|CUnsignedLongLong|CWideChar|CChar16|CChar32|CFloat|CDouble)\\b\n\n    # String\n    - constant.string:\n        start: \\\"\n        end: \\\"\n        skip: \\\\.\n        rules:\n            - constant.specialChar: (\\\\0|\\\\\\\\|\\\\t|\\\\n|\\\\r|\\\\\"|\\\\')\n            - constant.interpolation: \\\\\\([[:graph:]]*\\)\n            - constant.unicode: \\\\u\\{[[:xdigit:]]+}\n\n    # Shebang Line\n    - comment.shebang: ^(#!).*\n\n    # Doc Comment\n    - comment.doc: (///).*\n\n    # Line Comment\n    - comment.line: \"//.*\"\n\n    # Block Comment\n    - comment.block:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    # Doc Block Comment\n    - comment.block:\n        start: \"/\\\\*\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    # Todo\n    - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/syntax_converter.go",
    "content": "//go:build ignore\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n)\n\ntype SingleRule struct {\n\tcolor string\n\tregex string\n}\n\ntype MultiRule struct {\n\tcolor string\n\tstart string\n\tend   string\n}\n\n// JoinRule takes a syntax rule (which can be multiple regular expressions)\n// and joins it into one regular expression by ORing everything together\nfunc JoinRule(rule string) string {\n\tsplit := strings.Split(rule, `\" \"`)\n\tjoined := strings.Join(split, \"|\")\n\treturn joined\n}\n\nfunc parseFile(text, filename string) (filetype, syntax, header string, rules []any) {\n\tlines := strings.Split(text, \"\\n\")\n\n\t// Regex for parsing syntax statements\n\tsyntaxParser := regexp.MustCompile(`syntax \"(.*?)\"\\s+\"(.*)\"+`)\n\t// Regex for parsing header statements\n\theaderParser := regexp.MustCompile(`header \"(.*)\"`)\n\n\t// Regex for parsing standard syntax rules\n\truleParser := regexp.MustCompile(`color (.*?)\\s+(?:\\((.+?)?\\)\\s+)?\"(.*)\"`)\n\t// Regex for parsing syntax rules with start=\"...\" end=\"...\"\n\truleStartEndParser := regexp.MustCompile(`color (.*?)\\s+(?:\\((.+?)?\\)\\s+)?start=\"(.*)\"\\s+end=\"(.*)\"`)\n\n\tfor lineNum, line := range lines {\n\t\tline = strings.TrimSpace(line)\n\t\tif line == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif strings.HasPrefix(line, \"#\") {\n\t\t\tcontinue\n\t\t}\n\t\tif strings.HasPrefix(line, \"syntax\") {\n\t\t\tsyntaxMatches := syntaxParser.FindSubmatch([]byte(line))\n\t\t\tif len(syntaxMatches) == 3 {\n\t\t\t\tfiletype = string(syntaxMatches[1])\n\t\t\t\tsyntax = JoinRule(string(syntaxMatches[2]))\n\t\t\t} else {\n\t\t\t\tfmt.Println(filename, lineNum, \"Syntax statement is not valid: \"+line)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tif strings.HasPrefix(line, \"header\") {\n\t\t\t// Header statement\n\t\t\theaderMatches := headerParser.FindSubmatch([]byte(line))\n\t\t\tif len(headerMatches) == 2 {\n\t\t\t\theader = JoinRule(string(headerMatches[1]))\n\t\t\t} else {\n\t\t\t\tfmt.Println(filename, lineNum, \"Header statement is not valid: \"+line)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// Syntax rule, but it could be standard or start-end\n\t\tif ruleParser.MatchString(line) {\n\t\t\t// Standard syntax rule\n\t\t\t// Parse the line\n\t\t\tsubmatch := ruleParser.FindSubmatch([]byte(line))\n\t\t\tvar color string\n\t\t\tvar regexStr string\n\t\t\tvar flags string\n\t\t\tif len(submatch) == 4 {\n\t\t\t\t// If len is 4 then the user specified some additional flags to use\n\t\t\t\tcolor = string(submatch[1])\n\t\t\t\tflags = string(submatch[2])\n\t\t\t\tif flags != \"\" {\n\t\t\t\t\tregexStr = \"(?\" + flags + \")\" + JoinRule(string(submatch[3]))\n\t\t\t\t} else {\n\t\t\t\t\tregexStr = JoinRule(string(submatch[3]))\n\t\t\t\t}\n\t\t\t} else if len(submatch) == 3 {\n\t\t\t\t// If len is 3, no additional flags were given\n\t\t\t\tcolor = string(submatch[1])\n\t\t\t\tregexStr = JoinRule(string(submatch[2]))\n\t\t\t} else {\n\t\t\t\t// If len is not 3 or 4 there is a problem\n\t\t\t\tfmt.Println(filename, lineNum, \"Invalid statement: \"+line)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\trules = append(rules, SingleRule{color, regexStr})\n\t\t} else if ruleStartEndParser.MatchString(line) {\n\t\t\t// Start-end syntax rule\n\t\t\tsubmatch := ruleStartEndParser.FindSubmatch([]byte(line))\n\t\t\tvar color string\n\t\t\tvar start string\n\t\t\tvar end string\n\t\t\t// Use m and s flags by default\n\t\t\tif len(submatch) == 5 {\n\t\t\t\t// If len is 5 the user provided some additional flags\n\t\t\t\tcolor = string(submatch[1])\n\t\t\t\tstart = string(submatch[3])\n\t\t\t\tend = string(submatch[4])\n\t\t\t} else if len(submatch) == 4 {\n\t\t\t\t// If len is 4 the user did not provide additional flags\n\t\t\t\tcolor = string(submatch[1])\n\t\t\t\tstart = string(submatch[2])\n\t\t\t\tend = string(submatch[3])\n\t\t\t} else {\n\t\t\t\t// If len is not 4 or 5 there is a problem\n\t\t\t\tfmt.Println(filename, lineNum, \"Invalid statement: \"+line)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// rules[color] = \"(?\" + flags + \")\" + \"(\" + start + \").*?(\" + end + \")\"\n\t\t\trules = append(rules, MultiRule{color, start, end})\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc generateFile(filetype, syntax, header string, rules []any) string {\n\toutput := \"\"\n\n\toutput += fmt.Sprintf(\"filetype: %s\\n\\n\", filetype)\n\toutput += fmt.Sprintf(\"detect: \\n    filename: \\\"%s\\\"\\n\", strings.Replace(strings.Replace(syntax, \"\\\\\", \"\\\\\\\\\", -1), \"\\\"\", \"\\\\\\\"\", -1))\n\n\tif header != \"\" {\n\t\toutput += fmt.Sprintf(\"    signature: \\\"%s\\\"\\n\", strings.Replace(strings.Replace(header, \"\\\\\", \"\\\\\\\\\", -1), \"\\\"\", \"\\\\\\\"\", -1))\n\t}\n\n\toutput += \"\\nrules:\\n\"\n\n\tfor _, r := range rules {\n\t\tif rule, ok := r.(SingleRule); ok {\n\t\t\toutput += fmt.Sprintf(\"    - %s: \\\"%s\\\"\\n\", rule.color, strings.Replace(strings.Replace(rule.regex, \"\\\\\", \"\\\\\\\\\", -1), \"\\\"\", \"\\\\\\\"\", -1))\n\t\t} else if rule, ok := r.(MultiRule); ok {\n\t\t\toutput += fmt.Sprintf(\"    - %s:\\n\", rule.color)\n\t\t\toutput += fmt.Sprintf(\"        start: \\\"%s\\\"\\n\", strings.Replace(strings.Replace(rule.start, \"\\\\\", \"\\\\\\\\\", -1), \"\\\"\", \"\\\\\\\"\", -1))\n\t\t\toutput += fmt.Sprintf(\"        end: \\\"%s\\\"\\n\", strings.Replace(strings.Replace(rule.end, \"\\\\\", \"\\\\\\\\\", -1), \"\\\"\", \"\\\\\\\"\", -1))\n\t\t}\n\t}\n\n\treturn output\n}\n\nfunc main() {\n\tif len(os.Args) < 2 {\n\t\tfmt.Println(\"no args\")\n\t\treturn\n\t}\n\n\tdata, _ := os.ReadFile(os.Args[1])\n\tfmt.Print(generateFile(parseFile(string(data), os.Args[1])))\n}\n"
  },
  {
    "path": "runtime/syntax/systemd.yaml",
    "content": "filetype: systemd\n\ndetect:\n    filename: \"\\\\.(service|socket|timer)$\"\n    header: \"^\\\\[Unit\\\\]$\"\n\nrules:\n    - statement: \"^(Accept|After|Alias|AllowIsolate|Also|ANSI_COLOR|_AUDIT_LOGINUID|_AUDIT_SESSION|Backlog|Before|BindIPv6Only|BindsTo|BindToDevice|BlockIOReadBandwidth|BlockIOWeight|BlockIOWriteBandwidth|_BOOT_ID|Broadcast|BUG_REPORT_URL|BusName|Capabilities|CapabilityBoundingSet|CHASSIS|cipher|class|_CMDLINE|CODE_FILE|CODE_FUNC|CODE_LINE|_COMM|Compress|ConditionACPower|ConditionCapability|ConditionDirectoryNotEmpty|ConditionFileIsExecutable|ConditionFileNotEmpty|ConditionHost|ConditionKernelCommandLine|ConditionNull|ConditionPathExists|ConditionPathExistsGlob|ConditionPathIsDirectory|ConditionPathIsMountPoint|ConditionPathIsReadWrite|ConditionPathIsSymbolicLink|ConditionSecurity|ConditionVirtualization|Conflicts|ControlGroup|ControlGroupAttribute|ControlGroupModify|ControlGroupPersistent|controllers|Controllers|CPE_NAME|CPUAffinity|CPUSchedulingPolicy|CPUSchedulingPriority|CPUSchedulingResetOnFork|CPUShares|CrashChVT|CrashShell|__CURSOR|debug|DefaultControllers|DefaultDependencies|DefaultLimitAS|DefaultLimitCORE|DefaultLimitCPU|DefaultLimitDATA|DefaultLimitFSIZE|DefaultLimitLOCKS|DefaultLimitMEMLOCK|DefaultLimitMSGQUEUE|DefaultLimitNICE|DefaultLimitNOFILE|DefaultLimitNPROC|DefaultLimitRSS|DefaultLimitRTPRIO|DefaultLimitRTTIME|DefaultLimitSIGPENDING|DefaultLimitSTACK|DefaultStandardError|DefaultStandardOutput|Description|DeviceAllow|DeviceDeny|DirectoryMode|DirectoryNotEmpty|Documentation|DumpCore|entropy|Environment|EnvironmentFile|ERRNO|event_timeout|_EXE|ExecReload|ExecStart|ExecStartPost|ExecStartPre|ExecStop|ExecStopPost|ExecStopPre|filter|FONT|FONT_MAP|FONT_UNIMAP|ForwardToConsole|ForwardToKMsg|ForwardToSyslog|FreeBind|freq|FsckPassNo|fstab|_GID|Group|GuessMainPID|HandleHibernateKey|HandleLidSwitch|HandlePowerKey|HandleSuspendKey|hash|HibernateKeyIgnoreInhibited|HOME_URL|_HOSTNAME|ICON_NAME|ID|IdleAction|IdleActionSec|ID_LIKE|ID_MODEL|ID_MODEL_FROM_DATABASE|IgnoreOnIsolate|IgnoreOnSnapshot|IgnoreSIGPIPE|InaccessibleDirectories|InhibitDelayMaxSec|init|IOSchedulingClass|IOSchedulingPriority|IPTOS|IPTTL|JobTimeoutSec|JoinControllers|KeepAlive|KEYMAP|KEYMAP_TOGGLE|KillExcludeUsers|KillMode|KillOnlyUsers|KillSignal|KillUserProcesses|LidSwitchIgnoreInhibited|LimitAS|LimitCORE|LimitCPU|LimitDATA|LimitFSIZE|LimitLOCKS|LimitMEMLOCK|LimitMSGQUEUE|LimitNICE|LimitNOFILE|LimitNPROC|LimitRSS|LimitRTPRIO|LimitRTTIME|LimitSIGPENDING|LimitSTACK|link_priority|valueListenDatagram|ListenFIFO|ListenMessageQueue|ListenNetlink|ListenSequentialPacket|ListenSpecial|ListenStream|LogColor|LogLevel|LogLocation|LogTarget|luks|_MACHINE_ID|MakeDirectory|Mark|MaxConnections|MaxFileSec|MaxLevelConsole|MaxLevelKMsg|MaxLevelStore|MaxLevelSyslog|MaxRetentionSec|MemoryLimit|MemorySoftLimit|MESSAGE|MESSAGE_ID|MessageQueueMaxMessages|MessageQueueMessageSize|__MONOTONIC_TIMESTAMP|MountFlags|NAME|NAutoVTs|Nice|NonBlocking|NoNewPrivileges|NotifyAccess|OnActiveSec|OnBootSec|OnCalendar|OnFailure|OnFailureIsolate|OnStartupSec|OnUnitActiveSec|OnUnitInactiveSec|OOMScoreAdjust|Options|output|PAMName|PartOf|PassCredentials|PassSecurity|PathChanged|PathExists|PathExistsGlob|PathModified|PermissionsStartOnly|_PID|PIDFile|PipeSize|PowerKeyIgnoreInhibited|PRETTY_HOSTNAME|PRETTY_NAME|Priority|PRIORITY|PrivateNetwork|PrivateTmp|PropagatesReloadTo|pss|RateLimitBurst|RateLimitInterval|ReadOnlyDirectories|ReadWriteDirectories|__REALTIME_TIMESTAMP|ReceiveBuffer|RefuseManualStart|RefuseManualStop|rel|ReloadPropagatedFrom|RemainAfterExit|RequiredBy|Requires|RequiresMountsFor|RequiresOverridable|Requisite|RequisiteOverridable|ReserveVT|ResetControllers|Restart|RestartPreventExitStatus|RestartSec|RootDirectory|RootDirectoryStartOnly|RuntimeKeepFree|RuntimeMaxFileSize|RuntimeMaxUse|RuntimeWatchdogSec|samples|scale_x|scale_y|Seal|SecureBits|_SELINUX_CONTEXT|SendBuffer|SendSIGKILL|Service|ShowStatus|ShutdownWatchdogSec|size|SmackLabel|SmackLabelIPIn|SmackLabelIPOut|SocketMode|Sockets|SourcePath|_SOURCE_REALTIME_TIMESTAMP|SplitMode|StandardError|StandardInput|StandardOutput|StartLimitAction|StartLimitBurst|StartLimitInterval|static_node|StopWhenUnneeded|Storage|string_escape|none|replaceSuccessExitStatus|SupplementaryGroups|SUPPORT_URL|SuspendKeyIgnoreInhibited|SyslogFacility|SYSLOG_FACILITY|SyslogIdentifier|SYSLOG_IDENTIFIER|SyslogLevel|SyslogLevelPrefix|SYSLOG_PID|SystemCallFilter|SYSTEMD_ALIAS|_SYSTEMD_CGROUP|_SYSTEMD_OWNER_UID|SYSTEMD_READY|_SYSTEMD_SESSION|_SYSTEMD_UNIT|_SYSTEMD_USER_UNIT|SYSTEMD_WANTS|SystemKeepFree|SystemMaxFileSize|SystemMaxUse|SysVStartPriority|TCPCongestion|TCPWrapName|timeout|TimeoutSec|TimeoutStartSec|TimeoutStopSec|TimerSlackNSec|Transparent|_TRANSPORT|tries|TTYPath|TTYReset|TTYVHangup|TTYVTDisallocate|Type|_UID|UMask|Unit|User|UtmpIdentifier|VERSION|VERSION_ID|WantedBy|Wants|WatchdogSec|What|Where|WorkingDirectory)=\"\n    - preproc: \"^\\\\.include\\\\>\"\n    - symbol: \"=\"\n    - special: \"^\\\\[(Unit|Install|Service|Socket|Timer)\\\\]\"\n    - identifier.class: \"\\\\$MAINPID\"\n    - constant.bool: \"\\\\b(true|false)\\\\b\"\n    - comment: \"(^|[[:space:]])#([^{].*)?$\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n    - indent-char: \"\t+ +| +\t+\"\n"
  },
  {
    "path": "runtime/syntax/tcl.yaml",
    "content": "filetype: tcl\n\ndetect:\n    filename: \"\\\\.tcl$\"\n    header: \"^#!.*/(env +)?tclsh( |$)\"\n\nrules:\n    - statement: \"\\\\b(after|append|array|auto_execok|auto_import|auto_load|auto_load_index|auto_qualify|binary|break|case|catch|cd|clock|close|concat|continue|else|elseif|encoding|eof|error|eval|exec|exit|expr|fblocked|fconfigure|fcopy|file|fileevent|flush|for|foreach|format|gets|glob|global|history|if|incr|info|interp|join|lappend|lindex|linsert|list|llength|load|lrange|lreplace|lsearch|lset|lsort|namespace|open|package|pid|puts|pwd|read|regexp|regsub|rename|return|scan|seek|set|socket|source|split|string|subst|switch|tclLog|tell|time|trace|unknown|unset|update|uplevel|upvar|variable|vwait|while)\\\\b\"\n    - statement: \"\\\\b(array anymore|array donesearch|array exists|array get|array names|array nextelement|array set|array size|array startsearch|array statistics|array unset)\\\\b\"\n    - statement: \"\\\\b(string bytelength|string compare|string equal|string first|string index|string is|string last|string length|string map|string match|string range|string repeat|string replace|string to|string tolower|string totitle|string toupper|string trim|string trimleft|string trimright|string will|string wordend|string wordstart)\\\\b\"\n    - statement: \"\\\\b(alarm|auto_load_pkg|bsearch|catclose|catgets|catopen|ccollate|cconcat|cequal|chgrp|chmod|chown|chroot|cindex|clength|cmdtrace|commandloop|crange|csubstr|ctoken|ctype|dup|echo|execl|fcntl|flock|fork|fstat|ftruncate|funlock|host_info|id|infox|keyldel|keylget|keylkeys|keylset|kill|lassign|lcontain|lempty|lgets|link|lmatch|loadlibindex|loop|lvarcat|lvarpop|lvarpush|max|min|nice|pipe|profile|random|readdir|replicate|scancontext|scanfile|scanmatch|select|server_accept|server_create|signal|sleep|sync|system|tclx_findinit|tclx_fork|tclx_load_tndxs|tclx_sleep|tclx_system|tclx_wait|times|translit|try_eval|umask|wait)\\\\b\"\n    - identifier.class: \"proc[[:space:]]|(\\\\{|\\\\})\"\n    - symbol.operator: \"(\\\\(|\\\\)|\\\\;|`|\\\\\\\\|\\\\$|<|>|!|=|&|\\\\|)\"\n    - constant.number: \"\\\\b[0-9]+(\\\\.[0-9]+)?\\\\b\"\n    - constant.string: \"\\\"(\\\\\\\\.|[^\\\"])*\\\"|'(\\\\\\\\.|[^'])*'\"\n    - identifier.var: \"\\\\$\\\\{?[0-9A-Z_!@#$*?-]+\\\\}?\"\n    - comment: \"(^|;)[[:space:]]*#.*\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n"
  },
  {
    "path": "runtime/syntax/terraform.yaml",
    "content": "#\n# This syntax definition is based on the Terraform guide:\n# https://www.terraform.io/docs/configuration/index.html\n#\n# Formatting is loosely based on Sublime's and VSCode's syntax highlighting for Terraform:\n# https://github.com/totoroot/Terraform.tmLanguage/blob/master/Terraform.sublime-syntax\n# https://github.com/hashicorp/vscode-terraform/blob/main/syntaxes/terraform.tmGrammar.json\n#\n\nfiletype: terraform\n\ndetect:\n    # File Extensions:\n    #\n    # - \".tf\": the standard file extension\n    #   https://www.terraform.io/docs/configuration/index.html#code-organization\n    #\n    # - \".hcl\": non-terraform tools often use this HCL syntax, i.e. Vault\n    #   https://www.vaultproject.io/docs/configuration/\n    filename: \"\\\\.tf$|\\\\.hcl$\"\n\nrules:\n    # Named Values\n    #\n    # https://www.terraform.io/docs/language/expressions/references.html\n    - identifier: \"\\\\b(var|local|module|data|path|terraform)\\\\b\"\n\n    # Block types\n    #\n    # resource: https://www.terraform.io/docs/language/resources/syntax.html\n    # provider: https://www.terraform.io/docs/language/providers/configuration.html\n    # variable: https://www.terraform.io/docs/language/values/variables.html\n    # output: https://www.terraform.io/docs/language/values/outputs.html\n    # locals: https://www.terraform.io/docs/language/values/locals.html\n    # module: https://www.terraform.io/docs/language/modules/syntax.html\n    # data: https://www.terraform.io/docs/language/data-sources/index.html\n    # terraform: https://www.terraform.io/docs/language/settings/index.html#terraform-block-syntax\n    - special: \"\\\\b(resource|provider|variable|output|locals|module|terraform)\\\\b\"\n\n    # Built-In type keywords\n    #\n    # https://www.terraform.io/docs/language/expressions/type-constraints.html#primitive-types\n    # https://www.terraform.io/docs/language/expressions/type-constraints.html#dynamic-types-the-quot-any-quot-constraint\n    - type.keyword: \"\\\\b(any|string|number|bool)\\\\b\"\n\n    # Built-In Functions\n    #\n    # https://www.terraform.io/docs/language/functions/index.html\n    - statement: \"\\\\b(abs|ceil|floor|log|max|min|parseint|pow|signum|chomp|format|formatlist|indent|join|lower|regex|regexall|replace|split|strrev|substr|title|trim|trimprefix|trimsuffix|trimspace|upper|alltrue|anytrue|chunklist|coalesce|coalescelist|compact|concat|contains|distinct|element|flatten|index|keys|length|list|lookup|map|matchkeys|merge|one|range|reverse|setintersection|setproduct|setsubtract|setunion|slice|sort|sum|transpose|values|zipmap|base64decode|base64encode|base64gzip|csvdecode|jsondecode|jsonencode|textdecodebase64|textencodebase64|urlencode|yamldecode|yamlencode|abspath|dirname|pathexpand|basename|file|fileexists|fileset|filebase64|templatefile|formatdate|timeadd|timestamp|base64sha256|base64sha512|bcrypt|filebase64sha256|filebase64sha512|filemd5|filesha1|filesha256|filesha512|md5|rsadecrypt|sha1|sha256|sha512|uuid|uuidv5|cidrhost|cidrnetmask|cidrsubnet|cidrsubnets|can|defaults|nonsensitive|sensitive|tobool|tolist|tomap|tonumber|toset|tostring|try)\\\\b\"\n\n    - symbol.operator: \"([~^.:;,+*|=!\\\\%@]|<|>|/|-|&)\"\n\n    - symbol.brackets: \"([(){}]|\\\\[|\\\\])\"\n\n    - constant.number: \"\\\\b([0-9]+|0x[0-9a-fA-F]*)\\\\b|'.'\"\n\n    - constant.bool: \"\\\\b(true|false|null)\\\\b\"\n\n    - constant.string:\n          start: \"\\\"\"\n          end: \"\\\"\"\n          skip: \"\\\\\\\\.\"\n          rules:\n              - constant.specialChar: \"%.\"\n              - constant.specialChar: \"\\\\\\\\[abfnrtv'\\\\\\\"\\\\\\\\]\"\n              - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})\"\n\n    - constant.string:\n          start: \"''\"\n          end: \"''\"\n          skip: \"\\\\\\\\.\"\n          rules:\n              - constant.specialChar: \"%.\"\n              - constant.specialChar: \"\\\\\\\\[abfnrtv'\\\\\\\"\\\\\\\\]\"\n              - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})\"\n\n    - comment:\n          start: \"#|//\"\n          end: \"$\\\\n?\"\n          rules:\n              - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n          start: \"/\\\\*\"\n          end: \"\\\\*/\"\n          rules:\n              - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/tex.yaml",
    "content": "filetype: tex\n\ndetect:\n    filename: \"\\\\.tex$|\\\\.bib$|\\\\.cls$\"\n\nrules:\n    # colorize the identifiers of {<identifier>} and [<identifier>]\n    - identifier:\n        start: \"\\\\{\"\n        end: \"\\\\}\"\n        rules: []\n    - identifier:\n        start: \"\\\\[\"\n        end: \"\\\\]\"\n        rules: []\n    # numbers\n    - constant.number: \"\\\\b[0-9]+(\\\\.[0-9]+)?([[:space:]](pt|mm|cm|in|ex|em|bp|pc|dd|cc|nd|nc|sp))?\\\\b\"\n    # let brackets have the default color again\n    - default: \"[{}\\\\[\\\\]]\"\n    - special: \"[&\\\\\\\\]\"\n    # macros\n    - statement: \"\\\\\\\\@?[a-zA-Z_]+\"\n    - statement: \"\\\\\\\\%\"\n    # comments\n    - comment:\n        start: \"[^\\\\\\\\]%|^%\"\n        end: \"$\"\n        rules: []\n    - comment:\n        start: \"\\\\\\\\begin\\\\{comment\\\\}\"\n        end: \"\\\\\\\\end\\\\{comment\\\\}\"\n        rules: []\n"
  },
  {
    "path": "runtime/syntax/toml.yaml",
    "content": "filetype: toml\n\ndetect:\n    filename: \"\\\\.toml\"\n\nrules:\n    # Punctuation\n    - symbol: '[=,\\.]'\n    - symbol.brackets: '[{\\[\\]}]'\n    # Strings\n    - constant.string:\n        start: '\"\"\"'\n        end: '\\\"{3,5}'\n        skip: '\\\\.'\n        rules:\n            - constant.specialChar: '\\\\u[[:xdigit:]]{4}'\n            - constant.specialChar: '\\\\U[[:xdigit:]]{8}'\n            - constant.specialChar: '\\\\[btnfr\"\\\\]'\n    - constant.string:\n        start: '\"'\n        end: '\"'\n        skip: '\\\\.'\n        rules:\n            - constant.specialChar: '\\\\u[[:xdigit:]]{4}'\n            - constant.specialChar: '\\\\U[[:xdigit:]]{8}'\n            - constant.specialChar: '\\\\[btnfr\"\\\\]'\n    - constant.string:\n        start: \"'''\"\n        end: \"'{3,5}\"\n        rules: []\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        rules: []\n    # Integer\n    - constant.number: '[+-]?(\\d+_)*\\d+\\b'\n    - constant.number: '(0x([[:xdigit:]]+_)*[[:xdigit:]]+|0o([0-7]_)*[0-7]+|0b([01]+_)*[01]+)'\n    # Float\n    - constant.number: '[+-]?(\\d+_)*\\d+\\.(\\d+_)*\\d+'\n    - constant.number: '[+-]?(\\d+_)*\\d+(\\.(\\d+_)*\\d+)?[Ee][+-]?(\\d+_)*\\d+'\n    - constant.number: '(\\+|-)(inf|nan)'\n    # Bare key, keys starting with a digit or dash are ambiguous with numbers and are skipped\n    - identifier: '\\b[A-Za-z_][A-Za-z0-9_-]*\\b'\n    # Boolean and inf, nan without sign\n    - constant.bool.true: '\\btrue\\b'\n    - constant.bool.false: '\\bfalse\\b'\n    - constant.number: '\\b(inf|nan)\\b'\n    # Date and Time\n    - constant: '\\d+-\\d{2}-\\d{2}([T ]\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?([+-]\\d{2}:\\d{2}|Z)?)?'\n    - constant: '\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?'\n    # Comments\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/twig.yaml",
    "content": "filetype: twig\n\ndetect:\n    filename: \"\\\\.twig$\"\n\nrules:\n    - include: \"html\"\n    - symbol.tag:\n        start: \"\\\\{\\\\{[[:space:]]\"\n        end: \"[[:space:]]\\\\}\\\\}\"\n        rules:\n            - identifier: \"\\\\b(abs|batch|capitalize|convert|encoding|date(_modify)?|default|escape|first|format|join|json_encode|keys|last|length|lower|merge|nl2br|number_format|raw|replace|reverse|round|slice|sort|split|striptags|title|trim|upper|url_encode)\\\\b\"\n            - identifier.class: \"\\\\b(attribute|block|constant|cycle|date|dump|include|max|min|parent|random|range|source|template_from_string)\\\\b\"\n            - type.keyword: \"\\\\b(and|as|constant|defined|divisibleby|empty|even|false|in|is|iterable|not|null|odd|or|same(as)?|true|with)\\\\b\"\n            - symbol.operator: \"[.:;,+*?|=!\\\\%]|<|>|/|-|&\"\n            - symbol.brackets: \"[(){}]|\\\\[|\\\\]\"\n            - constant.number: \"\\\\b[0-9]+\\\\b|\\\\b0x[0-9A-Fa-f]+\\\\b\"\n            - constant.string:\n                start: \"\\\"\"\n                end: \"\\\"\"\n                skip: \"\\\\\\\\\"\n                rules:\n                    - constant.specialChar: \"\\\\\\\\.\"\n            - constant.string:\n                start: \"'\"\n                end: \"'\"\n                skip: \"\\\\\\\\\"\n                rules:\n                    - constant.specialChar: \"\\\\\\\\.\"\n    - symbol.tag:\n        start: \"\\\\{%[[:space:]]\"\n        end: \"[[:space:]]%\\\\}\"\n        rules:\n            - identifier: \"\\\\b(abs|batch|capitalize|convert|encoding|date(_modify)?|default|escape|first|format|join|json_encode|keys|last|length|lower|merge|nl2br|number_format|raw|replace|reverse|round|slice|sort|split|striptags|title|trim|upper|url_encode)\\\\b\"\n            - identifier.class: \"\\\\b(attribute|block|constant|cycle|date|dump|include|max|min|parent|random|range|source|template_from_string)\\\\b\"\n            - type.keyword: \"\\\\b(and|as|constant|defined|divisibleby|empty|even|false|in|is|iterable|not|null|odd|or|same(as)?|true|with)\\\\b\"\n            - symbol.operator: \"[.:;,+*?|=!\\\\%]|<|>|/|-|&\"\n            - symbol.brackets: \"[(){}]|\\\\[|\\\\]\"\n            - constant.number: \"\\\\b[0-9]+\\\\b|\\\\b0x[0-9A-Fa-f]+\\\\b\"\n            - constant.string:\n                start: \"\\\"\"\n                end: \"\\\"\"\n                skip: \"\\\\\\\\\"\n                rules:\n                    - constant.specialChar: \"\\\\\\\\.\"\n            - constant.string:\n                start: \"'\"\n                end: \"'\"\n                skip: \"\\\\\\\\\"\n                rules:\n                   - constant.specialChar: \"\\\\\\\\.\"\n    - comment:\n        start: \"\\\\{#\"\n        end: \"#\\\\}\"\n        rules: []\n"
  },
  {
    "path": "runtime/syntax/typescript.yaml",
    "content": "filetype: typescript\n\ndetect:\n    filename: \"\\\\.tsx?$\"\n\nrules:\n    - constant.number: \"\\\\b[-+]?([1-9][0-9]*|0[0-7]*|0x[0-9a-fA-F]+)([uU][lL]?|[lL][uU]?)?\\\\b\"\n    - constant.number: \"\\\\b[-+]?([0-9]+\\\\.[0-9]*|[0-9]*\\\\.[0-9]+)([EePp][+-]?[0-9]+)?[fFlL]?\"\n    - constant.number: \"\\\\b[-+]?([0-9]+[EePp][+-]?[0-9]+)[fFlL]?\"\n    - identifier: \"[A-Za-z_][A-Za-z0-9_]*[[:space:]]*[(]\"\n    - statement: \"\\\\b(abstract|as|async|await|break|case|catch|class|const|constructor|continue)\\\\b\"\n    - statement: \"\\\\b(debugger|declare|default|delete|do|else|enum|export|extends|finally|for|from)\\\\b\"\n    - statement: \"\\\\b(function|get|if|implements|import|in|instanceof|interface|is|let|module|namespace)\\\\b\"\n    - statement: \"\\\\b(new|of|package|private|protected|public|require|return|set|static|super|switch)\\\\b\"\n    - statement: \"\\\\b(this|throw|try|type|typeof|var|void|while|with|yield)\\\\b\"\n    - constant: \"\\\\b(false|true|null|undefined|NaN)\\\\b\"\n    - type: \"\\\\b(Array|Boolean|Date|Enumerator|Error|Function|Math)\\\\b\"\n    - type: \"\\\\b(Number|Object|RegExp|String|Symbol)\\\\b\"\n    - type: \"\\\\b(any|unknown|boolean|never|number|string|symbol)\\\\b\"\n    - statement: \"[-+/*=<>!~%?:&|]\"\n    - constant: \"/[^*]([^/]|(\\\\\\\\/))*[^\\\\\\\\]/[gim]*\"\n    - constant: \"\\\\\\\\[0-7][0-7]?[0-7]?|\\\\\\\\x[0-9a-fA-F]+|\\\\\\\\[bfnrt'\\\"\\\\?\\\\\\\\]\"\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules: []\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"TODO:?\"\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n    - constant.string:\n        start: \"`\"\n        end: \"`\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n            - identifier: \"\\\\x24\\\\{.*?\\\\}\"\n"
  },
  {
    "path": "runtime/syntax/v.yaml",
    "content": "filetype: v\n\ndetect:\n\nrules:\n      # Conditionals and control flow\n    - preproc: \"\\\\b(module|import)\\\\b\"\n    - statement: \"\\\\b(if|else|for|match|select|defer|or|unsafe)\\\\b\"\n    - statement: \"\\\\b(break|continue|goto|return)\\\\b\"\n    - type.keyword: \"\\\\b(assert|const|enum|fn|struct|interface|type)\\\\b\"\n    - type.keyword: \"\\\\b(pub|mut|__global)\\\\b\"\n\n    - preproc: \"\\\\$\\\\b(if|else)\\\\b\"\n    - identifier.os: \"\\\\b(mac|macos|linux|windows|freebsd|openbsd|netbsd|dragonfly|android|solaris|haiku)\\\\b\"\n    - identifier.compiler: \"\\\\b(gcc|tinyc|clang|mingw|msvc|cplusplus)\\\\b\"\n    - identifier.platform: \"\\\\b(amd64|aarch64|x64|x32|little_endian|big_endian)\\\\b\"\n    - identifier.other: \"\\\\b(debug|test|js|glibc|prealloc|no_bounds_checking)\\\\b\"\n\n    - identifier.class: \"\\\\b([A-Z][A-Za-z0-9_]*)\\\\b\"\n    - identifier.function: \"\\\\b([a-z_]+\\\\()\"\n    - symbol.operator: \"\\\\b(i[ns])\\\\b|[-+/*<>!=~*%&:|,.?]\"\n    - symbol.attribute:\n        start: \"^\\\\[\"\n        end: \"\\\\]$\"\n        rules:\n            - default: \".*\"\n            - symbol: \"\\\\b(deprecated|direct_array_access|if|inline|live|ref_only|typedef|windows_stdcall)\\\\b\"\n\n      # Types\n    - type: \"\\\\b(byte|u(16|32|64|128)|i(nt|8|16|64|128)|f(32|64))\\\\b\"\n    - type: \"\\\\b(bool|cha[nr]|map|rune|string)\\\\b\"\n    - type: \"\\\\b(any(_int|_float)?|size_t|(uint|byte|char|void)ptr)\\\\b\"\n    - constant.bool: \"\\\\b(true|false)\\\\b\"\n    - constant.none: \"\\\\b(none)\\\\b\"\n\n      # Brackets\n    - symbol.brackets: \"(\\\\{|\\\\})\"\n    - symbol.brackets: \"(\\\\(|\\\\))\"\n    - symbol.brackets: \"(\\\\[|\\\\])\"\n\n      # Numbers and strings\n    - constant.number: \"\\\\b(0b[01_]+)\\\\b\"\n    - constant.number: \"\\\\b(0o[0-7_]+)\\\\b\"\n    - constant.number: \"\\\\b(0x[0-9a-fA-F_]+)\\\\b\"\n    - constant.number: \"\\\\b([0-9_]+)\\\\b\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"%.\"\n            - constant.specialChar: \"\\\\\\\\[abefnrtv'\\\\\\\"\\\\\\\\]\"\n            - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"%.\"\n            - constant.specialChar: \"\\\\\\\\[abefnrtv'\\\\\\\"\\\\\\\\]\"\n            - constant.specialChar: \"\\\\\\\\([0-7]{3}|x[A-Fa-f0-9]{2}|u[A-Fa-f0-9]{4}|U[A-Fa-f0-9]{8})\"\n\n    - constant.string:\n        start: \"`\"\n        end: \"`\"\n        rules: []\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/vala.yaml",
    "content": "filetype: vala\n\ndetect:\n    filename: \"\\\\.vala$\"\n\nrules:\n    - type: \"\\\\b(float|double|bool|u?char|u?int(8|16|32|64)?|u?short|u?long|void|s?size_t|unichar)\\\\b\"\n    - identifier.class: \"[A-Za-z_][A-Za-z0-9_]*[[:space:]]*[()]\"\n    - statement: \"\\\\b(for|if|while|do|else|case|default|switch|try|throw|catch)\\\\b\"\n    - statement: \"\\\\b(inline|typedef|struct|enum|union|extern|static|const)\\\\b\"\n    - statement: \"\\\\b(operator|new|delete|return|null)\\\\b\"\n    - statement: \"\\\\b(class|override|private|public|signal|this|weak)\\\\b\"\n    - special: \"\\\\b(goto|break|continue)\\\\b\"\n    - constant.bool: \"\\\\b(true|false)\\\\b\"\n    - constant.number: \"\\\\b([0-9]+)\\\\b\"\n    - symbol.operator: \"[\\\\-+/*=<>?:!~%&|]|->\"\n    - constant.string: \"\\\"(\\\\\\\\.|[^\\\"])*\\\"|'(\\\\\\\\.|[^'])*'\"\n    - comment: \"(^|[[:space:]])//.*\"\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules: []\n\n    - todo: \"TODO:?\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n    - indent-char: \"\t+ +| +\t+\"\n"
  },
  {
    "path": "runtime/syntax/verilog.yaml",
    "content": "filetype: verilog\n\ndetect:\n    filename: \"\\\\.(v|vh|sv|svh)$\"\n\nrules:\n    - preproc: \"\\\\b(module|package|program|endmodule|endpackage|endprogram)\\\\b\"\n    - type.keyword: \"\\\\b(task|interface|class|endtask|endinterface|endclass)\\\\b\"\n    # builtin functions like $display\n    - special: \"\\\\$[0-9A-Za-z_]+\"\n\n    # Verilog keywords\n    - statement: \"\\\\b(always|and|assign|automatic|begin|buf|bufif0|bufif1|case|casex|casez|cell|cmos|config)\\\\b\"\n    - statement: \"\\\\b(deassign|default|defparam|design|disable|edge|else|end|endcase|endconfig|endfunction|endgenerate)\\\\b\"\n    - statement: \"\\\\b(endprimitive|endspecify|endtable|event|for|force|forever|fork|function|generate)\\\\b\"\n    - statement: \"\\\\b(genvar|highz0|highz1|if|iff|ifnone|incdir|include|initial|input|instance|join)\\\\b\"\n    - statement: \"\\\\b(large|liblist|library|localparam|macromodule|medium|nand|negedge|nmos|nor|noshowcancelled)\\\\b\"\n    - statement: \"\\\\b(not|notif0|notif1|null|or|output|parameter|pmos|posedge|primitive|pull0|pull1|pulldown|pullup)\\\\b\"\n    - statement: \"\\\\b(pulsestyle_onevent|pulsestyle_ondetect|rcmos|realtime|reg|release|repeat|rnmos|rpmos|rtran)\\\\b\"\n    - statement: \"\\\\b(rtranif0|rtranif1|scalared|showcancelled|small|specify|specparam|strong0|strong1|supply0)\\\\b\"\n    - statement: \"\\\\b(supply1|table|time|tran|tranif0|tranif1|tri0|tri1|triand|trior|trireg|use|uwire)\\\\b\"\n    - statement: \"\\\\b(vectored|wait|wand|weak0|weak1|while|wor|xnor|xor)\\\\b\"\n\n    # SystemVerilog keywords\n    - statement: \"\\\\b(alias|always_comb|always_ff|always_latch|assert|assume|before|bind|bins|binsof|break)\\\\b\"\n    - statement: \"\\\\b(chandle|clocking|const|constraint|context|continue|cover|covergroup|coverpoint|cross|dist|do)\\\\b\"\n    - statement: \"\\\\b(endclocking|endgroup|endproperty|endsequence|enum)\\\\b\"\n    - statement: \"\\\\b(expect|export|extends|extern|final|first_match|foreach|forkjoin|ignore_bins|illegal_bins|import)\\\\b\"\n    - statement: \"\\\\b(inside|intersect|join_any|join_none|local|longint|matches|modport|new)\\\\b\"\n    - statement: \"\\\\b(packed|priority|property|protected|pure|rand|randc|randcase|randsequence|ref|return)\\\\b\"\n    - statement: \"\\\\b(sequence|solve|static|struct|super|tagged|this|throughout|timeprecision)\\\\b\"\n    - statement: \"\\\\b(timeunit|type|typedef|union|unique|virtual|wait_order|wildcard|with|within)\\\\b\"\n\n    # types\n    - type.keyword: \"\\\\b(int|integer|logic|wire|tri|unsigned|signed|inout|var|shortint|shortreal|real|void|string|bit|byte)\\\\b\"\n\n    # constants\n    - constant.number: \"\\\\b[0-9]+\\\\b\"\n    - constant.number: \"\\\\b'[su]?[dboh][0-9xzXZa-fA-F]+\\\\b\"\n    # .asdf(...) argument syntax\n    - special: \"\\\\.((\\\\*)|([A-Za-z][A-Za-z0-9_]*))\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/vhdl.yaml",
    "content": "filetype: vhdl\n\ndetect:\n    filename: \"\\\\.vhdl?$\"\n\nrules:\n    - type: \"(?i)\\\\b(string|integer|natural|positive|(un)?signed|std_u?logic(_vector)?|bit(_vector)?|boolean|u?x01z?|array|range)\\\\b\"\n    - identifier: \"(?i)library[[:space:]]+[a-zA-Z_0-9]+\"\n    - identifier: \"(?i)use[[:space:]]+[a-zA-Z_0-9\\\\.]+\"\n    - identifier: \"(?i)component[[:space:]]+[a-zA-Z_0-9]+\"\n    - identifier: \"(?i)(architecture|configuration)[[:space:]]+[a-zA-Z_0-9]+[[:space:]]+of[[:space:]]+[a-zA-Z_0-9]+\"\n    - identifier: \"(?i)(entity|package)[[:space:]]+[a-zA-Z_0-9]+[[:space:]]+is\"\n    - identifier: \"(?i)end[[:space:]]+((architecture|entity|component|process|package|generate)[[:space:]]+)?[a-zA-Z_0-9]+\"\n    - statement: \"(?i)\\\\b(abs|access|after|alias|all|and|architecture|assert|attribute)\\\\b\"\n    - statement: \"(?i)\\\\b(begin|block|body|buffer|bus|case|component|configuration|constant)\\\\b\"\n    - statement: \"(?i)\\\\b(disconnect|downto|else|elsif|end|entity|exit)\\\\b\"\n    - statement: \"(?i)\\\\b(file|for|function|generate|generic|guarded)\\\\b\"\n    - statement: \"(?i)\\\\b(if|impure|in|inertial|inout|is)\\\\b\"\n    - statement: \"(?i)\\\\b(label|library|linkage|literal|loop|map|mod)\\\\b\"\n    - statement: \"(?i)\\\\b(nand|new|next|nor|not|null|of|on|open|or|others|out)\\\\b\"\n    - statement: \"(?i)\\\\b(package|port|postponed|procedure|process|pure)\\\\b\"\n    - statement: \"(?i)\\\\b(range|record|register|reject|rem|report|return|rol|ror)\\\\b\"\n    - statement: \"(?i)\\\\b(select|severity|shared|signal|sla|sll|sra|srl|subtype)\\\\b\"\n    - statement: \"(?i)\\\\b(then|to|transport|type|unaffected|units|until|use)\\\\b\"\n    - statement: \"(?i)\\\\b(variable|wait|when|while|with|xnor|xor)\\\\b\"\n    - statement: \"(?i)'(base|left|right|high|low|pos|val|succ|pred|leftof|rightof|image|(last_)?value)\"\n    - statement: \"(?i)'((reverse_)?range|length|ascending|event|stable)\"\n    - statement: \"(?i)'(simple|path|instance)_name\"\n    - statement: \"(?i)\\\\b(std_match|(rising|falling)_edge|is_x)\\\\b\"\n    - statement: \"(?i)\\\\bto_(unsigned|signed|integer|u?x01z?|stdu?logic(vector)?)\\\\b\"\n    - symbol.operator: \"(\\\\+|-|\\\\*|/|&|<|>|=|\\\\.|:)\"\n    - constant.number: \"(?i)'([0-1]|u|x|z|w|l|h|-)'|[box]?\\\"([0-1a-fA-F]|u|x|z|w|l|h|-)+\\\"\"\n    - constant.number: \"(?i)\\\\b[0-9\\\\._]+(e[\\\\-]?[0-9]+)?( ?[fpnum]?s)?\\\\b\"\n    - constant.bool: \"(?i)\\\\b(true|false)\\\\b\"\n    - constant: \"(?i)\\\\b(note|warning|error|failure)\\\\b\"\n    - constant.string: \"\\\"[^\\\"]*\\\"\"\n    - comment: \"--.*\"\n"
  },
  {
    "path": "runtime/syntax/vi.yaml",
    "content": "filetype: vi\n\ndetect:\n    filename: \"(^|/|\\\\.)(ex|vim)rc$|\\\\.vim\"\n\nrules:\n    - identifier: \"[A-Za-z_][A-Za-z0-9_]*[(]+[A-Za-z0-9_:.,\\\\s]*[)]+\"\n    - special: \"[()]+\"\n    - statement: \"\\\\b([nvxsoilc]?(nore|un)?map|[nvlx]n|[ico]?no|[cilovx][um]|s?unm)\\\\b\"\n    - statement: \"\\\\b(snor|nun|nm|set|if|endif|let|unlet|source)\\\\b\"\n    - statement: \"[!&=?]\"\n    - constant.number: \"\\\\b[0-9]+\\\\b\"\n\n    - comment:\n        start: \"(^\\\"|[ \\t]+\\\" |[ \\t]+\\\"$)\"\n        end: \"$\"\n        rules: []\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n"
  },
  {
    "path": "runtime/syntax/vue.yaml",
    "content": "filetype: vue\n\ndetect:\n    filename: \"\\\\.vue$\"\n\nrules:\n    - default:\n        start: \"<template.*?>\"\n        end: \"</template.*?>\"\n        limit-group: symbol.tag\n        rules:\n            - error: \"<[^!].*?>\"\n            - symbol.tag: \"(?i)<[/]?(a|a(bbr|ddress|rea|rticle|side|udio)|b|b(ase|d(i|o)|lockquote|r|utton)|ca(nvas|ption)|center|cite|co(de|l|lgroup)|d(ata|atalist|d|el|etails|fn|ialog|l|t)|em|embed|fieldset|fig(caption|ure)|form|iframe|h[1-6]|hr|i|img|in(put|s)|kbd|keygen|label|legend|li|link|ma(in|p|rk)|menu|menuitem|met(a|er)|nav|noscript|o(bject|l|pt(group|ion)|utput)|p|param|picture|pre|progress|q|r(p|t|uby)|s|samp|se(ction|lect)|svg|small|source|span|strong|su(b|p|mmary)|textarea|time|track|u|ul|var|video|wbr)( .*)*?>\"\n            - symbol.tag.extended: \"(?i)<[/]?(body|div|html|head(er)?|footer|title|table|t(body|d|h(ead)?|r|foot))( .*)*?>\"\n            - preproc: \"(?i)<[/]?(script|style)( .*)*?>\"\n            - special: \"&[^;[[:space:]]]*;\"\n\n            - identifier: \"(alt|bgcolor|class|height|href|id|label|longdesc|name|on(click|focus|load|mouseover)|placeholder|size|span|src|style|target|type|value|width)=\"\n            - symbol: \"[:=]\"\n            - constant.string: \"\\\"[^\\\"]*\\\"\"\n            - constant.number: \"(?i)#[0-9a-fA-F]{6,6}\"\n\n            - symbol.tag: \"<|>\"\n            - constant.string.url: \"(ftp(s)?|http(s)?|git|chrome)://[^ \t]+\"\n            - comment: \"<!--.+?-->\"\n            #- preproc: \"<!DOCTYPE.+?>\"\n            - comment.block:\n                start: \"<!\\\\-\\\\-\"\n                end: \"\\\\-\\\\->\"\n                rules: []\n\n            # Bootstrap\n            - symbol.tag.extended: \"(?i)<[/]?(b-alert|b-aspect|b-avatar|b-badge|b-icon|b-breadcrumb|b-button-group|b-button-toolbar|b-button|b-calendar|b-card-text|b-card-input|b-card|b-carousel-slide|b-carousel|b-collapse|b-dropdown|b-dropdown-item|b-dropdown-divider|b-embed|b-form-checkbox-group|b-form-checkbox|b-form-datepicker|b-form-file|b-form-group|b-form-input|b-form-radio|b-form-rating|b-form-select|b-form-spinbutton|b-form-tags|b-form-textarea|b-form|b-form-timepicker|b-img-lazy|b-img|b-input-group|b-jumbotron|b-input|b-container|b-row|b-col|b-link|b-list-group|b-list-group-item|b-media|b-modal|b-nav|b-nav-item|b-nav-item-dropdown|b-nav-text|b-nav-form|b-navbar|b-navbar-brand|b-navbar-toggle|b-navbar-nav|b-overlay|b-pagination|b-pagination-nav|b-popover|b-progress|b-progress-bar|b-sidebar|b-skeleton-wrapper|b-skeleton|b-spinner|b-table|b-table-lite|b-table-simple|b-tabs|b-tab|b-time|b-toast|b-tooltip)\\\\b\"\n            - identifier: \"(variant|title|show|shadow|icon|align-h|align-v|label-for|@submit|tag|img-alt|img-src|data-toggle|data-target|aria-controls|aria-expanded|aria-label|aria-disabled|tabindex|:interval|background|img-width|img-height|@sliding-start|@sliding-end|cols|header|@reset)=\"\n            - symbol: \"[:=]\"\n            # Vue\n            - symbol.tag.extended: \"(?i)<[/]?(component|transition|transition-group|keep-alive|slot)\\\\b\"\n            - identifier: \"(v-text|v-html|v-show|v-if|v-else|v-else-if|v-for|v-on|v-bind|v-model|v-slot|v-pre|v-cloak|v-once|key|ref|is|@click)=\"\n            - symbol: \"[:=]\"\n            # Vue-router\n            - symbol.tag.extended: \"(?i)<[/]?(router-link|router-view)\\\\b\"\n            - identifier: \"(to|v-slot)=\"\n            - symbol: \"[:=]\"\n\n\n    - default:\n        start: \"<script>\"\n        end: \"</script>\"\n        limit-group: symbol.tag\n        rules:\n            - include: \"javascript\"\n\n    - default:\n        start: \"<script[ ]+lang=(\\\"ts\\\"|'ts')>\"\n        end: \"</script>\"\n        rules:\n            - include: \"typescript\"\n\n    - default:\n        start: \"<style.*?>\"\n        end: \"</style.*?>\"\n        limit-group: symbol.tag\n        rules:\n            - include: \"css\"\n"
  },
  {
    "path": "runtime/syntax/xml.yaml",
    "content": "filetype: xml\n\ndetect:\n    filename: \"\\\\.(xml|sgml?|rng|svg|plist)$\"\n    header: \"<\\\\?xml.*\\\\?>\"\n\nrules:\n    - preproc:\n        start: \"<!DOCTYPE\"\n        end: \"[/]?>\"\n        rules: []\n\n    - comment:\n        start: \"<!--\"\n        end: \"-->\"\n        rules: []\n\n    - symbol.tag:\n        start: \"<\\\\??\"\n        end: \"\\\\??>\"\n        rules:\n            - identifier:\n                start: \" \"\n                end: \"=\"\n                rules: []\n            - constant.string:\n                start: \"\\\"\"\n                end: \"\\\"\"\n                skip: \"\\\\\\\\.\"\n                rules:\n                    - constant.specialChar: \"\\\\\\\\.\"\n            - constant.string:\n                start: \"'\"\n                end: \"'\"\n                skip: \"\\\\\\\\.\"\n                rules:\n                    - constant.specialChar: \"\\\\\\\\.\"\n"
  },
  {
    "path": "runtime/syntax/xresources.yaml",
    "content": "filetype: xresources\n\ndetect:\n    filename: \"X(defaults|resources)$\"\n\nrules:\n    - special: \"^[[:alnum:]]+\\\\*\"\n    - identifier.var: \"\\\\*[[:alnum:]]+\\\\:\"\n    - constant.number: \"\\\\b[0-9]+\\\\b\"\n    - symbol.operator: \"[*:=]\"\n    - constant.bool: \"\\\\b(true|false)\\\\b\"\n    - comment: \"(^|[[:space:]])!([^{].*)?$\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n    - indent-char: \"\t+ +| +\t+\"\n"
  },
  {
    "path": "runtime/syntax/yaml.yaml",
    "content": "filetype: yaml\n\ndetect:\n    filename: \"\\\\.ya?ml$\"\n    header: \"%YAML\"\n\nrules:\n    - type: \"(^| )!!(binary|bool|float|int|map|null|omap|seq|set|str) \"\n    - constant:  \"\\\\b(YES|yes|Y|y|ON|on|TRUE|True|true|NO|no|N|n|OFF|off|FALSE|False|false)\\\\b\"\n    - statement: \"(:[[:space:]]|\\\\[|\\\\]|:[[:space:]]+[|>]|^[[:space:]]*- )\"\n    - identifier: \"[[:space:]][\\\\*&][A-Za-z0-9]+\"\n    - type: \"[-.\\\\w]+:\"\n    - statement: \":\"\n    - special:  \"(^---|^\\\\.\\\\.\\\\.|^%YAML|^%TAG)\"\n\n    - constant.string:\n        start: \"(^| )\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"(^| )'\"\n        end: \"'\"\n        skip: \"(\\\\\\\\.)|('')\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - comment:\n        start: \"#\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/yum.yaml",
    "content": "filetype: yum\n\ndetect:\n    filename: \"\\\\.repo$|yum.*\\\\.conf$\"\n\nrules:\n    - identifier: \"^[[:space:]]*[^=]*=\"\n    - constant.specialChar: \"^[[:space:]]*\\\\[.*\\\\]$\"\n    - statement: \"\\\\$(releasever|arch|basearch|uuid|YUM[0-9])\"\n    - comment: \"(^|[[:space:]])#([^{].*)?$\"\n    - indent-char.whitespace: \"[[:space:]]+$\"\n    - indent-char: \"\t+ +| +\t+\"\n"
  },
  {
    "path": "runtime/syntax/zig.yaml",
    "content": "filetype: zig\n\ndetect:\n    filename: \"\\\\.z(ig|on)$\"\n\nrules:\n      # Reserved words\n    - statement: \"\\\\b(addrspace|align|allowzero|and|asm|async|await|break|callconv|catch|comptime|const|continue|defer|else|errdefer|error|export|extern|fn|for|if|inline|noalias|noinline|nosuspend|or|orelse|packed|pub|resume|return|linksection|suspend|switch|test|threadlocal|try|unreachable|usingnamespace|var|volatile|while)\\\\b\"\n      # builtin functions\n    - special: \"@[a-zA-Z_]+\"\n      # Primitive Types\n    - type: \"\\\\b(anyframe|anytype|anyerror|anyopaque|bool|comptime_int|comptime_float|enum|f(16|32|64|80|128)|i(8|16|32|64|128)|isize|noreturn|opaque|struct|type|union|u(8|16|32|64|128)|usize|void)\\\\b\"\n    - type: \"\\\\b(c_u?(short|int|long(long)?)|c_longdouble|c_void)\\\\b\"\n\n     # Operators\n    - symbol.operator: \"[-!|=;%.+^*:&?<>~]\"\n\n     # Parenthesis\n    - symbol.brackets: \"[(){}]|\\\\[|\\\\]\"\n\n     # Constants\n    - constant: \"\\\\b(null|undefined)\\\\b\"\n    - constant.number: \"\\\\b(0b[01_]+|0o[0-7_]+|[0-9_]+|0x[a-fA-F0-9_]+)\\\\b\"\n    - constant.bool: \"\\\\b(true|false)\\\\b\"\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([nrt\\\\\\\\'\\\"]|x[a-fA-F0-9]{2}|u{[a-fA-F0-9]+})\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - error: \"..+\"\n            - constant.specialChar: \"\\\\\\\\([nrt\\\\\\\\'\\\"]|x[a-fA-F0-9]{2}|u{[a-fA-F0-9]+})\"\n\n    - constant.string:\n        start: \"\\\\\\\\\\\\\\\\\"\n        end: \"$\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([nrt\\\\\\\\'\\\"]|x[a-fA-F0-9]{2}|u{[a-fA-F0-9]+})\"\n\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/zscript.yaml",
    "content": "filetype: zscript\n# Loosely based on the csharp.yaml definition\n# (?i) on everything because ZScript isn't case sensitive\n\ndetect:\n    filename: \"(?i)\\\\.z(c|sc)$\"\n\nrules:\n\n    # ZScript only has one preprocessor directive and a required engine version declaration\n    - preproc: \"(?i)#include\"\n    - preproc: \"(?i)version\"\n\n    # State labels (\"goto\" word overridden by state logic rule below)\n    - symbol.tag: \"(?i)[a-z0-9.]+:\"\n    - symbol.tag: \"(?i)goto [a-z0-9]+[\\\\+0-9]*\"\n\n    # Classes\n    - identifier.class: \"(?i)class +[a-z0-9_]+ *((:) +[a-z0-9.]+)?\"\n\n    # Functions (open paren overridden by symbol.brackets rule because perl regex apparently doesn't support postive lookahead)\n    - identifier: \"(?i)[\\\\.]*[a-z0-9_]+[ ]*[(]+\"\n\n    # Variable types\n    - type: \"(?i)\\\\b(actor|object|vector2|vector3|name|string|color|sound|void|double|bool|int|float|float64|uint8|uint16|uint|int8|int16|TextureID|SpriteID|Array|voidptr|short|action|state|statelabel)\\\\b\"\n\n    # Keywords\n    - statement: \"(?i)\\\\b(class|default|private|static|native|return|if|else|for|while|do|deprecated|null|readonly|true|false|struct|extend|clearscope|vararg|ui|play|virtual|virtualscope|meta|Property|in|out|states|override|super|is|let|const|replaces|protected|self|abstract|enum|switch|case)\\\\b\"\n\n    # State logic keywords\n    - special: \"(?i)\\\\b(goto|loop|stop|break|continue|fail)\\\\b\"\n\n    # Symbols\n    - symbol.operator: \"[\\\\-+/*=<>?:!~%&|]\"\n    - symbol.brackets: \"[(){}]|\\\\[|\\\\]\"\n\n    # Constants\n    - constant.bool: \"(?i)(\\\\b(true|false)\\\\b|NULL)\"\n    - constant.number: \"(?i)\\\\b([0-9][.]*[0-9]*)+?\\\\b\"\n    - constant.number: \"(?i)\\\\b(0x[A-Fa-f0-9_]+)?\\\\b\"\n    - constant.number: \"(?i)\\\\b(0b[0-1_]+)[FL]?\\\\b\"\n    #- constant.number: \"(?i)\\\\b(([0-9][.]*[0-9]*)+|0x[A-Fa-f0-9_]+|0b[0-1_]+)[FL]?\\\\b\"\n\n    # Strings\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([btnfr]|'|\\\\\\\"|\\\\\\\\)\"\n            - constant.specialChar: \"\\\\\\\\u[A-Fa-f0-9]{4}\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\([btnfr]|'|\\\\\\\"|\\\\\\\\)\"\n            - constant.specialChar: \"\\\\\\\\u[A-Fa-f0-9]{4}\"\n\n    # Comments\n    - comment:\n        start: \"//\"\n        end: \"$\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n\n    - comment:\n        start: \"/\\\\*\"\n        end: \"\\\\*/\"\n        rules:\n            - todo: \"(TODO|XXX|FIXME):?\"\n"
  },
  {
    "path": "runtime/syntax/zsh.yaml",
    "content": "filetype: zsh\n\ndetect:\n    filename: \"(\\\\.zsh$|\\\\.?(zshenv|zprofile|zshrc|zlogin|zlogout)$)\"\n    header: \"^#!.*/(env +)?zsh( |$)\"\n\nrules:\n    ## Numbers\n    - constant.number: \"\\\\b[0-9]+\\\\b\"\n\n      ## Conditionals and control flow\n    - statement: \"\\\\b(always|break|bye|case|continue|disown|do|done|elif|else|esac|exit|fi|for|function|if|in|local|read|return|select|shift|then|time|until|while)\\\\b\"\n\n    - statement: \"(\\\\{|\\\\}|\\\\(|\\\\)|\\\\;|\\\\]|\\\\[|`|\\\\\\\\|\\\\$|<|>|!|=|&|\\\\|)\"\n      ## Conditional flags\n    - special: \"-[Ldefgruwx]\\\\b\"\n    - special: \"-(eq|ne|gt|lt|ge|le|s|n|z)\\\\b\"\n\n      ## Bash-inherited\n    - statement: \"\\\\b((un)?alias|bindkey|builtin|cd|declare|eval|exec|export|jobs|let|popd|pushd|set|source|typeset|umask|unset)\\\\b\"\n      ## ZSH-specific\n    - type: \"\\\\b(add-zsh-hook|autoload|chdir|compinit|dirs|(dis|en)able|echotc|emulate|print|prompt(init)?|(un)?setopt|zle|zmodload|zstyle|whence)\\\\b\"\n\n      ## Common linux commands\n    - statement: \"\\\\b((g|ig)?awk|find|\\\\w{0,4}grep|kill|killall|\\\\w{0,4}less|make|pkill|sed|tar)\\\\b\"\n\n      ## Coreutils commands\n    - statement: \"\\\\b(base64|basename|cat|chcon|chgrp|chmod|chown|chroot|cksum|comm|cp|csplit|cut|date|dd|df|dir|dircolors|dirname|du|echo|env|expand|expr|factor|false|fmt|fold|head|hostid|id|install|join|link|ln|logname|ls|md5sum|mkdir|mkfifo|mknod|mktemp|mv|nice|nl|nohup|nproc|numfmt|od|paste|pathchk|pinky|pr|printenv|printf|ptx|pwd|readlink|realpath|rm|rmdir|runcon|seq|(sha1|sha224|sha256|sha384|sha512)sum|shred|shuf|sleep|sort|split|stat|stdbuf|stty|sum|sync|tac|tail|tee|test|timeout|touch|tr|true|truncate|tsort|tty|uname|unexpand|uniq|unlink|users|vdir|wc|who|whoami|yes)\\\\b\"\n\n      ## Function definition\n    - identifier: \"^\\\\s+(function\\\\s+)[0-9A-Z_]+\\\\s+\\\\(\\\\)\" # (i)\n\n      ## Variables\n    - identifier: \"\\\\$\\\\{?[0-9A-Z_!@#$*?-]+\\\\}?\" #(i)\n\n    - constant.string:\n        start: \"\\\"\"\n        end: \"\\\"\"\n        skip: \"\\\\\\\\.\"\n        rules:\n            - constant.specialChar: \"\\\\\\\\.\"\n\n    - constant.string:\n        start: \"'\"\n        end: \"'\"\n        rules: []\n\n    - comment:\n        start: \"(^|\\\\s)#\"\n        end: \"$\"\n        rules: []\n\n"
  },
  {
    "path": "snapcraft.yaml",
    "content": "name: micro\nsummary: A modern and intuitive terminal-based text editor\ndescription: |\n  Micro is a terminal-based text editor that aims to be easy to use and\n  intuitive, while also taking advantage of the full capabilities of modern\n  terminals.\nconfinement: classic\nadopt-info: micro\nbase: core20\n\napps:\n  micro:\n    command: bin/micro\n\nparts:\n  micro:\n    source: .\n    source-type: git\n    plugin: go\n    build-packages: [make]\n    build-attributes: [no-patchelf]\n    override-pull: |\n      snapcraftctl pull\n      version=\"$(go run $SNAPCRAFT_PART_SRC/tools/build-version.go)\"\n      [ -n \"$(echo $version | grep \"dev\")\" ] && grade=devel || grade=stable\n      snapcraftctl set-version \"$version\"\n      snapcraftctl set-grade \"$grade\"\n    override-build: |\n      make build-tags\n      mkdir $SNAPCRAFT_PART_INSTALL/bin\n      mv ./micro $SNAPCRAFT_PART_INSTALL/bin/\n"
  },
  {
    "path": "tools/build-date.go",
    "content": "//go:build ignore\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"time\"\n)\n\nfunc main() {\n\tvar buildTime time.Time\n\tepoch := os.Getenv(\"SOURCE_DATE_EPOCH\")\n\tif epoch != \"\" {\n\t\ti, err := strconv.Atoi(epoch)\n\t\tif err != nil {\n\t\t\tfmt.Errorf(\"SOURCE_DATE_EPOCH is not a valid integer\")\n\t\t\tos.Exit(1)\n\t\t}\n\t\tbuildTime = time.Unix(int64(i), 0)\n\t} else {\n\t\tbuildTime = time.Now().Local()\n\t}\n\tfmt.Println(buildTime.Format(\"January 02, 2006\"))\n}\n"
  },
  {
    "path": "tools/build-version.go",
    "content": "//go:build ignore\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/blang/semver\"\n)\n\nfunc getTag(match ...string) (string, *semver.PRVersion) {\n\targs := append([]string{\n\t\t\"describe\", \"--tags\",\n\t}, match...)\n\tif tag, err := exec.Command(\"git\", args...).Output(); err != nil {\n\t\treturn \"\", nil\n\t} else {\n\t\ttagParts := strings.Split(string(tag), \"-\")\n\t\tif len(tagParts) == 3 {\n\t\t\tahead, err := semver.NewPRVersion(tagParts[1])\n\t\t\tif err == nil {\n\t\t\t\treturn tagParts[0], &ahead\n\t\t\t}\n\t\t\tlog.Printf(\"semver.NewPRVersion(%s): %v\", tagParts[1], err)\n\t\t} else if len(tagParts) == 4 {\n\t\t\tahead, err := semver.NewPRVersion(tagParts[2])\n\t\t\tif err == nil {\n\t\t\t\treturn tagParts[0] + \"-\" + tagParts[1], &ahead\n\t\t\t}\n\t\t\tlog.Printf(\"semver.NewPRVersion(%s): %v\", tagParts[2], err)\n\t\t}\n\n\t\treturn string(tag), nil\n\t}\n}\n\nfunc main() {\n\t// Find the last vX.X.X Tag and get how many builds we are ahead of it.\n\tversionStr, ahead := getTag(\"--match\", \"v*\")\n\tversion, err := semver.ParseTolerant(versionStr)\n\tif err != nil {\n\t\t// no version tag found so just return what ever we can find.\n\t\tlog.Printf(\"semver.ParseTolerant(%s): %v\", versionStr, err)\n\t\tfmt.Println(\"0.0.0-unknown\")\n\t\treturn\n\t}\n\tif ahead == nil {\n\t\t// Seems that we are going to build a release.\n\t\t// So the version number should already be correct.\n\t\tfmt.Println(version.String())\n\t\treturn\n\t}\n\n\t// Get the tag of the current revision.\n\ttag, _ := getTag(\"--exact-match\")\n\n\t// If we don't have any tag assume \"dev\"\n\tif tag == \"\" || strings.HasPrefix(tag, \"nightly\") {\n\t\ttag = \"dev\"\n\t}\n\t// Get the most likely next version:\n\tif !strings.Contains(version.String(), \"rc\") {\n\t\tversion.Patch = version.Patch + 1\n\t}\n\n\tif pr, err := semver.NewPRVersion(tag); err == nil {\n\t\t// append the tag as pre-release name\n\t\tversion.Pre = append(version.Pre, pr)\n\t} else {\n\t\tlog.Printf(\"semver.NewPRVersion(%s): %v\", tag, err)\n\t}\n\n\t// append how many commits we are ahead of the last release\n\tversion.Pre = append(version.Pre, *ahead)\n\n\tfmt.Println(version.String())\n}\n"
  },
  {
    "path": "tools/compile-linux.sh",
    "content": "cd ..\n\nmkdir -p binaries\nmkdir -p micro-$1\n\ncp LICENSE micro-$1\ncp README.md micro-$1\ncp LICENSE-THIRD-PARTY micro-$1\n\nHASH=\"$(git rev-parse --short HEAD)\"\nVERSION=\"$(go run tools/build-version.go)\"\nDATE=\"$(go run tools/build-date.go)\"\nADDITIONAL_GO_LINKER_FLAGS=\"$(go run tools/info-plist.go $VERSION)\"\n\necho \"Linux 64\"\nGOOS=linux GOARCH=amd64 go build -ldflags \"-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$DATE'\" -o micro-$1/micro ./cmd/micro\ntar -czf micro-$1-linux64.tar.gz micro-$1\nmv micro-$1-linux64.tar.gz binaries\necho \"Linux 32\"\nGOOS=linux GOARCH=386 go build -ldflags \"-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$DATE'\" -o micro-$1/micro ./cmd/micro\ntar -czf micro-$1-linux32.tar.gz micro-$1\nmv micro-$1-linux32.tar.gz binaries\necho \"Linux arm 32\"\nGOOS=linux GOARM=6 GOARCH=arm go build -ldflags \"-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$DATE'\" -o micro-$1/micro ./cmd/micro\ntar -czf micro-$1-linux-arm.tar.gz micro-$1\nmv micro-$1-linux-arm.tar.gz binaries\necho \"Linux arm 64\"\nGOOS=linux GOARCH=arm64 go build -ldflags \"-s -w -X main.Version=$1 -X main.CommitHash=$HASH -X 'main.CompileDate=$DATE'\" -o micro-$1/micro ./cmd/micro\ntar -czf micro-$1-linux-arm64.tar.gz micro-$1\nmv micro-$1-linux-arm64.tar.gz binaries\n\nrm -rf micro-$1\n"
  },
  {
    "path": "tools/cross-compile.sh",
    "content": "#!/bin/sh\n\nset -e\n\nVERSION=\"$1\"\nif [ -z \"$VERSION\" ]; then\n\tVERSION=\"$(go run tools/build-version.go)\"\nfi\n\nmkdir -p binaries\nmkdir -p micro-$VERSION\n\ncp LICENSE micro-$VERSION\ncp README.md micro-$VERSION\ncp LICENSE-THIRD-PARTY micro-$VERSION\ncp assets/packaging/micro.1 micro-$VERSION\ncp assets/packaging/micro.desktop micro-$VERSION\ncp assets/micro-logo-mark.svg micro-$VERSION/micro.svg\n\ncreate_artefact_generic()\n{\n\tmv micro micro-$VERSION/\n\ttar -czf micro-$VERSION-$1.tar.gz micro-$VERSION\n\tsha256sum micro-$VERSION-$1.tar.gz > micro-$VERSION-$1.tar.gz.sha\n\tmv micro-$VERSION-$1.* binaries\n\trm micro-$VERSION/micro\n}\n\ncreate_artefact_windows()\n{\n\tmv micro.exe micro-$VERSION/\n\tzip -r -q -T micro-$VERSION-$1.zip micro-$VERSION\n\tsha256sum micro-$VERSION-$1.zip > micro-$VERSION-$1.zip.sha\n\tmv micro-$VERSION-$1.* binaries\n\trm micro-$VERSION/micro.exe\n}\n\n# Mac\necho \"OSX 64\"\nGOOS=darwin GOARCH=amd64 make build\ncreate_artefact_generic \"osx\"\n\n# Mac ARM64\necho \"MacOS ARM64\"\nGOOS=darwin GOARCH=arm64 make build\ncreate_artefact_generic \"macos-arm64\"\n\n# Linux\necho \"Linux 64\"\nGOOS=linux GOARCH=amd64 make build\nif ./tools/package-deb.sh $VERSION; then\n\tsha256sum micro-$VERSION-amd64.deb > micro-$VERSION-amd64.deb.sha\n\tmv micro-$VERSION-amd64.* binaries\nfi\ncreate_artefact_generic \"linux64\"\n\necho \"Linux 32\"\nGOOS=linux GOARCH=386 make build\ncreate_artefact_generic \"linux32\"\n\necho \"Linux ARM 32\"\nGOOS=linux GOARM=6 GOARCH=arm make build\ncreate_artefact_generic \"linux-arm\"\n\necho \"Linux ARM 64\"\nGOOS=linux GOARCH=arm64 make build\ncreate_artefact_generic \"linux-arm64\"\n\n# Solaris\necho \"Solaris 64\"\nGOOS=solaris GOARCH=amd64 make build\ncreate_artefact_generic \"solaris64\"\n\n# Illumos\necho \"Illumos 64\"\nGOOS=illumos GOARCH=amd64 make build\ncreate_artefact_generic \"illumos64\"\n\n# NetBSD\necho \"NetBSD 64\"\nGOOS=netbsd GOARCH=amd64 make build\ncreate_artefact_generic \"netbsd64\"\n\necho \"NetBSD 32\"\nGOOS=netbsd GOARCH=386 make build\ncreate_artefact_generic \"netbsd32\"\n\n# OpenBSD\necho \"OpenBSD 64\"\nGOOS=openbsd GOARCH=amd64 make build\ncreate_artefact_generic \"openbsd64\"\n\necho \"OpenBSD 32\"\nGOOS=openbsd GOARCH=386 make build\ncreate_artefact_generic \"openbsd32\"\n\n# FreeBSD\necho \"FreeBSD 64\"\nGOOS=freebsd GOARCH=amd64 make build\ncreate_artefact_generic \"freebsd64\"\n\necho \"FreeBSD 32\"\nGOOS=freebsd GOARCH=386 make build\ncreate_artefact_generic \"freebsd32\"\n\n# Windows\necho \"Windows 64\"\nGOOS=windows GOARCH=amd64 make build\ncreate_artefact_windows \"win64\"\n\necho \"Windows ARM 64\"\nGOOS=windows GOARCH=arm64 make build\ncreate_artefact_windows \"win-arm64\"\n\necho \"Windows 32\"\nGOOS=windows GOARCH=386 make build\ncreate_artefact_windows \"win32\"\n\nrm -rf micro-$VERSION\n"
  },
  {
    "path": "tools/info-plist.go",
    "content": "//go:build ignore\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"runtime\"\n)\n\nfunc main() {\n\tif runtime.GOOS != \"darwin\" {\n\t\treturn\n\t}\n\tif len(os.Args) != 3 {\n\t\tpanic(\"missing arguments\")\n\t}\n\tif os.Args[1] != \"darwin\" {\n\t\treturn\n\t}\n\trawInfoPlist := `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\t<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\t<plist version=\"1.0\">\n\t<dict>\n\t\t<key>CFBundleIdentifier</key>\n\t\t<string>io.github.micro-editor</string>\n\t\t<key>CFBundleName</key>\n\t\t<string>micro</string>\n\t\t<key>CFBundleInfoDictionaryVersion</key>\n\t\t<string>6.0</string>\n\t\t<key>CFBundlePackageType</key>\n\t\t<string>APPL</string>\n\t\t<key>CFBundleShortVersionString</key>\n\t\t<string>` + os.Args[2] + `</string>\n\t</dict>\n\t</plist>\n\t`\n\n\terr := os.WriteFile(\"/tmp/micro-info.plist\", []byte(rawInfoPlist), 0666)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Println(\"-linkmode external -extldflags -Wl,-sectcreate,__TEXT,__info_plist,/tmp/micro-info.plist\")\n}\n"
  },
  {
    "path": "tools/nightly-release.sh",
    "content": "# This script updates the nightly release on Github for micro\n# Must be run from inside the micro git repository\n\ncommitID=$(git rev-parse --short HEAD)\n\ngo run remove-nightly-assets.go\n\necho \"Cross compiling binaries\"\n./cross-compile.sh $1\nmv ../binaries .\n\nMESSAGE=$'Nightly build\\n\\nAutogenerated nightly build of micro'\n\necho \"Updating release\"\nhub release edit nightly \\\n    --prerelease \\\n    --draft=false \\\n    --message \"$MESSAGE (please DISREGARD the creation date of this Github release). Assets uploaded on $(date) for commit $commitID.\" \\\n    --attach \"binaries/micro-$1-osx.tar.gz\" \\\n    --attach \"binaries/micro-$1-macos-arm64.tar.gz\" \\\n    --attach \"binaries/micro-$1-linux64.tar.gz\" \\\n    --attach \"binaries/micro-$1-linux64-static.tar.gz\" \\\n    --attach \"binaries/micro-$1-amd64.deb\" \\\n    --attach \"binaries/micro-$1-linux32.tar.gz\" \\\n    --attach \"binaries/micro-$1-linux-arm.tar.gz\" \\\n    --attach \"binaries/micro-$1-linux-arm64.tar.gz\" \\\n    --attach \"binaries/micro-$1-freebsd64.tar.gz\" \\\n    --attach \"binaries/micro-$1-freebsd32.tar.gz\" \\\n    --attach \"binaries/micro-$1-openbsd64.tar.gz\" \\\n    --attach \"binaries/micro-$1-openbsd32.tar.gz\" \\\n    --attach \"binaries/micro-$1-netbsd64.tar.gz\" \\\n    --attach \"binaries/micro-$1-netbsd32.tar.gz\" \\\n    --attach \"binaries/micro-$1-win64.zip\" \\\n    --attach \"binaries/micro-$1-win32.zip\"\n"
  },
  {
    "path": "tools/package-deb.sh",
    "content": "command -v fpm > /dev/null && fpm -s dir -t deb -p micro-$1-amd64.deb --name micro --license mit --version $1 --deb-recommends xclip --description \"A modern and intuitive terminal-based text editor\" --after-install ./assets/packaging/deb/micro.postinst --before-remove ./assets/packaging/deb/micro.prerm ./micro=/usr/bin/micro ./assets/packaging/micro.1=/usr/share/man/man1/micro.1\n"
  },
  {
    "path": "tools/pre-release.sh",
    "content": "# This script creates releases on Github for micro\n# You must have the correct Github access token to run this script\n\n# $1 is the title, $2 is the description\n\ncommitID=$(git rev-parse HEAD)\ntag=\"v$1\"\n\necho \"Creating tag\"\ngit tag $tag $commitID\nhub push --tags\n\necho \"Cross compiling binaries\"\n./cross-compile.sh $1\nmv ../binaries .\n\nNL=$'\\n'\n\necho \"Creating new release\"\nhub release create $tag \\\n    --prerelease \\\n    --message \"$1${NL}${NL}$2\" \\\n    --attach \"binaries/micro-$1-osx.tar.gz\" \\\n    --attach \"binaries/micro-$1-macos-arm64.tar.gz\" \\\n    --attach \"binaries/micro-$1-linux64.tar.gz\" \\\n    --attach \"binaries/micro-$1-linux64-static.tar.gz\" \\\n    --attach \"binaries/micro-$1-amd64.deb\" \\\n    --attach \"binaries/micro-$1-linux32.tar.gz\" \\\n    --attach \"binaries/micro-$1-linux-arm.tar.gz\" \\\n    --attach \"binaries/micro-$1-linux-arm64.tar.gz\" \\\n    --attach \"binaries/micro-$1-freebsd64.tar.gz\" \\\n    --attach \"binaries/micro-$1-freebsd32.tar.gz\" \\\n    --attach \"binaries/micro-$1-openbsd64.tar.gz\" \\\n    --attach \"binaries/micro-$1-openbsd32.tar.gz\" \\\n    --attach \"binaries/micro-$1-netbsd64.tar.gz\" \\\n    --attach \"binaries/micro-$1-netbsd32.tar.gz\" \\\n    --attach \"binaries/micro-$1-win64.zip\" \\\n    --attach \"binaries/micro-$1-win32.zip\"\n"
  },
  {
    "path": "tools/release.sh",
    "content": "# This script creates releases on Github for micro\n# You must have the correct Github access token to run this script\n\n# $1 is the title, $2 is the description\n\ncommitID=$(git rev-parse HEAD)\ntag=\"v$1\"\n\necho \"Creating tag\"\ngit tag $tag $commitID\nhub push --tags\n\nNL=$'\\n'\n\necho \"Cross compiling binaries\"\n./cross-compile.sh $1\nmv ../binaries .\n\necho \"Creating new release\"\nhub release create $tag \\\n    --message \"$1${NL}${NL}$2\" \\\n    --attach \"binaries/micro-$1-osx.tar.gz\" \\\n    --attach \"binaries/micro-$1-macos-arm64.tar.gz\" \\\n    --attach \"binaries/micro-$1-linux64.tar.gz\" \\\n    --attach \"binaries/micro-$1-linux64-static.tar.gz\" \\\n    --attach \"binaries/micro-$1-amd64.deb\" \\\n    --attach \"binaries/micro-$1-linux32.tar.gz\" \\\n    --attach \"binaries/micro-$1-linux-arm.tar.gz\" \\\n    --attach \"binaries/micro-$1-linux-arm64.tar.gz\" \\\n    --attach \"binaries/micro-$1-freebsd64.tar.gz\" \\\n    --attach \"binaries/micro-$1-freebsd32.tar.gz\" \\\n    --attach \"binaries/micro-$1-openbsd64.tar.gz\" \\\n    --attach \"binaries/micro-$1-openbsd32.tar.gz\" \\\n    --attach \"binaries/micro-$1-netbsd64.tar.gz\" \\\n    --attach \"binaries/micro-$1-netbsd32.tar.gz\" \\\n    --attach \"binaries/micro-$1-win64.zip\" \\\n    --attach \"binaries/micro-$1-win32.zip\"\n"
  },
  {
    "path": "tools/remove-nightly-assets.go",
    "content": "//go:build ignore\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/micro-editor/json5\"\n)\n\nfunc main() {\n\tresp, err := http.Get(\"https://api.github.com/repos/micro-editor/micro/releases\")\n\tif err != nil {\n\t\tfmt.Println(err.Error())\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\tbody, err := io.ReadAll(resp.Body)\n\n\tvar data any\n\n\terr = json5.Unmarshal(body, &data)\n\n\tfor _, val := range data.([]any) {\n\t\tm := val.(map[string]any)\n\t\treleaseName := m[\"name\"].(string)\n\t\tassets := m[\"assets\"].([]any)\n\t\tfor _, asset := range assets {\n\t\t\tassetInfo := asset.(map[string]any)\n\t\t\turl := assetInfo[\"url\"].(string)\n\t\t\tif strings.Contains(strings.ToLower(releaseName), \"nightly\") {\n\t\t\t\tcmd := exec.Command(\"hub\", \"api\", \"-X\", \"DELETE\", url)\n\t\t\t\tcmd.Run()\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "tools/testgen.go",
    "content": "//go:build ignore\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/robertkrimen/otto/ast\"\n\t\"github.com/robertkrimen/otto/parser\"\n)\n\ntype walker struct {\n\tnodes []ast.Node\n}\n\nfunc (w *walker) Enter(node ast.Node) ast.Visitor {\n\tw.nodes = append(w.nodes, node)\n\treturn w\n}\n\nfunc (w *walker) Exit(node ast.Node) {\n}\n\nfunc getAllNodes(node ast.Node) []ast.Node {\n\tw := &walker{}\n\tast.Walk(w, node)\n\treturn w.nodes\n}\n\nfunc getCalls(node ast.Node, name string) []*ast.CallExpression {\n\tnodes := []*ast.CallExpression{}\n\tfor _, n := range getAllNodes(node) {\n\t\tif ce, ok := n.(*ast.CallExpression); ok {\n\t\t\tvar calleeName string\n\t\t\tswitch callee := ce.Callee.(type) {\n\t\t\tcase *ast.Identifier:\n\t\t\t\tcalleeName = callee.Name\n\t\t\tcase *ast.DotExpression:\n\t\t\t\tcalleeName = callee.Identifier.Name\n\t\t\tdefault:\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif calleeName == name {\n\t\t\t\tnodes = append(nodes, ce)\n\t\t\t}\n\t\t}\n\t}\n\treturn nodes\n}\n\nfunc getPropertyValue(node ast.Node, key string) ast.Expression {\n\tfor _, p := range node.(*ast.ObjectLiteral).Value {\n\t\tif p.Key == key {\n\t\t\treturn p.Value\n\t\t}\n\t}\n\treturn nil\n}\n\ntype operation struct {\n\tstartLine   int\n\tstartColumn int\n\tendLine     int\n\tendColumn   int\n\ttext        []string\n}\n\ntype check struct {\n\tbefore     []string\n\toperations []operation\n\tafter      []string\n}\n\ntype test struct {\n\tdescription string\n\tchecks      []check\n}\n\nfunc stringSliceToGoSource(slice []string) string {\n\tvar b strings.Builder\n\tb.WriteString(\"[]string{\\n\")\n\tfor _, s := range slice {\n\t\tb.WriteString(fmt.Sprintf(\"%#v,\\n\", s))\n\t}\n\tb.WriteString(\"}\")\n\treturn b.String()\n}\n\nfunc testToGoTest(test test, name string) string {\n\tvar b strings.Builder\n\n\tb.WriteString(\"func Test\")\n\tb.WriteString(name)\n\tb.WriteString(\"(t *testing.T) {\\n\")\n\n\tfor _, c := range test.checks {\n\t\tb.WriteString(\"check(\\n\")\n\t\tb.WriteString(\"t,\\n\")\n\t\tb.WriteString(fmt.Sprintf(\"%v,\\n\", stringSliceToGoSource(c.before)))\n\t\tb.WriteString(\"[]operation{\\n\")\n\t\tfor _, op := range c.operations {\n\t\t\tb.WriteString(\"operation{\\n\")\n\t\t\tb.WriteString(fmt.Sprintf(\"start: Loc{%v, %v},\\n\", op.startColumn, op.startLine))\n\t\t\tb.WriteString(fmt.Sprintf(\"end: Loc{%v, %v},\\n\", op.endColumn, op.endLine))\n\t\t\tb.WriteString(fmt.Sprintf(\"text: %v,\\n\", stringSliceToGoSource(op.text)))\n\t\t\tb.WriteString(\"},\\n\")\n\t\t}\n\t\tb.WriteString(\"},\\n\")\n\t\tb.WriteString(fmt.Sprintf(\"%v,\\n\", stringSliceToGoSource(c.after)))\n\t\tb.WriteString(\")\\n\")\n\t}\n\n\tb.WriteString(\"}\\n\")\n\n\treturn b.String()\n}\n\nfunc nodeToStringSlice(node ast.Node) []string {\n\tvar result []string\n\tfor _, s := range node.(*ast.ArrayLiteral).Value {\n\t\tresult = append(result, s.(*ast.StringLiteral).Value)\n\t}\n\treturn result\n}\n\nfunc nodeToStringSlice2(node ast.Node) []string {\n\tvar result []string\n\tfor _, o := range node.(*ast.ArrayLiteral).Value {\n\t\tresult = append(result, getPropertyValue(o, \"text\").(*ast.StringLiteral).Value)\n\t}\n\treturn result\n}\n\nfunc nodeToInt(node ast.Node) int {\n\treturn int(node.(*ast.NumberLiteral).Value.(int64))\n}\n\nfunc getChecks(node ast.Node) []check {\n\tchecks := []check{}\n\n\tfor _, ce := range getCalls(node, \"testApplyEdits\") {\n\t\tif len(ce.ArgumentList) != 3 {\n\t\t\t// Wrong function\n\t\t\tcontinue\n\t\t}\n\n\t\tbefore := nodeToStringSlice2(ce.ArgumentList[0])\n\t\tafter := nodeToStringSlice2(ce.ArgumentList[2])\n\n\t\tvar operations []operation\n\t\tfor _, op := range ce.ArgumentList[1].(*ast.ArrayLiteral).Value {\n\t\t\targs := getPropertyValue(op, \"range\").(*ast.NewExpression).ArgumentList\n\t\t\toperations = append(operations, operation{\n\t\t\t\tstartLine:   nodeToInt(args[0]) - 1,\n\t\t\t\tstartColumn: nodeToInt(args[1]) - 1,\n\t\t\t\tendLine:     nodeToInt(args[2]) - 1,\n\t\t\t\tendColumn:   nodeToInt(args[3]) - 1,\n\t\t\t\ttext:        []string{getPropertyValue(op, \"text\").(*ast.StringLiteral).Value},\n\t\t\t})\n\t\t}\n\n\t\tchecks = append(checks, check{before, operations, after})\n\t}\n\n\tfor _, ce := range getCalls(node, \"testApplyEditsWithSyncedModels\") {\n\t\tif len(ce.ArgumentList) > 3 && ce.ArgumentList[3].(*ast.BooleanLiteral).Value {\n\t\t\t// inputEditsAreInvalid == true\n\t\t\tcontinue\n\t\t}\n\n\t\tbefore := nodeToStringSlice(ce.ArgumentList[0])\n\t\tafter := nodeToStringSlice(ce.ArgumentList[2])\n\n\t\tvar operations []operation\n\t\tfor _, op := range getCalls(ce.ArgumentList[1], \"editOp\") {\n\t\t\toperations = append(operations, operation{\n\t\t\t\tstartLine:   nodeToInt(op.ArgumentList[0]) - 1,\n\t\t\t\tstartColumn: nodeToInt(op.ArgumentList[1]) - 1,\n\t\t\t\tendLine:     nodeToInt(op.ArgumentList[2]) - 1,\n\t\t\t\tendColumn:   nodeToInt(op.ArgumentList[3]) - 1,\n\t\t\t\ttext:        nodeToStringSlice(op.ArgumentList[4]),\n\t\t\t})\n\t\t}\n\n\t\tchecks = append(checks, check{before, operations, after})\n\t}\n\n\treturn checks\n}\n\nfunc getTests(node ast.Node) []test {\n\ttests := []test{}\n\tfor _, ce := range getCalls(node, \"test\") {\n\t\tdescription := ce.ArgumentList[0].(*ast.StringLiteral).Value\n\t\tbody := ce.ArgumentList[1].(*ast.FunctionLiteral).Body\n\t\tchecks := getChecks(body)\n\t\tif len(checks) > 0 {\n\t\t\ttests = append(tests, test{description, checks})\n\t\t}\n\t}\n\treturn tests\n}\n\nfunc main() {\n\tvar tests []test\n\n\tfor _, filename := range os.Args[1:] {\n\t\tsource, err := os.ReadFile(filename)\n\t\tif err != nil {\n\t\t\tlog.Fatalln(err)\n\t\t}\n\n\t\tprogram, err := parser.ParseFile(nil, \"\", source, parser.IgnoreRegExpErrors)\n\t\tif err != nil {\n\t\t\tlog.Fatalln(err)\n\t\t}\n\n\t\ttests = append(tests, getTests(program)...)\n\t}\n\n\tif len(tests) == 0 {\n\t\tlog.Fatalln(\"no tests found!\")\n\t}\n\n\tfmt.Println(\"// This file is generated from VSCode model tests by the testgen tool.\")\n\tfmt.Println(\"// DO NOT EDIT THIS FILE BY HAND; your changes will be overwritten!\\n\")\n\tfmt.Println(\"package buffer\")\n\tfmt.Println(`import \"testing\"`)\n\n\tre := regexp.MustCompile(`[^\\w]`)\n\tusedNames := map[string]bool{}\n\n\tfor _, test := range tests {\n\t\tname := strings.Title(strings.ToLower(test.description))\n\t\tname = re.ReplaceAllLiteralString(name, \"\")\n\t\tif name == \"\" {\n\t\t\tname = \"Unnamed\"\n\t\t}\n\t\tif usedNames[name] {\n\t\t\tfor i := 2; ; i++ {\n\t\t\t\tnewName := fmt.Sprintf(\"%v_%v\", name, i)\n\t\t\t\tif !usedNames[newName] {\n\t\t\t\t\tname = newName\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tusedNames[name] = true\n\n\t\tfmt.Println(testToGoTest(test, name))\n\t}\n}\n"
  },
  {
    "path": "tools/update-nightly-tag.sh",
    "content": "commitID=$(git rev-parse --short HEAD)\necho \"Moving tag\"\nhub push origin :refs/tags/nightly\ngit tag -f nightly $commitID\nhub push --tags\n\nMESSAGE=$'Nightly build\\n\\nAutogenerated nightly build of micro'\n\necho \"Creating new release\"\nhub release create nightly \\\n    --prerelease \\\n    --draft=false \\\n    --message \"$MESSAGE.\"\n"
  },
  {
    "path": "tools/vendor-src.sh",
    "content": "cd ../..\n\ntar czf \"$1\".tar.gz micro\nzip -rq \"$1\".zip micro\nmv \"$1\".tar.gz micro\nmv \"$1\".zip micro\n"
  }
]